Compare commits

...

8 Commits

Author SHA1 Message Date
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
64 changed files with 1139 additions and 123 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "4.0.0",
"version": "4.0.1-canary.0",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -23,7 +23,7 @@
"@types/cross-spawn": "6.0.0",
"@types/end-of-stream": "^1.4.0",
"@types/fs-extra": "9.0.13",
"@types/glob": "^7.1.1",
"@types/glob": "7.2.0",
"@types/jest": "27.4.1",
"@types/js-yaml": "3.12.1",
"@types/ms": "0.7.31",
@@ -40,7 +40,7 @@
"cross-spawn": "6.0.5",
"end-of-stream": "1.4.1",
"fs-extra": "10.0.0",
"glob": "7.1.3",
"glob": "8.0.3",
"into-stream": "5.0.0",
"js-yaml": "3.13.1",
"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

@@ -117,5 +117,14 @@ export const isStaticRuntime = (name?: string): boolean => {
};
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';

View File

@@ -428,7 +428,7 @@ export interface BuildResultV2Typical {
export type BuildResultV2 = BuildResultV2Typical | BuildResultBuildOutput;
export interface BuildResultV3 {
output: Lambda;
output: Lambda | EdgeFunction;
}
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(
stat => stat.type === 'dir'
);
return (
await Promise.all(
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:
once: ^1.4.0
importers:
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:
/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:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
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:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
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,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

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "25.0.1",
"version": "25.0.2-canary.0",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -42,15 +42,15 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "4.0.0",
"@vercel/go": "2.0.0",
"@vercel/next": "3.0.0",
"@vercel/node": "2.0.1",
"@vercel/python": "3.0.0",
"@vercel/redwood": "1.0.0",
"@vercel/remix": "1.0.0",
"@vercel/ruby": "1.3.8",
"@vercel/static-build": "1.0.0",
"@vercel/build-utils": "4.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",
"update-notifier": "5.1.0"
},
"devDependencies": {
@@ -94,7 +94,7 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.0.0",
"@vercel/client": "12.0.1-canary.0",
"@vercel/frameworks": "1.0.1",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",

View File

@@ -4,6 +4,7 @@ import dotenv from 'dotenv';
import { join, relative } from 'path';
import {
detectBuilders,
normalizePath,
Files,
FileFsRef,
PackageJson,
@@ -178,7 +179,7 @@ export default async function main(client: Client): Promise<number> {
// Get a list of source files
const files = (await getFiles(workPath, client)).map(f =>
relative(workPath, f)
normalizePath(relative(workPath, f))
);
const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} });
@@ -266,19 +267,20 @@ export default async function main(client: Client): Promise<number> {
}
// 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();
// Create fresh new output directory
await fs.mkdirp(OUTPUT_DIR);
await fs.mkdirp(outputDir);
const ops: Promise<Error | void>[] = [];
// Write the `detectedBuilders` result to output dir
ops.push(
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.',
target,
@@ -356,6 +358,7 @@ export default async function main(client: Client): Promise<number> {
// Start flushing the file outputs to the filesystem asynchronously
ops.push(
writeBuildResult(
outputDir,
buildResult,
build,
builder,
@@ -387,7 +390,7 @@ export default async function main(client: Client): Promise<number> {
if (hadError) return 1;
// 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
const existingConfig = await readJSONFile<any>(configPath);
if (existingConfig instanceof CantParseJSONFile) {
@@ -445,7 +448,7 @@ export default async function main(client: Client): Promise<number> {
wildcard: mergedWildcard,
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(
`${prependEmoji(

View File

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

View File

@@ -1,5 +1,4 @@
import { resolve } from 'path';
import _glob, { IOptions as GlobOptions } from 'glob';
import fs from 'fs-extra';
import { getVercelIgnore } from '@vercel/client';
import uniqueStrings from './unique-strings';
@@ -21,14 +20,6 @@ function flatten(
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,
* and maintains absolutes as such.
@@ -65,15 +56,16 @@ interface StaticFilesOptions {
export async function staticFiles(
path: string,
{ output, src }: StaticFilesOptions
) {
): Promise<string[]> {
const { debug, time } = output;
let files: string[] = [];
// The package.json `files` whitelist still
// honors ignores: https://docs.npmjs.com/files/package.json#files
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
const { ig } = await getVercelIgnore(path);
@@ -104,7 +96,7 @@ export async function staticFiles(
// Locate files
files = await time(
`Locating files ${path}`,
explode(search, {
explode([search], {
accepts,
output,
})
@@ -164,7 +156,7 @@ async function explode(
const all = await fs.readdir(file);
/* eslint-disable no-use-before-define */
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 */
}
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.
* 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
* projects
* @param {Array} arr Input array that potentially has duplicates
* @returns {Array} An array of the unique values in `arr`
*/
export default (arr: string[]) => {
export default (arr: string[]): string[] => {
const len = arr.length;
const res: string[] = [];
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

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

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "3.0.0",
"version": "3.0.1-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -45,7 +45,7 @@
"@types/semver": "6.0.0",
"@types/text-table": "0.2.1",
"@types/webpack-sources": "3.2.0",
"@vercel/build-utils": "4.0.0",
"@vercel/build-utils": "4.0.1-canary.0",
"@vercel/nft": "0.19.1",
"@vercel/routing-utils": "1.13.4",
"async-sema": "3.0.1",

View File

@@ -54,6 +54,7 @@ import prettyBytes from 'pretty-bytes';
// related PR: https://github.com/vercel/next.js/pull/30046
const CORRECT_NOT_FOUND_ROUTES_VERSION = 'v12.0.1';
const CORRECT_MIDDLEWARE_ORDER_VERSION = 'v12.1.7-canary.29';
export async function serverBuild({
dynamicPages,
@@ -131,6 +132,10 @@ export async function serverBuild({
nextVersion,
CORRECT_NOT_FOUND_ROUTES_VERSION
);
const isCorrectMiddlewareOrder = semver.gte(
nextVersion,
CORRECT_MIDDLEWARE_ORDER_VERSION
);
let hasStatic500 = !!staticPages[path.join(entryDirectory, '500')];
if (lambdaPageKeys.length === 0) {
@@ -788,6 +793,7 @@ export async function serverBuild({
entryPath,
outputDirectory,
routesManifest,
isCorrectMiddlewareOrder,
});
const dynamicRoutes = await getDynamicRoutes(
@@ -1025,6 +1031,10 @@ export async function serverBuild({
...redirects,
// middleware comes directly after redirects but before
// beforeFiles rewrites as middleware is not a "file" route
...(isCorrectMiddlewareOrder ? middleware.staticRoutes : []),
...beforeFilesRewrites,
// 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/`
// folder

View File

@@ -2142,10 +2142,12 @@ export async function getMiddlewareBundle({
entryPath,
outputDirectory,
routesManifest,
isCorrectMiddlewareOrder,
}: {
entryPath: string;
outputDirectory: string;
routesManifest: RoutesManifest;
isCorrectMiddlewareOrder: boolean;
}) {
const middlewareManifest = await getMiddlewareManifest(
entryPath,
@@ -2247,13 +2249,16 @@ export async function getMiddlewareBundle({
const edgeFile = worker.edgeFunction.name;
worker.edgeFunction.name = edgeFile.replace(/^pages\//, '');
source.edgeFunctions[edgeFile] = worker.edgeFunction;
const route = {
const route: Source = {
continue: true,
override: true,
middlewarePath: edgeFile,
src: worker.routeSrc,
};
if (isCorrectMiddlewareOrder) {
route.override = true;
}
if (routesManifest.version > 3 && isDynamicRoute(worker.page)) {
source.dynamicRouteMap.set(worker.page, route);
} else {

View File

@@ -6,7 +6,7 @@
"path": "/redirect-me",
"status": 307,
"responseHeaders": {
"Location": "/from-next-config/"
"Location": "/from-middleware/"
},
"fetchOptions": {
"redirect": "manual"

View File

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

View File

@@ -11,6 +11,23 @@
"fetchOptions": {
"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(redirectIndex).toBeLessThan(middlewareIndex);
expect(redirectIndex).toBeLessThan(beforeFilesIndex);
expect(beforeFilesIndex).toBeLessThan(middlewareIndex);
expect(middlewareIndex).toBeLessThan(beforeFilesIndex);
expect(middlewareIndex).toBeLessThan(handleFileSystemIndex);
});

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "2.0.1",
"version": "2.0.2-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -45,9 +45,10 @@
"@types/etag": "1.8.0",
"@types/jest": "27.4.1",
"@types/test-listen": "1.1.0",
"@vercel/build-utils": "4.0.0",
"@vercel/build-utils": "4.0.1-canary.0",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.19.1",
"@vercel/static-config": "2.0.1-canary.0",
"content-type": "1.0.4",
"cookie": "0.4.0",
"etag": "1.8.1",

View File

@@ -16,18 +16,15 @@ import {
sep,
parse as parsePath,
} from 'path';
import { Project } from 'ts-morph';
import once from '@tootallnate/once';
import { nodeFileTrace } from '@vercel/nft';
import {
File,
Files,
Meta,
Config,
StartDevServerOptions,
glob,
download,
FileBlob,
FileFsRef,
EdgeFunction,
NodejsLambda,
runNpmInstall,
runPackageJsonScript,
@@ -37,11 +34,20 @@ import {
debug,
isSymbolicLink,
walkParentDirs,
} from '@vercel/build-utils';
import type {
File,
Files,
Meta,
Config,
StartDevServerOptions,
BuildV3,
PrepareCache,
StartDevServer,
NodeVersion,
BuildResultV3,
} from '@vercel/build-utils';
import { getConfig } from '@vercel/static-config';
import { Register, register } from './typescript';
@@ -69,6 +75,8 @@ function isPortInfo(v: any): v is PortInfo {
return v && typeof v.port === 'number';
}
const ALLOWED_RUNTIMES = ['nodejs', 'experimental-edge'];
const require_ = eval('require');
const tscPath = resolve(dirname(require_.resolve('typescript')), '../bin/tsc');
@@ -365,20 +373,52 @@ export const build: BuildV3 = async ({
);
debug(`Trace complete [${Date.now() - traceTime}ms]`);
const shouldAddHelpers = !(
config.helpers === false || process.env.NODEJS_HELPERS === '0'
);
const project = new Project();
const staticConfig = getConfig(project, entrypointPath);
const lambda = new NodejsLambda({
files: preparedFiles,
handler: renameTStoJS(relative(baseDir, entrypointPath)),
runtime: nodeVersion.runtime,
shouldAddHelpers,
shouldAddSourcemapSupport,
awsLambdaHandler,
});
let output: BuildResultV3['output'] | undefined;
const handler = renameTStoJS(relative(baseDir, entrypointPath));
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 }) => {

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

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

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/redwood",
"version": "1.0.0",
"version": "1.0.1-canary.0",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs",
@@ -28,6 +28,6 @@
"@types/aws-lambda": "8.10.19",
"@types/node": "*",
"@types/semver": "6.0.0",
"@vercel/build-utils": "4.0.0"
"@vercel/build-utils": "4.0.1-canary.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/remix",
"version": "1.0.0",
"version": "1.0.1-canary.0",
"license": "MIT",
"main": "./dist/index.js",
"homepage": "https://vercel.com/docs",
@@ -27,7 +27,7 @@
"devDependencies": {
"@types/jest": "27.5.1",
"@types/node": "*",
"@vercel/build-utils": "4.0.0",
"@vercel/build-utils": "4.0.1-canary.0",
"typescript": "4.6.4"
}
}

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/static-build",
"version": "1.0.0",
"version": "1.0.1-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step",
@@ -37,7 +37,7 @@
"@types/ms": "0.7.31",
"@types/node-fetch": "2.5.4",
"@types/promise-timeout": "1.3.0",
"@vercel/build-utils": "4.0.0",
"@vercel/build-utils": "4.0.1-canary.0",
"@vercel/frameworks": "1.0.1",
"@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "1.13.4",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/static-config",
"version": "2.0.0",
"version": "2.0.1-canary.0",
"license": "MIT",
"main": "./dist/index",
"repository": {

View File

@@ -13,7 +13,7 @@ import { validate } from './validation';
export const BaseFunctionConfigSchema = {
type: 'object',
properties: {
use: { type: 'string' },
runtime: { type: 'string' },
memory: { type: 'number' },
maxDuration: { type: 'number' },
regions: {

View File

@@ -2,7 +2,7 @@ import ms from 'https://denopkg.com/TooTallNate/ms';
import { readerFromStreamReader } from 'https://deno.land/std@0.107.0/io/streams.ts';
export const config = {
use: 'deno',
runtime: 'deno',
location: 'https://example.com/page',
};

View File

@@ -1,3 +1,3 @@
export const config = {
use: 0,
runtime: 0,
};

View File

@@ -1,7 +1,7 @@
import fs from 'fs';
export const config = {
use: 'node',
runtime: 'nodejs',
memory: 1024,
};

View File

@@ -10,7 +10,7 @@ describe('getConfig()', () => {
expect(config).toMatchInlineSnapshot(`
Object {
"memory": 1024,
"use": "node",
"runtime": "nodejs",
}
`);
});
@@ -27,7 +27,7 @@ describe('getConfig()', () => {
expect(config).toMatchInlineSnapshot(`
Object {
"location": "https://example.com/page",
"use": "deno",
"runtime": "deno",
}
`);
});

View File

@@ -57,7 +57,7 @@ describe('getConfig for swc', () => {
expect(config).toMatchInlineSnapshot(`
Object {
"memory": 1024,
"use": "node",
"runtime": "nodejs",
}
`);
});
@@ -73,7 +73,7 @@ describe('getConfig for swc', () => {
expect(config).toMatchInlineSnapshot(`
Object {
"location": "https://example.com/page",
"use": "deno",
"runtime": "deno",
}
`);
});

View File

@@ -2426,6 +2426,14 @@
"@types/minimatch" "*"
"@types/node" "*"
"@types/glob@7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==
dependencies:
"@types/minimatch" "*"
"@types/node" "*"
"@types/glob@^7.1.1":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987"
@@ -6641,17 +6649,16 @@ glob@7.1.2:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
glob@8.0.3:
version "8.0.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e"
integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
minimatch "^5.0.1"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4:
version "7.1.6"
@@ -9138,6 +9145,13 @@ minimatch@5.0.1:
dependencies:
brace-expansion "^2.0.1"
minimatch@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7"
integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==
dependencies:
brace-expansion "^2.0.1"
minimist-options@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954"