99 * minimal PATH and a require hook that turns any source-checkout read into a
1010 * hard failure, so a checkout-only fallback cannot make this pass.
1111 */
12- import { spawn } from "node:child_process" ;
1312import { existsSync } from "node:fs" ;
1413import { mkdtemp , mkdir , readdir , rm , writeFile } from "node:fs/promises" ;
1514import { createServer as createHttpServer } from "node:http" ;
1615import { createServer } from "node:net" ;
1716import os from "node:os" ;
1817import path from "node:path" ;
18+ import { closeServerBounded , runBounded , spawnProcessGroup , stopProcessTree } from "./process-cleanup.mjs" ;
1919import { readSseFrames } from "./sse-frames.mjs" ;
2020
2121const packageRoot = path . resolve ( process . cwd ( ) ) ;
@@ -27,15 +27,18 @@ const sourceAuditHook = path.join(tempRoot, "block-checkout-access.cjs");
2727const port = await reservePort ( ) ;
2828const fixtureProvider = await startSetupFixtureProvider ( ) ;
2929let serverProcess ;
30+ let phase = "initialize" ;
3031
3132try {
33+ markPhase ( "pack" ) ;
3234 await Promise . all ( [ mkdir ( packDirectory ) , mkdir ( installRoot ) , mkdir ( workspaceRoot ) ] ) ;
3335 await run ( "pnpm" , [ "pack" , "--pack-destination" , packDirectory ] , { cwd : packageRoot } ) ;
3436
3537 const packageTarball = await onlyTarball ( packDirectory ) ;
3638 const packedFiles = await tarFiles ( packageTarball ) ;
3739 assertPublishedFiles ( packedFiles ) ;
3840
41+ markPhase ( "install" ) ;
3942 await run ( "npm" , [ "init" , "--yes" ] , { cwd : installRoot } ) ;
4043 await run ( "npm" , [ "install" , "--no-audit" , "--no-fund" , packageTarball ] , { cwd : installRoot } ) ;
4144
@@ -49,20 +52,23 @@ try {
4952 }
5053 }
5154
55+ markPhase ( "start" ) ;
5256 await writeFile ( sourceAuditHook , createSourceAuditHook ( ) , { mode : 0o600 } ) ;
5357 const childEnv = controlledEnvironment ( { installRoot, workspaceRoot, port, sourceAuditHook, fixtureEndpoint : fixtureProvider . endpoint } ) ;
54- serverProcess = spawn ( "npx" , [ "--no-install" , "genbi" ] , {
58+ serverProcess = spawnProcessGroup ( "npx" , [ "--no-install" , "genbi" ] , {
5559 cwd : installRoot ,
5660 env : childEnv ,
5761 stdio : [ "ignore" , "pipe" , "pipe" ] ,
5862 } ) ;
5963 const output = collectOutput ( serverProcess ) ;
6064
65+ markPhase ( "setup-connect" ) ;
6166 await waitForServer ( port , output ) ;
6267 await verifyFirstRunSetup ( port , workspaceRoot , fixtureProvider ) ;
63- await stop ( serverProcess ) ;
68+ await stopProcessTree ( serverProcess ) ;
6469 serverProcess = undefined ;
6570
71+ markPhase ( "complete" ) ;
6672 process . stdout . write ( `${ JSON . stringify ( {
6773 ok : true ,
6874 checks : [
7278 ] ,
7379 } , null , 2 ) } \n`) ;
7480} finally {
75- if ( serverProcess ) await stop ( serverProcess ) . catch ( ( ) => undefined ) ;
76- await Promise . all ( [ closeServer ( fixtureProvider . server ) , rm ( tempRoot , { recursive : true , force : true } ) ] ) ;
81+ const cleanupErrors = [ ] ;
82+ markPhase ( "cleanup-child" ) ;
83+ if ( serverProcess ) await stopProcessTree ( serverProcess ) . catch ( ( error ) => cleanupErrors . push ( error ) ) ;
84+ markPhase ( "cleanup-fixture" ) ;
85+ await closeServerBounded ( fixtureProvider . server ) . catch ( ( error ) => cleanupErrors . push ( error ) ) ;
86+ markPhase ( "cleanup-temp" ) ;
87+ await rm ( tempRoot , { recursive : true , force : true } ) . catch ( ( error ) => cleanupErrors . push ( error ) ) ;
88+ if ( cleanupErrors . length > 0 ) throw new AggregateError ( cleanupErrors , `installed package cleanup failed during ${ phase } ` ) ;
7789}
7890
7991function controlledEnvironment ( { installRoot, workspaceRoot, port : selectedPort , sourceAuditHook : hook , fixtureEndpoint } ) {
@@ -291,20 +303,6 @@ function collectOutput(child) {
291303 return { get exitCode ( ) { return exitCode ; } , text : ( ) => `${ stdout } ${ stderr } ` . slice ( - 4_000 ) } ;
292304}
293305
294- async function stop ( child ) {
295- if ( child . exitCode !== null || child . signalCode !== null ) return ;
296- child . kill ( "SIGTERM" ) ;
297- await new Promise ( ( resolve ) => {
298- const forceKill = setTimeout ( ( ) => {
299- if ( child . exitCode === null && child . signalCode === null ) child . kill ( "SIGKILL" ) ;
300- } , 5_000 ) ;
301- child . once ( "exit" , ( ) => {
302- clearTimeout ( forceKill ) ;
303- resolve ( ) ;
304- } ) ;
305- } ) ;
306- }
307-
308306async function onlyTarball ( directory ) {
309307 const files = ( await readdir ( directory ) ) . filter ( ( file ) => file . endsWith ( ".tgz" ) ) ;
310308 if ( files . length !== 1 ) throw new Error ( `expected one package tarball, found ${ JSON . stringify ( files ) } ` ) ;
@@ -327,17 +325,7 @@ function assertPublishedFiles(files) {
327325}
328326
329327function run ( command , args , options ) {
330- return new Promise ( ( resolve , reject ) => {
331- const child = spawn ( command , args , { ...options , stdio : [ "ignore" , "pipe" , "pipe" ] } ) ;
332- let stdout = "" ;
333- let stderr = "" ;
334- child . stdout . setEncoding ( "utf8" ) ;
335- child . stderr . setEncoding ( "utf8" ) ;
336- child . stdout . on ( "data" , ( chunk ) => ( stdout += chunk ) ) ;
337- child . stderr . on ( "data" , ( chunk ) => ( stderr += chunk ) ) ;
338- child . once ( "error" , reject ) ;
339- child . once ( "exit" , ( code ) => code === 0 ? resolve ( { stdout, stderr } ) : reject ( new Error ( `${ command } ${ args . join ( " " ) } failed (${ code } ): ${ stderr . slice ( - 4_000 ) } ` ) ) ) ;
340- } ) ;
328+ return runBounded ( command , args , options ) ;
341329}
342330
343331async function reservePort ( ) {
@@ -356,7 +344,7 @@ function delay(milliseconds) {
356344 return new Promise ( ( resolve ) => setTimeout ( resolve , milliseconds ) ) ;
357345}
358346
359- function closeServer ( server ) {
360- if ( ! server . listening ) return Promise . resolve ( ) ;
361- return new Promise ( ( resolve , reject ) => server . close ( ( error ) => error ? reject ( error ) : resolve ( ) ) ) ;
347+ function markPhase ( nextPhase ) {
348+ phase = nextPhase ;
349+ process . stderr . write ( `[installed-package] phase= ${ phase } \n` ) ;
362350}
0 commit comments