@netlify/plugin-nextjs 4.26.0 → 4.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/helpers/analysis.js +108 -0
- package/lib/helpers/config.js +1 -1
- package/lib/helpers/edge.js +1 -7
- package/lib/helpers/files.js +27 -1
- package/lib/helpers/functions.js +73 -10
- package/lib/helpers/redirects.js +2 -2
- package/lib/helpers/utils.js +35 -13
- package/lib/index.js +5 -2
- package/lib/templates/getApiHandler.js +114 -0
- package/lib/templates/getHandler.js +3 -2
- package/lib/templates/getPageResolver.js +22 -1
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.extractConfigFromFile = exports.validateConfigValue = void 0;
|
|
27
|
+
const fs_1 = __importStar(require("fs"));
|
|
28
|
+
const pathe_1 = require("pathe");
|
|
29
|
+
const validateConfigValue = (config, apiFilePath) => {
|
|
30
|
+
if (config.type === "experimental-scheduled" /* ApiRouteType.SCHEDULED */) {
|
|
31
|
+
if (!config.schedule) {
|
|
32
|
+
console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: schedule is required when type is "${"experimental-scheduled" /* ApiRouteType.SCHEDULED */}"`);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (config.runtime === 'experimental-edge') {
|
|
36
|
+
console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: edge runtime is not supported for scheduled functions`);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (!config.type || config.type === "experimental-background" /* ApiRouteType.BACKGROUND */) {
|
|
42
|
+
if (config.schedule) {
|
|
43
|
+
console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: schedule is not allowed unless type is "${"experimental-scheduled" /* ApiRouteType.SCHEDULED */}"`);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (config.type && config.runtime === 'experimental-edge') {
|
|
47
|
+
console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: edge runtime is not supported for background functions`);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: type ${config.type} is not supported`);
|
|
53
|
+
return false;
|
|
54
|
+
};
|
|
55
|
+
exports.validateConfigValue = validateConfigValue;
|
|
56
|
+
let extractConstValue;
|
|
57
|
+
let parseModule;
|
|
58
|
+
let hasWarnedAboutNextVersion = false;
|
|
59
|
+
/**
|
|
60
|
+
* Uses Next's swc static analysis to extract the config values from a file.
|
|
61
|
+
*/
|
|
62
|
+
const extractConfigFromFile = async (apiFilePath) => {
|
|
63
|
+
if (!apiFilePath || !(0, fs_1.existsSync)(apiFilePath)) {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
if (!extractConstValue) {
|
|
68
|
+
extractConstValue = require('next/dist/build/analysis/extract-const-value');
|
|
69
|
+
}
|
|
70
|
+
if (!parseModule) {
|
|
71
|
+
// eslint-disable-next-line prefer-destructuring, @typescript-eslint/no-var-requires
|
|
72
|
+
parseModule = require('next/dist/build/analysis/parse-module').parseModule;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
77
|
+
if (!hasWarnedAboutNextVersion) {
|
|
78
|
+
console.log("This version of Next.js doesn't support advanced API routes. Skipping...");
|
|
79
|
+
hasWarnedAboutNextVersion = true;
|
|
80
|
+
}
|
|
81
|
+
// Old Next.js version
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
const { extractExportedConstValue, UnsupportedValueError } = extractConstValue;
|
|
87
|
+
const fileContent = await fs_1.default.promises.readFile(apiFilePath, 'utf8');
|
|
88
|
+
// No need to parse if there's no "config"
|
|
89
|
+
if (!fileContent.includes('config')) {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
const ast = await parseModule(apiFilePath, fileContent);
|
|
93
|
+
let config;
|
|
94
|
+
try {
|
|
95
|
+
config = extractExportedConstValue(ast, 'config');
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (UnsupportedValueError && error instanceof UnsupportedValueError) {
|
|
99
|
+
console.warn(`Unsupported config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}`);
|
|
100
|
+
}
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
if ((0, exports.validateConfigValue)(config, apiFilePath)) {
|
|
104
|
+
return config;
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`Unsupported config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}`);
|
|
107
|
+
};
|
|
108
|
+
exports.extractConfigFromFile = extractConfigFromFile;
|
package/lib/helpers/config.js
CHANGED
|
@@ -69,7 +69,7 @@ const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [] }
|
|
|
69
69
|
}
|
|
70
70
|
/* eslint-enable no-underscore-dangle */
|
|
71
71
|
;
|
|
72
|
-
[constants_1.HANDLER_FUNCTION_NAME, constants_1.ODB_FUNCTION_NAME].forEach((functionName) => {
|
|
72
|
+
[constants_1.HANDLER_FUNCTION_NAME, constants_1.ODB_FUNCTION_NAME, '_api_*'].forEach((functionName) => {
|
|
73
73
|
var _a, _b;
|
|
74
74
|
(_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
|
|
75
75
|
netlifyConfig.functions[functionName].node_bundler = 'nft';
|
package/lib/helpers/edge.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.writeEdgeFunctions = exports.writeDevEdgeFunction = exports.cleanupEdgeFunctions = exports.loadMiddlewareManifest = void 0;
|
|
7
7
|
/* eslint-disable max-lines */
|
|
8
8
|
const fs_1 = require("fs");
|
|
9
9
|
const path_1 = require("path");
|
|
@@ -181,10 +181,4 @@ const writeEdgeFunctions = async (netlifyConfig) => {
|
|
|
181
181
|
await (0, fs_extra_1.writeJson)((0, path_1.join)(edgeFunctionRoot, 'manifest.json'), manifest);
|
|
182
182
|
};
|
|
183
183
|
exports.writeEdgeFunctions = writeEdgeFunctions;
|
|
184
|
-
const enableEdgeInNextConfig = async (publish) => {
|
|
185
|
-
const configFile = (0, path_1.join)(publish, 'required-server-files.json');
|
|
186
|
-
const config = await (0, fs_extra_1.readJSON)(configFile);
|
|
187
|
-
await (0, fs_extra_1.writeJSON)(configFile, config);
|
|
188
|
-
};
|
|
189
|
-
exports.enableEdgeInNextConfig = enableEdgeInNextConfig;
|
|
190
184
|
/* eslint-enable max-lines */
|
package/lib/helpers/files.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.movePublicFiles = exports.unpatchNextFiles = exports.unpatchFile = exports.patchNextFiles = exports.moveStaticPages = exports.getMiddleware = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
|
|
6
|
+
exports.movePublicFiles = exports.unpatchNextFiles = exports.unpatchFile = exports.patchNextFiles = exports.getDependenciesOfFile = exports.getSourceFileForPage = exports.moveStaticPages = exports.getMiddleware = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
|
|
7
7
|
/* eslint-disable max-lines */
|
|
8
8
|
const os_1 = require("os");
|
|
9
9
|
const chalk_1 = require("chalk");
|
|
@@ -16,6 +16,7 @@ const slash_1 = __importDefault(require("slash"));
|
|
|
16
16
|
const constants_1 = require("../constants");
|
|
17
17
|
const utils_1 = require("./utils");
|
|
18
18
|
const TEST_ROUTE = /(|\/)\[[^/]+?](\/|\.html|$)/;
|
|
19
|
+
const SOURCE_FILE_EXTENSIONS = ['js', 'jsx', 'ts', 'tsx'];
|
|
19
20
|
const isDynamicRoute = (route) => TEST_ROUTE.test(route);
|
|
20
21
|
exports.isDynamicRoute = isDynamicRoute;
|
|
21
22
|
const stripLocale = (rawPath, locales = []) => {
|
|
@@ -270,6 +271,31 @@ const getServerFile = (root, includeBase = true) => {
|
|
|
270
271
|
}
|
|
271
272
|
return (0, utils_1.findModuleFromBase)({ candidates, paths: [root] });
|
|
272
273
|
};
|
|
274
|
+
/**
|
|
275
|
+
* Find the source file for a given page route
|
|
276
|
+
*/
|
|
277
|
+
const getSourceFileForPage = (page, root) => {
|
|
278
|
+
for (const extension of SOURCE_FILE_EXTENSIONS) {
|
|
279
|
+
const file = (0, pathe_1.join)(root, `${page}.${extension}`);
|
|
280
|
+
if ((0, fs_extra_1.existsSync)(file)) {
|
|
281
|
+
return file;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
console.log('Could not find source file for page', page);
|
|
285
|
+
};
|
|
286
|
+
exports.getSourceFileForPage = getSourceFileForPage;
|
|
287
|
+
/**
|
|
288
|
+
* Reads the node file trace file for a given file, and resolves the dependencies
|
|
289
|
+
*/
|
|
290
|
+
const getDependenciesOfFile = async (file) => {
|
|
291
|
+
const nft = `${file}.nft.json`;
|
|
292
|
+
if (!(0, fs_extra_1.existsSync)(nft)) {
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
const dependencies = await (0, fs_extra_1.readJson)(nft, 'utf8');
|
|
296
|
+
return dependencies.files.map((dep) => (0, pathe_1.resolve)(file, dep));
|
|
297
|
+
};
|
|
298
|
+
exports.getDependenciesOfFile = getDependenciesOfFile;
|
|
273
299
|
const baseServerReplacements = [
|
|
274
300
|
// force manual revalidate during cache fetches
|
|
275
301
|
[
|
package/lib/helpers/functions.js
CHANGED
|
@@ -3,24 +3,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.setupImageFunction = exports.generatePagesResolver = exports.generateFunctions = void 0;
|
|
6
|
+
exports.warnOnApiRoutes = exports.getApiRouteConfigs = exports.setupImageFunction = exports.generatePagesResolver = exports.generateFunctions = void 0;
|
|
7
7
|
const node_bridge_1 = __importDefault(require("@vercel/node-bridge"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
9
|
const destr_1 = __importDefault(require("destr"));
|
|
9
10
|
const fs_extra_1 = require("fs-extra");
|
|
11
|
+
const outdent_1 = require("outdent");
|
|
10
12
|
const pathe_1 = require("pathe");
|
|
11
13
|
const constants_1 = require("../constants");
|
|
14
|
+
const getApiHandler_1 = require("../templates/getApiHandler");
|
|
12
15
|
const getHandler_1 = require("../templates/getHandler");
|
|
13
16
|
const getPageResolver_1 = require("../templates/getPageResolver");
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
17
|
+
const analysis_1 = require("./analysis");
|
|
18
|
+
const files_1 = require("./files");
|
|
19
|
+
const utils_1 = require("./utils");
|
|
20
|
+
const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir, apiRoutes) => {
|
|
21
|
+
const publish = (0, pathe_1.resolve)(PUBLISH_DIR);
|
|
22
|
+
const functionsDir = (0, pathe_1.resolve)(INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC);
|
|
23
|
+
console.log({ functionsDir });
|
|
24
|
+
const functionDir = (0, pathe_1.join)(functionsDir, constants_1.HANDLER_FUNCTION_NAME);
|
|
25
|
+
const publishDir = (0, pathe_1.relative)(functionDir, publish);
|
|
26
|
+
for (const { route, config, compiled } of apiRoutes) {
|
|
27
|
+
const apiHandlerSource = await (0, getApiHandler_1.getApiHandler)({
|
|
28
|
+
page: route,
|
|
29
|
+
config,
|
|
30
|
+
});
|
|
31
|
+
const functionName = (0, utils_1.getFunctionNameForPage)(route, config.type === "experimental-background" /* ApiRouteType.BACKGROUND */);
|
|
32
|
+
await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, functionName));
|
|
33
|
+
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, `${functionName}.js`), apiHandlerSource);
|
|
34
|
+
await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir, functionName, 'bridge.js'));
|
|
35
|
+
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, functionName, 'handlerUtils.js'));
|
|
36
|
+
const resolverSource = await (0, getPageResolver_1.getSinglePageResolver)({
|
|
37
|
+
functionsDir,
|
|
38
|
+
sourceFile: (0, pathe_1.join)(publish, 'server', compiled),
|
|
39
|
+
});
|
|
40
|
+
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, 'pages.js'), resolverSource);
|
|
41
|
+
}
|
|
42
|
+
const writeHandler = async (functionName, isODB) => {
|
|
19
43
|
const handlerSource = await (0, getHandler_1.getHandler)({ isODB, publishDir, appDir: (0, pathe_1.relative)(functionDir, appDir) });
|
|
20
|
-
await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir,
|
|
21
|
-
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir,
|
|
22
|
-
await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir,
|
|
23
|
-
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir,
|
|
44
|
+
await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, functionName));
|
|
45
|
+
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, `${functionName}.js`), handlerSource);
|
|
46
|
+
await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir, functionName, 'bridge.js'));
|
|
47
|
+
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, functionName, 'handlerUtils.js'));
|
|
24
48
|
};
|
|
25
49
|
await writeHandler(constants_1.HANDLER_FUNCTION_NAME, false);
|
|
26
50
|
await writeHandler(constants_1.ODB_FUNCTION_NAME, true);
|
|
@@ -92,3 +116,42 @@ const setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIO
|
|
|
92
116
|
}
|
|
93
117
|
};
|
|
94
118
|
exports.setupImageFunction = setupImageFunction;
|
|
119
|
+
/**
|
|
120
|
+
* Look for API routes, and extract the config from the source file.
|
|
121
|
+
*/
|
|
122
|
+
const getApiRouteConfigs = async (publish, baseDir) => {
|
|
123
|
+
const pages = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(publish, 'server', 'pages-manifest.json'));
|
|
124
|
+
const apiRoutes = Object.keys(pages).filter((page) => page.startsWith('/api/'));
|
|
125
|
+
const pagesDir = (0, pathe_1.join)(baseDir, 'pages');
|
|
126
|
+
return Promise.all(apiRoutes.map(async (apiRoute) => {
|
|
127
|
+
const filePath = (0, files_1.getSourceFileForPage)(apiRoute, pagesDir);
|
|
128
|
+
return { route: apiRoute, config: await (0, analysis_1.extractConfigFromFile)(filePath), compiled: pages[apiRoute] };
|
|
129
|
+
}));
|
|
130
|
+
};
|
|
131
|
+
exports.getApiRouteConfigs = getApiRouteConfigs;
|
|
132
|
+
/**
|
|
133
|
+
* Warn the user of the caveats if they're using background or scheduled API routes
|
|
134
|
+
*/
|
|
135
|
+
const warnOnApiRoutes = async ({ FUNCTIONS_DIST, }) => {
|
|
136
|
+
const functionsManifestPath = (0, pathe_1.join)(FUNCTIONS_DIST, 'manifest.json');
|
|
137
|
+
if (!(0, fs_extra_1.existsSync)(functionsManifestPath)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const { functions } = await (0, fs_extra_1.readJSON)(functionsManifestPath);
|
|
141
|
+
if (functions.some((func) => func.name.endsWith('-background'))) {
|
|
142
|
+
console.warn((0, outdent_1.outdent) `
|
|
143
|
+
${chalk_1.default.yellowBright `Using background API routes`}
|
|
144
|
+
If your account type does not support background functions, the deploy will fail.
|
|
145
|
+
During local development, background API routes will run as regular API routes, but in production they will immediately return an empty "202 Accepted" response.
|
|
146
|
+
`);
|
|
147
|
+
}
|
|
148
|
+
if (functions.some((func) => func.schedule)) {
|
|
149
|
+
console.warn((0, outdent_1.outdent) `
|
|
150
|
+
${chalk_1.default.yellowBright `Using scheduled API routes`}
|
|
151
|
+
These are run on a schedule when deployed to production.
|
|
152
|
+
You can test them locally by loading them in your browser but this will not be available when deployed, and any returned value is ignored.
|
|
153
|
+
`);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
exports.warnOnApiRoutes = warnOnApiRoutes;
|
|
157
|
+
/* eslint-enable max-lines */
|
package/lib/helpers/redirects.js
CHANGED
|
@@ -152,7 +152,7 @@ const generateDynamicRewrites = ({ dynamicRoutes, prerenderedDynamicRoutes, midd
|
|
|
152
152
|
dynamicRewrites,
|
|
153
153
|
};
|
|
154
154
|
};
|
|
155
|
-
const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath, trailingSlash, appDir }, buildId, }) => {
|
|
155
|
+
const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath, trailingSlash, appDir }, buildId, apiRoutes, }) => {
|
|
156
156
|
const { dynamicRoutes: prerenderedDynamicRoutes, routes: prerenderedStaticRoutes } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(netlifyConfig.build.publish, 'prerender-manifest.json'));
|
|
157
157
|
const { dynamicRoutes, staticRoutes } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(netlifyConfig.build.publish, 'routes-manifest.json'));
|
|
158
158
|
netlifyConfig.redirects.push(...generateHiddenPathRedirects({ basePath }));
|
|
@@ -162,7 +162,7 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
|
|
|
162
162
|
// This is only used in prod, so dev uses `next dev` directly
|
|
163
163
|
netlifyConfig.redirects.push(
|
|
164
164
|
// API routes always need to be served from the regular function
|
|
165
|
-
...(0, utils_1.getApiRewrites)(basePath),
|
|
165
|
+
...(0, utils_1.getApiRewrites)(basePath, apiRoutes),
|
|
166
166
|
// Preview mode gets forced to the function, to bypass pre-rendered pages, but static files need to be skipped
|
|
167
167
|
...(await (0, utils_1.getPreviewRewrites)({ basePath, appDir })));
|
|
168
168
|
const middleware = await (0, files_1.getMiddleware)(netlifyConfig.build.publish);
|
package/lib/helpers/utils.js
CHANGED
|
@@ -3,10 +3,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getRemotePatterns = exports.isBundleSizeCheckDisabled = exports.getCustomImageResponseHeaders = exports.isNextAuthInstalled = exports.findModuleFromBase = exports.shouldSkip = exports.getPreviewRewrites = exports.getApiRewrites = exports.redirectsForNextRouteWithData = exports.redirectsForNextRoute = exports.is404Route = exports.isApiRoute = exports.routeToDataRoute = exports.netlifyRoutesForNextRouteWithData = exports.toNetlifyRoute = void 0;
|
|
6
|
+
exports.getRemotePatterns = exports.isBundleSizeCheckDisabled = exports.getCustomImageResponseHeaders = exports.isNextAuthInstalled = exports.findModuleFromBase = exports.shouldSkip = exports.getPreviewRewrites = exports.getApiRewrites = exports.redirectsForNextRouteWithData = exports.redirectsForNextRoute = exports.is404Route = exports.isApiRoute = exports.routeToDataRoute = exports.netlifyRoutesForNextRouteWithData = exports.toNetlifyRoute = exports.getFunctionNameForPage = void 0;
|
|
7
7
|
const globby_1 = __importDefault(require("globby"));
|
|
8
8
|
const pathe_1 = require("pathe");
|
|
9
9
|
const constants_1 = require("../constants");
|
|
10
|
+
const RESERVED_FILENAME = /[^\w_-]/g;
|
|
11
|
+
/**
|
|
12
|
+
* Given a Next route, generates a valid Netlify function name.
|
|
13
|
+
* If "background" is true then the function name will have `-background`
|
|
14
|
+
* appended to it, meaning that it is executed as a background function.
|
|
15
|
+
*/
|
|
16
|
+
const getFunctionNameForPage = (page, background = false) => `${page
|
|
17
|
+
.replace(constants_1.CATCH_ALL_REGEX, '_$1-SPLAT')
|
|
18
|
+
.replace(constants_1.OPTIONAL_CATCH_ALL_REGEX, '-SPLAT')
|
|
19
|
+
.replace(constants_1.DYNAMIC_PARAMETER_REGEX, '_$1-PARAM')
|
|
20
|
+
.replace(RESERVED_FILENAME, '_')}-${background ? 'background' : 'handler'}`;
|
|
21
|
+
exports.getFunctionNameForPage = getFunctionNameForPage;
|
|
10
22
|
const toNetlifyRoute = (nextRoute) => {
|
|
11
23
|
const netlifyRoutes = [nextRoute];
|
|
12
24
|
// If the route is an optional catch-all route, we need to add a second
|
|
@@ -79,18 +91,28 @@ const redirectsForNextRouteWithData = ({ route, dataRoute, basePath, to, status
|
|
|
79
91
|
force,
|
|
80
92
|
}));
|
|
81
93
|
exports.redirectsForNextRouteWithData = redirectsForNextRouteWithData;
|
|
82
|
-
const getApiRewrites = (basePath) =>
|
|
83
|
-
{
|
|
84
|
-
from
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
const getApiRewrites = (basePath, apiRoutes) => {
|
|
95
|
+
const apiRewrites = apiRoutes.map((apiRoute) => {
|
|
96
|
+
const [from] = (0, exports.toNetlifyRoute)(`${basePath}${apiRoute.route}`);
|
|
97
|
+
// Scheduled functions can't be invoked directly, so we 404 them.
|
|
98
|
+
if (apiRoute.config.type === "experimental-scheduled" /* ApiRouteType.SCHEDULED */) {
|
|
99
|
+
return { from, to: '/404.html', status: 404 };
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
from,
|
|
103
|
+
to: `/.netlify/functions/${(0, exports.getFunctionNameForPage)(apiRoute.route, apiRoute.config.type === "experimental-background" /* ApiRouteType.BACKGROUND */)}`,
|
|
104
|
+
status: 200,
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
return [
|
|
108
|
+
...apiRewrites,
|
|
109
|
+
{
|
|
110
|
+
from: `${basePath}/api/*`,
|
|
111
|
+
to: constants_1.HANDLER_FUNCTION_PATH,
|
|
112
|
+
status: 200,
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
};
|
|
94
116
|
exports.getApiRewrites = getApiRewrites;
|
|
95
117
|
const getPreviewRewrites = async ({ basePath, appDir }) => {
|
|
96
118
|
const publicFiles = await (0, globby_1.default)('**/*', { cwd: (0, pathe_1.join)(appDir, 'public') });
|
package/lib/index.js
CHANGED
|
@@ -90,10 +90,11 @@ const plugin = {
|
|
|
90
90
|
}
|
|
91
91
|
const buildId = (0, fs_extra_1.readFileSync)((0, path_1.join)(publish, 'BUILD_ID'), 'utf8').trim();
|
|
92
92
|
await (0, config_1.configureHandlerFunctions)({ netlifyConfig, ignore, publish: (0, path_1.relative)(process.cwd(), publish) });
|
|
93
|
-
await (0, functions_1.
|
|
93
|
+
const apiRoutes = await (0, functions_1.getApiRouteConfigs)(publish, appDir);
|
|
94
|
+
await (0, functions_1.generateFunctions)(constants, appDir, apiRoutes);
|
|
94
95
|
await (0, functions_1.generatePagesResolver)({ target, constants });
|
|
95
96
|
await (0, files_1.movePublicFiles)({ appDir, outdir, publish });
|
|
96
|
-
await (0, files_1.patchNextFiles)(
|
|
97
|
+
await (0, files_1.patchNextFiles)(appDir);
|
|
97
98
|
if (!(0, destr_1.default)(process.env.SERVE_STATIC_FILES_FROM_ORIGIN)) {
|
|
98
99
|
await (0, files_1.moveStaticPages)({ target, netlifyConfig, i18n, basePath });
|
|
99
100
|
}
|
|
@@ -113,6 +114,7 @@ const plugin = {
|
|
|
113
114
|
netlifyConfig,
|
|
114
115
|
nextConfig: { basePath, i18n, trailingSlash, appDir },
|
|
115
116
|
buildId,
|
|
117
|
+
apiRoutes,
|
|
116
118
|
});
|
|
117
119
|
await (0, edge_1.writeEdgeFunctions)(netlifyConfig);
|
|
118
120
|
},
|
|
@@ -134,6 +136,7 @@ const plugin = {
|
|
|
134
136
|
(0, config_1.generateCustomHeaders)(nextConfig, headers);
|
|
135
137
|
(0, verification_1.warnForProblematicUserRewrites)({ basePath, redirects });
|
|
136
138
|
(0, verification_1.warnForRootRedirects)({ appDir });
|
|
139
|
+
await (0, functions_1.warnOnApiRoutes)({ FUNCTIONS_DIST });
|
|
137
140
|
},
|
|
138
141
|
};
|
|
139
142
|
// The types haven't been updated yet
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getApiHandler = void 0;
|
|
4
|
+
// Aliasing like this means the editor may be able to syntax-highlight the string
|
|
5
|
+
const outdent_1 = require("outdent");
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
7
|
+
const { Server } = require('http');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
// eslint-disable-next-line n/prefer-global/url, n/prefer-global/url-search-params
|
|
10
|
+
const { URLSearchParams, URL } = require('url');
|
|
11
|
+
const { Bridge } = require('@vercel/node-bridge/bridge');
|
|
12
|
+
const { getMultiValueHeaders, getNextServer } = require('./handlerUtils');
|
|
13
|
+
// We return a function and then call `toString()` on it to serialise it as the launcher function
|
|
14
|
+
const makeHandler = (conf, app, pageRoot, page) => {
|
|
15
|
+
var _a;
|
|
16
|
+
// Change working directory into the site root, unless using Nx, which moves the
|
|
17
|
+
// dist directory and handles this itself
|
|
18
|
+
const dir = path.resolve(__dirname, app);
|
|
19
|
+
if (pageRoot.startsWith(dir)) {
|
|
20
|
+
process.chdir(dir);
|
|
21
|
+
}
|
|
22
|
+
// This is just so nft knows about the page entrypoints. It's not actually used
|
|
23
|
+
try {
|
|
24
|
+
// eslint-disable-next-line n/no-missing-require
|
|
25
|
+
require.resolve('./pages.js');
|
|
26
|
+
}
|
|
27
|
+
catch { }
|
|
28
|
+
// React assumes you want development mode if NODE_ENV is unset.
|
|
29
|
+
;
|
|
30
|
+
(_a = process.env).NODE_ENV || (_a.NODE_ENV = 'production');
|
|
31
|
+
// We don't want to write ISR files to disk in the lambda environment
|
|
32
|
+
conf.experimental.isrFlushToDisk = false;
|
|
33
|
+
// This is our flag that we use when patching the source
|
|
34
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
35
|
+
process.env._BYPASS_SSG = 'true';
|
|
36
|
+
for (const [key, value] of Object.entries(conf.env)) {
|
|
37
|
+
process.env[key] = String(value);
|
|
38
|
+
}
|
|
39
|
+
// We memoize this because it can be shared between requests, but don't instantiate it until
|
|
40
|
+
// the first request because we need the host and port.
|
|
41
|
+
let bridge;
|
|
42
|
+
const getBridge = (event) => {
|
|
43
|
+
if (bridge) {
|
|
44
|
+
return bridge;
|
|
45
|
+
}
|
|
46
|
+
// Scheduled functions don't have a URL, but we need to give one so Next knows the route to serve
|
|
47
|
+
const url = event.rawUrl ? new URL(event.rawUrl) : new URL(path, process.env.URL || 'http://n');
|
|
48
|
+
const port = Number.parseInt(url.port) || 80;
|
|
49
|
+
const NextServer = getNextServer();
|
|
50
|
+
const nextServer = new NextServer({
|
|
51
|
+
// We know we're just an API route, so can enable minimal mode
|
|
52
|
+
minimalMode: true,
|
|
53
|
+
conf,
|
|
54
|
+
dir,
|
|
55
|
+
customServer: false,
|
|
56
|
+
hostname: url.hostname,
|
|
57
|
+
port,
|
|
58
|
+
});
|
|
59
|
+
const requestHandler = nextServer.getRequestHandler();
|
|
60
|
+
const server = new Server(async (req, res) => {
|
|
61
|
+
try {
|
|
62
|
+
await requestHandler(req, res);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(error);
|
|
66
|
+
throw new Error('Error handling request. See function logs for details.');
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
bridge = new Bridge(server);
|
|
70
|
+
bridge.listen();
|
|
71
|
+
return bridge;
|
|
72
|
+
};
|
|
73
|
+
return async function handler(event, context) {
|
|
74
|
+
// Ensure that paths are encoded - but don't double-encode them
|
|
75
|
+
event.path = event.rawUrl ? new URL(event.rawUrl).pathname : page;
|
|
76
|
+
// Next expects to be able to parse the query from the URL
|
|
77
|
+
const query = new URLSearchParams(event.queryStringParameters).toString();
|
|
78
|
+
event.path = query ? `${event.path}?${query}` : event.path;
|
|
79
|
+
// We know the page
|
|
80
|
+
event.headers['x-matched-path'] = page;
|
|
81
|
+
const { headers, ...result } = await getBridge(event).launcher(event, context);
|
|
82
|
+
// Convert all headers to multiValueHeaders
|
|
83
|
+
const multiValueHeaders = getMultiValueHeaders(headers);
|
|
84
|
+
multiValueHeaders['cache-control'] = ['public, max-age=0, must-revalidate'];
|
|
85
|
+
console.log(`[${event.httpMethod}] ${event.path} (API)`);
|
|
86
|
+
return {
|
|
87
|
+
...result,
|
|
88
|
+
multiValueHeaders,
|
|
89
|
+
isBase64Encoded: result.encoding === 'base64',
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Handlers for API routes are simpler than page routes, but they each have a separate one
|
|
95
|
+
*/
|
|
96
|
+
const getApiHandler = ({ page, config, publishDir = '../../../.next', appDir = '../../..', }) =>
|
|
97
|
+
// This is a string, but if you have the right editor plugin it should format as js
|
|
98
|
+
(0, outdent_1.outdent /* javascript */) `
|
|
99
|
+
const { Server } = require("http");
|
|
100
|
+
// We copy the file here rather than requiring from the node module
|
|
101
|
+
const { Bridge } = require("./bridge");
|
|
102
|
+
const { getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils')
|
|
103
|
+
|
|
104
|
+
${config.type === "experimental-scheduled" /* ApiRouteType.SCHEDULED */ ? `const { schedule } = require("@netlify/functions")` : ''}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
const { config } = require("${publishDir}/required-server-files.json")
|
|
108
|
+
let staticManifest
|
|
109
|
+
const path = require("path");
|
|
110
|
+
const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "serverless", "pages"));
|
|
111
|
+
const handler = (${makeHandler.toString()})(config, "${appDir}", pageRoot, ${JSON.stringify(page)})
|
|
112
|
+
exports.handler = ${config.type === "experimental-scheduled" /* ApiRouteType.SCHEDULED */ ? `schedule(${JSON.stringify(config.schedule)}, handler);` : 'handler'}
|
|
113
|
+
`;
|
|
114
|
+
exports.getApiHandler = getApiHandler;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getHandler = void 0;
|
|
4
|
+
// Aliasing like this means the editor may be able to syntax-highlight the string
|
|
4
5
|
const outdent_1 = require("outdent");
|
|
5
6
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
6
7
|
const { promises } = require('fs');
|
|
@@ -127,14 +128,14 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
|
|
|
127
128
|
};
|
|
128
129
|
const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '../../..' }) =>
|
|
129
130
|
// This is a string, but if you have the right editor plugin it should format as js
|
|
130
|
-
(0, outdent_1.outdent) `
|
|
131
|
+
(0, outdent_1.outdent /* javascript */) `
|
|
131
132
|
const { Server } = require("http");
|
|
132
133
|
const { promises } = require("fs");
|
|
133
134
|
// We copy the file here rather than requiring from the node module
|
|
134
135
|
const { Bridge } = require("./bridge");
|
|
135
136
|
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils')
|
|
136
137
|
|
|
137
|
-
const { builder } = require("@netlify/functions")
|
|
138
|
+
${isODB ? `const { builder } = require("@netlify/functions")` : ''}
|
|
138
139
|
const { config } = require("${publishDir}/required-server-files.json")
|
|
139
140
|
let staticManifest
|
|
140
141
|
try {
|
|
@@ -3,12 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getPageResolver = void 0;
|
|
6
|
+
exports.getSinglePageResolver = exports.getPageResolver = void 0;
|
|
7
7
|
const path_1 = require("path");
|
|
8
8
|
const outdent_1 = require("outdent");
|
|
9
|
+
const pathe_1 = require("pathe");
|
|
9
10
|
const slash_1 = __importDefault(require("slash"));
|
|
10
11
|
const tiny_glob_1 = __importDefault(require("tiny-glob"));
|
|
11
12
|
const constants_1 = require("../constants");
|
|
13
|
+
const files_1 = require("../helpers/files");
|
|
12
14
|
// Generate a file full of require.resolve() calls for all the pages in the
|
|
13
15
|
// build. This is used by the nft bundler to find all the pages.
|
|
14
16
|
const getPageResolver = async ({ publish, target }) => {
|
|
@@ -31,3 +33,22 @@ const getPageResolver = async ({ publish, target }) => {
|
|
|
31
33
|
`;
|
|
32
34
|
};
|
|
33
35
|
exports.getPageResolver = getPageResolver;
|
|
36
|
+
/**
|
|
37
|
+
* API routes only need the dependencies for a single entrypoint, so we use the
|
|
38
|
+
* NFT trace file to get the dependencies.
|
|
39
|
+
*/
|
|
40
|
+
const getSinglePageResolver = async ({ functionsDir, sourceFile, }) => {
|
|
41
|
+
const dependencies = await (0, files_1.getDependenciesOfFile)(sourceFile);
|
|
42
|
+
// We don't need the actual name, just the relative path.
|
|
43
|
+
const functionDir = (0, pathe_1.resolve)(functionsDir, 'functionName');
|
|
44
|
+
const pageFiles = [sourceFile, ...dependencies]
|
|
45
|
+
.map((file) => `require.resolve('${(0, pathe_1.relative)(functionDir, file)}')`)
|
|
46
|
+
.sort();
|
|
47
|
+
return (0, outdent_1.outdent /* javascript */) `
|
|
48
|
+
// This file is purely to allow nft to know about these pages.
|
|
49
|
+
try {
|
|
50
|
+
${pageFiles.join('\n ')}
|
|
51
|
+
} catch {}
|
|
52
|
+
`;
|
|
53
|
+
};
|
|
54
|
+
exports.getSinglePageResolver = getSinglePageResolver;
|