ttsc 0.20.1 → 0.22.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 (95) 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/internal/lspserver/lsp_document_diagnostics_omits_cached_project_when_current_result_has_none_test.go +48 -0
  5. package/internal/lspserver/lsp_executable_project_input_change_requests_launcher_restart_test.go +98 -0
  6. package/internal/lspserver/lsp_external_watched_change_retains_program_test.go +53 -0
  7. package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +9 -0
  8. package/internal/lspserver/lsp_native_plugin_source.go +171 -55
  9. package/internal/lspserver/lsp_native_resident.go +110 -9
  10. package/internal/lspserver/lsp_plugin.go +6 -0
  11. package/internal/lspserver/lsp_project_context_transport_identity_test.go +43 -0
  12. package/internal/lspserver/lsp_project_diagnostics.go +235 -0
  13. package/internal/lspserver/lsp_project_diagnostics_capability_is_independent_test.go +67 -0
  14. package/internal/lspserver/lsp_project_diagnostics_program_input_overlap_refreshes_all_producers_test.go +78 -0
  15. package/internal/lspserver/lsp_project_diagnostics_refresh_accumulates_owner_freshness_test.go +65 -0
  16. package/internal/lspserver/lsp_project_diagnostics_refresh_keeps_each_producer_last_good_test.go +90 -0
  17. package/internal/lspserver/lsp_project_diagnostics_refreshes_only_input_owners_test.go +62 -0
  18. package/internal/lspserver/lsp_project_diagnostics_resident_unsupported_falls_back_test.go +123 -0
  19. package/internal/lspserver/lsp_project_diagnostics_resume_preserves_generation_test.go +52 -0
  20. package/internal/lspserver/lsp_project_diagnostics_translates_relative_client_project_test.go +90 -0
  21. package/internal/lspserver/lsp_project_input_ownership_resolves_declared_spellings_test.go +95 -0
  22. package/internal/lspserver/lsp_project_input_registration.go +504 -0
  23. package/internal/lspserver/lsp_project_input_registration_rechecks_selection_baseline_test.go +71 -0
  24. package/internal/lspserver/lsp_project_input_registration_reconciles_rejected_pending_request_test.go +268 -0
  25. package/internal/lspserver/lsp_project_input_registration_shapes_patterns_test.go +94 -0
  26. package/internal/lspserver/lsp_project_input_watchers_anchor_missing_paths_test.go +48 -0
  27. package/internal/lspserver/lsp_project_inputs.go +944 -0
  28. package/internal/lspserver/lsp_project_inputs_match_missing_exact_and_glob_test.go +129 -0
  29. package/internal/lspserver/lsp_project_inputs_reject_invalid_and_foreign_snapshots_test.go +110 -0
  30. package/internal/lspserver/lsp_proxy.go +591 -28
  31. package/internal/lspserver/lsp_reload_directories_compare_immediate_topology_test.go +188 -0
  32. package/internal/lspserver/lsp_reload_file_fingerprint_preserves_raw_symlink_target_test.go +47 -0
  33. package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +1 -1
  34. package/internal/lspserver/lsp_resident_invalidation_broadcasts_program_input_overlap_test.go +60 -0
  35. package/internal/lspserver/lsp_resident_invalidation_targets_project_input_owners_test.go +51 -0
  36. package/internal/lspserver/lsp_same_transport_entries_are_deduplicated_test.go +66 -0
  37. package/lib/compiler/internal/project/readProjectConfig.d.ts +5 -3
  38. package/lib/compiler/internal/project/readProjectConfig.js +46 -14
  39. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  40. package/lib/compiler/internal/residentCheckProcess.d.ts +46 -0
  41. package/lib/compiler/internal/residentCheckProcess.js +227 -0
  42. package/lib/compiler/internal/residentCheckProcess.js.map +1 -0
  43. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -1
  44. package/lib/compiler/internal/resolveEmittedJavaScript.js +35 -18
  45. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  46. package/lib/compiler/internal/runBuild.d.ts +64 -1
  47. package/lib/compiler/internal/runBuild.js +484 -7
  48. package/lib/compiler/internal/runBuild.js.map +1 -1
  49. package/lib/compiler/internal/runSingleFileEmit.js +1 -0
  50. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  51. package/lib/flags/parser.js +13 -17
  52. package/lib/flags/parser.js.map +1 -1
  53. package/lib/flags/schema.d.ts +10 -3
  54. package/lib/flags/schema.js +99 -0
  55. package/lib/flags/schema.js.map +1 -1
  56. package/lib/internal/projectInputPathIdentity.d.ts +23 -0
  57. package/lib/internal/projectInputPathIdentity.js +222 -0
  58. package/lib/internal/projectInputPathIdentity.js.map +1 -0
  59. package/lib/launcher/internal/runTtsc.d.ts +17 -0
  60. package/lib/launcher/internal/runTtsc.js +121 -88
  61. package/lib/launcher/internal/runTtsc.js.map +1 -1
  62. package/lib/launcher/internal/runTtscserver.d.ts +13 -1
  63. package/lib/launcher/internal/runTtscserver.js +415 -45
  64. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  65. package/lib/launcher/internal/singleFileOutput.d.ts +16 -0
  66. package/lib/launcher/internal/singleFileOutput.js +103 -0
  67. package/lib/launcher/internal/singleFileOutput.js.map +1 -0
  68. package/lib/launcher/internal/watchTopology.d.ts +214 -2
  69. package/lib/launcher/internal/watchTopology.js +1560 -41
  70. package/lib/launcher/internal/watchTopology.js.map +1 -1
  71. package/lib/plugin/internal/loadProjectPlugins.d.ts +1 -0
  72. package/lib/plugin/internal/loadProjectPlugins.js +5 -0
  73. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  74. package/lib/structures/ITtscPluginCapabilities.d.ts +31 -0
  75. package/lib/structures/internal/ITtscProjectInputSnapshot.d.ts +29 -0
  76. package/lib/structures/internal/ITtscProjectInputSnapshot.js +3 -0
  77. package/lib/structures/internal/ITtscProjectInputSnapshot.js.map +1 -0
  78. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +5 -0
  79. package/package.json +8 -8
  80. package/src/compiler/internal/project/readProjectConfig.ts +62 -8
  81. package/src/compiler/internal/residentCheckProcess.ts +279 -0
  82. package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -21
  83. package/src/compiler/internal/runBuild.ts +728 -12
  84. package/src/compiler/internal/runSingleFileEmit.ts +1 -0
  85. package/src/flags/parser.ts +17 -17
  86. package/src/flags/schema.ts +111 -3
  87. package/src/internal/projectInputPathIdentity.ts +273 -0
  88. package/src/launcher/internal/runTtsc.ts +141 -117
  89. package/src/launcher/internal/runTtscserver.ts +561 -54
  90. package/src/launcher/internal/singleFileOutput.ts +133 -0
  91. package/src/launcher/internal/watchTopology.ts +2008 -59
  92. package/src/plugin/internal/loadProjectPlugins.ts +8 -0
  93. package/src/structures/ITtscPluginCapabilities.ts +34 -0
  94. package/src/structures/internal/ITtscProjectInputSnapshot.ts +29 -0
  95. package/src/structures/internal/TtscSingleFileEmitOptions.ts +5 -0
@@ -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 = {