import url , { URL } from 'url' ; import http from 'http' ; import fs from 'fs-extra' ; import chalk from 'chalk' ; import plural from 'pluralize' ; import rawBody from 'raw-body' ; import listen from 'async-listen' ; import minimatch from 'minimatch' ; import ms from 'ms' ; import httpProxy from 'http-proxy' ; import { randomBytes } from 'crypto' ; import serveHandler from 'serve-handler' ; import { watch , FSWatcher } from 'chokidar' ; import { parse as parseDotenv } from 'dotenv' ; import path , { isAbsolute , 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' ; import isPortReachable from 'is-port-reachable' ; import deepEqual from 'fast-deep-equal' ; import which from 'which' ; import npa from 'npm-package-arg' ; import { getVercelIgnore , fileNameSymbol } from '@vercel/client' ; import { getTransformedRoutes , appendRoutesToPhase , HandleValue , Route , } from '@vercel/routing-utils' ; import { Builder , Env , StartDevServerResult , FileFsRef , PackageJson , detectBuilders , detectApiDirectory , detectApiExtensions , spawnCommand , isOfficialRuntime , detectFileSystemAPI , } from '@vercel/build-utils' ; import frameworkList from '@vercel/frameworks' ; import cmd from '../output/cmd' ; import link from '../output/link' ; import sleep from '../sleep' ; import { Output } from '../output' ; import { relative } from '../path-helpers' ; import { getDistTag } from '../get-dist-tag' ; import getVercelConfigPath from '../config/local-path' ; import { MissingDotenvVarsError } from '../errors-ts' ; import cliPkg from '../pkg' ; import { getVercelDirectory } from '../projects/link' ; import { staticFiles as getFiles } from '../get-files' ; import { validateConfig } from './validate' ; import { devRouter , getRoutesTypes } from './router' ; import getMimeType from './mime-type' ; import { executeBuild , getBuildMatches , shutdownBuilder } from './builder' ; import { generateErrorMessage , generateHttpStatusDescription } from './errors' ; import { installBuilders , updateBuilders , builderDirPromise , } from './builder-cache' ; import errorTemplate from './templates/error' ; import errorTemplateBase from './templates/error_base' ; import errorTemplate404 from './templates/error_404' ; import errorTemplate502 from './templates/error_502' ; import redirectTemplate from './templates/redirect' ; import { VercelConfig , DevServerOptions , BuildMatch , BuildResult , BuilderInputs , BuilderOutput , HttpHandler , InvokePayload , InvokeResult , ListenSpec , RouteResult , HttpHeadersConfig , EnvConfigs , } from './types' ; import { ProjectEnvVariable , ProjectSettings } from '../../types' ; import exposeSystemEnvs from './expose-system-envs' ; import { loadCliPlugins } from '../plugins' ; const frontendRuntimeSet = new Set ( frameworkList . map ( f => f . useRuntime ?. use || '@vercel/static-build' ) ) ; interface FSEvent { type : string ; path : string ; } type WithFileNameSymbol < T > = T & { [ fileNameSymbol ] : string ; } ; function sortBuilders ( buildA , buildB ) { if ( buildA && buildA . use && isOfficialRuntime ( 'static-build' , buildA . use ) ) { return 1 ; } if ( buildB && buildB . use && isOfficialRuntime ( 'static-build' , buildB . use ) ) { return - 1 ; } return 0 ; } export default class DevServer { public cwd : string ; public output : Output ; public proxy : httpProxy ; public envConfigs : EnvConfigs ; public frameworkSlug ? : string ; public files : BuilderInputs ; public address : string ; public devCacheDir : string ; private caseSensitive : boolean ; private apiDir : string | null ; private apiExtensions : Set < string > ; private server : http . Server ; private stopping : boolean ; private buildMatches : Map < string , BuildMatch > ; private inProgressBuilds : Map < string , Promise < void > > ; private watcher ? : FSWatcher ; private watchAggregationId : NodeJS . Timer | null ; private watchAggregationEvents : FSEvent [ ] ; private watchAggregationTimeout : number ; private filter : ( path : string ) => boolean ; private podId : string ; private devCommand ? : string ; private devProcess ? : ChildProcess ; private devProcessPort ? : number ; private devServerPids : Set < number > ; private projectSettings ? : ProjectSettings ; private vercelConfigWarning : boolean ; private getVercelConfigPromise : Promise < VercelConfig > | null ; private blockingBuildsPromise : Promise < void > | null ; private updateBuildersPromise : Promise < void > | null ; private updateBuildersTimeout : NodeJS . Timeout | undefined ; private startPromise : Promise < void > | null ; private systemEnvValues : string [ ] ; private projectEnvs : ProjectEnvVariable [ ] ; constructor ( cwd , options ) { this . cwd = cwd ; this . output = options . output ; this . envConfigs = { buildEnv : { } , runEnv : { } , allEnv : { } } ; this . systemEnvValues = options . systemEnvValues || [ ] ; this . projectEnvs = options . projectEnvs || [ ] ; this . files = { } ; this . address = '' ; this . devCommand = options . devCommand ; this . projectSettings = options . projectSettings ; this . frameworkSlug = options . frameworkSlug ; this . caseSensitive = false ; this . apiDir = null ; this . apiExtensions = new Set ( ) ; this . proxy = httpProxy . createProxyServer ( { changeOrigin : true , ws : true , xfwd : true , } ) ; this . server = http . createServer ( this . devServerHandler ) ; this . server . timeout = 0 ; this . stopping = false ; this . buildMatches = new Map ( ) ; this . inProgressBuilds = new Map ( ) ; this . devCacheDir = join ( getVercelDirectory ( cwd ) , 'cache' ) ; this . vercelConfigWarning = false ; this . getVercelConfigPromise = null ; this . blockingBuildsPromise = null ; this . updateBuildersPromise = null ; this . startPromise = null ; this . watchAggregationId = null ; this . watchAggregationEvents = [ ] ; this . watchAggregationTimeout = 500 ; this . filter = path => Boolean ( path ) ; this . podId = Math . random ( ) . toString ( 32 ) . slice ( - 5 ) ; this . devServerPids = new Set ( ) ; } async exit ( code = 1 ) { await this . stop ( code ) ; process . exit ( code ) ; } enqueueFsEvent ( type , path ) { this . watchAggregationEvents . push ( { type , path } ) ; if ( this . watchAggregationId === null ) { this . watchAggregationId = setTimeout ( ( ) => { const events = this . watchAggregationEvents . slice ( ) ; this . watchAggregationEvents . length = 0 ; this . watchAggregationId = null ; this . handleFilesystemEvents ( events ) ; } , this . watchAggregationTimeout ) ; } } async handleFilesystemEvents ( events ) { this . output . debug ( `Filesystem watcher notified of ${ events . length } events` ) ; const filesChanged = new Set ( ) ; const filesRemoved = new Set ( ) ; const distPaths = [ ] ; for ( const buildMatch of this . buildMatches . values ( ) ) { for ( const buildResult of buildMatch . buildResults . values ( ) ) { if ( buildResult . distPath ) { distPaths . push ( buildResult . distPath ) ; } } } events = events . filter ( event => distPaths . every ( distPath => ! event . path . startsWith ( distPath ) ) ) ; for ( const event of events ) { if ( event . type === 'add' ) { await this . handleFileCreated ( event . path , filesChanged , filesRemoved ) ; } else if ( event . type === 'unlink' ) { this . handleFileDeleted ( event . path , filesChanged , filesRemoved ) ; } else if ( event . type === 'change' ) { await this . handleFileModified ( event . path , filesChanged , filesRemoved ) ; } } const vercelConfig = await this . getVercelConfig ( ) ; await this . updateBuildMatches ( vercelConfig ) ; const filesChangedArray = [ ... filesChanged ] ; const filesRemovedArray = [ ... filesRemoved ] ; const needsRebuild = new Map ( ) ; for ( const match of this . buildMatches . values ( ) ) { for ( const [ requestPath , result ] of match . buildResults ) { if ( needsRebuild . has ( result ) ) continue ; if ( Array . isArray ( result . watch ) ) { for ( const pattern of result . watch ) { if ( minimatches ( filesChangedArray , pattern ) || minimatches ( filesRemovedArray , pattern ) ) { needsRebuild . set ( result , [ requestPath , match ] ) ; break ; } } } } } if ( needsRebuild . size > 0 ) { this . output . debug ( `Triggering ${ needsRebuild . size } rebuilds` ) ; if ( filesChangedArray . length > 0 ) { this . output . debug ( `Files changed: ${ filesChangedArray . join ( ', ' ) }` ) ; } if ( filesRemovedArray . length > 0 ) { this . output . debug ( `Files removed: ${ filesRemovedArray . join ( ', ' ) }` ) ; } for ( const [ result , [ requestPath , match ] ] of needsRebuild ) { if ( requestPath === null || ( await shouldServe ( match , this . files , requestPath , this , vercelConfig ) ) ) { this . triggerBuild ( match , requestPath , null , vercelConfig , result , filesChangedArray , filesRemovedArray ) . catch ( ( err ) => { this . output . warn ( `An error occurred while rebuilding \`${ match . src }\`:` ) ; console . error ( err . stack ) ; } ) ; } else { this . output . debug ( `Not rebuilding because \`shouldServe()\` returned \`false\` for "${ match . use }" request path "${ requestPath }"` ) ; } } } } async handleFileCreated ( fsPath , changed , removed ) { const name = relative ( this . cwd , fsPath ) ; try { this . files [ name ] = await FileFsRef . fromFsPath ( { fsPath } ) ; const extensionless = this . getExtensionlessFile ( name ) ; if ( extensionless ) { this . files [ extensionless ] = await FileFsRef . fromFsPath ( { fsPath } ) ; } fileChanged ( name , changed , removed ) ; this . output . debug ( `File created: ${ name }` ) ; } catch ( err ) { if ( err . code === 'ENOENT' ) { this . output . debug ( `File created, but has since been deleted: ${ name }` ) ; fileRemoved ( name , this . files , changed , removed ) ; } else { throw err ; } } } handleFileDeleted ( fsPath , changed , removed ) { const name = relative ( this . cwd , fsPath ) ; this . output . debug ( `File deleted: ${ name }` ) ; fileRemoved ( name , this . files , changed , removed ) ; const extensionless = this . getExtensionlessFile ( name ) ; if ( extensionless ) { this . output . debug ( `File deleted: ${ extensionless }` ) ; fileRemoved ( extensionless , this . files , changed , removed ) ; } } async handleFileModified ( fsPath , changed , removed ) { const name = relative ( this . cwd , fsPath ) ; try { this . files [ name ] = await FileFsRef . fromFsPath ( { fsPath } ) ; fileChanged ( name , changed , removed ) ; this . output . debug ( `File modified: ${ name }` ) ; } catch ( err ) { if ( err . code === 'ENOENT' ) { this . output . debug ( `File modified, but has since been deleted: ${ name }` ) ; fileRemoved ( name , this . files , changed , removed ) ; } else { throw err ; } } } async updateBuildMatches ( vercelConfig , isInitial = false ) { const fileList = this . resolveBuildFiles ( this . files ) ; const matches = await getBuildMatches ( vercelConfig , this . cwd , this . output , this , fileList ) ; const sources = matches . map ( m => m . src ) ; if ( isInitial && fileList . length === 0 ) { this . output . warn ( 'There are no files inside your deployment.' ) ; } const ops = [ ] ; for ( const src of this . buildMatches . keys ( ) ) { if ( ! sources . includes ( src ) ) { this . output . debug ( `Removing build match for "${ src }"` ) ; const match = this . buildMatches . get ( src ) ; if ( match ) { ops . push ( shutdownBuilder ( match , this . output ) ) ; } this . buildMatches . delete ( src ) ; } } await Promise . all ( ops ) ; const blockingBuilds = [ ] ; for ( const match of matches ) { const currentMatch = this . buildMatches . get ( match . src ) ; if ( ! buildMatchEquals ( currentMatch , match ) ) { this . output . debug ( `Adding build match for "${ match . src }" with "${ match . use }"` ) ; this . buildMatches . set ( match . src , match ) ; if ( ! isInitial && needsBlockingBuild ( match ) ) { const buildPromise = executeBuild ( vercelConfig , this , this . files , match , null , false ) ; blockingBuilds . push ( buildPromise ) ; } } } if ( blockingBuilds . length > 0 ) { this . output . debug ( `Waiting for ${ blockingBuilds . length } "blocking builds"` ) ; this . blockingBuildsPromise = Promise . all ( blockingBuilds ) . then ( ( ) => { this . output . debug ( `Cleaning up "blockingBuildsPromise" after successful resolve` ) ; this . blockingBuildsPromise = null ; } ) . catch ( ( err ? ) => { this . output . debug ( `Cleaning up "blockingBuildsPromise" after error: ${ err }` ) ; this . blockingBuildsPromise = null ; if ( err ) { this . output . prettyError ( err ) ; } } ) ; } this . buildMatches = new Map ( [ ... this . buildMatches . entries ( ) ] . sort ( ( matchA , matchB ) => { return sortBuilders ( matchA [ 1 ] as Builder , matchB [ 1 ] as Builder ) ; } ) ) ; } async invalidateBuildMatches ( vercelConfig , updatedBuilders ) { if ( updatedBuilders . length === 0 ) { this . output . debug ( 'No builders were updated' ) ; return ; } for ( const buildMatch of this . buildMatches . values ( ) ) { const { src , builderWithPkg : { package : pkg } , } = buildMatch ; if ( isOfficialRuntime ( 'static' , pkg . name ) ) continue ; if ( pkg . name && updatedBuilders . includes ( pkg . name ) ) { shutdownBuilder ( buildMatch , this . output ) ; this . buildMatches . delete ( src ) ; this . output . debug ( `Invalidated build match for "${ src }"` ) ; } } await this . updateBuildMatches ( vercelConfig ) ; } async getLocalEnv ( fileName , base ? ) { const filePath = join ( this . cwd , fileName ) ; let env = { } ; try { const dotenv = await fs . readFile ( filePath , 'utf8' ) ; this . output . debug ( `Using local env: ${ filePath }` ) ; env = parseDotenv ( dotenv ) ; env = this . injectSystemValuesInDotenv ( env ) ; } catch ( err ) { if ( err . code !== 'ENOENT' ) { throw err ; } } try { return { ... this . validateEnvConfig ( fileName , base || { } , env ) , } ; } catch ( err ) { if ( err instanceof MissingDotenvVarsError ) { this . output . error ( err . message ) ; await this . exit ( ) ; } else { throw err ; } } return { } ; } clearVercelConfigPromise = ( ) => { this . getVercelConfigPromise = null ; } ; getVercelConfig ( ) { if ( this . getVercelConfigPromise ) { return this . getVercelConfigPromise ; } this . getVercelConfigPromise = this . _getVercelConfig ( ) ; const clear = this . clearVercelConfigPromise ; this . getVercelConfigPromise . finally ( clear ) ; return this . getVercelConfigPromise ; } async _getVercelConfig ( ) { const configPath = getVercelConfigPath ( this . cwd ) ; const [ pkg = null , vercelConfig = { version : 2 , [ fileNameSymbol ] : 'vercel.json' } , ] = await Promise . all ( [ this . readJsonFile < PackageJson > ( 'package.json' ) , this . readJsonFile < VercelConfig > ( configPath ) , ] ) ; await this . validateVercelConfig ( vercelConfig ) ; const { error : routeError , routes : maybeRoutes } = getTransformedRoutes ( { nowConfig : vercelConfig , } ) ; if ( routeError ) { this . output . prettyError ( routeError ) ; await this . exit ( ) ; } vercelConfig . routes = maybeRoutes || [ ] ; if ( ! vercelConfig . builds || vercelConfig . builds . length === 0 ) { const featHandleMiss = true ; const { projectSettings , cleanUrls , trailingSlash } = vercelConfig ; const opts = { output : this . output } ; const files = ( await getFiles ( this . cwd , opts ) ) . map ( f => relative ( this . cwd , f ) ) ; let { builders , warnings , errors , defaultRoutes , redirectRoutes , rewriteRoutes , errorRoutes , } = await detectBuilders ( files , pkg , { tag : getDistTag ( cliPkg . version ) === 'canary' ? 'canary' : 'latest' , functions : vercelConfig . functions , projectSettings : projectSettings || this . projectSettings , featHandleMiss , cleanUrls , trailingSlash , } ) ; if ( errors ) { this . output . error ( errors [ 0 ] . message ) ; await this . exit ( ) ; } if ( warnings && warnings . length > 0 ) { warnings . forEach ( warning => this . output . warn ( warning . message , null , warning . link , warning . action ) ) ; } const { reason , metadata } = await detectFileSystemAPI ( { files , builders : builders || [ ] , projectSettings : projectSettings || this . projectSettings || { } , vercelConfig , pkg , tag : '' , enableFlag : true , } ) ; if ( reason ) { if ( metadata . hasMiddleware ) { this . output . error ( `Detected middleware usage which requires the latest API. ${ reason }` ) ; await this . exit ( ) ; } else if ( metadata . plugins . length > 0 ) { this . output . error ( `Detected CLI plugins which requires the latest API. ${ reason }` ) ; await this . exit ( ) ; } else { this . output . warn ( `Unable to use latest API. ${ reason }` ) ; } } if ( builders ) { if ( this . devCommand ) { builders = builders . filter ( filterFrontendBuilds ) ; } vercelConfig . builds = vercelConfig . builds || [ ] ; vercelConfig . builds . push ( ... builders ) ; delete vercelConfig . functions ; } let routes = [ ] ; routes . push ( ... ( redirectRoutes || [ ] ) ) ; routes . push ( ... appendRoutesToPhase ( { routes : vercelConfig . routes , newRoutes : rewriteRoutes , phase : 'filesystem' , } ) ) ; routes = appendRoutesToPhase ( { routes , newRoutes : errorRoutes , phase : 'error' , } ) ; routes . push ( ... ( defaultRoutes || [ ] ) ) ; vercelConfig . routes = routes ; } if ( Array . isArray ( vercelConfig . builds ) ) { if ( this . devCommand ) { vercelConfig . builds = vercelConfig . builds . filter ( filterFrontendBuilds ) ; } vercelConfig . builds . sort ( sortBuilders ) ; } await this . validateVercelConfig ( vercelConfig ) ; vercelConfig . routes = ( vercelConfig . routes || [ ] ) . filter ( route => { if ( 'has' in route ) { if ( ! this . vercelConfigWarning ) { this . vercelConfigWarning = true ; this . output . warn ( `The "has" property in ${ vercelConfig [ fileNameSymbol ] } will be ignored during development. Deployments will work as expected.` ) ; } return false ; } return true ; } ) ; this . caseSensitive = hasNewRoutingProperties ( vercelConfig ) ; this . apiDir = detectApiDirectory ( vercelConfig . builds || [ ] ) ; this . apiExtensions = detectApiExtensions ( vercelConfig . builds || [ ] ) ; let [ runEnv , buildEnv ] = await Promise . all ( [ this . getLocalEnv ( '.env' , vercelConfig . env ) , this . getLocalEnv ( '.env.build' , vercelConfig . build ?. env ) , ] ) ; let allEnv = { ... buildEnv , ... runEnv } ; if ( Object . keys ( allEnv ) . length === 0 ) { const cloudEnv = exposeSystemEnvs ( this . projectEnvs || [ ] , this . systemEnvValues || [ ] , this . projectSettings ?. autoExposeSystemEnvs , new URL ( this . address ) . host ) ; allEnv = { ... cloudEnv } ; runEnv = { ... cloudEnv } ; buildEnv = { ... cloudEnv } ; } runEnv [ 'NOW_REGION' ] = 'dev1' ; buildEnv [ 'NOW_REGION' ] = 'dev1' ; allEnv [ 'NOW_REGION' ] = 'dev1' ; if ( this . projectSettings ?. autoExposeSystemEnvs ) { runEnv [ 'VERCEL_REGION' ] = 'dev1' ; } this . envConfigs = { buildEnv , runEnv , allEnv } ; return vercelConfig ; } async readJsonFile < T > ( filePath ) { let rel , abs ; if ( isAbsolute ( filePath ) ) { rel = path . relative ( this . cwd , filePath ) ; abs = filePath ; } else { rel = filePath ; abs = join ( this . cwd , filePath ) ; } this . output . debug ( `Reading \`${ rel }\` file` ) ; try { const raw = await fs . readFile ( abs , 'utf8' ) ; const parsed = JSON . parse ( raw ) ; parsed [ fileNameSymbol ] = rel ; return parsed ; } catch ( err ) { if ( err . code === 'ENOENT' ) { this . output . debug ( `No \`${ rel }\` file present` ) ; } else if ( err . name === 'SyntaxError' ) { this . output . warn ( `There is a syntax error in the \`${ rel }\` file: ${ err . message }` ) ; } else { throw err ; } } } async tryValidateOrExit ( config , validate ) { const message = validate ( config ) ; if ( message ) { this . output . error ( message ) ; await this . exit ( 1 ) ; } } async validateVercelConfig ( config ) { if ( config . version === 1 ) { this . output . error ( 'Cannot run `version: 1` projects.' ) ; await this . exit ( 1 ) ; return ; } const error = validateConfig ( config ) ; if ( error ) { this . output . prettyError ( error ) ; await this . exit ( 1 ) ; } } validateEnvConfig ( type , env = { } , localEnv = { } ) { const missing = Object . entries ( env ) . filter ( ( [ name , value ] ) => typeof value === 'string' && value . startsWith ( '@' ) && ! hasOwnProperty ( localEnv , name ) ) . map ( ( [ name ] ) => name ) ; if ( missing . length > 0 ) { throw new MissingDotenvVarsError ( type , missing ) ; } const merged = { ... env , ... localEnv } ; let hasInvalidName = false ; for ( const key of Object . keys ( merged ) ) { if ( ! /^[a-zA-Z][a-zA-Z0-9_]*$/ . test ( key ) ) { this . output . warn ( `Ignoring ${ type . split ( '.' ) . slice ( 1 ) . reverse ( ) . join ( ' ' ) } var ${ JSON . stringify ( key ) } because name is invalid` ) ; hasInvalidName = true ; delete merged [ key ] ; } } if ( hasInvalidName ) { this . output . log ( 'Env var names must start with letters, and can only contain alphanumeric characters and underscores' ) ; } return merged ; } injectSystemValuesInDotenv ( env ) { for ( const name of Object . keys ( env ) ) { if ( name === 'VERCEL_URL' ) { env [ 'VERCEL_URL' ] = new URL ( this . address ) . host ; } else if ( name === 'VERCEL_REGION' ) { env [ 'VERCEL_REGION' ] = 'dev1' ; } } return env ; } /**
     * Create an array of from builder inputs
     * and filter them
     */ resolveBuildFiles ( files ) { return Object . keys ( files ) . filter ( this . filter ) ; } start ( ... listenSpec ) { if ( ! this . startPromise ) { this . startPromise = this . _start ( ... listenSpec ) . catch ( err => { this . stop ( ) ; throw err ; } ) ; } return this . startPromise ; } /**
     * Launches the `vercel dev` server.
     */ async _start ( ... listenSpec ) { if ( ! fs . existsSync ( this . cwd ) ) { throw new Error ( `${ chalk . bold ( this . cwd ) } doesn't exist` ) ; } if ( ! fs . lstatSync ( this . cwd ) . isDirectory ( ) ) { throw new Error ( `${ chalk . bold ( this . cwd ) } is not a directory` ) ; } const { ig } = await getVercelIgnore ( this . cwd ) ; this . filter = ig . createFilter ( ) ; let address = null ; while ( typeof address !== 'string' ) { try { address = await listen ( this . server , ... listenSpec ) ; } catch ( err ) { this . output . debug ( `Got listen error: ${ err . code }` ) ; if ( err . code === 'EADDRINUSE' ) { if ( typeof listenSpec [ 0 ] === 'number' ) { this . output . note ( `Requested port ${ chalk . yellow ( String ( listenSpec [ 0 ] ) ) } is already in use` ) ; listenSpec [ 0 ] ++ ; } else { this . output . error ( `Requested socket ${ chalk . cyan ( listenSpec [ 0 ] ) } is already in use` ) ; process . exit ( 1 ) ; } } else { throw err ; } } } this . address = address . replace ( '[::]' , 'localhost' ) . replace ( '127.0.0.1' , 'localhost' ) ; const vercelConfig = await this . getVercelConfig ( ) ; const devCommandPromise = this . runDevCommand ( ) ; const files = await getFiles ( this . cwd , { output : this . output } ) ; this . files = { } ; for ( const fsPath of files ) { let path = relative ( this . cwd , fsPath ) ; const { mode } = await fs . stat ( fsPath ) ; this . files [ path ] = new FileFsRef ( { mode , fsPath } ) ; const extensionless = this . getExtensionlessFile ( path ) ; if ( extensionless ) { this . files [ extensionless ] = new FileFsRef ( { mode , fsPath } ) ; } } const builders = new Set < string > ( ( vercelConfig . builds || [ ] ) . filter ( ( b ) => b . use ) . map ( ( b ) => b . use ) ) ; await installBuilders ( builders , this . output ) ; await this . updateBuildMatches ( vercelConfig , true ) ; this . updateBuildersTimeout = setTimeout ( ( ) => { this . updateBuildersPromise = updateBuilders ( builders , this . output ) . then ( updatedBuilders => { this . updateBuildersPromise = null ; this . invalidateBuildMatches ( vercelConfig , updatedBuilders ) ; } ) . catch ( err => { this . updateBuildersPromise = null ; this . output . prettyError ( err ) ; this . output . debug ( err . stack ) ; } ) ; } , ms ( '30s' ) ) ; const blockingBuilds = Array . from ( this . buildMatches . values ( ) ) . filter ( needsBlockingBuild ) ; if ( blockingBuilds . length > 0 ) { this . output . log ( `Creating initial ${ plural ( 'build' , blockingBuilds . length ) }` ) ; for ( const match of blockingBuilds ) { await executeBuild ( vercelConfig , this , this . files , match , null , true ) ; } this . output . success ( 'Build completed' ) ; } await fs . mkdirp ( this . devCacheDir ) ; this . watcher = watch ( this . cwd , { ignored : ( path ) => ! this . filter ( path ) , ignoreInitial : true , useFsEvents : false , usePolling : false , persistent : true , } ) ; this . watcher . on ( 'add' , ( path ) => { this . enqueueFsEvent ( 'add' , path ) ; } ) ; this . watcher . on ( 'change' , ( path ) => { this . enqueueFsEvent ( 'change' , path ) ; } ) ; this . watcher . on ( 'unlink' , ( path ) => { this . enqueueFsEvent ( 'unlink' , path ) ; } ) ; this . watcher . on ( 'error' , ( err ) => { this . output . error ( `Watcher error: ${ err }` ) ; } ) ; await once ( this . watcher , 'ready' ) ; this . server . on ( 'upgrade' , async ( req , socket , head ) => { await this . startPromise ; if ( ! this . devProcessPort ) { this . output . debug ( `Detected "upgrade" event, but closing socket because no frontend dev server is running` ) ; socket . destroy ( ) ; return ; } const target = `http://127.0.0.1:${ this . devProcessPort }` ; this . output . debug ( `Detected "upgrade" event, proxying to ${ target }` ) ; this . proxy . ws ( req , socket , head , { target } ) ; } ) ; await devCommandPromise ; this . output . ready ( `Available at ${ link ( this . address ) }` ) ; } /**
     * Shuts down the `vercel dev` server, and cleans up any temporary resources.
     */ async stop ( exitCode ? ) { const { devProcess } = this ; const { debug } = this . output ; if ( this . stopping ) return ; this . stopping = true ; if ( typeof this . updateBuildersTimeout !== 'undefined' ) { clearTimeout ( this . updateBuildersTimeout ) ; } const ops = [ ] ; for ( const match of this . buildMatches . values ( ) ) { ops . push ( shutdownBuilder ( match , this . output ) ) ; } if ( devProcess ) { ops . push ( new Promise < void > ( ( resolve , reject ) => { devProcess . once ( 'exit' , ( ) => resolve ( ) ) ; try { process . kill ( devProcess . pid ) ; } catch ( err ) { if ( err . code === 'ESRCH' ) { return resolve ( ) ; } reject ( err ) ; } } ) ) ; } ops . push ( close ( this . server ) ) ; if ( this . watcher ) { debug ( `Closing file watcher` ) ; ops . push ( this . watcher . close ( ) ) ; } if ( this . updateBuildersPromise ) { debug ( `Waiting for builders update to complete` ) ; ops . push ( this . updateBuildersPromise ) ; } for ( const pid of this . devServerPids ) { ops . push ( this . killBuilderDevServer ( pid ) ) ; } ops . push ( builderDirPromise ) ; try { await Promise . all ( ops ) ; } catch ( err ) { if ( err . code === 'ERR_SERVER_NOT_RUNNING' || err . message === 'Not running' ) { process . exit ( exitCode || 0 ) ; } else { throw err ; } } } async killBuilderDevServer ( pid ) { 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 , res , requestId ) { return this . sendError ( req , res , requestId , 'NOT_FOUND' , 404 ) ; } async sendError ( req , res , requestId , errorCode ? , statusCode = 500 , headers = { } ) { res . statusCode = statusCode ; this . setResponseHeaders ( res , requestId , headers ) ; const http_status_description = generateHttpStatusDescription ( statusCode ) ; const error_code = errorCode || http_status_description ; const errorMessage = generateErrorMessage ( statusCode , error_code ) ; let body ; const { accept = 'text/plain' } = req . headers ; if ( accept . includes ( 'json' ) ) { res . setHeader ( 'content-type' , 'application/json' ) ; const json = JSON . stringify ( { error : { code : statusCode , message : errorMessage . title , } , } ) ; body = `${ json }\n` ; } else if ( accept . includes ( 'html' ) ) { res . setHeader ( 'content-type' , 'text/html; charset=utf-8' ) ; let view ; if ( statusCode === 404 ) { view = errorTemplate404 ( { ... errorMessage , http_status_code : statusCode , http_status_description , error_code , request_id : requestId , } ) ; } else if ( statusCode === 502 ) { view = errorTemplate502 ( { ... errorMessage , http_status_code : statusCode , http_status_description , error_code , request_id : requestId , } ) ; } else { view = errorTemplate ( { http_status_code : statusCode , http_status_description , request_id : requestId , } ) ; } body = errorTemplateBase ( { http_status_code : statusCode , http_status_description , view , } ) ; } else { res . setHeader ( 'content-type' , 'text/plain; charset=utf-8' ) ; body = `${ errorMessage . title }\n\n${ error_code }\n` ; } res . end ( body ) ; } async sendRedirect ( req , res , requestId , location , statusCode = 302 ) { this . output . debug ( `Redirect ${ statusCode }: ${ location }` ) ; res . statusCode = statusCode ; this . setResponseHeaders ( res , requestId , { location } ) ; let body ; const { accept = 'text/plain' } = req . headers ; if ( accept . includes ( 'json' ) ) { res . setHeader ( 'content-type' , 'application/json' ) ; const json = JSON . stringify ( { redirect : location , status : String ( statusCode ) , } ) ; body = `${ json }\n` ; } else if ( accept . includes ( 'html' ) ) { res . setHeader ( 'content-type' , 'text/html' ) ; body = redirectTemplate ( { location , statusCode } ) ; } else { res . setHeader ( 'content-type' , 'text/plain' ) ; body = `Redirecting to ${ location } (${ statusCode })\n` ; } res . end ( body ) ; } getRequestIp ( req ) { return req . connection . remoteAddress || '127.0.0.1' ; } /**
     * Sets the response `headers` including the platform headers to `res`.
     */ setResponseHeaders ( res , requestId , headers = { } ) { const allHeaders = { 'cache-control' : 'public, max-age=0, must-revalidate' , ... headers , server : 'Vercel' , 'x-vercel-id' : requestId , 'x-vercel-cache' : 'MISS' , } ; for ( const [ name , value ] of Object . entries ( allHeaders ) ) { res . setHeader ( name , value ) ; } } /**
     * Returns the request `headers` that will be sent to the Lambda.
     */ getProxyHeaders ( req , requestId , xfwd ) { const ip = this . getRequestIp ( req ) ; const { host } = req . headers ; const headers = { connection : 'close' , 'x-real-ip' : ip , 'x-vercel-deployment-url' : host , 'x-vercel-forwarded-for' : ip , 'x-vercel-id' : requestId , } ; if ( xfwd ) { headers [ 'x-forwarded-host' ] = host ; headers [ 'x-forwarded-proto' ] = 'http' ; headers [ 'x-forwarded-for' ] = ip ; } return headers ; } async triggerBuild ( match , requestPath , req , vercelConfig , previousBuildResult ? , filesChanged ? , filesRemoved ? ) { const buildKey = requestPath === null ? match . entrypoint : `${ match . entrypoint }-${ requestPath }` ; let buildPromise = this . inProgressBuilds . get ( buildKey ) ; if ( buildPromise ) { let msg = `De-duping build "${ buildKey }"` ; if ( req ) { msg += ` for "${ req . method } ${ req . url }"` ; } this . output . debug ( msg ) ; } else { if ( previousBuildResult ) { for ( const [ name ] of Object . entries ( previousBuildResult . output ) ) { this . output . debug ( `Removing asset "${ name }"` ) ; delete match . buildOutput [ name ] ; } } let msg = `Building asset "${ buildKey }"` ; if ( req ) { msg += ` for "${ req . method } ${ req . url }"` ; } this . output . debug ( msg ) ; buildPromise = executeBuild ( vercelConfig , this , this . files , match , requestPath , false , filesChanged , filesRemoved ) ; this . inProgressBuilds . set ( buildKey , buildPromise ) ; } try { await buildPromise ; } finally { this . output . debug ( `Built asset ${ buildKey }` ) ; this . inProgressBuilds . delete ( buildKey ) ; } } getExtensionlessFile = ( path ) => { const ext = extname ( path ) ; if ( this . apiDir && path . startsWith ( this . apiDir + '/' ) && this . apiExtensions . has ( ext ) ) { return path . slice ( 0 , - ext . length ) ; } return null ; } ; /**
     * DevServer HTTP handler
     */ devServerHandler : HttpHandler = async ( req , res ) => { await this . startPromise ; let requestId = generateRequestId ( this . podId ) ; if ( this . stopping ) { res . setHeader ( 'Connection' , 'close' ) ; await this . send404 ( req , res , requestId ) ; return ; } const method = req . method || 'GET' ; this . output . debug ( `${ chalk . bold ( method ) } ${ req . url }` ) ; try { const vercelConfig = await this . getVercelConfig ( ) ; await this . serveProjectAsNowV2 ( req , res , requestId , vercelConfig ) ; } catch ( err ) { console . error ( err ) ; this . output . debug ( err . stack ) ; if ( ! res . finished ) { res . statusCode = 500 ; res . end ( err . message ) ; } } } ; /**
     * This is the equivalent to now-proxy exit_with_status() function.
     */ exitWithStatus = async ( match , routeResult , phase , req , res , requestId ) : Promise < boolean > => { const { status , headers , dest } = routeResult ; const location = headers [ 'location' ] || dest ; if ( status && location && 300 <= status && status <= 399 ) { this . output . debug ( `Route found with redirect status code ${ status }` ) ; await this . sendRedirect ( req , res , requestId , location , status ) ; return true ; } if ( ! match && status && phase !== 'miss' ) { this . output . debug ( `Route found with with status code ${ status }` ) ; await this . sendError ( req , res , requestId , '' , status , headers ) ; return true ; } return false ; } ; runDevMiddleware = async ( req , res ) => { const { devMiddlewarePlugins } = await loadCliPlugins ( this . cwd , this . output ) ; try { for ( let plugin of devMiddlewarePlugins ) { const result = await plugin . plugin . runDevMiddleware ( req , res , this . cwd ) ; if ( result . finished ) { return result ; } } return { finished : false } ; } catch ( e ) { return { finished : true , error : e , } ; } } ; /**
     * Serve project directory as a v2 deployment.
     */ serveProjectAsNowV2 = async ( req , res , requestId , vercelConfig , routes = vercelConfig . routes , callLevel = 0 ) => { const { debug } = this . output ; const parsed = url . parse ( req . url || '/' ) ; if ( typeof parsed . pathname === 'string' && parsed . pathname . includes ( '//' ) ) { let location = parsed . pathname . replace ( /\/+/g , '/' ) ; if ( parsed . search ) { location += parsed . search ; } if ( req . method === 'GET' ) { await this . sendRedirect ( req , res , requestId , location , 301 ) ; return ; } debug ( `Rewriting URL from "${ req . url }" to "${ location }"` ) ; req . url = location ; } if ( callLevel === 0 ) { await this . updateBuildMatches ( vercelConfig ) ; } if ( this . blockingBuildsPromise ) { debug ( 'Waiting for builds to complete before handling request' ) ; await this . blockingBuildsPromise ; } const getReqUrl = ( rr ) : string | undefined => { if ( rr . dest ) { if ( rr . uri_args ) { const destParsed = url . parse ( rr . dest , true ) ; Object . assign ( destParsed . query , rr . uri_args ) ; return url . format ( destParsed ) ; } return rr . dest ; } return req . url ; } ; const handleMap = getRoutesTypes ( routes ) ; const missRoutes = handleMap . get ( 'miss' ) || [ ] ; const hitRoutes = handleMap . get ( 'hit' ) || [ ] ; const errorRoutes = handleMap . get ( 'error' ) || [ ] ; const filesystemRoutes = handleMap . get ( 'filesystem' ) || [ ] ; const phases = [ null , 'filesystem' ] ; let routeResult = null ; let match = null ; let statusCode ; let prevUrl = req . url ; let prevHeaders = { } ; const middlewareResult = await this . runDevMiddleware ( req , res ) ; if ( middlewareResult ) { if ( middlewareResult . error ) { this . sendError ( req , res , requestId , 'EDGE_FUNCTION_INVOCATION_FAILED' , 500 ) ; return ; } if ( middlewareResult . finished ) { return ; } if ( middlewareResult . pathname ) { const origUrl = url . parse ( req . url || '/' , true ) ; origUrl . pathname = middlewareResult . pathname ; prevUrl = url . format ( origUrl ) ; } if ( middlewareResult . query && prevUrl ) { const origUrl = url . parse ( req . url || '/' , true ) ; delete origUrl . search ; Object . assign ( origUrl . query , middlewareResult . query ) ; prevUrl = url . format ( origUrl ) ; } } for ( const phase of phases ) { statusCode = undefined ; const phaseRoutes = handleMap . get ( phase ) || [ ] ; routeResult = await devRouter ( prevUrl , req . method , phaseRoutes , this , vercelConfig , prevHeaders , missRoutes , phase ) ; prevUrl = routeResult . continue && routeResult . dest ? getReqUrl ( routeResult ) : req . url ; prevHeaders = routeResult . continue && routeResult . headers ? routeResult . headers : { } ; if ( routeResult . isDestUrl ) { const destParsed = url . parse ( routeResult . dest , true ) ; delete destParsed . search ; Object . assign ( destParsed . query , routeResult . uri_args ) ; const destUrl = url . format ( destParsed ) ; debug ( `ProxyPass: ${ destUrl }` ) ; this . setResponseHeaders ( res , requestId ) ; return proxyPass ( req , res , destUrl , this , requestId ) ; } match = await findBuildMatch ( this . buildMatches , this . files , routeResult . dest , this , vercelConfig ) ; if ( await this . exitWithStatus ( match , routeResult , phase , req , res , requestId ) ) { return ; } if ( ! match && missRoutes . length > 0 ) { routeResult = await devRouter ( getReqUrl ( routeResult ) , req . method , missRoutes , this , vercelConfig , routeResult . headers , [ ] , 'miss' ) ; match = await findBuildMatch ( this . buildMatches , this . files , routeResult . dest , this , vercelConfig ) ; if ( await this . exitWithStatus ( match , routeResult , phase , req , res , requestId ) ) { return ; } } else if ( match && hitRoutes . length > 0 ) { const prevStatus = routeResult . status ; routeResult = await devRouter ( getReqUrl ( routeResult ) , req . method , hitRoutes , this , vercelConfig , routeResult . headers , [ ] , 'hit' ) ; routeResult . status = prevStatus ; } statusCode = routeResult . status ; if ( match ) { break ; } if ( phase === null && filesystemRoutes . length === 0 ) { break ; } } if ( ! match && routeResult && errorRoutes . length > 0 ) { const routeResultForError = await devRouter ( getReqUrl ( routeResult ) , req . method , errorRoutes , this , vercelConfig , routeResult . headers , [ ] , 'error' ) ; const { matched_route } = routeResultForError ; const matchForError = await findBuildMatch ( this . buildMatches , this . files , routeResultForError . dest , this , vercelConfig ) ; if ( matchForError ) { debug ( `Route match detected in error phase, breaking loop` ) ; routeResult = routeResultForError ; statusCode = routeResultForError . status ; match = matchForError ; } else if ( matched_route && matched_route . src && ! matched_route . dest ) { debug ( 'Route without `dest` detected in error phase, attempting to exit early' ) ; if ( await this . exitWithStatus ( matchForError , routeResultForError , 'error' , req , res , requestId ) ) { return ; } } } if ( ! routeResult ) { throw new Error ( 'Expected Route Result but none was found.' ) ; } const { dest , headers , uri_args } = routeResult ; for ( const [ name , value ] of Object . entries ( headers ) ) { res . setHeader ( name , value ) ; } if ( statusCode ) { Object . defineProperty ( res , 'statusCode' , { get ( ) { return statusCode ; } , set ( _ ) { } , } ) ; } const requestPath = dest . replace ( /^\// , '' ) ; if ( ! match ) { if ( this . devProcessPort ) { const upstream = `http://127.0.0.1:${ this . devProcessPort }` ; debug ( `Proxying to frontend dev server: ${ upstream }` ) ; const headers = this . getProxyHeaders ( req , requestId , false ) ; for ( const [ name , value ] of Object . entries ( headers ) ) { req . headers [ name ] = value ; } this . setResponseHeaders ( res , requestId ) ; const origUrl = url . parse ( req . url || '/' , true ) ; delete origUrl . search ; origUrl . pathname = dest ; Object . assign ( origUrl . query , uri_args ) ; req . url = url . format ( origUrl ) ; return proxyPass ( req , res , upstream , this , requestId , false ) ; } if ( ( statusCode === 404 && routeResult . phase === 'miss' ) || ! this . renderDirectoryListing ( req , res , requestPath , requestId ) ) { await this . send404 ( req , res , requestId ) ; } return ; } const buildRequestPath = match . buildResults . has ( null ) ? null : requestPath ; const buildResult = match . buildResults . get ( buildRequestPath ) ; if ( buildResult && Array . isArray ( buildResult . routes ) && buildResult . routes . length > 0 ) { const origUrl = url . parse ( req . url || '/' , true ) ; delete origUrl . search ; origUrl . pathname = dest ; Object . assign ( origUrl . query , uri_args ) ; const newUrl = url . format ( origUrl ) ; debug ( `Checking build result's ${ buildResult . routes . length } \`routes\` to match ${ newUrl }` ) ; const matchedRoute = await devRouter ( newUrl , req . method , buildResult . routes , this , vercelConfig ) ; if ( matchedRoute . found && callLevel === 0 ) { debug ( `Found matching route ${ matchedRoute . dest } for ${ newUrl }` ) ; req . url = newUrl ; await this . serveProjectAsNowV2 ( req , res , requestId , vercelConfig , buildResult . routes , callLevel + 1 ) ; return ; } } const { builder , package : builderPkg } = match . builderWithPkg ; if ( typeof builder . startDevServer === 'function' ) { let devServerResult = null ; try { const { envConfigs , files , devCacheDir , cwd : workPath } = this ; devServerResult = await builder . startDevServer ( { files , entrypoint : match . entrypoint , workPath , config : match . config || { } , meta : { isDev : true , requestPath , devCacheDir , env : { ... envConfigs . runEnv } , buildEnv : { ... envConfigs . buildEnv } , } , } ) ; } catch ( err ) { if ( err . code === 'ENOENT' ) { err . message = `Command not found: ${ chalk . cyan ( err . path , ... err . spawnargs ) }\nPlease ensure that ${ cmd ( err . path ) } is properly installed` ; err . link = 'https://vercel.link/command-not-found' ; } this . output . prettyError ( err ) ; await this . sendError ( req , res , requestId , 'NO_RESPONSE_FROM_FUNCTION' , 502 ) ; return ; } if ( devServerResult ) { requestId = 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 })` ) ; const parsed = url . parse ( req . url || '/' , true ) ; Object . assign ( parsed . query , uri_args ) ; req . url = url . format ( { pathname : parsed . pathname , query : parsed . query , } ) ; const headers = this . getProxyHeaders ( req , requestId , false ) ; for ( const [ name , value ] of Object . entries ( headers ) ) { req . headers [ name ] = value ; } this . setResponseHeaders ( res , requestId ) ; return proxyPass ( req , res , `http://127.0.0.1:${ port }` , this , requestId , false ) ; } else { debug ( `Skipping \`startDevServer()\` for ${ match . entrypoint }` ) ; } } let foundAsset = findAsset ( match , requestPath , vercelConfig ) ; if ( ! foundAsset && callLevel === 0 ) { await this . triggerBuild ( match , buildRequestPath , req , vercelConfig ) ; foundAsset = findAsset ( match , requestPath , vercelConfig ) ; } if ( this . devProcessPort && ( ! foundAsset || ( foundAsset && foundAsset . asset . type !== 'Lambda' ) ) ) { debug ( 'Proxying to frontend dev server' ) ; const headers = this . getProxyHeaders ( req , requestId , false ) ; for ( const [ name , value ] of Object . entries ( headers ) ) { req . headers [ name ] = value ; } this . setResponseHeaders ( res , requestId ) ; return proxyPass ( req , res , `http://127.0.0.1:${ this . devProcessPort }` , this , requestId , false ) ; } if ( ! foundAsset ) { await this . send404 ( req , res , requestId ) ; return ; } const { asset , assetKey } = foundAsset ; debug ( `Serving asset: [${ asset . type }] ${ assetKey } ${ ( asset as any ) . contentType || '' }` ) ; switch ( asset . type ) { case 'FileFsRef' : this . setResponseHeaders ( res , requestId ) ; req . url = `/${ basename ( asset . fsPath ) }` ; return serveStaticFile ( req , res , dirname ( asset . fsPath ) , { headers : [ { source : '**/*' , headers : [ { key : 'Content-Type' , value : asset . contentType || getMimeType ( assetKey ) , } , ] , } , ] , } ) ; case 'FileBlob' : const headers = { 'Content-Length' : asset . data . length , 'Content-Type' : asset . contentType || getMimeType ( assetKey ) , } ; this . setResponseHeaders ( res , requestId , headers ) ; res . end ( asset . data ) ; return ; case 'Lambda' : if ( ! asset . fn ) { await this . sendError ( req , res , requestId , 'INTERNAL_LAMBDA_NOT_FOUND' ) ; return ; } requestId = generateRequestId ( this . podId , true ) ; const parsed = url . parse ( req . url || '/' , true ) ; Object . assign ( parsed . query , uri_args ) ; const path = url . format ( { pathname : parsed . pathname , query : parsed . query , } ) ; const body = await rawBody ( req ) ; const payload = { method : req . method || 'GET' , host : req . headers . host , path , headers : { ... req . headers , ... this . getProxyHeaders ( req , requestId , true ) , } , encoding : 'base64' , body : body . toString ( 'base64' ) , } ; debug ( `Invoking lambda: "${ assetKey }" with ${ path }` ) ; let result ; try { result = await asset . fn < InvokeResult > ( { Action : 'Invoke' , body : JSON . stringify ( payload ) , } ) ; } catch ( err ) { console . error ( err ) ; await this . sendError ( req , res , requestId , 'NO_RESPONSE_FROM_FUNCTION' , 502 ) ; return ; } if ( ! statusCode ) { res . statusCode = result . statusCode ; } this . setResponseHeaders ( res , requestId , result . headers ) ; let resBody ; if ( result . encoding === 'base64' && typeof result . body === 'string' ) { resBody = Buffer . from ( result . body , 'base64' ) ; } else { resBody = result . body ; } return res . end ( resBody ) ; default : await this . sendError ( req , res , requestId , 'UNKNOWN_ASSET_TYPE' ) ; } } ; renderDirectoryListing ( _req , res , requestPath , requestId ) { if ( this . projectSettings ?. directoryListing === false ) { return false ; } let prefix = requestPath ; if ( prefix . length > 0 && ! prefix . endsWith ( '/' ) ) { prefix += '/' ; } const dirs = new Set ( ) ; const files = Array . from ( this . buildMatches . keys ( ) ) . filter ( p => { const base = basename ( p ) ; if ( base === 'now.json' || base === 'vercel.json' || base === '.nowignore' || base === '.vercelignore' || ! p . startsWith ( prefix ) ) { return false ; } const rel = relative ( prefix , p ) ; if ( rel . includes ( '/' ) ) { const dir = rel . split ( '/' ) [ 0 ] ; if ( dirs . has ( dir ) ) { return false ; } dirs . add ( dir ) ; } return true ; } ) . map ( p => { let base = basename ( p ) ; let ext = '' ; let type = 'file' ; let href ; const rel = relative ( prefix , p ) ; if ( rel . includes ( '/' ) ) { type = 'folder' ; base = rel . split ( '/' ) [ 0 ] ; href = `/${ prefix }${ base }/` ; } else { ext = extname ( p ) . substring ( 1 ) ; href = `/${ prefix }${ base }` ; } return { type , relative : href , ext , title : href , base , } ; } ) ; if ( files . length === 0 ) { return false ; } const directory = `/${ prefix }` ; const paths = [ { name : directory , url : requestPath , } , ] ; const directoryHtml = directoryTemplate ( { files , paths , directory , } ) ; this . setResponseHeaders ( res , requestId ) ; res . setHeader ( 'Content-Type' , 'text/html; charset=utf-8' ) ; res . setHeader ( 'Content-Length' , String ( Buffer . byteLength ( directoryHtml , 'utf8' ) ) ) ; res . end ( directoryHtml ) ; return true ; } async hasFilesystem ( dest , vercelConfig ) { if ( await findBuildMatch ( this . buildMatches , this . files , dest , this , vercelConfig , true ) ) { return true ; } return false ; } isCaseSensitive ( ) { return this . caseSensitive ; } async runDevCommand ( ) { const { devCommand , cwd } = this ; if ( ! devCommand ) { return ; } this . output . log ( `Running Dev Command ${ chalk . cyan . bold ( `“${ devCommand }”` ) }` ) ; const port = await getPort ( ) ; const env = { FORCE_COLOR : process . stdout . isTTY ? '1' : '0' , ... ( this . frameworkSlug === 'create-react-app' ? { BROWSER : 'none' } : { } ) , ... process . env , ... this . envConfigs . allEnv , PORT : `${ port }` , } ; let command = devCommand . replace ( /\$PORT/g , `${ port }` ) . replace ( /%PORT%/g , `${ port }` ) ; this . output . debug ( `Starting dev command with parameters : ${ JSON . stringify ( { cwd , command , port , } ) }` ) ; const isNpxAvailable = await which ( 'npx' ) . then ( ( ) => true ) . catch ( ( ) => false ) ; if ( isNpxAvailable ) { command = `npx --no-install ${ command }` ; } else { const isYarnAvailable = await which ( 'yarn' ) . then ( ( ) => true ) . catch ( ( ) => false ) ; if ( isYarnAvailable ) { command = `yarn run --silent ${ command }` ; } } this . output . debug ( `Spawning dev command: ${ command }` ) ; const devPort = new URL ( this . address ) . port ; const proxyPort = new RegExp ( port . toString ( ) , 'g' ) ; const p = spawnCommand ( command , { stdio : [ 'inherit' , 'pipe' , 'pipe' ] , cwd , env , } ) ; if ( ! p . stdout || ! p . stderr ) { throw new Error ( 'Expected child process to have stdout and stderr' ) ; } p . stderr . pipe ( process . stderr ) ; p . stdout . setEncoding ( 'utf8' ) ; p . stdout . on ( 'data' , ( data ) => { process . stdout . write ( data . replace ( proxyPort , devPort ) ) ; } ) ; p . on ( 'exit' , ( code ) => { if ( code > 0 ) { process . exit ( code ) ; } this . devProcessPort = undefined ; } ) ; await checkForPort ( port , 1000 * 60 * 5 ) ; this . devProcessPort = port ; this . devProcess = p ; } } /**
 * Mimic nginx's `proxy_pass` for routes using a URL as `dest`.
 */ function proxyPass ( req , res , dest , devServer , requestId , ignorePath = true ) { return devServer . proxy . web ( req , res , { target : dest , ignorePath } , ( error ) => { devServer . output . error ( `Failed to complete request to ${ req . url }: ${ error }` ) ; if ( ! res . headersSent ) { devServer . sendError ( req , res , requestId , 'NO_RESPONSE_FROM_FUNCTION' , 502 ) ; } } ) ; } /**
 * Handle requests for static files with serve-handler.
 */ function serveStaticFile ( req , res , cwd , opts ? ) { return serveHandler ( req , res , { public : cwd , cleanUrls : false , etag : true , ... opts , } ) ; } function close ( server ) { return new Promise ( ( resolve , reject ) => { server . close ( ( err ? ) => { if ( err ) { reject ( err ) ; } else { resolve ( ) ; } } ) ; } ) ; } /**
 * Generates a (fake) tracing ID for an HTTP request.
 *
 * Example: dev1:q4wlg-1562364135397-7a873ac99c8e
 */ function generateRequestId ( podId , isInvoke = false ) { const invoke = isInvoke ? 'dev1::' : '' ; return `dev1::${ invoke }${ [ podId , Date . now ( ) , randomBytes ( 6 ) . toString ( 'hex' ) , ] . join ( '-' ) }` ; } function hasOwnProperty ( obj , prop ) { return Object . prototype . hasOwnProperty . call ( obj , prop ) ; } async function findBuildMatch ( matches , files , requestPath , devServer , vercelConfig , isFilesystem = false ) { requestPath = requestPath . replace ( /^\// , '' ) ; let bestIndexMatch ; for ( const match of matches . values ( ) ) { if ( await shouldServe ( match , files , requestPath , devServer , vercelConfig , isFilesystem ) ) { if ( ! isIndex ( match . src ) ) { return match ; } else { bestIndexMatch = match ; if ( extname ( match . src ) === '.html' ) { return bestIndexMatch ; } } } } return bestIndexMatch || null ; } async function shouldServe ( match , files , requestPath , devServer , vercelConfig , isFilesystem = false ) { const { src , config , builderWithPkg : { builder } , } = match ; const cleanSrc = src . endsWith ( '.html' ) ? src . slice ( 0 , - 5 ) : src ; const trimmedPath = requestPath . endsWith ( '/' ) ? requestPath . slice ( 0 , - 1 ) : requestPath ; if ( vercelConfig . cleanUrls && vercelConfig . trailingSlash && cleanSrc === trimmedPath ) { return true ; } else if ( vercelConfig . cleanUrls && ! vercelConfig . trailingSlash && cleanSrc === requestPath ) { return true ; } else if ( ! vercelConfig . cleanUrls && vercelConfig . trailingSlash && src === trimmedPath ) { return true ; } else if ( typeof builder . shouldServe === 'function' ) { const shouldServe = await builder . shouldServe ( { entrypoint : src , files , config : config || { } , requestPath , workPath : devServer . cwd , } ) ; if ( shouldServe ) { return true ; } } else if ( findAsset ( match , requestPath , vercelConfig ) ) { return true ; } else if ( ! isFilesystem && ( await findMatchingRoute ( match , requestPath , devServer , vercelConfig ) ) ) { return true ; } return false ; } async function findMatchingRoute ( match , requestPath , devServer , vercelConfig ) { const reqUrl = `/${ requestPath }` ; for ( const buildResult of match . buildResults . values ( ) ) { if ( ! Array . isArray ( buildResult . routes ) ) continue ; const route = await devRouter ( reqUrl , undefined , buildResult . routes , devServer , vercelConfig ) ; if ( route . found ) { return route ; } } } function findAsset ( match , requestPath , vercelConfig ) { if ( ! match . buildOutput ) { return ; } let assetKey = requestPath . replace ( /\/$/ , '' ) ; let asset = match . buildOutput [ requestPath ] ; if ( vercelConfig . trailingSlash && requestPath . endsWith ( '/' ) ) { asset = match . buildOutput [ requestPath . slice ( 0 , - 1 ) ] ; } if ( ! asset ) { for ( const [ name , a ] of Object . entries ( match . buildOutput ) ) { if ( isIndex ( name ) && dirnameWithoutDot ( name ) === assetKey ) { asset = a ; assetKey = name ; break ; } } } if ( asset ) { return { asset , assetKey } ; } } function dirnameWithoutDot ( path ) { let dir = dirname ( path ) ; if ( dir === '.' ) { dir = '' ; } return dir ; } function isIndex ( path ) { const ext = extname ( path ) ; const name = basename ( path , ext ) ; return name === 'index' ; } function minimatches ( files , pattern ) { return files . some ( file => file === pattern || minimatch ( file , pattern , { dot : true } ) ) ; } function fileChanged ( name , changed , removed ) { changed . add ( name ) ; removed . delete ( name ) ; } function fileRemoved ( name , files , changed , removed ) { delete files [ name ] ; changed . delete ( name ) ; removed . add ( name ) ; } function needsBlockingBuild ( buildMatch ) { const { builder } = buildMatch . builderWithPkg ; return typeof builder . shouldServe !== 'function' ; } async function checkForPort ( port , timeout ) { const start = Date . now ( ) ; while ( ! ( await isPortReachable ( port ) ) ) { if ( Date . now ( ) - start > timeout ) { throw new Error ( `Detecting port ${ port } timed out after ${ timeout }ms` ) ; } await sleep ( 100 ) ; } } function filterFrontendBuilds ( build ) { const { name } = npa ( build . use ) ; return ! frontendRuntimeSet . has ( name || '' ) ; } function hasNewRoutingProperties ( vercelConfig ) { return ( typeof vercelConfig . cleanUrls !== undefined || typeof vercelConfig . headers !== undefined || typeof vercelConfig . redirects !== undefined || typeof vercelConfig . rewrites !== undefined || typeof vercelConfig . trailingSlash !== undefined ) ; } function buildMatchEquals ( a ? , b ? ) { if ( ! a || ! b ) return false ; if ( a . src !== b . src ) return false ; if ( a . use !== b . use ) return false ; if ( ! deepEqual ( a . config || { } , b . config || { } ) ) return false ; return true ; }