44import { spawn } from "node:child_process" ;
55
66const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000 ;
7- const HEARTBEAT_CHILD_SCRIPT = [
8- "const intervalMs = Number(process.argv[1]);" ,
9- "const parentPid = Number(process.argv[2]);" ,
10- "const startedAt = Date.now();" ,
11- "const stop = () => process.exit(0);" ,
12- 'process.on("SIGINT", stop);' ,
13- 'process.on("SIGTERM", stop);' ,
14- "setInterval(() => {" ,
15- " try { process.kill(parentPid, 0); } catch { stop(); return; }" ,
16- " const elapsedSeconds = Math.max(0, Math.round((Date.now() - startedAt) / 1000));" ,
17- " process.stdout.write(` ⏳ Still working on sandbox base image build… (${elapsedSeconds}s elapsed)\\n`);" ,
18- "}, intervalMs);" ,
19- ] . join ( "\n" ) ;
7+
8+ type HeartbeatActivity = "build" | "pull" ;
9+
10+ function heartbeatChildScript ( activity : HeartbeatActivity ) : string {
11+ return [
12+ "const intervalMs = Number(process.argv[1]);" ,
13+ "const parentPid = Number(process.argv[2]);" ,
14+ "const startedAt = Date.now();" ,
15+ "const stop = () => process.exit(0);" ,
16+ 'process.on("SIGINT", stop);' ,
17+ 'process.on("SIGTERM", stop);' ,
18+ "setInterval(() => {" ,
19+ " try { process.kill(parentPid, 0); } catch { stop(); return; }" ,
20+ " const elapsedSeconds = Math.max(0, Math.round((Date.now() - startedAt) / 1000));" ,
21+ ` process.stdout.write(\` ⏳ Still working on sandbox base image ${ activity } … (\${elapsedSeconds}s elapsed)\\n\`);` ,
22+ "}, intervalMs);" ,
23+ ] . join ( "\n" ) ;
24+ }
2025
2126type SpawnHeartbeat = typeof spawn ;
2227
2328export interface LocalBuildHeartbeatOptions {
29+ activity ?: HeartbeatActivity ;
2430 intervalMs ?: number ;
2531 nodeExecutable ?: string ;
2632 parentPid ?: number ;
2733 spawnImpl ?: SpawnHeartbeat ;
2834}
2935
30- /** Keep quiet synchronous Docker builds observable without exposing their captured logs. */
36+ /** Keep quiet synchronous Docker work observable without exposing its captured logs. */
3137export function withLocalBuildHeartbeat < T > (
32- build : ( ) => T ,
38+ operation : ( ) => T ,
3339 options : LocalBuildHeartbeatOptions = { } ,
3440) : T {
3541 const intervalMs =
@@ -40,7 +46,12 @@ export function withLocalBuildHeartbeat<T>(
4046 try {
4147 child = ( options . spawnImpl ?? spawn ) (
4248 options . nodeExecutable ?? process . execPath ,
43- [ "-e" , HEARTBEAT_CHILD_SCRIPT , String ( intervalMs ) , String ( options . parentPid ?? process . pid ) ] ,
49+ [
50+ "-e" ,
51+ heartbeatChildScript ( options . activity ?? "build" ) ,
52+ String ( intervalMs ) ,
53+ String ( options . parentPid ?? process . pid ) ,
54+ ] ,
4455 { env : { } , stdio : [ "ignore" , "inherit" , "inherit" ] } ,
4556 ) ;
4657 child . on ( "error" , ( ) => undefined ) ;
@@ -50,12 +61,12 @@ export function withLocalBuildHeartbeat<T>(
5061 }
5162
5263 try {
53- return build ( ) ;
64+ return operation ( ) ;
5465 } finally {
5566 try {
5667 child ?. kill ( "SIGTERM" ) ;
5768 } catch {
58- // Progress reporting must never replace the Docker build result.
69+ // Progress reporting must never replace the Docker operation result.
5970 }
6071 }
6172}
0 commit comments