ttsc 0.26.2 → 0.28.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/graphbench/main.go +26 -16
- package/cmd/graphdump/main.go +37 -12
- package/cmd/graphdump/run_declares_the_capability_its_output_carries_test.go +137 -0
- package/cmd/graphdump/run_reports_an_unknown_flag_without_exiting_the_process_test.go +56 -0
- package/cmd/graphdump/run_reports_an_unloadable_project_without_printing_a_dump_test.go +46 -0
- package/cmd/ttsc/api_transform.go +28 -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/dump.go +43 -11
- package/cmd/ttscgraph/run_dump_claims_artifacts_only_when_it_asked_test.go +135 -0
- package/cmd/ttscgraph/run_dump_declares_the_capability_its_output_carries_test.go +96 -0
- package/cmd/ttscgraph/serve.go +357 -60
- package/cmd/ttscgraph/serve_adopts_a_republished_artifact_set_test.go +348 -0
- package/cmd/ttscgraph/serve_projects_an_artifact_no_source_owns_test.go +90 -0
- package/cmd/ttscgraph/serve_shards.go +31 -9
- package/driver/config_discovery.go +121 -0
- package/driver/emit_plugin.go +77 -11
- 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 +506 -15
- package/driver/program.go +54 -1
- package/driver/resolution_candidates.go +13 -5
- package/driver/rewrite.go +45 -2
- package/driver/transform_dependencies.go +283 -0
- package/driver/transform_dependencies_declares_every_file_without_a_contributor_test.go +28 -0
- package/driver/transform_dependencies_drops_a_self_reported_dependency_test.go +21 -0
- package/driver/transform_dependencies_keys_a_reported_path_like_the_envelope_test.go +46 -0
- package/driver/transform_dependencies_requires_every_contributor_to_declare_test.go +38 -0
- package/driver/transform_dependencies_stays_silent_for_an_undeclared_contributor_test.go +23 -0
- package/driver/transform_dependencies_withdraws_completeness_after_an_unusable_dependency_test.go +37 -0
- package/internal/graph/artifacts.go +230 -0
- package/internal/graph/artifacts_index_what_a_plugin_published_test.go +154 -0
- package/internal/graph/artifacts_survive_a_partial_rebuild_test.go +89 -0
- package/internal/graph/build.go +35 -0
- package/internal/graph/build_releases_the_scratch_it_no_longer_reads_test.go +118 -0
- package/internal/graph/doc_refs_are_the_use_the_checker_already_counts_test.go +93 -0
- package/internal/graph/doc_refs_resolve_documentation_links_test.go +335 -0
- package/internal/graph/doc_tags.go +416 -0
- package/internal/graph/doc_tags_are_claimed_before_they_are_read_test.go +131 -0
- package/internal/graph/doc_tags_capture_every_unrecognized_tag_test.go +165 -0
- package/internal/graph/doc_tags_read_only_what_the_parser_attached_test.go +97 -0
- package/internal/graph/doc_tags_stop_at_the_external_boundary_test.go +59 -0
- package/internal/graph/doc_tags_survive_line_endings_and_repeated_blocks_test.go +146 -0
- package/internal/graph/dump.go +60 -13
- package/internal/graph/dump_checkout_paths_are_stable_test.go +2 -2
- package/internal/graph/dump_paths.go +2 -2
- package/internal/graph/dump_paths_are_portable_and_injective_test.go +1 -1
- package/internal/graph/edge_kinds_names_every_declared_kind_test.go +49 -0
- package/internal/graph/edges.go +1 -0
- package/internal/graph/graph.go +79 -4
- package/internal/graph/provenance.go +29 -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/project/createNativeProjectContextArgs.d.ts +9 -0
- package/lib/compiler/internal/project/createNativeProjectContextArgs.js +16 -4
- package/lib/compiler/internal/project/createNativeProjectContextArgs.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/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.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 +47 -3
- package/lib/launcher/internal/runtimeHooks.js +746 -64
- 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/capabilityResolutionCache.d.ts +76 -0
- package/lib/plugin/internal/capabilityResolutionCache.js +224 -0
- package/lib/plugin/internal/capabilityResolutionCache.js.map +1 -0
- 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/plugin/resolveCapabilityPlugins.d.ts +55 -0
- package/lib/plugin/resolveCapabilityPlugins.js +118 -0
- package/lib/plugin/resolveCapabilityPlugins.js.map +1 -0
- 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 +38 -0
- package/lib/structures/ITtscPlugin.d.ts +33 -0
- package/package.json +16 -9
- package/shim/ast/parent.go +16 -3
- package/shim/checker/enums_gen.go +11 -0
- package/shim/checker/go.mod +9 -0
- package/shim/checker/go.sum +22 -0
- package/shim/checker/shim.go +78 -0
- package/shim/compiler/emithook.go +10 -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/project/createNativeProjectContextArgs.ts +19 -4
- 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/index.ts +1 -0
- 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 +934 -68
- package/src/plugin/internal/buildSourcePlugin.ts +984 -79
- package/src/plugin/internal/capabilityResolutionCache.ts +305 -0
- package/src/plugin/internal/loadProjectPlugins.ts +1718 -104
- package/src/plugin/resolveCapabilityPlugins.ts +171 -0
- package/src/register.ts +47 -0
- package/src/structures/ITtscCompilerContext.ts +6 -3
- package/src/structures/ITtscCompilerTransformation.ts +46 -0
- package/src/structures/ITtscPlugin.ts +36 -0
- package/tools/shim_audit/baseline.json +0 -2
- package/utility/host.go +18 -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
|
@@ -3,17 +3,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PLUGIN_DESCRIPTOR_SHIM_SOURCE = void 0;
|
|
6
|
+
exports.PLUGIN_DESCRIPTOR_SHIM_SOURCE = exports.COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE = void 0;
|
|
7
7
|
exports.loadProjectPlugins = loadProjectPlugins;
|
|
8
|
+
exports.hashHostInputPaths = hashHostInputPaths;
|
|
9
|
+
exports.realpathHostInput = realpathHostInput;
|
|
10
|
+
exports.realpathHostInputPaths = realpathHostInputPaths;
|
|
11
|
+
exports.collectProjectHostInputs = collectProjectHostInputs;
|
|
8
12
|
exports.hasProjectPluginEntries = hasProjectPluginEntries;
|
|
9
13
|
const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
14
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
10
15
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
16
|
const node_module_1 = require("node:module");
|
|
12
|
-
const node_os_1 = __importDefault(require("node:os"));
|
|
13
17
|
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
+
const node_url_1 = require("node:url");
|
|
14
19
|
const paths_1 = require("../../compiler/internal/paths");
|
|
15
20
|
const readConfigJson_1 = require("../../compiler/internal/project/readConfigJson");
|
|
16
21
|
const readProjectConfig_1 = require("../../compiler/internal/project/readProjectConfig");
|
|
22
|
+
const createCanonicalTempDirectory_1 = require("../../internal/createCanonicalTempDirectory");
|
|
23
|
+
const resolveNodeBinary_1 = require("../../internal/resolveNodeBinary");
|
|
17
24
|
const buildSourcePlugin_1 = require("./buildSourcePlugin");
|
|
18
25
|
const descriptorProcessFailure_1 = require("./descriptorProcessFailure");
|
|
19
26
|
const GO_MOD_SEARCH_MAX_DEPTH = 3;
|
|
@@ -23,17 +30,18 @@ const GO_MOD_SEARCH_MAX_DEPTH = 3;
|
|
|
23
30
|
* Reads the project config, discovers plugin entries (from tsconfig and package
|
|
24
31
|
* auto-discovery), validates and composes their descriptors, then invokes
|
|
25
32
|
* `buildSourcePlugin` to compile each Go source package into a cached binary.
|
|
26
|
-
* Returns the ordered
|
|
27
|
-
*
|
|
33
|
+
* Returns the ordered native plugins, parsed project config, and exact
|
|
34
|
+
* JavaScript-host files that universally influence the loaded selection.
|
|
28
35
|
*
|
|
29
36
|
* @param options.binary - Absolute path to the ttsc native helper binary.
|
|
30
37
|
* @param options.cacheDir - Override the plugin binary cache directory.
|
|
31
38
|
* @param options.cwd - Working directory for resolving relative paths.
|
|
32
39
|
* @param options.entries - Explicit plugin entries; `false` disables all
|
|
33
40
|
* plugins (skips both tsconfig entries and package auto-discovery).
|
|
34
|
-
* @param options.env - Effective environment for source-plugin builds and
|
|
35
|
-
*
|
|
36
|
-
*
|
|
41
|
+
* @param options.env - Effective environment for source-plugin builds and
|
|
42
|
+
* isolated descriptor evaluators, including the `ttsx` fallback (`{
|
|
43
|
+
* ...process.env, ...context.env }`). Defaults to `process.env` for CLI
|
|
44
|
+
* callers, so ambient behavior is unchanged.
|
|
37
45
|
* @param options.file - Path to the tsconfig/jsconfig file.
|
|
38
46
|
* @param options.pluginConfigDir - Caller-declared anchor for plugin
|
|
39
47
|
* config-file discovery (see `ITtscPluginFactoryContext.pluginConfigDir`).
|
|
@@ -41,19 +49,29 @@ const GO_MOD_SEARCH_MAX_DEPTH = 3;
|
|
|
41
49
|
* @param options.tsconfig - Alias for `file`.
|
|
42
50
|
*/
|
|
43
51
|
function loadProjectPlugins(options) {
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
// Snapshot the caller environment before `withPluginLoaderEnv` injects
|
|
53
|
+
// host-owned Node/ttsx locators into process.env. Under a Bun parent the
|
|
54
|
+
// direct descriptor evaluator must remain Bun unless the caller explicitly
|
|
55
|
+
// selected another runtime.
|
|
56
|
+
const effectiveEnv = { ...(options.env ?? process.env) };
|
|
57
|
+
const projectSnapshot = readProjectHostInputSnapshot({
|
|
46
58
|
cwd: options.cwd,
|
|
47
59
|
file: options.file,
|
|
60
|
+
includePluginDiscovery: options.entries === undefined,
|
|
48
61
|
projectRoot: options.projectRoot,
|
|
49
62
|
tsconfig: options.tsconfig,
|
|
50
63
|
});
|
|
64
|
+
const { project } = projectSnapshot;
|
|
65
|
+
const projectHostInputs = projectSnapshot.hostInputs;
|
|
66
|
+
const projectHostInputHashes = projectSnapshot.hostInputHashes;
|
|
67
|
+
const projectHostInputRealpaths = projectSnapshot.hostInputRealpaths;
|
|
51
68
|
const entries = options.entries === false
|
|
52
69
|
? []
|
|
53
70
|
: resolvePluginEntries(project, options.entries).filter((entry) => entry.config.enabled !== false);
|
|
54
71
|
if (entries.length === 0) {
|
|
55
72
|
options.onWatchInputs?.([]);
|
|
56
73
|
return {
|
|
74
|
+
...collectHostInputSnapshot(project, {}, [], projectHostInputs, revalidateHostInputHashes(projectHostInputHashes, projectHostInputs), revalidateHostInputRealpaths(projectHostInputRealpaths, projectHostInputs)),
|
|
57
75
|
nativePlugins: [],
|
|
58
76
|
project,
|
|
59
77
|
};
|
|
@@ -68,7 +86,38 @@ function loadProjectPlugins(options) {
|
|
|
68
86
|
projectRoot: project.root,
|
|
69
87
|
tsconfig: project.path,
|
|
70
88
|
};
|
|
71
|
-
const
|
|
89
|
+
const loadedEntries = withPluginLoaderEnv(() => entries.map((entry) => {
|
|
90
|
+
const specifier = entry.config.transform;
|
|
91
|
+
if (typeof specifier !== "string" || specifier.length === 0) {
|
|
92
|
+
throw new Error(`ttsc: plugin entry is missing a string "transform" field`);
|
|
93
|
+
}
|
|
94
|
+
const entryCandidates = collectModuleResolutionCandidates(specifier, node_path_1.default.join(entry.baseDir, "package.json"), undefined);
|
|
95
|
+
// Capture every candidate before resolution chooses the descriptor entry.
|
|
96
|
+
// A post-resolution snapshot could bless a higher-priority file created
|
|
97
|
+
// after the resolver had already selected the old entry.
|
|
98
|
+
const entryCandidateHashes = hashHostInputPaths(entryCandidates);
|
|
99
|
+
const entryCandidateRealpaths = realpathHostInputPaths(entryCandidates);
|
|
100
|
+
const request = resolvePluginRequest(specifier, entry.baseDir);
|
|
101
|
+
const loaded = loadPluginEntry(entry.config, { ...context, plugin: entry.config }, request, effectiveEnv);
|
|
102
|
+
const loadedHostInputHashes = mergeObservedHostInputHashes(loaded.hostInputHashes, hashHostInputPaths(Object.keys(loaded.hostInputHashes)));
|
|
103
|
+
const loadedHostInputRealpaths = mergeObservedHostInputRealpaths(loaded.hostInputRealpaths, realpathHostInputPaths(Object.keys(loaded.hostInputRealpaths)));
|
|
104
|
+
const hostInputHashes = mergeObservedHostInputHashes(entryCandidateHashes, loadedHostInputHashes);
|
|
105
|
+
const hostInputRealpaths = mergeObservedHostInputRealpaths(entryCandidateRealpaths, loadedHostInputRealpaths);
|
|
106
|
+
for (const input of loaded.hostInputs) {
|
|
107
|
+
const absolute = node_path_1.default.resolve(input);
|
|
108
|
+
if (!Object.prototype.hasOwnProperty.call(loadedHostInputHashes, absolute)) {
|
|
109
|
+
delete hostInputHashes[absolute];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
...loaded,
|
|
114
|
+
hostInputHashes,
|
|
115
|
+
hostInputRealpaths,
|
|
116
|
+
hostInputs: [...loaded.hostInputs, ...entryCandidates],
|
|
117
|
+
request,
|
|
118
|
+
};
|
|
119
|
+
}));
|
|
120
|
+
const plugins = composePluginSources(entries, loadedEntries.map((entry) => entry.plugin));
|
|
72
121
|
const ttscVersion = readTtscVersion();
|
|
73
122
|
const tsgoVersion = readTsgoVersion(context.projectRoot);
|
|
74
123
|
const records = plugins.map((plugin, index) => {
|
|
@@ -83,6 +132,9 @@ function loadProjectPlugins(options) {
|
|
|
83
132
|
const linkedContributorName = kind === "linked"
|
|
84
133
|
? `linked_${String(index).padStart(6, "0")}`
|
|
85
134
|
: undefined;
|
|
135
|
+
const hostInputs = validatePluginHostInputs(plugin, index);
|
|
136
|
+
const pluginHostInputHashes = validatePluginHostInputHashes(plugin, index, hostInputs);
|
|
137
|
+
const pluginHostInputRealpaths = validatePluginHostInputRealpaths(plugin, index, hostInputs);
|
|
86
138
|
return {
|
|
87
139
|
capabilities: plugin.capabilities,
|
|
88
140
|
contributors,
|
|
@@ -92,6 +144,10 @@ function loadProjectPlugins(options) {
|
|
|
92
144
|
linkedContributorName,
|
|
93
145
|
name: plugin.name,
|
|
94
146
|
reportsTypeScriptDiagnostics: plugin.reportsTypeScriptDiagnostics === true,
|
|
147
|
+
request: loadedEntries[index].request,
|
|
148
|
+
hostInputHashes: mergePluginHostInputHashes(loadedEntries[index].hostInputHashes, pluginHostInputHashes, loadedEntries[index].hostInputs, hostInputs),
|
|
149
|
+
hostInputRealpaths: mergePluginHostInputHashes(loadedEntries[index].hostInputRealpaths, pluginHostInputRealpaths, loadedEntries[index].hostInputs, hostInputs),
|
|
150
|
+
hostInputs: [...loadedEntries[index].hostInputs, ...hostInputs],
|
|
95
151
|
source,
|
|
96
152
|
stage,
|
|
97
153
|
};
|
|
@@ -167,10 +223,451 @@ function loadProjectPlugins(options) {
|
|
|
167
223
|
};
|
|
168
224
|
});
|
|
169
225
|
return {
|
|
226
|
+
...collectHostInputSnapshot(project, context, records, projectHostInputs, revalidateHostInputHashes(projectHostInputHashes, projectHostInputs), revalidateHostInputRealpaths(projectHostInputRealpaths, projectHostInputs)),
|
|
170
227
|
nativePlugins: orderNativePlugins(nativePlugins),
|
|
171
228
|
project,
|
|
172
229
|
};
|
|
173
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Collect exact JavaScript-host files that universally influence the loaded
|
|
233
|
+
* transform. Program files belong to the native reference graph; this list is
|
|
234
|
+
* deliberately limited to config ancestry, descriptor entries, the project
|
|
235
|
+
* manifest controlling auto-discovery, and explicit plugin config files.
|
|
236
|
+
*/
|
|
237
|
+
function collectHostInputSnapshot(project, context, records, baselineInputs, baselineHashes, baselineRealpaths) {
|
|
238
|
+
const inputs = new Set(baselineInputs.map((file) => node_path_1.default.resolve(file)));
|
|
239
|
+
const configBase = context.pluginConfigDir ?? node_path_1.default.dirname(project.path);
|
|
240
|
+
for (const record of records) {
|
|
241
|
+
inputs.add(node_path_1.default.resolve(record.request));
|
|
242
|
+
for (const hostInput of record.hostInputs) {
|
|
243
|
+
inputs.add(node_path_1.default.resolve(hostInput));
|
|
244
|
+
}
|
|
245
|
+
const descriptorManifest = findNearestPackageJson(record.request);
|
|
246
|
+
if (descriptorManifest !== undefined) {
|
|
247
|
+
inputs.add(node_path_1.default.resolve(descriptorManifest));
|
|
248
|
+
}
|
|
249
|
+
const configFile = record.config.configFile;
|
|
250
|
+
if (typeof configFile === "string" && configFile.trim() !== "") {
|
|
251
|
+
inputs.add(node_path_1.default.isAbsolute(configFile)
|
|
252
|
+
? node_path_1.default.resolve(configFile)
|
|
253
|
+
: node_path_1.default.resolve(configBase, configFile));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const hostInputs = [...inputs].sort();
|
|
257
|
+
const evaluationHashes = mergeObservedHostInputHashes(baselineHashes, ...records.map((record) => record.hostInputHashes));
|
|
258
|
+
const evaluationRealpaths = mergeObservedHostInputRealpaths(baselineRealpaths, ...records.map((record) => record.hostInputRealpaths));
|
|
259
|
+
for (const record of records) {
|
|
260
|
+
for (const input of record.hostInputs) {
|
|
261
|
+
const absolute = node_path_1.default.resolve(input);
|
|
262
|
+
if (!Object.prototype.hasOwnProperty.call(record.hostInputHashes, absolute)) {
|
|
263
|
+
delete evaluationHashes[absolute];
|
|
264
|
+
}
|
|
265
|
+
if (!Object.prototype.hasOwnProperty.call(record.hostInputRealpaths, absolute)) {
|
|
266
|
+
delete evaluationRealpaths[absolute];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const hostInputHashes = Object.fromEntries(hostInputs.flatMap((file) => Object.prototype.hasOwnProperty.call(evaluationHashes, file)
|
|
271
|
+
? [[file, evaluationHashes[file]]]
|
|
272
|
+
: []));
|
|
273
|
+
const hostInputRealpaths = Object.fromEntries(hostInputs.flatMap((file) => Object.prototype.hasOwnProperty.call(evaluationRealpaths, file)
|
|
274
|
+
? [[file, evaluationRealpaths[file]]]
|
|
275
|
+
: []));
|
|
276
|
+
return { hostInputHashes, hostInputRealpaths, hostInputs };
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Read one project configuration while its complete discovery surface remains
|
|
280
|
+
* unchanged. The preliminary read discovers the input set; the accepted read is
|
|
281
|
+
* bracketed by equal fingerprints for that same set. A repeatedly changing
|
|
282
|
+
* project is still returned so compilation can make progress, but without any
|
|
283
|
+
* cache proof for the ambiguous snapshot.
|
|
284
|
+
*/
|
|
285
|
+
function readProjectHostInputSnapshot(options) {
|
|
286
|
+
const { includePluginDiscovery, ...projectOptions } = options;
|
|
287
|
+
let project = (0, readProjectConfig_1.readProjectConfig)(projectOptions);
|
|
288
|
+
let hostInputs = collectProjectHostInputs(project, includePluginDiscovery);
|
|
289
|
+
const observedInputs = new Set(hostInputs);
|
|
290
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
291
|
+
const before = hashHostInputPaths(hostInputs);
|
|
292
|
+
const beforeRealpaths = realpathHostInputPaths(hostInputs);
|
|
293
|
+
const beforeSignatures = hostInputMetadataSignatures(hostInputs);
|
|
294
|
+
const candidateProject = (0, readProjectConfig_1.readProjectConfig)(projectOptions);
|
|
295
|
+
const candidateInputs = collectProjectHostInputs(candidateProject, includePluginDiscovery);
|
|
296
|
+
for (const input of candidateInputs)
|
|
297
|
+
observedInputs.add(input);
|
|
298
|
+
const after = hashHostInputPaths(candidateInputs);
|
|
299
|
+
const afterRealpaths = realpathHostInputPaths(candidateInputs);
|
|
300
|
+
const afterSignatures = hostInputMetadataSignatures(candidateInputs);
|
|
301
|
+
if (equalHostInputLists(hostInputs, candidateInputs) &&
|
|
302
|
+
equalHostInputHashes(before, after) &&
|
|
303
|
+
equalHostInputHashes(beforeRealpaths, afterRealpaths) &&
|
|
304
|
+
beforeSignatures !== undefined &&
|
|
305
|
+
afterSignatures !== undefined &&
|
|
306
|
+
equalHostInputHashes(beforeSignatures, afterSignatures)) {
|
|
307
|
+
return {
|
|
308
|
+
hostInputHashes: after,
|
|
309
|
+
hostInputRealpaths: afterRealpaths,
|
|
310
|
+
hostInputs: candidateInputs,
|
|
311
|
+
project: candidateProject,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
project = candidateProject;
|
|
315
|
+
hostInputs = candidateInputs;
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
hostInputHashes: {},
|
|
319
|
+
hostInputRealpaths: {},
|
|
320
|
+
hostInputs: [...observedInputs].sort(),
|
|
321
|
+
project,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function equalHostInputLists(first, second) {
|
|
325
|
+
return (first.length === second.length &&
|
|
326
|
+
first.every((file, index) => node_path_1.default.resolve(file) === node_path_1.default.resolve(second[index])));
|
|
327
|
+
}
|
|
328
|
+
function equalHostInputHashes(first, second) {
|
|
329
|
+
const files = Object.keys(first);
|
|
330
|
+
return (files.length === Object.keys(second).length &&
|
|
331
|
+
files.every((file) => Object.prototype.hasOwnProperty.call(second, file) &&
|
|
332
|
+
first[file] === second[file]));
|
|
333
|
+
}
|
|
334
|
+
/** Retain proof only for inputs whose initial and final observations agree. */
|
|
335
|
+
function revalidateHostInputHashes(initial, inputs) {
|
|
336
|
+
const current = hashHostInputPaths(inputs);
|
|
337
|
+
const output = {};
|
|
338
|
+
for (const input of inputs) {
|
|
339
|
+
const absolute = node_path_1.default.resolve(input);
|
|
340
|
+
if (Object.prototype.hasOwnProperty.call(initial, absolute) &&
|
|
341
|
+
initial[absolute] === current[absolute]) {
|
|
342
|
+
output[absolute] = current[absolute];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return output;
|
|
346
|
+
}
|
|
347
|
+
/** Keep physical-identity proof only while the same path resolves identically. */
|
|
348
|
+
function revalidateHostInputRealpaths(initial, inputs) {
|
|
349
|
+
const current = realpathHostInputPaths(inputs);
|
|
350
|
+
return Object.fromEntries(inputs.flatMap((input) => {
|
|
351
|
+
const absolute = node_path_1.default.resolve(input);
|
|
352
|
+
return Object.prototype.hasOwnProperty.call(initial, absolute) &&
|
|
353
|
+
initial[absolute] === current[absolute]
|
|
354
|
+
? [[absolute, current[absolute]]]
|
|
355
|
+
: [];
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
function hashHostInput(file) {
|
|
359
|
+
try {
|
|
360
|
+
if (node_fs_1.default.statSync(file).isDirectory()) {
|
|
361
|
+
return node_crypto_1.default
|
|
362
|
+
.createHash("sha256")
|
|
363
|
+
.update("ttsc:host-input:directory\0")
|
|
364
|
+
.digest("hex");
|
|
365
|
+
}
|
|
366
|
+
return node_crypto_1.default
|
|
367
|
+
.createHash("sha256")
|
|
368
|
+
.update(node_fs_1.default.readFileSync(file))
|
|
369
|
+
.digest("hex");
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function hashHostInputPaths(files) {
|
|
376
|
+
return Object.fromEntries(files.map((file) => [node_path_1.default.resolve(file), hashHostInput(file)]));
|
|
377
|
+
}
|
|
378
|
+
/** Physical path selected by a host input, or null while it is unresolved. */
|
|
379
|
+
function realpathHostInput(file) {
|
|
380
|
+
try {
|
|
381
|
+
return node_fs_1.default.realpathSync.native(file);
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function realpathHostInputPaths(files) {
|
|
388
|
+
return Object.fromEntries(files.map((file) => [node_path_1.default.resolve(file), realpathHostInput(file)]));
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Metadata identity that survives content-preserving reads but exposes A-B-A
|
|
392
|
+
* replacement. Missing paths are tied to the nearest existing ancestor whose
|
|
393
|
+
* directory metadata changes when the missing branch appears or disappears.
|
|
394
|
+
*/
|
|
395
|
+
function hostInputMetadataSignature(file) {
|
|
396
|
+
const requested = node_path_1.default.resolve(file);
|
|
397
|
+
let current = requested;
|
|
398
|
+
for (;;) {
|
|
399
|
+
try {
|
|
400
|
+
const link = node_fs_1.default.lstatSync(current, { bigint: true });
|
|
401
|
+
let target = link;
|
|
402
|
+
if (link.isSymbolicLink()) {
|
|
403
|
+
try {
|
|
404
|
+
target = node_fs_1.default.statSync(current, { bigint: true });
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
// A broken link's own metadata cannot expose its target appearing
|
|
408
|
+
// and disappearing during evaluation. Decline cache proof instead.
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return [
|
|
413
|
+
node_path_1.default.relative(current, requested),
|
|
414
|
+
link.dev,
|
|
415
|
+
link.ino,
|
|
416
|
+
link.mode,
|
|
417
|
+
link.size,
|
|
418
|
+
link.mtimeNs,
|
|
419
|
+
link.ctimeNs,
|
|
420
|
+
target.dev,
|
|
421
|
+
target.ino,
|
|
422
|
+
target.mode,
|
|
423
|
+
target.size,
|
|
424
|
+
target.mtimeNs,
|
|
425
|
+
target.ctimeNs,
|
|
426
|
+
].join(":");
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
if (!isMissingPathError(error))
|
|
430
|
+
return undefined;
|
|
431
|
+
const parent = node_path_1.default.dirname(current);
|
|
432
|
+
if (parent === current)
|
|
433
|
+
return undefined;
|
|
434
|
+
current = parent;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function hostInputMetadataSignatures(files) {
|
|
439
|
+
const output = {};
|
|
440
|
+
for (const file of files) {
|
|
441
|
+
const signature = hostInputMetadataSignature(file);
|
|
442
|
+
if (signature === undefined)
|
|
443
|
+
return undefined;
|
|
444
|
+
output[node_path_1.default.resolve(file)] = signature;
|
|
445
|
+
}
|
|
446
|
+
return output;
|
|
447
|
+
}
|
|
448
|
+
function isMissingPathError(error) {
|
|
449
|
+
const code = error?.code;
|
|
450
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
451
|
+
}
|
|
452
|
+
/** Return config ancestry and the manifest controlling package discovery. */
|
|
453
|
+
function collectProjectHostInputs(project, includePluginDiscovery = true) {
|
|
454
|
+
const inputs = new Set(project.configPaths.map((file) => node_path_1.default.resolve(file)));
|
|
455
|
+
if (!includePluginDiscovery)
|
|
456
|
+
return [...inputs].sort();
|
|
457
|
+
const manifestCandidates = collectNearestPackageJsonCandidates(project.root);
|
|
458
|
+
for (const candidate of manifestCandidates)
|
|
459
|
+
inputs.add(candidate);
|
|
460
|
+
const manifest = manifestCandidates.find(existingFile);
|
|
461
|
+
if (manifest !== undefined) {
|
|
462
|
+
const projectManifest = readPackageManifest(manifest);
|
|
463
|
+
if (projectManifest !== undefined) {
|
|
464
|
+
const projectRoot = node_path_1.default.dirname(manifest);
|
|
465
|
+
for (const dependency of directDependencyNames(projectManifest)) {
|
|
466
|
+
const dependencyManifest = resolveDependencyPackageJson(dependency, projectRoot);
|
|
467
|
+
for (const candidate of collectDependencyManifestCandidates(dependency, manifest, dependencyManifest)) {
|
|
468
|
+
inputs.add(candidate);
|
|
469
|
+
}
|
|
470
|
+
if (dependencyManifest !== undefined) {
|
|
471
|
+
inputs.add(node_path_1.default.resolve(dependencyManifest));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return [...inputs].sort();
|
|
477
|
+
}
|
|
478
|
+
// The direct evaluator preloads ttsx's supported Node hook, whose
|
|
479
|
+
// extensionless rescue order includes TypeScript and ESM/CJS spellings in
|
|
480
|
+
// addition to Node's ordinary CommonJS probes.
|
|
481
|
+
const MODULE_PROBE_EXTENSIONS = [
|
|
482
|
+
".ts",
|
|
483
|
+
".tsx",
|
|
484
|
+
".mts",
|
|
485
|
+
".cts",
|
|
486
|
+
".js",
|
|
487
|
+
".mjs",
|
|
488
|
+
".cjs",
|
|
489
|
+
".json",
|
|
490
|
+
".node",
|
|
491
|
+
];
|
|
492
|
+
/** Record every file whose later appearance can change one module resolution. */
|
|
493
|
+
function collectModuleResolutionCandidates(specifier, parentFile, resolvedFile) {
|
|
494
|
+
const inputs = new Set();
|
|
495
|
+
const recordedBases = new Set();
|
|
496
|
+
const candidates = (base) => [
|
|
497
|
+
base,
|
|
498
|
+
...MODULE_PROBE_EXTENSIONS.map((extension) => base + extension),
|
|
499
|
+
node_path_1.default.join(base, "package.json"),
|
|
500
|
+
...MODULE_PROBE_EXTENSIONS.map((extension) => node_path_1.default.join(base, `index${extension}`)),
|
|
501
|
+
];
|
|
502
|
+
const recordManifestTargets = (value, directory, allowBare = false) => {
|
|
503
|
+
if (typeof value === "string") {
|
|
504
|
+
if (value !== "" &&
|
|
505
|
+
(allowBare || value.startsWith("./") || value.startsWith("../"))) {
|
|
506
|
+
recordBase(node_path_1.default.resolve(directory, value));
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (Array.isArray(value)) {
|
|
511
|
+
for (const item of value) {
|
|
512
|
+
recordManifestTargets(item, directory, allowBare);
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (isRecord(value)) {
|
|
517
|
+
for (const item of Object.values(value)) {
|
|
518
|
+
recordManifestTargets(item, directory, allowBare);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
const recordBase = (base) => {
|
|
523
|
+
const resolvedBase = node_path_1.default.resolve(base);
|
|
524
|
+
if (recordedBases.has(resolvedBase))
|
|
525
|
+
return;
|
|
526
|
+
recordedBases.add(resolvedBase);
|
|
527
|
+
for (const candidate of candidates(resolvedBase)) {
|
|
528
|
+
inputs.add(node_path_1.default.resolve(candidate));
|
|
529
|
+
}
|
|
530
|
+
try {
|
|
531
|
+
const manifest = (0, readConfigJson_1.readJsonFile)(node_path_1.default.join(resolvedBase, "package.json"));
|
|
532
|
+
if (isRecord(manifest)) {
|
|
533
|
+
recordManifestTargets(manifest.exports, resolvedBase);
|
|
534
|
+
recordManifestTargets(manifest.module, resolvedBase, true);
|
|
535
|
+
recordManifestTargets(manifest.main, resolvedBase, true);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
// A malformed selected manifest is reported by normal resolution/load.
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
const selectedBy = (base) => {
|
|
543
|
+
if (resolvedFile === undefined)
|
|
544
|
+
return false;
|
|
545
|
+
let selected;
|
|
546
|
+
try {
|
|
547
|
+
selected = node_fs_1.default.realpathSync.native(resolvedFile);
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
selected = node_path_1.default.resolve(resolvedFile);
|
|
551
|
+
}
|
|
552
|
+
for (const candidate of candidates(base)) {
|
|
553
|
+
try {
|
|
554
|
+
const canonical = node_fs_1.default.realpathSync.native(candidate);
|
|
555
|
+
const relative = node_path_1.default.relative(canonical, selected);
|
|
556
|
+
if (relative === "" ||
|
|
557
|
+
(node_fs_1.default.statSync(canonical).isDirectory() &&
|
|
558
|
+
relative !== ".." &&
|
|
559
|
+
!relative.startsWith(`..${node_path_1.default.sep}`) &&
|
|
560
|
+
!node_path_1.default.isAbsolute(relative))) {
|
|
561
|
+
return true;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
// Missing candidates are the inputs this function intentionally keeps.
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return false;
|
|
569
|
+
};
|
|
570
|
+
const localBases = () => {
|
|
571
|
+
if (specifier.startsWith("file:"))
|
|
572
|
+
return [(0, node_url_1.fileURLToPath)(specifier)];
|
|
573
|
+
const directory = node_path_1.default.dirname(parentFile);
|
|
574
|
+
const raw = node_path_1.default.resolve(directory, specifier);
|
|
575
|
+
const suffixStart = specifier.search(/[?#]/);
|
|
576
|
+
if (suffixStart === -1)
|
|
577
|
+
return [raw];
|
|
578
|
+
const pathname = specifier.slice(0, suffixStart);
|
|
579
|
+
return pathname === ""
|
|
580
|
+
? [raw]
|
|
581
|
+
: [...new Set([raw, node_path_1.default.resolve(directory, pathname)])];
|
|
582
|
+
};
|
|
583
|
+
if (specifier.startsWith(".") ||
|
|
584
|
+
node_path_1.default.isAbsolute(specifier) ||
|
|
585
|
+
specifier.startsWith("file:")) {
|
|
586
|
+
try {
|
|
587
|
+
for (const base of localBases()) {
|
|
588
|
+
// An existing exact file wins before extension and directory probes.
|
|
589
|
+
// Its own recorded identity is therefore sufficient; siblings cannot
|
|
590
|
+
// supersede it while it exists.
|
|
591
|
+
if (selectedByExactFile(base, resolvedFile) ||
|
|
592
|
+
(resolvedFile === undefined && existingFile(base))) {
|
|
593
|
+
inputs.add(node_path_1.default.resolve(base));
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
recordBase(base);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
// Invalid URL spellings are diagnosed by the real resolver.
|
|
602
|
+
}
|
|
603
|
+
return [...inputs];
|
|
604
|
+
}
|
|
605
|
+
const parts = specifier.split("/");
|
|
606
|
+
const packageParts = parts[0]?.startsWith("@")
|
|
607
|
+
? parts.slice(0, 2)
|
|
608
|
+
: parts.slice(0, 1);
|
|
609
|
+
if (packageParts.some((part) => part === undefined || part === "")) {
|
|
610
|
+
return [...inputs];
|
|
611
|
+
}
|
|
612
|
+
const packageName = packageParts.join("/");
|
|
613
|
+
const subpath = parts.slice(packageParts.length);
|
|
614
|
+
const searchPaths = (0, node_module_1.createRequire)(parentFile).resolve.paths(specifier) ?? [];
|
|
615
|
+
for (const searchPath of searchPaths) {
|
|
616
|
+
const packageDirectory = node_path_1.default.join(searchPath, packageName);
|
|
617
|
+
recordBase(packageDirectory);
|
|
618
|
+
if (subpath.length !== 0) {
|
|
619
|
+
recordBase(node_path_1.default.join(packageDirectory, ...subpath));
|
|
620
|
+
}
|
|
621
|
+
if (selectedBy(packageDirectory))
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
return [...inputs];
|
|
625
|
+
}
|
|
626
|
+
function existingFile(file) {
|
|
627
|
+
try {
|
|
628
|
+
return node_fs_1.default.statSync(file).isFile();
|
|
629
|
+
}
|
|
630
|
+
catch {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
function selectedByExactFile(candidate, selected) {
|
|
635
|
+
if (selected === undefined)
|
|
636
|
+
return false;
|
|
637
|
+
try {
|
|
638
|
+
return (node_fs_1.default.realpathSync.native(candidate) === node_fs_1.default.realpathSync.native(selected));
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
return node_path_1.default.resolve(candidate) === node_path_1.default.resolve(selected);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/** Record package manifests whose later appearance can redirect discovery. */
|
|
645
|
+
function collectDependencyManifestCandidates(packageName, parentFile, selectedManifest) {
|
|
646
|
+
const out = [];
|
|
647
|
+
let selectedDirectory;
|
|
648
|
+
if (selectedManifest !== undefined) {
|
|
649
|
+
try {
|
|
650
|
+
selectedDirectory = node_fs_1.default.realpathSync.native(node_path_1.default.dirname(selectedManifest));
|
|
651
|
+
}
|
|
652
|
+
catch {
|
|
653
|
+
selectedDirectory = node_path_1.default.resolve(node_path_1.default.dirname(selectedManifest));
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
for (const searchPath of (0, node_module_1.createRequire)(parentFile).resolve.paths(packageName) ?? []) {
|
|
657
|
+
const packageDirectory = node_path_1.default.join(searchPath, ...packageName.split("/"));
|
|
658
|
+
out.push(node_path_1.default.join(packageDirectory, "package.json"));
|
|
659
|
+
if (selectedDirectory === undefined)
|
|
660
|
+
continue;
|
|
661
|
+
try {
|
|
662
|
+
if (node_fs_1.default.realpathSync.native(packageDirectory) === selectedDirectory)
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
catch {
|
|
666
|
+
// Keep searching until the selected package directory is reached.
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return out;
|
|
670
|
+
}
|
|
174
671
|
function composePluginSources(entries, plugins) {
|
|
175
672
|
const aggregates = plugins
|
|
176
673
|
.map((plugin, index) => ({ index, plugin }))
|
|
@@ -379,7 +876,7 @@ function directDependencyNames(manifest) {
|
|
|
379
876
|
function resolveDependencyPackageJson(name, projectRoot) {
|
|
380
877
|
const direct = node_path_1.default.join(projectRoot, "node_modules", ...name.split("/"));
|
|
381
878
|
const directManifest = node_path_1.default.join(direct, "package.json");
|
|
382
|
-
if (
|
|
879
|
+
if (existingFile(directManifest)) {
|
|
383
880
|
return resolveRealPath(directManifest);
|
|
384
881
|
}
|
|
385
882
|
const projectPackage = node_path_1.default.join(projectRoot, "package.json");
|
|
@@ -397,18 +894,23 @@ function resolveDependencyPackageJson(name, projectRoot) {
|
|
|
397
894
|
}
|
|
398
895
|
}
|
|
399
896
|
function findNearestPackageJson(location) {
|
|
897
|
+
const selected = collectNearestPackageJsonCandidates(location).find(existingFile);
|
|
898
|
+
return selected === undefined ? undefined : resolveRealPath(selected);
|
|
899
|
+
}
|
|
900
|
+
/** Every package-scope candidate through the first regular manifest file. */
|
|
901
|
+
function collectNearestPackageJsonCandidates(location) {
|
|
400
902
|
let current = node_fs_1.default.statSync(location).isDirectory()
|
|
401
903
|
? location
|
|
402
904
|
: node_path_1.default.dirname(location);
|
|
905
|
+
const candidates = [];
|
|
403
906
|
while (true) {
|
|
404
|
-
const manifest = node_path_1.default.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
907
|
+
const manifest = node_path_1.default.resolve(current, "package.json");
|
|
908
|
+
candidates.push(manifest);
|
|
909
|
+
if (existingFile(manifest))
|
|
910
|
+
return candidates;
|
|
408
911
|
const parent = node_path_1.default.dirname(current);
|
|
409
|
-
if (parent === current)
|
|
410
|
-
return
|
|
411
|
-
}
|
|
912
|
+
if (parent === current)
|
|
913
|
+
return candidates;
|
|
412
914
|
current = parent;
|
|
413
915
|
}
|
|
414
916
|
}
|
|
@@ -419,7 +921,7 @@ function findNearestPackageJson(location) {
|
|
|
419
921
|
* message worse here than anywhere else.
|
|
420
922
|
*/
|
|
421
923
|
function readPackageManifest(file) {
|
|
422
|
-
if (!
|
|
924
|
+
if (!existingFile(file)) {
|
|
423
925
|
return undefined;
|
|
424
926
|
}
|
|
425
927
|
const parsed = (0, readConfigJson_1.readJsonFile)(file);
|
|
@@ -448,42 +950,32 @@ function orderNativePlugins(plugins) {
|
|
|
448
950
|
...plugins.filter((plugin) => plugin.stage === "transform"),
|
|
449
951
|
];
|
|
450
952
|
}
|
|
451
|
-
function loadPluginEntry(entry, base,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
953
|
+
function loadPluginEntry(entry, base, request, effectiveEnv) {
|
|
954
|
+
const specifier = entry.transform;
|
|
955
|
+
if (typeof specifier !== "string" || specifier.length === 0) {
|
|
956
|
+
throw new Error(`ttsc: plugin entry is missing a string "transform" field`);
|
|
957
|
+
}
|
|
958
|
+
// `dirname`/`filename` are per-entry: each plugin entry resolves to its own
|
|
959
|
+
// descriptor module, so they are derived here from the resolved `request`
|
|
960
|
+
// rather than carried on the shared base context. They give factories a
|
|
961
|
+
// load-mode-independent stand-in for `__dirname`/`__filename`, which are
|
|
962
|
+
// undefined when a descriptor loads through ttsx or as ESM.
|
|
963
|
+
const context = {
|
|
964
|
+
...base,
|
|
965
|
+
dirname: node_path_1.default.dirname(request),
|
|
966
|
+
filename: request,
|
|
967
|
+
};
|
|
968
|
+
const loaded = loadPluginDescriptor(request, context, effectiveEnv);
|
|
969
|
+
if (isTtscPlugin(loaded.descriptor)) {
|
|
970
|
+
rejectJsTransformFunctions(specifier, loaded.descriptor);
|
|
971
|
+
return {
|
|
972
|
+
hostInputHashes: loaded.hostInputHashes,
|
|
973
|
+
hostInputRealpaths: loaded.hostInputRealpaths,
|
|
974
|
+
hostInputs: loaded.inputs,
|
|
975
|
+
plugin: loaded.descriptor,
|
|
467
976
|
};
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
mod.default ??
|
|
471
|
-
mod.plugin ??
|
|
472
|
-
mod;
|
|
473
|
-
if (typeof candidate === "function") {
|
|
474
|
-
const plugin = candidate(context);
|
|
475
|
-
if (!isTtscPlugin(plugin)) {
|
|
476
|
-
throw new Error(`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`);
|
|
477
|
-
}
|
|
478
|
-
rejectJsTransformFunctions(specifier, plugin);
|
|
479
|
-
return plugin;
|
|
480
|
-
}
|
|
481
|
-
if (isTtscPlugin(candidate)) {
|
|
482
|
-
rejectJsTransformFunctions(specifier, candidate);
|
|
483
|
-
return candidate;
|
|
484
|
-
}
|
|
485
|
-
throw new Error(`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`);
|
|
486
|
-
});
|
|
977
|
+
}
|
|
978
|
+
throw new Error(`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`);
|
|
487
979
|
}
|
|
488
980
|
/**
|
|
489
981
|
* Require a plugin descriptor entry, falling back to `ttsx` when Node cannot
|
|
@@ -500,20 +992,624 @@ function loadPluginEntry(entry, base, baseDir, effectiveEnv) {
|
|
|
500
992
|
* never runs and cannot deadlock. A package that loads directly (a compiled
|
|
501
993
|
* descriptor, or Bun's native `.ts`) never reaches the fallback.
|
|
502
994
|
*/
|
|
503
|
-
function
|
|
995
|
+
function loadPluginDescriptor(request, context, effectiveEnv) {
|
|
504
996
|
try {
|
|
505
|
-
return
|
|
997
|
+
return loadCommonJsDescriptor(request, context, effectiveEnv);
|
|
506
998
|
}
|
|
507
999
|
catch (error) {
|
|
508
|
-
if (!TS_SOURCE_PATTERN.test(request)
|
|
1000
|
+
if (!TS_SOURCE_PATTERN.test(request) ||
|
|
1001
|
+
!(error instanceof CommonJsDescriptorLoadError) ||
|
|
1002
|
+
!error.retryWithTtsx) {
|
|
509
1003
|
throw error;
|
|
510
1004
|
}
|
|
511
|
-
const
|
|
512
|
-
if (
|
|
1005
|
+
const loaded = loadDescriptorViaTtsx(request, context, effectiveEnv);
|
|
1006
|
+
if (loaded === undefined) {
|
|
513
1007
|
throw error;
|
|
514
1008
|
}
|
|
515
|
-
return
|
|
1009
|
+
return loaded;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
class CommonJsDescriptorLoadError extends Error {
|
|
1013
|
+
retryWithTtsx;
|
|
1014
|
+
constructor(message, retryWithTtsx) {
|
|
1015
|
+
super(message);
|
|
1016
|
+
this.retryWithTtsx = retryWithTtsx;
|
|
1017
|
+
this.name = "CommonJsDescriptorLoadError";
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Evaluate one CommonJS descriptor in a fresh runtime module-cache generation.
|
|
1022
|
+
*
|
|
1023
|
+
* Reloading an external descriptor dependency inside this process has no safe
|
|
1024
|
+
* cache operation: retaining it serves stale exports, while deleting it splits
|
|
1025
|
+
* any application singleton that required the same module first. Isolation
|
|
1026
|
+
* gives every descriptor load current bytes without mutating the host's cache.
|
|
1027
|
+
* The child invokes the factory before walking its graph, so lazy `require()`
|
|
1028
|
+
* calls are included, and a failed first load cannot strand poisoned children.
|
|
1029
|
+
*/
|
|
1030
|
+
function loadCommonJsDescriptor(request, context, effectiveEnv) {
|
|
1031
|
+
const runtime = pluginDescriptorRuntimeBinary(effectiveEnv);
|
|
1032
|
+
const runtimeCapabilities = (0, resolveNodeBinary_1.javascriptRuntimeCapabilities)(runtime, effectiveEnv, context.projectRoot);
|
|
1033
|
+
const node = !runtimeCapabilities.bun &&
|
|
1034
|
+
runtimeCapabilities.registerHooks &&
|
|
1035
|
+
runtimeCapabilities.executable !== undefined
|
|
1036
|
+
? runtimeCapabilities.executable
|
|
1037
|
+
: (0, resolveNodeBinary_1.resolveNodeBinary)(effectiveEnv, context.projectRoot);
|
|
1038
|
+
const dir = createEvaluationTempDir();
|
|
1039
|
+
const out = node_path_1.default.join(dir, "descriptor.json");
|
|
1040
|
+
const inputsOut = node_path_1.default.join(dir, "descriptor-inputs.ndjson");
|
|
1041
|
+
const diagnostics = node_path_1.default.join(dir, "descriptor.stderr");
|
|
1042
|
+
const bunConfig = node_path_1.default.join(dir, "bunfig.toml");
|
|
1043
|
+
const runtimeHookPreload = node_path_1.default.join(__dirname, "..", "..", "launcher", "internal", "runtimeHookPreload.js");
|
|
1044
|
+
try {
|
|
1045
|
+
if (runtimeCapabilities.bun) {
|
|
1046
|
+
// A descriptor receives exactly the environment supplied by its ttsc
|
|
1047
|
+
// invocation. Bun otherwise auto-loads project `.env*`, local/global
|
|
1048
|
+
// bunfig preloads and loaders, and may install a missing package from the
|
|
1049
|
+
// network. Those implicit authorities are neither part of Node's loader
|
|
1050
|
+
// contract nor reproducible host inputs, so isolate this evaluator from
|
|
1051
|
+
// them while retaining Bun's native TypeScript/module semantics.
|
|
1052
|
+
node_fs_1.default.writeFileSync(bunConfig, "", "utf8");
|
|
1053
|
+
}
|
|
1054
|
+
const diagnosticsFd = node_fs_1.default.openSync(diagnostics, "w");
|
|
1055
|
+
let result;
|
|
1056
|
+
try {
|
|
1057
|
+
result = node_child_process_1.default.spawnSync(runtime, [
|
|
1058
|
+
...(runtimeCapabilities.bun
|
|
1059
|
+
? [
|
|
1060
|
+
"--no-env-file",
|
|
1061
|
+
"--no-install",
|
|
1062
|
+
`--config=${bunConfig}`,
|
|
1063
|
+
`--tsconfig-override=${context.tsconfig}`,
|
|
1064
|
+
]
|
|
1065
|
+
: []),
|
|
1066
|
+
...(runtimeCapabilities.registerHooks
|
|
1067
|
+
? ["--require", runtimeHookPreload]
|
|
1068
|
+
: []),
|
|
1069
|
+
"-e",
|
|
1070
|
+
exports.COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE,
|
|
1071
|
+
], {
|
|
1072
|
+
cwd: context.projectRoot,
|
|
1073
|
+
env: {
|
|
1074
|
+
...effectiveEnv,
|
|
1075
|
+
// The direct evaluator may be Bun, but ttsx and native config
|
|
1076
|
+
// loaders require a real Node runtime with synchronous hooks.
|
|
1077
|
+
...(node === undefined ? {} : { TTSC_NODE_BINARY: node }),
|
|
1078
|
+
TTSC_TTSX_BINARY: effectiveEnv.TTSC_TTSX_BINARY ?? process.env.TTSC_TTSX_BINARY,
|
|
1079
|
+
TTSC_PLUGIN_CONTEXT: JSON.stringify(context),
|
|
1080
|
+
TTSC_PLUGIN_DESCRIPTOR_LOAD: "1",
|
|
1081
|
+
TTSC_PLUGIN_DESCRIPTOR_OUT: out,
|
|
1082
|
+
TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE: "1",
|
|
1083
|
+
TTSC_PLUGIN_DESCRIPTOR_INPUTS_OUT: inputsOut,
|
|
1084
|
+
TTSC_PLUGIN_ENTRY: request,
|
|
1085
|
+
},
|
|
1086
|
+
// Hold direct-evaluator diagnostics until its retry decision is
|
|
1087
|
+
// known. A successful ttsx fallback must not inherit the expected
|
|
1088
|
+
// loader stack from the discarded first attempt.
|
|
1089
|
+
stdio: ["ignore", diagnosticsFd, diagnosticsFd],
|
|
1090
|
+
windowsHide: true,
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
finally {
|
|
1094
|
+
node_fs_1.default.closeSync(diagnosticsFd);
|
|
1095
|
+
}
|
|
1096
|
+
const failure = commonJsDescriptorProcessFailure(result, request);
|
|
1097
|
+
if (failure !== undefined) {
|
|
1098
|
+
const reason = (0, descriptorProcessFailure_1.pluginDescriptorFailureReason)(out);
|
|
1099
|
+
const retryWithTtsx = commonJsDescriptorRetryWithTtsx(out);
|
|
1100
|
+
if (!retryWithTtsx)
|
|
1101
|
+
replayEvaluationDiagnostics(diagnostics);
|
|
1102
|
+
throw new CommonJsDescriptorLoadError(reason === "" ? failure.message : `${failure.message}\n${reason}`, retryWithTtsx);
|
|
1103
|
+
}
|
|
1104
|
+
replayEvaluationDiagnostics(diagnostics);
|
|
1105
|
+
if (!node_fs_1.default.existsSync(out)) {
|
|
1106
|
+
throw new Error(`ttsc: plugin descriptor "${request}" evaluation in an isolated process produced no descriptor output.`);
|
|
1107
|
+
}
|
|
1108
|
+
let parsed;
|
|
1109
|
+
try {
|
|
1110
|
+
parsed = JSON.parse(node_fs_1.default.readFileSync(out, "utf8"));
|
|
1111
|
+
}
|
|
1112
|
+
catch (error) {
|
|
1113
|
+
throw new Error(`ttsc: plugin descriptor "${request}" produced invalid isolated output: ${errorMessage(error)}`);
|
|
1114
|
+
}
|
|
1115
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.inputs)) {
|
|
1116
|
+
throw new Error(`ttsc: plugin descriptor "${request}" produced an invalid isolated result`);
|
|
1117
|
+
}
|
|
1118
|
+
const parsedHashes = isRecord(parsed.inputHashes)
|
|
1119
|
+
? Object.fromEntries(Object.entries(parsed.inputHashes).flatMap(([file, hash]) => typeof hash === "string" || hash === null
|
|
1120
|
+
? [[node_path_1.default.resolve(file), hash]]
|
|
1121
|
+
: []))
|
|
1122
|
+
: {};
|
|
1123
|
+
const parsedRealpaths = isRecord(parsed.inputRealpaths)
|
|
1124
|
+
? Object.fromEntries(Object.entries(parsed.inputRealpaths).flatMap(([file, realpath]) => (typeof realpath === "string" && node_path_1.default.isAbsolute(realpath)) ||
|
|
1125
|
+
realpath === null
|
|
1126
|
+
? [
|
|
1127
|
+
[
|
|
1128
|
+
node_path_1.default.resolve(file),
|
|
1129
|
+
realpath === null ? null : node_path_1.default.resolve(realpath),
|
|
1130
|
+
],
|
|
1131
|
+
]
|
|
1132
|
+
: []))
|
|
1133
|
+
: {};
|
|
1134
|
+
const stableParsedRealpaths = mergeObservedHostInputRealpaths(parsedRealpaths, realpathHostInputPaths(Object.keys(parsedRealpaths)));
|
|
1135
|
+
for (const input of parsed.inputs) {
|
|
1136
|
+
const absolute = node_path_1.default.resolve(String(input));
|
|
1137
|
+
if (!Object.prototype.hasOwnProperty.call(stableParsedRealpaths, absolute)) {
|
|
1138
|
+
delete parsedHashes[absolute];
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
const runtimeInputs = readTtsxDescriptorInputs(inputsOut, request);
|
|
1142
|
+
return {
|
|
1143
|
+
descriptor: parsed.descriptor,
|
|
1144
|
+
hostInputHashes: omitUnstableHostInputHashes(mergeObservedHostInputHashes(parsedHashes, runtimeInputs.hostInputHashes), runtimeInputs.unstableInputs),
|
|
1145
|
+
hostInputRealpaths: mergeObservedHostInputRealpaths(stableParsedRealpaths, runtimeInputs.hostInputRealpaths),
|
|
1146
|
+
inputs: [
|
|
1147
|
+
...new Set([
|
|
1148
|
+
...parsed.inputs.map((input) => String(input)),
|
|
1149
|
+
...runtimeInputs.inputs,
|
|
1150
|
+
]),
|
|
1151
|
+
].sort(),
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
finally {
|
|
1155
|
+
removeEvaluationTempDir(dir);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
/** Validate plugin-declared universal host inputs. */
|
|
1159
|
+
function validatePluginHostInputs(plugin, index) {
|
|
1160
|
+
const label = plugin.name ?? `#${index + 1}`;
|
|
1161
|
+
if (plugin.hostInputs === undefined)
|
|
1162
|
+
return [];
|
|
1163
|
+
if (!Array.isArray(plugin.hostInputs)) {
|
|
1164
|
+
throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputs"; expected an array of absolute paths`);
|
|
1165
|
+
}
|
|
1166
|
+
return plugin.hostInputs.map((file) => {
|
|
1167
|
+
if (typeof file !== "string" || !node_path_1.default.isAbsolute(file)) {
|
|
1168
|
+
throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputs" entry ${JSON.stringify(file)}; expected an absolute path`);
|
|
1169
|
+
}
|
|
1170
|
+
return node_path_1.default.resolve(file);
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
/** Validate descriptor-supplied evaluation fingerprints for host inputs. */
|
|
1174
|
+
function validatePluginHostInputHashes(plugin, index, hostInputs) {
|
|
1175
|
+
const label = plugin.name ?? `#${index + 1}`;
|
|
1176
|
+
if (plugin.hostInputHashes === undefined)
|
|
1177
|
+
return {};
|
|
1178
|
+
if (!isRecord(plugin.hostInputHashes) ||
|
|
1179
|
+
Array.isArray(plugin.hostInputHashes)) {
|
|
1180
|
+
throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputHashes"; expected an object keyed by absolute hostInputs paths`);
|
|
1181
|
+
}
|
|
1182
|
+
const allowed = new Set(hostInputs.map((file) => node_path_1.default.resolve(file)));
|
|
1183
|
+
const output = {};
|
|
1184
|
+
for (const [file, hash] of Object.entries(plugin.hostInputHashes)) {
|
|
1185
|
+
if (!node_path_1.default.isAbsolute(file)) {
|
|
1186
|
+
throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputHashes" key ${JSON.stringify(file)}; expected an absolute path`);
|
|
1187
|
+
}
|
|
1188
|
+
const absolute = node_path_1.default.resolve(file);
|
|
1189
|
+
if (!allowed.has(absolute)) {
|
|
1190
|
+
throw new Error(`ttsc: plugin ${JSON.stringify(label)} fingerprints ${JSON.stringify(file)} without listing it in "hostInputs"`);
|
|
1191
|
+
}
|
|
1192
|
+
if (hash !== null &&
|
|
1193
|
+
(typeof hash !== "string" || !/^[0-9a-f]{64}$/.test(hash))) {
|
|
1194
|
+
throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid fingerprint for ${JSON.stringify(file)}; expected a lowercase SHA-256 digest or null`);
|
|
1195
|
+
}
|
|
1196
|
+
output[absolute] = hash;
|
|
1197
|
+
}
|
|
1198
|
+
return output;
|
|
1199
|
+
}
|
|
1200
|
+
/** Validate descriptor-supplied physical identities for host inputs. */
|
|
1201
|
+
function validatePluginHostInputRealpaths(plugin, index, hostInputs) {
|
|
1202
|
+
const label = plugin.name ?? `#${index + 1}`;
|
|
1203
|
+
if (plugin.hostInputRealpaths === undefined)
|
|
1204
|
+
return {};
|
|
1205
|
+
if (!isRecord(plugin.hostInputRealpaths) ||
|
|
1206
|
+
Array.isArray(plugin.hostInputRealpaths)) {
|
|
1207
|
+
throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputRealpaths"; expected an object keyed by absolute hostInputs paths`);
|
|
1208
|
+
}
|
|
1209
|
+
const allowed = new Set(hostInputs.map((file) => node_path_1.default.resolve(file)));
|
|
1210
|
+
const output = {};
|
|
1211
|
+
for (const [file, realpath] of Object.entries(plugin.hostInputRealpaths)) {
|
|
1212
|
+
if (!node_path_1.default.isAbsolute(file)) {
|
|
1213
|
+
throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputRealpaths" key ${JSON.stringify(file)}; expected an absolute path`);
|
|
1214
|
+
}
|
|
1215
|
+
const absolute = node_path_1.default.resolve(file);
|
|
1216
|
+
if (!allowed.has(absolute)) {
|
|
1217
|
+
throw new Error(`ttsc: plugin ${JSON.stringify(label)} identifies ${JSON.stringify(file)} without listing it in "hostInputs"`);
|
|
1218
|
+
}
|
|
1219
|
+
if (realpath !== null &&
|
|
1220
|
+
(typeof realpath !== "string" || !node_path_1.default.isAbsolute(realpath))) {
|
|
1221
|
+
throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid physical identity for ${JSON.stringify(file)}; expected an absolute realpath or null`);
|
|
1222
|
+
}
|
|
1223
|
+
output[absolute] =
|
|
1224
|
+
realpath === null ? null : node_path_1.default.resolve(realpath);
|
|
1225
|
+
}
|
|
1226
|
+
return output;
|
|
1227
|
+
}
|
|
1228
|
+
/** Merge snapshots and omit every path observed in contradictory states. */
|
|
1229
|
+
function mergeObservedHostInputHashes(...sources) {
|
|
1230
|
+
const conflicts = new Set();
|
|
1231
|
+
const output = {};
|
|
1232
|
+
for (const source of sources) {
|
|
1233
|
+
for (const [file, hash] of Object.entries(source)) {
|
|
1234
|
+
const absolute = node_path_1.default.resolve(file);
|
|
1235
|
+
if (conflicts.has(absolute))
|
|
1236
|
+
continue;
|
|
1237
|
+
if (Object.prototype.hasOwnProperty.call(output, absolute) &&
|
|
1238
|
+
output[absolute] !== hash) {
|
|
1239
|
+
delete output[absolute];
|
|
1240
|
+
conflicts.add(absolute);
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
output[absolute] = hash;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
return output;
|
|
1247
|
+
}
|
|
1248
|
+
const mergeObservedHostInputRealpaths = mergeObservedHostInputHashes;
|
|
1249
|
+
/** Prevent a later plugin claim from reviving an unstable loader input. */
|
|
1250
|
+
function mergePluginHostInputHashes(first, second, loaderInputs, pluginInputs) {
|
|
1251
|
+
const output = { ...first };
|
|
1252
|
+
const guarded = new Set(loaderInputs.map((file) => node_path_1.default.resolve(file)));
|
|
1253
|
+
for (const input of pluginInputs) {
|
|
1254
|
+
const absolute = node_path_1.default.resolve(input);
|
|
1255
|
+
if (!Object.prototype.hasOwnProperty.call(second, absolute)) {
|
|
1256
|
+
delete output[absolute];
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
for (const [file, hash] of Object.entries(second)) {
|
|
1260
|
+
const absolute = node_path_1.default.resolve(file);
|
|
1261
|
+
if (guarded.has(absolute) &&
|
|
1262
|
+
!Object.prototype.hasOwnProperty.call(first, absolute)) {
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
if (Object.prototype.hasOwnProperty.call(output, absolute) &&
|
|
1266
|
+
output[absolute] !== hash) {
|
|
1267
|
+
delete output[absolute];
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
output[absolute] = hash;
|
|
1271
|
+
}
|
|
1272
|
+
return output;
|
|
1273
|
+
}
|
|
1274
|
+
function omitUnstableHostInputHashes(hashes, unstableInputs) {
|
|
1275
|
+
for (const input of unstableInputs)
|
|
1276
|
+
delete hashes[node_path_1.default.resolve(input)];
|
|
1277
|
+
return hashes;
|
|
1278
|
+
}
|
|
1279
|
+
/** CommonJS evaluator emitted into a clean Node process for every load. */
|
|
1280
|
+
exports.COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE = [
|
|
1281
|
+
`const crypto = require("node:crypto");`,
|
|
1282
|
+
`const fs = require("node:fs");`,
|
|
1283
|
+
`const Module = require("node:module");`,
|
|
1284
|
+
`const path = require("node:path");`,
|
|
1285
|
+
`const { fileURLToPath } = require("node:url");`,
|
|
1286
|
+
`const out = process.env.TTSC_PLUGIN_DESCRIPTOR_OUT;`,
|
|
1287
|
+
`let retryWithTtsx = false;`,
|
|
1288
|
+
`const moduleLoadFailures = new WeakMap();`,
|
|
1289
|
+
`const moduleResolutionFailures = new WeakMap();`,
|
|
1290
|
+
`function existingFile(file) { try { return fs.statSync(file).isFile(); } catch { return false; } }`,
|
|
1291
|
+
`function shouldRetryWithTtsx(error) {`,
|
|
1292
|
+
` const loadFailure = error && (typeof error === "object" || typeof error === "function") ? moduleLoadFailures.get(error) : undefined;`,
|
|
1293
|
+
` if (loadFailure?.retryWithTtsx === true) return true;`,
|
|
1294
|
+
` const failure = error && (typeof error === "object" || typeof error === "function") ? moduleResolutionFailures.get(error) : undefined;`,
|
|
1295
|
+
` return failure?.retryWithTtsx === true;`,
|
|
1296
|
+
`}`,
|
|
1297
|
+
`try {`,
|
|
1298
|
+
` const request = process.env.TTSC_PLUGIN_ENTRY;`,
|
|
1299
|
+
` const context = JSON.parse(process.env.TTSC_PLUGIN_CONTEXT);`,
|
|
1300
|
+
` const inputs = new Set();`,
|
|
1301
|
+
` const inputHashes = new Map();`,
|
|
1302
|
+
` const inputRealpaths = new Map();`,
|
|
1303
|
+
` const inputSignatures = new Map();`,
|
|
1304
|
+
` const unstableInputHashes = new Set();`,
|
|
1305
|
+
` function missingPathError(error) { return error && (error.code === "ENOENT" || error.code === "ENOTDIR"); }`,
|
|
1306
|
+
` function inputMetadataSignature(file) {`,
|
|
1307
|
+
` const requested = path.resolve(file);`,
|
|
1308
|
+
` let current = requested;`,
|
|
1309
|
+
` for (;;) {`,
|
|
1310
|
+
` try {`,
|
|
1311
|
+
` const link = fs.lstatSync(current, { bigint: true });`,
|
|
1312
|
+
` let target = link;`,
|
|
1313
|
+
` if (link.isSymbolicLink()) {`,
|
|
1314
|
+
` try { target = fs.statSync(current, { bigint: true }); }`,
|
|
1315
|
+
` catch { return undefined; }`,
|
|
1316
|
+
` }`,
|
|
1317
|
+
` return [path.relative(current, requested), link.dev, link.ino, link.mode, link.size, link.mtimeNs, link.ctimeNs, target.dev, target.ino, target.mode, target.size, target.mtimeNs, target.ctimeNs].join(":");`,
|
|
1318
|
+
` } catch (error) {`,
|
|
1319
|
+
` if (!missingPathError(error)) return undefined;`,
|
|
1320
|
+
` const parent = path.dirname(current);`,
|
|
1321
|
+
` if (parent === current) return undefined;`,
|
|
1322
|
+
` current = parent;`,
|
|
1323
|
+
` }`,
|
|
1324
|
+
` }`,
|
|
1325
|
+
` }`,
|
|
1326
|
+
` function recordOneInput(file) {`,
|
|
1327
|
+
` file = path.resolve(file);`,
|
|
1328
|
+
` inputs.add(file);`,
|
|
1329
|
+
` if (unstableInputHashes.has(file)) return;`,
|
|
1330
|
+
` const beforeSignature = inputMetadataSignature(file);`,
|
|
1331
|
+
` let observedRealpath;`,
|
|
1332
|
+
` try { observedRealpath = fs.realpathSync.native(file); } catch { observedRealpath = null; }`,
|
|
1333
|
+
` let observed;`,
|
|
1334
|
+
` try { observed = fs.statSync(file).isDirectory() ? crypto.createHash("sha256").update("ttsc:host-input:directory\\0").digest("hex") : crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex"); } catch { observed = null; }`,
|
|
1335
|
+
` const afterSignature = inputMetadataSignature(file);`,
|
|
1336
|
+
` if (beforeSignature === undefined || afterSignature === undefined || beforeSignature !== afterSignature || (inputSignatures.has(file) && inputSignatures.get(file) !== afterSignature) || (inputRealpaths.has(file) && inputRealpaths.get(file) !== observedRealpath) || (inputHashes.has(file) && inputHashes.get(file) !== observed)) { inputHashes.delete(file); inputRealpaths.delete(file); inputSignatures.delete(file); unstableInputHashes.add(file); return; }`,
|
|
1337
|
+
` inputSignatures.set(file, afterSignature);`,
|
|
1338
|
+
` inputRealpaths.set(file, observedRealpath);`,
|
|
1339
|
+
` inputHashes.set(file, observed);`,
|
|
1340
|
+
` }`,
|
|
1341
|
+
` function recordInput(file) {`,
|
|
1342
|
+
` file = path.resolve(file);`,
|
|
1343
|
+
` recordOneInput(file);`,
|
|
1344
|
+
` const parsed = path.parse(file);`,
|
|
1345
|
+
` let current = parsed.root;`,
|
|
1346
|
+
` const relative = path.relative(parsed.root, file);`,
|
|
1347
|
+
` for (const segment of relative.split(path.sep).slice(0, -1)) {`,
|
|
1348
|
+
` if (segment === "") continue;`,
|
|
1349
|
+
` current = path.join(current, segment);`,
|
|
1350
|
+
` try {`,
|
|
1351
|
+
` if (fs.lstatSync(current).isSymbolicLink()) recordOneInput(current);`,
|
|
1352
|
+
` } catch {`,
|
|
1353
|
+
` break;`,
|
|
1354
|
+
` }`,
|
|
1355
|
+
` }`,
|
|
1356
|
+
` }`,
|
|
1357
|
+
` function asFile(resolved) {`,
|
|
1358
|
+
` if (typeof resolved !== "string") return undefined;`,
|
|
1359
|
+
` if (!resolved.startsWith("file:")) return path.isAbsolute(resolved) ? path.resolve(resolved) : undefined;`,
|
|
1360
|
+
` try { return path.resolve(fileURLToPath(resolved)); } catch { return undefined; }`,
|
|
1361
|
+
` }`,
|
|
1362
|
+
` function recordFile(resolved) {`,
|
|
1363
|
+
` const file = asFile(resolved);`,
|
|
1364
|
+
` if (file === undefined) return;`,
|
|
1365
|
+
` recordInput(file);`,
|
|
1366
|
+
` for (let directory = path.dirname(file);;) {`,
|
|
1367
|
+
` const manifest = path.join(directory, "package.json");`,
|
|
1368
|
+
` recordInput(manifest);`,
|
|
1369
|
+
` if (existingFile(manifest)) break;`,
|
|
1370
|
+
` const parent = path.dirname(directory);`,
|
|
1371
|
+
` if (parent === directory) break;`,
|
|
1372
|
+
` directory = parent;`,
|
|
1373
|
+
` }`,
|
|
1374
|
+
` }`,
|
|
1375
|
+
` function recordPackageManifests(file) {`,
|
|
1376
|
+
` for (let directory = path.dirname(path.resolve(file));;) {`,
|
|
1377
|
+
` const manifest = path.join(directory, "package.json");`,
|
|
1378
|
+
` recordInput(manifest);`,
|
|
1379
|
+
` if (existingFile(manifest)) return;`,
|
|
1380
|
+
` const parent = path.dirname(directory);`,
|
|
1381
|
+
` if (parent === directory) return;`,
|
|
1382
|
+
` directory = parent;`,
|
|
1383
|
+
` }`,
|
|
1384
|
+
` }`,
|
|
1385
|
+
` recordFile(request);`,
|
|
1386
|
+
` const moduleProbeExtensions = typeof globalThis.Bun === "object" ? [".tsx", ".jsx", ".ts", ".mjs", ".js", ".cjs", ".json"] : [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json", ".node"];`,
|
|
1387
|
+
` const jsToTsProbeExtensions = new Map([[".js", [".ts", ".tsx"]], [".jsx", [".tsx"]], [".mjs", [".mts"]], [".cjs", [".cts"]]]);`,
|
|
1388
|
+
` function sourceSubstitutionCandidates(base) {`,
|
|
1389
|
+
` const extension = path.extname(base).toLowerCase();`,
|
|
1390
|
+
` const substitutions = jsToTsProbeExtensions.get(extension);`,
|
|
1391
|
+
` if (substitutions === undefined) return [];`,
|
|
1392
|
+
` const stem = base.slice(0, base.length - extension.length);`,
|
|
1393
|
+
` return substitutions.map((candidate) => stem + candidate);`,
|
|
1394
|
+
` }`,
|
|
1395
|
+
` function moduleCandidates(base) {`,
|
|
1396
|
+
` return [base, ...sourceSubstitutionCandidates(base), ...moduleProbeExtensions.map((extension) => base + extension), path.join(base, "package.json"), ...moduleProbeExtensions.map((extension) => path.join(base, "index" + extension))];`,
|
|
1397
|
+
` }`,
|
|
1398
|
+
` const recordedModuleBases = new Set();`,
|
|
1399
|
+
` function recordManifestTargets(value, directory, allowBare = false) {`,
|
|
1400
|
+
` if (typeof value === "string") {`,
|
|
1401
|
+
` if (value !== "" && (allowBare || value.startsWith("./") || value.startsWith("../"))) recordModuleCandidates(path.resolve(directory, value));`,
|
|
1402
|
+
` return;`,
|
|
1403
|
+
` }`,
|
|
1404
|
+
` if (Array.isArray(value)) { for (const item of value) recordManifestTargets(item, directory, allowBare); return; }`,
|
|
1405
|
+
` if (value && typeof value === "object") for (const item of Object.values(value)) recordManifestTargets(item, directory, allowBare);`,
|
|
1406
|
+
` }`,
|
|
1407
|
+
` function recordModuleCandidates(base) {`,
|
|
1408
|
+
` const resolvedBase = path.resolve(base);`,
|
|
1409
|
+
` if (recordedModuleBases.has(resolvedBase)) return;`,
|
|
1410
|
+
` recordedModuleBases.add(resolvedBase);`,
|
|
1411
|
+
` for (const candidate of moduleCandidates(resolvedBase)) recordInput(candidate);`,
|
|
1412
|
+
` try {`,
|
|
1413
|
+
` const manifest = JSON.parse(fs.readFileSync(path.join(resolvedBase, "package.json"), "utf8").replace(/^\uFEFF/, ""));`,
|
|
1414
|
+
` recordManifestTargets(manifest.exports, resolvedBase);`,
|
|
1415
|
+
` recordManifestTargets(manifest.module, resolvedBase, true);`,
|
|
1416
|
+
` recordManifestTargets(manifest.main, resolvedBase, true);`,
|
|
1417
|
+
` } catch {}`,
|
|
1418
|
+
` }`,
|
|
1419
|
+
` function candidateSelected(base, selected) {`,
|
|
1420
|
+
` if (selected === undefined) return false;`,
|
|
1421
|
+
` for (const candidate of moduleCandidates(base)) {`,
|
|
1422
|
+
` try {`,
|
|
1423
|
+
` const canonical = fs.realpathSync.native(candidate);`,
|
|
1424
|
+
` const relative = path.relative(canonical, selected);`,
|
|
1425
|
+
` if (relative === "" || (fs.statSync(canonical).isDirectory() && relative !== ".." && !relative.startsWith(".." + path.sep) && !path.isAbsolute(relative))) return true;`,
|
|
1426
|
+
` } catch {}`,
|
|
1427
|
+
` }`,
|
|
1428
|
+
` return false;`,
|
|
1429
|
+
` }`,
|
|
1430
|
+
` function localBases(specifier, parentFile) {`,
|
|
1431
|
+
` if (specifier.startsWith("file:")) return [fileURLToPath(specifier)];`,
|
|
1432
|
+
` const directory = path.dirname(parentFile);`,
|
|
1433
|
+
` const raw = path.resolve(directory, specifier);`,
|
|
1434
|
+
` const suffixStart = specifier.search(/[?#]/);`,
|
|
1435
|
+
` if (suffixStart === -1) return [raw];`,
|
|
1436
|
+
` const pathname = specifier.slice(0, suffixStart);`,
|
|
1437
|
+
` return pathname === "" ? [raw] : [...new Set([raw, path.resolve(directory, pathname)])];`,
|
|
1438
|
+
` }`,
|
|
1439
|
+
` function recordResolutionCandidates(specifier, parent, resolved) {`,
|
|
1440
|
+
` const parentFile = asFile(parent);`,
|
|
1441
|
+
` if (typeof specifier !== "string" || parentFile === undefined) return;`,
|
|
1442
|
+
` let selected;`,
|
|
1443
|
+
` try { const file = asFile(resolved); selected = file === undefined ? undefined : fs.realpathSync.native(file); } catch {}`,
|
|
1444
|
+
` if (specifier.startsWith(".") || path.isAbsolute(specifier) || specifier.startsWith("file:")) {`,
|
|
1445
|
+
` try {`,
|
|
1446
|
+
` for (const base of localBases(specifier, parentFile)) {`,
|
|
1447
|
+
` recordPackageManifests(base);`,
|
|
1448
|
+
` let exact = false;`,
|
|
1449
|
+
` try { exact = selected === undefined ? fs.statSync(base).isFile() : fs.realpathSync.native(base) === selected; } catch {}`,
|
|
1450
|
+
` if (exact) recordInput(base);`,
|
|
1451
|
+
` if (!exact) recordModuleCandidates(base);`,
|
|
1452
|
+
` }`,
|
|
1453
|
+
` } catch {}`,
|
|
1454
|
+
` return;`,
|
|
1455
|
+
` }`,
|
|
1456
|
+
` if (Module.isBuiltin(specifier) || specifier.startsWith("#")) return;`,
|
|
1457
|
+
` const parts = specifier.split("/");`,
|
|
1458
|
+
` const packageParts = parts[0].startsWith("@") ? parts.slice(0, 2) : parts.slice(0, 1);`,
|
|
1459
|
+
` if (packageParts.some((part) => part === undefined || part === "")) return;`,
|
|
1460
|
+
` const packageName = packageParts.join("/");`,
|
|
1461
|
+
` const subpath = parts.slice(packageParts.length);`,
|
|
1462
|
+
` const searchPaths = Module.createRequire(parentFile).resolve.paths(specifier) ?? [];`,
|
|
1463
|
+
` for (const searchPath of searchPaths) {`,
|
|
1464
|
+
` const packageDirectory = path.join(searchPath, packageName);`,
|
|
1465
|
+
` recordModuleCandidates(packageDirectory);`,
|
|
1466
|
+
` if (subpath.length !== 0) recordModuleCandidates(path.join(packageDirectory, ...subpath));`,
|
|
1467
|
+
` if (candidateSelected(packageDirectory, selected)) break;`,
|
|
1468
|
+
` }`,
|
|
1469
|
+
` }`,
|
|
1470
|
+
` function scanBunModuleGraph(entry) {`,
|
|
1471
|
+
` if (typeof globalThis.Bun !== "object" || typeof globalThis.Bun.Transpiler !== "function") return;`,
|
|
1472
|
+
` const scanned = new Set();`,
|
|
1473
|
+
` function scan(file) {`,
|
|
1474
|
+
` file = asFile(file);`,
|
|
1475
|
+
` if (file === undefined) return;`,
|
|
1476
|
+
` let canonical;`,
|
|
1477
|
+
` try { canonical = fs.realpathSync.native(file); } catch { return; }`,
|
|
1478
|
+
` if (scanned.has(canonical)) return;`,
|
|
1479
|
+
` scanned.add(canonical);`,
|
|
1480
|
+
` const extension = path.extname(canonical).toLowerCase();`,
|
|
1481
|
+
` if (![".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"].includes(extension)) return;`,
|
|
1482
|
+
` const loader = extension === ".tsx" ? "tsx" : extension === ".jsx" ? "jsx" : [".ts", ".mts", ".cts"].includes(extension) ? "ts" : "js";`,
|
|
1483
|
+
` let imports;`,
|
|
1484
|
+
` try {`,
|
|
1485
|
+
` const source = fs.readFileSync(canonical, "utf8");`,
|
|
1486
|
+
` imports = new globalThis.Bun.Transpiler({ loader }).scan(source).imports;`,
|
|
1487
|
+
` } catch { return; }`,
|
|
1488
|
+
` for (const imported of imports) {`,
|
|
1489
|
+
` const specifier = imported && imported.path;`,
|
|
1490
|
+
` if (typeof specifier !== "string" || specifier.startsWith("node:") || Module.isBuiltin(specifier)) continue;`,
|
|
1491
|
+
` recordResolutionCandidates(specifier, canonical, undefined);`,
|
|
1492
|
+
` try {`,
|
|
1493
|
+
` const resolved = globalThis.Bun.resolveSync(specifier, path.dirname(canonical));`,
|
|
1494
|
+
` recordResolutionCandidates(specifier, canonical, resolved);`,
|
|
1495
|
+
` recordFile(resolved);`,
|
|
1496
|
+
` scan(resolved);`,
|
|
1497
|
+
` } catch {`,
|
|
1498
|
+
` // The real descriptor load below owns user-facing resolution errors.`,
|
|
1499
|
+
` }`,
|
|
1500
|
+
` }`,
|
|
1501
|
+
` }`,
|
|
1502
|
+
` scan(entry);`,
|
|
1503
|
+
` }`,
|
|
1504
|
+
` if (typeof Module.registerHooks === "function") {`,
|
|
1505
|
+
` Module.registerHooks({`,
|
|
1506
|
+
` load(url, loadContext, nextLoad) {`,
|
|
1507
|
+
` try { return nextLoad(url, loadContext); }`,
|
|
1508
|
+
` catch (error) {`,
|
|
1509
|
+
` if (error && (typeof error === "object" || typeof error === "function")) moduleLoadFailures.set(error, { retryWithTtsx: error.code === "ERR_UNKNOWN_FILE_EXTENSION" });`,
|
|
1510
|
+
` throw error;`,
|
|
1511
|
+
` }`,
|
|
1512
|
+
` },`,
|
|
1513
|
+
` resolve(specifier, resolveContext, nextResolve) {`,
|
|
1514
|
+
` recordResolutionCandidates(specifier, resolveContext.parentURL, undefined);`,
|
|
1515
|
+
` let resolved;`,
|
|
1516
|
+
` try {`,
|
|
1517
|
+
` resolved = nextResolve(specifier, resolveContext);`,
|
|
1518
|
+
` } catch (error) {`,
|
|
1519
|
+
` if (error && (typeof error === "object" || typeof error === "function")) {`,
|
|
1520
|
+
` const parent = asFile(resolveContext.parentURL);`,
|
|
1521
|
+
` const anchor = typeof parent === "string" ? parent : request;`,
|
|
1522
|
+
` const candidate = specifier.startsWith(".") && path.extname(specifier) === "" ? path.resolve(path.dirname(anchor), specifier) : undefined;`,
|
|
1523
|
+
` const retryWithTtsx = candidate !== undefined && [".ts", ".cts", ".mts", ".tsx"].some((extension) => existingFile(candidate + extension) || existingFile(path.join(candidate, "index" + extension)));`,
|
|
1524
|
+
` moduleResolutionFailures.set(error, { retryWithTtsx });`,
|
|
1525
|
+
` }`,
|
|
1526
|
+
` throw error;`,
|
|
1527
|
+
` }`,
|
|
1528
|
+
` const url = typeof resolved === "string" ? resolved : resolved && resolved.url;`,
|
|
1529
|
+
` recordResolutionCandidates(specifier, resolveContext.parentURL, url);`,
|
|
1530
|
+
` recordFile(url);`,
|
|
1531
|
+
` return resolved;`,
|
|
1532
|
+
` },`,
|
|
1533
|
+
` });`,
|
|
1534
|
+
` }`,
|
|
1535
|
+
` if (typeof globalThis.Bun === "object" && typeof globalThis.Bun.plugin === "function") {`,
|
|
1536
|
+
` let insideBunResolve = false;`,
|
|
1537
|
+
` globalThis.Bun.plugin({`,
|
|
1538
|
+
` name: "ttsc-plugin-descriptor-inputs",`,
|
|
1539
|
+
` setup(build) {`,
|
|
1540
|
+
` build.onResolve({ filter: /.*/ }, (args) => {`,
|
|
1541
|
+
` if (insideBunResolve || args.path.startsWith("node:")) return;`,
|
|
1542
|
+
` try {`,
|
|
1543
|
+
` insideBunResolve = true;`,
|
|
1544
|
+
` const importer = args.importer && args.importer.startsWith("file:") ? fileURLToPath(args.importer) : args.importer;`,
|
|
1545
|
+
` const from = importer ? path.dirname(importer) : process.cwd();`,
|
|
1546
|
+
` recordResolutionCandidates(args.path, importer, undefined);`,
|
|
1547
|
+
` const resolved = globalThis.Bun.resolveSync(args.path, from);`,
|
|
1548
|
+
` recordResolutionCandidates(args.path, importer, resolved);`,
|
|
1549
|
+
` recordFile(resolved);`,
|
|
1550
|
+
` } catch {`,
|
|
1551
|
+
` // The real resolver below owns user-facing resolution errors.`,
|
|
1552
|
+
` } finally {`,
|
|
1553
|
+
` insideBunResolve = false;`,
|
|
1554
|
+
` }`,
|
|
1555
|
+
` });`,
|
|
1556
|
+
` },`,
|
|
1557
|
+
` });`,
|
|
1558
|
+
` }`,
|
|
1559
|
+
` scanBunModuleGraph(request);`,
|
|
1560
|
+
` let mod;`,
|
|
1561
|
+
` try {`,
|
|
1562
|
+
` mod = require(request);`,
|
|
1563
|
+
` } catch (error) {`,
|
|
1564
|
+
` retryWithTtsx = shouldRetryWithTtsx(error);`,
|
|
1565
|
+
` throw error;`,
|
|
1566
|
+
` }`,
|
|
1567
|
+
` const candidate = mod.createTtscPlugin ?? mod.default ?? mod.plugin ?? mod;`,
|
|
1568
|
+
` const descriptor = typeof candidate === "function" ? candidate(context) : candidate;`,
|
|
1569
|
+
` if (descriptor && typeof descriptor === "object" && ("transformSource" in descriptor || "transformOutput" in descriptor)) {`,
|
|
1570
|
+
` throw new Error("ttsc: plugin descriptor declares unsupported JS transform functions; declare a native backend instead");`,
|
|
1571
|
+
` }`,
|
|
1572
|
+
// Serialize the descriptor while the module-resolution hooks are still
|
|
1573
|
+
// collecting. Getters are allowed by JavaScript's object model and can
|
|
1574
|
+
// lazily require a config; snapshotting inputs first would silently omit
|
|
1575
|
+
// that influence.
|
|
1576
|
+
` const serializedDescriptor = JSON.stringify(descriptor);`,
|
|
1577
|
+
` for (const input of [...inputs]) recordInput(input);`,
|
|
1578
|
+
` const payload = { inputHashes: Object.fromEntries(inputHashes), inputRealpaths: Object.fromEntries(inputRealpaths), inputs: [...inputs].sort() };`,
|
|
1579
|
+
` if (serializedDescriptor !== undefined) payload.descriptor = JSON.parse(serializedDescriptor);`,
|
|
1580
|
+
` fs.writeFileSync(out, JSON.stringify(payload));`,
|
|
1581
|
+
`} catch (error) {`,
|
|
1582
|
+
` const message = error instanceof Error ? error.message : String(error);`,
|
|
1583
|
+
` const stack = error instanceof Error && error.stack ? error.stack : String(error);`,
|
|
1584
|
+
` try {`,
|
|
1585
|
+
` fs.writeFileSync(out, JSON.stringify({ __ttscLoaderError: message, __ttscRetryWithTtsx: retryWithTtsx }));`,
|
|
1586
|
+
` } catch {}`,
|
|
1587
|
+
` try { process.stderr.write(stack + "\\n"); } catch {}`,
|
|
1588
|
+
` process.exit(1);`,
|
|
1589
|
+
`}`,
|
|
1590
|
+
``,
|
|
1591
|
+
].join("\n");
|
|
1592
|
+
/** Read the isolated loader's explicit retry classification. */
|
|
1593
|
+
function commonJsDescriptorRetryWithTtsx(file) {
|
|
1594
|
+
try {
|
|
1595
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
|
|
1596
|
+
return isRecord(parsed) && parsed.__ttscRetryWithTtsx === true;
|
|
1597
|
+
}
|
|
1598
|
+
catch {
|
|
1599
|
+
return false;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
function commonJsDescriptorProcessFailure(result, request) {
|
|
1603
|
+
if (result.error !== undefined) {
|
|
1604
|
+
return new Error(`ttsc: failed to launch an isolated process for plugin descriptor "${request}": ${result.error.message}`);
|
|
1605
|
+
}
|
|
1606
|
+
if (result.signal !== null) {
|
|
1607
|
+
return new Error(`ttsc: plugin descriptor "${request}" isolated evaluation was killed by signal ${result.signal}.`);
|
|
516
1608
|
}
|
|
1609
|
+
if (result.status !== 0) {
|
|
1610
|
+
return new Error(`ttsc: plugin descriptor "${request}" isolated evaluation failed with exit code ${String(result.status)}`);
|
|
1611
|
+
}
|
|
1612
|
+
return undefined;
|
|
517
1613
|
}
|
|
518
1614
|
const TS_SOURCE_PATTERN = /\.(?:[cm]?ts|tsx)$/i;
|
|
519
1615
|
/**
|
|
@@ -537,6 +1633,11 @@ exports.PLUGIN_DESCRIPTOR_SHIM_SOURCE = [
|
|
|
537
1633
|
// both — "Cannot find module ./missing" is as actionable as anything the
|
|
538
1634
|
// factory could have said.
|
|
539
1635
|
`try {`,
|
|
1636
|
+
// Runtime hooks are installed before this shim loads. Arm their internal
|
|
1637
|
+
// side channel only for the descriptor import itself, after this shim's own
|
|
1638
|
+
// imports have resolved, so ttsc implementation files never become project
|
|
1639
|
+
// cache inputs.
|
|
1640
|
+
` process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE = "1";`,
|
|
540
1641
|
` const mod = await import(pathToFileURL(process.env.TTSC_PLUGIN_ENTRY).href);`,
|
|
541
1642
|
` const context = JSON.parse(process.env.TTSC_PLUGIN_CONTEXT);`,
|
|
542
1643
|
` const candidate = mod.createTtscPlugin ?? mod.default ?? mod.plugin ?? mod;`,
|
|
@@ -570,28 +1671,27 @@ function loadDescriptorViaTtsx(request, context, effectiveEnv) {
|
|
|
570
1671
|
// Binary discovery prefers the instance environment, then the ambient
|
|
571
1672
|
// process.env (where `withPluginLoaderEnv` injects ttsc's own node/ttsx paths
|
|
572
1673
|
// just before this runs), then the running interpreter.
|
|
573
|
-
const node = effectiveEnv.
|
|
574
|
-
process.env.TTSC_NODE_BINARY ??
|
|
575
|
-
process.execPath;
|
|
1674
|
+
const node = (0, resolveNodeBinary_1.resolveNodeBinary)(effectiveEnv, context.projectRoot);
|
|
576
1675
|
const ttsx = effectiveEnv.TTSC_TTSX_BINARY ?? process.env.TTSC_TTSX_BINARY;
|
|
577
|
-
if (ttsx === undefined || ttsx.length === 0) {
|
|
1676
|
+
if (node === undefined || ttsx === undefined || ttsx.length === 0) {
|
|
578
1677
|
return undefined;
|
|
579
1678
|
}
|
|
580
|
-
const dir =
|
|
1679
|
+
const dir = createEvaluationTempDir();
|
|
581
1680
|
const out = node_path_1.default.join(dir, "descriptor.json");
|
|
1681
|
+
const inputsOut = node_path_1.default.join(dir, "descriptor-inputs.ndjson");
|
|
582
1682
|
const shim = node_path_1.default.join(dir, "load-descriptor.mts");
|
|
583
1683
|
// ttsx type-checks and builds the shim's own project, so it needs a tsconfig
|
|
584
1684
|
// to anchor on; a minimal one is enough (the shim is `@ts-nocheck`).
|
|
585
|
-
node_fs_1.default.writeFileSync(node_path_1.default.join(dir, "tsconfig.json"), JSON.stringify({
|
|
586
|
-
compilerOptions: {
|
|
587
|
-
module: "nodenext",
|
|
588
|
-
moduleResolution: "nodenext",
|
|
589
|
-
skipLibCheck: true,
|
|
590
|
-
target: "es2022",
|
|
591
|
-
},
|
|
592
|
-
}));
|
|
593
|
-
node_fs_1.default.writeFileSync(shim, exports.PLUGIN_DESCRIPTOR_SHIM_SOURCE);
|
|
594
1685
|
try {
|
|
1686
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(dir, "tsconfig.json"), JSON.stringify({
|
|
1687
|
+
compilerOptions: {
|
|
1688
|
+
module: "nodenext",
|
|
1689
|
+
moduleResolution: "nodenext",
|
|
1690
|
+
skipLibCheck: true,
|
|
1691
|
+
target: "es2022",
|
|
1692
|
+
},
|
|
1693
|
+
}));
|
|
1694
|
+
node_fs_1.default.writeFileSync(shim, exports.PLUGIN_DESCRIPTOR_SHIM_SOURCE);
|
|
595
1695
|
const result = node_child_process_1.default.spawnSync(node, [ttsx, "--no-plugins", shim], {
|
|
596
1696
|
cwd: context.projectRoot,
|
|
597
1697
|
encoding: "utf8",
|
|
@@ -608,11 +1708,13 @@ function loadDescriptorViaTtsx(request, context, effectiveEnv) {
|
|
|
608
1708
|
dirname: context.dirname,
|
|
609
1709
|
filename: context.filename,
|
|
610
1710
|
plugin: context.plugin,
|
|
1711
|
+
pluginConfigDir: context.pluginConfigDir,
|
|
611
1712
|
projectRoot: context.projectRoot,
|
|
612
1713
|
tsconfig: context.tsconfig,
|
|
613
1714
|
}),
|
|
614
1715
|
TTSC_PLUGIN_DESCRIPTOR_LOAD: "1",
|
|
615
1716
|
TTSC_PLUGIN_DESCRIPTOR_OUT: out,
|
|
1717
|
+
TTSC_PLUGIN_DESCRIPTOR_INPUTS_OUT: inputsOut,
|
|
616
1718
|
TTSC_PLUGIN_ENTRY: request,
|
|
617
1719
|
},
|
|
618
1720
|
// Both child streams are human output, and they go straight to this
|
|
@@ -638,7 +1740,13 @@ ${reason}`);
|
|
|
638
1740
|
}
|
|
639
1741
|
const text = node_fs_1.default.readFileSync(out, "utf8");
|
|
640
1742
|
try {
|
|
641
|
-
|
|
1743
|
+
const inputSnapshot = readTtsxDescriptorInputs(inputsOut, request);
|
|
1744
|
+
return {
|
|
1745
|
+
descriptor: JSON.parse(text),
|
|
1746
|
+
hostInputHashes: omitUnstableHostInputHashes(inputSnapshot.hostInputHashes, inputSnapshot.unstableInputs),
|
|
1747
|
+
hostInputRealpaths: inputSnapshot.hostInputRealpaths,
|
|
1748
|
+
inputs: inputSnapshot.inputs,
|
|
1749
|
+
};
|
|
642
1750
|
}
|
|
643
1751
|
catch (error) {
|
|
644
1752
|
throw new Error(`ttsc: plugin descriptor "${request}" produced invalid JSON: ${errorMessage(error)}`);
|
|
@@ -648,13 +1756,121 @@ ${reason}`);
|
|
|
648
1756
|
removeEvaluationTempDir(dir);
|
|
649
1757
|
}
|
|
650
1758
|
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Expand the ttsx runtime's selected module edges into the same exact and
|
|
1761
|
+
* missing resolution inputs used by the direct isolated evaluator.
|
|
1762
|
+
*/
|
|
1763
|
+
function readTtsxDescriptorInputs(file, request) {
|
|
1764
|
+
const inputs = new Set([node_path_1.default.resolve(request)]);
|
|
1765
|
+
const hashes = new Map();
|
|
1766
|
+
const realpaths = new Map();
|
|
1767
|
+
const signatures = new Map();
|
|
1768
|
+
const unstableInputs = new Set();
|
|
1769
|
+
let text;
|
|
1770
|
+
try {
|
|
1771
|
+
text = node_fs_1.default.readFileSync(file, "utf8");
|
|
1772
|
+
}
|
|
1773
|
+
catch {
|
|
1774
|
+
return {
|
|
1775
|
+
hostInputHashes: {},
|
|
1776
|
+
hostInputRealpaths: {},
|
|
1777
|
+
inputs: [...inputs],
|
|
1778
|
+
unstableInputs: [],
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1782
|
+
if (line.trim() === "")
|
|
1783
|
+
continue;
|
|
1784
|
+
let record;
|
|
1785
|
+
try {
|
|
1786
|
+
record = JSON.parse(line);
|
|
1787
|
+
}
|
|
1788
|
+
catch {
|
|
1789
|
+
continue;
|
|
1790
|
+
}
|
|
1791
|
+
if (typeof record.resolved === "string") {
|
|
1792
|
+
const resolved = node_path_1.default.resolve(record.resolved);
|
|
1793
|
+
inputs.add(resolved);
|
|
1794
|
+
if (record.unstable === true) {
|
|
1795
|
+
hashes.delete(resolved);
|
|
1796
|
+
realpaths.delete(resolved);
|
|
1797
|
+
signatures.delete(resolved);
|
|
1798
|
+
unstableInputs.add(resolved);
|
|
1799
|
+
continue;
|
|
1800
|
+
}
|
|
1801
|
+
if (typeof record.signature !== "string" ||
|
|
1802
|
+
(signatures.has(resolved) &&
|
|
1803
|
+
signatures.get(resolved) !== record.signature)) {
|
|
1804
|
+
hashes.delete(resolved);
|
|
1805
|
+
realpaths.delete(resolved);
|
|
1806
|
+
signatures.delete(resolved);
|
|
1807
|
+
unstableInputs.add(resolved);
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
signatures.set(resolved, record.signature);
|
|
1811
|
+
if ((typeof record.realpath === "string" &&
|
|
1812
|
+
node_path_1.default.isAbsolute(record.realpath)) ||
|
|
1813
|
+
record.realpath === null) {
|
|
1814
|
+
const observed = record.realpath === null ? null : node_path_1.default.resolve(record.realpath);
|
|
1815
|
+
if (realpaths.has(resolved) && realpaths.get(resolved) !== observed) {
|
|
1816
|
+
hashes.delete(resolved);
|
|
1817
|
+
realpaths.delete(resolved);
|
|
1818
|
+
signatures.delete(resolved);
|
|
1819
|
+
unstableInputs.add(resolved);
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
realpaths.set(resolved, observed);
|
|
1823
|
+
}
|
|
1824
|
+
if (typeof record.hash === "string" || record.hash === null) {
|
|
1825
|
+
if (unstableInputs.has(resolved)) {
|
|
1826
|
+
// Keep a previously observed contradiction unstable.
|
|
1827
|
+
}
|
|
1828
|
+
else if (hashes.has(resolved) &&
|
|
1829
|
+
hashes.get(resolved) !== record.hash) {
|
|
1830
|
+
hashes.delete(resolved);
|
|
1831
|
+
realpaths.delete(resolved);
|
|
1832
|
+
signatures.delete(resolved);
|
|
1833
|
+
unstableInputs.add(resolved);
|
|
1834
|
+
}
|
|
1835
|
+
else {
|
|
1836
|
+
hashes.set(resolved, record.hash);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
if (typeof record.specifier === "string" &&
|
|
1841
|
+
typeof record.parent === "string") {
|
|
1842
|
+
for (const candidate of collectModuleResolutionCandidates(record.specifier, record.parent, record.resolved)) {
|
|
1843
|
+
inputs.add(node_path_1.default.resolve(candidate));
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
for (const [resolved, observed] of realpaths) {
|
|
1848
|
+
if (realpathHostInput(resolved) === observed &&
|
|
1849
|
+
signatures.get(resolved) === hostInputMetadataSignature(resolved)) {
|
|
1850
|
+
continue;
|
|
1851
|
+
}
|
|
1852
|
+
hashes.delete(resolved);
|
|
1853
|
+
realpaths.delete(resolved);
|
|
1854
|
+
signatures.delete(resolved);
|
|
1855
|
+
unstableInputs.add(resolved);
|
|
1856
|
+
}
|
|
1857
|
+
return {
|
|
1858
|
+
hostInputHashes: Object.fromEntries(hashes),
|
|
1859
|
+
hostInputRealpaths: Object.fromEntries(realpaths),
|
|
1860
|
+
inputs: [...inputs].sort(),
|
|
1861
|
+
unstableInputs: [...unstableInputs],
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
651
1864
|
function errorMessage(error) {
|
|
652
1865
|
return error instanceof Error ? error.message : String(error);
|
|
653
1866
|
}
|
|
654
1867
|
function withPluginLoaderEnv(run) {
|
|
655
1868
|
const previousNode = process.env.TTSC_NODE_BINARY;
|
|
656
1869
|
const previousTtsx = process.env.TTSC_TTSX_BINARY;
|
|
657
|
-
|
|
1870
|
+
const node = (0, resolveNodeBinary_1.resolveNodeBinary)({}, process.cwd());
|
|
1871
|
+
if (process.env.TTSC_NODE_BINARY === undefined && node !== undefined) {
|
|
1872
|
+
process.env.TTSC_NODE_BINARY = node;
|
|
1873
|
+
}
|
|
658
1874
|
process.env.TTSC_TTSX_BINARY ??= node_path_1.default.join(__dirname, "..", "..", "launcher", "ttsx.js");
|
|
659
1875
|
try {
|
|
660
1876
|
return run();
|
|
@@ -664,6 +1880,45 @@ function withPluginLoaderEnv(run) {
|
|
|
664
1880
|
restoreEnv("TTSC_TTSX_BINARY", previousTtsx);
|
|
665
1881
|
}
|
|
666
1882
|
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Select the executable for isolated descriptor evaluation.
|
|
1885
|
+
*
|
|
1886
|
+
* Under Bun, `process.execPath` names Bun itself. Bun implements enough of the
|
|
1887
|
+
* Node module surface to evaluate descriptors natively, but not Node's
|
|
1888
|
+
* synchronous `module.registerHooks` preload. The caller therefore recognizes
|
|
1889
|
+
* this default and omits the Node-only preload while retaining the fresh
|
|
1890
|
+
* process boundary. An explicitly configured Node executable remains
|
|
1891
|
+
* authoritative and receives the preload as usual.
|
|
1892
|
+
*/
|
|
1893
|
+
function pluginDescriptorRuntimeBinary(env) {
|
|
1894
|
+
if (env.TTSC_NODE_BINARY === undefined &&
|
|
1895
|
+
typeof process.versions.bun ===
|
|
1896
|
+
"string") {
|
|
1897
|
+
return process.execPath;
|
|
1898
|
+
}
|
|
1899
|
+
return (env.TTSC_NODE_BINARY ?? process.env.TTSC_NODE_BINARY ?? process.execPath);
|
|
1900
|
+
}
|
|
1901
|
+
/** Replay a child's human output without imposing a fixed output ceiling. */
|
|
1902
|
+
function replayEvaluationDiagnostics(file) {
|
|
1903
|
+
let fd;
|
|
1904
|
+
try {
|
|
1905
|
+
fd = node_fs_1.default.openSync(file, "r");
|
|
1906
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
1907
|
+
for (;;) {
|
|
1908
|
+
const length = node_fs_1.default.readSync(fd, buffer, 0, buffer.length, null);
|
|
1909
|
+
if (length === 0)
|
|
1910
|
+
break;
|
|
1911
|
+
node_fs_1.default.writeSync(2, buffer, 0, length);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
catch {
|
|
1915
|
+
// Diagnostic replay must never replace the descriptor result.
|
|
1916
|
+
}
|
|
1917
|
+
finally {
|
|
1918
|
+
if (fd !== undefined)
|
|
1919
|
+
node_fs_1.default.closeSync(fd);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
667
1922
|
function restoreEnv(key, value) {
|
|
668
1923
|
if (value === undefined) {
|
|
669
1924
|
delete process.env[key];
|
|
@@ -910,7 +2165,7 @@ function resolvePluginExportCondition(specifier, baseDir) {
|
|
|
910
2165
|
return null;
|
|
911
2166
|
}
|
|
912
2167
|
const file = node_path_1.default.resolve(node_path_1.default.dirname(packageJson), resolved);
|
|
913
|
-
return
|
|
2168
|
+
return existingFile(file) ? resolveRealPath(file) : null;
|
|
914
2169
|
}
|
|
915
2170
|
/**
|
|
916
2171
|
* Split a bare specifier into its package name and the `.`-prefixed subpath it
|
|
@@ -1098,6 +2353,13 @@ function readTsgoVersion(projectRoot) {
|
|
|
1098
2353
|
return "unknown";
|
|
1099
2354
|
}
|
|
1100
2355
|
}
|
|
2356
|
+
/**
|
|
2357
|
+
* Create an evaluator directory whose cleanup path cannot follow a retargeted
|
|
2358
|
+
* parent alias.
|
|
2359
|
+
*/
|
|
2360
|
+
function createEvaluationTempDir() {
|
|
2361
|
+
return (0, createCanonicalTempDirectory_1.createCanonicalTempDirectory)("ttsc-plugin-descriptor-");
|
|
2362
|
+
}
|
|
1101
2363
|
/**
|
|
1102
2364
|
* Remove an evaluation temp directory without letting cleanup replace a result.
|
|
1103
2365
|
*
|