diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index b520b9b5144b46..27981ac4004f92 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -169,6 +169,9 @@ cli '--force', `[boolean] force the optimizer to ignore the cache and re-bundle`, ) + .option('--watchStdin', `[boolean] watch stdin and exit on EOF`, { + default: !process.stdin.isTTY, + }) .action(async (root: string, options: ServerOptions & GlobalCLIOptions) => { filterDuplicateOptions(options) // output structure is preserved even after bundling so require() @@ -364,6 +367,9 @@ cli .option('--strictPort', `[boolean] exit if specified port is already in use`) .option('--open [path]', `[boolean | string] open browser on startup`) .option('--outDir ', `[string] output directory (default: dist)`) + .option('--watchStdin', `[boolean] watch stdin and exit on EOF`, { + default: !process.stdin.isTTY, + }) .action( async ( root: string, @@ -373,6 +379,7 @@ cli open?: boolean | string strictPort?: boolean outDir?: string + watchStdin?: boolean } & GlobalCLIOptions, ) => { filterDuplicateOptions(options) @@ -392,6 +399,7 @@ cli strictPort: options.strictPort, host: options.host, open: options.open, + watchStdin: options.watchStdin, }, }) server.printUrls() diff --git a/packages/vite/src/node/http.ts b/packages/vite/src/node/http.ts index ec1bf5e645641d..89560162811c99 100644 --- a/packages/vite/src/node/http.ts +++ b/packages/vite/src/node/http.ts @@ -67,6 +67,10 @@ export interface CommonServerOptions { * Specify server response headers. */ headers?: HttpServerHeaders + /** + * Watch stdin and exit on EOF + */ + watchStdin?: boolean } /** diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts index 614c16f8aaac06..c9c76bde95df43 100644 --- a/packages/vite/src/node/preview.ts +++ b/packages/vite/src/node/preview.ts @@ -60,6 +60,7 @@ export function resolvePreviewOptions( proxy: preview?.proxy ?? server.proxy, cors: preview?.cors ?? server.cors, headers: preview?.headers ?? server.headers, + watchStdin: preview?.watchStdin ?? server.watchStdin, } } @@ -151,7 +152,7 @@ export async function preview( // Promise used by `server.close()` to ensure `closeServer()` is only called once let closeServerPromise: Promise | undefined const closeServer = async () => { - teardownSIGTERMListener(closeServerAndExit) + teardownSIGTERMListener(config.preview.watchStdin, closeServerAndExit) await closeHttpServer() server.resolvedUrls = null } @@ -188,7 +189,7 @@ export async function preview( } } - setupSIGTERMListener(closeServerAndExit) + setupSIGTERMListener(config.preview.watchStdin, closeServerAndExit) // apply server hooks from plugins const postHooks: ((() => void) | void)[] = [] diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 486b245d9c71de..badb21b910ca28 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -527,7 +527,7 @@ export async function _createServer( let closeServerPromise: Promise | undefined const closeServer = async () => { if (!middlewareMode) { - teardownSIGTERMListener(closeServerAndExit) + teardownSIGTERMListener(config.server.watchStdin, closeServerAndExit) } await Promise.allSettled([ @@ -766,7 +766,7 @@ export async function _createServer( } if (!middlewareMode) { - setupSIGTERMListener(closeServerAndExit) + setupSIGTERMListener(config.server.watchStdin, closeServerAndExit) } const onHMRUpdate = async ( @@ -1045,6 +1045,7 @@ export const serverConfigDefaults = Object.freeze({ host: 'localhost', https: undefined, open: false, + watchStdin: false, proxy: undefined, cors: true, headers: {}, diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 582a3c2c6e43ef..c94a25e90bd422 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -1544,24 +1544,28 @@ const parentSigtermCallback: SigtermCallback = async (signal, exitCode) => { } export const setupSIGTERMListener = ( + watchStdin: boolean, callback: (signal?: 'SIGTERM', exitCode?: number) => Promise, ): void => { if (sigtermCallbacks.size === 0) { process.once('SIGTERM', parentSigtermCallback) - if (process.env.CI !== 'true') { + if (watchStdin) { process.stdin.on('end', parentSigtermCallback) + // resume stdin to allow the server to exit on EOF + process.stdin.resume() } } sigtermCallbacks.add(callback) } export const teardownSIGTERMListener = ( - callback: Parameters[0], + watchStdin: boolean, + callback: Parameters[1], ): void => { sigtermCallbacks.delete(callback) if (sigtermCallbacks.size === 0) { process.off('SIGTERM', parentSigtermCallback) - if (process.env.CI !== 'true') { + if (watchStdin) { process.stdin.off('end', parentSigtermCallback) } }