import { execa } from 'execa'; import * as fs from 'fs/promises'; import { Writable, Readable } from 'stream'; import * as path from 'path'; import getPort from 'get-port'; import { ChildProcess } from 'child_process'; import stripAnsi from 'strip-ansi'; const VERBOSE = true; const FORCE_COLOR = true; async function appReady(stdout: Readable): Promise<void> { return new Promise<void>((resolve) => { stdout.pipe( new Writable({ write(chunk, encoding, callback) { if (/ready - started server on/i.test(stripAnsi(String(chunk)))) { resolve(); } callback(); }, }) ); }); } function redirectOutput(cp: ChildProcess) { if (VERBOSE) { process.stdin.pipe(cp.stdin); cp.stdout.pipe(process.stdout); cp.stderr.pipe(process.stderr); } } export async function buildNext(appPath: string) { const cp = execa('next', ['build'], { preferLocal: true, cwd: appPath, env: { FORCE_COLOR: VERBOSE ? '1' : '0', }, }); redirectOutput(cp); await cp; } export interface RunningNextApp { url: string; kill: (...args: any[]) => boolean; } export async function startNext( appPath: string, port?: number ): Promise<RunningNextApp> { port = port || (await getPort()); const app = execa('next', ['start', '-p', String(port)], { preferLocal: true, cwd: appPath, env: { FORCE_COLOR: FORCE_COLOR ? '1' : '0', }, }); redirectOutput(app); await appReady(app.stdout); return { url: `http://localhost:${port}/`, kill: app.kill.bind(app), }; } export async function cleanup(appPath: string): Promise<void> { try { await fs.rm(path.resolve(appPath, './.next'), { recursive: true }); } catch (err) { if (err.code === 'ENOENT') { return; } throw err; } }