4040 *
4141 * The tsx source entry (`bin/run-dev.js`), as every `runServe()` caller spawns
4242 * it, for `os start` and for `os package install` alike. It pins
43- * `NODE_ENV=development`, which lets `serve` auto-shift off a port taken
44- * between the probe and the bind, so the ready banner is read back
45- * (`portDriftError `) before any request is addressed to the port . The CLI runs
43+ * `NODE_ENV=development`, where a taken port is a hop rather than an exit, so
44+ * each boot takes a kernel-assigned port and reads it back off its own ready
45+ * banner (`readKernelAssignedPort `) before any request is addressed to it . The CLI runs
4646 * from `src/`, but every workspace package it loads — `@objectstack/runtime`
4747 * and `@objectstack/cloud-connection` included — resolves through its
4848 * `exports` to `dist/`: an ablation of either package's source reaches this
@@ -60,10 +60,9 @@ import {
6060 CLI ,
6161 childEnv ,
6262 E2E_SECRET_KEY ,
63- portContentionError ,
64- portDriftError ,
63+ KERNEL_ASSIGNED_PORT ,
6564 probeThroughChild ,
66- randomPort ,
65+ readKernelAssignedPort ,
6766 TSX ,
6867} from './helpers/serve-process.js' ;
6968
@@ -142,9 +141,9 @@ interface LiveStart {
142141 output : ( ) => string ;
143142}
144143
145- function bootStart ( cwd : string , home : string , port : string , extra : string [ ] = [ ] ) : Promise < LiveStart > {
144+ function bootStart ( cwd : string , home : string , extra : string [ ] = [ ] ) : Promise < LiveStart > {
146145 return new Promise ( ( resolveBoot , rejectBoot ) => {
147- const child = spawn ( TSX , [ CLI , 'start' , '-p' , port , '--home' , home , '--auth-secret' , E2E_SECRET_KEY , '--no-ui' , ...extra ] , {
146+ const child = spawn ( TSX , [ CLI , 'start' , '-p' , KERNEL_ASSIGNED_PORT , '--home' , home , '--auth-secret' , E2E_SECRET_KEY , '--no-ui' , ...extra ] , {
148147 cwd,
149148 // `childEnv`, never a bare `...process.env` — see its header.
150149 env : childEnv ( { NO_COLOR : '1' , OS_CLOUD_URL : 'off' , OS_LOG_LEVEL : 'warn' , OS_SECRET_KEY : E2E_SECRET_KEY } ) ,
@@ -155,27 +154,26 @@ function bootStart(cwd: string, home: string, port: string, extra: string[] = []
155154 groups . push ( child ) ;
156155 let out = '' ;
157156 let settled = false ;
158- const settle = ( err : Error | null ) => {
157+ const settle = ( verdict : Error | number ) => {
159158 if ( settled ) return ;
160159 settled = true ;
161160 clearTimeout ( timer ) ;
162- if ( err ) rejectBoot ( err ) ;
163- else resolveBoot ( { child, base : `http://localhost:${ port } ` , output : ( ) => out } ) ;
161+ if ( verdict instanceof Error ) rejectBoot ( verdict ) ;
162+ else resolveBoot ( { child, base : `http://localhost:${ verdict } ` , output : ( ) => out } ) ;
164163 } ;
165164 const timer = setTimeout (
166165 ( ) => settle ( new Error ( `os start never printed ${ READY } \n--- output ---\n${ out . slice ( - 4000 ) } ` ) ) ,
167166 BOOT_TIMEOUT_MS ,
168167 ) ;
169168 const onData = ( d : unknown ) => {
170169 out += String ( d ) ;
171- // The child is the authority on the port it bound.
172- if ( READY . test ( out ) ) settle ( portDriftError ( out , 'os start' , port ) ) ;
170+ // The child is the authority on the port it bound: a kernel-assigned one (#24342) .
171+ if ( READY . test ( out ) ) settle ( readKernelAssignedPort ( out , 'os start' ) ) ;
173172 } ;
174173 child . stdout ?. on ( 'data' , onData ) ;
175174 child . stderr ?. on ( 'data' , onData ) ;
176175 child . on ( 'exit' , ( code ) =>
177- settle ( portContentionError ( out , 'os start' , port )
178- ?? new Error ( `os start exited ${ String ( code ) } before ${ READY } \n--- output ---\n${ out . slice ( - 4000 ) } ` ) ) ,
176+ settle ( new Error ( `os start exited ${ String ( code ) } before ${ READY } \n--- output ---\n${ out . slice ( - 4000 ) } ` ) ) ,
179177 ) ;
180178 } ) ;
181179}
@@ -327,10 +325,9 @@ beforeAll(async () => {
327325 const runtimeDir = join ( root , 'runtime' ) ;
328326 mkdirSync ( runtimeDir , { recursive : true } ) ;
329327 const home = join ( runtimeDir , 'home' ) ;
330- const port = randomPort ( ) ;
331328
332329 // ── boot 1: empty `os start`, install, probe, reinstall, probe ─────────
333- const first = await bootStart ( runtimeDir , home , port ) ;
330+ const first = await bootStart ( runtimeDir , home ) ;
334331 const token = await authenticate ( first ) ;
335332 installs . push ( await packageInstall ( appDir , first ) ) ;
336333 phases . install = await probe ( first , token ) ;
@@ -339,14 +336,14 @@ beforeAll(async () => {
339336 await stopGroup ( first . child ) ;
340337
341338 // ── boot 2: same home and cwd — the ledger rehydrates on kernel:ready ──
342- const second = await bootStart ( runtimeDir , home , port ) ;
339+ const second = await bootStart ( runtimeDir , home ) ;
343340 phases . restart = await probe ( second , await authenticate ( second ) ) ;
344341 await stopGroup ( second . child ) ;
345342
346343 // ── boot 3: the CONTROL — the same file as the boot artifact ───────────
347344 const controlDir = join ( root , 'control' ) ;
348345 mkdirSync ( controlDir , { recursive : true } ) ;
349- const third = await bootStart ( controlDir , join ( controlDir , 'home' ) , port , [ '--artifact' , join ( appDir , 'dist' , 'objectstack.json' ) ] ) ;
346+ const third = await bootStart ( controlDir , join ( controlDir , 'home' ) , [ '--artifact' , join ( appDir , 'dist' , 'objectstack.json' ) ] ) ;
350347 phases . control = await probe ( third , await authenticate ( third ) ) ;
351348 await stopGroup ( third . child ) ;
352349} , 4 * BOOT_TIMEOUT_MS ) ;
0 commit comments