ttsc 0.21.0 → 0.23.0

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 (119) hide show
  1. package/cmd/ttscserver/main.go +79 -3
  2. package/cmd/ttscserver/run_lsp_uses_native_plugin_manifest_source_test.go +96 -16
  3. package/driver/lsp.go +4 -0
  4. package/go.mod +1 -1
  5. package/internal/lspserver/lsp_document_diagnostics_omits_cached_project_when_current_result_has_none_test.go +48 -0
  6. package/internal/lspserver/lsp_executable_project_input_change_requests_launcher_restart_test.go +98 -0
  7. package/internal/lspserver/lsp_external_watched_change_retains_program_test.go +53 -0
  8. package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +9 -0
  9. package/internal/lspserver/lsp_launcher_reload_directory_fingerprint_parity_windows_test.go +94 -0
  10. package/internal/lspserver/lsp_native_plugin_source.go +171 -55
  11. package/internal/lspserver/lsp_native_resident.go +110 -9
  12. package/internal/lspserver/lsp_plugin.go +6 -0
  13. package/internal/lspserver/lsp_project_context_transport_identity_test.go +43 -0
  14. package/internal/lspserver/lsp_project_diagnostics.go +235 -0
  15. package/internal/lspserver/lsp_project_diagnostics_capability_is_independent_test.go +67 -0
  16. package/internal/lspserver/lsp_project_diagnostics_program_input_overlap_refreshes_all_producers_test.go +78 -0
  17. package/internal/lspserver/lsp_project_diagnostics_refresh_accumulates_owner_freshness_test.go +65 -0
  18. package/internal/lspserver/lsp_project_diagnostics_refresh_keeps_each_producer_last_good_test.go +90 -0
  19. package/internal/lspserver/lsp_project_diagnostics_refreshes_only_input_owners_test.go +62 -0
  20. package/internal/lspserver/lsp_project_diagnostics_resident_unsupported_falls_back_test.go +123 -0
  21. package/internal/lspserver/lsp_project_diagnostics_resume_preserves_generation_test.go +52 -0
  22. package/internal/lspserver/lsp_project_diagnostics_translates_relative_client_project_test.go +90 -0
  23. package/internal/lspserver/lsp_project_input_glob_path_other.go +16 -0
  24. package/internal/lspserver/lsp_project_input_glob_path_windows.go +36 -0
  25. package/internal/lspserver/lsp_project_input_ownership_resolves_declared_spellings_test.go +95 -0
  26. package/internal/lspserver/lsp_project_input_path_key_other.go +17 -0
  27. package/internal/lspserver/lsp_project_input_path_key_windows.go +228 -0
  28. package/internal/lspserver/lsp_project_input_path_key_windows_test.go +460 -0
  29. package/internal/lspserver/lsp_project_input_registration.go +504 -0
  30. package/internal/lspserver/lsp_project_input_registration_rechecks_selection_baseline_test.go +71 -0
  31. package/internal/lspserver/lsp_project_input_registration_reconciles_rejected_pending_request_test.go +268 -0
  32. package/internal/lspserver/lsp_project_input_registration_shapes_patterns_test.go +94 -0
  33. package/internal/lspserver/lsp_project_input_watchers_anchor_missing_paths_test.go +48 -0
  34. package/internal/lspserver/lsp_project_inputs.go +1012 -0
  35. package/internal/lspserver/lsp_project_inputs_match_missing_exact_and_glob_test.go +129 -0
  36. package/internal/lspserver/lsp_project_inputs_reject_invalid_and_foreign_snapshots_test.go +110 -0
  37. package/internal/lspserver/lsp_proxy.go +591 -28
  38. package/internal/lspserver/lsp_reload_directories_compare_immediate_topology_test.go +188 -0
  39. package/internal/lspserver/lsp_reload_directories_separate_glob_territory_test.go +181 -0
  40. package/internal/lspserver/lsp_reload_file_fingerprint_preserves_raw_symlink_target_test.go +47 -0
  41. package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +1 -1
  42. package/internal/lspserver/lsp_resident_invalidation_broadcasts_program_input_overlap_test.go +60 -0
  43. package/internal/lspserver/lsp_resident_invalidation_targets_project_input_owners_test.go +51 -0
  44. package/internal/lspserver/lsp_same_transport_entries_are_deduplicated_test.go +66 -0
  45. package/lib/compiler/internal/project/readProjectConfig.d.ts +5 -3
  46. package/lib/compiler/internal/project/readProjectConfig.js +46 -14
  47. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  48. package/lib/compiler/internal/residentCheckProcess.d.ts +46 -0
  49. package/lib/compiler/internal/residentCheckProcess.js +227 -0
  50. package/lib/compiler/internal/residentCheckProcess.js.map +1 -0
  51. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -1
  52. package/lib/compiler/internal/resolveEmittedJavaScript.js +35 -18
  53. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  54. package/lib/compiler/internal/runBuild.d.ts +64 -1
  55. package/lib/compiler/internal/runBuild.js +484 -7
  56. package/lib/compiler/internal/runBuild.js.map +1 -1
  57. package/lib/compiler/internal/runSingleFileEmit.js +1 -0
  58. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  59. package/lib/flags/parser.js +13 -17
  60. package/lib/flags/parser.js.map +1 -1
  61. package/lib/flags/schema.d.ts +10 -3
  62. package/lib/flags/schema.js +99 -0
  63. package/lib/flags/schema.js.map +1 -1
  64. package/lib/internal/projectInputPathIdentity.d.ts +31 -0
  65. package/lib/internal/projectInputPathIdentity.js +276 -0
  66. package/lib/internal/projectInputPathIdentity.js.map +1 -0
  67. package/lib/internal/windowsDirectoryCaseSensitivity.d.ts +10 -0
  68. package/lib/internal/windowsDirectoryCaseSensitivity.js +34 -0
  69. package/lib/internal/windowsDirectoryCaseSensitivity.js.map +1 -0
  70. package/lib/launcher/internal/runTtsc.d.ts +17 -0
  71. package/lib/launcher/internal/runTtsc.js +126 -88
  72. package/lib/launcher/internal/runTtsc.js.map +1 -1
  73. package/lib/launcher/internal/runTtscserver.d.ts +13 -1
  74. package/lib/launcher/internal/runTtscserver.js +468 -47
  75. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  76. package/lib/launcher/internal/runTtsx.js +61 -7
  77. package/lib/launcher/internal/runTtsx.js.map +1 -1
  78. package/lib/launcher/internal/runtimeHooks.d.ts +6 -5
  79. package/lib/launcher/internal/runtimeHooks.js +7 -15
  80. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  81. package/lib/launcher/internal/singleFileOutput.d.ts +16 -0
  82. package/lib/launcher/internal/singleFileOutput.js +103 -0
  83. package/lib/launcher/internal/singleFileOutput.js.map +1 -0
  84. package/lib/launcher/internal/watchTopology.d.ts +288 -2
  85. package/lib/launcher/internal/watchTopology.js +1921 -54
  86. package/lib/launcher/internal/watchTopology.js.map +1 -1
  87. package/lib/plugin/internal/descriptorProcessFailure.d.ts +32 -0
  88. package/lib/plugin/internal/descriptorProcessFailure.js +73 -0
  89. package/lib/plugin/internal/descriptorProcessFailure.js.map +1 -0
  90. package/lib/plugin/internal/loadProjectPlugins.d.ts +1 -0
  91. package/lib/plugin/internal/loadProjectPlugins.js +34 -8
  92. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  93. package/lib/structures/ITtscPluginCapabilities.d.ts +31 -0
  94. package/lib/structures/internal/ITtscProjectInputSnapshot.d.ts +29 -0
  95. package/lib/structures/internal/ITtscProjectInputSnapshot.js +3 -0
  96. package/lib/structures/internal/ITtscProjectInputSnapshot.js.map +1 -0
  97. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +5 -0
  98. package/package.json +12 -8
  99. package/src/compiler/internal/project/readProjectConfig.ts +62 -8
  100. package/src/compiler/internal/residentCheckProcess.ts +279 -0
  101. package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -21
  102. package/src/compiler/internal/runBuild.ts +728 -12
  103. package/src/compiler/internal/runSingleFileEmit.ts +1 -0
  104. package/src/flags/parser.ts +17 -17
  105. package/src/flags/schema.ts +112 -3
  106. package/src/internal/projectInputPathIdentity.ts +377 -0
  107. package/src/internal/windowsDirectoryCaseSensitivity.ts +33 -0
  108. package/src/launcher/internal/runTtsc.ts +148 -117
  109. package/src/launcher/internal/runTtscserver.ts +633 -56
  110. package/src/launcher/internal/runTtsx.ts +87 -10
  111. package/src/launcher/internal/runtimeHooks.ts +16 -16
  112. package/src/launcher/internal/singleFileOutput.ts +133 -0
  113. package/src/launcher/internal/watchTopology.ts +2494 -74
  114. package/src/plugin/internal/descriptorProcessFailure.ts +100 -0
  115. package/src/plugin/internal/loadProjectPlugins.ts +49 -8
  116. package/src/structures/ITtscPluginCapabilities.ts +34 -0
  117. package/src/structures/internal/ITtscProjectInputSnapshot.ts +29 -0
  118. package/src/structures/internal/TtscSingleFileEmitOptions.ts +5 -0
  119. package/tools/shim_audit/baseline.json +0 -9
