Skip to content

Commit 8547cfa

Browse files
committed
fix: ensure logTailStopper runs before process.exit
process.exit() does not unwind the stack, so finally blocks are skipped. Restore explicit logTailStopper() calls before each process.exit() to prevent leaking the background daemon log tail process. The finally block remains as a safety net for normal return paths. Also refactor writeCommandCliOutput to return an exit code instead of calling process.exit() directly, keeping the exit decision in the caller where cleanup is visible.
1 parent 2c58db7 commit 8547cfa

1 file changed

Lines changed: 33 additions & 31 deletions

File tree

src/cli.ts

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,11 @@ export async function runCli(argv: string[], deps: CliDeps = DEFAULT_CLI_DEPS):
242242
});
243243

244244
if (response.ok) {
245-
writeCommandCliOutput(command, positionals, flags, response.data ?? {});
245+
const exitCode = writeCommandCliOutput(command, positionals, flags, response.data ?? {});
246+
if (exitCode !== 0) {
247+
if (logTailStopper) logTailStopper();
248+
process.exit(exitCode);
249+
}
246250
return;
247251
}
248252

@@ -282,6 +286,7 @@ export async function runCli(argv: string[], deps: CliDeps = DEFAULT_CLI_DEPS):
282286
}
283287
}
284288
}
289+
if (logTailStopper) logTailStopper();
285290
process.exit(1);
286291
} finally {
287292
if (logTailStopper) logTailStopper();
@@ -331,60 +336,56 @@ function writeCommandCliOutput(
331336
positionals: string[],
332337
flags: { json?: boolean; verbose?: boolean; snapshotRaw?: boolean; snapshotInteractiveOnly?: boolean; reportJunit?: string },
333338
data: Record<string, unknown>,
334-
): void {
339+
): number {
335340
if (flags.json) {
336341
if (command === 'test') {
337-
const testExitCode = renderReplayTestResponse({
342+
return renderReplayTestResponse({
338343
suite: data as ReplaySuiteResult,
339344
json: true,
340345
reportJunit: flags.reportJunit,
341346
});
342-
if (testExitCode !== 0) process.exit(testExitCode);
343-
return;
344347
}
345348
printJson({ success: true, data });
346-
return;
349+
return 0;
347350
}
348351

349352
if (command === 'snapshot') {
350353
process.stdout.write(formatSnapshotText(data, {
351354
raw: flags.snapshotRaw,
352355
flatten: flags.snapshotInteractiveOnly,
353356
}));
354-
return;
357+
return 0;
355358
}
356359
if (command === 'test') {
357-
const testExitCode = renderReplayTestResponse({
360+
return renderReplayTestResponse({
358361
suite: data as ReplaySuiteResult,
359362
verbose: flags.verbose,
360363
reportJunit: flags.reportJunit,
361364
});
362-
if (testExitCode !== 0) process.exit(testExitCode);
363-
return;
364365
}
365366
if (command === 'diff' && positionals[0] === 'snapshot') {
366367
process.stdout.write(formatSnapshotDiffText(data));
367-
return;
368+
return 0;
368369
}
369370
if (command === 'get') {
370371
const sub = positionals[0];
371-
if (sub === 'text') { process.stdout.write(`${(data as any)?.text ?? ''}\n`); return; }
372-
if (sub === 'attrs') { process.stdout.write(`${JSON.stringify((data as any)?.node ?? {}, null, 2)}\n`); return; }
372+
if (sub === 'text') { process.stdout.write(`${(data as any)?.text ?? ''}\n`); return 0; }
373+
if (sub === 'attrs') { process.stdout.write(`${JSON.stringify((data as any)?.node ?? {}, null, 2)}\n`); return 0; }
373374
}
374375
if (command === 'find') {
375-
if (typeof (data as any)?.text === 'string') { process.stdout.write(`${(data as any).text}\n`); return; }
376-
if (typeof (data as any)?.found === 'boolean') { process.stdout.write(`Found: ${(data as any).found}\n`); return; }
377-
if ((data as any)?.node) { process.stdout.write(`${JSON.stringify((data as any).node, null, 2)}\n`); return; }
376+
if (typeof (data as any)?.text === 'string') { process.stdout.write(`${(data as any).text}\n`); return 0; }
377+
if (typeof (data as any)?.found === 'boolean') { process.stdout.write(`Found: ${(data as any).found}\n`); return 0; }
378+
if ((data as any)?.node) { process.stdout.write(`${JSON.stringify((data as any).node, null, 2)}\n`); return 0; }
378379
}
379380
if (command === 'is') {
380381
process.stdout.write(`Passed: is ${(data as any)?.predicate ?? 'assertion'}\n`);
381-
return;
382+
return 0;
382383
}
383384
if (command === 'boot') {
384385
const platform = (data as any)?.platform ?? 'unknown';
385386
const device = (data as any)?.device ?? (data as any)?.id ?? 'unknown';
386387
process.stdout.write(`Boot ready: ${device} (${platform})\n`);
387-
return;
388+
return 0;
388389
}
389390
if (command === 'ensure-simulator') {
390391
const udid = typeof data?.udid === 'string' ? data.udid : 'unknown';
@@ -394,38 +395,38 @@ function writeCommandCliOutput(
394395
const bootedSuffix = data?.booted === true ? ' (booted)' : '';
395396
process.stdout.write(`${action}: ${device} ${udid}${bootedSuffix}\n`);
396397
if (runtime) process.stdout.write(`Runtime: ${runtime}\n`);
397-
return;
398+
return 0;
398399
}
399400
if (command === 'screenshot') {
400401
const pathOut = typeof (data as any)?.path === 'string' ? (data as any).path : '';
401402
if (pathOut) process.stdout.write(`${pathOut}\n`);
402-
return;
403+
return 0;
403404
}
404405
if (command === 'record') {
405406
const outPath = typeof data?.outPath === 'string' ? data.outPath : '';
406407
if (outPath) process.stdout.write(`${outPath}\n`);
407-
return;
408+
return 0;
408409
}
409410
if (command === 'logs') {
410411
writeLogsCliOutput(data, flags);
411-
return;
412+
return 0;
412413
}
413414
if (command === 'clipboard') {
414415
const action = (positionals[0] ?? (typeof data?.action === 'string' ? data.action : '')).toLowerCase();
415-
if (action === 'read') { process.stdout.write(`${typeof data?.text === 'string' ? data.text : ''}\n`); return; }
416-
if (action === 'write') { process.stdout.write('Clipboard updated\n'); return; }
416+
if (action === 'read') { process.stdout.write(`${typeof data?.text === 'string' ? data.text : ''}\n`); return 0; }
417+
if (action === 'write') { process.stdout.write('Clipboard updated\n'); return 0; }
417418
}
418419
if (command === 'network') {
419420
writeNetworkCliOutput(data);
420-
return;
421+
return 0;
421422
}
422423
if (command === 'click' || command === 'press') {
423424
const ref = (data as any)?.ref ?? '';
424425
const x = (data as any)?.x;
425426
const y = (data as any)?.y;
426427
if (ref && typeof x === 'number' && typeof y === 'number') {
427428
process.stdout.write(`Tapped @${ref} (${x}, ${y})\n`);
428-
return;
429+
return 0;
429430
}
430431
}
431432
if (command === 'devices') {
@@ -438,7 +439,7 @@ function writeCommandCliOutput(
438439
const booted = typeof d?.booted === 'boolean' ? ` booted=${d.booted}` : '';
439440
return `${name} (${platform}${kind}${target})${booted}`;
440441
}).join('\n')}\n`);
441-
return;
442+
return 0;
442443
}
443444
if (command === 'apps') {
444445
const apps = Array.isArray((data as any).apps) ? (data as any).apps : [];
@@ -453,25 +454,25 @@ function writeCommandCliOutput(
453454
}
454455
return String(app);
455456
}).join('\n')}\n`);
456-
return;
457+
return 0;
457458
}
458459
if (command === 'appstate') {
459460
const platform = (data as any)?.platform;
460461
if (platform === 'ios') {
461462
process.stdout.write(`Foreground app: ${(data as any)?.appName ?? (data as any)?.appBundleId ?? 'unknown'}\n`);
462463
if ((data as any)?.appBundleId) process.stdout.write(`Bundle: ${(data as any).appBundleId}\n`);
463464
if ((data as any)?.source) process.stdout.write(`Source: ${(data as any).source}\n`);
464-
return;
465+
return 0;
465466
}
466467
if (platform === 'android') {
467468
process.stdout.write(`Foreground app: ${(data as any)?.package ?? 'unknown'}\n`);
468469
if ((data as any)?.activity) process.stdout.write(`Activity: ${(data as any).activity}\n`);
469-
return;
470+
return 0;
470471
}
471472
}
472473
if (command === 'perf') {
473474
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
474-
return;
475+
return 0;
475476
}
476477
const successText = readCommandMessage(data);
477478
if (successText) {
@@ -480,6 +481,7 @@ function writeCommandCliOutput(
480481
process.stdout.write(`${extraLine}\n`);
481482
}
482483
}
484+
return 0;
483485
}
484486

485487
function writeLogsCliOutput(data: Record<string, unknown>, flags: { json?: boolean }): void {

0 commit comments

Comments
 (0)