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.
- package/cmd/ttscserver/main.go +79 -3
- package/cmd/ttscserver/run_lsp_uses_native_plugin_manifest_source_test.go +96 -16
- package/driver/lsp.go +4 -0
- package/internal/lspserver/lsp_document_diagnostics_omits_cached_project_when_current_result_has_none_test.go +48 -0
- package/internal/lspserver/lsp_executable_project_input_change_requests_launcher_restart_test.go +98 -0
- package/internal/lspserver/lsp_external_watched_change_retains_program_test.go +53 -0
- package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +9 -0
- package/internal/lspserver/lsp_native_plugin_source.go +171 -55
- package/internal/lspserver/lsp_native_resident.go +110 -9
- package/internal/lspserver/lsp_plugin.go +6 -0
- package/internal/lspserver/lsp_project_context_transport_identity_test.go +43 -0
- package/internal/lspserver/lsp_project_diagnostics.go +235 -0
- package/internal/lspserver/lsp_project_diagnostics_capability_is_independent_test.go +67 -0
- package/internal/lspserver/lsp_project_diagnostics_program_input_overlap_refreshes_all_producers_test.go +78 -0
- package/internal/lspserver/lsp_project_diagnostics_refresh_accumulates_owner_freshness_test.go +65 -0
- package/internal/lspserver/lsp_project_diagnostics_refresh_keeps_each_producer_last_good_test.go +90 -0
- package/internal/lspserver/lsp_project_diagnostics_refreshes_only_input_owners_test.go +62 -0
- package/internal/lspserver/lsp_project_diagnostics_resident_unsupported_falls_back_test.go +123 -0
- package/internal/lspserver/lsp_project_diagnostics_resume_preserves_generation_test.go +52 -0
- package/internal/lspserver/lsp_project_diagnostics_translates_relative_client_project_test.go +90 -0
- package/internal/lspserver/lsp_project_input_ownership_resolves_declared_spellings_test.go +95 -0
- package/internal/lspserver/lsp_project_input_registration.go +504 -0
- package/internal/lspserver/lsp_project_input_registration_rechecks_selection_baseline_test.go +71 -0
- package/internal/lspserver/lsp_project_input_registration_reconciles_rejected_pending_request_test.go +268 -0
- package/internal/lspserver/lsp_project_input_registration_shapes_patterns_test.go +94 -0
- package/internal/lspserver/lsp_project_input_watchers_anchor_missing_paths_test.go +48 -0
- package/internal/lspserver/lsp_project_inputs.go +944 -0
- package/internal/lspserver/lsp_project_inputs_match_missing_exact_and_glob_test.go +129 -0
- package/internal/lspserver/lsp_project_inputs_reject_invalid_and_foreign_snapshots_test.go +110 -0
- package/internal/lspserver/lsp_proxy.go +591 -28
- package/internal/lspserver/lsp_reload_directories_compare_immediate_topology_test.go +188 -0
- package/internal/lspserver/lsp_reload_file_fingerprint_preserves_raw_symlink_target_test.go +47 -0
- package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +1 -1
- package/internal/lspserver/lsp_resident_invalidation_broadcasts_program_input_overlap_test.go +60 -0
- package/internal/lspserver/lsp_resident_invalidation_targets_project_input_owners_test.go +51 -0
- package/internal/lspserver/lsp_same_transport_entries_are_deduplicated_test.go +66 -0
- package/lib/compiler/internal/project/readProjectConfig.d.ts +5 -3
- package/lib/compiler/internal/project/readProjectConfig.js +46 -14
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/residentCheckProcess.d.ts +46 -0
- package/lib/compiler/internal/residentCheckProcess.js +227 -0
- package/lib/compiler/internal/residentCheckProcess.js.map +1 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.js +35 -18
- package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +64 -1
- package/lib/compiler/internal/runBuild.js +484 -7
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.js +1 -0
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/flags/parser.js +13 -17
- package/lib/flags/parser.js.map +1 -1
- package/lib/flags/schema.d.ts +10 -3
- package/lib/flags/schema.js +99 -0
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/projectInputPathIdentity.d.ts +23 -0
- package/lib/internal/projectInputPathIdentity.js +222 -0
- package/lib/internal/projectInputPathIdentity.js.map +1 -0
- package/lib/launcher/internal/runTtsc.d.ts +17 -0
- package/lib/launcher/internal/runTtsc.js +121 -88
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.d.ts +13 -1
- package/lib/launcher/internal/runTtscserver.js +415 -45
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/singleFileOutput.d.ts +16 -0
- package/lib/launcher/internal/singleFileOutput.js +103 -0
- package/lib/launcher/internal/singleFileOutput.js.map +1 -0
- package/lib/launcher/internal/watchTopology.d.ts +214 -2
- package/lib/launcher/internal/watchTopology.js +1560 -41
- package/lib/launcher/internal/watchTopology.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +1 -0
- package/lib/plugin/internal/loadProjectPlugins.js +5 -0
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscPluginCapabilities.d.ts +31 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.d.ts +29 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.js +3 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.js.map +1 -0
- package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +5 -0
- package/package.json +8 -8
- package/src/compiler/internal/project/readProjectConfig.ts +62 -8
- package/src/compiler/internal/residentCheckProcess.ts +279 -0
- package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -21
- package/src/compiler/internal/runBuild.ts +728 -12
- package/src/compiler/internal/runSingleFileEmit.ts +1 -0
- package/src/flags/parser.ts +17 -17
- package/src/flags/schema.ts +111 -3
- package/src/internal/projectInputPathIdentity.ts +273 -0
- package/src/launcher/internal/runTtsc.ts +141 -117
- package/src/launcher/internal/runTtscserver.ts +561 -54
- package/src/launcher/internal/singleFileOutput.ts +133 -0
- package/src/launcher/internal/watchTopology.ts +2008 -59
- package/src/plugin/internal/loadProjectPlugins.ts +8 -0
- package/src/structures/ITtscPluginCapabilities.ts +34 -0
- package/src/structures/internal/ITtscProjectInputSnapshot.ts +29 -0
- 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
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
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 =
|
|
789
|
-
|
|
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 = {
|