@@ -1,16 +1,28 @@
1
+ import { type ProjectInputPathIdentityContext } from "../../internal/projectInputPathIdentity";
1
2
  import type { ITtscCompilerDiagnostic } from "../../structures/ITtscCompilerDiagnostic";
3
+ import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
2
4
  import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscParsedProjectConfig";
5
+ import type { ITtscProjectInputSnapshot } from "../../structures/internal/ITtscProjectInputSnapshot";
3
6
  import type { TtscBuildOptions } from "../../structures/internal/TtscBuildOptions";
4
7
  import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
5
- type RunBuildOptions = TtscBuildOptions & {
8
+ import { type ResidentCheckRequest } from "./residentCheckProcess";
9
+ export type RunBuildOptions = TtscBuildOptions & {
6
10
  skipDiagnosticsCheck?: boolean;
7
11
  forceListEmittedFiles?: boolean;
12
+ /** Keep every compiler-owned side product inside this private directory. */
13
+ isolateOutputsTo?: string;
8
14
  /**
9
15
  * Receives selected native-plugin source roots after the project resolves.
10
16
  * The watch launcher uses these roots to invalidate a sidecar when its Go
11
17
  * implementation changes between rebuilds.
12
18
  */
13
19
  onWatchInputs?: (inputs: readonly string[]) => void;
20
+ /**
21
+ * Receives the reconciled project-rule filesystem dependency snapshot. Called
22
+ * only by watch launchers; ordinary builds do not probe the optional sidecar
23
+ * command.
24
+ */
25
+ onProjectInputs?: (inputs: ITtscProjectInputSnapshot) => void;
14
26
  /**
15
27
  * Emit an external source map from the direct tsgo build lane even when the
16
28
  * project configures none. Set by the ttsx runtime builds so a served emit
@@ -27,6 +39,57 @@ type RunBuildOptions = TtscBuildOptions & {
27
39
  * decide how to surface diagnostics. Does not throw on non-zero exit.
28
40
  */
29
41
  export declare function runBuild(options?: RunBuildOptions): TtscBuildResult;
42
+ export type ResidentCheckWatchChange = {
43
+ /** Re-resolve project, plugin, contributor, and Program topology. */
44
+ reload?: boolean;
45
+ /** Retain the sidecar and execution selection but cold-load its Program. */
46
+ invalidate?: boolean;
47
+ /** Local compiler or data paths changed since the prior cycle. */
48
+ changed?: readonly string[];
49
+ /** Subset of changed paths declared by ProjectRules as external inputs. */
50
+ external?: readonly string[];
51
+ };
52
+ /**
53
+ * Analysis-only watch coordinator.
54
+ *
55
+ * The selected project and compatible check-stage processes stay resident
56
+ * across ordinary source/data edits. A config, root-set, contributor, or plugin
57
+ * topology transition calls for a full reset before the next cycle. Emit and
58
+ * transform lanes can pass through the coordinator, but compatibility checks
59
+ * keep them on the established one-shot path without starting sidecars.
60
+ */
61
+ export declare class ResidentCheckWatchSession {
62
+ private execution;
63
+ private readonly pendingChanges;
64
+ private projectInputs;
65
+ private readonly processes;
66
+ run(options: RunBuildOptions, change?: ResidentCheckWatchChange): Promise<TtscBuildResult>;
67
+ /** Terminate every sidecar and discard the cached selection context. */
68
+ dispose(): void;
69
+ private reset;
70
+ private captureProjectInputs;
71
+ private refreshProjectInputTopology;
72
+ private runCheckPlugins;
73
+ }
74
+ export type ResidentCheckEntryPlan = {
75
+ args: string[];
76
+ entryIndex: number;
77
+ key: string | undefined;
78
+ plugin: ITtscLoadedNativePlugin;
79
+ };
80
+ /**
81
+ * Plan every configured check entry while sharing resident processes only by
82
+ * binary/name/argument identity.
83
+ */
84
+ export declare function planResidentCheckEntries(plugins: readonly ITtscLoadedNativePlugin[], createArgs: (plugin: ITtscLoadedNativePlugin) => string[]): ResidentCheckEntryPlan[];
85
+ /** Retain one complete change stream per configured resident check entry. */
86
+ export declare function bufferResidentCheckEntryRequests(pending: Map<number, ResidentCheckRequest>, checks: readonly ResidentCheckEntryPlan[], request: ResidentCheckRequest): void;
87
+ /** Consume exactly one configured entry's buffered request. */
88
+ export declare function takeResidentCheckEntryRequest(pending: Map<number, ResidentCheckRequest>, entryIndex: number): ResidentCheckRequest;
89
+ export declare function residentCheckRequest(change: ResidentCheckWatchChange, cwd: string): ResidentCheckRequest;
90
+ export declare function mergeProjectInputSnapshots(fallbackRoot: string, snapshots: readonly ITtscProjectInputSnapshot[], identities?: ProjectInputPathIdentityContext): ITtscProjectInputSnapshot;
91
+ export declare function parseProjectInputSnapshot(text: string, plugin: ITtscLoadedNativePlugin): ITtscProjectInputSnapshot;
92
+ export declare function isAbsoluteLocalProjectInputPath(location: string, platform?: NodeJS.Platform): boolean;
30
93
  /**
31
94
  * Merge two `TtscBuildResult` values into one.
32
95
  *
@@ -3,15 +3,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ResidentCheckWatchSession = void 0;
6
7
  exports.runBuild = runBuild;
8
+ exports.planResidentCheckEntries = planResidentCheckEntries;
9
+ exports.bufferResidentCheckEntryRequests = bufferResidentCheckEntryRequests;
10
+ exports.takeResidentCheckEntryRequest = takeResidentCheckEntryRequest;
11
+ exports.residentCheckRequest = residentCheckRequest;
12
+ exports.mergeProjectInputSnapshots = mergeProjectInputSnapshots;
13
+ exports.parseProjectInputSnapshot = parseProjectInputSnapshot;
14
+ exports.isAbsoluteLocalProjectInputPath = isAbsoluteLocalProjectInputPath;
7
15
  exports.appendBuildOutput = appendBuildOutput;
8
16
  exports.createProcessDiagnostic = createProcessDiagnostic;
9
17
  exports.normalizeBuildOutput = normalizeBuildOutput;
10
18
  const node_path_1 = __importDefault(require("node:path"));
11
19
  const schema_1 = require("../../flags/schema");
20
+ const projectInputPathIdentity_1 = require("../../internal/projectInputPathIdentity");
12
21
  const loadProjectPlugins_1 = require("../../plugin/internal/loadProjectPlugins");
13
22
  const createNativeProjectContextArgs_1 = require("./project/createNativeProjectContextArgs");
14
23
  const readProjectConfig_1 = require("./project/readProjectConfig");
24
+ const residentCheckProcess_1 = require("./residentCheckProcess");
15
25
  const resolveBinary_1 = require("./resolveBinary");
16
26
  const resolveTsgo_1 = require("./resolveTsgo");
17
27
  const sharedHostHelpers_1 = require("./sharedHostHelpers");
@@ -133,24 +143,276 @@ function runBuild(options = {}) {
133
143
  const result = runBuildTimed(options, timing);
134
144
  return appendTimingOutput(result, timing);
135
145
  }
146
+ /**
147
+ * Analysis-only watch coordinator.
148
+ *
149
+ * The selected project and compatible check-stage processes stay resident
150
+ * across ordinary source/data edits. A config, root-set, contributor, or plugin
151
+ * topology transition calls for a full reset before the next cycle. Emit and
152
+ * transform lanes can pass through the coordinator, but compatibility checks
153
+ * keep them on the established one-shot path without starting sidecars.
154
+ */
155
+ class ResidentCheckWatchSession {
156
+ execution;
157
+ pendingChanges = new Map();
158
+ projectInputs;
159
+ processes = new Map();
160
+ async run(options, change = {}) {
161
+ if (change.reload === true)
162
+ this.reset();
163
+ const timing = createBuildTiming(options);
164
+ const projectFree = runProjectFreeTerminalFlag(options);
165
+ if (projectFree !== null) {
166
+ this.reset();
167
+ return appendTimingOutput(projectFree, timing);
168
+ }
169
+ let execution = this.execution;
170
+ const reusedExecution = execution !== undefined;
171
+ let buildOptions;
172
+ if (execution === undefined) {
173
+ const setupStartedAt = process.hrtime.bigint();
174
+ execution = resolveExecutionContext(options);
175
+ const discoveryOptions = this.captureProjectInputs(options);
176
+ const prepared = prepareBuildExecution(discoveryOptions, timing, execution, setupStartedAt);
177
+ buildOptions = prepared.buildOptions;
178
+ if (prepared.result !== undefined) {
179
+ this.reset();
180
+ return appendTimingOutput(prepared.result, timing);
181
+ }
182
+ if (!residentCheckExecutionIsCompatible(buildOptions, execution)) {
183
+ this.reset();
184
+ return appendTimingOutput(runPreparedBuild(options, timing, execution, buildOptions), timing);
185
+ }
186
+ this.execution = execution;
187
+ }
188
+ else {
189
+ buildOptions = applyProjectNoEmit(options, execution);
190
+ }
191
+ if (reusedExecution &&
192
+ this.refreshProjectInputTopology(options, execution)) {
193
+ this.reset();
194
+ return this.run(options);
195
+ }
196
+ const checked = await this.runCheckPlugins(buildOptions, execution, timing, change);
197
+ let result;
198
+ if (checked.status !== 0) {
199
+ result = appendTypeScriptDiagnosticsAfterPluginFailure(checked, buildOptions, execution);
200
+ }
201
+ else if (checkPluginsReportTypeScriptDiagnostics(execution.nativePlugins)) {
202
+ result = checked;
203
+ }
204
+ else {
205
+ result = appendBuildOutput(checked, runTsgo(execution, ["--noEmit"], buildOptions));
206
+ }
207
+ return appendTimingOutput(result, timing);
208
+ }
209
+ /** Terminate every sidecar and discard the cached selection context. */
210
+ dispose() {
211
+ this.reset();
212
+ }
213
+ reset() {
214
+ for (const process of this.processes.values())
215
+ process.dispose();
216
+ this.processes.clear();
217
+ this.pendingChanges.clear();
218
+ this.execution = undefined;
219
+ this.projectInputs = undefined;
220
+ }
221
+ captureProjectInputs(options) {
222
+ const onProjectInputs = options.onProjectInputs;
223
+ if (onProjectInputs === undefined)
224
+ return options;
225
+ return {
226
+ ...options,
227
+ onProjectInputs: (snapshot) => {
228
+ this.projectInputs = snapshot;
229
+ onProjectInputs(snapshot);
230
+ },
231
+ };
232
+ }
233
+ refreshProjectInputTopology(options, execution) {
234
+ if (options.onProjectInputs === undefined)
235
+ return false;
236
+ const next = discoverNativeProjectInputs(options, execution);
237
+ const changed = this.projectInputs !== undefined &&
238
+ !projectInputSnapshotsEqual(this.projectInputs, next);
239
+ this.projectInputs = next;
240
+ options.onProjectInputs(next);
241
+ return changed;
242
+ }
243
+ async runCheckPlugins(options, execution, timing, change) {
244
+ let out = {
245
+ diagnostics: [],
246
+ status: 0,
247
+ stderr: "",
248
+ stdout: "",
249
+ };
250
+ const checks = planResidentCheckEntries(execution.nativePlugins, (plugin) => createNativeCheckArgs(execution, options, plugin));
251
+ const request = residentCheckRequest(change, execution.projectRoot);
252
+ // Buffer the cycle for every resident plugin before running any of them.
253
+ // An earlier plugin may fail and short-circuit diagnostics, but a later
254
+ // sidecar must still receive every filesystem transition when it resumes.
255
+ bufferResidentCheckEntryRequests(this.pendingChanges, checks, request);
256
+ for (const { args, entryIndex, key, plugin } of checks) {
257
+ let result;
258
+ if (key === undefined) {
259
+ result = runNativePluginCommand(plugin, args, options, execution, "ttsc.check", timing, `ttsc check plugin ${plugin.name} time`);
260
+ }
261
+ else {
262
+ const startedAt = process.hrtime.bigint();
263
+ let resident = this.processes.get(key);
264
+ if (resident === undefined) {
265
+ resident = new residentCheckProcess_1.ResidentCheckProcess({
266
+ args: ["check-serve", ...args.slice(1)],
267
+ binary: plugin.binary,
268
+ cwd: execution.projectRoot,
269
+ env: nativePluginEnv(options.env, execution, plugin),
270
+ });
271
+ this.processes.set(key, resident);
272
+ }
273
+ try {
274
+ const reply = await resident.request(takeResidentCheckEntryRequest(this.pendingChanges, entryIndex));
275
+ result = normalizeBuildOutput({
276
+ status: reply.status,
277
+ stderr: reply.stderr,
278
+ stdout: reply.stdout,
279
+ }, execution.projectRoot);
280
+ }
281
+ catch {
282
+ resident.dispose();
283
+ this.processes.delete(key);
284
+ // The one-shot fallback observes the complete current filesystem,
285
+ // and a later sidecar starts cold, so neither needs old deltas.
286
+ // A capability-aware host may still disappear or violate framing.
287
+ // Preserve correctness by running the established one-shot command
288
+ // for this cycle; the next cycle gets one clean respawn attempt.
289
+ result = runNativePluginCommand(plugin, args, options, execution, "ttsc.check", { ...timing, enabled: false }, "");
290
+ }
291
+ recordTiming(timing, `ttsc check plugin ${plugin.name} time`, startedAt);
292
+ }
293
+ out = appendBuildOutput(out, result);
294
+ if (result.status !== 0)
295
+ return out;
296
+ }
297
+ return out;
298
+ }
299
+ }
300
+ exports.ResidentCheckWatchSession = ResidentCheckWatchSession;
301
+ function projectInputSnapshotsEqual(left, right) {
302
+ const leftReloadFiles = left.reloadFiles ?? [];
303
+ const rightReloadFiles = right.reloadFiles ?? [];
304
+ const leftReloadDirectories = left.reloadDirectories ?? [];
305
+ const rightReloadDirectories = right.reloadDirectories ?? [];
306
+ return (left.root === right.root &&
307
+ left.files.length === right.files.length &&
308
+ left.globs.length === right.globs.length &&
309
+ leftReloadDirectories.length === rightReloadDirectories.length &&
310
+ leftReloadFiles.length === rightReloadFiles.length &&
311
+ left.files.every((value, index) => value === right.files[index]) &&
312
+ left.globs.every((value, index) => value === right.globs[index]) &&
313
+ leftReloadDirectories.every((value, index) => value === rightReloadDirectories[index]) &&
314
+ leftReloadFiles.every((value, index) => value === rightReloadFiles[index]));
315
+ }
316
+ function residentCheckExecutionIsCompatible(options, execution) {
317
+ return (options.emit === false &&
318
+ options.fix !== true &&
319
+ options.format !== true &&
320
+ forwardsTerminalTsgoFlag(options) === false &&
321
+ execution.nativePlugins.every((plugin) => plugin.stage === "check"));
322
+ }
323
+ function residentCheckProcessKey(plugin, args) {
324
+ return `${plugin.binary}\0${plugin.name}\0${JSON.stringify(args)}`;
325
+ }
326
+ /**
327
+ * Plan every configured check entry while sharing resident processes only by
328
+ * binary/name/argument identity.
329
+ */
330
+ function planResidentCheckEntries(plugins, createArgs) {
331
+ return plugins
332
+ .filter((candidate) => candidate.stage === "check")
333
+ .map((plugin, entryIndex) => {
334
+ const args = createArgs(plugin);
335
+ return {
336
+ args,
337
+ entryIndex,
338
+ key: plugin.capabilities?.residentCheck === true
339
+ ? residentCheckProcessKey(plugin, args)
340
+ : undefined,
341
+ plugin,
342
+ };
343
+ });
344
+ }
345
+ /** Retain one complete change stream per configured resident check entry. */
346
+ function bufferResidentCheckEntryRequests(pending, checks, request) {
347
+ for (const check of checks) {
348
+ if (check.key === undefined)
349
+ continue;
350
+ pending.set(check.entryIndex, mergeResidentCheckRequests(pending.get(check.entryIndex), request));
351
+ }
352
+ }
353
+ /** Consume exactly one configured entry's buffered request. */
354
+ function takeResidentCheckEntryRequest(pending, entryIndex) {
355
+ const request = pending.get(entryIndex);
356
+ if (request === undefined) {
357
+ throw new Error(`ttsc: resident check entry ${String(entryIndex)} has no buffered request`);
358
+ }
359
+ pending.delete(entryIndex);
360
+ return request;
361
+ }
362
+ function residentCheckRequest(change, cwd) {
363
+ const normalize = (values) => [...new Set(values?.map((value) => node_path_1.default.resolve(cwd, value)) ?? [])].sort();
364
+ const changed = normalize(change.changed);
365
+ const external = normalize(change.external);
366
+ return {
367
+ ...(change.invalidate === true ? { invalidate: true } : {}),
368
+ ...(changed.length === 0 ? {} : { changed }),
369
+ ...(external.length === 0 ? {} : { external }),
370
+ };
371
+ }
372
+ function mergeResidentCheckRequests(previous, current) {
373
+ const merge = (left, right) => [...new Set([...(left ?? []), ...(right ?? [])])].sort();
374
+ const changed = merge(previous?.changed, current.changed);
375
+ const external = merge(previous?.external, current.external);
376
+ return {
377
+ ...(changed.length === 0 ? {} : { changed }),
378
+ ...(external.length === 0 ? {} : { external }),
379
+ ...(previous?.invalidate === true || current.invalidate === true
380
+ ? { invalidate: true }
381
+ : {}),
382
+ };
383
+ }
136
384
  function runBuildTimed(options, timing) {
137
385
  const projectFree = runProjectFreeTerminalFlag(options);
138
386
  if (projectFree !== null)
139
387
  return projectFree;
140
388
  const setupStartedAt = process.hrtime.bigint();
141
389
  const execution = resolveExecutionContext(options);
142
- options.onWatchInputs?.(execution.nativePlugins.flatMap((plugin) => [
143
- plugin.source,
144
- ...(plugin.contributors?.map((contributor) => contributor.source) ?? []),
145
- ]));
390
+ return runBuildWithExecution(options, timing, execution, setupStartedAt);
391
+ }
392
+ function runBuildWithExecution(options, timing, execution, setupStartedAt) {
393
+ const prepared = prepareBuildExecution(options, timing, execution, setupStartedAt);
394
+ if (prepared.result !== undefined)
395
+ return prepared.result;
396
+ return runPreparedBuild(options, timing, execution, prepared.buildOptions);
397
+ }
398
+ function prepareBuildExecution(options, timing, execution, setupStartedAt) {
146
399
  if (execution.nativePlugins.length > 0 ||
147
400
  execution.pluginSetupFailure !== undefined) {
148
401
  recordTiming(timing, "ttsc plugin setup time", setupStartedAt);
149
402
  }
150
403
  const buildOptions = applyProjectNoEmit(options, execution);
151
404
  if (execution.pluginSetupFailure !== undefined) {
152
- return appendTypeScriptDiagnosticsAfterPluginFailure(execution.pluginSetupFailure, buildOptions, execution);
405
+ return {
406
+ buildOptions,
407
+ result: appendTypeScriptDiagnosticsAfterPluginFailure(execution.pluginSetupFailure, buildOptions, execution),
408
+ };
153
409
  }
410
+ if (options.onProjectInputs !== undefined) {
411
+ options.onProjectInputs(discoverNativeProjectInputs(options, execution));
412
+ }
413
+ return { buildOptions };
414
+ }
415
+ function runPreparedBuild(options, timing, execution, buildOptions) {
154
416
  if (execution.nativePlugins.length > 0) {
155
417
  const compilers = execution.nativePlugins.filter((plugin) => plugin.stage === "transform");
156
418
  const checked = runNativeCheckPlugins(buildOptions, execution, timing);
@@ -612,6 +874,7 @@ function createTsgoBuildArgs(execution, options, flags) {
612
874
  args.push(...createTsgoDiagnosticArgs(options));
613
875
  args.push(...createTsgoThreadingArgs(options));
614
876
  args.push(...(options.passthrough ?? []));
877
+ args.push(...isolatedTsgoOutputArgs(options));
615
878
  if (flags.noEmitOnError === true) {
616
879
  args.push("--noEmitOnError");
617
880
  }
@@ -710,6 +973,18 @@ function createNativeCheckArgs(execution, options, plugin) {
710
973
  args.push(...createNativeTsgoArgs(options));
711
974
  return args;
712
975
  }
976
+ function createNativeProjectInputsArgs(execution, plugin) {
977
+ const args = [
978
+ "project-inputs",
979
+ "--tsconfig=" + execution.tsconfig,
980
+ "--plugins-json=" + serializeNativePlugins(execution.nativePlugins),
981
+ "--cwd=" + execution.projectRoot,
982
+ ];
983
+ if (plugin.capabilities?.projectContextArgs === true) {
984
+ args.push(...(0, createNativeProjectContextArgs_1.createNativeProjectContextArgs)(execution.project, execution.pluginConfigDir));
985
+ }
986
+ return args;
987
+ }
713
988
  // `--singleThreaded` / `--checkers` are forwarded to native check-stage hosts
714
989
  // only when the host is one ttsc itself owns (currently `@ttsc/lint`). #113
715
990
  // forwarded both flags as bare CLI tokens to every native sidecar, then
@@ -785,12 +1060,33 @@ function transformHostTimingLabel(plugins) {
785
1060
  * single token so the sidecars' unknown-flag filters keep it intact.
786
1061
  */
787
1062
  function createNativeTsgoArgs(options) {
788
- const passthrough = nativeTsgoPassthroughArgs(options);
789
- if (passthrough === undefined || passthrough.length === 0) {
1063
+ const passthrough = [
1064
+ ...(nativeTsgoPassthroughArgs(options) ?? []),
1065
+ ...isolatedTsgoOutputArgs(options),
1066
+ ];
1067
+ if (passthrough.length === 0) {
790
1068
  return [];
791
1069
  }
792
1070
  return ["--tsgo-args=" + JSON.stringify(passthrough)];
793
1071
  }
1072
+ function isolatedTsgoOutputArgs(options) {
1073
+ const target = "isolateOutputsTo" in options &&
1074
+ typeof options.isolateOutputsTo === "string"
1075
+ ? node_path_1.default.resolve(options.isolateOutputsTo)
1076
+ : undefined;
1077
+ if (target === undefined)
1078
+ return [];
1079
+ return [
1080
+ "--outFile",
1081
+ "null",
1082
+ "--declarationDir",
1083
+ "null",
1084
+ "--tsBuildInfoFile",
1085
+ "null",
1086
+ "--outDir",
1087
+ target,
1088
+ ];
1089
+ }
794
1090
  function nativeTsgoPassthroughArgs(options) {
795
1091
  const passthrough = options.passthrough;
796
1092
  if (passthrough === undefined)
@@ -862,6 +1158,183 @@ function runNativeCheckPlugins(options, execution, timing) {
862
1158
  }
863
1159
  return out;
864
1160
  }
1161
+ function discoverNativeProjectInputs(options, execution) {
1162
+ const snapshots = [];
1163
+ for (const plugin of execution.nativePlugins.filter((candidate) => candidate.stage === "check" &&
1164
+ candidate.capabilities?.projectInputs === true)) {
1165
+ const res = (0, spawnNative_1.spawnNative)(plugin.binary, createNativeProjectInputsArgs(execution, plugin), {
1166
+ cwd: execution.projectRoot,
1167
+ env: nativePluginEnv(options.env, execution, plugin),
1168
+ encoding: "utf8",
1169
+ });
1170
+ if (res.error) {
1171
+ throw new Error(`ttsc.project-inputs: failed to spawn ${plugin.binary}: ${res.error.message}`);
1172
+ }
1173
+ const stdout = (0, spawnNative_1.outputText)(res.stdout).trim();
1174
+ if (res.status !== 0) {
1175
+ const detail = (0, spawnNative_1.outputText)(res.stderr).trim() || stdout;
1176
+ throw new Error(`ttsc.project-inputs: ${plugin.name ?? plugin.binary} failed${detail ? `: ${detail}` : ""}`);
1177
+ }
1178
+ const snapshot = parseProjectInputSnapshot(stdout, plugin);
1179
+ snapshots.push(snapshot);
1180
+ }
1181
+ return mergeProjectInputSnapshots(execution.projectRoot, snapshots);
1182
+ }
1183
+ function arraysEqual(left, right) {
1184
+ return (left.length === right.length && left.every((item, i) => item === right[i]));
1185
+ }
1186
+ /**
1187
+ * Keep every declared spelling of one identity, ordered without regard to which
1188
+ * producer ran first.
1189
+ *
1190
+ * Two contributors can name the same file through different aliases, and each
1191
+ * alias is a path a watcher has to observe: keeping only one of them would
1192
+ * discard the link whose retarget the other spelling exists to catch. Insertion
1193
+ * order is not canonical, so the caller sorts the flattened result.
1194
+ */
1195
+ function retainDeclaredSpelling(target, key, declared) {
1196
+ const previous = target.get(key);
1197
+ if (previous === undefined) {
1198
+ target.set(key, [declared]);
1199
+ return;
1200
+ }
1201
+ if (previous.includes(declared))
1202
+ return;
1203
+ previous.push(declared);
1204
+ }
1205
+ function mergeProjectInputSnapshots(fallbackRoot, snapshots, identities = (0, projectInputPathIdentity_1.createProjectInputPathIdentityContext)()) {
1206
+ const files = new Map();
1207
+ const globs = new Map();
1208
+ const reloadDirectories = new Map();
1209
+ const reloadFiles = new Map();
1210
+ // Deduplicated on the same identity keys, but holding what the contributors
1211
+ // wrote. Normalization resolves a declaration through its symlinks, which is
1212
+ // right for every comparison and wrong for the watcher that has to observe
1213
+ // the link itself being retargeted.
1214
+ const declaredFiles = new Map();
1215
+ const declaredGlobs = new Map();
1216
+ const declaredReloadDirectories = new Map();
1217
+ const declaredReloadFiles = new Map();
1218
+ const rootIdentity = identities.resolve(fallbackRoot);
1219
+ for (const snapshot of snapshots) {
1220
+ const candidateRoot = identities.resolve(snapshot.root);
1221
+ if (rootIdentity.key !== candidateRoot.key) {
1222
+ throw new Error(`ttsc.project-inputs: plugin root ${candidateRoot.path} differs from the selected project root ${rootIdentity.path}`);
1223
+ }
1224
+ for (const file of snapshot.files) {
1225
+ const identity = identities.resolve(file);
1226
+ files.set(identity.key, identity.path);
1227
+ retainDeclaredSpelling(declaredFiles, identity.key, node_path_1.default.resolve(file));
1228
+ }
1229
+ for (const glob of snapshot.globs) {
1230
+ const identity = identities.resolve(glob);
1231
+ globs.set(identity.key, identity.path.split(node_path_1.default.sep).join("/"));
1232
+ retainDeclaredSpelling(declaredGlobs, identity.key, node_path_1.default.resolve(glob).split(node_path_1.default.sep).join("/"));
1233
+ }
1234
+ for (const reloadFile of snapshot.reloadFiles ?? []) {
1235
+ const identity = identities.resolve(reloadFile);
1236
+ reloadFiles.set(identity.key, identity.path);
1237
+ retainDeclaredSpelling(declaredReloadFiles, identity.key, node_path_1.default.resolve(reloadFile));
1238
+ }
1239
+ for (const reloadDirectory of snapshot.reloadDirectories ?? []) {
1240
+ const identity = identities.resolve(reloadDirectory);
1241
+ reloadDirectories.set(identity.key, identity.path);
1242
+ retainDeclaredSpelling(declaredReloadDirectories, identity.key, node_path_1.default.resolve(reloadDirectory));
1243
+ }
1244
+ }
1245
+ const normalized = {
1246
+ root: rootIdentity.path,
1247
+ files: [...files.values()].sort(),
1248
+ globs: [...globs.values()].sort(),
1249
+ reloadDirectories: [...reloadDirectories.values()].sort(),
1250
+ reloadFiles: [...reloadFiles.values()].sort(),
1251
+ };
1252
+ const declared = {
1253
+ files: [...declaredFiles.values()].flat().sort(),
1254
+ globs: [...declaredGlobs.values()].flat().sort(),
1255
+ reloadDirectories: [...declaredReloadDirectories.values()].flat().sort(),
1256
+ reloadFiles: [...declaredReloadFiles.values()].flat().sort(),
1257
+ };
1258
+ // Carried only when it says something the normalized arrays do not. A tree
1259
+ // with no alias on any declaration produces the same four lists, and a
1260
+ // consumer that has to scan both would pay twice for one population.
1261
+ return arraysEqual(declared.files, normalized.files) &&
1262
+ arraysEqual(declared.globs, normalized.globs) &&
1263
+ arraysEqual(declared.reloadDirectories, normalized.reloadDirectories) &&
1264
+ arraysEqual(declared.reloadFiles, normalized.reloadFiles)
1265
+ ? normalized
1266
+ : { ...normalized, declared };
1267
+ }
1268
+ function parseProjectInputSnapshot(text, plugin) {
1269
+ let value;
1270
+ try {
1271
+ value = JSON.parse(text);
1272
+ }
1273
+ catch (error) {
1274
+ throw new Error(`ttsc.project-inputs: ${plugin.name ?? plugin.binary} returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
1275
+ }
1276
+ if (typeof value !== "object" ||
1277
+ value === null ||
1278
+ typeof value.root !== "string" ||
1279
+ !isStringArray(value.files) ||
1280
+ !isStringArray(value.globs) ||
1281
+ (value.reloadDirectories !==
1282
+ undefined &&
1283
+ !isStringArray(value.reloadDirectories)) ||
1284
+ (value.reloadFiles !== undefined &&
1285
+ !isStringArray(value.reloadFiles))) {
1286
+ throw new Error(`ttsc.project-inputs: ${plugin.name ?? plugin.binary} returned an invalid snapshot`);
1287
+ }
1288
+ const snapshot = value;
1289
+ const invalid = [
1290
+ ["root", snapshot.root],
1291
+ ...snapshot.files.map((file) => ["file", file]),
1292
+ ...snapshot.globs.map((glob) => ["glob", glob]),
1293
+ ...(snapshot.reloadDirectories ?? []).map((directory) => ["reload directory", directory]),
1294
+ ...(snapshot.reloadFiles ?? []).map((file) => ["reload file", file]),
1295
+ ].find(([, location]) => location.length === 0 || !isAbsoluteLocalProjectInputPath(location));
1296
+ if (invalid !== undefined) {
1297
+ throw new Error(`ttsc.project-inputs: ${plugin.name ?? plugin.binary} returned an invalid snapshot: ${invalid[0]} ${JSON.stringify(invalid[1])} is not an absolute local path`);
1298
+ }
1299
+ return {
1300
+ ...snapshot,
1301
+ reloadDirectories: snapshot.reloadDirectories ?? [],
1302
+ reloadFiles: snapshot.reloadFiles ?? [],
1303
+ };
1304
+ }
1305
+ function isAbsoluteLocalProjectInputPath(location, platform = process.platform) {
1306
+ if (location.includes("\0"))
1307
+ return false;
1308
+ if (platform !== "win32")
1309
+ return node_path_1.default.posix.isAbsolute(location);
1310
+ const normalized = location.replaceAll("/", "\\");
1311
+ if (/^[A-Za-z]:\\/.test(normalized))
1312
+ return true;
1313
+ if (normalized.startsWith("\\\\?\\")) {
1314
+ const extended = normalized.slice(4);
1315
+ if (/^[A-Za-z]:\\/.test(extended))
1316
+ return true;
1317
+ if (extended.toLowerCase().startsWith("unc\\")) {
1318
+ return isWindowsUncProjectInputPath(`\\\\${extended.slice(4)}`);
1319
+ }
1320
+ return false;
1321
+ }
1322
+ if (normalized.startsWith("\\\\.\\"))
1323
+ return false;
1324
+ return isWindowsUncProjectInputPath(normalized);
1325
+ }
1326
+ function isWindowsUncProjectInputPath(location) {
1327
+ const matched = /^\\\\([^\\]+)\\([^\\]+)(?:\\|$)/.exec(location);
1328
+ return (matched !== null &&
1329
+ isWindowsUncVolumeSegment(matched[1]) &&
1330
+ isWindowsUncVolumeSegment(matched[2]));
1331
+ }
1332
+ function isWindowsUncVolumeSegment(segment) {
1333
+ return segment !== "." && segment !== ".." && !/[\0<>:"/\\|?*]/.test(segment);
1334
+ }
1335
+ function isStringArray(value) {
1336
+ return (Array.isArray(value) && value.every((item) => typeof item === "string"));
1337
+ }
865
1338
  /**
866
1339
  * Spawn a single native plugin binary with the given args and return the
867
1340
  * normalized build result. `label` is used in the thrown error message so the
@@ -943,11 +1416,15 @@ function resolveExecutionContext(options) {
943
1416
  cwd,
944
1417
  entries: options.plugins,
945
1418
  env: { ...process.env, ...options.env },
1419
+ onWatchInputs: options.onWatchInputs,
946
1420
  pluginConfigDir: options.pluginConfigDir,
947
1421
  projectRoot,
948
1422
  tsconfig,
949
1423
  }).nativePlugins;
950
1424
  }
1425
+ else {
1426
+ options.onWatchInputs?.([]);
1427
+ }
951
1428
  }
952
1429
  catch (error) {
953
1430
  pluginSetupFailure = {