Compare commits

..

17 Commits

Author SHA1 Message Date
Steven
bef1aec766 Publish Stable
- @vercel/build-utils@5.0.3
 - vercel@27.1.5
 - @vercel/client@12.1.2
 - @vercel/frameworks@1.1.1
 - @vercel/fs-detectors@2.0.1
 - @vercel/go@2.0.7
 - @vercel/hydrogen@0.0.4
 - @vercel/next@3.1.7
 - @vercel/node@2.4.4
 - @vercel/python@3.0.7
 - @vercel/redwood@1.0.8
 - @vercel/remix@1.0.9
 - @vercel/routing-utils@2.0.0
 - @vercel/ruby@1.3.15
 - @vercel/static-build@1.0.8
2022-07-15 15:40:37 -04:00
Steven
4f4a42813f [build-utils][node][python][ruby] Update error message for EOL runtimes (#8167)
This PR updates the error message when the runtime version detected is EOL
2022-07-15 15:38:38 -04:00
Steven
181a492d91 [routing-utils] MAJOR refactor getTransformedRoutes and types (#8155)
This is a semver major change to the public API for `@vercel/routing-utils` which includes the following breaking changes.

1. `getTransformedRoutes({ nowConfig })` props changed to `getTransformedRoutes(nowConfig)`
2. `type Source` renamed `type RouteWithSrc`
3. `type Handler` renamed `type RouteWithHandle`
4. `interface VercelConfig` removed
5. `type NowConfig` removed
6. `type NowRewrite` removed
7. `type NowRedirect` removed
8. `type NowHeader` removed
9. `type NowHeaderKeyValue` removed
2022-07-15 14:05:08 -04:00
Sean Massa
1be7a80bb8 Publish Stable
- vercel@27.1.4
 - @vercel/next@3.1.6
2022-07-15 11:20:56 -05:00
Sean Massa
0428d4744e [cli] write config.json when exiting because of error in builder (#8163)
Co-authored-by: Steven <steven@ceriously.com>
2022-07-15 11:16:31 -05:00
JJ Kasper
2a929a4bb9 [next] Update allowQuery for prerendered paths (#8158)
This updates our `allowQuery` generating to ignore all query values for build-time prerender paths as these will match before dynamic routes since they are filesystem routes and the query values will not be overridden properly like they are for fallback prerender paths. This also adds testing for both prerender path types with on-demand ISR to ensure the cache is updated as expected regardless of the query.  

Deployment with patch can be seen here https://nextjs-issue-odr-simple-hrjt2dagm-ijjk-testing.vercel.app/

### Related Issues

x-ref: https://github.com/vercel/next.js/issues/38306
x-ref: https://github.com/vercel/next.js/issues/38653
2022-07-15 11:37:05 -04:00
JJ Kasper
accd308dc5 [tests] Update log for update-canary-tag script (#8156)
### Related Issues

Noticed this log was not being converted to a string so we're losing some context so this corrects in case we have a failure on this step in the future. 

Fixes: https://github.com/vercel/vercel/runs/7344626478?check_suite_focus=true#step:8:258

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-07-15 02:16:51 +00:00
Sean Massa
e2d4efab08 Publish Stable
- vercel@27.1.3
2022-07-14 12:04:05 -05:00
JJ Kasper
7e0dd6f808 [tests] Update to latest version of turbo (#8152)
### Related Issues

Updates to latest turbo which includes patches for cached files. 

x-ref: [slack thread](https://vercel.slack.com/archives/C02CDC2ALJH/p1657767763630359?thread_ts=1657757803.039099&cid=C02CDC2ALJH)

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-07-14 12:41:28 +00:00
Nathan Rajlich
8971e02e49 [cli] Normalize output file paths in vercel build (#8149)
When a Builder returns an output asset that contains irregular slashes (multiple slashes or leading/trailing slashes), then have them be removed from the file path before creating the output asset.

This fixes an edge case where `@vercel/next` could end up outputting a Serverless Function with a trailing slash (i.e. `en-US/`). Before this PR, that would be serialized to the filesystem at `en-US/.func`, but after this fix it's saved in the correct `en-US.func` directory.
2022-07-14 07:58:20 +00:00
Nathan Rajlich
10c91c8579 [cli] Store build error in "builds.json" file in vc build (#8148)
When a build fails, store the serialized Error in the "builds.json" file under the "build" object of the Builder that failed.

Example:

```json
{
  "//": "This file was generated by the `vercel build` command. It is not part of the Build Output API.",
  "target": "preview",
  "argv": [
    "/usr/local/bin/node",
    "/Users/nrajlich/Code/vercel/vercel/packages/cli/src/index.ts",
    "build",
    "--cwd",
    "/Users/nrajlich/Downloads/vc-build-next-repro/"
  ],
  "builds": [
    {
      "require": "@vercel/next",
      "requirePath": "/Users/nrajlich/Code/vercel/vercel/packages/next/dist/index",
      "apiVersion": 2,
      "src": "package.json",
      "use": "@vercel/next",
      "config": {
        "zeroConfig": true,
        "framework": "nextjs"
      },
      "error": {
        "name": "Error",
        "message": "Command \"pnpm run build\" exited with 1",
        "stack": "Error: Command \"pnpm run build\" exited with 1\n    at ChildProcess.<anonymous> (/Users/nrajlich/Code/vercel/vercel/packages/build-utils/dist/index.js:20591:20)\n    at ChildProcess.emit (node:events:527:28)\n    at ChildProcess.emit (node:domain:475:12)\n    at maybeClose (node:internal/child_process:1092:16)\n    at Process.ChildProcess._handle.onexit (node:internal/child_process:302:5)",
        "hideStackTrace": true,
        "code": "BUILD_UTILS_SPAWN_1"
      }
    }
  ]
}
```
2022-07-14 01:05:00 +00:00
Nathan Rajlich
bfdbe58675 Publish Stable
- vercel@27.1.2
 - @vercel/static-build@1.0.7
2022-07-13 13:51:02 -07:00
Matthew Stanciu
7bdaf107b7 [cli] Consume --no-clipboard (#8147)
#8085 removed the clipboard copy feature in `vc deploy`, along with the `--no-clipboard` flag. Right now, the CLI exits and returns an error when someone includes the `--no-clipboard` flag. This PR instead consumes the flag and warns the user that the flag is deprecated.

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-07-13 20:32:21 +00:00
Nathan Rajlich
8de100f0e1 [static-build] Return BOA v3 result without vercel build (#8146)
This will allow Build Output API v3 to be produced by a build script without the `ENABLE_VC_BUILD=1` env var being necessary.
2022-07-13 18:17:53 +00:00
Steven
38a6785859 Publish Stable
- vercel@27.1.1
2022-07-13 12:00:03 -04:00
Steven
c67d1a8525 [cli] Fix vercel build sort order (#8144)
- Fixes #8063
2022-07-13 11:58:56 -04:00
Matthew Stanciu
c5a7c574a2 [tests] Add missing function import in Project mock (#8143)
A function `formatProvider` wasn't imported in the `Project` mock endpoint. This wasn't caught before #8100 was merged, and dodged CI.

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-07-12 21:35:27 +00:00
61 changed files with 768 additions and 410 deletions

View File

@@ -1,4 +1,5 @@
# https://prettier.io/docs/en/ignore.html # https://prettier.io/docs/en/ignore.html
# ignore this file with an intentional syntax error # ignore these files with an intentional syntax error
packages/cli/test/dev/fixtures/edge-function-error/api/edge-error-syntax.js packages/cli/test/dev/fixtures/edge-function-error/api/edge-error-syntax.js
packages/cli/test/fixtures/unit/commands/build/node-error/api/typescript.ts

View File

@@ -31,7 +31,7 @@
"prettier": "2.6.2", "prettier": "2.6.2",
"ts-eager": "2.0.2", "ts-eager": "2.0.2",
"ts-jest": "28.0.5", "ts-jest": "28.0.5",
"turbo": "1.3.1" "turbo": "1.3.2-canary.1"
}, },
"scripts": { "scripts": {
"lerna": "lerna", "lerna": "lerna",

View File

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

View File

@@ -33,9 +33,6 @@ function getHint(isAuto = false) {
: `Please set "engines": { "node": "${range}" } in your \`package.json\` file to use Node.js ${major}.`; : `Please set "engines": { "node": "${range}" } in your \`package.json\` file to use Node.js ${major}.`;
} }
const upstreamProvider =
'This change is the result of a decision made by an upstream infrastructure provider (AWS).';
export function getLatestNodeVersion() { export function getLatestNodeVersion() {
return allOptions[0]; return allOptions[0];
} }
@@ -75,7 +72,7 @@ export async function getSupportedNodeVersion(
throw new NowBuildError({ throw new NowBuildError({
code: 'BUILD_UTILS_NODE_VERSION_DISCONTINUED', code: 'BUILD_UTILS_NODE_VERSION_DISCONTINUED',
link: 'http://vercel.link/node-version', link: 'http://vercel.link/node-version',
message: `${intro} ${getHint(isAuto)} ${upstreamProvider}`, message: `${intro} ${getHint(isAuto)}`,
}); });
} }
@@ -86,9 +83,9 @@ export async function getSupportedNodeVersion(
console.warn( console.warn(
`Error: Node.js version ${ `Error: Node.js version ${
selection.range selection.range
} is deprecated. Deployments created on or after ${d} will fail to build. ${getHint( } has reached End-of-Life. Deployments created on or after ${d} will fail to build. ${getHint(
isAuto isAuto
)} ${upstreamProvider}` )}`
); );
} }

View File

@@ -387,10 +387,10 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
12 12
); );
expect(warningMessages).toStrictEqual([ expect(warningMessages).toStrictEqual([
'Error: Node.js version 10.x is deprecated. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).', 'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 10.x is deprecated. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).', 'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
'Error: Node.js version 12.x is deprecated. Deployments created on or after 2022-08-09 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).', 'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 12.x is deprecated. Deployments created on or after 2022-08-09 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).', 'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
]); ]);
global.Date.now = realDateNow; global.Date.now = realDateNow;

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "27.1.0", "version": "27.1.5",
"preferGlobal": true, "preferGlobal": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "The command-line interface for Vercel", "description": "The command-line interface for Vercel",
@@ -42,16 +42,16 @@
"node": ">= 14" "node": ">= 14"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "5.0.2", "@vercel/build-utils": "5.0.3",
"@vercel/go": "2.0.6", "@vercel/go": "2.0.7",
"@vercel/hydrogen": "0.0.3", "@vercel/hydrogen": "0.0.4",
"@vercel/next": "3.1.5", "@vercel/next": "3.1.7",
"@vercel/node": "2.4.3", "@vercel/node": "2.4.4",
"@vercel/python": "3.0.6", "@vercel/python": "3.0.7",
"@vercel/redwood": "1.0.7", "@vercel/redwood": "1.0.8",
"@vercel/remix": "1.0.8", "@vercel/remix": "1.0.9",
"@vercel/ruby": "1.3.14", "@vercel/ruby": "1.3.15",
"@vercel/static-build": "1.0.6", "@vercel/static-build": "1.0.8",
"update-notifier": "5.1.0" "update-notifier": "5.1.0"
}, },
"devDependencies": { "devDependencies": {
@@ -96,9 +96,9 @@
"@types/which": "1.3.2", "@types/which": "1.3.2",
"@types/write-json-file": "2.2.1", "@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0", "@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.1.1", "@vercel/client": "12.1.2",
"@vercel/frameworks": "1.1.0", "@vercel/frameworks": "1.1.1",
"@vercel/fs-detectors": "2.0.0", "@vercel/fs-detectors": "2.0.1",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2", "@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2", "@zeit/source-map-support": "0.6.2",

View File

@@ -48,9 +48,17 @@ import {
} from '../util/build/write-build-result'; } from '../util/build/write-build-result';
import { importBuilders, BuilderWithPkg } from '../util/build/import-builders'; import { importBuilders, BuilderWithPkg } from '../util/build/import-builders';
import { initCorepack, cleanupCorepack } from '../util/build/corepack'; import { initCorepack, cleanupCorepack } from '../util/build/corepack';
import { sortBuilders } from '../util/build/sort-builders';
type BuildResult = BuildResultV2 | BuildResultV3; type BuildResult = BuildResultV2 | BuildResultV3;
interface SerializedBuilder extends Builder {
error?: Error;
require?: string;
requirePath?: string;
apiVersion: number;
}
const help = () => { const help = () => {
return console.log(` return console.log(`
${chalk.bold(`${cli.logo} ${cli.name} build`)} ${chalk.bold(`${cli.logo} ${cli.name} build`)}
@@ -198,7 +206,7 @@ export default async function main(client: Client): Promise<number> {
normalizePath(relative(workPath, f)) normalizePath(relative(workPath, f))
); );
const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} }); const routesResult = getTransformedRoutes(vercelConfig || {});
if (routesResult.error) { if (routesResult.error) {
output.prettyError(routesResult.error); output.prettyError(routesResult.error);
return 1; return 1;
@@ -296,32 +304,36 @@ export default async function main(client: Client): Promise<number> {
const ops: Promise<Error | void>[] = []; const ops: Promise<Error | void>[] = [];
// Write the `detectedBuilders` result to output dir // Write the `detectedBuilders` result to output dir
ops.push( const buildsJsonBuilds = new Map<Builder, SerializedBuilder>(
fs.writeJSON( builds.map(build => {
join(outputDir, 'builds.json'), const builderWithPkg = buildersWithPkgs.get(build.use);
{ if (!builderWithPkg) {
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.', throw new Error(`Failed to load Builder "${build.use}"`);
target,
argv: process.argv,
builds: builds.map(build => {
const builderWithPkg = buildersWithPkgs.get(build.use);
if (!builderWithPkg) {
throw new Error(`Failed to load Builder "${build.use}"`);
}
const { builder, pkg: builderPkg } = builderWithPkg;
return {
require: builderPkg.name,
requirePath: builderWithPkg.path,
apiVersion: builder.version,
...build,
};
}),
},
{
spaces: 2,
} }
) const { builder, pkg: builderPkg } = builderWithPkg;
return [
build,
{
require: builderPkg.name,
requirePath: builderWithPkg.path,
apiVersion: builder.version,
...build,
},
];
})
); );
const buildsJson = {
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
target,
argv: process.argv,
builds: Array.from(buildsJsonBuilds.values()),
};
const buildsJsonPath = join(outputDir, 'builds.json');
const writeBuildsJsonPromise = fs.writeJSON(buildsJsonPath, buildsJson, {
spaces: 2,
});
ops.push(writeBuildsJsonPromise);
// The `meta` config property is re-used for each Builder // The `meta` config property is re-used for each Builder
// invocation so that Builders can share state between // invocation so that Builders can share state between
@@ -332,64 +344,95 @@ export default async function main(client: Client): Promise<number> {
}; };
// Execute Builders for detected entrypoints // Execute Builders for detected entrypoints
// TODO: parallelize builds // TODO: parallelize builds (except for frontend)
const sortedBuilders = sortBuilders(builds);
const buildResults: Map<Builder, BuildResult> = new Map(); const buildResults: Map<Builder, BuildResult> = new Map();
const overrides: PathOverride[] = []; const overrides: PathOverride[] = [];
const repoRootPath = cwd; const repoRootPath = cwd;
const corepackShimDir = await initCorepack({ repoRootPath }); const corepackShimDir = await initCorepack({ repoRootPath });
for (const build of builds) { for (const build of sortedBuilders) {
if (typeof build.src !== 'string') continue; if (typeof build.src !== 'string') continue;
const builderWithPkg = buildersWithPkgs.get(build.use); const builderWithPkg = buildersWithPkgs.get(build.use);
if (!builderWithPkg) { if (!builderWithPkg) {
throw new Error(`Failed to load Builder "${build.use}"`); throw new Error(`Failed to load Builder "${build.use}"`);
} }
const { builder, pkg: builderPkg } = builderWithPkg;
const buildConfig: Config = { try {
outputDirectory: project.settings.outputDirectory ?? undefined, const { builder, pkg: builderPkg } = builderWithPkg;
...build.config,
projectSettings: project.settings,
installCommand: project.settings.installCommand ?? undefined,
devCommand: project.settings.devCommand ?? undefined,
buildCommand: project.settings.buildCommand ?? undefined,
framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion,
};
const buildOptions: BuildOptions = {
files: filesMap,
entrypoint: build.src,
workPath,
repoRootPath,
config: buildConfig,
meta,
};
output.debug(
`Building entrypoint "${build.src}" with "${builderPkg.name}"`
);
const buildResult = await builder.build(buildOptions);
// Store the build result to generate the final `config.json` after const buildConfig: Config = {
// all builds have completed outputDirectory: project.settings.outputDirectory ?? undefined,
buildResults.set(build, buildResult); ...build.config,
projectSettings: project.settings,
installCommand: project.settings.installCommand ?? undefined,
devCommand: project.settings.devCommand ?? undefined,
buildCommand: project.settings.buildCommand ?? undefined,
framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion,
};
const buildOptions: BuildOptions = {
files: filesMap,
entrypoint: build.src,
workPath,
repoRootPath,
config: buildConfig,
meta,
};
output.debug(
`Building entrypoint "${build.src}" with "${builderPkg.name}"`
);
const buildResult = await builder.build(buildOptions);
// Start flushing the file outputs to the filesystem asynchronously // Store the build result to generate the final `config.json` after
ops.push( // all builds have completed
writeBuildResult( buildResults.set(build, buildResult);
outputDir,
buildResult, // Start flushing the file outputs to the filesystem asynchronously
build, ops.push(
builder, writeBuildResult(
builderPkg, outputDir,
vercelConfig?.cleanUrls buildResult,
).then( build,
override => { builder,
if (override) overrides.push(override); builderPkg,
}, vercelConfig?.cleanUrls
err => err ).then(
) override => {
); if (override) overrides.push(override);
},
err => err
)
);
} catch (err: any) {
const configJson = {
version: 3,
};
const configJsonPromise = fs.writeJSON(
join(outputDir, 'config.json'),
configJson,
{ spaces: 2 }
);
await Promise.all([writeBuildsJsonPromise, configJsonPromise]);
const buildJsonBuild = buildsJsonBuilds.get(build);
if (buildJsonBuild) {
buildJsonBuild.error = {
name: err.name,
message: err.message,
stack: err.stack,
...err,
};
await fs.writeJSON(buildsJsonPath, buildsJson, {
spaces: 2,
});
}
return 1;
}
} }
if (corepackShimDir) { if (corepackShimDir) {

View File

@@ -95,6 +95,7 @@ export default async (client: Client) => {
// deprecated // deprecated
'--name': String, '--name': String,
'-n': '--name', '-n': '--name',
'--no-clipboard': Boolean,
'--target': String, '--target': String,
}); });
} catch (error) { } catch (error) {
@@ -183,6 +184,17 @@ export default async (client: Client) => {
); );
} }
if (argv['--no-clipboard']) {
output.print(
`${prependEmoji(
`The ${param(
'--no-clipboard'
)} option was ignored because it is the default behavior. Please remove it.`,
emoji('warning')
)}\n`
);
}
// build `target` // build `target`
const target = parseTarget(output, argv['--target'], argv['--prod']); const target = parseTarget(output, argv['--target'], argv['--prod']);
if (typeof target === 'number') { if (typeof target === 'number') {

View File

@@ -0,0 +1,12 @@
import frameworkList from '@vercel/frameworks';
export function sortBuilders<B extends { use: string }>(builds: B[]): B[] {
const frontendRuntimeSet = new Set(
frameworkList.map(f => f.useRuntime?.use || '@vercel/static-build')
);
const toNumber = (build: B) => (frontendRuntimeSet.has(build.use) ? 0 : 1);
return builds.sort((build1, build2) => {
return toNumber(build1) - toNumber(build2);
});
}

View File

@@ -1,6 +1,14 @@
import fs from 'fs-extra'; import fs from 'fs-extra';
import mimeTypes from 'mime-types'; import mimeTypes from 'mime-types';
import { basename, dirname, extname, join, relative, resolve } from 'path'; import {
basename,
dirname,
extname,
join,
relative,
resolve,
posix,
} from 'path';
import { import {
Builder, Builder,
BuildResultV2, BuildResultV2,
@@ -20,6 +28,7 @@ import pipe from 'promisepipe';
import { unzip } from './unzip'; import { unzip } from './unzip';
import { VERCEL_DIR } from '../projects/link'; import { VERCEL_DIR } from '../projects/link';
const { normalize } = posix;
export const OUTPUT_DIR = join(VERCEL_DIR, 'output'); export const OUTPUT_DIR = join(VERCEL_DIR, 'output');
export async function writeBuildResult( export async function writeBuildResult(
@@ -67,6 +76,13 @@ export interface PathOverride {
path?: string; path?: string;
} }
/**
* Remove duplicate slashes as well as leading/trailing slashes.
*/
function stripDuplicateSlashes(path: string): string {
return normalize(path).replace(/(^\/|\/$)/g, '');
}
/** /**
* Writes the output from the `build()` return value of a v2 Builder to * Writes the output from the `build()` return value of a v2 Builder to
* the filesystem. * the filesystem.
@@ -84,16 +100,17 @@ async function writeBuildResultV2(
const lambdas = new Map<Lambda, string>(); const lambdas = new Map<Lambda, string>();
const overrides: Record<string, PathOverride> = {}; const overrides: Record<string, PathOverride> = {};
for (const [path, output] of Object.entries(buildResult.output)) { for (const [path, output] of Object.entries(buildResult.output)) {
const normalizedPath = stripDuplicateSlashes(path);
if (isLambda(output)) { if (isLambda(output)) {
await writeLambda(outputDir, output, path, lambdas); await writeLambda(outputDir, output, normalizedPath, lambdas);
} else if (isPrerender(output)) { } else if (isPrerender(output)) {
await writeLambda(outputDir, output.lambda, path, lambdas); await writeLambda(outputDir, output.lambda, normalizedPath, lambdas);
// Write the fallback file alongside the Lambda directory // Write the fallback file alongside the Lambda directory
let fallback = output.fallback; let fallback = output.fallback;
if (fallback) { if (fallback) {
const ext = getFileExtension(fallback); const ext = getFileExtension(fallback);
const fallbackName = `${path}.prerender-fallback${ext}`; const fallbackName = `${normalizedPath}.prerender-fallback${ext}`;
const fallbackPath = join(outputDir, 'functions', fallbackName); const fallbackPath = join(outputDir, 'functions', fallbackName);
const stream = fallback.toStream(); const stream = fallback.toStream();
await pipe( await pipe(
@@ -109,7 +126,7 @@ async function writeBuildResultV2(
const prerenderConfigPath = join( const prerenderConfigPath = join(
outputDir, outputDir,
'functions', 'functions',
`${path}.prerender-config.json` `${normalizedPath}.prerender-config.json`
); );
const prerenderConfig = { const prerenderConfig = {
...output, ...output,
@@ -118,12 +135,20 @@ async function writeBuildResultV2(
}; };
await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 }); await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 });
} else if (isFile(output)) { } else if (isFile(output)) {
await writeStaticFile(outputDir, output, path, overrides, cleanUrls); await writeStaticFile(
outputDir,
output,
normalizedPath,
overrides,
cleanUrls
);
} else if (isEdgeFunction(output)) { } else if (isEdgeFunction(output)) {
await writeEdgeFunction(outputDir, output, path); await writeEdgeFunction(outputDir, output, normalizedPath);
} else { } else {
throw new Error( throw new Error(
`Unsupported output type: "${(output as any).type}" for ${path}` `Unsupported output type: "${
(output as any).type
}" for ${normalizedPath}`
); );
} }
} }
@@ -145,9 +170,9 @@ async function writeBuildResultV3(
throw new Error(`Expected "build.src" to be a string`); throw new Error(`Expected "build.src" to be a string`);
} }
const ext = extname(src); const ext = extname(src);
const path = build.config?.zeroConfig const path = stripDuplicateSlashes(
? src.substring(0, src.length - ext.length) build.config?.zeroConfig ? src.substring(0, src.length - ext.length) : src
: src; );
if (isLambda(output)) { if (isLambda(output)) {
await writeLambda(outputDir, output, path); await writeLambda(outputDir, output, path);
} else if (isEdgeFunction(output)) { } else if (isEdgeFunction(output)) {

View File

@@ -558,9 +558,8 @@ export default class DevServer {
]); ]);
await this.validateVercelConfig(vercelConfig); await this.validateVercelConfig(vercelConfig);
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({ const { error: routeError, routes: maybeRoutes } =
nowConfig: vercelConfig, getTransformedRoutes(vercelConfig);
});
if (routeError) { if (routeError) {
this.output.prettyError(routeError); this.output.prettyError(routeError);
await this.exit(); await this.exit();

View File

@@ -0,0 +1,7 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null
}
}

View File

@@ -0,0 +1 @@
export default (req, res) => res.end('Vercel');

View File

@@ -0,0 +1 @@
module.exports = (req, res) => res.end('Vercel');

View File

@@ -0,0 +1 @@
export default (req, res) => res.end('Vercel');

View File

@@ -0,0 +1,4 @@
import { IncomingMessage, ServerResponse } from 'http';
// Intentional syntax error to make the build fail
export default (req: IncomingMessage, res: ServerResponse => res.end('Vercel');

View File

@@ -1,9 +1,17 @@
const { FileBlob } = require('@vercel/build-utils'); const { FileBlob, Lambda } = require('@vercel/build-utils');
exports.build = async () => { exports.build = async () => {
const file = new FileBlob({ const file = new FileBlob({
data: Buffer.from('file contents') data: Buffer.from('file contents')
}); });
const output = { file }; const lambda = new Lambda({
files: {},
runtime: 'provided',
handler: 'example.js'
})
const output = {
file,
'withTrailingSlash/': lambda
};
return { output }; return { output };
}; };

View File

@@ -1,5 +1,6 @@
import { client } from './client'; import { client } from './client';
import { Project } from '../../src/types'; import { Project } from '../../src/types';
import { formatProvider } from '../../src/util/projects/connect-git-provider';
const envs = [ const envs = [
{ {

View File

@@ -589,8 +589,6 @@ describe('build', () => {
const output = join(cwd, '.vercel/output'); const output = join(cwd, '.vercel/output');
try { try {
process.chdir(cwd); process.chdir(cwd);
client.stderr.pipe(process.stderr);
client.setArgv('build');
const exitCode = await build(client); const exitCode = await build(client);
expect(exitCode).toEqual(0); expect(exitCode).toEqual(0);
@@ -614,6 +612,40 @@ describe('build', () => {
expect(await fs.readFile(join(output, 'static/file'), 'utf8')).toEqual( expect(await fs.readFile(join(output, 'static/file'), 'utf8')).toEqual(
'file contents' 'file contents'
); );
// "functions" directory has output Functions
const functions = await fs.readdir(join(output, 'functions'));
expect(functions.sort()).toEqual(['withTrailingSlash.func']);
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
it('should store Builder error in `builds.json`', async () => {
const cwd = fixture('node-error');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(1);
// `builds.json` contains "error" build
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds.builds).toHaveLength(4);
const errorBuilds = builds.builds.filter((b: any) => 'error' in b);
expect(errorBuilds).toHaveLength(1);
expect(errorBuilds[0].error.name).toEqual('Error');
expect(errorBuilds[0].error.message).toMatch(`TS1005`);
expect(errorBuilds[0].error.message).toMatch(`',' expected.`);
expect(errorBuilds[0].error.hideStackTrace).toEqual(true);
expect(errorBuilds[0].error.code).toEqual('NODE_TYPESCRIPT_ERROR');
// `config.json`` contains `version`
const configJson = await fs.readJSON(join(output, 'config.json'));
expect(configJson.version).toBe(3);
} finally { } finally {
process.chdir(originalCwd); process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING; delete process.env.__VERCEL_BUILD_RUNNING;

View File

@@ -0,0 +1,51 @@
import { sortBuilders } from '../../../../src/util/build/sort-builders';
describe('sortBuilders()', () => {
test.each([
{
name: 'should sort @vercel/next from middle to beginning',
input: ['@vercel/node', '@vercel/next', '@vercel/python'],
output: ['@vercel/next', '@vercel/node', '@vercel/python'],
},
{
name: 'should sort @vercel/static-build from middle to beginning',
input: ['@vercel/node', '@vercel/static-build', '@vercel/python'],
output: ['@vercel/static-build', '@vercel/node', '@vercel/python'],
},
{
name: 'should sort @vercel/remix from end to beginning',
input: ['@vercel/python', '@vercel/node', '@vercel/remix'],
output: ['@vercel/remix', '@vercel/python', '@vercel/node'],
},
{
name: 'should sort @vercel/redwood from beginning to beginning',
input: ['@vercel/redwood', '@vercel/python', '@vercel/ruby'],
output: ['@vercel/redwood', '@vercel/python', '@vercel/ruby'],
},
{
name: 'should sort @vercel/hydrogen from end to beginning',
input: ['@vercel/python', '@vercel/hydrogen'],
output: ['@vercel/hydrogen', '@vercel/python'],
},
{
name: 'should sort @vercel/static-build to beginning with many @vercel/node',
input: [
'@vercel/node',
'@vercel/node',
'@vercel/node',
'@vercel/static-build',
'@vercel/node',
],
output: [
'@vercel/static-build',
'@vercel/node',
'@vercel/node',
'@vercel/node',
'@vercel/node',
],
},
])('$name', ({ input, output }) => {
const builders = sortBuilders(input.map(use => ({ use })));
expect(builders.map(b => b.use)).toEqual(output);
});
});

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/client", "name": "@vercel/client",
"version": "12.1.1", "version": "12.1.2",
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",
"homepage": "https://vercel.com", "homepage": "https://vercel.com",
@@ -42,8 +42,8 @@
] ]
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "5.0.2", "@vercel/build-utils": "5.0.3",
"@vercel/routing-utils": "1.13.5", "@vercel/routing-utils": "2.0.0",
"@zeit/fetch": "5.2.0", "@zeit/fetch": "5.2.0",
"async-retry": "1.2.3", "async-retry": "1.2.3",
"async-sema": "3.0.0", "async-sema": "3.0.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/frameworks", "name": "@vercel/frameworks",
"version": "1.1.0", "version": "1.1.1",
"main": "./dist/frameworks.js", "main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts", "types": "./dist/frameworks.d.ts",
"files": [ "files": [
@@ -21,7 +21,7 @@
"@types/js-yaml": "3.12.1", "@types/js-yaml": "3.12.1",
"@types/node": "12.0.4", "@types/node": "12.0.4",
"@types/node-fetch": "2.5.8", "@types/node-fetch": "2.5.8",
"@vercel/routing-utils": "1.13.5", "@vercel/routing-utils": "2.0.0",
"ajv": "6.12.2", "ajv": "6.12.2",
"typescript": "4.3.4" "typescript": "4.3.4"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/fs-detectors", "name": "@vercel/fs-detectors",
"version": "2.0.0", "version": "2.0.1",
"description": "Vercel filesystem detectors", "description": "Vercel filesystem detectors",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -20,8 +20,8 @@
"test-unit": "yarn test" "test-unit": "yarn test"
}, },
"dependencies": { "dependencies": {
"@vercel/frameworks": "1.1.0", "@vercel/frameworks": "1.1.1",
"@vercel/routing-utils": "1.13.5", "@vercel/routing-utils": "2.0.0",
"glob": "8.0.3", "glob": "8.0.3",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"minimatch": "3.0.4", "minimatch": "3.0.4",

View File

@@ -1,7 +1,7 @@
import minimatch from 'minimatch'; import minimatch from 'minimatch';
import { valid as validSemver } from 'semver'; import { valid as validSemver } from 'semver';
import { parse as parsePath, extname } from 'path'; import { parse as parsePath, extname } from 'path';
import type { Route, Source } from '@vercel/routing-utils'; import type { Route, RouteWithSrc } from '@vercel/routing-utils';
import frameworkList, { Framework } from '@vercel/frameworks'; import frameworkList, { Framework } from '@vercel/frameworks';
import type { import type {
PackageJson, PackageJson,
@@ -155,8 +155,8 @@ export async function detectBuilders(
let fallbackEntrypoint: string | null = null; let fallbackEntrypoint: string | null = null;
const apiRoutes: Source[] = []; const apiRoutes: RouteWithSrc[] = [];
const dynamicRoutes: Source[] = []; const dynamicRoutes: RouteWithSrc[] = [];
// API // API
for (const fileName of sortedFiles) { for (const fileName of sortedFiles) {
@@ -692,7 +692,7 @@ function getApiRoute(
options: Options, options: Options,
absolutePathCache: Map<string, string> absolutePathCache: Map<string, string>
): { ): {
apiRoute: Source | null; apiRoute: RouteWithSrc | null;
isDynamic: boolean; isDynamic: boolean;
routeError: ErrorResponse | null; routeError: ErrorResponse | null;
} { } {
@@ -886,7 +886,7 @@ function createRouteFromPath(
filePath: string, filePath: string,
featHandleMiss: boolean, featHandleMiss: boolean,
cleanUrls: boolean cleanUrls: boolean
): { route: Source; isDynamic: boolean } { ): { route: RouteWithSrc; isDynamic: boolean } {
const parts = filePath.split('/'); const parts = filePath.split('/');
let counter = 1; let counter = 1;
@@ -932,7 +932,7 @@ function createRouteFromPath(
? `^/${srcParts.slice(0, -1).join('/')}${srcParts.slice(-1)[0]}$` ? `^/${srcParts.slice(0, -1).join('/')}${srcParts.slice(-1)[0]}$`
: `^/${srcParts.join('/')}$`; : `^/${srcParts.join('/')}$`;
let route: Source; let route: RouteWithSrc;
if (featHandleMiss) { if (featHandleMiss) {
const extensionless = ext ? filePath.slice(0, -ext.length) : filePath; const extensionless = ext ? filePath.slice(0, -ext.length) : filePath;
@@ -959,8 +959,8 @@ interface LimitedRoutes {
function getRouteResult( function getRouteResult(
pkg: PackageJson | undefined | null, pkg: PackageJson | undefined | null,
apiRoutes: Source[], apiRoutes: RouteWithSrc[],
dynamicRoutes: Source[], dynamicRoutes: RouteWithSrc[],
outputDirectory: string, outputDirectory: string,
apiBuilders: Builder[], apiBuilders: Builder[],
frontendBuilder: Builder | null, frontendBuilder: Builder | null,

View File

@@ -1,4 +1,8 @@
import type { Source, Route, Handler } from '@vercel/routing-utils'; import type {
Route,
RouteWithHandle as Handler,
RouteWithSrc as Source,
} from '@vercel/routing-utils';
import { import {
detectBuilders, detectBuilders,
detectOutputDirectory, detectOutputDirectory,

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/go", "name": "@vercel/go",
"version": "2.0.6", "version": "2.0.7",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
@@ -25,7 +25,7 @@
"@types/fs-extra": "^5.0.5", "@types/fs-extra": "^5.0.5",
"@types/node-fetch": "^2.3.0", "@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0", "@types/tar": "^4.0.0",
"@vercel/build-utils": "5.0.2", "@vercel/build-utils": "5.0.3",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"async-retry": "1.3.1", "async-retry": "1.3.1",
"execa": "^1.0.0", "execa": "^1.0.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/hydrogen", "name": "@vercel/hydrogen",
"version": "0.0.3", "version": "0.0.4",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"homepage": "https://vercel.com/docs", "homepage": "https://vercel.com/docs",
@@ -22,7 +22,7 @@
"devDependencies": { "devDependencies": {
"@types/jest": "27.5.1", "@types/jest": "27.5.1",
"@types/node": "*", "@types/node": "*",
"@vercel/build-utils": "5.0.2", "@vercel/build-utils": "5.0.3",
"typescript": "4.6.4" "typescript": "4.6.4"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/next", "name": "@vercel/next",
"version": "3.1.5", "version": "3.1.7",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -45,9 +45,9 @@
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@types/text-table": "0.2.1", "@types/text-table": "0.2.1",
"@types/webpack-sources": "3.2.0", "@types/webpack-sources": "3.2.0",
"@vercel/build-utils": "5.0.2", "@vercel/build-utils": "5.0.3",
"@vercel/nft": "0.20.1", "@vercel/nft": "0.20.1",
"@vercel/routing-utils": "1.13.5", "@vercel/routing-utils": "2.0.0",
"async-sema": "3.0.1", "async-sema": "3.0.1",
"buffer-crc32": "0.2.13", "buffer-crc32": "0.2.13",
"cheerio": "1.0.0-rc.10", "cheerio": "1.0.0-rc.10",

View File

@@ -24,7 +24,7 @@ import {
NodejsLambda, NodejsLambda,
BuildResultV2Typical as BuildResult, BuildResultV2Typical as BuildResult,
} from '@vercel/build-utils'; } from '@vercel/build-utils';
import { Handler, Route, Source } from '@vercel/routing-utils'; import { Route, RouteWithHandle, RouteWithSrc } from '@vercel/routing-utils';
import { import {
convertHeaders, convertHeaders,
convertRedirects, convertRedirects,
@@ -896,7 +896,7 @@ export const build: BuildV2 = async ({
...(output[path.join('./', entryDirectory, '404')] || ...(output[path.join('./', entryDirectory, '404')] ||
output[path.join('./', entryDirectory, '404/index')] output[path.join('./', entryDirectory, '404/index')]
? [ ? [
{ handle: 'error' } as Handler, { handle: 'error' } as RouteWithHandle,
{ {
status: 404, status: 404,
@@ -928,7 +928,7 @@ export const build: BuildV2 = async ({
let trailingSlash = false; let trailingSlash = false;
redirects = redirects.filter(_redir => { redirects = redirects.filter(_redir => {
const redir = _redir as Source; const redir = _redir as RouteWithSrc;
// detect the trailing slash redirect and make sure it's // detect the trailing slash redirect and make sure it's
// kept above the wildcard mapping to prevent erroneous redirects // kept above the wildcard mapping to prevent erroneous redirects
// since non-continue routes come after continue the $wildcard // since non-continue routes come after continue the $wildcard
@@ -1146,7 +1146,7 @@ export const build: BuildV2 = async ({
continue; continue;
} }
const route: Source & { dest: string } = { const route: RouteWithSrc & { dest: string } = {
src: ( src: (
dataRoute.namedDataRouteRegex || dataRoute.dataRouteRegex dataRoute.namedDataRouteRegex || dataRoute.dataRouteRegex
).replace(/^\^/, `^${appMountPrefixNoTrailingSlash}`), ).replace(/^\^/, `^${appMountPrefixNoTrailingSlash}`),
@@ -1175,7 +1175,7 @@ export const build: BuildV2 = async ({
if (isOmittedRoute && isServerMode) { if (isOmittedRoute && isServerMode) {
// only match this route when in preview mode so // only match this route when in preview mode so
// preview works for non-prerender fallback: false pages // preview works for non-prerender fallback: false pages
(route as Source).has = [ (route as RouteWithSrc).has = [
{ {
type: 'cookie', type: 'cookie',
key: '__prerender_bypass', key: '__prerender_bypass',
@@ -2454,7 +2454,7 @@ export const build: BuildV2 = async ({
? [] ? []
: [ : [
// Custom Next.js 404 page // Custom Next.js 404 page
{ handle: 'error' } as Handler, { handle: 'error' } as RouteWithHandle,
...(i18n && (static404Page || hasIsr404Page) ...(i18n && (static404Page || hasIsr404Page)
? [ ? [

View File

@@ -14,7 +14,7 @@ import {
Files, Files,
BuildResultV2Typical as BuildResult, BuildResultV2Typical as BuildResult,
} from '@vercel/build-utils'; } from '@vercel/build-utils';
import { Handler, Route, Source } from '@vercel/routing-utils'; import { Route, RouteWithHandle, RouteWithSrc } from '@vercel/routing-utils';
import { MAX_AGE_ONE_YEAR } from '.'; import { MAX_AGE_ONE_YEAR } from '.';
import { import {
NextRequiredServerFilesManifest, NextRequiredServerFilesManifest,
@@ -56,6 +56,7 @@ import prettyBytes from 'pretty-bytes';
const CORRECT_NOT_FOUND_ROUTES_VERSION = 'v12.0.1'; const CORRECT_NOT_FOUND_ROUTES_VERSION = 'v12.0.1';
const CORRECT_MIDDLEWARE_ORDER_VERSION = 'v12.1.7-canary.29'; const CORRECT_MIDDLEWARE_ORDER_VERSION = 'v12.1.7-canary.29';
const NEXT_DATA_MIDDLEWARE_RESOLVING_VERSION = 'v12.1.7-canary.33'; const NEXT_DATA_MIDDLEWARE_RESOLVING_VERSION = 'v12.1.7-canary.33';
const EMPTY_ALLOW_QUERY_FOR_PRERENDERED_VERSION = 'v12.2.0';
export async function serverBuild({ export async function serverBuild({
dynamicPages, dynamicPages,
@@ -133,6 +134,10 @@ export async function serverBuild({
const lambdaPageKeys = Object.keys(lambdaPages); const lambdaPageKeys = Object.keys(lambdaPages);
const internalPages = ['_app.js', '_error.js', '_document.js']; const internalPages = ['_app.js', '_error.js', '_document.js'];
const pageBuildTraces = await glob('**/*.js.nft.json', pagesDir); const pageBuildTraces = await glob('**/*.js.nft.json', pagesDir);
const isEmptyAllowQueryForPrendered = semver.gte(
nextVersion,
EMPTY_ALLOW_QUERY_FOR_PRERENDERED_VERSION
);
const isCorrectNotFoundRoutes = semver.gte( const isCorrectNotFoundRoutes = semver.gte(
nextVersion, nextVersion,
CORRECT_NOT_FOUND_ROUTES_VERSION CORRECT_NOT_FOUND_ROUTES_VERSION
@@ -756,6 +761,7 @@ export async function serverBuild({
static404Page, static404Page,
hasPages404: routesManifest.pages404, hasPages404: routesManifest.pages404,
isCorrectNotFoundRoutes, isCorrectNotFoundRoutes,
isEmptyAllowQueryForPrendered,
}); });
Object.keys(prerenderManifest.staticRoutes).forEach(route => Object.keys(prerenderManifest.staticRoutes).forEach(route =>
@@ -822,7 +828,7 @@ export async function serverBuild({
const { staticFiles, publicDirectoryFiles, staticDirectoryFiles } = const { staticFiles, publicDirectoryFiles, staticDirectoryFiles } =
await getStaticFiles(entryPath, entryDirectory, outputDirectory); await getStaticFiles(entryPath, entryDirectory, outputDirectory);
const notFoundPreviewRoutes: Source[] = []; const notFoundPreviewRoutes: RouteWithSrc[] = [];
if (prerenderManifest.notFoundRoutes?.length > 0 && canUsePreviewMode) { if (prerenderManifest.notFoundRoutes?.length > 0 && canUsePreviewMode) {
// we combine routes into one src here to reduce the number of needed // we combine routes into one src here to reduce the number of needed
@@ -1378,7 +1384,7 @@ export async function serverBuild({
}, },
// error handling // error handling
{ handle: 'error' } as Handler, { handle: 'error' } as RouteWithHandle,
// Custom Next.js 404 page // Custom Next.js 404 page
...(i18n && (static404Page || hasIsr404Page || lambdaPages['404.js']) ...(i18n && (static404Page || hasIsr404Page || lambdaPages['404.js'])

View File

@@ -16,7 +16,7 @@ import {
EdgeFunction, EdgeFunction,
} from '@vercel/build-utils'; } from '@vercel/build-utils';
import { NodeFileTraceReasons } from '@vercel/nft'; import { NodeFileTraceReasons } from '@vercel/nft';
import { Header, Rewrite, Route, Source } from '@vercel/routing-utils'; import { Header, Rewrite, Route, RouteWithSrc } from '@vercel/routing-utils';
import { Sema } from 'async-sema'; import { Sema } from 'async-sema';
import crc32 from 'buffer-crc32'; import crc32 from 'buffer-crc32';
import fs, { lstat, stat } from 'fs-extra'; import fs, { lstat, stat } from 'fs-extra';
@@ -273,8 +273,8 @@ export async function getDynamicRoutes(
canUsePreviewMode?: boolean, canUsePreviewMode?: boolean,
bypassToken?: string, bypassToken?: string,
isServerMode?: boolean, isServerMode?: boolean,
dynamicMiddlewareRouteMap?: Map<string, Source> dynamicMiddlewareRouteMap?: Map<string, RouteWithSrc>
): Promise<Source[]> { ): Promise<RouteWithSrc[]> {
if (routesManifest) { if (routesManifest) {
switch (routesManifest.version) { switch (routesManifest.version) {
case 1: case 1:
@@ -307,7 +307,7 @@ export async function getDynamicRoutes(
} }
const { page, namedRegex, regex, routeKeys } = params; const { page, namedRegex, regex, routeKeys } = params;
const route: Source = { const route: RouteWithSrc = {
src: namedRegex || regex, src: namedRegex || regex,
dest: `${!isDev ? path.join('/', entryDirectory, page) : page}${ dest: `${!isDev ? path.join('/', entryDirectory, page) : page}${
routeKeys routeKeys
@@ -400,7 +400,7 @@ export async function getDynamicRoutes(
matcher: getRouteRegex && getRouteRegex(pageName).re, matcher: getRouteRegex && getRouteRegex(pageName).re,
})); }));
const routes: Source[] = []; const routes: RouteWithSrc[] = [];
pageMatchers.forEach(pageMatcher => { pageMatchers.forEach(pageMatcher => {
// in `vercel dev` we don't need to prefix the destination // in `vercel dev` we don't need to prefix the destination
const dest = !isDev const dest = !isDev
@@ -419,7 +419,7 @@ export async function getDynamicRoutes(
} }
export function localizeDynamicRoutes( export function localizeDynamicRoutes(
dynamicRoutes: Source[], dynamicRoutes: RouteWithSrc[],
dynamicPrefix: string, dynamicPrefix: string,
entryDirectory: string, entryDirectory: string,
staticPages: Files, staticPages: Files,
@@ -427,8 +427,8 @@ export function localizeDynamicRoutes(
routesManifest?: RoutesManifest, routesManifest?: RoutesManifest,
isServerMode?: boolean, isServerMode?: boolean,
isCorrectLocaleAPIRoutes?: boolean isCorrectLocaleAPIRoutes?: boolean
): Source[] { ): RouteWithSrc[] {
return dynamicRoutes.map((route: Source) => { return dynamicRoutes.map((route: RouteWithSrc) => {
// i18n is already handled for middleware // i18n is already handled for middleware
if (route.middleware !== undefined || route.middlewarePath !== undefined) if (route.middleware !== undefined || route.middlewarePath !== undefined)
return route; return route;
@@ -1665,6 +1665,7 @@ type OnPrerenderRouteArgs = {
pageLambdaMap: { [key: string]: string }; pageLambdaMap: { [key: string]: string };
routesManifest?: RoutesManifest; routesManifest?: RoutesManifest;
isCorrectNotFoundRoutes?: boolean; isCorrectNotFoundRoutes?: boolean;
isEmptyAllowQueryForPrendered?: boolean;
}; };
let prerenderGroup = 1; let prerenderGroup = 1;
@@ -1698,6 +1699,7 @@ export const onPrerenderRoute =
pageLambdaMap, pageLambdaMap,
routesManifest, routesManifest,
isCorrectNotFoundRoutes, isCorrectNotFoundRoutes,
isEmptyAllowQueryForPrendered,
} = prerenderRouteArgs; } = prerenderRouteArgs;
if (isBlocking && isFallback) { if (isBlocking && isFallback) {
@@ -1901,7 +1903,6 @@ export const onPrerenderRoute =
// a given path. All other query keys will be striped. We can automatically // a given path. All other query keys will be striped. We can automatically
// detect this for prerender (ISR) pages by reading the routes manifest file. // detect this for prerender (ISR) pages by reading the routes manifest file.
const pageKey = srcRoute || routeKey; const pageKey = srcRoute || routeKey;
const isDynamic = isDynamicRoute(pageKey);
const route = routesManifest?.dynamicRoutes.find( const route = routesManifest?.dynamicRoutes.find(
(r): r is RoutesManifestRoute => (r): r is RoutesManifestRoute =>
r.page === pageKey && !('isMiddleware' in r) r.page === pageKey && !('isMiddleware' in r)
@@ -1911,14 +1912,33 @@ export const onPrerenderRoute =
// we have sufficient information to set it // we have sufficient information to set it
let allowQuery: string[] | undefined; let allowQuery: string[] | undefined;
if (routeKeys) { if (isEmptyAllowQueryForPrendered) {
// if we have routeKeys in the routes-manifest we use those const isDynamic = isDynamicRoute(routeKey);
// for allowQuery for dynamic routes
allowQuery = Object.values(routeKeys); if (!isDynamic) {
} else if (!isDynamic) { // for non-dynamic routes we use an empty array since
// for non-dynamic routes we use an empty array since // no query values bust the cache for non-dynamic prerenders
// no query values bust the cache for non-dynamic prerenders // prerendered paths also do not pass allowQuery as they match
allowQuery = []; // during handle: 'filesystem' so should not cache differently
// by query values
allowQuery = [];
} else if (routeKeys) {
// if we have routeKeys in the routes-manifest we use those
// for allowQuery for dynamic routes
allowQuery = Object.values(routeKeys);
}
} else {
const isDynamic = isDynamicRoute(pageKey);
if (routeKeys) {
// if we have routeKeys in the routes-manifest we use those
// for allowQuery for dynamic routes
allowQuery = Object.values(routeKeys);
} else if (!isDynamic) {
// for non-dynamic routes we use an empty array since
// no query values bust the cache for non-dynamic prerenders
allowQuery = [];
}
} }
prerenders[outputPathPage] = new Prerender({ prerenders[outputPathPage] = new Prerender({
@@ -2292,7 +2312,7 @@ export async function getMiddlewareBundle({
const source: { const source: {
staticRoutes: Route[]; staticRoutes: Route[];
dynamicRouteMap: Map<string, Source>; dynamicRouteMap: Map<string, RouteWithSrc>;
edgeFunctions: Record<string, EdgeFunction>; edgeFunctions: Record<string, EdgeFunction>;
} = { } = {
staticRoutes: [], staticRoutes: [],

View File

@@ -1,8 +1,144 @@
/* eslint-env jest */
const path = require('path'); const path = require('path');
const { deployAndTest } = require('../../utils'); const cheerio = require('cheerio');
const { deployAndTest, check, waitFor } = require('../../utils');
const fetch = require('../../../../../test/lib/deployment/fetch-retry');
async function checkForChange(url, initialValue, getNewValue) {
return check(async () => {
const res = await fetch(url);
if (res.status !== 200) {
throw new Error(`Invalid status code ${res.status}`);
}
const newValue = await getNewValue(res);
return initialValue !== newValue
? 'success'
: JSON.stringify({ initialValue, newValue });
}, 'success');
}
const ctx = {};
describe(`${__dirname.split(path.sep).pop()}`, () => { describe(`${__dirname.split(path.sep).pop()}`, () => {
it('should deploy and pass probe checks', async () => { it('should deploy and pass probe checks', async () => {
await deployAndTest(__dirname); const info = await deployAndTest(__dirname);
Object.assign(ctx, info);
});
it.each([
{
title: 'should update content for prerendered path correctly',
pathsToCheck: [
{ urlPath: '/fallback-blocking/first' },
{ urlPath: '/fallback-blocking/first', query: '?slug=first' },
{ urlPath: '/fallback-blocking/first', query: '?slug=random' },
{ urlPath: '/fallback-blocking/first', query: '?another=value' },
],
},
{
title: 'should update content for non-prerendered path correctly',
pathsToCheck: [
{ urlPath: '/fallback-blocking/on-demand-2' },
{
urlPath: '/fallback-blocking/on-demand-2',
query: '?slug=on-demand-2',
},
{ urlPath: '/fallback-blocking/on-demand-2', query: '?slug=random' },
{ urlPath: '/fallback-blocking/on-demand-2', query: '?another=value' },
],
},
])('$title', async ({ pathsToCheck }) => {
let initialRandom;
let initialRandomData;
let preRevalidateRandom;
let preRevalidateRandomData;
const checkPaths = async pathsToCheck => {
for (const { urlPath, query } of pathsToCheck) {
console.log('checking', {
urlPath,
query,
initialRandom,
preRevalidateRandom,
});
if (preRevalidateRandom) {
// wait for change as cache may take a little to propagate
const initialUrl = `${ctx.deploymentUrl}${urlPath}${query || ''}`;
await checkForChange(initialUrl, preRevalidateRandom, async () => {
const res = await fetch(initialUrl);
const $ = cheerio.load(await res.text());
return JSON.parse($('#props').text()).random;
});
}
const res = await fetch(`${ctx.deploymentUrl}${urlPath}${query || ''}`);
expect(res.status).toBe(200);
const $ = await cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
if (initialRandom) {
// for fallback paths the initial value is generated
// in the foreground and then a revalidation is kicked off
// in the background so the initial value will be replaced
if (initialRandom !== props.random && urlPath.includes('on-demand')) {
initialRandom = props.random;
} else {
expect(initialRandom).toBe(props.random);
}
} else {
initialRandom = props.random;
}
expect(isNaN(initialRandom)).toBe(false);
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id${urlPath}.json${
query || ''
}`
);
expect(dataRes.status).toBe(200);
const { pageProps: dataProps } = await dataRes.json();
if (initialRandomData) {
// for fallback paths the initial value is generated
// in the foreground and then a revalidation is kicked off
// in the background so the initial value will be replaced
if (
initialRandomData !== dataProps.random &&
urlPath.includes('on-demand-2')
) {
initialRandomData = dataProps.random;
} else {
expect(initialRandomData).toBe(dataProps.random);
}
} else {
initialRandomData = dataProps.random;
}
expect(isNaN(initialRandomData)).toBe(false);
}
};
await checkPaths(pathsToCheck);
preRevalidateRandom = initialRandom;
preRevalidateRandomData = initialRandomData;
initialRandom = undefined;
initialRandomData = undefined;
const revalidateRes = await fetch(
`${ctx.deploymentUrl}/api/revalidate?urlPath=${pathsToCheck[0].urlPath}`
);
expect(revalidateRes.status).toBe(200);
expect((await revalidateRes.json()).revalidated).toBe(true);
await checkPaths(pathsToCheck);
expect(preRevalidateRandom).toBeDefined();
expect(preRevalidateRandomData).toBeDefined();
}); });
}); });

View File

@@ -0,0 +1,10 @@
export default async function handler(req, res) {
try {
console.log('revalidating', req.query.urlPath);
await res.revalidate(req.query.urlPath);
return res.json({ revalidated: true });
} catch (err) {
console.error(err);
return res.json({ revalidated: false });
}
}

View File

@@ -16,7 +16,7 @@ export const getStaticProps = ({ params }) => {
export const getStaticPaths = () => { export const getStaticPaths = () => {
return { return {
paths: ['/fallback-blocking/first'], paths: ['/fallback-blocking/first', '/fallback-blocking/on-demand-1'],
fallback: 'blocking', fallback: 'blocking',
}; };
}; };

View File

@@ -78,8 +78,20 @@ it('should build using server build', async () => {
expect(output['dynamic/[slug]'].maxDuration).toBe(5); expect(output['dynamic/[slug]'].maxDuration).toBe(5);
expect(output['fallback/[slug]'].type).toBe('Prerender'); expect(output['fallback/[slug]'].type).toBe('Prerender');
expect(output['fallback/[slug]'].allowQuery).toEqual(['slug']); expect(output['fallback/[slug]'].allowQuery).toEqual(['slug']);
expect(output['_next/data/testing-build-id/fallback/[slug].json'].type).toBe(
'Prerender'
);
expect(
output['_next/data/testing-build-id/fallback/[slug].json'].allowQuery
).toEqual(['slug']);
expect(output['fallback/first'].type).toBe('Prerender'); expect(output['fallback/first'].type).toBe('Prerender');
expect(output['fallback/first'].allowQuery).toEqual(['slug']); expect(output['fallback/first'].allowQuery).toEqual([]);
expect(output['_next/data/testing-build-id/fallback/first.json'].type).toBe(
'Prerender'
);
expect(
output['_next/data/testing-build-id/fallback/first.json'].allowQuery
).toEqual([]);
expect(output['api'].type).toBe('Lambda'); expect(output['api'].type).toBe('Lambda');
expect(output['api'].allowQuery).toBe(undefined); expect(output['api'].allowQuery).toBe(undefined);
expect(output['api'].memory).toBe(128); expect(output['api'].memory).toBe(128);

View File

@@ -1,3 +1,6 @@
module.exports = (phase, { defaultConfig }) => ({ module.exports = (phase, { defaultConfig }) => ({
pageExtensions: [...defaultConfig.pageExtensions, 'hello.js'], pageExtensions: [...defaultConfig.pageExtensions, 'hello.js'],
generateBuildId() {
return 'testing-build-id';
},
}); });

View File

@@ -129,7 +129,7 @@ export async function deployAndTest(fixtureDir) {
}; };
} }
async function waitFor(milliseconds) { export async function waitFor(milliseconds) {
return new Promise(resolve => { return new Promise(resolve => {
setTimeout(resolve, milliseconds); setTimeout(resolve, milliseconds);
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/node", "name": "@vercel/node",
"version": "2.4.3", "version": "2.4.4",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -31,7 +31,7 @@
}, },
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"@vercel/build-utils": "5.0.2", "@vercel/build-utils": "5.0.3",
"@vercel/node-bridge": "3.0.0", "@vercel/node-bridge": "3.0.0",
"@vercel/static-config": "2.0.1", "@vercel/static-config": "2.0.1",
"edge-runtime": "1.0.1", "edge-runtime": "1.0.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/python", "name": "@vercel/python",
"version": "3.0.6", "version": "3.0.7",
"main": "./dist/index.js", "main": "./dist/index.js",
"license": "MIT", "license": "MIT",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -23,7 +23,7 @@
"devDependencies": { "devDependencies": {
"@types/execa": "^0.9.0", "@types/execa": "^0.9.0",
"@types/jest": "27.4.1", "@types/jest": "27.4.1",
"@vercel/build-utils": "5.0.2", "@vercel/build-utils": "5.0.3",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"execa": "^1.0.0", "execa": "^1.0.0",
"typescript": "4.3.4" "typescript": "4.3.4"

View File

@@ -25,9 +25,6 @@ const allOptions: PythonVersion[] = [
}, },
]; ];
const upstreamProvider =
'This change is the result of a decision made by an upstream infrastructure provider (AWS)';
function getDevPythonVersion(): PythonVersion { function getDevPythonVersion(): PythonVersion {
// Use the system-installed version of `python3` when running `vercel dev` // Use the system-installed version of `python3` when running `vercel dev`
return { return {
@@ -75,14 +72,14 @@ export function getSupportedPythonVersion({
throw new NowBuildError({ throw new NowBuildError({
code: 'BUILD_UTILS_PYTHON_VERSION_DISCONTINUED', code: 'BUILD_UTILS_PYTHON_VERSION_DISCONTINUED',
link: 'http://vercel.link/python-version', link: 'http://vercel.link/python-version',
message: `Python version "${selection.version}" detected in Pipfile.lock is discontinued and must be upgraded. ${upstreamProvider}.`, message: `Python version "${selection.version}" detected in Pipfile.lock is discontinued and must be upgraded.`,
}); });
} }
if (selection.discontinueDate) { if (selection.discontinueDate) {
const d = selection.discontinueDate.toISOString().split('T')[0]; const d = selection.discontinueDate.toISOString().split('T')[0];
console.warn( console.warn(
`Error: Python version "${selection.version}" detected in Pipfile.lock is deprecated. Deployments created on or after ${d} will fail to build. ${upstreamProvider}. http://vercel.link/python-version` `Error: Python version "${selection.version}" detected in Pipfile.lock has reached End-of-Life. Deployments created on or after ${d} will fail to build. http://vercel.link/python-version`
); );
} }

View File

@@ -5,7 +5,7 @@
{ {
"path": "/", "path": "/",
"mustContain": "wsgi:RANDOMNESS_PLACEHOLDER", "mustContain": "wsgi:RANDOMNESS_PLACEHOLDER",
"logMustContain": "Python version \"3.6\" detected in Pipfile.lock is deprecated. Deployments created on or after 2022-07-18 will fail to build" "logMustContain": "Python version \"3.6\" detected in Pipfile.lock has reached End-of-Life. Deployments created on or after 2022-07-18 will fail to build"
} }
] ]
} }

View File

@@ -8,7 +8,7 @@
{ {
"path": "/", "path": "/",
"mustContain": "RANDOMNESS_PLACEHOLDER:env", "mustContain": "RANDOMNESS_PLACEHOLDER:env",
"logMustContain": "Python version \"3.6\" detected in Pipfile.lock is deprecated. Deployments created on or after 2022-07-18 will fail to build" "logMustContain": "Python version \"3.6\" detected in Pipfile.lock has reached End-of-Life. Deployments created on or after 2022-07-18 will fail to build"
} }
] ]
} }

View File

@@ -58,7 +58,7 @@ it('should throw for discontinued versions', async () => {
expect(() => expect(() =>
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' }) getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
).toThrow( ).toThrow(
'Python version "3.6" detected in Pipfile.lock is discontinued and must be upgraded. This change is the result of a decision made by an upstream infrastructure provider (AWS).' 'Python version "3.6" detected in Pipfile.lock is discontinued and must be upgraded.'
); );
expect(warningMessages).toStrictEqual([]); expect(warningMessages).toStrictEqual([]);
}); });
@@ -70,6 +70,6 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' }) getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
).toHaveProperty('runtime', 'python3.6'); ).toHaveProperty('runtime', 'python3.6');
expect(warningMessages).toStrictEqual([ expect(warningMessages).toStrictEqual([
'Error: Python version "3.6" detected in Pipfile.lock is deprecated. Deployments created on or after 2022-07-18 will fail to build. This change is the result of a decision made by an upstream infrastructure provider (AWS). http://vercel.link/python-version', 'Error: Python version "3.6" detected in Pipfile.lock has reached End-of-Life. Deployments created on or after 2022-07-18 will fail to build. http://vercel.link/python-version',
]); ]);
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/redwood", "name": "@vercel/redwood",
"version": "1.0.7", "version": "1.0.8",
"main": "./dist/index.js", "main": "./dist/index.js",
"license": "MIT", "license": "MIT",
"homepage": "https://vercel.com/docs", "homepage": "https://vercel.com/docs",
@@ -21,13 +21,13 @@
}, },
"dependencies": { "dependencies": {
"@vercel/nft": "0.20.1", "@vercel/nft": "0.20.1",
"@vercel/routing-utils": "1.13.5", "@vercel/routing-utils": "2.0.0",
"semver": "6.1.1" "semver": "6.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/aws-lambda": "8.10.19", "@types/aws-lambda": "8.10.19",
"@types/node": "*", "@types/node": "*",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@vercel/build-utils": "5.0.2" "@vercel/build-utils": "5.0.3"
} }
} }

