@@ -48,7 +48,7 @@ export function main(
48
48
const state : BootstrapState = {
49
49
shouldUseChildProcess : false ,
50
50
isInChildProcess : false ,
51
- entrypoint : __filename ,
51
+ tsNodeScript : __filename ,
52
52
parseArgvResult : args ,
53
53
} ;
54
54
return bootstrap ( state ) ;
@@ -62,7 +62,7 @@ export function main(
62
62
export interface BootstrapState {
63
63
isInChildProcess : boolean ;
64
64
shouldUseChildProcess : boolean ;
65
- entrypoint : string ;
65
+ tsNodeScript : string ;
66
66
parseArgvResult : ReturnType < typeof parseArgv > ;
67
67
phase2Result ?: ReturnType < typeof phase2 > ;
68
68
phase3Result ?: ReturnType < typeof phase3 > ;
@@ -319,28 +319,16 @@ Options:
319
319
process . exit ( 0 ) ;
320
320
}
321
321
322
- // Figure out which we are executing: piped stdin, --eval, REPL, and/or entrypoint
323
- // This is complicated because node's behavior is complicated
324
- // `node -e code -i ./script.js` ignores -e
325
- const executeEval = code != null && ! ( interactive && restArgs . length ) ;
326
- const executeEntrypoint = ! executeEval && restArgs . length > 0 ;
327
- const executeRepl =
328
- ! executeEntrypoint &&
329
- ( interactive || ( process . stdin . isTTY && ! executeEval ) ) ;
330
- const executeStdin = ! executeEval && ! executeRepl && ! executeEntrypoint ;
331
-
332
322
const cwd = cwdArg || process . cwd ( ) ;
333
- /** Unresolved. May point to a symlink, not realpath. May be missing file extension */
334
- const scriptPath = executeEntrypoint ? resolve ( cwd , restArgs [ 0 ] ) : undefined ;
335
323
336
- if ( esm ) payload . shouldUseChildProcess = true ;
324
+ // If ESM is explicitly enabled through the flag, stage3 should be run in a child process
325
+ // with the ESM loaders configured.
326
+ if ( esm ) {
327
+ payload . shouldUseChildProcess = true ;
328
+ }
329
+
337
330
return {
338
- executeEval,
339
- executeEntrypoint,
340
- executeRepl,
341
- executeStdin,
342
331
cwd,
343
- scriptPath,
344
332
} ;
345
333
}
346
334
@@ -372,7 +360,18 @@ function phase3(payload: BootstrapState) {
372
360
esm,
373
361
experimentalSpecifierResolution,
374
362
} = payload . parseArgvResult ;
375
- const { cwd , scriptPath } = payload . phase2Result ! ;
363
+ const { cwd } = payload . phase2Result ! ;
364
+
365
+ // NOTE: When we transition to a child process for ESM, the entry-point script determined
366
+ // here might not be the one used later in `phase4`. This can happen when we execute the
367
+ // original entry-point but then the process forks itself using e.g. `child_process.fork`.
368
+ // We will always use the original TS project in forked processes anyway, so it is
369
+ // expected and acceptable to retrieve the entry-point information here in `phase2`.
370
+ // See: https://github.com/TypeStrong/ts-node/issues/1812.
371
+ const { entryPointPath } = getEntryPointInfo (
372
+ payload . parseArgvResult ! ,
373
+ payload . phase2Result !
374
+ ) ;
376
375
377
376
const preloadedConfig = findAndReadConfig ( {
378
377
cwd,
@@ -387,7 +386,12 @@ function phase3(payload: BootstrapState) {
387
386
compilerHost,
388
387
ignore,
389
388
logError,
390
- projectSearchDir : getProjectSearchDir ( cwd , scriptMode , cwdMode , scriptPath ) ,
389
+ projectSearchDir : getProjectSearchDir (
390
+ cwd ,
391
+ scriptMode ,
392
+ cwdMode ,
393
+ entryPointPath
394
+ ) ,
391
395
project,
392
396
skipProject,
393
397
skipIgnore,
@@ -403,23 +407,76 @@ function phase3(payload: BootstrapState) {
403
407
experimentalSpecifierResolution as ExperimentalSpecifierResolution ,
404
408
} ) ;
405
409
406
- if ( preloadedConfig . options . esm ) payload . shouldUseChildProcess = true ;
410
+ // If ESM is enabled through the parsed tsconfig, stage4 should be run in a child
411
+ // process with the ESM loaders configured.
412
+ if ( preloadedConfig . options . esm ) {
413
+ payload . shouldUseChildProcess = true ;
414
+ }
415
+
407
416
return { preloadedConfig } ;
408
417
}
409
418
419
+ /**
420
+ * Determines the entry-point information from the argv and phase2 result. This
421
+ * method will be invoked in two places:
422
+ *
423
+ * 1. In phase 3 to be able to find a project from the potential entry-point script.
424
+ * 2. In phase 4 to determine the actual entry-point script.
425
+ *
426
+ * Note that we need to explicitly re-resolve the entry-point information in the final
427
+ * stage because the previous stage information could be modified when the bootstrap
428
+ * invocation transitioned into a child process for ESM.
429
+ *
430
+ * Stages before (phase 4) can and will be cached by the child process through the Brotli
431
+ * configuration and entry-point information is only reliable in the final phase. More
432
+ * details can be found in here: https://github.com/TypeStrong/ts-node/issues/1812.
433
+ */
434
+ function getEntryPointInfo (
435
+ argvResult : NonNullable < BootstrapState [ 'parseArgvResult' ] > ,
436
+ phase2Result : NonNullable < BootstrapState [ 'phase2Result' ] >
437
+ ) {
438
+ const { code, interactive, restArgs } = argvResult ;
439
+ const { cwd } = phase2Result ;
440
+
441
+ // Figure out which we are executing: piped stdin, --eval, REPL, and/or entrypoint
442
+ // This is complicated because node's behavior is complicated
443
+ // `node -e code -i ./script.js` ignores -e
444
+ const executeEval = code != null && ! ( interactive && restArgs . length ) ;
445
+ const executeEntrypoint = ! executeEval && restArgs . length > 0 ;
446
+ const executeRepl =
447
+ ! executeEntrypoint &&
448
+ ( interactive || ( process . stdin . isTTY && ! executeEval ) ) ;
449
+ const executeStdin = ! executeEval && ! executeRepl && ! executeEntrypoint ;
450
+
451
+ /** Unresolved. May point to a symlink, not realpath. May be missing file extension */
452
+ const entryPointPath = executeEntrypoint
453
+ ? resolve ( cwd , restArgs [ 0 ] )
454
+ : undefined ;
455
+
456
+ return {
457
+ executeEval,
458
+ executeEntrypoint,
459
+ executeRepl,
460
+ executeStdin,
461
+ entryPointPath,
462
+ } ;
463
+ }
464
+
410
465
function phase4 ( payload : BootstrapState ) {
411
- const { isInChildProcess, entrypoint } = payload ;
466
+ const { isInChildProcess , tsNodeScript } = payload ;
412
467
const { version , showConfig , restArgs , code , print , argv } =
413
468
payload . parseArgvResult ;
469
+ const { cwd } = payload . phase2Result ! ;
470
+ const { preloadedConfig } = payload . phase3Result ! ;
471
+
414
472
const {
473
+ entryPointPath ,
474
+ executeEntrypoint ,
415
475
executeEval ,
416
- cwd,
417
- executeStdin,
418
476
executeRepl ,
419
- executeEntrypoint,
420
- scriptPath,
421
- } = payload . phase2Result ! ;
422
- const { preloadedConfig } = payload . phase3Result ! ;
477
+ executeStdin ,
478
+ } = getEntryPointInfo ( payload . parseArgvResult ! , payload . phase2Result ! ) ;
479
+
423
480
/**
424
481
* <repl>, [stdin], and [eval] are all essentially virtual files that do not exist on disc and are backed by a REPL
425
482
* service to handle eval-ing of code.
@@ -566,12 +623,13 @@ function phase4(payload: BootstrapState) {
566
623
567
624
// Prepend `ts-node` arguments to CLI for child processes.
568
625
process . execArgv . push (
569
- entrypoint ,
626
+ tsNodeScript ,
570
627
...argv . slice ( 2 , argv . length - restArgs . length )
571
628
) ;
629
+
572
630
// TODO this comes from BoostrapState
573
631
process . argv = [ process . argv [ 1 ] ]
574
- . concat ( executeEntrypoint ? ( [ scriptPath ] as string [ ] ) : [ ] )
632
+ . concat ( executeEntrypoint ? ( [ entryPointPath ] as string [ ] ) : [ ] )
575
633
. concat ( restArgs . slice ( executeEntrypoint ? 1 : 0 ) ) ;
576
634
577
635
// Execute the main contents (either eval, script or piped).
0 commit comments