Compare commits

..

6 Commits

Author SHA1 Message Date
Steven
c3abf73f58 Publish Canary
- @vercel/build-utils@2.12.3-canary.18
 - vercel@23.1.3-canary.30
 - @vercel/client@10.2.3-canary.19
 - vercel-plugin-go@1.0.0-canary.2
 - vercel-plugin-node@1.12.2-canary.8
 - vercel-plugin-python@1.0.0-canary.2
 - vercel-plugin-ruby@1.0.0-canary.2
2021-11-11 14:54:33 -05:00
Steven
4873b8b379 [build-utils][node] Add updateFunctionsManifest function (#6971) 2021-11-11 14:53:43 -05:00
Miroslav Simulcik
6248139281 [cli] Prevent recursion of vercel build and vercel dev (#6960)
* Prevent infinite recursion of vercel build and dev

* Improved text

* Improved the text further

* Add tests

* Debug test

* Add debug log

* Debug test

* debug test

* Debug test

* Fix test

* Debug tests

* Debug test

* Remove only from tests

* Add error documentation

* Improved docs

* Fix other test

* Fixed links to error docs

* Remove legacy error page

* Use nicer error links

Co-authored-by: Leo Lamprecht <leo@vercel.com>
2021-11-11 10:51:30 -05:00
Miroslav Simulcik
507a5de3cd [tests] Fix getting of config paths (#6969)
Co-authored-by: Steven <steven@ceriously.com>
2021-11-09 14:34:41 -05:00
Gary Borton
be1c78e72f Publish Canary
- vercel@23.1.3-canary.29
 - vercel-plugin-middleware@0.0.0-canary.7
2021-11-09 11:07:01 -08:00
Gary Borton
c277c649c6 Fix middleware build result. Return middleware next when invalid result. (#6963)
* Fix middleware build result. Return middleware next when invalid result.

* Add a unit test that would have failed before previous commit.

* Copy next.js behavior.

* Add a test for invalid responses from middleware.

Co-authored-by: Leo Lamprecht <leo@vercel.com>
2021-11-09 11:06:16 -08:00
25 changed files with 397 additions and 311 deletions

View File

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

View File

@@ -2,7 +2,7 @@ import fs from 'fs-extra';
import { join, dirname, relative } from 'path';
import glob from './fs/glob';
import { normalizePath } from './fs/normalize-path';
import { FILES_SYMBOL, Lambda } from './lambda';
import { FILES_SYMBOL, getLambdaOptionsFromFunction, Lambda } from './lambda';
import type FileBlob from './file-blob';
import type { BuilderFunctions, BuildOptions, Files } from './types';
import minimatch from 'minimatch';
@@ -21,8 +21,8 @@ export function convertRuntimeToPlugin(
const files = await glob('**', opts);
delete files['vercel.json']; // Builders/Runtimes didn't have vercel.json
const entrypoints = await glob(`api/**/*${ext}`, opts);
const functionsManifest: { [key: string]: any } = {};
const functions = await readVercelConfigFunctions(workPath);
const pages: { [key: string]: any } = {};
const { functions = {} } = await readVercelConfig(workPath);
const traceDir = join(workPath, '.output', 'runtime-traced-files');
await fs.ensureDir(traceDir);
@@ -44,11 +44,11 @@ export function convertRuntimeToPlugin(
},
});
functionsManifest[entrypoint] = {
pages[entrypoint] = {
handler: output.handler,
runtime: output.runtime,
memory: config.memory || output.memory,
maxDuration: config.maxDuration || output.maxDuration,
memory: output.memory,
maxDuration: output.maxDuration,
environment: output.environment,
allowQuery: output.allowQuery,
regions: output.regions,
@@ -98,10 +98,7 @@ export function convertRuntimeToPlugin(
await fs.writeFile(nft, json);
}
await fs.writeFile(
join(workPath, '.output', 'functions-manifest.json'),
JSON.stringify(functionsManifest)
);
await updateFunctionsManifest({ workPath, pages });
};
}
@@ -115,14 +112,10 @@ async function linkOrCopy(existingPath: string, newPath: string) {
}
}
async function readVercelConfigFunctions(
workPath: string
): Promise<BuilderFunctions> {
const vercelJsonPath = join(workPath, 'vercel.json');
async function readJson(filePath: string): Promise<{ [key: string]: any }> {
try {
const str = await fs.readFile(vercelJsonPath, 'utf8');
const obj = JSON.parse(str);
return obj.functions || {};
const str = await fs.readFile(filePath, 'utf8');
return JSON.parse(str);
} catch (err) {
if (err.code === 'ENOENT') {
return {};
@@ -130,3 +123,49 @@ async function readVercelConfigFunctions(
throw err;
}
}
async function readVercelConfig(
workPath: string
): Promise<{ functions?: BuilderFunctions; regions?: string[] }> {
const vercelJsonPath = join(workPath, 'vercel.json');
return readJson(vercelJsonPath);
}
/**
* If `.output/functions-manifest.json` exists, append to the pages
* property. Otherwise write a new file. This will also read `vercel.json`
* and apply relevant `functions` property config.
*/
export async function updateFunctionsManifest({
workPath,
pages,
}: {
workPath: string;
pages: { [key: string]: any };
}) {
const functionsManifestPath = join(
workPath,
'.output',
'functions-manifest.json'
);
const vercelConfig = await readVercelConfig(workPath);
const functionsManifest = await readJson(functionsManifestPath);
if (!functionsManifest.version) functionsManifest.version = 1;
if (!functionsManifest.pages) functionsManifest.pages = {};
for (const [pageKey, pageConfig] of Object.entries(pages)) {
const fnConfig = await getLambdaOptionsFromFunction({
sourceFile: pageKey,
config: vercelConfig,
});
functionsManifest.pages[pageKey] = {
...pageConfig,
memory: fnConfig.memory || pageConfig.memory,
maxDuration: fnConfig.maxDuration || pageConfig.maxDuration,
regions: vercelConfig.regions || pageConfig.regions,
};
}
await fs.writeFile(functionsManifestPath, JSON.stringify(functionsManifest));
}

View File

@@ -82,7 +82,10 @@ export { detectFramework } from './detect-framework';
export { DetectorFilesystem } from './detectors/filesystem';
export { readConfigFile } from './fs/read-config-file';
export { normalizePath } from './fs/normalize-path';
export { convertRuntimeToPlugin } from './convert-runtime-to-plugin';
export {
convertRuntimeToPlugin,
updateFunctionsManifest,
} from './convert-runtime-to-plugin';
export * from './schemas';
export * from './types';

View File

@@ -36,7 +36,7 @@ interface CreateLambdaOptions {
interface GetLambdaOptionsFromFunctionOptions {
sourceFile: string;
config?: Config;
config?: Pick<Config, 'functions'>;
}
export const FILES_SYMBOL = Symbol('files');

View File

@@ -71,9 +71,12 @@ describe('convert-runtime-to-plugin', () => {
const funcManifest = JSON.parse(output['functions-manifest.json']);
expect(funcManifest).toMatchObject({
'api/index.py': lambdaOptions,
'api/users/get.py': lambdaOptions,
'api/users/post.py': { ...lambdaOptions, memory: 3008 },
version: 1,
pages: {
'api/index.py': lambdaOptions,
'api/users/get.py': lambdaOptions,
'api/users/post.py': { ...lambdaOptions, memory: 3008 },
},
});
const indexJson = JSON.parse(output.server.pages.api['index.py.nft.json']);

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "23.1.3-canary.28",
"version": "23.1.3-canary.30",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -43,14 +43,14 @@
"node": ">= 12"
},
"dependencies": {
"@vercel/build-utils": "2.12.3-canary.17",
"@vercel/build-utils": "2.12.3-canary.18",
"@vercel/go": "1.2.4-canary.3",
"@vercel/node": "1.12.2-canary.6",
"@vercel/python": "2.0.6-canary.5",
"@vercel/ruby": "1.2.8-canary.4",
"update-notifier": "4.1.0",
"vercel-plugin-middleware": "0.0.0-canary.6",
"vercel-plugin-node": "1.12.2-canary.7"
"vercel-plugin-middleware": "0.0.0-canary.7",
"vercel-plugin-node": "1.12.2-canary.8"
},
"devDependencies": {
"@next/env": "11.1.2",

View File

@@ -78,6 +78,22 @@ const fields: {
];
export default async function main(client: Client) {
if (process.env.__VERCEL_BUILD_RUNNING) {
client.output.error(
`${cmd(
`${getPkgName()} build`
)} must not recursively invoke itself. Check the Build Command in the Project Settings or the ${cmd(
'build'
)} script in ${cmd('package.json')}`
);
client.output.error(
`Learn More: https://vercel.link/recursive-invocation-of-commands`
);
return 1;
} else {
process.env.__VERCEL_BUILD_RUNNING = '1';
}
let argv;
const buildStamp = stamp();
try {

View File

@@ -48,6 +48,22 @@ const help = () => {
};
export default async function main(client: Client) {
if (process.env.__VERCEL_DEV_RUNNING) {
client.output.error(
`${cmd(
`${getPkgName()} dev`
)} must not recursively invoke itself. Check the Development Command in the Project Settings or the ${cmd(
'dev'
)} script in ${cmd('package.json')}`
);
client.output.error(
`Learn More: https://vercel.link/recursive-invocation-of-commands`
);
return 1;
} else {
process.env.__VERCEL_DEV_RUNNING = '1';
}
let argv;
let args;
const { output } = client;
@@ -90,22 +106,21 @@ export default async function main(client: Client) {
if (pkg) {
const { scripts } = pkg as PackageJson;
if (scripts && scripts.dev && /\bnow\b\W+\bdev\b/.test(scripts.dev)) {
output.error(
`The ${cmd('dev')} script in ${cmd(
'package.json'
)} must not contain ${cmd('now dev')}`
if (
scripts &&
scripts.dev &&
/\b(now|vercel)\b\W+\bdev\b/.test(scripts.dev)
) {
client.output.error(
`${cmd(
`${getPkgName()} dev`
)} must not recursively invoke itself. Check the Development Command in the Project Settings or the ${cmd(
'dev'
)} script in ${cmd('package.json')}`
);
output.error(`Learn More: http://err.sh/vercel/now-dev-as-dev-script`);
return 1;
}
if (scripts && scripts.dev && /\bvercel\b\W+\bdev\b/.test(scripts.dev)) {
output.error(
`The ${cmd('dev')} script in ${cmd(
'package.json'
)} must not contain ${cmd('vercel dev')}`
client.output.error(
`Learn More: https://vercel.link/recursive-invocation-of-commands`
);
output.error(`Learn More: http://err.sh/vercel/now-dev-as-dev-script`);
return 1;
}
}

View File

@@ -18,12 +18,8 @@ export const isDirectory = (path: string): boolean => {
const getGlobalPathConfig = (): string => {
let customPath: string | undefined;
try {
const argv = getArgs(process.argv.slice(2), {});
customPath = argv['--global-config'];
} catch (_error) {
// args are optional so consume error
}
const argv = getArgs(process.argv.slice(2), {}, { permissive: true });
customPath = argv['--global-config'];
const vercelDirectories = XDGAppPaths('com.vercel.cli').dataDirs();

View File

@@ -7,12 +7,8 @@ import getArgs from '../../util/get-args';
export default function getLocalPathConfig(prefix: string) {
let customPath: string | undefined;
try {
const argv = getArgs(process.argv.slice(2), {});
customPath = argv['--local-config'];
} catch (_error) {
// args are optional so consume error
}
const argv = getArgs(process.argv.slice(2), {}, { permissive: true });
customPath = argv['--local-config'];
// If `--local-config` flag was specified, then that takes priority
if (customPath) {

View File

@@ -1358,13 +1358,20 @@ export default class DevServer {
this.cwd,
this.output
);
for (let plugin of devMiddlewarePlugins) {
const result = await plugin.plugin.runDevMiddleware(req, res, this.cwd);
if (result.finished) {
return result;
try {
for (let plugin of devMiddlewarePlugins) {
const result = await plugin.plugin.runDevMiddleware(req, res, this.cwd);
if (result.finished) {
return result;
}
}
return { finished: false };
} catch (e) {
return {
finished: true,
error: e,
};
}
return { finished: false };
};
/**
@@ -2154,7 +2161,10 @@ export default class DevServer {
process.stdout.write(data.replace(proxyPort, devPort));
});
p.on('exit', () => {
p.on('exit', (code: number) => {
if (code > 0) {
process.exit(code);
}
this.devProcessPort = undefined;
});

View File

@@ -0,0 +1,3 @@
export default function () {
return 'freecandy';
}

View File

@@ -19,7 +19,7 @@ const getRevertAliasConfigFile = () => {
],
});
};
module.exports = async function prepare(session) {
module.exports = async function prepare(session, binaryPath) {
const spec = {
'static-single-file': {
'first.png': getImageFile(session, { size: 30 }),
@@ -114,6 +114,23 @@ module.exports = async function prepare(session) {
2
),
},
'dev-fail-on-recursion-command': {
'package.json': '{}',
},
'build-fail-on-recursion-command': {
'package.json': '{}',
},
'build-fail-on-recursion-script': {
'package.json': JSON.stringify(
{
scripts: {
build: `${binaryPath} build`,
},
},
null,
2
),
},
'static-deployment': {
'index.txt': 'Hello World',
},

View File

@@ -252,10 +252,69 @@ const createUser = async () => {
const getConfigAuthPath = () => path.join(globalDir, 'auth.json');
async function setupProject(process, projectName, overrides) {
await waitForPrompt(process, chunk => /Set up [^?]+\?/.test(chunk));
process.stdin.write('yes\n');
await waitForPrompt(process, chunk => /Which scope [^?]+\?/.test(chunk));
process.stdin.write('\n');
await waitForPrompt(process, chunk =>
chunk.includes('Link to existing project?')
);
process.stdin.write('no\n');
await waitForPrompt(process, chunk =>
chunk.includes('Whats your projects name?')
);
process.stdin.write(`${projectName}\n`);
await waitForPrompt(process, chunk =>
chunk.includes('In which directory is your code located?')
);
process.stdin.write('\n');
await waitForPrompt(process, chunk =>
chunk.includes('Want to override the settings?')
);
if (overrides) {
process.stdin.write('yes\n');
const { buildCommand, outputDirectory, devCommand } = overrides;
await waitForPrompt(process, chunk =>
chunk.includes(
'Which settings would you like to overwrite (select multiple)?'
)
);
process.stdin.write('a\n'); // 'a' means select all
await waitForPrompt(process, chunk =>
chunk.includes(`What's your Build Command?`)
);
process.stdin.write(`${buildCommand ?? ''}\n`);
await waitForPrompt(process, chunk =>
chunk.includes(`What's your Output Directory?`)
);
process.stdin.write(`${outputDirectory ?? ''}\n`);
await waitForPrompt(process, chunk =>
chunk.includes(`What's your Development Command?`)
);
process.stdin.write(`${devCommand ?? ''}\n`);
} else {
process.stdin.write('no\n');
}
await waitForPrompt(process, chunk => chunk.includes('Linked to'));
}
test.before(async () => {
try {
await createUser();
await prepareFixtures(contextName);
await prepareFixtures(contextName, binaryPath);
} catch (err) {
console.log('Failed `test.before`');
console.log(err);
@@ -2227,13 +2286,93 @@ test('whoami', async t => {
t.is(stdout, contextName, formatOutput({ stdout, stderr }));
});
test('fail `now dev` dev script without now.json', async t => {
test('[vercel dev] fails when dev script calls vercel dev recursively', async t => {
const deploymentPath = fixture('now-dev-fail-dev-script');
const { exitCode, stderr } = await execute(['dev', deploymentPath]);
t.is(exitCode, 1);
t.true(
stderr.includes('must not contain `now dev`'),
stderr.includes('must not recursively invoke itself'),
`Received instead: "${stderr}"`
);
});
test('[vercel dev] fails when development commad calls vercel dev recursively', async t => {
const dir = fixture('dev-fail-on-recursion-command');
const projectName = `dev-fail-on-recursion-command-${
Math.random().toString(36).split('.')[1]
}`;
const dev = execa(binaryPath, ['dev', ...defaultArgs], {
cwd: dir,
reject: false,
});
await setupProject(dev, projectName, {
devCommand: `${binaryPath} dev`,
});
const { exitCode, stderr } = await dev;
t.is(exitCode, 1);
t.true(
stderr.includes('must not recursively invoke itself'),
`Received instead: "${stderr}"`
);
});
test('[vercel build] fails when build commad calls vercel build recursively', async t => {
const dir = fixture('build-fail-on-recursion-command');
const projectName = `build-fail-on-recursion-command-${
Math.random().toString(36).split('.')[1]
}`;
const build = execa(binaryPath, ['build', ...defaultArgs], {
cwd: dir,
reject: false,
});
await waitForPrompt(build, chunk =>
chunk.includes('No Project Settings found locally')
);
build.stdin.write('yes\n');
await setupProject(build, projectName, {
buildCommand: `${binaryPath} build`,
});
const { exitCode, stderr } = await build;
t.is(exitCode, 1);
t.true(
stderr.includes('must not recursively invoke itself'),
`Received instead: "${stderr}"`
);
});
test('[vercel build] fails when build script calls vercel build recursively', async t => {
const dir = fixture('build-fail-on-recursion-script');
const projectName = `build-fail-on-recursion-script-${
Math.random().toString(36).split('.')[1]
}`;
const build = execa(binaryPath, ['build', ...defaultArgs], {
cwd: dir,
reject: false,
});
await waitForPrompt(build, chunk =>
chunk.includes('No Project Settings found locally')
);
build.stdin.write('yes\n');
await setupProject(build, projectName);
const { exitCode, stderr } = await build;
t.is(exitCode, 1);
t.true(
stderr.includes('must not recursively invoke itself'),
`Received instead: "${stderr}"`
);
});
@@ -2683,59 +2822,10 @@ test('should show prompts to set up project during first deploy', async t => {
const now = execa(binaryPath, [dir, ...defaultArgs]);
await waitForPrompt(now, chunk => /Set up and deploy [^?]+\?/.test(chunk));
now.stdin.write('yes\n');
await waitForPrompt(now, chunk =>
chunk.includes('Which scope do you want to deploy to?')
);
now.stdin.write('\n');
await waitForPrompt(now, chunk =>
chunk.includes('Link to existing project?')
);
now.stdin.write('no\n');
await waitForPrompt(now, chunk =>
chunk.includes('Whats your projects name?')
);
now.stdin.write(`${projectName}\n`);
await waitForPrompt(now, chunk =>
chunk.includes('In which directory is your code located?')
);
now.stdin.write('\n');
await waitForPrompt(now, chunk =>
chunk.includes('Want to override the settings?')
);
now.stdin.write('yes\n');
await waitForPrompt(now, chunk =>
chunk.includes(
'Which settings would you like to overwrite (select multiple)?'
)
);
now.stdin.write('a\n'); // 'a' means select all
await waitForPrompt(now, chunk =>
chunk.includes(`What's your Build Command?`)
);
now.stdin.write(
`mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html\n`
);
await waitForPrompt(now, chunk =>
chunk.includes(`What's your Output Directory?`)
);
now.stdin.write(`o\n`);
await waitForPrompt(now, chunk =>
chunk.includes(`What's your Development Command?`)
);
now.stdin.write(`\n`);
await waitForPrompt(now, chunk => chunk.includes('Linked to'));
await setupProject(now, projectName, {
buildCommand: `mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html`,
outputDirectory: 'o',
});
const output = await now;
@@ -3301,55 +3391,10 @@ test('[vc link] should show prompts to set up project', async t => {
const vc = execa(binaryPath, ['link', ...defaultArgs], { cwd: dir });
await waitForPrompt(vc, chunk => /Set up [^?]+\?/.test(chunk));
vc.stdin.write('yes\n');
await waitForPrompt(vc, chunk =>
chunk.includes('Which scope should contain your project?')
);
vc.stdin.write('\n');
await waitForPrompt(vc, chunk => chunk.includes('Link to existing project?'));
vc.stdin.write('no\n');
await waitForPrompt(vc, chunk =>
chunk.includes('Whats your projects name?')
);
vc.stdin.write(`${projectName}\n`);
await waitForPrompt(vc, chunk =>
chunk.includes('In which directory is your code located?')
);
vc.stdin.write('\n');
await waitForPrompt(vc, chunk =>
chunk.includes('Want to override the settings?')
);
vc.stdin.write('yes\n');
await waitForPrompt(vc, chunk =>
chunk.includes(
'Which settings would you like to overwrite (select multiple)?'
)
);
vc.stdin.write('a\n'); // 'a' means select all
await waitForPrompt(vc, chunk =>
chunk.includes(`What's your Build Command?`)
);
vc.stdin.write(`mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html\n`);
await waitForPrompt(vc, chunk =>
chunk.includes(`What's your Output Directory?`)
);
vc.stdin.write(`o\n`);
await waitForPrompt(vc, chunk =>
chunk.includes(`What's your Development Command?`)
);
vc.stdin.write(`\n`);
await waitForPrompt(vc, chunk => chunk.includes('Linked to'));
await setupProject(vc, projectName, {
buildCommand: `mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html`,
outputDirectory: 'o',
});
const output = await vc;
@@ -3422,59 +3467,10 @@ test('[vc dev] should show prompts to set up project', async t => {
cwd: dir,
});
await waitForPrompt(dev, chunk => /Set up and develop [^?]+\?/.test(chunk));
dev.stdin.write('yes\n');
await waitForPrompt(dev, chunk =>
chunk.includes('Which scope should contain your project?')
);
dev.stdin.write('\n');
await waitForPrompt(dev, chunk =>
chunk.includes('Link to existing project?')
);
dev.stdin.write('no\n');
await waitForPrompt(dev, chunk =>
chunk.includes('Whats your projects name?')
);
dev.stdin.write(`${projectName}\n`);
await waitForPrompt(dev, chunk =>
chunk.includes('In which directory is your code located?')
);
dev.stdin.write('\n');
await waitForPrompt(dev, chunk =>
chunk.includes('Want to override the settings?')
);
dev.stdin.write('yes\n');
await waitForPrompt(dev, chunk =>
chunk.includes(
'Which settings would you like to overwrite (select multiple)?'
)
);
dev.stdin.write('a\n'); // 'a' means select all
await waitForPrompt(dev, chunk =>
chunk.includes(`What's your Build Command?`)
);
dev.stdin.write(
`mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html\n`
);
await waitForPrompt(dev, chunk =>
chunk.includes(`What's your Output Directory?`)
);
dev.stdin.write(`o\n`);
await waitForPrompt(dev, chunk =>
chunk.includes(`What's your Development Command?`)
);
dev.stdin.write(`\n`);
await waitForPrompt(dev, chunk => chunk.includes('Linked to'));
await setupProject(dev, projectName, {
buildCommand: `mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html`,
outputDirectory: 'o',
});
// Ensure .gitignore is created
const gitignore = await readFile(path.join(dir, '.gitignore'), 'utf8');
@@ -3574,59 +3570,12 @@ test('[vc dev] should send the platform proxy request headers to frontend dev se
cwd: dir,
});
await waitForPrompt(dev, chunk => /Set up and develop [^?]+\?/.test(chunk));
dev.stdin.write('yes\n');
await setupProject(dev, projectName, {
buildCommand: `mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html`,
outputDirectory: 'o',
devCommand: 'node server.js',
});
await waitForPrompt(dev, chunk =>
chunk.includes('Which scope should contain your project?')
);
dev.stdin.write('\n');
await waitForPrompt(dev, chunk =>
chunk.includes('Link to existing project?')
);
dev.stdin.write('no\n');
await waitForPrompt(dev, chunk =>
chunk.includes('Whats your projects name?')
);
dev.stdin.write(`${projectName}\n`);
await waitForPrompt(dev, chunk =>
chunk.includes('In which directory is your code located?')
);
dev.stdin.write('\n');
await waitForPrompt(dev, chunk =>
chunk.includes('Want to override the settings?')
);
dev.stdin.write('yes\n');
await waitForPrompt(dev, chunk =>
chunk.includes(
'Which settings would you like to overwrite (select multiple)?'
)
);
dev.stdin.write('a\n'); // 'a' means select all
await waitForPrompt(dev, chunk =>
chunk.includes(`What's your Build Command?`)
);
dev.stdin.write(
`mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html\n`
);
await waitForPrompt(dev, chunk =>
chunk.includes(`What's your Output Directory?`)
);
dev.stdin.write(`o\n`);
await waitForPrompt(dev, chunk =>
chunk.includes(`What's your Development Command?`)
);
dev.stdin.write(`node server.js\n`);
await waitForPrompt(dev, chunk => chunk.includes('Linked to'));
await waitForPrompt(dev, chunk => chunk.includes('Ready! Available at'));
// Ensure that `vc dev` also works

View File

@@ -374,4 +374,15 @@ describe('DevServer', () => {
);
})
);
it(
'should render an error page when the middleware returns not a Response',
testFixture('edge-middleware-invalid-response', async server => {
const response = await fetch(`${server.address}/index.html`);
const body = await response.text();
expect(body).toStrictEqual(
'A server error has occurred\n\nEDGE_FUNCTION_INVOCATION_FAILED\n'
);
})
);
});

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "10.2.3-canary.18",
"version": "10.2.3-canary.19",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -40,7 +40,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.12.3-canary.17",
"@vercel/build-utils": "2.12.3-canary.18",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "vercel-plugin-middleware",
"version": "0.0.0-canary.6",
"version": "0.0.0-canary.7",
"license": "MIT",
"main": "./dist/index",
"homepage": "",

View File

@@ -3,13 +3,16 @@ _ENTRIES = typeof _ENTRIES === 'undefined' ? {} : _ENTRIES;
_ENTRIES['middleware_pages/_middleware'] = {
default: async function (ev) {
const result = await middleware.default(ev.request, ev);
if (result instanceof Response) {
return {
promise: Promise.resolve(),
waitUntil: Promise.resolve(),
response: result,
};
}
return result;
return {
promise: Promise.resolve(),
waitUntil: Promise.resolve(),
response:
result ||
new Response(null, {
headers: {
'x-middleware-next': 1,
},
}),
};
},
};

View File

@@ -1,8 +1,19 @@
import { join } from 'path';
import { promises as fsp } from 'fs';
import { build } from '../src';
import { Response } from 'node-fetch';
describe('build()', () => {
beforeEach(() => {
//@ts-ignore
global.Response = Response;
});
afterEach(() => {
//@ts-ignore
delete global.Response;
//@ts-ignore
delete global._ENTRIES;
});
it('should build simple middleware', async () => {
const fixture = join(__dirname, 'fixtures/simple');
await build({
@@ -17,8 +28,30 @@ describe('build()', () => {
);
expect(middlewareManifest).toMatchSnapshot();
const outputFile = join(fixture, '.output/server/pages/_middleware.js');
expect(await fsp.stat(outputFile)).toBeTruthy();
require(outputFile);
//@ts-ignore
const middleware = global._ENTRIES['middleware_pages/_middleware'].default;
expect(typeof middleware).toStrictEqual('function');
const handledResponse = await middleware({
request: {
url: 'http://google.com',
},
});
const unhandledResponse = await middleware({
request: {
url: 'literallyanythingelse',
},
});
expect(String(handledResponse.response.body)).toEqual('Hi from the edge!');
expect(
await fsp.stat(join(fixture, '.output/server/pages/_middleware.js'))
).toBeTruthy();
(handledResponse.response as Response).headers.get('x-middleware-next')
).toEqual(null);
expect(unhandledResponse.response.body).toEqual(null);
expect(
(unhandledResponse.response as Response).headers.get('x-middleware-next')
).toEqual('1');
});
});

View File

@@ -1 +1,5 @@
export default () => new Response('Hi from the edge!');
export default req => {
if (req.url === 'http://google.com') {
return new Response('Hi from the edge!');
}
};

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "vercel-plugin-go",
"version": "1.0.0-canary.1",
"version": "1.0.0-canary.2",
"main": "dist/index.js",
"license": "MIT",
"files": [
@@ -17,7 +17,7 @@
"prepublishOnly": "tsc"
},
"dependencies": {
"@vercel/build-utils": "2.12.3-canary.17",
"@vercel/build-utils": "2.12.3-canary.18",
"@vercel/go": "1.2.4-canary.3"
},
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "vercel-plugin-node",
"version": "1.12.2-canary.7",
"version": "1.12.2-canary.8",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -33,6 +33,7 @@
"@types/jest": "27.0.2",
"@types/node-fetch": "2",
"@types/test-listen": "1.1.0",
"@vercel/build-utils": "2.12.3-canary.18",
"@vercel/fun": "1.0.1",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.14.0",

View File

@@ -35,6 +35,7 @@ import {
debug,
isSymbolicLink,
runNpmInstall,
updateFunctionsManifest,
walkParentDirs,
} from '@vercel/build-utils';
import { FromSchema } from 'json-schema-to-ts';
@@ -482,27 +483,13 @@ export async function buildEntrypoint({
await finishPromise;
}
// Update the `functions-mainifest.json` file with this entrypoint
// TODO NATE: turn this into a `@vercel/plugin-utils` helper function?
const functionsManifestPath = join(outputDirPath, 'functions-manifest.json');
let functionsManifest: any = {};
try {
functionsManifest = JSON.parse(
await fsp.readFile(functionsManifestPath, 'utf8')
);
} catch (_err) {
// ignore...
}
if (!functionsManifest.version) functionsManifest.version = 1;
if (!functionsManifest.pages) functionsManifest.pages = {};
functionsManifest.pages[entrypointWithoutExtIndex] = {
handler: `${getFileName(LAUNCHER_FILENAME).slice(0, -3)}.launcher`,
runtime: nodeVersion.runtime,
const pages = {
[entrypointWithoutExtIndex]: {
handler: `${getFileName(LAUNCHER_FILENAME).slice(0, -3)}.launcher`,
runtime: nodeVersion.runtime,
},
};
await fsp.writeFile(
functionsManifestPath,
JSON.stringify(functionsManifest, null, 2)
);
await updateFunctionsManifest({ workPath, pages });
// Update the `routes-mainifest.json` file with the wildcard route
// when the entrypoint is dynamic (i.e. `/api/[id].ts`).

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "vercel-plugin-python",
"version": "1.0.0-canary.1",
"version": "1.0.0-canary.2",
"main": "dist/index.js",
"license": "MIT",
"files": [
@@ -17,7 +17,7 @@
"prepublishOnly": "tsc"
},
"dependencies": {
"@vercel/build-utils": "2.12.3-canary.17",
"@vercel/build-utils": "2.12.3-canary.18",
"@vercel/python": "2.0.6-canary.5"
},
"devDependencies": {

View File

@@ -1,7 +1,7 @@
{
"private": false,
"name": "vercel-plugin-ruby",
"version": "1.0.0-canary.1",
"version": "1.0.0-canary.2",
"main": "dist/index.js",
"license": "MIT",
"files": [
@@ -17,7 +17,7 @@
"prepublishOnly": "tsc"
},
"dependencies": {
"@vercel/build-utils": "2.12.3-canary.17",
"@vercel/build-utils": "2.12.3-canary.18",
"@vercel/ruby": "1.2.8-canary.4"
},
"devDependencies": {