mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-12 12:57:47 +00:00
Compare commits
13 Commits
vercel-plu
...
vercel-plu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aa6a13912 | ||
|
|
81ea0082f1 | ||
|
|
6dff0875f5 | ||
|
|
30aa392c0a | ||
|
|
c4fc060030 | ||
|
|
3fa08bf64f | ||
|
|
43056bde1f | ||
|
|
a49966b9b4 | ||
|
|
7f55de71bb | ||
|
|
db8e36e04c | ||
|
|
82924bb5c4 | ||
|
|
18b5fac93e | ||
|
|
a6012e600b |
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",
|
||||
"version": "23.1.3-canary.30",
|
||||
"version": "23.1.3-canary.34",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -44,13 +44,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.12.3-canary.18",
|
||||
"@vercel/go": "1.2.4-canary.3",
|
||||
"@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.7",
|
||||
"vercel-plugin-node": "1.12.2-canary.8"
|
||||
"vercel-plugin-node": "1.12.2-canary.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/env": "11.1.2",
|
||||
|
||||
@@ -169,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]
|
||||
@@ -185,7 +185,7 @@ export default async function main(client: Client) {
|
||||
: isSettingValue(defaults)
|
||||
? defaults.value
|
||||
: chalk.italic(`${defaults.placeholder}`)
|
||||
}`}\n`
|
||||
}`}`
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -201,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}`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,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
|
||||
@@ -247,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
|
||||
@@ -286,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
|
||||
@@ -352,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()}`
|
||||
@@ -573,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
|
||||
@@ -608,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;
|
||||
@@ -660,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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
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}${
|
||||
targetOrSubcommand === 'dev'
|
||||
? ' dev (beta)'
|
||||
: targetOrSubcommand === 'build'
|
||||
? ' build (beta)'
|
||||
: ''
|
||||
}${
|
||||
isCanary ||
|
||||
targetOrSubcommand === 'dev' ||
|
||||
targetOrSubcommand === 'build'
|
||||
? ' — https://vercel.com/feedback'
|
||||
: ''
|
||||
isCanary ? ' — https://vercel.com/feedback' : ''
|
||||
}`
|
||||
)}\n`
|
||||
);
|
||||
}
|
||||
|
||||
// Handle `--version` directly
|
||||
if (!targetOrSubcommand && argv['--version']) {
|
||||
|
||||
@@ -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,11 +125,15 @@ export class Output {
|
||||
this.debug(`Spinner invoked (${message}) with a ${delay}ms delay`);
|
||||
return;
|
||||
}
|
||||
if (this.isTTY) {
|
||||
if (this._spinner) {
|
||||
this._spinner.text = message;
|
||||
} else {
|
||||
this._spinner = wait(message, delay);
|
||||
}
|
||||
} else {
|
||||
this.print(`${message}\n`);
|
||||
}
|
||||
};
|
||||
|
||||
stopSpinner = () => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -365,6 +365,10 @@ module.exports = async function prepare(session, binaryPath) {
|
||||
'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"}]}',
|
||||
|
||||
23
packages/cli/test/integration.js
vendored
23
packages/cli/test/integration.js
vendored
@@ -3453,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;
|
||||
|
||||
@@ -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'}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "vercel-plugin-go",
|
||||
"version": "1.0.0-canary.2",
|
||||
"version": "1.0.0-canary.3",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.12.3-canary.18",
|
||||
"@vercel/go": "1.2.4-canary.3"
|
||||
"@vercel/go": "1.2.4-canary.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel-plugin-node",
|
||||
"version": "1.12.2-canary.8",
|
||||
"version": "1.12.2-canary.9",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
||||
@@ -33,8 +33,9 @@
|
||||
"@types/jest": "27.0.2",
|
||||
"@types/node-fetch": "2",
|
||||
"@types/test-listen": "1.1.0",
|
||||
"@types/yazl": "2.4.2",
|
||||
"@vercel/build-utils": "2.12.3-canary.18",
|
||||
"@vercel/fun": "1.0.1",
|
||||
"@vercel/fun": "1.0.3",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/nft": "0.14.0",
|
||||
"@vercel/node-bridge": "2.1.1-canary.2",
|
||||
@@ -47,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,
|
||||
@@ -44,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 {
|
||||
@@ -118,9 +120,14 @@ async function downloadInstallAndBundle({
|
||||
const nodeVersion = await getNodeVersion(entrypointFsDirname);
|
||||
const spawnOpts = getSpawnOptions({}, nodeVersion);
|
||||
|
||||
// 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,
|
||||
@@ -384,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 =
|
||||
@@ -471,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];
|
||||
@@ -481,10 +493,36 @@ 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),
|
||||
});
|
||||
}
|
||||
|
||||
await fsp.writeFile(
|
||||
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 = {
|
||||
[entrypointWithoutExtIndex]: {
|
||||
[pageKey]: {
|
||||
handler: `${getFileName(LAUNCHER_FILENAME).slice(0, -3)}.launcher`,
|
||||
runtime: nodeVersion.runtime,
|
||||
},
|
||||
|
||||
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.2",
|
||||
"version": "1.0.0-canary.3",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.12.3-canary.18",
|
||||
"@vercel/python": "2.0.6-canary.5"
|
||||
"@vercel/python": "2.0.6-canary.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "*",
|
||||
|
||||
@@ -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