UNPKG

@epic-web/app-launcher

Version:

Utility for launching your applications on a per-test basis.

136 lines (118 loc) 3.13 kB
import type { ChildProcess } from 'node:child_process' import { AppProcess, kUrl } from './app-process.js' import { waitForPort } from './wait-for-port.js' export interface DefineLauncherOptions<Context> { /** * Provide a launcher to extend. * When extended, the base launcher options, like `env`, * will be inherited by the new launcher. */ extends?: Launcher /** * Run the launcher in debug mode. */ debug?: boolean /** * Define a context object exposed to the other launcher methods. * * @example * defineLauncher({ * async context() { * return { port: await getRandomPort() } * } * }) */ context?: () => Context | Promise<Context> /** * Define environment variables for this launcher. * Environment variables will be forwarded to the run `command` * automatically. * * @example * defineLauncher({ * env() { * return { FOO: 'bar' } * } * }) */ env?: (options: { context: Context }) => Record<string, string> | Promise<Record<string, string>> /** * Define a command that runs your application. * * @example * defineLauncher({ * command() { * return `npm start` * } * }) */ command: (options: { context: Context env: Record<string, string> }) => string | Promise<string> /** * Return the resolved application URL. * * @example * defineLauncher({ * url({ context }) { * return `http://localhost:${context.port}` * } * }) */ url: (options: { context: Context env: Record<string, string> appProcess: ChildProcess }) => string | URL | Promise<string | URL> } export interface Launcher { [kLauncherOptions]: DefineLauncherOptions<any> run: (runOptions?: RunOptions) => Promise<AppProcess> } export interface RunOptions { cwd?: string } export const kLauncherOptions = Symbol('kLauncherOptions') export function defineLauncher<Context>( options: DefineLauncherOptions<Context>, ): Launcher { return { [kLauncherOptions]: options, async run(runOptions) { const context = (await options.context?.()) ?? ({} as Context) const baseEnv = await options.extends?.[kLauncherOptions].env?.({ context, }) const customEnv = await options.env?.({ context }) const env = { ...(baseEnv || {}), ...(customEnv || {}), } const command = await options.command({ context, env }) // Spawn the application. const app = new AppProcess({ command, env, cwd: runOptions?.cwd, }) const childProcess = await app.launch() if (options?.debug) { childProcess.stdout?.pipe(process.stdout) childProcess.stderr?.pipe(process.stderr) } // Wait for the provided application URL to be up. const url = await options.url({ context, env, appProcess: childProcess, }) const resolvedUrl = url instanceof URL ? url : new URL(url) app[kUrl] = resolvedUrl await waitForPort(+resolvedUrl.port) return app }, } }