@vercel/fs-detectors 5.0.0 → 5.0.2

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.
Files changed (31) hide show
  1. package/dist/detect-builders.js +701 -748
  2. package/dist/detect-builders.js.map +7 -1
  3. package/dist/detect-file-system-api.js +177 -154
  4. package/dist/detect-file-system-api.js.map +7 -1
  5. package/dist/detect-framework.js +196 -143
  6. package/dist/detect-framework.js.map +7 -1
  7. package/dist/detectors/filesystem.js +106 -109
  8. package/dist/detectors/filesystem.js.map +7 -1
  9. package/dist/detectors/local-file-system-detector.js +90 -63
  10. package/dist/detectors/local-file-system-detector.js.map +7 -1
  11. package/dist/get-project-paths.js +74 -35
  12. package/dist/get-project-paths.js.map +7 -1
  13. package/dist/index.js +85 -48
  14. package/dist/index.js.map +7 -1
  15. package/dist/is-official-runtime.js +34 -19
  16. package/dist/is-official-runtime.js.map +7 -1
  17. package/dist/monorepos/get-monorepo-default-settings.js +154 -150
  18. package/dist/monorepos/get-monorepo-default-settings.js.map +7 -1
  19. package/dist/monorepos/monorepo-managers.js +112 -100
  20. package/dist/monorepos/monorepo-managers.js.map +7 -1
  21. package/dist/package-managers/package-managers.js +79 -55
  22. package/dist/package-managers/package-managers.js.map +7 -1
  23. package/dist/workspaces/get-glob-fs.js +90 -66
  24. package/dist/workspaces/get-glob-fs.js.map +7 -1
  25. package/dist/workspaces/get-workspace-package-paths.js +130 -79
  26. package/dist/workspaces/get-workspace-package-paths.js.map +7 -1
  27. package/dist/workspaces/get-workspaces.js +71 -31
  28. package/dist/workspaces/get-workspaces.js.map +7 -1
  29. package/dist/workspaces/workspace-managers.js +104 -92
  30. package/dist/workspaces/workspace-managers.js.map +7 -1
  31. package/package.json +6 -6
@@ -1,821 +1,774 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
4
11
  };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.detectBuilders = exports.detectOutputDirectory = exports.detectApiDirectory = exports.detectApiExtensions = exports.sortFiles = void 0;
7
- const minimatch_1 = __importDefault(require("minimatch"));
8
- const semver_1 = require("semver");
9
- const path_1 = require("path");
10
- const frameworks_1 = __importDefault(require("@vercel/frameworks"));
11
- const is_official_runtime_1 = require("./is-official-runtime");
12
- const slugToFramework = new Map(frameworks_1.default.map(f => [f.slug, f]));
13
- // We need to sort the file paths by alphabet to make
14
- // sure the routes stay in the same order e.g. for deduping
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var detect_builders_exports = {};
30
+ __export(detect_builders_exports, {
31
+ detectApiDirectory: () => detectApiDirectory,
32
+ detectApiExtensions: () => detectApiExtensions,
33
+ detectBuilders: () => detectBuilders,
34
+ detectOutputDirectory: () => detectOutputDirectory,
35
+ sortFiles: () => sortFiles
36
+ });
37
+ module.exports = __toCommonJS(detect_builders_exports);
38
+ var import_minimatch = __toESM(require("minimatch"));
39
+ var import_semver = require("semver");
40
+ var import_path = require("path");
41
+ var import_frameworks = __toESM(require("@vercel/frameworks"));
42
+ var import_is_official_runtime = require("./is-official-runtime");
43
+ const slugToFramework = new Map(
44
+ import_frameworks.default.map((f) => [f.slug, f])
45
+ );
15
46
  function sortFiles(fileA, fileB) {
16
- return fileA.localeCompare(fileB);
47
+ return fileA.localeCompare(fileB);
17
48
  }
18
- exports.sortFiles = sortFiles;
19
49
  function detectApiExtensions(builders) {
20
- return new Set(builders
21
- .filter((b) => Boolean(b.config && b.config.zeroConfig && b.src?.startsWith('api/')))
22
- .map(b => (0, path_1.extname)(b.src))
23
- .filter(Boolean));
50
+ return new Set(
51
+ builders.filter(
52
+ (b) => Boolean(b.config && b.config.zeroConfig && b.src?.startsWith("api/"))
53
+ ).map((b) => (0, import_path.extname)(b.src)).filter(Boolean)
54
+ );
24
55
  }
25
- exports.detectApiExtensions = detectApiExtensions;
26
56
  function detectApiDirectory(builders) {
27
- // TODO: We eventually want to save the api directory to
28
- // builder.config.apiDirectory so it is only detected once
29
- const found = builders.some(b => b.config && b.config.zeroConfig && b.src?.startsWith('api/'));
30
- return found ? 'api' : null;
57
+ const found = builders.some(
58
+ (b) => b.config && b.config.zeroConfig && b.src?.startsWith("api/")
59
+ );
60
+ return found ? "api" : null;
31
61
  }
