ttsc 0.34.1 → 0.34.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cmd/ttsc/api_transform.go +13 -3
- package/cmd/ttsc/api_transform_config_recovery_test.go +44 -0
- package/driver/check_observations.go +24 -2
- package/driver/compileraliases/names.go +69 -0
- package/driver/compileraliases/names_test.go +57 -0
- package/driver/graph.go +40 -3
- package/driver/input_observation.go +16 -0
- package/driver/program.go +41 -15
- package/driver/recovery_graph.go +25 -0
- package/driver/target_ts70.go +12 -0
- package/driver/target_ts71.go +12 -0
- package/driver/transform_dependencies.go +22 -1
- package/driver/transform_membership.go +61 -0
- package/lib/compiler/internal/build/appendBuildOutput.d.ts +1 -1
- package/lib/compiler/internal/build/appendBuildOutput.js +37 -5
- package/lib/compiler/internal/build/appendBuildOutput.js.map +1 -1
- package/lib/compiler/internal/parseNativeTransformOutput.js +31 -1
- package/lib/compiler/internal/parseNativeTransformOutput.js.map +1 -1
- package/lib/compiler/internal/resolveNativeTypeScriptPackage.d.ts +27 -0
- package/lib/compiler/internal/resolveNativeTypeScriptPackage.js +103 -0
- package/lib/compiler/internal/resolveNativeTypeScriptPackage.js.map +1 -0
- package/lib/compiler/internal/resolveTsgo.d.ts +7 -6
- package/lib/compiler/internal/resolveTsgo.js +16 -35
- package/lib/compiler/internal/resolveTsgo.js.map +1 -1
- package/lib/internal/CompilerResultAdapter.d.ts +7 -5
- package/lib/internal/CompilerResultAdapter.js +29 -6
- package/lib/internal/CompilerResultAdapter.js.map +1 -1
- package/lib/internal/nativeBinary.d.ts +4 -0
- package/lib/internal/nativeBinary.js +6 -1
- package/lib/internal/nativeBinary.js.map +1 -1
- package/lib/launcher/internal/prepareExecution.js +5 -2
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/plugin/internal/source/pluginBuildVersions.d.ts +8 -7
- package/lib/plugin/internal/source/pluginBuildVersions.js +13 -13
- package/lib/plugin/internal/source/pluginBuildVersions.js.map +1 -1
- package/lib/structures/ITtscCompilerTransformation.d.ts +25 -1
- package/package.json +9 -9
- package/shim/compiler/neutral.go +14 -0
- package/src/compiler/internal/build/appendBuildOutput.ts +42 -5
- package/src/compiler/internal/parseNativeTransformOutput.ts +45 -1
- package/src/compiler/internal/resolveNativeTypeScriptPackage.ts +110 -0
- package/src/compiler/internal/resolveTsgo.ts +17 -36
- package/src/internal/CompilerResultAdapter.ts +29 -6
- package/src/internal/nativeBinary.ts +4 -0
- package/src/launcher/internal/prepareExecution.ts +7 -2
- package/src/plugin/internal/source/pluginBuildVersions.ts +13 -15
- package/src/structures/ITtscCompilerTransformation.ts +28 -1
- package/targets/7.1/bridge/compiler/neutral.go +39 -4
- package/utility/host.go +19 -6
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Find native TypeScript from a filename used as a Node resolution anchor.
|
|
7
|
+
*
|
|
8
|
+
* The nearest ancestor manifest declaring TypeScript or its npm aliases owns
|
|
9
|
+
* discovery. Its aliases targeting `typescript` precede the ordinary name. A
|
|
10
|
+
* manifest reporting a compiler major below 7 belongs to the JavaScript
|
|
11
|
+
* compiler and is skipped. Missing or nonnumeric version metadata retains the
|
|
12
|
+
* existing path-discovery behavior; executable availability is the caller's
|
|
13
|
+
* responsibility. Alias candidates must report the actual package name
|
|
14
|
+
* `typescript` and a native major. Multiple distinct native alias installs are
|
|
15
|
+
* ambiguous and require an explicit binary. Read and parse errors propagate,
|
|
16
|
+
* except a disappeared manifest is treated as an absent candidate.
|
|
17
|
+
*
|
|
18
|
+
* @evidence contracts/common.md#principled-implementation Project and ancestor dependency maps supply npm aliases by their declared target; Node resolves those arbitrary names from the caller's filename, and installed name/version metadata verifies native identity. Distinct native alias installs are rejected as ambiguous rather than selected by spelling or declaration order.
|
|
19
|
+
* @evidence contracts/common.md#clear-and-simple-design One owner selects manifest metadata for executable resolution and plugin version keys so they cannot choose different package names.
|
|
20
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts The real package identity and npm alias syntax define discovery; no particular alias, consumer identity or guessed executable is embedded.
|
|
21
|
+
* @evidence contracts/common.md#meaningful-documentation The comment defines anchor semantics, alias precedence, legacy rejection, incomplete metadata behavior and propagated read failures.
|
|
22
|
+
* @evidence contracts/portability.md#os-neutral-implementation createRequire follows native Node package resolution and synchronous filesystem reads without shell commands or path case assumptions.
|
|
23
|
+
* @evidence contracts/performance.md#efficient-algorithms The ancestor walk parses its manifests and visits their declared dependency entries once, then resolves and reads only A distinct aliases plus the ordinary name. Sorting alias names costs O(A log A); Node lookup, realpath and manifest bytes contribute delegated costs. Installed dependency trees are never scanned.
|
|
24
|
+
*
|
|
25
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work Project installations can change between calls, and this operation owns no invalidation source that would justify a retained result.
|
|
26
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Synchronous reads retain no open handles and the caller owns the returned metadata.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveNativeTypeScriptPackage(
|
|
29
|
+
from: string,
|
|
30
|
+
): { packageJson: string; manifest: Record<string, unknown> } | undefined {
|
|
31
|
+
const requireFrom = createRequire(from);
|
|
32
|
+
const aliases = new Set<string>();
|
|
33
|
+
for (
|
|
34
|
+
let directory = path.dirname(from);
|
|
35
|
+
;
|
|
36
|
+
directory = path.dirname(directory)
|
|
37
|
+
) {
|
|
38
|
+
const project = readManifest(path.join(directory, "package.json"));
|
|
39
|
+
let declaresTypeScript = false;
|
|
40
|
+
for (const field of [
|
|
41
|
+
"dependencies",
|
|
42
|
+
"devDependencies",
|
|
43
|
+
"optionalDependencies",
|
|
44
|
+
]) {
|
|
45
|
+
const dependencies = project?.[field];
|
|
46
|
+
if (dependencies === null || typeof dependencies !== "object") continue;
|
|
47
|
+
declaresTypeScript ||= Object.hasOwn(dependencies, "typescript");
|
|
48
|
+
for (const [name, specifier] of Object.entries(dependencies))
|
|
49
|
+
if (
|
|
50
|
+
name !== "typescript" &&
|
|
51
|
+
typeof specifier === "string" &&
|
|
52
|
+
/^npm:typescript(?:@|$)/.test(specifier)
|
|
53
|
+
)
|
|
54
|
+
aliases.add(name);
|
|
55
|
+
}
|
|
56
|
+
if (declaresTypeScript || aliases.size) break;
|
|
57
|
+
if (path.dirname(directory) === directory) break;
|
|
58
|
+
}
|
|
59
|
+
let selected:
|
|
60
|
+
| { packageJson: string; manifest: Record<string, unknown> }
|
|
61
|
+
| undefined;
|
|
62
|
+
for (const name of [...aliases].sort().concat("typescript")) {
|
|
63
|
+
if (name === "typescript" && selected) return selected;
|
|
64
|
+
let packageJson: string;
|
|
65
|
+
try {
|
|
66
|
+
packageJson = requireFrom.resolve(`${name}/package.json`);
|
|
67
|
+
} catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const manifest = readManifest(packageJson);
|
|
71
|
+
if (!manifest) continue;
|
|
72
|
+
const majorText =
|
|
73
|
+
typeof manifest.version === "string"
|
|
74
|
+
? manifest.version.split(".")[0]!
|
|
75
|
+
: "";
|
|
76
|
+
const major = /^\d+$/.test(majorText) ? Number(majorText) : NaN;
|
|
77
|
+
if (major < 7) continue;
|
|
78
|
+
if (
|
|
79
|
+
name !== "typescript" &&
|
|
80
|
+
(manifest.name !== "typescript" || !Number.isFinite(major))
|
|
81
|
+
)
|
|
82
|
+
continue;
|
|
83
|
+
const candidate = { packageJson, manifest };
|
|
84
|
+
if (name === "typescript") return candidate;
|
|
85
|
+
if (
|
|
86
|
+
selected &&
|
|
87
|
+
fs.realpathSync(selected.packageJson) !== fs.realpathSync(packageJson)
|
|
88
|
+
)
|
|
89
|
+
throw new Error(
|
|
90
|
+
"ttsc: multiple native TypeScript aliases are installed; select one with --binary or TTSC_TSGO_BINARY.",
|
|
91
|
+
);
|
|
92
|
+
selected = candidate;
|
|
93
|
+
}
|
|
94
|
+
return selected;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Read current manifest bytes, including after Node cached an older path. */
|
|
98
|
+
function readManifest(file: string): Record<string, unknown> | undefined {
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(fs.readFileSync(file, "utf8")) as Record<string, unknown>;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (
|
|
103
|
+
["ENOENT", "ENOTDIR"].includes(
|
|
104
|
+
(error as NodeJS.ErrnoException).code ?? "",
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
return undefined;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { resolveNativeTypeScriptPackage } from "./resolveNativeTypeScriptPackage";
|
|
5
6
|
import { SidecarEnvironment } from "./sharedHost/SidecarEnvironment";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -12,13 +13,14 @@ import { SidecarEnvironment } from "./sharedHost/SidecarEnvironment";
|
|
|
12
13
|
* 1. Select `opts.binary`, or consult `TTSC_TSGO_BINARY` only when it is absent. A
|
|
13
14
|
* nonempty override must be an existing absolute path; returns `{ binary,
|
|
14
15
|
* packageJson: "", packageRoot, version: "custom" }`.
|
|
15
|
-
* 2.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* 2. Declared npm aliases targeting TypeScript, then native `typescript`, from
|
|
17
|
+
* `cwd`.
|
|
18
|
+
* 3. The same discovery from `opts.resolveFrom` (for test harnesses and embedders
|
|
19
|
+
* that anchor to a different directory).
|
|
18
20
|
*
|
|
19
21
|
* Throws a descriptive error when the package or platform path is missing.
|
|
20
22
|
* Manifest read/parse failures can propagate directly. Existence does not
|
|
21
|
-
* certify a regular executable, native ABI or compiler
|
|
23
|
+
* certify a regular executable, native ABI or compiler-target compatibility;
|
|
22
24
|
* this operation selects paths and metadata without executing them. Platform
|
|
23
25
|
* package resolution is anchored at the chosen TypeScript manifest and can
|
|
24
26
|
* follow Node's ancestor search, rather than proving both packages occupy one
|
|
@@ -29,7 +31,7 @@ import { SidecarEnvironment } from "./sharedHost/SidecarEnvironment";
|
|
|
29
31
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts Package names and lib/tsc suffixes follow the native TypeScript distribution contract. Missing dependencies remain descriptive errors rather than invented compiler paths or fixture-specific substitutes.
|
|
30
32
|
* @evidence contracts/common.md#meaningful-documentation Ordered precedence, custom metadata and failures are documented separately, and the fallback anchor member explains its filename semantics following the documentation skill.
|
|
31
33
|
* @evidence contracts/portability.md#os-neutral-implementation Native path APIs validate absolute overrides and select the Windows executable suffix; createRequire anchors resolution to actual package files without shell quoting or OS-derived case policy.
|
|
32
|
-
* @evidence contracts/performance.md#efficient-algorithms
|
|
34
|
+
* @evidence contracts/performance.md#efficient-algorithms Up to two anchors delegate ancestor manifest and alias discovery before one platform lookup. Dependency declaration counts, alias lookups, manifest bytes and path work determine cost; Windows environment selection scans environment keys through SidecarEnvironment. Returned metadata does not retain parsed manifests.
|
|
33
35
|
*
|
|
34
36
|
* @evidenceExclude contracts/performance.md#reuse-equivalent-work Overrides and consumer installations are mutable filesystem authority; this resolver owns no dependency invalidation source that could justify retaining a custom result across calls.
|
|
35
37
|
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Synchronous package reads retain no handles or tasks; returned path and version strings transfer to the caller without a historical metadata table.
|
|
@@ -39,7 +41,7 @@ export function resolveTsgo(
|
|
|
39
41
|
/** Explicit path to a native `tsc` binary; bypasses package resolution. */
|
|
40
42
|
binary?: string;
|
|
41
43
|
|
|
42
|
-
/** Directory from which to discover
|
|
44
|
+
/** Directory from which to discover native TypeScript. */
|
|
43
45
|
cwd?: string;
|
|
44
46
|
|
|
45
47
|
/**
|
|
@@ -75,26 +77,23 @@ export function resolveTsgo(
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
const cwd = path.resolve(opts.cwd ?? process.cwd());
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let packageJson: string;
|
|
81
|
-
packageJson =
|
|
82
|
-
resolveTypeScriptPackageJson(path.join(cwd, "package.json")) ??
|
|
80
|
+
const selected =
|
|
81
|
+
resolveNativeTypeScriptPackage(path.join(cwd, "package.json")) ??
|
|
83
82
|
(opts.resolveFrom
|
|
84
|
-
?
|
|
85
|
-
: undefined)
|
|
86
|
-
|
|
87
|
-
if (!packageJson) {
|
|
83
|
+
? resolveNativeTypeScriptPackage(opts.resolveFrom)
|
|
84
|
+
: undefined);
|
|
85
|
+
if (!selected) {
|
|
88
86
|
throw new Error(
|
|
89
87
|
[
|
|
90
|
-
"ttsc:
|
|
88
|
+
"ttsc: native TypeScript 7 or newer is required.",
|
|
91
89
|
"Install the native TypeScript compiler in the consuming project:",
|
|
92
90
|
" npm i -D typescript",
|
|
91
|
+
"For legacy tooling, declare an npm:typescript@^7 alias in package.json.",
|
|
93
92
|
].join("\n"),
|
|
94
93
|
);
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
const manifest =
|
|
96
|
+
const { packageJson, manifest } = selected;
|
|
98
97
|
const packageRoot = path.dirname(packageJson);
|
|
99
98
|
const platformPackage = `@typescript/typescript-${process.platform}-${process.arch}`;
|
|
100
99
|
const platformResolver = createRequire(packageJson);
|
|
@@ -107,7 +106,7 @@ export function resolveTsgo(
|
|
|
107
106
|
throw new Error(
|
|
108
107
|
[
|
|
109
108
|
`ttsc: platform-specific TypeScript binary not found (${platformPackage}).`,
|
|
110
|
-
"Reinstall
|
|
109
|
+
"Reinstall the selected native TypeScript package with optional dependencies enabled.",
|
|
111
110
|
].join("\n"),
|
|
112
111
|
);
|
|
113
112
|
}
|
|
@@ -132,21 +131,3 @@ export function resolveTsgo(
|
|
|
132
131
|
typeof manifest.version === "string" ? manifest.version : "unknown",
|
|
133
132
|
};
|
|
134
133
|
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Resolve TypeScript relative to an absolute filename accepted by
|
|
138
|
-
* createRequire. Returns undefined when the anchor or package cannot be
|
|
139
|
-
* resolved.
|
|
140
|
-
*/
|
|
141
|
-
function resolveTypeScriptPackageJson(from: string): string | undefined {
|
|
142
|
-
try {
|
|
143
|
-
return createRequire(from).resolve("typescript/package.json");
|
|
144
|
-
} catch {
|
|
145
|
-
return undefined;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** Parse a JSON file and return the result as a plain object. */
|
|
150
|
-
function readPackageJson(file: string): Record<string, unknown> {
|
|
151
|
-
return JSON.parse(fs.readFileSync(file, "utf8")) as Record<string, unknown>;
|
|
152
|
-
}
|
|
@@ -217,15 +217,17 @@ function toCompilerResult(project: ProjectResult): ITtscCompilerResult {
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
/**
|
|
220
|
-
* Adapt source/build outcome and
|
|
221
|
-
*
|
|
220
|
+
* Adapt source/build outcome and preserve optional advisory values. Mapper
|
|
221
|
+
* project inputs join each source's declared dependencies on success and
|
|
222
|
+
* failure; they do not become compiler-read observations or completeness
|
|
223
|
+
* certificates.
|
|
222
224
|
*
|
|
223
|
-
* @evidence contracts/common.md#principled-implementation Undefined advisory fields are omitted
|
|
225
|
+
* @evidence contracts/common.md#principled-implementation Undefined advisory fields are omitted and present empty/false values survive both success and failure. Universal mapper declarations join source dependencies with own-key safety; compiler completeness and read witnesses retain their independent meaning.
|
|
224
226
|
* @evidence contracts/common.md#clear-and-simple-design One optional-field projection is shared by the two result branches.
|
|
225
227
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts The adapter does not manufacture graph, host-read or completeness proof and does not discard it solely because the build failed.
|
|
226
|
-
* @evidence contracts/common.md#meaningful-documentation
|
|
228
|
+
* @evidence contracts/common.md#meaningful-documentation Native prose distinguishes mapper declarations from read proof and completeness on both result branches.
|
|
227
229
|
* @evidenceExclude contracts/portability.md#os-neutral-implementation Advisory values are passed through without native path normalization or capability inference.
|
|
228
|
-
* @evidence contracts/performance.md#efficient-algorithms
|
|
230
|
+
* @evidence contracts/performance.md#efficient-algorithms Optional fields retain references unless mapper inputs require a per-source Set union. Work and copied storage follow source keys plus each source's dependency/input text; diagnostic classification and failure formatting keep their delegated costs.
|
|
229
231
|
* @evidenceExclude contracts/performance.md#reuse-equivalent-work This adaptation coordinates no reusable compiler/worker or outcome cache.
|
|
230
232
|
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Returned source/advisory objects remain reachable under caller ownership; this operation owns no process, lease or historical registry.
|
|
231
233
|
*/
|
|
@@ -233,7 +235,7 @@ export function toCompilerTransformation(
|
|
|
233
235
|
project: ProjectTransformation,
|
|
234
236
|
): ITtscCompilerTransformation {
|
|
235
237
|
const {
|
|
236
|
-
dependencies,
|
|
238
|
+
dependencies: reportedDependencies,
|
|
237
239
|
dependenciesComplete,
|
|
238
240
|
graph,
|
|
239
241
|
hostInputHashes,
|
|
@@ -247,6 +249,27 @@ export function toCompilerTransformation(
|
|
|
247
249
|
typescript,
|
|
248
250
|
volatile,
|
|
249
251
|
} = project;
|
|
252
|
+
let dependencies = reportedDependencies;
|
|
253
|
+
if (graph?.contentMapperInputs?.length) {
|
|
254
|
+
const keys = new Set([
|
|
255
|
+
...Object.keys(typescript),
|
|
256
|
+
...Object.keys(graph.edges),
|
|
257
|
+
]);
|
|
258
|
+
dependencies = Object.fromEntries([
|
|
259
|
+
...Object.entries(dependencies ?? {}).filter(([key]) => !keys.has(key)),
|
|
260
|
+
...Array.from(keys, (key) => [
|
|
261
|
+
key,
|
|
262
|
+
[
|
|
263
|
+
...new Set([
|
|
264
|
+
...(Object.hasOwn(dependencies ?? {}, key)
|
|
265
|
+
? dependencies![key]!
|
|
266
|
+
: []),
|
|
267
|
+
...graph.contentMapperInputs!,
|
|
268
|
+
]),
|
|
269
|
+
].filter((input) => input !== key),
|
|
270
|
+
]),
|
|
271
|
+
]);
|
|
272
|
+
}
|
|
250
273
|
const advisoryFields = {
|
|
251
274
|
...(dependencies === undefined ? {} : { dependencies }),
|
|
252
275
|
...(dependenciesComplete === undefined ? {} : { dependenciesComplete }),
|
|
@@ -7,5 +7,9 @@
|
|
|
7
7
|
* `@ttsc/unplugin` runs its native watch helpers, imports them from here, so
|
|
8
8
|
* the helper always comes from the binary ttsc itself uses and no consumer
|
|
9
9
|
* carries a copy of the rules.
|
|
10
|
+
*
|
|
11
|
+
* `resolveTsgo` selects the consumer's native TypeScript compiler, including
|
|
12
|
+
* npm aliases targeting TypeScript used alongside legacy tooling.
|
|
10
13
|
*/
|
|
11
14
|
export { resolveBinary } from "../compiler/internal/resolveBinary";
|
|
15
|
+
export { resolveTsgo } from "../compiler/internal/resolveTsgo";
|
|
@@ -743,7 +743,9 @@ function collectLinkDirectories(projectRoot: string): string[] {
|
|
|
743
743
|
}
|
|
744
744
|
|
|
745
745
|
/**
|
|
746
|
-
* Whether mirroring `directory` would reach a filesystem
|
|
746
|
+
* Whether mirroring `directory` would reach a filesystem root, the temporary
|
|
747
|
+
* root or any of its ancestors. A caller can place its temporary root inside
|
|
748
|
+
* its project; its parents still contain shared entries outside that project.
|
|
747
749
|
* Identity comparison is required on Windows, where `os.tmpdir()` can carry an
|
|
748
750
|
* 8.3 component while a project created below it is returned in long spelling.
|
|
749
751
|
*/
|
|
@@ -754,7 +756,10 @@ function isUnsafeVirtualParent(
|
|
|
754
756
|
const resolved = identities.resolve(directory);
|
|
755
757
|
const root = identities.resolve(path.parse(resolved.path).root);
|
|
756
758
|
const temporaryRoot = identities.resolve(os.tmpdir());
|
|
757
|
-
return
|
|
759
|
+
return (
|
|
760
|
+
resolved.key === root.key ||
|
|
761
|
+
identities.isWithin(resolved.path, temporaryRoot.path)
|
|
762
|
+
);
|
|
758
763
|
}
|
|
759
764
|
|
|
760
765
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
1
|
import path from "node:path";
|
|
4
2
|
|
|
3
|
+
import { resolveNativeTypeScriptPackage } from "../../../compiler/internal/resolveNativeTypeScriptPackage";
|
|
5
4
|
import { ttscPackageVersion } from "./ttscPackageVersion";
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -14,14 +13,15 @@ import { ttscPackageVersion } from "./ttscPackageVersion";
|
|
|
14
13
|
* consumer that keeps a transform's output beyond the process that produced it,
|
|
15
14
|
* as a persisted shared compile of `@ttsc/unplugin` is, names it by these
|
|
16
15
|
* versions with the rule the build applies instead of a copy of it: ttsc's own
|
|
17
|
-
* package version, and the version of the
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
16
|
+
* package version, and the version of the native TypeScript package the project
|
|
17
|
+
* selects (declared npm aliases before `typescript`), or `"unknown"` when
|
|
18
|
+
* resolution/read/parse fails or its version is absent. The host label falls
|
|
19
|
+
* back to `"0.0.0"` and is cached even after an unsuccessful first read. The
|
|
20
|
+
* host installation is assumed stable for the process lifetime; changing its
|
|
21
|
+
* manifest does not refresh that label.
|
|
22
22
|
*
|
|
23
|
-
* @param projectRoot The project base used to resolve
|
|
24
|
-
*
|
|
23
|
+
* @param projectRoot The project base used to resolve the native TypeScript
|
|
24
|
+
* manifest.
|
|
25
25
|
* @evidence contracts/common.md#principled-implementation Package-version labels supply the same metadata tuple used by plugin keys; absent/unreadable metadata receives the documented unknown/default label rather than an executable identity certificate.
|
|
26
26
|
* @evidence contracts/common.md#clear-and-simple-design The host label comes from its own owner (`ttscPackageVersion`), which a reader needing only that label calls without this function's project resolution; the project-version reader is separate because project resolution varies per call.
|
|
27
27
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts Node package resolution determines the actual project dependency; no particular project or fixture version is embedded.
|
|
@@ -40,14 +40,12 @@ export function pluginBuildVersions(projectRoot: string): {
|
|
|
40
40
|
|
|
41
41
|
function readTsgoVersion(projectRoot: string): string {
|
|
42
42
|
try {
|
|
43
|
-
const
|
|
43
|
+
const selected = resolveNativeTypeScriptPackage(
|
|
44
44
|
path.join(projectRoot, "package.json"),
|
|
45
45
|
);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
};
|
|
50
|
-
return pkg.version ?? "unknown";
|
|
46
|
+
return typeof selected?.manifest.version === "string"
|
|
47
|
+
? selected.manifest.version
|
|
48
|
+
: "unknown";
|
|
51
49
|
} catch {
|
|
52
50
|
return "unknown";
|
|
53
51
|
}
|
|
@@ -51,6 +51,23 @@ export namespace ITtscCompilerTransformation {
|
|
|
51
51
|
* @evidence contracts/portability.md#os-neutral-implementation Graph keys use the producer's cwd-relative slash spelling with absolute slash paths for outside-root or cross-volume inputs; this protocol spelling is distinct from reported native realpaths carried by inputRealpaths and observations, which can retain lexical fallback on native VFS errors. The reported compiler case policy governs compiler membership comparisons rather than an OS-name assumption; absent policy or proof does not establish native filesystem capabilities.
|
|
52
52
|
*/
|
|
53
53
|
export interface IReferenceGraph {
|
|
54
|
+
/**
|
|
55
|
+
* Effective native possible-root suffixes and output-directory exclusions.
|
|
56
|
+
* A report that differs from a host's provisional policy requires a new
|
|
57
|
+
* membership walk. Directory paths are absolute; null clears an option.
|
|
58
|
+
* Authored files/include and explicit excludes remain configuration
|
|
59
|
+
* inputs.
|
|
60
|
+
*/
|
|
61
|
+
membership?: {
|
|
62
|
+
/** Original suffix spellings admitted by the compiler and its mappers. */
|
|
63
|
+
inputExtensions: string[];
|
|
64
|
+
|
|
65
|
+
/** Effective absolute output directory, or null when unset. */
|
|
66
|
+
outDir: string | null;
|
|
67
|
+
|
|
68
|
+
/** Effective absolute declaration directory, or null when unset. */
|
|
69
|
+
declarationDir: string | null;
|
|
70
|
+
};
|
|
54
71
|
/**
|
|
55
72
|
* Direct resolved references per file: imports, re-exports, `///
|
|
56
73
|
* <reference>` targets, and type reference directives — type-only edges
|
|
@@ -82,10 +99,20 @@ export namespace ITtscCompilerTransformation {
|
|
|
82
99
|
|
|
83
100
|
/**
|
|
84
101
|
* Automatic type discovery and resolution inputs that affect every source,
|
|
85
|
-
* including effective type-root directory
|
|
102
|
+
* including compiler response files and effective type-root directory
|
|
103
|
+
* membership. Failed loads can retain their observed CLI/config recovery
|
|
104
|
+
* paths here without a completed Program or source population.
|
|
86
105
|
*/
|
|
87
106
|
resolutionInputs?: string[];
|
|
88
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Mapper package manifests and dynamic project watchedFiles. These are
|
|
110
|
+
* declared dependencies for every output, including files whose plugin
|
|
111
|
+
* dependencies are complete. They carry no compiler-read proof: consumers
|
|
112
|
+
* must witness them before compilation and compare afterward.
|
|
113
|
+
*/
|
|
114
|
+
contentMapperInputs?: string[];
|
|
115
|
+
|
|
89
116
|
/**
|
|
90
117
|
* Filesystem predicates recorded during construction and resolver replay
|
|
91
118
|
* for this graph. Each property is independent: `fileExists: false` and
|
|
@@ -3,6 +3,8 @@ package compiler
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
5
|
"errors"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"sort"
|
|
6
8
|
"sync"
|
|
7
9
|
|
|
8
10
|
innerast "github.com/microsoft/TypeScript/tsc/internal/ast"
|
|
@@ -52,7 +54,7 @@ func NewHost(currentDirectory innertspath.RootedDirectoryPath, fs innervfs.FS, d
|
|
|
52
54
|
// @evidence contracts/common.md#prohibited-implementation-shortcuts The compiler's own runExternalCode gate and mapper resolution decide whether mappers run; no ttsc authorization or mapping policy is added.
|
|
53
55
|
// @evidence contracts/common.md#meaningful-documentation Native prose states when mappers run, the nil meaning and the 7.0 difference.
|
|
54
56
|
// @evidence contracts/portability.md#os-neutral-implementation Mapper processes start through spawnContentMapper's os/exec boundary in each mapper package's directory; the supplied FS owns every file read.
|
|
55
|
-
// @evidenceExclude contracts/performance.md#efficient-algorithms
|
|
57
|
+
// @evidenceExclude contracts/performance.md#efficient-algorithms This declaration chooses no algorithm; its fields retain shared mapper state and resolved manifest names.
|
|
56
58
|
// @evidence contracts/performance.md#reuse-equivalent-work Every host built from one ProjectHosts shares one mapper project, so an incremental generation reuses the processes the first generation started.
|
|
57
59
|
// @evidence contracts/performance.md#bound-retention-and-release-resources The mapper project and host, and with them every spawned mapper process, live until Close; Close releases them once.
|
|
58
60
|
type ProjectHosts struct {
|
|
@@ -60,6 +62,7 @@ type ProjectHosts struct {
|
|
|
60
62
|
project contentmapper.Project
|
|
61
63
|
closeOnce sync.Once
|
|
62
64
|
closeErr error
|
|
65
|
+
manifests []string
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
// NewProjectHosts returns the hosts of Programs built from config. It starts
|
|
@@ -69,8 +72,8 @@ type ProjectHosts struct {
|
|
|
69
72
|
// @evidence contracts/common.md#clear-and-simple-design One gate returns either a nil value or a value holding the mapper project.
|
|
70
73
|
// @evidence contracts/common.md#prohibited-implementation-shortcuts Mapper resolution and its diagnostics stay with the config parser; this function adds none.
|
|
71
74
|
// @evidence contracts/common.md#meaningful-documentation Native prose states that construction starts no process.
|
|
72
|
-
// @evidence contracts/portability.md#os-neutral-implementation
|
|
73
|
-
// @
|
|
75
|
+
// @evidence contracts/portability.md#os-neutral-implementation Native filepath.Join constructs manifest paths in resolved package directories; mapper processes start later through spawnContentMapper.
|
|
76
|
+
// @evidence contracts/performance.md#efficient-algorithms Construction checks the gate and copies each configured mapper package directory into a manifest path. Cost and returned path storage follow mapper count and path text.
|
|
74
77
|
// @evidenceExclude contracts/performance.md#reuse-equivalent-work Each call opens its own mapper state; callers share it by holding the value.
|
|
75
78
|
// @evidence contracts/performance.md#bound-retention-and-release-resources The returned value owns the mapper host and project until Close.
|
|
76
79
|
func NewProjectHosts(config *innertsoptions.ParsedCommandLine) *ProjectHosts {
|
|
@@ -79,8 +82,15 @@ func NewProjectHosts(config *innertsoptions.ParsedCommandLine) *ProjectHosts {
|
|
|
79
82
|
}
|
|
80
83
|
diagnosticLocale, _ := innerlocale.Parse(config.CompilerOptions().Locale)
|
|
81
84
|
mappers := contentmapper.NewHost(context.Background(), contentmapper.SpawnerFunc(spawnContentMapper), diagnosticLocale)
|
|
85
|
+
manifests := make([]string, 0, len(config.ContentMappers()))
|
|
86
|
+
for _, mapper := range config.ContentMappers() {
|
|
87
|
+
if mapper.PackageDirectory != "" {
|
|
88
|
+
manifests = append(manifests, filepath.Join(string(mapper.PackageDirectory), "package.json"))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
82
91
|
return &ProjectHosts{
|
|
83
|
-
|
|
92
|
+
manifests: manifests,
|
|
93
|
+
mappers: mappers,
|
|
84
94
|
project: mappers.Project(contentmapper.ProjectSpec{
|
|
85
95
|
ConfigFileName: config.ConfigName(),
|
|
86
96
|
Mappers: config.ContentMappers(),
|
|
@@ -89,6 +99,31 @@ func NewProjectHosts(config *innertsoptions.ParsedCommandLine) *ProjectHosts {
|
|
|
89
99
|
}
|
|
90
100
|
}
|
|
91
101
|
|
|
102
|
+
// Dependencies returns mapper manifests and upstream-declared dynamic inputs.
|
|
103
|
+
// WatchedFiles can open dynamic project configuration; its error is returned
|
|
104
|
+
// alongside manifests so callers keep recovery inputs without claiming success.
|
|
105
|
+
//
|
|
106
|
+
// @evidence contracts/common.md#principled-implementation Uses the pinned upstream Project.WatchedFiles declaration and resolved mapper package directories; it does not infer reads from mapper code.
|
|
107
|
+
// @evidence contracts/common.md#clear-and-simple-design One projection transfers the project-owned dependency population without changing the mapper protocol.
|
|
108
|
+
// @evidence contracts/common.md#prohibited-implementation-shortcuts Upstream errors remain errors and manifests remain available for recovery; no post-compile hash is invented.
|
|
109
|
+
// @evidence contracts/common.md#meaningful-documentation Native prose documents lazy opening, declaration scope and partial error results.
|
|
110
|
+
// @evidence contracts/portability.md#os-neutral-implementation Native filepath.Join anchors manifests in resolved package directories; upstream watched paths retain their absolute native spelling.
|
|
111
|
+
// @evidence contracts/performance.md#efficient-algorithms Copies declared input names and sorts them; upstream WatchedFiles can open dynamic mapper projects and perform protocol IO.
|
|
112
|
+
// @evidence contracts/performance.md#reuse-equivalent-work Upstream project state shares opened configuration; each returned slice is independently owned.
|
|
113
|
+
// @evidenceExclude contracts/performance.md#bound-retention-and-release-resources The returned names escape to the caller; ProjectHosts still owns mapper lifetime through Close.
|
|
114
|
+
func (h *ProjectHosts) Dependencies() ([]string, error) {
|
|
115
|
+
if h == nil {
|
|
116
|
+
return nil, nil
|
|
117
|
+
}
|
|
118
|
+
inputs := append([]string(nil), h.manifests...)
|
|
119
|
+
watched, err := h.project.WatchedFiles()
|
|
120
|
+
for _, file := range watched {
|
|
121
|
+
inputs = append(inputs, string(file))
|
|
122
|
+
}
|
|
123
|
+
sort.Strings(inputs)
|
|
124
|
+
return inputs, err
|
|
125
|
+
}
|
|
126
|
+
|
|
92
127
|
// Host returns a compiler host over fs, like NewHost, that transforms
|
|
93
128
|
// content-mapped files through this project's mappers.
|
|
94
129
|
//
|
package/utility/host.go
CHANGED
|
@@ -48,6 +48,9 @@ type hostOptions struct {
|
|
|
48
48
|
// host sets it (to an OverlayFS), so build/check/transform leave it nil and
|
|
49
49
|
// LoadProgram falls back to the default filesystem.
|
|
50
50
|
fs vfs.FS
|
|
51
|
+
|
|
52
|
+
// failureInputs receives the loader's actual pre-Program recovery graph.
|
|
53
|
+
failureInputs func(*driver.TransformGraph)
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
// transformResult is the JSON envelope written to stdout by RunTransform.
|
|
@@ -115,8 +118,8 @@ func RunCheck(args []string) int {
|
|
|
115
118
|
//
|
|
116
119
|
// After successful flag admission, requested metadata is attempted on both
|
|
117
120
|
// successful and failed checks and belongs to that check generation. A missing
|
|
118
|
-
// generation is explicitly incomplete
|
|
119
|
-
// publication defer. Input authority does not erase status or diagnostics.
|
|
121
|
+
// generation is explicitly incomplete but retains observed load-recovery paths.
|
|
122
|
+
// Rejected flag admission installs no publication defer. Input authority does not erase status or diagnostics.
|
|
120
123
|
// Callers own the metadata artifact's removal and supplied-stream lifetime.
|
|
121
124
|
// Diagnostic/report writes are best-effort and are not delivery receipts.
|
|
122
125
|
//
|
|
@@ -135,9 +138,13 @@ func RunCheckWithIO(args []string, stdout, stderr io.Writer) (status int) {
|
|
|
135
138
|
}
|
|
136
139
|
opts.noEmit = true
|
|
137
140
|
var prog *driver.Program
|
|
141
|
+
var recovery *driver.TransformGraph
|
|
142
|
+
if opts.checkObservationsJSON != "" {
|
|
143
|
+
opts.failureInputs = func(graph *driver.TransformGraph) { recovery = graph }
|
|
144
|
+
}
|
|
138
145
|
defer func() {
|
|
139
146
|
if opts.checkObservationsJSON != "" {
|
|
140
|
-
if err := driver.
|
|
147
|
+
if err := driver.WriteCheckRecoveryObservationsJSON(opts.checkObservationsJSON, prog, recovery); err != nil {
|
|
141
148
|
fmt.Fprintf(opts.stderr, "ttsc utility: check observations write failed: %v\n", err)
|
|
142
149
|
if status == 0 {
|
|
143
150
|
status = 3
|
|
@@ -307,8 +314,9 @@ func RunTransform(args []string) int {
|
|
|
307
314
|
// Failed projects retain diagnostics and recovery inputs but publish no partial
|
|
308
315
|
// TypeScript. Encoding or response-write failure returns 3; project failure
|
|
309
316
|
// returns 2.
|
|
310
|
-
//
|
|
311
|
-
//
|
|
317
|
+
// Rejected flag admission or a loader error returns before response construction.
|
|
318
|
+
// Config/argument diagnostics can retain observed recovery predicates without a
|
|
319
|
+
// Program. A loaded generation's graph is captured
|
|
312
320
|
// before linked program hooks mutate its ASTs. Diagnostic stream reporting is
|
|
313
321
|
// best-effort; response encoding and returned write errors determine delivery
|
|
314
322
|
// status. Callers own the supplied stream lifetimes.
|
|
@@ -326,16 +334,20 @@ func RunTransformWithIO(args []string, stdout, stderr io.Writer) int {
|
|
|
326
334
|
if !ok {
|
|
327
335
|
return 2
|
|
328
336
|
}
|
|
337
|
+
out := transformResult{TypeScript: map[string]string{}}
|
|
338
|
+
opts.failureInputs = func(graph *driver.TransformGraph) { out.Graph = graph }
|
|
329
339
|
prog, _, diags, ok := loadUtilityProgramWithDiagnostics(opts)
|
|
330
340
|
if !ok {
|
|
331
341
|
return 2
|
|
332
342
|
}
|
|
333
|
-
out := transformResult{TypeScript: map[string]string{}}
|
|
334
343
|
if prog != nil {
|
|
335
344
|
defer prog.Close()
|
|
336
345
|
// The same failed Program owns the missing resolution candidates needed
|
|
337
346
|
// for recovery. Capture them before closing it or applying mutations.
|
|
338
347
|
out.Graph = driver.NewTransformGraph(prog, opts.cwd)
|
|
348
|
+
} else {
|
|
349
|
+
unavailable := false
|
|
350
|
+
out.ObservationsComplete = &unavailable
|
|
339
351
|
}
|
|
340
352
|
if len(diags) != 0 {
|
|
341
353
|
driver.WritePrettyDiagnostics(opts.stderr, diags, opts.cwd)
|
|
@@ -605,6 +617,7 @@ func loadUtilityProgramWithDiagnostics(opts hostOptions) (*driver.Program, []dri
|
|
|
605
617
|
Checkers: opts.checkers,
|
|
606
618
|
TsgoArgs: opts.tsgoArgs,
|
|
607
619
|
FS: opts.fs,
|
|
620
|
+
OnFailureInputs: opts.failureInputs,
|
|
608
621
|
})
|
|
609
622
|
if err != nil {
|
|
610
623
|
fmt.Fprintf(opts.stderr, "ttsc utility: %v\n", err)
|