Compare commits

..

12 Commits

Author SHA1 Message Date
Nathan Rajlich
547e88228e Publish Stable
- @vercel/build-utils@4.2.1
 - vercel@25.2.3
 - @vercel/client@12.0.3
 - @vercel/go@2.0.3
 - @vercel/next@3.1.2
 - @vercel/node@2.3.3
 - @vercel/python@3.0.3
 - @vercel/redwood@1.0.4
 - @vercel/remix@1.0.4
 - @vercel/ruby@1.3.11
 - @vercel/static-build@1.0.3
2022-06-30 12:24:13 -07:00
Luc Leray
9bfb5dd535 [build-utils] Handle npm bin exit code 7 (#8058)
In some rare cases, `npm bin` exits with code 7, but still outputs the right bin path.

To reproduce, try:
```
npm init -y
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
vc
# enter "echo build" for the build command, leave the other configuration as default
```

The build will fail with `Error: Command exited with 7` because `npm bin` fails with code 7, for some reason.

In this PR, we do 2 things:
(1) Ignore exit codes from `npm bin`. It still outputs the right path when it exits with code 7 so we just read the output and check if it's a valid path.
(2) Throw a more specific error message when `npm bin` fails to give us the bin path. The current error was hard to debug because it looked like it was coming from the install commmand. We can do better by emitting a custom error.

Alternative considered for (2): Do not throw errors. If `npm bin` fails, emit a warning and let the build continue.

Related Issues:
- https://github.com/vercel/customer-issues/issues/585 (internal)
2022-06-30 17:27:52 +00:00
Nathan Rajlich
81ea84fae8 [cli] Fix vc build lambda serialization when there's a broken symlink (#8050)
There's some cleanup directory walking logic that was choking when
a Lambda outputs a file with a broken symlink. We shouldn't need to
traverse into those directories in the case of a symlink anyways, so use
`lstat()` instead of `stat()` to prevent that filesystem call from
throwing an error.
2022-06-29 16:04:31 -07:00
Nathan Rajlich
fa8bf07be4 [cli] Add Client#stdin / Client#stdout (#8039)
This will allow for mockability of the input streams (i.e. prompts)
for CLI commands in unit tests.

**Example:**

```typescript
import confirm from '../../src/util/input/confirm';
import { client } from '../mocks/client';

describe('MockClient', () => {
  it('should mock `confirm()`', async () => {
    const confirmedPromise = confirm(client, 'Do the thing?', false);

    client.stdin.write('yes\n');

    const confirmed = await confirmedPromise;
    expect(confirmed).toEqual(true);
  });
});
```
2022-06-29 16:03:56 -07:00
JJ Kasper
cc9dce73ad [next] Ensure uncompressed limit is correct (#8049)
* Revert "Revert "[next] Update max size warning to handle initial layer better" (#8047)"

This reverts commit 8c62de16ce.

* Ensure uncompressed limit is correct

* apply suggestion
2022-06-29 15:13:15 -05:00
Sean Massa
bba7cbd411 [cli][dev] fix: creating "api/some-func.js" after "vc dev" now works (#8041)
If there is no `api` directory, then you run `vc dev`, then you create a new function `api/some-func.js`, then this file would not be served as a new function.

This was being caused by incomplete "new file" handling logic. This PR ensures that the proper detection is done in each new file (`getVercelConfig`) that populates key properties (`apiDir`, `apiExtensions`, and extensionless `files`) for determining when a file is used to serve a request.
2022-06-29 18:37:48 +00:00
Steven
9a3739bebd Publish Stable
- vercel@25.2.2
 - @vercel/next@3.1.1
 - @vercel/node@2.3.2
 - @vercel/redwood@1.0.3
 - @vercel/remix@1.0.3
2022-06-29 09:27:34 -04:00
Gal Schlezinger
8c62de16ce Revert "[next] Update max size warning to handle initial layer better" (#8047)
Revert "[next] Update max size warning to handle initial layer better (#8013)"

This reverts commit f20703b15d.
2022-06-29 09:26:29 -04:00
Steven
e9333988d7 [next][node][redwood][remix] Bump @vercel/nft to 0.20.1 (#8042)
- https://github.com/vercel/nft/releases/tag/0.20.0
- https://github.com/vercel/nft/releases/tag/0.20.1
2022-06-29 00:21:14 +00:00
Nathan Rajlich
fb001ce7eb [tests] Remove TODO comments from Middleware matchers vc dev test (#8037) 2022-06-28 18:26:56 +00:00
Sean Massa
b399fe7037 Publish Stable
- vercel@25.2.1
 - @vercel/node@2.3.1
2022-06-28 12:04:57 -05:00
Sean Massa
88385b3c84 [cli][node] switch to esbuild for compiling edge functions (#8032) 2022-06-28 11:27:56 -05:00
67 changed files with 723 additions and 367 deletions

View File

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

View File

@@ -61,6 +61,13 @@ export interface SpawnOptionsExtended extends SpawnOptions {
* Pretty formatted command that is being spawned for logging purposes.
*/
prettyCommand?: string;
/**
* Returns instead of throwing an error when the process exits with a
* non-0 exit code. When relevant, the returned object will include
* the error code, stdout and stderr.
*/
ignoreNon0Exit?: boolean;
}
export function spawnAsync(
@@ -79,7 +86,7 @@ export function spawnAsync(
child.on('error', reject);
child.on('close', (code, signal) => {
if (code === 0) {
if (code === 0 || opts.ignoreNon0Exit) {
return resolve();
}
@@ -123,24 +130,24 @@ export function execAsync(
child.on('error', reject);
child.on('close', (code, signal) => {
if (code !== 0) {
const cmd = opts.prettyCommand
? `Command "${opts.prettyCommand}"`
: 'Command';
return reject(
new NowBuildError({
code: `BUILD_UTILS_EXEC_${code || signal}`,
message: `${cmd} exited with ${code || signal}`,
})
);
if (code === 0 || opts.ignoreNon0Exit) {
return resolve({
code,
stdout: Buffer.concat(stdoutList).toString(),
stderr: Buffer.concat(stderrList).toString(),
});
}
return resolve({
code,
stdout: Buffer.concat(stdoutList).toString(),
stderr: Buffer.concat(stderrList).toString(),
});
const cmd = opts.prettyCommand
? `Command "${opts.prettyCommand}"`
: 'Command';
return reject(
new NowBuildError({
code: `BUILD_UTILS_EXEC_${code || signal}`,
message: `${cmd} exited with ${code || signal}`,
})
);
});
}
);
@@ -166,9 +173,30 @@ export async function execCommand(command: string, options: SpawnOptions = {}) {
return true;
}
export async function getNodeBinPath({ cwd }: { cwd: string }) {
const { stdout } = await execAsync('npm', ['bin'], { cwd });
return stdout.trim();
export async function getNodeBinPath({
cwd,
}: {
cwd: string;
}): Promise<string | undefined> {
const { code, stdout, stderr } = await execAsync('npm', ['bin'], {
cwd,
prettyCommand: 'npm bin',
// in some rare cases, we saw `npm bin` exit with a non-0 code, but still
// output the right bin path, so we ignore the exit code
ignoreNon0Exit: true,
});
const nodeBinPath = stdout.trim();
if (path.isAbsolute(nodeBinPath)) {
return nodeBinPath;
}
throw new NowBuildError({
code: `BUILD_UTILS_GET_NODE_BIN_PATH`,
message: `Running \`npm bin\` failed to return a valid bin path (code=${code}, stdout=${stdout}, stderr=${stderr})`,
});
}
async function chmodPlusX(fsPath: string) {

View File

@@ -0,0 +1,29 @@
import { execAsync, NowBuildError } from '../src';
it('should execute a command', async () => {
const { code, stdout, stderr } = await execAsync('echo', ['hello']);
expect(code).toBe(0);
expect(stdout).toContain('hello');
expect(stderr).toBe('');
});
it('should throw if the command exits with non-0 code', async () => {
await expect(execAsync('find', ['unknown-file'])).rejects.toBeInstanceOf(
NowBuildError
);
});
it('should return if the command exits with non-0 code and ignoreNon0Exit=true', async () => {
const { code, stdout, stderr } = await execAsync('find', ['unknown-file'], {
ignoreNon0Exit: true,
});
expect(code).toBe(process.platform === 'win32' ? 2 : 1);
expect(stdout).toBe('');
expect(stderr).toContain(
process.platform === 'win32'
? 'Parameter format not correct'
: 'No such file or directory'
);
});

View File

@@ -0,0 +1,21 @@
import { spawnAsync, NowBuildError } from '../src';
it('should execute a command', async () => {
// should resolve (it doesn't return anything, so it resolves with "undefined")
await expect(spawnAsync('echo', ['hello'])).resolves.toBeUndefined();
});
it('should throw if the command exits with non-0 code', async () => {
await expect(spawnAsync('find', ['unknown-file'])).rejects.toBeInstanceOf(
NowBuildError
);
});
it('should return if the command exits with non-0 code and ignoreNon0Exit=true', async () => {
// should resolve (it doesn't return anything, so it resolves with "undefined")
await expect(
spawnAsync('find', ['unknown-file'], {
ignoreNon0Exit: true,
})
).resolves.toBeUndefined();
});

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "25.2.0",
"version": "25.2.3",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -42,15 +42,15 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "4.2.0",
"@vercel/go": "2.0.2",
"@vercel/next": "3.1.0",
"@vercel/node": "2.3.0",
"@vercel/python": "3.0.2",
"@vercel/redwood": "1.0.2",
"@vercel/remix": "1.0.2",
"@vercel/ruby": "1.3.10",
"@vercel/static-build": "1.0.2",
"@vercel/build-utils": "4.2.1",
"@vercel/go": "2.0.3",
"@vercel/next": "3.1.2",
"@vercel/node": "2.3.3",
"@vercel/python": "3.0.3",
"@vercel/redwood": "1.0.4",
"@vercel/remix": "1.0.4",
"@vercel/ruby": "1.3.11",
"@vercel/static-build": "1.0.3",
"update-notifier": "5.1.0"
},
"devDependencies": {
@@ -95,7 +95,7 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.0.2",
"@vercel/client": "12.0.3",
"@vercel/frameworks": "1.0.2",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",

View File

@@ -10,7 +10,6 @@ import confirm from '../../util/input/confirm';
import findAliasByAliasOrId from '../../util/alias/find-alias-by-alias-or-id';
import { Alias } from '../../types';
import { Output } from '../../util/output';
import { isValidName } from '../../util/is-valid-name';
import { getCommandName } from '../../util/pkg-name';
@@ -71,7 +70,7 @@ export default async function rm(
}
const removeStamp = stamp();
if (!opts['--yes'] && !(await confirmAliasRemove(output, alias))) {
if (!opts['--yes'] && !(await confirmAliasRemove(client, alias))) {
output.log('Aborted');
return 0;
}
@@ -85,7 +84,7 @@ export default async function rm(
return 0;
}
async function confirmAliasRemove(output: Output, alias: Alias) {
async function confirmAliasRemove(client: Client, alias: Alias) {
const srcUrl = alias.deployment
? chalk.underline(alias.deployment.url)
: null;
@@ -104,7 +103,7 @@ async function confirmAliasRemove(output: Output, alias: Alias) {
}
);
output.log(`The following alias will be removed permanently`);
output.print(` ${tbl}\n`);
return confirm(chalk.red('Are you sure?'), false);
client.output.log(`The following alias will be removed permanently`);
client.output.print(` ${tbl}\n`);
return confirm(client, chalk.red('Are you sure?'), false);
}

View File

@@ -187,6 +187,7 @@ export default async client => {
if (cardId) {
const label = `Are you sure that you to set this card as the default?`;
const confirmation = await promptBool(label, {
...client,
trailing: '\n',
});
@@ -262,7 +263,7 @@ export default async client => {
// typed `vercel billing rm <some-id>`) is valid
if (cardId) {
const label = `Are you sure that you want to remove this card?`;
const confirmation = await promptBool(label);
const confirmation = await promptBool(label, client);
if (!confirmation) {
console.log('Aborted');
break;

View File

@@ -140,6 +140,7 @@ export default async function main(client: Client): Promise<number> {
}
confirmed = await confirm(
client,
`No Project Settings found locally. Run ${cli.getCommandName(
'pull'
)} for retrieving them?`,

View File

@@ -165,7 +165,7 @@ export default async (client: Client) => {
const quiet = !isTTY;
// check paths
const pathValidation = await validatePaths(output, paths);
const pathValidation = await validatePaths(client, paths);
if (!pathValidation.valid) {
return pathValidation.exitCode;
@@ -243,6 +243,7 @@ export default async (client: Client) => {
const shouldStartSetup =
autoConfirm ||
(await confirm(
client,
`Set up and deploy ${chalk.cyan(`${toHumanPath(path)}`)}?`,
true
));
@@ -287,7 +288,7 @@ export default async (client: Client) => {
if (typeof projectOrNewProjectName === 'string') {
newProjectName = projectOrNewProjectName;
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
rootDirectory = await inputRootDirectory(client, path, autoConfirm);
} else {
project = projectOrNewProjectName;
rootDirectory = project.rootDirectory;
@@ -521,7 +522,7 @@ export default async (client: Client) => {
}
const settings = await editProjectSettings(
output,
client,
projectSettings,
framework,
false,

View File

@@ -46,7 +46,7 @@ export default async function add(
const addStamp = stamp();
const { domain, data: argData } = parsedParams;
const data = await getDNSData(output, argData);
const data = await getDNSData(client, argData);
if (!data) {
output.log(`Aborted`);
return 1;

View File

@@ -87,7 +87,8 @@ export default async function buy(
!(await promptBool(
`Buy now for ${chalk.bold(`$${price}`)} (${`${period}yr${
period > 1 ? 's' : ''
}`})?`
}`})?`,
client
))
) {
return 0;
@@ -99,7 +100,7 @@ export default async function buy(
: `Auto renew every ${renewalPrice.period} years for ${chalk.bold(
`$${price}`
)}?`,
{ defaultValue: true }
{ ...client, defaultValue: true }
);
let buyResult;

View File

@@ -77,7 +77,8 @@ export default async function move(
!(await promptBool(
`Are you sure you want to move ${param(domainName)} to ${param(
destination
)}?`
)}?`,
client
))
) {
output.log('Aborted');
@@ -95,7 +96,8 @@ export default async function move(
);
if (
!(await promptBool(
`Are you sure you want to move ${param(domainName)}?`
`Are you sure you want to move ${param(domainName)}?`,
client
))
) {
output.log('Aborted');

View File

@@ -92,7 +92,10 @@ export default async function rm(
const skipConfirmation = opts['--yes'] || false;
if (
!skipConfirmation &&
!(await promptBool(`Are you sure you want to remove ${param(domainName)}?`))
!(await promptBool(
`Are you sure you want to remove ${param(domainName)}?`,
client
))
) {
output.log('Aborted');
return 0;
@@ -230,7 +233,7 @@ async function removeDomain(
if (
!skipConfirmation &&
!(await promptBool(`Remove conflicts associated with domain?`))
!(await promptBool(`Remove conflicts associated with domain?`, client))
) {
output.log('Aborted');
return 0;

View File

@@ -81,7 +81,8 @@ export default async function transferIn(
const shouldTransfer = await promptBool(
transferPolicy === 'no-change'
? `Transfer now for ${chalk.bold(`$${price}`)}?`
: `Transfer now with 1yr renewal for ${chalk.bold(`$${price}`)}?`
: `Transfer now with 1yr renewal for ${chalk.bold(`$${price}`)}?`,
client
);
if (!shouldTransfer) {
return 0;

View File

@@ -31,7 +31,7 @@ export default async function add(
// improve the way we show inquirer prompts
require('../../util/input/patch-inquirer');
const stdInput = await readStandardInput();
const stdInput = await readStandardInput(client.stdin);
let [envName, envTargetArg, envGitBranch] = args;
if (args.length > 3) {

View File

@@ -74,6 +74,7 @@ export default async function pull(
exists &&
!skipConfirmation &&
!(await confirm(
client,
`Found existing file ${param(filename)}. Do you want to overwrite?`,
false
))

View File

@@ -104,6 +104,7 @@ export default async function rm(
if (
!skipConfirmation &&
!(await confirm(
client,
`Removing Environment Variable ${param(env.key)} from ${formatEnvTarget(
env
)} in Project ${chalk.bold(project.name)}. Are you sure?`,

View File

@@ -65,7 +65,7 @@ export default async function init(
return extractExample(client, name, dir, force, 'v1');
}
const found = await guess(exampleList, name);
const found = await guess(client, exampleList, name);
if (typeof found === 'string') {
return extractExample(client, found, dir, force);
@@ -194,7 +194,7 @@ function prepareFolder(cwd: string, folder: string, force?: boolean) {
/**
* Guess which example user try to init
*/
async function guess(exampleList: string[], name: string) {
async function guess(client: Client, exampleList: string[], name: string) {
const GuessError = new Error(
`No example found for ${chalk.bold(name)}, run ${getCommandName(
`init`
@@ -208,7 +208,7 @@ async function guess(exampleList: string[], name: string) {
const found = didYouMean(name, exampleList, 0.7);
if (typeof found === 'string') {
if (await promptBool(`Did you mean ${chalk.bold(found)}?`)) {
if (await promptBool(`Did you mean ${chalk.bold(found)}?`, client)) {
return found;
}
} else {

View File

@@ -387,6 +387,8 @@ const main = async () => {
// Shared API `Client` instance for all sub-commands to utilize
client = new Client({
apiUrl,
stdin: process.stdin,
stdout: process.stdout,
output,
config,
authConfig,

View File

@@ -385,8 +385,14 @@ export async function* findDirs(
}
for (const path of paths) {
const abs = join(dir, path);
const s = await fs.stat(abs);
if (s.isDirectory()) {
let stat: fs.Stats;
try {
stat = await fs.lstat(abs);
} catch (err: any) {
if (err.code === 'ENOENT') continue;
throw err;
}
if (stat.isDirectory()) {
if (path === name) {
yield relative(root, abs);
} else {

View File

@@ -32,6 +32,8 @@ export interface ClientOptions {
argv: string[];
apiUrl: string;
authConfig: AuthConfig;
stdin: NodeJS.ReadStream;
stdout: NodeJS.WriteStream;
output: Output;
config: GlobalConfig;
localConfig?: VercelConfig;
@@ -45,6 +47,8 @@ export default class Client extends EventEmitter {
argv: string[];
apiUrl: string;
authConfig: AuthConfig;
stdin: NodeJS.ReadStream;
stdout: NodeJS.WriteStream;
output: Output;
config: GlobalConfig;
localConfig?: VercelConfig;
@@ -55,6 +59,8 @@ export default class Client extends EventEmitter {
this.argv = opts.argv;
this.apiUrl = opts.apiUrl;
this.authConfig = opts.authConfig;
this.stdin = opts.stdin;
this.stdout = opts.stdout;
this.output = opts.output;
this.config = opts.config;
this.localConfig = opts.localConfig;

View File

@@ -329,6 +329,8 @@ export default class DevServer {
): Promise<void> {
const name = relative(this.cwd, fsPath);
try {
await this.getVercelConfig();
this.files[name] = await FileFsRef.fromFsPath({ fsPath });
const extensionless = this.getExtensionlessFile(name);
if (extensionless) {

View File

@@ -2,26 +2,29 @@ import chalk from 'chalk';
import { DNSRecordData } from '../../types';
import textInput from '../input/text';
import promptBool from '../input/prompt-bool';
import { Output } from '../output';
import Client from '../client';
const RECORD_TYPES = ['A', 'AAAA', 'ALIAS', 'CAA', 'CNAME', 'MX', 'SRV', 'TXT'];
export default async function getDNSData(
output: Output,
client: Client,
data: null | DNSRecordData
): Promise<DNSRecordData | null> {
if (data) {
return data;
}
const { output } = client;
try {
// first ask for type, branch from there
const possibleTypes = new Set(RECORD_TYPES);
const type = (await textInput({
label: `- Record type (${RECORD_TYPES.join(', ')}): `,
validateValue: (v: string) =>
Boolean(v && possibleTypes.has(v.trim().toUpperCase()))
}))
const type = (
await textInput({
label: `- Record type (${RECORD_TYPES.join(', ')}): `,
validateValue: (v: string) =>
Boolean(v && possibleTypes.has(v.trim().toUpperCase())),
})
)
.trim()
.toUpperCase();
@@ -39,7 +42,7 @@ export default async function getDNSData(
target
)}.`
);
return (await verifyData())
return (await verifyData(client))
? {
name,
type,
@@ -47,8 +50,8 @@ export default async function getDNSData(
priority,
weight,
port,
target
}
target,
},
}
: null;
}
@@ -61,23 +64,23 @@ export default async function getDNSData(
`${mxPriority}`
)} ${chalk.cyan(value)}`
);
return (await verifyData())
return (await verifyData(client))
? {
name,
type,
value,
mxPriority
mxPriority,
}
: null;
}
const value = await getTrimmedString(`- ${type} value: `);
output.log(`${chalk.cyan(name)} ${chalk.bold(type)} ${chalk.cyan(value)}`);
return (await verifyData())
return (await verifyData(client))
? {
name,
type,
value
value,
}
: null;
} catch (error) {
@@ -85,13 +88,13 @@ export default async function getDNSData(
}
}
async function verifyData() {
return promptBool('Is this correct?');
async function verifyData(client: Client) {
return promptBool('Is this correct?', client);
}
async function getRecordName(type: string) {
const input = await textInput({
label: `- ${type} name: `
label: `- ${type} name: `,
});
return input === '@' ? '' : input;
}
@@ -100,14 +103,14 @@ async function getNumber(label: string) {
return Number(
await textInput({
label,
validateValue: v => Boolean(v && Number(v))
validateValue: v => Boolean(v && Number(v)),
})
);
}
async function getTrimmedString(label: string) {
const res = await textInput({
label,
validateValue: v => Boolean(v && v.trim().length > 0)
validateValue: v => Boolean(v && v.trim().length > 0),
});
return res.trim();
}

View File

@@ -54,7 +54,8 @@ export default async function purchaseDomainIfAvailable(
!(await promptBool(
`Buy ${chalk.underline(domain)} for ${chalk.bold(
`$${price}`
)} (${plural('yr', period, true)})?`
)} (${plural('yr', period, true)})?`,
client
))
) {
output.print(eraseLines(1));

View File

@@ -1,12 +1,19 @@
import inquirer from 'inquirer';
import Client from '../client';
export default async function confirm(
client: Client,
message: string,
preferred: boolean
): Promise<boolean> {
require('./patch-inquirer');
const answers = await inquirer.prompt({
const prompt = inquirer.createPromptModule({
input: client.stdin,
output: client.stdout,
});
const answers = await prompt({
type: 'confirm',
name: 'value',
message,

View File

@@ -1,8 +1,8 @@
import inquirer from 'inquirer';
import confirm from './confirm';
import chalk from 'chalk';
import { Output } from '../output';
import frameworkList, { Framework } from '@vercel/frameworks';
import Client from '../client';
import { isSettingValue } from '../is-setting-value';
import { ProjectSettings } from '../../types';
@@ -22,12 +22,14 @@ const settingKeys = Object.keys(settingMap).sort() as unknown as readonly [
export type PartialProjectSettings = Pick<ProjectSettings, ConfigKeys>;
export default async function editProjectSettings(
output: Output,
client: Client,
projectSettings: PartialProjectSettings | null,
framework: Framework | null,
autoConfirm: boolean,
localConfigurationOverrides: PartialProjectSettings | null
): Promise<ProjectSettings> {
const { output } = client;
// Create initial settings object defaulting everything to `null` and assigning what may exist in `projectSettings`
const settings: ProjectSettings = Object.assign(
{
@@ -118,7 +120,7 @@ export default async function editProjectSettings(
// Prompt the user if they want to modify any settings not defined by local configuration.
if (
autoConfirm ||
!(await confirm('Want to modify these settings?', false))
!(await confirm(client, 'Want to modify these settings?', false))
) {
return settings;
}

View File

@@ -47,11 +47,16 @@ export default async function inputProject(
if (!detectedProject) {
// did not auto-detect a project to link
shouldLinkProject = await confirm(`Link to existing project?`, false);
shouldLinkProject = await confirm(
client,
`Link to existing project?`,
false
);
} else {
// auto-detected a project to link
if (
await confirm(
client,
`Found project ${chalk.cyan(
`${org.slug}/${detectedProject.name}`
)}. Link to it?`,
@@ -63,6 +68,7 @@ export default async function inputProject(
// user doesn't want to link the auto-detected project
shouldLinkProject = await confirm(
client,
`Link to different existing project?`,
true
);
@@ -73,7 +79,11 @@ export default async function inputProject(
let project: Project | ProjectNotFound | null = null;
while (!project || project instanceof ProjectNotFound) {
const answers = await inquirer.prompt({
const prompt = inquirer.createPromptModule({
input: client.stdin,
output: client.stdout,
});
const answers = await prompt({
type: 'input',
name: 'existingProjectName',
message: `Whats the name of your existing project?`,

View File

@@ -1,12 +1,12 @@
import path from 'path';
import chalk from 'chalk';
import inquirer from 'inquirer';
import { Output } from '../output';
import { validateRootDirectory } from '../validate-paths';
import Client from '../client';
export async function inputRootDirectory(
client: Client,
cwd: string,
output: Output,
autoConfirm = false
) {
if (autoConfirm) {
@@ -15,7 +15,11 @@ export async function inputRootDirectory(
// eslint-disable-next-line no-constant-condition
while (true) {
const { rootDirectory } = await inquirer.prompt({
const prompt = inquirer.createPromptModule({
input: client.stdin,
output: client.stdout,
});
const { rootDirectory } = await prompt({
type: 'input',
name: 'rootDirectory',
message: `In which directory is your code located?`,
@@ -38,7 +42,7 @@ export async function inputRootDirectory(
if (
(await validateRootDirectory(
output,
client.output,
cwd,
fullPath,
'Please choose a different one.'

View File

@@ -5,21 +5,21 @@ type Options = {
defaultValue?: boolean;
noChar?: string;
resolveChars?: Set<string>;
stdin?: NodeJS.ReadStream;
stdout?: NodeJS.WriteStream;
stdin: NodeJS.ReadStream;
stdout: NodeJS.WriteStream;
trailing?: string;
yesChar?: string;
};
export default async function promptBool(label: string, options: Options = {}) {
export default async function promptBool(label: string, options: Options) {
const {
stdin,
stdout,
defaultValue = false,
abortSequences = new Set(['\u0003']),
resolveChars = new Set(['\r']),
yesChar = 'y',
noChar = 'n',
stdin = process.stdin,
stdout = process.stdout,
trailing = '',
} = options;

View File

@@ -1,13 +1,15 @@
export default async function readStandardInput(): Promise<string> {
export default async function readStandardInput(
stdin: NodeJS.ReadStream
): Promise<string> {
return new Promise<string>(resolve => {
setTimeout(() => resolve(''), 500);
if (process.stdin.isTTY) {
if (stdin.isTTY) {
// found tty so we know there is nothing piped to stdin
resolve('');
} else {
process.stdin.setEncoding('utf8');
process.stdin.once('data', resolve);
stdin.setEncoding('utf8');
stdin.once('data', resolve);
}
});
}

View File

@@ -80,6 +80,7 @@ export default async function setupAndLink(
const shouldStartSetup =
autoConfirm ||
(await confirm(
client,
`${setupMsg} ${chalk.cyan(`${toHumanPath(path)}`)}?`,
true
));
@@ -120,7 +121,7 @@ export default async function setupAndLink(
if (typeof projectOrNewProjectName === 'string') {
newProjectName = projectOrNewProjectName;
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
rootDirectory = await inputRootDirectory(client, path, autoConfirm);
} else {
const project = projectOrNewProjectName;
@@ -224,7 +225,7 @@ export default async function setupAndLink(
const { projectSettings, framework } = deployment;
settings = await editProjectSettings(
output,
client,
projectSettings,
framework,
autoConfirm,

View File

@@ -14,7 +14,7 @@ export default async function reauthenticate(
client.output.log(
`You must re-authenticate with SAML to use ${bold(error.scope)} scope.`
);
if (await confirm(`Log in with SAML?`, true)) {
if (await confirm(client, `Log in with SAML?`, true)) {
return doSamlLogin(client, error.teamId);
}
} else {

View File

@@ -5,6 +5,7 @@ import chalk from 'chalk';
import { homedir } from 'os';
import confirm from './input/confirm';
import toHumanPath from './humanize-path';
import Client from './client';
const stat = promisify(lstatRaw);
@@ -51,9 +52,11 @@ export async function validateRootDirectory(
}
export default async function validatePaths(
output: Output,
client: Client,
paths: string[]
): Promise<{ valid: true; path: string } | { valid: false; exitCode: number }> {
const { output } = client;
// can't deploy more than 1 path
if (paths.length > 1) {
output.print(`${chalk.red('Error!')} Can't deploy more than one path.\n`);
@@ -85,6 +88,7 @@ export default async function validatePaths(
// ask confirmation if the directory is home
if (path === homedir()) {
const shouldDeployHomeDirectory = await confirm(
client,
`You are deploying your home directory. Do you want to continue?`,
false
);

View File

@@ -2,7 +2,7 @@ export const config = {
runtime: 'experimental-edge'
}
export default async function edge(request: Request, event: Event) {
export default async function edge(request, event) {
return new Response('some response body');
// intentional missing closing bracket to produce syntax error

View File

@@ -0,0 +1,3 @@
{
"version": 2
}

View File

@@ -161,14 +161,13 @@ test('[vercel dev] should handle startup errors thrown in edge functions', async
});
validateResponseHeaders(res);
const { stdout, stderr } = await dev.kill('SIGTERM');
const { stderr } = await dev.kill('SIGTERM');
expect(await res.text()).toMatch(
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
);
expect(stdout).toMatch(
/Failed to instantiate edge runtime: intentional startup error/g
);
expect(stderr).toMatch(/Failed to instantiate edge runtime./g);
expect(stderr).toMatch(/intentional startup error/g);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-error-startup: Error: socket hang up/g
);
@@ -193,14 +192,13 @@ test('[vercel dev] should handle syntax errors thrown in edge functions', async
});
validateResponseHeaders(res);
const { stdout, stderr } = await dev.kill('SIGTERM');
const { stderr } = await dev.kill('SIGTERM');
expect(await res.text()).toMatch(
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
);
expect(stdout).toMatch(
/Failed to instantiate edge runtime: Module parse failed: Unexpected token/g
);
expect(stderr).toMatch(/Failed to instantiate edge runtime./g);
expect(stderr).toMatch(/Unexpected end of file/g);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-error-syntax: Error: socket hang up/g
);
@@ -228,13 +226,13 @@ test('[vercel dev] should handle import errors thrown in edge functions', async
);
validateResponseHeaders(res);
const { stdout, stderr } = await dev.kill('SIGTERM');
const { stderr } = await dev.kill('SIGTERM');
expect(await res.text()).toMatch(
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
);
expect(stdout).toMatch(
/Failed to instantiate edge runtime: Code generation from strings disallowed for this context/g
expect(stderr).toMatch(
/Could not resolve "unknown-module-893427589372458934795843"/g
);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-error-unknown-import: Error: socket hang up/g
@@ -244,7 +242,7 @@ test('[vercel dev] should handle import errors thrown in edge functions', async
}
});
test('[vercel dev] should handle import errors thrown in edge functions', async () => {
test('[vercel dev] should handle missing handler errors thrown in edge functions', async () => {
const dir = fixture('edge-function-error');
const { dev, port, readyResolver } = await testFixture(dir);

View File

@@ -1,7 +1,17 @@
// eslint-disable-next-line
import path from 'path';
import { join } from 'path';
import ms from 'ms';
import fs, { mkdirp } from 'fs-extra';
const { exec, fixture, testFixture, testFixtureStdio } = require('./utils.js');
const {
exec,
fetch,
fixture,
sleep,
testFixture,
testFixtureStdio,
validateResponseHeaders,
} = require('./utils.js');
test('[vercel dev] validate redirects', async () => {
const directory = fixture('invalid-redirects');
@@ -334,3 +344,44 @@ test(
await testPath(200, '/', /A simple deployment with the Vercel API!/m);
})
);
test(
'[vercel dev] add a `api/fn.ts` when `api` does not exist at startup`',
testFixtureStdio('no-api', async (_testPath: any, port: any) => {
const directory = fixture('no-api');
const apiDir = join(directory, 'api');
try {
{
const response = await fetch(`http://localhost:${port}/api/new-file`);
validateResponseHeaders(response);
expect(response.status).toBe(404);
}
const fileContents = `
export const config = {
runtime: 'experimental-edge'
}
export default async function edge(request, event) {
return new Response('from new file');
}
`;
await mkdirp(apiDir);
await fs.writeFile(join(apiDir, 'new-file.js'), fileContents);
// Wait until file events have been processed
await sleep(ms('1s'));
{
const response = await fetch(`http://localhost:${port}/api/new-file`);
validateResponseHeaders(response);
const body = await response.text();
expect(body.trim()).toBe('from new file');
}
} finally {
await fs.remove(apiDir);
}
})
);

View File

@@ -508,18 +508,10 @@ test(
test(
'[vercel dev] Middleware with `matchers` config',
testFixtureStdio(
'middleware-matchers',
async (testPath: any) => {
// TODO: remove once latest `@vercel/node` is shipped to stable with `matchers` support (fails because `directoryListing`)
//await testPath(404, '/');
await testPath(404, '/another');
await testPath(200, '/about/page', 'middleware response');
await testPath(200, '/dashboard/home', 'middleware response');
},
{
// TODO: remove once latest `@vercel/node` is shipped to stable with `matchers` support
skipDeploy: true,
}
)
testFixtureStdio('middleware-matchers', async (testPath: any) => {
await testPath(404, '/');
await testPath(404, '/another');
await testPath(200, '/about/page', 'middleware response');
await testPath(200, '/dashboard/home', 'middleware response');
})
);

View File

@@ -1,4 +1,5 @@
import chalk from 'chalk';
import { PassThrough } from 'stream';
import { createServer, Server } from 'http';
import express, { Express, Router } from 'express';
import listen from 'async-listen';
@@ -23,10 +24,13 @@ export class MockClient extends Client {
// Gets populated in `startMockServer()`
apiUrl: '',
authConfig: {},
stdin: new PassThrough(),
stdout: new PassThrough(),
output: new Output(),
config: {},
localConfig: {},
});
this.mockOutput = jest.fn();
this.app = express();
@@ -53,6 +57,12 @@ export class MockClient extends Client {
}
reset() {
this.stdin = new PassThrough();
this.stdin.isTTY = true;
this.stdout = new PassThrough();
this.stdout.isTTY = true;
this.output = new Output();
this.mockOutput = jest.fn();
this.output.print = s => {

View File

@@ -0,0 +1,25 @@
import confirm from '../../src/util/input/confirm';
import { client } from '../mocks/client';
describe('MockClient', () => {
it('should mock `confirm()`', async () => {
// true
let confirmedPromise = confirm(client, 'Do the thing?', false);
client.stdin.write('yes\n');
client.stdout.setEncoding('utf8');
client.stdout.on('data', d => console.log({ d }));
let confirmed = await confirmedPromise;
expect(confirmed).toEqual(true);
// false
confirmedPromise = confirm(client, 'Do the thing?', false);
client.stdin.write('no\n');
confirmed = await confirmedPromise;
expect(confirmed).toEqual(false);
});
});

View File

@@ -1,13 +1,6 @@
import { Framework, frameworks } from '@vercel/frameworks';
import editProjectSettings from '../../../../src/util/input/edit-project-settings';
import { Output } from '../../../../src/util/output';
let output: Output;
beforeEach(() => {
output = new Output();
output.print = jest.fn();
});
import { client } from '../../../mocks/client';
const otherFramework = frameworks.find(
fwk => fwk.name === 'Other'
@@ -20,7 +13,7 @@ describe('editProjectSettings', () => {
describe('with no settings, "Other" framework, and no overrides provided', () => {
test('should default all settings to `null` and print user default framework settings', async () => {
const settings = await editProjectSettings(
output,
client,
null,
otherFramework,
true,
@@ -34,22 +27,14 @@ describe('editProjectSettings', () => {
installCommand: null,
outputDirectory: null,
});
expect((output.print as jest.Mock).mock.calls.length).toBe(5);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
expect(client.mockOutput.mock.calls.length).toBe(5);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
/No framework detected. Default Project Settings:/
);
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Development Command/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
/Install Command/
);
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Output Directory/
);
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Development Command/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(/Install Command/);
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Output Directory/);
});
});
@@ -63,29 +48,21 @@ describe('editProjectSettings', () => {
outputDirectory: 'OUTPUT_DIRECTORY',
};
const settings = await editProjectSettings(
output,
client,
projectSettings,
otherFramework,
true,
null
);
expect(settings).toStrictEqual({ ...projectSettings, framework: null });
expect((output.print as jest.Mock).mock.calls.length).toBe(5);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
expect(client.mockOutput.mock.calls.length).toBe(5);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
/No framework detected. Default Project Settings:/
);
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Development Command/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
/Install Command/
);
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Output Directory/
);
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Development Command/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(/Install Command/);
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Output Directory/);
});
});
@@ -99,28 +76,20 @@ describe('editProjectSettings', () => {
outputDirectory: 'OUTPUT_DIRECTORY',
};
const settings = await editProjectSettings(
output,
client,
projectSettings,
nextJSFramework,
true,
null
);
expect((output.print as jest.Mock).mock.calls.length).toBe(5);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
expect(client.mockOutput.mock.calls.length).toBe(5);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
/Auto-detected Project Settings/
);
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Development Command/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
/Install Command/
);
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Output Directory/
);
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Development Command/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(/Install Command/);
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Output Directory/);
expect(settings).toStrictEqual({
...projectSettings,
framework: nextJSFramework.slug,
@@ -146,38 +115,28 @@ describe('editProjectSettings', () => {
outputDirectory: 'OUTPUT_DIRECTORY',
};
const settings = await editProjectSettings(
output,
client,
projectSettings,
nextJSFramework,
true,
overrides
);
expect((output.print as jest.Mock).mock.calls.length).toBe(9);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
expect(client.mockOutput.mock.calls.length).toBe(9);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
/Local settings detected in vercel.json:/
);
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command:/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Ignore Command:/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command:/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Ignore Command:/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(
/Development Command:/
);
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Framework:/
);
expect((output.print as jest.Mock).mock.calls[5][0]).toMatch(
/Install Command:/
);
expect((output.print as jest.Mock).mock.calls[6][0]).toMatch(
/Output Directory:/
);
expect((output.print as jest.Mock).mock.calls[7][0]).toMatch(
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Framework:/);
expect(client.mockOutput.mock.calls[5][0]).toMatch(/Install Command:/);
expect(client.mockOutput.mock.calls[6][0]).toMatch(/Output Directory:/);
expect(client.mockOutput.mock.calls[7][0]).toMatch(
/Merging default Project Settings for Svelte. Previously listed overrides are prioritized./
);
expect((output.print as jest.Mock).mock.calls[8][0]).toMatch(
expect(client.mockOutput.mock.calls[8][0]).toMatch(
/Auto-detected Project Settings/
);
@@ -196,38 +155,28 @@ describe('editProjectSettings', () => {
outputDirectory: 'OUTPUT_DIRECTORY',
};
const settings = await editProjectSettings(
output,
client,
null,
nextJSFramework,
true,
overrides
);
expect((output.print as jest.Mock).mock.calls.length).toBe(9);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
expect(client.mockOutput.mock.calls.length).toBe(9);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
/Local settings detected in vercel.json:/
);
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command:/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Ignore Command:/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command:/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Ignore Command:/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(
/Development Command:/
);
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Framework:/
);
expect((output.print as jest.Mock).mock.calls[5][0]).toMatch(
/Install Command:/
);
expect((output.print as jest.Mock).mock.calls[6][0]).toMatch(
/Output Directory:/
);
expect((output.print as jest.Mock).mock.calls[7][0]).toMatch(
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Framework:/);
expect(client.mockOutput.mock.calls[5][0]).toMatch(/Install Command:/);
expect(client.mockOutput.mock.calls[6][0]).toMatch(/Output Directory:/);
expect(client.mockOutput.mock.calls[7][0]).toMatch(
/Merging default Project Settings for Svelte. Previously listed overrides are prioritized./
);
expect((output.print as jest.Mock).mock.calls[8][0]).toMatch(
expect(client.mockOutput.mock.calls[8][0]).toMatch(
/Auto-detected Project Settings/
);
expect(settings).toStrictEqual(overrides);
@@ -245,38 +194,28 @@ describe('editProjectSettings', () => {
outputDirectory: 'OUTPUT_DIRECTORY',
};
const settings = await editProjectSettings(
output,
client,
null,
null,
true,
overrides
);
expect((output.print as jest.Mock).mock.calls.length).toBe(9);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
expect(client.mockOutput.mock.calls.length).toBe(9);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
/Local settings detected in vercel.json:/
);
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command:/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Ignore Command:/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command:/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Ignore Command:/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(
/Development Command:/
);
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Framework:/
);
expect((output.print as jest.Mock).mock.calls[5][0]).toMatch(
/Install Command:/
);
expect((output.print as jest.Mock).mock.calls[6][0]).toMatch(
/Output Directory:/
);
expect((output.print as jest.Mock).mock.calls[7][0]).toMatch(
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Framework:/);
expect(client.mockOutput.mock.calls[5][0]).toMatch(/Install Command:/);
expect(client.mockOutput.mock.calls[6][0]).toMatch(/Output Directory:/);
expect(client.mockOutput.mock.calls[7][0]).toMatch(
/Merging default Project Settings for Svelte. Previously listed overrides are prioritized./
);
expect((output.print as jest.Mock).mock.calls[8][0]).toMatch(
expect(client.mockOutput.mock.calls[8][0]).toMatch(
/Auto-detected Project Settings/
);

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "12.0.2",
"version": "12.0.3",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -42,7 +42,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "4.2.0",
"@vercel/build-utils": "4.2.1",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "3.1.0",
"version": "3.1.2",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -45,8 +45,8 @@
"@types/semver": "6.0.0",
"@types/text-table": "0.2.1",
"@types/webpack-sources": "3.2.0",
"@vercel/build-utils": "4.2.0",
"@vercel/nft": "0.19.1",
"@vercel/build-utils": "4.2.1",
"@vercel/nft": "0.20.1",
"@vercel/routing-utils": "1.13.5",
"async-sema": "3.0.1",
"buffer-crc32": "0.2.13",

View File

@@ -1520,35 +1520,35 @@ export const build: BuildV2 = async ({
const pageLambdaGroups: Array<LambdaGroup> = [];
if (isSharedLambdas) {
const initialPageLambdaGroups = await getPageLambdaGroups(
const initialPageLambdaGroups = await getPageLambdaGroups({
entryPath,
config,
nonApiPages,
new Set(),
pages: nonApiPages,
prerenderRoutes: new Set(),
pageTraces,
compressedPages,
tracedPseudoLayer?.pseudoLayer || {},
{ pseudoLayer: {}, pseudoLayerBytes: 0 },
0,
tracedPseudoLayer: tracedPseudoLayer?.pseudoLayer || {},
initialPseudoLayer: { pseudoLayer: {}, pseudoLayerBytes: 0 },
initialPseudoLayerUncompressed: 0,
lambdaCompressedByteLimit,
// internal pages are already referenced in traces for serverless
// like builds
[]
);
internalPages: [],
});
const initialApiLambdaGroups = await getPageLambdaGroups(
const initialApiLambdaGroups = await getPageLambdaGroups({
entryPath,
config,
apiPages,
new Set(),
pages: apiPages,
prerenderRoutes: new Set(),
pageTraces,
compressedPages,
tracedPseudoLayer?.pseudoLayer || {},
{ pseudoLayer: {}, pseudoLayerBytes: 0 },
0,
tracedPseudoLayer: tracedPseudoLayer?.pseudoLayer || {},
initialPseudoLayer: { pseudoLayer: {}, pseudoLayerBytes: 0 },
initialPseudoLayerUncompressed: 0,
lambdaCompressedByteLimit,
[]
);
internalPages: [],
});
debug(
JSON.stringify(

View File

@@ -610,34 +610,34 @@ export async function serverBuild({
const pageExtensions = requiredServerFilesManifest.config?.pageExtensions;
const pageLambdaGroups = await getPageLambdaGroups(
requiredServerFilesManifest.appDir || entryPath,
const pageLambdaGroups = await getPageLambdaGroups({
entryPath: requiredServerFilesManifest.appDir || entryPath,
config,
nonApiPages,
pages: nonApiPages,
prerenderRoutes,
pageTraces,
compressedPages,
tracedPseudoLayer.pseudoLayer,
tracedPseudoLayer: tracedPseudoLayer.pseudoLayer,
initialPseudoLayer,
lambdaCompressedByteLimit,
uncompressedInitialSize,
initialPseudoLayerUncompressed: uncompressedInitialSize,
internalPages,
pageExtensions
);
pageExtensions,
});
const apiLambdaGroups = await getPageLambdaGroups(
requiredServerFilesManifest.appDir || entryPath,
const apiLambdaGroups = await getPageLambdaGroups({
entryPath: requiredServerFilesManifest.appDir || entryPath,
config,
apiPages,
pages: apiPages,
prerenderRoutes,
pageTraces,
compressedPages,
tracedPseudoLayer.pseudoLayer,
tracedPseudoLayer: tracedPseudoLayer.pseudoLayer,
initialPseudoLayer,
uncompressedInitialSize,
initialPseudoLayerUncompressed: uncompressedInitialSize,
lambdaCompressedByteLimit,
internalPages
);
internalPages,
});
debug(
JSON.stringify(

View File

@@ -1275,26 +1275,39 @@ export const MAX_UNCOMPRESSED_LAMBDA_SIZE = 250 * 1000 * 1000; // 250MB
const LAMBDA_RESERVED_UNCOMPRESSED_SIZE = 2.5 * 1000 * 1000; // 2.5MB
const LAMBDA_RESERVED_COMPRESSED_SIZE = 250 * 1000; // 250KB
export async function getPageLambdaGroups(
entryPath: string,
config: Config,
pages: string[],
prerenderRoutes: Set<string>,
export async function getPageLambdaGroups({
entryPath,
config,
pages,
prerenderRoutes,
pageTraces,
compressedPages,
tracedPseudoLayer,
initialPseudoLayer,
initialPseudoLayerUncompressed,
lambdaCompressedByteLimit,
internalPages,
pageExtensions,
}: {
entryPath: string;
config: Config;
pages: string[];
prerenderRoutes: Set<string>;
pageTraces: {
[page: string]: {
[key: string]: FileFsRef;
};
},
};
compressedPages: {
[page: string]: PseudoFile;
},
tracedPseudoLayer: PseudoLayer,
initialPseudoLayer: PseudoLayerResult,
initialPseudoLayerUncompressed: number,
lambdaCompressedByteLimit: number,
internalPages: string[],
pageExtensions?: string[]
) {
};
tracedPseudoLayer: PseudoLayer;
initialPseudoLayer: PseudoLayerResult;
initialPseudoLayerUncompressed: number;
lambdaCompressedByteLimit: number;
internalPages: string[];
pageExtensions?: string[];
}) {
const groups: Array<LambdaGroup> = [];
for (const page of pages) {

View File

@@ -105,6 +105,21 @@ it('should build using server build', async () => {
log.includes('WARNING: Unable to find source file for page')
)
).toBeFalsy();
const lambdas = new Set();
let totalLambdas = 0;
for (const item of Object.values(output)) {
if (item.type === 'Lambda') {
totalLambdas += 1;
lambdas.add(item);
} else if (item.type === 'Prerender') {
lambdas.add(item.lambda);
totalLambdas += 1;
}
}
expect(lambdas.size).toBe(5);
expect(lambdas.size).toBeLessThan(totalLambdas);
});
it('should build custom error lambda correctly', async () => {
@@ -835,3 +850,66 @@ it('Should provide lambda info when limit is hit (uncompressed)', async () => {
expect(logs).toMatch(/data\.txt/);
expect(logs).toMatch(/\.next\/server\/pages/);
});
it('Should de-dupe correctly when limit is close (uncompressed)', async () => {
const origLog = console.log;
const origError = console.error;
const caughtLogs = [];
console.log = function (...args) {
caughtLogs.push(args.join(' '));
origLog.apply(this, args);
};
console.error = function (...args) {
caughtLogs.push(args.join(' '));
origError.apply(this, args);
};
const {
buildResult: { output },
} = await runBuildLambda(
path.join(__dirname, 'test-limit-large-uncompressed-files')
);
console.log = origLog;
console.error = origError;
expect(output['index']).toBeDefined();
expect(output['another']).toBeDefined();
expect(output['api/hello']).toBeDefined();
expect(output['api/hello-1']).toBeDefined();
expect(output['api/hello-2']).toBeDefined();
expect(output['api/hello-3']).toBeDefined();
expect(output['api/hello-4']).toBeDefined();
expect(output['_app']).not.toBeDefined();
expect(output['_error']).not.toBeDefined();
expect(output['_document']).not.toBeDefined();
expect(output['index'] === output['another']).toBe(true);
expect(output['index'] !== output['api/hello']).toBe(true);
expect(output['api/hello'] === output['api/hello-1']).toBe(true);
expect(output['api/hello'] === output['api/hello-2']).toBe(true);
expect(output['api/hello'] === output['api/hello-3']).toBe(true);
expect(output['api/hello'] === output['api/hello-4']).toBe(true);
expect(
caughtLogs.some(log =>
log.includes('WARNING: Unable to find source file for page')
)
).toBeFalsy();
const lambdas = new Set();
let totalLambdas = 0;
for (const item of Object.values(output)) {
if (item.type === 'Lambda') {
totalLambdas += 1;
lambdas.add(item);
} else if (item.type === 'Prerender') {
lambdas.add(item.lambda);
totalLambdas += 1;
}
}
expect(lambdas.size).toBe(2);
expect(lambdas.size).toBeLessThan(totalLambdas);
});

View File

@@ -0,0 +1,6 @@
const fs = require('fs');
// generate 200MB file which will be traced in `/api/hello`
fs.writeFileSync('data.txt', Buffer.alloc(200 * 1024 * 1024));
module.exports = {};

View File

@@ -0,0 +1,12 @@
{
"name": "test-limit",
"version": "1.0.0",
"scripts": {
"build": "next build"
},
"dependencies": {
"next": "canary",
"react": "17.0.2",
"react-dom": "17.0.2"
}
}

View File

@@ -0,0 +1,16 @@
/* eslint-disable */
import React from 'react';
if (typeof window === 'undefined') {
try {
const fs = require('fs');
const path = require('path');
fs.readdirSync(path.join(process.cwd(), 'public'));
fs.readdirSync(path.join(process.cwd(), 'node_modules/chrome-aws-lambda'));
fs.readdirSync(path.join(process.cwd(), 'node_modules/firebase'));
} catch (_) {}
}
export default function MyApp({ Component, pageProps }) {
return React.createElement(Component, pageProps);
}

View File

@@ -0,0 +1,10 @@
export default function Home() {
return 'another page';
}
export function getServerSideProps() {
require('fs').readFileSync(require('path').join(process.cwd(), 'data.txt'));
return {
props: {},
};
}

View File

@@ -0,0 +1,12 @@
import fs from 'fs';
import path from 'path';
try {
fs.readFileSync(path.join(process.cwd(), 'data.txt'));
} catch (_) {
/**/
}
export default function handler(req, res) {
res.end('hello');
}

View File

@@ -0,0 +1,12 @@
import fs from 'fs';
import path from 'path';
try {
fs.readFileSync(path.join(process.cwd(), 'data.txt'));
} catch (_) {
/**/
}
export default function handler(req, res) {
res.end('hello');
}

View File

@@ -0,0 +1,12 @@
import fs from 'fs';
import path from 'path';
try {
fs.readFileSync(path.join(process.cwd(), 'data.txt'));
} catch (_) {
/**/
}
export default function handler(req, res) {
res.end('hello');
}

View File

@@ -0,0 +1,12 @@
import fs from 'fs';
import path from 'path';
try {
fs.readFileSync(path.join(process.cwd(), 'data.txt'));
} catch (_) {
/**/
}
export default function handler(req, res) {
res.end('hello');
}

View File

@@ -0,0 +1,12 @@
import fs from 'fs';
import path from 'path';
try {
fs.readFileSync(path.join(process.cwd(), 'data.txt'));
} catch (_) {
/**/
}
export default function handler(req, res) {
res.end('hello');
}

View File

@@ -0,0 +1,10 @@
export default function Home() {
return 'index page';
}
export function getServerSideProps() {
require('fs').readFileSync(require('path').join(process.cwd(), 'data.txt'));
return {
props: {},
};
}

View File

@@ -0,0 +1,9 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "2.3.0",
"version": "2.3.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -31,11 +31,11 @@
},
"dependencies": {
"@types/node": "*",
"@vercel/build-utils": "4.2.0",
"@vercel/ncc": "0.24.0",
"@vercel/build-utils": "4.2.1",
"@vercel/node-bridge": "3.0.0",
"@vercel/static-config": "2.0.1",
"edge-runtime": "1.0.1",
"esbuild": "0.14.47",
"exit-hook": "2.2.1",
"node-fetch": "2.6.1",
"ts-node": "8.9.1",
@@ -52,7 +52,8 @@
"@types/jest": "27.4.1",
"@types/node-fetch": "^2.6.1",
"@types/test-listen": "1.1.0",
"@vercel/nft": "0.19.1",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.20.1",
"content-type": "1.0.4",
"cookie": "0.4.0",
"etag": "1.8.1",

View File

@@ -79,8 +79,9 @@ import exitHook from 'exit-hook';
import { EdgeRuntime, Primitives, runServer } from 'edge-runtime';
import { getConfig } from '@vercel/static-config';
import { Project } from 'ts-morph';
import ncc from '@vercel/ncc';
import esbuild from 'esbuild';
import fetch from 'node-fetch';
import { TextDecoder } from 'util';
function logError(error: Error) {
console.error(error.message);
@@ -149,10 +150,22 @@ async function serializeRequest(message: IncomingMessage) {
async function compileUserCode(entrypoint: string) {
try {
const buildResult = await ncc(entrypoint, {
target: 'es2022',
const result = await esbuild.build({
platform: 'node',
target: 'node14',
sourcemap: 'inline',
bundle: true,
entryPoints: [entrypoint],
write: false, // operate in memory
format: 'cjs',
});
const userCode = buildResult.code;
const compiledFile = result.outputFiles?.[0];
if (!compiledFile) {
throw new Error(`Compilation of ${entrypoint} produced no output files.`);
}
const userCode = new TextDecoder().decode(compiledFile.contents);
return `
${userCode};
@@ -200,7 +213,8 @@ async function compileUserCode(entrypoint: string) {
} catch (error) {
// We can't easily show a meaningful stack trace from ncc -> edge-runtime.
// So, stick with just the message for now.
console.log(`Failed to instantiate edge runtime: ${error.message}`);
console.error(`Failed to instantiate edge runtime.`);
logError(error);
return undefined;
}
}
@@ -231,7 +245,8 @@ async function createEdgeRuntime(userCode: string | undefined) {
} catch (error) {
// We can't easily show a meaningful stack trace from ncc -> edge-runtime.
// So, stick with just the message for now.
console.log(`Failed to instantiate edge runtime: ${error.message}`);
console.error('Failed to instantiate edge runtime.');
logError(error);
return undefined;
}
}

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/redwood",
"version": "1.0.2",
"version": "1.0.4",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs",
@@ -20,7 +20,7 @@
"prepublishOnly": "node build.js"
},
"dependencies": {
"@vercel/nft": "0.19.1",
"@vercel/nft": "0.20.1",
"@vercel/routing-utils": "1.13.5",
"semver": "6.1.1"
},
@@ -28,6 +28,6 @@
"@types/aws-lambda": "8.10.19",
"@types/node": "*",
"@types/semver": "6.0.0",
"@vercel/build-utils": "4.2.0"
"@vercel/build-utils": "4.2.1"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/remix",
"version": "1.0.2",
"version": "1.0.4",
"license": "MIT",
"main": "./dist/index.js",
"homepage": "https://vercel.com/docs",
@@ -21,12 +21,12 @@
"default-server.js"
],
"dependencies": {
"@vercel/nft": "0.19.1"
"@vercel/nft": "0.20.1"
},
"devDependencies": {
"@types/jest": "27.5.1",
"@types/node": "*",
"@vercel/build-utils": "4.2.0",
"@vercel/build-utils": "4.2.1",
"typescript": "4.6.4"
}
}

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/static-build",
"version": "1.0.2",
"version": "1.0.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step",
@@ -37,7 +37,7 @@
"@types/ms": "0.7.31",
"@types/node-fetch": "2.5.4",
"@types/promise-timeout": "1.3.0",
"@vercel/build-utils": "4.2.0",
"@vercel/build-utils": "4.2.1",
"@vercel/frameworks": "1.0.2",
"@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "1.13.5",

View File

@@ -3042,10 +3042,10 @@
resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.24.0.tgz#a2e8783a185caa99b5d8961a57dfc9665de16296"
integrity sha512-crqItMcIwCkvdXY/V3/TzrHJQx6nbIaRqE1cOopJhgGX6izvNov40SmD//nS5flfEvdK54YGjwVVq+zG6crjOg==
"@vercel/nft@0.19.1":
version "0.19.1"
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.19.1.tgz#dcd3c20d7ef14d2050244e7d5edcfecc7303dd46"
integrity sha512-klR5oN7S3WJsZz0r6Xsq7o8YlFEyU3/00VmlpZzIPVFzKfbcEjXo/sVR5lQBUqNKuOzhcbxaFtzW9aOyHjmPYA==
"@vercel/nft@0.20.1":
version "0.20.1"
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.20.1.tgz#41e559af189405c526ac1f6709773bc99995b95b"
integrity sha512-hSLcr64KHOkcNiTAlv154K4p4faEFBwYIi2eIgu1QCDhB1qyQYvFuEhtw3eaapNjA4/7x/2jcclfCAjILua/ag==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.5"
acorn "^8.6.0"
@@ -3055,7 +3055,6 @@
graceful-fs "^4.2.9"
micromatch "^4.0.2"
node-gyp-build "^4.2.2"
node-pre-gyp "^0.13.0"
resolve-from "^5.0.0"
rollup-pluginutils "^2.8.2"
@@ -4986,7 +4985,7 @@ debug@4, debug@4.1.1, debug@^4.1.0, debug@^4.1.1:
dependencies:
ms "^2.1.1"
debug@^3.0.0, debug@^3.1.0, debug@^3.2.6:
debug@^3.0.0, debug@^3.1.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -5175,7 +5174,7 @@ detect-indent@^5.0.0:
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
detect-libc@^1.0.2, detect-libc@^1.0.3:
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
@@ -5607,12 +5606,7 @@ esbuild@0.12.22:
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.22.tgz#6031a1257b8d0307d306bed673b79c3668607f51"
integrity sha512-yWCr9RoFehpqoe/+MwZXJpYOEIt7KOEvNnjIeMZpMSyQt+KCBASM3y7yViiN5dJRphf1wGdUz1+M4rTtWd/ulA==
esbuild@^0.11.20:
version "0.11.23"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.23.tgz#c42534f632e165120671d64db67883634333b4b8"
integrity sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==
esbuild@^0.14.25:
esbuild@0.14.47, esbuild@^0.14.25:
version "0.14.47"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.47.tgz#0d6415f6bd8eb9e73a58f7f9ae04c5276cda0e4d"
integrity sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==
@@ -5638,6 +5632,11 @@ esbuild@^0.14.25:
esbuild-windows-64 "0.14.47"
esbuild-windows-arm64 "0.14.47"
esbuild@^0.11.20:
version "0.11.23"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.23.tgz#c42534f632e165120671d64db67883634333b4b8"
integrity sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -7077,7 +7076,7 @@ husky@7.0.4:
resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535"
integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -9328,15 +9327,6 @@ ndjson@2.0.0:
split2 "^3.0.0"
through2 "^4.0.0"
needle@^2.2.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.2.tgz#cf1a8fce382b5a280108bba90a14993c00e4010a"
integrity sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==
dependencies:
debug "^3.2.6"
iconv-lite "^0.4.4"
sax "^1.2.4"
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -9403,22 +9393,6 @@ node-int64@^0.4.0:
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=
node-pre-gyp@^0.13.0:
version "0.13.0"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz#df9ab7b68dd6498137717838e4f92a33fc9daa42"
integrity sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==
dependencies:
detect-libc "^1.0.2"
mkdirp "^0.5.1"
needle "^2.2.1"
nopt "^4.0.1"
npm-packlist "^1.1.6"
npmlog "^4.0.2"
rc "^1.2.7"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^4"
node-releases@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.3.tgz#225ee7488e4a5e636da8da52854844f9d716ca96"
@@ -9510,7 +9484,7 @@ npm-package-arg@6.1.0:
semver "^5.6.0"
validate-npm-package-name "^3.0.0"
npm-packlist@^1.1.6, npm-packlist@^1.4.4:
npm-packlist@^1.4.4:
version "1.4.8"
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
@@ -9549,7 +9523,7 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1:
dependencies:
path-key "^3.0.0"
npmlog@^4.0.2, npmlog@^4.1.2:
npmlog@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@@ -10532,7 +10506,7 @@ rc9@1.2.0:
destr "^1.0.0"
flat "^5.0.0"
rc@^1.2.7, rc@^1.2.8:
rc@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
@@ -10972,7 +10946,7 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -11039,11 +11013,6 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
@@ -11063,7 +11032,7 @@ semver-diff@^3.1.1:
dependencies:
semver "^6.3.0"
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.3, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.3, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -11864,7 +11833,7 @@ tar@4.4.8:
safe-buffer "^5.1.2"
yallist "^3.0.2"
tar@^4, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8:
tar@^4.4.10, tar@^4.4.12, tar@^4.4.8:
version "4.4.13"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==