1 | "use strict";
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
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 _1 = require("./");
|
12 | const frameworkList = frameworks_1.default;
|
13 | const slugToFramework = new Map(frameworkList.map(f => [f.slug, f]));
|
14 |
|
15 |
|
16 | function sortFiles(fileA, fileB) {
|
17 | return fileA.localeCompare(fileB);
|
18 | }
|
19 | exports.sortFiles = sortFiles;
|
20 | function detectApiExtensions(builders) {
|
21 | return new Set(builders
|
22 | .filter((b) => { var _a; return Boolean(b.config && b.config.zeroConfig && ((_a = b.src) === null || _a === void 0 ? void 0 : _a.startsWith('api/'))); })
|
23 | .map(b => path_1.extname(b.src))
|
24 | .filter(Boolean));
|
25 | }
|
26 | exports.detectApiExtensions = detectApiExtensions;
|
27 | function detectApiDirectory(builders) {
|
28 |
|
29 |
|
30 | const found = builders.some(b => { var _a; return b.config && b.config.zeroConfig && ((_a = b.src) === null || _a === void 0 ? void 0 : _a.startsWith('api/')); });
|
31 | return found ? 'api' : null;
|
32 | }
|
33 | exports.detectApiDirectory = detectApiDirectory;
|
34 |
|
35 | function getPublicBuilder(builders) {
|
36 | for (const builder of builders) {
|
37 | if (typeof builder.src === 'string' &&
|
38 | _1.isOfficialRuntime('static', builder.use) &&
|
39 | /^.*\/\*\*\/\*$/.test(builder.src) &&
|
40 | builder.config &&
|
41 | builder.config.zeroConfig === true) {
|
42 | return builder;
|
43 | }
|
44 | }
|
45 | return null;
|
46 | }
|
47 | function detectOutputDirectory(builders) {
|
48 |
|
49 |
|
50 | const publicBuilder = getPublicBuilder(builders);
|
51 | return publicBuilder ? publicBuilder.src.replace('/**/*', '') : null;
|
52 | }
|
53 | exports.detectOutputDirectory = detectOutputDirectory;
|
54 | async function detectBuilders(files, pkg, options = {}) {
|
55 | var _a;
|
56 | const errors = [];
|
57 | const warnings = [];
|
58 | const apiBuilders = [];
|
59 | let frontendBuilder = null;
|
60 | const functionError = validateFunctions(options);
|
61 | if (functionError) {
|
62 | return {
|
63 | builders: null,
|
64 | errors: [functionError],
|
65 | warnings,
|
66 | defaultRoutes: null,
|
67 | redirectRoutes: null,
|
68 | rewriteRoutes: null,
|
69 | errorRoutes: null,
|
70 | };
|
71 | }
|
72 | const sortedFiles = files.sort(sortFiles);
|
73 | const apiSortedFiles = files.sort(sortFilesBySegmentCount);
|
74 |
|
75 | const usedFunctions = new Set();
|
76 | const addToUsedFunctions = (builder) => {
|
77 | const key = Object.keys(builder.config.functions || {})[0];
|
78 | if (key)
|
79 | usedFunctions.add(key);
|
80 | };
|
81 | const absolutePathCache = new Map();
|
82 | const { projectSettings = {} } = options;
|
83 | const { buildCommand, outputDirectory, framework } = projectSettings;
|
84 | const ignoreRuntimes = new Set((_a = slugToFramework.get(framework || '')) === null || _a === void 0 ? void 0 : _a.ignoreRuntimes);
|
85 | const withTag = options.tag ? `@${options.tag}` : '';
|
86 | const apiMatches = getApiMatches()
|
87 | .filter(b => !ignoreRuntimes.has(b.use))
|
88 | .map(b => {
|
89 | b.use = `${b.use}${withTag}`;
|
90 | return b;
|
91 | });
|
92 |
|
93 | const makeFrontendStatic = buildCommand === '' || outputDirectory === '';
|
94 |
|
95 |
|
96 | const usedOutputDirectory = outputDirectory || 'public';
|
97 | let hasUsedOutputDirectory = false;
|
98 | let hasNoneApiFiles = false;
|
99 | let hasNextApiFiles = false;
|
100 | let fallbackEntrypoint = null;
|
101 | const apiRoutes = [];
|
102 | const dynamicRoutes = [];
|
103 |
|
104 | for (const fileName of sortedFiles) {
|
105 | const apiBuilder = maybeGetApiBuilder(fileName, apiMatches, options);
|
106 | if (apiBuilder) {
|
107 | const { routeError, apiRoute, isDynamic } = getApiRoute(fileName, apiSortedFiles, options, absolutePathCache);
|
108 | if (routeError) {
|
109 | return {
|
110 | builders: null,
|
111 | errors: [routeError],
|
112 | warnings,
|
113 | defaultRoutes: null,
|
114 | redirectRoutes: null,
|
115 | rewriteRoutes: null,
|
116 | errorRoutes: null,
|
117 | };
|
118 | }
|
119 | if (apiRoute) {
|
120 | apiRoutes.push(apiRoute);
|
121 | if (isDynamic) {
|
122 | dynamicRoutes.push(apiRoute);
|
123 | }
|
124 | }
|
125 | addToUsedFunctions(apiBuilder);
|
126 | apiBuilders.push(apiBuilder);
|
127 | continue;
|
128 | }
|
129 | if (!hasUsedOutputDirectory &&
|
130 | fileName.startsWith(`${usedOutputDirectory}/`)) {
|
131 | hasUsedOutputDirectory = true;
|
132 | }
|
133 | if (!hasNoneApiFiles &&
|
134 | !fileName.startsWith('api/') &&
|
135 | fileName !== 'package.json') {
|
136 | hasNoneApiFiles = true;
|
137 | }
|
138 | if (!hasNextApiFiles &&
|
139 | (fileName.startsWith('pages/api') || fileName.startsWith('src/pages/api'))) {
|
140 | hasNextApiFiles = true;
|
141 | }
|
142 | if (!fallbackEntrypoint &&
|
143 | buildCommand &&
|
144 | !fileName.includes('/') &&
|
145 | fileName !== 'now.json' &&
|
146 | fileName !== 'vercel.json') {
|
147 | fallbackEntrypoint = fileName;
|
148 | }
|
149 | }
|
150 | if (!makeFrontendStatic &&
|
151 | (hasBuildScript(pkg) || buildCommand || framework)) {
|
152 |
|
153 | frontendBuilder = detectFrontBuilder(pkg, files, usedFunctions, fallbackEntrypoint, options);
|
154 | }
|
155 | else {
|
156 | if (pkg &&
|
157 | !makeFrontendStatic &&
|
158 | !apiBuilders.length &&
|
159 | !options.ignoreBuildScript) {
|
160 |
|
161 |
|
162 | errors.push(getMissingBuildScriptError());
|
163 | return {
|
164 | errors,
|
165 | warnings,
|
166 | builders: null,
|
167 | redirectRoutes: null,
|
168 | defaultRoutes: null,
|
169 | rewriteRoutes: null,
|
170 | errorRoutes: null,
|
171 | };
|
172 | }
|
173 |
|
174 |
|
175 | if (hasUsedOutputDirectory && outputDirectory !== '') {
|
176 | frontendBuilder = {
|
177 | use: '@vercel/static',
|
178 | src: `${usedOutputDirectory}/**/*`,
|
179 | config: {
|
180 | zeroConfig: true,
|
181 | outputDirectory: usedOutputDirectory,
|
182 | },
|
183 | };
|
184 | }
|
185 | else if (apiBuilders.length && hasNoneApiFiles) {
|
186 |
|
187 |
|
188 | frontendBuilder = {
|
189 | use: '@vercel/static',
|
190 | src: '!{api/**,package.json}',
|
191 | config: {
|
192 | zeroConfig: true,
|
193 | },
|
194 | };
|
195 | }
|
196 | }
|
197 | const unusedFunctionError = checkUnusedFunctions(frontendBuilder, usedFunctions, options);
|
198 | if (unusedFunctionError) {
|
199 | return {
|
200 | builders: null,
|
201 | errors: [unusedFunctionError],
|
202 | warnings,
|
203 | redirectRoutes: null,
|
204 | defaultRoutes: null,
|
205 | rewriteRoutes: null,
|
206 | errorRoutes: null,
|
207 | };
|
208 | }
|
209 | const builders = [];
|
210 | if (apiBuilders.length) {
|
211 | builders.push(...apiBuilders);
|
212 | }
|
213 | if (frontendBuilder) {
|
214 | builders.push(frontendBuilder);
|
215 | if (hasNextApiFiles && apiBuilders.length) {
|
216 | warnings.push({
|
217 | code: 'conflicting_files',
|
218 | message: 'It is not possible to use `api` and `pages/api` at the same time, please only use one option',
|
219 | });
|
220 | }
|
221 | }
|
222 | const routesResult = getRouteResult(apiRoutes, dynamicRoutes, usedOutputDirectory, apiBuilders, frontendBuilder, options);
|
223 | return {
|
224 | warnings,
|
225 | builders: builders.length ? builders : null,
|
226 | errors: errors.length ? errors : null,
|
227 | redirectRoutes: routesResult.redirectRoutes,
|
228 | defaultRoutes: routesResult.defaultRoutes,
|
229 | rewriteRoutes: routesResult.rewriteRoutes,
|
230 | errorRoutes: routesResult.errorRoutes,
|
231 | };
|
232 | }
|
233 | exports.detectBuilders = detectBuilders;
|
234 | function maybeGetApiBuilder(fileName, apiMatches, options) {
|
235 | if (!fileName.startsWith('api/')) {
|
236 | return null;
|
237 | }
|
238 | if (fileName.includes('/.')) {
|
239 | return null;
|
240 | }
|
241 | if (fileName.includes('/_')) {
|
242 | return null;
|
243 | }
|
244 | if (fileName.includes('/node_modules/')) {
|
245 | return null;
|
246 | }
|
247 | if (fileName.endsWith('.d.ts')) {
|
248 | return null;
|
249 | }
|
250 | const match = apiMatches.find(({ src = '**' }) => {
|
251 | return src === fileName || minimatch_1.default(fileName, src);
|
252 | });
|
253 | const { fnPattern, func } = getFunction(fileName, options);
|
254 | const use = (func && func.runtime) || (match && match.use);
|
255 | if (!use) {
|
256 | return null;
|
257 | }
|
258 | const config = { zeroConfig: true };
|
259 | if (fnPattern && func) {
|
260 | config.functions = { [fnPattern]: func };
|
261 | if (func.includeFiles) {
|
262 | config.includeFiles = func.includeFiles;
|
263 | }
|
264 | if (func.excludeFiles) {
|
265 | config.excludeFiles = func.excludeFiles;
|
266 | }
|
267 | }
|
268 | const builder = {
|
269 | use,
|
270 | src: fileName,
|
271 | config,
|
272 | };
|
273 | return builder;
|
274 | }
|
275 | function getFunction(fileName, { functions = {} }) {
|
276 | const keys = Object.keys(functions);
|
277 | if (!keys.length) {
|
278 | return { fnPattern: null, func: null };
|
279 | }
|
280 | const func = keys.find(key => key === fileName || minimatch_1.default(fileName, key));
|
281 | return func
|
282 | ? { fnPattern: func, func: functions[func] }
|
283 | : { fnPattern: null, func: null };
|
284 | }
|
285 | function getApiMatches() {
|
286 | const config = { zeroConfig: true };
|
287 | return [
|
288 | { src: 'api/**/*.js', use: `@vercel/node`, config },
|
289 | { src: 'api/**/*.ts', use: `@vercel/node`, config },
|
290 | { src: 'api/**/!(*_test).go', use: `@vercel/go`, config },
|
291 | { src: 'api/**/*.py', use: `@vercel/python`, config },
|
292 | { src: 'api/**/*.rb', use: `@vercel/ruby`, config },
|
293 | ];
|
294 | }
|
295 | function hasBuildScript(pkg) {
|
296 | const { scripts = {} } = pkg || {};
|
297 | return Boolean(scripts && scripts['build']);
|
298 | }
|
299 | function detectFrontBuilder(pkg, files, usedFunctions, fallbackEntrypoint, options) {
|
300 | const { tag, projectSettings = {} } = options;
|
301 | const withTag = tag ? `@${tag}` : '';
|
302 | const { createdAt = 0 } = projectSettings;
|
303 | let { framework } = projectSettings;
|
304 | const config = {
|
305 | zeroConfig: true,
|
306 | };
|
307 | if (framework) {
|
308 | config.framework = framework;
|
309 | }
|
310 | if (projectSettings.devCommand) {
|
311 | config.devCommand = projectSettings.devCommand;
|
312 | }
|
313 | if (typeof projectSettings.installCommand === 'string') {
|
314 | config.installCommand = projectSettings.installCommand;
|
315 | }
|
316 | if (projectSettings.buildCommand) {
|
317 | config.buildCommand = projectSettings.buildCommand;
|
318 | }
|
319 | if (projectSettings.outputDirectory) {
|
320 | config.outputDirectory = projectSettings.outputDirectory;
|
321 | }
|
322 | if (pkg &&
|
323 | (framework === undefined || createdAt < Date.parse('2020-03-01'))) {
|
324 | const deps = {
|
325 | ...pkg.dependencies,
|
326 | ...pkg.devDependencies,
|
327 | };
|
328 | if (deps['next']) {
|
329 | framework = 'nextjs';
|
330 | }
|
331 | }
|
332 | if (options.functions) {
|
333 |
|
334 | Object.entries(options.functions).forEach(([key, func]) => {
|
335 | if (!usedFunctions.has(key)) {
|
336 | if (!config.functions)
|
337 | config.functions = {};
|
338 | config.functions[key] = { ...func };
|
339 | }
|
340 | });
|
341 | }
|
342 | const f = slugToFramework.get(framework || '');
|
343 | if (f && f.useRuntime) {
|
344 | const { src, use } = f.useRuntime;
|
345 | return { src, use: `${use}${withTag}`, config };
|
346 | }
|
347 |
|
348 |
|
349 | const entrypoints = new Set([
|
350 | 'package.json',
|
351 | 'config.yaml',
|
352 | 'config.toml',
|
353 | 'config.json',
|
354 | '_config.yml',
|
355 | 'config.yml',
|
356 | 'config.rb',
|
357 | ]);
|
358 | const source = pkg
|
359 | ? 'package.json'
|
360 | : files.find(file => entrypoints.has(file)) ||
|
361 | fallbackEntrypoint ||
|
362 | 'package.json';
|
363 | return {
|
364 | src: source || 'package.json',
|
365 | use: `@vercel/static-build${withTag}`,
|
366 | config,
|
367 | };
|
368 | }
|
369 | function getMissingBuildScriptError() {
|
370 | return {
|
371 | code: 'missing_build_script',
|
372 | message: 'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
|
373 | '\nLearn More: https://vercel.com/docs/v2/platform/frequently-asked-questions#missing-build-script',
|
374 | };
|
375 | }
|
376 | function validateFunctions({ functions = {} }) {
|
377 | for (const [path, func] of Object.entries(functions)) {
|
378 | if (path.length > 256) {
|
379 | return {
|
380 | code: 'invalid_function_glob',
|
381 | message: 'Function globs must be less than 256 characters long.',
|
382 | };
|
383 | }
|
384 | if (!func || typeof func !== 'object') {
|
385 | return {
|
386 | code: 'invalid_function',
|
387 | message: 'Function must be an object.',
|
388 | };
|
389 | }
|
390 | if (Object.keys(func).length === 0) {
|
391 | return {
|
392 | code: 'invalid_function',
|
393 | message: 'Function must contain at least one property.',
|
394 | };
|
395 | }
|
396 | if (func.maxDuration !== undefined &&
|
397 | (func.maxDuration < 1 ||
|
398 | func.maxDuration > 900 ||
|
399 | !Number.isInteger(func.maxDuration))) {
|
400 | return {
|
401 | code: 'invalid_function_duration',
|
402 | message: 'Functions must have a duration between 1 and 900.',
|
403 | };
|
404 | }
|
405 | if (func.memory !== undefined &&
|
406 | (func.memory < 128 || func.memory > 3008 || func.memory % 64 !== 0)) {
|
407 | return {
|
408 | code: 'invalid_function_memory',
|
409 | message: 'Functions must have a memory value between 128 and 3008 in steps of 64.',
|
410 | };
|
411 | }
|
412 | if (path.startsWith('/')) {
|
413 | return {
|
414 | code: 'invalid_function_source',
|
415 | message: `The function path "${path}" is invalid. The path must be relative to your project root and therefore cannot start with a slash.`,
|
416 | };
|
417 | }
|
418 | if (func.runtime !== undefined) {
|
419 | const tag = `${func.runtime}`.split('@').pop();
|
420 | if (!tag || !semver_1.valid(tag)) {
|
421 | return {
|
422 | code: 'invalid_function_runtime',
|
423 | message: 'Function Runtimes must have a valid version, for example `now-php@1.0.0`.',
|
424 | };
|
425 | }
|
426 | }
|
427 | if (func.includeFiles !== undefined) {
|
428 | if (typeof func.includeFiles !== 'string') {
|
429 | return {
|
430 | code: 'invalid_function_property',
|
431 | message: `The property \`includeFiles\` must be a string.`,
|
432 | };
|
433 | }
|
434 | }
|
435 | if (func.excludeFiles !== undefined) {
|
436 | if (typeof func.excludeFiles !== 'string') {
|
437 | return {
|
438 | code: 'invalid_function_property',
|
439 | message: `The property \`excludeFiles\` must be a string.`,
|
440 | };
|
441 | }
|
442 | }
|
443 | }
|
444 | return null;
|
445 | }
|
446 | function checkUnusedFunctions(frontendBuilder, usedFunctions, options) {
|
447 | const unusedFunctions = new Set(Object.keys(options.functions || {}).filter(key => !usedFunctions.has(key)));
|
448 | if (!unusedFunctions.size) {
|
449 | return null;
|
450 | }
|
451 |
|
452 | if (frontendBuilder && _1.isOfficialRuntime('next', frontendBuilder.use)) {
|
453 | for (const fnKey of unusedFunctions.values()) {
|
454 | if (fnKey.startsWith('pages/') || fnKey.startsWith('src/pages')) {
|
455 | unusedFunctions.delete(fnKey);
|
456 | }
|
457 | else {
|
458 | return {
|
459 | code: 'unused_function',
|
460 | message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions.`,
|
461 | action: 'Learn More',
|
462 | link: 'https://vercel.link/unmatched-function-pattern',
|
463 | };
|
464 | }
|
465 | }
|
466 | }
|
467 | if (unusedFunctions.size) {
|
468 | const [fnKey] = Array.from(unusedFunctions);
|
469 | return {
|
470 | code: 'unused_function',
|
471 | message: `The pattern "${fnKey}" defined in \`functions\` doesn't match any Serverless Functions inside the \`api\` directory.`,
|
472 | action: 'Learn More',
|
473 | link: 'https://vercel.link/unmatched-function-pattern',
|
474 | };
|
475 | }
|
476 | return null;
|
477 | }
|
478 | function getApiRoute(fileName, sortedFiles, options, absolutePathCache) {
|
479 | const conflictingSegment = getConflictingSegment(fileName);
|
480 | if (conflictingSegment) {
|
481 | return {
|
482 | apiRoute: null,
|
483 | isDynamic: false,
|
484 | routeError: {
|
485 | code: 'conflicting_path_segment',
|
486 | message: `The segment "${conflictingSegment}" occurs more than ` +
|
487 | `one time in your path "${fileName}". Please make sure that ` +
|
488 | `every segment in a path is unique.`,
|
489 | },
|
490 | };
|
491 | }
|
492 | const occurrences = pathOccurrences(fileName, sortedFiles, absolutePathCache);
|
493 | if (occurrences.length > 0) {
|
494 | const messagePaths = concatArrayOfText(occurrences.map(name => `"${name}"`));
|
495 | return {
|
496 | apiRoute: null,
|
497 | isDynamic: false,
|
498 | routeError: {
|
499 | code: 'conflicting_file_path',
|
500 | message: `Two or more files have conflicting paths or names. ` +
|
501 | `Please make sure path segments and filenames, without their extension, are unique. ` +
|
502 | `The path "${fileName}" has conflicts with ${messagePaths}.`,
|
503 | },
|
504 | };
|
505 | }
|
506 | const out = createRouteFromPath(fileName, Boolean(options.featHandleMiss), Boolean(options.cleanUrls));
|
507 | return {
|
508 | apiRoute: out.route,
|
509 | isDynamic: out.isDynamic,
|
510 | routeError: null,
|
511 | };
|
512 | }
|
513 |
|
514 |
|
515 | function getConflictingSegment(filePath) {
|
516 | const segments = new Set();
|
517 | for (const segment of filePath.split('/')) {
|
518 | const name = getSegmentName(segment);
|
519 | if (name !== null && segments.has(name)) {
|
520 | return name;
|
521 | }
|
522 | if (name) {
|
523 | segments.add(name);
|
524 | }
|
525 | }
|
526 | return null;
|
527 | }
|
528 |
|
529 |
|
530 |
|
531 |
|
532 | function getSegmentName(segment) {
|
533 | const { name } = path_1.parse(segment);
|
534 | if (name.startsWith('[') && name.endsWith(']')) {
|
535 | return name.slice(1, -1);
|
536 | }
|
537 | return null;
|
538 | }
|
539 | function getAbsolutePath(unresolvedPath) {
|
540 | const { dir, name } = path_1.parse(unresolvedPath);
|
541 | const parts = joinPath(dir, name).split('/');
|
542 | return parts.map(part => part.replace(/\[.*\]/, '1')).join('/');
|
543 | }
|
544 |
|
545 |
|
546 | function pathOccurrences(fileName, files, absolutePathCache) {
|
547 | let currentAbsolutePath = absolutePathCache.get(fileName);
|
548 | if (!currentAbsolutePath) {
|
549 | currentAbsolutePath = getAbsolutePath(fileName);
|
550 | absolutePathCache.set(fileName, currentAbsolutePath);
|
551 | }
|
552 | const prev = [];
|
553 |
|
554 |
|
555 | for (const file of files) {
|
556 | if (file === fileName) {
|
557 | continue;
|
558 | }
|
559 | let absolutePath = absolutePathCache.get(file);
|
560 | if (!absolutePath) {
|
561 | absolutePath = getAbsolutePath(file);
|
562 | absolutePathCache.set(file, absolutePath);
|
563 | }
|
564 | if (absolutePath === currentAbsolutePath) {
|
565 | prev.push(file);
|
566 | }
|
567 | else if (partiallyMatches(fileName, file)) {
|
568 | prev.push(file);
|
569 | }
|
570 | }
|
571 | return prev;
|
572 | }
|
573 | function joinPath(...segments) {
|
574 | const joinedPath = segments.join('/');
|
575 | return joinedPath.replace(/\/{2,}/g, '/');
|
576 | }
|
577 | function escapeName(name) {
|
578 | const special = '[]^$.|?*+()'.split('');
|
579 | for (const char of special) {
|
580 | name = name.replace(new RegExp(`\\${char}`, 'g'), `\\${char}`);
|
581 | }
|
582 | return name;
|
583 | }
|
584 | function concatArrayOfText(texts) {
|
585 | if (texts.length <= 2) {
|
586 | return texts.join(' and ');
|
587 | }
|
588 | const last = texts.pop();
|
589 | return `${texts.join(', ')}, and ${last}`;
|
590 | }
|
591 |
|
592 |
|
593 | function partiallyMatches(pathA, pathB) {
|
594 | const partsA = pathA.split('/');
|
595 | const partsB = pathB.split('/');
|
596 | const long = partsA.length > partsB.length ? partsA : partsB;
|
597 | const short = long === partsA ? partsB : partsA;
|
598 | let index = 0;
|
599 | for (const segmentShort of short) {
|
600 | const segmentLong = long[index];
|
601 | const nameLong = getSegmentName(segmentLong);
|
602 | const nameShort = getSegmentName(segmentShort);
|
603 |
|
604 |
|
605 | if (segmentShort !== segmentLong && (!nameLong || !nameShort)) {
|
606 | return false;
|
607 | }
|
608 | if (nameLong !== nameShort) {
|
609 | return true;
|
610 | }
|
611 | index += 1;
|
612 | }
|
613 | return false;
|
614 | }
|
615 | function createRouteFromPath(filePath, featHandleMiss, cleanUrls) {
|
616 | const parts = filePath.split('/');
|
617 | let counter = 1;
|
618 | const query = [];
|
619 | let isDynamic = false;
|
620 | const srcParts = parts.map((segment, i) => {
|
621 | const name = getSegmentName(segment);
|
622 | const isLast = i === parts.length - 1;
|
623 | if (name !== null) {
|
624 |
|
625 | query.push(`${name}=$${counter++}`);
|
626 | isDynamic = true;
|
627 | return `([^/]+)`;
|
628 | }
|
629 | else if (isLast) {
|
630 | const { name: fileName, ext } = path_1.parse(segment);
|
631 | const isIndex = fileName === 'index';
|
632 | const prefix = isIndex ? '/' : '';
|
633 | const names = [
|
634 | isIndex ? prefix : `${fileName}/`,
|
635 | prefix + escapeName(fileName),
|
636 | featHandleMiss && cleanUrls
|
637 | ? ''
|
638 | : prefix + escapeName(fileName) + escapeName(ext),
|
639 | ].filter(Boolean);
|
640 |
|
641 |
|
642 |
|
643 | return `(${names.join('|')})${isIndex ? '?' : ''}`;
|
644 | }
|
645 | return segment;
|
646 | });
|
647 | const { name: fileName, ext } = path_1.parse(filePath);
|
648 | const isIndex = fileName === 'index';
|
649 | const queryString = `${query.length ? '?' : ''}${query.join('&')}`;
|
650 | const src = isIndex
|
651 | ? `^/${srcParts.slice(0, -1).join('/')}${srcParts.slice(-1)[0]}$`
|
652 | : `^/${srcParts.join('/')}$`;
|
653 | let route;
|
654 | if (featHandleMiss) {
|
655 | const extensionless = ext ? filePath.slice(0, -ext.length) : filePath;
|
656 | route = {
|
657 | src,
|
658 | dest: `/${extensionless}${queryString}`,
|
659 | check: true,
|
660 | };
|
661 | }
|
662 | else {
|
663 | route = {
|
664 | src,
|
665 | dest: `/${filePath}${queryString}`,
|
666 | };
|
667 | }
|
668 | return { route, isDynamic };
|
669 | }
|
670 | function getRouteResult(apiRoutes, dynamicRoutes, outputDirectory, apiBuilders, frontendBuilder, options) {
|
671 | var _a, _b;
|
672 | const defaultRoutes = [];
|
673 | const redirectRoutes = [];
|
674 | const rewriteRoutes = [];
|
675 | const errorRoutes = [];
|
676 | const framework = ((_a = frontendBuilder === null || frontendBuilder === void 0 ? void 0 : frontendBuilder.config) === null || _a === void 0 ? void 0 : _a.framework) || '';
|
677 | const use = (frontendBuilder === null || frontendBuilder === void 0 ? void 0 : frontendBuilder.use) || '';
|
678 | const isNextjs = framework === 'nextjs' || use.startsWith('@vercel/next');
|
679 | const ignoreRuntimes = (_b = slugToFramework.get(framework)) === null || _b === void 0 ? void 0 : _b.ignoreRuntimes;
|
680 | if (apiRoutes && apiRoutes.length > 0) {
|
681 | if (options.featHandleMiss) {
|
682 | const extSet = detectApiExtensions(apiBuilders);
|
683 | if (extSet.size > 0) {
|
684 | const exts = Array.from(extSet)
|
685 | .map(ext => ext.slice(1))
|
686 | .join('|');
|
687 | const extGroup = `(?:\\.(?:${exts}))`;
|
688 | if (options.cleanUrls) {
|
689 | redirectRoutes.push({
|
690 | src: `^/(api(?:.+)?)/index${extGroup}?/?$`,
|
691 | headers: { Location: options.trailingSlash ? '/$1/' : '/$1' },
|
692 | status: 308,
|
693 | });
|
694 | redirectRoutes.push({
|
695 | src: `^/api/(.+)${extGroup}/?$`,
|
696 | headers: {
|
697 | Location: options.trailingSlash ? '/api/$1/' : '/api/$1',
|
698 | },
|
699 | status: 308,
|
700 | });
|
701 | }
|
702 | else {
|
703 | defaultRoutes.push({ handle: 'miss' });
|
704 | defaultRoutes.push({
|
705 | src: `^/api/(.+)${extGroup}$`,
|
706 | dest: '/api/$1',
|
707 | check: true,
|
708 | });
|
709 | }
|
710 | }
|
711 | rewriteRoutes.push(...dynamicRoutes);
|
712 | if (typeof ignoreRuntimes === 'undefined') {
|
713 |
|
714 |
|
715 |
|
716 |
|
717 | rewriteRoutes.push({
|
718 | src: '^/api(/.*)?$',
|
719 | status: 404,
|
720 | });
|
721 | }
|
722 | }
|
723 | else {
|
724 | defaultRoutes.push(...apiRoutes);
|
725 | if (apiRoutes.length) {
|
726 | defaultRoutes.push({
|
727 | status: 404,
|
728 | src: '^/api(/.*)?$',
|
729 | });
|
730 | }
|
731 | }
|
732 | }
|
733 | if (outputDirectory &&
|
734 | frontendBuilder &&
|
735 | !options.featHandleMiss &&
|
736 | _1.isOfficialRuntime('static', frontendBuilder.use)) {
|
737 | defaultRoutes.push({
|
738 | src: '/(.*)',
|
739 | dest: `/${outputDirectory}/$1`,
|
740 | });
|
741 | }
|
742 | if (options.featHandleMiss && !isNextjs) {
|
743 |
|
744 |
|
745 | errorRoutes.push({
|
746 | status: 404,
|
747 | src: '^/(?!.*api).*$',
|
748 | dest: options.cleanUrls ? '/404' : '/404.html',
|
749 | });
|
750 | }
|
751 | return {
|
752 | defaultRoutes,
|
753 | redirectRoutes,
|
754 | rewriteRoutes,
|
755 | errorRoutes,
|
756 | };
|
757 | }
|
758 | function sortFilesBySegmentCount(fileA, fileB) {
|
759 | const lengthA = fileA.split('/').length;
|
760 | const lengthB = fileB.split('/').length;
|
761 | if (lengthA > lengthB) {
|
762 | return -1;
|
763 | }
|
764 | if (lengthA < lengthB) {
|
765 | return 1;
|
766 | }
|
767 |
|
768 |
|
769 | const countSegments = (prev, segment) => getSegmentName(segment) ? prev + 1 : 0;
|
770 | const segmentLengthA = fileA.split('/').reduce(countSegments, 0);
|
771 | const segmentLengthB = fileB.split('/').reduce(countSegments, 0);
|
772 | if (segmentLengthA > segmentLengthB) {
|
773 | return 1;
|
774 | }
|
775 | if (segmentLengthA < segmentLengthB) {
|
776 | return -1;
|
777 | }
|
778 | return fileA.localeCompare(fileB);
|
779 | }
|