Compare commits

..

2 Commits

Author SHA1 Message Date
Steven
7bd338618c Publish Canary
- @vercel/build-utils@2.12.3-canary.23
 - vercel@23.1.3-canary.41
 - @vercel/client@10.2.3-canary.24
 - vercel-plugin-go@1.0.0-canary.8
 - vercel-plugin-node@1.12.2-canary.14
 - vercel-plugin-python@1.0.0-canary.9
 - vercel-plugin-ruby@1.0.0-canary.7
2021-11-24 22:03:54 -05:00
Steven
9048a6f584 [build-utils] Fix zero config routes for vercel build (#7063) 2021-11-24 22:00:49 -05:00
11 changed files with 167 additions and 178 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "2.12.3-canary.22",
"version": "2.12.3-canary.23",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",

View File

@@ -2,7 +2,6 @@ import fs from 'fs-extra';
import { join, dirname, relative } from 'path';
import glob from './fs/glob';
import { normalizePath } from './fs/normalize-path';
import { detectBuilders } from './detect-builders';
import { FILES_SYMBOL, getLambdaOptionsFromFunction, Lambda } from './lambda';
import type FileBlob from './file-blob';
import type { BuilderFunctions, BuildOptions, Files } from './types';
@@ -25,8 +24,6 @@ export function convertRuntimeToPlugin(
vercelConfig: {
functions?: BuilderFunctions;
regions?: string[];
trailingSlash?: boolean;
cleanUrls?: boolean;
};
workPath: string;
}) {
@@ -35,7 +32,7 @@ export function convertRuntimeToPlugin(
delete files['vercel.json']; // Builders/Runtimes didn't have vercel.json
const entrypoints = await glob(`api/**/*${ext}`, opts);
const pages: { [key: string]: any } = {};
const { functions = {}, cleanUrls, trailingSlash } = vercelConfig;
const { functions = {} } = vercelConfig;
const traceDir = join(workPath, '.output', 'runtime-traced-files');
await fs.ensureDir(traceDir);
@@ -64,7 +61,7 @@ export function convertRuntimeToPlugin(
maxDuration: output.maxDuration,
environment: output.environment,
allowQuery: output.allowQuery,
regions: output.regions,
//regions: output.regions,
};
// @ts-ignore This symbol is a private API
@@ -112,61 +109,6 @@ export function convertRuntimeToPlugin(
}
await updateFunctionsManifest({ vercelConfig, workPath, pages });
const {
warnings,
errors,
//defaultRoutes,
redirectRoutes,
//rewriteRoutes,
dynamicRoutesWithKeys,
// errorRoutes, already handled by pages404
} = await detectBuilders(Object.keys(files), null, {
tag: 'latest',
functions: functions,
projectSettings: undefined,
featHandleMiss: true,
cleanUrls,
trailingSlash,
});
if (errors) {
throw new Error(errors[0].message);
}
if (warnings) {
warnings.forEach(warning => console.warn(warning.message, warning.link));
}
const redirects = redirectRoutes
?.filter(r => r.src && 'headers' in r)
?.map(r => ({
source: r.src || '',
destination:
'headers' in r && r.headers?.Location ? r.headers.Location : '',
statusCode: 'status' in r && r.status ? r.status : 307,
regex: r.src || '',
}));
const dynamicRoutes = dynamicRoutesWithKeys?.map(r => {
const keys = Object.keys(r.routeKeys);
return {
page: '/' + r.fileName.slice(0, -ext.length),
regex: r.regex,
routeKeys: r.routeKeys,
namedRegex: r.regex
.split('([^/]+)')
.map((str, i) => str + (keys[i] ? `(?<${keys[i]}>[^/]+)` : ''))
.join(''),
};
});
await updateRoutesManifest({
workPath,
redirects,
rewrites: [],
dynamicRoutes,
});
};
}

View File

