ttsc 0.31.6 → 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 (261) hide show
  1. package/cmd/ttsc/compiler_argument_cwd_test.go +38 -38
  2. package/cmd/ttscserver/main.go +2 -12
  3. package/cmd/ttscserver/resolve_launch_uses_native_flag_authority_test.go +7 -7
  4. package/driver/lsp.go +4 -43
  5. package/driver/rewrite.go +1 -1
  6. package/internal/e2etrace/completion_hints.go +3 -3
  7. package/internal/e2etrace/completion_hints_test.go +26 -12
  8. package/internal/graph/build.go +5 -6
  9. package/internal/graph/graph.go +3 -3
  10. package/internal/graph/line_model.go +2 -35
  11. package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +13 -36
  12. package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +9 -3
  13. package/internal/lspserver/lsp_plugin.go +19 -0
  14. package/internal/lspserver/lsp_proxy.go +9 -129
  15. package/internal/lspserver/lsp_server.go +0 -8
  16. package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +6 -36
  17. package/internal/sourceprocess/windows_target_owns_suspended_admission_test.go +5 -5
  18. package/lib/TtscCompiler.js +1 -1
  19. package/lib/TtscCompiler.js.map +1 -1
  20. package/lib/compiler/internal/ResidentCheckProcess.d.ts +74 -96
  21. package/lib/compiler/internal/ResidentCheckProcess.js +150 -335
  22. package/lib/compiler/internal/ResidentCheckProcess.js.map +1 -1
  23. package/lib/compiler/internal/ResidentCheckProcessHost.d.ts +127 -0
  24. package/lib/compiler/internal/ResidentCheckProcessHost.js +447 -0
  25. package/lib/compiler/internal/ResidentCheckProcessHost.js.map +1 -0
  26. package/lib/compiler/internal/ResidentCheckProcessProtocol.d.ts +64 -0
  27. package/lib/compiler/internal/ResidentCheckProcessProtocol.js +3 -0
  28. package/lib/compiler/internal/ResidentCheckProcessProtocol.js.map +1 -0
  29. package/lib/compiler/internal/build/BuildExecution.d.ts +103 -1
  30. package/lib/compiler/internal/build/BuildExecution.js +92 -12
  31. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  32. package/lib/compiler/internal/build/ResidentCheckWatchSession.d.ts +16 -11
  33. package/lib/compiler/internal/build/ResidentCheckWatchSession.js +37 -21
  34. package/lib/compiler/internal/build/ResidentCheckWatchSession.js.map +1 -1
  35. package/lib/compiler/internal/build/RunBuildOptions.d.ts +6 -5
  36. package/lib/compiler/internal/build/TsgoArguments.js.map +1 -1
  37. package/lib/compiler/internal/build/WatchInputPublication.d.ts +59 -0
  38. package/lib/compiler/internal/build/WatchInputPublication.js +93 -0
  39. package/lib/compiler/internal/build/WatchInputPublication.js.map +1 -0
  40. package/lib/compiler/internal/build/runBuild.d.ts +29 -2
  41. package/lib/compiler/internal/build/runBuild.js +45 -19
  42. package/lib/compiler/internal/build/runBuild.js.map +1 -1
  43. package/lib/compiler/internal/buildNativeCompiler.d.ts +2 -0
  44. package/lib/compiler/internal/buildNativeCompiler.js +1 -0
  45. package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
  46. package/lib/compiler/internal/compileProjectInMemory.js +4 -2
  47. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  48. package/lib/compiler/internal/project/CompilerProjectSelection.d.ts +9 -8
  49. package/lib/compiler/internal/project/CompilerProjectSelection.js +11 -9
  50. package/lib/compiler/internal/project/CompilerProjectSelection.js.map +1 -1
  51. package/lib/compiler/internal/project/resolveOwningProjectConfig.d.ts +25 -8
  52. package/lib/compiler/internal/project/resolveOwningProjectConfig.js +26 -14
  53. package/lib/compiler/internal/project/resolveOwningProjectConfig.js.map +1 -1
  54. package/lib/compiler/internal/readEffectiveCompilerOptions.js +1 -1
  55. package/lib/compiler/internal/readEffectiveCompilerOptions.js.map +1 -1
  56. package/lib/compiler/internal/resolveBinary.d.ts +1 -0
  57. package/lib/compiler/internal/resolveBinary.js.map +1 -1
  58. package/lib/compiler/internal/resolveTsgo.d.ts +6 -0
  59. package/lib/compiler/internal/resolveTsgo.js.map +1 -1
  60. package/lib/compiler/internal/runSingleFileEmit.d.ts +32 -4
  61. package/lib/compiler/internal/runSingleFileEmit.js +124 -89
  62. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  63. package/lib/compiler/internal/startResidentTransform.js +8 -2
  64. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  65. package/lib/compiler/internal/transformProjectInMemory.js +11 -3
  66. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  67. package/lib/compiler/internal/transformProjectInWorker.d.ts +1 -1
  68. package/lib/compiler/internal/transformProjectInWorker.js +3 -2
  69. package/lib/compiler/internal/transformProjectInWorker.js.map +1 -1
  70. package/lib/flags/ParseOptions.d.ts +2 -2
  71. package/lib/internal/E2ETrace.js.map +1 -1
  72. package/lib/internal/OwnedNativeProcess.d.ts +2 -2
  73. package/lib/internal/OwnedNativeProcess.js +2 -2
  74. package/lib/internal/SourceNativeRetirement.d.ts +8 -7
  75. package/lib/internal/SourceNativeRetirement.js +19 -10
  76. package/lib/internal/SourceNativeRetirement.js.map +1 -1
  77. package/lib/internal/TtsxNodeOptions.d.ts +79 -0
  78. package/lib/internal/TtsxNodeOptions.js +121 -0
  79. package/lib/internal/TtsxNodeOptions.js.map +1 -0
  80. package/lib/internal/javascriptRuntimeCapabilities.js +4 -1
  81. package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
  82. package/lib/internal/parseWindowsDirectoryCaseSensitivity.d.ts +5 -5
  83. package/lib/internal/parseWindowsDirectoryCaseSensitivity.js +5 -5
  84. package/lib/internal/pathIdentity/createCaseSensitivityObservations.d.ts +35 -0
  85. package/lib/internal/pathIdentity/createCaseSensitivityObservations.js +209 -0
  86. package/lib/internal/pathIdentity/createCaseSensitivityObservations.js.map +1 -0
  87. package/lib/internal/pathIdentity/createFilesystemPathIdentityContext.js +2 -143
  88. package/lib/internal/pathIdentity/createFilesystemPathIdentityContext.js.map +1 -1
  89. package/lib/internal/projectInputPathIdentity.d.ts +1 -0
  90. package/lib/internal/projectInputPathIdentity.js +1 -0
  91. package/lib/internal/projectInputPathIdentity.js.map +1 -1
  92. package/lib/internal/workerModuleUrl.d.ts +28 -0
  93. package/lib/internal/workerModuleUrl.js +40 -0
  94. package/lib/internal/workerModuleUrl.js.map +1 -0
  95. package/lib/launcher/internal/buildSingleRootProject.js +2 -1
  96. package/lib/launcher/internal/buildSingleRootProject.js.map +1 -1
  97. package/lib/launcher/internal/getCompilerVersionText.js +3 -2
  98. package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
  99. package/lib/launcher/internal/parseTtsxCLI.d.ts +5 -4
  100. package/lib/launcher/internal/parseTtsxCLI.js +5 -4
  101. package/lib/launcher/internal/parseTtsxCLI.js.map +1 -1
  102. package/lib/launcher/internal/prepareExecution.js +2 -1
  103. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  104. package/lib/launcher/internal/resolveSingleFileOutput.d.ts +8 -9
  105. package/lib/launcher/internal/resolveSingleFileOutput.js +8 -9
  106. package/lib/launcher/internal/resolveSingleFileOutput.js.map +1 -1
  107. package/lib/launcher/internal/runTtsc.js +91 -41
  108. package/lib/launcher/internal/runTtsc.js.map +1 -1
  109. package/lib/launcher/internal/runTtsx.js +5 -2
  110. package/lib/launcher/internal/runTtsx.js.map +1 -1
  111. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.d.ts +10 -6
  112. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.js +11 -9
  113. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.js.map +1 -1
  114. package/lib/launcher/internal/runtime/RuntimeEntrySources.d.ts +40 -0
  115. package/lib/launcher/internal/runtime/RuntimeEntrySources.js +57 -0
  116. package/lib/launcher/internal/runtime/RuntimeEntrySources.js.map +1 -0
  117. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.d.ts +5 -4
  118. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.js +8 -6
  119. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.js.map +1 -1
  120. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.d.ts +20 -0
  121. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.js +27 -0
  122. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.js.map +1 -0
  123. package/lib/launcher/internal/runtime/commonJsExportNames.d.ts +2 -1
  124. package/lib/launcher/internal/runtime/commonJsExportNames.js +2 -1
  125. package/lib/launcher/internal/runtime/commonJsExportNames.js.map +1 -1
  126. package/lib/launcher/internal/runtime/installRuntimeHooks.d.ts +5 -3
  127. package/lib/launcher/internal/runtime/installRuntimeHooks.js +42 -12
  128. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  129. package/lib/launcher/internal/runtime/resolveRuntimeCleanTargets.js +3 -1
  130. package/lib/launcher/internal/runtime/resolveRuntimeCleanTargets.js.map +1 -1
  131. package/lib/launcher/internal/watch/AsyncWatchTopology.d.ts +113 -0
  132. package/lib/launcher/internal/watch/AsyncWatchTopology.js +232 -0
  133. package/lib/launcher/internal/watch/AsyncWatchTopology.js.map +1 -0
  134. package/lib/launcher/internal/watch/WatchSettlement.d.ts +49 -0
  135. package/lib/launcher/internal/watch/WatchSettlement.js +70 -0
  136. package/lib/launcher/internal/watch/WatchSettlement.js.map +1 -0
  137. package/lib/launcher/internal/watch/WatchTopology.d.ts +103 -13
  138. package/lib/launcher/internal/watch/WatchTopology.js +380 -95
  139. package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
  140. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.d.ts +121 -0
  141. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.js +3 -0
  142. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.js.map +1 -0
  143. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.d.ts +4 -1
  144. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.js +3 -2
  145. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.js.map +1 -1
  146. package/lib/launcher/internal/watch/serveWatchTopologyWorker.d.ts +19 -0
  147. package/lib/launcher/internal/watch/serveWatchTopologyWorker.js +62 -0
  148. package/lib/launcher/internal/watch/serveWatchTopologyWorker.js.map +1 -0
  149. package/lib/launcher/internal/watch/watchTopologyWorker.d.ts +1 -0
  150. package/lib/launcher/internal/watch/watchTopologyWorker.js +22 -0
  151. package/lib/launcher/internal/watch/watchTopologyWorker.js.map +1 -0
  152. package/lib/plugin/CapabilityPluginResolver.d.ts +1 -1
  153. package/lib/plugin/CapabilityPluginResolver.js +3 -2
  154. package/lib/plugin/CapabilityPluginResolver.js.map +1 -1
  155. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +15 -12
  156. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +13 -11
  157. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
  158. package/lib/plugin/internal/load/PluginPackageResolution.d.ts +2 -2
  159. package/lib/plugin/internal/load/PluginPackageResolution.js +13 -7
  160. package/lib/plugin/internal/load/PluginPackageResolution.js.map +1 -1
  161. package/lib/plugin/internal/load/ProjectPluginEntries.d.ts +23 -7
  162. package/lib/plugin/internal/load/ProjectPluginEntries.js +36 -14
  163. package/lib/plugin/internal/load/ProjectPluginEntries.js.map +1 -1
  164. package/lib/plugin/internal/load/collectProjectHostInputs.d.ts +3 -1
  165. package/lib/plugin/internal/load/collectProjectHostInputs.js +5 -2
  166. package/lib/plugin/internal/load/collectProjectHostInputs.js.map +1 -1
  167. package/lib/plugin/internal/load/hasProjectPluginEntries.d.ts +3 -1
  168. package/lib/plugin/internal/load/hasProjectPluginEntries.js +4 -2
  169. package/lib/plugin/internal/load/hasProjectPluginEntries.js.map +1 -1
  170. package/lib/plugin/internal/load/loadProjectPlugins.d.ts +36 -5
  171. package/lib/plugin/internal/load/loadProjectPlugins.js +477 -421
  172. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  173. package/lib/plugin/internal/load/pluginResolutionFailure.d.ts +18 -0
  174. package/lib/plugin/internal/load/pluginResolutionFailure.js +32 -0
  175. package/lib/plugin/internal/load/pluginResolutionFailure.js.map +1 -0
  176. package/lib/plugin/internal/load/resolveNativeSource.js +2 -3
  177. package/lib/plugin/internal/load/resolveNativeSource.js.map +1 -1
  178. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +1 -1
  179. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +3 -2
  180. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
  181. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +4 -1
  182. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +9 -2
  183. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
  184. package/lib/structures/ITtscCompilerContext.d.ts +12 -12
  185. package/lib/structures/ITtscPlugin.d.ts +7 -5
  186. package/lib/structures/ITtscPluginFactoryContext.d.ts +9 -8
  187. package/lib/structures/internal/TtscCommonOptions.d.ts +2 -2
  188. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +12 -6
  189. package/package.json +9 -9
  190. package/src/TtscCompiler.ts +1 -1
  191. package/src/compiler/internal/ResidentCheckProcess.ts +159 -374
  192. package/src/compiler/internal/ResidentCheckProcessHost.ts +502 -0
  193. package/src/compiler/internal/ResidentCheckProcessProtocol.ts +53 -0
  194. package/src/compiler/internal/build/BuildExecution.ts +123 -11
  195. package/src/compiler/internal/build/ResidentCheckWatchSession.ts +41 -24
  196. package/src/compiler/internal/build/RunBuildOptions.ts +9 -5
  197. package/src/compiler/internal/build/TsgoArguments.ts +4 -1
  198. package/src/compiler/internal/build/WatchInputPublication.ts +106 -0
  199. package/src/compiler/internal/build/runBuild.ts +58 -34
  200. package/src/compiler/internal/buildNativeCompiler.ts +4 -0
  201. package/src/compiler/internal/compileProjectInMemory.ts +8 -2
  202. package/src/compiler/internal/project/CompilerProjectSelection.ts +14 -14
  203. package/src/compiler/internal/project/resolveOwningProjectConfig.ts +42 -16
  204. package/src/compiler/internal/readEffectiveCompilerOptions.ts +1 -1
  205. package/src/compiler/internal/resolveBinary.ts +4 -1
  206. package/src/compiler/internal/resolveTsgo.ts +7 -0
  207. package/src/compiler/internal/runSingleFileEmit.ts +145 -101
  208. package/src/compiler/internal/startResidentTransform.ts +9 -2
  209. package/src/compiler/internal/transformProjectInMemory.ts +12 -3
  210. package/src/compiler/internal/transformProjectInWorker.ts +10 -2
  211. package/src/flags/ParseOptions.ts +2 -2
  212. package/src/internal/E2ETrace.ts +3 -1
  213. package/src/internal/OwnedNativeProcess.ts +2 -2
  214. package/src/internal/SourceNativeRetirement.ts +42 -19
  215. package/src/internal/TtsxNodeOptions.ts +124 -0
  216. package/src/internal/javascriptRuntimeCapabilities.ts +6 -1
  217. package/src/internal/parseWindowsDirectoryCaseSensitivity.ts +5 -5
  218. package/src/internal/pathIdentity/createCaseSensitivityObservations.ts +242 -0
  219. package/src/internal/pathIdentity/createFilesystemPathIdentityContext.ts +2 -169
  220. package/src/internal/projectInputPathIdentity.ts +1 -0
  221. package/src/internal/workerModuleUrl.ts +34 -0
  222. package/src/launcher/internal/buildSingleRootProject.ts +2 -0
  223. package/src/launcher/internal/getCompilerVersionText.ts +3 -2
  224. package/src/launcher/internal/parseTtsxCLI.ts +5 -4
  225. package/src/launcher/internal/prepareExecution.ts +6 -1
  226. package/src/launcher/internal/resolveSingleFileOutput.ts +8 -9
  227. package/src/launcher/internal/runTtsc.ts +127 -49
  228. package/src/launcher/internal/runTtsx.ts +17 -8
  229. package/src/launcher/internal/runtime/CommonJsRuntimeSource.ts +11 -9
  230. package/src/launcher/internal/runtime/RuntimeEntrySources.ts +57 -0
  231. package/src/launcher/internal/runtime/RuntimeProjectOwnership.ts +23 -10
  232. package/src/launcher/internal/runtime/advertiseTypeScriptExtensions.ts +25 -0
  233. package/src/launcher/internal/runtime/commonJsExportNames.ts +11 -4
  234. package/src/launcher/internal/runtime/installRuntimeHooks.ts +62 -16
  235. package/src/launcher/internal/runtime/resolveRuntimeCleanTargets.ts +17 -5
  236. package/src/launcher/internal/watch/AsyncWatchTopology.ts +267 -0
  237. package/src/launcher/internal/watch/WatchSettlement.ts +67 -0
  238. package/src/launcher/internal/watch/WatchTopology.ts +461 -102
  239. package/src/launcher/internal/watch/WatchTopologyWorkerProtocol.ts +106 -0
  240. package/src/launcher/internal/watch/projectInputReloadEventShouldNotify.ts +14 -9
  241. package/src/launcher/internal/watch/serveWatchTopologyWorker.ts +75 -0
  242. package/src/launcher/internal/watch/watchTopologyWorker.ts +29 -0
  243. package/src/plugin/CapabilityPluginResolver.ts +9 -2
  244. package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +17 -12
  245. package/src/plugin/internal/load/PluginPackageResolution.ts +14 -9
  246. package/src/plugin/internal/load/ProjectPluginEntries.ts +40 -15
  247. package/src/plugin/internal/load/collectProjectHostInputs.ts +7 -1
  248. package/src/plugin/internal/load/hasProjectPluginEntries.ts +8 -3
  249. package/src/plugin/internal/load/loadProjectPlugins.ts +683 -593
  250. package/src/plugin/internal/load/pluginResolutionFailure.ts +37 -0
  251. package/src/plugin/internal/load/resolveNativeSource.ts +10 -4
  252. package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +8 -2
  253. package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +10 -1
  254. package/src/structures/ITtscCompilerContext.ts +12 -12
  255. package/src/structures/ITtscPlugin.ts +7 -5
  256. package/src/structures/ITtscPluginFactoryContext.ts +9 -8
  257. package/src/structures/internal/TtscCommonOptions.ts +2 -2
  258. package/src/structures/internal/TtscSingleFileEmitOptions.ts +22 -11
  259. package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +0 -78
  260. package/internal/graphsymbols/provider.go +0 -650
  261. package/internal/lspserver/lsp_symbols.go +0 -212
