mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 21:07:47 +00:00
Compare commits
18 Commits
@vercel/py
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e66d4b2cc | ||
|
|
44d7473e7c | ||
|
|
fddec1286c | ||
|
|
6e5e700e8d | ||
|
|
b6e8609b83 | ||
|
|
78b7bd5ec8 | ||
|
|
4104a45c2d | ||
|
|
4c20218e05 | ||
|
|
02a0004719 | ||
|
|
123bffb776 | ||
|
|
074535f27c | ||
|
|
05243fb6e9 | ||
|
|
097725580c | ||
|
|
4b09c89e7d | ||
|
|
3a1eede63b | ||
|
|
9cee0dd5d7 | ||
|
|
b801c6e593 | ||
|
|
505050b923 |
@@ -48,6 +48,6 @@
|
|||||||
"qunit-dom": "^0.8.4"
|
"qunit-dom": "^0.8.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "8.* || >= 10.*"
|
"node": "14.x"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
examples/remix/package-lock.json
generated
2
examples/remix/package-lock.json
generated
@@ -21,7 +21,7 @@
|
|||||||
"typescript": "^4.1.2"
|
"typescript": "^4.1.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": "14.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"typescript": "^4.1.2"
|
"typescript": "^4.1.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": "14.x"
|
||||||
},
|
},
|
||||||
"sideEffects": false
|
"sideEffects": false
|
||||||
}
|
}
|
||||||
|
|||||||
1
examples/solidstart/.gitignore
vendored
1
examples/solidstart/.gitignore
vendored
@@ -2,6 +2,7 @@ dist
|
|||||||
worker
|
worker
|
||||||
.solid
|
.solid
|
||||||
.vercel
|
.vercel
|
||||||
|
.output
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"jsx": "preserve",
|
|
||||||
"jsxImportSource": "solid-js",
|
|
||||||
"paths": {
|
|
||||||
"~/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,14 +7,14 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"solid-app-router": "^0.1.14",
|
"solid-app-router": "^0.3.2",
|
||||||
"solid-js": "^1.2.6",
|
"solid-js": "^1.3.15",
|
||||||
"solid-meta": "^0.27.2",
|
"solid-meta": "^0.27.3",
|
||||||
"solid-start": "next",
|
"solid-start": "next",
|
||||||
"solid-start-vercel": "next",
|
"solid-start-vercel": "next",
|
||||||
"vite": "^2.7.1"
|
"vite": "^2.9.9"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": "16.x"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
examples/solidstart/src/entry-client.tsx
Normal file
4
examples/solidstart/src/entry-client.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { hydrate } from "solid-js/web";
|
||||||
|
import { StartClient } from "solid-start/entry-client";
|
||||||
|
|
||||||
|
hydrate(() => <StartClient />, document);
|
||||||
7
examples/solidstart/src/entry-server.tsx
Normal file
7
examples/solidstart/src/entry-server.tsx
Normal 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} />)
|
||||||
|
);
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
25
examples/solidstart/src/root.tsx
Normal file
25
examples/solidstart/src/root.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
examples/solidstart/tsconfig.json
Normal file
16
examples/solidstart/tsconfig.json
Normal 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/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
examples/solidstart/vercel.json
Normal file
7
examples/solidstart/vercel.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"env": {
|
||||||
|
"ENABLE_VC_BUILD": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/build-utils",
|
"name": "@vercel/build-utils",
|
||||||
"version": "2.17.0",
|
"version": "3.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.js",
|
"types": "./dist/index.d.js",
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
export interface Stat {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
type: 'file' | 'dir';
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* `DetectorFilesystem` is an abstract class that represents a virtual filesystem
|
* `DetectorFilesystem` is an abstract class that represents a virtual filesystem
|
||||||
* to perform read-only operations on in order to detect which framework is being
|
* 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 _hasPath(name: string): Promise<boolean>;
|
||||||
protected abstract _readFile(name: string): Promise<Buffer>;
|
protected abstract _readFile(name: string): Promise<Buffer>;
|
||||||
protected abstract _isFile(name: string): Promise<boolean>;
|
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 pathCache: Map<string, Promise<boolean>>;
|
||||||
private fileCache: Map<string, Promise<boolean>>;
|
private fileCache: Map<string, Promise<boolean>>;
|
||||||
private readFileCache: Map<string, Promise<Buffer>>;
|
private readFileCache: Map<string, Promise<Buffer>>;
|
||||||
|
private readdirCache: Map<string, Promise<Stat[]>>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.pathCache = new Map();
|
this.pathCache = new Map();
|
||||||
this.fileCache = new Map();
|
this.fileCache = new Map();
|
||||||
this.readFileCache = new Map();
|
this.readFileCache = new Map();
|
||||||
|
this.readdirCache = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasPath = async (path: string): Promise<boolean> => {
|
public hasPath = async (path: string): Promise<boolean> => {
|
||||||
@@ -64,4 +73,23 @@ export abstract class DetectorFilesystem {
|
|||||||
}
|
}
|
||||||
return p;
|
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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -437,13 +437,9 @@ export function getEnvForPackageManager({
|
|||||||
console.log('Detected `package-lock.json` generated by npm 7...');
|
console.log('Detected `package-lock.json` generated by npm 7...');
|
||||||
}
|
}
|
||||||
} else if (cliType === 'pnpm') {
|
} else if (cliType === 'pnpm') {
|
||||||
if (
|
if (typeof lockfileVersion === 'number' && lockfileVersion === 5.4) {
|
||||||
typeof lockfileVersion === 'number' &&
|
|
||||||
lockfileVersion === 5.4 &&
|
|
||||||
(nodeVersion?.major || 0) > 12
|
|
||||||
) {
|
|
||||||
// Ensure that pnpm 7 is at the beginning of the `$PATH`
|
// 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...');
|
console.log('Detected `pnpm-lock.yaml` generated by pnpm 7...');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
"probes": [
|
"probes": [
|
||||||
{
|
{
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"mustContain": "pnpm version: 7",
|
"mustContain": "pnpm version: 7"
|
||||||
"logMustContain": "pnpm run build"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
|
import path from 'path';
|
||||||
import frameworkList from '@vercel/frameworks';
|
import frameworkList from '@vercel/frameworks';
|
||||||
import { detectFramework, DetectorFilesystem } from '../src';
|
import { detectFramework, DetectorFilesystem } from '../src';
|
||||||
|
import { Stat } from '../src/detectors/filesystem';
|
||||||
|
|
||||||
|
const posixPath = path.posix;
|
||||||
|
|
||||||
class VirtualFilesystem extends DetectorFilesystem {
|
class VirtualFilesystem extends DetectorFilesystem {
|
||||||
private files: Map<string, Buffer>;
|
private files: Map<string, Buffer>;
|
||||||
|
private cwd: string;
|
||||||
|
|
||||||
constructor(files: { [key: string]: string | Buffer }) {
|
constructor(files: { [key: string]: string | Buffer }, cwd = '') {
|
||||||
super();
|
super();
|
||||||
this.files = new Map();
|
this.files = new Map();
|
||||||
|
this.cwd = cwd;
|
||||||
Object.entries(files).map(([key, value]) => {
|
Object.entries(files).map(([key, value]) => {
|
||||||
const buffer = typeof value === 'string' ? Buffer.from(value) : value;
|
const buffer = typeof value === 'string' ? Buffer.from(value) : value;
|
||||||
this.files.set(key, buffer);
|
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()) {
|
for (const file of this.files.keys()) {
|
||||||
if (file.startsWith(path)) {
|
if (file.startsWith(basePath)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,11 +35,13 @@ class VirtualFilesystem extends DetectorFilesystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _isFile(name: string): Promise<boolean> {
|
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> {
|
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) {
|
if (file === undefined) {
|
||||||
throw new Error('File does not exist');
|
throw new Error('File does not exist');
|
||||||
@@ -40,115 +53,291 @@ class VirtualFilesystem extends DetectorFilesystem {
|
|||||||
|
|
||||||
return file;
|
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('#detectFramework', () => {
|
describe('DetectorFilesystem', () => {
|
||||||
it('Do not detect anything', async () => {
|
it('should return the directory contents relative to the cwd', async () => {
|
||||||
const fs = new VirtualFilesystem({
|
const files = {
|
||||||
'README.md': '# hi',
|
'package.json': '{}',
|
||||||
'api/cheese.js': 'export default (req, res) => res.end("cheese");',
|
'packages/app1/package.json': '{}',
|
||||||
});
|
'packages/app2/package.json': '{}',
|
||||||
|
};
|
||||||
|
|
||||||
expect(await detectFramework({ fs, frameworkList })).toBe(null);
|
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('Detect Next.js', async () => {
|
it('should be able to change directories', async () => {
|
||||||
const fs = new VirtualFilesystem({
|
const nextPackageJson = JSON.stringify({
|
||||||
'package.json': JSON.stringify({
|
dependencies: {
|
||||||
dependencies: {
|
next: '9.0.0',
|
||||||
next: '9.0.0',
|
},
|
||||||
},
|
});
|
||||||
}),
|
const gatsbyPackageJson = JSON.stringify({
|
||||||
|
dependencies: {
|
||||||
|
gatsby: '1.0.0',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await detectFramework({ fs, frameworkList })).toBe('nextjs');
|
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'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Detect Nuxt.js', async () => {
|
describe('#detectFramework', () => {
|
||||||
const fs = new VirtualFilesystem({
|
it('Do not detect anything', async () => {
|
||||||
'package.json': JSON.stringify({
|
const fs = new VirtualFilesystem({
|
||||||
dependencies: {
|
'README.md': '# hi',
|
||||||
nuxt: '1.0.0',
|
'api/cheese.js': 'export default (req, res) => res.end("cheese");',
|
||||||
},
|
});
|
||||||
}),
|
|
||||||
|
expect(await detectFramework({ fs, frameworkList })).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await detectFramework({ fs, frameworkList })).toBe('nuxtjs');
|
it('Detect Next.js', async () => {
|
||||||
});
|
const fs = new VirtualFilesystem({
|
||||||
|
'package.json': JSON.stringify({
|
||||||
|
dependencies: {
|
||||||
|
next: '9.0.0',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
it('Detect Gatsby', async () => {
|
expect(await detectFramework({ fs, frameworkList })).toBe('nextjs');
|
||||||
const fs = new VirtualFilesystem({
|
|
||||||
'package.json': JSON.stringify({
|
|
||||||
dependencies: {
|
|
||||||
gatsby: '1.0.0',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await detectFramework({ fs, frameworkList })).toBe('gatsby');
|
it('Detect Nuxt.js', async () => {
|
||||||
});
|
const fs = new VirtualFilesystem({
|
||||||
|
'package.json': JSON.stringify({
|
||||||
|
dependencies: {
|
||||||
|
nuxt: '1.0.0',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
it('Detect Hugo #1', async () => {
|
expect(await detectFramework({ fs, frameworkList })).toBe('nuxtjs');
|
||||||
const fs = new VirtualFilesystem({
|
|
||||||
'config.yaml': 'baseURL: http://example.org/',
|
|
||||||
'content/post.md': '# hello world',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
it('Detect Gatsby', async () => {
|
||||||
});
|
const fs = new VirtualFilesystem({
|
||||||
|
'package.json': JSON.stringify({
|
||||||
|
dependencies: {
|
||||||
|
gatsby: '1.0.0',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
it('Detect Hugo #2', async () => {
|
expect(await detectFramework({ fs, frameworkList })).toBe('gatsby');
|
||||||
const fs = new VirtualFilesystem({
|
|
||||||
'config.json': '{ "baseURL": "http://example.org/" }',
|
|
||||||
'content/post.md': '# hello world',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
it('Detect Hugo #1', async () => {
|
||||||
});
|
const fs = new VirtualFilesystem({
|
||||||
|
'config.yaml': 'baseURL: http://example.org/',
|
||||||
|
'content/post.md': '# hello world',
|
||||||
|
});
|
||||||
|
|
||||||
it('Detect Hugo #3', async () => {
|
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
||||||
const fs = new VirtualFilesystem({
|
|
||||||
'config.toml': 'baseURL = "http://example.org/"',
|
|
||||||
'content/post.md': '# hello world',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
it('Detect Hugo #2', async () => {
|
||||||
});
|
const fs = new VirtualFilesystem({
|
||||||
|
'config.json': '{ "baseURL": "http://example.org/" }',
|
||||||
|
'content/post.md': '# hello world',
|
||||||
|
});
|
||||||
|
|
||||||
it('Detect Jekyll', async () => {
|
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
||||||
const fs = new VirtualFilesystem({
|
|
||||||
'_config.yml': 'config',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await detectFramework({ fs, frameworkList })).toBe('jekyll');
|
it('Detect Hugo #3', async () => {
|
||||||
});
|
const fs = new VirtualFilesystem({
|
||||||
|
'config.toml': 'baseURL = "http://example.org/"',
|
||||||
|
'content/post.md': '# hello world',
|
||||||
|
});
|
||||||
|
|
||||||
it('Detect Middleman', async () => {
|
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
||||||
const fs = new VirtualFilesystem({
|
|
||||||
'config.rb': 'config',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await detectFramework({ fs, frameworkList })).toBe('middleman');
|
it('Detect Jekyll', async () => {
|
||||||
});
|
const fs = new VirtualFilesystem({
|
||||||
|
'_config.yml': 'config',
|
||||||
|
});
|
||||||
|
|
||||||
it('Detect Scully', async () => {
|
expect(await detectFramework({ fs, frameworkList })).toBe('jekyll');
|
||||||
const fs = new VirtualFilesystem({
|
|
||||||
'package.json': JSON.stringify({
|
|
||||||
dependencies: {
|
|
||||||
'@angular/cli': 'latest',
|
|
||||||
'@scullyio/init': 'latest',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await detectFramework({ fs, frameworkList })).toBe('scully');
|
it('Detect Middleman', async () => {
|
||||||
});
|
const fs = new VirtualFilesystem({
|
||||||
|
'config.rb': 'config',
|
||||||
|
});
|
||||||
|
|
||||||
it('Detect Zola', async () => {
|
expect(await detectFramework({ fs, frameworkList })).toBe('middleman');
|
||||||
const fs = new VirtualFilesystem({
|
|
||||||
'config.toml': 'base_url = "/"',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await detectFramework({ fs, frameworkList })).toBe('zola');
|
it('Detect Scully', async () => {
|
||||||
|
const fs = new VirtualFilesystem({
|
||||||
|
'package.json': JSON.stringify({
|
||||||
|
dependencies: {
|
||||||
|
'@angular/cli': 'latest',
|
||||||
|
'@scullyio/init': 'latest',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await detectFramework({ fs, frameworkList })).toBe('scully');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Detect Zola', async () => {
|
||||||
|
const fs = new VirtualFilesystem({
|
||||||
|
'config.toml': 'base_url = "/"',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await detectFramework({ fs, frameworkList })).toBe('zola');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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: {
|
args: {
|
||||||
cliType: 'pnpm',
|
cliType: 'pnpm',
|
||||||
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
|
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
|
||||||
@@ -97,21 +97,7 @@ describe('Test `getEnvForPackageManager()`', () => {
|
|||||||
},
|
},
|
||||||
want: {
|
want: {
|
||||||
FOO: 'bar',
|
FOO: 'bar',
|
||||||
PATH: '/pnpm7/pnpm:foo',
|
PATH: '/pnpm7/node_modules/.bin: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',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { promises } from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { DetectorFilesystem } from '../../src';
|
import { DetectorFilesystem } from '../../src';
|
||||||
|
import { Stat } from '../../src/detectors/filesystem';
|
||||||
|
|
||||||
const { stat, readFile } = promises;
|
const { stat, readFile, readdir } = promises;
|
||||||
|
|
||||||
export class FixtureFilesystem extends DetectorFilesystem {
|
export class FixtureFilesystem extends DetectorFilesystem {
|
||||||
private rootPath: string;
|
private rootPath: string;
|
||||||
@@ -32,4 +33,19 @@ export class FixtureFilesystem extends DetectorFilesystem {
|
|||||||
const filePath = path.join(this.rootPath, name);
|
const filePath = path.join(this.rootPath, name);
|
||||||
return (await stat(filePath)).isFile();
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel",
|
"name": "vercel",
|
||||||
"version": "24.2.1",
|
"version": "24.2.2",
|
||||||
"preferGlobal": true,
|
"preferGlobal": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "The command-line interface for Vercel",
|
"description": "The command-line interface for Vercel",
|
||||||
@@ -43,11 +43,11 @@
|
|||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "2.17.0",
|
"@vercel/build-utils": "3.0.1",
|
||||||
"@vercel/go": "1.4.1",
|
"@vercel/go": "1.4.2",
|
||||||
"@vercel/node": "1.15.1",
|
"@vercel/node": "1.15.2",
|
||||||
"@vercel/python": "2.3.1",
|
"@vercel/python": "2.3.2",
|
||||||
"@vercel/ruby": "1.3.4",
|
"@vercel/ruby": "1.3.5",
|
||||||
"update-notifier": "4.1.0"
|
"update-notifier": "4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"@types/update-notifier": "5.1.0",
|
"@types/update-notifier": "5.1.0",
|
||||||
"@types/which": "1.3.2",
|
"@types/which": "1.3.2",
|
||||||
"@types/write-json-file": "2.2.1",
|
"@types/write-json-file": "2.2.1",
|
||||||
"@vercel/client": "11.0.1",
|
"@vercel/client": "11.0.2",
|
||||||
"@vercel/frameworks": "0.9.0",
|
"@vercel/frameworks": "0.9.0",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"@zeit/fun": "0.11.2",
|
"@zeit/fun": "0.11.2",
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export default async function dev(
|
|||||||
}
|
}
|
||||||
|
|
||||||
[{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
|
[{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
|
||||||
getDecryptedEnvRecords(output, client, project.id),
|
getDecryptedEnvRecords(output, client, project.id, 'vercel-cli:dev'),
|
||||||
project.autoExposeSystemEnvs
|
project.autoExposeSystemEnvs
|
||||||
? getSystemEnvValues(output, client, project.id)
|
? getSystemEnvValues(output, client, project.id)
|
||||||
: { systemEnvValues: [] },
|
: { systemEnvValues: [] },
|
||||||
|
|||||||
7
packages/cli/src/commands/env/add.ts
vendored
7
packages/cli/src/commands/env/add.ts
vendored
@@ -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(
|
const existing = new Set(
|
||||||
envs.filter(r => r.key === envName).map(r => r.target)
|
envs.filter(r => r.key === envName).map(r => r.target)
|
||||||
);
|
);
|
||||||
|
|||||||
3
packages/cli/src/commands/env/index.ts
vendored
3
packages/cli/src/commands/env/index.ts
vendored
@@ -147,7 +147,8 @@ export default async function main(client: Client) {
|
|||||||
argv,
|
argv,
|
||||||
args,
|
args,
|
||||||
output,
|
output,
|
||||||
cwd
|
cwd,
|
||||||
|
'vercel-cli:env:pull'
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
output.error(getInvalidSubcommand(COMMAND_CONFIG));
|
output.error(getInvalidSubcommand(COMMAND_CONFIG));
|
||||||
|
|||||||
14
packages/cli/src/commands/env/ls.ts
vendored
14
packages/cli/src/commands/env/ls.ts
vendored
@@ -48,10 +48,16 @@ export default async function ls(
|
|||||||
|
|
||||||
const lsStamp = stamp();
|
const lsStamp = stamp();
|
||||||
|
|
||||||
const { envs } = await getEnvRecords(output, client, project.id, {
|
const { envs } = await getEnvRecords(
|
||||||
target: envTarget,
|
output,
|
||||||
gitBranch: envGitBranch,
|
client,
|
||||||
});
|
project.id,
|
||||||
|
'vercel-cli:env:ls',
|
||||||
|
{
|
||||||
|
target: envTarget,
|
||||||
|
gitBranch: envGitBranch,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (envs.length === 0) {
|
if (envs.length === 0) {
|
||||||
output.log(
|
output.log(
|
||||||
|
|||||||
6
packages/cli/src/commands/env/pull.ts
vendored
6
packages/cli/src/commands/env/pull.ts
vendored
@@ -13,6 +13,7 @@ import { Output } from '../../util/output';
|
|||||||
import param from '../../util/output/param';
|
import param from '../../util/output/param';
|
||||||
import stamp from '../../util/output/stamp';
|
import stamp from '../../util/output/stamp';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
import { EnvRecordsSource } from '../../util/env/get-env-records';
|
||||||
|
|
||||||
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
|
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
|
||||||
|
|
||||||
@@ -49,7 +50,8 @@ export default async function pull(
|
|||||||
opts: Partial<Options>,
|
opts: Partial<Options>,
|
||||||
args: string[],
|
args: string[],
|
||||||
output: Output,
|
output: Output,
|
||||||
cwd: string
|
cwd: string,
|
||||||
|
source: Extract<EnvRecordsSource, 'vercel-cli:env:pull' | 'vercel-cli:pull'>
|
||||||
) {
|
) {
|
||||||
if (args.length > 1) {
|
if (args.length > 1) {
|
||||||
output.error(
|
output.error(
|
||||||
@@ -90,7 +92,7 @@ export default async function pull(
|
|||||||
output.spinner('Downloading');
|
output.spinner('Downloading');
|
||||||
|
|
||||||
const [{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
|
const [{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
|
||||||
getDecryptedEnvRecords(output, client, project.id, environment),
|
getDecryptedEnvRecords(output, client, project.id, source, environment),
|
||||||
project.autoExposeSystemEnvs
|
project.autoExposeSystemEnvs
|
||||||
? getSystemEnvValues(output, client, project.id)
|
? getSystemEnvValues(output, client, project.id)
|
||||||
: { systemEnvValues: [] },
|
: { systemEnvValues: [] },
|
||||||
|
|||||||
14
packages/cli/src/commands/env/rm.ts
vendored
14
packages/cli/src/commands/env/rm.ts
vendored
@@ -67,10 +67,16 @@ export default async function rm(
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await getEnvRecords(output, client, project.id, {
|
const result = await getEnvRecords(
|
||||||
target: envTarget,
|
output,
|
||||||
gitBranch: envGitBranch,
|
client,
|
||||||
});
|
project.id,
|
||||||
|
'vercel-cli:env:rm',
|
||||||
|
{
|
||||||
|
target: envTarget,
|
||||||
|
gitBranch: envGitBranch,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
let envs = result.envs.filter(env => env.key === envName);
|
let envs = result.envs.filter(env => env.key === envName);
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,8 @@ async function pullAllEnvFiles(
|
|||||||
argv,
|
argv,
|
||||||
[join('.vercel', environmentFile)],
|
[join('.vercel', environmentFile)],
|
||||||
client.output,
|
client.output,
|
||||||
cwd
|
cwd,
|
||||||
|
'vercel-cli:pull'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
packages/cli/src/util/env/get-env-records.ts
vendored
13
packages/cli/src/util/env/get-env-records.ts
vendored
@@ -3,10 +3,20 @@ import Client from '../client';
|
|||||||
import { ProjectEnvVariable, ProjectEnvTarget } from '../../types';
|
import { ProjectEnvVariable, ProjectEnvTarget } from '../../types';
|
||||||
import { URLSearchParams } from 'url';
|
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(
|
export default async function getEnvRecords(
|
||||||
output: Output,
|
output: Output,
|
||||||
client: Client,
|
client: Client,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
source: EnvRecordsSource,
|
||||||
{
|
{
|
||||||
target,
|
target,
|
||||||
gitBranch,
|
gitBranch,
|
||||||
@@ -31,6 +41,9 @@ export default async function getEnvRecords(
|
|||||||
if (decrypt) {
|
if (decrypt) {
|
||||||
query.set('decrypt', decrypt.toString());
|
query.set('decrypt', decrypt.toString());
|
||||||
}
|
}
|
||||||
|
if (source) {
|
||||||
|
query.set('source', source);
|
||||||
|
}
|
||||||
|
|
||||||
const url = `/v8/projects/${projectId}/env?${query}`;
|
const url = `/v8/projects/${projectId}/env?${query}`;
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,16 @@ import {
|
|||||||
ProjectEnvVariable,
|
ProjectEnvVariable,
|
||||||
Secret,
|
Secret,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import getEnvRecords from './env/get-env-records';
|
import getEnvRecords, { EnvRecordsSource } from './env/get-env-records';
|
||||||
|
|
||||||
export default async function getDecryptedEnvRecords(
|
export default async function getDecryptedEnvRecords(
|
||||||
output: Output,
|
output: Output,
|
||||||
client: Client,
|
client: Client,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
source: EnvRecordsSource,
|
||||||
target?: ProjectEnvTarget
|
target?: ProjectEnvTarget
|
||||||
): Promise<{ envs: ProjectEnvVariable[] }> {
|
): Promise<{ envs: ProjectEnvVariable[] }> {
|
||||||
const { envs } = await getEnvRecords(output, client, projectId, {
|
const { envs } = await getEnvRecords(output, client, projectId, source, {
|
||||||
target: target || ProjectEnvTarget.Development,
|
target: target || ProjectEnvTarget.Development,
|
||||||
decrypt: true,
|
decrypt: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -126,6 +126,12 @@ describe('DevServer', () => {
|
|||||||
it(
|
it(
|
||||||
'should maintain query when builder defines routes',
|
'should maintain query when builder defines routes',
|
||||||
testFixture('now-dev-next', async server => {
|
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`);
|
const res = await fetch(`${server.address}/something?url-param=a`);
|
||||||
validateResponseHeaders(res);
|
validateResponseHeaders(res);
|
||||||
|
|
||||||
@@ -171,6 +177,12 @@ describe('DevServer', () => {
|
|||||||
it(
|
it(
|
||||||
'should support default builds and routes',
|
'should support default builds and routes',
|
||||||
testFixture('now-dev-default-builds-and-routes', async server => {
|
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 podId: string;
|
||||||
|
|
||||||
let res = await fetch(`${server.address}/`);
|
let res = await fetch(`${server.address}/`);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/client",
|
"name": "@vercel/client",
|
||||||
"version": "11.0.1",
|
"version": "11.0.2",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/index.d.ts",
|
||||||
"homepage": "https://vercel.com",
|
"homepage": "https://vercel.com",
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "2.17.0",
|
"@vercel/build-utils": "3.0.1",
|
||||||
"@zeit/fetch": "5.2.0",
|
"@zeit/fetch": "5.2.0",
|
||||||
"async-retry": "1.2.3",
|
"async-retry": "1.2.3",
|
||||||
"async-sema": "3.0.0",
|
"async-sema": "3.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/go",
|
"name": "@vercel/go",
|
||||||
"version": "1.4.1",
|
"version": "1.4.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"@types/fs-extra": "^5.0.5",
|
"@types/fs-extra": "^5.0.5",
|
||||||
"@types/node-fetch": "^2.3.0",
|
"@types/node-fetch": "^2.3.0",
|
||||||
"@types/tar": "^4.0.0",
|
"@types/tar": "^4.0.0",
|
||||||
"@vercel/build-utils": "2.17.0",
|
"@vercel/build-utils": "3.0.1",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"async-retry": "1.3.1",
|
"async-retry": "1.3.1",
|
||||||
"execa": "^1.0.0",
|
"execa": "^1.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/node",
|
"name": "@vercel/node",
|
||||||
"version": "1.15.1",
|
"version": "1.15.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"@types/etag": "1.8.0",
|
"@types/etag": "1.8.0",
|
||||||
"@types/jest": "27.4.1",
|
"@types/jest": "27.4.1",
|
||||||
"@types/test-listen": "1.1.0",
|
"@types/test-listen": "1.1.0",
|
||||||
"@vercel/build-utils": "2.17.0",
|
"@vercel/build-utils": "3.0.1",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"@vercel/nft": "0.18.1",
|
"@vercel/nft": "0.18.1",
|
||||||
"content-type": "1.0.4",
|
"content-type": "1.0.4",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "12.x"
|
"node": "14.x"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "12.0.0 - 12.99.99"
|
"node": "14.0.0 - 14.99.99"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"probes": [
|
"probes": [
|
||||||
{ "path": "/empty", "mustContain": "RANDOMNESS_PLACEHOLDER:14" },
|
{ "path": "/empty", "mustContain": "RANDOMNESS_PLACEHOLDER:16" },
|
||||||
{ "path": "/greater", "mustContain": "RANDOMNESS_PLACEHOLDER:14" },
|
{ "path": "/greater", "mustContain": "RANDOMNESS_PLACEHOLDER:16" },
|
||||||
{ "path": "/major", "mustContain": "RANDOMNESS_PLACEHOLDER:12" },
|
{ "path": "/major", "mustContain": "RANDOMNESS_PLACEHOLDER:14" },
|
||||||
{ "path": "/range", "mustContain": "RANDOMNESS_PLACEHOLDER:12" }
|
{ "path": "/range", "mustContain": "RANDOMNESS_PLACEHOLDER:14" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sharp": "0.23.0"
|
"sharp": "0.30.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/python",
|
"name": "@vercel/python",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/execa": "^0.9.0",
|
"@types/execa": "^0.9.0",
|
||||||
"@types/jest": "27.4.1",
|
"@types/jest": "27.4.1",
|
||||||
"@vercel/build-utils": "2.17.0",
|
"@vercel/build-utils": "3.0.1",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"execa": "^1.0.0",
|
"execa": "^1.0.0",
|
||||||
"typescript": "4.3.4"
|
"typescript": "4.3.4"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/redwood",
|
"name": "@vercel/redwood",
|
||||||
"version": "0.8.1",
|
"version": "0.8.2",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://vercel.com/docs",
|
"homepage": "https://vercel.com/docs",
|
||||||
@@ -27,6 +27,6 @@
|
|||||||
"@types/aws-lambda": "8.10.19",
|
"@types/aws-lambda": "8.10.19",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"@types/semver": "6.0.0",
|
"@types/semver": "6.0.0",
|
||||||
"@vercel/build-utils": "2.17.0"
|
"@vercel/build-utils": "3.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,29 +73,39 @@ export const build: BuildV2 = async ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const spawnOpts = getSpawnOptions(meta, nodeVersion);
|
const spawnOpts = getSpawnOptions(meta, nodeVersion);
|
||||||
|
if (!spawnOpts.env) {
|
||||||
|
spawnOpts.env = {};
|
||||||
|
}
|
||||||
|
const { cliType, lockfileVersion } = await scanParentDirs(
|
||||||
|
entrypointFsDirname
|
||||||
|
);
|
||||||
|
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 (typeof installCommand === 'string') {
|
if (typeof installCommand === 'string') {
|
||||||
if (installCommand.trim()) {
|
if (installCommand.trim()) {
|
||||||
console.log(`Running "install" command: \`${installCommand}\`...`);
|
console.log(`Running "install" command: \`${installCommand}\`...`);
|
||||||
const { cliType, lockfileVersion } = await scanParentDirs(
|
|
||||||
entrypointFsDirname
|
|
||||||
);
|
|
||||||
const env: Record<string, string> = {
|
const env: Record<string, string> = {
|
||||||
YARN_NODE_LINKER: 'node-modules',
|
YARN_NODE_LINKER: 'node-modules',
|
||||||
...spawnOpts.env,
|
...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, {
|
await execCommand(installCommand, {
|
||||||
...spawnOpts,
|
...spawnOpts,
|
||||||
env,
|
env,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/ruby",
|
"name": "@vercel/ruby",
|
||||||
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
||||||
"version": "1.3.4",
|
"version": "1.3.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/fs-extra": "8.0.0",
|
"@types/fs-extra": "8.0.0",
|
||||||
"@types/semver": "6.0.0",
|
"@types/semver": "6.0.0",
|
||||||
"@vercel/build-utils": "2.17.0",
|
"@vercel/build-utils": "3.0.1",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"execa": "2.0.4",
|
"execa": "2.0.4",
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/static-build",
|
"name": "@vercel/static-build",
|
||||||
"version": "0.24.1",
|
"version": "0.25.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"homepage": "https://vercel.com/docs/build-step",
|
"homepage": "https://vercel.com/docs/build-step",
|
||||||
@@ -31,14 +31,16 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/aws-lambda": "8.10.64",
|
"@types/aws-lambda": "8.10.64",
|
||||||
"@types/cross-spawn": "6.0.0",
|
"@types/cross-spawn": "6.0.0",
|
||||||
|
"@types/fs-extra": "9.0.13",
|
||||||
"@types/jest": "27.4.1",
|
"@types/jest": "27.4.1",
|
||||||
"@types/ms": "0.7.31",
|
"@types/ms": "0.7.31",
|
||||||
"@types/node-fetch": "2.5.4",
|
"@types/node-fetch": "2.5.4",
|
||||||
"@types/promise-timeout": "1.3.0",
|
"@types/promise-timeout": "1.3.0",
|
||||||
"@vercel/build-utils": "2.17.0",
|
"@vercel/build-utils": "3.0.1",
|
||||||
"@vercel/frameworks": "0.9.0",
|
"@vercel/frameworks": "0.9.0",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"@vercel/routing-utils": "1.13.2",
|
"@vercel/routing-utils": "1.13.2",
|
||||||
|
"fs-extra": "10.0.0",
|
||||||
"get-port": "5.0.0",
|
"get-port": "5.0.0",
|
||||||
"is-port-reachable": "2.0.1",
|
"is-port-reachable": "2.0.1",
|
||||||
"ms": "2.1.2",
|
"ms": "2.1.2",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
} from '@vercel/build-utils';
|
} from '@vercel/build-utils';
|
||||||
import type { Route, Source } from '@vercel/routing-utils';
|
import type { Route, Source } from '@vercel/routing-utils';
|
||||||
import * as BuildOutputV1 from './utils/build-output-v1';
|
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 BuildOutputV3 from './utils/build-output-v3';
|
||||||
import * as GatsbyUtils from './utils/gatsby';
|
import * as GatsbyUtils from './utils/gatsby';
|
||||||
import * as NuxtUtils from './utils/nuxt';
|
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 ({
|
export const build: BuildV2 = async ({
|
||||||
files,
|
files,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
@@ -376,6 +408,10 @@ export const build: BuildV2 = async ({
|
|||||||
);
|
);
|
||||||
const spawnOpts = getSpawnOptions(meta, nodeVersion);
|
const spawnOpts = getSpawnOptions(meta, nodeVersion);
|
||||||
|
|
||||||
|
if (!spawnOpts.env) {
|
||||||
|
spawnOpts.env = {};
|
||||||
|
}
|
||||||
|
|
||||||
/* Don't fail the build on warnings from Create React App.
|
/* 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.
|
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
|
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
|
https://github.com/vercel/community/discussions/30
|
||||||
*/
|
*/
|
||||||
if (framework?.slug === 'create-react-app') {
|
if (framework?.slug === 'create-react-app') {
|
||||||
if (!spawnOpts.env) {
|
|
||||||
spawnOpts.env = {};
|
|
||||||
}
|
|
||||||
spawnOpts.env.CI = 'false';
|
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) {
|
if (meta.isDev) {
|
||||||
debug('Skipping dependency installation because dev mode is enabled');
|
debug('Skipping dependency installation because dev mode is enabled');
|
||||||
} else {
|
} else {
|
||||||
@@ -410,25 +463,11 @@ export const build: BuildV2 = async ({
|
|||||||
} else if (typeof installCommand === 'string') {
|
} else if (typeof installCommand === 'string') {
|
||||||
if (installCommand.trim()) {
|
if (installCommand.trim()) {
|
||||||
console.log(`Running "install" command: \`${installCommand}\`...`);
|
console.log(`Running "install" command: \`${installCommand}\`...`);
|
||||||
const { cliType, lockfileVersion } = await scanParentDirs(
|
|
||||||
entrypointDir
|
|
||||||
);
|
|
||||||
const env: Record<string, string> = {
|
const env: Record<string, string> = {
|
||||||
YARN_NODE_LINKER: 'node-modules',
|
YARN_NODE_LINKER: 'node-modules',
|
||||||
...spawnOpts.env,
|
...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, {
|
await execCommand(installCommand, {
|
||||||
...spawnOpts,
|
...spawnOpts,
|
||||||
|
|
||||||
@@ -622,48 +661,36 @@ export const build: BuildV2 = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const outputDirPrefix = path.join(workPath, path.dirname(entrypoint));
|
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
|
// If the Build Command or Framework output files according to the
|
||||||
// Build Output v3 API, then stop processing here in `static-build`
|
// Build Output v3 API, then stop processing here in `static-build`
|
||||||
// since the output is already in its final form.
|
// since the output is already in its final form.
|
||||||
const buildOutputPath = await BuildOutputV3.getBuildOutputDirectory(
|
const buildOutputPathV3 = await BuildOutputV3.getBuildOutputDirectory(
|
||||||
outputDirPrefix
|
outputDirPrefix
|
||||||
);
|
);
|
||||||
|
if (buildOutputPathV3) {
|
||||||
if (buildOutputPath) {
|
|
||||||
// Ensure that `vercel build` is being used for this Deployment
|
// Ensure that `vercel build` is being used for this Deployment
|
||||||
if (!meta.cliVersion) {
|
return BuildOutputV3.createBuildOutput(
|
||||||
let buildCommandName: string;
|
meta,
|
||||||
if (buildCommand) buildCommandName = `"${buildCommand}"`;
|
buildCommand,
|
||||||
else if (framework) buildCommandName = framework.name;
|
buildOutputPathV3,
|
||||||
else buildCommandName = 'the "build" script';
|
framework
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (framework) {
|
const buildOutputPathV2 = await BuildOutputV2.getBuildOutputDirectory(
|
||||||
const outputDirName = config.outputDirectory
|
outputDirPrefix
|
||||||
? config.outputDirectory
|
);
|
||||||
: await framework.getOutputDirName(outputDirPrefix);
|
if (buildOutputPathV2) {
|
||||||
|
return await BuildOutputV2.createBuildOutput(workPath);
|
||||||
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 extraOutputs = await BuildOutputV1.readBuildOutputDirectory({
|
const extraOutputs = await BuildOutputV1.readBuildOutputDirectory({
|
||||||
|
|||||||
170
packages/static-build/src/utils/build-output-v2.ts
Normal file
170
packages/static-build/src/utils/build-output-v2.ts
Normal 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 };
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { promises as fs } from 'fs';
|
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';
|
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,
|
* `config.json` file was created by the framework / build script,
|
||||||
* or `undefined` if the framework did not create the v3 output.
|
* or `undefined` if the framework did not create the v3 output.
|
||||||
*/
|
*/
|
||||||
@@ -34,3 +36,27 @@ export async function readConfig(
|
|||||||
}
|
}
|
||||||
return undefined;
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
1
packages/static-build/test/build-fixtures/09-build-output-v3/.gitignore
vendored
Normal file
1
packages/static-build/test/build-fixtures/09-build-output-v3/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
yarn.lock
|
||||||
@@ -3,5 +3,5 @@ fs.mkdirSync('.vercel/output/static', { recursive: true });
|
|||||||
fs.writeFileSync('.vercel/output/config.json', '{}');
|
fs.writeFileSync('.vercel/output/config.json', '{}');
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
'.vercel/output/static/index.html',
|
'.vercel/output/static/index.html',
|
||||||
'<h1>Build Output API</h1>'
|
'<h1>Build Output API v3</h1>'
|
||||||
);
|
);
|
||||||
|
|||||||
1
packages/static-build/test/build-fixtures/10-build-output-v2/.gitignore
vendored
Normal file
1
packages/static-build/test/build-fixtures/10-build-output-v2/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
yarn.lock
|
||||||
60
packages/static-build/test/build-fixtures/10-build-output-v2/build.js
Executable file
60
packages/static-build/test/build-fixtures/10-build-output-v2/build.js
Executable 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', {});
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`
|
||||||
|
);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "10-build-output-v2",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "node build.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
108
packages/static-build/test/build.test.ts
vendored
108
packages/static-build/test/build.test.ts
vendored
@@ -1,54 +1,88 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { remove } from 'fs-extra';
|
||||||
import { build } from '../src';
|
import { build } from '../src';
|
||||||
|
|
||||||
describe('build()', () => {
|
describe('build()', () => {
|
||||||
it('should detect Builder Output v3', async () => {
|
describe('Build Output API v2', () => {
|
||||||
const workPath = path.join(
|
it('should detect the output format', async () => {
|
||||||
__dirname,
|
const workPath = path.join(
|
||||||
'build-fixtures',
|
__dirname,
|
||||||
'09-build-output-v3'
|
'build-fixtures',
|
||||||
);
|
'10-build-output-v2'
|
||||||
const buildResult = await build({
|
);
|
||||||
files: {},
|
|
||||||
entrypoint: 'package.json',
|
try {
|
||||||
workPath,
|
const buildResult = await build({
|
||||||
config: {},
|
files: {},
|
||||||
meta: {
|
entrypoint: 'package.json',
|
||||||
skipDownload: true,
|
workPath,
|
||||||
cliVersion: '0.0.0',
|
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'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if ('output' in buildResult) {
|
|
||||||
throw new Error('Unexpected `output` in build result');
|
|
||||||
}
|
|
||||||
expect(buildResult.buildOutputVersion).toEqual(3);
|
|
||||||
expect(buildResult.buildOutputPath).toEqual(
|
|
||||||
path.join(workPath, '.vercel/output')
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an Error with Builder Output v3 without `vercel build`', async () => {
|
describe('Build Output API v3', () => {
|
||||||
let err;
|
it('should detect the output format', async () => {
|
||||||
const workPath = path.join(
|
const workPath = path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
'build-fixtures',
|
'build-fixtures',
|
||||||
'09-build-output-v3'
|
'09-build-output-v3'
|
||||||
);
|
);
|
||||||
try {
|
const buildResult = await build({
|
||||||
await build({
|
|
||||||
files: {},
|
files: {},
|
||||||
entrypoint: 'package.json',
|
entrypoint: 'package.json',
|
||||||
workPath,
|
workPath,
|
||||||
config: {},
|
config: {},
|
||||||
meta: {
|
meta: {
|
||||||
skipDownload: true,
|
skipDownload: true,
|
||||||
|
cliVersion: '0.0.0',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (_err: any) {
|
if ('output' in buildResult) {
|
||||||
err = _err;
|
throw new Error('Unexpected `output` in build result');
|
||||||
}
|
}
|
||||||
expect(err.message).toEqual(
|
expect(buildResult.buildOutputVersion).toEqual(3);
|
||||||
`Detected Build Output v3 from the "build" script, but this Deployment is not using \`vercel build\`.\nPlease set the \`ENABLE_VC_BUILD=1\` environment variable.`
|
expect(buildResult.buildOutputPath).toEqual(
|
||||||
);
|
path.join(workPath, '.vercel/output')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an Error without `vercel build`', async () => {
|
||||||
|
let err;
|
||||||
|
const workPath = path.join(
|
||||||
|
__dirname,
|
||||||
|
'build-fixtures',
|
||||||
|
'09-build-output-v3'
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await build({
|
||||||
|
files: {},
|
||||||
|
entrypoint: 'package.json',
|
||||||
|
workPath,
|
||||||
|
config: {},
|
||||||
|
meta: {
|
||||||
|
skipDownload: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (_err: any) {
|
||||||
|
err = _err;
|
||||||
|
}
|
||||||
|
expect(err.message).toEqual(
|
||||||
|
`Detected Build Output v3 from the "build" script, but this Deployment is not using \`vercel build\`.\nPlease set the \`ENABLE_VC_BUILD=1\` environment variable.`
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
9
packages/static-build/test/fixtures/75-pnpm7-custom-commands/package.json
vendored
Normal file
9
packages/static-build/test/fixtures/75-pnpm7-custom-commands/package.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
packages/static-build/test/fixtures/75-pnpm7-custom-commands/pnpm-lock.yaml
generated
vendored
Normal file
19
packages/static-build/test/fixtures/75-pnpm7-custom-commands/pnpm-lock.yaml
generated
vendored
Normal 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
|
||||||
15
packages/static-build/test/fixtures/75-pnpm7-custom-commands/vercel.json
vendored
Normal file
15
packages/static-build/test/fixtures/75-pnpm7-custom-commands/vercel.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [
|
||||||
|
{
|
||||||
|
"src": "package.json",
|
||||||
|
"use": "@vercel/static-build"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"probes": [
|
||||||
|
{
|
||||||
|
"path": "/",
|
||||||
|
"mustContain": "pnpm version: 7"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user