mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 21:07:46 +00:00
[cli] Switch to npm and add more context to install errors during Builder import (#8545)
Before: <img width="1012" alt="Screen Shot 2022-09-09 at 7 03 01 PM" src="https://user-images.githubusercontent.com/71256/189464732-8cf6398a-9432-423b-8509-a52bd714333e.png"> After: <img width="579" alt="Screen Shot 2022-09-12 at 12 27 31 PM" src="https://user-images.githubusercontent.com/71256/189739091-86399428-d9b8-4d03-b0e6-7d27a1037bce.png"> This is a precursor to https://github.com/vercel/vercel/pull/8485, since the last remaining test failure there is related to specialized messaging we had for these same cases.
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import { URL } from 'url';
|
||||||
|
import plural from 'pluralize';
|
||||||
import npa from 'npm-package-arg';
|
import npa from 'npm-package-arg';
|
||||||
import { satisfies } from 'semver';
|
import { satisfies } from 'semver';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
@@ -13,6 +15,9 @@ import { VERCEL_DIR } from '../projects/link';
|
|||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
import readJSONFile from '../read-json-file';
|
import readJSONFile from '../read-json-file';
|
||||||
import { CantParseJSONFile } from '../errors-ts';
|
import { CantParseJSONFile } from '../errors-ts';
|
||||||
|
import { errorToString, isErrnoException, isError } from '../is-error';
|
||||||
|
import cmd from '../output/cmd';
|
||||||
|
import code from '../output/code';
|
||||||
|
|
||||||
export interface BuilderWithPkg {
|
export interface BuilderWithPkg {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -201,15 +206,54 @@ async function installBuilders(
|
|||||||
if (err.code !== 'EEXIST') throw err;
|
if (err.code !== 'EEXIST') throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.debug(`Installing Builders: ${Array.from(buildersToAdd).join(', ')}`);
|
output.log(
|
||||||
await spawnAsync('yarn', ['add', '@vercel/build-utils', ...buildersToAdd], {
|
`Installing ${plural('Builder', buildersToAdd.size)}: ${Array.from(
|
||||||
cwd: buildersDir,
|
buildersToAdd
|
||||||
});
|
).join(', ')}`
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await spawnAsync(
|
||||||
|
'npm',
|
||||||
|
['install', '@vercel/build-utils', ...buildersToAdd],
|
||||||
|
{
|
||||||
|
cwd: buildersDir,
|
||||||
|
stdio: 'pipe',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
if (isError(err)) {
|
||||||
|
(err as any).link =
|
||||||
|
'https://vercel.link/builder-dependencies-install-failed';
|
||||||
|
if (isErrnoException(err) && err.code === 'ENOENT') {
|
||||||
|
// `npm` is not installed
|
||||||
|
err.message = `Please install ${cmd('npm')} before continuing`;
|
||||||
|
} else {
|
||||||
|
const message = errorToString(err);
|
||||||
|
const notFound = /GET (.*) - Not found/.exec(message);
|
||||||
|
if (notFound) {
|
||||||
|
const url = new URL(notFound[1]);
|
||||||
|
const packageName = decodeURIComponent(url.pathname.slice(1));
|
||||||
|
err.message = `The package ${code(
|
||||||
|
packageName
|
||||||
|
)} is not published on the npm registry`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
// Symlink `@now/build-utils` -> `@vercel/build-utils` to support legacy Builders
|
// Symlink `@now/build-utils` -> `@vercel/build-utils` to support legacy Builders
|
||||||
const nowScopePath = join(buildersDir, 'node_modules/@now');
|
const nowScopePath = join(buildersDir, 'node_modules/@now');
|
||||||
await mkdirp(nowScopePath);
|
await mkdirp(nowScopePath);
|
||||||
await symlink('../@vercel/build-utils', join(nowScopePath, 'build-utils'));
|
|
||||||
|
try {
|
||||||
|
await symlink('../@vercel/build-utils', join(nowScopePath, 'build-utils'));
|
||||||
|
} catch (err: unknown) {
|
||||||
|
if (!isErrnoException(err) || err.code !== 'EEXIST') {
|
||||||
|
// Throw unless the error is due to the symlink already existing
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cross-reference any builderSpecs from the saved `package.json` file,
|
// Cross-reference any builderSpecs from the saved `package.json` file,
|
||||||
// in case they were installed from a URL
|
// in case they were installed from a URL
|
||||||
|
|||||||
@@ -37,20 +37,56 @@ describe('importBuilders()', () => {
|
|||||||
const cwd = await getWriteableDirectory();
|
const cwd = await getWriteableDirectory();
|
||||||
try {
|
try {
|
||||||
const spec = 'vercel-deno@2.0.1';
|
const spec = 'vercel-deno@2.0.1';
|
||||||
const specs = new Set([spec]);
|
const tarballSpec = 'https://test2020-h5hdll5dz-tootallnate.vercel.app';
|
||||||
|
const specs = new Set([spec, tarballSpec]);
|
||||||
const builders = await importBuilders(specs, cwd, client.output);
|
const builders = await importBuilders(specs, cwd, client.output);
|
||||||
expect(builders.size).toEqual(1);
|
expect(builders.size).toEqual(2);
|
||||||
expect(builders.get(spec)?.pkg.name).toEqual('vercel-deno');
|
expect(builders.get(spec)?.pkg.name).toEqual('vercel-deno');
|
||||||
expect(builders.get(spec)?.pkg.version).toEqual('2.0.1');
|
expect(builders.get(spec)?.pkg.version).toEqual('2.0.1');
|
||||||
expect(builders.get(spec)?.pkgPath).toEqual(
|
expect(builders.get(spec)?.pkgPath).toEqual(
|
||||||
join(cwd, '.vercel/builders/node_modules/vercel-deno/package.json')
|
join(cwd, '.vercel/builders/node_modules/vercel-deno/package.json')
|
||||||
);
|
);
|
||||||
expect(typeof builders.get(spec)?.builder.build).toEqual('function');
|
expect(builders.get(tarballSpec)?.pkg.name).toEqual('vercel-bash');
|
||||||
|
expect(builders.get(tarballSpec)?.pkg.version).toEqual('4.1.0');
|
||||||
|
expect(builders.get(tarballSpec)?.pkgPath).toEqual(
|
||||||
|
join(cwd, '.vercel/builders/node_modules/vercel-bash/package.json')
|
||||||
|
);
|
||||||
|
expect(typeof builders.get(tarballSpec)?.builder.build).toEqual(
|
||||||
|
'function'
|
||||||
|
);
|
||||||
|
await expect(client.stderr).toOutput(
|
||||||
|
'> Installing Builders: vercel-deno@2.0.1, https://test2020-h5hdll5dz-tootallnate.vercel.app'
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
await remove(cwd);
|
await remove(cwd);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw when importing a Builder that is not on npm registry', async () => {
|
||||||
|
let err: Error | undefined;
|
||||||
|
const cwd = await getWriteableDirectory();
|
||||||
|
try {
|
||||||
|
const spec = '@vercel/does-not-exist@0.0.1';
|
||||||
|
const specs = new Set([spec]);
|
||||||
|
await importBuilders(specs, cwd, client.output);
|
||||||
|
} catch (_err) {
|
||||||
|
err = _err;
|
||||||
|
} finally {
|
||||||
|
await remove(cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!err) {
|
||||||
|
throw new Error('Expected `err` to be defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(err.message).toEqual(
|
||||||
|
'The package `@vercel/does-not-exist` is not published on the npm registry'
|
||||||
|
);
|
||||||
|
expect((err as any).link).toEqual(
|
||||||
|
'https://vercel.link/builder-dependencies-install-failed'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should import legacy `@now/build-utils` Builders', async () => {
|
it('should import legacy `@now/build-utils` Builders', async () => {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
// this test creates symlinks which require admin by default on Windows
|
// this test creates symlinks which require admin by default on Windows
|
||||||
|
|||||||
Reference in New Issue
Block a user