ttsc 0.22.0 → 0.24.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/ttscgraph/serve.go +264 -48
- package/cmd/ttscgraph/serve_early_error_encode_failure_is_observable_test.go +40 -0
- package/cmd/ttscgraph/serve_graph_snapshot_version_matches_typescript_client_test.go +32 -0
- package/cmd/ttscgraph/serve_phase_trace_accounts_for_load_failure_test.go +45 -0
- package/cmd/ttscgraph/serve_phase_trace_is_opt_in_and_payload_free_test.go +52 -0
- package/cmd/ttscgraph/serve_protocol_negotiates_incremental_shards_test.go +44 -0
- package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +2 -2
- package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +46 -20
- package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +9 -9
- package/cmd/ttscgraph/serve_shards.go +1390 -0
- package/cmd/ttscgraph/serve_shards_abandoned_generation_converges_on_retry_test.go +92 -0
- package/cmd/ttscgraph/serve_shards_delete_removed_source_test.go +57 -0
- package/cmd/ttscgraph/serve_shards_external_endpoints_test.go +53 -0
- package/cmd/ttscgraph/serve_shards_keep_source_distributed_dependencies_at_the_boundary_test.go +108 -0
- package/cmd/ttscgraph/serve_shards_match_full_dump_oracle_test.go +88 -0
- package/cmd/ttscgraph/serve_shards_merge_pending_and_new_edits_test.go +50 -0
- package/cmd/ttscgraph/serve_shards_prune_unreferenced_external_endpoints_test.go +65 -0
- package/cmd/ttscgraph/serve_shards_publish_created_source_test.go +92 -0
- package/cmd/ttscgraph/serve_shards_rebuild_declaration_edit_test.go +43 -0
- package/cmd/ttscgraph/serve_shards_refresh_cross_file_implementation_test.go +63 -0
- package/cmd/ttscgraph/serve_shards_refresh_dependent_diagnostics_test.go +80 -0
- package/cmd/ttscgraph/serve_shards_rename_moves_source_identity_test.go +95 -0
- package/cmd/ttscgraph/serve_shards_retry_preserves_committed_generation_test.go +48 -0
- package/cmd/ttscgraph/serve_shards_reuse_unaffected_sources_test.go +121 -0
- package/driver/declaration_shape.go +85 -0
- package/driver/program.go +39 -10
- package/go.mod +1 -1
- package/internal/graph/build.go +62 -10
- package/internal/graph/build_files_reuses_committed_cross_file_endpoints_test.go +87 -0
- package/internal/graph/build_keeps_source_distributed_dependencies_at_the_external_boundary_test.go +74 -0
- package/internal/graph/dump.go +84 -43
- package/internal/graph/dump_path_mapper_caches_alias_resolution_test.go +29 -0
- package/internal/graph/dump_paths.go +134 -11
- package/internal/graph/dump_paths_canonicalize_windows_short_root_test.go +39 -0
- package/internal/graph/dump_rejects_provenance_only_path_errors_test.go +32 -0
- package/internal/graph/dump_serializes_the_full_graph_test.go +2 -2
- package/internal/graph/dump_symlink_project_publishes_matching_base_test.go +82 -0
- package/internal/graph/edges.go +58 -16
- package/internal/graph/exports.go +9 -2
- package/internal/graph/graph.go +47 -7
- package/internal/graph/graph_kind_contracts_match_their_producers_test.go +121 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +1 -1
- package/internal/graph/provenance.go +11 -1
- package/internal/graph/resolve.go +1 -4
- package/internal/graph/source.go +23 -0
- package/internal/graph/wire_node_ids_share_alias_resolution_test.go +29 -0
- package/internal/lspserver/lsp_launcher_reload_directory_fingerprint_parity_windows_test.go +94 -0
- package/internal/lspserver/lsp_project_input_glob_path_other.go +16 -0
- package/internal/lspserver/lsp_project_input_glob_path_windows.go +36 -0
- package/internal/lspserver/lsp_project_input_path_key_other.go +17 -0
- package/internal/lspserver/lsp_project_input_path_key_windows.go +228 -0
- package/internal/lspserver/lsp_project_input_path_key_windows_test.go +460 -0
- package/internal/lspserver/lsp_project_inputs.go +117 -49
- package/internal/lspserver/lsp_reload_directories_separate_glob_territory_test.go +181 -0
- package/lib/flags/schema.js +1 -1
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/projectInputPathIdentity.d.ts +13 -5
- package/lib/internal/projectInputPathIdentity.js +95 -41
- package/lib/internal/projectInputPathIdentity.js.map +1 -1
- package/lib/internal/windowsDirectoryCaseSensitivity.d.ts +10 -0
- package/lib/internal/windowsDirectoryCaseSensitivity.js +34 -0
- package/lib/internal/windowsDirectoryCaseSensitivity.js.map +1 -0
- package/lib/launcher/internal/runTtsc.js +5 -0
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +59 -8
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +61 -7
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +6 -5
- package/lib/launcher/internal/runtimeHooks.js +7 -15
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/launcher/internal/watchTopology.d.ts +82 -8
- package/lib/launcher/internal/watchTopology.js +535 -187
- package/lib/launcher/internal/watchTopology.js.map +1 -1
- package/lib/plugin/internal/descriptorProcessFailure.d.ts +32 -0
- package/lib/plugin/internal/descriptorProcessFailure.js +73 -0
- package/lib/plugin/internal/descriptorProcessFailure.js.map +1 -0
- package/lib/plugin/internal/loadProjectPlugins.js +29 -8
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/package.json +12 -8
- package/src/flags/schema.ts +2 -1
- package/src/internal/projectInputPathIdentity.ts +150 -46
- package/src/internal/windowsDirectoryCaseSensitivity.ts +33 -0
- package/src/launcher/internal/runTtsc.ts +7 -0
- package/src/launcher/internal/runTtscserver.ts +81 -11
- package/src/launcher/internal/runTtsx.ts +87 -10
- package/src/launcher/internal/runtimeHooks.ts +16 -16
- package/src/launcher/internal/watchTopology.ts +679 -208
- package/src/plugin/internal/descriptorProcessFailure.ts +100 -0
- package/src/plugin/internal/loadProjectPlugins.ts +41 -8
- package/tools/shim_audit/baseline.json +0 -9
|
@@ -39,11 +39,14 @@ class WatchTopology {
|
|
|
39
39
|
callbacks;
|
|
40
40
|
analysisOnly = false;
|
|
41
41
|
closed = false;
|
|
42
|
+
compilerPostRegistrationMembershipRefresh = false;
|
|
43
|
+
compilerPostRegistrationReconciliationScheduled = false;
|
|
44
|
+
compilerPostRegistrationSkipUnobservedProjectInputWatchRoots = true;
|
|
42
45
|
directories = new Map();
|
|
43
46
|
directoryWatchers = new Map();
|
|
44
47
|
extraInputs = [];
|
|
45
48
|
extraWatchers = new Map();
|
|
46
|
-
|
|
49
|
+
compilerFileSnapshots = new Map();
|
|
47
50
|
files = new Map();
|
|
48
51
|
fileWatchers = new Map();
|
|
49
52
|
observedDirectories = new Map();
|
|
@@ -65,10 +68,16 @@ class WatchTopology {
|
|
|
65
68
|
reloadFiles: [],
|
|
66
69
|
root: "",
|
|
67
70
|
};
|
|
71
|
+
projectInputRecoveryScheduled = false;
|
|
72
|
+
projectInputPostRegistrationReconciliationScheduled = false;
|
|
68
73
|
projectInputRejectedWatchRoots = new Set();
|
|
74
|
+
projectInputRequiredWatchRoots = new Map();
|
|
75
|
+
projectInputUnobservedWatchRoots = new Map();
|
|
69
76
|
projectInputWatchRoots = new Map();
|
|
70
77
|
projectInputWatchers = new Map();
|
|
71
78
|
projectInputLinkWatchers = new Map();
|
|
79
|
+
projectInputCompilerOutputOverlaps = new WeakMap();
|
|
80
|
+
projectInputCompilerAcknowledgements = new Map();
|
|
72
81
|
reloadFiles = new Map();
|
|
73
82
|
constructor(options, callbacks) {
|
|
74
83
|
this.options = options;
|
|
@@ -76,12 +85,21 @@ class WatchTopology {
|
|
|
76
85
|
}
|
|
77
86
|
/** Re-resolve compiler inputs and notify only when their membership changed. */
|
|
78
87
|
refresh(notify) {
|
|
88
|
+
this.refreshCompilerInputs(notify, false);
|
|
89
|
+
}
|
|
90
|
+
refreshCompilerInputs(notify, skipUnobservedProjectInputWatchRoots) {
|
|
79
91
|
const next = resolveWatchTopology(this.options, this.extraInputs);
|
|
80
|
-
const
|
|
92
|
+
const compilerProgramMembershipChange = next.analysisOnly &&
|
|
81
93
|
mapsEqual(this.reloadFiles, next.reloadFiles) &&
|
|
82
94
|
mapsEqual(this.outputFiles, next.outputFiles) &&
|
|
83
95
|
mapsEqual(this.outputs, next.outputs)
|
|
84
|
-
?
|
|
96
|
+
? compilerMembershipChange(this.files, next.files)
|
|
97
|
+
: [];
|
|
98
|
+
const projectInputProgramOverlap = projectInputCompilerMembershipChange(this.projectInputs, compilerProgramMembershipChange);
|
|
99
|
+
const projectInputProgramChange = compilerProgramMembershipChange.length !== 0 &&
|
|
100
|
+
projectInputProgramOverlap.length ===
|
|
101
|
+
compilerProgramMembershipChange.length
|
|
102
|
+
? projectInputProgramOverlap
|
|
85
103
|
: undefined;
|
|
86
104
|
const changed = this.analysisOnly !== next.analysisOnly ||
|
|
87
105
|
mapsEqual(this.files, next.files) === false ||
|
|
@@ -94,41 +112,105 @@ class WatchTopology {
|
|
|
94
112
|
// Stamp the tracked set as it is resolved, so the first event that cannot
|
|
95
113
|
// name what changed compares against the state the compiler just saw rather
|
|
96
114
|
// than against nothing, which would make it nominate everything once.
|
|
97
|
-
for (const key of [...this.
|
|
115
|
+
for (const key of [...this.compilerFileSnapshots.keys()]) {
|
|
98
116
|
if (!next.files.has(key))
|
|
99
|
-
this.
|
|
117
|
+
this.compilerFileSnapshots.delete(key);
|
|
100
118
|
}
|
|
101
119
|
for (const [key, file] of next.files) {
|
|
102
120
|
// Only a file with no stamp yet is seeded. Restamping one that already
|
|
103
121
|
// has a baseline would advance it past a change nobody reported, and the
|
|
104
122
|
// next unnamed event would then read that change as no change at all.
|
|
105
|
-
if (!this.
|
|
106
|
-
this.
|
|
123
|
+
if (!this.compilerFileSnapshots.has(key)) {
|
|
124
|
+
this.compilerFileSnapshots.set(key, compilerFileSnapshot(file));
|
|
107
125
|
}
|
|
108
126
|
}
|
|
109
127
|
this.directories = next.directories;
|
|
110
128
|
this.outputFiles = next.outputFiles;
|
|
111
129
|
this.outputs = next.outputs;
|
|
112
130
|
this.reloadFiles = next.reloadFiles;
|
|
113
|
-
this.
|
|
114
|
-
|
|
131
|
+
for (const key of this.projectInputCompilerAcknowledgements.keys()) {
|
|
132
|
+
if (!next.files.has(key)) {
|
|
133
|
+
this.projectInputCompilerAcknowledgements.delete(key);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const projectInputProgramReload = projectInputProgramOverlap.length === 0
|
|
137
|
+
? false
|
|
138
|
+
: this.acknowledgeProjectInputCompilerMembership(projectInputProgramOverlap);
|
|
139
|
+
const fileWatcherRegistered = this.syncFileWatchers();
|
|
140
|
+
const directoryWatcherRegistered = this.syncDirectoryWatchers();
|
|
115
141
|
this.syncExtraWatchers();
|
|
116
|
-
this.syncProjectInputWatchers();
|
|
142
|
+
this.syncProjectInputWatchers(skipUnobservedProjectInputWatchRoots);
|
|
143
|
+
if (fileWatcherRegistered || directoryWatcherRegistered) {
|
|
144
|
+
this.scheduleCompilerPostRegistrationReconciliation(directoryWatcherRegistered, skipUnobservedProjectInputWatchRoots);
|
|
145
|
+
}
|
|
117
146
|
if (notify && changed) {
|
|
118
147
|
if (projectInputProgramChange !== undefined) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
148
|
+
const changedPath = projectInputProgramChange.length === 1
|
|
149
|
+
? projectInputProgramChange[0]
|
|
150
|
+
: undefined;
|
|
151
|
+
this.callbacks.onInputChange(projectInputProgramReload
|
|
152
|
+
? { kind: "config", path: changedPath }
|
|
153
|
+
: {
|
|
154
|
+
invalidate: true,
|
|
155
|
+
kind: "project",
|
|
156
|
+
path: changedPath,
|
|
157
|
+
});
|
|
126
158
|
}
|
|
127
159
|
else {
|
|
128
160
|
this.callbacks.onTopologyChange();
|
|
129
161
|
}
|
|
130
162
|
}
|
|
131
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Hand one Program-membership transition from the compiler lane to the
|
|
166
|
+
* overlapping project-input lane.
|
|
167
|
+
*
|
|
168
|
+
* Windows can deliver the compiler membership refresh before the recursive
|
|
169
|
+
* project watcher names the same creation. The rebuild scheduled here already
|
|
170
|
+
* consumes the current project bytes, so publishing their strong fingerprints
|
|
171
|
+
* keeps the later parent event from rediscovering the same population delta.
|
|
172
|
+
* A newly tracked compiler file also remembers that fingerprint until its
|
|
173
|
+
* first named content delivery; identical bytes are the delayed creation,
|
|
174
|
+
* while different bytes are a real later edit and remain observable even
|
|
175
|
+
* inside filesystem timestamp resolution.
|
|
176
|
+
*/
|
|
177
|
+
acknowledgeProjectInputCompilerMembership(changed) {
|
|
178
|
+
const matches = this.collectProjectInputMatches();
|
|
179
|
+
const fingerprints = fingerprintProjectInputMatches(matches);
|
|
180
|
+
const identities = (0, projectInputPathIdentity_1.createProjectInputPathIdentityContext)();
|
|
181
|
+
const changedInputs = projectInputChangedPaths({
|
|
182
|
+
next: matches,
|
|
183
|
+
nextFingerprints: fingerprints,
|
|
184
|
+
previous: this.projectInputMatches,
|
|
185
|
+
previousFingerprints: this.projectInputFingerprints,
|
|
186
|
+
});
|
|
187
|
+
const population = this.projectInputPopulation();
|
|
188
|
+
const causedBy = projectInputCompilerMembershipProjectChanges(changed, population.globs);
|
|
189
|
+
const reload = projectInputReloadEventShouldNotify({
|
|
190
|
+
causedBy,
|
|
191
|
+
changed: causedBy.length === 1 ? causedBy[0] : undefined,
|
|
192
|
+
changedInputs,
|
|
193
|
+
globs: population.globs,
|
|
194
|
+
reloadDirectories: population.reloadDirectories,
|
|
195
|
+
reloadFiles: population.reloadFiles ?? [],
|
|
196
|
+
});
|
|
197
|
+
// The callback below consumes the complete population observed by this
|
|
198
|
+
// scan. Crucially, reload classification runs against the old baseline
|
|
199
|
+
// first, so a concurrent selection delta becomes one cold transition
|
|
200
|
+
// instead of disappearing behind the warm compiler-membership handoff.
|
|
201
|
+
this.projectInputMatches = matches;
|
|
202
|
+
this.projectInputFingerprints = fingerprints;
|
|
203
|
+
for (const location of changed) {
|
|
204
|
+
const compilerKey = pathKey(location);
|
|
205
|
+
if (!this.files.has(compilerKey))
|
|
206
|
+
continue;
|
|
207
|
+
const fingerprint = fingerprints.get(identities.resolve(location).key);
|
|
208
|
+
if (fingerprint !== undefined && fingerprint !== "") {
|
|
209
|
+
this.projectInputCompilerAcknowledgements.set(compilerKey, fingerprint);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return reload;
|
|
213
|
+
}
|
|
132
214
|
/** Add Go plugin source trees discovered by the real build lane. */
|
|
133
215
|
setExtraInputs(inputs) {
|
|
134
216
|
const next = uniqueExistingPaths(inputs);
|
|
@@ -157,20 +239,11 @@ class WatchTopology {
|
|
|
157
239
|
}
|
|
158
240
|
this.projectInputs = next;
|
|
159
241
|
this.projectInputRejectedWatchRoots.clear();
|
|
160
|
-
|
|
242
|
+
this.pruneProjectInputWatchRoots([
|
|
161
243
|
next,
|
|
162
244
|
inputs,
|
|
163
245
|
{ ...(inputs.declared ?? inputs), root: inputs.root },
|
|
164
|
-
]
|
|
165
|
-
...snapshot.files.map((file) => projectInputDeclarationKey("file", file)),
|
|
166
|
-
...snapshot.globs.map((glob) => projectInputDeclarationKey("glob", glob)),
|
|
167
|
-
...(snapshot.reloadFiles ?? []).map((file) => projectInputDeclarationKey("reload", file)),
|
|
168
|
-
...(snapshot.reloadDirectories ?? []).map((directory) => projectInputDeclarationKey("reload-directory", directory)),
|
|
169
|
-
]));
|
|
170
|
-
for (const key of this.projectInputWatchRoots.keys()) {
|
|
171
|
-
if (!declarations.has(key))
|
|
172
|
-
this.projectInputWatchRoots.delete(key);
|
|
173
|
-
}
|
|
246
|
+
]);
|
|
174
247
|
this.projectInputMatches = this.collectProjectInputMatches();
|
|
175
248
|
this.projectInputFingerprints = fingerprintProjectInputMatches(this.projectInputMatches);
|
|
176
249
|
this.syncProjectInputWatchers();
|
|
@@ -184,31 +257,43 @@ class WatchTopology {
|
|
|
184
257
|
closeWatchers(this.projectInputWatchers);
|
|
185
258
|
closeWatchers(this.projectInputLinkWatchers);
|
|
186
259
|
}
|
|
187
|
-
syncFileWatchers() {
|
|
188
|
-
const
|
|
260
|
+
syncFileWatchers(skipMissing = false) {
|
|
261
|
+
const previous = new Map(this.fileWatchers);
|
|
262
|
+
const files = process.platform === "win32"
|
|
263
|
+
? new Map()
|
|
264
|
+
: skipMissing
|
|
265
|
+
? new Map([...this.files].filter(([, location]) => node_fs_1.default.existsSync(location)))
|
|
266
|
+
: this.files;
|
|
189
267
|
syncWatchers(this.fileWatchers, files, (location) => node_fs_1.default.watch(watcherRegistrationPath(location), { persistent: true }, () => {
|
|
190
268
|
// A per-file watcher fires on any filesystem attention its target
|
|
191
269
|
// receives, and it carries no filename to distinguish an edit from
|
|
192
270
|
// a touch. It answers the same question the unnamed directory event
|
|
193
271
|
// answers, so it answers it the same way: from the bytes.
|
|
194
|
-
|
|
272
|
+
const movement = this.compilerFileMovement(location);
|
|
273
|
+
if (movement.owner)
|
|
274
|
+
this.rearmFileWatchers([location], true);
|
|
275
|
+
if (!movement.content)
|
|
195
276
|
return;
|
|
196
277
|
this.callbacks.onInputChange({
|
|
197
278
|
kind: this.classifyCompilerInput(location),
|
|
198
279
|
path: location,
|
|
199
280
|
});
|
|
200
|
-
}), (location, error) => this.callbacks.onError(location, error));
|
|
281
|
+
}), (location, error) => this.callbacks.onError(location, error), () => this.closed === false);
|
|
282
|
+
return [...this.fileWatchers].some(([key, watcher]) => previous.get(key) !== watcher);
|
|
201
283
|
}
|
|
202
|
-
/**
|
|
203
|
-
|
|
284
|
+
/** Compare a tracked file's content and physical owner with its snapshot. */
|
|
285
|
+
compilerFileMovement(location) {
|
|
204
286
|
const key = pathKey(location);
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
287
|
+
const previous = this.compilerFileSnapshots.get(key);
|
|
288
|
+
const next = compilerFileSnapshot(location);
|
|
289
|
+
this.compilerFileSnapshots.set(key, next);
|
|
290
|
+
return {
|
|
291
|
+
content: previous?.content !== next.content,
|
|
292
|
+
owner: previous?.owner !== next.owner,
|
|
293
|
+
};
|
|
210
294
|
}
|
|
211
295
|
syncDirectoryWatchers() {
|
|
296
|
+
const previous = new Map(this.directoryWatchers);
|
|
212
297
|
const desired = new Map(this.directories);
|
|
213
298
|
for (const [key, location] of this.observedDirectories) {
|
|
214
299
|
if (isDirectory(location) === false ||
|
|
@@ -258,7 +343,79 @@ class WatchTopology {
|
|
|
258
343
|
}
|
|
259
344
|
if (plan.refresh)
|
|
260
345
|
this.refreshFromDirectory(location, changed);
|
|
261
|
-
}), (location, error) => this.callbacks.onError(location, error));
|
|
346
|
+
}), (location, error) => this.callbacks.onError(location, error), () => this.closed === false);
|
|
347
|
+
return [...this.directoryWatchers].some(([key, watcher]) => previous.get(key) !== watcher);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Reconcile tracked compiler files after a newly registered watcher returns.
|
|
351
|
+
*
|
|
352
|
+
* A file or directory watcher can be returned before its backend is ready to
|
|
353
|
+
* deliver the first event. The compiler-file stamps were captured before
|
|
354
|
+
* registration, so one coalesced microtask can recover a change in that
|
|
355
|
+
* handoff window. A real event updates the same stamp first and makes this
|
|
356
|
+
* bounded scan a no-op.
|
|
357
|
+
*/
|
|
358
|
+
scheduleCompilerPostRegistrationReconciliation(refreshMembership, skipUnobservedProjectInputWatchRoots) {
|
|
359
|
+
if (this.closed)
|
|
360
|
+
return;
|
|
361
|
+
if (refreshMembership) {
|
|
362
|
+
this.compilerPostRegistrationMembershipRefresh = true;
|
|
363
|
+
this.compilerPostRegistrationSkipUnobservedProjectInputWatchRoots =
|
|
364
|
+
this.compilerPostRegistrationSkipUnobservedProjectInputWatchRoots &&
|
|
365
|
+
skipUnobservedProjectInputWatchRoots;
|
|
366
|
+
}
|
|
367
|
+
if (this.compilerPostRegistrationReconciliationScheduled)
|
|
368
|
+
return;
|
|
369
|
+
this.compilerPostRegistrationReconciliationScheduled = true;
|
|
370
|
+
queueMicrotask(() => {
|
|
371
|
+
this.compilerPostRegistrationReconciliationScheduled = false;
|
|
372
|
+
if (this.closed)
|
|
373
|
+
return;
|
|
374
|
+
const refreshCompilerMembership = this.compilerPostRegistrationMembershipRefresh;
|
|
375
|
+
const skipUnobservedProjectInputWatchRoots = this.compilerPostRegistrationSkipUnobservedProjectInputWatchRoots;
|
|
376
|
+
this.compilerPostRegistrationMembershipRefresh = false;
|
|
377
|
+
this.compilerPostRegistrationSkipUnobservedProjectInputWatchRoots = true;
|
|
378
|
+
const changed = [];
|
|
379
|
+
const rearm = [];
|
|
380
|
+
for (const file of this.files.values()) {
|
|
381
|
+
const movement = this.compilerFileMovement(file);
|
|
382
|
+
if (movement.content)
|
|
383
|
+
changed.push(file);
|
|
384
|
+
if (movement.owner)
|
|
385
|
+
rearm.push(file);
|
|
386
|
+
}
|
|
387
|
+
// A replacement can move the path to a new inode without changing its
|
|
388
|
+
// cheap content stamp or topology key. Rebind its physical owner without
|
|
389
|
+
// inventing a content notification. Missing entries remain covered by
|
|
390
|
+
// their parent directory and are retried when recreation is observed.
|
|
391
|
+
this.rearmFileWatchers(rearm, true);
|
|
392
|
+
for (const file of changed) {
|
|
393
|
+
this.callbacks.onInputChange({
|
|
394
|
+
kind: this.classifyCompilerInput(file),
|
|
395
|
+
path: file,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
if (!refreshCompilerMembership)
|
|
399
|
+
return;
|
|
400
|
+
try {
|
|
401
|
+
// Directory watchers own files not present in the current Program.
|
|
402
|
+
// Re-resolve even when every tracked stamp is unchanged so a swallowed
|
|
403
|
+
// startup event cannot strand a newly included source.
|
|
404
|
+
this.refreshCompilerInputs(true, skipUnobservedProjectInputWatchRoots);
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
const reported = new Set(changed.map(pathKey));
|
|
408
|
+
const reconciledChange = changed.length === 1 ? changed[0] : undefined;
|
|
409
|
+
for (const reload of reloadInputsForFailedTopologyRefresh(this.reloadFiles.values(), reconciledChange)) {
|
|
410
|
+
if (reported.has(pathKey(reload)))
|
|
411
|
+
continue;
|
|
412
|
+
this.callbacks.onInputChange({ kind: "config", path: reload });
|
|
413
|
+
}
|
|
414
|
+
this.callbacks.onError(reconciledChange === undefined
|
|
415
|
+
? (this.options.projectRoot ?? this.options.cwd)
|
|
416
|
+
: node_path_1.default.dirname(reconciledChange), error);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
262
419
|
}
|
|
263
420
|
/**
|
|
264
421
|
* Narrow a plan's changes to the tracked files that actually moved.
|
|
@@ -282,22 +439,29 @@ class WatchTopology {
|
|
|
282
439
|
// Those two are decided from the bytes, and the rearm they drive is
|
|
283
440
|
// unaffected, because rebinding is about the inode and not the content.
|
|
284
441
|
if (changed !== undefined && event !== "rename") {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
442
|
+
return changes.filter((file) => {
|
|
443
|
+
const key = pathKey(file);
|
|
444
|
+
const acknowledged = this.projectInputCompilerAcknowledgements.get(key);
|
|
445
|
+
this.projectInputCompilerAcknowledgements.delete(key);
|
|
446
|
+
this.recordCompilerFileSnapshot(file);
|
|
447
|
+
return (acknowledged === undefined ||
|
|
448
|
+
acknowledged !== fingerprintProjectInputFile(file));
|
|
449
|
+
});
|
|
288
450
|
}
|
|
289
|
-
return changes.filter((file) => this.
|
|
451
|
+
return changes.filter((file) => this.compilerFileMovement(file).content);
|
|
290
452
|
}
|
|
291
|
-
|
|
292
|
-
this.
|
|
453
|
+
recordCompilerFileSnapshot(file) {
|
|
454
|
+
this.compilerFileSnapshots.set(pathKey(file), compilerFileSnapshot(file));
|
|
293
455
|
}
|
|
294
|
-
rearmFileWatchers(files) {
|
|
456
|
+
rearmFileWatchers(files, skipMissing = false) {
|
|
295
457
|
for (const file of files) {
|
|
296
458
|
const key = pathKey(file);
|
|
297
459
|
this.fileWatchers.get(key)?.close();
|
|
298
460
|
this.fileWatchers.delete(key);
|
|
299
461
|
}
|
|
300
|
-
this.syncFileWatchers()
|
|
462
|
+
if (this.syncFileWatchers(skipMissing)) {
|
|
463
|
+
this.scheduleCompilerPostRegistrationReconciliation(false, true);
|
|
464
|
+
}
|
|
301
465
|
}
|
|
302
466
|
syncExtraWatchers() {
|
|
303
467
|
const directories = new Map();
|
|
@@ -314,20 +478,21 @@ class WatchTopology {
|
|
|
314
478
|
kind: "plugin",
|
|
315
479
|
path: changed ?? location,
|
|
316
480
|
});
|
|
317
|
-
}), (location, error) => this.callbacks.onError(location, error));
|
|
481
|
+
}), (location, error) => this.callbacks.onError(location, error), () => this.closed === false);
|
|
318
482
|
}
|
|
319
|
-
syncProjectInputWatchers() {
|
|
483
|
+
syncProjectInputWatchers(skipUnobservedProjectInputWatchRoots = false) {
|
|
320
484
|
if (this.closed)
|
|
321
485
|
return;
|
|
486
|
+
const previous = new Map(this.projectInputWatchers);
|
|
487
|
+
const previousLinks = new Map(this.projectInputLinkWatchers);
|
|
322
488
|
const identities = (0, projectInputPathIdentity_1.createProjectInputPathIdentityContext)();
|
|
323
489
|
const desired = new Map();
|
|
490
|
+
const required = new Map();
|
|
324
491
|
for (const file of this.projectInputDeclarations("file")) {
|
|
325
492
|
if (this.isProjectInputCompilerOutput(file, identities))
|
|
326
493
|
continue;
|
|
327
494
|
const location = this.projectInputWatchRoot("file", file, node_path_1.default.dirname(file));
|
|
328
|
-
|
|
329
|
-
this.retainProjectInputWatchRoot(desired, identities, location);
|
|
330
|
-
}
|
|
495
|
+
this.retainProjectInputWatchRoot(required, desired, identities, location, node_path_1.default.dirname(file), skipUnobservedProjectInputWatchRoots);
|
|
331
496
|
}
|
|
332
497
|
for (const glob of this.projectInputDeclarations("glob")) {
|
|
333
498
|
const root = literalGlobRoot(glob);
|
|
@@ -335,32 +500,27 @@ class WatchTopology {
|
|
|
335
500
|
continue;
|
|
336
501
|
}
|
|
337
502
|
const location = this.projectInputWatchRoot("glob", glob, root);
|
|
338
|
-
|
|
339
|
-
this.retainProjectInputWatchRoot(desired, identities, location);
|
|
340
|
-
}
|
|
503
|
+
this.retainProjectInputWatchRoot(required, desired, identities, location, root, skipUnobservedProjectInputWatchRoots);
|
|
341
504
|
}
|
|
342
505
|
for (const file of this.projectInputDeclarations("reload")) {
|
|
343
506
|
if (this.isProjectInputCompilerOutput(file, identities))
|
|
344
507
|
continue;
|
|
345
508
|
const location = this.projectInputWatchRoot("reload", file, node_path_1.default.dirname(file));
|
|
346
|
-
|
|
347
|
-
this.retainProjectInputWatchRoot(desired, identities, location);
|
|
348
|
-
}
|
|
509
|
+
this.retainProjectInputWatchRoot(required, desired, identities, location, node_path_1.default.dirname(file), skipUnobservedProjectInputWatchRoots);
|
|
349
510
|
}
|
|
350
511
|
for (const directory of this.projectInputDeclarations("reload-directory")) {
|
|
351
512
|
if (this.isProjectInputCompilerOutputDirectory(directory, identities)) {
|
|
352
513
|
continue;
|
|
353
514
|
}
|
|
354
515
|
const location = this.projectInputWatchRoot("reload-directory", directory, directory);
|
|
355
|
-
|
|
356
|
-
this.retainProjectInputWatchRoot(desired, identities, location);
|
|
357
|
-
}
|
|
516
|
+
this.retainProjectInputWatchRoot(required, desired, identities, location, directory, skipUnobservedProjectInputWatchRoots);
|
|
358
517
|
}
|
|
359
518
|
const active = new Map();
|
|
360
519
|
for (const location of projectInputActiveWatchDirectories(desired.values(), identities)) {
|
|
361
520
|
const identity = identities.resolve(location);
|
|
362
521
|
active.set(identity.key, identity.path);
|
|
363
522
|
}
|
|
523
|
+
this.projectInputRequiredWatchRoots = required;
|
|
364
524
|
syncWatchers(this.projectInputWatchers, active, (location) => node_fs_1.default.watch(watcherRegistrationPath(location), { persistent: true, recursive: true }, (_event, filename) => {
|
|
365
525
|
const changed = filename === null
|
|
366
526
|
? undefined
|
|
@@ -371,12 +531,24 @@ class WatchTopology {
|
|
|
371
531
|
const firstFailure = !this.projectInputRejectedWatchRoots.has(key);
|
|
372
532
|
this.projectInputRejectedWatchRoots.add(key);
|
|
373
533
|
this.callbacks.onError(location, error);
|
|
374
|
-
if (firstFailure) {
|
|
375
|
-
|
|
534
|
+
if (firstFailure && !this.closed) {
|
|
535
|
+
this.scheduleProjectInputWatcherRecovery();
|
|
376
536
|
}
|
|
377
|
-
});
|
|
537
|
+
}, () => this.closed === false);
|
|
538
|
+
if (this.closed)
|
|
539
|
+
return;
|
|
540
|
+
if (!this.projectInputRecoveryScheduled) {
|
|
541
|
+
this.reportUnobservedProjectInputWatchRoots(required);
|
|
542
|
+
}
|
|
378
543
|
this.syncProjectInputLinkWatchers(identities);
|
|
379
|
-
|
|
544
|
+
const watcherRegistered = [...this.projectInputWatchers].some(([key, watcher]) => previous.get(key) !== watcher) ||
|
|
545
|
+
[...this.projectInputLinkWatchers].some(([key, watcher]) => previousLinks.get(key) !== watcher);
|
|
546
|
+
if (watcherRegistered) {
|
|
547
|
+
this.scheduleProjectInputPostRegistrationReconciliation();
|
|
548
|
+
}
|
|
549
|
+
this.callbacks.onProjectInputWatchRoots?.([...this.projectInputWatchers.keys()]
|
|
550
|
+
.map((key) => active.get(key) ?? identities.resolve(key).path)
|
|
551
|
+
.sort());
|
|
380
552
|
}
|
|
381
553
|
/**
|
|
382
554
|
* Watch the directory that holds a declaration which is itself a link.
|
|
@@ -415,7 +587,7 @@ class WatchTopology {
|
|
|
415
587
|
? undefined
|
|
416
588
|
: node_path_1.default.resolve(location, filename.toString());
|
|
417
589
|
this.refreshProjectInputs(location, changed);
|
|
418
|
-
}), (location, error) => this.callbacks.onError(location, error));
|
|
590
|
+
}), (location, error) => this.callbacks.onError(location, error), () => this.closed === false);
|
|
419
591
|
}
|
|
420
592
|
/** Drop the watcher that just reported a directory replacement. */
|
|
421
593
|
retireProjectInputWatcher(location, identities) {
|
|
@@ -435,13 +607,127 @@ class WatchTopology {
|
|
|
435
607
|
watchers.delete(key);
|
|
436
608
|
}
|
|
437
609
|
}
|
|
438
|
-
retainProjectInputWatchRoot(desired, identities, location) {
|
|
610
|
+
retainProjectInputWatchRoot(required, desired, identities, location, target, skipUnobservedProjectInputWatchRoots) {
|
|
611
|
+
const requiredIdentity = identities.resolve(location ?? target);
|
|
612
|
+
required.set(requiredIdentity.key, requiredIdentity.path);
|
|
613
|
+
if (skipUnobservedProjectInputWatchRoots) {
|
|
614
|
+
const retainedActiveRoot = [...this.projectInputWatchers.keys()].find((root) => (0, projectInputPathIdentity_1.isProjectInputPathIdentityWithin)(root, requiredIdentity.key));
|
|
615
|
+
if (retainedActiveRoot !== undefined) {
|
|
616
|
+
desired.set(retainedActiveRoot, retainedActiveRoot);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (location === undefined ||
|
|
621
|
+
(skipUnobservedProjectInputWatchRoots &&
|
|
622
|
+
this.projectInputUnobservedWatchRoots.has(requiredIdentity.key))) {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
439
625
|
const available = projectInputAvailableWatchDirectory(location, this.projectInputRejectedWatchRoots, identities, this.projectInputs.root);
|
|
440
626
|
if (available === undefined)
|
|
441
627
|
return;
|
|
442
628
|
const identity = identities.resolve(available);
|
|
443
629
|
desired.set(identity.key, identity.path);
|
|
444
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* Retry a failed root on the next reconciliation instead of retiring it for
|
|
633
|
+
* the session.
|
|
634
|
+
*
|
|
635
|
+
* This immediate recovery pass still honors the rejected root so it can
|
|
636
|
+
* install a safe ancestor where one exists. Only the recovery fixpoint
|
|
637
|
+
* reports a genuinely uncovered lane; transient gaps between fallback
|
|
638
|
+
* candidates are not user-visible. The rejection then expires. A later
|
|
639
|
+
* compiler refresh or an unchanged project-input republication can retry the
|
|
640
|
+
* original root, while a permanently failing backend costs at most one
|
|
641
|
+
* attempt per sync.
|
|
642
|
+
*/
|
|
643
|
+
scheduleProjectInputWatcherRecovery() {
|
|
644
|
+
if (this.projectInputRecoveryScheduled)
|
|
645
|
+
return;
|
|
646
|
+
this.projectInputRecoveryScheduled = true;
|
|
647
|
+
queueMicrotask(() => {
|
|
648
|
+
try {
|
|
649
|
+
let previousRejectionCount = -1;
|
|
650
|
+
while (this.closed === false &&
|
|
651
|
+
previousRejectionCount !== this.projectInputRejectedWatchRoots.size) {
|
|
652
|
+
previousRejectionCount = this.projectInputRejectedWatchRoots.size;
|
|
653
|
+
this.syncProjectInputWatchers();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
finally {
|
|
657
|
+
this.projectInputRejectedWatchRoots.clear();
|
|
658
|
+
this.projectInputRecoveryScheduled = false;
|
|
659
|
+
if (!this.closed) {
|
|
660
|
+
this.reportUnobservedProjectInputWatchRoots(this.projectInputRequiredWatchRoots);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Reconcile the snapshot-to-watcher handoff after the caller's current turn.
|
|
667
|
+
*
|
|
668
|
+
* A recursive watcher can return before its backend is ready to deliver the
|
|
669
|
+
* first event. The publication baseline is necessarily captured before that
|
|
670
|
+
* watcher exists, so an input materialized synchronously after
|
|
671
|
+
* `setProjectInputs()` would otherwise depend entirely on that startup event.
|
|
672
|
+
* The ordinary fingerprint update makes this scan and a real backend event
|
|
673
|
+
* race safely: whichever arrives first records the new population and the
|
|
674
|
+
* other becomes a no-op.
|
|
675
|
+
*/
|
|
676
|
+
scheduleProjectInputPostRegistrationReconciliation() {
|
|
677
|
+
if (this.closed ||
|
|
678
|
+
this.projectInputPostRegistrationReconciliationScheduled) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
this.projectInputPostRegistrationReconciliationScheduled = true;
|
|
682
|
+
queueMicrotask(() => {
|
|
683
|
+
this.projectInputPostRegistrationReconciliationScheduled = false;
|
|
684
|
+
if (this.closed)
|
|
685
|
+
return;
|
|
686
|
+
this.refreshPublishedProjectInputIdentities();
|
|
687
|
+
this.refreshProjectInputs(this.projectInputs.root, undefined, true);
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Re-resolve declarations after watcher registration.
|
|
692
|
+
*
|
|
693
|
+
* A missing path can become a symlink before the handoff scan. The retained
|
|
694
|
+
* normalized snapshot still names the pre-link spelling in that case, so a
|
|
695
|
+
* scan can find the first target file without installing the physical owner
|
|
696
|
+
* that must observe later target changes.
|
|
697
|
+
*/
|
|
698
|
+
refreshPublishedProjectInputIdentities() {
|
|
699
|
+
const next = normalizeProjectInputSnapshot(this.declaredProjectInputs);
|
|
700
|
+
if (projectInputSnapshotsEqual(this.projectInputs, next))
|
|
701
|
+
return;
|
|
702
|
+
this.projectInputs = next;
|
|
703
|
+
this.pruneProjectInputWatchRoots([next, this.declaredProjectInputs]);
|
|
704
|
+
}
|
|
705
|
+
/** Drop retained owner choices for declarations no longer published. */
|
|
706
|
+
pruneProjectInputWatchRoots(snapshots) {
|
|
707
|
+
const declarations = new Set(snapshots.flatMap((snapshot) => [
|
|
708
|
+
...snapshot.files.map((file) => projectInputDeclarationKey("file", file)),
|
|
709
|
+
...snapshot.globs.map((glob) => projectInputDeclarationKey("glob", glob)),
|
|
710
|
+
...(snapshot.reloadFiles ?? []).map((file) => projectInputDeclarationKey("reload", file)),
|
|
711
|
+
...(snapshot.reloadDirectories ?? []).map((directory) => projectInputDeclarationKey("reload-directory", directory)),
|
|
712
|
+
]));
|
|
713
|
+
for (const key of this.projectInputWatchRoots.keys()) {
|
|
714
|
+
if (!declarations.has(key))
|
|
715
|
+
this.projectInputWatchRoots.delete(key);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
/** Report only newly uncovered project-input roots as an observation loss. */
|
|
719
|
+
reportUnobservedProjectInputWatchRoots(required) {
|
|
720
|
+
const active = [...this.projectInputWatchers.keys()];
|
|
721
|
+
const unavailable = new Map([...required].filter(([key]) => active.every((root) => !(0, projectInputPathIdentity_1.isProjectInputPathIdentityWithin)(root, key))));
|
|
722
|
+
const newlyUnavailable = [...unavailable]
|
|
723
|
+
.filter(([key]) => !this.projectInputUnobservedWatchRoots.has(key))
|
|
724
|
+
.map(([, location]) => location)
|
|
725
|
+
.sort();
|
|
726
|
+
this.projectInputUnobservedWatchRoots = unavailable;
|
|
727
|
+
if (newlyUnavailable.length !== 0) {
|
|
728
|
+
this.callbacks.onProjectInputWatchUnavailable?.(newlyUnavailable);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
445
731
|
/**
|
|
446
732
|
* One snapshot holding every spelling of every declaration.
|
|
447
733
|
*
|
|
@@ -504,7 +790,7 @@ class WatchTopology {
|
|
|
504
790
|
this.projectInputWatchRoots.set(key, resolved);
|
|
505
791
|
return resolved;
|
|
506
792
|
}
|
|
507
|
-
refreshProjectInputs(location, changed) {
|
|
793
|
+
refreshProjectInputs(location, changed, skipUnobservedProjectInputWatchRoots = false) {
|
|
508
794
|
try {
|
|
509
795
|
const previous = this.projectInputMatches;
|
|
510
796
|
const identities = (0, projectInputPathIdentity_1.createProjectInputPathIdentityContext)();
|
|
@@ -547,19 +833,20 @@ class WatchTopology {
|
|
|
547
833
|
previous,
|
|
548
834
|
previousFingerprints: this.projectInputFingerprints,
|
|
549
835
|
});
|
|
836
|
+
const reconciledChange = changed ?? (changedInputs.length === 1 ? changedInputs[0] : undefined);
|
|
550
837
|
// Both spellings classify the event. The normalized form names the file a
|
|
551
838
|
// link pointed at when the snapshot was published, so after a retarget it
|
|
552
839
|
// names the wrong one; only the declared form resolves to what the link
|
|
553
840
|
// points at now, which is the selection this lane exists to protect.
|
|
554
841
|
const reload = projectInputReloadEventShouldNotify({
|
|
555
|
-
changed,
|
|
842
|
+
changed: reconciledChange,
|
|
556
843
|
changedInputs,
|
|
557
844
|
globs: population.globs,
|
|
558
845
|
reloadDirectories: population.reloadDirectories ?? [],
|
|
559
846
|
reloadFiles: population.reloadFiles ?? [],
|
|
560
847
|
});
|
|
561
848
|
const invalidate = projectInputMembershipInvalidatesProgram({
|
|
562
|
-
changed,
|
|
849
|
+
changed: reconciledChange,
|
|
563
850
|
changedInputs,
|
|
564
851
|
contentChanged,
|
|
565
852
|
next,
|
|
@@ -567,26 +854,28 @@ class WatchTopology {
|
|
|
567
854
|
});
|
|
568
855
|
this.projectInputMatches = next;
|
|
569
856
|
this.projectInputFingerprints = nextFingerprints;
|
|
570
|
-
this.syncProjectInputWatchers();
|
|
857
|
+
this.syncProjectInputWatchers(skipUnobservedProjectInputWatchRoots);
|
|
571
858
|
// A JSON/TS/JS project-input member can simultaneously enter or leave
|
|
572
859
|
// the compiler Program. Reconcile the compiler watch snapshot before
|
|
573
860
|
// scheduling its resident invalidation, so runWatch's post-cycle refresh
|
|
574
861
|
// does not rediscover the same delta as a broader execution reload.
|
|
575
|
-
if (invalidate)
|
|
576
|
-
this.
|
|
862
|
+
if (invalidate) {
|
|
863
|
+
this.refreshCompilerInputs(false, skipUnobservedProjectInputWatchRoots);
|
|
864
|
+
}
|
|
577
865
|
if (projectInputEventShouldNotify({
|
|
578
866
|
contentChanged,
|
|
579
867
|
directlyMatched,
|
|
580
868
|
membershipChanged,
|
|
581
869
|
}) &&
|
|
582
|
-
(
|
|
583
|
-
this.isProjectInputCompilerOutput(
|
|
870
|
+
(reconciledChange === undefined ||
|
|
871
|
+
this.isProjectInputCompilerOutput(reconciledChange, identities) ===
|
|
872
|
+
false)) {
|
|
584
873
|
this.callbacks.onInputChange(reload
|
|
585
|
-
? { kind: "config", path:
|
|
874
|
+
? { kind: "config", path: reconciledChange }
|
|
586
875
|
: {
|
|
587
876
|
...(invalidate ? { invalidate: true } : {}),
|
|
588
877
|
kind: "project",
|
|
589
|
-
path:
|
|
878
|
+
path: reconciledChange,
|
|
590
879
|
});
|
|
591
880
|
}
|
|
592
881
|
}
|
|
@@ -595,7 +884,7 @@ class WatchTopology {
|
|
|
595
884
|
// replacement is readable. Rebind ancestor ownership even when the
|
|
596
885
|
// population scan races that transient gap, so a later create cannot be
|
|
597
886
|
// stranded without a watcher.
|
|
598
|
-
this.syncProjectInputWatchers();
|
|
887
|
+
this.syncProjectInputWatchers(skipUnobservedProjectInputWatchRoots);
|
|
599
888
|
this.callbacks.onError(location, error);
|
|
600
889
|
}
|
|
601
890
|
}
|
|
@@ -691,7 +980,14 @@ class WatchTopology {
|
|
|
691
980
|
}
|
|
692
981
|
isProjectInputCompilerOutputDirectory(location, identities) {
|
|
693
982
|
const root = this.projectInputs.root;
|
|
983
|
+
let overlaps = this.projectInputCompilerOutputOverlaps.get(identities);
|
|
984
|
+
if (overlaps === undefined) {
|
|
985
|
+
overlaps = new Map();
|
|
986
|
+
this.projectInputCompilerOutputOverlaps.set(identities, overlaps);
|
|
987
|
+
}
|
|
694
988
|
return [...this.outputs.values()].some((output) => {
|
|
989
|
+
if (!identities.isWithin(output, location))
|
|
990
|
+
return false;
|
|
695
991
|
// An output directory that is the project itself, or holds it, is not a
|
|
696
992
|
// place where only build products live -- the sources are there too. A
|
|
697
993
|
// project emitting in place declares exactly that, and honouring it
|
|
@@ -701,7 +997,14 @@ class WatchTopology {
|
|
|
701
997
|
// rebuild, while an input that does not is never seen again.
|
|
702
998
|
if (root !== "" && identities.isWithin(output, root))
|
|
703
999
|
return false;
|
|
704
|
-
|
|
1000
|
+
let overlapsCompilerInput = overlaps.get(output);
|
|
1001
|
+
if (overlapsCompilerInput === undefined) {
|
|
1002
|
+
overlapsCompilerInput = [...this.files.values()].some((input) => identities.isWithin(output, input));
|
|
1003
|
+
overlaps.set(output, overlapsCompilerInput);
|
|
1004
|
+
}
|
|
1005
|
+
if (overlapsCompilerInput)
|
|
1006
|
+
return false;
|
|
1007
|
+
return true;
|
|
705
1008
|
});
|
|
706
1009
|
}
|
|
707
1010
|
isProjectInputCompilerOutput(location, identities) {
|
|
@@ -791,7 +1094,7 @@ function resolveWatchTopology(options, extraInputs) {
|
|
|
791
1094
|
const compilerInputs = listCompilerInputs(project, options);
|
|
792
1095
|
const compilerOutputs = resolveCompilerOutputs(project, options);
|
|
793
1096
|
addPaths(outputFiles, compilerOutputs.files);
|
|
794
|
-
addPaths(outputFiles,
|
|
1097
|
+
addPaths(outputFiles, inferPerSourceCompilerOutputs(project, options, compilerInputs));
|
|
795
1098
|
addPaths(outputs, compilerOutputs.directories);
|
|
796
1099
|
addPaths(files, compilerInputs);
|
|
797
1100
|
}
|
|
@@ -876,13 +1179,16 @@ function listCompilerInputs(project, options) {
|
|
|
876
1179
|
if (result.error) {
|
|
877
1180
|
throw new Error(`ttsc: failed to list compiler inputs: ${result.error.message}`);
|
|
878
1181
|
}
|
|
879
|
-
|
|
1182
|
+
// `--listFilesOnly` is the authority for compiler inputs. A path may also be
|
|
1183
|
+
// a predicted product of another input, but that collision does not revoke
|
|
1184
|
+
// its Program membership—especially while the compiler is reporting an
|
|
1185
|
+
// overwrite diagnostic. Product inference is used only to classify future
|
|
1186
|
+
// filesystem events, never to subtract from the compiler's answer.
|
|
880
1187
|
const inputs = (0, spawnNative_1.outputText)(result.stdout)
|
|
881
1188
|
.split(/\r?\n/)
|
|
882
1189
|
.map((line) => line.trim())
|
|
883
1190
|
.filter((line) => node_path_1.default.isAbsolute(line))
|
|
884
|
-
.map((line) => node_path_1.default.resolve(line))
|
|
885
|
-
.filter((file) => isCompilerOutput(file, outputs) === false);
|
|
1191
|
+
.map((line) => node_path_1.default.resolve(line));
|
|
886
1192
|
if (result.status !== 0 && inputs.length === 0) {
|
|
887
1193
|
throw new Error(`ttsc: failed to list compiler inputs:\n${(0, spawnNative_1.outputText)(result.stderr) || (0, spawnNative_1.outputText)(result.stdout)}`);
|
|
888
1194
|
}
|
|
@@ -899,19 +1205,6 @@ function resolveCompilerOutputs(project, options) {
|
|
|
899
1205
|
if (emit.declaration && emit.declarationDir !== undefined) {
|
|
900
1206
|
directories.add(emit.declarationDir);
|
|
901
1207
|
}
|
|
902
|
-
if (emit.outFile !== undefined) {
|
|
903
|
-
if (emit.javascript) {
|
|
904
|
-
files.add(emit.outFile);
|
|
905
|
-
if (emit.sourceMap)
|
|
906
|
-
files.add(`${emit.outFile}.map`);
|
|
907
|
-
}
|
|
908
|
-
if (emit.declaration) {
|
|
909
|
-
const declaration = replaceOutputExtension(emit.outFile, ".d.ts");
|
|
910
|
-
files.add(declaration);
|
|
911
|
-
if (emit.declarationMap)
|
|
912
|
-
files.add(`${declaration}.map`);
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
1208
|
if (emit.incremental) {
|
|
916
1209
|
files.add(defaultTsBuildInfoFile(project, emit));
|
|
917
1210
|
}
|
|
@@ -920,47 +1213,72 @@ function resolveCompilerOutputs(project, options) {
|
|
|
920
1213
|
files: [...files],
|
|
921
1214
|
};
|
|
922
1215
|
}
|
|
923
|
-
function
|
|
1216
|
+
function inferPerSourceCompilerOutputs(project, options, inputs) {
|
|
924
1217
|
const emit = effectiveCompilerEmit(project, options);
|
|
925
1218
|
const outputs = new Set();
|
|
1219
|
+
// TypeScript-Go 7 requires an explicit rootDir when output layout would
|
|
1220
|
+
// otherwise need inference. While reporting TS5011 it still emits relative
|
|
1221
|
+
// to the config directory, so the watch model must use that same recovery
|
|
1222
|
+
// layout rather than the legacy common-source-directory rule.
|
|
1223
|
+
const sourceRoot = emit.rootDir ?? project.root;
|
|
926
1224
|
for (const input of inputs) {
|
|
927
1225
|
const extension = node_path_1.default.extname(input).toLowerCase();
|
|
928
|
-
if (!isCompilerEmittableSourceExtension(extension)) {
|
|
929
|
-
continue;
|
|
930
|
-
}
|
|
931
1226
|
if (/\.d\.(?:ts|mts|cts)$/i.test(input))
|
|
932
1227
|
continue;
|
|
933
1228
|
const stem = input.slice(0, -extension.length);
|
|
1229
|
+
const mappedStem = (directory, adjacentWhenOutside) => {
|
|
1230
|
+
if (directory === undefined)
|
|
1231
|
+
return stem;
|
|
1232
|
+
if (!isPathWithin(sourceRoot, input)) {
|
|
1233
|
+
return adjacentWhenOutside ? stem : undefined;
|
|
1234
|
+
}
|
|
1235
|
+
return node_path_1.default.resolve(directory, node_path_1.default.relative(sourceRoot, stem));
|
|
1236
|
+
};
|
|
1237
|
+
if (extension === ".json") {
|
|
1238
|
+
if (emit.javascript &&
|
|
1239
|
+
emit.resolveJsonModule &&
|
|
1240
|
+
emit.outDir !== undefined) {
|
|
1241
|
+
const jsonStem = mappedStem(emit.outDir, false);
|
|
1242
|
+
if (jsonStem !== undefined)
|
|
1243
|
+
outputs.add(`${jsonStem}.json`);
|
|
1244
|
+
}
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
if (!isCompilerEmittableSourceExtension(extension))
|
|
1248
|
+
continue;
|
|
934
1249
|
if (emit.javascript &&
|
|
935
|
-
emit.outDir
|
|
936
|
-
|
|
937
|
-
(!isJavaScriptSourceExtension(extension) ||
|
|
1250
|
+
(emit.outDir !== undefined ||
|
|
1251
|
+
!isJavaScriptSourceExtension(extension) ||
|
|
938
1252
|
(extension === ".jsx" && emit.jsx !== "preserve"))) {
|
|
939
|
-
const javascriptExtension = extension === ".mts"
|
|
1253
|
+
const javascriptExtension = extension === ".mts" || extension === ".mjs"
|
|
940
1254
|
? ".mjs"
|
|
941
|
-
: extension === ".cts"
|
|
1255
|
+
: extension === ".cts" || extension === ".cjs"
|
|
942
1256
|
? ".cjs"
|
|
943
|
-
: extension === ".tsx"
|
|
1257
|
+
: (extension === ".tsx" || extension === ".jsx") &&
|
|
1258
|
+
emit.jsx === "preserve"
|
|
944
1259
|
? ".jsx"
|
|
945
1260
|
: ".js";
|
|
946
|
-
const
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
outputs.add(
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1261
|
+
const javascriptStem = mappedStem(emit.outDir, true);
|
|
1262
|
+
if (javascriptStem !== undefined) {
|
|
1263
|
+
const javascript = javascriptStem + javascriptExtension;
|
|
1264
|
+
outputs.add(javascript);
|
|
1265
|
+
if (emit.sourceMap)
|
|
1266
|
+
outputs.add(`${javascript}.map`);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
if (emit.declaration) {
|
|
955
1270
|
const declarationExtension = extension === ".mts" || extension === ".mjs"
|
|
956
1271
|
? ".d.mts"
|
|
957
1272
|
: extension === ".cts" || extension === ".cjs"
|
|
958
1273
|
? ".d.cts"
|
|
959
1274
|
: ".d.ts";
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
outputs.add(
|
|
1275
|
+
const declarationStem = mappedStem(emit.declarationDir ?? emit.outDir, true);
|
|
1276
|
+
if (declarationStem !== undefined) {
|
|
1277
|
+
const declaration = declarationStem + declarationExtension;
|
|
1278
|
+
outputs.add(declaration);
|
|
1279
|
+
if (emit.declarationMap)
|
|
1280
|
+
outputs.add(`${declaration}.map`);
|
|
1281
|
+
}
|
|
964
1282
|
}
|
|
965
1283
|
}
|
|
966
1284
|
return [...outputs];
|
|
@@ -1013,16 +1331,16 @@ function effectiveCompilerEmit(project, options) {
|
|
|
1013
1331
|
compilerOptions.declarationMap === true);
|
|
1014
1332
|
const cliOutDir = passthroughPathOption(passthrough, "--outDir");
|
|
1015
1333
|
const cliDeclarationDir = passthroughPathOption(passthrough, "--declarationDir");
|
|
1016
|
-
const cliOutFile = passthroughPathOption(passthrough, "--outFile");
|
|
1017
1334
|
const cliRootDir = passthroughPathOption(passthrough, "--rootDir");
|
|
1018
1335
|
const cliTsBuildInfoFile = passthroughPathOption(passthrough, "--tsBuildInfoFile");
|
|
1019
1336
|
const jsx = passthroughStringOption(passthrough, "--jsx") ?? compilerOptions.jsx;
|
|
1337
|
+
const compilerCwd = project.root;
|
|
1020
1338
|
return {
|
|
1021
1339
|
declaration,
|
|
1022
1340
|
declarationDir: cliDeclarationDir === null
|
|
1023
1341
|
? undefined
|
|
1024
1342
|
: cliDeclarationDir !== undefined
|
|
1025
|
-
? node_path_1.default.resolve(
|
|
1343
|
+
? node_path_1.default.resolve(compilerCwd, cliDeclarationDir)
|
|
1026
1344
|
: typeof compilerOptions.declarationDir === "string"
|
|
1027
1345
|
? node_path_1.default.resolve(compilerOptions.declarationDir)
|
|
1028
1346
|
: undefined,
|
|
@@ -1032,23 +1350,18 @@ function effectiveCompilerEmit(project, options) {
|
|
|
1032
1350
|
outDir: cliOutDir === null
|
|
1033
1351
|
? undefined
|
|
1034
1352
|
: cliOutDir !== undefined
|
|
1035
|
-
? node_path_1.default.resolve(
|
|
1353
|
+
? node_path_1.default.resolve(compilerCwd, cliOutDir)
|
|
1036
1354
|
: options.outDir !== undefined
|
|
1037
1355
|
? node_path_1.default.resolve(options.cwd, options.outDir)
|
|
1038
1356
|
: typeof compilerOptions.outDir === "string"
|
|
1039
1357
|
? node_path_1.default.resolve(compilerOptions.outDir)
|
|
1040
1358
|
: undefined,
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
: cliOutFile !== undefined
|
|
1044
|
-
? node_path_1.default.resolve(options.cwd, cliOutFile)
|
|
1045
|
-
: typeof compilerOptions.outFile === "string"
|
|
1046
|
-
? node_path_1.default.resolve(compilerOptions.outFile)
|
|
1047
|
-
: undefined,
|
|
1359
|
+
resolveJsonModule: passthroughBooleanOption(passthrough, "--resolveJsonModule") ??
|
|
1360
|
+
compilerOptions.resolveJsonModule === true,
|
|
1048
1361
|
rootDir: cliRootDir === null
|
|
1049
1362
|
? undefined
|
|
1050
1363
|
: cliRootDir !== undefined
|
|
1051
|
-
? node_path_1.default.resolve(
|
|
1364
|
+
? node_path_1.default.resolve(compilerCwd, cliRootDir)
|
|
1052
1365
|
: typeof compilerOptions.rootDir === "string"
|
|
1053
1366
|
? node_path_1.default.resolve(compilerOptions.rootDir)
|
|
1054
1367
|
: undefined,
|
|
@@ -1056,7 +1369,7 @@ function effectiveCompilerEmit(project, options) {
|
|
|
1056
1369
|
tsBuildInfoFile: cliTsBuildInfoFile === null
|
|
1057
1370
|
? undefined
|
|
1058
1371
|
: cliTsBuildInfoFile !== undefined
|
|
1059
|
-
? node_path_1.default.resolve(
|
|
1372
|
+
? node_path_1.default.resolve(compilerCwd, cliTsBuildInfoFile)
|
|
1060
1373
|
: typeof compilerOptions.tsBuildInfoFile === "string"
|
|
1061
1374
|
? node_path_1.default.resolve(compilerOptions.tsBuildInfoFile)
|
|
1062
1375
|
: undefined,
|
|
@@ -1124,10 +1437,6 @@ function passthroughOptionMatches(token, name) {
|
|
|
1124
1437
|
return false;
|
|
1125
1438
|
return (0, schema_1.resolveFlagSpec)(token)?.name === (0, schema_1.resolveFlagSpec)(name)?.name;
|
|
1126
1439
|
}
|
|
1127
|
-
function isCompilerOutput(file, outputs) {
|
|
1128
|
-
return (outputs.files.some((output) => pathKey(output) === pathKey(file)) ||
|
|
1129
|
-
outputs.directories.some((directory) => isPathWithin(directory, file)));
|
|
1130
|
-
}
|
|
1131
1440
|
function collectTopologyDirectories(files, roots) {
|
|
1132
1441
|
const directories = new Map();
|
|
1133
1442
|
for (const root of roots) {
|
|
@@ -1191,9 +1500,13 @@ function isDirectory(location) {
|
|
|
1191
1500
|
return false;
|
|
1192
1501
|
}
|
|
1193
1502
|
}
|
|
1194
|
-
function syncWatchers(watchers, desired, create, onError) {
|
|
1503
|
+
function syncWatchers(watchers, desired, create, onError, shouldContinue = () => true) {
|
|
1195
1504
|
let complete = true;
|
|
1196
1505
|
for (const [key, location] of desired) {
|
|
1506
|
+
if (!shouldContinue()) {
|
|
1507
|
+
complete = false;
|
|
1508
|
+
break;
|
|
1509
|
+
}
|
|
1197
1510
|
if (watchers.has(key))
|
|
1198
1511
|
continue;
|
|
1199
1512
|
try {
|
|
@@ -1412,19 +1725,23 @@ function projectInputRecursiveWatchRoot(target, projectRoot, identities = (0, pr
|
|
|
1412
1725
|
return undefined;
|
|
1413
1726
|
}
|
|
1414
1727
|
/**
|
|
1415
|
-
*
|
|
1728
|
+
* Cheap content and physical-owner identities for a tracked file.
|
|
1416
1729
|
*
|
|
1417
|
-
* Modification time and size
|
|
1418
|
-
*
|
|
1419
|
-
* answers
|
|
1730
|
+
* Modification time and size answer "did the bytes move" without reading the
|
|
1731
|
+
* file. Device and inode answer whether a POSIX per-file watcher still owns the
|
|
1732
|
+
* path. Keeping the answers separate lets an identical atomic replacement
|
|
1733
|
+
* rebind its watcher without inventing a compiler notification.
|
|
1420
1734
|
*/
|
|
1421
|
-
function
|
|
1735
|
+
function compilerFileSnapshot(location) {
|
|
1422
1736
|
try {
|
|
1423
1737
|
const stats = node_fs_1.default.statSync(location);
|
|
1424
|
-
return
|
|
1738
|
+
return {
|
|
1739
|
+
content: `${stats.mtimeMs}:${stats.size}`,
|
|
1740
|
+
owner: `${stats.dev}:${stats.ino}`,
|
|
1741
|
+
};
|
|
1425
1742
|
}
|
|
1426
1743
|
catch {
|
|
1427
|
-
return "";
|
|
1744
|
+
return { content: "", owner: "" };
|
|
1428
1745
|
}
|
|
1429
1746
|
}
|
|
1430
1747
|
function isSymbolicLink(location) {
|
|
@@ -1613,14 +1930,15 @@ function projectInputReloadEventShouldNotify(input) {
|
|
|
1613
1930
|
// resolution moved. A missed reselection is a wrong answer; a spare restart is
|
|
1614
1931
|
// a slow one.
|
|
1615
1932
|
const explains = (directory) => {
|
|
1616
|
-
|
|
1617
|
-
return false;
|
|
1933
|
+
const causes = input.causedBy ?? (input.changed === undefined ? [] : [input.changed]);
|
|
1618
1934
|
// Only an immediate entry can move this directory's digest, so only an
|
|
1619
1935
|
// immediate entry can account for the delta. Asking whether the event was
|
|
1620
1936
|
// data somewhere else answers a different directory's question, and one
|
|
1621
1937
|
// exemption would then cancel every other directory's evidence.
|
|
1622
|
-
|
|
1623
|
-
|
|
1938
|
+
return causes.some((location) => {
|
|
1939
|
+
const parent = identities.resolve(node_path_1.default.dirname(location)).key;
|
|
1940
|
+
return parent === directory.key && exemptedFrom(directory, location);
|
|
1941
|
+
});
|
|
1624
1942
|
};
|
|
1625
1943
|
const isReloadDirectoryEvent = (location) => namesDirectory(location) || holdsAsImmediateEntry(location);
|
|
1626
1944
|
const isReloadDirectoryDelta = (location) => {
|
|
@@ -1673,7 +1991,7 @@ function projectInputPathMayAffectProgram(location) {
|
|
|
1673
1991
|
const extension = node_path_1.default.extname(location).toLowerCase();
|
|
1674
1992
|
return extension === ".json" || isCompilerEmittableSourceExtension(extension);
|
|
1675
1993
|
}
|
|
1676
|
-
function
|
|
1994
|
+
function compilerMembershipChange(previous, next) {
|
|
1677
1995
|
const changed = new Map();
|
|
1678
1996
|
for (const [key, location] of previous) {
|
|
1679
1997
|
if (next.has(key) === false)
|
|
@@ -1683,54 +2001,84 @@ function projectInputCompilerMembershipChange(snapshot, previous, next) {
|
|
|
1683
2001
|
if (previous.has(key) === false)
|
|
1684
2002
|
changed.set(key, location);
|
|
1685
2003
|
}
|
|
1686
|
-
if (changed.size === 0 ||
|
|
1687
|
-
[...changed.values()].some((location) => matchesProjectInput(snapshot, location) === false ||
|
|
1688
|
-
projectInputPathMayAffectProgram(location) === false)) {
|
|
1689
|
-
return undefined;
|
|
1690
|
-
}
|
|
1691
2004
|
return [...changed.values()].sort();
|
|
1692
2005
|
}
|
|
2006
|
+
function projectInputCompilerMembershipChange(snapshot, changed) {
|
|
2007
|
+
return changed.filter((location) => matchesProjectInput(snapshot, location) &&
|
|
2008
|
+
projectInputPathMayAffectProgram(location));
|
|
2009
|
+
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Name a compiler-membership transition as the recursive project watcher does.
|
|
2012
|
+
*
|
|
2013
|
+
* Creating the first member below a missing glob root makes Windows report the
|
|
2014
|
+
* root directory before it reports the member. Reload-directory fingerprints
|
|
2015
|
+
* must see that same causal path: the new root then explains its ancestor's
|
|
2016
|
+
* immediate-entry delta instead of turning ordinary project data into a cold
|
|
2017
|
+
* selection reload. The deepest matching glob owns the most specific delivery.
|
|
2018
|
+
*/
|
|
2019
|
+
function projectInputCompilerMembershipProjectChanges(locations, globs) {
|
|
2020
|
+
const identities = (0, projectInputPathIdentity_1.createProjectInputPathIdentityContext)();
|
|
2021
|
+
const roots = globs.map((glob) => identities.resolve(literalGlobRoot(glob)));
|
|
2022
|
+
const changes = new Map();
|
|
2023
|
+
for (const location of locations) {
|
|
2024
|
+
const candidate = identities.resolve(location);
|
|
2025
|
+
const root = roots
|
|
2026
|
+
.filter((entry) => identities.isWithin(entry.path, candidate.path))
|
|
2027
|
+
.sort((left, right) => right.path.length - left.path.length)[0];
|
|
2028
|
+
const change = root ?? candidate;
|
|
2029
|
+
changes.set(change.key, change.path);
|
|
2030
|
+
}
|
|
2031
|
+
return [...changes.values()].sort();
|
|
2032
|
+
}
|
|
1693
2033
|
function fingerprintProjectInputMatches(matches) {
|
|
1694
2034
|
const fingerprints = new Map();
|
|
1695
2035
|
for (const [key, location] of matches) {
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
: node_crypto_1.default
|
|
1700
|
-
.createHash("sha256")
|
|
1701
|
-
.update(node_fs_1.default.readFileSync(location))
|
|
1702
|
-
.digest("hex"));
|
|
1703
|
-
}
|
|
1704
|
-
catch {
|
|
1705
|
-
fingerprints.set(key, "");
|
|
1706
|
-
}
|
|
2036
|
+
fingerprints.set(key, isDirectory(location)
|
|
2037
|
+
? fingerprintProjectInputDirectory(location)
|
|
2038
|
+
: fingerprintProjectInputFile(location));
|
|
1707
2039
|
}
|
|
1708
2040
|
return fingerprints;
|
|
1709
2041
|
}
|
|
2042
|
+
function fingerprintProjectInputFile(location) {
|
|
2043
|
+
try {
|
|
2044
|
+
return node_crypto_1.default
|
|
2045
|
+
.createHash("sha256")
|
|
2046
|
+
.update(node_fs_1.default.readFileSync(location))
|
|
2047
|
+
.digest("hex");
|
|
2048
|
+
}
|
|
2049
|
+
catch {
|
|
2050
|
+
return "";
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
1710
2053
|
function fingerprintProjectInputDirectory(location) {
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
2054
|
+
try {
|
|
2055
|
+
const entries = node_fs_1.default
|
|
2056
|
+
.readdirSync(location, { withFileTypes: true })
|
|
2057
|
+
.map((entry) => {
|
|
2058
|
+
const kind = entry.isDirectory()
|
|
2059
|
+
? "directory"
|
|
2060
|
+
: entry.isFile()
|
|
2061
|
+
? "file"
|
|
2062
|
+
: entry.isSymbolicLink()
|
|
2063
|
+
? "symlink"
|
|
2064
|
+
: "other";
|
|
2065
|
+
let target = "";
|
|
2066
|
+
if (entry.isSymbolicLink()) {
|
|
2067
|
+
try {
|
|
2068
|
+
target = node_fs_1.default.readlinkSync(node_path_1.default.join(location, entry.name));
|
|
2069
|
+
}
|
|
2070
|
+
catch {
|
|
2071
|
+
target = "<unreadable>";
|
|
2072
|
+
}
|
|
1728
2073
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
.
|
|
1733
|
-
|
|
2074
|
+
return entry.name + "\0" + kind + "\0" + target;
|
|
2075
|
+
})
|
|
2076
|
+
.sort();
|
|
2077
|
+
return node_crypto_1.default.createHash("sha256").update(entries.join("\0")).digest("hex");
|
|
2078
|
+
}
|
|
2079
|
+
catch {
|
|
2080
|
+
return "";
|
|
2081
|
+
}
|
|
1734
2082
|
}
|
|
1735
2083
|
function matchesProjectInputGlob(pattern, location, identities = (0, projectInputPathIdentity_1.createProjectInputPathIdentityContext)()) {
|
|
1736
2084
|
const root = identities.resolve(literalGlobRoot(pattern));
|