Compare commits

...

32 Commits

Author SHA1 Message Date
Steven
0c7b54edad Publish Stable
- @vercel/build-utils@4.1.0
 - vercel@25.1.0
 - @vercel/client@12.0.1
 - @vercel/go@2.0.1
 - @vercel/next@3.0.1
 - @vercel/node@2.1.0
 - @vercel/python@3.0.1
 - @vercel/redwood@1.0.1
 - @vercel/remix@1.0.1
 - @vercel/ruby@1.3.9
 - @vercel/static-build@1.0.1
 - @vercel/static-config@2.0.1
2022-06-08 10:44:17 -04:00
Steven
6d42816395 Publish Canary
- vercel@25.0.2-canary.1
 - @vercel/next@3.0.1-canary.1
 - @vercel/static-build@1.0.1-canary.1
2022-06-08 08:48:04 -04:00
Seiya Nuta
6fe6d05a42 Increase the maximum size of an edge function to 1MiB (#7903) 2022-06-08 16:30:49 +09:00
Steven
50a201f145 [cli][static-build] Fix case when vc dev is throwing the wrong error w/ BOAv3 (#7927)
Fixes an issue brought up in https://github.com/vercel/og-image/issues/207

The problem is that we allow a corner case with `vc dev` to run the build script, but this can break if the user ran `vc build` first due to the BOA V3 detection.

The workaround is to delete the BOA V3 directory when there is no Development Command provided since that will run the Build Command during development.

We also add a nicer error in the case when someone actually intended to emit BOA V3.
2022-06-07 21:31:43 +00:00
Gal Schlezinger
701a02ae9d [next] Support Edge API Endpoints (#7905)
* Add regression test for nested middleware

* [next] Allow edge api endpoints in Next.js

This reverts commit d4cef69cc9 (#7898)

* delete `functions`, not `middleware`

* Add an assertion

Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-06-07 23:36:46 +03:00
agadzik
39f7586621 Publish Canary
- @vercel/build-utils@4.0.1-canary.0
 - vercel@25.0.2-canary.0
 - @vercel/client@12.0.1-canary.0
 - @vercel/go@2.0.1-canary.0
 - @vercel/next@3.0.1-canary.0
 - @vercel/node@2.0.2-canary.0
 - @vercel/python@3.0.1-canary.0
 - @vercel/redwood@1.0.1-canary.0
 - @vercel/remix@1.0.1-canary.0
 - @vercel/ruby@1.3.9-canary.0
 - @vercel/static-build@1.0.1-canary.0
 - @vercel/static-config@2.0.1-canary.0
2022-06-07 12:33:07 -04:00
Andrew Gadzik
c4a39c8d29 [build-utils] Add getWorkspacePackagePaths function (#7859)
Gets the package paths from the workspace definition for a given git repository

```ts
/** 
 *  Gets the list of workspace package paths based on the configuration 
 * 
 * @example
 *
 * my-repo
 * |-- packages
 * |    |-- api-1
 * |    |-- api-2
 * |-- package.json
 * |-- pnpm-workspaces.yaml // packages: ["packages/*"]
 *
 * const fs = new ... // based on 'my-repo'
 * const workspace = { ..., type: "pnpm" }
 * getWorkspacePackagePaths({ fs, workspace }) => [
 *   "my-repo/packages/api-1",
 *   "my-repo/packages/api-2"
 * ]
 */
function getWorkspacePackagePaths(fs: DetectorFilesystem, workspace: Workspace): string[]
```

### Related Issues

Closes https://github.com/vercel/vercel/issues/7749

### 📋 Checklist

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

#### 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

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR
2022-06-07 15:55:23 +00:00
Nathan Rajlich
3ac238cf08 [node] Add support for EdgeFunction output type (#7877)
Adds support for `@vercel/node` to return an `EdgeFunction` output when it detects `runtime: 'experimental-edge'` from the static `config` object. This is implemented in `@vercel/node` so that it can share the same file extension (`.js`/`.ts`) as Node.js Serverless Functions without modifying the zero-config detection logic.

**Example:**

```js
export const config = {
  runtime: 'experimental-edge',
};

export default req => {
  return new Response(`Hi, from an Edge Function!`);
};
```
2022-06-07 02:20:08 +00:00
Nathan Rajlich
8384813a0d [cli] Output EdgeFunctions from version 3 Builders in vc build (#7924)
Serializes `EdgeFunction` outputs in `vc build` when provided by a version 3 Builder.
2022-06-07 01:42:33 +00:00
Nathan Rajlich
c4587de439 [cli] Add initial vc build unit tests (#7869)
Adds some initial unit tests for the `vc build` command which includes some fixes for Windows.
2022-06-06 22:16:18 +00:00
Nathan Rajlich
d997dc4fbc [static-config] Use "runtime" instead of "use" (#7922)
The static configuration property name has been decided to be `runtime`.

This is a precursor for #7877.
2022-06-06 19:02:32 +00:00
Nathan Rajlich
d15b90bd4d [build-utils] Allow EdgeFunction as a valid output type for version 3 Builders (#7923)
A version 3 Builder is allowed to output an `EdgeFunction` output type. This already is handled correctly by the Vercel build infrastructure.
2022-06-06 18:42:14 +00:00
JJ Kasper
5b31297f0c [next] Ensure middleware route comes before beforeFiles rewrites (#7912)
* Ensure middleware route comes before beforeFiles rewrites

* update test

* add version lock and more tests

* update nested middleware test for version lock
2022-06-06 13:03:31 -04:00
Steven
e232566cbe Publish Stable
- vercel@25.0.1
 - @vercel/node@2.0.1
2022-06-05 21:38:44 -04:00
Steven
592689cad1 Publish Canary
- vercel@25.0.1-canary.0
 - @vercel/node@2.0.1-canary.0
2022-06-05 20:58:34 -04:00
Steven
9b08e72f76 [node] Skip TS compile for .d.ts assets (#7918)
Fixes an issue where `.d.ts` assets were incorrectly being compiled after a fix to nft in https://github.com/vercel/nft/pull/289

- [Repro Steps](https://github.com/cheapsteak/repro-vercel-cli-build-dts-error)
- [Incident](https://app.kintaba.com/incident/755531679765890512)
- [Slack](https://vercel.slack.com/archives/C03JNHA844R/p1654334373086879)

<img src="https://user-images.githubusercontent.com/229881/172055403-d2edfe1d-3a60-459f-bf40-506fea1d1ca4.png" height=500 />
2022-06-06 00:36:41 +00:00
Sean Massa
bd0e10cfe7 [cli] update vc env pull --help (#7911)
* update `vc env pull --help`

* update `vc env pull` help documentation

* try longer timeout

* remove dupe "env"

* remove unused --env option

* Update packages/cli/src/commands/env/index.ts

Co-authored-by: Steven <steven@ceriously.com>

Co-authored-by: Steven <steven@ceriously.com>
2022-06-05 16:22:32 -05:00
Sean Massa
28436ade60 [tests] increase test timeout; it has been failing recently (#7913) 2022-06-03 10:06:36 -05:00
Sean Massa
de0d2fba0b Publish Stable
- @vercel/build-utils@4.0.0
 - vercel@25.0.0
 - @vercel/client@12.0.0
 - @vercel/frameworks@1.0.1
 - @vercel/go@2.0.0
 - @vercel/next@3.0.0
 - @vercel/node-bridge@3.0.0
 - @vercel/node@2.0.0
 - @vercel/python@3.0.0
 - @vercel/redwood@1.0.0
 - @vercel/remix@1.0.0
 - @vercel/routing-utils@1.13.4
 - @vercel/ruby@1.3.8
 - @vercel/static-build@1.0.0
 - @vercel/static-config@2.0.0
2022-06-02 14:17:21 -05:00
Sean Massa
e0900128d6 [cli][client][tests] update to node14-compatible target (drop support for node12) (#7865)
Node 12 is EOL and we're starting to reference packages that don't install on node lower than 14, such as nuxt@3 and remix. Let's update to 14 for now.

---

Reimplements: https://github.com/vercel/vercel/pull/7819
2022-06-02 19:01:34 +00:00
Nathan Rajlich
8d15f30579 [tests] Remove ava compileEnhancements property from package.json (#7904)
VS Code was complaining about this property, so I guess it's not doing anything:

> Property `compileEnhancements` is not allowed.
2022-06-01 21:14:01 +00:00
Nathan Rajlich
960c66584c [build-utils] Make repoRootPath non-optional (#7909)
This value is always set in the Vercel build infra, so mark the type as non-optional.
2022-06-01 19:39:45 +00:00
JJ Kasper
1c8f91031a [next] Add regression test for nested middleware (#7901)
Add regression test for nested middleware
2022-06-01 10:04:16 -05:00
Steven
68cb23c3cc [cli] Add experimental corepack support (#7871)
This PR adds support for experimental [corepack](https://nodejs.org/api/corepack.html) to `vc build`.

Since this is still experimental, we only enable if the env var `ENABLE_EXPERIMENTAL_COREPACK=1` is set.

#### 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`
2022-06-01 14:31:33 +00:00
Nathan Rajlich
94f6ae2595 [examples] Remove ENABLE_FILE_SYSTEM_API env var from "remix" template (#7886)
Since the `@vercel/remix` Builder is now being used, this environment variable is no longer necessary.
2022-06-01 01:16:24 +00:00
Steven
b92aeac84d [build-utils] Fix warning for package.json engines (#7900)
This PR updates the way we handle warning for engines.node in `package.json`.

- should not warn when the engines version satisfies the project settings (previously it was an exact match)
- should warn when engines version is exact instead of range since it cannot be satisfied exactly
- should warn when engines version is greater than since it might introduce breaking changes for a future node.js version
2022-06-01 00:15:00 +00:00
JJ Kasper
00420b7a01 Publish Canary
- vercel@24.2.6-canary.0
 - @vercel/next@2.9.1-canary.0
 - @vercel/static-config@1.0.2-canary.0
2022-05-31 18:52:50 -05:00
Steven
a5128790d0 [tests] Fix turbo cache invalidation for GH Actions RUNNER_OS (#7899)
This PR fixes an issue where turbo was caching the result regardless of OS by adding the env var `RUNNER_OS` to the cache key.

https://docs.github.com/en/actions/learn-github-actions/environment-variables#detecting-the-operating-system
2022-05-31 22:42:26 +00:00
Seiya Nuta
ae9aa91f4f [static-config] Support extracting export const config from swc's AST (#7791)
This PR adds support for extracting `config` from swc's AST. `static-config` supports parsing from the source program but in some cases we already have parsed an AST to do other static code analysis.

### Related Issues

None

### 📋 Checklist

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

#### 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-05-31 20:44:29 +00:00
JJ Kasper
d4cef69cc9 Revert "[next] Allow edge api endpoints in Next.js" (#7898)
Reverts vercel/vercel#7855

We can re-land after we ensure the cases we have found failing are resolved.
2022-05-31 20:07:46 +00:00
Sean Massa
323f67c31a [cli] update update-notifier to fix notification of non-latest (#7897)
Looks like this is a bug in update-notifier, but they [smoothed it over](https://github.com/yeoman/update-notifier/pull/192) with a change that was deployed in [`5.0.0`](https://github.com/yeoman/update-notifier/releases/tag/v5.0.0). We’re currently on `4.1.0`.

This can cause an update notification for a PREVIOUS version, like so:

```
$  vc --version
> UPDATE AVAILABLE Run `npm i -g vercel@latest` to install Vercel CLI 24.2.5-canary.3
> Changelog: https://github.com/vercel/vercel/releases/tag/vercel@24.2.5-canary.3
Vercel CLI 24.2.5
24.2.5
```

While we're here, also sets the [`updatecheckinterval`](https://github.com/yeoman/update-notifier#updatecheckinterval) to 1 week.

---

Previous PR: https://github.com/vercel/vercel/pull/4896
2022-05-31 19:14:39 +00:00
Steven
63c499a826 [tests] Update domain tests from .org to .com (#7896)
Use a regex matcher and change the .org to .com
2022-05-31 18:07:25 +00:00
130 changed files with 2652 additions and 308 deletions

View File

@@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "esnext", "target": "ES2020",
"skipLibCheck": true, "skipLibCheck": true,
"strict": false, "strict": false,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,

View File

@@ -22,8 +22,5 @@
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.9",
"typescript": "^4.1.2" "typescript": "^4.1.2"
}, },
"engines": {
"node": "14.x"
},
"sideEffects": false "sideEffects": false
} }

View File

@@ -1,7 +0,0 @@
{
"build": {
"env": {
"ENABLE_FILE_SYSTEM_API": "1"
}
}
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/build-utils", "name": "@vercel/build-utils",
"version": "3.1.1", "version": "4.1.0",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.js", "types": "./dist/index.d.js",
@@ -23,7 +23,7 @@
"@types/cross-spawn": "6.0.0", "@types/cross-spawn": "6.0.0",
"@types/end-of-stream": "^1.4.0", "@types/end-of-stream": "^1.4.0",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/glob": "^7.1.1", "@types/glob": "7.2.0",
"@types/jest": "27.4.1", "@types/jest": "27.4.1",
"@types/js-yaml": "3.12.1", "@types/js-yaml": "3.12.1",
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
@@ -31,7 +31,7 @@
"@types/node-fetch": "^2.1.6", "@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@types/yazl": "2.4.2", "@types/yazl": "2.4.2",
"@vercel/frameworks": "1.0.0", "@vercel/frameworks": "1.0.1",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1", "aggregate-error": "3.0.1",
"async-retry": "1.2.3", "async-retry": "1.2.3",
@@ -40,7 +40,7 @@
"cross-spawn": "6.0.5", "cross-spawn": "6.0.5",
"end-of-stream": "1.4.1", "end-of-stream": "1.4.1",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"glob": "7.1.3", "glob": "8.0.3",
"into-stream": "5.0.0", "into-stream": "5.0.0",
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"minimatch": "3.0.4", "minimatch": "3.0.4",

View File

@@ -0,0 +1,85 @@
import fs from 'fs';
import { DetectorFilesystem } from '../detectors/filesystem';
type GlobFs = typeof fs;
function normalizePath(path: string) {
// on windows, this will return a path like
// D:/c/package.json
// since we abstract the filesystem, we need to remove windows specific info from the path
// and let the FS decide how to process the path
// D:/c/package.json => /c/package.json
return path.replace(/^[a-zA-Z]:/, '');
}
export function getGlobFs(_fs: DetectorFilesystem): GlobFs {
const readdir = (
path: fs.PathLike,
callback: (err: NodeJS.ErrnoException | null, files: string[]) => void
): void => {
_fs
.readdir(normalizePath(String(path)))
.then(stats =>
callback(
null,
stats.map(stat => stat.name)
)
)
.catch(err => callback(err, []));
};
const stat = (
path: fs.PathLike,
callback: (
err: NodeJS.ErrnoException | null,
stats: fs.Stats | null
) => void
): void => {
_fs
.isFile(normalizePath(String(path)))
.then(isPathAFile => {
callback(null, {
ino: 0,
mode: 0,
nlink: 0,
uid: 0,
gid: 0,
rdev: 0,
size: 0,
blksize: 0,
blocks: 0,
atimeMs: 0,
mtimeMs: 0,
ctimeMs: 0,
birthtimeMs: 0,
atime: new Date(),
mtime: new Date(),
ctime: new Date(),
birthtime: new Date(),
dev: 0,
isBlockDevice: () => false,
isCharacterDevice: () => false,
isDirectory: () => !isPathAFile,
isFIFO: () => false,
isFile: () => isPathAFile,
isSocket: () => false,
isSymbolicLink: () => false,
});
})
.catch(err => callback(err, null));
};
return new Proxy(fs, {
get(_target, prop) {
switch (prop) {
case 'readdir':
return readdir;
case 'lstat':
case 'stat':
return stat;
default:
throw new Error('Not Implemented');
}
},
});
}

View File

@@ -3,6 +3,7 @@ import fs from 'fs-extra';
import path from 'path'; import path from 'path';
import Sema from 'async-sema'; import Sema from 'async-sema';
import spawn from 'cross-spawn'; import spawn from 'cross-spawn';
import { coerce, intersects, validRange } from 'semver';
import { SpawnOptions } from 'child_process'; import { SpawnOptions } from 'child_process';
import { deprecate } from 'util'; import { deprecate } from 'util';
import debug from '../debug'; import debug from '../debug';
@@ -219,9 +220,9 @@ export async function getNodeVersion(
config: Config = {}, config: Config = {},
meta: Meta = {} meta: Meta = {}
): Promise<NodeVersion> { ): Promise<NodeVersion> {
const latest = getLatestNodeVersion();
if (meta && meta.isDev) { if (meta && meta.isDev) {
// Use the system-installed version of `node` in PATH for `vercel dev` // Use the system-installed version of `node` in PATH for `vercel dev`
const latest = getLatestNodeVersion();
return { ...latest, runtime: 'nodejs' }; return { ...latest, runtime: 'nodejs' };
} }
const { packageJson } = await scanParentDirs(destPath, true); const { packageJson } = await scanParentDirs(destPath, true);
@@ -229,10 +230,27 @@ export async function getNodeVersion(
let isAuto = true; let isAuto = true;
if (packageJson && packageJson.engines && packageJson.engines.node) { if (packageJson && packageJson.engines && packageJson.engines.node) {
const { node } = packageJson.engines; const { node } = packageJson.engines;
if (nodeVersion && nodeVersion !== node && !meta.isDev) { if (
nodeVersion &&
validRange(node) &&
!intersects(nodeVersion, node) &&
!meta.isDev
) {
console.warn( console.warn(
`Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version` `Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version`
); );
} else if (coerce(node)?.raw === node && !meta.isDev) {
console.warn(
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version`
);
} else if (
validRange(node) &&
intersects(`${latest.major + 1}.x`, node) &&
!meta.isDev
) {
console.warn(
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version`
);
} }
nodeVersion = node; nodeVersion = node;
isAuto = false; isAuto = false;

View File

@@ -117,5 +117,14 @@ export const isStaticRuntime = (name?: string): boolean => {
}; };
export { workspaceManagers } from './workspaces/workspace-managers'; export { workspaceManagers } from './workspaces/workspace-managers';
export { getWorkspaces } from './workspaces/get-workspaces'; export {
getWorkspaces,
GetWorkspaceOptions,
Workspace,
WorkspaceType,
} from './workspaces/get-workspaces';
export {
getWorkspacePackagePaths,
GetWorkspacePackagePathsOptions,
} from './workspaces/get-workspace-package-paths';
export { monorepoManagers } from './monorepos/monorepo-managers'; export { monorepoManagers } from './monorepos/monorepo-managers';

View File

@@ -82,7 +82,7 @@ export interface BuildOptions {
* is the Git Repository Root. This is only relevant for Monorepos. * is the Git Repository Root. This is only relevant for Monorepos.
* See https://vercel.com/blog/monorepos * See https://vercel.com/blog/monorepos
*/ */
repoRootPath?: string; repoRootPath: string;
/** /**
* An arbitrary object passed by the user in the build definition defined * An arbitrary object passed by the user in the build definition defined
@@ -123,7 +123,7 @@ export interface PrepareCacheOptions {
* is the Git Repository Root. This is only relevant for Monorepos. * is the Git Repository Root. This is only relevant for Monorepos.
* See https://vercel.com/blog/monorepos * See https://vercel.com/blog/monorepos
*/ */
repoRootPath?: string; repoRootPath: string;
/** /**
* An arbitrary object passed by the user in the build definition defined * An arbitrary object passed by the user in the build definition defined
@@ -295,6 +295,7 @@ export interface PackageJson {
readonly preferGlobal?: boolean; readonly preferGlobal?: boolean;
readonly private?: boolean; readonly private?: boolean;
readonly publishConfig?: PackageJson.PublishConfig; readonly publishConfig?: PackageJson.PublishConfig;
readonly packageManager?: string;
} }
export interface NodeVersion { export interface NodeVersion {
@@ -427,7 +428,7 @@ export interface BuildResultV2Typical {
export type BuildResultV2 = BuildResultV2Typical | BuildResultBuildOutput; export type BuildResultV2 = BuildResultV2Typical | BuildResultBuildOutput;
export interface BuildResultV3 { export interface BuildResultV3 {
output: Lambda; output: Lambda | EdgeFunction;
} }
export type BuildV2 = (options: BuildOptions) => Promise<BuildResultV2>; export type BuildV2 = (options: BuildOptions) => Promise<BuildResultV2>;

View File

@@ -0,0 +1,113 @@
import _path from 'path';
import yaml from 'js-yaml';
import glob from 'glob';
import { DetectorFilesystem } from '../detectors/filesystem';
import { Workspace } from './get-workspaces';
import { getGlobFs } from '../fs/get-glob-fs';
import { normalizePath } from '../fs/normalize-path';
const posixPath = _path.posix;
interface GetPackagePathOptions {
fs: DetectorFilesystem;
}
export interface GetWorkspacePackagePathsOptions extends GetPackagePathOptions {
fs: DetectorFilesystem;
workspace: Workspace;
}
export async function getWorkspacePackagePaths({
fs,
workspace,
}: GetWorkspacePackagePathsOptions): Promise<string[]> {
const { type, rootPath } = workspace;
const workspaceFs = fs.chdir(rootPath);
let results: string[] = [];
switch (type) {
case 'yarn':
case 'npm':
results = await getPackageJsonWorkspacePackagePaths({ fs: workspaceFs });
break;
case 'pnpm':
results = await getPnpmWorkspacePackagePaths({ fs: workspaceFs });
break;
default:
throw new Error(`Unknown workspace implementation: ${type}`);
}
return results.map(packagePath => {
return posixPath.join(rootPath, posixPath.dirname(packagePath));
});
}
type PackageJsonWithWorkspace = {
workspaces?:
| {
packages?: string[];
noHoist?: string[];
}
| string[];
};
type PnpmWorkspaces = {
packages?: string[];
};
async function getPackagePaths(
packages: string[],
fs: DetectorFilesystem
): Promise<string[]> {
return (
await Promise.all(
packages.map(
packageGlob =>
new Promise<string[]>((resolve, reject) => {
glob(
normalizePath(posixPath.join(packageGlob, 'package.json')),
{
cwd: '/',
fs: getGlobFs(fs),
},
(err, matches) => {
if (err) reject(err);
else resolve(matches);
}
);
})
)
)
).flat();
}
async function getPackageJsonWorkspacePackagePaths({
fs,
}: GetPackagePathOptions): Promise<string[]> {
const packageJsonAsBuffer = await fs.readFile('package.json');
const { workspaces } = JSON.parse(
packageJsonAsBuffer.toString()
) as PackageJsonWithWorkspace;
let packages: string[] = [];
if (Array.isArray(workspaces)) {
packages = workspaces;
} else {
packages = workspaces?.packages ?? [];
}
return getPackagePaths(packages, fs);
}
async function getPnpmWorkspacePackagePaths({
fs,
}: GetPackagePathOptions): Promise<string[]> {
const pnpmWorkspaceAsBuffer = await fs.readFile('pnpm-workspace.yaml');
const { packages = [] } = yaml.load(
pnpmWorkspaceAsBuffer.toString()
) as PnpmWorkspaces;
return getPackagePaths(packages, fs);
}

View File

@@ -36,7 +36,7 @@ export async function getWorkspaces({
const childDirectories = directoryContents.filter( const childDirectories = directoryContents.filter(
stat => stat.type === 'dir' stat => stat.type === 'dir'
); );
return ( return (
await Promise.all( await Promise.all(
childDirectories.map(childDirectory => childDirectories.map(childDirectory =>

View File

@@ -0,0 +1,15 @@
{
"name": "a",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"debug": "^4.3.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": "a",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"debug": "^4.3.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

@@ -1,19 +1,260 @@
lockfileVersion: 5.3 lockfileVersion: 5.4
specifiers: importers:
once: ^1.4.0
dependencies: .:
once: 1.4.0 specifiers:
once: ^1.4.0
dependencies:
once: 1.4.0
a:
specifiers:
debug: ^4.3.2
dependencies:
debug: 4.3.4
b:
specifiers:
cowsay: ^1.5.0
dependencies:
cowsay: 1.5.0
packages: packages:
/ansi-regex/3.0.1:
resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==}
engines: {node: '>=4'}
dev: false
/ansi-regex/5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
dev: false
/ansi-styles/4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
dependencies:
color-convert: 2.0.1
dev: false
/camelcase/5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
dev: false
/cliui/6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
dev: false
/color-convert/2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
dev: false
/color-name/1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: false
/cowsay/1.5.0:
resolution: {integrity: sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==}
engines: {node: '>= 4'}
hasBin: true
dependencies:
get-stdin: 8.0.0
string-width: 2.1.1
strip-final-newline: 2.0.0
yargs: 15.4.1
dev: false
/debug/4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
dev: false
/decamelize/1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
dev: false
/emoji-regex/8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: false
/find-up/4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
dev: false
/get-caller-file/2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
dev: false
/get-stdin/8.0.0:
resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==}
engines: {node: '>=10'}
dev: false
/is-fullwidth-code-point/2.0.0:
resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==}
engines: {node: '>=4'}
dev: false
/is-fullwidth-code-point/3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
dev: false
/locate-path/5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
dependencies:
p-locate: 4.1.0
dev: false
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: false
/once/1.4.0: /once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
dev: false dev: false
/p-limit/2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
dependencies:
p-try: 2.2.0
dev: false
/p-locate/4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
dependencies:
p-limit: 2.3.0
dev: false
/p-try/2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
dev: false
/path-exists/4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
dev: false
/require-directory/2.1.1:
resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=}
engines: {node: '>=0.10.0'}
dev: false
/require-main-filename/2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
dev: false
/set-blocking/2.0.0:
resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=}
dev: false
/string-width/2.1.1:
resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==}
engines: {node: '>=4'}
dependencies:
is-fullwidth-code-point: 2.0.0
strip-ansi: 4.0.0
dev: false
/string-width/4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
dev: false
/strip-ansi/4.0.0:
resolution: {integrity: sha1-qEeQIusaw2iocTibY1JixQXuNo8=}
engines: {node: '>=4'}
dependencies:
ansi-regex: 3.0.1
dev: false
/strip-ansi/6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
dependencies:
ansi-regex: 5.0.1
dev: false
/strip-final-newline/2.0.0:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
engines: {node: '>=6'}
dev: false
/which-module/2.0.0:
resolution: {integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=}
dev: false
/wrap-ansi/6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: false
/wrappy/1.0.2: /wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: false dev: false
/y18n/4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
dev: false
/yargs-parser/18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
dev: false
/yargs/15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.0
y18n: 4.0.3
yargs-parser: 18.1.3
dev: false

View File

@@ -0,0 +1,6 @@
{
"private": true,
"engines": {
"node": "16.14.0"
}
}

View File

@@ -0,0 +1,6 @@
{
"private": true,
"engines": {
"node": ">=16"
}
}

View File

@@ -0,0 +1,38 @@
import path from 'path';
import { getWorkspaces } from '../src/workspaces/get-workspaces';
import { getWorkspacePackagePaths } from '../src/workspaces/get-workspace-package-paths';
import { FixtureFilesystem } from './utils/fixture-filesystem';
describe.each<[string, string[]]>([
['21-npm-workspaces', ['/a', '/b']],
['23-pnpm-workspaces', ['/c', '/d']],
['27-yarn-workspaces', ['/a', '/b']],
['25-multiple-lock-files-yarn', ['/a', '/b']],
['26-multiple-lock-files-pnpm', ['/a', '/b']],
[
'29-nested-workspaces',
['/backend/c', '/backend/d', '/frontend/a', '/frontend/b'],
],
['22-pnpm', []],
])('`getWorkspacesPackagePaths()`', (fixturePath, packagePaths) => {
const testName =
packagePaths.length > 0
? `should detect ${packagePaths.join()} package${
packagePaths.length > 1 ? 's' : ''
} for ${fixturePath}`
: `should not detect any workspace for ${fixturePath}`;
it(testName, async () => {
const fixture = path.join(__dirname, 'fixtures', fixturePath);
const fs = new FixtureFilesystem(fixture);
const workspaces = await getWorkspaces({ fs });
const actualPackagePaths = (
await Promise.all(
workspaces.map(workspace => getWorkspacePackagePaths({ fs, workspace }))
)
).flat();
expect(actualPackagePaths).toEqual(packagePaths);
});
});

View File

@@ -277,7 +277,45 @@ it('should prefer package.json engines over project setting from config and warn
]); ]);
}); });
it('should warn when package.json engines is exact version', async () => {
expect(
await getNodeVersion(
path.join(__dirname, 'pkg-engine-node-exact'),
undefined,
{},
{}
)
).toHaveProperty('range', '16.x');
expect(warningMessages).toStrictEqual([
'Warning: Detected "engines": { "node": "16.14.0" } in your `package.json` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version',
]);
});
it('should warn when package.json engines is greater than', async () => {
expect(
await getNodeVersion(
path.join(__dirname, 'pkg-engine-node-greaterthan'),
undefined,
{},
{}
)
).toHaveProperty('range', '16.x');
expect(warningMessages).toStrictEqual([
'Warning: Detected "engines": { "node": ">=16" } in your `package.json` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version',
]);
});
it('should not warn when package.json engines matches project setting from config', async () => { it('should not warn when package.json engines matches project setting from config', async () => {
expect(
await getNodeVersion(
path.join(__dirname, 'pkg-engine-node'),
undefined,
{ nodeVersion: '14' },
{}
)
).toHaveProperty('range', '14.x');
expect(warningMessages).toStrictEqual([]);
expect( expect(
await getNodeVersion( await getNodeVersion(
path.join(__dirname, 'pkg-engine-node'), path.join(__dirname, 'pkg-engine-node'),
@@ -287,6 +325,16 @@ it('should not warn when package.json engines matches project setting from confi
) )
).toHaveProperty('range', '14.x'); ).toHaveProperty('range', '14.x');
expect(warningMessages).toStrictEqual([]); expect(warningMessages).toStrictEqual([]);
expect(
await getNodeVersion(
path.join(__dirname, 'pkg-engine-node'),
undefined,
{ nodeVersion: '<15' },
{}
)
).toHaveProperty('range', '14.x');
expect(warningMessages).toStrictEqual([]);
}); });
it('should get latest node version', async () => { it('should get latest node version', async () => {

View File

@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"declaration": true, "declaration": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["esnext"], "lib": ["ES2020"],
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",
"noEmitOnError": true, "noEmitOnError": true,
@@ -13,7 +13,7 @@
"outDir": "./dist", "outDir": "./dist",
"types": ["node", "jest"], "types": ["node", "jest"],
"strict": true, "strict": true,
"target": "es2019" "target": "ES2020"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules"] "exclude": ["node_modules"]

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "24.2.5", "version": "25.1.0",
"preferGlobal": true, "preferGlobal": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "The command-line interface for Vercel", "description": "The command-line interface for Vercel",
@@ -30,7 +30,6 @@
"scripts/preinstall.js" "scripts/preinstall.js"
], ],
"ava": { "ava": {
"compileEnhancements": false,
"extensions": [ "extensions": [
"ts" "ts"
], ],
@@ -40,19 +39,19 @@
] ]
}, },
"engines": { "engines": {
"node": ">= 12" "node": ">= 14"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "3.1.1", "@vercel/build-utils": "4.1.0",
"@vercel/go": "1.4.4", "@vercel/go": "2.0.1",
"@vercel/next": "2.9.0", "@vercel/next": "3.0.1",
"@vercel/node": "1.15.4", "@vercel/node": "2.1.0",
"@vercel/python": "2.3.4", "@vercel/python": "3.0.1",
"@vercel/redwood": "0.8.4", "@vercel/redwood": "1.0.1",
"@vercel/remix": "0.0.2", "@vercel/remix": "1.0.1",
"@vercel/ruby": "1.3.7", "@vercel/ruby": "1.3.9",
"@vercel/static-build": "0.26.0", "@vercel/static-build": "1.0.1",
"update-notifier": "4.1.0" "update-notifier": "5.1.0"
}, },
"devDependencies": { "devDependencies": {
"@alex_neo/jest-expect-message": "1.0.5", "@alex_neo/jest-expect-message": "1.0.5",
@@ -95,8 +94,8 @@
"@types/which": "1.3.2", "@types/which": "1.3.2",
"@types/write-json-file": "2.2.1", "@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0", "@types/yauzl-promise": "2.1.0",
"@vercel/client": "11.0.4", "@vercel/client": "12.0.1",
"@vercel/frameworks": "1.0.0", "@vercel/frameworks": "1.0.1",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2", "@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2", "@zeit/source-map-support": "0.6.2",

View File

@@ -4,6 +4,7 @@ import dotenv from 'dotenv';
import { join, relative } from 'path'; import { join, relative } from 'path';
import { import {
detectBuilders, detectBuilders,
normalizePath,
Files, Files,
FileFsRef, FileFsRef,
PackageJson, PackageJson,
@@ -45,6 +46,7 @@ import {
writeBuildResult, writeBuildResult,
} from '../util/build/write-build-result'; } from '../util/build/write-build-result';
import { importBuilders, BuilderWithPkg } from '../util/build/import-builders'; import { importBuilders, BuilderWithPkg } from '../util/build/import-builders';
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
type BuildResult = BuildResultV2 | BuildResultV3; type BuildResult = BuildResultV2 | BuildResultV3;
@@ -177,7 +179,7 @@ export default async function main(client: Client): Promise<number> {
// Get a list of source files // Get a list of source files
const files = (await getFiles(workPath, client)).map(f => const files = (await getFiles(workPath, client)).map(f =>
relative(workPath, f) normalizePath(relative(workPath, f))
); );
const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} }); const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} });
@@ -265,19 +267,20 @@ export default async function main(client: Client): Promise<number> {
} }
// Delete output directory from potential previous build // Delete output directory from potential previous build
await fs.remove(OUTPUT_DIR); const outputDir = join(cwd, OUTPUT_DIR);
await fs.remove(outputDir);
const buildStamp = stamp(); const buildStamp = stamp();
// Create fresh new output directory // Create fresh new output directory
await fs.mkdirp(OUTPUT_DIR); await fs.mkdirp(outputDir);
const ops: Promise<Error | void>[] = []; const ops: Promise<Error | void>[] = [];
// Write the `detectedBuilders` result to output dir // Write the `detectedBuilders` result to output dir
ops.push( ops.push(
fs.writeJSON( fs.writeJSON(
join(OUTPUT_DIR, 'builds.json'), join(outputDir, 'builds.json'),
{ {
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.', '//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
target, target,
@@ -312,6 +315,10 @@ export default async function main(client: Client): Promise<number> {
// TODO: parallelize builds // TODO: parallelize builds
const buildResults: Map<Builder, BuildResult> = new Map(); const buildResults: Map<Builder, BuildResult> = new Map();
const overrides: PathOverride[] = []; const overrides: PathOverride[] = [];
const repoRootPath = cwd;
const rootPackageJsonPath = repoRootPath || workPath;
const corepackShimDir = await initCorepack({ cwd, rootPackageJsonPath });
for (const build of builds) { for (const build of builds) {
if (typeof build.src !== 'string') continue; if (typeof build.src !== 'string') continue;
@@ -331,7 +338,6 @@ export default async function main(client: Client): Promise<number> {
framework: project.settings.framework, framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion, nodeVersion: project.settings.nodeVersion,
}; };
const repoRootPath = cwd === workPath ? undefined : cwd;
const buildOptions: BuildOptions = { const buildOptions: BuildOptions = {
files: filesMap, files: filesMap,
entrypoint: build.src, entrypoint: build.src,
@@ -352,6 +358,7 @@ export default async function main(client: Client): Promise<number> {
// Start flushing the file outputs to the filesystem asynchronously // Start flushing the file outputs to the filesystem asynchronously
ops.push( ops.push(
writeBuildResult( writeBuildResult(
outputDir,
buildResult, buildResult,
build, build,
builder, builder,
@@ -366,6 +373,10 @@ export default async function main(client: Client): Promise<number> {
); );
} }
if (corepackShimDir) {
cleanupCorepack(corepackShimDir);
}
// Wait for filesystem operations to complete // Wait for filesystem operations to complete
// TODO render progress bar? // TODO render progress bar?
let hadError = false; let hadError = false;
@@ -379,7 +390,7 @@ export default async function main(client: Client): Promise<number> {
if (hadError) return 1; if (hadError) return 1;
// Merge existing `config.json` file into the one that will be produced // Merge existing `config.json` file into the one that will be produced
const configPath = join(OUTPUT_DIR, 'config.json'); const configPath = join(outputDir, 'config.json');
// TODO: properly type // TODO: properly type
const existingConfig = await readJSONFile<any>(configPath); const existingConfig = await readJSONFile<any>(configPath);
if (existingConfig instanceof CantParseJSONFile) { if (existingConfig instanceof CantParseJSONFile) {
@@ -437,7 +448,7 @@ export default async function main(client: Client): Promise<number> {
wildcard: mergedWildcard, wildcard: mergedWildcard,
overrides: mergedOverrides, overrides: mergedOverrides,
}; };
await fs.writeJSON(join(OUTPUT_DIR, 'config.json'), config, { spaces: 2 }); await fs.writeJSON(join(outputDir, 'config.json'), config, { spaces: 2 });
output.print( output.print(
`${prependEmoji( `${prependEmoji(

View File

@@ -1,4 +1,5 @@
import { resolve, join } from 'path'; import { resolve, join } from 'path';
import fs from 'fs-extra';
import DevServer from '../../util/dev/server'; import DevServer from '../../util/dev/server';
import parseListen from '../../util/dev/parse-listen'; import parseListen from '../../util/dev/parse-listen';
@@ -12,6 +13,7 @@ import setupAndLink from '../../util/link/setup-and-link';
import getSystemEnvValues from '../../util/env/get-system-env-values'; import getSystemEnvValues from '../../util/env/get-system-env-values';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
import param from '../../util/output/param'; import param from '../../util/output/param';
import { OUTPUT_DIR } from '../../util/build/write-build-result';
type Options = { type Options = {
'--listen': string; '--listen': string;
@@ -104,6 +106,15 @@ export default async function dev(
devCommand = process.env.VERCEL_DEV_COMMAND; devCommand = process.env.VERCEL_DEV_COMMAND;
} }
// If there is no Development Command, we must delete the
// v3 Build Output because it will incorrectly be detected by
// @vercel/static-build in BuildOutputV3.getBuildOutputDirectory()
if (!devCommand) {
output.log(`Removing ${OUTPUT_DIR}`);
const outputDir = join(cwd, OUTPUT_DIR);
await fs.remove(outputDir);
}
const devServer = new DevServer(cwd, { const devServer = new DevServer(cwd, {
output, output,
devCommand, devCommand,

View File

@@ -42,6 +42,13 @@ const help = () => {
${chalk.dim('Examples:')} ${chalk.dim('Examples:')}
${chalk.gray(
''
)} Pull all Development Environment Variables down from the cloud
${chalk.cyan(`$ ${getPkgName()} env pull <file>`)}
${chalk.cyan(`$ ${getPkgName()} env pull .env.development.local`)}
${chalk.gray('')} Add a new variable to multiple Environments ${chalk.gray('')} Add a new variable to multiple Environments
${chalk.cyan(`$ ${getPkgName()} env add <name>`)} ${chalk.cyan(`$ ${getPkgName()} env add <name>`)}

View File

@@ -25,7 +25,7 @@ import {
const help = () => { const help = () => {
return console.log(` return console.log(`
${chalk.bold(`${logo} ${getPkgName()} pull`)} [path] ${chalk.bold(`${logo} ${getPkgName()} pull`)} [project-path]
${chalk.dim('Options:')} ${chalk.dim('Options:')}
@@ -42,25 +42,29 @@ const help = () => {
${chalk.dim('Examples:')} ${chalk.dim('Examples:')}
${chalk.gray('')} Pull the latest Project Settings from the cloud ${chalk.gray(
''
)} Pull the latest Environment Variables and Project Settings from the cloud
and stores them in \`.vercel/.env.\${target}.local\` and \`.vercel/project.json\` respectively.
${chalk.cyan(`$ ${getPkgName()} pull`)} ${chalk.cyan(`$ ${getPkgName()} pull`)}
${chalk.cyan(`$ ${getPkgName()} pull ./path-to-project`)} ${chalk.cyan(`$ ${getPkgName()} pull ./path-to-project`)}
${chalk.cyan(`$ ${getPkgName()} pull --env .env.local`)}
${chalk.cyan(`$ ${getPkgName()} pull ./path-to-project --env .env.local`)}
${chalk.gray('')} Pull specific environment's Project Settings from the cloud ${chalk.gray('')} Pull for a specific environment
${chalk.cyan( ${chalk.cyan(
`$ ${getPkgName()} pull --environment=${getEnvTargetPlaceholder()}` `$ ${getPkgName()} pull --environment=${getEnvTargetPlaceholder()}`
)} )}
${chalk.gray(
'If you want to download environment variables to a specific file, use `vercel env pull` instead.'
)}
`); `);
}; };
function processArgs(client: Client) { function processArgs(client: Client) {
return getArgs(client.argv.slice(2), { return getArgs(client.argv.slice(2), {
'--yes': Boolean, '--yes': Boolean,
'--env': String, // deprecated
'--environment': String, '--environment': String,
'--debug': Boolean, '--debug': Boolean,
'-d': '--debug', '-d': '--debug',

View File

@@ -58,6 +58,7 @@ const isCanary = pkg.version.includes('canary');
const notifier = updateNotifier({ const notifier = updateNotifier({
pkg, pkg,
distTag: isCanary ? 'canary' : 'latest', distTag: isCanary ? 'canary' : 'latest',
updateCheckInterval: 1000 * 60 * 60 * 24 * 7, // 1 week
}); });
const VERCEL_DIR = getGlobalPathConfig(); const VERCEL_DIR = getGlobalPathConfig();

View File

@@ -0,0 +1,82 @@
import { delimiter, join } from 'path';
import { PackageJson, spawnAsync } from '@vercel/build-utils';
import fs from 'fs-extra';
import { CantParseJSONFile } from '../errors-ts';
import { VERCEL_DIR } from '../projects/link';
import readJSONFile from '../read-json-file';
export async function initCorepack({
cwd,
rootPackageJsonPath,
}: {
cwd: string;
rootPackageJsonPath: string;
}): Promise<string | null> {
if (process.env.ENABLE_EXPERIMENTAL_COREPACK !== '1') {
// Since corepack is experimental, we need to exit early
// unless the user explicitly enables it with the env var.
return null;
}
const pkg = await readJSONFile<PackageJson>(
join(rootPackageJsonPath, 'package.json')
);
if (pkg instanceof CantParseJSONFile) {
console.warn(
'Warning: Could not enable corepack because package.json is invalid JSON'
);
} else if (!pkg?.packageManager) {
console.warn(
'Warning: Could not enable corepack because package.json is missing "packageManager" property'
);
} else {
console.log(
`Detected ENABLE_EXPERIMENTAL_COREPACK=1 and "${pkg.packageManager}" in package.json`
);
const corepackRootDir = join(cwd, VERCEL_DIR, 'cache', 'corepack');
const corepackHomeDir = join(corepackRootDir, 'home');
const corepackShimDir = join(corepackRootDir, 'shim');
await fs.mkdirp(corepackHomeDir);
await fs.mkdirp(corepackShimDir);
process.env.COREPACK_HOME = corepackHomeDir;
process.env.PATH = `${corepackShimDir}${delimiter}${process.env.PATH}`;
process.env.DEBUG = process.env.DEBUG
? `corepack,${process.env.DEBUG}`
: 'corepack';
const pkgManagerName = pkg.packageManager.split('@')[0];
// We must explicitly call `corepack enable npm` since `corepack enable`
// doesn't work with npm. See https://github.com/nodejs/corepack/pull/24
// Also, `corepack enable` is too broad and will change the verison of
// yarn & pnpm even though those versions are not specified by the user.
// See https://github.com/nodejs/corepack#known-good-releases
// Finally, we use `--install-directory` so we can cache the result to
// reuse for subsequent builds. See `@vercel/vc-build` for `prepareCache`.
await spawnAsync(
'corepack',
['enable', pkgManagerName, '--install-directory', corepackShimDir],
{
prettyCommand: `corepack enable ${pkgManagerName}`,
}
);
return corepackShimDir;
}
return null;
}
export function cleanupCorepack(corepackShimDir: string) {
if (process.env.COREPACK_HOME) {
delete process.env.COREPACK_HOME;
}
if (process.env.PATH) {
process.env.PATH = process.env.PATH.replace(
`${corepackShimDir}${delimiter}`,
''
);
}
if (process.env.DEBUG) {
if (process.env.DEBUG === 'corepack') {
delete process.env.DEBUG;
} else {
process.env.DEBUG = process.env.DEBUG.replace('corepack,', '');
}
}
}

View File

@@ -106,9 +106,10 @@ export async function resolveBuilders(
// If `pkgPath` wasn't found in `.vercel/builders` then try as a CLI local // If `pkgPath` wasn't found in `.vercel/builders` then try as a CLI local
// dependency. `require.resolve()` will throw if the Builder is not a CLI // dependency. `require.resolve()` will throw if the Builder is not a CLI
// dep, in which case we'll install it into `.vercel/builders`. // dep, in which case we'll install it into `.vercel/builders`.
pkgPath = require.resolve(`${name}/package.json`, { // NOTE: `eval('require')` is necessary to avoid bad transpilation to `__webpack_require__`
pkgPath = eval('require').resolve(`${name}/package.json`, {
paths: [__dirname], paths: [__dirname],
}); }) as string;
builderPkg = await readJSON(pkgPath); builderPkg = await readJSON(pkgPath);
} }
@@ -148,7 +149,9 @@ export async function resolveBuilders(
// TODO: handle `parsed.type === 'tag'` ("latest" vs. anything else?) // TODO: handle `parsed.type === 'tag'` ("latest" vs. anything else?)
const path = join(dirname(pkgPath), builderPkg.main || 'index.js'); const path = join(dirname(pkgPath), builderPkg.main || 'index.js');
const builder = require(path);
// NOTE: `eval('require')` is necessary to avoid bad transpilation to `__webpack_require__`
const builder = eval('require')(path);
builders.set(spec, { builders.set(spec, {
builder, builder,

View File

@@ -23,6 +23,7 @@ import { VERCEL_DIR } from '../projects/link';
export const OUTPUT_DIR = join(VERCEL_DIR, 'output'); export const OUTPUT_DIR = join(VERCEL_DIR, 'output');
export async function writeBuildResult( export async function writeBuildResult(
outputDir: string,
buildResult: BuildResultV2 | BuildResultV3, buildResult: BuildResultV2 | BuildResultV3,
build: Builder, build: Builder,
builder: BuilderV2 | BuilderV3, builder: BuilderV2 | BuilderV3,
@@ -31,9 +32,13 @@ export async function writeBuildResult(
) { ) {
const { version } = builder; const { version } = builder;
if (version === 2) { if (version === 2) {
return writeBuildResultV2(buildResult as BuildResultV2, cleanUrls); return writeBuildResultV2(
outputDir,
buildResult as BuildResultV2,
cleanUrls
);
} else if (version === 3) { } else if (version === 3) {
return writeBuildResultV3(buildResult as BuildResultV3, build); return writeBuildResultV3(outputDir, buildResult as BuildResultV3, build);
} }
throw new Error( throw new Error(
`Unsupported Builder version \`${version}\` from "${builderPkg.name}"` `Unsupported Builder version \`${version}\` from "${builderPkg.name}"`
@@ -67,11 +72,12 @@ export interface PathOverride {
* the filesystem. * the filesystem.
*/ */
async function writeBuildResultV2( async function writeBuildResultV2(
outputDir: string,
buildResult: BuildResultV2, buildResult: BuildResultV2,
cleanUrls?: boolean cleanUrls?: boolean
) { ) {
if ('buildOutputPath' in buildResult) { if ('buildOutputPath' in buildResult) {
await mergeBuilderOutput(buildResult); await mergeBuilderOutput(outputDir, buildResult);
return; return;
} }
@@ -79,16 +85,16 @@ async function writeBuildResultV2(
const overrides: Record<string, PathOverride> = {}; const overrides: Record<string, PathOverride> = {};
for (const [path, output] of Object.entries(buildResult.output)) { for (const [path, output] of Object.entries(buildResult.output)) {
if (isLambda(output)) { if (isLambda(output)) {
await writeLambda(output, path, lambdas); await writeLambda(outputDir, output, path, lambdas);
} else if (isPrerender(output)) { } else if (isPrerender(output)) {
await writeLambda(output.lambda, path, lambdas); await writeLambda(outputDir, output.lambda, path, lambdas);
// Write the fallback file alongside the Lambda directory // Write the fallback file alongside the Lambda directory
let fallback = output.fallback; let fallback = output.fallback;
if (fallback) { if (fallback) {
const ext = getFileExtension(fallback); const ext = getFileExtension(fallback);
const fallbackName = `${path}.prerender-fallback${ext}`; const fallbackName = `${path}.prerender-fallback${ext}`;
const fallbackPath = join(OUTPUT_DIR, 'functions', fallbackName); const fallbackPath = join(outputDir, 'functions', fallbackName);
const stream = fallback.toStream(); const stream = fallback.toStream();
await pipe( await pipe(
stream, stream,
@@ -101,7 +107,7 @@ async function writeBuildResultV2(
} }
const prerenderConfigPath = join( const prerenderConfigPath = join(
OUTPUT_DIR, outputDir,
'functions', 'functions',
`${path}.prerender-config.json` `${path}.prerender-config.json`
); );
@@ -112,9 +118,9 @@ async function writeBuildResultV2(
}; };
await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 }); await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 });
} else if (isFile(output)) { } else if (isFile(output)) {
await writeStaticFile(output, path, overrides, cleanUrls); await writeStaticFile(outputDir, output, path, overrides, cleanUrls);
} else if (isEdgeFunction(output)) { } else if (isEdgeFunction(output)) {
await writeEdgeFunction(output, path); await writeEdgeFunction(outputDir, output, path);
} else { } else {
throw new Error( throw new Error(
`Unsupported output type: "${(output as any).type}" for ${path}` `Unsupported output type: "${(output as any).type}" for ${path}`
@@ -128,15 +134,24 @@ async function writeBuildResultV2(
* Writes the output from the `build()` return value of a v3 Builder to * Writes the output from the `build()` return value of a v3 Builder to
* the filesystem. * the filesystem.
*/ */
async function writeBuildResultV3(buildResult: BuildResultV3, build: Builder) { async function writeBuildResultV3(
outputDir: string,
buildResult: BuildResultV3,
build: Builder
) {
const { output } = buildResult; const { output } = buildResult;
const src = build.src;
if (typeof src !== 'string') {
throw new Error(`Expected "build.src" to be a string`);
}
const ext = extname(src);
const path = build.config?.zeroConfig
? src.substring(0, src.length - ext.length)
: src;
if (isLambda(output)) { if (isLambda(output)) {
const src = build.src!; await writeLambda(outputDir, output, path);
const ext = extname(src); } else if (isEdgeFunction(output)) {
const path = build.config?.zeroConfig await writeEdgeFunction(outputDir, output, path);
? src.substring(0, src.length - ext.length)
: src;
await writeLambda(output, path);
} else { } else {
throw new Error( throw new Error(
`Unsupported output type: "${(output as any).type}" for ${build.src}` `Unsupported output type: "${(output as any).type}" for ${build.src}`
@@ -154,6 +169,7 @@ async function writeBuildResultV3(buildResult: BuildResultV3, build: Builder) {
* @param overrides Record of override configuration when a File is renamed or has other metadata * @param overrides Record of override configuration when a File is renamed or has other metadata
*/ */
async function writeStaticFile( async function writeStaticFile(
outputDir: string,
file: File, file: File,
path: string, path: string,
overrides: Record<string, PathOverride>, overrides: Record<string, PathOverride>,
@@ -191,7 +207,7 @@ async function writeStaticFile(
overrides[fsPath] = override; overrides[fsPath] = override;
} }
const dest = join(OUTPUT_DIR, 'static', fsPath); const dest = join(outputDir, 'static', fsPath);
await fs.mkdirp(dirname(dest)); await fs.mkdirp(dirname(dest));
// TODO: handle (or skip) symlinks? // TODO: handle (or skip) symlinks?
@@ -205,8 +221,12 @@ async function writeStaticFile(
* @param edgeFunction The `EdgeFunction` instance * @param edgeFunction The `EdgeFunction` instance
* @param path The URL path where the `EdgeFunction` can be accessed from * @param path The URL path where the `EdgeFunction` can be accessed from
*/ */
async function writeEdgeFunction(edgeFunction: EdgeFunction, path: string) { async function writeEdgeFunction(
const dest = join(OUTPUT_DIR, 'functions', `${path}.func`); outputDir: string,
edgeFunction: EdgeFunction,
path: string
) {
const dest = join(outputDir, 'functions', `${path}.func`);
await fs.mkdirp(dest); await fs.mkdirp(dest);
const ops: Promise<any>[] = []; const ops: Promise<any>[] = [];
@@ -235,11 +255,12 @@ async function writeEdgeFunction(edgeFunction: EdgeFunction, path: string) {
* @param lambdas (optional) Map of `Lambda` instances that have previously been written * @param lambdas (optional) Map of `Lambda` instances that have previously been written
*/ */
async function writeLambda( async function writeLambda(
outputDir: string,
lambda: Lambda, lambda: Lambda,
path: string, path: string,
lambdas?: Map<Lambda, string> lambdas?: Map<Lambda, string>
) { ) {
const dest = join(OUTPUT_DIR, 'functions', `${path}.func`); const dest = join(outputDir, 'functions', `${path}.func`);
// If the `lambda` has already been written to the filesystem at a different // If the `lambda` has already been written to the filesystem at a different
// location then create a symlink to the previous location instead of copying // location then create a symlink to the previous location instead of copying
@@ -248,7 +269,7 @@ async function writeLambda(
if (existingLambdaPath) { if (existingLambdaPath) {
const destDir = dirname(dest); const destDir = dirname(dest);
const targetDest = join( const targetDest = join(
OUTPUT_DIR, outputDir,
'functions', 'functions',
`${existingLambdaPath}.func` `${existingLambdaPath}.func`
); );
@@ -312,14 +333,17 @@ async function writeLambda(
* `.vercel/output` directory that was specified by the Builder into the * `.vercel/output` directory that was specified by the Builder into the
* `vc build` output directory. * `vc build` output directory.
*/ */
async function mergeBuilderOutput(buildResult: BuildResultBuildOutput) { async function mergeBuilderOutput(
const absOutputDir = resolve(OUTPUT_DIR); outputDir: string,
buildResult: BuildResultBuildOutput
) {
const absOutputDir = resolve(outputDir);
if (absOutputDir === buildResult.buildOutputPath) { if (absOutputDir === buildResult.buildOutputPath) {
// `.vercel/output` dir is already in the correct location, // `.vercel/output` dir is already in the correct location,
// so no need to do anything // so no need to do anything
return; return;
} }
await fs.copy(buildResult.buildOutputPath, OUTPUT_DIR); await fs.copy(buildResult.buildOutputPath, outputDir);
} }
/** /**

View File

@@ -142,6 +142,7 @@ export async function executeBuild(
files, files,
entrypoint, entrypoint,
workPath, workPath,
repoRootPath: workPath,
config, config,
meta: { meta: {
isDev: true, isDev: true,

View File

@@ -1735,6 +1735,7 @@ export default class DevServer {
entrypoint: match.entrypoint, entrypoint: match.entrypoint,
workPath, workPath,
config: match.config || {}, config: match.config || {},
repoRootPath: this.cwd,
meta: { meta: {
isDev: true, isDev: true,
requestPath, requestPath,

View File

@@ -1,5 +1,4 @@
import { resolve } from 'path'; import { resolve } from 'path';
import _glob, { IOptions as GlobOptions } from 'glob';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { getVercelIgnore } from '@vercel/client'; import { getVercelIgnore } from '@vercel/client';
import uniqueStrings from './unique-strings'; import uniqueStrings from './unique-strings';
@@ -21,14 +20,6 @@ function flatten(
return res; return res;
} }
async function glob(pattern: string, options: GlobOptions): Promise<string[]> {
return new Promise((resolve, reject) => {
_glob(pattern, options, (err, files) => {
err ? reject(err) : resolve(files);
});
});
}
/** /**
* Transform relative paths into absolutes, * Transform relative paths into absolutes,
* and maintains absolutes as such. * and maintains absolutes as such.
@@ -65,15 +56,16 @@ interface StaticFilesOptions {
export async function staticFiles( export async function staticFiles(
path: string, path: string,
{ output, src }: StaticFilesOptions { output, src }: StaticFilesOptions
) { ): Promise<string[]> {
const { debug, time } = output; const { debug, time } = output;
let files: string[] = []; let files: string[] = [];
// The package.json `files` whitelist still // The package.json `files` whitelist still
// honors ignores: https://docs.npmjs.com/files/package.json#files // honors ignores: https://docs.npmjs.com/files/package.json#files
const source = src || '.'; const source = src || '.';
// Convert all filenames into absolute paths
const search = await glob(source, { cwd: path, absolute: true, dot: true }); // Ensure that `path` is an absolute path
const search = resolve(path, source);
// Compile list of ignored patterns and files // Compile list of ignored patterns and files
const { ig } = await getVercelIgnore(path); const { ig } = await getVercelIgnore(path);
@@ -104,7 +96,7 @@ export async function staticFiles(
// Locate files // Locate files
files = await time( files = await time(
`Locating files ${path}`, `Locating files ${path}`,
explode(search, { explode([search], {
accepts, accepts,
output, output,
}) })
@@ -164,7 +156,7 @@ async function explode(
const all = await fs.readdir(file); const all = await fs.readdir(file);
/* eslint-disable no-use-before-define */ /* eslint-disable no-use-before-define */
const recursive = many(all.map(subdir => asAbsolute(subdir, file))); const recursive = many(all.map(subdir => asAbsolute(subdir, file)));
return (recursive as any) as Promise<string | null>; return recursive as any as Promise<string | null>;
/* eslint-enable no-use-before-define */ /* eslint-enable no-use-before-define */
} }
if (!s.isFile()) { if (!s.isFile()) {

View File

@@ -1,12 +1,12 @@
/** /**
* A fast implementation of an algorithm that takes an array and returns a copy of the array without duplicates. * A fast implementation of an algorithm that takes an array and returns a copy of the array without duplicates.
* We used to use `array-unique` ( https://github.com/jonschlinkert/array-unique/blob/5d1fbe560da8125e28e4ad6fbfa9daaf9f2ec120/index.js ) * We used to use `array-unique` (https://github.com/jonschlinkert/array-unique/blob/5d1fbe560da8125e28e4ad6fbfa9daaf9f2ec120/index.js)
* but from running benchmarks, found the implementation to be too slow. This implementation has show to be upto ~10x faster for large * but from running benchmarks, found the implementation to be too slow. This implementation has show to be upto ~10x faster for large
* projects * projects
* @param {Array} arr Input array that potentially has duplicates * @param {Array} arr Input array that potentially has duplicates
* @returns {Array} An array of the unique values in `arr` * @returns {Array} An array of the unique values in `arr`
*/ */
export default (arr: string[]) => { export default (arr: string[]): string[] => {
const len = arr.length; const len = arr.length;
const res: string[] = []; const res: string[] = [];
const o: { [key: string]: string | number } = {}; const o: { [key: string]: string | number } = {};

View File

@@ -0,0 +1,2 @@
!/*/.vercel
/*/.vercel/output

View File

@@ -0,0 +1 @@
!node_modules

View File

@@ -0,0 +1,15 @@
const { EdgeFunction } = require('@vercel/build-utils');
exports.version = 3;
exports.build = async ({ entrypoint, files }) => {
const output = new EdgeFunction({
name: entrypoint,
deploymentTarget: 'v8-worker',
entrypoint,
files: {
[entrypoint]: files[entrypoint]
},
});
return { output };
};

View File

@@ -0,0 +1,6 @@
{
"name": "edge-function",
"private": true,
"version": "0.0.0",
"main": "builder.js"
}

View File

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

View File

@@ -0,0 +1,5 @@
export const config = {
runtime: 'experimental-edge',
};
export default req => new Response('from edge');

View File

@@ -0,0 +1,7 @@
{
"functions": {
"api/*.js": {
"runtime": "edge-function@0.0.0"
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
import { IncomingMessage, ServerResponse } from 'http';
export default (req: IncomingMessage, res: ServerResponse) => res.end('Vercel');

View File

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

View File

@@ -0,0 +1 @@
<h1>Vercel</h1>

View File

@@ -0,0 +1 @@
!node_modules

View File

@@ -0,0 +1,14 @@
const { Lambda } = require('@vercel/build-utils');
exports.version = 3;
exports.build = async ({ entrypoint, files }) => {
const output = new Lambda({
files: {
[entrypoint]: files[entrypoint]
},
runtime: 'provided',
handler: entrypoint
});
return { output };
};

View File

@@ -0,0 +1,6 @@
{
"name": "txt-builder",
"private": true,
"version": "0.0.0",
"main": "index.js"
}

View File

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

View File

@@ -0,0 +1 @@
Text file

View File

@@ -0,0 +1,7 @@
{
"functions": {
"api/*.txt": {
"runtime": "txt-builder@0.0.0"
}
}
}

View File

@@ -19,7 +19,7 @@ const getRevertAliasConfigFile = () => {
], ],
}); });
}; };
module.exports = async function prepare(session, binaryPath) { module.exports = async function prepare(session, binaryPath, tmpFixturesDir) {
const spec = { const spec = {
'static-single-file': { 'static-single-file': {
'first.png': getImageFile(session, { size: 30 }), 'first.png': getImageFile(session, { size: 30 }),
@@ -440,16 +440,58 @@ module.exports = async function prepare(session, binaryPath) {
}, },
}), }),
}, },
'vc-build-corepack-npm': {
'.vercel/project.json': JSON.stringify({
orgId: '.',
projectId: '.',
settings: {
framework: null,
},
}),
'package.json': JSON.stringify({
private: true,
packageManager: 'npm@8.1.0',
scripts: {
build: 'mkdir -p public && npm --version > public/index.txt',
},
}),
},
'vc-build-corepack-pnpm': {
'.vercel/project.json': JSON.stringify({
orgId: '.',
projectId: '.',
settings: {
framework: null,
},
}),
'package.json': JSON.stringify({
private: true,
packageManager: 'pnpm@7.1.0',
scripts: {
build: 'mkdir -p public && pnpm --version > public/index.txt',
},
}),
},
'vc-build-corepack-yarn': {
'.vercel/project.json': JSON.stringify({
orgId: '.',
projectId: '.',
settings: {
framework: null,
},
}),
'package.json': JSON.stringify({
private: true,
packageManager: 'yarn@2.4.3',
scripts: {
build: 'mkdir -p public && yarn --version > public/index.txt',
},
}),
},
}; };
for (const [typeName, needed] of Object.entries(spec)) { for (const [typeName, needed] of Object.entries(spec)) {
const directory = join( const directory = join(tmpFixturesDir, typeName);
__dirname,
'..',
'fixtures',
'integration',
typeName
);
await mkdirp(directory); await mkdirp(directory);

View File

@@ -4,7 +4,7 @@ import { URL, parse as parseUrl } from 'url';
import test from 'ava'; import test from 'ava';
import semVer from 'semver'; import semVer from 'semver';
import { Readable } from 'stream'; import { Readable } from 'stream';
import { homedir } from 'os'; import { homedir, tmpdir } from 'os';
import _execa from 'execa'; import _execa from 'execa';
import XDGAppPaths from 'xdg-app-paths'; import XDGAppPaths from 'xdg-app-paths';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
@@ -31,7 +31,7 @@ function execa(file, args, options) {
} }
function fixture(name) { function fixture(name) {
const directory = path.join(__dirname, 'fixtures', 'integration', name); const directory = path.join(tmpFixturesDir, name);
const config = path.join(directory, 'project.json'); const config = path.join(directory, 'project.json');
// We need to remove it, otherwise we can't re-use fixtures // We need to remove it, otherwise we can't re-use fixtures
@@ -146,6 +146,7 @@ let email;
let contextName; let contextName;
let tmpDir; let tmpDir;
let tmpFixturesDir = path.join(tmpdir(), 'tmp-fixtures');
let globalDir = XDGAppPaths('com.vercel.cli').dataDirs()[0]; let globalDir = XDGAppPaths('com.vercel.cli').dataDirs()[0];
@@ -327,7 +328,7 @@ async function setupProject(process, projectName, overrides) {
test.before(async () => { test.before(async () => {
try { try {
await createUser(); await createUser();
await prepareFixtures(contextName, binaryPath); await prepareFixtures(contextName, binaryPath, tmpFixturesDir);
} catch (err) { } catch (err) {
console.log('Failed `test.before`'); console.log('Failed `test.before`');
console.log(err); console.log(err);
@@ -335,6 +336,8 @@ test.before(async () => {
}); });
test.after.always(async () => { test.after.always(async () => {
delete process.env.ENABLE_EXPERIMENTAL_COREPACK;
if (loginApiServer) { if (loginApiServer) {
// Stop mock server // Stop mock server
loginApiServer.close(); loginApiServer.close();
@@ -349,6 +352,11 @@ test.after.always(async () => {
// Remove config directory entirely // Remove config directory entirely
tmpDir.removeCallback(); tmpDir.removeCallback();
} }
if (tmpFixturesDir) {
console.log('removing tmpFixturesDir', tmpFixturesDir);
fs.removeSync(tmpFixturesDir);
}
}); });
test('default command should prompt login with empty auth.json', async t => { test('default command should prompt login with empty auth.json', async t => {
@@ -390,6 +398,99 @@ test('login', async t => {
t.is(auth.token, token); t.is(auth.token, token);
}); });
test('[vc build] should build project with corepack and select npm@8.1.0', async t => {
process.env.ENABLE_EXPERIMENTAL_COREPACK = '1';
const directory = fixture('vc-build-corepack-npm');
const before = await _execa('npm', ['--version'], {
cwd: directory,
reject: false,
});
const output = await execute(['build'], { cwd: directory });
t.is(output.exitCode, 0, formatOutput(output));
t.regex(output.stderr, /Build Completed/gm);
const after = await _execa('npm', ['--version'], {
cwd: directory,
reject: false,
});
// Ensure global npm didn't change
t.is(before.stdout, after.stdout);
// Ensure version is correct
t.is(
await fs.readFile(
path.join(directory, '.vercel/output/static/index.txt'),
'utf8'
),
'8.1.0\n'
);
// Ensure corepack will be cached
const contents = fs.readdirSync(
path.join(directory, '.vercel/cache/corepack')
);
t.deepEqual(contents, ['home', 'shim']);
});
test('[vc build] should build project with corepack and select pnpm@7.1.0', async t => {
process.env.ENABLE_EXPERIMENTAL_COREPACK = '1';
const directory = fixture('vc-build-corepack-pnpm');
const before = await _execa('pnpm', ['--version'], {
cwd: directory,
reject: false,
});
const output = await execute(['build'], { cwd: directory });
t.is(output.exitCode, 0, formatOutput(output));
t.regex(output.stderr, /Build Completed/gm);
const after = await _execa('pnpm', ['--version'], {
cwd: directory,
reject: false,
});
// Ensure global pnpm didn't change
t.is(before.stdout, after.stdout);
// Ensure version is correct
t.is(
await fs.readFile(
path.join(directory, '.vercel/output/static/index.txt'),
'utf8'
),
'7.1.0\n'
);
// Ensure corepack will be cached
const contents = fs.readdirSync(
path.join(directory, '.vercel/cache/corepack')
);
t.deepEqual(contents, ['home', 'shim']);
});
test('[vc build] should build project with corepack and select yarn@2.4.3', async t => {
process.env.ENABLE_EXPERIMENTAL_COREPACK = '1';
const directory = fixture('vc-build-corepack-yarn');
const before = await _execa('yarn', ['--version'], {
cwd: directory,
reject: false,
});
const output = await execute(['build'], { cwd: directory });
t.is(output.exitCode, 0, formatOutput(output));
t.regex(output.stderr, /Build Completed/gm);
const after = await _execa('yarn', ['--version'], {
cwd: directory,
reject: false,
});
// Ensure global yarn didn't change
t.is(before.stdout, after.stdout);
// Ensure version is correct
t.is(
await fs.readFile(
path.join(directory, '.vercel/output/static/index.txt'),
'utf8'
),
'2.4.3\n'
);
// Ensure corepack will be cached
const contents = fs.readdirSync(
path.join(directory, '.vercel/cache/corepack')
);
t.deepEqual(contents, ['home', 'shim']);
});
test('default command should deploy directory', async t => { test('default command should deploy directory', async t => {
const projectDir = fixture('deploy-default-with-sub-directory'); const projectDir = fixture('deploy-default-with-sub-directory');
const target = 'output'; const target = 'output';
@@ -1507,7 +1608,7 @@ test('try to purchase a domain', async t => {
const { stderr, stdout, exitCode } = await execa( const { stderr, stdout, exitCode } = await execa(
binaryPath, binaryPath,
['domains', 'buy', `${session}-test.org`, ...defaultArgs], ['domains', 'buy', `${session}-test.com`, ...defaultArgs],
{ {
reject: false, reject: false,
input: stream, input: stream,
@@ -1522,10 +1623,9 @@ test('try to purchase a domain', async t => {
console.log(exitCode); console.log(exitCode);
t.is(exitCode, 1); t.is(exitCode, 1);
t.true( t.regex(
stderr.includes( stderr,
`Error! Could not purchase domain. Please add a payment method using \`vercel billing add\`.` /Error! Could not purchase domain\. Please add a payment method using/
)
); );
}); });
@@ -1537,7 +1637,7 @@ test('try to transfer-in a domain with "--code" option', async t => {
'transfer-in', 'transfer-in',
'--code', '--code',
'xyz', 'xyz',
`${session}-test.org`, `${session}-test.com`,
...defaultArgs, ...defaultArgs,
], ],
{ {
@@ -1551,7 +1651,7 @@ test('try to transfer-in a domain with "--code" option', async t => {
t.true( t.true(
stderr.includes( stderr.includes(
`Error! The domain "${session}-test.org" is not transferable.` `Error! The domain "${session}-test.com" is not transferable.`
) )
); );
t.is(exitCode, 1); t.is(exitCode, 1);

View File

@@ -0,0 +1,240 @@
import ms from 'ms';
import fs from 'fs-extra';
import { join } from 'path';
import { client } from '../../mocks/client';
import build from '../../../src/commands/build';
jest.setTimeout(ms('1 minute'));
const fixture = (name: string) =>
join(__dirname, '../../fixtures/unit/commands/build', name);
describe('build', () => {
const originalCwd = process.cwd();
it('should build with `@vercel/static`', async () => {
const cwd = fixture('static');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// `builds.json` says that "@vercel/static" was run
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds).toMatchObject({
target: 'preview',
builds: [
{
require: '@vercel/static',
apiVersion: 2,
src: '**',
use: '@vercel/static',
},
],
});
// "static" directory contains static files
const files = await fs.readdir(join(output, 'static'));
expect(files.sort()).toEqual(['index.html']);
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
it('should build with `@vercel/node`', async () => {
const cwd = fixture('node');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// `builds.json` says that "@vercel/node" was run
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds).toMatchObject({
target: 'preview',
builds: [
{
require: '@vercel/node',
apiVersion: 3,
use: '@vercel/node',
src: 'api/es6.js',
config: { zeroConfig: true },
},
{
require: '@vercel/node',
apiVersion: 3,
use: '@vercel/node',
src: 'api/index.js',
config: { zeroConfig: true },
},
{
require: '@vercel/node',
apiVersion: 3,
use: '@vercel/node',
src: 'api/mjs.mjs',
config: { zeroConfig: true },
},
{
require: '@vercel/node',
apiVersion: 3,
use: '@vercel/node',
src: 'api/typescript.ts',
config: { zeroConfig: true },
},
],
});
// "static" directory is empty
const hasStaticFiles = await fs.pathExists(join(output, 'static'));
expect(
hasStaticFiles,
'Expected ".vercel/output/static" to not exist'
).toEqual(false);
// "functions/api" directory has output Functions
const functions = await fs.readdir(join(output, 'functions/api'));
expect(functions.sort()).toEqual([
'es6.func',
'index.func',
'mjs.func',
'typescript.func',
]);
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
it('should build with 3rd party Builder', async () => {
const cwd = fixture('third-party-builder');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// `builds.json` says that "@vercel/node" was run
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds).toMatchObject({
target: 'preview',
builds: [
{
require: 'txt-builder',
apiVersion: 3,
use: 'txt-builder@0.0.0',
src: 'api/foo.txt',
config: {
zeroConfig: true,
functions: {
'api/*.txt': {
runtime: 'txt-builder@0.0.0',
},
},
},
},
{
require: '@vercel/static',
apiVersion: 2,
use: '@vercel/static',
src: '!{api/**,package.json}',
config: {
zeroConfig: true,
},
},
],
});
// "static" directory is empty
const hasStaticFiles = await fs.pathExists(join(output, 'static'));
expect(
hasStaticFiles,
'Expected ".vercel/output/static" to not exist'
).toEqual(false);
// "functions/api" directory has output Functions
const functions = await fs.readdir(join(output, 'functions/api'));
expect(functions.sort()).toEqual(['foo.func']);
const vcConfig = await fs.readJSON(
join(output, 'functions/api/foo.func/.vc-config.json')
);
expect(vcConfig).toMatchObject({
handler: 'api/foo.txt',
runtime: 'provided',
environment: {},
});
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
it('should serialize `EdgeFunction` output in version 3 Builder', async () => {
const cwd = fixture('edge-function');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
client.setArgv('build', '--prod');
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// `builds.json` says that "@vercel/node" was run
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds).toMatchObject({
target: 'production',
builds: [
{
require: 'edge-function',
apiVersion: 3,
use: 'edge-function@0.0.0',
src: 'api/edge.js',
config: {
zeroConfig: true,
functions: {
'api/*.js': {
runtime: 'edge-function@0.0.0',
},
},
},
},
{
require: '@vercel/static',
apiVersion: 2,
use: '@vercel/static',
src: '!{api/**,package.json}',
config: {
zeroConfig: true,
},
},
],
});
// "static" directory is empty
const hasStaticFiles = await fs.pathExists(join(output, 'static'));
expect(
hasStaticFiles,
'Expected ".vercel/output/static" to not exist'
).toEqual(false);
// "functions/api" directory has output Functions
const functions = await fs.readdir(join(output, 'functions/api'));
expect(functions.sort()).toEqual(['edge.func']);
const vcConfig = await fs.readJSON(
join(output, 'functions/api/edge.func/.vc-config.json')
);
expect(vcConfig).toMatchObject({
runtime: 'edge',
name: 'api/edge.js',
deploymentTarget: 'v8-worker',
entrypoint: 'api/edge.js',
});
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
});

View File

@@ -10,7 +10,7 @@ import {
import vercelNextPkg from '@vercel/next/package.json'; import vercelNextPkg from '@vercel/next/package.json';
import vercelNodePkg from '@vercel/node/package.json'; import vercelNodePkg from '@vercel/node/package.json';
jest.setTimeout(ms('20 seconds')); jest.setTimeout(ms('30 seconds'));
describe('importBuilders()', () => { describe('importBuilders()', () => {
it('should import built-in Builders', async () => { it('should import built-in Builders', async () => {

View File

@@ -5,10 +5,10 @@
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"moduleResolution": "node", "moduleResolution": "node",
"module": "commonjs", "module": "commonjs",
"target": "es2019", "target": "ES2020",
"esModuleInterop": true, "esModuleInterop": true,
"allowJs": true, "allowJs": true,
"lib": ["esnext"], "lib": ["ES2020"],
"resolveJsonModule": true, "resolveJsonModule": true,
"sourceMap": true, "sourceMap": true,
"outDir": "./dist", "outDir": "./dist",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/client", "name": "@vercel/client",
"version": "11.0.4", "version": "12.0.1",
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",
"homepage": "https://vercel.com", "homepage": "https://vercel.com",
@@ -20,7 +20,7 @@
"test-unit": "yarn test tests/unit.*test.*" "test-unit": "yarn test tests/unit.*test.*"
}, },
"engines": { "engines": {
"node": ">= 12" "node": ">= 14"
}, },
"devDependencies": { "devDependencies": {
"@types/async-retry": "1.4.1", "@types/async-retry": "1.4.1",
@@ -42,7 +42,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "3.1.1", "@vercel/build-utils": "4.1.0",
"@zeit/fetch": "5.2.0", "@zeit/fetch": "5.2.0",
"async-retry": "1.2.3", "async-retry": "1.2.3",
"async-sema": "3.0.0", "async-sema": "3.0.0",

View File

@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"declaration": true, "declaration": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["esnext"], "lib": ["ES2020"],
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",
"outDir": "dist", "outDir": "dist",
@@ -12,7 +12,7 @@
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"strict": true, "strict": true,
"target": "es2019" "target": "ES2020"
}, },
"include": ["./src"] "include": ["./src"]
} }

View File

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

View File

@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"declaration": true, "declaration": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["esnext"], "lib": ["ES2020"],
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",
"noEmitOnError": true, "noEmitOnError": true,
@@ -13,7 +13,7 @@
"outDir": "./dist", "outDir": "./dist",
"types": ["node", "jest"], "types": ["node", "jest"],
"strict": true, "strict": true,
"target": "esnext" "target": "ES2020"
}, },
"include": ["src/*.ts"], "include": ["src/*.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]

View File

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

View File

@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"declaration": false, "declaration": false,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["esnext"], "lib": ["ES2020"],
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",
"noEmitOnError": true, "noEmitOnError": true,
@@ -13,6 +13,6 @@
"noImplicitThis": false, "noImplicitThis": false,
"types": ["node"], "types": ["node"],
"strict": true, "strict": true,
"target": "es2018" "target": "ES2020"
} }
} }

View File

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

View File

@@ -2,23 +2,6 @@ const KIB = 1024;
const MIB = 1024 * KIB; const MIB = 1024 * KIB;
/** /**
* The limit after compression. it has to be kibibyte instead of kilobyte * The maximum size of a *compressed* edge function.
* See https://github.com/cloudflare/wrangler/blob/8907b12add3d70ee21ac597b69cd66f6807571f4/src/wranglerjs/output.rs#L44
*/ */
const EDGE_FUNCTION_SCRIPT_SIZE_LIMIT = MIB; export const EDGE_FUNCTION_SIZE_LIMIT = MIB;
/**
* This safety buffer must cover the size of our whole runtime layer compressed
* plus some extra space to allow it to grow in the future. At the time of
* writing this comment the compressed size size is ~7KiB so 20KiB should
* be more than enough.
*/
const EDGE_FUNCTION_SCRIPT_SIZE_BUFFER = 20 * KIB;
/**
* The max size we allow for compressed user code is the compressed script
* limit minus the compressed safety buffer. We must check this limit after
* compressing the user code.
*/
export const EDGE_FUNCTION_USER_SCRIPT_SIZE_LIMIT =
EDGE_FUNCTION_SCRIPT_SIZE_LIMIT - EDGE_FUNCTION_SCRIPT_SIZE_BUFFER;

View File

@@ -3,7 +3,7 @@ import { readFile } from 'fs-extra';
import { ConcatSource, Source } from 'webpack-sources'; import { ConcatSource, Source } from 'webpack-sources';
import { fileToSource, raw, sourcemapped } from '../sourcemapped'; import { fileToSource, raw, sourcemapped } from '../sourcemapped';
import { join } from 'path'; import { join } from 'path';
import { EDGE_FUNCTION_USER_SCRIPT_SIZE_LIMIT } from './constants'; import { EDGE_FUNCTION_SIZE_LIMIT } from './constants';
import zlib from 'zlib'; import zlib from 'zlib';
import { promisify } from 'util'; import { promisify } from 'util';
import bytes from 'pretty-bytes'; import bytes from 'pretty-bytes';
@@ -74,11 +74,11 @@ function getWasmImportStatements(wasm: { name: string }[] = []) {
async function validateScript(content: string) { async function validateScript(content: string) {
const gzipped = await gzip(content); const gzipped = await gzip(content);
if (gzipped.length > EDGE_FUNCTION_USER_SCRIPT_SIZE_LIMIT) { if (gzipped.length > EDGE_FUNCTION_SIZE_LIMIT) {
throw new Error( throw new Error(
`Exceeds maximum edge function script size: ${bytes( `Exceeds maximum edge function script size: ${bytes(
gzipped.length gzipped.length
)} / ${bytes(EDGE_FUNCTION_USER_SCRIPT_SIZE_LIMIT)}` )} / ${bytes(EDGE_FUNCTION_SIZE_LIMIT)}`
); );
} }
} }

View File

@@ -78,6 +78,7 @@ import {
updateRouteSrc, updateRouteSrc,
validateEntrypoint, validateEntrypoint,
} from './utils'; } from './utils';
import assert from 'assert';
export const version = 2; export const version = 2;
export const htmlContentType = 'text/html; charset=utf-8'; export const htmlContentType = 'text/html; charset=utf-8';
@@ -131,10 +132,11 @@ function getRealNextVersion(entryPath: string): string | false {
// First try to resolve the `next` dependency and get the real version from its // First try to resolve the `next` dependency and get the real version from its
// package.json. This allows the builder to be used with frameworks like Blitz that // package.json. This allows the builder to be used with frameworks like Blitz that
// bundle Next but where Next isn't in the project root's package.json // bundle Next but where Next isn't in the project root's package.json
const nextVersion: string = require(resolveFrom(
entryPath, // NOTE: `eval('require')` is necessary to avoid bad transpilation to `__webpack_require__`
'next/package.json' const nextVersion: string = eval('require')(
)).version; resolveFrom(entryPath, 'next/package.json')
).version;
console.log(`Detected Next.js version: ${nextVersion}`); console.log(`Detected Next.js version: ${nextVersion}`);
return nextVersion; return nextVersion;
} catch (_ignored) { } catch (_ignored) {
@@ -2596,8 +2598,17 @@ async function getServerlessPages(params: {
// Edge Functions do not consider as Serverless Functions // Edge Functions do not consider as Serverless Functions
for (const edgeFunctionFile of Object.keys( for (const edgeFunctionFile of Object.keys(
middlewareManifest?.middleware ?? {} middlewareManifest?.functions ?? {}
)) { )) {
// `getStaticProps` are expecting `Prerender` output which is a Serverless function
// and not an Edge Function. Therefore we only remove API endpoints for now, as they
// don't have `getStaticProps`.
//
// Context: https://github.com/vercel/vercel/pull/7905#discussion_r890213165
assert(
edgeFunctionFile.startsWith('/api/'),
`Only API endpoints are currently supported for Edge endpoints.`
);
delete pages[edgeFunctionFile.slice(1) + '.js']; delete pages[edgeFunctionFile.slice(1) + '.js'];
} }

View File

@@ -54,6 +54,7 @@ import prettyBytes from 'pretty-bytes';
// related PR: https://github.com/vercel/next.js/pull/30046 // related PR: https://github.com/vercel/next.js/pull/30046
const CORRECT_NOT_FOUND_ROUTES_VERSION = 'v12.0.1'; const CORRECT_NOT_FOUND_ROUTES_VERSION = 'v12.0.1';
const CORRECT_MIDDLEWARE_ORDER_VERSION = 'v12.1.7-canary.29';
export async function serverBuild({ export async function serverBuild({
dynamicPages, dynamicPages,
@@ -131,6 +132,10 @@ export async function serverBuild({
nextVersion, nextVersion,
CORRECT_NOT_FOUND_ROUTES_VERSION CORRECT_NOT_FOUND_ROUTES_VERSION
); );
const isCorrectMiddlewareOrder = semver.gte(
nextVersion,
CORRECT_MIDDLEWARE_ORDER_VERSION
);
let hasStatic500 = !!staticPages[path.join(entryDirectory, '500')]; let hasStatic500 = !!staticPages[path.join(entryDirectory, '500')];
if (lambdaPageKeys.length === 0) { if (lambdaPageKeys.length === 0) {
@@ -788,6 +793,7 @@ export async function serverBuild({
entryPath, entryPath,
outputDirectory, outputDirectory,
routesManifest, routesManifest,
isCorrectMiddlewareOrder,
}); });
const dynamicRoutes = await getDynamicRoutes( const dynamicRoutes = await getDynamicRoutes(
@@ -1025,6 +1031,10 @@ export async function serverBuild({
...redirects, ...redirects,
// middleware comes directly after redirects but before
// beforeFiles rewrites as middleware is not a "file" route
...(isCorrectMiddlewareOrder ? middleware.staticRoutes : []),
...beforeFilesRewrites, ...beforeFilesRewrites,
// Make sure to 404 for the /404 path itself // Make sure to 404 for the /404 path itself
@@ -1067,7 +1077,10 @@ export async function serverBuild({
}, },
]), ]),
...middleware.staticRoutes, // while middleware was in beta the order came right before
// handle: 'filesystem' we maintain this for older versions
// to prevent a local/deploy mismatch
...(!isCorrectMiddlewareOrder ? middleware.staticRoutes : []),
// Next.js page lambdas, `static/` folder, reserved assets, and `public/` // Next.js page lambdas, `static/` folder, reserved assets, and `public/`
// folder // folder

View File

@@ -244,9 +244,7 @@ export async function getRoutesManifest(
}); });
} }
// eslint-disable-next-line @typescript-eslint/no-var-requires const routesManifest: RoutesManifest = await fs.readJSON(pathRoutesManifest);
const routesManifest: RoutesManifest = require(pathRoutesManifest);
// remove temporary array based routeKeys from v1/v2 of routes // remove temporary array based routeKeys from v1/v2 of routes
// manifest since it can result in invalid routes // manifest since it can result in invalid routes
for (const route of routesManifest.dataRoutes || []) { for (const route of routesManifest.dataRoutes || []) {
@@ -368,10 +366,10 @@ export async function getDynamicRoutes(
let getSortedRoutes: ((normalizedPages: string[]) => string[]) | undefined; let getSortedRoutes: ((normalizedPages: string[]) => string[]) | undefined;
try { try {
({ getRouteRegex, getSortedRoutes } = require(resolveFrom( // NOTE: `eval('require')` is necessary to avoid bad transpilation to `__webpack_require__`
entryPath, ({ getRouteRegex, getSortedRoutes } = eval('require')(
'next-server/dist/lib/router/utils' resolveFrom(entryPath, 'next-server/dist/lib/router/utils')
))); ));
if (typeof getRouteRegex !== 'function') { if (typeof getRouteRegex !== 'function') {
getRouteRegex = undefined; getRouteRegex = undefined;
} }
@@ -379,10 +377,10 @@ export async function getDynamicRoutes(
if (!getRouteRegex || !getSortedRoutes) { if (!getRouteRegex || !getSortedRoutes) {
try { try {
({ getRouteRegex, getSortedRoutes } = require(resolveFrom( // NOTE: `eval('require')` is necessary to avoid bad transpilation to `__webpack_require__`
entryPath, ({ getRouteRegex, getSortedRoutes } = eval('require')(
'next/dist/next-server/lib/router/utils' resolveFrom(entryPath, 'next/dist/next-server/lib/router/utils')
))); ));
if (typeof getRouteRegex !== 'function') { if (typeof getRouteRegex !== 'function') {
getRouteRegex = undefined; getRouteRegex = undefined;
} }
@@ -536,9 +534,7 @@ export async function getImagesManifest(
return undefined; return undefined;
} }
// eslint-disable-next-line @typescript-eslint/no-var-requires return fs.readJson(pathImagesManifest);
const imagesManifest: NextImagesManifest = require(pathImagesManifest);
return imagesManifest;
} }
type FileMap = { [page: string]: FileFsRef }; type FileMap = { [page: string]: FileFsRef };
@@ -2145,10 +2141,12 @@ export async function getMiddlewareBundle({
entryPath, entryPath,
outputDirectory, outputDirectory,
routesManifest, routesManifest,
isCorrectMiddlewareOrder,
}: { }: {
entryPath: string; entryPath: string;
outputDirectory: string; outputDirectory: string;
routesManifest: RoutesManifest; routesManifest: RoutesManifest;
isCorrectMiddlewareOrder: boolean;
}) { }) {
const middlewareManifest = await getMiddlewareManifest( const middlewareManifest = await getMiddlewareManifest(
entryPath, entryPath,
@@ -2270,16 +2268,17 @@ export async function getMiddlewareBundle({
const route: Route = { const route: Route = {
continue: true, continue: true,
src: worker.routeSrc, src: worker.routeSrc,
...(worker.type === 'middleware'
? {
middlewarePath: shortPath,
override: true,
}
: {
dest: shortPath,
}),
}; };
if (worker.type === 'function') {
route.dest = shortPath;
} else {
route.middlewarePath = shortPath;
if (isCorrectMiddlewareOrder) {
route.override = true;
}
}
if (routesManifest.version > 3 && isDynamicRoute(worker.page)) { if (routesManifest.version > 3 && isDynamicRoute(worker.page)) {
source.dynamicRouteMap.set(worker.page, route); source.dynamicRouteMap.set(worker.page, route);
} else { } else {
@@ -2321,8 +2320,7 @@ export async function getMiddlewareManifest(
return; return;
} }
// eslint-disable-next-line @typescript-eslint/no-var-requires return fs.readJSON(middlewareManifestPath);
return require(middlewareManifestPath);
} }
/** /**

View File

@@ -4,6 +4,8 @@ const cheerio = require('cheerio');
const { check, deployAndTest } = require('../../utils'); const { check, deployAndTest } = require('../../utils');
const fetch = require('../../../../../test/lib/deployment/fetch-retry'); const fetch = require('../../../../../test/lib/deployment/fetch-retry');
const ABSOLUTE_URL_PATTERN = /^https?:\/\//i;
async function checkForChange(url, initialValue, hardError) { async function checkForChange(url, initialValue, hardError) {
return check( return check(
async () => { async () => {
@@ -32,6 +34,13 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
it('should deploy and pass probe checks', async () => { it('should deploy and pass probe checks', async () => {
const info = await deployAndTest(__dirname); const info = await deployAndTest(__dirname);
Object.assign(ctx, info); Object.assign(ctx, info);
if (!ABSOLUTE_URL_PATTERN.test(ctx.deploymentUrl)) {
const details = JSON.stringify(ctx);
throw new Error(
`Deployment did not result in an absolute deploymentUrl: ${details}`
);
}
}); });
it('should revalidate content properly from /', async () => { it('should revalidate content properly from /', async () => {

View File

@@ -0,0 +1,8 @@
const path = require('path');
const { deployAndTest } = require('../../utils');
describe(`${__dirname.split(path.sep).pop()}`, () => {
it('should deploy and pass probe checks', async () => {
await deployAndTest(__dirname);
});
});

View File

@@ -0,0 +1,21 @@
module.exports = {
redirects() {
return [
{
source: '/redirect-me',
destination: '/from-next-config',
permanent: false,
},
];
},
rewrites() {
return {
beforeFiles: [
{
source: '/rewrite-before-files',
destination: '/somewhere',
},
],
};
},
};

View File

@@ -0,0 +1,11 @@
{
"scripts": {
"dev": "next dev",
"build": "next build"
},
"dependencies": {
"next": "12.1.6",
"react": "latest",
"react-dom": "latest"
}
}

View File

@@ -0,0 +1,226 @@
import { NextResponse } from 'next/server';
const ALLOWED = ['allowed'];
export function middleware(request) {
const url = request.nextUrl;
const pathname = url.pathname;
if (process.env.FOO) {
console.log(`Includes env variable ${process.env.FOO}`);
}
if (url.pathname === '/redirect-me') {
url.pathname = '/from-middleware';
return NextResponse.redirect(url);
}
if (url.pathname === '/next') {
return NextResponse.next();
}
if (url.pathname === '/version') {
return NextResponse.json({
enumerable: Object.keys(self).includes('VercelRuntime'),
version: self.VercelRuntime.version,
});
}
if (url.pathname === '/globals') {
const globalThisKeys = Object.keys(globalThis);
const globalKeys = globalThisKeys.reduce((acc, globalName) => {
const key = globalName.toString();
if (global[key]) acc.push(key);
return acc;
}, []);
const res = NextResponse.next();
res.headers.set(
'data',
JSON.stringify({ globals: globalKeys, globalThis: globalThisKeys })
);
return res;
}
if (url.pathname === '/log') {
console.log('hi there');
return;
}
if (url.pathname === '/logs') {
console.clear();
for (let i = 0; i < 3; i++) console.count();
console.count('test');
console.count('test');
console.dir({ hello: 'world' });
console.log('hello');
console.log('world');
return;
}
if (url.pathname === '/greetings') {
const data = { message: 'hello world!' };
const res = NextResponse.next();
res.headers.set('x-example', 'edge');
res.headers.set('data', JSON.stringify(data));
return res;
}
if (url.pathname === '/rewrite-me-to-about') {
url.pathname = '/about';
url.searchParams.set('middleware', 'foo');
return NextResponse.rewrite(url);
}
if (url.pathname === '/redirect-me-to-about') {
url.pathname = '/about';
url.searchParams.set('middleware', 'foo');
return Response.redirect(url);
}
if (url.pathname === '/rewrite-absolute') {
return NextResponse.rewrite('https://example.vercel.sh/foo?foo=bar');
}
if (url.pathname === '/rewrite-relative') {
url.pathname = '/foo';
url.searchParams.set('foo', 'bar');
return NextResponse.rewrite(url);
}
if (url.pathname === '/redirect-absolute') {
return Response.redirect('https://vercel.com');
}
if (url.pathname === '/redirect-301') {
url.pathname = '/greetings';
return NextResponse.redirect(url, 301);
}
if (url.pathname === '/reflect') {
const res = NextResponse.next();
res.headers.set(
'data',
JSON.stringify({
geo: request.geo,
headers: Object.fromEntries(request.headers),
ip: request.ip,
method: request.method,
nextUrl: {
hash: request.nextUrl.hash,
hostname: request.nextUrl.hostname,
pathname: request.nextUrl.pathname,
port: request.nextUrl.port,
protocol: request.nextUrl.protocol,
search: request.nextUrl.search,
},
url: request.url,
})
);
return res;
}
if (url.pathname === '/stream-response') {
const { readable, writable } = new TransformStream();
const waitUntil = (async () => {
const enc = new TextEncoder();
const writer = writable.getWriter();
writer.write(enc.encode('this is a streamed '));
writer.write(enc.encode('response '));
return writer.close();
})();
return {
waitUntil,
response: NextResponse.next(),
};
}
if (url.pathname === '/throw-error') {
const error = new Error('oh no!');
console.log('This is not worker.js');
console.error(error);
return new Promise((_, reject) => reject(error));
}
if (url.pathname === '/throw-error-internal') {
function myFunctionName() {
throw new Error('Oh no!');
}
function anotherFunction() {
return myFunctionName();
}
try {
anotherFunction();
} catch (err) {
console.error(err);
}
return new Promise((_, reject) => reject(new Error('oh no!')));
}
if (url.pathname === '/unhandledrejection') {
Promise.reject(new TypeError('captured unhandledrejection error.'));
return NextResponse.next();
}
if (pathname.startsWith('/query-params')) {
if (pathname.endsWith('/clear')) {
const strategy =
url.searchParams.get('strategy') === 'rewrite' ? 'rewrite' : 'redirect';
for (const key of [...url.searchParams.keys()]) {
if (!ALLOWED.includes(key)) {
url.searchParams.delete(key);
}
}
const newPath = url.pathname.replace(/\/clear$/, '');
url.pathname = newPath;
if (strategy === 'redirect') {
return NextResponse.redirect(url);
} else {
return NextResponse.rewrite(url);
}
}
const obj = Object.fromEntries([...url.searchParams.entries()]);
const res = NextResponse.next();
res.headers.set('data', JSON.stringify(obj));
return res;
}
if (pathname.startsWith('/home')) {
if (!request.cookies.bucket) {
const bucket = Math.random() >= 0.5 ? 'a' : 'b';
url.pathname = `/home/${bucket}`;
const response = NextResponse.rewrite(url);
response.cookie('bucket', bucket);
return response;
}
url.pathname = `/home/${request.cookies.bucket}`;
return NextResponse.rewrite(url);
}
if (pathname.startsWith('/fetch-subrequest')) {
const destinationUrl =
url.searchParams.get('url') || 'https://example.vercel.sh';
return fetch(destinationUrl, { headers: request.headers });
}
if (url.pathname === '/dynamic/greet') {
const res = NextResponse.next();
res.headers.set(
'data',
JSON.stringify({
message: url.searchParams.get('greeting') || 'Hi friend',
})
);
return res;
}
}

View File

@@ -0,0 +1,16 @@
export default function Main({ message, middleware }) {
return (
<div>
<h1 className="title">About Page</h1>
<p className={message}>{message}</p>
<p className="middleware">{middleware}</p>
</div>
);
}
export const getServerSideProps = ({ query }) => ({
props: {
middleware: query.middleware || '',
message: query.message || '',
},
});

View File

@@ -0,0 +1,6 @@
import { NextResponse } from 'next/server';
export const middleware = async () => {
// Just chillin doing nothing
return NextResponse.next();
};

View File

@@ -0,0 +1,10 @@
export default function Page() {
return <p>blog</p>;
}
export function getStaticProps() {
return {
props: {},
revalidate: 1,
};
}

View File

@@ -0,0 +1,3 @@
export default function Index() {
return <p className="title">Dynamic route</p>;
}

View File

@@ -0,0 +1,16 @@
export default function Main({ message, middleware }) {
return (
<div>
<h1 className="title">About Page</h1>
<p className={message}>{message}</p>
<p className="middleware">{middleware}</p>
</div>
);
}
export const getServerSideProps = ({ query }) => ({
props: {
middleware: query.middleware || '',
message: query.message || '',
},
});

View File

@@ -0,0 +1,3 @@
export default function Home() {
return <h1>Home A</h1>;
}

View File

@@ -0,0 +1,3 @@
export default function Home() {
return <h1>Home B</h1>;
}

View File

@@ -0,0 +1,81 @@
import Link from 'next/link';
export default function Index() {
return (
<div>
<h1>Demo</h1>
<ul>
<li>
<Link href="/home">
<a>A/B Testing</a>
</Link>
</li>
<li>
<Link href="/rewrite-me-to-about">
<a>Rewrite to existing page</a>
</Link>
</li>
<li>
<Link href="/redirect-me-to-about">
<a>Redirect to existing page</a>
</Link>
</li>
<li>
<Link href="/rewrite">
<a>Rewrite to external site</a>
</Link>
</li>
<li>
<Link href="/redirect">
<a>Redirect to external site</a>
</Link>
</li>
<li>
<Link href="/greetings">
<a>Respond with JSON</a>
</Link>
</li>
<li>
<Link href="/stream-response">
<a>Respond with Stream</a>
</Link>
</li>
<li>
<Link href="/dynamic/greet?greeting=hola">
<a>Dynamic Nested Middleware</a>
</Link>
</li>
<li>
<Link href="/eval">
<a>do a eval</a>
</Link>
</li>
<li>
<Link href="/logs">
<a>print some logs</a>
</Link>
</li>
<li>
<Link href="/fetch">
<a>perform a fetch</a>
</Link>
</li>
<li>
<Link href="/throw-error">
<a>throw an error</a>
</Link>
</li>
<li>
<Link href="/throw-error-internal">
<a>throw a controller error</a>
</Link>
</li>
<li>
<Link href="/timeout">
<a>simulate timeout</a>
</Link>
</li>
</ul>
</div>
);
}

View File

@@ -0,0 +1,16 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@vercel/next" }],
"probes": [
{
"path": "/redirect-me",
"status": 307,
"responseHeaders": {
"Location": "/from-middleware/"
},
"fetchOptions": {
"redirect": "manual"
}
}
]
}

View File

@@ -47,6 +47,11 @@ export function middleware(request) {
return; return;
} }
if (url.pathname === '/somewhere') {
url.pathname = '/from-middleware';
return NextResponse.redirect(url);
}
if (url.pathname === '/logs') { if (url.pathname === '/logs') {
console.clear(); console.clear();
for (let i = 0; i < 3; i++) console.count(); for (let i = 0; i < 3; i++) console.count();

View File

@@ -11,6 +11,23 @@
"fetchOptions": { "fetchOptions": {
"redirect": "manual" "redirect": "manual"
} }
},
{
"path": "/rewrite-before-files",
"status": 404,
"fetchOptions": {
"redirect": "manual"
}
},
{
"path": "/somewhere",
"status": 307,
"responseHeaders": {
"Location": "/from-middleware/"
},
"fetchOptions": {
"redirect": "manual"
}
} }
] ]
} }

View File

@@ -45,7 +45,7 @@ describe('Middleware simple project', () => {
expect(typeof beforeFilesIndex).toBe('number'); expect(typeof beforeFilesIndex).toBe('number');
expect(redirectIndex).toBeLessThan(middlewareIndex); expect(redirectIndex).toBeLessThan(middlewareIndex);
expect(redirectIndex).toBeLessThan(beforeFilesIndex); expect(redirectIndex).toBeLessThan(beforeFilesIndex);
expect(beforeFilesIndex).toBeLessThan(middlewareIndex); expect(middlewareIndex).toBeLessThan(beforeFilesIndex);
expect(middlewareIndex).toBeLessThan(handleFileSystemIndex); expect(middlewareIndex).toBeLessThan(handleFileSystemIndex);
}); });

View File

@@ -2,8 +2,8 @@
"compilerOptions": { "compilerOptions": {
"strict": true, "strict": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["esnext"], "lib": ["ES2020"],
"target": "es2018", "target": "ES2020",
"module": "commonjs", "module": "commonjs",
"outDir": "dist", "outDir": "dist",
"sourceMap": false, "sourceMap": false,

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/node-bridge", "name": "@vercel/node-bridge",
"version": "2.2.2", "version": "3.0.0",
"license": "MIT", "license": "MIT",
"main": "./index.js", "main": "./index.js",
"repository": { "repository": {

View File

@@ -2,11 +2,13 @@
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"lib": ["esnext"], "lib": ["ES2020"],
"noEmit": true, "noEmit": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"strict": true, "strict": true,
"declaration": true "target": "ES2020",
"declaration": true,
"module": "commonjs"
}, },
"include": ["helpers.ts", "bridge.js", "launcher.js"], "include": ["helpers.ts", "bridge.js", "launcher.js"],
"exclude": ["node_modules"] "exclude": ["node_modules"]

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/node", "name": "@vercel/node",
"version": "1.15.4", "version": "2.1.0",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -31,7 +31,7 @@
}, },
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"@vercel/node-bridge": "2.2.2", "@vercel/node-bridge": "3.0.0",
"ts-node": "8.9.1", "ts-node": "8.9.1",
"typescript": "4.3.4" "typescript": "4.3.4"
}, },
@@ -45,9 +45,10 @@
"@types/etag": "1.8.0", "@types/etag": "1.8.0",
"@types/jest": "27.4.1", "@types/jest": "27.4.1",
"@types/test-listen": "1.1.0", "@types/test-listen": "1.1.0",
"@vercel/build-utils": "3.1.1", "@vercel/build-utils": "4.1.0",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@vercel/nft": "0.19.1", "@vercel/nft": "0.19.1",
"@vercel/static-config": "2.0.1",
"content-type": "1.0.4", "content-type": "1.0.4",
"cookie": "0.4.0", "cookie": "0.4.0",
"etag": "1.8.1", "etag": "1.8.1",

View File

@@ -16,18 +16,15 @@ import {
sep, sep,
parse as parsePath, parse as parsePath,
} from 'path'; } from 'path';
import { Project } from 'ts-morph';
import once from '@tootallnate/once'; import once from '@tootallnate/once';
import { nodeFileTrace } from '@vercel/nft'; import { nodeFileTrace } from '@vercel/nft';
import { import {
File,
Files,
Meta,
Config,
StartDevServerOptions,
glob, glob,
download, download,
FileBlob, FileBlob,
FileFsRef, FileFsRef,
EdgeFunction,
NodejsLambda, NodejsLambda,
runNpmInstall, runNpmInstall,
runPackageJsonScript, runPackageJsonScript,
@@ -37,11 +34,20 @@ import {
debug, debug,
isSymbolicLink, isSymbolicLink,
walkParentDirs, walkParentDirs,
} from '@vercel/build-utils';
import type {
File,
Files,
Meta,
Config,
StartDevServerOptions,
BuildV3, BuildV3,
PrepareCache, PrepareCache,
StartDevServer, StartDevServer,
NodeVersion, NodeVersion,
BuildResultV3,
} from '@vercel/build-utils'; } from '@vercel/build-utils';
import { getConfig } from '@vercel/static-config';
import { Register, register } from './typescript'; import { Register, register } from './typescript';
@@ -69,6 +75,8 @@ function isPortInfo(v: any): v is PortInfo {
return v && typeof v.port === 'number'; return v && typeof v.port === 'number';
} }
const ALLOWED_RUNTIMES = ['nodejs', 'experimental-edge'];
const require_ = eval('require'); const require_ = eval('require');
const tscPath = resolve(dirname(require_.resolve('typescript')), '../bin/tsc'); const tscPath = resolve(dirname(require_.resolve('typescript')), '../bin/tsc');
@@ -187,7 +195,10 @@ async function compile(
if (cached === null) return null; if (cached === null) return null;
try { try {
let source: string | Buffer = readFileSync(fsPath); let source: string | Buffer = readFileSync(fsPath);
if (fsPath.endsWith('.ts') || fsPath.endsWith('.tsx')) { if (
(fsPath.endsWith('.ts') && !fsPath.endsWith('.d.ts')) ||
fsPath.endsWith('.tsx')
) {
source = compileTypeScript(fsPath, source.toString()); source = compileTypeScript(fsPath, source.toString());
} }
const { mode } = lstatSync(fsPath); const { mode } = lstatSync(fsPath);
@@ -362,20 +373,52 @@ export const build: BuildV3 = async ({
); );
debug(`Trace complete [${Date.now() - traceTime}ms]`); debug(`Trace complete [${Date.now() - traceTime}ms]`);
const shouldAddHelpers = !( const project = new Project();
config.helpers === false || process.env.NODEJS_HELPERS === '0' const staticConfig = getConfig(project, entrypointPath);
);
const lambda = new NodejsLambda({ let output: BuildResultV3['output'] | undefined;
files: preparedFiles, const handler = renameTStoJS(relative(baseDir, entrypointPath));
handler: renameTStoJS(relative(baseDir, entrypointPath)),
runtime: nodeVersion.runtime,
shouldAddHelpers,
shouldAddSourcemapSupport,
awsLambdaHandler,
});
return { output: lambda }; if (staticConfig?.runtime) {
if (!ALLOWED_RUNTIMES.includes(staticConfig.runtime)) {
throw new Error(
`Unsupported "runtime" property in \`config\`: ${JSON.stringify(
staticConfig.runtime
)} (must be one of: ${JSON.stringify(ALLOWED_RUNTIMES)})`
);
}
if (staticConfig.runtime === 'experimental-edge') {
const name = config.zeroConfig
? handler.substring(0, handler.length - 3)
: handler;
output = new EdgeFunction({
entrypoint: handler,
files: preparedFiles,
// TODO: remove - these two properties should not be required
name,
deploymentTarget: 'v8-worker',
});
}
}
if (!output) {
// "nodejs" runtime is the default
const shouldAddHelpers = !(
config.helpers === false || process.env.NODEJS_HELPERS === '0'
);
output = new NodejsLambda({
files: preparedFiles,
handler,
runtime: nodeVersion.runtime,
shouldAddHelpers,
shouldAddSourcemapSupport,
awsLambdaHandler,
});
}
return { output };
}; };
export const prepareCache: PrepareCache = ({ repoRootPath, workPath }) => { export const prepareCache: PrepareCache = ({ repoRootPath, workPath }) => {

View File

@@ -450,7 +450,6 @@ export function fixConfig(
delete config.compilerOptions.tsBuildInfoFile; delete config.compilerOptions.tsBuildInfoFile;
delete config.compilerOptions.incremental; delete config.compilerOptions.incremental;
// Target esnext output by default (instead of ES3).
// This will prevent TS from polyfill/downlevel emit. // This will prevent TS from polyfill/downlevel emit.
if (config.compilerOptions.target === undefined) { if (config.compilerOptions.target === undefined) {
// See https://github.com/tsconfig/bases/tree/main/bases // See https://github.com/tsconfig/bases/tree/main/bases

View File

@@ -0,0 +1,9 @@
import { join } from 'path';
import { readFileSync } from 'fs';
const filePath = join(__dirname, 'test.d.ts');
const fileContent = readFileSync(filePath, 'utf8');
export default function handler(_req: any, res: any) {
res.end(fileContent);
}

View File

@@ -0,0 +1,6 @@
{
"private": true,
"dependencies": {
"@types/node": "^16.0.0"
}
}

View File

@@ -0,0 +1,3 @@
declare namespace Test {
// example .d.ts file
}

View File

@@ -0,0 +1,10 @@
{
"version": 2,
"builds": [{ "src": "index.ts", "use": "@vercel/node" }],
"probes": [
{
"path": "/",
"mustContain": "declare namespace Test"
}
]
}

View File

@@ -0,0 +1,7 @@
export const config = {
runtime: 'experimental-edge',
};
export default req => {
return new Response(`RANDOMNESS_PLACEHOLDER:edge`);
};

View File

@@ -0,0 +1,9 @@
{
"builds": [{ "src": "api/**/*.js", "use": "@vercel/node" }],
"probes": [
{
"path": "/api/edge.js",
"mustContain": "RANDOMNESS_PLACEHOLDER:edge"
}
]
}

View File

@@ -2,8 +2,8 @@
"compilerOptions": { "compilerOptions": {
"strict": true, "strict": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["esnext"], "lib": ["ES2020"],
"target": "es2018", "target": "ES2020",
"module": "commonjs", "module": "commonjs",
"outDir": "dist", "outDir": "dist",
"sourceMap": false, "sourceMap": false,

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