@@ -1,13 +1,10 @@
1
- import childProcess from "node:child_process";
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
3
 
5
- import { E2ETrace } from "../E2ETrace";
6
- import { OwnedSynchronousProcess } from "../OwnedSynchronousProcess";
7
- import { parseWindowsDirectoryCaseSensitivity } from "../parseWindowsDirectoryCaseSensitivity";
8
4
  import type { FilesystemPathIdentity } from "./FilesystemPathIdentity";
9
5
  import type { FilesystemPathIdentityContext } from "./FilesystemPathIdentityContext";
10
6
  import type { FilesystemPathIdentityOperations } from "./FilesystemPathIdentityOperations";
7
+ import { createCaseSensitivityObservations } from "./createCaseSensitivityObservations";
11
8
  import { isFilesystemPathIdentityWithin } from "./isFilesystemPathIdentityWithin";
12
9
  import { resolveFilesystemPath } from "./resolveFilesystemPath";
13
10
 
@@ -47,19 +44,7 @@ export function createFilesystemPathIdentityContext(
47
44
  const readdir = operations.readdir ?? fs.readdirSync;
48
45
  const caseSensitive =
49
46
  operations.caseSensitive ??
50
- ((directory: string) => {
51
- try {
52
- return filesystemDirectoryIsCaseSensitive(directory, platform, {
53
- lstat,
54
- readdir,
55
- realpath,
56
- });
57
- } catch {
58
- // A failed capability observation is unknown. This does not suppress
59
- // strict physical identity errors in the separate realpath resolver.
60
- return undefined;
61
- }
62
- });
47
+ createCaseSensitivityObservations({ lstat, platform, readdir, realpath });
63
48
 
64
49
  const resolve = (location: string): FilesystemPathIdentity => {
65
50
  const normalized = resolveFilesystemPath(location, platform);
@@ -262,150 +247,6 @@ function physicalRealpath(location: string): string {
262
247
  return fs.realpathSync.native?.(location) ?? fs.realpathSync(location);
263
248
  }
264
249
 
265
- function filesystemDirectoryIsCaseSensitive(
266
- directory: string,
267
- platform: NodeJS.Platform,
268
- operations: {
269
- lstat(location: string): fs.Stats | fs.BigIntStats;
270
- readdir(directory: string): string[];
271
- realpath(location: string): string;
272
- },
273
- ): boolean | undefined {
274
- const pathApi = platform === "win32" ? path.win32 : path.posix;
275
- let entries: string[];
276
- try {
277
- entries = operations.readdir(directory);
278
- } catch {
279
- return undefined;
280
- }
281
- const foldedNames = new Map<string, string>();
282
- for (const name of entries) {
283
- const folded = foldAsciiCase(name);
284
- const previous = foldedNames.get(folded);
285
- if (previous !== undefined && previous !== name) return true;
286
- foldedNames.set(folded, name);
287
- }
288
- for (const name of entries) {
289
- const alternate = alternateCase(name);
290
- if (alternate === name) continue;
291
- try {
292
- operations.lstat(pathApi.join(directory, alternate));
293
- } catch (error) {
294
- if (isMissingFilesystemEntry(error)) {
295
- // Enumeration is not an atomic snapshot. An absent alternate proves
296
- // sensitivity only if the original spelling still names an entry.
297
- try {
298
- operations.lstat(pathApi.join(directory, name));
299
- return true;
300
- } catch (originalError) {
301
- if (isMissingFilesystemEntry(originalError) === false)
302
- throw originalError;
303
- }
304
- continue;
305
- }
306
- throw error;
307
- }
308
- try {
309
- const actual = resolveFilesystemPath(
310
- operations.realpath(pathApi.join(directory, name)),
311
- platform,
312
- );
313
- const alias = resolveFilesystemPath(
314
- operations.realpath(pathApi.join(directory, alternate)),
315
- platform,
316
- );
317
- return actual !== alias;
318
- } catch (error) {
319
- if (isMissingFilesystemEntry(error) === false) throw error;
320
- }
321
- }
322
- if (platform === "darwin") {
323
- // APFS/HFS case semantics are volume-wide. An empty directory has no child
324
- // name to probe, so ask the same read-only question of its existing name in
325
- // the parent and walk upward until a name with ASCII case is available.
326
- // This avoids a write probe while still distinguishing default APFS from a
327
- // case-sensitive volume.
328
- let current = resolveFilesystemPath(directory, platform);
329
- while (true) {
330
- const parent = pathApi.dirname(current);
331
- if (parent === current) break;
332
- // Names are interpreted by their parent; another mount is not evidence
333
- // of the directory's own volume case policy.
334
- if (operations.lstat(current).dev !== operations.lstat(parent).dev)
335
- return undefined;
336
- const name = pathApi.basename(current);
337
- const alternate = alternateCase(name);
338
- if (alternate !== name) {
339
- try {
340
- const actual = resolveFilesystemPath(
341
- operations.realpath(current),
342
- platform,
343
- );
344
- const alias = resolveFilesystemPath(
345
- operations.realpath(pathApi.join(parent, alternate)),
346
- platform,
347
- );
348
- return actual !== alias;
349
- } catch (error) {
350
- if (isMissingFilesystemEntry(error) === false) throw error;
351
- }
352
- }
353
- current = parent;
354
- }
355
- return undefined;
356
- }
357
- if (platform !== "win32") return undefined;
358
- // Node does not expose the Windows per-directory flag. Prefer fsutil's
359
- // read-only answer; unavailable capability remains unknown.
360
- const queried = queryWindowsDirectoryCaseSensitivity(directory);
361
- if (queried !== undefined) return queried;
362
- return undefined;
363
- }
364
-
365
- /**
366
- * Read the per-directory flag without depending on fsutil's display language.
367
- *
368
- * English output is cheap to recognize directly. Other Windows locales write
369
- * console-code-page bytes that Node cannot reliably decode, so their raw
370
- * message is interpreted against the volume-root query.
371
- */
372
- function queryWindowsDirectoryCaseSensitivity(
373
- directory: string,
374
- ): boolean | undefined {
375
- const result = queryWindowsDirectoryCaseSensitivityBytes(directory);
376
- if (result === undefined) return undefined;
377
- const volumeRoot = path.win32.parse(directory).root;
378
- const direct = parseWindowsDirectoryCaseSensitivity(
379
- result,
380
- undefined,
381
- volumeRoot,
382
- );
383
- if (direct !== undefined) return direct;
384
- const volume = queryWindowsDirectoryCaseSensitivityBytes(volumeRoot);
385
- return parseWindowsDirectoryCaseSensitivity(result, volume, volumeRoot);
386
- }
387
-
388
- function queryWindowsDirectoryCaseSensitivityBytes(
389
- directory: string,
390
- ): Buffer | undefined {
391
- const args = ["file", "queryCaseSensitiveInfo", directory];
392
- const result = E2ETrace.synchronous(
393
- "fsutil.exe",
394
- args,
395
- {},
396
- "path-case-probe",
397
- () =>
398
- OwnedSynchronousProcess.launch("fsutil.exe", args, {
399
- windowsHide: true,
400
- }) ?? childProcess.spawnSync("fsutil.exe", args, { windowsHide: true }),
401
- );
402
- return result.error === undefined &&
403
- result.status === 0 &&
404
- Buffer.isBuffer(result.stdout)
405
- ? result.stdout
406
- : undefined;
407
- }
408
-
409
250
  function filesystemPathIdentityKey(
410
251
  location: string,
411
252
  platform: NodeJS.Platform,
@@ -415,14 +256,6 @@ function filesystemPathIdentityKey(
415
256
  return `${root.toLowerCase()}${location.slice(root.length)}`;
416
257
  }
417
258
 
418
- function alternateCase(value: string): string {
419
- return value.replace(/[A-Za-z]/g, (character) =>
420
- character === character.toLowerCase()
421
- ? character.toUpperCase()
422
- : character.toLowerCase(),
423
- );
424
- }
425
-
426
259
  /** ASCII folding is the case equivalence observed by the read-only probes. */
427
260
  function foldAsciiCase(value: string): string {
428
261
  return value.replace(/[A-Z]/g, (character) => character.toLowerCase());
@@ -16,6 +16,7 @@
16
16
  export * from "./pathIdentity/FilesystemPathIdentity";
17
17
  export * from "./pathIdentity/FilesystemPathIdentityOperations";
18
18
  export * from "./pathIdentity/FilesystemPathIdentityContext";
19
+ export * from "./pathIdentity/createCaseSensitivityObservations";
19
20
  export * from "./pathIdentity/createFilesystemPathIdentityContext";
20
21
  export * from "./pathIdentity/isFilesystemPathIdentityWithin";
21
22
  export * from "./pathIdentity/resolveFilesystemPath";
@@ -0,0 +1,34 @@
1
+ import path from "node:path";
2
+
3
+ /**
4
+ * Load an owned CommonJS worker through a module URL while retaining Node's
5
+ * inherited execution options and preload hooks.
6
+ *
7
+ * A parent launched with --input-type interprets stdin or evaluated source.
8
+ * With a preload, module resolution rejects that inherited option at a file
9
+ * worker entry before the worker can start. A supported data URL supplies
10
+ * module source that requires the real entry. Setting execArgv instead would
11
+ * replace Node's inherited-option handling and can reject otherwise valid
12
+ * process options such as --max-old-space-size. createRequire preserves the
13
+ * entry's CommonJS resolution, including literal POSIX backslashes that file
14
+ * URL imports reject as encoded separators. The worker entry must use the
15
+ * package's CommonJS source or emitted format.
16
+ *
17
+ * @param filename Native entry path, resolved from cwd when relative.
18
+ * @returns A data URL accepted by the Worker constructor; it owns no thread.
19
+ * @evidence contracts/common.md#principled-implementation Node documents data URL worker entries and inherited options; a module-source entry requires the selected CommonJS owner without applying the parent's stdin-only input type to that file's initial module resolution.
20
+ * @evidence contracts/common.md#clear-and-simple-design One URL constructor owns the bootstrap representation while callers retain worker options, thread lifetime and actual entry selection.
21
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Supported createRequire loads the actual CommonJS owner; no flags are guessed, stripped or replaced and no foreign process state is changed.
22
+ * @evidence contracts/common.md#meaningful-documentation Prose explains the stdin inheritance failure, the explicit-execArgv hazard, CommonJS entry requirements and the native path input.
23
+ * @evidence contracts/portability.md#os-neutral-implementation Native path resolution and createRequire retain filesystem path spelling, including POSIX literal backslashes; JSON string encoding and data URL encoding separate that path from JavaScript and URL syntax without routing it through ESM file-URL separator rules.
24
+ * @evidence contracts/performance.md#efficient-algorithms Native path resolution, string encoding and data URL construction each visit entry-path text; the returned URL and its module source occupy linear space in that text.
25
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work Each call represents its selected entry and retains no computed answer.
26
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources This operation returns a URL without acquiring a worker, file handle or persistent state; the caller owns the URL and thread.
27
+ * @see https://nodejs.org/api/worker_threads.html#new-workerfilename-options
28
+ * @see https://nodejs.org/api/cli.html#--input-typetype
29
+ */
30
+ export function workerModuleUrl(filename: string): URL {
31
+ const entry = JSON.stringify(path.resolve(filename));
32
+ const source = `import { createRequire } from "node:module"; createRequire(${entry})(${entry});`;
33
+ return new URL(`data:text/javascript,${encodeURIComponent(source)}`);
34
+ }
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { runBuild } from "../../compiler/internal/build/runBuild";
5
5
  import { readProjectConfig } from "../../compiler/internal/project/readProjectConfig";
6
6
  import { readEffectiveCompilerOptions } from "../../compiler/internal/readEffectiveCompilerOptions";
7
+ import { SidecarEnvironment } from "../../compiler/internal/sharedHost/SidecarEnvironment";
7
8
  import { createFilesystemPathIdentityContext } from "../../internal/pathIdentity/createFilesystemPathIdentityContext";
8
9
  import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
9
10
  import type { TtscCommonOptions } from "../../structures/internal/TtscCommonOptions";
@@ -186,6 +187,7 @@ export function buildSingleRootProject(props: {
186
187
  project,
187
188
  options.passthrough,
188
189
  options.binary,
190
+ SidecarEnvironment.merge(process.env, options.env),
189
191
  );
190
192
  const result = runBuild({
191
193
  binary: options.binary,
@@ -24,10 +24,11 @@ import type { TtscCommonOptions } from "../../structures/internal/TtscCommonOpti
24
24
  export function getCompilerVersionText(
25
25
  options: TtscCommonOptions = {},
26
26
  ): string {
27
- const tsgo = resolveTsgo(options);
27
+ const env = SidecarEnvironment.merge(process.env, options.env);
28
+ const tsgo = resolveTsgo({ ...options, env });
28
29
  const res = spawnNative(tsgo.binary, ["--version"], {
29
30
  cwd: options.cwd,
30
- env: SidecarEnvironment.merge(process.env, options.env),
31
+ env,
31
32
  });
32
33
  if (res.error || res.status !== 0) {
33
34
  throw new Error(
@@ -14,10 +14,11 @@ import { assertTtsxNoWatch } from "./assertTtsxNoWatch";
14
14
  * Compiler flags before the first entry, repeated preloads and launcher-owned
15
15
  * settings are separated once through the shared flag schema. Terminal requests
16
16
  * are recognized only before the entry; unsupported watch/build requests fail
17
- * before a check, build or program is started. These decisions inspect the supplied
18
- * argv frame; this parser does not read response-file contents or certify their
19
- * eventual native effects. Preparation inspects response frames at the selected
20
- * project's actual compiler directory before requesting its build.
17
+ * before a check, build or program is started. These decisions inspect the
18
+ * supplied argv frame; this parser does not read response-file contents or
19
+ * certify their eventual native effects. Preparation inspects response frames
20
+ * at the selected project's actual compiler directory before requesting its
21
+ * build.
21
22
  *
22
23
  * @param argv Command tokens as received from the launcher.
23
24
  * @returns A terminal request or the launcher's typed option record.
@@ -324,8 +324,12 @@ function createProjectContext(
324
324
  project,
325
325
  options.passthrough,
326
326
  options.binary,
327
+ env,
328
+ );
329
+ const privateEmitRootDir = resolveRuntimeSourceRoot(
330
+ project,
331
+ effectiveOptions,
327
332
  );
328
- const privateEmitRootDir = resolveRuntimeSourceRoot(project, effectiveOptions);
329
333
  // Resolved once: resolution costs a realpath (and, for a missing directory on
330
334
  // Windows, a case-sensitivity probe).
331
335
  const runtimeRootDir = createFilesystemPathIdentityContext({
@@ -437,6 +441,7 @@ function discoverOwningProject(
437
441
  });
438
442
  const owning = resolveOwningProjectConfig({
439
443
  binary: options.binary,
444
+ env: SidecarEnvironment.merge(process.env, options.env),
440
445
  file,
441
446
  tsconfig: nearest.path,
442
447
  });
@@ -14,15 +14,14 @@ import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscPa
14
14
  * then copies one transformed JavaScript file to this path, so project-mode
15
15
  * declaration, map, build-info, outFile, and broad outDir products are not
16
16
  * positional outputs. Explicit output mirrors cwd for contained sources and
17
- * uses the source basename for outside-layout or different-volume sources.
18
- * A supplied invocation project owns placement without
19
- * resolving the original locator again. Forwarded JSX uses the supplied native
20
- * argv frames and does not expand response-file requests here. CLI enum
21
- * normalization; an explicit reset selects the default suffix instead of
22
- * reviving the configured JSX. Placement-setting read failures use the
23
- * no-project fallback; that fallback does not certify a successful build or
24
- * valid configuration. Physical spelling is a current native observation, not a
25
- * pinned filesystem object.
17
+ * uses the source basename for outside-layout or different-volume sources. A
18
+ * supplied invocation project owns placement without resolving the original
19
+ * locator again. Forwarded JSX uses the supplied native argv frames and does
20
+ * not expand response-file requests here. CLI enum normalization; an explicit
21
+ * reset selects the default suffix instead of reviving the configured JSX.
22
+ * Placement-setting read failures use the no-project fallback; that fallback
23
+ * does not certify a successful build or valid configuration. Physical spelling
24
+ * is a current native observation, not a pinned filesystem object.
26
25
  *
27
26
  * @evidence contracts/common.md#principled-implementation CLI output wins over the selected project output; a contained cwd-relative source mirrors its layout while an outside-layout or cross-volume source uses its basename inside that explicit root. Supported extensions choose the emitted suffix, and physical root/file relation preserves configured project layout through links.
28
27
  * @evidence contracts/common.md#clear-and-simple-design Output placement delegates project settings and isolates containment, extension and forwarded-option readers; it does not materialize compiler side products.
@@ -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,
@@ -578,16 +579,30 @@ function printCacheHelp(): void {
578
579
 
579
580
  /**
580
581
  * Adapt one normalized CLI request to its selected private producer and public
581
- * copy. The existing selection owns config and original argument anchoring;
582
- * the full request retains cache, reporting and watch callbacks. Only the
583
- * private helper writes the selected public file. Success presentation follows
584
- * that completed copy and describes public emission, without private products
585
- * or a fabricated transform-site count. Watch supplies quiet true itself.
582
+ * copy. The existing selection owns config and original argument anchoring; the
583
+ * full request retains cache, reporting and watch callbacks. Only the private
584
+ * helper writes the selected public file. Success presentation follows that
585
+ * completed copy and describes public emission, without private products or a
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.