Compare commits

...

18 Commits

Author SHA1 Message Date
Steven
5e66d4b2cc Publish Stable
- @vercel/build-utils@3.0.1
 - vercel@24.2.2
 - @vercel/client@11.0.2
 - @vercel/go@1.4.2
 - @vercel/node@1.15.2
 - @vercel/python@2.3.2
 - @vercel/redwood@0.8.2
 - @vercel/ruby@1.3.5
 - @vercel/static-build@0.25.0
2022-05-12 17:19:06 -04:00
Ethan Arrowood
44d7473e7c Publish Canary
- @vercel/redwood@0.8.2-canary.2
 - @vercel/static-build@0.24.2-canary.2
2022-05-12 13:48:21 -06:00
Ethan Arrowood
fddec1286c [redwood][static-build] move path logic up so both commands get pnpm7 (#7792)
move path logic up so both commands get pnpm7
2022-05-12 13:47:09 -06:00
Ethan Arrowood
6e5e700e8d Publish Canary
- vercel@24.2.2-canary.2
 - @vercel/node@1.15.2-canary.1
 - @vercel/redwood@0.8.2-canary.1
 - @vercel/static-build@0.24.2-canary.1
2022-05-12 09:04:20 -06:00
Ryan Carniato
b6e8609b83 [examples] Update SolidStart to Build Output API v3 (#7790) 2022-05-11 23:12:53 -07:00
Ethan Arrowood
78b7bd5ec8 [redwood][static-build] add pnpm7 detection logic to builders (#7787)
add pnpm7 detection logic to builders
2022-05-11 19:37:01 -04:00
Steven
4104a45c2d [tests] Fix node tests (#7786)
Since we released Node.js 16, these tests now resolve `engines` differently
2022-05-11 23:36:18 +00:00
Ethan Arrowood
4c20218e05 Publish Canary
- @vercel/build-utils@3.0.1-canary.1
 - vercel@24.2.2-canary.1
 - @vercel/client@11.0.2-canary.0
 - @vercel/go@1.4.2-canary.0
 - @vercel/node@1.15.2-canary.0
 - @vercel/python@2.3.2-canary.0
 - @vercel/redwood@0.8.2-canary.0
 - @vercel/ruby@1.3.5-canary.0
 - @vercel/static-build@0.24.2-canary.0
2022-05-11 15:47:48 -06:00
Ethan Arrowood
02a0004719 [build-utils] Fix pnpm 7 path setting (#7785)
### Related Issues

Fixes pnpm 7 support. Now uses a yarn installed version and drops an unnecessary check for node version.

### 📋 Checklist

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

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-05-11 21:37:44 +00:00
Steven
123bffb776 [examples] Pin Ember to 14.x (#7782) 2022-05-10 14:34:46 -04:00
Steven
074535f27c [build-utils] Downgrade remix and solidstart to Node.js 14 (fsapi) (#7781)
These frameworks (remix and solidstart) use the legacy file system api (v2) so they can't support Node.js 16.x so we change engines to pin to Node.js 14.x

I also added the missing `ENABLE_FILE_SYSTEM_API=1` env var to solidstart to match remix.

https://vercel.com/docs/file-system-api/v2
2022-05-10 13:20:07 -04:00
Sean Massa
05243fb6e9 [static-build] Support subset of Build Output API v2 (#7690) 2022-05-09 14:01:26 -07:00
Sean Massa
097725580c [tests] skip flakey test (#7777)
Skips two flakey tests on Mac OS only. The error often looks like:

<img width="1453" alt="Screen Shot 2022-05-09 at 2 26 52 PM" src="https://user-images.githubusercontent.com/41545/167483088-49498f69-5470-4c1a-98f5-96ca811b838b.png">

Created internal tracking card to dig deeper. Skipping this for now will unclog other work.
2022-05-09 21:00:49 +00:00
Andrew Gadzik
4b09c89e7d [build-utils] Fix version mismatch (#7776)
Fixes 3a1eede63b
2022-05-09 14:51:04 -04:00
agadzik
3a1eede63b Publish Canary
- @vercel/build-utils@3.0.1-canary.0
 - vercel@24.2.2-canary.0
2022-05-09 13:51:37 -04:00
agadzik
9cee0dd5d7 BREAKING CHANGE: updating build-utils to version 3 for DetectorFilesystem changes 2022-05-09 13:47:11 -04:00
John Pham
b801c6e593 [cli] Track source of getting decrypted environment variables (#7754)
* Track source of getting decrypted environment variables

* Add source as a query param

* Revert lock file changes

* Change source from strings to type

* Differential between pull and env pull
2022-05-09 10:24:43 -07:00
Andrew Gadzik
505050b923 [build-utils] Add readdir and chdir functions to DetectorFilesystem (#7751)
In order to support various `fs` operations for monorepo detection, the team needs to add two new abstract functions to `DetectorFilesystem` in order to traverse down into children directories

* readdir
* chdir

```ts
interface Stat {
  name: string
  path: string
  type: "file" | "dir"
}

export abstract class DetectorFilesystem {
  ...
  /**
   * Returns a list of Stat objects from the current working directory.
   * 
   * @example
   * 
   * const url = "https://github.com/vercel/front"
   * const fs = new GitDetectorFilesystem(...) // based on url
   *
   * // calls "https://api.github.com/repos/vercel/front/contents" behind the scenes
   * await fs.readdir() => [
   *    { name: "docs", path: "docs", type: "dir" },
   *    { name: "front", path: "front", type: "dir" },
   *    ...,
   *    { name: "package.json", path: "package.json", type: "file" },
   * ]
   */
   protected abstract _readdir(name: string): Promise<Stat[]>;

 /**
   * Changes the current directory to the specified path and returns a new instance of DetectorFilesystem.
   * 
   * @example
   * 
   * my-repo
   * |-- backend
   * |    |-- api-1
   * |    |-- api-2
   * |    |-- package.json // workspaces: ["api-1", "api-2"]
   * |    |-- yarn.lock
   * |-- frontend
   * |    |-- nextjs-app
   * |    |-- gatsby-app
   * |    |-- package.json
   * |    |-- pnpm-workspaces.yaml // packages: ["nextjs-app", "gatsby-app"]
   * 
   * const fs = new (...) // based on "my-repo" as the root
   * const backendFs = fs.chdir("backend")
   * const frontendFs = fs.chdir("frontend")
   * 
   * const backendWorkspaceManager = detectFramework({ fs: backendFs, frameworkList: workspaceManagers }) // "yarn"
   * const frontendWorkspaceManager = detectFramework({ fs: frontendFs, frameworkList: workspaceManagers }) // "pnpm"
   */
   protected abstract _chdir(name: string): DetectorFilesystem
   ...
}
```

### Related Issues

> Related to https://github.com/vercel/vercel/issues/7750

### 📋 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-05-09 17:12:30 +00:00
80 changed files with 1915 additions and 764 deletions

View File

@@ -48,6 +48,6 @@
"qunit-dom": "^0.8.4"
},
"engines": {
"node": "8.* || >= 10.*"
"node": "14.x"
}
}

View File

@@ -21,7 +21,7 @@
"typescript": "^4.1.2"
},
"engines": {
"node": ">=14"
"node": "14.x"
}
},
"node_modules/@babel/code-frame": {

View File

@@ -23,7 +23,7 @@
"typescript": "^4.1.2"
},
"engines": {
"node": ">=14"
"node": "14.x"
},
"sideEffects": false
}

View File

@@ -2,6 +2,7 @@ dist
worker
.solid
.vercel
.output
# dependencies
/node_modules

View File

@@ -1,9 +0,0 @@
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "solid-js",
"paths": {
"~/*": ["./src/*"]
}
}
}

View File

@@ -7,14 +7,14 @@
"type": "module",
"private": true,
"devDependencies": {
"solid-app-router": "^0.1.14",
"solid-js": "^1.2.6",
"solid-meta": "^0.27.2",
"solid-app-router": "^0.3.2",
"solid-js": "^1.3.15",
"solid-meta": "^0.27.3",
"solid-start": "next",
"solid-start-vercel": "next",
"vite": "^2.7.1"
"vite": "^2.9.9"
},
"engines": {
"node": ">=14"
"node": "16.x"
}
}

View File

@@ -0,0 +1,4 @@
import { hydrate } from "solid-js/web";
import { StartClient } from "solid-start/entry-client";
hydrate(() => <StartClient />, document);

View File

@@ -0,0 +1,7 @@
import { StartServer, createHandler, renderAsync } from "solid-start/entry-server";
import { inlineServerModules } from "solid-start/server";
export default createHandler(
inlineServerModules,
renderAsync((context) => <StartServer context={context} />)
);

View File

@@ -1,21 +0,0 @@
// @refresh reload
import { Links, Meta, Outlet, Scripts } from "solid-start/components";
export default function Root({ Start }) {
return (
<Start>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
</Start>
);
}

View File

@@ -0,0 +1,25 @@
// @refresh reload
import { Links, Meta, Routes, Scripts } from "solid-start/root";
import { ErrorBoundary } from "solid-start/error-boundary";
import { Suspense } from "solid-js";
export default function Root() {
return (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<ErrorBoundary>
<Suspense>
<Routes />
</Suspense>
</ErrorBoundary>
<Scripts />
</body>
</html>
);
}

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"jsxImportSource": "solid-js",
"jsx": "preserve",
"types": ["vite/client"],
"baseUrl": "./",
"paths": {
"~/*": ["./src/*"]
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,3 +1,8 @@
export interface Stat {
name: string;
path: string;
type: 'file' | 'dir';
}
/**
* `DetectorFilesystem` is an abstract class that represents a virtual filesystem
* to perform read-only operations on in order to detect which framework is being
@@ -27,15 +32,19 @@ export abstract class DetectorFilesystem {
protected abstract _hasPath(name: string): Promise<boolean>;
protected abstract _readFile(name: string): Promise<Buffer>;
protected abstract _isFile(name: string): Promise<boolean>;
protected abstract _readdir(name: string): Promise<Stat[]>;
protected abstract _chdir(name: string): DetectorFilesystem;
private pathCache: Map<string, Promise<boolean>>;
private fileCache: Map<string, Promise<boolean>>;
private readFileCache: Map<string, Promise<Buffer>>;
private readdirCache: Map<string, Promise<Stat[]>>;
constructor() {
this.pathCache = new Map();
this.fileCache = new Map();
this.readFileCache = new Map();
this.readdirCache = new Map();
}
public hasPath = async (path: string): Promise<boolean> => {
@@ -64,4 +73,23 @@ export abstract class DetectorFilesystem {
}
return p;
};
/**
* Returns a list of Stat objects from the current working directory.
*/
public readdir = async (name: string): Promise<Stat[]> => {
let p = this.readdirCache.get(name);
if (!p) {
p = this._readdir(name);
this.readdirCache.set(name, p);
}
return p;
};
/**
* Changes the current directory to the specified path and returns a new instance of DetectorFilesystem.
*/
public chdir = (name: string): DetectorFilesystem => {
return this._chdir(name);
};
}

View File

@@ -437,13 +437,9 @@ export function getEnvForPackageManager({
console.log('Detected `package-lock.json` generated by npm 7...');
}
} else if (cliType === 'pnpm') {
if (
typeof lockfileVersion === 'number' &&
lockfileVersion === 5.4 &&
(nodeVersion?.major || 0) > 12
) {
if (typeof lockfileVersion === 'number' && lockfileVersion === 5.4) {
// Ensure that pnpm 7 is at the beginning of the `$PATH`
newEnv.PATH = `/pnpm7/pnpm:${env.PATH}`;
newEnv.PATH = `/pnpm7/node_modules/.bin:${env.PATH}`;
console.log('Detected `pnpm-lock.yaml` generated by pnpm 7...');
}
} else {

View File

@@ -4,8 +4,7 @@
"probes": [
{
"path": "/",
"mustContain": "pnpm version: 7",
"logMustContain": "pnpm run build"
"mustContain": "pnpm version: 7"
}
]
}

View File

@@ -1,21 +1,32 @@
import path from 'path';
import frameworkList from '@vercel/frameworks';
import { detectFramework, DetectorFilesystem } from '../src';
import { Stat } from '../src/detectors/filesystem';
const posixPath = path.posix;
class VirtualFilesystem extends DetectorFilesystem {
private files: Map<string, Buffer>;
private cwd: string;
constructor(files: { [key: string]: string | Buffer }) {
constructor(files: { [key: string]: string | Buffer }, cwd = '') {
super();
this.files = new Map();
this.cwd = cwd;
Object.entries(files).map(([key, value]) => {
const buffer = typeof value === 'string' ? Buffer.from(value) : value;
this.files.set(key, buffer);
});
}
async _hasPath(path: string): Promise<boolean> {
private _normalizePath(rawPath: string): string {
return posixPath.normalize(rawPath);
}
async _hasPath(name: string): Promise<boolean> {
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
for (const file of this.files.keys()) {
if (file.startsWith(path)) {
if (file.startsWith(basePath)) {
return true;
}
}
@@ -24,11 +35,13 @@ class VirtualFilesystem extends DetectorFilesystem {
}
async _isFile(name: string): Promise<boolean> {
return this.files.has(name);
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
return this.files.has(basePath);
}
async _readFile(name: string): Promise<Buffer> {
const file = this.files.get(name);
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
const file = this.files.get(basePath);
if (file === undefined) {
throw new Error('File does not exist');
@@ -40,8 +53,183 @@ class VirtualFilesystem extends DetectorFilesystem {
return file;
}
/**
* An example of how to implement readdir for a virtual filesystem.
*/
async _readdir(name = '/'): Promise<Stat[]> {
return (
[...this.files.keys()]
.map(filepath => {
const basePath = this._normalizePath(
posixPath.join(this.cwd, name === '/' ? '' : name)
);
const fileDirectoryName = posixPath.dirname(filepath);
if (fileDirectoryName === basePath) {
return {
name: posixPath.basename(filepath),
path: filepath.replace(
this.cwd === '' ? this.cwd : `${this.cwd}/`,
''
),
type: 'file',
};
}
if (
(basePath === '.' && fileDirectoryName !== '.') ||
fileDirectoryName.startsWith(basePath)
) {
let subDirectoryName = fileDirectoryName.replace(
basePath === '.' ? '' : `${basePath}/`,
''
);
if (subDirectoryName.includes('/')) {
subDirectoryName = subDirectoryName.split('/')[0];
}
return {
name: subDirectoryName,
path:
name === '/'
? subDirectoryName
: this._normalizePath(posixPath.join(name, subDirectoryName)),
type: 'dir',
};
}
return null;
})
// remove nulls
.filter((stat): stat is Stat => stat !== null)
// remove duplicates
.filter(
(stat, index, self) =>
index ===
self.findIndex(s => s.name === stat.name && s.path === stat.path)
)
);
}
/**
* An example of how to implement chdir for a virtual filesystem.
*/
_chdir(name: string): DetectorFilesystem {
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
const files = Object.fromEntries(
[...this.files.keys()].map(key => [key, this.files.get(key) ?? ''])
);
return new VirtualFilesystem(files, basePath);
}
}
describe('DetectorFilesystem', () => {
it('should return the directory contents relative to the cwd', async () => {
const files = {
'package.json': '{}',
'packages/app1/package.json': '{}',
'packages/app2/package.json': '{}',
};
const fs = new VirtualFilesystem(files);
expect(await fs.readdir('/')).toEqual([
{ name: 'package.json', path: 'package.json', type: 'file' },
{ name: 'packages', path: 'packages', type: 'dir' },
]);
expect(await fs.readdir('packages')).toEqual([
{ name: 'app1', path: 'packages/app1', type: 'dir' },
{ name: 'app2', path: 'packages/app2', type: 'dir' },
]);
expect(await fs.readdir('./packages')).toEqual([
{ name: 'app1', path: 'packages/app1', type: 'dir' },
{ name: 'app2', path: 'packages/app2', type: 'dir' },
]);
expect(await fs.readdir('packages/app1')).toEqual([
{
name: 'package.json',
path: 'packages/app1/package.json',
type: 'file',
},
]);
});
it('should be able to change directories', async () => {
const nextPackageJson = JSON.stringify({
dependencies: {
next: '9.0.0',
},
});
const gatsbyPackageJson = JSON.stringify({
dependencies: {
gatsby: '1.0.0',
},
});
const files = {
'package.json': '{}',
'packages/app1/package.json': nextPackageJson,
'packages/app2/package.json': gatsbyPackageJson,
};
const fs = new VirtualFilesystem(files);
const packagesFs = fs.chdir('packages');
expect(await packagesFs.readdir('/')).toEqual([
{ name: 'app1', path: 'app1', type: 'dir' },
{ name: 'app2', path: 'app2', type: 'dir' },
]);
expect(await packagesFs.hasPath('app1')).toBe(true);
expect(await packagesFs.hasPath('app3')).toBe(false);
expect(await packagesFs.isFile('app1')).toBe(false);
expect(await packagesFs.isFile('app2')).toBe(false);
expect(await packagesFs.isFile('app1/package.json')).toBe(true);
expect(await packagesFs.isFile('app2/package.json')).toBe(true);
expect(
await (await packagesFs.readFile('app1/package.json')).toString()
).toEqual(nextPackageJson);
expect(
await (await packagesFs.readFile('app2/package.json')).toString()
).toEqual(gatsbyPackageJson);
expect(await detectFramework({ fs: packagesFs, frameworkList })).toBe(null);
const nextAppFs = packagesFs.chdir('app1');
expect(await nextAppFs.readdir('/')).toEqual([
{ name: 'package.json', path: 'package.json', type: 'file' },
]);
expect(await (await nextAppFs.readFile('package.json')).toString()).toEqual(
nextPackageJson
);
expect(await detectFramework({ fs: nextAppFs, frameworkList })).toBe(
'nextjs'
);
const gatsbyAppFs = packagesFs.chdir('./app2');
expect(await gatsbyAppFs.readdir('/')).toEqual([
{ name: 'package.json', path: 'package.json', type: 'file' },
]);
expect(
await (await gatsbyAppFs.readFile('package.json')).toString()
).toEqual(gatsbyPackageJson);
expect(await detectFramework({ fs: gatsbyAppFs, frameworkList })).toBe(
'gatsby'
);
});
describe('#detectFramework', () => {
it('Do not detect anything', async () => {
const fs = new VirtualFilesystem({
@@ -152,3 +340,4 @@ describe('#detectFramework', () => {
expect(await detectFramework({ fs, frameworkList })).toBe('zola');
});
});
});

View File

@@ -85,7 +85,7 @@ describe('Test `getEnvForPackageManager()`', () => {
},
},
{
name: 'should set path if pnpm 7+ is detected and Node version is greater than 12',
name: 'should set path if pnpm 7+ is detected',
args: {
cliType: 'pnpm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
@@ -97,21 +97,7 @@ describe('Test `getEnvForPackageManager()`', () => {
},
want: {
FOO: 'bar',
PATH: '/pnpm7/pnpm:foo',
},
},
{
name: 'should not set path if pnpm 7+ is detected and Node version is less than or equal to 12',
args: {
cliType: 'pnpm',
nodeVersion: { major: 12, range: '12.x', runtime: 'nodejs12.x' },
lockfileVersion: 5.4,
env: {
FOO: 'bar',
},
},
want: {
FOO: 'bar',
PATH: '/pnpm7/node_modules/.bin:foo',
},
},
{

View File

@@ -2,8 +2,9 @@ import { promises } from 'fs';
import path from 'path';
import { DetectorFilesystem } from '../../src';
import { Stat } from '../../src/detectors/filesystem';
const { stat, readFile } = promises;
const { stat, readFile, readdir } = promises;
export class FixtureFilesystem extends DetectorFilesystem {
private rootPath: string;
@@ -32,4 +33,19 @@ export class FixtureFilesystem extends DetectorFilesystem {
const filePath = path.join(this.rootPath, name);
return (await stat(filePath)).isFile();
}
async _readdir(name: string): Promise<Stat[]> {
const dirPath = path.join(this.rootPath, name);
const files = await readdir(dirPath, { withFileTypes: true });
return files.map(file => ({
name: file.name,
type: file.isFile() ? 'file' : 'dir',
path: path.join(name, file.name),
}));
}
_chdir(name: string): DetectorFilesystem {
return new FixtureFilesystem(path.join(this.rootPath, name));
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "24.2.1",
"version": "24.2.2",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -43,11 +43,11 @@
"node": ">= 12"
},
"dependencies": {
"@vercel/build-utils": "2.17.0",
"@vercel/go": "1.4.1",
"@vercel/node": "1.15.1",
"@vercel/python": "2.3.1",
"@vercel/ruby": "1.3.4",
"@vercel/build-utils": "3.0.1",
"@vercel/go": "1.4.2",
"@vercel/node": "1.15.2",
"@vercel/python": "2.3.2",
"@vercel/ruby": "1.3.5",
"update-notifier": "4.1.0"
},
"devDependencies": {
@@ -90,7 +90,7 @@
"@types/update-notifier": "5.1.0",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@vercel/client": "11.0.1",
"@vercel/client": "11.0.2",
"@vercel/frameworks": "0.9.0",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",

View File

@@ -91,7 +91,7 @@ export default async function dev(
}
[{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
getDecryptedEnvRecords(output, client, project.id),
getDecryptedEnvRecords(output, client, project.id, 'vercel-cli:dev'),
project.autoExposeSystemEnvs
? getSystemEnvValues(output, client, project.id)
: { systemEnvValues: [] },

View File

@@ -79,7 +79,12 @@ export default async function add(
}
}
const { envs } = await getEnvRecords(output, client, project.id);
const { envs } = await getEnvRecords(
output,
client,
project.id,
'vercel-cli:env:add'
);
const existing = new Set(
envs.filter(r => r.key === envName).map(r => r.target)
);

View File

@@ -147,7 +147,8 @@ export default async function main(client: Client) {
argv,
args,
output,
cwd
cwd,
'vercel-cli:env:pull'
);
default:
output.error(getInvalidSubcommand(COMMAND_CONFIG));

View File

@@ -48,10 +48,16 @@ export default async function ls(
const lsStamp = stamp();
const { envs } = await getEnvRecords(output, client, project.id, {
const { envs } = await getEnvRecords(
output,
client,
project.id,
'vercel-cli:env:ls',
{
target: envTarget,
gitBranch: envGitBranch,
});
}
);
if (envs.length === 0) {
output.log(

View File

@@ -13,6 +13,7 @@ import { Output } from '../../util/output';
import param from '../../util/output/param';
import stamp from '../../util/output/stamp';
import { getCommandName } from '../../util/pkg-name';
import { EnvRecordsSource } from '../../util/env/get-env-records';
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
@@ -49,7 +50,8 @@ export default async function pull(
opts: Partial<Options>,
args: string[],
output: Output,
cwd: string
cwd: string,
source: Extract<EnvRecordsSource, 'vercel-cli:env:pull' | 'vercel-cli:pull'>
) {
if (args.length > 1) {
output.error(
@@ -90,7 +92,7 @@ export default async function pull(
output.spinner('Downloading');
const [{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
getDecryptedEnvRecords(output, client, project.id, environment),
getDecryptedEnvRecords(output, client, project.id, source, environment),
project.autoExposeSystemEnvs
? getSystemEnvValues(output, client, project.id)
: { systemEnvValues: [] },

View File

@@ -67,10 +67,16 @@ export default async function rm(
return 1;
}
const result = await getEnvRecords(output, client, project.id, {
const result = await getEnvRecords(
output,
client,
project.id,
'vercel-cli:env:rm',
{
target: envTarget,
gitBranch: envGitBranch,
});
}
);
let envs = result.envs.filter(env => env.key === envName);

View File

@@ -131,7 +131,8 @@ async function pullAllEnvFiles(
argv,
[join('.vercel', environmentFile)],
client.output,
cwd
cwd,
'vercel-cli:pull'
);
}

View File

@@ -3,10 +3,20 @@ import Client from '../client';
import { ProjectEnvVariable, ProjectEnvTarget } from '../../types';
import { URLSearchParams } from 'url';
/** The CLI command that was used that needs the environment variables. */
export type EnvRecordsSource =
| 'vercel-cli:env:ls'
| 'vercel-cli:env:add'
| 'vercel-cli:env:rm'
| 'vercel-cli:env:pull'
| 'vercel-cli:dev'
| 'vercel-cli:pull';
export default async function getEnvRecords(
output: Output,
client: Client,
projectId: string,
source: EnvRecordsSource,
{
target,
gitBranch,
@@ -31,6 +41,9 @@ export default async function getEnvRecords(
if (decrypt) {
query.set('decrypt', decrypt.toString());
}
if (source) {
query.set('source', source);
}
const url = `/v8/projects/${projectId}/env?${query}`;

View File

@@ -6,15 +6,16 @@ import {
ProjectEnvVariable,
Secret,
} from '../types';
import getEnvRecords from './env/get-env-records';
import getEnvRecords, { EnvRecordsSource } from './env/get-env-records';
export default async function getDecryptedEnvRecords(
output: Output,
client: Client,
projectId: string,
source: EnvRecordsSource,
target?: ProjectEnvTarget
): Promise<{ envs: ProjectEnvVariable[] }> {
const { envs } = await getEnvRecords(output, client, projectId, {
const { envs } = await getEnvRecords(output, client, projectId, source, {
target: target || ProjectEnvTarget.Development,
decrypt: true,
});

View File

@@ -126,6 +126,12 @@ describe('DevServer', () => {
it(
'should maintain query when builder defines routes',
testFixture('now-dev-next', async server => {
if (process.platform === 'darwin') {
// this test very often fails on Mac OS only due to timeouts
console.log('Skipping test on macOS');
return;
}
const res = await fetch(`${server.address}/something?url-param=a`);
validateResponseHeaders(res);
@@ -171,6 +177,12 @@ describe('DevServer', () => {
it(
'should support default builds and routes',
testFixture('now-dev-default-builds-and-routes', async server => {
if (process.platform === 'darwin') {
// this test very often fails on Mac OS only due to timeouts
console.log('Skipping test on macOS');
return;
}
let podId: string;
let res = await fetch(`${server.address}/`);

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "11.0.1",
"version": "11.0.2",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -41,7 +41,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.17.0",
"@vercel/build-utils": "3.0.1",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "1.15.1",
"version": "1.15.2",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -44,7 +44,7 @@
"@types/etag": "1.8.0",
"@types/jest": "27.4.1",
"@types/test-listen": "1.1.0",
"@vercel/build-utils": "2.17.0",
"@vercel/build-utils": "3.0.1",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.18.1",
"content-type": "1.0.4",

View File

@@ -1,5 +1,5 @@
{
"engines": {
"node": "12.x"
"node": "14.x"
}
}

View File

@@ -1,5 +1,5 @@
{
"engines": {
"node": "12.0.0 - 12.99.99"
"node": "14.0.0 - 14.99.99"
}
}

View File

@@ -7,9 +7,9 @@
}
],
"probes": [
{ "path": "/empty", "mustContain": "RANDOMNESS_PLACEHOLDER:14" },
{ "path": "/greater", "mustContain": "RANDOMNESS_PLACEHOLDER:14" },
{ "path": "/major", "mustContain": "RANDOMNESS_PLACEHOLDER:12" },
{ "path": "/range", "mustContain": "RANDOMNESS_PLACEHOLDER:12" }
{ "path": "/empty", "mustContain": "RANDOMNESS_PLACEHOLDER:16" },
{ "path": "/greater", "mustContain": "RANDOMNESS_PLACEHOLDER:16" },
{ "path": "/major", "mustContain": "RANDOMNESS_PLACEHOLDER:14" },
{ "path": "/range", "mustContain": "RANDOMNESS_PLACEHOLDER:14" }
]
}

View File

@@ -1,6 +1,6 @@
{
"private": true,
"dependencies": {
"sharp": "0.23.0"
"sharp": "0.30.4"
}
}

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/redwood",
"version": "0.8.1",
"version": "0.8.2",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs",
@@ -27,6 +27,6 @@
"@types/aws-lambda": "8.10.19",
"@types/node": "*",
"@types/semver": "6.0.0",
"@vercel/build-utils": "2.17.0"
"@vercel/build-utils": "3.0.1"
}
}

View File

@@ -73,17 +73,12 @@ export const build: BuildV2 = async ({
);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
if (typeof installCommand === 'string') {
if (installCommand.trim()) {
console.log(`Running "install" command: \`${installCommand}\`...`);
if (!spawnOpts.env) {
spawnOpts.env = {};
}
const { cliType, lockfileVersion } = await scanParentDirs(
entrypointFsDirname
);
const env: Record<string, string> = {
YARN_NODE_LINKER: 'node-modules',
...spawnOpts.env,
};
if (cliType === 'npm') {
if (
typeof lockfileVersion === 'number' &&
@@ -91,10 +86,25 @@ export const build: BuildV2 = async ({
(nodeVersion?.major || 0) < 16
) {
// Ensure that npm 7 is at the beginning of the `$PATH`
env.PATH = `/node16/bin-npm7:${env.PATH}`;
spawnOpts.env.PATH = `/node16/bin-npm7:${spawnOpts.env.PATH}`;
console.log('Detected `package-lock.json` generated by npm 7...');
}
} else if (cliType === 'pnpm') {
if (typeof lockfileVersion === 'number' && lockfileVersion === 5.4) {
// Ensure that pnpm 7 is at the beginning of the `$PATH`
spawnOpts.env.PATH = `/pnpm7/node_modules/.bin:${spawnOpts.env.PATH}`;
console.log('Detected `pnpm-lock.yaml` generated by pnpm 7...');
}
}
if (typeof installCommand === 'string') {
if (installCommand.trim()) {
console.log(`Running "install" command: \`${installCommand}\`...`);
const env: Record<string, string> = {
YARN_NODE_LINKER: 'node-modules',
...spawnOpts.env,
};
await execCommand(installCommand, {
...spawnOpts,

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/static-build",
"version": "0.24.1",
"version": "0.25.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step",
@@ -31,14 +31,16 @@
"devDependencies": {
"@types/aws-lambda": "8.10.64",
"@types/cross-spawn": "6.0.0",
"@types/fs-extra": "9.0.13",
"@types/jest": "27.4.1",
"@types/ms": "0.7.31",
"@types/node-fetch": "2.5.4",
"@types/promise-timeout": "1.3.0",
"@vercel/build-utils": "2.17.0",
"@vercel/build-utils": "3.0.1",
"@vercel/frameworks": "0.9.0",
"@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "1.13.2",
"fs-extra": "10.0.0",
"get-port": "5.0.0",
"is-port-reachable": "2.0.1",
"ms": "2.1.2",

View File

@@ -32,6 +32,7 @@ import {
} from '@vercel/build-utils';
import type { Route, Source } from '@vercel/routing-utils';
import * as BuildOutputV1 from './utils/build-output-v1';
import * as BuildOutputV2 from './utils/build-output-v2';
import * as BuildOutputV3 from './utils/build-output-v3';
import * as GatsbyUtils from './utils/gatsby';
import * as NuxtUtils from './utils/nuxt';
@@ -261,6 +262,37 @@ async function fetchBinary(url: string, framework: string, version: string) {
});
}
async function getUpdatedDistPath(
framework: Framework | undefined,
outputDirPrefix: string,
entrypointDir: string,
distPath: string,
config: Config
): Promise<string | undefined> {
if (framework) {
const outputDirName = config.outputDirectory
? config.outputDirectory
: await framework.getOutputDirName(outputDirPrefix);
return path.join(outputDirPrefix, outputDirName);
}
if (!config || !config.distDir) {
// Select either `dist` or `public` as directory
const publicPath = path.join(entrypointDir, 'public');
if (
!existsSync(distPath) &&
existsSync(publicPath) &&
statSync(publicPath).isDirectory()
) {
return publicPath;
}
}
return undefined;
}
export const build: BuildV2 = async ({
files,
entrypoint,
@@ -376,6 +408,10 @@ export const build: BuildV2 = async ({
);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
if (!spawnOpts.env) {
spawnOpts.env = {};
}
/* Don't fail the build on warnings from Create React App.
Node.js will load 'false' as a string, not a boolean, so it's truthy still.
This is to ensure we don't accidentally break other packages that check
@@ -386,12 +422,29 @@ export const build: BuildV2 = async ({
https://github.com/vercel/community/discussions/30
*/
if (framework?.slug === 'create-react-app') {
if (!spawnOpts.env) {
spawnOpts.env = {};
}
spawnOpts.env.CI = 'false';
}
const { cliType, lockfileVersion } = await scanParentDirs(entrypointDir);
if (cliType === 'npm') {
if (
typeof lockfileVersion === 'number' &&
lockfileVersion >= 2 &&
(nodeVersion?.major || 0) < 16
) {
// Ensure that npm 7 is at the beginning of the `$PATH`
spawnOpts.env.PATH = `/node16/bin-npm7:${spawnOpts.env.PATH}`;
console.log('Detected `package-lock.json` generated by npm 7...');
}
} else if (cliType === 'pnpm') {
if (typeof lockfileVersion === 'number' && lockfileVersion === 5.4) {
// Ensure that pnpm 7 is at the beginning of the `$PATH`
spawnOpts.env.PATH = `/pnpm7/node_modules/.bin:${spawnOpts.env.PATH}`;
console.log('Detected `pnpm-lock.yaml` generated by pnpm 7...');
}
}
if (meta.isDev) {
debug('Skipping dependency installation because dev mode is enabled');
} else {
@@ -410,25 +463,11 @@ export const build: BuildV2 = async ({
} else if (typeof installCommand === 'string') {
if (installCommand.trim()) {
console.log(`Running "install" command: \`${installCommand}\`...`);
const { cliType, lockfileVersion } = await scanParentDirs(
entrypointDir
);
const env: Record<string, string> = {
YARN_NODE_LINKER: 'node-modules',
...spawnOpts.env,
};
if (cliType === 'npm') {
if (
typeof lockfileVersion === 'number' &&
lockfileVersion >= 2 &&
(nodeVersion?.major || 0) < 16
) {
// Ensure that npm 7 is at the beginning of the `$PATH`
env.PATH = `/node16/bin-npm7:${env.PATH}`;
console.log('Detected `package-lock.json` generated by npm 7...');
}
}
await execCommand(installCommand, {
...spawnOpts,
@@ -622,48 +661,36 @@ export const build: BuildV2 = async ({
}
const outputDirPrefix = path.join(workPath, path.dirname(entrypoint));
distPath =
(await getUpdatedDistPath(
framework,
outputDirPrefix,
entrypointDir,
distPath,
config
)) || distPath;
// If the Build Command or Framework output files according to the
// Build Output v3 API, then stop processing here in `static-build`
// since the output is already in its final form.
const buildOutputPath = await BuildOutputV3.getBuildOutputDirectory(
const buildOutputPathV3 = await BuildOutputV3.getBuildOutputDirectory(
outputDirPrefix
);
if (buildOutputPath) {
if (buildOutputPathV3) {
// Ensure that `vercel build` is being used for this Deployment
if (!meta.cliVersion) {
let buildCommandName: string;
if (buildCommand) buildCommandName = `"${buildCommand}"`;
else if (framework) buildCommandName = framework.name;
else buildCommandName = 'the "build" script';
throw new Error(
`Detected Build Output v3 from ${buildCommandName}, but this Deployment is not using \`vercel build\`.\nPlease set the \`ENABLE_VC_BUILD=1\` environment variable.`
return BuildOutputV3.createBuildOutput(
meta,
buildCommand,
buildOutputPathV3,
framework
);
}
return {
buildOutputVersion: 3,
buildOutputPath,
};
}
if (framework) {
const outputDirName = config.outputDirectory
? config.outputDirectory
: await framework.getOutputDirName(outputDirPrefix);
distPath = path.join(outputDirPrefix, outputDirName);
} else if (!config || !config.distDir) {
// Select either `dist` or `public` as directory
const publicPath = path.join(entrypointDir, 'public');
if (
!existsSync(distPath) &&
existsSync(publicPath) &&
statSync(publicPath).isDirectory()
) {
distPath = publicPath;
}
const buildOutputPathV2 = await BuildOutputV2.getBuildOutputDirectory(
outputDirPrefix
);
if (buildOutputPathV2) {
return await BuildOutputV2.createBuildOutput(workPath);
}
const extraOutputs = await BuildOutputV1.readBuildOutputDirectory({

View File

@@ -0,0 +1,170 @@
import path from 'path';
import { pathExists, readJson, appendFile } from 'fs-extra';
import { Route } from '@vercel/routing-utils';
import {
Files,
FileFsRef,
debug,
glob,
EdgeFunction,
BuildResultV2,
} from '@vercel/build-utils';
import { isObjectEmpty } from './_shared';
const BUILD_OUTPUT_DIR = '.output';
const BRIDGE_MIDDLEWARE_V2_TO_V3 = `
// appended to convert v2 middleware to v3 middleware
export default async (request) => {
const { response } = await _ENTRIES['middleware_pages/_middleware'].default({ request });
return response;
}
`;
/**
* Returns the path to the Build Output API v2 directory when the
* `config.json` file was created by the framework / build script,
* or `undefined` if the framework did not create the v3 output.
*/
export async function getBuildOutputDirectory(
workingDir: string
): Promise<string | undefined> {
const outputDir = path.join(workingDir, BUILD_OUTPUT_DIR);
const outputPathExists = await pathExists(outputDir);
if (outputPathExists) {
return outputDir;
}
return undefined;
}
/**
* Reads the BUILD_OUTPUT_DIR directory and returns and object
* that should be merged with the build outputs.
*/
export async function readBuildOutputDirectory({
workPath,
}: {
workPath: string;
}) {
// Functions are not supported, but are used to support Middleware
const functions: Record<string, EdgeFunction> = {};
// Routes are not supported, but are used to support Middleware
const routes: Array<Route> = [];
const middleware = await getMiddleware(workPath);
if (middleware) {
routes.push(middleware.route);
functions['middleware'] = new EdgeFunction({
deploymentTarget: 'v8-worker',
entrypoint: '_middleware.js',
files: {
'_middleware.js': middleware.file,
},
name: 'middleware',
});
}
const staticFiles = await readStaticFiles({ workPath });
const outputs = {
staticFiles: isObjectEmpty(staticFiles) ? null : staticFiles,
functions: isObjectEmpty(functions) ? null : functions,
routes: routes.length ? routes : null,
};
if (outputs.functions) {
debug(`Detected Serverless Functions in "${BUILD_OUTPUT_DIR}"`);
}
if (outputs.staticFiles) {
debug(`Detected Static Assets in "${BUILD_OUTPUT_DIR}"`);
}
if (outputs.routes) {
debug(`Detected Routes Configuration in "${BUILD_OUTPUT_DIR}"`);
}
return outputs;
}
async function getMiddleware(
workPath: string
): Promise<{ route: Route; file: FileFsRef } | undefined> {
const manifestPath = path.join(
workPath,
BUILD_OUTPUT_DIR,
'functions-manifest.json'
);
try {
const manifest = await readJson(manifestPath);
if (manifest.pages['_middleware.js'].runtime !== 'web') {
return;
}
} catch (error) {
if (error.code !== 'ENOENT') throw error;
return;
}
const middlewareRelativePath = path.join(
BUILD_OUTPUT_DIR,
'server/pages/_middleware.js'
);
const middlewareAbsoluatePath = path.join(workPath, middlewareRelativePath);
await appendFile(middlewareAbsoluatePath, BRIDGE_MIDDLEWARE_V2_TO_V3);
const route = {
src: '/(.*)',
middlewarePath: 'middleware',
continue: true,
};
return {
route,
file: new FileFsRef({
fsPath: middlewareRelativePath,
}),
};
}
async function readStaticFiles({
workPath,
}: {
workPath: string;
}): Promise<Files> {
const staticFilePath = path.join(workPath, BUILD_OUTPUT_DIR, 'static');
const staticFiles = await glob('**', {
cwd: staticFilePath,
});
return staticFiles;
}
export async function createBuildOutput(
workPath: string
): Promise<BuildResultV2> {
let output: Files = {};
const routes: Route[] = [];
const extraOutputs = await readBuildOutputDirectory({
workPath,
});
if (extraOutputs.routes) {
routes.push(...extraOutputs.routes);
}
if (extraOutputs.staticFiles) {
output = Object.assign(
{},
extraOutputs.staticFiles,
extraOutputs.functions
);
}
return { routes, output };
}

View File

@@ -1,10 +1,12 @@
import { join } from 'path';
import { promises as fs } from 'fs';
import { BuildResultV2, Meta } from '../../../build-utils/dist';
import { Framework } from '../../../frameworks/dist/types';
const BUILD_OUTPUT_DIR = '.vercel/output';
/**
* Returns the path to the Build Output v3 directory when the
* Returns the path to the Build Output API v3 directory when the
* `config.json` file was created by the framework / build script,
* or `undefined` if the framework did not create the v3 output.
*/
@@ -34,3 +36,27 @@ export async function readConfig(
}
return undefined;
}
export function createBuildOutput(
meta: Meta,
buildCommand: string | null,
buildOutputPath: string,
framework?: Framework
): BuildResultV2 {
if (!meta.cliVersion) {
let buildCommandName: string;
if (buildCommand) buildCommandName = `"${buildCommand}"`;
else if (framework) buildCommandName = framework.name;
else buildCommandName = 'the "build" script';
throw new Error(
`Detected Build Output v3 from ${buildCommandName}, but this Deployment is not using \`vercel build\`.\nPlease set the \`ENABLE_VC_BUILD=1\` environment variable.`
);
}
return {
buildOutputVersion: 3,
buildOutputPath,
};
}

View File

@@ -0,0 +1 @@
yarn.lock

View File

@@ -3,5 +3,5 @@ fs.mkdirSync('.vercel/output/static', { recursive: true });
fs.writeFileSync('.vercel/output/config.json', '{}');
fs.writeFileSync(
'.vercel/output/static/index.html',
'<h1>Build Output API</h1>'
'<h1>Build Output API v3</h1>'
);

View File

@@ -0,0 +1 @@
yarn.lock

View File

@@ -0,0 +1,60 @@
const fs = require('fs');
fs.mkdirSync('.output/static', { recursive: true });
fs.mkdirSync('.output/server/pages/api', { recursive: true });
fs.writeFileSync(
'.output/functions-manifest.json',
JSON.stringify(
{
version: 1,
pages: {
'_middleware.js': {
runtime: 'web',
env: [],
files: ['server/pages/_middleware.js'],
name: 'pages/_middleware',
page: '/',
regexp: '^/.*$',
sortingIndex: 1,
},
},
},
null,
2
)
);
fs.writeFileSync('.output/static/index.html', '<h1>Build Output API v2</h1>');
fs.writeFileSync('.output/server/pages/about.html', '<h1>Some Site</h1>');
fs.writeFileSync(
'.output/server/pages/api/user.js',
`export default function handler(request, response) {
response.status(200).json({
body: 'some user info'
});
}`
);
fs.writeFileSync(
'.output/server/pages/_middleware.js',
`
const getResult = (body, options) => ({
promise: Promise.resolve(),
waitUntil: Promise.resolve(),
response: new Response(body, options),
});
_ENTRIES = typeof _ENTRIES === 'undefined' ? {} : _ENTRIES;
_ENTRIES['middleware_pages/_middleware'] = {
default: async function ({ request }) {
return getResult('hi from the edge', {});
},
};
`
);

View File

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

View File

@@ -1,8 +1,41 @@
import path from 'path';
import { remove } from 'fs-extra';
import { build } from '../src';
describe('build()', () => {
it('should detect Builder Output v3', async () => {
describe('Build Output API v2', () => {
it('should detect the output format', async () => {
const workPath = path.join(
__dirname,
'build-fixtures',
'10-build-output-v2'
);
try {
const buildResult = await build({
files: {},
entrypoint: 'package.json',
workPath,
config: {},
meta: {
skipDownload: true,
cliVersion: '0.0.0',
},
});
if ('buildOutputVersion' in buildResult) {
throw new Error('Unexpected `buildOutputVersion` in build result');
}
expect(buildResult.output['index.html']).toBeTruthy();
expect(buildResult.output['middleware']).toBeTruthy();
} finally {
remove(path.join(workPath, '.output'));
}
});
});
describe('Build Output API v3', () => {
it('should detect the output format', async () => {
const workPath = path.join(
__dirname,
'build-fixtures',
@@ -27,7 +60,7 @@ describe('build()', () => {
);
});
it('should throw an Error with Builder Output v3 without `vercel build`', async () => {
it('should throw an Error without `vercel build`', async () => {
let err;
const workPath = path.join(
__dirname,
@@ -52,3 +85,4 @@ describe('build()', () => {
);
});
});
});

View File

@@ -0,0 +1,9 @@
{
"private": "true",
"scripts": {
"build": "mkdir public && pnpm -v && (printf \"pnpm version: \" && pnpm -v) >> public/index.txt"
},
"dependencies": {
"once": "^1.4.0"
}
}

View File

@@ -0,0 +1,19 @@
lockfileVersion: 5.4
specifiers:
once: ^1.4.0
dependencies:
once: 1.4.0
packages:
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: false
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: false

View File

@@ -0,0 +1,15 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/static-build"
}
],
"probes": [
{
"path": "/",
"mustContain": "pnpm version: 7"
}
]
}