ttsc 0.32.0 → 0.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/lib/TtscCompiler.js +1 -1
  2. package/lib/TtscCompiler.js.map +1 -1
  3. package/lib/compiler/internal/ResidentCheckProcess.d.ts +74 -96
  4. package/lib/compiler/internal/ResidentCheckProcess.js +150 -335
  5. package/lib/compiler/internal/ResidentCheckProcess.js.map +1 -1
  6. package/lib/compiler/internal/ResidentCheckProcessHost.d.ts +127 -0
  7. package/lib/compiler/internal/ResidentCheckProcessHost.js +447 -0
  8. package/lib/compiler/internal/ResidentCheckProcessHost.js.map +1 -0
  9. package/lib/compiler/internal/ResidentCheckProcessProtocol.d.ts +64 -0
  10. package/lib/compiler/internal/ResidentCheckProcessProtocol.js +3 -0
  11. package/lib/compiler/internal/ResidentCheckProcessProtocol.js.map +1 -0
  12. package/lib/compiler/internal/build/BuildExecution.d.ts +103 -1
  13. package/lib/compiler/internal/build/BuildExecution.js +91 -11
  14. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  15. package/lib/compiler/internal/build/ResidentCheckWatchSession.d.ts +16 -11
  16. package/lib/compiler/internal/build/ResidentCheckWatchSession.js +37 -21
  17. package/lib/compiler/internal/build/ResidentCheckWatchSession.js.map +1 -1
  18. package/lib/compiler/internal/build/RunBuildOptions.d.ts +6 -5
  19. package/lib/compiler/internal/build/WatchInputPublication.d.ts +59 -0
  20. package/lib/compiler/internal/build/WatchInputPublication.js +93 -0
  21. package/lib/compiler/internal/build/WatchInputPublication.js.map +1 -0
  22. package/lib/compiler/internal/build/runBuild.d.ts +29 -2
  23. package/lib/compiler/internal/build/runBuild.js +45 -19
  24. package/lib/compiler/internal/build/runBuild.js.map +1 -1
  25. package/lib/compiler/internal/buildNativeCompiler.d.ts +2 -0
  26. package/lib/compiler/internal/buildNativeCompiler.js +1 -0
  27. package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
  28. package/lib/compiler/internal/compileProjectInMemory.js +4 -2
  29. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  30. package/lib/compiler/internal/project/resolveOwningProjectConfig.d.ts +7 -2
  31. package/lib/compiler/internal/project/resolveOwningProjectConfig.js +6 -3
  32. package/lib/compiler/internal/project/resolveOwningProjectConfig.js.map +1 -1
  33. package/lib/compiler/internal/readEffectiveCompilerOptions.js +1 -1
  34. package/lib/compiler/internal/readEffectiveCompilerOptions.js.map +1 -1
  35. package/lib/compiler/internal/resolveBinary.d.ts +1 -0
  36. package/lib/compiler/internal/resolveBinary.js.map +1 -1
  37. package/lib/compiler/internal/resolveTsgo.d.ts +6 -0
  38. package/lib/compiler/internal/resolveTsgo.js.map +1 -1
  39. package/lib/compiler/internal/runSingleFileEmit.d.ts +28 -1
  40. package/lib/compiler/internal/runSingleFileEmit.js +120 -86
  41. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  42. package/lib/compiler/internal/startResidentTransform.js +8 -2
  43. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  44. package/lib/compiler/internal/transformProjectInMemory.js +11 -3
  45. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  46. package/lib/compiler/internal/transformProjectInWorker.d.ts +1 -1
  47. package/lib/compiler/internal/transformProjectInWorker.js +3 -2
  48. package/lib/compiler/internal/transformProjectInWorker.js.map +1 -1
  49. package/lib/internal/workerModuleUrl.d.ts +28 -0
  50. package/lib/internal/workerModuleUrl.js +40 -0
  51. package/lib/internal/workerModuleUrl.js.map +1 -0
  52. package/lib/launcher/internal/buildSingleRootProject.js +2 -1
  53. package/lib/launcher/internal/buildSingleRootProject.js.map +1 -1
  54. package/lib/launcher/internal/getCompilerVersionText.js +3 -2
  55. package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
  56. package/lib/launcher/internal/prepareExecution.js +2 -1
  57. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  58. package/lib/launcher/internal/runTtsc.js +91 -41
  59. package/lib/launcher/internal/runTtsc.js.map +1 -1
  60. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.d.ts +10 -6
  61. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.js +11 -9
  62. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.js.map +1 -1
  63. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.d.ts +20 -0
  64. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.js +27 -0
  65. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.js.map +1 -0
  66. package/lib/launcher/internal/runtime/installRuntimeHooks.d.ts +5 -3
  67. package/lib/launcher/internal/runtime/installRuntimeHooks.js +7 -3
  68. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  69. package/lib/launcher/internal/watch/AsyncWatchTopology.d.ts +113 -0
  70. package/lib/launcher/internal/watch/AsyncWatchTopology.js +232 -0
  71. package/lib/launcher/internal/watch/AsyncWatchTopology.js.map +1 -0
  72. package/lib/launcher/internal/watch/WatchSettlement.d.ts +49 -0
  73. package/lib/launcher/internal/watch/WatchSettlement.js +70 -0
  74. package/lib/launcher/internal/watch/WatchSettlement.js.map +1 -0
  75. package/lib/launcher/internal/watch/WatchTopology.d.ts +29 -1
  76. package/lib/launcher/internal/watch/WatchTopology.js +86 -10
  77. package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
  78. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.d.ts +121 -0
  79. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.js +3 -0
  80. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.js.map +1 -0
  81. package/lib/launcher/internal/watch/serveWatchTopologyWorker.d.ts +19 -0
  82. package/lib/launcher/internal/watch/serveWatchTopologyWorker.js +62 -0
  83. package/lib/launcher/internal/watch/serveWatchTopologyWorker.js.map +1 -0
  84. package/lib/launcher/internal/watch/watchTopologyWorker.d.ts +1 -0
  85. package/lib/launcher/internal/watch/watchTopologyWorker.js +22 -0
  86. package/lib/launcher/internal/watch/watchTopologyWorker.js.map +1 -0
  87. package/lib/plugin/CapabilityPluginResolver.d.ts +1 -1
  88. package/lib/plugin/CapabilityPluginResolver.js +3 -2
  89. package/lib/plugin/CapabilityPluginResolver.js.map +1 -1
  90. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +15 -12
  91. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +13 -11
  92. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
  93. package/lib/plugin/internal/load/PluginPackageResolution.d.ts +2 -2
  94. package/lib/plugin/internal/load/PluginPackageResolution.js +13 -7
  95. package/lib/plugin/internal/load/PluginPackageResolution.js.map +1 -1
  96. package/lib/plugin/internal/load/ProjectPluginEntries.d.ts +23 -7
  97. package/lib/plugin/internal/load/ProjectPluginEntries.js +36 -14
  98. package/lib/plugin/internal/load/ProjectPluginEntries.js.map +1 -1
  99. package/lib/plugin/internal/load/collectProjectHostInputs.d.ts +3 -1
  100. package/lib/plugin/internal/load/collectProjectHostInputs.js +5 -2
  101. package/lib/plugin/internal/load/collectProjectHostInputs.js.map +1 -1
  102. package/lib/plugin/internal/load/hasProjectPluginEntries.d.ts +3 -1
  103. package/lib/plugin/internal/load/hasProjectPluginEntries.js +4 -2
  104. package/lib/plugin/internal/load/hasProjectPluginEntries.js.map +1 -1
  105. package/lib/plugin/internal/load/loadProjectPlugins.d.ts +36 -5
  106. package/lib/plugin/internal/load/loadProjectPlugins.js +466 -416
  107. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  108. package/lib/plugin/internal/load/pluginResolutionFailure.d.ts +18 -0
  109. package/lib/plugin/internal/load/pluginResolutionFailure.js +32 -0
  110. package/lib/plugin/internal/load/pluginResolutionFailure.js.map +1 -0
  111. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +1 -1
  112. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +3 -2
  113. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
  114. package/lib/structures/ITtscCompilerContext.d.ts +12 -12
  115. package/lib/structures/ITtscPlugin.d.ts +7 -5
  116. package/lib/structures/ITtscPluginFactoryContext.d.ts +9 -8
  117. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +12 -6
  118. package/package.json +9 -9
  119. package/src/TtscCompiler.ts +1 -1
  120. package/src/compiler/internal/ResidentCheckProcess.ts +159 -374
  121. package/src/compiler/internal/ResidentCheckProcessHost.ts +502 -0
  122. package/src/compiler/internal/ResidentCheckProcessProtocol.ts +53 -0
  123. package/src/compiler/internal/build/BuildExecution.ts +118 -9
  124. package/src/compiler/internal/build/ResidentCheckWatchSession.ts +41 -24
  125. package/src/compiler/internal/build/RunBuildOptions.ts +9 -5
  126. package/src/compiler/internal/build/WatchInputPublication.ts +106 -0
  127. package/src/compiler/internal/build/runBuild.ts +58 -34
  128. package/src/compiler/internal/buildNativeCompiler.ts +4 -0
  129. package/src/compiler/internal/compileProjectInMemory.ts +8 -2
  130. package/src/compiler/internal/project/resolveOwningProjectConfig.ts +16 -5
  131. package/src/compiler/internal/readEffectiveCompilerOptions.ts +1 -1
  132. package/src/compiler/internal/resolveBinary.ts +4 -1
  133. package/src/compiler/internal/resolveTsgo.ts +7 -0
  134. package/src/compiler/internal/runSingleFileEmit.ts +139 -99
  135. package/src/compiler/internal/startResidentTransform.ts +9 -2
  136. package/src/compiler/internal/transformProjectInMemory.ts +12 -3
  137. package/src/compiler/internal/transformProjectInWorker.ts +10 -2
  138. package/src/internal/workerModuleUrl.ts +34 -0
  139. package/src/launcher/internal/buildSingleRootProject.ts +2 -0
  140. package/src/launcher/internal/getCompilerVersionText.ts +3 -2
  141. package/src/launcher/internal/prepareExecution.ts +2 -0
  142. package/src/launcher/internal/runTtsc.ts +123 -45
  143. package/src/launcher/internal/runtime/CommonJsRuntimeSource.ts +11 -9
  144. package/src/launcher/internal/runtime/advertiseTypeScriptExtensions.ts +25 -0
  145. package/src/launcher/internal/runtime/installRuntimeHooks.ts +7 -3
  146. package/src/launcher/internal/watch/AsyncWatchTopology.ts +267 -0
  147. package/src/launcher/internal/watch/WatchSettlement.ts +67 -0
  148. package/src/launcher/internal/watch/WatchTopology.ts +84 -13
  149. package/src/launcher/internal/watch/WatchTopologyWorkerProtocol.ts +106 -0
  150. package/src/launcher/internal/watch/serveWatchTopologyWorker.ts +75 -0
  151. package/src/launcher/internal/watch/watchTopologyWorker.ts +29 -0
  152. package/src/plugin/CapabilityPluginResolver.ts +9 -2
  153. package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +17 -12
  154. package/src/plugin/internal/load/PluginPackageResolution.ts +14 -9
  155. package/src/plugin/internal/load/ProjectPluginEntries.ts +40 -15
  156. package/src/plugin/internal/load/collectProjectHostInputs.ts +7 -1
  157. package/src/plugin/internal/load/hasProjectPluginEntries.ts +8 -3
  158. package/src/plugin/internal/load/loadProjectPlugins.ts +672 -594
  159. package/src/plugin/internal/load/pluginResolutionFailure.ts +37 -0
  160. package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +8 -2
  161. package/src/structures/ITtscCompilerContext.ts +12 -12
  162. package/src/structures/ITtscPlugin.ts +7 -5
  163. package/src/structures/ITtscPluginFactoryContext.ts +9 -8
  164. package/src/structures/internal/TtscSingleFileEmitOptions.ts +22 -11
