mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
11 Commits
@vercel/py
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c20218e05 | ||
|
|
02a0004719 | ||
|
|
123bffb776 | ||
|
|
074535f27c | ||
|
|
05243fb6e9 | ||
|
|
097725580c | ||
|
|
4b09c89e7d | ||
|
|
3a1eede63b | ||
|
|
9cee0dd5d7 | ||
|
|
b801c6e593 | ||
|
|
505050b923 |
@@ -48,6 +48,6 @@
|
||||
"qunit-dom": "^0.8.4"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": "14.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": "14.x"
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
}
|
||||
|
||||
1
examples/solidstart/.gitignore
vendored
1
examples/solidstart/.gitignore
vendored
@@ -2,6 +2,7 @@ dist
|
||||
worker
|
||||
.solid
|
||||
.vercel
|
||||
.output
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
@@ -15,6 +15,6 @@
|
||||
"vite": "^2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": "14.x"
|
||||
}
|
||||
}
|
||||
|
||||
7
examples/solidstart/vercel.json
Normal file
7
examples/solidstart/vercel.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"build": {
|
||||
"env": {
|
||||
"ENABLE_FILE_SYSTEM_API": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "2.17.0",
|
||||
"version": "3.0.1-canary.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.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
|
||||
* 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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
"probes": [
|
||||
{
|
||||
"path": "/",
|
||||
"mustContain": "pnpm version: 7",
|
||||
"logMustContain": "pnpm run build"
|
||||
"mustContain": "pnpm version: 7"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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,115 +53,291 @@ 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('#detectFramework', () => {
|
||||
it('Do not detect anything', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'README.md': '# hi',
|
||||
'api/cheese.js': 'export default (req, res) => res.end("cheese");',
|
||||
});
|
||||
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': '{}',
|
||||
};
|
||||
|
||||
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 () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
next: '9.0.0',
|
||||
},
|
||||
}),
|
||||
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',
|
||||
},
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
nuxt: '1.0.0',
|
||||
},
|
||||
}),
|
||||
describe('#detectFramework', () => {
|
||||
it('Do not detect anything', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'README.md': '# hi',
|
||||
'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 () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
gatsby: '1.0.0',
|
||||
},
|
||||
}),
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('nextjs');
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.yaml': 'baseURL: http://example.org/',
|
||||
'content/post.md': '# hello world',
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('nuxtjs');
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.json': '{ "baseURL": "http://example.org/" }',
|
||||
'content/post.md': '# hello world',
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('gatsby');
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.toml': 'baseURL = "http://example.org/"',
|
||||
'content/post.md': '# hello world',
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'_config.yml': 'config',
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.rb': 'config',
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('middleman');
|
||||
});
|
||||
it('Detect Jekyll', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'_config.yml': 'config',
|
||||
});
|
||||
|
||||
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('jekyll');
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('scully');
|
||||
});
|
||||
it('Detect Middleman', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.rb': 'config',
|
||||
});
|
||||
|
||||
it('Detect Zola', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.toml': 'base_url = "/"',
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('middleman');
|
||||
});
|
||||
|
||||
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: {
|
||||
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',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "24.2.1",
|
||||
"version": "24.2.2-canary.1",
|
||||
"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-canary.1",
|
||||
"@vercel/go": "1.4.2-canary.0",
|
||||
"@vercel/node": "1.15.2-canary.0",
|
||||
"@vercel/python": "2.3.2-canary.0",
|
||||
"@vercel/ruby": "1.3.5-canary.0",
|
||||
"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-canary.0",
|
||||
"@vercel/frameworks": "0.9.0",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@zeit/fun": "0.11.2",
|
||||
|
||||
@@ -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: [] },
|
||||
|
||||
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(
|
||||
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,
|
||||
args,
|
||||
output,
|
||||
cwd
|
||||
cwd,
|
||||
'vercel-cli:env:pull'
|
||||
);
|
||||
default:
|
||||
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 { envs } = await getEnvRecords(output, client, project.id, {
|
||||
target: envTarget,
|
||||
gitBranch: envGitBranch,
|
||||
});
|
||||
const { envs } = await getEnvRecords(
|
||||
output,
|
||||
client,
|
||||
project.id,
|
||||
'vercel-cli:env:ls',
|
||||
{
|
||||
target: envTarget,
|
||||
gitBranch: envGitBranch,
|
||||
}
|
||||
);
|
||||
|
||||
if (envs.length === 0) {
|
||||
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 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: [] },
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const result = await getEnvRecords(output, client, project.id, {
|
||||
target: envTarget,
|
||||
gitBranch: envGitBranch,
|
||||
});
|
||||
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);
|
||||
|
||||
|
||||
@@ -131,7 +131,8 @@ async function pullAllEnvFiles(
|
||||
argv,
|
||||
[join('.vercel', environmentFile)],
|
||||
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 { 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}`;
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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}/`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "11.0.1",
|
||||
"version": "11.0.2-canary.0",
|
||||
"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-canary.1",
|
||||
"@zeit/fetch": "5.2.0",
|
||||
"async-retry": "1.2.3",
|
||||
"async-sema": "3.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/go",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2-canary.0",
|
||||
"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-canary.1",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"async-retry": "1.3.1",
|
||||
"execa": "^1.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/node",
|
||||
"version": "1.15.1",
|
||||
"version": "1.15.2-canary.0",
|
||||
"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-canary.1",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/nft": "0.18.1",
|
||||
"content-type": "1.0.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/python",
|
||||
"version": "2.3.1",
|
||||
"version": "2.3.2-canary.0",
|
||||
"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-canary.1",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"execa": "^1.0.0",
|
||||
"typescript": "4.3.4"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/redwood",
|
||||
"version": "0.8.1",
|
||||
"version": "0.8.2-canary.0",
|
||||
"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-canary.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@vercel/ruby",
|
||||
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
||||
"version": "1.3.4",
|
||||
"version": "1.3.5-canary.0",
|
||||
"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-canary.1",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"execa": "2.0.4",
|
||||
"fs-extra": "^7.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/static-build",
|
||||
"version": "0.24.1",
|
||||
"version": "0.24.2-canary.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-canary.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",
|
||||
|
||||
@@ -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,
|
||||
@@ -622,48 +654,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 {
|
||||
buildOutputVersion: 3,
|
||||
buildOutputPath,
|
||||
};
|
||||
return BuildOutputV3.createBuildOutput(
|
||||
meta,
|
||||
buildCommand,
|
||||
buildOutputPathV3,
|
||||
framework
|
||||
);
|
||||
}
|
||||
|
||||
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({
|
||||
|
||||
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 { 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,
|
||||
};
|
||||
}
|
||||
|
||||
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/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 { remove } from 'fs-extra';
|
||||
import { build } from '../src';
|
||||
|
||||
describe('build()', () => {
|
||||
it('should detect Builder Output v3', async () => {
|
||||
const workPath = path.join(
|
||||
__dirname,
|
||||
'build-fixtures',
|
||||
'09-build-output-v3'
|
||||
);
|
||||
const buildResult = await build({
|
||||
files: {},
|
||||
entrypoint: 'package.json',
|
||||
workPath,
|
||||
config: {},
|
||||
meta: {
|
||||
skipDownload: true,
|
||||
cliVersion: '0.0.0',
|
||||
},
|
||||
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'));
|
||||
}
|
||||
});
|
||||
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 () => {
|
||||
let err;
|
||||
const workPath = path.join(
|
||||
__dirname,
|
||||
'build-fixtures',
|
||||
'09-build-output-v3'
|
||||
);
|
||||
try {
|
||||
await build({
|
||||
describe('Build Output API v3', () => {
|
||||
it('should detect the output format', async () => {
|
||||
const workPath = path.join(
|
||||
__dirname,
|
||||
'build-fixtures',
|
||||
'09-build-output-v3'
|
||||
);
|
||||
const buildResult = await build({
|
||||
files: {},
|
||||
entrypoint: 'package.json',
|
||||
workPath,
|
||||
config: {},
|
||||
meta: {
|
||||
skipDownload: true,
|
||||
cliVersion: '0.0.0',
|
||||
},
|
||||
});
|
||||
} 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.`
|
||||
);
|
||||
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 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.`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user