gatsby-adapter-netlify
Version:
Gatsby adapter for Netlify
213 lines (210 loc) • 8.43 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.NETLIFY_PLUGIN_MARKER_START = exports.NETLIFY_PLUGIN_MARKER_END = exports.GATSBY_PLUGIN_MARKER_START = exports.ADAPTER_MARKER_START = exports.ADAPTER_MARKER_END = void 0;
exports.handleRoutesManifest = handleRoutesManifest;
exports.injectEntries = injectEntries;
exports.processRoutesManifest = processRoutesManifest;
var _os = require("os");
var _stream = require("stream");
var _path = require("path");
var _fsExtra = _interopRequireDefault(require("fs-extra"));
var _prettyUrls = require("./pretty-urls");
const NETLIFY_REDIRECT_KEYWORDS_ALLOWLIST = new Set([`query`, `conditions`, `headers`, `signed`, `edge_handler`]);
const NETLIFY_CONDITIONS_ALLOWLIST = new Set([`language`, `country`]);
const toNetlifyPath = (fromPath, toPath) => {
// Modifies query parameter redirects, having no effect on other fromPath strings
const netlifyFromPath = fromPath.replace(/[&?]/, ` `);
// Modifies wildcard & splat redirects, having no effect on other toPath strings
const netlifyToPath = toPath.replace(/\*/, `:splat`);
return [netlifyFromPath, netlifyToPath];
};
const ADAPTER_MARKER_START = `# gatsby-adapter-netlify start`;
exports.ADAPTER_MARKER_START = ADAPTER_MARKER_START;
const ADAPTER_MARKER_END = `# gatsby-adapter-netlify end`;
exports.ADAPTER_MARKER_END = ADAPTER_MARKER_END;
const NETLIFY_PLUGIN_MARKER_START = `# @netlify/plugin-gatsby redirects start`;
exports.NETLIFY_PLUGIN_MARKER_START = NETLIFY_PLUGIN_MARKER_START;
const NETLIFY_PLUGIN_MARKER_END = `# @netlify/plugin-gatsby redirects end`;
exports.NETLIFY_PLUGIN_MARKER_END = NETLIFY_PLUGIN_MARKER_END;
const GATSBY_PLUGIN_MARKER_START = `## Created with gatsby-plugin-netlify`;
exports.GATSBY_PLUGIN_MARKER_START = GATSBY_PLUGIN_MARKER_START;
async function injectEntries(fileName, content) {
await _fsExtra.default.ensureFile(fileName);
const tmpFile = (0, _path.join)(await _fsExtra.default.mkdtemp((0, _path.join)((0, _os.tmpdir)(), (0, _path.basename)(fileName))), `out.txt`);
let tail = ``;
let insideNetlifyPluginGatsby = false;
let insideGatsbyPluginNetlify = false;
let insideGatsbyAdapterNetlify = false;
let injectedEntries = false;
const annotatedContent = `${ADAPTER_MARKER_START}\n${content}\n${ADAPTER_MARKER_END}\n`;
function getContentToAdd(final) {
const lines = tail.split(`\n`);
tail = ``;
let contentToAdd = ``;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (!final && i === lines.length - 1) {
tail = line;
break;
}
let skipLine = insideGatsbyAdapterNetlify || insideGatsbyPluginNetlify || insideNetlifyPluginGatsby;
if (line.includes(ADAPTER_MARKER_START)) {
skipLine = true;
insideGatsbyAdapterNetlify = true;
} else if (line.includes(ADAPTER_MARKER_END)) {
insideGatsbyAdapterNetlify = false;
contentToAdd += annotatedContent;
injectedEntries = true;
} else if (line.includes(NETLIFY_PLUGIN_MARKER_START)) {
insideNetlifyPluginGatsby = true;
skipLine = true;
} else if (line.includes(NETLIFY_PLUGIN_MARKER_END)) {
insideNetlifyPluginGatsby = false;
} else if (line.includes(GATSBY_PLUGIN_MARKER_START)) {
insideGatsbyPluginNetlify = true;
skipLine = true;
}
if (!skipLine) {
contentToAdd += line + `\n`;
}
}
return contentToAdd;
}
const streamReplacer = new _stream.Transform({
transform(chunk, _encoding, callback) {
tail = tail + chunk.toString();
try {
callback(null, getContentToAdd(false));
} catch (e) {
callback(e);
}
},
flush(callback) {
try {
let contentToAdd = getContentToAdd(true);
if (!injectedEntries) {
contentToAdd += annotatedContent;
}
callback(null, contentToAdd);
} catch (e) {
callback(e);
}
}
});
await new Promise((resolve, reject) => {
const writeStream = _fsExtra.default.createWriteStream(tmpFile);
const pipeline = _fsExtra.default.createReadStream(fileName).pipe(streamReplacer).pipe(writeStream);
pipeline.on(`finish`, resolve);
pipeline.on(`error`, reject);
streamReplacer.on(`error`, reject);
});
// remove previous file and move new file from tmp to final path
await _fsExtra.default.remove(fileName);
await _fsExtra.default.move(tmpFile, fileName);
}
function buildHeaderString(path, headers) {
return `${encodeURI(path)}\n${headers.reduce((acc, curr) => {
acc += ` ${curr.key}: ${curr.value}\n`;
return acc;
}, ``)}`;
}
function processRoutesManifest(routesManifest, headerRoutes) {
const lambdasThatUseCaching = new Map();
const {
ensureStaticAssetPath,
fileMovingDone
} = (0, _prettyUrls.createStaticAssetsPathHandler)();
let _redirects = ``;
let _headers = ``;
for (const route of routesManifest) {
const fromPath = route.path.replace(/\*.*/, `*`);
if (route.type === `function`) {
let functionName = route.functionId;
if (route.cache) {
functionName = `${route.functionId}-odb`;
if (!lambdasThatUseCaching.has(route.functionId)) {
lambdasThatUseCaching.set(route.functionId, functionName);
}
}
const invocationURL = `/.netlify/${route.cache ? `builders` : `functions`}/${functionName}`;
_redirects += `${encodeURI(fromPath)} ${invocationURL} 200\n`;
} else if (route.type === `redirect`) {
const {
status: routeStatus,
toPath,
// TODO: add headers handling
headers,
...rest
} = route;
let status = String(routeStatus);
if (rest.force) {
status = `${status}!`;
}
const [netlifyFromPath, netlifyToPath] = toNetlifyPath(fromPath, toPath);
// The order of the first 3 parameters is significant.
// The order for rest params (key-value pairs) is arbitrary.
const pieces = [netlifyFromPath, netlifyToPath, status];
for (const [key, value] of Object.entries(rest)) {
if (NETLIFY_REDIRECT_KEYWORDS_ALLOWLIST.has(key)) {
if (key === `conditions`) {
// "conditions" key from Gatsby contains only "language" and "country"
// which need special transformation to match Netlify _redirects
// https://www.gatsbyjs.com/docs/reference/config-files/actions/#createRedirect
if (value && typeof value === `object`) {
for (const [conditionKey, conditionValueRaw] of Object.entries(value)) {
if (NETLIFY_CONDITIONS_ALLOWLIST.has(conditionKey)) {
const conditionValue = Array.isArray(conditionValueRaw) ? conditionValueRaw.join(`,`) : conditionValueRaw;
// Gatsby gives us "country", we want "Country"
const conditionName = conditionKey.charAt(0).toUpperCase() + conditionKey.slice(1);
pieces.push(`${conditionName}=${conditionValue}`);
}
}
}
} else {
pieces.push(`${key}=${value}`);
}
}
}
_redirects += pieces.join(` `) + `\n`;
} else if (route.type === `static`) {
const {
finalFilePath,
isDynamic
} = ensureStaticAssetPath(route.filePath, fromPath);
if (isDynamic) {
_redirects += `${encodeURI(fromPath)} ${finalFilePath.replace(/^public/, ``)} 200\n`;
}
if (!headerRoutes) {
// don't generate _headers from routesManifest if headerRoutes are provided
_headers += buildHeaderString(route.path, route.headers);
}
}
}
if (headerRoutes) {
_headers = headerRoutes.reduce((acc, curr) => {
acc += buildHeaderString(curr.path, curr.headers);
return acc;
}, ``);
}
return {
redirects: _redirects,
headers: _headers,
lambdasThatUseCaching,
fileMovingPromise: fileMovingDone()
};
}
async function handleRoutesManifest(routesManifest, headerRoutes) {
const {
redirects,
headers,
lambdasThatUseCaching,
fileMovingPromise
} = processRoutesManifest(routesManifest, headerRoutes);
await injectEntries(`public/_redirects`, redirects);
await injectEntries(`public/_headers`, headers);
await fileMovingPromise;
return {
lambdasThatUseCaching
};
}