Compare commits

..

11 Commits

Author SHA1 Message Date
Sean Massa
619ca93421 Publish Stable
- @vercel/build-utils@5.4.3
 - vercel@28.3.0
 - @vercel/client@12.2.5
 - @vercel/frameworks@1.1.6
 - @vercel/fs-detectors@3.3.0
 - @vercel/go@2.2.6
 - @vercel/hydrogen@0.0.19
 - @vercel/next@3.1.26
 - @vercel/node@2.5.15
 - @vercel/python@3.1.15
 - @vercel/redwood@1.0.24
 - @vercel/remix@1.0.25
 - @vercel/ruby@1.3.32
 - @vercel/static-build@1.0.24
2022-09-19 16:40:10 -05:00
Steven
cea2981512 [tests] Change some runNpmInstall() unit tests to mocks (#8581)
- Closes #8580
2022-09-19 21:30:32 +00:00
JJ Kasper
1f30e3a4b7 [next] Update app test fixture (#8584)
### Related Issues

Updates our test fixture for related changes in latest canary of
Next.js.

Fixes: https://vercel.slack.com/archives/CGU8HUTUH/p1663607276817069

### 📋 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-09-19 13:08:10 -07:00
Steven
20e8fdd049 [tests] Add yarn cache to github actions (#8567)
We had this enable for some workflows but not all so this PR enables the yarn cache for the remaining workflows.
2022-09-15 02:05:26 +00:00
Chris Barber
3df8313d69 [next] Use posix path separator to join routes and overrides paths (#8530)
Use posix path separators for routes (static pages, lambdas, etc) instead of the native platform path separator (e.g. backslash on Windows).

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] 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-09-14 23:47:57 +00:00
Steven
95c9ea92c4 [build-utils] Add flag for npm install --legacy-peer-deps (#8550)
Prior to `npm@8.6.0`, running `npm install --legacy-peer-deps` to generate a lock file and then later running `npm install` would work fine.

After `npm@8.6.0`, running `npm install` with that same lock file will fail with `code ERESOLVE` and `Conflicting peer dependency`.

Steps to reproduce:

```sh
corepack enable npm
echo '{"packageManager":"npm@8.5.5","dependencies":{"next":"12.3.0","react":"16.8.0"}}' > package.json
npm install --legacy-peer-deps
npm install # works
echo '{"packageManager":"npm@8.6.0","dependencies":{"next":"12.3.0","react":"16.8.0"}}' > package.json
npm install # fails with code ERESOLVE
```


This PR introduces a flag so we can safely upgrade npm to a newer version without breaking thousands of deployments.

The [npm docs](https://docs.npmjs.com/cli/v8/using-npm/config#legacy-peer-deps) say the flag is not recommended so we also make sure that once we upgrade to Node.js 18, we stop flagging `--legacy-peer-deps` because its safe to introduce breaking changes.
2022-09-14 22:18:40 +00:00
Chris Barber
7ddebb099d [build-utils][cli][go][node][ruby][static-build] Explicitly set PATH when copying env vars (#8532)
On Windows 10 and 11 machines, environment variables are not case sensitive. The system PATH is actually defined as `process.env.Path`, however Node.js kindly handles the case sensitivity and will automatically return the system path when specifying `process.env.PATH`.

When we clone the environment variables via `{ ...process.env }`, we lose the automatic resolving of `Path` to `PATH`. To fix this, we need to explicitly copy the `PATH`.

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] 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-09-14 20:29:52 +00:00
chloetedder
6498fd1aab [fs-detectors] Add Nx Workspace (#8549)
### Related Issues


https://nx.dev/more-concepts/folder-structure#integrated-repo-folder-structure

Nx monorepo has an option to use Nx workspaces.
Nx workspace is defined within the root `workspace.json` file
Within this `workspace.json` file the workspace packages are under
projects
```{
  "$schema": "./node_modules/nx/schemas/workspace-schema.json",
  "version": 2,
  "projects": {
    "myblog": "apps/myblog",
    "svelte-app": "apps/svelte-app",
  }
}
```
Within `getNxWorkspacePackagePaths` get the projects object values for
the paths
Nx is listed as the last workspace manager within
`workspace-managers.ts` because other workspace managers could exist to
check for before Nx workspaces because the `workspace.json` could exist
but not be the correct workspace manager

Nx workspace file can exist when yarn/npm workspaces exist. When this
happens, the workspace.json file is empty with no projects so it will
not add any package paths to the list to look through for projects.


### 📋 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-09-14 15:10:14 -05:00
Nathan Rajlich
9d7d822da3 [tests] Add test for path segments when go.mod is present (#7361)
Path segment previously were not working when a `go.mod` file is present. This was fixed likely by https://github.com/vercel/vercel/pull/8291, so now this PR is just a test to ensure this case stays working.
2022-09-14 19:10:49 +00:00
Nathan Rajlich
b8110d97d1 [tests] Add more tests for importBuilders() scenarios (#8554)
Covers:
  - Importing a built-in Builder with `@latest` should use the CLI's version
  - Importing a built-in Builder with `@canary` should use the CLI's version
  - Importing a built-in Builder with an explicit version should install to `.vercel/builders`
2022-09-13 23:56:10 +00:00
Nathan Rajlich
2d9373b0f1 [tests] Fix "50-ionic-react" test fixture (#8556)
Update "typescript" and add a `yarn.lock` file.

It started failing without the lockfile with compilation issues:

```
$ react-scripts build
Creating an optimized production build...
Failed to compile.
/vercel/path1/node_modules/@types/react-router/index.d.ts
TypeScript error in /vercel/path1/node_modules/@types/react-router/index.d.ts(149,100):
Type expected.  TS1110
    147 | ): match<Params> | null;
    148 |
  > 149 | export type ExtractRouteOptionalParam<T extends string, U = string | number | boolean> = T extends `${infer Param}?`
        |                                                                                                    ^
    150 |     ? { [k in Param]?: U }
    151 |     : T extends `${infer Param}*`
    152 |     ? { [k in Param]?: U }
error Command failed with exit code 1.
```
2022-09-13 23:28:36 +00:00
112 changed files with 13417 additions and 445 deletions

View File

@@ -34,6 +34,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 14
cache: 'yarn'
- name: Install
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
run: yarn install --check-files --frozen-lockfile --network-timeout 1000000

View File

@@ -25,19 +25,16 @@ jobs:
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
- uses: actions/checkout@v3
with:
fetch-depth: 2
- uses: actions/setup-go@v3
with:
go-version: '1.13.15'
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v3
with:
fetch-depth: 100
- run: git --version
- run: git fetch origin main --depth=100
- run: git fetch origin ${{ github.ref }} --depth=100
- run: git diff origin/main...HEAD --name-only
cache: 'yarn'
- run: yarn install --network-timeout 1000000 --frozen-lockfile
- run: yarn run build
- run: yarn test-integration-cli

View File

@@ -28,16 +28,13 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: '1.13.15'
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v3
with:
fetch-depth: 100
- run: git --version
- run: git fetch origin main --depth=100
- run: git fetch origin ${{ github.ref }} --depth=100
- run: git diff origin/main...HEAD --name-only
fetch-depth: 2
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
cache: 'yarn'
- run: yarn install --network-timeout 1000000 --frozen-lockfile
- run: yarn run build
- run: yarn run lint

View File

@@ -20,8 +20,11 @@ jobs:
dplUrl: ${{ steps.waitForTarball.outputs.url }}
steps:
- uses: actions/checkout@v3
- run: git --version
- run: git fetch origin main
with:
fetch-depth: 2
- uses: actions/setup-go@v3
with:
go-version: '1.13.15'
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}

View File

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

View File

@@ -0,0 +1,29 @@
import type { Env } from './types';
const { hasOwnProperty } = Object.prototype;
/**
* Clones zero or more objects into a single new object while ensuring that the
* `PATH` environment variable is defined when the `PATH` or `Path` environment
* variables are defined.
*
* @param {Object} [...envs] Objects and/or `process.env` to clone and merge
* @returns {Object} The new object
*/
export function cloneEnv(...envs: (Env | undefined)[]): Env {
return envs.reduce((obj: Env, env) => {
if (env === undefined || env === null) {
return obj;
}
// the system path is called `Path` on Windows and Node.js will
// automatically return the system path when accessing `PATH`,
// however we lose this proxied value when we destructure and
// thus we must explicitly copy it
if (hasOwnProperty.call(env, 'PATH') || hasOwnProperty.call(env, 'Path')) {
obj.PATH = env.PATH;
}
return Object.assign(obj, env);
}, {});
}

View File

@@ -11,6 +11,7 @@ import { NowBuildError } from '../errors';
import { Meta, PackageJson, NodeVersion, Config } from '../types';
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
import { readConfigFile } from './read-config-file';
import { cloneEnv } from '../clone-env';
// Only allow one `runNpmInstall()` invocation to run concurrently
const runNpmInstallSema = new Sema(1);
@@ -217,7 +218,7 @@ export function getSpawnOptions(
nodeVersion: NodeVersion
): SpawnOptions {
const opts = {
env: { ...process.env },
env: cloneEnv(process.env),
};
if (!meta.isDev) {
@@ -449,7 +450,7 @@ export async function runNpmInstall(
debug(`Installing to ${destPath}`);
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
const env = opts.env ? { ...opts.env } : { ...process.env };
const env = cloneEnv(opts.env || process.env);
delete env.NODE_ENV;
opts.env = getEnvForPackageManager({
cliType,
@@ -464,6 +465,19 @@ export async function runNpmInstall(
commandArgs = args
.filter(a => a !== '--prefer-offline')
.concat(['install', '--no-audit', '--unsafe-perm']);
if (
nodeVersion?.major === 16 &&
spawnOpts?.env?.VERCEL_NPM_LEGACY_PEER_DEPS === '1' &&
spawnOpts?.env?.ENABLE_EXPERIMENTAL_COREPACK !== '1'
) {
// Starting in npm@8.6.0, if you ran `npm install --legacy-peer-deps`,
// and then later ran `npm install`, it would fail. So the only way
// to safely upgrade npm from npm@8.5.0 is to set this flag. The docs
// say this flag is not recommended so its is behind a feature flag
// so we can remove it in node@18, which can introduce breaking changes.
// See https://docs.npmjs.com/cli/v8/using-npm/config#legacy-peer-deps
commandArgs.push('--legacy-peer-deps');
}
} 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
@@ -591,10 +605,7 @@ export async function runPackageJsonScript(
cliType,
lockfileVersion,
nodeVersion: undefined,
env: {
...process.env,
...spawnOpts?.env,
},
env: cloneEnv(process.env, spawnOpts?.env),
}),
};

View File

@@ -41,6 +41,7 @@ import debug from './debug';
import getIgnoreFilter from './get-ignore-filter';
import { getPlatformEnv } from './get-platform-env';
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
import { cloneEnv } from './clone-env';
export {
FileBlob,
@@ -84,6 +85,7 @@ export {
getLambdaOptionsFromFunction,
scanParentDirs,
getIgnoreFilter,
cloneEnv,
};
export { EdgeFunction } from './edge-function';

View File

@@ -5,6 +5,7 @@
"requires": true,
"packages": {
"": {
"name": "20-npm-7",
"version": "1.0.0"
}
}

View File

@@ -21,6 +21,8 @@ const skipFixtures: string[] = [
'08-zero-config-middleman',
'21-npm-workspaces',
'23-pnpm-workspaces',
'41-nx-monorepo',
'42-npm-workspace-with-nx',
];
// eslint-disable-next-line no-restricted-syntax

View File

@@ -0,0 +1,120 @@
import { cloneEnv } from '../src';
it('should clone env with Path', () => {
expect(
cloneEnv(
new Proxy(
{
foo: 'bar',
Path: 'baz',
},
{
get(target: typeof process.env, prop: string) {
if (prop === 'PATH') {
return target.PATH ?? target.Path;
}
return target[prop];
},
}
)
)
).toEqual({
foo: 'bar',
Path: 'baz',
PATH: 'baz',
});
});
it('should clone env with PATH', () => {
expect(
cloneEnv({
foo: 'bar',
PATH: 'baz',
})
).toEqual({
foo: 'bar',
PATH: 'baz',
});
});
it('should clone and merge multiple env objects', () => {
// note: this also tests the last object doesn't overwrite `PATH` with
// `undefined`
expect(
cloneEnv(
{
foo: 'bar',
},
{
PATH: 'baz',
},
{
baz: 'wiz',
}
)
).toEqual({
foo: 'bar',
PATH: 'baz',
baz: 'wiz',
});
});
it('should clone the actual process.env object', () => {
expect(cloneEnv(process.env).PATH).toEqual(process.env.PATH);
});
it('should overwrite PATH with last value', () => {
expect(
cloneEnv(
new Proxy(
{
Path: 'foo',
},
{
get(target: typeof process.env, prop: string) {
if (prop === 'PATH') {
return target.PATH ?? target.Path;
}
return target[prop];
},
}
),
{
PATH: 'bar',
},
{
PATH: undefined,
}
)
).toEqual({
Path: 'foo',
PATH: undefined,
});
});
it('should handle process.env at any argument position', () => {
expect(
cloneEnv(
{
foo: 'bar',
},
new Proxy(
{
Path: 'baz',
},
{
get(target: typeof process.env, prop: string) {
if (prop === 'PATH') {
return target.PATH ?? target.Path;
}
return target[prop];
},
}
)
)
).toEqual({
foo: 'bar',
Path: 'baz',
PATH: 'baz',
});
});

View File

@@ -0,0 +1,172 @@
const spawnMock = jest.fn();
jest.mock('cross-spawn', () => {
const spawn = (...args: any) => {
spawnMock(...args);
const child = {
on: (type: string, fn: (code: number) => void) => {
if (type === 'close') {
return fn(0);
}
},
};
return child;
};
return spawn;
});
afterEach(() => {
spawnMock.mockClear();
});
import path from 'path';
import { runNpmInstall, cloneEnv } from '../src';
import type { Meta } from '../src/types';
function getTestSpawnOpts(env: Record<string, string>) {
return { env: cloneEnv(process.env, env) };
}
function getNodeVersion(major: number) {
return { major, range: `${major}.x`, runtime: `nodejs${major}.x` };
}
it('should not include peer dependencies when missing VERCEL_NPM_LEGACY_PEER_DEPS on node16', async () => {
const fixture = path.join(__dirname, 'fixtures', '20-npm-7');
const meta: Meta = {};
const spawnOpts = getTestSpawnOpts({});
const nodeVersion = { major: 16 } as any;
await runNpmInstall(fixture, [], spawnOpts, meta, nodeVersion);
expect(spawnMock.mock.calls.length).toBe(1);
const args = spawnMock.mock.calls[0];
expect(args[0]).toEqual('npm');
expect(args[1]).toEqual(['install', '--no-audit', '--unsafe-perm']);
expect(args[2]).toEqual({
cwd: fixture,
prettyCommand: 'npm install',
stdio: 'inherit',
env: expect.any(Object),
});
});
it('should include peer dependencies when VERCEL_NPM_LEGACY_PEER_DEPS=1 on node16', async () => {
const fixture = path.join(__dirname, 'fixtures', '20-npm-7');
const meta: Meta = {};
const spawnOpts = getTestSpawnOpts({ VERCEL_NPM_LEGACY_PEER_DEPS: '1' });
const nodeVersion = getNodeVersion(16);
await runNpmInstall(fixture, [], spawnOpts, meta, nodeVersion);
expect(spawnMock.mock.calls.length).toBe(1);
const args = spawnMock.mock.calls[0];
expect(args[0]).toEqual('npm');
expect(args[1]).toEqual([
'install',
'--no-audit',
'--unsafe-perm',
'--legacy-peer-deps',
]);
expect(args[2]).toEqual({
cwd: fixture,
prettyCommand: 'npm install',
stdio: 'inherit',
env: expect.any(Object),
});
});
it('should not include peer dependencies when VERCEL_NPM_LEGACY_PEER_DEPS=1 on node14', async () => {
const fixture = path.join(__dirname, 'fixtures', '20-npm-7');
const meta: Meta = {};
const spawnOpts = getTestSpawnOpts({ VERCEL_NPM_LEGACY_PEER_DEPS: '1' });
const nodeVersion = getNodeVersion(14);
await runNpmInstall(fixture, [], spawnOpts, meta, nodeVersion);
expect(spawnMock.mock.calls.length).toBe(1);
const args = spawnMock.mock.calls[0];
expect(args[0]).toEqual('npm');
expect(args[1]).toEqual(['install', '--no-audit', '--unsafe-perm']);
expect(args[2]).toEqual({
cwd: fixture,
prettyCommand: 'npm install',
stdio: 'inherit',
env: expect.any(Object),
});
});
it('should not include peer dependencies when VERCEL_NPM_LEGACY_PEER_DEPS=1 on node16 with corepack enabled', async () => {
const fixture = path.join(__dirname, 'fixtures', '20-npm-7');
const meta: Meta = {};
const spawnOpts = getTestSpawnOpts({
VERCEL_NPM_LEGACY_PEER_DEPS: '1',
ENABLE_EXPERIMENTAL_COREPACK: '1',
});
const nodeVersion = getNodeVersion(16);
await runNpmInstall(fixture, [], spawnOpts, meta, nodeVersion);
expect(spawnMock.mock.calls.length).toBe(1);
const args = spawnMock.mock.calls[0];
expect(args[0]).toEqual('npm');
expect(args[1]).toEqual(['install', '--no-audit', '--unsafe-perm']);
expect(args[2]).toEqual({
cwd: fixture,
prettyCommand: 'npm install',
stdio: 'inherit',
env: expect.any(Object),
});
});
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);
expect(spawnMock.mock.calls.length).toBe(1);
const args = spawnMock.mock.calls[0];
expect(args[0]).toEqual('yarn');
expect(args[1]).toEqual(['install']);
expect(args[2]).toEqual({
cwd: apiDir,
prettyCommand: 'yarn install',
stdio: 'inherit',
env: expect.any(Object),
});
});
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);
expect(spawnMock.mock.calls.length).toBe(1);
const args = spawnMock.mock.calls[0];
expect(args[0]).toEqual('yarn');
expect(args[1]).toEqual(['install']);
expect(args[2]).toEqual({
cwd: apiDir,
prettyCommand: 'yarn install',
stdio: 'inherit',
env: expect.any(Object),
});
});

View File

@@ -1,7 +1,6 @@
import ms from 'ms';
import path from 'path';
import fs, { readlink } from 'fs-extra';
import retry from 'async-retry';
import { strict as assert, strictEqual } from 'assert';
import { createZip } from '../src/lambda';
import { getSupportedNodeVersion } from '../src/fs/node-version';
@@ -16,7 +15,6 @@ import {
runPackageJsonScript,
scanParentDirs,
FileBlob,
Meta,
} from '../src';
jest.setTimeout(10 * 1000);
@@ -577,54 +575,3 @@ it('should detect package.json in nested frontend', async () => {
// There is no lockfile but this test will pick up vercel/vercel/yarn.lock
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
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 retryOpts = { maxRetryTime: 1000 };
let run1, run2, run3;
await retry(async () => {
run1 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run1).toEqual(true);
expect(
(meta.runNpmInstallSet as Set<string>).has(
path.join(fixture, 'package.json')
)
).toEqual(true);
}, retryOpts);
await retry(async () => {
run2 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run2).toEqual(false);
}, retryOpts);
await retry(async () => {
run3 = await runNpmInstall(fixture, [], undefined, meta);
expect(run3).toEqual(false);
}, retryOpts);
});
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');
let results: [boolean, boolean, boolean] | undefined;
await retry(
async () => {
results = await Promise.all([
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(fixture, [], undefined, meta),
]);
},
{ maxRetryTime: 3000 }
);
const [run1, run2, run3] = results || [];
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",
"version": "28.2.5",
"version": "28.3.0",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -41,16 +41,16 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "5.4.2",
"@vercel/go": "2.2.5",
"@vercel/hydrogen": "0.0.18",
"@vercel/next": "3.1.25",
"@vercel/node": "2.5.14",
"@vercel/python": "3.1.14",
"@vercel/redwood": "1.0.23",
"@vercel/remix": "1.0.24",
"@vercel/ruby": "1.3.31",
"@vercel/static-build": "1.0.23",
"@vercel/build-utils": "5.4.3",
"@vercel/go": "2.2.6",
"@vercel/hydrogen": "0.0.19",
"@vercel/next": "3.1.26",
"@vercel/node": "2.5.15",
"@vercel/python": "3.1.15",
"@vercel/redwood": "1.0.24",
"@vercel/remix": "1.0.25",
"@vercel/ruby": "1.3.32",
"@vercel/static-build": "1.0.24",
"update-notifier": "5.1.0"
},
"devDependencies": {
@@ -95,9 +95,9 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.2.4",
"@vercel/frameworks": "1.1.5",
"@vercel/fs-detectors": "3.2.0",
"@vercel/client": "12.2.5",
"@vercel/frameworks": "1.1.6",
"@vercel/fs-detectors": "3.3.0",
"@vercel/fun": "1.0.4",
"@vercel/ncc": "0.24.0",
"@zeit/source-map-support": "0.6.2",

View File

@@ -31,6 +31,7 @@ import {
} from '@vercel/routing-utils';
import {
Builder,
cloneEnv,
Env,
StartDevServerResult,
FileFsRef,
@@ -2222,18 +2223,22 @@ export default class DevServer {
const port = await getPort();
const env: Env = {
// Because of child process 'pipe' below, isTTY will be false.
// Most frameworks use `chalk`/`supports-color` so we enable it anyway.
FORCE_COLOR: process.stdout.isTTY ? '1' : '0',
// Prevent framework dev servers from automatically opening a web
// browser window, since it will not be the port that `vc dev`
// is listening on and thus will be missing Vercel features.
BROWSER: 'none',
...process.env,
...this.envConfigs.allEnv,
PORT: `${port}`,
};
const env: Env = cloneEnv(
{
// Because of child process 'pipe' below, isTTY will be false.
// Most frameworks use `chalk`/`supports-color` so we enable it anyway.
FORCE_COLOR: process.stdout.isTTY ? '1' : '0',
// Prevent framework dev servers from automatically opening a web
// browser window, since it will not be the port that `vc dev`
// is listening on and thus will be missing Vercel features.
BROWSER: 'none',
},
process.env,
this.envConfigs.allEnv,
{
PORT: `${port}`,
}
);
// This is necesary so that the dev command in the Project
// will work cross-platform (especially Windows).

View File

@@ -12,6 +12,8 @@ import vercelNodePkg from '@vercel/node/package.json';
jest.setTimeout(ms('30 seconds'));
const repoRoot = join(__dirname, '../../../../../..');
describe('importBuilders()', () => {
it('should import built-in Builders', async () => {
const specs = new Set(['@vercel/node', '@vercel/next']);
@@ -19,6 +21,12 @@ describe('importBuilders()', () => {
expect(builders.size).toEqual(2);
expect(builders.get('@vercel/node')?.pkg).toMatchObject(vercelNodePkg);
expect(builders.get('@vercel/next')?.pkg).toMatchObject(vercelNextPkg);
expect(builders.get('@vercel/node')?.pkgPath).toEqual(
join(repoRoot, 'packages/node/package.json')
);
expect(builders.get('@vercel/next')?.pkgPath).toEqual(
join(repoRoot, 'packages/next/package.json')
);
expect(typeof builders.get('@vercel/node')?.builder.build).toEqual(
'function'
);
@@ -27,7 +35,82 @@ describe('importBuilders()', () => {
);
});
it('should import 3rd party Builders', async () => {
it('should import built-in Builders using `@latest`', async () => {
const specs = new Set(['@vercel/node@latest', '@vercel/next@latest']);
const builders = await importBuilders(specs, process.cwd(), client.output);
expect(builders.size).toEqual(2);
expect(builders.get('@vercel/node@latest')?.pkg).toMatchObject(
vercelNodePkg
);
expect(builders.get('@vercel/next@latest')?.pkg).toMatchObject(
vercelNextPkg
);
expect(builders.get('@vercel/node@latest')?.pkgPath).toEqual(
join(repoRoot, 'packages/node/package.json')
);
expect(builders.get('@vercel/next@latest')?.pkgPath).toEqual(
join(repoRoot, 'packages/next/package.json')
);
expect(typeof builders.get('@vercel/node@latest')?.builder.build).toEqual(
'function'
);
expect(typeof builders.get('@vercel/next@latest')?.builder.build).toEqual(
'function'
);
});
it('should import built-in Builders using `@canary`', async () => {
const specs = new Set(['@vercel/node@canary', '@vercel/next@canary']);
const builders = await importBuilders(specs, process.cwd(), client.output);
expect(builders.size).toEqual(2);
expect(builders.get('@vercel/node@canary')?.pkg).toMatchObject(
vercelNodePkg
);
expect(builders.get('@vercel/next@canary')?.pkg).toMatchObject(
vercelNextPkg
);
expect(builders.get('@vercel/node@canary')?.pkgPath).toEqual(
join(repoRoot, 'packages/node/package.json')
);
expect(builders.get('@vercel/next@canary')?.pkgPath).toEqual(
join(repoRoot, 'packages/next/package.json')
);
expect(typeof builders.get('@vercel/node@canary')?.builder.build).toEqual(
'function'
);
expect(typeof builders.get('@vercel/next@canary')?.builder.build).toEqual(
'function'
);
});
it('should install and import 1st party Builders with explicit version', async () => {
if (process.platform === 'win32') {
// this test creates symlinks which require admin by default on Windows
console.log('Skipping test on Windows');
return;
}
const cwd = await getWriteableDirectory();
try {
const spec = '@vercel/node@2.0.0';
const specs = new Set([spec]);
const builders = await importBuilders(specs, cwd, client.output);
expect(builders.size).toEqual(1);
expect(builders.get(spec)?.pkg.name).toEqual('@vercel/node');
expect(builders.get(spec)?.pkg.version).toEqual('2.0.0');
expect(builders.get(spec)?.pkgPath).toEqual(
join(cwd, '.vercel/builders/node_modules/@vercel/node/package.json')
);
expect(typeof builders.get(spec)?.builder.build).toEqual('function');
await expect(client.stderr).toOutput(
'> Installing Builder: @vercel/node'
);
} finally {
await remove(cwd);
}
});
it('should install and import 3rd party Builders', async () => {
if (process.platform === 'win32') {
// this test creates symlinks which require admin by default on Windows
console.log('Skipping test on Windows');
@@ -46,6 +129,7 @@ describe('importBuilders()', () => {
expect(builders.get(spec)?.pkgPath).toEqual(
join(cwd, '.vercel/builders/node_modules/vercel-deno/package.json')
);
expect(typeof builders.get(spec)?.builder.build).toEqual('function');
expect(builders.get(tarballSpec)?.pkg.name).toEqual('vercel-bash');
expect(builders.get(tarballSpec)?.pkg.version).toEqual('4.1.0');
expect(builders.get(tarballSpec)?.pkgPath).toEqual(
@@ -62,6 +146,30 @@ describe('importBuilders()', () => {
}
});
it('should install and import legacy `@now/build-utils` Builders', async () => {
if (process.platform === 'win32') {
// this test creates symlinks which require admin by default on Windows
console.log('Skipping test on Windows');
return;
}
const cwd = await getWriteableDirectory();
try {
const spec = '@frontity/now@1.2.0';
const specs = new Set([spec]);
const builders = await importBuilders(specs, cwd, client.output);
expect(builders.size).toEqual(1);
expect(builders.get(spec)?.pkg.name).toEqual('@frontity/now');
expect(builders.get(spec)?.pkg.version).toEqual('1.2.0');
expect(builders.get(spec)?.pkgPath).toEqual(
join(cwd, '.vercel/builders/node_modules/@frontity/now/package.json')
);
expect(typeof builders.get(spec)?.builder.build).toEqual('function');
} finally {
await remove(cwd);
}
});
it('should throw when importing a Builder that is not on npm registry', async () => {
let err: Error | undefined;
const cwd = await getWriteableDirectory();
@@ -86,30 +194,6 @@ describe('importBuilders()', () => {
'https://vercel.link/builder-dependencies-install-failed'
);
});
it('should import legacy `@now/build-utils` Builders', async () => {
if (process.platform === 'win32') {
// this test creates symlinks which require admin by default on Windows
console.log('Skipping test on Windows');
return;
}
const cwd = await getWriteableDirectory();
try {
const spec = '@frontity/now@1.2.0';
const specs = new Set([spec]);
const builders = await importBuilders(specs, cwd, client.output);
expect(builders.size).toEqual(1);
expect(builders.get(spec)?.pkg.name).toEqual('@frontity/now');
expect(builders.get(spec)?.pkg.version).toEqual('1.2.0');
expect(builders.get(spec)?.pkgPath).toEqual(
join(cwd, '.vercel/builders/node_modules/@frontity/now/package.json')
);
expect(typeof builders.get(spec)?.builder.build).toEqual('function');
} finally {
await remove(cwd);
}
});
});
describe('resolveBuilders()', () => {

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "12.2.4",
"version": "12.2.5",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -43,7 +43,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "5.4.2",
"@vercel/build-utils": "5.4.3",
"@vercel/routing-utils": "2.0.2",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/frameworks",
"version": "1.1.5",
"version": "1.1.6",
"main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts",
"files": [

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/fs-detectors",
"version": "3.2.0",
"version": "3.3.0",
"description": "Vercel filesystem detectors",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -19,7 +19,7 @@
"test-unit": "yarn test"
},
"dependencies": {
"@vercel/frameworks": "1.1.5",
"@vercel/frameworks": "1.1.6",
"@vercel/routing-utils": "2.0.2",
"glob": "8.0.3",
"js-yaml": "4.1.0",

View File

@@ -33,11 +33,8 @@ export async function getWorkspacePackagePaths({
case 'pnpm':
results = await getPnpmWorkspacePackagePaths({ fs: workspaceFs });
break;
case 'rush':
// /common/temp is the location that Rush defaults adding the pnpm workspace file
results = await getPnpmWorkspacePackagePaths({
fs: fs.chdir('/common/temp'),
});
case 'nx':
results = await getNxWorkspacePackagePaths({ fs: workspaceFs });
break;
default:
throw new Error(`Unknown workspace implementation: ${type}`);
@@ -109,6 +106,17 @@ async function getPackageJsonWorkspacePackagePaths({
return getPackagePaths(packages, fs);
}
async function getNxWorkspacePackagePaths({
fs,
}: GetPackagePathOptions): Promise<string[]> {
const nxWorkspaceJsonAsBuffer = await fs.readFile('workspace.json');
const { projects } = JSON.parse(nxWorkspaceJsonAsBuffer.toString());
const packages: string[] = Object.values(projects);
return getPackagePaths(packages, fs);
}
async function getPnpmWorkspacePackagePaths({
fs,
}: GetPackagePathOptions): Promise<string[]> {

View File

@@ -12,7 +12,7 @@ export interface GetWorkspaceOptions {
cwd?: string;
}
export type WorkspaceType = 'yarn' | 'pnpm' | 'npm' | 'rush';
export type WorkspaceType = 'yarn' | 'pnpm' | 'npm' | 'nx';
export type Workspace = {
type: WorkspaceType;

View File

@@ -60,13 +60,13 @@ export const workspaceManagers: Array<
},
},
{
name: 'rush',
slug: 'rush',
name: 'nx',
slug: 'nx',
detectors: {
every: [
{
path: 'rush.json',
matchContent: '"useWorkspaces":\\strue',
path: 'workspace.json',
matchContent: '"projects":\\s*{[^}]',
},
],
},

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,7 @@
{
"version": 2,
"projects": {
"myblog": "apps/app-one",
"svelte-app": "apps/app-two"
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,782 @@
{
"name": "42-npm-workspace-with-nx",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.0.0",
"workspaces": [
"apps/app-one",
"apps/app-two"
]
},
"apps/app-one": {
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"debug": "^4.3.2"
}
},
"apps/app-two": {
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
},
"node_modules/a": {
"resolved": "a",
"link": true
},
"node_modules/ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"engines": {
"node": ">=4"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/b": {
"resolved": "b",
"link": true
},
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"engines": {
"node": ">=6"
}
},
"node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/cliui/node_modules/ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dependencies": {
"ansi-regex": "^5.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/cowsay": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/cowsay/-/cowsay-1.5.0.tgz",
"integrity": "sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==",
"dependencies": {
"get-stdin": "8.0.0",
"string-width": "~2.1.1",
"strip-final-newline": "2.0.0",
"yargs": "15.4.1"
},
"bin": {
"cowsay": "cli.js",
"cowthink": "cli.js"
},
"engines": {
"node": ">= 4"
}
},
"node_modules/debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-stdin": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
"integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"engines": {
"node": ">=4"
}
},
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"engines": {
"node": ">=8"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"node_modules/string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dependencies": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dependencies": {
"ansi-regex": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"engines": {
"node": ">=6"
}
},
"node_modules/which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dependencies": {
"ansi-regex": "^5.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/yargs/node_modules/ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dependencies": {
"ansi-regex": "^5.0.0"
},
"engines": {
"node": ">=8"
}
}
},
"dependencies": {
"a": {
"version": "file:a",
"requires": {
"debug": "^4.3.2"
}
},
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"b": {
"version": "file:b",
"requires": {
"cowsay": "^1.5.0"
}
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"cowsay": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/cowsay/-/cowsay-1.5.0.tgz",
"integrity": "sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==",
"requires": {
"get-stdin": "8.0.0",
"string-width": "~2.1.1",
"strip-final-newline": "2.0.0",
"yargs": "15.4.1"
}
},
"debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"requires": {
"ms": "2.1.2"
}
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-stdin": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
"integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg=="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"requires": {
"p-locate": "^4.1.0"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"requires": {
"p-limit": "^2.2.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "^3.0.0"
}
},
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
}

View File

@@ -0,0 +1,8 @@
{
"name": "42-npm-workspace-with-nx",
"version": "1.0.0",
"workspaces": [
"apps/app-one",
"apps/app-two"
]
}

View File

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

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

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

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,4 @@
{
"version": 2,
"projects": "hello"
}

View File

@@ -1,5 +1,6 @@
import path from 'path';
import frameworkList from '@vercel/frameworks';
import workspaceManagers from '../src/workspaces/workspace-managers';
import { detectFramework, DetectorFilesystem } from '../src';
import { Stat } from '../src/detectors/filesystem';
@@ -271,6 +272,28 @@ describe('DetectorFilesystem', () => {
expect(await detectFramework({ fs, frameworkList })).toBe(null);
});
it('Detect nx', async () => {
const fs = new VirtualFilesystem({
'workspace.json': JSON.stringify({
projects: { 'app-one': 'apps/app-one' },
}),
});
expect(
await detectFramework({ fs, frameworkList: workspaceManagers })
).toBe('nx');
});
it('Do not detect anything', async () => {
const fs = new VirtualFilesystem({
'workspace.json': JSON.stringify({ projects: {} }),
});
expect(
await detectFramework({ fs, frameworkList: workspaceManagers })
).toBe(null);
});
it('Detect Next.js', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({

View File

@@ -14,6 +14,10 @@ describe.each<[string, string[]]>([
['/backend/c', '/backend/d', '/frontend/a', '/frontend/b'],
],
['22-pnpm', []],
['41-nx-workspace', ['/apps/app-one', '/apps/app-two']],
['42-npm-workspace-with-nx', ['/apps/app-one', '/apps/app-two']],
['43-nx-json-misshaped', []],
['44-nx-json-string', []],
])('`getWorkspacesPackagePaths()`', (fixturePath, packagePaths) => {
const testName =
packagePaths.length > 0

View File

@@ -27,6 +27,7 @@ import {
getWriteableDirectory,
shouldServe,
debug,
cloneEnv,
} from '@vercel/build-utils';
const TMP = tmpdir();
@@ -694,11 +695,9 @@ Learn more: https://vercel.com/docs/runtimes#official-runtimes/go`
`vercel-dev-port-${Math.random().toString(32).substring(2)}`
);
const env: typeof process.env = {
...process.env,
...meta.env,
const env = cloneEnv(process.env, meta.env, {
VERCEL_DEV_PORT_FILE: portFile,
};
});
const tmpRelative = `.${sep}${entrypointDir}`;
const child = spawn('go', ['run', tmpRelative], {

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/go",
"version": "2.2.5",
"version": "2.2.6",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
@@ -35,7 +35,7 @@
"@types/jest": "28.1.6",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"@vercel/build-utils": "5.4.2",
"@vercel/build-utils": "5.4.3",
"@vercel/ncc": "0.24.0",
"async-retry": "1.3.1",
"execa": "^1.0.0",

View File

@@ -0,0 +1,10 @@
package handler
import (
"fmt"
"net/http"
)
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello:RANDOMNESS_PLACEHOLDER")
}

View File

@@ -0,0 +1,10 @@
package handler
import (
"fmt"
"net/http"
)
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hi:RANDOMNESS_PLACEHOLDER")
}

View File

@@ -0,0 +1,3 @@
module github.com/vercel/vercel/packages/go/test/fixtures/21-square-brackets-with-go-mod
go 1.16

View File

@@ -0,0 +1,9 @@
{
"probes": [
{
"path": "/api/a",
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
},
{ "path": "/api/sub/b", "mustContain": "hi:RANDOMNESS_PLACEHOLDER" }
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/hydrogen",
"version": "0.0.18",
"version": "0.0.19",
"license": "MIT",
"main": "./dist/index.js",
"homepage": "https://vercel.com/docs",
@@ -21,7 +21,7 @@
"devDependencies": {
"@types/jest": "27.5.1",
"@types/node": "*",
"@vercel/build-utils": "5.4.2",
"@vercel/build-utils": "5.4.3",
"typescript": "4.6.4"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "3.1.25",
"version": "3.1.26",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -8,7 +8,7 @@
"build": "node build.js",
"build-dev": "node build.js --dev",
"test": "jest --env node --verbose --bail --runInBand",
"test-unit": "yarn test test/build.test.ts test/unit/",
"test-unit": "yarn test test/unit/",
"test-next-local": "jest --env node --verbose --bail --forceExit --testTimeout=360000 test/integration/*.test.js test/integration/*.test.ts",
"test-next-local:middleware": "jest --env node --verbose --bail --useStderr --testTimeout=360000 test/integration/middleware.test.ts",
"test-integration-once": "rm test/builder-info.json; jest --env node --verbose --runInBand --bail test/fixtures/**/*.test.js"
@@ -44,7 +44,7 @@
"@types/semver": "6.0.0",
"@types/text-table": "0.2.1",
"@types/webpack-sources": "3.2.0",
"@vercel/build-utils": "5.4.2",
"@vercel/build-utils": "5.4.3",
"@vercel/nft": "0.22.1",
"@vercel/routing-utils": "2.0.2",
"async-sema": "3.0.1",

View File

@@ -675,7 +675,7 @@ export const build: BuildV2 = async ({
}
}
let dynamicPrefix = path.join('/', entryDirectory);
let dynamicPrefix = path.posix.join('/', entryDirectory);
dynamicPrefix = dynamicPrefix === '/' ? '' : dynamicPrefix;
if (imagesManifest) {
@@ -824,7 +824,7 @@ export const build: BuildV2 = async ({
// Make sure to 404 for the /404 path itself
{
src: path.join('/', entryDirectory, '404/?'),
src: path.posix.join('/', entryDirectory, '404/?'),
status: 404,
continue: true,
},
@@ -838,7 +838,7 @@ export const build: BuildV2 = async ({
...(routesManifest?.basePath
? [
{
src: path.join('/', entryDirectory, '_next/image/?'),
src: path.posix.join('/', entryDirectory, '_next/image/?'),
dest: '/_next/image',
check: true,
},
@@ -848,12 +848,12 @@ export const build: BuildV2 = async ({
// No-op _next/data rewrite to trigger handle: 'rewrites' and then 404
// if no match to prevent rewriting _next/data unexpectedly
{
src: path.join('/', entryDirectory, '_next/data/(.*)'),
dest: path.join('/', entryDirectory, '_next/data/$1'),
src: path.posix.join('/', entryDirectory, '_next/data/(.*)'),
dest: path.posix.join('/', entryDirectory, '_next/data/$1'),
check: true,
},
{
src: path.join('/', entryDirectory, '_next/data/(.*)'),
src: path.posix.join('/', entryDirectory, '_next/data/(.*)'),
status: 404,
},
@@ -867,14 +867,14 @@ export const build: BuildV2 = async ({
...fallbackRewrites,
{ src: path.join('/', entryDirectory, '.*'), status: 404 },
{ src: path.posix.join('/', entryDirectory, '.*'), status: 404 },
// We need to make sure to 404 for /_next after handle: miss since
// handle: miss is called before rewrites and to prevent rewriting
// /_next
{ handle: 'miss' },
{
src: path.join(
src: path.posix.join(
'/',
entryDirectory,
'_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|image|media)/.+'
@@ -894,7 +894,7 @@ export const build: BuildV2 = async ({
{
// This ensures we only match known emitted-by-Next.js files and not
// user-emitted files which may be missing a hash in their filename.
src: path.join(
src: path.posix.join(
'/',
entryDirectory,
`_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|image|media|${escapedBuildId})/.+`
@@ -909,15 +909,15 @@ export const build: BuildV2 = async ({
},
// error handling
...(output[path.join('./', entryDirectory, '404')] ||
output[path.join('./', entryDirectory, '404/index')]
...(output[path.posix.join('./', entryDirectory, '404')] ||
output[path.posix.join('./', entryDirectory, '404/index')]
? [
{ handle: 'error' } as RouteWithHandle,
{
status: 404,
src: path.join(entryDirectory, '.*'),
dest: path.join('/', entryDirectory, '404'),
src: path.posix.join(entryDirectory, '.*'),
dest: path.posix.join('/', entryDirectory, '404'),
},
]
: []),
@@ -1063,7 +1063,7 @@ export const build: BuildV2 = async ({
}
debug(`Creating serverless function for page: "${page}"...`);
lambdas[path.join(entryDirectory, pathname)] = new NodejsLambda({
lambdas[path.posix.join(entryDirectory, pathname)] = new NodejsLambda({
files: {
...nextFiles,
...pageFiles,
@@ -1114,30 +1114,32 @@ export const build: BuildV2 = async ({
prerenderManifest,
routesManifest
);
hasStatic500 = !!staticPages[path.join(entryDirectory, '500')];
hasStatic500 = !!staticPages[path.posix.join(entryDirectory, '500')];
// this can be either 404.html in latest versions
// or _errors/404.html versions while this was experimental
static404Page =
staticPages[path.join(entryDirectory, '404')] && hasPages404
? path.join(entryDirectory, '404')
: staticPages[path.join(entryDirectory, '_errors/404')]
? path.join(entryDirectory, '_errors/404')
staticPages[path.posix.join(entryDirectory, '404')] && hasPages404
? path.posix.join(entryDirectory, '404')
: staticPages[path.posix.join(entryDirectory, '_errors/404')]
? path.posix.join(entryDirectory, '_errors/404')
: undefined;
const { i18n } = routesManifest || {};
if (!static404Page && i18n) {
static404Page = staticPages[
path.join(entryDirectory, i18n.defaultLocale, '404')
path.posix.join(entryDirectory, i18n.defaultLocale, '404')
]
? path.join(entryDirectory, i18n.defaultLocale, '404')
? path.posix.join(entryDirectory, i18n.defaultLocale, '404')
: undefined;
}
if (!hasStatic500 && i18n) {
hasStatic500 =
!!staticPages[path.join(entryDirectory, i18n.defaultLocale, '500')];
!!staticPages[
path.posix.join(entryDirectory, i18n.defaultLocale, '500')
];
}
if (routesManifest) {
@@ -1171,7 +1173,7 @@ export const build: BuildV2 = async ({
src: (
dataRoute.namedDataRouteRegex || dataRoute.dataRouteRegex
).replace(/^\^/, `^${appMountPrefixNoTrailingSlash}`),
dest: path.join(
dest: path.posix.join(
'/',
entryDirectory,
// make sure to route SSG data route to the data prerender

View File

@@ -169,7 +169,7 @@ export async function serverBuild({
CORRECTED_MANIFESTS_VERSION
);
let hasStatic500 = !!staticPages[path.join(entryDirectory, '500')];
let hasStatic500 = !!staticPages[path.posix.join(entryDirectory, '500')];
if (lambdaPageKeys.length === 0) {
throw new NowBuildError({
@@ -187,23 +187,23 @@ export async function serverBuild({
const hasPages404 = routesManifest.pages404;
let static404Page =
staticPages[path.join(entryDirectory, '404')] && hasPages404
? path.join(entryDirectory, '404')
: staticPages[path.join(entryDirectory, '_errors/404')]
? path.join(entryDirectory, '_errors/404')
staticPages[path.posix.join(entryDirectory, '404')] && hasPages404
? path.posix.join(entryDirectory, '404')
: staticPages[path.posix.join(entryDirectory, '_errors/404')]
? path.posix.join(entryDirectory, '_errors/404')
: undefined;
if (!static404Page && i18n) {
static404Page = staticPages[
path.join(entryDirectory, i18n.defaultLocale, '404')
path.posix.join(entryDirectory, i18n.defaultLocale, '404')
]
? path.join(entryDirectory, i18n.defaultLocale, '404')
? path.posix.join(entryDirectory, i18n.defaultLocale, '404')
: undefined;
}
if (!hasStatic500 && i18n) {
hasStatic500 =
!!staticPages[path.join(entryDirectory, i18n.defaultLocale, '500')];
!!staticPages[path.posix.join(entryDirectory, i18n.defaultLocale, '500')];
}
const lstatSema = new Sema(25);
@@ -377,7 +377,7 @@ export async function serverBuild({
if (i18n) {
for (const locale of i18n.locales) {
const static404File =
staticPages[path.join(entryDirectory, locale, '/404')] ||
staticPages[path.posix.join(entryDirectory, locale, '/404')] ||
new FileFsRef({
fsPath: path.join(pagesDir, locale, '/404.html'),
});
@@ -520,14 +520,14 @@ export async function serverBuild({
if (
entryDirectory !== '.' &&
path.join('/', entryDirectory) !== routesManifest.basePath
path.posix.join('/', entryDirectory) !== routesManifest.basePath
) {
// we normalize the entryDirectory in the request URL since
// Next.js isn't aware of it and it isn't included in the
// x-matched-path header
launcher = launcher.replace(
'// entryDirectory handler',
`req.url = req.url.replace(/^${path
`req.url = req.url.replace(/^${path.posix
.join('/', entryDirectory)
.replace(/\//g, '\\/')}/, '')`
);
@@ -848,7 +848,7 @@ export async function serverBuild({
}
const outputName = normalizeIndexOutput(
path.join(entryDirectory, pageNoExt),
path.posix.join(entryDirectory, pageNoExt),
true
);
@@ -863,7 +863,7 @@ export async function serverBuild({
for (const locale of i18n.locales) {
lambdas[
normalizeIndexOutput(
path.join(
path.posix.join(
entryDirectory,
locale,
pageNoExt === 'index' ? '' : pageNoExt
@@ -918,7 +918,7 @@ export async function serverBuild({
route = normalizeLocalePath(route, routesManifest.i18n.locales).pathname;
}
delete lambdas[
path.join('.', entryDirectory, route === '/' ? 'index' : route)
path.posix.join('.', entryDirectory, route === '/' ? 'index' : route)
];
});
@@ -968,7 +968,7 @@ export async function serverBuild({
// routes since only the status is being modified and we don't want
// to exceed the routes limit
const starterRouteSrc = `^${
entryDirectory !== '.' ? path.join('/', entryDirectory, '()') : '()'
entryDirectory !== '.' ? path.posix.join('/', entryDirectory, '()') : '()'
}`;
let currentRouteSrc = starterRouteSrc;
@@ -1019,14 +1019,14 @@ export async function serverBuild({
? [
// strip _next/data prefix for resolving
{
src: `^${path.join(
src: `^${path.posix.join(
'/',
entryDirectory,
'/_next/data/',
escapedBuildId,
'/(.*).json'
)}`,
dest: `${path.join(
dest: `${path.posix.join(
'/',
entryDirectory,
'/$1',
@@ -1044,14 +1044,18 @@ export async function serverBuild({
// normalize "/index" from "/_next/data/index.json" to -> just "/"
// as matches a rewrite sources will expect just "/"
{
src: path.join('^/', entryDirectory, '/index(?:/)?'),
src: path.posix.join('^/', entryDirectory, '/index(?:/)?'),
has: [
{
type: 'header',
key: 'x-nextjs-data',
},
],
dest: path.join('/', entryDirectory, trailingSlash ? '/' : ''),
dest: path.posix.join(
'/',
entryDirectory,
trailingSlash ? '/' : ''
),
...(isOverride ? { override: true } : {}),
continue: true,
},
@@ -1063,14 +1067,19 @@ export async function serverBuild({
return isNextDataServerResolving
? [
{
src: path.join('^/', entryDirectory, trailingSlash ? '/' : '', '$'),
src: path.posix.join(
'^/',
entryDirectory,
trailingSlash ? '/' : '',
'$'
),
has: [
{
type: 'header',
key: 'x-nextjs-data',
},
],
dest: `${path.join(
dest: `${path.posix.join(
'/',
entryDirectory,
'/_next/data/',
@@ -1081,7 +1090,7 @@ export async function serverBuild({
...(isOverride ? { override: true } : {}),
},
{
src: path.join(
src: path.posix.join(
'^/',
entryDirectory,
'((?!_next/)(?:.*[^/]|.*))/?$'
@@ -1092,7 +1101,7 @@ export async function serverBuild({
key: 'x-nextjs-data',
},
],
dest: `${path.join(
dest: `${path.posix.join(
'/',
entryDirectory,
'/_next/data/',
@@ -1176,7 +1185,7 @@ export async function serverBuild({
// Handle auto-adding current default locale to path based on
// $wildcard
{
src: `^${path.join(
src: `^${path.posix.join(
'/',
entryDirectory,
'/'
@@ -1186,7 +1195,9 @@ export async function serverBuild({
// we aren't able to ensure trailing slash mode here
// so ensure this comes after the trailing slash redirect
dest: `${
entryDirectory !== '.' ? path.join('/', entryDirectory) : ''
entryDirectory !== '.'
? path.posix.join('/', entryDirectory)
: ''
}$wildcard/$1`,
continue: true,
},
@@ -1197,7 +1208,10 @@ export async function serverBuild({
i18n.localeDetection !== false
? [
{
src: `^${path.join('/', entryDirectory)}/?(?:${i18n.locales
src: `^${path.posix.join(
'/',
entryDirectory
)}/?(?:${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})?/?$`,
locale: {
@@ -1251,8 +1265,12 @@ export async function serverBuild({
: []),
{
src: `^${path.join('/', entryDirectory)}$`,
dest: `${path.join('/', entryDirectory, i18n.defaultLocale)}`,
src: `^${path.posix.join('/', entryDirectory)}$`,
dest: `${path.posix.join(
'/',
entryDirectory,
i18n.defaultLocale
)}`,
continue: true,
},
@@ -1262,14 +1280,18 @@ export async function serverBuild({
// e.g. for /de/posts/[slug] x-now-route-matches would have
// 1=posts%2Fpost-1
{
src: `^${path.join(
src: `^${path.posix.join(
'/',
entryDirectory,
'/'
)}(?!(?:_next/.*|${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})(?:/.*|$))(.*)$`,
dest: `${path.join('/', entryDirectory, i18n.defaultLocale)}/$1`,
dest: `${path.posix.join(
'/',
entryDirectory,
i18n.defaultLocale
)}/$1`,
continue: true,
},
]
@@ -1293,7 +1315,11 @@ export async function serverBuild({
...(i18n
? [
{
src: `${path.join('/', entryDirectory, '/')}(?:${i18n.locales
src: `${path.posix.join(
'/',
entryDirectory,
'/'
)}(?:${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})?[/]?404/?`,
status: 404,
@@ -1302,7 +1328,7 @@ export async function serverBuild({
]
: [
{
src: path.join('/', entryDirectory, '404/?'),
src: path.posix.join('/', entryDirectory, '404/?'),
status: 404,
continue: true,
},
@@ -1314,7 +1340,11 @@ export async function serverBuild({
: i18n
? [
{
src: `${path.join('/', entryDirectory, '/')}(?:${i18n.locales
src: `${path.posix.join(
'/',
entryDirectory,
'/'
)}(?:${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})?[/]?500`,
status: 500,
@@ -1323,7 +1353,7 @@ export async function serverBuild({
]
: [
{
src: path.join('/', entryDirectory, '500'),
src: path.posix.join('/', entryDirectory, '500'),
status: 500,
continue: true,
},
@@ -1346,7 +1376,7 @@ export async function serverBuild({
...(routesManifest?.basePath
? [
{
src: path.join('/', entryDirectory, '_next/image/?'),
src: path.posix.join('/', entryDirectory, '_next/image/?'),
dest: '/_next/image',
check: true,
},
@@ -1361,8 +1391,8 @@ export async function serverBuild({
// No-op _next/data rewrite to trigger handle: 'rewrites' and then 404
// if no match to prevent rewriting _next/data unexpectedly
{
src: path.join('/', entryDirectory, '_next/data/(.*)'),
dest: path.join('/', entryDirectory, '_next/data/$1'),
src: path.posix.join('/', entryDirectory, '_next/data/(.*)'),
dest: path.posix.join('/', entryDirectory, '_next/data/$1'),
check: true,
},
]
@@ -1378,13 +1408,13 @@ export async function serverBuild({
...fallbackRewrites,
{ src: path.join('/', entryDirectory, '.*'), status: 404 },
{ src: path.posix.join('/', entryDirectory, '.*'), status: 404 },
// We need to make sure to 404 for /_next after handle: miss since
// handle: miss is called before rewrites and to prevent rewriting /_next
{ handle: 'miss' },
{
src: path.join(
src: path.posix.join(
'/',
entryDirectory,
'_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|image|media)/.+'
@@ -1399,10 +1429,10 @@ export async function serverBuild({
...(i18n
? [
{
src: `^${path.join('/', entryDirectory)}/?(?:${i18n.locales
src: `^${path.posix.join('/', entryDirectory)}/?(?:${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})/(.*)`,
dest: `${path.join('/', entryDirectory, '/')}$1`,
dest: `${path.posix.join('/', entryDirectory, '/')}$1`,
check: true,
},
]
@@ -1474,8 +1504,8 @@ export async function serverBuild({
// ensure we 404 for non-existent _next/data routes before
// trying page dynamic routes
{
src: path.join('/', entryDirectory, '_next/data/(.*)'),
dest: path.join('/', entryDirectory, '404'),
src: path.posix.join('/', entryDirectory, '_next/data/(.*)'),
dest: path.posix.join('/', entryDirectory, '404'),
status: 404,
},
]
@@ -1488,7 +1518,7 @@ export async function serverBuild({
...(isNextDataServerResolving
? [
{
src: `^${path.join(
src: `^${path.posix.join(
'/',
entryDirectory,
'/_next/data/',
@@ -1504,7 +1534,7 @@ export async function serverBuild({
// add a catch-all data route so we don't 404 when getting
// middleware effects
{
src: `^${path.join(
src: `^${path.posix.join(
'/',
entryDirectory,
'/_next/data/',
@@ -1522,7 +1552,7 @@ export async function serverBuild({
{
// This ensures we only match known emitted-by-Next.js files and not
// user-emitted files which may be missing a hash in their filename.
src: path.join(
src: path.posix.join(
'/',
entryDirectory,
`_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|image|media|${escapedBuildId})/.+`
@@ -1539,7 +1569,7 @@ export async function serverBuild({
// TODO: remove below workaround when `/` is allowed to be output
// different than `/index`
{
src: path.join('/', entryDirectory, '/index'),
src: path.posix.join('/', entryDirectory, '/index'),
headers: {
'x-matched-path': '/',
},
@@ -1547,7 +1577,7 @@ export async function serverBuild({
important: true,
},
{
src: path.join('/', entryDirectory, `/((?!index$).*)`),
src: path.posix.join('/', entryDirectory, `/((?!index$).*)`),
headers: {
'x-matched-path': '/$1',
},
@@ -1562,20 +1592,20 @@ export async function serverBuild({
...(i18n && (static404Page || hasIsr404Page || lambdaPages['404.js'])
? [
{
src: `${path.join(
src: `${path.posix.join(
'/',
entryDirectory,
'/'
)}(?<nextLocale>${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})(/.*|$)`,
dest: path.join('/', entryDirectory, '/$nextLocale/404'),
dest: path.posix.join('/', entryDirectory, '/$nextLocale/404'),
status: 404,
caseSensitive: true,
},
{
src: path.join('/', entryDirectory, '.*'),
dest: path.join(
src: path.posix.join('/', entryDirectory, '.*'),
dest: path.posix.join(
'/',
entryDirectory,
`/${i18n.defaultLocale}/404`
@@ -1585,13 +1615,13 @@ export async function serverBuild({
]
: [
{
src: path.join('/', entryDirectory, '.*'),
dest: path.join(
src: path.posix.join('/', entryDirectory, '.*'),
dest: path.posix.join(
'/',
entryDirectory,
static404Page ||
hasIsr404Page ||
lambdas[path.join(entryDirectory, '404')]
lambdas[path.posix.join(entryDirectory, '404')]
? '/404'
: '/_error'
),
@@ -1603,20 +1633,20 @@ export async function serverBuild({
...(i18n && (hasStatic500 || hasIsr500Page || lambdaPages['500.js'])
? [
{
src: `${path.join(
src: `${path.posix.join(
'/',
entryDirectory,
'/'
)}(?<nextLocale>${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})(/.*|$)`,
dest: path.join('/', entryDirectory, '/$nextLocale/500'),
dest: path.posix.join('/', entryDirectory, '/$nextLocale/500'),
status: 500,
caseSensitive: true,
},
{
src: path.join('/', entryDirectory, '.*'),
dest: path.join(
src: path.posix.join('/', entryDirectory, '.*'),
dest: path.posix.join(
'/',
entryDirectory,
`/${i18n.defaultLocale}/500`
@@ -1626,13 +1656,13 @@ export async function serverBuild({
]
: [
{
src: path.join('/', entryDirectory, '.*'),
dest: path.join(
src: path.posix.join('/', entryDirectory, '.*'),
dest: path.posix.join(
'/',
entryDirectory,
hasStatic500 ||
hasIsr500Page ||
lambdas[path.join(entryDirectory, '500')]
lambdas[path.posix.join(entryDirectory, '500')]
? '/500'
: '/_error'
),

View File

@@ -290,7 +290,7 @@ export async function getDynamicRoutes(
.map(({ page, regex }: { page: string; regex: string }) => {
return {
src: regex,
dest: !isDev ? path.join('/', entryDirectory, page) : page,
dest: !isDev ? path.posix.join('/', entryDirectory, page) : page,
check: true,
status:
canUsePreviewMode && omittedRoutes?.has(page) ? 404 : undefined,
@@ -315,7 +315,9 @@ export async function getDynamicRoutes(
const { page, namedRegex, regex, routeKeys } = params;
const route: RouteWithSrc = {
src: namedRegex || regex,
dest: `${!isDev ? path.join('/', entryDirectory, page) : page}${
dest: `${
!isDev ? path.posix.join('/', entryDirectory, page) : page
}${
routeKeys
? `?${Object.keys(routeKeys)
.map(key => `${routeKeys[key]}=$${key}`)
@@ -410,7 +412,7 @@ export async function getDynamicRoutes(
pageMatchers.forEach(pageMatcher => {
// in `vercel dev` we don't need to prefix the destination
const dest = !isDev
? path.join('/', entryDirectory, pageMatcher.pageName)
? path.posix.join('/', entryDirectory, pageMatcher.pageName)
: pageMatcher.pageName;
if (pageMatcher && pageMatcher.matcher) {
@@ -465,8 +467,8 @@ export function localizeDynamicRoutes(
// ensure destination has locale prefix to match prerender output
// path so that the prerender object is used
route.dest = route.dest!.replace(
`${path.join('/', entryDirectory, '/')}`,
`${path.join('/', entryDirectory, '$nextLocale', '/')}`
`${path.posix.join('/', entryDirectory, '/')}`,
`${path.posix.join('/', entryDirectory, '$nextLocale', '/')}`
);
}
} else {
@@ -575,7 +577,7 @@ export function filterStaticPages(
return;
}
const staticRoute = path.join(entryDirectory, pathname);
const staticRoute = path.posix.join(entryDirectory, pathname);
staticPages[staticRoute] = staticPageFiles[page];
staticPages[staticRoute].contentType = htmlContentType;
@@ -1616,11 +1618,11 @@ export const onPrerenderRouteInitial = (
// if the 404 page used getStaticProps we need to update static404Page
// since it wasn't populated from the staticPages group
if (routeNoLocale === '/404') {
static404Page = path.join(entryDirectory, routeKey);
static404Page = path.posix.join(entryDirectory, routeKey);
}
if (routeNoLocale === '/500') {
static500Page = path.join(entryDirectory, routeKey);
static500Page = path.posix.join(entryDirectory, routeKey);
}
if (
@@ -1851,7 +1853,10 @@ export const onPrerenderRoute =
'/',
srcRoute == null
? outputPathPageOrig
: path.join(entryDirectory, srcRoute === '/' ? '/index' : srcRoute)
: path.posix.join(
entryDirectory,
srcRoute === '/' ? '/index' : srcRoute
)
),
isServerMode
);
@@ -2066,17 +2071,17 @@ export async function getStaticFiles(
const publicDirectoryFiles: Record<string, FileFsRef> = {};
for (const file of Object.keys(nextStaticFiles)) {
staticFiles[path.join(entryDirectory, `_next/static/${file}`)] =
staticFiles[path.posix.join(entryDirectory, `_next/static/${file}`)] =
nextStaticFiles[file];
}
for (const file of Object.keys(staticFolderFiles)) {
staticDirectoryFiles[path.join(entryDirectory, 'static', file)] =
staticDirectoryFiles[path.posix.join(entryDirectory, 'static', file)] =
staticFolderFiles[file];
}
for (const file of Object.keys(publicFolderFiles)) {
publicDirectoryFiles[path.join(entryDirectory, file)] =
publicDirectoryFiles[path.posix.join(entryDirectory, file)] =
publicFolderFiles[file];
}

View File

@@ -0,0 +1,10 @@
export default function Root({ children }) {
return (
<html className="this-is-another-document-html">
<head>
<title>{`hello world`}</title>
</head>
<body className="this-is-another-document-body">{children}</body>
</html>
);
}

View File

@@ -1,18 +0,0 @@
export async function getServerSideProps() {
return {
props: {
world: 'world',
},
};
}
export default function Root({ children, world }) {
return (
<html className="this-is-another-document-html">
<head>
<title>{`hello ${world}`}</title>
</head>
<body className="this-is-another-document-body">{children}</body>
</html>
);
}

View File

@@ -1,3 +1,5 @@
'client';
import { useState, useEffect } from 'react';
import style from './style.module.css';

View File

@@ -1,3 +1,5 @@
'client';
import { useState, useEffect } from 'react';
import styles from './style.module.css';

View File

@@ -1,15 +1,7 @@
export async function getServerSideProps({ params }) {
return {
props: {
id: params.id,
},
};
}
export default function DeploymentsPage(props) {
return (
<>
<p>hello from app/dashboard/deployments/[id]. ID is: {props.id}</p>
<p>hello from app/dashboard/deployments/[id]. ID is: {props.params.id}</p>
</>
);
}

View File

@@ -0,0 +1,8 @@
export default function DeploymentsLayout({ message, children }) {
return (
<>
<h2>Deployments hello</h2>
{children}
</>
);
}

View File

@@ -1,16 +0,0 @@
export function getServerSideProps() {
return {
props: {
message: 'hello',
},
};
}
export default function DeploymentsLayout({ message, children }) {
return (
<>
<h2>Deployments {message}</h2>
{children}
</>
);
}

View File

@@ -1,3 +1,5 @@
'client';
export default function LazyComponent() {
return (
<>

View File

@@ -1,4 +1,4 @@
import { ClientComponent } from './test.client.js';
import { ClientComponent } from './test.js';
export default function DashboardIndexPage() {
return (

View File

@@ -1,6 +1,8 @@
'client';
import { useState, lazy } from 'react';
const Lazy = lazy(() => import('./lazy.client.js'));
const Lazy = lazy(() => import('./lazy.js'));
export function ClientComponent() {
let [state] = useState('client');

View File

@@ -1,11 +1,3 @@
export async function getServerSideProps({ params }) {
return {
props: {
params,
},
};
}
export default function IdLayout({ children, params }) {
return (
<>

View File

@@ -1,11 +1,3 @@
export async function getServerSideProps({ params }) {
return {
props: {
params,
},
};
}
export default function IdPage({ children, params }) {
return (
<>

View File

@@ -1,11 +1,3 @@
export async function getServerSideProps({ params }) {
return {
props: {
params,
},
};
}
export default function CategoryLayout({ children, params }) {
return (
<>

View File

@@ -1,11 +1,3 @@
export async function getServerSideProps({ params }) {
return {
props: {
params,
},
};
}
export default function DynamicLayout({ children, params }) {
return (
<>

View File

@@ -0,0 +1,14 @@
export default function Root({ children }) {
return (
<html className="this-is-the-document-html">
<head>
<title>{`hello world`}</title>
</head>
<body className="this-is-the-document-body">{children}</body>
</html>
);
}
export const config = {
revalidate: 0,
};

View File

@@ -1,18 +0,0 @@
export async function getServerSideProps() {
return {
props: {
world: 'world',
},
};
}
export default function Root({ children, custom, world }) {
return (
<html className="this-is-the-document-html">
<head>
<title>{`hello ${world}`}</title>
</head>
<body className="this-is-the-document-body">{children}</body>
</html>
);
}

View File

@@ -0,0 +1,7 @@
export default function DeploymentsPage(props) {
return (
<>
<p>hello from app/partial-match-[id]. ID is: {props.params.id}</p>
</>
);
}

View File

@@ -1,15 +0,0 @@
export async function getServerSideProps({ params }) {
return {
props: {
id: params.id,
},
};
}
export default function DeploymentsPage(props) {
return (
<>
<p>hello from app/partial-match-[id]. ID is: {props.id}</p>
</>
);
}

View File

@@ -1,3 +1,5 @@
'client';
export default function ShouldNotServeClientDotJs(props) {
return (
<>

View File

@@ -0,0 +1,18 @@
import { experimental_use as use } from 'react';
async function getData() {
await new Promise(resolve => setTimeout(resolve, 1000));
return {
message: 'hello from slow layout',
};
}
export default function SlowLayout(props) {
const data = use(getData());
return (
<>
<p id="slow-layout-message">{data.message}</p>
{props.children}
</>
);
}

View File

@@ -1,17 +0,0 @@
export async function getServerSideProps() {
await new Promise(resolve => setTimeout(resolve, 1000));
return {
props: {
message: 'hello from slow layout',
},
};
}
export default function SlowLayout(props) {
return (
<>
<p id="slow-layout-message">{props.message}</p>
{props.children}
</>
);
}

View File

@@ -0,0 +1,13 @@
import { experimental_use as use } from 'react';
async function getData() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
message: 'hello from slow page',
};
}
export default function SlowPage(props) {
const data = use(getData());
return <h1 id="slow-page-message">{data.message}</h1>;
}

View File

@@ -1,12 +0,0 @@
export async function getServerSideProps() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
props: {
message: 'hello from slow page',
},
};
}
export default function SlowPage(props) {
return <h1 id="slow-page-message">{props.message}</h1>;
}

View File

@@ -0,0 +1,18 @@
import { experimental_use as use } from 'react';
async function getData() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
message: 'hello from slow layout',
};
}
export default function SlowLayout(props) {
const data = use(getData());
return (
<>
<p id="slow-layout-message">{data.message}</p>
{props.children}
</>
);
}

View File

@@ -1,17 +0,0 @@
export async function getServerSideProps() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
props: {
message: 'hello from slow layout',
},
};
}
export default function SlowLayout(props) {
return (
<>
<p id="slow-layout-message">{props.message}</p>
{props.children}
</>
);
}

View File

@@ -0,0 +1,13 @@
import { experimental_use as use } from 'react';
async function getData() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
message: 'hello from slow page',
};
}
export default function SlowPage(props) {
const data = use(getData());
return <h1 id="slow-page-message">{data.message}</h1>;
}

View File

@@ -1,12 +0,0 @@
export async function getServerSideProps() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
props: {
message: 'hello from slow page',
},
};
}
export default function SlowPage(props) {
return <h1 id="slow-page-message">{props.message}</h1>;
}

View File

@@ -0,0 +1 @@
.vercel

View File

@@ -0,0 +1,48 @@
const fs = require('fs-extra');
const ms = require('ms');
const path = require('path');
const { build } = require('../../../../src');
const { FileFsRef } = require('@vercel/build-utils');
jest.setTimeout(ms('6m'));
describe(`${__dirname.split(path.sep).pop()}`, () => {
afterEach(() => fs.remove(path.join(__dirname, 'yarn.lock')));
it('should normalize routes in build results output', async () => {
const files = [
'index.test.js',
'next.config.js',
'package.json',
'pages/foo/bar/index.js',
'pages/foo/index.js',
'pages/index.js',
].reduce((filesMap, file) => {
const fsPath = path.join(__dirname, file);
const { mode } = fs.statSync(fsPath);
filesMap[path] = new FileFsRef({ mode, fsPath });
return filesMap;
}, {});
const { output } = await build({
config: {},
entrypoint: 'package.json',
files,
meta: {
skipDownload: true,
},
repoRootPath: __dirname,
workPath: __dirname,
});
const pagesDir = path.join(__dirname, '.next', 'server', 'pages');
const pages = ['foo', 'foo/bar', 'index'];
for (const page of pages) {
expect(output).toHaveProperty(page);
expect(path.resolve(output[page].fsPath)).toEqual(
path.join(pagesDir, `${page}.html`)
);
}
});
});

View File

@@ -0,0 +1,5 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
};

View File

@@ -0,0 +1,10 @@
{
"dependencies": {
"next": "canary",
"react": "experimental",
"react-dom": "experimental"
},
"scripts": {
"build": "next build"
}
}

View File

@@ -0,0 +1,7 @@
export default function Page() {
return (
<>
<p>hello from pages/foo/bar</p>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function Page() {
return (
<>
<p>hello from pages/foo</p>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function Page() {
return (
<>
<p>hello from pages</p>
</>
);
}

Some files were not shown because too many files have changed in this diff Show More