View File

@@ -283,12 +283,10 @@ export const build: BuildV2 = async ({
: '/index'; : '/index';
const defaultRoutesConfig = getTransformedRoutes({ const defaultRoutesConfig = getTransformedRoutes({
nowConfig: { // this makes sure we send back 200.html for unprerendered pages
// this makes sure we send back 200.html for unprerendered pages rewrites: [{ source: '/(.*)', destination: fallbackHtmlPage }],
rewrites: [{ source: '/(.*)', destination: fallbackHtmlPage }], cleanUrls: true,
cleanUrls: true, trailingSlash: false,
trailingSlash: false,
},
}); });
if (defaultRoutesConfig.error) { if (defaultRoutesConfig.error) {

View File

@@ -1,6 +1,5 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { version } = require('../package.json');
const { const {
packAndDeploy, packAndDeploy,
@@ -9,10 +8,15 @@ const {
jest.setTimeout(12 * 60 * 1000); jest.setTimeout(12 * 60 * 1000);
let buildUtilsUrl;
let builderUrl; let builderUrl;
const buildUtilsUrl = version.includes('canary') ? '@canary' : undefined;
beforeAll(async () => { beforeAll(async () => {
if (!buildUtilsUrl) {
const buildUtilsPath = path.resolve(__dirname, '..', '..', 'build-utils');
buildUtilsUrl = await packAndDeploy(buildUtilsPath);
console.log('buildUtilsUrl', buildUtilsUrl);
}
const builderPath = path.resolve(__dirname, '..'); const builderPath = path.resolve(__dirname, '..');
builderUrl = await packAndDeploy(builderPath); builderUrl = await packAndDeploy(builderPath);
console.log('builderUrl', builderUrl); console.log('builderUrl', builderUrl);

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/remix", "name": "@vercel/remix",
"version": "1.0.8", "version": "1.0.9",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"homepage": "https://vercel.com/docs", "homepage": "https://vercel.com/docs",
@@ -26,7 +26,7 @@
"devDependencies": { "devDependencies": {
"@types/jest": "27.5.1", "@types/jest": "27.5.1",
"@types/node": "*", "@types/node": "*",
"@vercel/build-utils": "5.0.2", "@vercel/build-utils": "5.0.3",
"typescript": "4.6.4" "typescript": "4.6.4"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/routing-utils", "name": "@vercel/routing-utils",
"version": "1.13.5", "version": "2.0.0",
"description": "Vercel routing utilities", "description": "Vercel routing utilities",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",

View File

@@ -1,28 +1,27 @@
import { parse as parseUrl } from 'url'; import { parse as parseUrl } from 'url';
export * from './schemas';
export * from './types';
import {
Route,
Handler,
NormalizedRoutes,
GetRoutesProps,
RouteApiError,
Redirect,
HasField,
} from './types';
import { import {
collectHasSegments,
convertCleanUrls, convertCleanUrls,
convertRewrites,
convertRedirects,
convertHeaders, convertHeaders,
convertRedirects,
convertRewrites,
convertTrailingSlash, convertTrailingSlash,
sourceToRegex, sourceToRegex,
collectHasSegments,
} from './superstatic'; } from './superstatic';
import {
export { getCleanUrls } from './superstatic'; GetRoutesProps,
export { mergeRoutes } from './merge'; HasField,
NormalizedRoutes,
Redirect,
Route,
RouteApiError,
RouteWithHandle,
} from './types';
export { appendRoutesToPhase } from './append'; export { appendRoutesToPhase } from './append';
export { mergeRoutes } from './merge';
export * from './schemas';
export { getCleanUrls } from './superstatic';
export * from './types';
const VALID_HANDLE_VALUES = [ const VALID_HANDLE_VALUES = [
'filesystem', 'filesystem',
@@ -35,8 +34,8 @@ const VALID_HANDLE_VALUES = [
const validHandleValues = new Set<string>(VALID_HANDLE_VALUES); const validHandleValues = new Set<string>(VALID_HANDLE_VALUES);
export type HandleValue = typeof VALID_HANDLE_VALUES[number]; export type HandleValue = typeof VALID_HANDLE_VALUES[number];
export function isHandler(route: Route): route is Handler { export function isHandler(route: Route): route is RouteWithHandle {
return typeof (route as Handler).handle !== 'undefined'; return typeof (route as RouteWithHandle).handle !== 'undefined';
} }
export function isValidHandleValue(handle: string): handle is HandleValue { export function isValidHandleValue(handle: string): handle is HandleValue {
@@ -249,11 +248,12 @@ function notEmpty<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined; return value !== null && value !== undefined;
} }
export function getTransformedRoutes({ export function getTransformedRoutes(
nowConfig, vercelConfig: GetRoutesProps
}: GetRoutesProps): NormalizedRoutes { ): NormalizedRoutes {
const { cleanUrls, rewrites, redirects, headers, trailingSlash } = nowConfig; const { cleanUrls, rewrites, redirects, headers, trailingSlash } =
let { routes = null } = nowConfig; vercelConfig;
let { routes = null } = vercelConfig;
if (routes) { if (routes) {
const hasNewProperties = const hasNewProperties =
typeof cleanUrls !== 'undefined' || typeof cleanUrls !== 'undefined' ||

View File

@@ -21,7 +21,7 @@ export type HasField = Array<
} }
>; >;
export type Source = { export type RouteWithSrc = {
src: string; src: string;
dest?: string; dest?: string;
headers?: { [name: string]: string }; headers?: { [name: string]: string };
@@ -49,14 +49,14 @@ export type Source = {
middleware?: number; middleware?: number;
}; };
export type Handler = { export type RouteWithHandle = {
handle: HandleValue; handle: HandleValue;
src?: string; src?: string;
dest?: string; dest?: string;
status?: number; status?: number;
}; };
export type Route = Source | Handler; export type Route = RouteWithSrc | RouteWithHandle;
export type NormalizedRoutes = { export type NormalizedRoutes = {
routes: Route[] | null; routes: Route[] | null;
@@ -64,7 +64,12 @@ export type NormalizedRoutes = {
}; };
export interface GetRoutesProps { export interface GetRoutesProps {
nowConfig: VercelConfig; routes?: Route[];
cleanUrls?: boolean;
rewrites?: Rewrite[];
redirects?: Redirect[];
headers?: Header[];
trailingSlash?: boolean;
} }
export interface MergeRoutesProps { export interface MergeRoutesProps {
@@ -78,17 +83,6 @@ export interface Build {
routes?: Route[]; routes?: Route[];
} }
export interface VercelConfig {
name?: string;
version?: number;
routes?: Route[];
cleanUrls?: boolean;
rewrites?: Rewrite[];
redirects?: Redirect[];
headers?: Header[];
trailingSlash?: boolean;
}
export interface Rewrite { export interface Rewrite {
source: string; source: string;
destination: string; destination: string;
@@ -131,18 +125,3 @@ export interface AppendRoutesToPhaseProps {
*/ */
phase: HandleValue; phase: HandleValue;
} }
/** @deprecated Use VercelConfig instead. */
export type NowConfig = VercelConfig;
/** @deprecated Use Rewrite instead. */
export type NowRewrite = Rewrite;
/** @deprecated Use Redirect instead. */
export type NowRedirect = Redirect;
/** @deprecated Use Header instead. */
export type NowHeader = Header;
/** @deprecated Use HeaderKeyValue instead. */
export type NowHeaderKeyValue = HeaderKeyValue;

View File

@@ -775,24 +775,24 @@ describe('normalizeRoutes', () => {
}); });
describe('getTransformedRoutes', () => { describe('getTransformedRoutes', () => {
test('should normalize nowConfig.routes', () => { test('should normalize vercelConfig.routes', () => {
const nowConfig = { routes: [{ src: '/page', dest: '/page.html' }] }; const vercelConfig = { routes: [{ src: '/page', dest: '/page.html' }] };
const actual = getTransformedRoutes({ nowConfig }); const actual = getTransformedRoutes(vercelConfig);
const expected = normalizeRoutes(nowConfig.routes); const expected = normalizeRoutes(vercelConfig.routes);
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
assertValid(actual.routes); assertValid(actual.routes);
}); });
test('should not error when routes is null and cleanUrls is true', () => { test('should not error when routes is null and cleanUrls is true', () => {
const nowConfig = { cleanUrls: true, routes: null }; const vercelConfig = { cleanUrls: true, routes: null };
// @ts-expect-error intentionally passing invalid `routes: null` here // @ts-expect-error intentionally passing invalid `routes: null` here
const actual = getTransformedRoutes({ nowConfig }); const actual = getTransformedRoutes(vercelConfig);
assert.equal(actual.error, null); assert.equal(actual.error, null);
assertValid(actual.routes); assertValid(actual.routes);
}); });
test('should not error when has segment is used in destination', () => { test('should not error when has segment is used in destination', () => {
const nowConfig = { const vercelConfig = {
redirects: [ redirects: [
{ {
source: '/redirect', source: '/redirect',
@@ -809,17 +809,17 @@ describe('getTransformedRoutes', () => {
}; };
// @ts-expect-error not sure if this one is an error or not… // @ts-expect-error not sure if this one is an error or not…
const actual = getTransformedRoutes({ nowConfig }); const actual = getTransformedRoutes(vercelConfig);
assert.equal(actual.error, null); assert.equal(actual.error, null);
assertValid(actual.routes); assertValid(actual.routes);
}); });
test('should error when routes is defined and cleanUrls is true', () => { test('should error when routes is defined and cleanUrls is true', () => {
const nowConfig = { const vercelConfig = {
cleanUrls: true, cleanUrls: true,
routes: [{ src: '/page', dest: '/file.html' }], routes: [{ src: '/page', dest: '/file.html' }],
}; };
const { error } = getTransformedRoutes({ nowConfig }); const { error } = getTransformedRoutes(vercelConfig);
assert.notEqual(error, null); assert.notEqual(error, null);
assert.equal(error?.code, 'invalid_mixed_routes'); assert.equal(error?.code, 'invalid_mixed_routes');
assert.equal( assert.equal(
@@ -831,10 +831,10 @@ describe('getTransformedRoutes', () => {
}); });
test('should error when redirects is invalid regex', () => { test('should error when redirects is invalid regex', () => {
const nowConfig = { const vercelConfig = {
redirects: [{ source: '^/(*.)\\.html$', destination: '/file.html' }], redirects: [{ source: '^/(*.)\\.html$', destination: '/file.html' }],
}; };
const { error } = getTransformedRoutes({ nowConfig }); const { error } = getTransformedRoutes(vercelConfig);
assert.notEqual(error, null); assert.notEqual(error, null);
assert.equal(error?.code, 'invalid_redirect'); assert.equal(error?.code, 'invalid_redirect');
assert.equal( assert.equal(
@@ -846,10 +846,10 @@ describe('getTransformedRoutes', () => {
}); });
test('should error when redirects is invalid pattern', () => { test('should error when redirects is invalid pattern', () => {
const nowConfig = { const vercelConfig = {
redirects: [{ source: '/:?', destination: '/file.html' }], redirects: [{ source: '/:?', destination: '/file.html' }],
}; };
const { error } = getTransformedRoutes({ nowConfig }); const { error } = getTransformedRoutes(vercelConfig);
assert.notEqual(error, null); assert.notEqual(error, null);
assert.equal(error?.code, 'invalid_redirect'); assert.equal(error?.code, 'invalid_redirect');
assert.equal( assert.equal(
@@ -861,7 +861,7 @@ describe('getTransformedRoutes', () => {
}); });
test('should error when redirects defines both permanent and statusCode', () => { test('should error when redirects defines both permanent and statusCode', () => {
const nowConfig = { const vercelConfig = {
redirects: [ redirects: [
{ {
source: '^/both$', source: '^/both$',
@@ -871,7 +871,7 @@ describe('getTransformedRoutes', () => {
}, },
], ],
}; };
const { error } = getTransformedRoutes({ nowConfig }); const { error } = getTransformedRoutes(vercelConfig);
assert.notEqual(error, null); assert.notEqual(error, null);
assert.equal(error?.code, 'invalid_redirect'); assert.equal(error?.code, 'invalid_redirect');
assert.equal( assert.equal(
@@ -883,7 +883,7 @@ describe('getTransformedRoutes', () => {
}); });
test('should error when headers is invalid regex', () => { test('should error when headers is invalid regex', () => {
const nowConfig = { const vercelConfig = {
headers: [ headers: [
{ {
source: '^/(*.)\\.html$', source: '^/(*.)\\.html$',
@@ -896,7 +896,7 @@ describe('getTransformedRoutes', () => {
}, },
], ],
}; };
const { error } = getTransformedRoutes({ nowConfig }); const { error } = getTransformedRoutes(vercelConfig);
assert.notEqual(error, null); assert.notEqual(error, null);
assert.equal(error?.code, 'invalid_header'); assert.equal(error?.code, 'invalid_header');
assert.equal( assert.equal(
@@ -908,12 +908,12 @@ describe('getTransformedRoutes', () => {
}); });
test('should error when headers is invalid pattern', () => { test('should error when headers is invalid pattern', () => {
const nowConfig = { const vercelConfig = {
headers: [ headers: [
{ source: '/:?', headers: [{ key: 'x-hello', value: 'world' }] }, { source: '/:?', headers: [{ key: 'x-hello', value: 'world' }] },
], ],
}; };
const { error } = getTransformedRoutes({ nowConfig }); const { error } = getTransformedRoutes(vercelConfig);
assert.notEqual(error, null); assert.notEqual(error, null);
assert.equal(error?.code, 'invalid_header'); assert.equal(error?.code, 'invalid_header');
assert.equal( assert.equal(
@@ -925,10 +925,10 @@ describe('getTransformedRoutes', () => {
}); });
test('should error when rewrites is invalid regex', () => { test('should error when rewrites is invalid regex', () => {
const nowConfig = { const vercelConfig = {
rewrites: [{ source: '^/(*.)\\.html$', destination: '/file.html' }], rewrites: [{ source: '^/(*.)\\.html$', destination: '/file.html' }],
}; };
const { error } = getTransformedRoutes({ nowConfig }); const { error } = getTransformedRoutes(vercelConfig);
assert.notEqual(error, null); assert.notEqual(error, null);
assert.equal(error?.code, 'invalid_rewrite'); assert.equal(error?.code, 'invalid_rewrite');
assert.equal( assert.equal(
@@ -940,10 +940,10 @@ describe('getTransformedRoutes', () => {
}); });
test('should error when rewrites is invalid pattern', () => { test('should error when rewrites is invalid pattern', () => {
const nowConfig = { const vercelConfig = {
rewrites: [{ source: '/:?', destination: '/file.html' }], rewrites: [{ source: '/:?', destination: '/file.html' }],
}; };
const { error } = getTransformedRoutes({ nowConfig }); const { error } = getTransformedRoutes(vercelConfig);
assert.notEqual(error, null); assert.notEqual(error, null);
assert.equal(error?.code, 'invalid_rewrite'); assert.equal(error?.code, 'invalid_rewrite');
assert.equal( assert.equal(
@@ -955,7 +955,7 @@ describe('getTransformedRoutes', () => {
}); });
test('should normalize all redirects before rewrites', () => { test('should normalize all redirects before rewrites', () => {
const nowConfig = { const vercelConfig = {
cleanUrls: true, cleanUrls: true,
rewrites: [{ source: '/v1', destination: '/v2/api.py' }], rewrites: [{ source: '/v1', destination: '/v2/api.py' }],
redirects: [ redirects: [
@@ -967,7 +967,7 @@ describe('getTransformedRoutes', () => {
}, },
], ],
}; };
const { error, routes } = getTransformedRoutes({ nowConfig }); const { error, routes } = getTransformedRoutes(vercelConfig);
const expected = [ const expected = [
{ {
src: '^/(?:(.+)/)?index(?:\\.html)?/?$', src: '^/(?:(.+)/)?index(?:\\.html)?/?$',
@@ -998,7 +998,7 @@ describe('getTransformedRoutes', () => {
}); });
test('should validate schemas', () => { test('should validate schemas', () => {
const nowConfig = { const vercelConfig = {
cleanUrls: true, cleanUrls: true,
rewrites: [ rewrites: [
{ source: '/page', destination: '/page.html' }, { source: '/page', destination: '/page.html' },
@@ -1071,22 +1071,22 @@ describe('getTransformedRoutes', () => {
], ],
trailingSlashSchema: false, trailingSlashSchema: false,
}; };
assertValid(nowConfig.cleanUrls, cleanUrlsSchema); assertValid(vercelConfig.cleanUrls, cleanUrlsSchema);
assertValid(nowConfig.rewrites, rewritesSchema); assertValid(vercelConfig.rewrites, rewritesSchema);
assertValid(nowConfig.redirects, redirectsSchema); assertValid(vercelConfig.redirects, redirectsSchema);
assertValid(nowConfig.headers, headersSchema); assertValid(vercelConfig.headers, headersSchema);
assertValid(nowConfig.trailingSlashSchema, trailingSlashSchema); assertValid(vercelConfig.trailingSlashSchema, trailingSlashSchema);
}); });
test('should return null routes if no transformations are performed', () => { test('should return null routes if no transformations are performed', () => {
const nowConfig = { routes: null }; const vercelConfig = { routes: null };
// @ts-expect-error intentionally passing invalid `routes: null` // @ts-expect-error intentionally passing invalid `routes: null`
const { routes } = getTransformedRoutes({ nowConfig }); const { routes } = getTransformedRoutes(vercelConfig);
assert.equal(routes, null); assert.equal(routes, null);
}); });
test('should error when segment is defined in `destination` but not `source`', () => { test('should error when segment is defined in `destination` but not `source`', () => {
const nowConfig = { const vercelConfig = {
redirects: [ redirects: [
{ {
source: '/iforgot/:id', source: '/iforgot/:id',
@@ -1094,7 +1094,7 @@ describe('getTransformedRoutes', () => {
}, },
], ],
}; };
const { routes, error } = getTransformedRoutes({ nowConfig }); const { routes, error } = getTransformedRoutes(vercelConfig);
assert.deepEqual(routes, null); assert.deepEqual(routes, null);
assert.ok( assert.ok(
error?.message.includes( error?.message.includes(
@@ -1105,7 +1105,7 @@ describe('getTransformedRoutes', () => {
}); });
test('should error when segment is defined in HTTPS `destination` but not `source`', () => { test('should error when segment is defined in HTTPS `destination` but not `source`', () => {
const nowConfig = { const vercelConfig = {
redirects: [ redirects: [
{ {
source: '/iforgot/:id', source: '/iforgot/:id',
@@ -1113,7 +1113,7 @@ describe('getTransformedRoutes', () => {
}, },
], ],
}; };
const { routes, error } = getTransformedRoutes({ nowConfig }); const { routes, error } = getTransformedRoutes(vercelConfig);
assert.deepEqual(routes, null); assert.deepEqual(routes, null);
assert.ok( assert.ok(
error?.message.includes( error?.message.includes(
@@ -1124,7 +1124,7 @@ describe('getTransformedRoutes', () => {
}); });
test('should error when segment is defined in `destination` query string but not `source`', () => { test('should error when segment is defined in `destination` query string but not `source`', () => {
const nowConfig = { const vercelConfig = {
redirects: [ redirects: [
{ {
source: '/iforgot/:id', source: '/iforgot/:id',
@@ -1132,7 +1132,7 @@ describe('getTransformedRoutes', () => {
}, },
], ],
}; };
const { routes, error } = getTransformedRoutes({ nowConfig }); const { routes, error } = getTransformedRoutes(vercelConfig);
assert.deepEqual(routes, null); assert.deepEqual(routes, null);
assert.ok( assert.ok(
error?.message.includes( error?.message.includes(
@@ -1143,7 +1143,7 @@ describe('getTransformedRoutes', () => {
}); });
test('should error when segment is defined in HTTPS `destination` query string but not `source`', () => { test('should error when segment is defined in HTTPS `destination` query string but not `source`', () => {
const nowConfig = { const vercelConfig = {
redirects: [ redirects: [
{ {
source: '/iforgot/:id', source: '/iforgot/:id',
@@ -1151,7 +1151,7 @@ describe('getTransformedRoutes', () => {
}, },
], ],
}; };
const { routes, error } = getTransformedRoutes({ nowConfig }); const { routes, error } = getTransformedRoutes(vercelConfig);
assert.deepEqual(routes, null); assert.deepEqual(routes, null);
assert.ok( assert.ok(
error?.message.includes( error?.message.includes(
@@ -1162,7 +1162,7 @@ describe('getTransformedRoutes', () => {
}); });
test('should work with content-security-policy header containing URL', () => { test('should work with content-security-policy header containing URL', () => {
const nowConfig = { const vercelConfig = {
headers: [ headers: [
{ {
source: '/(.*)', source: '/(.*)',
@@ -1201,7 +1201,7 @@ describe('getTransformedRoutes', () => {
}, },
], ],
}; };
const actual = getTransformedRoutes({ nowConfig }); const actual = getTransformedRoutes(vercelConfig);
assert.deepEqual(actual.routes, [ assert.deepEqual(actual.routes, [
{ {
continue: true, continue: true,

View File

@@ -1,5 +1,5 @@
import { deepEqual } from 'assert'; import { deepEqual } from 'assert';
import { Route, Source, normalizeRoutes } from '../src'; import { Route, RouteWithSrc, normalizeRoutes } from '../src';
import { import {
getCleanUrls, getCleanUrls,
convertCleanUrls, convertCleanUrls,
@@ -15,7 +15,7 @@ function routesToRegExps(routeArray: Route[]) {
throw error; throw error;
} }
return (routes || []) return (routes || [])
.filter((r): r is Source => 'src' in r) .filter((r): r is RouteWithSrc => 'src' in r)
.map(r => new RegExp(r.src)); .map(r => new RegExp(r.src));
} }

View File

@@ -56,12 +56,10 @@ function getRubyPath(meta: Meta, gemfileContents: string) {
const latest = getLatestRubyVersion(); const latest = getLatestRubyVersion();
const intro = `Found \`Gemfile\` with discontinued Ruby version: \`${line}.\``; const intro = `Found \`Gemfile\` with discontinued Ruby version: \`${line}.\``;
const hint = `Please set \`ruby "~> ${latest.range}"\` in your \`Gemfile\` to use Ruby ${latest.range}.`; const hint = `Please set \`ruby "~> ${latest.range}"\` in your \`Gemfile\` to use Ruby ${latest.range}.`;
const upstream =
'This change is the result of a decision made by an upstream infrastructure provider (AWS).';
throw new NowBuildError({ throw new NowBuildError({
code: 'RUBY_DISCONTINUED_VERSION', code: 'RUBY_DISCONTINUED_VERSION',
link: 'http://vercel.link/ruby-version', link: 'http://vercel.link/ruby-version',
message: `${intro} ${hint} ${upstream}`, message: `${intro} ${hint}`,
}); });
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "@vercel/ruby", "name": "@vercel/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>", "author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "1.3.14", "version": "1.3.15",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
@@ -23,7 +23,7 @@
"devDependencies": { "devDependencies": {
"@types/fs-extra": "8.0.0", "@types/fs-extra": "8.0.0",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@vercel/build-utils": "5.0.2", "@vercel/build-utils": "5.0.3",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"execa": "2.0.4", "execa": "2.0.4",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",

View File

@@ -26,7 +26,7 @@ const fixturesPath = path.resolve(__dirname, 'fixtures');
const testsThatFailToBuild = new Map([ const testsThatFailToBuild = new Map([
[ [
'11-version-2-5-error', '11-version-2-5-error',
'Found `Gemfile` with discontinued Ruby version: `ruby "~> 2.5.x".` Please set `ruby "~> 2.7.x"` in your `Gemfile` to use Ruby 2.7.x. This change is the result of a decision made by an upstream infrastructure provider (AWS).', 'Found `Gemfile` with discontinued Ruby version: `ruby "~> 2.5.x".` Please set `ruby "~> 2.7.x"` in your `Gemfile` to use Ruby 2.7.x.',
], ],
]); ]);

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/static-build", "name": "@vercel/static-build",
"version": "1.0.6", "version": "1.0.8",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step", "homepage": "https://vercel.com/docs/build-step",
@@ -37,10 +37,10 @@
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
"@types/node-fetch": "2.5.4", "@types/node-fetch": "2.5.4",
"@types/promise-timeout": "1.3.0", "@types/promise-timeout": "1.3.0",
"@vercel/build-utils": "5.0.2", "@vercel/build-utils": "5.0.3",
"@vercel/frameworks": "1.1.0", "@vercel/frameworks": "1.1.1",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "1.13.5", "@vercel/routing-utils": "2.0.0",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"get-port": "5.0.0", "get-port": "5.0.0",
"is-port-reachable": "2.0.1", "is-port-reachable": "2.0.1",

View File

@@ -31,7 +31,7 @@ import {
NowBuildError, NowBuildError,
scanParentDirs, scanParentDirs,
} from '@vercel/build-utils'; } from '@vercel/build-utils';
import type { Route, Source } from '@vercel/routing-utils'; import type { Route, RouteWithSrc } from '@vercel/routing-utils';
import * as BuildOutputV1 from './utils/build-output-v1'; import * as BuildOutputV1 from './utils/build-output-v1';
import * as BuildOutputV2 from './utils/build-output-v2'; import * as BuildOutputV2 from './utils/build-output-v2';
import * as BuildOutputV3 from './utils/build-output-v3'; import * as BuildOutputV3 from './utils/build-output-v3';
@@ -173,8 +173,8 @@ const nowDevChildProcesses = new Set<ChildProcess>();
}); });
}); });
const getDevRoute = (srcBase: string, devPort: number, route: Source) => { const getDevRoute = (srcBase: string, devPort: number, route: RouteWithSrc) => {
const basic: Source = { const basic: RouteWithSrc = {
src: `${srcBase}${route.src}`, src: `${srcBase}${route.src}`,
dest: `http://localhost:${devPort}${route.dest}`, dest: `http://localhost:${devPort}${route.dest}`,
}; };

View File

@@ -43,20 +43,15 @@ export function createBuildOutput(
buildOutputPath: string, buildOutputPath: string,
framework?: Framework framework?: Framework
): BuildResultV2 { ): BuildResultV2 {
if (!meta.cliVersion) { if (meta.isDev) {
let buildCommandName: string; let buildCommandName: string;
if (buildCommand) buildCommandName = `"${buildCommand}"`; if (buildCommand) buildCommandName = `"${buildCommand}"`;
else if (framework) buildCommandName = framework.name; else if (framework) buildCommandName = framework.name;
else buildCommandName = 'the "build" script'; else buildCommandName = 'the "build" script';
if (meta.isDev) {
throw new Error(
`Detected Build Output v3 from ${buildCommandName}, but it is not supported for \`vercel dev\`. Please set the Development Command in your Project Settings.`
);
}
throw new Error( throw new Error(
`Detected Build Output v3 from ${buildCommandName}, but this Deployment is not using \`vercel build\`.\nPlease set the \`ENABLE_VC_BUILD=1\` environment variable.` `Detected Build Output v3 from ${buildCommandName}, but it is not supported for \`vercel dev\`. Please set the Development Command in your Project Settings.`
); );
} }

View File

@@ -99,7 +99,7 @@ describe('build()', () => {
}); });
describe('Build Output API v3', () => { describe('Build Output API v3', () => {
it('should detect the output format', async () => { it('should detect the output format with `vercel build`', async () => {
const workPath = path.join( const workPath = path.join(
__dirname, __dirname,
'build-fixtures', 'build-fixtures',
@@ -125,29 +125,28 @@ describe('build()', () => {
); );
}); });
it('should throw an Error without `vercel build`', async () => { it('should detect the output format without `vercel build`', async () => {
let err;
const workPath = path.join( const workPath = path.join(
__dirname, __dirname,
'build-fixtures', 'build-fixtures',
'09-build-output-v3' '09-build-output-v3'
); );
try { const buildResult = await build({
await build({ files: {},
files: {}, entrypoint: 'package.json',
entrypoint: 'package.json', repoRootPath: workPath,
repoRootPath: workPath, workPath,
workPath, config: {},
config: {}, meta: {
meta: { skipDownload: true,
skipDownload: true, },
}, });
}); if ('output' in buildResult) {
} catch (_err: any) { throw new Error('Unexpected `output` in build result');
err = _err;
} }
expect(err.message).toEqual( expect(buildResult.buildOutputVersion).toEqual(3);
`Detected Build Output v3 from the "build" script, but this Deployment is not using \`vercel build\`.\nPlease set the \`ENABLE_VC_BUILD=1\` environment variable.` expect(buildResult.buildOutputPath).toEqual(
path.join(workPath, '.vercel/output')
); );
}); });

View File

@@ -24,7 +24,9 @@ for (const [package, version] of changedPackageVersions) {
`skipping ${package}@${version} as it is already a canary version` `skipping ${package}@${version} as it is already a canary version`
); );
} else { } else {
console.log(execSync(`npm dist-tag add ${package}@${version} canary`)); console.log(
execSync(`npm dist-tag add ${package}@${version} canary`).toString()
);
console.log(`updated canary dist-tag for ${package}@${version}`); console.log(`updated canary dist-tag for ${package}@${version}`);
} }
} }

148
yarn.lock
View File

@@ -12245,95 +12245,95 @@ tunnel-agent@^0.6.0:
dependencies: dependencies:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
turbo-android-arm64@1.3.1: turbo-android-arm64@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-android-arm64/-/turbo-android-arm64-1.3.1.tgz#dae9bd087d6a17409fbd10ed82489a1ff39cbcc8" resolved "https://registry.yarnpkg.com/turbo-android-arm64/-/turbo-android-arm64-1.3.2-canary.1.tgz#a2cc4b76b1b4302f712548a101195f88d0d3646c"
integrity sha512-JcnZh9tLbZDpKaXaao/s/k4qXt3TbNEc1xEYYXurVWnqiMueGeS7QAtThVB85ZSqzj7djk+ngSrZabPy5RG25Q== integrity sha512-XIX6/7iy7jT8/U3FSmN4HUxNk2/HAeFcYL2A7Vh0wGQ7aVJAqItYxV5Iawf49lokdmdxQnJnttQ2TRIEKH1ITg==
turbo-darwin-64@1.3.1: turbo-darwin-64@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.3.1.tgz#efe64b9589f6a0f475b5123538b487d29e00d242" resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.3.2-canary.1.tgz#a2ab459ea35021a02bcf7c1226547e6cee9bae82"
integrity sha512-TIGDradVFoGck86VIuM38KaDeNxdKaP2ti93UpQeFw26ZhPIeTAa6wUgnz4DQP6bjIvQmXlYJ16ETZb4tFYygg== integrity sha512-h1Dd6qx8L2FZG86zWgafEgw6JwuWI2hla4PNXP/jiLEp7QuALGONJ2jdwNmsuBj0+q47eQjY9Q1tZtVvnY/iKA==
turbo-darwin-arm64@1.3.1: turbo-darwin-arm64@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.3.1.tgz#94a361e4e73ad02995303cf6c3d8cb03e85fcae4" resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.3.2-canary.1.tgz#fb5672f917de5b72a0e654de0b22bba9dee05b98"
integrity sha512-aLBq8KiMMmop7uKBkvDt/y+eER2UzxZyUzh1KWcZ7DZB5tFZnknEUyf2qggY2vd2WcDVfQ1EUjZ0MFxhhVaVzA== integrity sha512-zM5BHkxHJltKK6isJilVL7L3dlYa6WF5VPfYOAekYor3u2ZhNsgFLGom+YXz22LD/csP2giRGokREObA7q0YJQ==
turbo-freebsd-64@1.3.1: turbo-freebsd-64@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-freebsd-64/-/turbo-freebsd-64-1.3.1.tgz#64b8fc1b9f2ad9b76b532ffdcce267b2934ecb86" resolved "https://registry.yarnpkg.com/turbo-freebsd-64/-/turbo-freebsd-64-1.3.2-canary.1.tgz#6f3752f8f274463eac4e54d3d0c871866161b058"
integrity sha512-BOr/ifmxjlBeuDkDQLUJtzqzXQ2zPHHcI14U9Ys+z4Mza1uzQn/oSJqQvU5RuyRBVai7noMrpPS7QuKtDz0Cyg== integrity sha512-ZepqMN1relzuqr3g16wh0bXVKoBKzEivkGh1NVm87sDE5nvwLuB9lOCnxg89xb31PdJAyVr5UGeqVE5SfYltww==
turbo-freebsd-arm64@1.3.1: turbo-freebsd-arm64@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-freebsd-arm64/-/turbo-freebsd-arm64-1.3.1.tgz#b798af9b8898210058ca2752eadb711369f9ecea" resolved "https://registry.yarnpkg.com/turbo-freebsd-arm64/-/turbo-freebsd-arm64-1.3.2-canary.1.tgz#b32ae4452b75ba597f528560f0aacc3dd03ce5f2"
integrity sha512-bHPZjK4xnGLz6/oxl5XmWhdYOdtBMSadrGhptWSZ0wBGNn/gQzDTeZAkQeqhh25AD0eM1hzDe8QUz8GlS43lrA== integrity sha512-gL/U8Su+GjvdI2yon+revsRFi/GfB/CdWGvDzdsore4eO1/V1DiACDRJDzf+/gvNdUzoD3cwbpiKGFuaZ7wVow==
turbo-linux-32@1.3.1: turbo-linux-32@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-linux-32/-/turbo-linux-32-1.3.1.tgz#9eecada1c13f6391a7c6267349a0486987144093" resolved "https://registry.yarnpkg.com/turbo-linux-32/-/turbo-linux-32-1.3.2-canary.1.tgz#ef8afa121abca9712deb30f1de9931b7f837771f"
integrity sha512-c5okimusfvivu9wS8MKSr+rXpQAV+M4TyR9JX+spIK8B1I7AjfECAqiK2D5WFWO1bQ33bUAuxXOEpUuLpgEm+g== integrity sha512-8jxYYI4q2AHODKgO1XQ7KL7ufRFfGT4phi0JPs4KilaYEgNvDGSIDr2d8BHPVJr3exlb884ZcAW1tm+0t0WCog==
turbo-linux-64@1.3.1: turbo-linux-64@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.3.1.tgz#eaf195b90a80f238561ab11fbde17c07af481c57" resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.3.2-canary.1.tgz#d06932e2820b81e44747221de85903870677be17"
integrity sha512-O0pNX+N5gbmRcyZT+jsCPUNCN3DpIZHqNN35j7MT5nr0IkZa83CGbZnrEc+7Qws//jFJ26EngqD/JyRB2E8nwQ== integrity sha512-uCHCQ1HZdns78hr2Z6N3zZwFrUYJdyYRIPWj12TwvtTEErTARAcKvtWV9mQkkXvzkkmUDHJoTsSl5cIyhtoreg==
turbo-linux-arm64@1.3.1: turbo-linux-arm64@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.3.1.tgz#ff9dc17c352c5f59440cb55cff59cb8b23db9d1b" resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.3.2-canary.1.tgz#c127b72925bca97e67f9ac5a31c99ab6eff56a3e"
integrity sha512-D6+1MeS/x+/VCCooHPU4NIpB8qI/eW70eMRA79bqTPaxxluP0g2CaxXgucco05P51YtNsSxeVcH7X76iadON6Q== integrity sha512-5JjIQTuDo5RALnKvxPholfljiWxVwhyUpBm8xYNMtufR8CmbkjsNZgNGm8cyFXl3syHdLOhL/PsCpeVaiWDlUw==
turbo-linux-arm@1.3.1: turbo-linux-arm@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-linux-arm/-/turbo-linux-arm-1.3.1.tgz#6be53e62a60a3c6f3ab33585f0442eb7441ecf3a" resolved "https://registry.yarnpkg.com/turbo-linux-arm/-/turbo-linux-arm-1.3.2-canary.1.tgz#e3323a97c236ebad748cd3e304e20ce7578f69fb"
integrity sha512-f+r6JIwv/7ylxxJtgVi8cVw+6oNoD/r1IMTU6ejH8bfyMZZko4kkNwH9VYribQ44KDkJEgzdltnzFG5f6Hz10g== integrity sha512-xEp/twlTo8Q5TaMz3g7UCtbPdO/VZS0ugAZ2egmbBpkEIdSQ7wjME8z7cgjK/Wc2l9y0BEuA/mc/ndmO43LaNw==
turbo-linux-mips64le@1.3.1: turbo-linux-mips64le@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-linux-mips64le/-/turbo-linux-mips64le-1.3.1.tgz#ceddf8c8e222e66d05f7a68a71f66a2a3a0272a3" resolved "https://registry.yarnpkg.com/turbo-linux-mips64le/-/turbo-linux-mips64le-1.3.2-canary.1.tgz#33bd2a9d35ea4ed46a8aaefac65a629afdae741c"
integrity sha512-yL64jgwVCziOpBcdpMxIsczkgwwOvmaqKObFKWyCNlk/LOl5NKODLwXEaryLaALtpwUAoS4ltMSI64gKqmLrOA== integrity sha512-oOZD03ZyfOxifgi3YAAbtcnZtCA6fpsc2/y6R5sm9jRpXlia7PCbQLiV4H1UWCfmsQzUsLCTTZmO+D8K2rjymg==
turbo-linux-ppc64le@1.3.1: turbo-linux-ppc64le@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-linux-ppc64le/-/turbo-linux-ppc64le-1.3.1.tgz#aa5658f6d19775e06b9fd136491dceab93ecffb2" resolved "https://registry.yarnpkg.com/turbo-linux-ppc64le/-/turbo-linux-ppc64le-1.3.2-canary.1.tgz#f352723088b736179b867de4646e777b0c54e9f5"
integrity sha512-tjnM+8RosykS1lBpOPLDXGOz/Po2h796ty17uBd7IFslWPOI16a/akFOFoLH8PCiGGJMe3CYgRhEKn4sPWNxFA== integrity sha512-JtFMPdO+N6lYCFO7gN042CdzQPWvF2ow77Hv1oOpVkyU2X9seK73XHmv+15dan92/flslZLFAW8T93kiXfsJug==
turbo-windows-32@1.3.1: turbo-windows-32@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-windows-32/-/turbo-windows-32-1.3.1.tgz#e6e570bb381d9a4f78ea6e72102ab9bf493a3ad2" resolved "https://registry.yarnpkg.com/turbo-windows-32/-/turbo-windows-32-1.3.2-canary.1.tgz#5c9b0c8a65002dfb476e0fc3b7049d02982a5d17"
integrity sha512-Snnv+TVigulqwK6guHKndMlrLw88NXj8BtHRGrEksPR0QkyuHlwLf+tHYB4HmvpUl4W9lnXQf4hsljWP64BEdw== integrity sha512-xDi61dm5CbLGpi2nu20+fWcl7IcSayqQYtQNztVqyAv9TpP84gzItuETOxwNyK7BivZ0BTMuVhtVUfc44ZxG+A==
turbo-windows-64@1.3.1: turbo-windows-64@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.3.1.tgz#f891097331125c935cdaa160cc80c4cfc3e61b0e" resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.3.2-canary.1.tgz#8a6ddd2f02a7518c253a19461e3e81e9b01eec62"
integrity sha512-gLeohHG07yIhON1Pp0YNE00i/yzip2GFhkA6HdJaK95uE5bKULpqxuO414hOS/WzGwrGVXBKCImfe24XXh5T+Q== integrity sha512-uj4RVdVXIffTOeozFk8df+hgLUOZInkFvUTJ9jEKSNR/9zF9QXz7EkeQF8Cq+f840W7zNMDxciGBls3SWWTX0w==
turbo-windows-arm64@1.3.1: turbo-windows-arm64@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.3.1.tgz#5abe1743b93e272641018cef213b21ac6c984f04" resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.3.2-canary.1.tgz#c7b2449c5aec3d2a14dd0169a0e79e7c7eb57b80"
integrity sha512-0MWcHLvYgs/qdcoTFZ55nu8HhrpeiwXEMw9cbNfgqTlzy3OsrAsovYEJFyQ8KSxeploiD+QJlCdvhxx+5C0tlA== integrity sha512-rtU97ntfMRIr3IscaB3VwCxXCb60CkaM35NdTM3Yr+5H8UplmzQt1iBV8+Zc8/JO5YDfWD82dGZKVw4Oou+f2w==
turbo@1.3.1: turbo@1.3.2-canary.1:
version "1.3.1" version "1.3.2-canary.1"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.3.1.tgz#8091f3c8b654addaac84f9452476204a879b32ea" resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.3.2-canary.1.tgz#df87f97ea67c4b627a378af941e6238e6122c53b"
integrity sha512-DXckoGKlZgvTn/PrHpBI/57aeXR7tfyPf2dK+4LmBczt24ELA3o6eYHeA7KzfpSYhB2LE9qveYFQ6mJ1OzGjjg== integrity sha512-qrSrKjkKDn7afDMz++RXg84sXBmd+CprnmJNNQq7l1Ao0eOPRpe//uh4JsULgjxYHHLeB2Re1fJ02HF282Kaww==
optionalDependencies: optionalDependencies:
turbo-android-arm64 "1.3.1" turbo-android-arm64 "1.3.2-canary.1"
turbo-darwin-64 "1.3.1" turbo-darwin-64 "1.3.2-canary.1"
turbo-darwin-arm64 "1.3.1" turbo-darwin-arm64 "1.3.2-canary.1"
turbo-freebsd-64 "1.3.1" turbo-freebsd-64 "1.3.2-canary.1"
turbo-freebsd-arm64 "1.3.1" turbo-freebsd-arm64 "1.3.2-canary.1"
turbo-linux-32 "1.3.1" turbo-linux-32 "1.3.2-canary.1"
turbo-linux-64 "1.3.1" turbo-linux-64 "1.3.2-canary.1"
turbo-linux-arm "1.3.1" turbo-linux-arm "1.3.2-canary.1"
turbo-linux-arm64 "1.3.1" turbo-linux-arm64 "1.3.2-canary.1"
turbo-linux-mips64le "1.3.1" turbo-linux-mips64le "1.3.2-canary.1"
turbo-linux-ppc64le "1.3.1" turbo-linux-ppc64le "1.3.2-canary.1"
turbo-windows-32 "1.3.1" turbo-windows-32 "1.3.2-canary.1"
turbo-windows-64 "1.3.1" turbo-windows-64 "1.3.2-canary.1"
turbo-windows-arm64 "1.3.1" turbo-windows-arm64 "1.3.2-canary.1"
tweetnacl@^0.14.3, tweetnacl@~0.14.0: tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5" version "0.14.5"