mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-10 12:57:47 +00:00
[cli] Use Builder importing logic from vc build in vc dev (#8485)
This makes `vc dev` utilize the same Builder installing and importing logic that `vc build` uses. So now the Builders will be installed locally on a per-project basis, rather than being installed to a shared global installation directory.
This commit is contained in:
@@ -342,6 +342,7 @@ export interface BuilderV2 {
|
|||||||
version: 2;
|
version: 2;
|
||||||
build: BuildV2;
|
build: BuildV2;
|
||||||
prepareCache?: PrepareCache;
|
prepareCache?: PrepareCache;
|
||||||
|
shouldServe?: ShouldServe;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BuilderV3 {
|
export interface BuilderV3 {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import minimatch from 'minimatch';
|
import minimatch from 'minimatch';
|
||||||
import { BuildV2, Files } from '@vercel/build-utils';
|
import { shouldServe as defaultShouldServe } from '@vercel/build-utils';
|
||||||
|
import type { BuildV2, Files, ShouldServe } from '@vercel/build-utils';
|
||||||
|
|
||||||
export const version = 2;
|
export const version = 2;
|
||||||
|
|
||||||
@@ -39,3 +40,18 @@ export const build: BuildV2 = async ({ entrypoint, files, config }) => {
|
|||||||
|
|
||||||
return { output };
|
return { output };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const shouldServe: ShouldServe = _opts => {
|
||||||
|
const opts = { ..._opts };
|
||||||
|
const {
|
||||||
|
config: { zeroConfig, outputDirectory },
|
||||||
|
} = opts;
|
||||||
|
|
||||||
|
// Add the output directory prefix
|
||||||
|
if (zeroConfig && outputDirectory) {
|
||||||
|
opts.entrypoint = `${outputDirectory}/${opts.entrypoint}`;
|
||||||
|
opts.requestPath = `${outputDirectory}/${opts.requestPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultShouldServe(opts);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,412 +0,0 @@
|
|||||||
import chalk from 'chalk';
|
|
||||||
import execa from 'execa';
|
|
||||||
import semver from 'semver';
|
|
||||||
import npa from 'npm-package-arg';
|
|
||||||
import pluralize from 'pluralize';
|
|
||||||
import { basename, join } from 'path';
|
|
||||||
import XDGAppPaths from 'xdg-app-paths';
|
|
||||||
import { mkdirp, readJSON, writeJSON } from 'fs-extra';
|
|
||||||
import { NowBuildError, PackageJson } from '@vercel/build-utils';
|
|
||||||
import cliPkg from '../pkg';
|
|
||||||
|
|
||||||
import cmd from '../output/cmd';
|
|
||||||
import { Output } from '../output';
|
|
||||||
import { NoBuilderCacheError } from '../errors-ts';
|
|
||||||
|
|
||||||
import * as staticBuilder from './static-builder';
|
|
||||||
import { BuilderWithPackage } from './types';
|
|
||||||
import { isErrnoException } from '../is-error';
|
|
||||||
|
|
||||||
const require_: typeof require = eval('require');
|
|
||||||
|
|
||||||
const registryTypes = new Set(['version', 'tag', 'range']);
|
|
||||||
|
|
||||||
const createStaticBuilder = (scope: string): BuilderWithPackage => {
|
|
||||||
return {
|
|
||||||
runInProcess: true,
|
|
||||||
requirePath: `${scope}/static`,
|
|
||||||
builder: Object.freeze(staticBuilder),
|
|
||||||
package: Object.freeze({ name: `@${scope}/static`, version: '' }),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const localBuilders: { [key: string]: BuilderWithPackage } = {
|
|
||||||
'@now/static': createStaticBuilder('now'),
|
|
||||||
'@vercel/static': createStaticBuilder('vercel'),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const cacheDirPromise = prepareCacheDir();
|
|
||||||
export const builderDirPromise = prepareBuilderDir();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare cache directory for installing Vercel runtimes.
|
|
||||||
*/
|
|
||||||
export async function prepareCacheDir() {
|
|
||||||
const designated = XDGAppPaths('com.vercel.cli').cache();
|
|
||||||
|
|
||||||
if (!designated) {
|
|
||||||
throw new NoBuilderCacheError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const cacheDir = join(designated, 'dev');
|
|
||||||
await mkdirp(cacheDir);
|
|
||||||
return cacheDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function prepareBuilderDir() {
|
|
||||||
const builderDir = join(await cacheDirPromise, 'builders');
|
|
||||||
await mkdirp(builderDir);
|
|
||||||
|
|
||||||
// Create an empty `package.json` file, only if one does not already exist
|
|
||||||
try {
|
|
||||||
const buildersPkg = join(builderDir, 'package.json');
|
|
||||||
await writeJSON(buildersPkg, { private: true }, { flag: 'wx' });
|
|
||||||
} catch (err: unknown) {
|
|
||||||
if (!isErrnoException(err) || err.code !== 'EEXIST') {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builderDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNpmVersion(use = ''): string {
|
|
||||||
const parsed = npa(use);
|
|
||||||
if (registryTypes.has(parsed.type)) {
|
|
||||||
return parsed.fetchSpec || '';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBuildUtils(packages: string[]): string {
|
|
||||||
const version = packages
|
|
||||||
.map(getNpmVersion)
|
|
||||||
.some(ver => ver.includes('canary'))
|
|
||||||
? 'canary'
|
|
||||||
: 'latest';
|
|
||||||
|
|
||||||
return `@vercel/build-utils@${version}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseVersionSafe(rawSpec: string) {
|
|
||||||
try {
|
|
||||||
return semver.parse(rawSpec);
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function filterPackage(
|
|
||||||
builderSpec: string,
|
|
||||||
buildersPkg: PackageJson,
|
|
||||||
cliPkg: Partial<PackageJson>
|
|
||||||
) {
|
|
||||||
if (builderSpec in localBuilders) return false;
|
|
||||||
const parsed = npa(builderSpec);
|
|
||||||
const parsedVersion = parseVersionSafe(parsed.rawSpec);
|
|
||||||
|
|
||||||
// Skip install of Runtimes that are part of Vercel CLI's `dependencies`
|
|
||||||
if (isBundledBuilder(parsed, cliPkg)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip install of already installed Runtime with exact version match
|
|
||||||
if (
|
|
||||||
parsed.name &&
|
|
||||||
parsed.type === 'version' &&
|
|
||||||
parsedVersion &&
|
|
||||||
buildersPkg.dependencies &&
|
|
||||||
parsedVersion.version == buildersPkg.dependencies[parsed.name]
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Install a list of builders to the cache directory.
|
|
||||||
*/
|
|
||||||
export async function installBuilders(
|
|
||||||
packagesSet: Set<string>,
|
|
||||||
output: Output,
|
|
||||||
builderDir?: string
|
|
||||||
): Promise<void> {
|
|
||||||
const packages = Array.from(packagesSet);
|
|
||||||
if (
|
|
||||||
packages.length === 0 ||
|
|
||||||
(packages.length === 1 &&
|
|
||||||
Object.hasOwnProperty.call(localBuilders, packages[0]))
|
|
||||||
) {
|
|
||||||
// Static deployment, no builders to install
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!builderDir) {
|
|
||||||
builderDir = await builderDirPromise;
|
|
||||||
}
|
|
||||||
const buildersPkgPath = join(builderDir, 'package.json');
|
|
||||||
const buildersPkgBefore = await readJSON(buildersPkgPath);
|
|
||||||
const depsBefore = {
|
|
||||||
...buildersPkgBefore.devDependencies,
|
|
||||||
...buildersPkgBefore.dependencies,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter out any packages that come packaged with Vercel CLI
|
|
||||||
const packagesToInstall = packages.filter(p =>
|
|
||||||
filterPackage(p, buildersPkgBefore, cliPkg)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (packagesToInstall.length === 0) {
|
|
||||||
output.debug('No Runtimes need to be installed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
packagesToInstall.push(getBuildUtils(packages));
|
|
||||||
|
|
||||||
await npmInstall(builderDir, output, packagesToInstall, false);
|
|
||||||
|
|
||||||
const updatedPackages: string[] = [];
|
|
||||||
const buildersPkgAfter = await readJSON(buildersPkgPath);
|
|
||||||
const depsAfter = {
|
|
||||||
...buildersPkgAfter.devDependencies,
|
|
||||||
...buildersPkgAfter.dependencies,
|
|
||||||
};
|
|
||||||
for (const [name, version] of Object.entries(depsAfter)) {
|
|
||||||
if (version !== depsBefore[name]) {
|
|
||||||
output.debug(`Runtime "${name}" updated to version \`${version}\``);
|
|
||||||
updatedPackages.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
purgeRequireCache(updatedPackages, builderDir, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function npmInstall(
|
|
||||||
cwd: string,
|
|
||||||
output: Output,
|
|
||||||
packagesToInstall: string[],
|
|
||||||
silent: boolean
|
|
||||||
) {
|
|
||||||
const sortedPackages = packagesToInstall.sort();
|
|
||||||
|
|
||||||
if (!silent) {
|
|
||||||
output.spinner(
|
|
||||||
`Installing ${pluralize(
|
|
||||||
'Runtime',
|
|
||||||
sortedPackages.length
|
|
||||||
)}: ${sortedPackages.join(', ')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.debug(`Running npm install in ${cwd}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const args = [
|
|
||||||
'install',
|
|
||||||
'--save-exact',
|
|
||||||
'--no-package-lock',
|
|
||||||
'--no-audit',
|
|
||||||
'--no-progress',
|
|
||||||
];
|
|
||||||
if (process.stderr.isTTY) {
|
|
||||||
// Force colors in the npm child process
|
|
||||||
// https://docs.npmjs.com/misc/config#color
|
|
||||||
args.push('--color=always');
|
|
||||||
}
|
|
||||||
args.push(...sortedPackages);
|
|
||||||
const result = await execa('npm', args, {
|
|
||||||
cwd,
|
|
||||||
reject: false,
|
|
||||||
stdio: output.isDebugEnabled() ? 'inherit' : 'pipe',
|
|
||||||
});
|
|
||||||
if (result.failed) {
|
|
||||||
output.stopSpinner();
|
|
||||||
if (result.stdout) {
|
|
||||||
console.log(result.stdout);
|
|
||||||
}
|
|
||||||
if (result.stderr) {
|
|
||||||
console.error(result.stderr);
|
|
||||||
}
|
|
||||||
throw new NowBuildError({
|
|
||||||
message:
|
|
||||||
(result as any).code === 'ENOENT'
|
|
||||||
? `Command not found: ${chalk.cyan(
|
|
||||||
'npm'
|
|
||||||
)}\nPlease ensure that ${cmd('npm')} is properly installed`
|
|
||||||
: 'Failed to install `vercel dev` dependencies',
|
|
||||||
code: 'NPM_INSTALL_ERROR',
|
|
||||||
link: 'https://vercel.link/npm-install-failed-dev',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
output.stopSpinner();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateBuilders(
|
|
||||||
packagesSet: Set<string>,
|
|
||||||
output: Output,
|
|
||||||
builderDir?: string
|
|
||||||
): Promise<string[]> {
|
|
||||||
if (!builderDir) {
|
|
||||||
builderDir = await builderDirPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedPackages: string[] = [];
|
|
||||||
const packages = Array.from(packagesSet);
|
|
||||||
const buildersPkgPath = join(builderDir, 'package.json');
|
|
||||||
const buildersPkgBefore = await readJSON(buildersPkgPath);
|
|
||||||
const depsBefore = {
|
|
||||||
...buildersPkgBefore.devDependencies,
|
|
||||||
...buildersPkgBefore.dependencies,
|
|
||||||
};
|
|
||||||
|
|
||||||
const packagesToUpdate = packages.filter(p => {
|
|
||||||
if (p in localBuilders) return false;
|
|
||||||
|
|
||||||
// If it's a builder that is part of Vercel CLI's
|
|
||||||
// `dependencies` then don't update it
|
|
||||||
if (isBundledBuilder(npa(p), cliPkg)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (packagesToUpdate.length > 0) {
|
|
||||||
packagesToUpdate.push(getBuildUtils(packages));
|
|
||||||
|
|
||||||
await npmInstall(builderDir, output, packagesToUpdate, true);
|
|
||||||
|
|
||||||
const buildersPkgAfter = await readJSON(buildersPkgPath);
|
|
||||||
const depsAfter = {
|
|
||||||
...buildersPkgAfter.devDependencies,
|
|
||||||
...buildersPkgAfter.dependencies,
|
|
||||||
};
|
|
||||||
for (const [name, version] of Object.entries(depsAfter)) {
|
|
||||||
if (version !== depsBefore[name]) {
|
|
||||||
output.debug(`Runtime "${name}" updated to version \`${version}\``);
|
|
||||||
updatedPackages.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
purgeRequireCache(updatedPackages, builderDir, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedPackages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a builder from the cache directory.
|
|
||||||
*/
|
|
||||||
export async function getBuilder(
|
|
||||||
builderPkg: string,
|
|
||||||
output: Output,
|
|
||||||
builderDir?: string,
|
|
||||||
isRetry = false
|
|
||||||
): Promise<BuilderWithPackage> {
|
|
||||||
let builderWithPkg: BuilderWithPackage = localBuilders[builderPkg];
|
|
||||||
if (!builderWithPkg) {
|
|
||||||
if (!builderDir) {
|
|
||||||
builderDir = await builderDirPromise;
|
|
||||||
}
|
|
||||||
let requirePath: string;
|
|
||||||
const parsed = npa(builderPkg);
|
|
||||||
|
|
||||||
// First check if it's a bundled Runtime in Vercel CLI's `node_modules`
|
|
||||||
const bundledBuilder = isBundledBuilder(parsed, cliPkg);
|
|
||||||
if (bundledBuilder && parsed.name) {
|
|
||||||
requirePath = parsed.name;
|
|
||||||
} else {
|
|
||||||
const buildersPkg = await readJSON(join(builderDir, 'package.json'));
|
|
||||||
const pkgName = getPackageName(parsed, buildersPkg) || builderPkg;
|
|
||||||
requirePath = join(builderDir, 'node_modules', pkgName);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
output.debug(`Requiring runtime: "${requirePath}"`);
|
|
||||||
const mod = require_(requirePath);
|
|
||||||
const pkg = require_(join(requirePath, 'package.json'));
|
|
||||||
builderWithPkg = {
|
|
||||||
requirePath,
|
|
||||||
builder: Object.freeze(mod),
|
|
||||||
package: Object.freeze(pkg),
|
|
||||||
};
|
|
||||||
} catch (err: unknown) {
|
|
||||||
if (
|
|
||||||
isErrnoException(err) &&
|
|
||||||
err.code === 'MODULE_NOT_FOUND' &&
|
|
||||||
!isRetry
|
|
||||||
) {
|
|
||||||
output.debug(
|
|
||||||
`Attempted to require ${requirePath}, but it is not installed`
|
|
||||||
);
|
|
||||||
const pkgSet = new Set([builderPkg]);
|
|
||||||
await installBuilders(pkgSet, output, builderDir);
|
|
||||||
|
|
||||||
// Run `getBuilder()` again now that the builder has been installed
|
|
||||||
return getBuilder(builderPkg, output, builderDir, true);
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's a bundled builder, then cache the require call
|
|
||||||
if (bundledBuilder) {
|
|
||||||
localBuilders[builderPkg] = builderWithPkg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builderWithPkg;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isBundledBuilder(
|
|
||||||
parsed: npa.Result,
|
|
||||||
{ dependencies = {} }: PackageJson
|
|
||||||
): boolean {
|
|
||||||
if (!parsed.name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const inCliDependencyList = !!dependencies[parsed.name];
|
|
||||||
const inScope = parsed.scope === '@vercel';
|
|
||||||
const isVersionedReference = ['tag', 'version', 'range'].includes(
|
|
||||||
parsed.type
|
|
||||||
);
|
|
||||||
|
|
||||||
return inCliDependencyList && inScope && isVersionedReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPackageName(
|
|
||||||
parsed: npa.Result,
|
|
||||||
buildersPkg: PackageJson
|
|
||||||
): string | null {
|
|
||||||
if (registryTypes.has(parsed.type)) {
|
|
||||||
return parsed.name;
|
|
||||||
}
|
|
||||||
const deps: PackageJson.DependencyMap = {
|
|
||||||
...buildersPkg.devDependencies,
|
|
||||||
...buildersPkg.dependencies,
|
|
||||||
};
|
|
||||||
for (const [name, dep] of Object.entries(deps)) {
|
|
||||||
if (dep === parsed.raw || basename(dep) === basename(parsed.raw)) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function purgeRequireCache(
|
|
||||||
packages: string[],
|
|
||||||
builderDir: string,
|
|
||||||
output: Output
|
|
||||||
) {
|
|
||||||
// The `require()` cache for the builder's assets must be purged
|
|
||||||
const packagesPaths = packages.map(b => join(builderDir, 'node_modules', b));
|
|
||||||
for (const id of Object.keys(require_.cache)) {
|
|
||||||
for (const path of packagesPaths) {
|
|
||||||
if (id.startsWith(path)) {
|
|
||||||
output.debug(`Purging require cache for "${id}"`);
|
|
||||||
delete require_.cache[id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
FileFsRef,
|
FileFsRef,
|
||||||
normalizePath,
|
normalizePath,
|
||||||
} from '@vercel/build-utils';
|
} from '@vercel/build-utils';
|
||||||
import { isOfficialRuntime } from '@vercel/fs-detectors';
|
import { isStaticRuntime } from '@vercel/fs-detectors';
|
||||||
import plural from 'pluralize';
|
import plural from 'pluralize';
|
||||||
import minimatch from 'minimatch';
|
import minimatch from 'minimatch';
|
||||||
|
|
||||||
@@ -26,7 +26,6 @@ import { relative } from '../path-helpers';
|
|||||||
import { LambdaSizeExceededError } from '../errors-ts';
|
import { LambdaSizeExceededError } from '../errors-ts';
|
||||||
|
|
||||||
import DevServer from './server';
|
import DevServer from './server';
|
||||||
import { getBuilder } from './builder-cache';
|
|
||||||
import {
|
import {
|
||||||
VercelConfig,
|
VercelConfig,
|
||||||
BuildMatch,
|
BuildMatch,
|
||||||
@@ -41,6 +40,7 @@ import {
|
|||||||
import { normalizeRoutes } from '@vercel/routing-utils';
|
import { normalizeRoutes } from '@vercel/routing-utils';
|
||||||
import getUpdateCommand from '../get-update-command';
|
import getUpdateCommand from '../get-update-command';
|
||||||
import { getTitleName } from '../pkg-name';
|
import { getTitleName } from '../pkg-name';
|
||||||
|
import { importBuilders } from '../build/import-builders';
|
||||||
|
|
||||||
interface BuildMessage {
|
interface BuildMessage {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -108,18 +108,18 @@ export async function executeBuild(
|
|||||||
filesRemoved?: string[]
|
filesRemoved?: string[]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const {
|
const {
|
||||||
builderWithPkg: { runInProcess, requirePath, builder, package: pkg },
|
builderWithPkg: { path: requirePath, builder, pkg },
|
||||||
} = match;
|
} = match;
|
||||||
const { entrypoint } = match;
|
const { entrypoint, use } = match;
|
||||||
|
const isStatic = isStaticRuntime(use);
|
||||||
const { envConfigs, cwd: workPath, devCacheDir } = devServer;
|
const { envConfigs, cwd: workPath, devCacheDir } = devServer;
|
||||||
const debug = devServer.output.isDebugEnabled();
|
const debug = devServer.output.isDebugEnabled();
|
||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const showBuildTimestamp =
|
const showBuildTimestamp = !isStatic && (!isInitialBuild || debug);
|
||||||
!isOfficialRuntime('static', match.use) && (!isInitialBuild || debug);
|
|
||||||
|
|
||||||
if (showBuildTimestamp) {
|
if (showBuildTimestamp) {
|
||||||
devServer.output.log(`Building ${match.use}:${entrypoint}`);
|
devServer.output.log(`Building ${use}:${entrypoint}`);
|
||||||
devServer.output.debug(
|
devServer.output.debug(
|
||||||
`Using \`${pkg.name}${pkg.version ? `@${pkg.version}` : ''}\``
|
`Using \`${pkg.name}${pkg.version ? `@${pkg.version}` : ''}\``
|
||||||
);
|
);
|
||||||
@@ -130,7 +130,7 @@ export async function executeBuild(
|
|||||||
let result: BuildResult;
|
let result: BuildResult;
|
||||||
|
|
||||||
let { buildProcess } = match;
|
let { buildProcess } = match;
|
||||||
if (!runInProcess && !buildProcess) {
|
if (!isStatic && !buildProcess) {
|
||||||
buildProcess = await createBuildProcess(
|
buildProcess = await createBuildProcess(
|
||||||
match,
|
match,
|
||||||
envConfigs,
|
envConfigs,
|
||||||
@@ -158,7 +158,7 @@ export async function executeBuild(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let buildResultOrOutputs: BuilderOutputs | BuildResult | BuildResultV3;
|
let buildResultOrOutputs;
|
||||||
if (buildProcess) {
|
if (buildProcess) {
|
||||||
buildProcess.send({
|
buildProcess.send({
|
||||||
type: 'build',
|
type: 'build',
|
||||||
@@ -198,16 +198,12 @@ export async function executeBuild(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort out build result to builder v2 shape
|
// Sort out build result to builder v2 shape
|
||||||
if (!builder.version || builder.version === 1) {
|
if (!builder.version || (builder as any).version === 1) {
|
||||||
// `BuilderOutputs` map was returned (Now Builder v1 behavior)
|
// `BuilderOutputs` map was returned (Now Builder v1 behavior)
|
||||||
result = {
|
result = {
|
||||||
output: buildResultOrOutputs as BuilderOutputs,
|
output: buildResultOrOutputs as BuilderOutputs,
|
||||||
routes: [],
|
routes: [],
|
||||||
watch: [],
|
watch: [],
|
||||||
distPath:
|
|
||||||
typeof buildResultOrOutputs.distPath === 'string'
|
|
||||||
? buildResultOrOutputs.distPath
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
} else if (builder.version === 2) {
|
} else if (builder.version === 2) {
|
||||||
result = buildResultOrOutputs as BuildResult;
|
result = buildResultOrOutputs as BuildResult;
|
||||||
@@ -253,7 +249,7 @@ export async function executeBuild(
|
|||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${getTitleName()} CLI does not support builder version ${
|
`${getTitleName()} CLI does not support builder version ${
|
||||||
builder.version
|
(builder as any).version
|
||||||
}.\nPlease run \`${await getUpdateCommand()}\` to update to the latest CLI.`
|
}.\nPlease run \`${await getUpdateCommand()}\` to update to the latest CLI.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -383,7 +379,7 @@ export async function executeBuild(
|
|||||||
if (showBuildTimestamp) {
|
if (showBuildTimestamp) {
|
||||||
const endTime = Date.now();
|
const endTime = Date.now();
|
||||||
devServer.output.log(
|
devServer.output.log(
|
||||||
`Built ${match.use}:${entrypoint} [${ms(endTime - startTime)}]`
|
`Built ${use}:${entrypoint} [${ms(endTime - startTime)}]`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,6 +401,8 @@ export async function getBuildMatches(
|
|||||||
|
|
||||||
const noMatches: Builder[] = [];
|
const noMatches: Builder[] = [];
|
||||||
const builds = vercelConfig.builds || [{ src: '**', use: '@vercel/static' }];
|
const builds = vercelConfig.builds || [{ src: '**', use: '@vercel/static' }];
|
||||||
|
const builderSpecs = new Set(builds.map(b => b.use).filter(Boolean));
|
||||||
|
const buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
|
||||||
|
|
||||||
for (const buildConfig of builds) {
|
for (const buildConfig of builds) {
|
||||||
let { src = '**', use, config = {} } = buildConfig;
|
let { src = '**', use, config = {} } = buildConfig;
|
||||||
@@ -439,6 +437,8 @@ export async function getBuildMatches(
|
|||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
src = relative(cwd, file);
|
src = relative(cwd, file);
|
||||||
|
|
||||||
|
const entrypoint = mapToEntrypoint.get(src) || src;
|
||||||
|
|
||||||
// Remove the output directory prefix
|
// Remove the output directory prefix
|
||||||
if (config.zeroConfig && config.outputDirectory) {
|
if (config.zeroConfig && config.outputDirectory) {
|
||||||
const outputMatch = config.outputDirectory + '/';
|
const outputMatch = config.outputDirectory + '/';
|
||||||
@@ -447,11 +447,15 @@ export async function getBuildMatches(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const builderWithPkg = await getBuilder(use, output);
|
const builderWithPkg = buildersWithPkgs.get(use);
|
||||||
|
if (!builderWithPkg) {
|
||||||
|
throw new Error(`Failed to load Builder "${use}"`);
|
||||||
|
}
|
||||||
|
|
||||||
matches.push({
|
matches.push({
|
||||||
...buildConfig,
|
...buildConfig,
|
||||||
src,
|
src,
|
||||||
entrypoint: mapToEntrypoint.get(src) || src,
|
entrypoint,
|
||||||
builderWithPkg,
|
builderWithPkg,
|
||||||
buildOutput: {},
|
buildOutput: {},
|
||||||
buildResults: new Map(),
|
buildResults: new Map(),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import ms from 'ms';
|
|
||||||
import url, { URL } from 'url';
|
import url, { URL } from 'url';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
@@ -62,11 +61,6 @@ import { devRouter, getRoutesTypes } from './router';
|
|||||||
import getMimeType from './mime-type';
|
import getMimeType from './mime-type';
|
||||||
import { executeBuild, getBuildMatches, shutdownBuilder } from './builder';
|
import { executeBuild, getBuildMatches, shutdownBuilder } from './builder';
|
||||||
import { generateErrorMessage, generateHttpStatusDescription } from './errors';
|
import { generateErrorMessage, generateHttpStatusDescription } from './errors';
|
||||||
import {
|
|
||||||
installBuilders,
|
|
||||||
updateBuilders,
|
|
||||||
builderDirPromise,
|
|
||||||
} from './builder-cache';
|
|
||||||
|
|
||||||
// HTML templates
|
// HTML templates
|
||||||
import errorTemplate from './templates/error';
|
import errorTemplate from './templates/error';
|
||||||
@@ -171,8 +165,6 @@ export default class DevServer {
|
|||||||
private vercelConfigWarning: boolean;
|
private vercelConfigWarning: boolean;
|
||||||
private getVercelConfigPromise: Promise<VercelConfig> | null;
|
private getVercelConfigPromise: Promise<VercelConfig> | null;
|
||||||
private blockingBuildsPromise: Promise<void> | null;
|
private blockingBuildsPromise: Promise<void> | null;
|
||||||
private updateBuildersPromise: Promise<void> | null;
|
|
||||||
private updateBuildersTimeout: NodeJS.Timeout | undefined;
|
|
||||||
private startPromise: Promise<void> | null;
|
private startPromise: Promise<void> | null;
|
||||||
|
|
||||||
private systemEnvValues: string[];
|
private systemEnvValues: string[];
|
||||||
@@ -211,7 +203,6 @@ export default class DevServer {
|
|||||||
this.vercelConfigWarning = false;
|
this.vercelConfigWarning = false;
|
||||||
this.getVercelConfigPromise = null;
|
this.getVercelConfigPromise = null;
|
||||||
this.blockingBuildsPromise = null;
|
this.blockingBuildsPromise = null;
|
||||||
this.updateBuildersPromise = null;
|
|
||||||
this.startPromise = null;
|
this.startPromise = null;
|
||||||
|
|
||||||
this.watchAggregationId = null;
|
this.watchAggregationId = null;
|
||||||
@@ -493,33 +484,6 @@ export default class DevServer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async invalidateBuildMatches(
|
|
||||||
vercelConfig: VercelConfig,
|
|
||||||
updatedBuilders: string[]
|
|
||||||
): Promise<void> {
|
|
||||||
if (updatedBuilders.length === 0) {
|
|
||||||
this.output.debug('No builders were updated');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete any build matches that have the old builder required already
|
|
||||||
for (const buildMatch of this.buildMatches.values()) {
|
|
||||||
const {
|
|
||||||
src,
|
|
||||||
builderWithPkg: { package: pkg },
|
|
||||||
} = buildMatch;
|
|
||||||
if (isOfficialRuntime('static', pkg.name)) continue;
|
|
||||||
if (pkg.name && updatedBuilders.includes(pkg.name)) {
|
|
||||||
shutdownBuilder(buildMatch, this.output);
|
|
||||||
this.buildMatches.delete(src);
|
|
||||||
this.output.debug(`Invalidated build match for "${src}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-add the build matches that were just removed, but with the new builder
|
|
||||||
await this.updateBuildMatches(vercelConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLocalEnv(fileName: string, base?: Env): Promise<Env> {
|
async getLocalEnv(fileName: string, base?: Env): Promise<Env> {
|
||||||
// TODO: use the file watcher to only invalidate the env `dotfile`
|
// TODO: use the file watcher to only invalidate the env `dotfile`
|
||||||
// once a change to the `fileName` occurs
|
// once a change to the `fileName` occurs
|
||||||
@@ -948,30 +912,8 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const builders = new Set<string>(
|
|
||||||
(vercelConfig.builds || [])
|
|
||||||
.filter((b: Builder) => b.use)
|
|
||||||
.map((b: Builder) => b.use)
|
|
||||||
);
|
|
||||||
|
|
||||||
await installBuilders(builders, this.output);
|
|
||||||
await this.updateBuildMatches(vercelConfig, true);
|
await this.updateBuildMatches(vercelConfig, true);
|
||||||
|
|
||||||
// Updating builders happens lazily, and any builders that were updated
|
|
||||||
// get their "build matches" invalidated so that the new version is used.
|
|
||||||
this.updateBuildersTimeout = setTimeout(() => {
|
|
||||||
this.updateBuildersPromise = updateBuilders(builders, this.output)
|
|
||||||
.then(updatedBuilders => {
|
|
||||||
this.updateBuildersPromise = null;
|
|
||||||
this.invalidateBuildMatches(vercelConfig, updatedBuilders);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
this.updateBuildersPromise = null;
|
|
||||||
this.output.prettyError(err);
|
|
||||||
this.output.debug(err.stack);
|
|
||||||
});
|
|
||||||
}, ms('30s'));
|
|
||||||
|
|
||||||
// Builders that do not define a `shouldServe()` function need to be
|
// Builders that do not define a `shouldServe()` function need to be
|
||||||
// executed at boot-up time in order to get the initial assets and/or routes
|
// executed at boot-up time in order to get the initial assets and/or routes
|
||||||
// that can be served by the builder.
|
// that can be served by the builder.
|
||||||
@@ -1047,16 +989,11 @@ export default class DevServer {
|
|||||||
* Shuts down the `vercel dev` server, and cleans up any temporary resources.
|
* Shuts down the `vercel dev` server, and cleans up any temporary resources.
|
||||||
*/
|
*/
|
||||||
async stop(exitCode?: number): Promise<void> {
|
async stop(exitCode?: number): Promise<void> {
|
||||||
const { devProcess } = this;
|
|
||||||
const { debug } = this.output;
|
|
||||||
if (this.stopping) return;
|
if (this.stopping) return;
|
||||||
|
|
||||||
this.stopping = true;
|
this.stopping = true;
|
||||||
|
|
||||||
if (typeof this.updateBuildersTimeout !== 'undefined') {
|
const { devProcess } = this;
|
||||||
clearTimeout(this.updateBuildersTimeout);
|
const { debug } = this.output;
|
||||||
}
|
|
||||||
|
|
||||||
const ops: Promise<any>[] = [];
|
const ops: Promise<any>[] = [];
|
||||||
|
|
||||||
for (const match of this.buildMatches.values()) {
|
for (const match of this.buildMatches.values()) {
|
||||||
@@ -1074,18 +1011,10 @@ export default class DevServer {
|
|||||||
ops.push(this.watcher.close());
|
ops.push(this.watcher.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.updateBuildersPromise) {
|
|
||||||
debug(`Waiting for builders update to complete`);
|
|
||||||
ops.push(this.updateBuildersPromise);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const pid of this.devServerPids) {
|
for (const pid of this.devServerPids) {
|
||||||
ops.push(this.killBuilderDevServer(pid));
|
ops.push(this.killBuilderDevServer(pid));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the builders module cache is created
|
|
||||||
ops.push(builderDirPromise);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(ops);
|
await Promise.all(ops);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
@@ -1484,8 +1413,9 @@ export default class DevServer {
|
|||||||
// the middleware server for every HTTP request?
|
// the middleware server for every HTTP request?
|
||||||
const { envConfigs, files, devCacheDir, cwd: workPath } = this;
|
const { envConfigs, files, devCacheDir, cwd: workPath } = this;
|
||||||
try {
|
try {
|
||||||
startMiddlewareResult =
|
const { builder } = middleware.builderWithPkg;
|
||||||
await middleware.builderWithPkg.builder.startDevServer?.({
|
if (builder.version === 3) {
|
||||||
|
startMiddlewareResult = await builder.startDevServer?.({
|
||||||
files,
|
files,
|
||||||
entrypoint: middleware.entrypoint,
|
entrypoint: middleware.entrypoint,
|
||||||
workPath,
|
workPath,
|
||||||
@@ -1499,6 +1429,7 @@ export default class DevServer {
|
|||||||
buildEnv: { ...envConfigs.buildEnv },
|
buildEnv: { ...envConfigs.buildEnv },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (startMiddlewareResult) {
|
if (startMiddlewareResult) {
|
||||||
const { port, pid } = startMiddlewareResult;
|
const { port, pid } = startMiddlewareResult;
|
||||||
@@ -1911,8 +1842,8 @@ export default class DevServer {
|
|||||||
// up a single-serve dev HTTP server that vercel dev will proxy this HTTP request
|
// up a single-serve dev HTTP server that vercel dev will proxy this HTTP request
|
||||||
// to. Once the proxied request is finished, vercel dev shuts down the dev
|
// to. Once the proxied request is finished, vercel dev shuts down the dev
|
||||||
// server child process.
|
// server child process.
|
||||||
const { builder, package: builderPkg } = match.builderWithPkg;
|
const { builder, pkg: builderPkg } = match.builderWithPkg;
|
||||||
if (typeof builder.startDevServer === 'function') {
|
if (builder.version === 3 && typeof builder.startDevServer === 'function') {
|
||||||
let devServerResult: StartDevServerResult = null;
|
let devServerResult: StartDevServerResult = null;
|
||||||
try {
|
try {
|
||||||
const { envConfigs, files, devCacheDir, cwd: workPath } = this;
|
const { envConfigs, files, devCacheDir, cwd: workPath } = this;
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import {
|
|
||||||
FileFsRef,
|
|
||||||
BuildOptions,
|
|
||||||
shouldServe as defaultShouldServe,
|
|
||||||
ShouldServeOptions,
|
|
||||||
} from '@vercel/build-utils';
|
|
||||||
import { BuildResult } from './types';
|
|
||||||
|
|
||||||
export const version = 2;
|
|
||||||
|
|
||||||
export function build({
|
|
||||||
files,
|
|
||||||
entrypoint,
|
|
||||||
config: { zeroConfig, outputDirectory },
|
|
||||||
}: BuildOptions): BuildResult {
|
|
||||||
const path =
|
|
||||||
zeroConfig && outputDirectory
|
|
||||||
? `${outputDirectory}/${entrypoint}`
|
|
||||||
: entrypoint;
|
|
||||||
return {
|
|
||||||
output: {
|
|
||||||
[entrypoint]: files[path] as FileFsRef,
|
|
||||||
},
|
|
||||||
routes: [],
|
|
||||||
watch: [path],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldServe(_opts: ShouldServeOptions) {
|
|
||||||
const opts = { ..._opts };
|
|
||||||
let {
|
|
||||||
config: { zeroConfig, outputDirectory },
|
|
||||||
} = opts;
|
|
||||||
|
|
||||||
// Add the output directory prefix
|
|
||||||
if (zeroConfig && outputDirectory) {
|
|
||||||
opts.entrypoint = `${outputDirectory}/${opts.entrypoint}`;
|
|
||||||
opts.requestPath = `${outputDirectory}/${opts.requestPath}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultShouldServe(opts);
|
|
||||||
}
|
|
||||||
@@ -12,12 +12,12 @@ import {
|
|||||||
FileBlob,
|
FileBlob,
|
||||||
FileFsRef,
|
FileFsRef,
|
||||||
Lambda,
|
Lambda,
|
||||||
PackageJson,
|
|
||||||
} from '@vercel/build-utils';
|
} from '@vercel/build-utils';
|
||||||
import { VercelConfig } from '@vercel/client';
|
import { VercelConfig } from '@vercel/client';
|
||||||
import { HandleValue, Route } from '@vercel/routing-utils';
|
import { HandleValue, Route } from '@vercel/routing-utils';
|
||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
import { ProjectEnvVariable, ProjectSettings } from '../../types';
|
import { ProjectEnvVariable, ProjectSettings } from '../../types';
|
||||||
|
import { BuilderWithPkg } from '../build/import-builders';
|
||||||
|
|
||||||
export { VercelConfig };
|
export { VercelConfig };
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ export interface EnvConfigs {
|
|||||||
export interface BuildMatch extends BuildConfig {
|
export interface BuildMatch extends BuildConfig {
|
||||||
entrypoint: string;
|
entrypoint: string;
|
||||||
src: string;
|
src: string;
|
||||||
builderWithPkg: BuilderWithPackage;
|
builderWithPkg: BuilderWithPkg;
|
||||||
buildOutput: BuilderOutputs;
|
buildOutput: BuilderOutputs;
|
||||||
buildResults: Map<string | null, BuildResult>;
|
buildResults: Map<string | null, BuildResult>;
|
||||||
buildTimestamp: number;
|
buildTimestamp: number;
|
||||||
@@ -122,13 +122,6 @@ export interface BuildResultV4 {
|
|||||||
distPath?: string;
|
distPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BuilderWithPackage {
|
|
||||||
runInProcess?: boolean;
|
|
||||||
requirePath: string;
|
|
||||||
builder: Readonly<Builder>;
|
|
||||||
package: Readonly<PackageJson>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HttpHeadersConfig {
|
export interface HttpHeadersConfig {
|
||||||
[name: string]: string;
|
[name: string]: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import url from 'url';
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import listen from 'async-listen';
|
import listen from 'async-listen';
|
||||||
|
import stripAnsi from 'strip-ansi';
|
||||||
import { createServer } from 'http';
|
import { createServer } from 'http';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -723,12 +724,15 @@ test('[vercel dev] should support custom 404 routes', async () => {
|
|||||||
test('[vercel dev] prints `npm install` errors', async () => {
|
test('[vercel dev] prints `npm install` errors', async () => {
|
||||||
const dir = fixture('runtime-not-installed');
|
const dir = fixture('runtime-not-installed');
|
||||||
const result = await exec(dir);
|
const result = await exec(dir);
|
||||||
expect(result.stderr.includes('npm ERR! 404')).toBeTruthy();
|
|
||||||
expect(
|
expect(
|
||||||
result.stderr.includes('Failed to install `vercel dev` dependencies')
|
stripAnsi(result.stderr).includes(
|
||||||
|
'Error: The package `@vercel/does-not-exist` is not published on the npm registry'
|
||||||
|
)
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(
|
expect(
|
||||||
result.stderr.includes('https://vercel.link/npm-install-failed-dev')
|
result.stderr.includes(
|
||||||
|
'https://vercel.link/builder-dependencies-install-failed'
|
||||||
|
)
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,235 +0,0 @@
|
|||||||
import npa from 'npm-package-arg';
|
|
||||||
import {
|
|
||||||
filterPackage,
|
|
||||||
getBuildUtils,
|
|
||||||
isBundledBuilder,
|
|
||||||
} from '../../../../src/util/dev/builder-cache';
|
|
||||||
|
|
||||||
describe('filterPackage', () => {
|
|
||||||
const cliPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'@vercel/build-utils': '0.0.1',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should filter package that does not appear in CLI package.json', () => {
|
|
||||||
const result = filterPackage('@vercel/other', {}, cliPkg);
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not filter "latest", cached canary', () => {
|
|
||||||
const buildersPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'@vercel/build-utils': '0.0.1-canary.0',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = filterPackage('@vercel/build-utils', buildersPkg, cliPkg);
|
|
||||||
expect(result).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter install "canary", cached stable', () => {
|
|
||||||
const buildersPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'@vercel/build-utils': '0.0.1',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = filterPackage(
|
|
||||||
'@vercel/build-utils@canary',
|
|
||||||
buildersPkg,
|
|
||||||
cliPkg
|
|
||||||
);
|
|
||||||
expect(result).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter install "latest", cached stable', () => {
|
|
||||||
const buildersPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'@vercel/build-utils': '0.0.1',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = filterPackage('@vercel/build-utils', buildersPkg, cliPkg);
|
|
||||||
expect(result).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter install "canary", cached canary', () => {
|
|
||||||
const buildersPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'@vercel/build-utils': '0.0.1-canary.0',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = filterPackage(
|
|
||||||
'@vercel/build-utils@canary',
|
|
||||||
buildersPkg,
|
|
||||||
cliPkg
|
|
||||||
);
|
|
||||||
expect(result).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter install URL, cached stable', () => {
|
|
||||||
const buildersPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'@vercel/build-utils': '0.0.1',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = filterPackage('https://tarball.now.sh', buildersPkg, cliPkg);
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter install URL, cached canary', () => {
|
|
||||||
const buildersPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'@vercel/build-utils': '0.0.1-canary.0',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = filterPackage('https://tarball.now.sh', buildersPkg, cliPkg);
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter install "latest", cached URL - canary', () => {
|
|
||||||
const buildersPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'@vercel/build-utils': 'https://tarball.now.sh',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = filterPackage('@vercel/build-utils', buildersPkg, cliPkg);
|
|
||||||
expect(result).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter install not bundled version, cached same version', () => {
|
|
||||||
const buildersPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'not-bundled-package': '0.0.1',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = filterPackage(
|
|
||||||
'not-bundled-package@0.0.1',
|
|
||||||
buildersPkg,
|
|
||||||
cliPkg
|
|
||||||
);
|
|
||||||
expect(result).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter install not bundled version, cached different version', () => {
|
|
||||||
const buildersPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'not-bundled-package': '0.0.9',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = filterPackage(
|
|
||||||
'not-bundled-package@0.0.1',
|
|
||||||
buildersPkg,
|
|
||||||
cliPkg
|
|
||||||
);
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter install not bundled stable, cached version', () => {
|
|
||||||
const buildersPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'not-bundled-package': '0.0.1',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = filterPackage('not-bundled-package', buildersPkg, cliPkg);
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter install not bundled tagged, cached tagged', () => {
|
|
||||||
const buildersPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'not-bundled-package': '16.9.0-alpha.0',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = filterPackage(
|
|
||||||
'not-bundled-package@alpha',
|
|
||||||
buildersPkg,
|
|
||||||
cliPkg
|
|
||||||
);
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getBuildUtils', () => {
|
|
||||||
const tests: [string[], string][] = [
|
|
||||||
[['@vercel/static', '@vercel/node@canary'], 'canary'],
|
|
||||||
[['@vercel/static', '@vercel/node@0.7.4-canary.0'], 'canary'],
|
|
||||||
[['@vercel/static', '@vercel/node@0.8.0'], 'latest'],
|
|
||||||
[['@vercel/static', '@vercel/node'], 'latest'],
|
|
||||||
[['@vercel/static'], 'latest'],
|
|
||||||
[['@vercel/md@canary'], 'canary'],
|
|
||||||
[['custom-builder'], 'latest'],
|
|
||||||
[['custom-builder@canary'], 'canary'],
|
|
||||||
[['canary-bird'], 'latest'],
|
|
||||||
[['canary-bird@4.0.0'], 'latest'],
|
|
||||||
[['canary-bird@canary'], 'canary'],
|
|
||||||
[['@canary/bird'], 'latest'],
|
|
||||||
[['@canary/bird@0.1.0'], 'latest'],
|
|
||||||
[['@canary/bird@canary'], 'canary'],
|
|
||||||
[['https://example.com'], 'latest'],
|
|
||||||
[[''], 'latest'],
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const [input, expected] of tests) {
|
|
||||||
it(`should install "${expected}" with input ${JSON.stringify(
|
|
||||||
input
|
|
||||||
)}`, () => {
|
|
||||||
const result = getBuildUtils(input);
|
|
||||||
expect(result).toEqual(`@vercel/build-utils@${expected}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('isBundledBuilder', () => {
|
|
||||||
const cliPkg = {
|
|
||||||
dependencies: {
|
|
||||||
'@vercel/node': '0.0.1',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should not detect when dependency does not appear in CLI package.json', () => {
|
|
||||||
const parsed = npa('@vercel/node');
|
|
||||||
const result = isBundledBuilder(parsed, {});
|
|
||||||
expect(result).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should detect "canary" tagged releases', () => {
|
|
||||||
const parsed = npa('@vercel/node@canary');
|
|
||||||
const result = isBundledBuilder(parsed, cliPkg);
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should detect "canary" versioned releases', () => {
|
|
||||||
const parsed = npa('@vercel/node@1.6.1-canary.0');
|
|
||||||
const result = isBundledBuilder(parsed, cliPkg);
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should detect latest releases', () => {
|
|
||||||
const parsed = npa('@vercel/node');
|
|
||||||
const result = isBundledBuilder(parsed, cliPkg);
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should detect "latest" tagged releases', () => {
|
|
||||||
const parsed = npa('@vercel/node@latest');
|
|
||||||
const result = isBundledBuilder(parsed, cliPkg);
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should detect versioned releases', () => {
|
|
||||||
const parsed = npa('@vercel/node@1.6.1');
|
|
||||||
const result = isBundledBuilder(parsed, cliPkg);
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should NOT detect URL releases', () => {
|
|
||||||
const parsed = npa('https://example.com');
|
|
||||||
const result = isBundledBuilder(parsed, cliPkg);
|
|
||||||
expect(result).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should NOT detect git url releases', () => {
|
|
||||||
const parsed = npa('git://example.com/repo.git');
|
|
||||||
const result = isBundledBuilder(parsed, cliPkg);
|
|
||||||
expect(result).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user