ttsc 0.26.2 → 0.27.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/README.md +9 -0
- package/cmd/ttsc/api_transform.go +17 -3
- package/cmd/ttscgraph/auxiliary_identity_state_tracks_retargets_not_source_contents_test.go +85 -0
- package/cmd/ttscgraph/disk_realpath_other.go +16 -0
- package/cmd/ttscgraph/disk_realpath_windows.go +56 -0
- package/cmd/ttscgraph/serve.go +104 -35
- package/driver/emit_plugin.go +20 -3
- package/driver/graph.go +64 -11
- package/driver/input_observation.go +273 -0
- package/driver/input_observation_test.go +149 -0
- package/driver/plugins.go +384 -4
- package/driver/program.go +54 -1
- package/driver/resolution_candidates.go +13 -5
- package/driver/rewrite.go +45 -2
- package/lib/TtscCompiler.d.ts +12 -11
- package/lib/TtscCompiler.js +44 -29
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/captureProcessOutput.js +2 -2
- package/lib/compiler/internal/captureProcessOutput.js.map +1 -1
- package/lib/compiler/internal/compileProjectInMemory.js +4 -3
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.js +3 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +1 -1
- package/lib/compiler/internal/runBuild.js +80 -34
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.js +2 -2
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +28 -0
- package/lib/compiler/internal/sharedHostHelpers.js +39 -0
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.js +12 -4
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.d.ts +3 -0
- package/lib/compiler/internal/transformProjectInMemory.js +239 -35
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/schema.js +12 -5
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/createCanonicalTempDirectory.d.ts +17 -0
- package/lib/internal/createCanonicalTempDirectory.js +42 -0
- package/lib/internal/createCanonicalTempDirectory.js.map +1 -0
- package/lib/internal/projectInputPathIdentity.d.ts +3 -0
- package/lib/internal/projectInputPathIdentity.js +11 -5
- package/lib/internal/projectInputPathIdentity.js.map +1 -1
- package/lib/internal/resolveNodeBinary.d.ts +14 -0
- package/lib/internal/resolveNodeBinary.js +150 -0
- package/lib/internal/resolveNodeBinary.js.map +1 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.d.ts +15 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.js +61 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.js.map +1 -0
- package/lib/launcher/internal/prepareExecution.d.ts +3 -0
- package/lib/launcher/internal/prepareExecution.js +42 -14
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +25 -13
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +18 -3
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +2 -0
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +37 -1
- package/lib/launcher/internal/runtimeHooks.js +653 -62
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.d.ts +62 -8
- package/lib/plugin/internal/buildSourcePlugin.js +754 -72
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +17 -5
- package/lib/plugin/internal/loadProjectPlugins.js +1339 -77
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/register.d.ts +1 -0
- package/lib/register.js +46 -0
- package/lib/register.js.map +1 -0
- package/lib/structures/ITtscCompilerContext.d.ts +6 -3
- package/lib/structures/ITtscCompilerTransformation.d.ts +31 -0
- package/lib/structures/ITtscPlugin.d.ts +33 -0
- package/package.json +16 -9
- package/shim/checker/go.mod +9 -0
- package/shim/checker/go.sum +22 -0
- package/shim/checker/shim.go +75 -0
- package/shim/compiler/incremental.go +45 -8
- package/src/TtscCompiler.ts +55 -33
- package/src/compiler/internal/captureProcessOutput.ts +3 -2
- package/src/compiler/internal/compileProjectInMemory.ts +4 -3
- package/src/compiler/internal/resolveEmittedJavaScript.ts +6 -0
- package/src/compiler/internal/runBuild.ts +102 -39
- package/src/compiler/internal/runSingleFileEmit.ts +2 -2
- package/src/compiler/internal/sharedHostHelpers.ts +44 -0
- package/src/compiler/internal/startResidentTransform.ts +12 -3
- package/src/compiler/internal/transformProjectInMemory.ts +410 -39
- package/src/flags/schema.ts +12 -5
- package/src/internal/createCanonicalTempDirectory.ts +53 -0
- package/src/internal/projectInputPathIdentity.ts +17 -4
- package/src/internal/resolveNodeBinary.ts +173 -0
- package/src/internal/resolveSafeCacheCleanupTargets.ts +85 -0
- package/src/launcher/internal/prepareExecution.ts +50 -14
- package/src/launcher/internal/runTtsc.ts +24 -13
- package/src/launcher/internal/runTtscserver.ts +18 -3
- package/src/launcher/internal/runTtsx.ts +2 -0
- package/src/launcher/internal/runtimeHooks.ts +823 -66
- package/src/plugin/internal/buildSourcePlugin.ts +984 -79
- package/src/plugin/internal/loadProjectPlugins.ts +1718 -104
- package/src/register.ts +47 -0
- package/src/structures/ITtscCompilerContext.ts +6 -3
- package/src/structures/ITtscCompilerTransformation.ts +39 -0
- package/src/structures/ITtscPlugin.ts +36 -0
- package/tools/shim_audit/baseline.json +0 -1
- package/utility/host.go +9 -3
- package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +0 -5
- package/lib/internal/assertSafeExplicitCacheDirectory.js +0 -26
- package/lib/internal/assertSafeExplicitCacheDirectory.js.map +0 -1
- package/src/internal/assertSafeExplicitCacheDirectory.ts +0 -29
|
@@ -10,7 +10,9 @@ exports.entryModuleFormat = entryModuleFormat;
|
|
|
10
10
|
exports.restoreStrippedNodeBuiltinScheme = restoreStrippedNodeBuiltinScheme;
|
|
11
11
|
exports.projectModuleOptions = projectModuleOptions;
|
|
12
12
|
exports.isWithin = isWithin;
|
|
13
|
+
exports.dependencyCacheKey = dependencyCacheKey;
|
|
13
14
|
exports.readDependencyCache = readDependencyCache;
|
|
15
|
+
exports.dependencyCacheRoot = dependencyCacheRoot;
|
|
14
16
|
exports.acquireDependencyBuildLock = acquireDependencyBuildLock;
|
|
15
17
|
exports.releaseDependencyBuildLock = releaseDependencyBuildLock;
|
|
16
18
|
exports.reclaimDependencyBuildLock = reclaimDependencyBuildLock;
|
|
@@ -27,6 +29,7 @@ const resolveEmittedJavaScript_1 = require("../../compiler/internal/resolveEmitt
|
|
|
27
29
|
const resolveTsgo_1 = require("../../compiler/internal/resolveTsgo");
|
|
28
30
|
const runBuild_1 = require("../../compiler/internal/runBuild");
|
|
29
31
|
const spawnNative_1 = require("../../compiler/internal/spawnNative");
|
|
32
|
+
const createCanonicalTempDirectory_1 = require("../../internal/createCanonicalTempDirectory");
|
|
30
33
|
const projectInputPathIdentity_1 = require("../../internal/projectInputPathIdentity");
|
|
31
34
|
const servedSourceMap_1 = require("./servedSourceMap");
|
|
32
35
|
/**
|
|
@@ -67,25 +70,39 @@ const RESOLVABLE_EXTENSIONS = [
|
|
|
67
70
|
".mjs",
|
|
68
71
|
".cjs",
|
|
69
72
|
];
|
|
73
|
+
/** Union of the ttsx rescue probes and Node's built-in CommonJS probes. */
|
|
74
|
+
const DESCRIPTOR_PROBE_EXTENSIONS = [
|
|
75
|
+
...RESOLVABLE_EXTENSIONS,
|
|
76
|
+
".json",
|
|
77
|
+
".node",
|
|
78
|
+
];
|
|
70
79
|
/** TypeScript source extensions these hooks compile. */
|
|
71
80
|
const TYPESCRIPT_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts"];
|
|
72
|
-
let
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
let environmentManifestCache;
|
|
82
|
+
const registeredManifests = [];
|
|
83
|
+
function environmentManifest() {
|
|
84
|
+
if (environmentManifestCache !== undefined) {
|
|
85
|
+
return environmentManifestCache;
|
|
76
86
|
}
|
|
77
87
|
const file = process.env.TTSX_RUNTIME_MANIFEST;
|
|
78
88
|
if (file === undefined || file.length === 0) {
|
|
79
|
-
|
|
80
|
-
return
|
|
89
|
+
environmentManifestCache = null;
|
|
90
|
+
return environmentManifestCache;
|
|
81
91
|
}
|
|
82
92
|
try {
|
|
83
|
-
|
|
93
|
+
environmentManifestCache = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
|
|
84
94
|
}
|
|
85
95
|
catch {
|
|
86
|
-
|
|
96
|
+
environmentManifestCache = null;
|
|
87
97
|
}
|
|
88
|
-
return
|
|
98
|
+
return environmentManifestCache;
|
|
99
|
+
}
|
|
100
|
+
/** Every checked entry emit available to this runtime, in ownership order. */
|
|
101
|
+
function runtimeManifests() {
|
|
102
|
+
const inherited = environmentManifest();
|
|
103
|
+
return inherited === null
|
|
104
|
+
? registeredManifests
|
|
105
|
+
: [inherited, ...registeredManifests];
|
|
89
106
|
}
|
|
90
107
|
/**
|
|
91
108
|
* Lowest Node.js the ttsx source runtime supports. The synchronous
|
|
@@ -154,6 +171,7 @@ function assertNodeRuntimeSupport() {
|
|
|
154
171
|
}
|
|
155
172
|
}
|
|
156
173
|
let installed = false;
|
|
174
|
+
let prepareRuntimeEntry;
|
|
157
175
|
/**
|
|
158
176
|
* Install the source-loading hooks on the current (main) thread. Idempotent:
|
|
159
177
|
* the bootstrap installs them for the entry process, and `NODE_OPTIONS`
|
|
@@ -167,7 +185,10 @@ let installed = false;
|
|
|
167
185
|
* graph goes through `Module._extensions` — the canonical loader extension
|
|
168
186
|
* point `ts-node`/`tsx` use for the same reason.
|
|
169
187
|
*/
|
|
170
|
-
function installRuntimeHooks() {
|
|
188
|
+
function installRuntimeHooks(options = {}) {
|
|
189
|
+
if (options.prepareEntry !== undefined) {
|
|
190
|
+
prepareRuntimeEntry = options.prepareEntry;
|
|
191
|
+
}
|
|
171
192
|
if (installed) {
|
|
172
193
|
return;
|
|
173
194
|
}
|
|
@@ -191,7 +212,8 @@ function installRuntimeHooks() {
|
|
|
191
212
|
function installCommonJsHook() {
|
|
192
213
|
const extensions = node_module_1.Module._extensions;
|
|
193
214
|
const compile = (module, filename) => {
|
|
194
|
-
module.
|
|
215
|
+
const parent = module.parent?.filename;
|
|
216
|
+
module._compile(resolveServedSource(filename, (0, node_url_1.pathToFileURL)(filename).href, typeof parent !== "string" || !isTypeScriptSource(parent)).source, filename);
|
|
195
217
|
};
|
|
196
218
|
for (const extension of [".ts", ".tsx", ".cts"]) {
|
|
197
219
|
extensions[extension] = compile;
|
|
@@ -204,11 +226,14 @@ function installCommonJsHook() {
|
|
|
204
226
|
* `require` or an ESM `import`.
|
|
205
227
|
*/
|
|
206
228
|
function entryModuleFormat(entryFile) {
|
|
207
|
-
const
|
|
208
|
-
|
|
229
|
+
const real = realPath(entryFile);
|
|
230
|
+
const owner = findEntryEmit(real)?.manifest;
|
|
231
|
+
return moduleFormat(entryFile, owner === undefined ? null : (owner.moduleOptions ?? {})) === "module"
|
|
209
232
|
? "module"
|
|
210
233
|
: "commonjs";
|
|
211
234
|
}
|
|
235
|
+
/** TypeScript URLs resolved at a JavaScript-to-TypeScript entry boundary. */
|
|
236
|
+
const runtimeEntryUrls = new Set();
|
|
212
237
|
/**
|
|
213
238
|
* Rescue an extensionless or directory relative specifier that Node's resolver
|
|
214
239
|
* rejected. Only runs after `nextResolve` throws, so a successful resolution is
|
|
@@ -216,17 +241,344 @@ function entryModuleFormat(entryFile) {
|
|
|
216
241
|
* original error is rethrown, preserving `ERR_MODULE_NOT_FOUND`.
|
|
217
242
|
*/
|
|
218
243
|
function resolve(specifier, context, nextResolve) {
|
|
244
|
+
recordPluginDescriptorResolutionCandidates(specifier, context.parentURL);
|
|
219
245
|
try {
|
|
220
|
-
|
|
246
|
+
const result = rememberRuntimeEntry(rememberCommonJsNamedInterop(restoreStrippedNodeBuiltinScheme(specifier, nextResolve(specifier, context)), context), context);
|
|
247
|
+
recordPluginDescriptorResolution(specifier, context.parentURL, result.url);
|
|
248
|
+
return result;
|
|
221
249
|
}
|
|
222
250
|
catch (error) {
|
|
223
251
|
const rescued = probeRescuableSpecifier(specifier, context.parentURL);
|
|
224
252
|
if (rescued === null) {
|
|
225
253
|
throw error;
|
|
226
254
|
}
|
|
227
|
-
|
|
255
|
+
const result = rememberRuntimeEntry(rememberCommonJsNamedInterop({ shortCircuit: true, url: rescued }, context), context);
|
|
256
|
+
recordPluginDescriptorResolution(specifier, context.parentURL, result.url);
|
|
257
|
+
return result;
|
|
228
258
|
}
|
|
229
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Fingerprint every path whose state can redirect one descriptor import.
|
|
262
|
+
*
|
|
263
|
+
* This runs before the real resolver. Reporting candidates only after the
|
|
264
|
+
* descriptor finished would pair an earlier descriptor result with later file
|
|
265
|
+
* state when a higher-priority candidate appeared during evaluation.
|
|
266
|
+
*/
|
|
267
|
+
function recordPluginDescriptorResolutionCandidates(specifier, parentURL) {
|
|
268
|
+
if (process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE !== "1")
|
|
269
|
+
return;
|
|
270
|
+
if ((0, node_module_1.isBuiltin)(specifier) || specifier.startsWith("node:"))
|
|
271
|
+
return;
|
|
272
|
+
const parent = runtimeFilePath(parentURL);
|
|
273
|
+
if (parent === undefined)
|
|
274
|
+
return;
|
|
275
|
+
const recorded = new Set();
|
|
276
|
+
const record = (candidate) => {
|
|
277
|
+
const resolved = node_path_1.default.resolve(candidate);
|
|
278
|
+
if (recorded.has(resolved))
|
|
279
|
+
return;
|
|
280
|
+
recorded.add(resolved);
|
|
281
|
+
recordPluginDescriptorInput({
|
|
282
|
+
parent,
|
|
283
|
+
resolved,
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
const recordManifestTargets = (value, directory, allowBare = false) => {
|
|
287
|
+
if (typeof value === "string") {
|
|
288
|
+
if (value !== "" &&
|
|
289
|
+
(allowBare || value.startsWith("./") || value.startsWith("../"))) {
|
|
290
|
+
recordBase(node_path_1.default.resolve(directory, value));
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (Array.isArray(value)) {
|
|
295
|
+
for (const item of value) {
|
|
296
|
+
recordManifestTargets(item, directory, allowBare);
|
|
297
|
+
}
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (value !== null && typeof value === "object") {
|
|
301
|
+
for (const item of Object.values(value)) {
|
|
302
|
+
recordManifestTargets(item, directory, allowBare);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const recordPackageManifests = (file) => {
|
|
307
|
+
for (let directory = node_path_1.default.dirname(file);; directory = node_path_1.default.dirname(directory)) {
|
|
308
|
+
const manifest = node_path_1.default.join(directory, "package.json");
|
|
309
|
+
record(manifest);
|
|
310
|
+
if (isFile(manifest))
|
|
311
|
+
return;
|
|
312
|
+
const parentDirectory = node_path_1.default.dirname(directory);
|
|
313
|
+
if (parentDirectory === directory)
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
const localBases = (value) => {
|
|
318
|
+
if (value.startsWith("file:"))
|
|
319
|
+
return [(0, node_url_1.fileURLToPath)(value)];
|
|
320
|
+
const raw = node_path_1.default.resolve(node_path_1.default.dirname(parent), value);
|
|
321
|
+
const suffixStart = value.search(/[?#]/);
|
|
322
|
+
if (suffixStart === -1)
|
|
323
|
+
return [raw];
|
|
324
|
+
const pathname = value.slice(0, suffixStart);
|
|
325
|
+
return pathname === ""
|
|
326
|
+
? [raw]
|
|
327
|
+
: [...new Set([raw, node_path_1.default.resolve(node_path_1.default.dirname(parent), pathname)])];
|
|
328
|
+
};
|
|
329
|
+
const bases = new Set();
|
|
330
|
+
const recordBase = (base) => {
|
|
331
|
+
const resolvedBase = node_path_1.default.resolve(base);
|
|
332
|
+
if (bases.has(resolvedBase))
|
|
333
|
+
return;
|
|
334
|
+
bases.add(resolvedBase);
|
|
335
|
+
record(resolvedBase);
|
|
336
|
+
for (const candidate of typescriptSourcesForJavaScriptSpecifier(resolvedBase)) {
|
|
337
|
+
record(candidate);
|
|
338
|
+
}
|
|
339
|
+
for (const extension of DESCRIPTOR_PROBE_EXTENSIONS) {
|
|
340
|
+
record(resolvedBase + extension);
|
|
341
|
+
}
|
|
342
|
+
const manifestFile = node_path_1.default.join(resolvedBase, "package.json");
|
|
343
|
+
record(manifestFile);
|
|
344
|
+
for (const extension of DESCRIPTOR_PROBE_EXTENSIONS) {
|
|
345
|
+
record(node_path_1.default.join(resolvedBase, `index${extension}`));
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
const manifest = JSON.parse(node_fs_1.default.readFileSync(manifestFile, "utf8").replace(/^\uFEFF/, ""));
|
|
349
|
+
recordManifestTargets(manifest.exports, resolvedBase);
|
|
350
|
+
recordManifestTargets(manifest.module, resolvedBase, true);
|
|
351
|
+
recordManifestTargets(manifest.main, resolvedBase, true);
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
// The real resolver owns malformed package diagnostics.
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
if (specifier.startsWith(".") ||
|
|
358
|
+
node_path_1.default.isAbsolute(specifier) ||
|
|
359
|
+
specifier.startsWith("file:")) {
|
|
360
|
+
try {
|
|
361
|
+
for (const base of localBases(specifier)) {
|
|
362
|
+
recordPackageManifests(base);
|
|
363
|
+
if (isFile(base))
|
|
364
|
+
record(base);
|
|
365
|
+
else
|
|
366
|
+
recordBase(base);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
// The real resolver owns invalid URL spellings.
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const parts = specifier.split("/");
|
|
375
|
+
const packageParts = parts[0]?.startsWith("@")
|
|
376
|
+
? parts.slice(0, 2)
|
|
377
|
+
: parts.slice(0, 1);
|
|
378
|
+
if (packageParts.some((part) => part === undefined || part === ""))
|
|
379
|
+
return;
|
|
380
|
+
const packageName = packageParts.join("/");
|
|
381
|
+
const subpath = parts.slice(packageParts.length);
|
|
382
|
+
for (const searchPath of (0, node_module_1.createRequire)(parent).resolve.paths(specifier) ??
|
|
383
|
+
[]) {
|
|
384
|
+
const packageDirectory = node_path_1.default.join(searchPath, packageName);
|
|
385
|
+
recordBase(packageDirectory);
|
|
386
|
+
if (subpath.length !== 0) {
|
|
387
|
+
recordBase(node_path_1.default.join(packageDirectory, ...subpath));
|
|
388
|
+
}
|
|
389
|
+
// CommonJS resolution continues past an existing but unusable package
|
|
390
|
+
// directory. Record every search root before the resolver runs so a
|
|
391
|
+
// farther selected package retains evaluation-time hashes for its
|
|
392
|
+
// superseding candidates.
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Report one resolved descriptor edge to the parent loader. The channel is
|
|
397
|
+
* armed by the generated descriptor shim only after ttsx's own bootstrap and
|
|
398
|
+
* imports have loaded, keeping compiler implementation files out of the
|
|
399
|
+
* project's persistent-cache inputs.
|
|
400
|
+
*/
|
|
401
|
+
function recordPluginDescriptorResolution(specifier, parentURL, resolvedURL) {
|
|
402
|
+
if (process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE !== "1")
|
|
403
|
+
return;
|
|
404
|
+
const out = process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_OUT;
|
|
405
|
+
if (out === undefined || out.length === 0)
|
|
406
|
+
return;
|
|
407
|
+
const resolved = runtimeFilePath(resolvedURL);
|
|
408
|
+
if (resolved === undefined)
|
|
409
|
+
return;
|
|
410
|
+
const parent = runtimeFilePath(parentURL);
|
|
411
|
+
recordPluginDescriptorInput({
|
|
412
|
+
...(parent === undefined ? {} : { parent }),
|
|
413
|
+
resolved,
|
|
414
|
+
specifier,
|
|
415
|
+
});
|
|
416
|
+
for (let directory = node_path_1.default.dirname(resolved);; directory = node_path_1.default.dirname(directory)) {
|
|
417
|
+
const manifest = node_path_1.default.join(directory, "package.json");
|
|
418
|
+
recordPluginDescriptorInput({
|
|
419
|
+
...(parent === undefined ? {} : { parent }),
|
|
420
|
+
resolved: manifest,
|
|
421
|
+
});
|
|
422
|
+
if (isFile(manifest))
|
|
423
|
+
break;
|
|
424
|
+
const parentDirectory = node_path_1.default.dirname(directory);
|
|
425
|
+
if (parentDirectory === directory)
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function recordPluginDescriptorInput(record) {
|
|
430
|
+
if (process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE !== "1")
|
|
431
|
+
return;
|
|
432
|
+
const out = process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_OUT;
|
|
433
|
+
if (out === undefined || out.length === 0)
|
|
434
|
+
return;
|
|
435
|
+
recordPluginDescriptorInputOnce(record);
|
|
436
|
+
const resolved = node_path_1.default.resolve(record.resolved);
|
|
437
|
+
const parsed = node_path_1.default.parse(resolved);
|
|
438
|
+
let current = parsed.root;
|
|
439
|
+
const relative = node_path_1.default.relative(parsed.root, resolved);
|
|
440
|
+
for (const segment of relative.split(node_path_1.default.sep).slice(0, -1)) {
|
|
441
|
+
if (segment === "")
|
|
442
|
+
continue;
|
|
443
|
+
current = node_path_1.default.join(current, segment);
|
|
444
|
+
try {
|
|
445
|
+
if (node_fs_1.default.lstatSync(current).isSymbolicLink()) {
|
|
446
|
+
recordPluginDescriptorInputOnce({ resolved: current });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/** Record one path without recursively revisiting its lexical ancestors. */
|
|
455
|
+
function recordPluginDescriptorInputOnce(record) {
|
|
456
|
+
if (process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE !== "1")
|
|
457
|
+
return;
|
|
458
|
+
const out = process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_OUT;
|
|
459
|
+
if (out === undefined || out.length === 0)
|
|
460
|
+
return;
|
|
461
|
+
try {
|
|
462
|
+
const beforeSignature = pluginDescriptorInputMetadataSignature(record.resolved);
|
|
463
|
+
const observedHash = pluginDescriptorInputHash(record.resolved);
|
|
464
|
+
const observedRealpath = pluginDescriptorInputRealpath(record.resolved);
|
|
465
|
+
const afterSignature = pluginDescriptorInputMetadataSignature(record.resolved);
|
|
466
|
+
const unstable = record.unstable === true ||
|
|
467
|
+
beforeSignature === undefined ||
|
|
468
|
+
afterSignature === undefined ||
|
|
469
|
+
beforeSignature !== afterSignature ||
|
|
470
|
+
(record.hash !== undefined && record.hash !== observedHash) ||
|
|
471
|
+
(record.realpath !== undefined && record.realpath !== observedRealpath) ||
|
|
472
|
+
(record.signature !== undefined && record.signature !== afterSignature);
|
|
473
|
+
node_fs_1.default.appendFileSync(out, `${JSON.stringify({
|
|
474
|
+
...record,
|
|
475
|
+
hash: observedHash,
|
|
476
|
+
realpath: observedRealpath,
|
|
477
|
+
...(unstable ? { unstable: true } : { signature: afterSignature }),
|
|
478
|
+
})}\n`, "utf8");
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
// Dependency reporting is advisory to cache reuse; the selected entry is
|
|
482
|
+
// still retained by the parent if this best-effort side channel fails.
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function pluginDescriptorInputRealpath(file) {
|
|
486
|
+
try {
|
|
487
|
+
return node_fs_1.default.realpathSync.native(file);
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/** Metadata identity that exposes content-preserving A-B-A replacement. */
|
|
494
|
+
function pluginDescriptorInputMetadataSignature(file) {
|
|
495
|
+
const requested = node_path_1.default.resolve(file);
|
|
496
|
+
let current = requested;
|
|
497
|
+
for (;;) {
|
|
498
|
+
try {
|
|
499
|
+
const link = node_fs_1.default.lstatSync(current, { bigint: true });
|
|
500
|
+
let target = link;
|
|
501
|
+
if (link.isSymbolicLink()) {
|
|
502
|
+
try {
|
|
503
|
+
target = node_fs_1.default.statSync(current, { bigint: true });
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
// A broken link's own metadata cannot expose its target appearing
|
|
507
|
+
// and disappearing during evaluation. Decline cache proof instead.
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return [
|
|
512
|
+
node_path_1.default.relative(current, requested),
|
|
513
|
+
link.dev,
|
|
514
|
+
link.ino,
|
|
515
|
+
link.mode,
|
|
516
|
+
link.size,
|
|
517
|
+
link.mtimeNs,
|
|
518
|
+
link.ctimeNs,
|
|
519
|
+
target.dev,
|
|
520
|
+
target.ino,
|
|
521
|
+
target.mode,
|
|
522
|
+
target.size,
|
|
523
|
+
target.mtimeNs,
|
|
524
|
+
target.ctimeNs,
|
|
525
|
+
].join(":");
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
if (!isMissingPathError(error))
|
|
529
|
+
return undefined;
|
|
530
|
+
const parent = node_path_1.default.dirname(current);
|
|
531
|
+
if (parent === current)
|
|
532
|
+
return undefined;
|
|
533
|
+
current = parent;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function pluginDescriptorInputHash(file) {
|
|
538
|
+
try {
|
|
539
|
+
if (node_fs_1.default.statSync(file).isDirectory()) {
|
|
540
|
+
return pluginDescriptorDirectoryHash();
|
|
541
|
+
}
|
|
542
|
+
return node_crypto_1.default
|
|
543
|
+
.createHash("sha256")
|
|
544
|
+
.update(node_fs_1.default.readFileSync(file))
|
|
545
|
+
.digest("hex");
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
/** Stable public fingerprint for an existing directory candidate's kind. */
|
|
552
|
+
function pluginDescriptorDirectoryHash() {
|
|
553
|
+
return node_crypto_1.default
|
|
554
|
+
.createHash("sha256")
|
|
555
|
+
.update("ttsc:host-input:directory\0")
|
|
556
|
+
.digest("hex");
|
|
557
|
+
}
|
|
558
|
+
function runtimeFilePath(value) {
|
|
559
|
+
if (value === undefined)
|
|
560
|
+
return undefined;
|
|
561
|
+
if (value.startsWith("file:")) {
|
|
562
|
+
try {
|
|
563
|
+
return node_path_1.default.resolve((0, node_url_1.fileURLToPath)(value));
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
return undefined;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return node_path_1.default.isAbsolute(value) ? node_path_1.default.resolve(value) : undefined;
|
|
570
|
+
}
|
|
571
|
+
/** Remember an ESM root until its synchronous load hook prepares the project. */
|
|
572
|
+
function rememberRuntimeEntry(result, context) {
|
|
573
|
+
if (result.url.startsWith("file:") &&
|
|
574
|
+
isTypeScriptSource((0, node_url_1.fileURLToPath)(result.url)) &&
|
|
575
|
+
(context.parentURL === undefined ||
|
|
576
|
+
!context.parentURL.startsWith("file:") ||
|
|
577
|
+
!isTypeScriptSource((0, node_url_1.fileURLToPath)(context.parentURL)))) {
|
|
578
|
+
runtimeEntryUrls.add(result.url);
|
|
579
|
+
}
|
|
580
|
+
return result;
|
|
581
|
+
}
|
|
230
582
|
/**
|
|
231
583
|
* Restore a `node:` builtin URL when affected Node releases return the exact
|
|
232
584
|
* prefix-stripped spelling from their synchronous CommonJS resolver.
|
|
@@ -244,6 +596,12 @@ function restoreStrippedNodeBuiltinScheme(specifier, result) {
|
|
|
244
596
|
: result;
|
|
245
597
|
}
|
|
246
598
|
const builtProjects = new Map();
|
|
599
|
+
// A descriptor evaluator's dependency emit must never be reused by another
|
|
600
|
+
// process. PIDs are eventually recycled while the disk cache persists, so PID
|
|
601
|
+
// alone cannot provide that isolation. One cryptographically random process
|
|
602
|
+
// nonce keeps every evaluator generation distinct while `builtProjects` still
|
|
603
|
+
// shares repeated imports inside this process.
|
|
604
|
+
const descriptorProcessCacheNonce = node_crypto_1.default.randomBytes(16).toString("hex");
|
|
247
605
|
/** File URLs whose CommonJS source was reached from an ESM parent import. */
|
|
248
606
|
const commonJsNamedInteropUrls = new Set();
|
|
249
607
|
const commonJsNameScanSources = new Map();
|
|
@@ -255,15 +613,15 @@ function load(url, context, nextLoad) {
|
|
|
255
613
|
if (!isTypeScriptSource(filename)) {
|
|
256
614
|
return nextLoad(url, context);
|
|
257
615
|
}
|
|
258
|
-
const { format, source } = resolveRuntimeSource(filename, url);
|
|
616
|
+
const { format, source } = resolveRuntimeSource(filename, url, runtimeEntryUrls.delete(url) || isProcessEntry(filename));
|
|
259
617
|
return {
|
|
260
618
|
format,
|
|
261
619
|
shortCircuit: true,
|
|
262
620
|
source,
|
|
263
621
|
};
|
|
264
622
|
}
|
|
265
|
-
function resolveRuntimeSource(filename, url = (0, node_url_1.pathToFileURL)(filename).href) {
|
|
266
|
-
const served = resolveServedSource(filename, url);
|
|
623
|
+
function resolveRuntimeSource(filename, url = (0, node_url_1.pathToFileURL)(filename).href, prepareAsEntry = false) {
|
|
624
|
+
const served = resolveServedSource(filename, url, prepareAsEntry);
|
|
267
625
|
const format = moduleFormat(filename, served.moduleOptions);
|
|
268
626
|
return {
|
|
269
627
|
format,
|
|
@@ -272,6 +630,13 @@ function resolveRuntimeSource(filename, url = (0, node_url_1.pathToFileURL)(file
|
|
|
272
630
|
: served.source,
|
|
273
631
|
};
|
|
274
632
|
}
|
|
633
|
+
/** Whether `filename` is the TypeScript main module named on Node's argv. */
|
|
634
|
+
function isProcessEntry(filename) {
|
|
635
|
+
const entry = process.argv[1];
|
|
636
|
+
return (entry !== undefined &&
|
|
637
|
+
isTypeScriptSource(entry) &&
|
|
638
|
+
realPath(node_path_1.default.resolve(entry)) === realPath(filename));
|
|
639
|
+
}
|
|
275
640
|
function rememberCommonJsNamedInterop(result, context) {
|
|
276
641
|
if (shouldExposeCommonJsNamedExports(result.url, context.parentURL)) {
|
|
277
642
|
commonJsNamedInteropUrls.add(result.url);
|
|
@@ -316,9 +681,9 @@ function owningModuleOptions(filename) {
|
|
|
316
681
|
return null;
|
|
317
682
|
}
|
|
318
683
|
const real = realPath(filename);
|
|
319
|
-
const
|
|
320
|
-
if (
|
|
321
|
-
return
|
|
684
|
+
const owner = findEntryEmit(real)?.manifest;
|
|
685
|
+
if (owner !== undefined) {
|
|
686
|
+
return owner.moduleOptions ?? {};
|
|
322
687
|
}
|
|
323
688
|
const tsconfig = nearestTsconfig(real);
|
|
324
689
|
if (tsconfig === null) {
|
|
@@ -330,10 +695,7 @@ function owningModuleOptions(filename) {
|
|
|
330
695
|
}
|
|
331
696
|
let options = {};
|
|
332
697
|
try {
|
|
333
|
-
const project = (
|
|
334
|
-
cwd: node_path_1.default.dirname(tsconfig),
|
|
335
|
-
tsconfig,
|
|
336
|
-
});
|
|
698
|
+
const project = readPluginDescriptorProjectConfig(tsconfig);
|
|
337
699
|
options = projectModuleOptions(project.compilerOptions);
|
|
338
700
|
}
|
|
339
701
|
catch {
|
|
@@ -347,6 +709,108 @@ function owningModuleOptions(filename) {
|
|
|
347
709
|
moduleOptionsCache.set(tsconfig, options);
|
|
348
710
|
return options;
|
|
349
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* Read one owning config while its discovered chain stays unchanged.
|
|
714
|
+
*
|
|
715
|
+
* A preliminary read discovers `extends`; an accepted read is bracketed by
|
|
716
|
+
* equal fingerprints over that exact chain. If churn never settles, ttsx can
|
|
717
|
+
* still execute the last project but reports every observed config as unstable,
|
|
718
|
+
* preventing a later snapshot from certifying the torn result.
|
|
719
|
+
*/
|
|
720
|
+
function readPluginDescriptorProjectConfig(tsconfig) {
|
|
721
|
+
const read = () => (0, readProjectConfig_1.readProjectConfig)({ cwd: node_path_1.default.dirname(tsconfig), tsconfig });
|
|
722
|
+
if (process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE !== "1")
|
|
723
|
+
return read();
|
|
724
|
+
const observed = new Set([node_path_1.default.resolve(tsconfig)]);
|
|
725
|
+
let project;
|
|
726
|
+
try {
|
|
727
|
+
project = read();
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
recordPluginDescriptorProjectInputs(observed, true);
|
|
731
|
+
throw error;
|
|
732
|
+
}
|
|
733
|
+
let inputs = normalizedProjectConfigPaths(project, tsconfig);
|
|
734
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
735
|
+
for (const input of inputs)
|
|
736
|
+
observed.add(input);
|
|
737
|
+
const before = pluginDescriptorInputHashes(inputs);
|
|
738
|
+
const beforeRealpaths = pluginDescriptorInputRealpaths(inputs);
|
|
739
|
+
const beforeSignatures = pluginDescriptorInputMetadataSignatures(inputs);
|
|
740
|
+
let candidate;
|
|
741
|
+
try {
|
|
742
|
+
candidate = read();
|
|
743
|
+
}
|
|
744
|
+
catch (error) {
|
|
745
|
+
recordPluginDescriptorProjectInputs(observed, true);
|
|
746
|
+
throw error;
|
|
747
|
+
}
|
|
748
|
+
const candidateInputs = normalizedProjectConfigPaths(candidate, tsconfig);
|
|
749
|
+
for (const input of candidateInputs)
|
|
750
|
+
observed.add(input);
|
|
751
|
+
const after = pluginDescriptorInputHashes(candidateInputs);
|
|
752
|
+
const afterRealpaths = pluginDescriptorInputRealpaths(candidateInputs);
|
|
753
|
+
const afterSignatures = pluginDescriptorInputMetadataSignatures(candidateInputs);
|
|
754
|
+
if (equalPluginDescriptorInputLists(inputs, candidateInputs) &&
|
|
755
|
+
equalPluginDescriptorInputHashes(before, after) &&
|
|
756
|
+
equalPluginDescriptorInputHashes(beforeRealpaths, afterRealpaths) &&
|
|
757
|
+
beforeSignatures !== undefined &&
|
|
758
|
+
afterSignatures !== undefined &&
|
|
759
|
+
equalPluginDescriptorInputHashes(beforeSignatures, afterSignatures)) {
|
|
760
|
+
for (const input of candidateInputs) {
|
|
761
|
+
recordPluginDescriptorInput({
|
|
762
|
+
hash: after[input],
|
|
763
|
+
realpath: afterRealpaths[input],
|
|
764
|
+
resolved: input,
|
|
765
|
+
signature: afterSignatures[input],
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
return candidate;
|
|
769
|
+
}
|
|
770
|
+
project = candidate;
|
|
771
|
+
inputs = candidateInputs;
|
|
772
|
+
}
|
|
773
|
+
recordPluginDescriptorProjectInputs(observed, true);
|
|
774
|
+
return project;
|
|
775
|
+
}
|
|
776
|
+
function normalizedProjectConfigPaths(project, requestedConfig) {
|
|
777
|
+
return [
|
|
778
|
+
...new Set([requestedConfig, ...project.configPaths].map((file) => node_path_1.default.resolve(file))),
|
|
779
|
+
].sort();
|
|
780
|
+
}
|
|
781
|
+
function pluginDescriptorInputHashes(inputs) {
|
|
782
|
+
return Object.fromEntries(inputs.map((input) => [input, pluginDescriptorInputHash(input)]));
|
|
783
|
+
}
|
|
784
|
+
function pluginDescriptorInputRealpaths(inputs) {
|
|
785
|
+
return Object.fromEntries(inputs.map((input) => [input, pluginDescriptorInputRealpath(input)]));
|
|
786
|
+
}
|
|
787
|
+
function pluginDescriptorInputMetadataSignatures(inputs) {
|
|
788
|
+
const output = {};
|
|
789
|
+
for (const input of inputs) {
|
|
790
|
+
const signature = pluginDescriptorInputMetadataSignature(input);
|
|
791
|
+
if (signature === undefined)
|
|
792
|
+
return undefined;
|
|
793
|
+
output[node_path_1.default.resolve(input)] = signature;
|
|
794
|
+
}
|
|
795
|
+
return output;
|
|
796
|
+
}
|
|
797
|
+
function equalPluginDescriptorInputLists(first, second) {
|
|
798
|
+
return (first.length === second.length &&
|
|
799
|
+
first.every((input, index) => input === second[index]));
|
|
800
|
+
}
|
|
801
|
+
function equalPluginDescriptorInputHashes(first, second) {
|
|
802
|
+
return (Object.keys(first).length === Object.keys(second).length &&
|
|
803
|
+
Object.entries(first).every(([input, hash]) => second[input] === hash));
|
|
804
|
+
}
|
|
805
|
+
function recordPluginDescriptorProjectInputs(inputs, unstable) {
|
|
806
|
+
for (const input of inputs) {
|
|
807
|
+
const resolved = node_path_1.default.resolve(input);
|
|
808
|
+
recordPluginDescriptorInput({
|
|
809
|
+
resolved,
|
|
810
|
+
...(unstable ? { unstable: true } : {}),
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
}
|
|
350
814
|
/** Narrow a resolved project's compiler options to the emit-format pair. */
|
|
351
815
|
function projectModuleOptions(compilerOptions) {
|
|
352
816
|
return {
|
|
@@ -365,12 +829,31 @@ function projectModuleOptions(compilerOptions) {
|
|
|
365
829
|
* "transform"` type-strip. Shared by the ESM `load` hook and the CommonJS
|
|
366
830
|
* `require` handler.
|
|
367
831
|
*/
|
|
368
|
-
function resolveServedSource(filename, url = (0, node_url_1.pathToFileURL)(filename).href) {
|
|
832
|
+
function resolveServedSource(filename, url = (0, node_url_1.pathToFileURL)(filename).href, prepareAsEntry = false) {
|
|
369
833
|
const real = realPath(filename);
|
|
370
|
-
|
|
834
|
+
// Only the public preload can prepare a newly discovered root. Direct ttsx
|
|
835
|
+
// has one pre-built manifest and historically relies on trailing-stem
|
|
836
|
+
// recovery when Windows presents the same source through its short and long
|
|
837
|
+
// temp-path spellings (the lint TypeScript-config loader is one such case).
|
|
838
|
+
// Treating that boundary as prepare-only would discard the existing emit and
|
|
839
|
+
// feed ESM source into CommonJS interop, producing ERR_REQUIRE_CYCLE_MODULE.
|
|
840
|
+
const prepareEntry = prepareAsEntry ? prepareRuntimeEntry : undefined;
|
|
841
|
+
// A JavaScript-to-TypeScript boundary is a new checked root unless an
|
|
842
|
+
// existing manifest proves exact ownership. Trailing-stem recovery is not
|
|
843
|
+
// ownership evidence: two out-of-include `index.ts` roots can otherwise map
|
|
844
|
+
// to the first manifest's `index.js`, skipping the second root's diagnostics.
|
|
845
|
+
let served = serveEntryEmit(real, prepareEntry === undefined);
|
|
371
846
|
if (served !== null) {
|
|
372
847
|
return withInlineSourceMap(served);
|
|
373
848
|
}
|
|
849
|
+
if (prepareEntry !== undefined) {
|
|
850
|
+
registeredManifests.push(prepareEntry(real));
|
|
851
|
+
served = serveEntryEmit(real);
|
|
852
|
+
if (served === null) {
|
|
853
|
+
throw new Error(`ttsx: prepared entry emit not found for ${filename}`);
|
|
854
|
+
}
|
|
855
|
+
return withInlineSourceMap(served);
|
|
856
|
+
}
|
|
374
857
|
const built = serveDependencyEmit(real);
|
|
375
858
|
if (built !== null) {
|
|
376
859
|
return withInlineSourceMap(built);
|
|
@@ -447,7 +930,7 @@ function emitOrphanAsCommonJs(filename) {
|
|
|
447
930
|
return hit;
|
|
448
931
|
}
|
|
449
932
|
}
|
|
450
|
-
const outDir =
|
|
933
|
+
const outDir = (0, createCanonicalTempDirectory_1.createCanonicalTempDirectory)("ttsx-orphan-");
|
|
451
934
|
try {
|
|
452
935
|
const res = (0, spawnNative_1.spawnNative)(tsgo, [
|
|
453
936
|
filename,
|
|
@@ -508,7 +991,7 @@ function emitCommonJsForNameScan(filename) {
|
|
|
508
991
|
commonJsNameScanSources.set(real, null);
|
|
509
992
|
return null;
|
|
510
993
|
}
|
|
511
|
-
const outDir =
|
|
994
|
+
const outDir = (0, createCanonicalTempDirectory_1.createCanonicalTempDirectory)("ttsx-export-scan-");
|
|
512
995
|
try {
|
|
513
996
|
const res = (0, spawnNative_1.spawnNative)(tsgo, [
|
|
514
997
|
real,
|
|
@@ -936,54 +1419,93 @@ function isIdentifierName(name) {
|
|
|
936
1419
|
* `rootDir` cannot have a mirrored emit, so it falls through to the dependency
|
|
937
1420
|
* paths.
|
|
938
1421
|
*/
|
|
939
|
-
function serveEntryEmit(real) {
|
|
940
|
-
const
|
|
941
|
-
if (
|
|
1422
|
+
function serveEntryEmit(real, allowStemFallback = true) {
|
|
1423
|
+
const owner = findEntryEmit(real, allowStemFallback);
|
|
1424
|
+
if (owner === null) {
|
|
942
1425
|
return null;
|
|
943
1426
|
}
|
|
944
|
-
const
|
|
945
|
-
if (emitted === null) {
|
|
946
|
-
return null;
|
|
947
|
-
}
|
|
948
|
-
const source = readFileOrNull(emitted);
|
|
1427
|
+
const source = readFileOrNull(owner.emittedFile);
|
|
949
1428
|
return source === null
|
|
950
1429
|
? null
|
|
951
1430
|
: {
|
|
952
|
-
emittedFile:
|
|
953
|
-
moduleOptions:
|
|
1431
|
+
emittedFile: owner.emittedFile,
|
|
1432
|
+
moduleOptions: owner.manifest.moduleOptions ?? {},
|
|
954
1433
|
source,
|
|
955
1434
|
sourceFile: real,
|
|
956
1435
|
};
|
|
957
1436
|
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Find the manifest that owns `real`. Explicit prepared roots win, followed by
|
|
1439
|
+
* exact mirrored paths across every manifest. Only then may legacy stem
|
|
1440
|
+
* recovery run, so one manifest's approximate match cannot shadow another's
|
|
1441
|
+
* exact emit.
|
|
1442
|
+
*/
|
|
1443
|
+
function findEntryEmit(real, allowStemFallback = true) {
|
|
1444
|
+
const manifests = runtimeManifests();
|
|
1445
|
+
for (const candidate of manifests) {
|
|
1446
|
+
if (candidate.entrySource !== undefined &&
|
|
1447
|
+
candidate.entryFile !== undefined &&
|
|
1448
|
+
realPath(candidate.entrySource) === real &&
|
|
1449
|
+
node_fs_1.default.existsSync(candidate.entryFile)) {
|
|
1450
|
+
return { emittedFile: candidate.entryFile, manifest: candidate };
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
for (const candidate of manifests) {
|
|
1454
|
+
const emitted = entryEmitPath(candidate, real, false);
|
|
1455
|
+
if (emitted !== null) {
|
|
1456
|
+
return { emittedFile: emitted, manifest: candidate };
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
if (!allowStemFallback) {
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
for (const candidate of manifests) {
|
|
1463
|
+
const emitted = entryEmitPath(candidate, real, true);
|
|
1464
|
+
if (emitted !== null) {
|
|
1465
|
+
return { emittedFile: emitted, manifest: candidate };
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return null;
|
|
1469
|
+
}
|
|
958
1470
|
/**
|
|
959
1471
|
* The entry project's emitted JavaScript for `real`, or `null` when that
|
|
960
1472
|
* project did not emit it. Shared with `owningModuleOptions` so "the entry
|
|
961
1473
|
* project owns this file" means exactly one thing in both places.
|
|
962
1474
|
*/
|
|
963
|
-
function entryEmitPath(m, real) {
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
1475
|
+
function entryEmitPath(m, real, allowStemFallback = true) {
|
|
1476
|
+
const cache = allowStemFallback
|
|
1477
|
+
? entryEmitPathCache
|
|
1478
|
+
: exactEntryEmitPathCache;
|
|
1479
|
+
let manifestCache = cache.get(m);
|
|
1480
|
+
if (manifestCache === undefined) {
|
|
1481
|
+
manifestCache = new Map();
|
|
1482
|
+
cache.set(m, manifestCache);
|
|
1483
|
+
}
|
|
1484
|
+
if (manifestCache.has(real)) {
|
|
1485
|
+
return manifestCache.get(real) ?? null;
|
|
967
1486
|
}
|
|
968
1487
|
const resolved = isWithin(real, m.rootDir)
|
|
969
1488
|
? (0, resolveEmittedJavaScript_1.resolveEmittedJavaScript)({
|
|
1489
|
+
allowStemFallback,
|
|
970
1490
|
emittedFiles: m.emittedFiles,
|
|
971
1491
|
outDir: m.emitDir,
|
|
972
1492
|
projectRoot: m.rootDir,
|
|
973
1493
|
sourceFile: real,
|
|
974
1494
|
})
|
|
975
1495
|
: null;
|
|
976
|
-
|
|
1496
|
+
manifestCache.set(real, resolved);
|
|
977
1497
|
return resolved;
|
|
978
1498
|
}
|
|
979
1499
|
/**
|
|
980
1500
|
* Memo for `entryEmitPath`, because it is now on the `resolve` hook's path
|
|
981
1501
|
* through `owningModuleOptions` — once per import specifier — and a miss inside
|
|
982
1502
|
* `resolveEmittedJavaScript` walks the whole emit tree. The entry emit is
|
|
983
|
-
* written once before the run starts and never changes under it
|
|
984
|
-
*
|
|
1503
|
+
* written once before the run starts and never changes under it. Registration
|
|
1504
|
+
* can add several independent entry emits, so the manifest is part of the cache
|
|
1505
|
+
* identity as well as the source path.
|
|
985
1506
|
*/
|
|
986
|
-
const entryEmitPathCache = new
|
|
1507
|
+
const entryEmitPathCache = new WeakMap();
|
|
1508
|
+
const exactEntryEmitPathCache = new WeakMap();
|
|
987
1509
|
/**
|
|
988
1510
|
* True when `real` is `directory` itself or sits beneath it. Handles a root
|
|
989
1511
|
* `directory` (`/`, `C:\`): naively appending a separator would yield `//`,
|
|
@@ -1075,11 +1597,7 @@ function ensureProjectBuilt(tsconfig) {
|
|
|
1075
1597
|
return built;
|
|
1076
1598
|
}
|
|
1077
1599
|
function dependencyCachePaths(tsconfig) {
|
|
1078
|
-
const key =
|
|
1079
|
-
.createHash("sha256")
|
|
1080
|
-
.update(tsconfig)
|
|
1081
|
-
.digest("hex")
|
|
1082
|
-
.slice(0, 16);
|
|
1600
|
+
const key = dependencyCacheKey(tsconfig);
|
|
1083
1601
|
const root = dependencyCacheRoot();
|
|
1084
1602
|
return {
|
|
1085
1603
|
cacheDir: node_path_1.default.join(root, key),
|
|
@@ -1088,6 +1606,24 @@ function dependencyCachePaths(tsconfig) {
|
|
|
1088
1606
|
root,
|
|
1089
1607
|
};
|
|
1090
1608
|
}
|
|
1609
|
+
/** Derive one dependency cache key; exported for isolation regressions. */
|
|
1610
|
+
function dependencyCacheKey(tsconfig, options = {}) {
|
|
1611
|
+
const descriptorLoad = options.descriptorLoad ?? process.env.TTSC_PLUGIN_DESCRIPTOR_LOAD === "1";
|
|
1612
|
+
return (node_crypto_1.default
|
|
1613
|
+
.createHash("sha256")
|
|
1614
|
+
.update(tsconfig)
|
|
1615
|
+
// Descriptor evaluation promises a result bound to this process's exact
|
|
1616
|
+
// input observations. Reusing an emit another evaluator built can pair
|
|
1617
|
+
// that process's old source/config bytes with this process's later hashes.
|
|
1618
|
+
// Keep ordinary ttsx worker sharing, but isolate descriptor builds with a
|
|
1619
|
+
// non-reusable process nonce; the in-process `builtProjects` map still
|
|
1620
|
+
// compiles each owning project once.
|
|
1621
|
+
.update(descriptorLoad
|
|
1622
|
+
? `\0descriptor-process:${options.descriptorNonce ?? descriptorProcessCacheNonce}`
|
|
1623
|
+
: "")
|
|
1624
|
+
.digest("hex")
|
|
1625
|
+
.slice(0, 16));
|
|
1626
|
+
}
|
|
1091
1627
|
/** The immutable emit directory of one build generation under `cacheDir`. */
|
|
1092
1628
|
function dependencyGenerationDir(cacheDir, generation) {
|
|
1093
1629
|
return node_path_1.default.join(cacheDir, `gen-${generation}`);
|
|
@@ -1216,7 +1752,7 @@ function sleepSync(ms) {
|
|
|
1216
1752
|
* never be reused.
|
|
1217
1753
|
*/
|
|
1218
1754
|
function buildDependency(tsconfig, cacheDir, metaPath) {
|
|
1219
|
-
const project = (
|
|
1755
|
+
const project = readPluginDescriptorProjectConfig(tsconfig);
|
|
1220
1756
|
const generation = newDependencyGeneration();
|
|
1221
1757
|
const emitDir = dependencyGenerationDir(cacheDir, generation);
|
|
1222
1758
|
node_fs_1.default.rmSync(emitDir, { force: true, recursive: true });
|
|
@@ -1303,10 +1839,19 @@ function publishDependencyMeta(metaPath, meta) {
|
|
|
1303
1839
|
throw error;
|
|
1304
1840
|
}
|
|
1305
1841
|
}
|
|
1306
|
-
function dependencyCacheRoot() {
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1842
|
+
function dependencyCacheRoot(env = process.env) {
|
|
1843
|
+
// Descriptor evaluators are disposable and must not leave one isolated emit
|
|
1844
|
+
// generation in the shared temp cache per load. Their result file already
|
|
1845
|
+
// lives in the evaluator-owned directory that the parent removes in
|
|
1846
|
+
// `finally`; put dependency emits beside it so that cleanup owns both.
|
|
1847
|
+
if (env.TTSC_PLUGIN_DESCRIPTOR_LOAD === "1" &&
|
|
1848
|
+
typeof env.TTSC_PLUGIN_DESCRIPTOR_OUT === "string" &&
|
|
1849
|
+
node_path_1.default.isAbsolute(env.TTSC_PLUGIN_DESCRIPTOR_OUT)) {
|
|
1850
|
+
return node_path_1.default.join(node_path_1.default.dirname(env.TTSC_PLUGIN_DESCRIPTOR_OUT), "dependency-cache");
|
|
1851
|
+
}
|
|
1852
|
+
const owner = runtimeManifests().find((candidate) => candidate.depCacheDir.length !== 0);
|
|
1853
|
+
return owner !== undefined
|
|
1854
|
+
? owner.depCacheDir
|
|
1310
1855
|
: node_path_1.default.join(node_os_1.default.tmpdir(), "ttsx-dep");
|
|
1311
1856
|
}
|
|
1312
1857
|
// -----------------------------------------------------------------------------
|
|
@@ -1588,7 +2133,6 @@ function formatDuration(ms) {
|
|
|
1588
2133
|
const remainder = seconds % 60;
|
|
1589
2134
|
return minutes === 0 ? `${seconds}s` : `${minutes}m ${remainder}s`;
|
|
1590
2135
|
}
|
|
1591
|
-
/** Owning-tsconfig cache keyed by directory, mirroring `packageTypeCache`. */
|
|
1592
2136
|
const tsconfigCache = new Map();
|
|
1593
2137
|
const moduleOptionsCache = new Map();
|
|
1594
2138
|
/**
|
|
@@ -1609,13 +2153,30 @@ function nearestTsconfig(file) {
|
|
|
1609
2153
|
for (;;) {
|
|
1610
2154
|
const cached = tsconfigCache.get(directory);
|
|
1611
2155
|
if (cached !== undefined) {
|
|
1612
|
-
|
|
2156
|
+
if (process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE !== "1") {
|
|
2157
|
+
return rememberTsconfig(chain, cached.result, cached.candidates);
|
|
2158
|
+
}
|
|
2159
|
+
// Bracket the cached selection with the same candidate observations the
|
|
2160
|
+
// parent later reconciles. A nearer config created between a liveness
|
|
2161
|
+
// check and reporting must conflict, not be paired with the cached
|
|
2162
|
+
// farther result and certified as current.
|
|
2163
|
+
recordPluginDescriptorTsconfigCandidates(cached.candidates);
|
|
2164
|
+
const current = nearestExistingTsconfig(cached.candidates);
|
|
2165
|
+
recordPluginDescriptorTsconfigCandidates(cached.candidates);
|
|
2166
|
+
if (current === cached.result) {
|
|
2167
|
+
return rememberTsconfig(chain, cached.result, cached.candidates);
|
|
2168
|
+
}
|
|
2169
|
+
// Descriptor factories can deliberately create a config before a lazy
|
|
2170
|
+
// import. A lookup cached while an earlier import ran must not keep
|
|
2171
|
+
// serving the orphan lane after the nearest candidate changed.
|
|
2172
|
+
tsconfigCache.delete(directory);
|
|
1613
2173
|
}
|
|
1614
2174
|
if (node_path_1.default.basename(directory) === "node_modules") {
|
|
1615
2175
|
return rememberTsconfig(chain, null);
|
|
1616
2176
|
}
|
|
1617
2177
|
chain.push(directory);
|
|
1618
2178
|
const candidate = node_path_1.default.join(directory, "tsconfig.json");
|
|
2179
|
+
recordPluginDescriptorTsconfigCandidates([candidate]);
|
|
1619
2180
|
if (isFile(candidate)) {
|
|
1620
2181
|
return rememberTsconfig(chain, candidate);
|
|
1621
2182
|
}
|
|
@@ -1626,12 +2187,33 @@ function nearestTsconfig(file) {
|
|
|
1626
2187
|
directory = parent;
|
|
1627
2188
|
}
|
|
1628
2189
|
}
|
|
1629
|
-
function
|
|
1630
|
-
for (const
|
|
1631
|
-
|
|
2190
|
+
function nearestExistingTsconfig(candidates) {
|
|
2191
|
+
for (const candidate of candidates) {
|
|
2192
|
+
if (isFile(candidate))
|
|
2193
|
+
return node_path_1.default.resolve(candidate);
|
|
2194
|
+
}
|
|
2195
|
+
return null;
|
|
2196
|
+
}
|
|
2197
|
+
function rememberTsconfig(directories, result, tailCandidates = []) {
|
|
2198
|
+
let candidates = [...tailCandidates];
|
|
2199
|
+
for (let index = directories.length - 1; index >= 0; index -= 1) {
|
|
2200
|
+
const directory = directories[index];
|
|
2201
|
+
candidates = [node_path_1.default.join(directory, "tsconfig.json"), ...candidates];
|
|
2202
|
+
tsconfigCache.set(directory, { candidates, result });
|
|
1632
2203
|
}
|
|
1633
2204
|
return result;
|
|
1634
2205
|
}
|
|
2206
|
+
/** Report every owning-config candidate observed by the nearest-config walk. */
|
|
2207
|
+
function recordPluginDescriptorTsconfigCandidates(candidates) {
|
|
2208
|
+
if (process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE !== "1")
|
|
2209
|
+
return;
|
|
2210
|
+
for (const candidate of candidates) {
|
|
2211
|
+
const resolved = node_path_1.default.resolve(candidate);
|
|
2212
|
+
recordPluginDescriptorInput({
|
|
2213
|
+
resolved,
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
1635
2217
|
function readFileOrNull(file) {
|
|
1636
2218
|
if (file === null) {
|
|
1637
2219
|
return null;
|
|
@@ -1730,6 +2312,15 @@ const JS_TO_TS_EXTENSIONS = new Map([
|
|
|
1730
2312
|
[".mjs", [".mts"]],
|
|
1731
2313
|
[".cjs", [".cts"]],
|
|
1732
2314
|
]);
|
|
2315
|
+
/** Candidate sources that can satisfy one missing JavaScript spelling. */
|
|
2316
|
+
function typescriptSourcesForJavaScriptSpecifier(file) {
|
|
2317
|
+
const extension = node_path_1.default.extname(file).toLowerCase();
|
|
2318
|
+
const substitutions = JS_TO_TS_EXTENSIONS.get(extension);
|
|
2319
|
+
if (substitutions === undefined)
|
|
2320
|
+
return [];
|
|
2321
|
+
const stem = file.slice(0, file.length - extension.length);
|
|
2322
|
+
return substitutions.map((candidate) => stem + candidate);
|
|
2323
|
+
}
|
|
1733
2324
|
/**
|
|
1734
2325
|
* Rescue a `specifier` that Node's resolver rejected: map a JavaScript
|
|
1735
2326
|
* extension back to its TypeScript source, or probe candidate extensions /
|