Compare commits

..

7 Commits

Author SHA1 Message Date
Nathan Rajlich
71b83d5587 Publish Canary
- @vercel/build-utils@2.15.2-canary.1
 - vercel@24.1.1-canary.1
 - @vercel/client@10.4.2-canary.1
 - @vercel/go@1.3.3-canary.1
 - @vercel/node@1.14.2-canary.1
 - @vercel/python@2.2.3-canary.1
 - @vercel/redwood@0.7.1-canary.1
 - @vercel/ruby@1.3.3-canary.1
 - @vercel/static-build@0.23.2-canary.1
2022-04-12 15:57:36 -07:00
Nathan Rajlich
d9e5fdc5e4 [build-utils] Move "Installing dependencies..." log to runNpmInstall() (#7672)
Follow-up to #7671. Since `runNpmInstall()` might now be de-duped, only
print "Installing dependencies..." when the dependencies are actually
being installed. This avoids printing the log message unnecessarily when
the command won't actually be run, and also removes some duplication in
the Builders' code.
2022-04-12 16:23:01 -04:00
Nathan Rajlich
58f479c603 [static-build] Add support for Build Output v3 detection (#7669)
Adds detection logic for a framework / build script outputting Build Output v3
format to the filesystem. In this case, `static-build` will simply stop processing
after the Build Command since deserialization happens in the build-container side
of things (this is different compared to the v1 output which gets handled in this
Builder. The reason for that is because the v3 output matches what `vc build`
outputs vs. v1 which is a different format).
2022-04-12 09:09:50 -07:00
Nathan Rajlich
d62461d952 [build-utils] Only allow runNpmInstall() to run once per package.json (#7671)
Adds a best-effort optimization to only run `npm install` once per
`pacakge.json` file. This will save a lot of time in the single-sandbox
build world (i.e. `vc build`).
2022-04-12 09:09:10 -07:00
Steven
e7f524defb Publish Canary
- @vercel/build-utils@2.15.2-canary.0
 - vercel@24.1.1-canary.0
 - @vercel/client@10.4.2-canary.0
 - @vercel/go@1.3.3-canary.0
 - @vercel/node@1.14.2-canary.0
 - @vercel/python@2.2.3-canary.0
 - @vercel/redwood@0.7.1-canary.0
 - @vercel/ruby@1.3.3-canary.0
 - @vercel/static-build@0.23.2-canary.0
2022-04-12 08:58:38 -04:00
Steven
bdefd0d05d [docs] Fix links to docs (#7668) 2022-04-12 07:52:12 -04:00
Ethan Arrowood
ca522fc9f1 [node][redwood] update @vercel/nft to 0.18.1 (#7670)
### Related Issues

Updates the @vercel/nft dependency across the repo to v0.18.1.

### 📋 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-04-11 22:07:45 +00:00
30 changed files with 376 additions and 116 deletions

View File

@@ -16,4 +16,4 @@ If you would not like to verify your domain, you can remove it from your account
#### Resources #### Resources
- [Vercel Custom Domains Documentation](https://vercel.com/docs/v2/custom-domains) - [Vercel Custom Domains Documentation](https://vercel.com/docs/concepts/projects/custom-domains)

View File

@@ -2,7 +2,7 @@
#### Why This Error Occurred #### Why This Error Occurred
You ran `vercel dev` inside a project that contains a `vercel.json` file with `env` or `build.env` properties that use [Vercel Secrets](https://vercel.com/docs/v2/build-step#environment-variables). You ran `vercel dev` inside a project that contains a `vercel.json` file with `env` or `build.env` properties that use [Vercel Secrets](https://vercel.com/docs/concepts/projects/environment-variables).
In order to use environment variables in your project locally that have values defined using the Vercel Secrets format (e.g. `@my-secret-value`), you will need to provide the value as an environment variable using a `.env`. In order to use environment variables in your project locally that have values defined using the Vercel Secrets format (e.g. `@my-secret-value`), you will need to provide the value as an environment variable using a `.env`.
@@ -24,4 +24,4 @@ TEST=value
In the above example, `TEST` represents the name of the environment variable and `value` its value. In the above example, `TEST` represents the name of the environment variable and `value` its value.
For more information on Environment Variables in development, [see the documentation](https://vercel.com/docs/v2/build-step#environment-variables). For more information on Environment Variables in development, [see the documentation](https://vercel.com/docs/concepts/projects/environment-variables).

View File

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

View File

@@ -538,7 +538,7 @@ function getMissingBuildScriptError() {
code: 'missing_build_script', code: 'missing_build_script',
message: message:
'Your `package.json` file is missing a `build` property inside the `scripts` property.' + 'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
'\nLearn More: https://vercel.com/docs/v2/platform/frequently-asked-questions#missing-build-script', '\nLearn More: https://vercel.link/missing-build-script',
}; };
} }

View File

@@ -1,15 +1,19 @@
import assert from 'assert'; import assert from 'assert';
import fs from 'fs-extra'; import fs from 'fs-extra';
import path from 'path'; import path from 'path';
import debug from '../debug'; import Sema from 'async-sema';
import spawn from 'cross-spawn'; import spawn from 'cross-spawn';
import { SpawnOptions } from 'child_process'; import { SpawnOptions } from 'child_process';
import { deprecate } from 'util'; import { deprecate } from 'util';
import debug from '../debug';
import { NowBuildError } from '../errors'; import { NowBuildError } from '../errors';
import { Meta, PackageJson, NodeVersion, Config } from '../types'; import { Meta, PackageJson, NodeVersion, Config } from '../types';
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version'; import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
import { readConfigFile } from './read-config-file'; import { readConfigFile } from './read-config-file';
// Only allow one `runNpmInstall()` invocation to run concurrently
const runNpmInstallSema = new Sema(1);
export type CliType = 'yarn' | 'npm' | 'pnpm'; export type CliType = 'yarn' | 'npm' | 'pnpm';
export interface ScanParentDirsResult { export interface ScanParentDirsResult {
@@ -17,6 +21,11 @@ export interface ScanParentDirsResult {
* "yarn", "npm", or "pnpm" depending on the presence of lockfiles. * "yarn", "npm", or "pnpm" depending on the presence of lockfiles.
*/ */
cliType: CliType; cliType: CliType;
/**
* The file path of found `package.json` file, or `undefined` if none was
* found.
*/
packageJsonPath?: string;
/** /**
* The contents of found `package.json` file, when the `readPackageJson` * The contents of found `package.json` file, when the `readPackageJson`
* option is enabled. * option is enabled.
@@ -237,12 +246,13 @@ export async function scanParentDirs(
let cliType: CliType = 'yarn'; let cliType: CliType = 'yarn';
let packageJson: PackageJson | undefined; let packageJson: PackageJson | undefined;
let packageJsonPath: string | undefined;
let currentDestPath = destPath; let currentDestPath = destPath;
let lockfileVersion: number | undefined; let lockfileVersion: number | undefined;
// eslint-disable-next-line no-constant-condition // eslint-disable-next-line no-constant-condition
while (true) { while (true) {
const packageJsonPath = path.join(currentDestPath, 'package.json'); packageJsonPath = path.join(currentDestPath, 'package.json');
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
if (await fs.pathExists(packageJsonPath)) { if (await fs.pathExists(packageJsonPath)) {
// Only read the contents of the *first* `package.json` file found, // Only read the contents of the *first* `package.json` file found,
@@ -293,7 +303,7 @@ export async function scanParentDirs(
currentDestPath = newDestPath; currentDestPath = newDestPath;
} }
return { cliType, packageJson, lockfileVersion }; return { cliType, packageJson, lockfileVersion, packageJsonPath };
} }
export async function walkParentDirs({ export async function walkParentDirs({
@@ -319,55 +329,87 @@ export async function walkParentDirs({
return null; return null;
} }
function isSet<T>(v: any): v is Set<T> {
return v?.constructor?.name === 'Set';
}
export async function runNpmInstall( export async function runNpmInstall(
destPath: string, destPath: string,
args: string[] = [], args: string[] = [],
spawnOpts?: SpawnOptions, spawnOpts?: SpawnOptions,
meta?: Meta, meta?: Meta,
nodeVersion?: NodeVersion nodeVersion?: NodeVersion
) { ): Promise<boolean> {
if (meta?.isDev) { if (meta?.isDev) {
debug('Skipping dependency installation because dev mode is enabled'); debug('Skipping dependency installation because dev mode is enabled');
return; return false;
} }
assert(path.isAbsolute(destPath)); assert(path.isAbsolute(destPath));
debug(`Installing to ${destPath}`);
const { cliType, lockfileVersion } = await scanParentDirs(destPath); try {
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts }; await runNpmInstallSema.acquire();
const env = opts.env ? { ...opts.env } : { ...process.env }; const { cliType, packageJsonPath, lockfileVersion } = await scanParentDirs(
delete env.NODE_ENV; destPath
opts.env = getEnvForPackageManager({ );
cliType,
lockfileVersion,
nodeVersion,
env,
});
let commandArgs: string[];
if (cliType === 'npm') { // Only allow `runNpmInstall()` to run once per `package.json`
opts.prettyCommand = 'npm install'; // when doing a default install (no additional args)
commandArgs = args if (meta && packageJsonPath && args.length === 0) {
.filter(a => a !== '--prefer-offline') if (!isSet<string>(meta.runNpmInstallSet)) {
.concat(['install', '--no-audit', '--unsafe-perm']); meta.runNpmInstallSet = new Set<string>();
} else if (cliType === 'pnpm') { }
// PNPM's install command is similar to NPM's but without the audit nonsense if (isSet<string>(meta.runNpmInstallSet)) {
// @see options https://pnpm.io/cli/install if (meta.runNpmInstallSet.has(packageJsonPath)) {
opts.prettyCommand = 'pnpm install'; return false;
commandArgs = args } else {
.filter(a => a !== '--prefer-offline') meta.runNpmInstallSet.add(packageJsonPath);
.concat(['install', '--unsafe-perm']); }
} else { }
opts.prettyCommand = 'yarn install'; }
commandArgs = ['install', ...args];
const installTime = Date.now();
console.log('Installing dependencies...');
debug(`Installing to ${destPath}`);
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
const env = opts.env ? { ...opts.env } : { ...process.env };
delete env.NODE_ENV;
opts.env = getEnvForPackageManager({
cliType,
lockfileVersion,
nodeVersion,
env,
});
let commandArgs: string[];
if (cliType === 'npm') {
opts.prettyCommand = 'npm install';
commandArgs = args
.filter(a => a !== '--prefer-offline')
.concat(['install', '--no-audit', '--unsafe-perm']);
} else if (cliType === 'pnpm') {
// PNPM's install command is similar to NPM's but without the audit nonsense
// @see options https://pnpm.io/cli/install
opts.prettyCommand = 'pnpm install';
commandArgs = args
.filter(a => a !== '--prefer-offline')
.concat(['install', '--unsafe-perm']);
} else {
opts.prettyCommand = 'yarn install';
commandArgs = ['install', ...args];
}
if (process.env.NPM_ONLY_PRODUCTION) {
commandArgs.push('--production');
}
await spawnAsync(cliType, commandArgs, opts);
debug(`Install complete [${Date.now() - installTime}ms]`);
return true;
} finally {
runNpmInstallSema.release();
} }
if (process.env.NPM_ONLY_PRODUCTION) {
commandArgs.push('--production');
}
return spawnAsync(cliType, commandArgs, opts);
} }
export function getEnvForPackageManager({ export function getEnvForPackageManager({

View File

@@ -356,7 +356,29 @@ export interface Images {
formats?: ImageFormat[]; formats?: ImageFormat[];
} }
export interface BuildResultV2 { /**
* If a Builder ends up creating filesystem outputs conforming to
* the Build Output API, then the Builder should return this type.
*/
export interface BuildResultBuildOutput {
/**
* Version number of the Build Output API that was created.
* Currently only `3` is a valid value.
* @example 3
*/
buildOutputVersion: 3;
/**
* Filesystem path to the Build Output directory.
* @example "/path/to/.vercel/output"
*/
buildOutputPath: string;
}
/**
* When a Builder implements `version: 2`, the `build()` function is expected
* to return this type.
*/
export interface BuildResultV2Typical {
// TODO: use proper `Route` type from `routing-utils` (perhaps move types to a common package) // TODO: use proper `Route` type from `routing-utils` (perhaps move types to a common package)
routes?: any[]; routes?: any[];
images?: Images; images?: Images;
@@ -369,6 +391,8 @@ export interface BuildResultV2 {
}>; }>;
} }
export type BuildResultV2 = BuildResultV2Typical | BuildResultBuildOutput;
export interface BuildResultV3 { export interface BuildResultV3 {
output: Lambda; output: Lambda;
} }

View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@@ -15,6 +15,7 @@ import {
runPackageJsonScript, runPackageJsonScript,
scanParentDirs, scanParentDirs,
FileBlob, FileBlob,
Meta,
} from '../src'; } from '../src';
async function expectBuilderError(promise: Promise<any>, pattern: string) { async function expectBuilderError(promise: Promise<any>, pattern: string) {
@@ -413,3 +414,39 @@ it('should detect pnpm Workspaces', async () => {
expect(result.cliType).toEqual('pnpm'); expect(result.cliType).toEqual('pnpm');
expect(result.lockfileVersion).toEqual(5.3); expect(result.lockfileVersion).toEqual(5.3);
}); });
it('should only invoke `runNpmInstall()` once per `package.json` file (serial)', async () => {
const meta: Meta = {};
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
const apiDir = path.join(fixture, 'api');
const run1 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run1).toEqual(true);
expect(
(meta.runNpmInstallSet as Set<string>).has(
path.join(fixture, 'package.json')
)
).toEqual(true);
const run2 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run2).toEqual(false);
const run3 = await runNpmInstall(fixture, [], undefined, meta);
expect(run3).toEqual(false);
});
it('should only invoke `runNpmInstall()` once per `package.json` file (parallel)', async () => {
const meta: Meta = {};
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
const apiDir = path.join(fixture, 'api');
const [run1, run2, run3] = await Promise.all([
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(fixture, [], undefined, meta),
]);
expect(run1).toEqual(true);
expect(run2).toEqual(false);
expect(run3).toEqual(false);
expect(
(meta.runNpmInstallSet as Set<string>).has(
path.join(fixture, 'package.json')
)
).toEqual(true);
});

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "24.1.0", "version": "24.1.1-canary.1",
"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",
@@ -43,11 +43,11 @@
"node": ">= 12" "node": ">= 12"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.15.1", "@vercel/build-utils": "2.15.2-canary.1",
"@vercel/go": "1.3.2", "@vercel/go": "1.3.3-canary.1",
"@vercel/node": "1.14.1", "@vercel/node": "1.14.2-canary.1",
"@vercel/python": "2.2.2", "@vercel/python": "2.2.3-canary.1",
"@vercel/ruby": "1.3.2", "@vercel/ruby": "1.3.3-canary.1",
"update-notifier": "4.1.0" "update-notifier": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
@@ -88,11 +88,10 @@
"@types/update-notifier": "5.1.0", "@types/update-notifier": "5.1.0",
"@types/which": "1.3.2", "@types/which": "1.3.2",
"@types/write-json-file": "2.2.1", "@types/write-json-file": "2.2.1",
"@vercel/client": "10.4.1", "@vercel/client": "10.4.2-canary.1",
"@vercel/fetch-retry": "5.0.3", "@vercel/fetch-retry": "5.0.3",
"@vercel/frameworks": "0.7.1", "@vercel/frameworks": "0.7.1",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@vercel/nft": "0.17.5",
"@zeit/fun": "0.11.2", "@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2", "@zeit/source-map-support": "0.6.2",
"ajv": "6.12.2", "ajv": "6.12.2",

View File

@@ -73,7 +73,7 @@ export default async function set(
if (args.length === 0) { if (args.length === 0) {
output.error( output.error(
`To ship to production, optionally configure your domains (${link( `To ship to production, optionally configure your domains (${link(
'https://vercel.com/docs/v2/custom-domains' 'https://vercel.link/domain-configuration'
)}) and run ${getCommandName(`--prod`)}.` )}) and run ${getCommandName(`--prod`)}.`
); );
return 1; return 1;

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/client", "name": "@vercel/client",
"version": "10.4.1", "version": "10.4.2-canary.1",
"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",
@@ -41,7 +41,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.15.1", "@vercel/build-utils": "2.15.2-canary.1",
"@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/go", "name": "@vercel/go",
"version": "1.3.2", "version": "1.3.3-canary.1",
"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",
@@ -24,7 +24,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": "2.15.1", "@vercel/build-utils": "2.15.2-canary.1",
"@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/node", "name": "@vercel/node",
"version": "1.14.1", "version": "1.14.2-canary.1",
"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",
@@ -32,9 +32,9 @@
"@types/cookie": "0.3.3", "@types/cookie": "0.3.3",
"@types/etag": "1.8.0", "@types/etag": "1.8.0",
"@types/test-listen": "1.1.0", "@types/test-listen": "1.1.0",
"@vercel/build-utils": "2.15.1", "@vercel/build-utils": "2.15.2-canary.1",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@vercel/nft": "0.17.5", "@vercel/nft": "0.18.1",
"content-type": "1.0.4", "content-type": "1.0.4",
"cookie": "0.4.0", "cookie": "0.4.0",
"etag": "1.8.1", "etag": "1.8.1",

View File

@@ -86,7 +86,6 @@ async function downloadInstallAndBundle({
meta, meta,
}: DownloadOptions) { }: DownloadOptions) {
const downloadedFiles = await download(files, workPath, meta); const downloadedFiles = await download(files, workPath, meta);
const entrypointFsDirname = join(workPath, dirname(entrypoint)); const entrypointFsDirname = join(workPath, dirname(entrypoint));
const nodeVersion = await getNodeVersion( const nodeVersion = await getNodeVersion(
entrypointFsDirname, entrypointFsDirname,
@@ -95,16 +94,7 @@ async function downloadInstallAndBundle({
meta meta
); );
const spawnOpts = getSpawnOptions(meta, nodeVersion); const spawnOpts = getSpawnOptions(meta, nodeVersion);
await runNpmInstall(entrypointFsDirname, [], spawnOpts, meta, nodeVersion);
if (meta.isDev) {
debug('Skipping dependency installation because dev mode is enabled');
} else {
const installTime = Date.now();
console.log('Installing dependencies...');
await runNpmInstall(entrypointFsDirname, [], spawnOpts, meta, nodeVersion);
debug(`Install complete [${Date.now() - installTime}ms]`);
}
const entrypointPath = downloadedFiles[entrypoint].fsPath; const entrypointPath = downloadedFiles[entrypoint].fsPath;
return { entrypointPath, entrypointFsDirname, nodeVersion, spawnOpts }; return { entrypointPath, entrypointFsDirname, nodeVersion, spawnOpts };
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/python", "name": "@vercel/python",
"version": "2.2.2", "version": "2.2.3-canary.1",
"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",
@@ -20,7 +20,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/execa": "^0.9.0", "@types/execa": "^0.9.0",
"@vercel/build-utils": "2.15.1", "@vercel/build-utils": "2.15.2-canary.1",
"@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

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/redwood", "name": "@vercel/redwood",
"version": "0.7.0", "version": "0.7.1-canary.1",
"main": "./dist/index.js", "main": "./dist/index.js",
"license": "MIT", "license": "MIT",
"homepage": "https://vercel.com/docs", "homepage": "https://vercel.com/docs",
@@ -18,7 +18,7 @@
"prepublishOnly": "node build.js" "prepublishOnly": "node build.js"
}, },
"dependencies": { "dependencies": {
"@vercel/nft": "0.17.5", "@vercel/nft": "0.18.1",
"@vercel/routing-utils": "1.13.1", "@vercel/routing-utils": "1.13.1",
"semver": "6.1.1" "semver": "6.1.1"
}, },
@@ -26,6 +26,6 @@
"@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": "2.15.1" "@vercel/build-utils": "2.15.2-canary.1"
} }
} }

View File

@@ -105,10 +105,7 @@ export const build: BuildV2 = async ({
console.log(`Skipping "install" command...`); console.log(`Skipping "install" command...`);
} }
} else { } else {
console.log('Installing dependencies...');
const installTime = Date.now();
await runNpmInstall(entrypointFsDirname, [], spawnOpts, meta, nodeVersion); await runNpmInstall(entrypointFsDirname, [], spawnOpts, meta, nodeVersion);
debug(`Install complete [${Date.now() - installTime}ms]`);
} }
if (meta.isDev) { if (meta.isDev) {

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.2", "version": "1.3.3-canary.1",
"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",
@@ -22,7 +22,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": "2.15.1", "@vercel/build-utils": "2.15.2-canary.1",
"@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

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/static-build", "name": "@vercel/static-build",
"version": "0.23.1", "version": "0.23.2-canary.1",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step", "homepage": "https://vercel.com/docs/build-step",
@@ -14,17 +14,28 @@
}, },
"scripts": { "scripts": {
"build": "node build", "build": "node build",
"test-unit": "jest --env node --verbose --runInBand --bail test/unit.test.js", "test-unit": "jest --env node --verbose --bail test/build.test.ts test/prepare-cache.test.ts",
"test-integration-once": "jest --env node --verbose --runInBand --bail test/integration.test.js", "test-integration-once": "jest --env node --verbose --runInBand --bail test/integration.test.js",
"prepublishOnly": "node build" "prepublishOnly": "node build"
}, },
"jest": {
"preset": "ts-jest/presets/default",
"testEnvironment": "node",
"globals": {
"ts-jest": {
"diagnostics": true,
"isolatedModules": true
}
}
},
"devDependencies": { "devDependencies": {
"@types/aws-lambda": "8.10.64", "@types/aws-lambda": "8.10.64",
"@types/cross-spawn": "6.0.0", "@types/cross-spawn": "6.0.0",
"@types/jest": "27.4.1",
"@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": "2.15.1", "@vercel/build-utils": "2.15.2-canary.1",
"@vercel/frameworks": "0.7.1", "@vercel/frameworks": "0.7.1",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "1.13.1", "@vercel/routing-utils": "1.13.1",

View File

@@ -4,7 +4,7 @@ import fetch from 'node-fetch';
import getPort from 'get-port'; import getPort from 'get-port';
import isPortReachable from 'is-port-reachable'; import isPortReachable from 'is-port-reachable';
import frameworks, { Framework } from '@vercel/frameworks'; import frameworks, { Framework } from '@vercel/frameworks';
import { ChildProcess, SpawnOptions } from 'child_process'; import type { ChildProcess, SpawnOptions } from 'child_process';
import { existsSync, readFileSync, statSync, readdirSync } from 'fs'; import { existsSync, readFileSync, statSync, readdirSync } from 'fs';
import { cpus } from 'os'; import { cpus } from 'os';
import { import {
@@ -30,14 +30,12 @@ import {
NowBuildError, NowBuildError,
scanParentDirs, scanParentDirs,
} from '@vercel/build-utils'; } from '@vercel/build-utils';
import { Route, Source } from '@vercel/routing-utils'; import type { Route, Source } from '@vercel/routing-utils';
import { import * as BuildOutputV1 from './utils/build-output-v1';
readBuildOutputDirectory, import * as BuildOutputV3 from './utils/build-output-v3';
readBuildOutputConfig,
} from './utils/read-build-output';
import * as GatsbyUtils from './utils/gatsby'; import * as GatsbyUtils from './utils/gatsby';
import * as NuxtUtils from './utils/nuxt'; import * as NuxtUtils from './utils/nuxt';
import { ImagesConfig, BuildConfig } from './utils/_shared'; import type { ImagesConfig, BuildConfig } from './utils/_shared';
const sleep = (n: number) => new Promise(resolve => setTimeout(resolve, n)); const sleep = (n: number) => new Promise(resolve => setTimeout(resolve, n));
@@ -255,7 +253,7 @@ async function fetchBinary(url: string, framework: string, version: string) {
throw new NowBuildError({ throw new NowBuildError({
code: 'STATIC_BUILD_BINARY_NOT_FOUND', code: 'STATIC_BUILD_BINARY_NOT_FOUND',
message: `Version ${version} of ${framework} does not exist. Please specify a different one.`, message: `Version ${version} of ${framework} does not exist. Please specify a different one.`,
link: 'https://vercel.com/docs/v2/build-step#framework-versioning', link: 'https://vercel.link/framework-versioning',
}); });
} }
await spawnAsync(`curl -sSL ${url} | tar -zx -C /usr/local/bin`, [], { await spawnAsync(`curl -sSL ${url} | tar -zx -C /usr/local/bin`, [], {
@@ -284,11 +282,8 @@ export const build: BuildV2 = async ({
); );
const pkg = getPkg(entrypoint, workPath); const pkg = getPkg(entrypoint, workPath);
const devScript = pkg ? getScriptName(pkg, 'dev', config) : null; const devScript = pkg ? getScriptName(pkg, 'dev', config) : null;
const framework = getFramework(config, pkg); const framework = getFramework(config, pkg);
const devCommand = getCommand('dev', pkg, config, framework); const devCommand = getCommand('dev', pkg, config, framework);
const buildCommand = getCommand('build', pkg, config, framework); const buildCommand = getCommand('build', pkg, config, framework);
const installCommand = getCommand('install', pkg, config, framework); const installCommand = getCommand('install', pkg, config, framework);
@@ -387,7 +382,7 @@ export const build: BuildV2 = async ({
Node.js will load 'false' as a string, not a boolean, so it's truthy still. Node.js will load 'false' as a string, not a boolean, so it's truthy still.
This is to ensure we don't accidentally break other packages that check This is to ensure we don't accidentally break other packages that check
if process.env.CI is true somewhere. if process.env.CI is true somewhere.
https://github.com/facebook/create-react-app/issues/2453 https://github.com/facebook/create-react-app/issues/2453
https://github.com/facebook/create-react-app/pull/2501 https://github.com/facebook/create-react-app/pull/2501
https://github.com/vercel/community/discussions/30 https://github.com/vercel/community/discussions/30
@@ -412,10 +407,7 @@ export const build: BuildV2 = async ({
if (!config.zeroConfig) { if (!config.zeroConfig) {
debug('Detected "builds" - not zero config'); debug('Detected "builds" - not zero config');
printInstall();
const installTime = Date.now();
await runNpmInstall(entrypointDir, [], spawnOpts, meta, nodeVersion); await runNpmInstall(entrypointDir, [], spawnOpts, meta, nodeVersion);
debug(`Install complete [${Date.now() - installTime}ms]`);
isNpmInstall = true; isNpmInstall = true;
} else if (typeof installCommand === 'string') { } else if (typeof installCommand === 'string') {
if (installCommand.trim()) { if (installCommand.trim()) {
@@ -487,11 +479,7 @@ export const build: BuildV2 = async ({
isPipInstall = true; isPipInstall = true;
} }
if (pkg) { if (pkg) {
console.log('Detected package.json');
printInstall();
const installTime = Date.now();
await runNpmInstall(entrypointDir, [], spawnOpts, meta, nodeVersion); await runNpmInstall(entrypointDir, [], spawnOpts, meta, nodeVersion);
debug(`Install complete [${Date.now() - installTime}ms]`);
isNpmInstall = true; isNpmInstall = true;
} }
} }
@@ -637,6 +625,30 @@ export const build: BuildV2 = async ({
const outputDirPrefix = path.join(workPath, path.dirname(entrypoint)); const outputDirPrefix = path.join(workPath, path.dirname(entrypoint));
// If the Build Command or Framework output files according to the
// Build Output v3 API, then stop processing here in `static-build`
// since the output is already in its final form.
const buildOutputPath = await BuildOutputV3.getBuildOutputDirectory(
outputDirPrefix
);
if (buildOutputPath) {
// Ensure that `vercel build` is being used for this Deployment
if (!meta.cliVersion) {
let buildCommandName: string;
if (buildCommand) buildCommandName = `"${buildCommand}"`;
else if (framework) buildCommandName = framework.name;
else buildCommandName = 'the "build" script';
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,
buildOutputPath,
};
}
if (framework) { if (framework) {
const outputDirName = config.outputDirectory const outputDirName = config.outputDirectory
? config.outputDirectory ? config.outputDirectory
@@ -656,7 +668,7 @@ export const build: BuildV2 = async ({
} }
} }
const extraOutputs = await readBuildOutputDirectory({ const extraOutputs = await BuildOutputV1.readBuildOutputDirectory({
workPath, workPath,
nodeVersion, nodeVersion,
}); });
@@ -750,7 +762,7 @@ export const prepareCache: PrepareCache = async ({
const cacheFiles: Files = {}; const cacheFiles: Files = {};
// File System API v1 cache files // File System API v1 cache files
const buildConfig = await readBuildOutputConfig<BuildConfig>({ const buildConfig = await BuildOutputV1.readBuildOutputConfig<BuildConfig>({
workPath, workPath,
configFileName: 'build.json', configFileName: 'build.json',
}); });

View File

@@ -0,0 +1,23 @@
import { join } from 'path';
import { promises as fs } from 'fs';
const BUILD_OUTPUT_DIR = '.vercel/output';
/**
* Returns the path to the Build Output v3 directory when the
* `config.json` file was created by the framework / build script,
* or `undefined` if the framework did not create the v3 output.
*/
export async function getBuildOutputDirectory(
path: string
): Promise<string | undefined> {
try {
const outputDir = join(path, BUILD_OUTPUT_DIR);
const configPath = join(outputDir, 'config.json');
await fs.stat(configPath);
return outputDir;
} catch (err: any) {
if (err.code !== 'ENOENT') throw err;
}
return undefined;
}

View File

@@ -30,9 +30,7 @@ export async function injectVercelAnalyticsPlugin(dir: string) {
`Injecting Gatsby.js analytics plugin "${gatsbyPluginPackageName}" to \`${gatsbyConfigPath}\`` `Injecting Gatsby.js analytics plugin "${gatsbyPluginPackageName}" to \`${gatsbyConfigPath}\``
); );
const pkgJson: DeepWriteable<PackageJson> = (await readPackageJson( const pkgJson = (await readPackageJson(dir)) as DeepWriteable<PackageJson>;
dir
)) as DeepWriteable<PackageJson>;
if (!pkgJson.dependencies) { if (!pkgJson.dependencies) {
pkgJson.dependencies = {}; pkgJson.dependencies = {};
} }

View File

@@ -0,0 +1,7 @@
const fs = require('fs');
fs.mkdirSync('.vercel/output/static', { recursive: true });
fs.writeFileSync('.vercel/output/config.json', '{}');
fs.writeFileSync(
'.vercel/output/static/index.html',
'<h1>Build Output API</h1>'
);

View File

@@ -0,0 +1,7 @@
{
"name": "09-build-output-v3",
"private": true,
"scripts": {
"build": "node build.js"
}
}

View File

@@ -0,0 +1,54 @@
import path from 'path';
import { build } from '../src';
describe('build()', () => {
it('should detect Builder Output v3', async () => {
const workPath = path.join(
__dirname,
'build-fixtures',
'09-build-output-v3'
);
const buildResult = await build({
files: {},
entrypoint: 'package.json',
workPath,
config: {},
meta: {
skipDownload: true,
cliVersion: '0.0.0',
},
});
if ('output' in buildResult) {
throw new Error('Unexpected `output` in build result');
}
expect(buildResult.buildOutputVersion).toEqual(3);
expect(buildResult.buildOutputPath).toEqual(
path.join(workPath, '.vercel/output')
);
});
it('should throw an Error with Builder Output v3 without `vercel build`', async () => {
let err;
const workPath = path.join(
__dirname,
'build-fixtures',
'09-build-output-v3'
);
try {
await build({
files: {},
entrypoint: 'package.json',
workPath,
config: {},
meta: {
skipDownload: true,
},
});
} catch (_err: any) {
err = _err;
}
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.`
);
});
});

View File

@@ -1,12 +1,13 @@
const { prepareCache } = require('../dist'); import path from 'path';
const path = require('path'); import { prepareCache } from '../src';
describe('prepareCache', () => { describe('prepareCache()', () => {
test('should cache node_modules and .shadow-cljs', async () => { test('should cache node_modules and .shadow-cljs', async () => {
const files = await prepareCache({ const files = await prepareCache({
config: { zeroConfig: true }, config: { zeroConfig: true },
workPath: path.resolve(__dirname, './cache-fixtures/default'), workPath: path.resolve(__dirname, './cache-fixtures/default'),
entrypoint: 'index.js', entrypoint: 'index.js',
files: {},
}); });
expect(files['node_modules/file']).toBeDefined(); expect(files['node_modules/file']).toBeDefined();
@@ -19,6 +20,7 @@ describe('prepareCache', () => {
config: { zeroConfig: true }, config: { zeroConfig: true },
workPath: path.resolve(__dirname, './cache-fixtures/withCacheConfig'), workPath: path.resolve(__dirname, './cache-fixtures/withCacheConfig'),
entrypoint: 'index.js', entrypoint: 'index.js',
files: {},
}); });
expect(files['node_modules/file']).toBeUndefined(); expect(files['node_modules/file']).toBeUndefined();
@@ -32,6 +34,7 @@ describe('prepareCache', () => {
config: { zeroConfig: true }, config: { zeroConfig: true },
workPath: path.resolve(__dirname, './cache-fixtures/gatsby'), workPath: path.resolve(__dirname, './cache-fixtures/gatsby'),
entrypoint: 'package.json', entrypoint: 'package.json',
files: {},
}); });
expect(files['node_modules/file2']).toBeDefined(); expect(files['node_modules/file2']).toBeDefined();
@@ -45,6 +48,7 @@ describe('prepareCache', () => {
config: { zeroConfig: true, framework: 'jekyll' }, config: { zeroConfig: true, framework: 'jekyll' },
workPath: path.resolve(__dirname, './cache-fixtures/jekyll'), workPath: path.resolve(__dirname, './cache-fixtures/jekyll'),
entrypoint: 'Gemfile', entrypoint: 'Gemfile',
files: {},
}); });
expect(files['vendor/bundle/b1']).toBeDefined(); expect(files['vendor/bundle/b1']).toBeDefined();

View File

@@ -0,0 +1,4 @@
{
"extends": "../tsconfig.json",
"include": ["*.test.ts"]
}

View File

@@ -11,7 +11,7 @@
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"outDir": "dist", "outDir": "dist",
"types": ["node"], "types": ["node", "jest"],
"strict": true, "strict": true,
"target": "es2018" "target": "es2018"
}, },

View File

@@ -2180,6 +2180,14 @@
jest-diff "^27.0.0" jest-diff "^27.0.0"
pretty-format "^27.0.0" pretty-format "^27.0.0"
"@types/jest@27.4.1":
version "27.4.1"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.1.tgz#185cbe2926eaaf9662d340cc02e548ce9e11ab6d"
integrity sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw==
dependencies:
jest-matcher-utils "^27.0.0"
pretty-format "^27.0.0"
"@types/js-yaml@3.12.1": "@types/js-yaml@3.12.1":
version "3.12.1" version "3.12.1"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.1.tgz#5c6f4a1eabca84792fbd916f0cb40847f123c656" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.1.tgz#5c6f4a1eabca84792fbd916f0cb40847f123c656"
@@ -2560,10 +2568,10 @@
resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.24.0.tgz#a2e8783a185caa99b5d8961a57dfc9665de16296" resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.24.0.tgz#a2e8783a185caa99b5d8961a57dfc9665de16296"
integrity sha512-crqItMcIwCkvdXY/V3/TzrHJQx6nbIaRqE1cOopJhgGX6izvNov40SmD//nS5flfEvdK54YGjwVVq+zG6crjOg== integrity sha512-crqItMcIwCkvdXY/V3/TzrHJQx6nbIaRqE1cOopJhgGX6izvNov40SmD//nS5flfEvdK54YGjwVVq+zG6crjOg==
"@vercel/nft@0.17.5": "@vercel/nft@0.18.1":
version "0.17.5" version "0.18.1"
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.17.5.tgz#eab288a3786b8bd6fc08c0ef0b70d162984d1643" resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.18.1.tgz#d1681f7fd538168553d8df19fc3761ca643ebb73"
integrity sha512-6n4uXmfkcHAmkI4rJlwFJb8yvWuH6uDOi5qme0yGC1B/KmWJ66dERupdAj9uj7eEmgM7N3bKNY5zOYE7cKZE1g== integrity sha512-i2zmXs8ueqCe/dmQ0fzQk9MmXoqdqxR1ZsxPN8poHZfc3NQES1WTEsXkEoaL+RV5BKZi04lNpkoGwtqcxERAOQ==
dependencies: dependencies:
"@mapbox/node-pre-gyp" "^1.0.5" "@mapbox/node-pre-gyp" "^1.0.5"
acorn "^8.6.0" acorn "^8.6.0"
@@ -4677,6 +4685,11 @@ diff-sequences@^27.0.6:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723"
integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ== integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==
diff-sequences@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==
diff@^4.0.1: diff@^4.0.1:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
@@ -7160,6 +7173,16 @@ jest-diff@^27.3.1:
jest-get-type "^27.3.1" jest-get-type "^27.3.1"
pretty-format "^27.3.1" pretty-format "^27.3.1"
jest-diff@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def"
integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==
dependencies:
chalk "^4.0.0"
diff-sequences "^27.5.1"
jest-get-type "^27.5.1"
pretty-format "^27.5.1"
jest-docblock@^27.0.6: jest-docblock@^27.0.6:
version "27.0.6" version "27.0.6"
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.0.6.tgz#cc78266acf7fe693ca462cbbda0ea4e639e4e5f3" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.0.6.tgz#cc78266acf7fe693ca462cbbda0ea4e639e4e5f3"
@@ -7213,6 +7236,11 @@ jest-get-type@^27.3.1:
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.3.1.tgz#a8a2b0a12b50169773099eee60a0e6dd11423eff" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.3.1.tgz#a8a2b0a12b50169773099eee60a0e6dd11423eff"
integrity sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg== integrity sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg==
jest-get-type@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1"
integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==
jest-haste-map@^27.3.1: jest-haste-map@^27.3.1:
version "27.3.1" version "27.3.1"
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.3.1.tgz#7656fbd64bf48bda904e759fc9d93e2c807353ee" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.3.1.tgz#7656fbd64bf48bda904e759fc9d93e2c807353ee"
@@ -7265,6 +7293,16 @@ jest-leak-detector@^27.3.1:
jest-get-type "^27.3.1" jest-get-type "^27.3.1"
pretty-format "^27.3.1" pretty-format "^27.3.1"
jest-matcher-utils@^27.0.0:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab"
integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==
dependencies:
chalk "^4.0.0"
jest-diff "^27.5.1"
jest-get-type "^27.5.1"
pretty-format "^27.5.1"
jest-matcher-utils@^27.3.1: jest-matcher-utils@^27.3.1:
version "27.3.1" version "27.3.1"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.3.1.tgz#257ad61e54a6d4044e080d85dbdc4a08811e9c1c" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.3.1.tgz#257ad61e54a6d4044e080d85dbdc4a08811e9c1c"
@@ -9540,6 +9578,15 @@ pretty-format@^27.3.1:
ansi-styles "^5.0.0" ansi-styles "^5.0.0"
react-is "^17.0.1" react-is "^17.0.1"
pretty-format@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
dependencies:
ansi-regex "^5.0.1"
ansi-styles "^5.0.0"
react-is "^17.0.1"
pretty-ms@^5.0.0: pretty-ms@^5.0.0:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-5.1.0.tgz#b906bdd1ec9e9799995c372e2b1c34f073f95384" resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-5.1.0.tgz#b906bdd1ec9e9799995c372e2b1c34f073f95384"