diff --git a/.gitignore b/.gitignore index 361b9c43c..8dbf46fa1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,6 @@ coverage.lcov *.swp *.bak *.tgz -packages/now-cli/.builders -packages/now-cli/assets packages/now-cli/src/util/dev/templates/*.ts packages/now-cli/src/util/constants.ts packages/now-cli/test/**/yarn.lock diff --git a/api/frameworks.ts b/api/frameworks.ts index 683fa2c12..7f2b8da75 100644 --- a/api/frameworks.ts +++ b/api/frameworks.ts @@ -21,6 +21,9 @@ export default withApiHandler(async function( ) { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET'); - res.setHeader('Access-Control-Allow-Headers', 'Authorization, Accept, Content-Type'); + res.setHeader( + 'Access-Control-Allow-Headers', + 'Authorization, Accept, Content-Type' + ); return res.status(200).json(frameworks); }); diff --git a/packages/now-build-utils/src/types.ts b/packages/now-build-utils/src/types.ts index 2edc685ef..368eba513 100644 --- a/packages/now-build-utils/src/types.ts +++ b/packages/now-build-utils/src/types.ts @@ -188,6 +188,34 @@ export interface ShouldServeOptions { config: Config; } +export interface StartDevServerOptions { + /** + * Name of entrypoint file for this particular build job. Value + * `files[entrypoint]` is guaranteed to exist and be a valid File reference. + * `entrypoint` is always a discrete file and never a glob, since globs are + * expanded into separate builds at deployment time. + */ + entrypoint: string; + + /** + * A writable temporary directory where you are encouraged to perform your + * build process. This directory will be populated with the restored cache. + */ + workPath: string; + + /** + * An arbitrary object passed by the user in the build definition defined + * in `now.json`. + */ + config: Config; + + /** + * Runtime environment variables configuration from the project's `now.json` + * and local `.env` file. + */ + env: Env; +} + export interface StartDevServerSuccess { /** * Port number where the dev server can be connected to, assumed to be running @@ -213,7 +241,7 @@ export type StartDevServerResult = StartDevServerSuccess | null; * Source: https://gist.github.com/iainreid820/5c1cc527fe6b5b7dba41fec7fe54bf6e */ // eslint-disable-next-line @typescript-eslint/no-namespace -namespace PackageJson { +export namespace PackageJson { /** * An author or contributor */ diff --git a/packages/now-cli/package.json b/packages/now-cli/package.json index 02f068ae4..34bd44250 100644 --- a/packages/now-cli/package.json +++ b/packages/now-cli/package.json @@ -61,9 +61,19 @@ "engines": { "node": ">= 10" }, + "dependencies": { + "@vercel/build-utils": "2.3.1", + "@vercel/go": "1.1.1", + "@vercel/next": "2.6.1", + "@vercel/node": "1.6.1", + "@vercel/python": "1.2.1", + "@vercel/ruby": "1.2.1", + "@vercel/static-build": "0.17.1" + }, "devDependencies": { "@sentry/node": "5.5.0", "@sindresorhus/slugify": "0.11.0", + "@tootallnate/once": "1.1.2", "@types/ansi-escapes": "3.0.0", "@types/ansi-regex": "4.0.0", "@types/async-retry": "1.2.1", @@ -179,7 +189,6 @@ "which": "2.0.2", "which-promise": "1.0.0", "write-json-file": "2.2.0", - "xdg-app-paths": "5.1.0", - "yarn": "1.22.0" + "xdg-app-paths": "5.1.0" } } diff --git a/packages/now-cli/scripts/build.ts b/packages/now-cli/scripts/build.ts index 6568e0c77..aeab8aad4 100644 --- a/packages/now-cli/scripts/build.ts +++ b/packages/now-cli/scripts/build.ts @@ -1,58 +1,10 @@ import cpy from 'cpy'; -import tar from 'tar-fs'; import execa from 'execa'; import { join } from 'path'; -import pipe from 'promisepipe'; -import { createGzip } from 'zlib'; -import { - createWriteStream, - mkdirp, - remove, - writeJSON, - writeFile, -} from 'fs-extra'; - -import { getDistTag } from '../src/util/get-dist-tag'; -import pkg from '../package.json'; -import { getBundledBuilders } from '../src/util/dev/get-bundled-builders'; +import { remove, writeFile } from 'fs-extra'; const dirRoot = join(__dirname, '..'); -async function createBuildersTarball() { - const distTag = getDistTag(pkg.version); - const builders = Array.from(getBundledBuilders()).map(b => `${b}@${distTag}`); - console.log(`Creating builders tarball with: ${builders.join(', ')}`); - - const buildersDir = join(dirRoot, '.builders'); - const assetsDir = join(dirRoot, 'assets'); - await mkdirp(buildersDir); - await mkdirp(assetsDir); - - const buildersTarballPath = join(assetsDir, 'builders.tar.gz'); - - try { - const buildersPkg = join(buildersDir, 'package.json'); - await writeJSON(buildersPkg, { private: true }, { flag: 'wx' }); - } catch (err) { - if (err.code !== 'EEXIST') { - throw err; - } - } - - const yarn = join(dirRoot, '../../node_modules/yarn/bin/yarn.js'); - await execa(process.execPath, [yarn, 'add', '--no-lockfile', ...builders], { - cwd: buildersDir, - stdio: 'inherit', - }); - - const packer = tar.pack(buildersDir); - await pipe( - packer, - createGzip(), - createWriteStream(buildersTarballPath) - ); -} - async function createConstants() { console.log('Creating constants.ts'); const filename = join(dirRoot, 'src/util/constants.ts'); @@ -81,10 +33,6 @@ async function main() { // During local development, these secrets will be empty. await createConstants(); - // Create a tarball from all the `@now` scoped builders which will be bundled - // with Now CLI - await createBuildersTarball(); - // `now dev` uses chokidar to watch the filesystem, but opts-out of the // `fsevents` feature using `useFsEvents: false`, so delete the module here so // that it is not compiled by ncc, which makes the npm package size larger diff --git a/packages/now-cli/src/commands/dev/dev.ts b/packages/now-cli/src/commands/dev/dev.ts index d29ea1194..26d91fde3 100644 --- a/packages/now-cli/src/commands/dev/dev.ts +++ b/packages/now-cli/src/commands/dev/dev.ts @@ -84,6 +84,7 @@ export default async function dev( }); process.once('SIGINT', () => devServer.stop()); + process.once('SIGTERM', () => devServer.stop()); await devServer.start(...listen); } diff --git a/packages/now-cli/src/util/dev/builder-cache.ts b/packages/now-cli/src/util/dev/builder-cache.ts index 17a7094e3..6bb9996a5 100644 --- a/packages/now-cli/src/util/dev/builder-cache.ts +++ b/packages/now-cli/src/util/dev/builder-cache.ts @@ -1,23 +1,13 @@ import execa from 'execa'; import semver from 'semver'; -import pipe from 'promisepipe'; import retry from 'async-retry'; import npa from 'npm-package-arg'; import pluralize from 'pluralize'; -import { extract } from 'tar-fs'; -import { createHash } from 'crypto'; -import { createGunzip } from 'zlib'; -import { join } from 'path'; +import { basename, join } from 'path'; +import { PackageJson } from '@vercel/build-utils'; import XDGAppPaths from 'xdg-app-paths'; -import { PackageJson, isStaticRuntime } from '@vercel/build-utils'; -import { - createReadStream, - mkdirp, - readFile, - readJSON, - writeFile, -} from 'fs-extra'; -import pkg from '../../../package.json'; +import { mkdirp, readJSON, writeJSON } from 'fs-extra'; +import nowCliPkg from '../pkg'; import { NoBuilderCacheError } from '../errors-ts'; import { Output } from '../output'; @@ -34,6 +24,7 @@ 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: '' }), }; @@ -44,36 +35,10 @@ const localBuilders: { [key: string]: BuilderWithPackage } = { '@vercel/static': createStaticBuilder('vercel'), }; -const distTag = getDistTag(pkg.version); +const distTag = nowCliPkg.version ? getDistTag(nowCliPkg.version) : 'canary'; export const cacheDirPromise = prepareCacheDir(); export const builderDirPromise = prepareBuilderDir(); -export const builderModulePathPromise = prepareBuilderModulePath(); - -function readFileOrNull( - filePath: string, - encoding?: null -): Promise; -function readFileOrNull( - filePath: string, - encoding: string -): Promise; -async function readFileOrNull( - filePath: string, - encoding?: string | null -): Promise { - try { - if (encoding) { - return await readFile(filePath, encoding); - } - return await readFile(filePath); - } catch (err) { - if (err.code === 'ENOENT') { - return null; - } - throw err; - } -} /** * Prepare cache directory for installing now-builders @@ -94,51 +59,19 @@ export async function prepareBuilderDir() { const builderDir = join(await cacheDirPromise, 'builders'); await mkdirp(builderDir); - // Extract the bundled `builders.tar.gz` file, if necessary - const bundledTarballPath = join(__dirname, '../../../assets/builders.tar.gz'); - - const existingPackageJson = - (await readFileOrNull(join(builderDir, 'package.json'), 'utf8')) || '{}'; - const { dependencies = {} } = JSON.parse(existingPackageJson); - - if (!hasBundledBuilders(dependencies)) { - const extractor = extract(builderDir); - await pipe( - createReadStream(bundledTarballPath), - createGunzip(), - extractor - ); + // 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) { + if (err.code !== 'EEXIST') { + throw err; + } } return builderDir; } -export async function prepareBuilderModulePath() { - const [builderDir, builderContents] = await Promise.all([ - builderDirPromise, - readFile(join(__dirname, 'builder-worker.js')), - ]); - let needsWrite = false; - const builderSha = getSha(builderContents); - const cachedBuilderPath = join(builderDir, 'builder.js'); - - const cachedBuilderContents = await readFileOrNull(cachedBuilderPath); - if (cachedBuilderContents) { - const cachedBuilderSha = getSha(cachedBuilderContents); - if (builderSha !== cachedBuilderSha) { - needsWrite = true; - } - } else { - needsWrite = true; - } - - if (needsWrite) { - await writeFile(cachedBuilderPath, builderContents); - } - - return cachedBuilderPath; -} - function getNpmVersion(use = ''): string { const parsed = npa(use); if (registryTypes.has(parsed.type)) { @@ -168,12 +101,20 @@ function parseVersionSafe(rawSpec: string) { export function filterPackage( builderSpec: string, distTag: string, - buildersPkg: PackageJson + buildersPkg: PackageJson, + nowCliPkg: PackageJson ) { if (builderSpec in localBuilders) return false; const parsed = npa(builderSpec); const parsedVersion = parseVersionSafe(parsed.rawSpec); - // skip install of already installed Runtime + + // If it's a builder that is part of Now CLI's `dependencies` then + // the builder is already installed into `node_modules` + if (isBundledBuilder(parsed, nowCliPkg)) { + return false; + } + + // Skip install of already installed Runtime if ( parsed.name && parsed.type === 'version' && @@ -215,7 +156,6 @@ export function filterPackage( */ export async function installBuilders( packagesSet: Set, - yarnDir: string, output: Output, builderDir?: string ): Promise { @@ -231,9 +171,12 @@ export async function installBuilders( if (!builderDir) { builderDir = await builderDirPromise; } - const yarnPath = join(yarnDir, 'yarn'); const buildersPkgPath = join(builderDir, 'package.json'); const buildersPkgBefore = await readJSON(buildersPkgPath); + const depsBefore = { + ...buildersPkgBefore.devDependencies, + ...buildersPkgBefore.dependencies, + }; packages.push( getBuildUtils(packages, 'vercel'), @@ -242,7 +185,7 @@ export async function installBuilders( // Filter out any packages that come packaged with `now-cli` const packagesToInstall = packages.filter(p => - filterPackage(p, distTag, buildersPkgBefore) + filterPackage(p, distTag, buildersPkgBefore, nowCliPkg) ); if (packagesToInstall.length === 0) { @@ -261,14 +204,11 @@ export async function installBuilders( await retry( () => execa( - process.execPath, + 'npm', [ - yarnPath, - 'add', - '--exact', - '--no-lockfile', - '--non-interactive', - '--ignore-workspace-root-check', + 'install', + '--save-exact', + '--no-package-lock', ...packagesToInstall, ], { @@ -283,8 +223,12 @@ export async function installBuilders( const updatedPackages: string[] = []; const buildersPkgAfter = await readJSON(buildersPkgPath); - for (const [name, version] of Object.entries(buildersPkgAfter.dependencies)) { - if (version !== buildersPkgBefore.dependencies[name]) { + 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); } @@ -295,7 +239,6 @@ export async function installBuilders( export async function updateBuilders( packagesSet: Set, - yarnDir: string, output: Output, builderDir?: string ): Promise { @@ -303,46 +246,59 @@ export async function updateBuilders( builderDir = await builderDirPromise; } + const updatedPackages: string[] = []; const packages = Array.from(packagesSet); - const yarnPath = join(yarnDir, 'yarn'); const buildersPkgPath = join(builderDir, 'package.json'); const buildersPkgBefore = await readJSON(buildersPkgPath); + const depsBefore = { + ...buildersPkgBefore.devDependencies, + ...buildersPkgBefore.dependencies, + }; - packages.push( - getBuildUtils(packages, 'vercel'), - getBuildUtils(packages, 'now') - ); + const packagesToUpdate = packages.filter(p => { + if (p in localBuilders) return false; - await retry( - () => - execa( - process.execPath, - [ - yarnPath, - 'add', - '--exact', - '--no-lockfile', - '--non-interactive', - '--ignore-workspace-root-check', - ...packages.filter(p => !isStaticRuntime(p)), - ], - { - cwd: builderDir, - } - ), - { retries: 2 } - ); - - const updatedPackages: string[] = []; - const buildersPkgAfter = await readJSON(buildersPkgPath); - for (const [name, version] of Object.entries(buildersPkgAfter.dependencies)) { - if (version !== buildersPkgBefore.dependencies[name]) { - output.debug(`Runtime "${name}" updated to version \`${version}\``); - updatedPackages.push(name); + // If it's a builder that is part of Now CLI's `dependencies` then + // don't update it + if (isBundledBuilder(npa(p), nowCliPkg)) { + return false; } - } - purgeRequireCache(updatedPackages, builderDir, output); + return true; + }); + + if (packagesToUpdate.length > 0) { + packages.push( + getBuildUtils(packages, 'vercel'), + getBuildUtils(packages, 'now') + ); + + await retry( + () => + execa( + 'npm', + ['install', '--save-exact', '--no-package-lock', ...packagesToUpdate], + { + cwd: builderDir, + } + ), + { retries: 2 } + ); + + 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; } @@ -352,7 +308,6 @@ export async function updateBuilders( */ export async function getBuilder( builderPkg: string, - yarnDir: string, output: Output, builderDir?: string, isRetry = false @@ -362,34 +317,74 @@ export async function getBuilder( if (!builderDir) { builderDir = await builderDirPromise; } + let requirePath: string; const parsed = npa(builderPkg); - const buildersPkg = await readJSON(join(builderDir, 'package.json')); - const pkgName = getPackageName(parsed, buildersPkg) || builderPkg; - const dest = join(builderDir, 'node_modules', pkgName); + + // First check if it's a bundled Runtime in Now CLI's `node_modules` + const bundledBuilder = isBundledBuilder(parsed, nowCliPkg); + 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 { - const mod = require(dest); - const pkg = require(join(dest, 'package.json')); + 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) { if (err.code === 'MODULE_NOT_FOUND' && !isRetry) { output.debug( - `Attempted to require ${builderPkg}, but it is not installed` + `Attempted to require ${requirePath}, but it is not installed` ); const pkgSet = new Set([builderPkg]); - await installBuilders(pkgSet, yarnDir, output, builderDir); + await installBuilders(pkgSet, output, builderDir); // Run `getBuilder()` again now that the builder has been installed - return getBuilder(builderPkg, yarnDir, output, builderDir, true); + 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, + pkg: PackageJson +): boolean { + if (!parsed.name || !pkg.dependencies) { + return false; + } + + const bundledVersion = pkg.dependencies[parsed.name]; + if (bundledVersion) { + if (parsed.type === 'tag') { + if (parsed.fetchSpec === 'canary') { + return bundledVersion.includes('canary'); + } else if (parsed.fetchSpec === 'latest') { + return !bundledVersion.includes('canary'); + } + } else if (parsed.type === 'version') { + return parsed.fetchSpec === bundledVersion; + } + } + + return false; +} + function getPackageName( parsed: npa.Result, buildersPkg: PackageJson @@ -397,30 +392,18 @@ function getPackageName( if (registryTypes.has(parsed.type)) { return parsed.name; } - const deps = { ...buildersPkg.devDependencies, ...buildersPkg.dependencies }; + const deps: { [name: string]: string } = { + ...buildersPkg.devDependencies, + ...buildersPkg.dependencies, + }; for (const [name, dep] of Object.entries(deps)) { - if (dep === parsed.raw) { + if (dep === parsed.raw || basename(dep) === basename(parsed.raw)) { return name; } } return null; } -function getSha(buffer: Buffer): string { - const hash = createHash('sha256'); - hash.update(buffer); - return hash.digest('hex'); -} - -function hasBundledBuilders(dependencies: { [name: string]: string }): boolean { - for (const name of getBundledBuilders()) { - if (!(name in dependencies)) { - return false; - } - } - return true; -} - function purgeRequireCache( packages: string[], builderDir: string, diff --git a/packages/now-cli/src/util/dev/builder-worker.js b/packages/now-cli/src/util/dev/builder-worker.js index 664ee6466..a6ffdb38d 100644 --- a/packages/now-cli/src/util/dev/builder-worker.js +++ b/packages/now-cli/src/util/dev/builder-worker.js @@ -1,10 +1,10 @@ /** - * This file gets copied out of the `pkg` snapshot filesystem into the `now dev` + * This file gets copied out of the `pkg` snapshot filesystem into the `vc dev` * builder cache directory, so it's very important that it does not rely on any * modules from npm that would not be available in that directory (so basically, - * only Now builders and `@now/build-utils`. + * only Vercel Runtimes and `@vercel/build-utils`. */ -const { FileFsRef } = require('@now/build-utils'); +const { FileFsRef } = require('@vercel/build-utils'); process.on('unhandledRejection', err => { console.error('Exiting builder due to build error:'); @@ -24,21 +24,21 @@ function onMessage(message) { } async function processMessage(message) { - const { builderName, buildParams } = message; - const builder = require(builderName); + const { requirePath, buildOptions } = message; + const builder = require(requirePath); // Convert the `files` to back into `FileFsRef` instances - for (const name of Object.keys(buildParams.files)) { + for (const name of Object.keys(buildOptions.files)) { const ref = Object.assign( Object.create(FileFsRef.prototype), - buildParams.files[name] + buildOptions.files[name] ); - buildParams.files[name] = ref; + buildOptions.files[name] = ref; } - const result = await builder.build(buildParams); + const result = await builder.build(buildOptions); - // `@now/next` sets this, but it causes "Converting circular + // `@vercel/next` sets this, but it causes "Converting circular // structure to JSON" errors, so delete the property... delete result.childProcesses; diff --git a/packages/now-cli/src/util/dev/builder.ts b/packages/now-cli/src/util/dev/builder.ts index 98ebb41d3..c31bdbe9d 100644 --- a/packages/now-cli/src/util/dev/builder.ts +++ b/packages/now-cli/src/util/dev/builder.ts @@ -2,12 +2,13 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import ms from 'ms'; import bytes from 'bytes'; -import { promisify } from 'util'; import { delimiter, dirname, join } from 'path'; import { fork, ChildProcess } from 'child_process'; import { createFunction } from '@zeit/fun'; import { Builder, + BuildOptions, + Env, File, Lambda, FileBlob, @@ -16,17 +17,16 @@ import { } from '@vercel/build-utils'; import plural from 'pluralize'; import minimatch from 'minimatch'; -import _treeKill from 'tree-kill'; import { Output } from '../output'; import highlight from '../output/highlight'; +import { treeKill } from '../tree-kill'; import { relative } from '../path-helpers'; import { LambdaSizeExceededError } from '../errors-ts'; import DevServer from './server'; -import { builderModulePathPromise, getBuilder } from './builder-cache'; +import { getBuilder } from './builder-cache'; import { - EnvConfig, NowConfig, BuildMatch, BuildResult, @@ -34,7 +34,6 @@ import { BuilderOutput, BuildResultV3, BuilderOutputs, - BuilderParams, EnvConfigs, } from './types'; import { normalizeRoutes } from '@vercel/routing-utils'; @@ -51,27 +50,18 @@ interface BuildMessageResult extends BuildMessage { error?: object; } -const treeKill = promisify(_treeKill); - async function createBuildProcess( match: BuildMatch, envConfigs: EnvConfigs, workPath: string, - output: Output, - yarnPath?: string + output: Output ): Promise { - const { execPath } = process; - const modulePath = await builderModulePathPromise; + const builderWorkerPath = join(__dirname, 'builder-worker.js'); // Ensure that `node` is in the builder's `PATH` - let PATH = `${dirname(execPath)}${delimiter}${process.env.PATH}`; + let PATH = `${dirname(process.execPath)}${delimiter}${process.env.PATH}`; - // Ensure that `yarn` is in the builder's `PATH` - if (yarnPath) { - PATH = `${yarnPath}${delimiter}${PATH}`; - } - - const env: EnvConfig = { + const env: Env = { ...process.env, PATH, ...envConfigs.allEnv, @@ -79,11 +69,9 @@ async function createBuildProcess( VERCEL_REGION: 'dev1', }; - const buildProcess = fork(modulePath, [], { + const buildProcess = fork(builderWorkerPath, [], { cwd: workPath, env, - execPath, - execArgv: [], }); match.buildProcess = buildProcess; @@ -117,10 +105,10 @@ export async function executeBuild( filesRemoved?: string[] ): Promise { const { - builderWithPkg: { runInProcess, builder, package: pkg }, + builderWithPkg: { runInProcess, requirePath, builder, package: pkg }, } = match; const { entrypoint } = match; - const { debug, envConfigs, yarnPath, cwd: workPath } = devServer; + const { debug, envConfigs, cwd: workPath } = devServer; const startTime = Date.now(); const showBuildTimestamp = @@ -144,15 +132,14 @@ export async function executeBuild( match, envConfigs, workPath, - devServer.output, - yarnPath + devServer.output ); } const vercelDir = getVercelDirectory(workPath); const devCacheDir = join(vercelDir, 'cache'); - const buildParams: BuilderParams = { + const buildOptions: BuildOptions = { files, entrypoint, workPath, @@ -174,8 +161,8 @@ export async function executeBuild( if (buildProcess) { buildProcess.send({ type: 'build', - builderName: pkg.name, - buildParams, + requirePath, + buildOptions, }); buildResultOrOutputs = await new Promise((resolve, reject) => { @@ -206,7 +193,7 @@ export async function executeBuild( buildProcess!.on('message', onMessage); }); } else { - buildResultOrOutputs = await builder.build(buildParams); + buildResultOrOutputs = await builder.build(buildOptions); } // Sort out build result to builder v2 shape @@ -404,7 +391,6 @@ export async function executeBuild( export async function getBuildMatches( nowConfig: NowConfig, cwd: string, - yarnDir: string, output: Output, devServer: DevServer, fileList: string[] @@ -456,7 +442,7 @@ export async function getBuildMatches( for (const file of files) { src = relative(cwd, file); - const builderWithPkg = await getBuilder(use, yarnDir, output); + const builderWithPkg = await getBuilder(use, output); matches.push({ ...buildConfig, src, diff --git a/packages/now-cli/src/util/dev/router.ts b/packages/now-cli/src/util/dev/router.ts index 69900ccc4..759da2fcc 100644 --- a/packages/now-cli/src/util/dev/router.ts +++ b/packages/now-cli/src/util/dev/router.ts @@ -4,7 +4,7 @@ import PCRE from 'pcre-to-regexp'; import isURL from './is-url'; import DevServer from './server'; -import { HttpHeadersConfig, RouteConfig, RouteResult } from './types'; +import { HttpHeadersConfig, RouteResult } from './types'; import { isHandler, Route, HandleValue } from '@vercel/routing-utils'; export function resolveRouteParameters( @@ -48,10 +48,10 @@ export function getRoutesTypes(routes: Route[] = []) { export async function devRouter( reqUrl: string = '/', reqMethod?: string, - routes?: RouteConfig[], + routes?: Route[], devServer?: DevServer, previousHeaders?: HttpHeadersConfig, - missRoutes?: RouteConfig[], + missRoutes?: Route[], phase?: HandleValue | null ): Promise { let result: RouteResult | undefined; diff --git a/packages/now-cli/src/util/dev/server.ts b/packages/now-cli/src/util/dev/server.ts index e7e6bfc95..d256b70cf 100644 --- a/packages/now-cli/src/util/dev/server.ts +++ b/packages/now-cli/src/util/dev/server.ts @@ -12,6 +12,7 @@ import serveHandler from 'serve-handler'; import { watch, FSWatcher } from 'chokidar'; import { parse as parseDotenv } from 'dotenv'; import { basename, dirname, extname, join } from 'path'; +import once from '@tootallnate/once'; import directoryTemplate from 'serve-handler/src/directory'; import getPort from 'get-port'; import { ChildProcess } from 'child_process'; @@ -23,9 +24,12 @@ import { getTransformedRoutes, appendRoutesToPhase, HandleValue, + Route, } from '@vercel/routing-utils'; import { Builder, + Env, + StartDevServerResult, FileFsRef, PackageJson, detectBuilders, @@ -35,7 +39,6 @@ import { isOfficialRuntime, } from '@vercel/build-utils'; -import { once } from '../once'; import link from '../output/link'; import { Output } from '../output'; import { relative } from '../path-helpers'; @@ -57,10 +60,13 @@ import { import { devRouter, getRoutesTypes } from './router'; import getMimeType from './mime-type'; -import { getYarnPath } from './yarn-installer'; import { executeBuild, getBuildMatches, shutdownBuilder } from './builder'; import { generateErrorMessage, generateHttpStatusDescription } from './errors'; -import { installBuilders, updateBuilders } from './builder-cache'; +import { + installBuilders, + updateBuilders, + builderDirPromise, +} from './builder-cache'; // HTML templates import errorTemplate from './templates/error'; @@ -70,7 +76,6 @@ import errorTemplate502 from './templates/error_502'; import redirectTemplate from './templates/redirect'; import { - EnvConfig, NowConfig, DevServerOptions, BuildMatch, @@ -81,7 +86,6 @@ import { InvokePayload, InvokeResult, ListenSpec, - RouteConfig, RouteResult, HttpHeadersConfig, EnvConfigs, @@ -111,7 +115,6 @@ export default class DevServer { public envConfigs: EnvConfigs; public frameworkSlug: string | null; public files: BuilderInputs; - public yarnPath: string; public address: string; private cachedNowConfig: NowConfig | null; @@ -131,6 +134,7 @@ export default class DevServer { private devCommand?: string; private devProcess?: ChildProcess; private devProcessPort?: number; + private devServerPids: Set; private getNowConfigPromise: Promise | null; private blockingBuildsPromise: Promise | null; @@ -145,14 +149,10 @@ export default class DevServer { this.address = ''; this.devCommand = options.devCommand; this.frameworkSlug = options.frameworkSlug; - - // This gets updated when `start()` is invoked - this.yarnPath = '/'; - this.cachedNowConfig = null; this.caseSensitive = false; this.apiDir = null; - this.apiExtensions = new Set(); + this.apiExtensions = new Set(); this.server = http.createServer(this.devServerHandler); this.server.timeout = 0; // Disable timeout this.stopping = false; @@ -171,6 +171,8 @@ export default class DevServer { this.podId = Math.random() .toString(32) .slice(-5); + + this.devServerPids = new Set(); } async exit(code = 1) { @@ -357,7 +359,6 @@ export default class DevServer { const matches = await getBuildMatches( nowConfig, this.cwd, - this.yarnPath, this.output, this, fileList @@ -449,11 +450,11 @@ export default class DevServer { await this.updateBuildMatches(nowConfig); } - async getLocalEnv(fileName: string, base?: EnvConfig): Promise { + 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 const filePath = join(this.cwd, fileName); - let env: EnvConfig = {}; + let env: Env = {}; try { const dotenv = await fs.readFile(filePath, 'utf8'); this.output.debug(`Using local env: ${filePath}`); @@ -583,7 +584,7 @@ export default class DevServer { config.builds = config.builds || []; config.builds.push(...builders); - const routes: RouteConfig[] = []; + const routes: Route[] = []; const { routes: nowConfigRoutes } = config; routes.push(...(redirectRoutes || [])); routes.push( @@ -656,6 +657,7 @@ export default class DevServer { if (config.version === 1) { this.output.error('Only `version: 2` is supported by `now dev`'); await this.exit(1); + return; } await this.tryValidateOrExit(config, validateNowConfigBuilds); @@ -668,13 +670,8 @@ export default class DevServer { await this.tryValidateOrExit(config, validateNowConfigFunctions); } - validateEnvConfig( - type: string, - env: EnvConfig = {}, - localEnv: EnvConfig = {} - ): EnvConfig { + validateEnvConfig(type: string, env: Env = {}, localEnv: Env = {}): Env { // Validate if there are any missing env vars defined in `vercel.json`, - // but not in the `.env` / `.build.env` file const missing: string[] = Object.entries(env) .filter( @@ -689,7 +686,7 @@ export default class DevServer { throw new MissingDotenvVarsError(type, missing); } - const merged: EnvConfig = { ...env, ...localEnv }; + const merged: Env = { ...env, ...localEnv }; // Validate that the env var name matches what AWS Lambda allows: // - https://docs.aws.amazon.com/lambda/latest/dg/env_variables.html @@ -737,7 +734,6 @@ export default class DevServer { } const { ig } = await getVercelIgnore(this.cwd); - this.yarnPath = await getYarnPath(this.output); this.filter = ig.createFilter(); // Retrieve the path of the native module @@ -770,16 +766,12 @@ export default class DevServer { .map((b: Builder) => b.use) ); - await installBuilders(builders, this.yarnPath, this.output); + await installBuilders(builders, this.output); await this.updateBuildMatches(nowConfig, true); // Updating builders happens lazily, and any builders that were updated // get their "build matches" invalidated so that the new version is used. - this.updateBuildersPromise = updateBuilders( - builders, - this.yarnPath, - this.output - ) + this.updateBuildersPromise = updateBuilders(builders, this.output) .then(updatedBuilders => { this.updateBuildersPromise = null; this.invalidateBuildMatches(nowConfig, updatedBuilders); @@ -875,6 +867,7 @@ export default class DevServer { */ async stop(exitCode?: number): Promise { const { devProcess } = this; + const { debug } = this.output; if (this.stopping) return; this.stopping = true; @@ -905,15 +898,22 @@ export default class DevServer { ops.push(close(this.server)); if (this.watcher) { - this.output.debug(`Closing file watcher`); + debug(`Closing file watcher`); this.watcher.close(); } if (this.updateBuildersPromise) { - this.output.debug(`Waiting for builders update to complete`); + 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) { @@ -929,6 +929,18 @@ export default class DevServer { } } + async killBuilderDevServer(pid: number) { + const { debug } = this.output; + debug(`Killing builder dev server with PID ${pid}`); + this.devServerPids.delete(pid); + try { + process.kill(pid, 'SIGTERM'); + debug(`Killed builder dev server with PID ${pid}`); + } catch (err) { + debug(`Failed to kill builder dev server with PID ${pid}: ${err}`); + } + } + async send404( req: http.IncomingMessage, res: http.ServerResponse, @@ -1222,9 +1234,11 @@ export default class DevServer { res: http.ServerResponse, nowRequestId: string, nowConfig: NowConfig, - routes: RouteConfig[] | undefined = nowConfig.routes, + routes: Route[] | undefined = nowConfig.routes, callLevel: number = 0 ) => { + const { debug } = this.output; + // If there is a double-slash present in the URL, // then perform a redirect to make it "clean". const parsed = url.parse(req.url || '/'); @@ -1241,16 +1255,16 @@ export default class DevServer { return; } - this.output.debug(`Rewriting URL from "${req.url}" to "${location}"`); + debug(`Rewriting URL from "${req.url}" to "${location}"`); req.url = location; } - await this.updateBuildMatches(nowConfig); + if (callLevel === 0) { + await this.updateBuildMatches(nowConfig); + } if (this.blockingBuildsPromise) { - this.output.debug( - 'Waiting for builds to complete before handling request' - ); + debug('Waiting for builds to complete before handling request'); await this.blockingBuildsPromise; } @@ -1291,7 +1305,7 @@ export default class DevServer { Object.assign(destParsed.query, routeResult.uri_args); const destUrl = url.format(destParsed); - this.output.debug(`ProxyPass: ${destUrl}`); + debug(`ProxyPass: ${destUrl}`); this.setResponseHeaders(res, nowRequestId); return proxyPass(req, res, destUrl, this.output); } @@ -1382,15 +1396,25 @@ export default class DevServer { }); if (statusCode) { - res.statusCode = statusCode; + // Set the `statusCode` as read-only so that `http-proxy` + // is not able to modify the value in the future + Object.defineProperty(res, 'statusCode', { + get() { + return statusCode; + }, + /* eslint-disable @typescript-eslint/no-unused-vars */ + set(_: number) { + /* ignore */ + }, + }); } const requestPath = dest.replace(/^\//, ''); if (!match) { - // if the dev command is started, proxy to it + // If the dev command is started, then proxy to it if (this.devProcessPort) { - this.output.debug('Proxy to dev command server'); + debug('Proxying to frontend dev server'); this.setResponseHeaders(res, nowRequestId); return proxyPass( req, @@ -1423,7 +1447,7 @@ export default class DevServer { origUrl.pathname = dest; Object.assign(origUrl.query, uri_args); const newUrl = url.format(origUrl); - this.output.debug( + debug( `Checking build result's ${buildResult.routes.length} \`routes\` to match ${newUrl}` ); const matchedRoute = await devRouter( @@ -1433,9 +1457,7 @@ export default class DevServer { this ); if (matchedRoute.found && callLevel === 0) { - this.output.debug( - `Found matching route ${matchedRoute.dest} for ${newUrl}` - ); + debug(`Found matching route ${matchedRoute.dest} for ${newUrl}`); req.url = newUrl; await this.serveProjectAsNowV2( req, @@ -1449,7 +1471,76 @@ export default class DevServer { } } + // Before doing any asset matching, check if this builder supports the + // `startDevServer()` "optimization". In this case, the now dev server invokes + // `startDevServer()` on the builder for every HTTP request so that it boots + // up a single-serve dev HTTP server that now dev will proxy this HTTP request + // to. Once the proxied request is finished, now dev shuts down the dev + // server child process. + const { builder, package: builderPkg } = match.builderWithPkg; + if (typeof builder.startDevServer === 'function') { + let devServerResult: StartDevServerResult = null; + try { + devServerResult = await builder.startDevServer({ + entrypoint: match.entrypoint, + workPath: this.cwd, + config: match.config || {}, + env: this.envConfigs.runEnv || {}, + }); + } catch (err) { + // `startDevServer()` threw an error. Most likely this means the dev + // server process exited before sending the port information message + // (missing dependency at runtime, for example). + debug(`Error starting "${builderPkg.name}" dev server: ${err}`); + await this.sendError( + req, + res, + nowRequestId, + 'NO_STATUS_CODE_FROM_DEV_SERVER', + 502 + ); + return; + } + + if (devServerResult) { + // When invoking lambda functions, the region where the lambda was invoked + // is also included in the request ID. So use the same `dev1` fake region. + nowRequestId = generateRequestId(this.podId, true); + + const { port, pid } = devServerResult; + this.devServerPids.add(pid); + + res.once('close', () => { + this.killBuilderDevServer(pid); + }); + + debug( + `Proxying to "${builderPkg.name}" dev server (port=${port}, pid=${pid})` + ); + + // Mix in the routing based query parameters + const parsed = url.parse(req.url || '/', true); + Object.assign(parsed.query, uri_args); + req.url = url.format({ + pathname: parsed.pathname, + query: parsed.query, + }); + + this.setResponseHeaders(res, nowRequestId); + return proxyPass( + req, + res, + `http://localhost:${port}`, + this.output, + false + ); + } else { + debug(`Skipping \`startDevServer()\` for ${match.entrypoint}`); + } + } + let foundAsset = findAsset(match, requestPath, nowConfig); + if (!foundAsset && callLevel === 0) { await this.triggerBuild(match, buildRequestPath, req); @@ -1464,7 +1555,7 @@ export default class DevServer { this.devProcessPort && (!foundAsset || (foundAsset && foundAsset.asset.type !== 'Lambda')) ) { - this.output.debug('Proxy to dev command server'); + debug('Proxying to frontend dev server'); this.setResponseHeaders(res, nowRequestId); return proxyPass( req, @@ -1481,7 +1572,7 @@ export default class DevServer { } const { asset, assetKey } = foundAsset; - this.output.debug( + debug( `Serving asset: [${asset.type}] ${assetKey} ${(asset as any) .contentType || ''}` ); @@ -1550,7 +1641,7 @@ export default class DevServer { body: body.toString('base64'), }; - this.output.debug(`Invoking lambda: "${assetKey}" with ${path}`); + debug(`Invoking lambda: "${assetKey}" with ${path}`); let result: InvokeResult; try { @@ -1699,7 +1790,7 @@ export default class DevServer { const port = await getPort(); - const env: EnvConfig = { + const env: Env = { // Because of child process 'pipe' below, isTTY will be false. // Most frameworks use `chalk`/`supports-color` so we enable it anyway. FORCE_COLOR: process.stdout.isTTY ? '1' : '0', @@ -1912,7 +2003,7 @@ async function shouldServe( const shouldServe = await builder.shouldServe({ entrypoint: src, files, - config, + config: config || {}, requestPath, workPath: devServer.cwd, }); diff --git a/packages/now-cli/src/util/dev/static-builder.ts b/packages/now-cli/src/util/dev/static-builder.ts index b98150af2..9431094f6 100644 --- a/packages/now-cli/src/util/dev/static-builder.ts +++ b/packages/now-cli/src/util/dev/static-builder.ts @@ -1,5 +1,10 @@ import { basename, extname, join } from 'path'; -import { BuilderParams, BuildResult, ShouldServeParams } from './types'; +import { + FileFsRef, + BuildOptions, + ShouldServeOptions, +} from '@vercel/build-utils'; +import { BuildResult } from './types'; export const version = 2; @@ -7,7 +12,7 @@ export function build({ files, entrypoint, config, -}: BuilderParams): BuildResult { +}: BuildOptions): BuildResult { let path = entrypoint; const outputDir = config.zeroConfig ? config.outputDirectory : ''; const outputMatch = outputDir + '/'; @@ -16,7 +21,7 @@ export function build({ path = path.slice(outputMatch.length); } const output = { - [path]: files[entrypoint], + [path]: files[entrypoint] as FileFsRef, }; const watch = [path]; @@ -28,7 +33,7 @@ export function shouldServe({ files, requestPath, config = {}, -}: ShouldServeParams) { +}: ShouldServeOptions) { let outputPrefix = ''; const outputDir = config.zeroConfig ? config.outputDirectory : ''; const outputMatch = outputDir + '/'; diff --git a/packages/now-cli/src/util/dev/types.ts b/packages/now-cli/src/util/dev/types.ts index 3f1833aeb..2d3a8b07d 100644 --- a/packages/now-cli/src/util/dev/types.ts +++ b/packages/now-cli/src/util/dev/types.ts @@ -3,12 +3,16 @@ import { ChildProcess } from 'child_process'; import { Lambda as FunLambda } from '@zeit/fun'; import { Builder as BuildConfig, + BuildOptions, + PrepareCacheOptions, + ShouldServeOptions, + StartDevServerOptions, + StartDevServerResult, + Env, FileBlob, FileFsRef, Lambda, PackageJson, - Config, - Meta, } from '@vercel/build-utils'; import { NowConfig } from '@vercel/client'; import { HandleValue, Route } from '@vercel/routing-utils'; @@ -23,25 +27,21 @@ export interface DevServerOptions { frameworkSlug: string | null; } -export interface EnvConfig { - [name: string]: string | undefined; -} - export interface EnvConfigs { /** * environment variables from `.env.build` file (deprecated) */ - buildEnv: EnvConfig; + buildEnv: Env; /** * environment variables from `.env` file */ - runEnv: EnvConfig; + runEnv: Env; /** * environment variables from `.env` and `.env.build` */ - allEnv: EnvConfig; + allEnv: Env; } export interface BuildMatch extends BuildConfig { @@ -53,8 +53,6 @@ export interface BuildMatch extends BuildConfig { buildProcess?: ChildProcess; } -export type RouteConfig = Route; - export interface HttpHandler { (req: http.IncomingMessage, res: http.ServerResponse): void; } @@ -63,9 +61,9 @@ export interface BuilderInputs { [path: string]: FileFsRef; } -export type BuiltLambda = Lambda & { +export interface BuiltLambda extends Lambda { fn?: FunLambda; -}; +} export type BuilderOutput = BuiltLambda | FileFsRef | FileBlob; @@ -79,21 +77,6 @@ export interface CacheOutputs { [path: string]: CacheOutput; } -export interface BuilderParamsBase { - files: BuilderInputs; - entrypoint: string; - config: Config; - meta?: Meta; -} - -export interface BuilderParams extends BuilderParamsBase { - workPath: string; -} - -export interface PrepareCacheParams extends BuilderParams { - cachePath: string; -} - export interface BuilderConfigAttr { maxLambdaSize?: string | number; } @@ -102,49 +85,43 @@ export interface Builder { version?: 1 | 2 | 3 | 4; config?: BuilderConfigAttr; build( - params: BuilderParams + opts: BuildOptions ): | BuilderOutputs | BuildResult | Promise | Promise; - shouldServe?(params: ShouldServeParams): boolean | Promise; prepareCache?( - params: PrepareCacheParams + opts: PrepareCacheOptions ): CacheOutputs | Promise; + shouldServe?(params: ShouldServeOptions): boolean | Promise; + startDevServer?(opts: StartDevServerOptions): Promise; } export interface BuildResult { output: BuilderOutputs; - routes: RouteConfig[]; + routes: Route[]; watch: string[]; distPath?: string; } export interface BuildResultV3 { output: Lambda; - routes: RouteConfig[]; + routes: Route[]; watch: string[]; distPath?: string; } export interface BuildResultV4 { output: { [filePath: string]: Lambda }; - routes: RouteConfig[]; + routes: Route[]; watch: string[]; distPath?: string; } -export interface ShouldServeParams { - files: BuilderInputs; - entrypoint: string; - config?: Config; - requestPath: string; - workPath: string; -} - export interface BuilderWithPackage { runInProcess?: boolean; + requirePath: string; builder: Readonly; package: Readonly; } @@ -167,7 +144,7 @@ export interface RouteResult { // "uri_args": uri_args?: { [key: string]: any }; // "matched_route": - matched_route?: RouteConfig; + matched_route?: Route; // "matched_route_idx": matched_route_idx?: number; // "userDest": diff --git a/packages/now-cli/src/util/dev/yarn-installer.ts b/packages/now-cli/src/util/dev/yarn-installer.ts deleted file mode 100644 index 7ea3d0807..000000000 --- a/packages/now-cli/src/util/dev/yarn-installer.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { createHash } from 'crypto'; -import { - mkdirp, - createWriteStream, - writeFile, - statSync, - chmodSync, - createReadStream, -} from 'fs-extra'; -import pipe from 'promisepipe'; -import { join } from 'path'; -import fetch from 'node-fetch'; -import { Output } from '../output/create-output'; -import { builderDirPromise } from './builder-cache'; - -const YARN_VERSION = '1.17.3'; -const YARN_SHA = '77f28b2793ca7d0ab5bd5da072afc423f7fdf733'; -const YARN_URL = `https://github.com/yarnpkg/yarn/releases/download/v${YARN_VERSION}/yarn-${YARN_VERSION}.js`; - -function plusxSync(file: string): void { - const s = statSync(file); - const newMode = s.mode | 64 | 8 | 1; - - if (s.mode === newMode) { - return; - } - - const base8 = newMode.toString(8).slice(-3); - chmodSync(file, base8); -} - -function getSha1(filePath: string): Promise { - return new Promise((resolve, reject) => { - const hash = createHash('sha1'); - const stream = createReadStream(filePath); - stream.on('error', err => { - if (err.code === 'ENOENT') { - resolve(null); - } else { - reject(err); - } - }); - stream.on('data', chunk => hash.update(chunk)); - stream.on('end', () => resolve(hash.digest('hex'))); - }); -} - -async function installYarn(output: Output): Promise { - // Loosely based on https://yarnpkg.com/install.sh - const dirName = await builderDirPromise; - const yarnBin = join(dirName, 'yarn'); - const sha1 = await getSha1(yarnBin); - - if (sha1 === YARN_SHA) { - output.debug('The yarn executable is already cached, not re-downloading'); - return dirName; - } - - output.debug(`Creating directory ${dirName}`); - await mkdirp(dirName); - output.debug(`Finished creating ${dirName}`); - - output.debug(`Downloading ${YARN_URL}`); - const response = await fetch(YARN_URL, { - compress: false, - redirect: 'follow', - }); - - if (response.status !== 200) { - throw new Error(`Received invalid response: ${await response.text()}`); - } - - const target = createWriteStream(yarnBin); - await pipe( - response.body, - target - ); - output.debug(`Finished downloading yarn ${yarnBin}`); - - output.debug(`Making the yarn binary executable`); - plusxSync(yarnBin); - output.debug(`Finished making the yarn binary executable`); - - if (process.platform === 'win32') { - // The `yarn.cmd` file is necessary for `yarn` to be executable - // when running `now dev` through cmd.exe - await writeFile( - `${yarnBin}.cmd`, - [ - '@echo off', - '@SETLOCAL', - '@SET PATHEXT=%PATHEXT:;.JS;=;%', - 'node "%~dp0\\yarn" %*', - ].join('\r\n') - ); - } - - return dirName; -} - -export async function getYarnPath(output: Output): Promise { - return installYarn(output); -} diff --git a/packages/now-cli/src/util/once.ts b/packages/now-cli/src/util/once.ts deleted file mode 100644 index 6d80bf831..000000000 --- a/packages/now-cli/src/util/once.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { EventEmitter } from 'events'; - -export function once(emitter: EventEmitter, name: string): Promise { - return new Promise((resolve, reject) => { - function cleanup() { - emitter.removeListener(name, onEvent); - emitter.removeListener('error', onError); - } - function onEvent(arg: T) { - cleanup(); - resolve(arg); - } - function onError(err: Error) { - cleanup(); - reject(err); - } - emitter.on(name, onEvent); - emitter.on('error', onError); - }); -} diff --git a/packages/now-cli/src/util/pkg.ts b/packages/now-cli/src/util/pkg.ts index 4ca82a681..387bf1747 100644 --- a/packages/now-cli/src/util/pkg.ts +++ b/packages/now-cli/src/util/pkg.ts @@ -1,13 +1,6 @@ -import path from 'path'; -import pkg from '../../package.json'; +import _pkg from '../../package.json'; +import { PackageJson } from '@vercel/build-utils'; -try { - const distDir = path.dirname(process.execPath); - // @ts-ignore - pkg._npmPkg = require(`${path.join(distDir, '../../package.json')}`); -} catch (err) { - // @ts-ignore - pkg._npmPkg = null; -} +const pkg: PackageJson = _pkg; export default pkg; diff --git a/packages/now-cli/src/util/tree-kill.ts b/packages/now-cli/src/util/tree-kill.ts new file mode 100644 index 000000000..31ea894e4 --- /dev/null +++ b/packages/now-cli/src/util/tree-kill.ts @@ -0,0 +1,4 @@ +import _treeKill from 'tree-kill'; +import { promisify } from 'util'; + +export const treeKill = promisify(_treeKill); diff --git a/packages/now-cli/test/dev-builder.unit.js b/packages/now-cli/test/dev-builder.unit.js index cd82d029c..2e0a48fca 100644 --- a/packages/now-cli/test/dev-builder.unit.js +++ b/packages/now-cli/test/dev-builder.unit.js @@ -1,17 +1,23 @@ import test from 'ava'; -import { filterPackage } from '../src/util/dev/builder-cache'; +import npa from 'npm-package-arg'; +import { filterPackage, isBundledBuilder } from '../src/util/dev/builder-cache'; -test('[dev-builder] filter install "latest", cached canary', async t => { +test('[dev-builder] filter install "latest", cached canary', t => { const buildersPkg = { dependencies: { '@vercel/build-utils': '0.0.1-canary.0', }, }; - const result = filterPackage('@vercel/build-utils', 'canary', buildersPkg); + const result = filterPackage( + '@vercel/build-utils', + 'canary', + buildersPkg, + {} + ); t.is(result, true); }); -test('[dev-builder] filter install "canary", cached stable', async t => { +test('[dev-builder] filter install "canary", cached stable', t => { const buildersPkg = { dependencies: { '@vercel/build-utils': '0.0.1', @@ -20,22 +26,28 @@ test('[dev-builder] filter install "canary", cached stable', async t => { const result = filterPackage( '@vercel/build-utils@canary', 'latest', - buildersPkg + buildersPkg, + {} ); t.is(result, true); }); -test('[dev-builder] filter install "latest", cached stable', async t => { +test('[dev-builder] filter install "latest", cached stable', t => { const buildersPkg = { dependencies: { '@vercel/build-utils': '0.0.1', }, }; - const result = filterPackage('@vercel/build-utils', 'latest', buildersPkg); + const result = filterPackage( + '@vercel/build-utils', + 'latest', + buildersPkg, + {} + ); t.is(result, false); }); -test('[dev-builder] filter install "canary", cached canary', async t => { +test('[dev-builder] filter install "canary", cached canary', t => { const buildersPkg = { dependencies: { '@vercel/build-utils': '0.0.1-canary.0', @@ -44,87 +56,209 @@ test('[dev-builder] filter install "canary", cached canary', async t => { const result = filterPackage( '@vercel/build-utils@canary', 'canary', - buildersPkg + buildersPkg, + {} ); t.is(result, false); }); -test('[dev-builder] filter install URL, cached stable', async t => { +test('[dev-builder] filter install URL, cached stable', t => { const buildersPkg = { dependencies: { '@vercel/build-utils': '0.0.1', }, }; - const result = filterPackage('https://tarball.now.sh', 'latest', buildersPkg); + const result = filterPackage( + 'https://tarball.now.sh', + 'latest', + buildersPkg, + {} + ); t.is(result, true); }); -test('[dev-builder] filter install URL, cached canary', async t => { +test('[dev-builder] filter install URL, cached canary', t => { const buildersPkg = { dependencies: { '@vercel/build-utils': '0.0.1-canary.0', }, }; - const result = filterPackage('https://tarball.now.sh', 'canary', buildersPkg); + const result = filterPackage( + 'https://tarball.now.sh', + 'canary', + buildersPkg, + {} + ); t.is(result, true); }); -test('[dev-builder] filter install "latest", cached URL - stable', async t => { +test('[dev-builder] filter install "latest", cached URL - stable', t => { const buildersPkg = { dependencies: { '@vercel/build-utils': 'https://tarball.now.sh', }, }; - const result = filterPackage('@vercel/build-utils', 'latest', buildersPkg); + const result = filterPackage( + '@vercel/build-utils', + 'latest', + buildersPkg, + {} + ); t.is(result, true); }); -test('[dev-builder] filter install "latest", cached URL - canary', async t => { +test('[dev-builder] filter install "latest", cached URL - canary', t => { const buildersPkg = { dependencies: { '@vercel/build-utils': 'https://tarball.now.sh', }, }; - const result = filterPackage('@vercel/build-utils', 'canary', buildersPkg); + const result = filterPackage( + '@vercel/build-utils', + 'canary', + buildersPkg, + {} + ); t.is(result, true); }); -test('[dev-builder] filter install not bundled version, cached same version', async t => { +test('[dev-builder] filter install not bundled version, cached same version', t => { const buildersPkg = { dependencies: { 'not-bundled-package': '0.0.1', }, }; - const result = filterPackage('not-bundled-package@0.0.1', '_', buildersPkg); + const result = filterPackage( + 'not-bundled-package@0.0.1', + '_', + buildersPkg, + {} + ); t.is(result, false); }); -test('[dev-builder] filter install not bundled version, cached different version', async t => { +test('[dev-builder] filter install not bundled version, cached different version', t => { const buildersPkg = { dependencies: { 'not-bundled-package': '0.0.9', }, }; - const result = filterPackage('not-bundled-package@0.0.1', '_', buildersPkg); + const result = filterPackage( + 'not-bundled-package@0.0.1', + '_', + buildersPkg, + {} + ); t.is(result, true); }); -test('[dev-builder] filter install not bundled stable, cached version', async t => { +test('[dev-builder] filter install not bundled stable, cached version', t => { const buildersPkg = { dependencies: { 'not-bundled-package': '0.0.1', }, }; - const result = filterPackage('not-bundled-package', '_', buildersPkg); + const result = filterPackage('not-bundled-package', '_', buildersPkg, {}); t.is(result, true); }); -test('[dev-builder] filter install not bundled tagged, cached tagged', async t => { +test('[dev-builder] filter install not bundled tagged, cached tagged', t => { const buildersPkg = { dependencies: { 'not-bundled-package': '16.9.0-alpha.0', }, }; - const result = filterPackage('not-bundled-package@alpha', '_', buildersPkg); + const result = filterPackage( + 'not-bundled-package@alpha', + '_', + buildersPkg, + {} + ); t.is(result, true); }); + +test('[dev-builder] isBundledBuilder() - stable', t => { + const nowCliPkg = { + dependencies: { + '@now/node': '1.5.2', + }, + }; + + // "canary" tag + { + const parsed = npa('@now/node@canary'); + const result = isBundledBuilder(parsed, nowCliPkg); + t.is(result, false); + } + + // "latest" tag + { + const parsed = npa('@now/node'); + const result = isBundledBuilder(parsed, nowCliPkg); + t.is(result, true); + } + + // specific matching version + { + const parsed = npa('@now/node@1.5.2'); + const result = isBundledBuilder(parsed, nowCliPkg); + t.is(result, true); + } + + // specific non-matching version + { + const parsed = npa('@now/node@1.5.1'); + const result = isBundledBuilder(parsed, nowCliPkg); + t.is(result, false); + } + + // URL + { + const parsed = npa('https://example.com'); + const result = isBundledBuilder(parsed, nowCliPkg); + t.is(result, false); + } +}); + +test('[dev-builder] isBundledBuilder() - canary', t => { + const nowCliPkg = { + dependencies: { + '@now/node': '1.5.2-canary.3', + }, + }; + + // "canary" tag + { + const parsed = npa('@now/node@canary'); + const result = isBundledBuilder(parsed, nowCliPkg); + t.is(result, true); + } + + // "latest" tag + { + const parsed = npa('@now/node'); + const result = isBundledBuilder(parsed, nowCliPkg); + t.is(result, false); + } + + // specific matching version + { + const parsed = npa('@now/node@1.5.2-canary.3'); + const result = isBundledBuilder(parsed, nowCliPkg); + t.is(result, true); + } + + // specific non-matching version + { + const parsed = npa('@now/node@1.5.2-canary.2'); + const result = isBundledBuilder(parsed, nowCliPkg); + t.is(result, false); + } + + // URL + { + const parsed = npa('https://example.com'); + const result = isBundledBuilder(parsed, nowCliPkg); + t.is(result, false); + } +}); diff --git a/packages/now-cli/test/dev-server.unit.js b/packages/now-cli/test/dev-server.unit.js index b005af17a..8d8164be7 100644 --- a/packages/now-cli/test/dev-server.unit.js +++ b/packages/now-cli/test/dev-server.unit.js @@ -289,6 +289,7 @@ test( testFixture('now-dev-static-routes', async (t, server) => { { const res = await fetch(`${server.address}/`); + t.is(res.status, 200); const body = await res.text(); t.is(body, 'Hello!\n'); } @@ -300,6 +301,7 @@ test( testFixture('now-dev-static-build-routing', async (t, server) => { { const res = await fetch(`${server.address}/api/date`); + t.is(res.status, 200); const body = await res.text(); t.is(body.startsWith('The current date:'), true); } diff --git a/packages/now-cli/test/dev/fixtures/26-nextjs-secrets/yarn.lock b/packages/now-cli/test/dev/fixtures/26-nextjs-secrets/yarn.lock index 24954e443..ab047df16 100644 --- a/packages/now-cli/test/dev/fixtures/26-nextjs-secrets/yarn.lock +++ b/packages/now-cli/test/dev/fixtures/26-nextjs-secrets/yarn.lock @@ -1074,11 +1074,6 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - accepts@~1.3.5: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -1147,11 +1142,6 @@ ansi-regex@^2.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" @@ -1185,19 +1175,11 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -aproba@^1.0.3, aproba@^1.1.1: +aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1809,11 +1791,6 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -1921,11 +1898,6 @@ console-browserify@^1.1.0: resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -2154,7 +2126,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@^3.0.0, debug@^3.2.6: +debug@^3.0.0: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -2173,11 +2145,6 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -2226,11 +2193,6 @@ del@^3.0.0: pify "^3.0.0" rimraf "^2.2.8" -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -2249,11 +2211,6 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - devalue@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/devalue/-/devalue-2.0.1.tgz#5d368f9adc0928e47b77eea53ca60d2f346f9762" @@ -2717,13 +2674,6 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-minipass@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -2757,20 +2707,6 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -2859,11 +2795,6 @@ has-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -2984,7 +2915,7 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -iconv-lite@0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -3013,13 +2944,6 @@ ignore-loader@0.1.2: resolved "https://registry.yarnpkg.com/ignore-loader/-/ignore-loader-0.1.2.tgz#d81f240376d0ba4f0d778972c3ad25874117a463" integrity sha1-2B8kA3bQuk8Nd4lyw60lh0EXpGM= -ignore-walk@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== - dependencies: - minimatch "^3.0.4" - import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -3080,11 +3004,6 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== - invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -3189,18 +3108,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -3749,21 +3656,6 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -3795,7 +3687,7 @@ mkdirp@0.5.1: dependencies: minimist "0.0.8" -mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -3851,15 +3743,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -needle@^2.2.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.1.tgz#14af48732463d7475696f937626b1b993247a56a" - integrity sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -4007,35 +3890,11 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-pre-gyp@*: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - node-releases@^1.1.44, node-releases@^1.1.53: version "1.1.53" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ== -nopt@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - normalize-html-whitespace@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/normalize-html-whitespace/-/normalize-html-whitespace-1.0.0.tgz#5e3c8e192f1b06c3b9eee4b7e7f28854c7601e34" @@ -4078,47 +3937,11 @@ normalize-url@1.9.1: query-string "^4.1.0" sort-keys "^1.0.0" -npm-bundled@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== - dependencies: - npm-normalize-package-bin "^1.0.1" - -npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-packlist@^1.1.6: - version "1.4.8" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" - integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-normalize-package-bin "^1.0.1" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -4210,24 +4033,6 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -4991,16 +4796,6 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - react-dom@16.13.0: version "16.13.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.0.tgz#cdde54b48eb9e8a0ca1b3dc9943d9bb409b81866" @@ -5044,7 +4839,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -5248,7 +5043,7 @@ rework@1.0.1: convert-source-map "^0.3.3" css "^2.0.0" -rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: +rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -5303,11 +5098,6 @@ sass-loader@8.0.2: schema-utils "^2.6.1" semver "^6.3.0" -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - scheduler@^0.19.0: version "0.19.1" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" @@ -5333,7 +5123,7 @@ schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.6.0, schema-utils@^2.6 ajv "^6.12.0" ajv-keywords "^3.4.1" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -5372,11 +5162,6 @@ serialize-javascript@^2.1.2: resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== -set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -5417,7 +5202,7 @@ shell-quote@^1.6.1: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -5603,23 +5388,6 @@ string-hash@1.1.3: resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs= -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -5641,30 +5409,18 @@ strip-ansi@5.2.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: +strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= dependencies: ansi-regex "^2.0.0" -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - style-loader@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.0.0.tgz#1d5296f9165e8e2c85d24eee0b7caf9ec8ca1f82" @@ -5721,19 +5477,6 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar@^4.4.2: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - terser-webpack-plugin@^1.4.1: version "1.4.3" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" @@ -6148,13 +5891,6 @@ whatwg-fetch@3.0.0: resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - worker-farm@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" @@ -6194,7 +5930,7 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: +yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/packages/now-cli/test/dev/fixtures/27-zero-config-env/package.json b/packages/now-cli/test/dev/fixtures/27-zero-config-env/package.json index 73b9a9b98..b1b562898 100644 --- a/packages/now-cli/test/dev/fixtures/27-zero-config-env/package.json +++ b/packages/now-cli/test/dev/fixtures/27-zero-config-env/package.json @@ -1,6 +1,6 @@ { "private": true, "scripts": { - "build": "mkdir public && echo $FOO > public/index.html" + "build": "mkdir -p public && echo $FOO > public/index.html" } } diff --git a/packages/now-cli/test/dev/fixtures/public-and-api/api/date.js b/packages/now-cli/test/dev/fixtures/public-and-api/api/date.js index f08b8c196..c279c7f4c 100644 --- a/packages/now-cli/test/dev/fixtures/public-and-api/api/date.js +++ b/packages/now-cli/test/dev/fixtures/public-and-api/api/date.js @@ -1,3 +1,3 @@ -export default (_req, res) => { +module.exports = (_req, res) => { res.end('current date: ' + new Date().toISOString()); }; diff --git a/packages/now-cli/test/dev/fixtures/public-and-api/api/rand.js b/packages/now-cli/test/dev/fixtures/public-and-api/api/rand.js index 47f067b12..dab352f96 100644 --- a/packages/now-cli/test/dev/fixtures/public-and-api/api/rand.js +++ b/packages/now-cli/test/dev/fixtures/public-and-api/api/rand.js @@ -1,3 +1,3 @@ -export default (_req, res) => { +module.exports = (_req, res) => { res.end('random number: ' + Math.random()); }; diff --git a/packages/now-cli/test/dev/integration.js b/packages/now-cli/test/dev/integration.js index 628edc83c..6c5d7c791 100644 --- a/packages/now-cli/test/dev/integration.js +++ b/packages/now-cli/test/dev/integration.js @@ -110,7 +110,10 @@ async function exec(directory, args = []) { async function runNpmInstall(fixturePath) { if (await fs.exists(join(fixturePath, 'package.json'))) { - return execa('yarn', ['install'], { cwd: fixturePath, shell: true }); + await execa('yarn', ['install'], { + cwd: fixturePath, + shell: true, + }); } } @@ -282,6 +285,9 @@ function testFixtureStdio( env, }); + dev.stdout.pipe(process.stdout); + dev.stderr.pipe(process.stderr); + dev.stdout.on('data', data => { stdoutList.push(data); }); diff --git a/packages/now-node/build.sh b/packages/now-node/build.sh index 241220148..dd8dc18bb 100755 --- a/packages/now-node/build.sh +++ b/packages/now-node/build.sh @@ -30,14 +30,6 @@ ncc build ../../node_modules/source-map-support/register -e @vercel/build-utils mv dist/source-map-support/index.js dist/source-map-support.js rm -rf dist/source-map-support -# build typescript -ncc build ../../node_modules/typescript/lib/typescript -e @vercel/build-utils -e @now/build-utils -o dist/typescript -mv dist/typescript/index.js dist/typescript.js -mkdir -p dist/typescript/lib -mv dist/typescript/typescript/lib/*.js dist/typescript/lib/ -mv dist/typescript/typescript/lib/*.d.ts dist/typescript/lib/ -rm -r dist/typescript/typescript - -ncc build src/index.ts -e @vercel/build-utils -e @now/build-utils -o dist/main +ncc build src/index.ts -e @vercel/build-utils -e @now/build-utils -e typescript -o dist/main mv dist/main/index.js dist/index.js rm -rf dist/main diff --git a/packages/now-node/package.json b/packages/now-node/package.json index bbb43d2c3..ecc782a21 100644 --- a/packages/now-node/package.json +++ b/packages/now-node/package.json @@ -19,11 +19,14 @@ "dist" ], "dependencies": { - "@types/node": "*" + "@types/node": "*", + "ts-node": "8.9.1", + "typescript": "3.8.3" }, "devDependencies": { "@babel/core": "7.5.0", "@babel/plugin-transform-modules-commonjs": "7.5.0", + "@tootallnate/once": "1.1.2", "@types/aws-lambda": "8.10.19", "@types/content-type": "1.1.3", "@types/cookie": "0.3.3", @@ -36,7 +39,6 @@ "etag": "1.8.1", "node-fetch": "2.6.0", "source-map-support": "0.5.12", - "test-listen": "1.1.0", - "typescript": "3.5.2" + "test-listen": "1.1.0" } } diff --git a/packages/now-node/src/dev-server.ts b/packages/now-node/src/dev-server.ts new file mode 100644 index 000000000..d009f97d2 --- /dev/null +++ b/packages/now-node/src/dev-server.ts @@ -0,0 +1,75 @@ +import { register } from 'ts-node'; + +// Use the project's version of TypeScript if available, +// otherwise fall back to using the copy that `@now/node` uses. +let compiler: string; +try { + compiler = require.resolve('typescript', { + paths: [process.cwd(), __dirname], + }); +} catch (e) { + compiler = 'typescript'; +} + +register({ + compiler, + compilerOptions: { + allowJs: true, + esModuleInterop: true, + jsx: 'react', + }, + transpileOnly: true, +}); + +import http from 'http'; +import path from 'path'; +import { createServerWithHelpers } from './helpers'; + +function listen( + server: http.Server, + port: number, + host: string +): Promise { + return new Promise(resolve => { + server.listen(port, host, () => { + resolve(); + }); + }); +} + +async function main() { + const entrypoint = process.env.NOW_DEV_ENTRYPOINT; + delete process.env.NOW_DEV_ENTRYPOINT; + + if (!entrypoint) { + throw new Error('`NOW_DEV_ENTRYPOINT` must be defined'); + } + + const config = JSON.parse(process.env.NOW_DEV_CONFIG || '{}'); + delete process.env.NOW_DEV_CONFIG; + + const shouldAddHelpers = !( + config.helpers === false || process.env.NODEJS_HELPERS === '0' + ); + + const entrypointPath = path.join(process.cwd(), entrypoint); + const handler = await import(entrypointPath); + + const server = shouldAddHelpers + ? createServerWithHelpers(handler.default) + : http.createServer(handler.default); + + await listen(server, 0, '127.0.0.1'); + + const address = server.address(); + if (typeof process.send === 'function') { + process.send(address); + } else { + console.log('Dev server listening:', address); + } +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/packages/now-node/src/helpers.ts b/packages/now-node/src/helpers.ts index d708b94be..a18c421ef 100644 --- a/packages/now-node/src/helpers.ts +++ b/packages/now-node/src/helpers.ts @@ -6,6 +6,7 @@ import { NowRequestBody, } from './types'; import { Server } from 'http'; +import { Readable } from 'stream'; import { Bridge } from './bridge'; function getBodyParser(req: NowRequest, body: Buffer) { @@ -81,6 +82,7 @@ function setCharset(type: string, charset: string) { return format(parsed); } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function createETag(body: any, encoding: 'utf8' | undefined) { // eslint-disable-next-line @typescript-eslint/no-var-requires const etag = require('etag'); @@ -88,6 +90,7 @@ function createETag(body: any, encoding: 'utf8' | undefined) { return etag(buf, { weak: true }); } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function send(req: NowRequest, res: NowResponse, body: any): NowResponse { let chunk: unknown = body; let encoding: 'utf8' | undefined; @@ -185,6 +188,7 @@ function send(req: NowRequest, res: NowResponse, body: any): NowResponse { return res; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function json(req: NowRequest, res: NowResponse, jsonBody: any): NowResponse { const body = JSON.stringify(jsonBody); @@ -233,9 +237,24 @@ function setLazyProp(req: NowRequest, prop: string, getter: () => T) { }); } +export function rawBody(readable: Readable): Promise { + return new Promise((resolve, reject) => { + let bytes = 0; + const chunks: Buffer[] = []; + readable.on('error', reject); + readable.on('data', chunk => { + chunks.push(chunk); + bytes += chunk.length; + }); + readable.on('end', () => { + resolve(Buffer.concat(chunks, bytes)); + }); + }); +} + export function createServerWithHelpers( - listener: (req: NowRequest, res: NowResponse) => void | Promise, - bridge: Bridge + handler: (req: NowRequest, res: NowResponse) => void | Promise, + bridge?: Bridge ) { const server = new Server(async (_req, _res) => { const req = _req as NowRequest; @@ -247,21 +266,23 @@ export function createServerWithHelpers( // don't expose this header to the client delete req.headers['x-now-bridge-request-id']; - if (typeof reqId !== 'string') { - throw new ApiError(500, 'Internal Server Error'); + let body: Buffer; + if (typeof reqId === 'string' && bridge) { + const event = bridge.consumeEvent(reqId); + body = event.body; + } else { + body = await rawBody(req); } - const event = bridge.consumeEvent(reqId); - setLazyProp(req, 'cookies', getCookieParser(req)); setLazyProp(req, 'query', getQueryParser(req)); - setLazyProp(req, 'body', getBodyParser(req, event.body)); + setLazyProp(req, 'body', getBodyParser(req, body)); res.status = statusCode => status(res, statusCode); res.send = body => send(req, res, body); res.json = jsonBody => json(req, res, jsonBody); - await listener(req, res); + await handler(req, res); } catch (err) { if (err instanceof ApiError) { sendError(res, err.statusCode, err.message); diff --git a/packages/now-node/src/index.ts b/packages/now-node/src/index.ts index 96e4e7ca8..807fff4f3 100644 --- a/packages/now-node/src/index.ts +++ b/packages/now-node/src/index.ts @@ -1,12 +1,22 @@ +import { fork, spawn } from 'child_process'; +import { + readFileSync, + lstatSync, + readlinkSync, + statSync, + promises as fsp, +} from 'fs'; import { basename, dirname, + extname, join, relative, resolve, sep, parse as parsePath, } from 'path'; +import once from '@tootallnate/once'; import nodeFileTrace from '@zeit/node-file-trace'; import buildUtils from './build-utils'; import { @@ -16,6 +26,8 @@ import { PrepareCacheOptions, BuildOptions, Config, + StartDevServerOptions, + StartDevServerResult, } from '@vercel/build-utils'; const { glob, @@ -30,12 +42,13 @@ const { shouldServe, debug, isSymbolicLink, + walkParentDirs, } = buildUtils; +import { makeNowLauncher, makeAwsLauncher } from './launcher'; +import { Register, register } from './typescript'; + export { shouldServe }; export { NowRequest, NowResponse } from './types'; -import { makeNowLauncher, makeAwsLauncher } from './launcher'; -import { readFileSync, lstatSync, readlinkSync, statSync } from 'fs'; -import { Register, register } from './typescript'; interface CompilerConfig { debug?: boolean; @@ -51,6 +64,19 @@ interface DownloadOptions { meta: Meta; } +interface PortInfo { + port: number; +} + +function isPortInfo(v: any): v is PortInfo { + return v && typeof v.port === 'number'; +} + +const tscPath = resolve( + dirname(require.resolve(eval('"typescript"'))), + '../bin/tsc' +); + // eslint-disable-next-line no-useless-escape const libPathRegEx = /^node_modules|[\/\\]node_modules[\/\\]/; @@ -317,6 +343,7 @@ export async function build({ const shouldAddHelpers = !( config.helpers === false || process.env.NODEJS_HELPERS === '0' ); + const awsLambdaHandler = getAWSLambdaHandler(entrypoint, config); const { @@ -396,3 +423,89 @@ export async function prepareCache({ const cache = await glob('node_modules/**', workPath); return cache; } + +export async function startDevServer( + opts: StartDevServerOptions +): Promise { + const { entrypoint, workPath, config, env } = opts; + const devServerPath = join(__dirname, 'dev-server.js'); + const child = fork(devServerPath, [], { + cwd: workPath, + env: { + ...process.env, + ...env, + NOW_DEV_ENTRYPOINT: entrypoint, + NOW_DEV_CONFIG: JSON.stringify(config), + }, + }); + + const { pid } = child; + const onMessage = once<{ port: number }>(child, 'message'); + const onExit = once.spread<[number, string | null]>(child, 'exit'); + const result = await Promise.race([onMessage, onExit]); + onExit.cancel(); + onMessage.cancel(); + + if (isPortInfo(result)) { + // "message" event + + const ext = extname(entrypoint); + if (ext === '.ts' || ext === '.tsx') { + // Invoke `tsc --noEmit` asynchronously in the background, so + // that the HTTP request is not blocked by the type checking. + doTypeCheck(opts).catch((err: Error) => { + console.error('Type check for %j failed:', entrypoint, err); + }); + } + + return { port: result.port, pid }; + } else { + // "exit" event + throw new Error( + `Failed to start dev server for "${entrypoint}" (code=${result[0]}, signal=${result[1]})` + ); + } +} + +async function doTypeCheck({ + entrypoint, + workPath, +}: StartDevServerOptions): Promise { + // In order to type-check a single file, a temporary tsconfig + // file needs to be created that inherits from the base one :( + // See: https://stackoverflow.com/a/44748041/376773 + const id = Math.random() + .toString(32) + .substring(2); + const tempConfigName = `.tsconfig-${id}.json`; + const projectTsConfig = await walkParentDirs({ + base: workPath, + start: join(workPath, dirname(entrypoint)), + filename: 'tsconfig.json', + }); + const tsconfig = { + extends: projectTsConfig || undefined, + include: [entrypoint], + }; + await fsp.writeFile(tempConfigName, JSON.stringify(tsconfig)); + + const child = spawn( + process.execPath, + [ + tscPath, + '--project', + tempConfigName, + '--noEmit', + '--allowJs', + '--esModuleInterop', + '--jsx', + 'react', + ], + { + cwd: workPath, + stdio: 'inherit', + } + ); + await once.spread<[number, string | null]>(child, 'exit'); + await fsp.unlink(tempConfigName); +} diff --git a/packages/now-node/src/typescript.ts b/packages/now-node/src/typescript.ts index 3254a219e..4abb6cc1c 100644 --- a/packages/now-node/src/typescript.ts +++ b/packages/now-node/src/typescript.ts @@ -157,7 +157,7 @@ export function register(opts: Options = {}): Register { paths: [options.project || cwd, nowNodeBase], }); } catch (e) { - compiler = require.resolve(eval('"./typescript"')); + compiler = require.resolve(eval('"typescript"')); } //eslint-disable-next-line @typescript-eslint/no-var-requires const ts: typeof _ts = require(compiler); diff --git a/yarn.lock b/yarn.lock index a42d79ce0..12f75359c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1422,10 +1422,10 @@ dependencies: defer-to-connect "^1.0.1" -"@tootallnate/once@1": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.0.0.tgz#9c13c2574c92d4503b005feca8f2e16cc1611506" - integrity sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA== +"@tootallnate/once@1", "@tootallnate/once@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== "@types/ansi-escapes@3.0.0": version "3.0.0" @@ -9963,6 +9963,14 @@ source-map-support@^0.5.12, source-map-support@^0.5.6: buffer-from "^1.0.0" source-map "^0.6.0" +source-map-support@^0.5.17: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -10775,6 +10783,17 @@ ts-node@8.3.0: source-map-support "^0.5.6" yn "^3.0.0" +ts-node@8.9.1: + version "8.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5" + integrity sha512-yrq6ODsxEFTLz0R3BX2myf0WBCSQh9A+py8PBo1dCzWIOcvisbyH6akNKqDHMgXePF2kir5mm5JXJTH3OUJYOQ== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" @@ -10858,6 +10877,11 @@ typescript@3.6.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d" integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg== +typescript@3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== + uglify-js@^3.1.4: version "3.7.3" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.3.tgz#f918fce9182f466d5140f24bb0ff35c2d32dcc6a" @@ -11486,11 +11510,6 @@ yargs@^13.3.0: y18n "^4.0.0" yargs-parser "^13.1.1" -yarn@1.22.0: - version "1.22.0" - resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.0.tgz#acf82906e36bcccd1ccab1cfb73b87509667c881" - integrity sha512-KMHP/Jq53jZKTY9iTUt3dIVl/be6UPs2INo96+BnZHLKxYNTfwMmlgHTaMWyGZoO74RI4AIFvnWhYrXq2USJkg== - yauzl-clone@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/yauzl-clone/-/yauzl-clone-1.0.4.tgz#8bc6d293b17cc98802bbbed2e289d18e7697c96c" @@ -11527,7 +11546,7 @@ yazl@2.4.3: dependencies: buffer-crc32 "~0.2.3" -yn@^3.0.0: +yn@3.1.1, yn@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==