diff --git a/packages/build-utils/src/types.ts b/packages/build-utils/src/types.ts index 08864577f..6713799c2 100644 --- a/packages/build-utils/src/types.ts +++ b/packages/build-utils/src/types.ts @@ -342,6 +342,7 @@ export interface BuilderV2 { version: 2; build: BuildV2; prepareCache?: PrepareCache; + shouldServe?: ShouldServe; } export interface BuilderV3 { diff --git a/packages/cli/src/util/build/static-builder.ts b/packages/cli/src/util/build/static-builder.ts index 5e9fd507b..192c4385b 100644 --- a/packages/cli/src/util/build/static-builder.ts +++ b/packages/cli/src/util/build/static-builder.ts @@ -1,5 +1,6 @@ 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; @@ -39,3 +40,18 @@ export const build: BuildV2 = async ({ entrypoint, files, config }) => { 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); +}; diff --git a/packages/cli/src/util/dev/builder-cache.ts b/packages/cli/src/util/dev/builder-cache.ts deleted file mode 100644 index 6c05a6885..000000000 --- a/packages/cli/src/util/dev/builder-cache.ts +++ /dev/null @@ -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 -) { - 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, - output: Output, - builderDir?: string -): Promise { - 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, - output: Output, - builderDir?: string -): Promise { - 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 { - 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]; - } - } - } -} diff --git a/packages/cli/src/util/dev/builder.ts b/packages/cli/src/util/dev/builder.ts index fff97c324..f573de0d2 100644 --- a/packages/cli/src/util/dev/builder.ts +++ b/packages/cli/src/util/dev/builder.ts @@ -15,7 +15,7 @@ import { FileFsRef, normalizePath, } from '@vercel/build-utils'; -import { isOfficialRuntime } from '@vercel/fs-detectors'; +import { isStaticRuntime } from '@vercel/fs-detectors'; import plural from 'pluralize'; import minimatch from 'minimatch'; @@ -26,7 +26,6 @@ import { relative } from '../path-helpers'; import { LambdaSizeExceededError } from '../errors-ts'; import DevServer from './server'; -import { getBuilder } from './builder-cache'; import { VercelConfig, BuildMatch, @@ -41,6 +40,7 @@ import { import { normalizeRoutes } from '@vercel/routing-utils'; import getUpdateCommand from '../get-update-command'; import { getTitleName } from '../pkg-name'; +import { importBuilders } from '../build/import-builders'; interface BuildMessage { type: string; @@ -108,18 +108,18 @@ export async function executeBuild( filesRemoved?: string[] ): Promise { const { - builderWithPkg: { runInProcess, requirePath, builder, package: pkg }, + builderWithPkg: { path: requirePath, builder, pkg }, } = match; - const { entrypoint } = match; + const { entrypoint, use } = match; + const isStatic = isStaticRuntime(use); const { envConfigs, cwd: workPath, devCacheDir } = devServer; const debug = devServer.output.isDebugEnabled(); const startTime = Date.now(); - const showBuildTimestamp = - !isOfficialRuntime('static', match.use) && (!isInitialBuild || debug); + const showBuildTimestamp = !isStatic && (!isInitialBuild || debug); if (showBuildTimestamp) { - devServer.output.log(`Building ${match.use}:${entrypoint}`); + devServer.output.log(`Building ${use}:${entrypoint}`); devServer.output.debug( `Using \`${pkg.name}${pkg.version ? `@${pkg.version}` : ''}\`` ); @@ -130,7 +130,7 @@ export async function executeBuild( let result: BuildResult; let { buildProcess } = match; - if (!runInProcess && !buildProcess) { + if (!isStatic && !buildProcess) { buildProcess = await createBuildProcess( match, envConfigs, @@ -158,7 +158,7 @@ export async function executeBuild( }, }; - let buildResultOrOutputs: BuilderOutputs | BuildResult | BuildResultV3; + let buildResultOrOutputs; if (buildProcess) { buildProcess.send({ type: 'build', @@ -198,16 +198,12 @@ export async function executeBuild( } // 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) result = { output: buildResultOrOutputs as BuilderOutputs, routes: [], watch: [], - distPath: - typeof buildResultOrOutputs.distPath === 'string' - ? buildResultOrOutputs.distPath - : undefined, }; } else if (builder.version === 2) { result = buildResultOrOutputs as BuildResult; @@ -253,7 +249,7 @@ export async function executeBuild( } else { throw new Error( `${getTitleName()} CLI does not support builder version ${ - builder.version + (builder as any).version }.\nPlease run \`${await getUpdateCommand()}\` to update to the latest CLI.` ); } @@ -383,7 +379,7 @@ export async function executeBuild( if (showBuildTimestamp) { const endTime = Date.now(); 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 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) { let { src = '**', use, config = {} } = buildConfig; @@ -439,6 +437,8 @@ export async function getBuildMatches( for (const file of files) { src = relative(cwd, file); + const entrypoint = mapToEntrypoint.get(src) || src; + // Remove the output directory prefix if (config.zeroConfig && 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({ ...buildConfig, src, - entrypoint: mapToEntrypoint.get(src) || src, + entrypoint, builderWithPkg, buildOutput: {}, buildResults: new Map(), diff --git a/packages/cli/src/util/dev/server.ts b/packages/cli/src/util/dev/server.ts index 13e28dff7..d555cfb6b 100644 --- a/packages/cli/src/util/dev/server.ts +++ b/packages/cli/src/util/dev/server.ts @@ -1,4 +1,3 @@ -import ms from 'ms'; import url, { URL } from 'url'; import http from 'http'; import fs from 'fs-extra'; @@ -62,11 +61,6 @@ import { devRouter, getRoutesTypes } from './router'; import getMimeType from './mime-type'; import { executeBuild, getBuildMatches, shutdownBuilder } from './builder'; import { generateErrorMessage, generateHttpStatusDescription } from './errors'; -import { - installBuilders, - updateBuilders, - builderDirPromise, -} from './builder-cache'; // HTML templates import errorTemplate from './templates/error'; @@ -171,8 +165,6 @@ export default class DevServer { private vercelConfigWarning: boolean; private getVercelConfigPromise: Promise | null; private blockingBuildsPromise: Promise | null; - private updateBuildersPromise: Promise | null; - private updateBuildersTimeout: NodeJS.Timeout | undefined; private startPromise: Promise | null; private systemEnvValues: string[]; @@ -211,7 +203,6 @@ export default class DevServer { this.vercelConfigWarning = false; this.getVercelConfigPromise = null; this.blockingBuildsPromise = null; - this.updateBuildersPromise = null; this.startPromise = null; this.watchAggregationId = null; @@ -493,33 +484,6 @@ export default class DevServer { ); } - async invalidateBuildMatches( - vercelConfig: VercelConfig, - updatedBuilders: string[] - ): Promise { - 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 { // TODO: use the file watcher to only invalidate the env `dotfile` // once a change to the `fileName` occurs @@ -948,30 +912,8 @@ export default class DevServer { } } - const builders = new Set( - (vercelConfig.builds || []) - .filter((b: Builder) => b.use) - .map((b: Builder) => b.use) - ); - - await installBuilders(builders, this.output); 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 // executed at boot-up time in order to get the initial assets and/or routes // 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. */ async stop(exitCode?: number): Promise { - const { devProcess } = this; - const { debug } = this.output; if (this.stopping) return; - this.stopping = true; - if (typeof this.updateBuildersTimeout !== 'undefined') { - clearTimeout(this.updateBuildersTimeout); - } - + const { devProcess } = this; + const { debug } = this.output; const ops: Promise[] = []; for (const match of this.buildMatches.values()) { @@ -1074,18 +1011,10 @@ export default class DevServer { 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) { ops.push(this.killBuilderDevServer(pid)); } - // Ensure that the builders module cache is created - ops.push(builderDirPromise); - try { await Promise.all(ops); } catch (err: unknown) { @@ -1484,8 +1413,9 @@ export default class DevServer { // the middleware server for every HTTP request? const { envConfigs, files, devCacheDir, cwd: workPath } = this; try { - startMiddlewareResult = - await middleware.builderWithPkg.builder.startDevServer?.({ + const { builder } = middleware.builderWithPkg; + if (builder.version === 3) { + startMiddlewareResult = await builder.startDevServer?.({ files, entrypoint: middleware.entrypoint, workPath, @@ -1499,6 +1429,7 @@ export default class DevServer { buildEnv: { ...envConfigs.buildEnv }, }, }); + } if (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 // to. Once the proxied request is finished, vercel dev shuts down the dev // server child process. - const { builder, package: builderPkg } = match.builderWithPkg; - if (typeof builder.startDevServer === 'function') { + const { builder, pkg: builderPkg } = match.builderWithPkg; + if (builder.version === 3 && typeof builder.startDevServer === 'function') { let devServerResult: StartDevServerResult = null; try { const { envConfigs, files, devCacheDir, cwd: workPath } = this; diff --git a/packages/cli/src/util/dev/static-builder.ts b/packages/cli/src/util/dev/static-builder.ts deleted file mode 100644 index f9dcd89c1..000000000 --- a/packages/cli/src/util/dev/static-builder.ts +++ /dev/null @@ -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); -} diff --git a/packages/cli/src/util/dev/types.ts b/packages/cli/src/util/dev/types.ts index 3547189c7..88b991343 100644 --- a/packages/cli/src/util/dev/types.ts +++ b/packages/cli/src/util/dev/types.ts @@ -12,12 +12,12 @@ import { FileBlob, FileFsRef, Lambda, - PackageJson, } from '@vercel/build-utils'; import { VercelConfig } from '@vercel/client'; import { HandleValue, Route } from '@vercel/routing-utils'; import { Output } from '../output'; import { ProjectEnvVariable, ProjectSettings } from '../../types'; +import { BuilderWithPkg } from '../build/import-builders'; export { VercelConfig }; @@ -48,7 +48,7 @@ export interface EnvConfigs { export interface BuildMatch extends BuildConfig { entrypoint: string; src: string; - builderWithPkg: BuilderWithPackage; + builderWithPkg: BuilderWithPkg; buildOutput: BuilderOutputs; buildResults: Map; buildTimestamp: number; @@ -122,13 +122,6 @@ export interface BuildResultV4 { distPath?: string; } -export interface BuilderWithPackage { - runInProcess?: boolean; - requirePath: string; - builder: Readonly; - package: Readonly; -} - export interface HttpHeadersConfig { [name: string]: string; } diff --git a/packages/cli/test/dev/integration-1.test.ts b/packages/cli/test/dev/integration-1.test.ts index 0e493eb6f..d3ec4750c 100644 --- a/packages/cli/test/dev/integration-1.test.ts +++ b/packages/cli/test/dev/integration-1.test.ts @@ -3,6 +3,7 @@ import url from 'url'; import fs from 'fs-extra'; import { join } from 'path'; import listen from 'async-listen'; +import stripAnsi from 'strip-ansi'; import { createServer } from 'http'; const { @@ -723,12 +724,15 @@ test('[vercel dev] should support custom 404 routes', async () => { test('[vercel dev] prints `npm install` errors', async () => { const dir = fixture('runtime-not-installed'); const result = await exec(dir); - expect(result.stderr.includes('npm ERR! 404')).toBeTruthy(); 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(); expect( - result.stderr.includes('https://vercel.link/npm-install-failed-dev') + result.stderr.includes( + 'https://vercel.link/builder-dependencies-install-failed' + ) ).toBeTruthy(); }); diff --git a/packages/cli/test/unit/util/dev/builder-cache.test.ts b/packages/cli/test/unit/util/dev/builder-cache.test.ts deleted file mode 100644 index 3578c9729..000000000 --- a/packages/cli/test/unit/util/dev/builder-cache.test.ts +++ /dev/null @@ -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); - }); -});