@@ -16,12 +16,6 @@ interface ErrorResponse {
link?: string;
}
interface DynamicRoutesWithKeys {
fileName: string;
regex: string;
routeKeys: { [key: string]: string };
}
interface Options {
tag?: 'canary' | 'latest' | string;
functions?: BuilderFunctions;
@@ -102,7 +96,7 @@ export async function detectBuilders(
redirectRoutes: Route[] | null;
rewriteRoutes: Route[] | null;
errorRoutes: Route[] | null;
dynamicRoutesWithKeys: DynamicRoutesWithKeys[] | null;
limitedRoutes: LimitedRoutes | null;
}> {
const errors: ErrorResponse[] = [];
const warnings: ErrorResponse[] = [];
@@ -121,7 +115,7 @@ export async function detectBuilders(
redirectRoutes: null,
rewriteRoutes: null,
errorRoutes: null,
dynamicRoutesWithKeys: null,
limitedRoutes: null,
};
}
@@ -165,14 +159,13 @@ export async function detectBuilders(
const apiRoutes: Source[] = [];
const dynamicRoutes: Source[] = [];
const dynamicRoutesWithKeys: DynamicRoutesWithKeys[] = [];
// API
for (const fileName of sortedFiles) {
const apiBuilder = maybeGetApiBuilder(fileName, apiMatches, options);
if (apiBuilder) {
const { routeError, apiRoute, isDynamic, routeKeys } = getApiRoute(
const { routeError, apiRoute, isDynamic } = getApiRoute(
fileName,
apiSortedFiles,
options,
@@ -188,7 +181,7 @@ export async function detectBuilders(
redirectRoutes: null,
rewriteRoutes: null,
errorRoutes: null,
dynamicRoutesWithKeys: null,
limitedRoutes: null,
};
}
@@ -196,11 +189,6 @@ export async function detectBuilders(
apiRoutes.push(apiRoute);
if (isDynamic) {
dynamicRoutes.push(apiRoute);
dynamicRoutesWithKeys.push({
fileName,
regex: apiRoute.src,
routeKeys,
});
}
}
@@ -272,7 +260,7 @@ export async function detectBuilders(
defaultRoutes: null,
rewriteRoutes: null,
errorRoutes: null,
dynamicRoutesWithKeys: null,
limitedRoutes: null,
};
}
@@ -315,7 +303,7 @@ export async function detectBuilders(
defaultRoutes: null,
rewriteRoutes: null,
errorRoutes: null,
dynamicRoutesWithKeys: null,
limitedRoutes: null,
};
}
@@ -343,6 +331,7 @@ export async function detectBuilders(
}
const routesResult = getRouteResult(
pkg,
apiRoutes,
dynamicRoutes,
usedOutputDirectory,
@@ -359,7 +348,7 @@ export async function detectBuilders(
defaultRoutes: routesResult.defaultRoutes,
rewriteRoutes: routesResult.rewriteRoutes,
errorRoutes: routesResult.errorRoutes,
dynamicRoutesWithKeys,
limitedRoutes: routesResult.limitedRoutes,
};
}
@@ -693,7 +682,6 @@ function getApiRoute(
): {
apiRoute: Source | null;
isDynamic: boolean;
routeKeys: { [key: string]: string };
routeError: ErrorResponse | null;
} {
const conflictingSegment = getConflictingSegment(fileName);
@@ -702,7 +690,6 @@ function getApiRoute(
return {
apiRoute: null,
isDynamic: false,
routeKeys: {},
routeError: {
code: 'conflicting_path_segment',
message:
@@ -723,7 +710,6 @@ function getApiRoute(
return {
apiRoute: null,
isDynamic: false,
routeKeys: {},
routeError: {
code: 'conflicting_file_path',
message:
@@ -743,7 +729,6 @@ function getApiRoute(
return {
apiRoute: out.route,
isDynamic: out.isDynamic,
routeKeys: out.routeKeys,
routeError: null,
};
}
@@ -889,12 +874,11 @@ function createRouteFromPath(
filePath: string,
featHandleMiss: boolean,
cleanUrls: boolean
): { route: Source; isDynamic: boolean; routeKeys: { [key: string]: string } } {
): { route: Source; isDynamic: boolean } {
const parts = filePath.split('/');
let counter = 1;
const query: string[] = [];
const routeKeys: { [key: string]: string } = {};
let isDynamic = false;
const srcParts = parts.map((segment, i): string => {
@@ -904,7 +888,6 @@ function createRouteFromPath(
if (name !== null) {
// We can't use `URLSearchParams` because `$` would get escaped
query.push(`${name}=$${counter++}`);
routeKeys[name] = name;
isDynamic = true;
return `([^/]+)`;
} else if (isLast) {
@@ -953,10 +936,17 @@ function createRouteFromPath(
};
}
return { route, isDynamic, routeKeys };
return { route, isDynamic };
}
interface LimitedRoutes {
defaultRoutes: Route[];
redirectRoutes: Route[];
rewriteRoutes: Route[];
}
function getRouteResult(
pkg: PackageJson | undefined | null,
apiRoutes: Source[],
dynamicRoutes: Source[],
outputDirectory: string,
@@ -968,11 +958,18 @@ function getRouteResult(
redirectRoutes: Route[];
rewriteRoutes: Route[];
errorRoutes: Route[];
limitedRoutes: LimitedRoutes;
} {
const deps = Object.assign({}, pkg?.dependencies, pkg?.devDependencies);
const defaultRoutes: Route[] = [];
const redirectRoutes: Route[] = [];
const rewriteRoutes: Route[] = [];
const errorRoutes: Route[] = [];
const limitedRoutes: LimitedRoutes = {
defaultRoutes: [],
redirectRoutes: [],
rewriteRoutes: [],
};
const framework = frontendBuilder?.config?.framework || '';
const isNextjs =
framework === 'nextjs' || isOfficialRuntime('next', frontendBuilder?.use);
@@ -980,14 +977,43 @@ function getRouteResult(
if (apiRoutes && apiRoutes.length > 0) {
if (options.featHandleMiss) {
// Exclude extension names if the corresponding plugin is not found in package.json
// detectBuilders({ignoreRoutesForBuilders: ['@vercel/python']})
// return a copy of routes.
// We should exclud errorRoutes and
const extSet = detectApiExtensions(apiBuilders);
const withTag = options.tag ? `@${options.tag}` : '';
const extSetLimited = detectApiExtensions(
apiBuilders.filter(b => {
if (
b.use === `@vercel/python${withTag}` &&
!('vercel-plugin-python' in deps)
) {
return false;
}
if (
b.use === `@vercel/go${withTag}` &&
!('vercel-plugin-go' in deps)
) {
return false;
}
if (
b.use === `@vercel/ruby${withTag}` &&
!('vercel-plugin-ruby' in deps)
) {
return false;
}
return true;
})
);
if (extSet.size > 0) {
const exts = Array.from(extSet)
const extGroup = `(?:\\.(?:${Array.from(extSet)
.map(ext => ext.slice(1))
.join('|');
const extGroup = `(?:\\.(?:${exts}))`;
.join('|')}))`;
const extGroupLimited = `(?:\\.(?:${Array.from(extSetLimited)
.map(ext => ext.slice(1))
.join('|')}))`;
if (options.cleanUrls) {
redirectRoutes.push({
@@ -1003,6 +1029,20 @@ function getRouteResult(
},
status: 308,
});
limitedRoutes.redirectRoutes.push({
src: `^/(api(?:.+)?)/index${extGroupLimited}?/?$`,
headers: { Location: options.trailingSlash ? '/$1/' : '/$1' },
status: 308,
});
limitedRoutes.redirectRoutes.push({
src: `^/api/(.+)${extGroupLimited}/?$`,
headers: {
Location: options.trailingSlash ? '/api/$1/' : '/api/$1',
},
status: 308,
});
} else {
defaultRoutes.push({ handle: 'miss' });
defaultRoutes.push({
@@ -1010,10 +1050,18 @@ function getRouteResult(
dest: '/api/$1',
check: true,
});
limitedRoutes.defaultRoutes.push({ handle: 'miss' });
limitedRoutes.defaultRoutes.push({
src: `^/api/(.+)${extGroupLimited}$`,
dest: '/api/$1',
check: true,
});
}
}
rewriteRoutes.push(...dynamicRoutes);
limitedRoutes.rewriteRoutes.push(...dynamicRoutes);
if (typeof ignoreRuntimes === 'undefined') {
// This route is only necessary to hide the directory listing
@@ -1064,6 +1112,7 @@ function getRouteResult(
redirectRoutes,
rewriteRoutes,
errorRoutes,
limitedRoutes,
};
}

View File

@@ -2385,13 +2385,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { defaultRoutes, rewriteRoutes, errorRoutes } = await detectBuilders(
files,
null,
{
const { defaultRoutes, rewriteRoutes, errorRoutes, limitedRoutes } =
await detectBuilders(files, null, {
featHandleMiss,
}
);
});
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
@@ -2414,6 +2411,22 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
},
]);
// Limited routes should have js but not go since the go plugin is not installed
expect(limitedRoutes).toStrictEqual({
redirectRoutes: [],
rewriteRoutes: [],
defaultRoutes: [
{
handle: 'miss',
},
{
src: '^/api/(.+)(?:\\.(?:js))$',
dest: '/api/$1',
check: true,
},
],
});
const pattern = new RegExp(errorRoutes![0].src!);
[
@@ -2816,8 +2829,13 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { defaultRoutes, redirectRoutes, rewriteRoutes, errorRoutes } =
await detectBuilders(files, null, options);
const {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
limitedRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([
@@ -2834,6 +2852,28 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
},
]);
// Limited routes should have js but not go since the go plugin is not installed
expect(limitedRoutes).toStrictEqual({
redirectRoutes: [
{
src: '^/(api(?:.+)?)/index(?:\\.(?:js))?/?$',
headers: {
Location: '/$1',
},
status: 308,
},
{
src: '^/api/(.+)(?:\\.(?:js))/?$',
headers: {
Location: '/api/$1',
},
status: 308,
},
],
rewriteRoutes: [],
defaultRoutes: [],
});
// expected redirect should match inputs
const getLocation = createReplaceLocation(redirectRoutes);
@@ -3077,7 +3117,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
{
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
const { defaultRoutes, redirectRoutes, rewriteRoutes, limitedRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]);
@@ -3088,6 +3128,28 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
},
]);
// Limited routes should have js but not go since the go plugin is not installed
expect(limitedRoutes).toStrictEqual({
redirectRoutes: [
{
src: '^/(api(?:.+)?)/index(?:\\.(?:js))?/?$',
headers: {
Location: '/$1/',
},
status: 308,
},
{
src: '^/api/(.+)(?:\\.(?:js))/?$',
headers: {
Location: '/api/$1/',
},
status: 308,
},
],
rewriteRoutes: [],
defaultRoutes: [],
});
// expected redirect should match inputs
const getLocation = createReplaceLocation(redirectRoutes);

View File

@@ -31,37 +31,6 @@ describe('convert-runtime-to-plugin', () => {
await fs.remove(join(pythonApiWorkpath, '.output'));
});
it('should error with invalid functions prop', async () => {
const workPath = invalidFuncWorkpath;
const lambdaOptions = {
handler: 'index.handler',
runtime: 'nodejs14.x',
memory: 512,
maxDuration: 5,
environment: {},
regions: ['sfo1'],
};
const buildRuntime = async (opts: BuildOptions) => {
const lambda = await createLambda({
files: opts.files,
...lambdaOptions,
});
return { output: lambda };
};
const lambdaFiles = await fsToJson(workPath);
const vercelConfig = JSON.parse(lambdaFiles['vercel.json']);
delete lambdaFiles['vercel.json'];
const build = await convertRuntimeToPlugin(buildRuntime, '.js');
expect(build({ vercelConfig, workPath })).rejects.toThrow(
new Error(
'The pattern "api/doesnt-exist.rb" defined in `functions` doesn\'t match any Serverless Functions inside the `api` directory.'
)
);
});
it('should create correct fileystem for python', async () => {
const workPath = pythonApiWorkpath;
const lambdaOptions = {
@@ -70,7 +39,6 @@ describe('convert-runtime-to-plugin', () => {
memory: 512,
maxDuration: 5,
environment: {},
regions: ['sfo1'],
};
const buildRuntime = async (opts: BuildOptions) => {
@@ -91,7 +59,6 @@ describe('convert-runtime-to-plugin', () => {
const output = await fsToJson(join(workPath, '.output'));
expect(output).toMatchObject({
'functions-manifest.json': expect.stringContaining('{'),
'routes-manifest.json': expect.stringContaining('{'),
'runtime-traced-files': lambdaFiles,
server: {
pages: {
@@ -119,37 +86,6 @@ describe('convert-runtime-to-plugin', () => {
},
});
const routesManifest = JSON.parse(output['routes-manifest.json']);
expect(routesManifest).toMatchObject({
version: 3,
pages404: true,
redirects: [],
rewrites: [
/* TODO: `handle: miss`
{
source: "^/api/(.+)(?:\\.(?:py))$",
destination: "/api/db/[id]?id=$1",
regex: "^/api/(.+)(?:\\.(?:py))$"
}
*/
],
dynamicRoutes: [
{
page: '/api/project/[aid]/[bid]/index',
regex: '^/api/project/([^/]+)/([^/]+)(/|/index|/index\\.py)?$',
routeKeys: { aid: 'aid', bid: 'bid' },
namedRegex:
'^/api/project/(?<aid>[^/]+)/(?<bid>[^/]+)(/|/index|/index\\.py)?$',
},
{
page: '/api/db/[id]',
regex: '^/api/db/([^/]+)$',
routeKeys: { id: 'id' },
namedRegex: '^/api/db/(?<id>[^/]+)$',
},
],
});
const indexJson = JSON.parse(output.server.pages.api['index.py.nft.json']);
expect(indexJson).toMatchObject({
version: 1,

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "23.1.3-canary.40",
"version": "23.1.3-canary.41",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -43,14 +43,14 @@
"node": ">= 12"
},
"dependencies": {
"@vercel/build-utils": "2.12.3-canary.22",
"@vercel/build-utils": "2.12.3-canary.23",
"@vercel/go": "1.2.4-canary.4",
"@vercel/node": "1.12.2-canary.7",
"@vercel/python": "2.1.2-canary.0",
"@vercel/ruby": "1.2.8-canary.4",
"update-notifier": "4.1.0",
"vercel-plugin-middleware": "0.0.0-canary.7",
"vercel-plugin-node": "1.12.2-canary.13"
"vercel-plugin-node": "1.12.2-canary.14"
},
"devDependencies": {
"@next/env": "11.1.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "10.2.3-canary.23",
"version": "10.2.3-canary.24",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -40,7 +40,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.12.3-canary.22",
"@vercel/build-utils": "2.12.3-canary.23",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "vercel-plugin-go",
"version": "1.0.0-canary.7",
"version": "1.0.0-canary.8",
"main": "dist/index.js",
"license": "MIT",
"files": [
@@ -17,7 +17,7 @@
"prepublishOnly": "tsc"
},
"dependencies": {
"@vercel/build-utils": "2.12.3-canary.22",
"@vercel/build-utils": "2.12.3-canary.23",
"@vercel/go": "1.2.4-canary.4"
},
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "vercel-plugin-node",
"version": "1.12.2-canary.13",
"version": "1.12.2-canary.14",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -34,7 +34,7 @@
"@types/node-fetch": "2",
"@types/test-listen": "1.1.0",
"@types/yazl": "2.4.2",
"@vercel/build-utils": "2.12.3-canary.22",
"@vercel/build-utils": "2.12.3-canary.23",
"@vercel/fun": "1.0.3",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.14.0",

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "vercel-plugin-python",
"version": "1.0.0-canary.8",
"version": "1.0.0-canary.9",
"main": "dist/index.js",
"license": "MIT",
"files": [
@@ -17,7 +17,7 @@
"prepublishOnly": "tsc"
},
"dependencies": {
"@vercel/build-utils": "2.12.3-canary.22",
"@vercel/build-utils": "2.12.3-canary.23",
"@vercel/python": "2.1.2-canary.0"
},
"devDependencies": {

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "vercel-plugin-ruby",
"version": "1.0.0-canary.6",
"version": "1.0.0-canary.7",
"main": "dist/index.js",
"license": "MIT",
"files": [
@@ -17,7 +17,7 @@
"prepublishOnly": "tsc"
},
"dependencies": {
"@vercel/build-utils": "2.12.3-canary.22",
"@vercel/build-utils": "2.12.3-canary.23",
"@vercel/ruby": "1.2.8-canary.4"
},
"devDependencies": {