32
- exports.detectApiDirectory = detectApiDirectory;
33
- // TODO: Replace this function with `config.outputDirectory`
34
62
  function getPublicBuilder(builders) {
35
- for (const builder of builders) {
36
- if (typeof builder.src === 'string' &&
37
- (0, is_official_runtime_1.isOfficialRuntime)('static', builder.use) &&
38
- /^.*\/\*\*\/\*$/.test(builder.src) &&
39
- builder.config?.zeroConfig === true) {
40
- return builder;
41
- }
63
+ for (const builder of builders) {
64
+ if (typeof builder.src === "string" && (0, import_is_official_runtime.isOfficialRuntime)("static", builder.use) && /^.*\/\*\*\/\*$/.test(builder.src) && builder.config?.zeroConfig === true) {
65
+ return builder;
42
66
  }
43
- return null;
67
+ }
68
+ return null;
44
69
  }
45
70
  function detectOutputDirectory(builders) {
46
- // TODO: We eventually want to save the output directory to
47
- // builder.config.outputDirectory so it is only detected once
48
- const publicBuilder = getPublicBuilder(builders);
49
- return publicBuilder ? publicBuilder.src.replace('/**/*', '') : null;
71
+ const publicBuilder = getPublicBuilder(builders);
72
+ return publicBuilder ? publicBuilder.src.replace("/**/*", "") : null;
50
73
  }
51
- exports.detectOutputDirectory = detectOutputDirectory;
52
74
  async function detectBuilders(files, pkg, options = {}) {
53
- const errors = [];
54
- const warnings = [];
55
- let apiBuilders = [];
56
- let frontendBuilder = null;
57
- const functionError = validateFunctions(options);
58
- if (functionError) {
59
- return {
60
- builders: null,
61
- errors: [functionError],
62
- warnings,
63
- defaultRoutes: null,
64
- redirectRoutes: null,
65
- rewriteRoutes: null,
66
- errorRoutes: null,
67
- };
68
- }
69
- const sortedFiles = files.sort(sortFiles);
70
- const apiSortedFiles = files.sort(sortFilesBySegmentCount);
71
- // Keep track of functions that are used
72
- const usedFunctions = new Set();
73
- const addToUsedFunctions = (builder) => {
74
- const key = Object.keys(builder.config.functions || {})[0];
75
- if (key)
76
- usedFunctions.add(key);
75
+ const errors = [];
76
+ const warnings = [];
77
+ let apiBuilders = [];
78
+ let frontendBuilder = null;
79
+ const functionError = validateFunctions(options);
80
+ if (functionError) {
81
+ return {
82
+ builders: null,
83
+ errors: [functionError],
84
+ warnings,
85
+ defaultRoutes: null,
86
+ redirectRoutes: null,
87
+ rewriteRoutes: null,
88
+ errorRoutes: null
77
89
  };
78
- const absolutePathCache = new Map();
79
- const { projectSettings = {} } = options;
80
- const { buildCommand, outputDirectory, framework } = projectSettings;
81
- const frameworkConfig = slugToFramework.get(framework || '');
82
- const ignoreRuntimes = new Set(frameworkConfig?.ignoreRuntimes);
83
- const withTag = options.tag ? `@${options.tag}` : '';
84
- const apiMatches = getApiMatches()
85
- .filter(b =>
86
- // Root-level middleware is enabled, unless `disableRootMiddleware: true`
87
- (b.config?.middleware && !frameworkConfig?.disableRootMiddleware) ||
88
- // "api" dir runtimes are enabled, unless opted-out via `ignoreRuntimes`
89
- !ignoreRuntimes.has(b.use))
90
- .map(b => {
91
- b.use = `${b.use}${withTag}`;
92
- return b;
93
- });
94
- // If either is missing we'll make the frontend static
95
- const makeFrontendStatic = buildCommand === '' || outputDirectory === '';
96
- // Only used when there is no frontend builder,
97
- // but prevents looping over the files again.
98
- const usedOutputDirectory = outputDirectory || 'public';
99
- let hasUsedOutputDirectory = false;
100
- let hasNoneApiFiles = false;
101
- let hasNextApiFiles = false;
102
- let fallbackEntrypoint = null;
103
- const apiRoutes = [];
104
- const dynamicRoutes = [];
105
- // API
106
- for (const fileName of sortedFiles) {
107
- const apiBuilder = maybeGetApiBuilder(fileName, apiMatches, options);
108
- if (apiBuilder) {
109
- const { routeError, apiRoute, isDynamic } = getApiRoute(fileName, apiSortedFiles, options, absolutePathCache);
110
- if (routeError) {
111
- return {
112
- builders: null,
113
- errors: [routeError],
114
- warnings,
115
- defaultRoutes: null,
116
- redirectRoutes: null,
117
- rewriteRoutes: null,
118
- errorRoutes: null,
119
- };
120
- }
121
- if (apiRoute) {
122
- apiRoutes.push(apiRoute);
123
- if (isDynamic) {
124
- dynamicRoutes.push(apiRoute);
125
- }
126
- }
127
- addToUsedFunctions(apiBuilder);
128
- apiBuilders.push(apiBuilder);
129
- continue;
130
- }
131
- if (!hasUsedOutputDirectory &&
132
- fileName.startsWith(`${usedOutputDirectory}/`)) {
133
- hasUsedOutputDirectory = true;
134
- }
135
- if (!hasNoneApiFiles &&
136
- !fileName.startsWith('api/') &&
137
- fileName !== 'package.json') {
138
- hasNoneApiFiles = true;
139
- }
140
- if (!hasNextApiFiles &&
141
- (fileName.startsWith('pages/api') || fileName.startsWith('src/pages/api'))) {
142
- hasNextApiFiles = true;
143
- }
144
- if (!fallbackEntrypoint &&
145
- buildCommand &&
146
- !fileName.includes('/') &&
147
- fileName !== 'now.json' &&
148
- fileName !== 'vercel.json') {
149
- fallbackEntrypoint = fileName;
150
- }
151
- }
152
- if (!makeFrontendStatic &&
153
- (hasBuildScript(pkg) || buildCommand || framework)) {
154
- // Framework or Build
155
- frontendBuilder = detectFrontBuilder(pkg, files, usedFunctions, fallbackEntrypoint, options);
156
- }
157
- else {
158
- if (pkg &&
159
- !makeFrontendStatic &&
160
- !apiBuilders.length &&
161
- !options.ignoreBuildScript) {
162
- // We only show this error when there are no api builders
163
- // since the dependencies of the pkg could be used for those
164
- errors.push(getMissingBuildScriptError());
165
- return {
166
- errors,
167
- warnings,
168
- builders: null,
169
- redirectRoutes: null,
170
- defaultRoutes: null,
171
- rewriteRoutes: null,
172
- errorRoutes: null,
173
- };
174
- }
175
- // If `outputDirectory` is an empty string,
176
- // we'll default to the root directory.
177
- if (hasUsedOutputDirectory && outputDirectory !== '') {
178
- frontendBuilder = {
179
- use: '@vercel/static',
180
- src: `${usedOutputDirectory}/**/*`,
181
- config: {
182
- zeroConfig: true,
183
- outputDirectory: usedOutputDirectory,
184
- },
185
- };
186
- }
187
- else if (apiBuilders.length && hasNoneApiFiles) {
188
- // Everything besides the api directory
189
- // and package.json can be served as static files
190
- frontendBuilder = {
191
- use: '@vercel/static',
192
- src: '!{api/**,package.json,middleware.[jt]s}',
193
- config: {
194
- zeroConfig: true,
195
- },
196
- };
197
- }
198
- }
199
- const unusedFunctionError = checkUnusedFunctions(frontendBuilder, usedFunctions, options);
200
- if (unusedFunctionError) {
90
+ }
91
+ const sortedFiles = files.sort(sortFiles);
92
+ const apiSortedFiles = files.sort(sortFilesBySegmentCount);
93
+ const usedFunctions = /* @__PURE__ */ new Set();
94
+ const addToUsedFunctions = (builder) => {
95
+ const key = Object.keys(builder.config.functions || {})[0];
96
+ if (key)
97
+ usedFunctions.add(key);
98
+ };
99
+ const absolutePathCache = /* @__PURE__ */ new Map();
100
+ const { projectSettings = {} } = options;
101
+ const { buildCommand, outputDirectory, framework } = projectSettings;
102
+ const frameworkConfig = slugToFramework.get(framework || "");
103
+ const ignoreRuntimes = new Set(frameworkConfig?.ignoreRuntimes);
104
+ const withTag = options.tag ? `@${options.tag}` : "";
105
+ const apiMatches = getApiMatches().filter(
106
+ (b) => (
107
+ // Root-level middleware is enabled, unless `disableRootMiddleware: true`
108
+ b.config?.middleware && !frameworkConfig?.disableRootMiddleware || // "api" dir runtimes are enabled, unless opted-out via `ignoreRuntimes`
109
+ !ignoreRuntimes.has(b.use)
110
+ )
111
+ ).map((b) => {
112
+ b.use = `${b.use}${withTag}`;
113
+ return b;
114
+ });
115
+ const makeFrontendStatic = buildCommand === "" || outputDirectory === "";
116
+ const usedOutputDirectory = outputDirectory || "public";
117
+ let hasUsedOutputDirectory = false;
118
+ let hasNoneApiFiles = false;
119
+ let hasNextApiFiles = false;
120
+ let fallbackEntrypoint = null;
121
+ const apiRoutes = [];
122
+ const dynamicRoutes = [];
123
+ for (const fileName of sortedFiles) {
124
+ const apiBuilder = maybeGetApiBuilder(fileName, apiMatches, options);
125
+ if (apiBuilder) {
126
+ const { routeError, apiRoute, isDynamic } = getApiRoute(
127
+ fileName,
128
+ apiSortedFiles,
129
+ options,
130
+ absolutePathCache
131
+ );
132
+ if (routeError) {
201
133
  return {
202
- builders: null,
203
- errors: [unusedFunctionError],
204
- warnings,
205
- redirectRoutes: null,
206
- defaultRoutes: null,
207
- rewriteRoutes: null,
208
- errorRoutes: null,
134
+ builders: null,
135
+ errors: [routeError],
136
+ warnings,
137
+ defaultRoutes: null,
138
+ redirectRoutes: null,
139
+ rewriteRoutes: null,
140
+ errorRoutes: null
209
141
  };
210
- }
211
- // Exclude the middleware builder for Next.js apps since @vercel/next
212
- // will build middlewares.
213
- //
214
- // While maybeGetApiBuilder() excludes the middleware builder, however,
215
- // we need to check if it's a Next.js app here again for the case where
216
- // `projectSettings.framework == null`.
217
- if (framework === null &&
218
- frontendBuilder?.use === '@vercel/next' &&
219
- apiBuilders.length > 0) {
220
- apiBuilders = apiBuilders.filter(builder => {
221
- const isMiddlewareBuilder = builder.use === '@vercel/node' && builder.config?.middleware;
222
- return !isMiddlewareBuilder;
223
- });
224
- }
225
- const builders = [];
226
- if (apiBuilders.length) {
227
- builders.push(...apiBuilders);
228
- }
229
- if (frontendBuilder) {
230
- builders.push(frontendBuilder);
231
- if (hasNextApiFiles &&
232
- apiBuilders.some(b => (0, is_official_runtime_1.isOfficialRuntime)('node', b.use))) {
233
- warnings.push({
234
- code: 'conflicting_files',
235
- message: 'When using Next.js, it is recommended to place JavaScript Functions inside of the `pages/api` (provided by Next.js) directory instead of `api` (provided by Vercel). Other languages (Python, Go, etc) should still go in the `api` directory.',
236
- link: 'https://nextjs.org/docs/api-routes/introduction',
237
- action: 'Learn More',
238
- });
239
- }
240
- }
241
- const routesResult = getRouteResult(apiRoutes, dynamicRoutes, usedOutputDirectory, apiBuilders, frontendBuilder, options);
242
- return {
142
+ }
143
+ if (apiRoute) {
144
+ apiRoutes.push(apiRoute);
145
+ if (isDynamic) {
146
+ dynamicRoutes.push(apiRoute);
147
+ }
148
+ }
149
+ addToUsedFunctions(apiBuilder);
150
+ apiBuilders.push(apiBuilder);
151
+ continue;
152
+ }
153
+ if (!hasUsedOutputDirectory && fileName.startsWith(`${usedOutputDirectory}/`)) {
154
+ hasUsedOutputDirectory = true;
155
+ }
156
+ if (!hasNoneApiFiles && !fileName.startsWith("api/") && fileName !== "package.json") {
157
+ hasNoneApiFiles = true;
158
+ }
159
+ if (!hasNextApiFiles && (fileName.startsWith("pages/api") || fileName.startsWith("src/pages/api"))) {
160
+ hasNextApiFiles = true;
161
+ }
162
+ if (!fallbackEntrypoint && buildCommand && !fileName.includes("/") && fileName !== "now.json" && fileName !== "vercel.json") {
163
+ fallbackEntrypoint = fileName;
164
+ }
165
+ }
166
+ if (!makeFrontendStatic && (hasBuildScript(pkg) || buildCommand || framework)) {
167
+ frontendBuilder = detectFrontBuilder(
168
+ pkg,
169
+ files,
170
+ usedFunctions,
171
+ fallbackEntrypoint,
172
+ options
173
+ );
174
+ } else {
175
+ if (pkg && !makeFrontendStatic && !apiBuilders.length && !options.ignoreBuildScript) {
176
+ errors.push(getMissingBuildScriptError());
177
+ return {
178
+ errors,
243
179
  warnings,
244
- builders: builders.length ? builders : null,
245
- errors: errors.length ? errors : null,
246
- redirectRoutes: routesResult.redirectRoutes,
247
- defaultRoutes: routesResult.defaultRoutes,
248
- rewriteRoutes: routesResult.rewriteRoutes,
249
- errorRoutes: routesResult.errorRoutes,
180
+ builders: null,
181
+ redirectRoutes: null,
182
+ defaultRoutes: null,
183
+ rewriteRoutes: null,
184
+ errorRoutes: null
185
+ };
186
+ }
187
+ if (hasUsedOutputDirectory && outputDirectory !== "") {
188
+ frontendBuilder = {
189
+ use: "@vercel/static",
190
+ src: `${usedOutputDirectory}/**/*`,
191
+ config: {
192
+ zeroConfig: true,
193
+ outputDirectory: usedOutputDirectory
194
+ }
195
+ };
196
+ } else if (apiBuilders.length && hasNoneApiFiles) {
197
+ frontendBuilder = {
198
+ use: "@vercel/static",
199
+ src: "!{api/**,package.json,middleware.[jt]s}",
200
+ config: {
201
+ zeroConfig: true
202
+ }
203
+ };
204
+ }
205
+ }
206
+ const unusedFunctionError = checkUnusedFunctions(
207
+ frontendBuilder,
208
+ usedFunctions,
209
+ options
210
+ );
211
+ if (unusedFunctionError) {
212
+ return {
213
+ builders: null,
214
+ errors: [unusedFunctionError],
215
+ warnings,
216
+ redirectRoutes: null,
217
+ defaultRoutes: null,
218
+ rewriteRoutes: null,
219
+ errorRoutes: null
250
220
  };
221
+ }
222
+ if (framework === null && frontendBuilder?.use === "@vercel/next" && apiBuilders.length > 0) {
223
+ apiBuilders = apiBuilders.filter((builder) => {
224
+ const isMiddlewareBuilder = builder.use === "@vercel/node" && builder.config?.middleware;
225
+ return !isMiddlewareBuilder;
226
+ });
227
+ }
228
+ const builders = [];
229
+ if (apiBuilders.length) {
230
+ builders.push(...apiBuilders);
231
+ }
232
+ if (frontendBuilder) {
233
+ builders.push(frontendBuilder);
234
+ if (hasNextApiFiles && apiBuilders.some((b) => (0, import_is_official_runtime.isOfficialRuntime)("node", b.use))) {
235
+ warnings.push({
236
+ code: "conflicting_files",
237
+ message: "When using Next.js, it is recommended to place JavaScript Functions inside of the `pages/api` (provided by Next.js) directory instead of `api` (provided by Vercel). Other languages (Python, Go, etc) should still go in the `api` directory.",
238
+ link: "https://nextjs.org/docs/api-routes/introduction",
239
+ action: "Learn More"
240
+ });
241
+ }
242
+ }
243
+ const routesResult = getRouteResult(
244
+ apiRoutes,
245
+ dynamicRoutes,
246
+ usedOutputDirectory,
247
+ apiBuilders,
248
+ frontendBuilder,
249
+ options
250
+ );
251
+ return {
252
+ warnings,
253
+ builders: builders.length ? builders : null,
254
+ errors: errors.length ? errors : null,
255
+ redirectRoutes: routesResult.redirectRoutes,
256
+ defaultRoutes: routesResult.defaultRoutes,
257
+ rewriteRoutes: routesResult.rewriteRoutes,
258
+ errorRoutes: routesResult.errorRoutes
259
+ };
251
260
  }
252
- exports.detectBuilders = detectBuilders;
253
261
  function maybeGetApiBuilder(fileName, apiMatches, options) {
254
- const middleware = fileName === 'middleware.js' || fileName === 'middleware.ts';
255
- // Root-level Middleware file is handled by `@vercel/next`, so don't
256
- // schedule a separate Builder when "nextjs" framework is selected
257
- if (middleware && options.projectSettings?.framework === 'nextjs') {
258
- return null;
259
- }
260
- if (!(fileName.startsWith('api/') || middleware)) {
261
- return null;
262
- }
263
- if (fileName.includes('/.')) {
264
- return null;
265
- }
266
- if (fileName.includes('/_')) {
267
- return null;
268
- }
269
- if (fileName.includes('/node_modules/')) {
270
- return null;
271
- }
272
- if (fileName.endsWith('.d.ts')) {
273
- return null;
274
- }
275
- const match = apiMatches.find(({ src = '**' }) => {
276
- return src === fileName || (0, minimatch_1.default)(fileName, src);
277
- });
278
- const { fnPattern, func } = getFunction(fileName, options);
279
- const use = func?.runtime || match?.use;
280
- if (!use) {
281
- return null;
282
- }
283
- const config = { zeroConfig: true };
284
- if (middleware) {
285
- config.middleware = true;
286
- }
287
- if (fnPattern && func) {
288
- config.functions = { [fnPattern]: func };
289
- if (func.includeFiles) {
290
- config.includeFiles = func.includeFiles;
291
- }
292
- if (func.excludeFiles) {
293
- config.excludeFiles = func.excludeFiles;
294
- }
295
- }
296
- const builder = {
297
- use,
298
- src: fileName,
299
- config,
300
- };
301
- return builder;
262
+ const middleware = fileName === "middleware.js" || fileName === "middleware.ts";
263
+ if (middleware && options.projectSettings?.framework === "nextjs") {
264
+ return null;
265
+ }
266
+ if (!(fileName.startsWith("api/") || middleware)) {
267
+ return null;
268
+ }
269
+ if (fileName.includes("/.")) {
270
+ return null;
271
+ }
272
+ if (fileName.includes("/_")) {
273
+ return null;
274
+ }
275
+ if (fileName.includes("/node_modules/")) {
276
+ return null;
277
+ }
278
+ if (fileName.endsWith(".d.ts")) {
279
+ return null;
280
+ }
281
+ const match = apiMatches.find(({ src = "**" }) => {
282
+ return src === fileName || (0, import_minimatch.default)(fileName, src);
283
+ });
284
+ const { fnPattern, func } = getFunction(fileName, options);
285
+ const use = func?.runtime || match?.use;
286
+ if (!use) {
287
+ return null;
288
+ }
289
+ const config = { zeroConfig: true };
290
+ if (middleware) {
291
+ config.middleware = true;
292
+ }
293
+ if (fnPattern && func) {
294
+ config.functions = { [fnPattern]: func };
295
+ if (func.includeFiles) {
296
+ config.includeFiles = func.includeFiles;
297
+ }
298
+ if (func.excludeFiles) {
299
+ config.excludeFiles = func.excludeFiles;
300
+ }
301
+ }
302
+ const builder = {
303
+ use,
304
+ src: fileName,
305
+ config
306
+ };
307
+ return builder;
302
308
  }
303
309
  function getFunction(fileName, { functions = {} }) {
304
- const keys = Object.keys(functions);
305
- if (!keys.length) {
306
- return { fnPattern: null, func: null };
307
- }
308
- const func = keys.find(key => key === fileName || (0, minimatch_1.default)(fileName, key));
309
- return func
310
- ? { fnPattern: func, func: functions[func] }
311
- : { fnPattern: null, func: null };
310
+ const keys = Object.keys(functions);
311
+ if (!keys.length) {
312
+ return { fnPattern: null, func: null };
313
+ }
314
+ const func = keys.find((key) => key === fileName || (0, import_minimatch.default)(fileName, key));
315
+ return func ? { fnPattern: func, func: functions[func] } : { fnPattern: null, func: null };
312
316
  }
313
317
  function getApiMatches() {
314
- const config = { zeroConfig: true };
315
- return [
316
- {
317
- src: 'middleware.[jt]s',
318
- use: `@vercel/node`,
319
- config: { ...config, middleware: true },
320
- },
321
- { src: 'api/**/*.+(js|mjs|ts|tsx)', use: `@vercel/node`, config },
322
- { src: 'api/**/!(*_test).go', use: `@vercel/go`, config },
323
- { src: 'api/**/*.py', use: `@vercel/python`, config },
324
- { src: 'api/**/*.rb', use: `@vercel/ruby`, config },
325
- ];
318
+ const config = { zeroConfig: true };
319
+ return [
320
+ {
321
+ src: "middleware.[jt]s",
322
+ use: `@vercel/node`,
323
+ config: { ...config, middleware: true }
324
+ },
325
+ { src: "api/**/*.+(js|mjs|ts|tsx)", use: `@vercel/node`, config },
326
+ { src: "api/**/!(*_test).go", use: `@vercel/go`, config },
327
+ { src: "api/**/*.py", use: `@vercel/python`, config },
328
+ { src: "api/**/*.rb", use: `@vercel/ruby`, config }
329
+ ];
326
330
  }
327
331
  function hasBuildScript(pkg) {
328
- const { scripts = {} } = pkg || {};
329
- return Boolean(scripts && scripts['build']);
332
+ const { scripts = {} } = pkg || {};
333
+ return Boolean(scripts && scripts["build"]);
330
334
  }
331
335
  function detectFrontBuilder(pkg, files, usedFunctions, fallbackEntrypoint, options) {
332
- const { tag, projectSettings = {} } = options;
333
- const withTag = tag ? `@${tag}` : '';
334
- const { createdAt = 0 } = projectSettings;
335
- let { framework } = projectSettings;
336
- const config = {
337
- zeroConfig: true,
338
- };
339
- if (framework) {
340
- config.framework = framework;
341
- }
342
- if (projectSettings.devCommand) {
343
- config.devCommand = projectSettings.devCommand;
344
- }
345
- if (typeof projectSettings.installCommand === 'string') {
346
- config.installCommand = projectSettings.installCommand;
347
- }
348
- if (projectSettings.buildCommand) {
349
- config.buildCommand = projectSettings.buildCommand;
350
- }
351
- if (projectSettings.outputDirectory) {
352
- config.outputDirectory = projectSettings.outputDirectory;
353
- }
354
- if (pkg &&
355
- (framework === undefined ||
356
- (framework !== 'storybook' && createdAt < Date.parse('2020-03-01')))) {
357
- const deps = {
358
- ...pkg.dependencies,
359
- ...pkg.devDependencies,
360
- };
361
- if (deps['next']) {
362
- framework = 'nextjs';
363
- }
364
- }
365
- if (options.functions) {
366
- // When the builder is not used yet we'll use it for the frontend
367
- Object.entries(options.functions).forEach(([key, func]) => {
368
- if (!usedFunctions.has(key)) {
369
- if (!config.functions)
370
- config.functions = {};
371
- config.functions[key] = { ...func };
372
- }
373
- });
374
- }
375
- const f = slugToFramework.get(framework || '');
376
- if (f && f.useRuntime) {
377
- const { src, use } = f.useRuntime;
378
- return { src, use: `${use}${withTag}`, config };
379
- }
380
- // Entrypoints for other frameworks
381
- // TODO - What if just a build script is provided, but no entrypoint.
382
- const entrypoints = new Set([
383
- 'package.json',
384
- 'config.yaml',
385
- 'config.toml',
386
- 'config.json',
387
- '_config.yml',
388
- 'config.yml',
389
- 'config.rb',
390
- ]);
391
- const source = pkg
392
- ? 'package.json'
393
- : files.find(file => entrypoints.has(file)) ||
394
- fallbackEntrypoint ||
395
- 'package.json';
396
- return {
397
- src: source || 'package.json',
398
- use: `@vercel/static-build${withTag}`,
399
- config,
336
+ const { tag, projectSettings = {} } = options;
337
+ const withTag = tag ? `@${tag}` : "";
338
+ const { createdAt = 0 } = projectSettings;
339
+ let { framework } = projectSettings;
340
+ const config = {
341
+ zeroConfig: true
342
+ };
343
+ if (framework) {
344
+ config.framework = framework;
345
+ }
346
+ if (projectSettings.devCommand) {
347
+ config.devCommand = projectSettings.devCommand;
348
+ }
349
+ if (typeof projectSettings.installCommand === "string") {
350
+ config.installCommand = projectSettings.installCommand;
351
+ }
352
+ if (projectSettings.buildCommand) {
353
+ config.buildCommand = projectSettings.buildCommand;
354
+ }
355
+ if (projectSettings.outputDirectory) {
356
+ config.outputDirectory = projectSettings.outputDirectory;
357
+ }
358
+ if (pkg && (framework === void 0 || framework !== "storybook" && createdAt < Date.parse("2020-03-01"))) {
359
+ const deps = {
360
+ ...pkg.dependencies,
361
+ ...pkg.devDependencies
400
362
  };
363
+ if (deps["next"]) {
364
+ framework = "nextjs";
365
+ }
366
+ }
367
+ if (options.functions) {
368
+ Object.entries(options.functions).forEach(([key, func]) => {
369
+ if (!usedFunctions.has(key)) {
370
+ if (!config.functions)
371
+ config.functions = {};
372
+ config.functions[key] = { ...func };
373
+ }
374
+ });
375
+ }
376
+ const f = slugToFramework.get(framework || "");
377
+ if (f && f.useRuntime) {
378
+ const { src, use } = f.useRuntime;
379
+ return { src, use: `${use}${withTag}`, config };
380
+ }
381
+ const entrypoints = /* @__PURE__ */ new Set([
382
+ "package.json",
383
+ "config.yaml",
384
+ "config.toml",
385
+ "config.json",
386
+ "_config.yml",
387
+ "config.yml",
388
+ "config.rb"
389
+ ]);
390
+ const source = pkg ? "package.json" : files.find((file) => entrypoints.has(file)) || fallbackEntrypoint || "package.json";
391
+ return {
392
+ src: source || "package.json",
393
+ use: `@vercel/static-build${withTag}`,
394
+ config
395
+ };
401
396
  }
402
397
  function getMissingBuildScriptError() {
403
- return {
404
- code: 'missing_build_script',
405
- message: 'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
406
- '\nLearn More: https://vercel.link/missing-build-script',
407
- };
398
+ return {
399
+ code: "missing_build_script",
400
+ message: "Your `package.json` file is missing a `build` property inside the `scripts` property.\nLearn More: https://vercel.link/missing-build-script"
401
+ };
408
402
  }
409
403
  function validateFunctions({ functions = {} }) {
410
- for (const [path, func] of Object.entries(functions)) {
411
- if (path.length > 256) {
412
- return {
413
- code: 'invalid_function_glob',
414
- message: 'Function globs must be less than 256 characters long.',
415
- };
416
- }
417
- if (!func || typeof func !== 'object') {
418
- return {
419
- code: 'invalid_function',
420
- message: 'Function must be an object.',
421
- };
422
- }
423
- if (Object.keys(func).length === 0) {
424
- return {
425
- code: 'invalid_function',
426
- message: 'Function must contain at least one property.',
427
- };
428
- }
429
- if (func.maxDuration !== undefined &&
430
- (func.maxDuration < 1 ||
431
- func.maxDuration > 900 ||
432
- !Number.isInteger(func.maxDuration))) {
433
- return {
434
- code: 'invalid_function_duration',
435
- message: 'Functions must have a duration between 1 and 900.',
436
- };
437
- }
438
- if (func.memory !== undefined &&
439
- (func.memory < 128 || func.memory > 3008)) {
440
- return {
441
- code: 'invalid_function_memory',
442
- message: 'Functions must have a memory value between 128 and 3008',
443
- };
444
- }
445
- if (path.startsWith('/')) {
446
- return {
447
- code: 'invalid_function_source',
448
- message: `The function path "${path}" is invalid. The path must be relative to your project root and therefore cannot start with a slash.`,
449
- };
450
- }
451
- if (func.runtime !== undefined) {
452
- const tag = `${func.runtime}`.split('@').pop();
453
- if (!tag || !(0, semver_1.valid)(tag)) {
454
- return {
455
- code: 'invalid_function_runtime',
456
- message: 'Function Runtimes must have a valid version, for example `now-php@1.0.0`.',
457
- };
458
- }
459
- }
460
- if (func.includeFiles !== undefined) {
461
- if (typeof func.includeFiles !== 'string') {
462
- return {
463
- code: 'invalid_function_property',
464
- message: `The property \`includeFiles\` must be a string.`,
465
- };
466
- }
467
- }
468
- if (func.excludeFiles !== undefined) {
469
- if (typeof func.excludeFiles !== 'string') {
470
- return {
471
- code: 'invalid_function_property',
472
- message: `The property \`excludeFiles\` must be a string.`,
473
- };
474
- }
475
- }
476
- }
477
- return null;
478
- }
479
- function checkUnusedFunctions(frontendBuilder, usedFunctions, options) {
480
- const unusedFunctions = new Set(Object.keys(options.functions || {}).filter(key => !usedFunctions.has(key)));
481
- if (!unusedFunctions.size) {
482
- return null;
483
- }
484
- // Next.js can use functions only for `src/pages` or `pages`
485
- if (frontendBuilder && (0, is_official_runtime_1.isOfficialRuntime)('next', frontendBuilder.use)) {
486
- for (const fnKey of unusedFunctions.values()) {
487
- if (fnKey.startsWith('pages/') ||
488
- fnKey.startsWith('src/pages') ||
489
- fnKey.startsWith('app/') ||
490
- fnKey.startsWith('src/app/')) {
491
- unusedFunctions.delete(fnKey);
492
- }
493
- else {
494
- return {
495
- code: 'unused_function',
496
- message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions.`,
497
- action: 'Learn More',
498
- link: 'https://vercel.link/unmatched-function-pattern',
499
- };
500
- }
501
- }
404
+ for (const [path, func] of Object.entries(functions)) {
405
+ if (path.length > 256) {
406
+ return {
407
+ code: "invalid_function_glob",
408
+ message: "Function globs must be less than 256 characters long."
409
+ };
410
+ }
411
+ if (!func || typeof func !== "object") {
412
+ return {
413
+ code: "invalid_function",
414
+ message: "Function must be an object."
415
+ };
416
+ }
417
+ if (Object.keys(func).length === 0) {
418
+ return {
419
+ code: "invalid_function",
420
+ message: "Function must contain at least one property."
421
+ };
422
+ }
423
+ if (func.maxDuration !== void 0 && (func.maxDuration < 1 || func.maxDuration > 900 || !Number.isInteger(func.maxDuration))) {
424
+ return {
425
+ code: "invalid_function_duration",
426
+ message: "Functions must have a duration between 1 and 900."
427
+ };
428
+ }
429
+ if (func.memory !== void 0 && (func.memory < 128 || func.memory > 3008)) {
430
+ return {
431
+ code: "invalid_function_memory",
432
+ message: "Functions must have a memory value between 128 and 3008"
433
+ };
434
+ }
435
+ if (path.startsWith("/")) {
436
+ return {
437
+ code: "invalid_function_source",
438
+ message: `The function path "${path}" is invalid. The path must be relative to your project root and therefore cannot start with a slash.`
439
+ };
440
+ }
441
+ if (func.runtime !== void 0) {
442
+ const tag = `${func.runtime}`.split("@").pop();
443
+ if (!tag || !(0, import_semver.valid)(tag)) {
444
+ return {
445
+ code: "invalid_function_runtime",
446
+ message: "Function Runtimes must have a valid version, for example `now-php@1.0.0`."
447
+ };
448
+ }
502
449
  }
503
- if (unusedFunctions.size) {
504
- const [fnKey] = Array.from(unusedFunctions);
450
+ if (func.includeFiles !== void 0) {
451
+ if (typeof func.includeFiles !== "string") {
505
452
  return {
506
- code: 'unused_function',
507
- message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
508
- action: 'Learn More',
509
- link: 'https://vercel.link/unmatched-function-pattern',
453
+ code: "invalid_function_property",
454
+ message: `The property \`includeFiles\` must be a string.`
510
455
  };
456
+ }
511
457
  }
512
- return null;
513
- }
514
- function getApiRoute(fileName, sortedFiles, options, absolutePathCache) {
515
- const conflictingSegment = getConflictingSegment(fileName);
516
- if (conflictingSegment) {
458
+ if (func.excludeFiles !== void 0) {
459
+ if (typeof func.excludeFiles !== "string") {
517
460
  return {
518
- apiRoute: null,
519
- isDynamic: false,
520
- routeError: {
521
- code: 'conflicting_path_segment',
522
- message: `The segment "${conflictingSegment}" occurs more than ` +
523
- `one time in your path "${fileName}". Please make sure that ` +
524
- `every segment in a path is unique.`,
525
- },
461
+ code: "invalid_function_property",
462
+ message: `The property \`excludeFiles\` must be a string.`
526
463
  };
464
+ }
527
465
  }
528
- const occurrences = pathOccurrences(fileName, sortedFiles, absolutePathCache);
529
- if (occurrences.length > 0) {
530
- const messagePaths = concatArrayOfText(occurrences.map(name => `"${name}"`));
466
+ }
467
+ return null;
468
+ }
469
+ function checkUnusedFunctions(frontendBuilder, usedFunctions, options) {
470
+ const unusedFunctions = new Set(
471
+ Object.keys(options.functions || {}).filter((key) => !usedFunctions.has(key))
472
+ );
473
+ if (!unusedFunctions.size) {
474
+ return null;
475
+ }
476
+ if (frontendBuilder && (0, import_is_official_runtime.isOfficialRuntime)("next", frontendBuilder.use)) {
477
+ for (const fnKey of unusedFunctions.values()) {
478
+ if (fnKey.startsWith("pages/") || fnKey.startsWith("src/pages") || fnKey.startsWith("app/") || fnKey.startsWith("src/app/")) {
479
+ unusedFunctions.delete(fnKey);
480
+ } else {
531
481
  return {
532
- apiRoute: null,
533
- isDynamic: false,
534
- routeError: {
535
- code: 'conflicting_file_path',
536
- message: `Two or more files have conflicting paths or names. ` +
537
- `Please make sure path segments and filenames, without their extension, are unique. ` +
538
- `The path "${fileName}" has conflicts with ${messagePaths}.`,
539
- },
482
+ code: "unused_function",
483
+ message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions.`,
484
+ action: "Learn More",
485
+ link: "https://vercel.link/unmatched-function-pattern"
540
486
  };
487
+ }
541
488
  }
542
- const out = createRouteFromPath(fileName, Boolean(options.featHandleMiss), Boolean(options.cleanUrls));
489
+ }
490
+ if (unusedFunctions.size) {
491
+ const [fnKey] = Array.from(unusedFunctions);
543
492
  return {
544
- apiRoute: out.route,
545
- isDynamic: out.isDynamic,
546
- routeError: null,
493
+ code: "unused_function",
494
+ message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
495
+ action: "Learn More",
496
+ link: "https://vercel.link/unmatched-function-pattern"
547
497
  };
498
+ }
499
+ return null;
500
+ }
501
+ function getApiRoute(fileName, sortedFiles, options, absolutePathCache) {
502
+ const conflictingSegment = getConflictingSegment(fileName);
503
+ if (conflictingSegment) {
504
+ return {
505
+ apiRoute: null,
506
+ isDynamic: false,
507
+ routeError: {
508
+ code: "conflicting_path_segment",
509
+ message: `The segment "${conflictingSegment}" occurs more than one time in your path "${fileName}". Please make sure that every segment in a path is unique.`
510
+ }
511
+ };
512
+ }
513
+ const occurrences = pathOccurrences(fileName, sortedFiles, absolutePathCache);
514
+ if (occurrences.length > 0) {
515
+ const messagePaths = concatArrayOfText(
516
+ occurrences.map((name) => `"${name}"`)
517
+ );
518
+ return {
519
+ apiRoute: null,
520
+ isDynamic: false,
521
+ routeError: {
522
+ code: "conflicting_file_path",
523
+ message: `Two or more files have conflicting paths or names. Please make sure path segments and filenames, without their extension, are unique. The path "${fileName}" has conflicts with ${messagePaths}.`
524
+ }
525
+ };
526
+ }
527
+ const out = createRouteFromPath(
528
+ fileName,
529
+ Boolean(options.featHandleMiss),
530
+ Boolean(options.cleanUrls)
531
+ );
532
+ return {
533
+ apiRoute: out.route,
534
+ isDynamic: out.isDynamic,
535
+ routeError: null
536
+ };
548
537
  }
549
- // Checks if a placeholder with the same name is used
550
- // multiple times inside the same path
551
538
  function getConflictingSegment(filePath) {
552
- const segments = new Set();
553
- for (const segment of filePath.split('/')) {
554
- const name = getSegmentName(segment);
555
- if (name !== null && segments.has(name)) {
556
- return name;
557
- }
558
- if (name) {
559
- segments.add(name);
560
- }
539
+ const segments = /* @__PURE__ */ new Set();
540
+ for (const segment of filePath.split("/")) {
541
+ const name = getSegmentName(segment);
542
+ if (name !== null && segments.has(name)) {
543
+ return name;
561
544
  }
562
- return null;
545
+ if (name) {
546
+ segments.add(name);
547
+ }
548
+ }
549
+ return null;
563
550
  }
564
- // Takes a filename or foldername, strips the extension
565
- // gets the part between the "[]" brackets.
566
- // It will return `null` if there are no brackets
567
- // and therefore no segment.
568
551
  function getSegmentName(segment) {
569
- const { name } = (0, path_1.parse)(segment);
570
- if (name.startsWith('[') && name.endsWith(']')) {
571
- return name.slice(1, -1);
572
- }
573
- return null;
552
+ const { name } = (0, import_path.parse)(segment);
553
+ if (name.startsWith("[") && name.endsWith("]")) {
554
+ return name.slice(1, -1);
555
+ }
556
+ return null;
574
557
  }
575
558
  function getAbsolutePath(unresolvedPath) {
576
- const { dir, name } = (0, path_1.parse)(unresolvedPath);
577
- const parts = joinPath(dir, name).split('/');
578
- return parts.map(part => part.replace(/\[.*\]/, '1')).join('/');
559
+ const { dir, name } = (0, import_path.parse)(unresolvedPath);
560
+ const parts = joinPath(dir, name).split("/");
561
+ return parts.map((part) => part.replace(/\[.*\]/, "1")).join("/");
579
562
  }
580
- // Counts how often a path occurs when all placeholders
581
- // got resolved, so we can check if they have conflicts
582
563
  function pathOccurrences(fileName, files, absolutePathCache) {
583
- let currentAbsolutePath = absolutePathCache.get(fileName);
584
- if (!currentAbsolutePath) {
585
- currentAbsolutePath = getAbsolutePath(fileName);
586
- absolutePathCache.set(fileName, currentAbsolutePath);
587
- }
588
- const prev = [];
589
- // Do not call expensive functions like `minimatch` in here
590
- // because we iterate over every file.
591
- for (const file of files) {
592
- if (file === fileName) {
593
- continue;
594
- }
595
- let absolutePath = absolutePathCache.get(file);
596
- if (!absolutePath) {
597
- absolutePath = getAbsolutePath(file);
598
- absolutePathCache.set(file, absolutePath);
599
- }
600
- if (absolutePath === currentAbsolutePath) {
601
- prev.push(file);
602
- }
603
- else if (partiallyMatches(fileName, file)) {
604
- prev.push(file);
605
- }
606
- }
607
- return prev;
564
+ let currentAbsolutePath = absolutePathCache.get(fileName);
565
+ if (!currentAbsolutePath) {
566
+ currentAbsolutePath = getAbsolutePath(fileName);
567
+ absolutePathCache.set(fileName, currentAbsolutePath);
568
+ }
569
+ const prev = [];
570
+ for (const file of files) {
571
+ if (file === fileName) {
572
+ continue;
573
+ }
574
+ let absolutePath = absolutePathCache.get(file);
575
+ if (!absolutePath) {
576
+ absolutePath = getAbsolutePath(file);
577
+ absolutePathCache.set(file, absolutePath);
578
+ }
579
+ if (absolutePath === currentAbsolutePath) {
580
+ prev.push(file);
581
+ } else if (partiallyMatches(fileName, file)) {
582
+ prev.push(file);
583
+ }
584
+ }
585
+ return prev;
608
586
  }
609
587
  function joinPath(...segments) {
610
- const joinedPath = segments.join('/');
611
- return joinedPath.replace(/\/{2,}/g, '/');
588
+ const joinedPath = segments.join("/");
589
+ return joinedPath.replace(/\/{2,}/g, "/");
612
590
  }
613
591
  function escapeName(name) {
614
- const special = '[]^$.|?*+()'.split('');
615
- for (const char of special) {
616
- name = name.replace(new RegExp(`\\${char}`, 'g'), `\\${char}`);
617
- }
618
- return name;
592
+ const special = "[]^$.|?*+()".split("");
593
+ for (const char of special) {
594
+ name = name.replace(new RegExp(`\\${char}`, "g"), `\\${char}`);
595
+ }
596
+ return name;
619
597
  }
620
598
  function concatArrayOfText(texts) {
621
- if (texts.length <= 2) {
622
- return texts.join(' and ');
623
- }
624
- const last = texts.pop();
625
- return `${texts.join(', ')}, and ${last}`;
599
+ if (texts.length <= 2) {
600
+ return texts.join(" and ");
601
+ }
602
+ const last = texts.pop();
603
+ return `${texts.join(", ")}, and ${last}`;
626
604
  }
627
- // Check if the path partially matches and has the same
628
- // name for the path segment at the same position
629
605
  function partiallyMatches(pathA, pathB) {
630
- const partsA = pathA.split('/');
631
- const partsB = pathB.split('/');
632
- const long = partsA.length > partsB.length ? partsA : partsB;
633
- const short = long === partsA ? partsB : partsA;
634
- let index = 0;
635
- for (const segmentShort of short) {
636
- const segmentLong = long[index];
637
- const nameLong = getSegmentName(segmentLong);
638
- const nameShort = getSegmentName(segmentShort);
639
- // If there are no segments or the paths differ we
640
- // return as they are not matching
641
- if (segmentShort !== segmentLong && (!nameLong || !nameShort)) {
642
- return false;
643
- }
644
- if (nameLong !== nameShort) {
645
- return true;
646
- }
647
- index += 1;
648
- }
649
- return false;
606
+ const partsA = pathA.split("/");
607
+ const partsB = pathB.split("/");
608
+ const long = partsA.length > partsB.length ? partsA : partsB;
609
+ const short = long === partsA ? partsB : partsA;
610
+ let index = 0;
611
+ for (const segmentShort of short) {
612
+ const segmentLong = long[index];
613
+ const nameLong = getSegmentName(segmentLong);
614
+ const nameShort = getSegmentName(segmentShort);
615
+ if (segmentShort !== segmentLong && (!nameLong || !nameShort)) {
616
+ return false;
617
+ }
618
+ if (nameLong !== nameShort) {
619
+ return true;
620
+ }
621
+ index += 1;
622
+ }
623
+ return false;
650
624
  }
651
625
  function createRouteFromPath(filePath, featHandleMiss, cleanUrls) {
652
- const parts = filePath.split('/');
653
- let counter = 1;
654
- const query = [];
655
- let isDynamic = false;
656
- const srcParts = parts.map((segment, i) => {
657
- const name = getSegmentName(segment);
658
- const isLast = i === parts.length - 1;
659
- if (name !== null) {
660
- // We can't use `URLSearchParams` because `$` would get escaped
661
- query.push(`${name}=$${counter++}`);
662
- isDynamic = true;
663
- return `([^/]+)`;
664
- }
665
- else if (isLast) {
666
- const { name: fileName, ext } = (0, path_1.parse)(segment);
667
- const isIndex = fileName === 'index';
668
- const prefix = isIndex ? '/' : '';
669
- const names = [
670
- isIndex ? prefix : `${fileName}/`,
671
- prefix + escapeName(fileName),
672
- featHandleMiss && cleanUrls
673
- ? ''
674
- : prefix + escapeName(fileName) + escapeName(ext),
675
- ].filter(Boolean);
676
- // Either filename with extension, filename without extension
677
- // or nothing when the filename is `index`.
678
- // When `cleanUrls: true` then do *not* add the filename with extension.
679
- return `(${names.join('|')})${isIndex ? '?' : ''}`;
680
- }
681
- return segment;
682
- });
683
- const { name: fileName, ext } = (0, path_1.parse)(filePath);
684
- const isIndex = fileName === 'index';
685
- const queryString = `${query.length ? '?' : ''}${query.join('&')}`;
686
- const src = isIndex
687
- ? `^/${srcParts.slice(0, -1).join('/')}${srcParts.slice(-1)[0]}$`
688
- : `^/${srcParts.join('/')}$`;
689
- let route;
690
- if (featHandleMiss) {
691
- const extensionless = ext ? filePath.slice(0, -ext.length) : filePath;
692
- route = {
693
- src,
694
- dest: `/${extensionless}${queryString}`,
695
- check: true,
696
- };
697
- }
698
- else {
699
- route = {
700
- src,
701
- dest: `/${filePath}${queryString}`,
702
- };
703
- }
704
- return { route, isDynamic };
626
+ const parts = filePath.split("/");
627
+ let counter = 1;
628
+ const query = [];
629
+ let isDynamic = false;
630
+ const srcParts = parts.map((segment, i) => {
631
+ const name = getSegmentName(segment);
632
+ const isLast = i === parts.length - 1;
633
+ if (name !== null) {
634
+ query.push(`${name}=$${counter++}`);
635
+ isDynamic = true;
636
+ return `([^/]+)`;
637
+ } else if (isLast) {
638
+ const { name: fileName2, ext: ext2 } = (0, import_path.parse)(segment);
639
+ const isIndex2 = fileName2 === "index";
640
+ const prefix = isIndex2 ? "/" : "";
641
+ const names = [
642
+ isIndex2 ? prefix : `${fileName2}/`,
643
+ prefix + escapeName(fileName2),
644
+ featHandleMiss && cleanUrls ? "" : prefix + escapeName(fileName2) + escapeName(ext2)
645
+ ].filter(Boolean);
646
+ return `(${names.join("|")})${isIndex2 ? "?" : ""}`;
647
+ }
648
+ return segment;
649
+ });
650
+ const { name: fileName, ext } = (0, import_path.parse)(filePath);
651
+ const isIndex = fileName === "index";
652
+ const queryString = `${query.length ? "?" : ""}${query.join("&")}`;
653
+ const src = isIndex ? `^/${srcParts.slice(0, -1).join("/")}${srcParts.slice(-1)[0]}$` : `^/${srcParts.join("/")}$`;
654
+ let route;
655
+ if (featHandleMiss) {
656
+ const extensionless = ext ? filePath.slice(0, -ext.length) : filePath;
657
+ route = {
658
+ src,
659
+ dest: `/${extensionless}${queryString}`,
660
+ check: true
661
+ };
662
+ } else {
663
+ route = {
664
+ src,
665
+ dest: `/${filePath}${queryString}`
666
+ };
667
+ }
668
+ return { route, isDynamic };
705
669
  }
706
670
  function getRouteResult(apiRoutes, dynamicRoutes, outputDirectory, apiBuilders, frontendBuilder, options) {
707
- const defaultRoutes = [];
708
- const redirectRoutes = [];
709
- const rewriteRoutes = [];
710
- const errorRoutes = [];
711
- const framework = frontendBuilder?.config?.framework || '';
712
- const isGatsby = framework === 'gatsby';
713
- const isNextjs = framework === 'nextjs' || (0, is_official_runtime_1.isOfficialRuntime)('next', frontendBuilder?.use);
714
- const ignoreRuntimes = slugToFramework.get(framework)?.ignoreRuntimes;
715
- if (apiRoutes && apiRoutes.length > 0) {
716
- if (options.featHandleMiss) {
717
- // Exclude extension names if the corresponding plugin is not found in package.json
718
- // detectBuilders({ignoreRoutesForBuilders: ['@vercel/python']})
719
- // return a copy of routes.
720
- // We should exclud errorRoutes and
721
- const extSet = detectApiExtensions(apiBuilders);
722
- if (extSet.size > 0) {
723
- const extGroup = `(?:\\.(?:${Array.from(extSet)
724
- .map(ext => ext.slice(1))
725
- .join('|')}))`;
726
- if (options.cleanUrls) {
727
- redirectRoutes.push({
728
- src: `^/(api(?:.+)?)/index${extGroup}?/?$`,
729
- headers: { Location: options.trailingSlash ? '/$1/' : '/$1' },
730
- status: 308,
731
- });
732
- redirectRoutes.push({
733
- src: `^/api/(.+)${extGroup}/?$`,
734
- headers: {
735
- Location: options.trailingSlash ? '/api/$1/' : '/api/$1',
736
- },
737
- status: 308,
738
- });
739
- }
740
- else {
741
- defaultRoutes.push({ handle: 'miss' });
742
- defaultRoutes.push({
743
- src: `^/api/(.+)${extGroup}$`,
744
- dest: '/api/$1',
745
- check: true,
746
- });
747
- }
748
- }
749
- rewriteRoutes.push(...dynamicRoutes);
750
- const hasApiBuild = apiBuilders.find(builder => {
751
- return builder.src?.startsWith('api/');
752
- });
753
- if (typeof ignoreRuntimes === 'undefined' && hasApiBuild) {
754
- // This route is only necessary to hide the directory listing
755
- // to avoid enumerating serverless function names.
756
- // But it causes issues in `vc dev` for frameworks that handle
757
- // their own functions such as redwood, so we ignore.
758
- rewriteRoutes.push({
759
- src: '^/api(/.*)?$',
760
- status: 404,
761
- });
762
- }
763
- }
764
- else {
765
- defaultRoutes.push(...apiRoutes);
766
- if (apiRoutes.length) {
767
- defaultRoutes.push({
768
- status: 404,
769
- src: '^/api(/.*)?$',
770
- });
771
- }
772
- }
773
- }
774
- if (outputDirectory &&
775
- frontendBuilder &&
776
- !options.featHandleMiss &&
777
- (0, is_official_runtime_1.isOfficialRuntime)('static', frontendBuilder.use)) {
778
- defaultRoutes.push({
779
- src: '/(.*)',
780
- dest: `/${outputDirectory}/$1`,
671
+ const defaultRoutes = [];
672
+ const redirectRoutes = [];
673
+ const rewriteRoutes = [];
674
+ const errorRoutes = [];
675
+ const framework = frontendBuilder?.config?.framework || "";
676
+ const isGatsby = framework === "gatsby";
677
+ const isNextjs = framework === "nextjs" || (0, import_is_official_runtime.isOfficialRuntime)("next", frontendBuilder?.use);
678
+ const ignoreRuntimes = slugToFramework.get(framework)?.ignoreRuntimes;
679
+ if (apiRoutes && apiRoutes.length > 0) {
680
+ if (options.featHandleMiss) {
681
+ const extSet = detectApiExtensions(apiBuilders);
682
+ if (extSet.size > 0) {
683
+ const extGroup = `(?:\\.(?:${Array.from(extSet).map((ext) => ext.slice(1)).join("|")}))`;
684
+ if (options.cleanUrls) {
685
+ redirectRoutes.push({
686
+ src: `^/(api(?:.+)?)/index${extGroup}?/?$`,
687
+ headers: { Location: options.trailingSlash ? "/$1/" : "/$1" },
688
+ status: 308
689
+ });
690
+ redirectRoutes.push({
691
+ src: `^/api/(.+)${extGroup}/?$`,
692
+ headers: {
693
+ Location: options.trailingSlash ? "/api/$1/" : "/api/$1"
694
+ },
695
+ status: 308
696
+ });
697
+ } else {
698
+ defaultRoutes.push({ handle: "miss" });
699
+ defaultRoutes.push({
700
+ src: `^/api/(.+)${extGroup}$`,
701
+ dest: "/api/$1",
702
+ check: true
703
+ });
704
+ }
705
+ }
706
+ rewriteRoutes.push(...dynamicRoutes);
707
+ const hasApiBuild = apiBuilders.find((builder) => {
708
+ return builder.src?.startsWith("api/");
709
+ });
710
+ if (typeof ignoreRuntimes === "undefined" && hasApiBuild) {
711
+ rewriteRoutes.push({
712
+ src: "^/api(/.*)?$",
713
+ status: 404
781
714
  });
782
- }
783
- if (options.featHandleMiss && !isNextjs && !isGatsby) {
784
- // Exclude Next.js (and Gatsby) to avoid overriding custom error page
785
- // https://nextjs.org/docs/advanced-features/custom-error-page
786
- errorRoutes.push({
787
- status: 404,
788
- src: '^(?!/api).*$',
789
- dest: options.cleanUrls ? '/404' : '/404.html',
715
+ }
716
+ } else {
717
+ defaultRoutes.push(...apiRoutes);
718
+ if (apiRoutes.length) {
719
+ defaultRoutes.push({
720
+ status: 404,
721
+ src: "^/api(/.*)?$"
790
722
  });
723
+ }
791
724
  }
792
- return {
793
- defaultRoutes,
794
- redirectRoutes,
795
- rewriteRoutes,
796
- errorRoutes,
797
- };
725
+ }
726
+ if (outputDirectory && frontendBuilder && !options.featHandleMiss && (0, import_is_official_runtime.isOfficialRuntime)("static", frontendBuilder.use)) {
727
+ defaultRoutes.push({
728
+ src: "/(.*)",
729
+ dest: `/${outputDirectory}/$1`
730
+ });
731
+ }
732
+ if (options.featHandleMiss && !isNextjs && !isGatsby) {
733
+ errorRoutes.push({
734
+ status: 404,
735
+ src: "^(?!/api).*$",
736
+ dest: options.cleanUrls ? "/404" : "/404.html"
737
+ });
738
+ }
739
+ return {
740
+ defaultRoutes,
741
+ redirectRoutes,
742
+ rewriteRoutes,
743
+ errorRoutes
744
+ };
798
745
  }
799
746
  function sortFilesBySegmentCount(fileA, fileB) {
800
- const lengthA = fileA.split('/').length;
801
- const lengthB = fileB.split('/').length;
802
- if (lengthA > lengthB) {
803
- return -1;
804
- }
805
- if (lengthA < lengthB) {
806
- return 1;
807
- }
808
- // Paths that have the same segment length but
809
- // less placeholders are preferred
810
- const countSegments = (prev, segment) => getSegmentName(segment) ? prev + 1 : 0;
811
- const segmentLengthA = fileA.split('/').reduce(countSegments, 0);
812
- const segmentLengthB = fileB.split('/').reduce(countSegments, 0);
813
- if (segmentLengthA > segmentLengthB) {
814
- return 1;
815
- }
816
- if (segmentLengthA < segmentLengthB) {
817
- return -1;
818
- }
819
- return fileA.localeCompare(fileB);
747
+ const lengthA = fileA.split("/").length;
748
+ const lengthB = fileB.split("/").length;
749
+ if (lengthA > lengthB) {
750
+ return -1;
751
+ }
752
+ if (lengthA < lengthB) {
753
+ return 1;
754
+ }
755
+ const countSegments = (prev, segment) => getSegmentName(segment) ? prev + 1 : 0;
756
+ const segmentLengthA = fileA.split("/").reduce(countSegments, 0);
757
+ const segmentLengthB = fileB.split("/").reduce(countSegments, 0);
758
+ if (segmentLengthA > segmentLengthB) {
759
+ return 1;
760
+ }
761
+ if (segmentLengthA < segmentLengthB) {
762
+ return -1;
763
+ }
764
+ return fileA.localeCompare(fileB);
820
765
  }
821
- //# sourceMappingURL=detect-builders.js.map
766
+ // Annotate the CommonJS export names for ESM import in node:
767
+ 0 && (module.exports = {
768
+ detectApiDirectory,
769
+ detectApiExtensions,
770
+ detectBuilders,
771
+ detectOutputDirectory,
772
+ sortFiles
773
+ });
774
+ //# sourceMappingURL=detect-builders.js.map