@@ -35,8 +35,9 @@ import { resolveCacheDir } from "./resolveCacheDir";
35
35
  import { resolveSingleFileOutput } from "./resolveSingleFileOutput";
36
36
  import { resolveRuntimeCleanTargets } from "./runtime/resolveRuntimeCleanTargets";
37
37
  import { withRuntimeDirectoryLock } from "./runtime/withRuntimeDirectoryLock";
38
+ import { AsyncWatchTopology } from "./watch/AsyncWatchTopology";
38
39
  import { type WatchInputChange } from "./watch/WatchInputChange";
39
- import { WatchTopology } from "./watch/WatchTopology";
40
+ import { WatchSettlement } from "./watch/WatchSettlement";
40
41
 
41
42
  /**
42
43
  * CLI entry point for `ttsc`. Dispatches argv to the appropriate lane (build,
@@ -582,12 +583,26 @@ function printCacheHelp(): void {
582
583
  * full request retains cache, reporting and watch callbacks. Only the private
583
584
  * helper writes the selected public file. Success presentation follows that
584
585
  * completed copy and describes public emission, without private products or a
585
- * fabricated transform-site count. Watch supplies quiet true itself.
586
+ * fabricated transform-site count. Watch supplies quiet true itself. The async
587
+ * watch executor awaits input publication before building; ordinary CLI calls
588
+ * keep their synchronous return contract and share output planning.
586
589
  */
