mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
19 Commits
vercel-plu
...
vercel-plu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aa6a13912 | ||
|
|
81ea0082f1 | ||
|
|
6dff0875f5 | ||
|
|
30aa392c0a | ||
|
|
c4fc060030 | ||
|
|
3fa08bf64f | ||
|
|
43056bde1f | ||
|
|
a49966b9b4 | ||
|
|
7f55de71bb | ||
|
|
db8e36e04c | ||
|
|
82924bb5c4 | ||
|
|
18b5fac93e | ||
|
|
a6012e600b | ||
|
|
c3abf73f58 | ||
|
|
4873b8b379 | ||
|
|
6248139281 | ||
|
|
507a5de3cd | ||
|
|
be1c78e72f | ||
|
|
c277c649c6 |
2
.github/workflows/test-integration-dev.yml
vendored
2
.github/workflows/test-integration-dev.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
name: Dev
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 75
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,3 +27,4 @@ test/lib/deployment/failed-page.txt
|
||||
/public
|
||||
__pycache__
|
||||
.vercel
|
||||
.output
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -36,7 +36,7 @@ interface CreateLambdaOptions {
|
||||
|
||||
interface GetLambdaOptionsFromFunctionOptions {
|
||||
sourceFile: string;
|
||||
config?: Config;
|
||||
config?: Pick<Config, 'functions'>;
|
||||
}
|
||||
|
||||
export const FILES_SYMBOL = Symbol('files');
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "23.1.3-canary.28",
|
||||
"version": "23.1.3-canary.34",
|
||||
"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/go": "1.2.4-canary.3",
|
||||
"@vercel/build-utils": "2.12.3-canary.18",
|
||||
"@vercel/go": "1.2.4-canary.4",
|
||||
"@vercel/node": "1.12.2-canary.6",
|
||||
"@vercel/python": "2.0.6-canary.5",
|
||||
"@vercel/python": "2.0.6-canary.6",
|
||||
"@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.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/env": "11.1.2",
|
||||
|
||||
@@ -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 {
|
||||
@@ -153,15 +169,15 @@ export default async function main(client: Client) {
|
||||
|
||||
const buildState = { ...project.settings };
|
||||
|
||||
client.output.log(`Retrieved Project Settings:`);
|
||||
client.output.print(
|
||||
chalk.dim(` - ${chalk.bold(`Framework Preset:`)} ${framework.name}\n`)
|
||||
console.log(`Retrieved Project Settings:`);
|
||||
console.log(
|
||||
chalk.dim(` - ${chalk.bold(`Framework Preset:`)} ${framework.name}`)
|
||||
);
|
||||
|
||||
for (let field of fields) {
|
||||
const defaults = (framework.settings as any)[field.value];
|
||||
if (defaults) {
|
||||
client.output.print(
|
||||
console.log(
|
||||
chalk.dim(
|
||||
` - ${chalk.bold(`${field.name}:`)} ${`${
|
||||
project.settings[field.value]
|
||||
@@ -169,7 +185,7 @@ export default async function main(client: Client) {
|
||||
: isSettingValue(defaults)
|
||||
? defaults.value
|
||||
: chalk.italic(`${defaults.placeholder}`)
|
||||
}`}\n`
|
||||
}`}`
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -185,14 +201,14 @@ export default async function main(client: Client) {
|
||||
}
|
||||
|
||||
if (loadedEnvFiles.length > 0) {
|
||||
client.output.log(
|
||||
console.log(
|
||||
`Loaded Environment Variables from ${loadedEnvFiles.length} ${pluralize(
|
||||
'file',
|
||||
loadedEnvFiles.length
|
||||
)}:`
|
||||
);
|
||||
for (let envFile of loadedEnvFiles) {
|
||||
client.output.print(chalk.dim(` - ${envFile.path}\n`));
|
||||
console.log(chalk.dim(` - ${envFile.path}`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +239,7 @@ export default async function main(client: Client) {
|
||||
};
|
||||
|
||||
if (plugins?.pluginCount && plugins?.pluginCount > 0) {
|
||||
client.output.log(
|
||||
console.log(
|
||||
`Loaded ${plugins.pluginCount} CLI ${pluralize(
|
||||
'Plugin',
|
||||
plugins.pluginCount
|
||||
@@ -231,7 +247,7 @@ export default async function main(client: Client) {
|
||||
);
|
||||
// preBuild Plugins
|
||||
if (plugins.preBuildPlugins.length > 0) {
|
||||
client.output.log(
|
||||
console.log(
|
||||
`Running ${plugins.pluginCount} CLI ${pluralize(
|
||||
'Plugin',
|
||||
plugins.pluginCount
|
||||
@@ -270,7 +286,7 @@ export default async function main(client: Client) {
|
||||
fs.removeSync(join(cwd, OUTPUT_DIR));
|
||||
|
||||
if (typeof buildState.buildCommand === 'string') {
|
||||
client.output.log(`Running Build Command: ${cmd(buildState.buildCommand)}`);
|
||||
console.log(`Running Build Command: ${cmd(buildState.buildCommand)}`);
|
||||
await execCommand(buildState.buildCommand, {
|
||||
...spawnOpts,
|
||||
// Yarn v2 PnP mode may be activated, so force
|
||||
@@ -336,7 +352,7 @@ export default async function main(client: Client) {
|
||||
)
|
||||
);
|
||||
client.output.stopSpinner();
|
||||
client.output.log(
|
||||
console.log(
|
||||
`Copied ${files.length.toLocaleString()} files from ${param(
|
||||
distDir
|
||||
)} to ${param(outputDir)} ${copyStamp()}`
|
||||
@@ -557,7 +573,7 @@ export default async function main(client: Client) {
|
||||
|
||||
// Build Plugins
|
||||
if (plugins?.buildPlugins && plugins.buildPlugins.length > 0) {
|
||||
client.output.log(
|
||||
console.log(
|
||||
`Running ${plugins.pluginCount} CLI ${pluralize(
|
||||
'Plugin',
|
||||
plugins.pluginCount
|
||||
@@ -592,13 +608,13 @@ export default async function main(client: Client) {
|
||||
}
|
||||
}
|
||||
|
||||
client.output.print(
|
||||
console.log(
|
||||
`${prependEmoji(
|
||||
`Build Completed in ${chalk.bold(OUTPUT_DIR)} ${chalk.gray(
|
||||
buildStamp()
|
||||
)}`,
|
||||
emoji('success')
|
||||
)}\n`
|
||||
)}`
|
||||
);
|
||||
|
||||
return 0;
|
||||
@@ -644,25 +660,37 @@ export async function runPackageJsonScript(
|
||||
}
|
||||
}
|
||||
|
||||
client.output.log(`Running Build Command: ${cmd(opts.prettyCommand)}\n`);
|
||||
console.log(`Running Build Command: ${cmd(opts.prettyCommand)}\n`);
|
||||
await spawnAsync(cliType, ['run', scriptName], opts);
|
||||
client.output.print('\n'); // give it some room
|
||||
console.log(); // give it some room
|
||||
client.output.debug(`Script complete [${Date.now() - runScriptTime}ms]`);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function linkOrCopy(existingPath: string, newPath: string) {
|
||||
try {
|
||||
await fs.createLink(existingPath, newPath);
|
||||
if (
|
||||
newPath.endsWith('.nft.json') ||
|
||||
newPath.endsWith('middleware-manifest.json') ||
|
||||
newPath.endsWith('required-server-files.json')
|
||||
) {
|
||||
await fs.copy(existingPath, newPath, {
|
||||
overwrite: true,
|
||||
});
|
||||
} else {
|
||||
await fs.createSymlink(existingPath, newPath, 'file');
|
||||
}
|
||||
} catch (err: any) {
|
||||
// eslint-disable-line
|
||||
// If a hard link to the same file already exists
|
||||
// If a symlink to the same file already exists
|
||||
// then trying to copy it will make an empty file from it.
|
||||
if (err['code'] === 'EEXIST') return;
|
||||
// In some VERY rare cases (1 in a thousand), hard-link creation fails on Windows.
|
||||
// In some VERY rare cases (1 in a thousand), symlink creation fails on Windows.
|
||||
// In that case, we just fall back to copying.
|
||||
// This issue is reproducible with "pnpm add @material-ui/icons@4.9.1"
|
||||
await fs.copyFile(existingPath, newPath);
|
||||
await fs.copy(existingPath, newPath, {
|
||||
overwrite: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,24 +160,26 @@ const main = async () => {
|
||||
// * a path to deploy (as in: `vercel path/`)
|
||||
// * a subcommand (as in: `vercel ls`)
|
||||
const targetOrSubcommand = argv._[2];
|
||||
const isBuildOrDev =
|
||||
targetOrSubcommand === 'build' || targetOrSubcommand === 'dev';
|
||||
|
||||
output.print(
|
||||
`${chalk.grey(
|
||||
`${getTitleName()} CLI ${pkg.version}${
|
||||
targetOrSubcommand === 'dev'
|
||||
? ' dev (beta)'
|
||||
: targetOrSubcommand === 'build'
|
||||
? ' build (beta)'
|
||||
: ''
|
||||
}${
|
||||
isCanary ||
|
||||
targetOrSubcommand === 'dev' ||
|
||||
targetOrSubcommand === 'build'
|
||||
? ' — https://vercel.com/feedback'
|
||||
: ''
|
||||
}`
|
||||
)}\n`
|
||||
);
|
||||
if (isBuildOrDev) {
|
||||
console.log(
|
||||
`${chalk.grey(
|
||||
`${getTitleName()} CLI ${
|
||||
pkg.version
|
||||
} ${targetOrSubcommand} (beta) — https://vercel.com/feedback`
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
output.print(
|
||||
`${chalk.grey(
|
||||
`${getTitleName()} CLI ${pkg.version}${
|
||||
isCanary ? ' — https://vercel.com/feedback' : ''
|
||||
}`
|
||||
)}\n`
|
||||
);
|
||||
}
|
||||
|
||||
// Handle `--version` directly
|
||||
if (!targetOrSubcommand && argv['--version']) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Stats } from 'fs';
|
||||
import { sep, dirname, join, resolve } from 'path';
|
||||
import { readJSON, lstat, readlink, readFile, realpath } from 'fs-extra';
|
||||
import { lstat, readlink, readFile, realpath } from 'fs-extra';
|
||||
import { isCanary } from './is-canary';
|
||||
import { getPkgName } from './pkg-name';
|
||||
|
||||
// `npm` tacks a bunch of extra properties on the `package.json` file,
|
||||
// so check for one of them to determine yarn vs. npm.
|
||||
async function isYarn(): Promise<boolean> {
|
||||
let s: Stats;
|
||||
let binPath = process.argv[1];
|
||||
@@ -20,8 +18,12 @@ async function isYarn(): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
const pkgPath = join(dirname(binPath), '..', 'package.json');
|
||||
const pkg = await readJSON(pkgPath).catch(() => ({}));
|
||||
return !('_id' in pkg);
|
||||
/*
|
||||
* Generally, pkgPath looks like:
|
||||
* "/Users/username/.config/yarn/global/node_modules/vercel/package.json"
|
||||
* "/usr/local/share/.config/yarn/global/node_modules/vercel/package.json"
|
||||
*/
|
||||
return pkgPath.includes(join('yarn', 'global'));
|
||||
}
|
||||
|
||||
async function getConfigPrefix() {
|
||||
|
||||
@@ -125,10 +125,14 @@ export class Output {
|
||||
this.debug(`Spinner invoked (${message}) with a ${delay}ms delay`);
|
||||
return;
|
||||
}
|
||||
if (this._spinner) {
|
||||
this._spinner.text = message;
|
||||
if (this.isTTY) {
|
||||
if (this._spinner) {
|
||||
this._spinner.text = message;
|
||||
} else {
|
||||
this._spinner = wait(message, delay);
|
||||
}
|
||||
} else {
|
||||
this._spinner = wait(message, delay);
|
||||
this.print(`${message}\n`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -244,20 +244,27 @@ export async function linkFolderToProject(
|
||||
try {
|
||||
const gitIgnorePath = join(path, '.gitignore');
|
||||
|
||||
const gitIgnore = await readFile(gitIgnorePath, 'utf8').catch(() => null);
|
||||
const EOL = gitIgnore && gitIgnore.includes('\r\n') ? '\r\n' : os.EOL;
|
||||
let gitIgnore =
|
||||
(await readFile(gitIgnorePath, 'utf8').catch(() => null)) ?? '';
|
||||
const EOL = gitIgnore.includes('\r\n') ? '\r\n' : os.EOL;
|
||||
let contentModified = false;
|
||||
|
||||
if (
|
||||
!gitIgnore ||
|
||||
!gitIgnore.split(EOL).includes(VERCEL_DIR) ||
|
||||
!gitIgnore.split(EOL).includes(VERCEL_OUTPUT_DIR)
|
||||
) {
|
||||
await writeFile(
|
||||
gitIgnorePath,
|
||||
gitIgnore
|
||||
? `${gitIgnore}${EOL}${VERCEL_DIR}${EOL}${VERCEL_OUTPUT_DIR}${EOL}`
|
||||
: `${VERCEL_DIR}${EOL}${VERCEL_OUTPUT_DIR}${EOL}`
|
||||
);
|
||||
if (!gitIgnore.split(EOL).includes(VERCEL_DIR)) {
|
||||
gitIgnore += `${
|
||||
gitIgnore.endsWith(EOL) || gitIgnore.length === 0 ? '' : EOL
|
||||
}${VERCEL_DIR}${EOL}`;
|
||||
contentModified = true;
|
||||
}
|
||||
|
||||
if (!gitIgnore.split(EOL).includes(VERCEL_OUTPUT_DIR)) {
|
||||
gitIgnore += `${
|
||||
gitIgnore.endsWith(EOL) || gitIgnore.length === 0 ? '' : EOL
|
||||
}${VERCEL_OUTPUT_DIR}${EOL}`;
|
||||
contentModified = true;
|
||||
}
|
||||
|
||||
if (contentModified) {
|
||||
await writeFile(gitIgnorePath, gitIgnore);
|
||||
isGitIgnoreUpdated = true;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
3
packages/cli/test/fixtures/unit/edge-middleware-invalid-response/_middleware.js
vendored
Normal file
3
packages/cli/test/fixtures/unit/edge-middleware-invalid-response/_middleware.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function () {
|
||||
return 'freecandy';
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
@@ -348,6 +365,10 @@ module.exports = async function prepare(session) {
|
||||
'project-link-dev': {
|
||||
'package.json': '{}',
|
||||
},
|
||||
'project-link-gitignore': {
|
||||
'package.json': '{}',
|
||||
'.gitignore': '.output',
|
||||
},
|
||||
'project-link-legacy': {
|
||||
'index.html': 'Hello',
|
||||
'vercel.json': '{"builds":[{"src":"*.html","use":"@vercel/static"}]}',
|
||||
|
||||
392
packages/cli/test/integration.js
vendored
392
packages/cli/test/integration.js
vendored
@@ -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('What’s your project’s 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('What’s your project’s 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('What’s your project’s 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;
|
||||
|
||||
@@ -3408,6 +3453,29 @@ test('[vc link --confirm] should not show prompts and autolink', async t => {
|
||||
);
|
||||
});
|
||||
|
||||
test('[vc link] should not duplicate paths in .gitignore', async t => {
|
||||
const dir = fixture('project-link-gitignore');
|
||||
|
||||
// remove previously linked project if it exists
|
||||
await remove(path.join(dir, '.vercel'));
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
['link', '--confirm', ...defaultArgs],
|
||||
{ cwd: dir, reject: false }
|
||||
);
|
||||
|
||||
// Ensure the exit code is right
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||
|
||||
// Ensure the message is correct pattern
|
||||
t.regex(stderr, /Linked to /m);
|
||||
|
||||
// Ensure .gitignore is created
|
||||
const gitignore = await readFile(path.join(dir, '.gitignore'), 'utf8');
|
||||
t.is(gitignore, '.output\n.vercel\n');
|
||||
});
|
||||
|
||||
test('[vc dev] should show prompts to set up project', async t => {
|
||||
const dir = fixture('project-link-dev');
|
||||
const port = 58352;
|
||||
@@ -3422,59 +3490,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('What’s your project’s 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 +3593,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('What’s your project’s 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
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ describe('getUpdateCommand', () => {
|
||||
it('should detect update command', async () => {
|
||||
const updateCommand = await getUpdateCommand();
|
||||
expect(updateCommand).toEqual(
|
||||
`yarn add vercel@${isCanary() ? 'canary' : 'latest'}`
|
||||
`npm i vercel@${isCanary() ? 'canary' : 'latest'}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -6,7 +6,8 @@ import { join } from 'path';
|
||||
import stringArgv from 'string-argv';
|
||||
import { debug } from '@vercel/build-utils';
|
||||
const versionMap = new Map([
|
||||
['1.16', '1.16'],
|
||||
['1.17', '1.17.3'],
|
||||
['1.16', '1.16.10'],
|
||||
['1.15', '1.15.8'],
|
||||
['1.14', '1.14.15'],
|
||||
['1.13', '1.13.15'],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/go",
|
||||
"version": "1.2.4-canary.3",
|
||||
"version": "1.2.4-canary.4",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
{ "src": "subdirectory/index.go", "use": "@vercel/go" }
|
||||
],
|
||||
"probes": [
|
||||
{ "path": "/", "mustContain": "cow:go1.16:RANDOMNESS_PLACEHOLDER" },
|
||||
{ "path": "/", "mustContain": "cow:go1.17.3:RANDOMNESS_PLACEHOLDER" },
|
||||
{
|
||||
"path": "/subdirectory",
|
||||
"mustContain": "subcow:go1.16:RANDOMNESS_PLACEHOLDER"
|
||||
"mustContain": "subcow:go1.17.3:RANDOMNESS_PLACEHOLDER"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module with-nested
|
||||
|
||||
go 1.12
|
||||
go 1.16
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
37
packages/middleware/test/build.test.ts
vendored
37
packages/middleware/test/build.test.ts
vendored
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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!');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "vercel-plugin-go",
|
||||
"version": "1.0.0-canary.1",
|
||||
"version": "1.0.0-canary.3",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
@@ -17,8 +17,8 @@
|
||||
"prepublishOnly": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.12.3-canary.17",
|
||||
"@vercel/go": "1.2.4-canary.3"
|
||||
"@vercel/build-utils": "2.12.3-canary.18",
|
||||
"@vercel/go": "1.2.4-canary.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel-plugin-node",
|
||||
"version": "1.12.2-canary.7",
|
||||
"version": "1.12.2-canary.9",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
||||
@@ -33,7 +33,9 @@
|
||||
"@types/jest": "27.0.2",
|
||||
"@types/node-fetch": "2",
|
||||
"@types/test-listen": "1.1.0",
|
||||
"@vercel/fun": "1.0.1",
|
||||
"@types/yazl": "2.4.2",
|
||||
"@vercel/build-utils": "2.12.3-canary.18",
|
||||
"@vercel/fun": "1.0.3",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/nft": "0.14.0",
|
||||
"@vercel/node-bridge": "2.1.1-canary.2",
|
||||
@@ -46,7 +48,8 @@
|
||||
"node-fetch": "2",
|
||||
"source-map-support": "0.5.12",
|
||||
"test-listen": "1.1.0",
|
||||
"ts-morph": "12.0.0"
|
||||
"ts-morph": "12.0.0",
|
||||
"yazl": "2.5.1"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "ts-jest",
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
readlinkSync,
|
||||
statSync,
|
||||
promises as fsp,
|
||||
existsSync,
|
||||
} from 'fs';
|
||||
import {
|
||||
basename,
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
debug,
|
||||
isSymbolicLink,
|
||||
runNpmInstall,
|
||||
updateFunctionsManifest,
|
||||
walkParentDirs,
|
||||
} from '@vercel/build-utils';
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
@@ -43,6 +45,7 @@ import { AbortController } from 'abort-controller';
|
||||
import { Register, register } from './typescript';
|
||||
import { pageToRoute } from './router/page-to-route';
|
||||
import { isDynamicRoute } from './router/is-dynamic';
|
||||
import crypto from 'crypto';
|
||||
|
||||
export { shouldServe };
|
||||
export {
|
||||
@@ -117,9 +120,14 @@ async function downloadInstallAndBundle({
|
||||
const nodeVersion = await getNodeVersion(entrypointFsDirname);
|
||||
const spawnOpts = getSpawnOptions({}, nodeVersion);
|
||||
|
||||
const installTime = Date.now();
|
||||
await runNpmInstall(entrypointFsDirname, [], spawnOpts, {}, nodeVersion);
|
||||
debug(`Install complete [${Date.now() - installTime}ms]`);
|
||||
// Only run when `package.json` exists.
|
||||
if (existsSync(join(entrypointFsDirname, 'package.json'))) {
|
||||
const installTime = Date.now();
|
||||
await runNpmInstall(entrypointFsDirname, [], spawnOpts, {}, nodeVersion);
|
||||
debug(`Install complete [${Date.now() - installTime}ms]`);
|
||||
} else {
|
||||
debug(`Skip install command for \`vercel-plugin-node\`.`);
|
||||
}
|
||||
|
||||
return {
|
||||
nodeVersion,
|
||||
@@ -383,20 +391,23 @@ export async function buildEntrypoint({
|
||||
entrypoint: string;
|
||||
config: FunctionConfig;
|
||||
}) {
|
||||
// Unique hash that will be used as directory name for `.output`.
|
||||
const entrypointHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(entrypoint)
|
||||
.digest('hex');
|
||||
const outputDirPath = join(workPath, '.output');
|
||||
|
||||
const { dir, name } = parsePath(entrypoint);
|
||||
const entrypointWithoutExt = join('/', dir, name);
|
||||
const entrypointWithoutExtIndex = join(
|
||||
dir,
|
||||
name,
|
||||
name === 'index' ? '' : 'index'
|
||||
);
|
||||
const outputWorkPath = join(
|
||||
outputDirPath,
|
||||
'server/pages',
|
||||
entrypointWithoutExtIndex
|
||||
);
|
||||
const outputWorkPath = join(outputDirPath, 'inputs', entrypointHash);
|
||||
const pagesDir = join(outputDirPath, 'server', 'pages');
|
||||
const pageOutput = join(pagesDir, renameTStoJS(entrypoint));
|
||||
const nftOutput = `${pageOutput}.nft.json`;
|
||||
|
||||
await fsp.mkdir(outputWorkPath, { recursive: true });
|
||||
await fsp.mkdir(parsePath(pageOutput).dir, { recursive: true });
|
||||
|
||||
console.log(`Compiling "${entrypoint}" to "${outputWorkPath}"`);
|
||||
|
||||
const shouldAddHelpers =
|
||||
@@ -470,6 +481,8 @@ export async function buildEntrypoint({
|
||||
...launcherFiles,
|
||||
};
|
||||
|
||||
const nftFiles: { input: string; output: string }[] = [];
|
||||
|
||||
for (const filename of Object.keys(files)) {
|
||||
const outPath = join(outputWorkPath, filename);
|
||||
const file = files[filename];
|
||||
@@ -480,30 +493,42 @@ export async function buildEntrypoint({
|
||||
const finishPromise = once(ws, 'finish');
|
||||
file.toStream().pipe(ws);
|
||||
await finishPromise;
|
||||
|
||||
// The `handler` will be `.output/server/pages/api/subdirectory/___vc/__launcher.launcher`
|
||||
// or `.output/server/pages/api/___vc/__launcher.launcher`.
|
||||
// This means everything has to be mounted to the `dirname` of the entrypoint.
|
||||
nftFiles.push({
|
||||
input: relative(dirname(nftOutput), outPath),
|
||||
output: join('.output', 'server', 'pages', dirname(entrypoint), filename),
|
||||
});
|
||||
}
|
||||
|
||||
// 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,
|
||||
};
|
||||
await fsp.writeFile(
|
||||
functionsManifestPath,
|
||||
JSON.stringify(functionsManifest, null, 2)
|
||||
nftOutput,
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
files: nftFiles,
|
||||
})
|
||||
);
|
||||
|
||||
await fsp.copyFile(
|
||||
join(outputWorkPath, renameTStoJS(entrypoint)),
|
||||
pageOutput
|
||||
);
|
||||
|
||||
let pageKey = relative(pagesDir, pageOutput);
|
||||
if (process.platform === 'win32') {
|
||||
pageKey = pageKey.replace(/\\/gm, '/');
|
||||
}
|
||||
|
||||
const pages = {
|
||||
[pageKey]: {
|
||||
handler: `${getFileName(LAUNCHER_FILENAME).slice(0, -3)}.launcher`,
|
||||
runtime: nodeVersion.runtime,
|
||||
},
|
||||
};
|
||||
await updateFunctionsManifest({ workPath, pages });
|
||||
|
||||
// Update the `routes-mainifest.json` file with the wildcard route
|
||||
// when the entrypoint is dynamic (i.e. `/api/[id].ts`).
|
||||
if (isDynamicRoute(entrypointWithoutExt)) {
|
||||
|
||||
58
packages/plugin-node/test/build.test.ts
vendored
58
packages/plugin-node/test/build.test.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import { join } from 'path';
|
||||
import path from 'path';
|
||||
import { parse } from 'url';
|
||||
import { promises as fsp } from 'fs';
|
||||
import { ZipFile } from 'yazl';
|
||||
import { createFunction, Lambda } from '@vercel/fun';
|
||||
import {
|
||||
Request,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
Headers,
|
||||
} from 'node-fetch';
|
||||
import { build } from '../src';
|
||||
import { runNpmInstall, streamToBuffer } from '@vercel/build-utils';
|
||||
|
||||
interface TestParams {
|
||||
fixture: string;
|
||||
@@ -50,40 +52,64 @@ function withFixture<T>(
|
||||
t: (props: TestParams) => Promise<T>
|
||||
): () => Promise<T> {
|
||||
return async () => {
|
||||
const fixture = join(__dirname, 'fixtures', name);
|
||||
const fixture = path.join(__dirname, 'fixtures', name);
|
||||
const functions = new Map<string, Lambda>();
|
||||
|
||||
async function fetch(r: RequestInfo, init?: RequestInit) {
|
||||
const req = new Request(r, init);
|
||||
const url = parse(req.url);
|
||||
const pathWithIndex = join(
|
||||
url.pathname!,
|
||||
url.pathname!.endsWith('/index') ? '' : 'index'
|
||||
).substring(1);
|
||||
const functionPath = url.pathname!.substring(1);
|
||||
|
||||
let status = 404;
|
||||
let headers: HeadersInit = {};
|
||||
let body: string | Buffer = 'Function not found';
|
||||
|
||||
let fn = functions.get(pathWithIndex);
|
||||
let fn = functions.get(functionPath);
|
||||
if (!fn) {
|
||||
const manifest = JSON.parse(
|
||||
await fsp.readFile(
|
||||
join(fixture, '.output/functions-manifest.json'),
|
||||
path.join(fixture, '.output/functions-manifest.json'),
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
const functionManifest = manifest.pages[pathWithIndex];
|
||||
|
||||
const keyFile = `${functionPath}.js`;
|
||||
const keyIndex = `${functionPath}/index.js`;
|
||||
const fnKey = keyFile in manifest.pages ? keyFile : keyIndex;
|
||||
const functionManifest = manifest.pages[fnKey];
|
||||
|
||||
if (functionManifest) {
|
||||
const dir = join(fixture, '.output/server/pages', pathWithIndex);
|
||||
const entry = path.join(fixture, '.output/server/pages', fnKey);
|
||||
const nftFile = JSON.parse(
|
||||
await fsp.readFile(`${entry}.nft.json`, 'utf8')
|
||||
);
|
||||
|
||||
const zip = new ZipFile();
|
||||
zip.addFile(
|
||||
path.join(fixture, '.output/server/pages', fnKey),
|
||||
path.join('.output/server/pages', fnKey)
|
||||
);
|
||||
|
||||
nftFile.files.forEach((f: { input: string; output: string }) => {
|
||||
const input = path.join(path.dirname(entry), f.input);
|
||||
zip.addFile(input, f.output);
|
||||
});
|
||||
zip.end();
|
||||
|
||||
const handler = path.posix.join(
|
||||
'.output/server/pages',
|
||||
path.dirname(fnKey),
|
||||
functionManifest.handler
|
||||
);
|
||||
|
||||
fn = await createFunction({
|
||||
Code: {
|
||||
Directory: dir,
|
||||
ZipFile: await streamToBuffer(zip.outputStream),
|
||||
},
|
||||
Handler: functionManifest.handler,
|
||||
Handler: handler,
|
||||
Runtime: functionManifest.runtime,
|
||||
});
|
||||
functions.set(pathWithIndex, fn);
|
||||
functions.set(functionPath, fn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +135,12 @@ function withFixture<T>(
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
await fsp.lstat(path.join(fixture, 'package.json')).catch(() => false)
|
||||
) {
|
||||
await runNpmInstall(fixture);
|
||||
}
|
||||
|
||||
await build({ workPath: fixture });
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "vercel-plugin-python",
|
||||
"version": "1.0.0-canary.1",
|
||||
"version": "1.0.0-canary.3",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
@@ -17,8 +17,8 @@
|
||||
"prepublishOnly": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.12.3-canary.17",
|
||||
"@vercel/python": "2.0.6-canary.5"
|
||||
"@vercel/build-utils": "2.12.3-canary.18",
|
||||
"@vercel/python": "2.0.6-canary.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "*",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/python",
|
||||
"version": "2.0.6-canary.5",
|
||||
"version": "2.0.6-canary.6",
|
||||
"main": "./dist/index.js",
|
||||
"license": "MIT",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
createLambda,
|
||||
shouldServe,
|
||||
debug,
|
||||
NowBuildError,
|
||||
} from '@vercel/build-utils';
|
||||
import { installRequirement, installRequirementsFile } from './install';
|
||||
|
||||
@@ -22,6 +23,7 @@ async function pipenvConvert(cmd: string, srcDir: string) {
|
||||
const out = await execa.stdout(cmd, [], {
|
||||
cwd: srcDir,
|
||||
});
|
||||
debug('Contents of requirements.txt is: ' + out);
|
||||
fs.writeFileSync(join(srcDir, 'requirements.txt'), out);
|
||||
} catch (err) {
|
||||
console.log('Failed to run "pipfile2req"');
|
||||
@@ -57,6 +59,10 @@ export const build = async ({
|
||||
meta = {},
|
||||
config,
|
||||
}: BuildOptions) => {
|
||||
let pipPath = meta.isDev ? 'pip3' : 'pip3.9';
|
||||
let pythonPath = meta.isDev ? 'python3' : 'python3.9';
|
||||
let pythonRuntime = meta.isDev ? 'python3' : 'python3.9';
|
||||
|
||||
workPath = await downloadFilesInWorkPath({
|
||||
workPath,
|
||||
files: originalFiles,
|
||||
@@ -85,6 +91,8 @@ export const build = async ({
|
||||
console.log('Installing required dependencies...');
|
||||
|
||||
await installRequirement({
|
||||
pythonPath,
|
||||
pipPath,
|
||||
dependency: 'werkzeug',
|
||||
version: '1.0.1',
|
||||
workPath,
|
||||
@@ -103,12 +111,29 @@ export const build = async ({
|
||||
if (pipfileLockDir) {
|
||||
debug('Found "Pipfile.lock"');
|
||||
|
||||
try {
|
||||
const json = await readFile(join(pipfileLockDir, 'Pipfile.lock'), 'utf8');
|
||||
const obj = JSON.parse(json);
|
||||
if (!meta.isDev && obj?._meta?.requires?.python_version === '3.6') {
|
||||
pipPath = 'pip3.6';
|
||||
pythonPath = 'python3.6';
|
||||
pythonRuntime = 'python3.6';
|
||||
}
|
||||
} catch (err) {
|
||||
throw new NowBuildError({
|
||||
code: 'INVALID_PIPFILE_LOCK',
|
||||
message: 'Unable to parse Pipfile.lock',
|
||||
});
|
||||
}
|
||||
|
||||
// Convert Pipenv.Lock to requirements.txt.
|
||||
// We use a different`workPath` here because we want `pipfile-requirements` and it's dependencies
|
||||
// to not be part of the lambda environment. By using pip's `--target` directive we can isolate
|
||||
// it into a separate folder.
|
||||
const tempDir = await getWriteableDirectory();
|
||||
await installRequirement({
|
||||
pythonPath,
|
||||
pipPath,
|
||||
dependency: 'pipfile-requirements',
|
||||
version: '0.3.0',
|
||||
workPath: tempDir,
|
||||
@@ -130,6 +155,8 @@ export const build = async ({
|
||||
debug('Found local "requirements.txt"');
|
||||
const requirementsTxtPath = fsFiles[requirementsTxt].fsPath;
|
||||
await installRequirementsFile({
|
||||
pythonPath,
|
||||
pipPath,
|
||||
filePath: requirementsTxtPath,
|
||||
workPath,
|
||||
meta,
|
||||
@@ -138,6 +165,8 @@ export const build = async ({
|
||||
debug('Found global "requirements.txt"');
|
||||
const requirementsTxtPath = fsFiles['requirements.txt'].fsPath;
|
||||
await installRequirementsFile({
|
||||
pythonPath,
|
||||
pipPath,
|
||||
filePath: requirementsTxtPath,
|
||||
workPath,
|
||||
meta,
|
||||
@@ -163,7 +192,7 @@ export const build = async ({
|
||||
await writeFile(join(workPath, `${handlerPyFilename}.py`), handlerPyContents);
|
||||
|
||||
// Use the system-installed version of `python3` when running via `vercel dev`
|
||||
const runtime = meta.isDev ? 'python3' : 'python3.6';
|
||||
const runtime = meta.isDev ? 'python3' : pythonRuntime;
|
||||
|
||||
const globOptions: GlobOptions = {
|
||||
cwd: workPath,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import execa from 'execa';
|
||||
import { Meta, debug } from '@vercel/build-utils';
|
||||
const pipPath = 'pip3';
|
||||
|
||||
const makeDependencyCheckCode = (dependency: string) => `
|
||||
from importlib import util
|
||||
@@ -9,10 +8,14 @@ spec = util.find_spec(dep)
|
||||
print(spec.origin)
|
||||
`;
|
||||
|
||||
async function isInstalled(dependency: string, cwd: string) {
|
||||
async function isInstalled(
|
||||
pythonPath: string,
|
||||
dependency: string,
|
||||
cwd: string
|
||||
) {
|
||||
try {
|
||||
const { stdout } = await execa(
|
||||
'python3',
|
||||
pythonPath,
|
||||
['-c', makeDependencyCheckCode(dependency)],
|
||||
{
|
||||
stdio: 'pipe',
|
||||
@@ -33,10 +36,14 @@ dependencies = distutils.text_file.TextFile(filename='${requirementsPath}').read
|
||||
pkg_resources.require(dependencies)
|
||||
`;
|
||||
|
||||
async function areRequirementsInstalled(requirementsPath: string, cwd: string) {
|
||||
async function areRequirementsInstalled(
|
||||
pythonPath: string,
|
||||
requirementsPath: string,
|
||||
cwd: string
|
||||
) {
|
||||
try {
|
||||
await execa(
|
||||
'python3',
|
||||
pythonPath,
|
||||
['-c', makeRequirementsCheckCode(requirementsPath)],
|
||||
{
|
||||
stdio: 'pipe',
|
||||
@@ -49,7 +56,7 @@ async function areRequirementsInstalled(requirementsPath: string, cwd: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function pipInstall(workPath: string, args: string[]) {
|
||||
async function pipInstall(pipPath: string, workPath: string, args: string[]) {
|
||||
const target = '.';
|
||||
// See: https://github.com/pypa/pip/issues/4222#issuecomment-417646535
|
||||
//
|
||||
@@ -66,19 +73,21 @@ async function pipInstall(workPath: string, args: string[]) {
|
||||
target,
|
||||
...args,
|
||||
];
|
||||
debug(`Running "pip3 ${cmdArgs.join(' ')}"...`);
|
||||
const pretty = `${pipPath} ${cmdArgs.join(' ')}`;
|
||||
debug(`Running "${pretty}"...`);
|
||||
try {
|
||||
await execa(pipPath, cmdArgs, {
|
||||
cwd: workPath,
|
||||
stdio: 'pipe',
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(`Failed to run "pip3 ${cmdArgs.join(' ')}"`);
|
||||
console.log(`Failed to run "${pretty}"`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
interface InstallRequirementArg {
|
||||
pythonPath: string;
|
||||
pipPath: string;
|
||||
dependency: string;
|
||||
version: string;
|
||||
workPath: string;
|
||||
@@ -91,23 +100,27 @@ interface InstallRequirementArg {
|
||||
// so vc_init should do runtime version checks to be compatible with any recent
|
||||
// version of its dependencies
|
||||
export async function installRequirement({
|
||||
pythonPath,
|
||||
pipPath,
|
||||
dependency,
|
||||
version,
|
||||
workPath,
|
||||
meta,
|
||||
args = [],
|
||||
}: InstallRequirementArg) {
|
||||
if (meta.isDev && (await isInstalled(dependency, workPath))) {
|
||||
if (meta.isDev && (await isInstalled(pythonPath, dependency, workPath))) {
|
||||
debug(
|
||||
`Skipping ${dependency} dependency installation, already installed in ${workPath}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const exact = `${dependency}==${version}`;
|
||||
await pipInstall(workPath, [exact, ...args]);
|
||||
await pipInstall(pipPath, workPath, [exact, ...args]);
|
||||
}
|
||||
|
||||
interface InstallRequirementsFileArg {
|
||||
pythonPath: string;
|
||||
pipPath: string;
|
||||
filePath: string;
|
||||
workPath: string;
|
||||
meta: Meta;
|
||||
@@ -115,14 +128,19 @@ interface InstallRequirementsFileArg {
|
||||
}
|
||||
|
||||
export async function installRequirementsFile({
|
||||
pythonPath,
|
||||
pipPath,
|
||||
filePath,
|
||||
workPath,
|
||||
meta,
|
||||
args = [],
|
||||
}: InstallRequirementsFileArg) {
|
||||
if (meta.isDev && (await areRequirementsInstalled(filePath, workPath))) {
|
||||
if (
|
||||
meta.isDev &&
|
||||
(await areRequirementsInstalled(pythonPath, filePath, workPath))
|
||||
) {
|
||||
debug(`Skipping requirements file installation, already installed`);
|
||||
return;
|
||||
}
|
||||
await pipInstall(workPath, ['--upgrade', '-r', filePath, ...args]);
|
||||
await pipInstall(pipPath, workPath, ['--upgrade', '-r', filePath, ...args]);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
"outDir": "dist",
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"target": "esnext"
|
||||
"target": "es2018"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,12 @@ async function nowDeploy(bodies, randomness, uploadNowJson) {
|
||||
(path.extname(n) === '.sh' ? 0o100755 : 0o100644),
|
||||
}));
|
||||
|
||||
const { FORCE_BUILD_IN_REGION, NOW_DEBUG, VERCEL_DEBUG } = process.env;
|
||||
const {
|
||||
FORCE_BUILD_IN_REGION,
|
||||
NOW_DEBUG,
|
||||
VERCEL_DEBUG,
|
||||
VERCEL_BUILDER_DEBUG,
|
||||
} = process.env;
|
||||
const nowJson = JSON.parse(bodies['vercel.json'] || bodies['now.json']);
|
||||
|
||||
const nowDeployPayload = {
|
||||
@@ -34,6 +39,7 @@ async function nowDeploy(bodies, randomness, uploadNowJson) {
|
||||
FORCE_BUILD_IN_REGION,
|
||||
NOW_DEBUG,
|
||||
VERCEL_DEBUG,
|
||||
VERCEL_BUILDER_DEBUG,
|
||||
},
|
||||
},
|
||||
name: 'test2020',
|
||||
|
||||
35
yarn.lock
35
yarn.lock
@@ -2537,7 +2537,7 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yazl@^2.4.1":
|
||||
"@types/yazl@2.4.2", "@types/yazl@^2.4.1":
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/yazl/-/yazl-2.4.2.tgz#d5f8a4752261badbf1a36e8b49e042dc18ec84bc"
|
||||
integrity sha512-T+9JH8O2guEjXNxqmybzQ92mJUh2oCwDDMSSimZSe1P+pceZiFROZLYmcbqkzV5EUwz6VwcKXCO2S2yUpra6XQ==
|
||||
@@ -2613,10 +2613,10 @@
|
||||
"@typescript-eslint/types" "4.28.0"
|
||||
eslint-visitor-keys "^2.0.0"
|
||||
|
||||
"@vercel/fun@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@vercel/fun/-/fun-1.0.1.tgz#4b25f50c27bbbdca5b7e71f25ef5018701467962"
|
||||
integrity sha512-v++KDTdKIr0357sm0AbyYk5jLmsqfUlOKhTsaN0Z/XHpk2YJHOLlGFTDzZPt+8i+ofdPG8lCfht729DHNQskJg==
|
||||
"@vercel/fun@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@vercel/fun/-/fun-1.0.3.tgz#5e5c4921a3a3a35ee129ce063e6b3f5c4b1eae48"
|
||||
integrity sha512-9LKNosC+Sdx7ZNU7dnj7XXasHzgRkuDnz9+N6JzSVvb0v8KOZN0ky4+wn3c1B0/wF3dTn2W5nEzgmnsrVCDtkQ==
|
||||
dependencies:
|
||||
"@tootallnate/once" "2.0.0"
|
||||
async-listen "1.0.0"
|
||||
@@ -2629,6 +2629,7 @@
|
||||
node-fetch "2.6.1"
|
||||
path-match "1.2.4"
|
||||
promisepipe "3.0.0"
|
||||
semver "7.3.5"
|
||||
stat-mode "0.3.0"
|
||||
stream-to-promise "2.2.0"
|
||||
tar "4.4.18"
|
||||
@@ -8992,10 +8993,10 @@ node-fetch-npm@^2.0.2:
|
||||
json-parse-better-errors "^1.0.0"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
node-fetch@2, node-fetch@^2:
|
||||
version "2.6.5"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd"
|
||||
integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==
|
||||
node-fetch@2:
|
||||
version "2.6.6"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"
|
||||
integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
@@ -9009,6 +9010,13 @@ node-fetch@2.6.1, node-fetch@^2.2.1, node-fetch@^2.3.0, node-fetch@^2.5.0, node-
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
node-fetch@^2:
|
||||
version "2.6.5"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd"
|
||||
integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-gyp-build@^4.2.2:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"
|
||||
@@ -10665,7 +10673,7 @@ semver@6.1.1:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b"
|
||||
integrity sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==
|
||||
|
||||
semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
|
||||
semver@7.3.5, semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
|
||||
version "7.3.5"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
|
||||
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
|
||||
@@ -12618,6 +12626,13 @@ yazl@2.4.3:
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
|
||||
yazl@2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35"
|
||||
integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
|
||||
yn@3.1.1, yn@^3.0.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||
|
||||
Reference in New Issue
Block a user