Compare commits

...

14 Commits

Author SHA1 Message Date
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
30 changed files with 586 additions and 204 deletions

View File

@@ -1,4 +1,5 @@
# 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/fixtures/unit/commands/build/node-error/api/typescript.ts

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "27.1.0",
"version": "27.1.4",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -45,13 +45,13 @@
"@vercel/build-utils": "5.0.2",
"@vercel/go": "2.0.6",
"@vercel/hydrogen": "0.0.3",
"@vercel/next": "3.1.5",
"@vercel/next": "3.1.6",
"@vercel/node": "2.4.3",
"@vercel/python": "3.0.6",
"@vercel/redwood": "1.0.7",
"@vercel/remix": "1.0.8",
"@vercel/ruby": "1.3.14",
"@vercel/static-build": "1.0.6",
"@vercel/static-build": "1.0.7",
"update-notifier": "5.1.0"
},
"devDependencies": {

View File

@@ -48,9 +48,17 @@ import {
} from '../util/build/write-build-result';
import { importBuilders, BuilderWithPkg } from '../util/build/import-builders';
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
import { sortBuilders } from '../util/build/sort-builders';
type BuildResult = BuildResultV2 | BuildResultV3;
interface SerializedBuilder extends Builder {
error?: Error;
require?: string;
requirePath?: string;
apiVersion: number;
}
const help = () => {
return console.log(`
${chalk.bold(`${cli.logo} ${cli.name} build`)}
@@ -296,32 +304,36 @@ export default async function main(client: Client): Promise<number> {
const ops: Promise<Error | void>[] = [];
// Write the `detectedBuilders` result to output dir
ops.push(
fs.writeJSON(
join(outputDir, 'builds.json'),
{
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
target,
argv: process.argv,
builds: builds.map(build => {
const buildsJsonBuilds = new Map<Builder, SerializedBuilder>(
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 {
return [
build,
{
require: builderPkg.name,
requirePath: builderWithPkg.path,
apiVersion: builder.version,
...build,
};
}),
},
{
spaces: 2,
}
)
];
})
);
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
// invocation so that Builders can share state between
@@ -332,19 +344,22 @@ export default async function main(client: Client): Promise<number> {
};
// 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 overrides: PathOverride[] = [];
const repoRootPath = cwd;
const corepackShimDir = await initCorepack({ repoRootPath });
for (const build of builds) {
for (const build of sortedBuilders) {
if (typeof build.src !== 'string') continue;
const builderWithPkg = buildersWithPkgs.get(build.use);
if (!builderWithPkg) {
throw new Error(`Failed to load Builder "${build.use}"`);
}
try {
const { builder, pkg: builderPkg } = builderWithPkg;
const buildConfig: Config = {
@@ -390,6 +405,34 @@ export default async function main(client: Client): Promise<number> {
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) {

View File

@@ -95,6 +95,7 @@ export default async (client: Client) => {
// deprecated
'--name': String,
'-n': '--name',
'--no-clipboard': Boolean,
'--target': String,
});
} 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`
const target = parseTarget(output, argv['--target'], argv['--prod']);
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 mimeTypes from 'mime-types';
import { basename, dirname, extname, join, relative, resolve } from 'path';
import {
basename,
dirname,
extname,
join,
relative,
resolve,
posix,
} from 'path';
import {
Builder,
BuildResultV2,
@@ -20,6 +28,7 @@ import pipe from 'promisepipe';
import { unzip } from './unzip';
import { VERCEL_DIR } from '../projects/link';
const { normalize } = posix;
export const OUTPUT_DIR = join(VERCEL_DIR, 'output');
export async function writeBuildResult(
@@ -67,6 +76,13 @@ export interface PathOverride {
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
* the filesystem.
@@ -84,16 +100,17 @@ async function writeBuildResultV2(
const lambdas = new Map<Lambda, string>();
const overrides: Record<string, PathOverride> = {};
for (const [path, output] of Object.entries(buildResult.output)) {
const normalizedPath = stripDuplicateSlashes(path);
if (isLambda(output)) {
await writeLambda(outputDir, output, path, lambdas);
await writeLambda(outputDir, output, normalizedPath, lambdas);
} 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
let fallback = output.fallback;
if (fallback) {
const ext = getFileExtension(fallback);
const fallbackName = `${path}.prerender-fallback${ext}`;
const fallbackName = `${normalizedPath}.prerender-fallback${ext}`;
const fallbackPath = join(outputDir, 'functions', fallbackName);
const stream = fallback.toStream();
await pipe(
@@ -109,7 +126,7 @@ async function writeBuildResultV2(
const prerenderConfigPath = join(
outputDir,
'functions',
`${path}.prerender-config.json`
`${normalizedPath}.prerender-config.json`
);
const prerenderConfig = {
...output,
@@ -118,12 +135,20 @@ async function writeBuildResultV2(
};
await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 });
} else if (isFile(output)) {
await writeStaticFile(outputDir, output, path, overrides, cleanUrls);
await writeStaticFile(
outputDir,
output,
normalizedPath,
overrides,
cleanUrls
);
} else if (isEdgeFunction(output)) {
await writeEdgeFunction(outputDir, output, path);
await writeEdgeFunction(outputDir, output, normalizedPath);
} else {
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`);
}
const ext = extname(src);
const path = build.config?.zeroConfig
? src.substring(0, src.length - ext.length)
: src;
const path = stripDuplicateSlashes(
build.config?.zeroConfig ? src.substring(0, src.length - ext.length) : src
);
if (isLambda(output)) {
await writeLambda(outputDir, output, path);
} else if (isEdgeFunction(output)) {

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 () => {
const file = new FileBlob({
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 };
};

View File

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

View File

@@ -589,8 +589,6 @@ describe('build', () => {
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
client.stderr.pipe(process.stderr);
client.setArgv('build');
const exitCode = await build(client);
expect(exitCode).toEqual(0);
@@ -614,6 +612,40 @@ describe('build', () => {
expect(await fs.readFile(join(output, 'static/file'), 'utf8')).toEqual(
'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 {
process.chdir(originalCwd);
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/next",
"version": "3.1.5",
"version": "3.1.6",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",

View File

@@ -56,6 +56,7 @@ import prettyBytes from 'pretty-bytes';
const CORRECT_NOT_FOUND_ROUTES_VERSION = 'v12.0.1';
const CORRECT_MIDDLEWARE_ORDER_VERSION = 'v12.1.7-canary.29';
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({
dynamicPages,
@@ -133,6 +134,10 @@ export async function serverBuild({
const lambdaPageKeys = Object.keys(lambdaPages);
const internalPages = ['_app.js', '_error.js', '_document.js'];
const pageBuildTraces = await glob('**/*.js.nft.json', pagesDir);
const isEmptyAllowQueryForPrendered = semver.gte(
nextVersion,
EMPTY_ALLOW_QUERY_FOR_PRERENDERED_VERSION
);
const isCorrectNotFoundRoutes = semver.gte(
nextVersion,
CORRECT_NOT_FOUND_ROUTES_VERSION
@@ -756,6 +761,7 @@ export async function serverBuild({
static404Page,
hasPages404: routesManifest.pages404,
isCorrectNotFoundRoutes,
isEmptyAllowQueryForPrendered,
});
Object.keys(prerenderManifest.staticRoutes).forEach(route =>

View File

@@ -1665,6 +1665,7 @@ type OnPrerenderRouteArgs = {
pageLambdaMap: { [key: string]: string };
routesManifest?: RoutesManifest;
isCorrectNotFoundRoutes?: boolean;
isEmptyAllowQueryForPrendered?: boolean;
};
let prerenderGroup = 1;
@@ -1698,6 +1699,7 @@ export const onPrerenderRoute =
pageLambdaMap,
routesManifest,
isCorrectNotFoundRoutes,
isEmptyAllowQueryForPrendered,
} = prerenderRouteArgs;
if (isBlocking && isFallback) {
@@ -1901,7 +1903,6 @@ export const onPrerenderRoute =
// 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.
const pageKey = srcRoute || routeKey;
const isDynamic = isDynamicRoute(pageKey);
const route = routesManifest?.dynamicRoutes.find(
(r): r is RoutesManifestRoute =>
r.page === pageKey && !('isMiddleware' in r)
@@ -1911,6 +1912,24 @@ export const onPrerenderRoute =
// we have sufficient information to set it
let allowQuery: string[] | undefined;
if (isEmptyAllowQueryForPrendered) {
const isDynamic = isDynamicRoute(routeKey);
if (!isDynamic) {
// for non-dynamic routes we use an empty array since
// no query values bust the cache for non-dynamic prerenders
// prerendered paths also do not pass allowQuery as they match
// 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
@@ -1920,6 +1939,7 @@ export const onPrerenderRoute =
// no query values bust the cache for non-dynamic prerenders
allowQuery = [];
}
}
prerenders[outputPathPage] = new Prerender({
expiration: initialRevalidate,

View File

@@ -1,8 +1,144 @@
/* eslint-env jest */
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()}`, () => {
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 = () => {
return {
paths: ['/fallback-blocking/first'],
paths: ['/fallback-blocking/first', '/fallback-blocking/on-demand-1'],
fallback: 'blocking',
};
};

View File

@@ -78,8 +78,20 @@ it('should build using server build', async () => {
expect(output['dynamic/[slug]'].maxDuration).toBe(5);
expect(output['fallback/[slug]'].type).toBe('Prerender');
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'].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'].allowQuery).toBe(undefined);
expect(output['api'].memory).toBe(128);

View File

@@ -1,3 +1,6 @@
module.exports = (phase, { defaultConfig }) => ({
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 => {
setTimeout(resolve, milliseconds);
});

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/static-build",
"version": "1.0.6",
"version": "1.0.7",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step",

View File

@@ -43,22 +43,17 @@ export function createBuildOutput(
buildOutputPath: string,
framework?: Framework
): BuildResultV2 {
if (!meta.cliVersion) {
if (meta.isDev) {
let buildCommandName: string;
if (buildCommand) buildCommandName = `"${buildCommand}"`;
else if (framework) buildCommandName = framework.name;
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(
`Detected Build Output v3 from ${buildCommandName}, but this Deployment is not using \`vercel build\`.\nPlease set the \`ENABLE_VC_BUILD=1\` environment variable.`
);
}
return {
buildOutputVersion: 3,

View File

@@ -99,7 +99,7 @@ describe('build()', () => {
});
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(
__dirname,
'build-fixtures',
@@ -125,15 +125,13 @@ describe('build()', () => {
);
});
it('should throw an Error without `vercel build`', async () => {
let err;
it('should detect the output format without `vercel build`', async () => {
const workPath = path.join(
__dirname,
'build-fixtures',
'09-build-output-v3'
);
try {
await build({
const buildResult = await build({
files: {},
entrypoint: 'package.json',
repoRootPath: workPath,
@@ -143,11 +141,12 @@ describe('build()', () => {
skipDownload: true,
},
});
} catch (_err: any) {
err = _err;
if ('output' in buildResult) {
throw new Error('Unexpected `output` in build result');
}
expect(err.message).toEqual(
`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.buildOutputVersion).toEqual(3);
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`
);
} 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}`);
}
}

148
yarn.lock
View File

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