587
590
  function runSingleFile(
588
591
  options: ReturnType<typeof parseTtscBuildArgs> &
589
592
  Pick<TtscSingleFileEmitOptions, "onProjectInputs" | "onWatchInputs">,
590
- ): number {
593
+ ): number;
594
+ function runSingleFile(
595
+ options: ReturnType<typeof parseTtscBuildArgs> &
596
+ Pick<TtscSingleFileEmitOptions, "onProjectInputs" | "onWatchInputs">,
597
+ execute: typeof runSingleFileEmit.async,
598
+ ): Promise<number>;
599
+ function runSingleFile(
600
+ options: ReturnType<typeof parseTtscBuildArgs> &
601
+ Pick<TtscSingleFileEmitOptions, "onProjectInputs" | "onWatchInputs">,
602
+ execute:
603
+ | typeof runSingleFileEmit
604
+ | typeof runSingleFileEmit.async = runSingleFileEmit,
605
+ ): number | Promise<number> {
591
606
  if (options.files.length !== 1) {
592
607
  throw new Error(
593
608
  "ttsc: single-file mode currently accepts exactly one input file",
@@ -610,20 +625,25 @@ function runSingleFile(
610
625
  project,
611
626
  )
612
627
  : undefined;
613
- runSingleFileEmit({ ...options, cwd, file, out }, selection);
614
- if (options.quiet === false) {
615
- process.stdout.write(
616
- `// ttsc: tsconfig=${project.path} cwd=${cwd} emit=${emit}\n`,
617
- );
628
+ const result = execute({ ...options, cwd, file, out }, selection);
629
+ const report = (): number => {
630
+ if (options.quiet === false) {
631
+ process.stdout.write(
632
+ `// ttsc: tsconfig=${project.path} cwd=${cwd} emit=${emit}\n`,
633
+ );
634
+ if (out !== undefined) {
635
+ process.stdout.write(`// ttsc: emitted=1 files\n`);
636
+ process.stdout.write(` + ${relativeToCwd(cwd, out) || out}\n`);
637
+ }
638
+ }
618
639
  if (out !== undefined) {
619
- process.stdout.write(`// ttsc: emitted=1 files\n`);
620
- process.stdout.write(` + ${relativeToCwd(cwd, out) || out}\n`);
640
+ process.stdout.write(
641
+ `${relativeToCwd(cwd, out) || path.basename(out)}\n`,
642
+ );
621
643
  }
622
- }
623
- if (out !== undefined) {
624
- process.stdout.write(`${relativeToCwd(cwd, out) || path.basename(out)}\n`);
625
- }
626
- return 0;
644
+ return 0;
645
+ };
646
+ return typeof result === "string" ? report() : result.then(report);
627
647
  }
628
648
 
629
649
  function runWatch(
@@ -631,7 +651,7 @@ function runWatch(
631
651
  checkOnly: boolean,
632
652
  ): number {
633
653
  const cwd = path.resolve(options.cwd ?? process.cwd());
634
- let topology: WatchTopology | undefined;
654
+ let topology: AsyncWatchTopology | undefined;
635
655
  const invocation = {
636
656
  ...(checkOnly ? { ...options, emit: false } : options),
637
657
  cwd,
@@ -639,10 +659,10 @@ function runWatch(
639
659
  inputs: readonly string[],
640
660
  identities?: PluginContentIdentities.Store,
641
661
  ) => {
642
- topology?.setExtraInputs(inputs, identities);
662
+ return topology?.setExtraInputs(inputs, identities);
643
663
  },
644
664
  onProjectInputs: (inputs: ITtscProjectInputSnapshot) => {
645
- topology?.setProjectInputs(inputs);
665
+ return topology?.setProjectInputs(inputs);
646
666
  },
647
667
  quiet: true,
648
668
  };
@@ -653,6 +673,8 @@ function runWatch(
653
673
  let closing: Promise<void> | undefined;
654
674
  let rerun = false;
655
675
  let timer: NodeJS.Timeout | null = null;
676
+ const settlement = new WatchSettlement();
677
+ let completedBuilds = 0;
656
678
  const resident =
657
679
  invocation.files.length === 0 ? new ResidentCheckWatchSession() : undefined;
658
680
  const pendingChanges = new PendingResidentCheckWatchChanges();
@@ -664,6 +686,7 @@ function runWatch(
664
686
  const runOnce = async () => {
665
687
  if (closed) return;
666
688
  running = true;
689
+ settlement.changed();
667
690
  const change = pendingChanges.take();
668
691
  if (process.env.TTSC_WATCH_DEBUG_INPUTS)
669
692
  debugWatchInputs(`cycle ${JSON.stringify(change)}`);
@@ -682,14 +705,14 @@ function runWatch(
682
705
  // rebuild reports a clear failure and the watcher keeps running instead
683
706
  // of crashing the process with an uncaught exception.
684
707
  const status = await (invocation.files.length !== 0
685
- ? Promise.resolve(runSingleFile(invocation))
708
+ ? runSingleFile(invocation, runSingleFileEmit.async)
686
709
  : (async () => {
687
710
  const buildOptions = checkOnly
688
711
  ? { ...invocation, emit: false }
689
712
  : invocation;
690
713
  const result =
691
714
  resident === undefined
692
- ? runBuild(buildOptions)
715
+ ? await runBuild.async(buildOptions)
693
716
  : await resident.run(buildOptions, change);
694
717
  // Watch output is one ordered stream. The diagnostics a build
695
718
  // produced and the "[ttsc] watch build …" marker that concludes
@@ -710,6 +733,7 @@ function runWatch(
710
733
  process.stdout.write(
711
734
  `[ttsc] ${status === 0 ? "watch build complete" : "watch build failed"}\n`,
712
735
  );
736
+ ++completedBuilds;
713
737
  } catch (error) {
714
738
  if (closed) return;
715
739
  // Same ordered-stream rule as the build path: the failure text goes on
@@ -719,30 +743,33 @@ function runWatch(
719
743
  lastStatus = lastStatus === 0 ? 2 : lastStatus;
720
744
  completedBuild = true;
721
745
  process.stdout.write(`[ttsc] watch build failed\n`);
746
+ ++completedBuilds;
722
747
  } finally {
723
- running = false;
724
748
  if (completed && !closed) {
725
749
  try {
726
750
  // A filesystem event can arrive after the build reports completion
727
- // but before this synchronous cleanup reaches its re-resolution.
751
+ // but before post-build reconciliation reaches its re-resolution.
728
752
  // Notify for a membership change here as well, otherwise that new
729
753
  // input is silently absorbed into the watch set without a rebuild.
730
- topology?.refresh(true);
754
+ await topology?.refresh(true);
731
755
  } catch (error) {
732
756
  process.stderr.write(
733
757
  `[ttsc] watch error on ${watchMessagePath(cwd, root)}: ${formatError(error)}\n`,
734
758
  );
735
759
  }
736
760
  }
761
+ running = false;
737
762
  }
738
763
  if (rerun) {
739
764
  rerun = false;
740
765
  trigger();
741
766
  }
767
+ settlement.changed();
742
768
  };
743
769
  const trigger = (change?: WatchInputChange, reload = false) => {
744
770
  if (closed) return;
745
771
  pendingChanges.push(change, reload);
772
+ settlement.changed();
746
773
  if (running) {
747
774
  rerun = true;
748
775
  return;
@@ -758,33 +785,30 @@ function runWatch(
758
785
  activeSelected: active !== undefined,
759
786
  });
760
787
  closed = true;
788
+ settlement.changed();
761
789
  if (timer) clearTimeout(timer);
762
790
  timer = null;
763
791
  process.off("SIGINT", onInterrupt);
764
792
  process.off("SIGTERM", onTerminate);
765
793
  process.off("message", onMessage);
766
794
  process.off("disconnect", onDisconnect);
767
- let topologyError: unknown;
768
- try {
769
- topology?.close();
770
- } catch (error) {
771
- topologyError = error;
772
- }
795
+ const topologyClosing = topology?.close() ?? Promise.resolve();
773
796
  const residentClosing = resident?.close() ?? Promise.resolve();
774
797
  const activeClosing = active ?? Promise.resolve();
775
- closing = Promise.allSettled([residentClosing, activeClosing]).then(
776
- (results) => {
777
- const errors = results.flatMap((result) =>
778
- result.status === "rejected" ? [result.reason] : [],
798
+ closing = Promise.allSettled([
799
+ topologyClosing,
800
+ residentClosing,
801
+ activeClosing,
802
+ ]).then((results) => {
803
+ const errors = results.flatMap((result) =>
804
+ result.status === "rejected" ? [result.reason] : [],
805
+ );
806
+ if (errors.length !== 0)
807
+ throw new AggregateError(
808
+ errors,
809
+ `ttsc: watch shutdown failed: ${errors.map(formatError).join("; ")}`,
779
810
  );
780
- if (topologyError !== undefined) errors.push(topologyError);
781
- if (errors.length !== 0)
782
- throw new AggregateError(
783
- errors,
784
- `ttsc: watch shutdown failed: ${errors.map(formatError).join("; ")}`,
785
- );
786
- },
787
- );
811
+ });
788
812
  if (process.env.TTSC_E2E_TRACE) {
789
813
  // Observe the original promises without substituting their settlements.
790
814
  void residentClosing.then(
@@ -893,9 +917,48 @@ function runWatch(
893
917
  const id = message.id;
894
918
  E2ETrace.watchShutdown("ipc-stop-received", { stopId: id });
895
919
  void finish(id);
920
+ } else if (
921
+ typeof message === "object" &&
922
+ message !== null &&
923
+ "type" in message &&
924
+ message.type === "ttsc.watch.settle" &&
925
+ "id" in message &&
926
+ typeof message.id === "string" &&
927
+ message.id.length !== 0
928
+ ) {
929
+ const id = message.id;
930
+ // Build markers retain their exact text and one marker per cycle. IPC
931
+ // travels on another pipe, so its receipt carries the completed count:
932
+ // a caller must drain those stdout markers before using the boundary.
933
+ void settlement
934
+ .wait(
935
+ async () => {
936
+ await topology?.whenIdle();
937
+ if (closed) throw new Error("ttsc: watch closed before settling");
938
+ return !running && !rerun && timer === null;
939
+ },
940
+ async () => {
941
+ if (process.connected && process.send !== undefined)
942
+ await new Promise<void>((resolve, reject) => {
943
+ process.send!(
944
+ { type: "ttsc.watch.settled", id, builds: completedBuilds },
945
+ (error: Error | null) =>
946
+ error === null ? resolve() : reject(error),
947
+ );
948
+ });
949
+ },
950
+ )
951
+ .catch((error: unknown) => {
952
+ if (!closed) {
953
+ process.stderr.write(`${formatError(error)}\n`);
954
+ lastStatus = lastStatus === 0 ? 2 : lastStatus;
955
+ void finish();
956
+ }
957
+ });
896
958
  }
897
959
  };
898
960
  const startRun = () => {
961
+ timer = null;
899
962
  active = runOnce();
900
963
  void active.catch((error: unknown) => {
901
964
  process.stderr.write(`${formatError(error)}\n`);
@@ -904,7 +967,13 @@ function runWatch(
904
967
  });
905
968
  };
906
969
 
907
- topology = new WatchTopology(invocation, {
970
+ topology = new AsyncWatchTopology(invocation, {
971
+ onFailure: (error) => {
972
+ if (closed) return;
973
+ lastStatus = lastStatus === 0 ? 2 : lastStatus;
974
+ process.stderr.write(`${formatError(error)}\n`);
975
+ void finish();
976
+ },
908
977
  onError: (location, error) => {
909
978
  process.stderr.write(
910
979
  `[ttsc] watch error on ${watchMessagePath(cwd, location)}: ${formatError(error)}\n`,
@@ -934,16 +1003,25 @@ function runWatch(
934
1003
  },
935
1004
  onTopologyChange: () => trigger(undefined, true),
936
1005
  });
1006
+ const start = async (): Promise<void> => {
1007
+ await topology!.refresh(false);
1008
+ if (closed) return;
1009
+ process.stdout.write(`[ttsc] watching ${watchMessagePath(cwd, root)}\n`);
1010
+ startRun();
1011
+ };
937
1012
  try {
938
- topology.refresh(false);
939
1013
  process.on("SIGINT", onInterrupt);
940
1014
  process.on("SIGTERM", onTerminate);
941
1015
  if (process.connected) {
942
1016
  process.on("message", onMessage);
943
1017
  process.on("disconnect", onDisconnect);
944
1018
  }
945
- process.stdout.write(`[ttsc] watching ${watchMessagePath(cwd, root)}\n`);
946
- startRun();
1019
+ void start().catch((error: unknown) => {
1020
+ if (closed) return;
1021
+ lastStatus = lastStatus === 0 ? 2 : lastStatus;
1022
+ process.stderr.write(`${formatError(error)}\n`);
1023
+ void finish();
1024
+ });
947
1025
  } catch (error) {
948
1026
  // Refresh may fail after acquiring only part of the watch set. Release that
949
1027
  // partial ownership as well as setup's signal listeners before returning.
@@ -71,16 +71,20 @@ export namespace CommonJsRuntimeSource {
71
71
  }
72
72
 
73
73
  /**
74
- * Construct an owned require with local TypeScript extension advertisements.
74
+ * Construct an owned require sharing the original live extension registry.
75
75
  *
76
- * @evidence contracts/common.md#principled-implementation createRequire loads through Node's shared CommonJS cache; copied public properties preserve cache/main access, while a copied registry advertises the already-supported source extensions without changing their actual loader.
76
+ * Node's loader and every native require share this registration object.
77
+ * Copying it hides later registrations, replacements and deletions from one
78
+ * side; source-extension advertisement belongs to runtime installation.
79
+ *
80
+ * @evidence contracts/common.md#principled-implementation createRequire loads through Node's shared CommonJS cache; copied public properties preserve cache/main access, and the original registry reference preserves the public extension-registration contract across later edits. Missing original metadata falls back to the native registry.
77
81
  * @evidence contracts/common.md#clear-and-simple-design The callable and resolve member share one filename anchor; source recovery remains in the configured policy.
78
- * @evidence contracts/common.md#prohibited-implementation-shortcuts Extension entries belong to this new function, not Module._extensions or a foreign require; no private loader slot is assigned.
79
- * @evidence contracts/common.md#meaningful-documentation Native prose identifies ownership and distinguishes advertisements from actual loading.
82
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Construction assigns only the owned function's properties, adds no extension capability and replaces no foreign handler or private loader slot. Runtime installation separately advertises its supported CommonJS source extensions.
83
+ * @evidence contracts/common.md#meaningful-documentation Native prose explains why live registry identity is required and assigns advertisement to the installation owner.
80
84
  * @evidence contracts/portability.md#os-neutral-implementation The public createRequire API takes the native filename; no OS-name or path-string case heuristic is introduced.
81
- * @evidence contracts/performance.md#efficient-algorithms Construction copies public require properties and E extension entries, including their property observations, and creates a native filename-anchored require. Calls delegate native resolution, module evaluation/cache work or the configured resolver; their cost is not bounded by construction or extension count.
85
+ * @evidence contracts/performance.md#efficient-algorithms Construction copies P public require properties and creates a native filename-anchored require without traversing or allocating another extension registry. Calls delegate native resolution, module evaluation/cache work or the configured resolver; their cost is not bounded by property count.
82
86
  * @evidence contracts/performance.md#reuse-equivalent-work Callable loading delegates to Node's CommonJS cache; the configured resolve policy remains a distinct per-call operation, not a cached result of constructing the function.
83
- * @evidence contracts/performance.md#bound-retention-and-release-resources The evaluated module owns the returned function, captured filename/native require and copied extension registry. Captured cache/main references share Node or original-require state rather than copying all cached modules; the factory adds no historical module collection or native handle.
87
+ * @evidence contracts/performance.md#bound-retention-and-release-resources The evaluated module owns the returned function and captured filename/native require. Cache, main and extension-registry references share Node or original-require state; the factory adds no registry copy, historical module collection or native handle.
84
88
  */
85
89
  export function create(
86
90
  original: NodeJS.Require,
@@ -107,9 +111,7 @@ export namespace CommonJsRuntimeSource {
107
111
  )) as NodeJS.RequireResolve;
108
112
  owned.resolve.paths = native.resolve.paths;
109
113
  owned.cache = original.cache ?? native.cache;
110
- owned.extensions = { ...(original.extensions ?? native.extensions) };
111
- for (const extension of [".ts", ".tsx", ".cts", ".mts"])
112
- owned.extensions[extension] = owned.extensions[".js"]!;
114
+ owned.extensions = original.extensions ?? native.extensions;
113
115
  return owned;
114
116
  }
115
117
 
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Publish the CommonJS source extensions served by the installed runtime hooks.
3
+ *
4
+ * Tools such as tsconfig-paths enumerate the process registry before asking
5
+ * Node to resolve a file, and rechoir checks it before loading a typed config.
6
+ * A module-local copy cannot satisfy those checks. Add only missing entries
7
+ * using Node's JavaScript handler; registerHooks still owns source compilation
8
+ * and existing loader handlers keep their identity. Node probes these entries
9
+ * after its existing extensions, preserving JavaScript precedence.
10
+ *
11
+ * @evidence contracts/common.md#principled-implementation The public require.extensions registry advertises supported CommonJS source loading to consumers that inspect extensions before resolution; absent entries delegate to Node's existing JavaScript handler, which consumes the source supplied by registerHooks.
12
+ * @evidence contracts/common.md#clear-and-simple-design One installation operation publishes loader capability without adding a second compilation or resolution path.
13
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Only missing public registry entries are added; no foreign handler or method is replaced and no consumer identity affects production behavior. The source suffixes describe the supported CommonJS contract rather than fixture-specific routes.
14
+ * @evidence contracts/common.md#meaningful-documentation The prose explains why process-wide advertisement is needed, who compiles the source, which handlers remain authoritative and how native precedence is preserved.
15
+ * @evidenceExclude contracts/portability.md#os-neutral-implementation Registry updates use JavaScript property semantics without filesystem, path or process arguments; Node's loader owns native resolution.
16
+ * @evidence contracts/performance.md#efficient-algorithms A fixed set of three CommonJS source extensions requires constant time and space, without scanning modules or files.
17
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work This capability publication shares no computed compilation or resolution result.
18
+ * @evidence contracts/performance.md#bound-retention-and-release-resources The caller's process registry retains at most three added references to an existing Node handler for the installed hooks' process lifetime; no handle or task is acquired.
19
+ */
20
+ export function advertiseTypeScriptExtensions(
21
+ extensions: NodeJS.RequireExtensions,
22
+ ): void {
23
+ for (const extension of [".ts", ".tsx", ".cts"])
24
+ extensions[extension] ??= extensions[".js"]!;
25
+ }
@@ -43,6 +43,7 @@ import type { RuntimeManifest } from "./RuntimeManifest";
43
43
  import { RuntimeManifestRegistry } from "./RuntimeManifestRegistry";
44
44
  import { RuntimeModuleFormat } from "./RuntimeModuleFormat";
45
45
  import { RuntimeProjectOwnership } from "./RuntimeProjectOwnership";
46
+ import { advertiseTypeScriptExtensions } from "./advertiseTypeScriptExtensions";
46
47
  import { checkNodeRuntimeSupport } from "./checkNodeRuntimeSupport";
47
48
  import { commonJsExportNames } from "./commonJsExportNames";
48
49
  import { commonJsImportFacade } from "./commonJsImportFacade";
@@ -94,8 +95,10 @@ import { selectRuntimePluginPolicy } from "./selectRuntimePluginPolicy";
94
95
  * reach the API lacks on some releases is `require.resolve`, which is probed
95
96
  * before installation. Served CommonJS bodies receive an owned require function
96
97
  * whose resolve member applies the same source policy on those releases. Its
97
- * copied extension registry advertises the supported source extensions; foreign
98
- * resolver methods and global extension registries are never replaced.
98
+ * shared extension registry preserves live registrations. Installation also
99
+ * advertises missing CommonJS source extensions in the process registry because
100
+ * ecosystem tools inspect it before resolution; existing handlers remain intact
101
+ * and foreign resolver methods are never replaced.
99
102
  *
100
103
  * Direct descriptor evaluation records user preloads after hook installation.
101
104
  * Only the generated CommonJS output sibling takes Node's native resolve/load
@@ -104,7 +107,7 @@ import { selectRuntimePluginPolicy } from "./selectRuntimePluginPolicy";
104
107
  *
105
108
  * @evidence contracts/common.md#principled-implementation Resolution preserves successful Node decisions, rescues source spellings only after documented resolution fails, and serves only outputs whose ownership index names the exact source. CommonJS facades delegate native evaluation and project detected own named exports under the selected namespace capability; an owned module-local require supplies source resolution when native require.resolve bypasses public hooks.
106
109
  * @evidence contracts/common.md#clear-and-simple-design One synchronous hook owner coordinates source selection, emission ownership and descriptor observation; the entry, owning-project and orphan lanes remain explicit because they have distinct compilation premises. Private helpers carry those policies without a second foreign-resolver layer.
107
- * @evidence contracts/common.md#prohibited-implementation-shortcuts Public registerHooks handles resolution and loading; installation mutates neither Module._resolveFilename nor require.extensions. Source-extension recovery implements emitted-to-source spelling under the runtime contract, and module-local require adaptation addresses the probed public-hook difference without replacing Node methods.
110
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Public registerHooks handles resolution and loading; installation adds missing public extension entries with Node's own JavaScript handler while preserving every existing handler and resolver method. Source-extension recovery implements emitted-to-source spelling under the runtime contract, and module-local require adaptation addresses the probed public-hook difference without replacing Node methods.
108
111
  * @evidence contracts/common.md#meaningful-documentation Native paragraphs describe the three serving lanes, compile gates, source identity, capability-based local adaptation and extension-registry ownership; helper comments state ownership and failure effects with descriptive prose separated from tags.
109
112
  * @evidence contracts/portability.md#os-neutral-implementation Node URL conversion, native filesystem paths and physical resolution preserve OS spelling boundaries. Actual public-hook probes select runtime capabilities, native emit uses executable arguments without shell interpolation, and unresolved filesystem observation refuses reusable descriptor proof.
110
113
  * @evidence contracts/performance.md#efficient-algorithms Source ownership indexes replace repeated whole-emit scans with indexed source/native-identity queries. Export discovery processes source/emit bytes, reexport edges, native resolution and Set/name unions under traversal-local seen sets; separate roots can repeat traversal. Config-chain validation reads discovered inputs. Compiler identity brackets and streams executable bytes on valid lookups, refusing failed observations. Native build/name-scan/orphan paths add compilation, path/metadata/IO cost; recursive graphs remain subject to stack limits.
@@ -134,6 +137,7 @@ export function installRuntimeHooks(options: RuntimeHookOptions = {}): void {
134
137
  process.setSourceMapsEnabled(true);
135
138
  }
136
139
  registerHooks({ load, resolve });
140
+ advertiseTypeScriptExtensions(require.extensions);
137
141
  PluginDescriptorInputObservation.begin(true);
138
142
  installed = true;
139
143
  }