ttsc 0.31.0 → 0.31.2
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/platform/main.go +2 -2
- package/internal/sourceprocess/contain_windows.go +10 -13
- package/internal/sourceprocess/started_windows.go +41 -0
- package/internal/sourceprocess/windows_started_publication_distinguishes_pending_from_refusal_test.go +76 -0
- package/internal/watchhelper/run.go +30 -20
- package/internal/watchhelper/run_other.go +4 -4
- package/internal/watchhelper/run_windows.go +471 -0
- package/internal/watchhelper/watchhelper.go +8 -2
- package/internal/watchhelper/windows_notifications_preserve_mutations_without_access_noise_test.go +506 -0
- package/lib/compiler/internal/build/BuildExecution.d.ts +4 -2
- package/lib/compiler/internal/build/BuildExecution.js +40 -12
- package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.js +5 -4
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +9 -7
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/internal/javascriptRuntimeCapabilities.d.ts +13 -4
- package/lib/internal/javascriptRuntimeCapabilities.js +56 -6
- package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
- package/lib/internal/nativeBinary.d.ts +3 -3
- package/lib/internal/nativeBinary.js +3 -3
- package/lib/internal/pluginSource.d.ts +1 -0
- package/lib/internal/pluginSource.js +1 -0
- package/lib/internal/pluginSource.js.map +1 -1
- package/lib/internal/resolveNodeBinary.d.ts +6 -1
- package/lib/internal/resolveNodeBinary.js +6 -2
- package/lib/internal/resolveNodeBinary.js.map +1 -1
- package/lib/internal/runtimeExecutableIdentity.d.ts +22 -13
- package/lib/internal/runtimeExecutableIdentity.js +192 -102
- package/lib/internal/runtimeExecutableIdentity.js.map +1 -1
- package/lib/launcher/internal/runtime/installRuntimeHooks.js +58 -4
- package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
- package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.d.ts +5 -1
- package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js +7 -2
- package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js.map +1 -1
- package/lib/launcher/internal/ttscserver/runTtscserver.js +8 -1
- package/lib/launcher/internal/ttscserver/runTtscserver.js.map +1 -1
- package/lib/plugin/internal/CapabilityResolutionFormat.d.ts +21 -62
- package/lib/plugin/internal/CapabilityResolutionFormat.js +15 -106
- package/lib/plugin/internal/CapabilityResolutionFormat.js.map +1 -1
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +6 -0
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +4 -4
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
- package/lib/plugin/internal/load/loadProjectPlugins.d.ts +24 -14
- package/lib/plugin/internal/load/loadProjectPlugins.js +141 -56
- package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
- package/lib/plugin/internal/readCapabilityResolution.d.ts +6 -0
- package/lib/plugin/internal/readCapabilityResolution.js +8 -5
- package/lib/plugin/internal/readCapabilityResolution.js.map +1 -1
- package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +48 -0
- package/lib/plugin/internal/source/GoEnvironmentReading.js +156 -0
- package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -0
- package/lib/plugin/internal/source/NativeSourcePackages.d.ts +51 -13
- package/lib/plugin/internal/source/NativeSourcePackages.js +182 -27
- package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +42 -3
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +68 -7
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.d.ts +5 -3
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js +14 -8
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js.map +1 -1
- package/lib/plugin/internal/source/PluginContentIdentities.d.ts +265 -0
- package/lib/plugin/internal/source/PluginContentIdentities.js +452 -0
- package/lib/plugin/internal/source/PluginContentIdentities.js.map +1 -0
- package/lib/plugin/internal/source/PluginLoadAnswers.d.ts +63 -0
- package/lib/plugin/internal/source/PluginLoadAnswers.js +128 -0
- package/lib/plugin/internal/source/PluginLoadAnswers.js.map +1 -0
- package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +10 -0
- package/lib/plugin/internal/source/SourceBuildCacheLayout.js +12 -0
- package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
- package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +53 -19
- package/lib/plugin/internal/source/SourcePluginWorkspace.js +111 -22
- package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
- package/lib/plugin/internal/source/buildSourcePlugin.d.ts +34 -18
- package/lib/plugin/internal/source/buildSourcePlugin.js +52 -24
- package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/source/computeCacheKey.d.ts +25 -7
- package/lib/plugin/internal/source/computeCacheKey.js +85 -34
- package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.d.ts +7 -3
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +75 -102
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/pluginBuildEnvironment.d.ts +8 -3
- package/lib/plugin/internal/source/pluginBuildEnvironment.js +8 -4
- package/lib/plugin/internal/source/pluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js +12 -2
- package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js.map +1 -1
- package/lib/plugin/internal/source/pluginBuildVersions.d.ts +2 -2
- package/lib/plugin/internal/source/pluginBuildVersions.js +4 -17
- package/lib/plugin/internal/source/pluginBuildVersions.js.map +1 -1
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +6 -5
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +10 -17
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
- package/lib/plugin/internal/source/pluginSourceState.d.ts +6 -2
- package/lib/plugin/internal/source/pluginSourceState.js +20 -8
- package/lib/plugin/internal/source/pluginSourceState.js.map +1 -1
- package/lib/plugin/internal/source/pluginSourceStateHolds.d.ts +7 -2
- package/lib/plugin/internal/source/pluginSourceStateHolds.js +12 -7
- package/lib/plugin/internal/source/pluginSourceStateHolds.js.map +1 -1
- package/lib/plugin/internal/source/processPluginBuildEnvironment.d.ts +7 -2
- package/lib/plugin/internal/source/processPluginBuildEnvironment.js +8 -3
- package/lib/plugin/internal/source/processPluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/provenPluginSourceDigest.d.ts +28 -0
- package/lib/plugin/internal/source/provenPluginSourceDigest.js +41 -0
- package/lib/plugin/internal/source/provenPluginSourceDigest.js.map +1 -0
- package/lib/plugin/internal/source/pruneCacheFileRoot.d.ts +3 -2
- package/lib/plugin/internal/source/pruneCacheFileRoot.js +3 -2
- package/lib/plugin/internal/source/pruneCacheFileRoot.js.map +1 -1
- package/lib/plugin/internal/source/ttscPackageVersion.d.ts +26 -0
- package/lib/plugin/internal/source/ttscPackageVersion.js +47 -0
- package/lib/plugin/internal/source/ttscPackageVersion.js.map +1 -0
- package/lib/plugin/internal/writeCapabilityResolution.d.ts +6 -0
- package/lib/plugin/internal/writeCapabilityResolution.js +11 -7
- package/lib/plugin/internal/writeCapabilityResolution.js.map +1 -1
- package/lib/plugin/resolveCapabilityPlugins.js +67 -15
- package/lib/plugin/resolveCapabilityPlugins.js.map +1 -1
- package/package.json +8 -8
- package/src/compiler/internal/build/BuildExecution.ts +46 -13
- package/src/compiler/internal/startResidentTransform.ts +12 -3
- package/src/compiler/internal/transformProjectInMemory.ts +10 -2
- package/src/internal/javascriptRuntimeCapabilities.ts +71 -5
- package/src/internal/nativeBinary.ts +3 -3
- package/src/internal/pluginSource.ts +1 -0
- package/src/internal/resolveNodeBinary.ts +12 -1
- package/src/internal/runtimeExecutableIdentity.ts +227 -141
- package/src/launcher/internal/runtime/installRuntimeHooks.ts +68 -3
- package/src/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.ts +11 -2
- package/src/launcher/internal/ttscserver/runTtscserver.ts +18 -1
- package/src/plugin/internal/CapabilityResolutionFormat.ts +23 -107
- package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +14 -4
- package/src/plugin/internal/load/loadProjectPlugins.ts +421 -211
- package/src/plugin/internal/readCapabilityResolution.ts +17 -6
- package/src/plugin/internal/source/GoEnvironmentReading.ts +178 -0
- package/src/plugin/internal/source/NativeSourcePackages.ts +365 -48
- package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +83 -5
- package/src/plugin/internal/source/PluginBuildEnvironmentWitness.ts +18 -8
- package/src/plugin/internal/source/PluginContentIdentities.ts +563 -0
- package/src/plugin/internal/source/PluginLoadAnswers.ts +151 -0
- package/src/plugin/internal/source/SourceBuildCacheLayout.ts +14 -0
- package/src/plugin/internal/source/SourcePluginWorkspace.ts +148 -25
- package/src/plugin/internal/source/buildSourcePlugin.ts +104 -36
- package/src/plugin/internal/source/computeCacheKey.ts +129 -55
- package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +152 -105
- package/src/plugin/internal/source/pluginBuildEnvironment.ts +9 -2
- package/src/plugin/internal/source/pluginBuildEnvironmentWorker.ts +18 -2
- package/src/plugin/internal/source/pluginBuildVersions.ts +5 -26
- package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +13 -25
- package/src/plugin/internal/source/pluginSourceState.ts +28 -12
- package/src/plugin/internal/source/pluginSourceStateHolds.ts +15 -8
- package/src/plugin/internal/source/processPluginBuildEnvironment.ts +8 -2
- package/src/plugin/internal/source/provenPluginSourceDigest.ts +45 -0
- package/src/plugin/internal/source/pruneCacheFileRoot.ts +3 -2
- package/src/plugin/internal/source/ttscPackageVersion.ts +45 -0
- package/src/plugin/internal/writeCapabilityResolution.ts +29 -8
- package/src/plugin/resolveCapabilityPlugins.ts +87 -13
|
@@ -2,9 +2,11 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { GoEnvironmentReading } from "./GoEnvironmentReading";
|
|
5
6
|
import { GoSourceInputs } from "./GoSourceInputs";
|
|
6
7
|
import { GoToolResolution } from "./GoToolResolution";
|
|
7
8
|
import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
9
|
+
import { PluginContentIdentities } from "./PluginContentIdentities";
|
|
8
10
|
import type { SourceBuildFilesystemOperations } from "./SourceBuildFilesystemOperations";
|
|
9
11
|
import { spawnGoTool } from "./spawnGoTool";
|
|
10
12
|
|
|
@@ -41,14 +43,17 @@ import { spawnGoTool } from "./spawnGoTool";
|
|
|
41
43
|
* variable carries: the Go tool, the Go environment file `go env -w` writes,
|
|
42
44
|
* the executables the C toolchain commands name, and GOROOT. A consumer that
|
|
43
45
|
* keeps the reading compares their metadata before reusing it.
|
|
46
|
+
* @param identities Record store that lets a new process prove the GOROOT
|
|
47
|
+
* corpus and executable bytes from their metadata (`PluginContentIdentities`,
|
|
48
|
+
* #1722); without it every process reads them.
|
|
44
49
|
* @evidence contracts/common.md#principled-implementation Fixed artifact flags, observed compiler bytes/version, selected reported-or-fallback build values, named command executables and selected SDK files enter the same framed identity used by source-state reporting. Fallback values and metadata-qualified reuse are explicit premises, not complete observation of arbitrary toolchain inputs.
|
|
45
50
|
* @evidence contracts/common.md#clear-and-simple-design Private helpers separate compiler identity, Go-reported settings, external variables and SDK content while sharing one hash sink and optional pre-read witness.
|
|
46
51
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts Cache signatures include real identity/change metadata and effective invocation context; failed SDK witnessing refuses reuse rather than accepting a VERSION-only proxy.
|
|
47
52
|
* @evidence contracts/common.md#meaningful-documentation Native documentation names the input classes and witness purpose; private comments explain context-sensitive compiler memoization and SDK exclusions without treating a passing check as proof.
|
|
48
53
|
* @evidence contracts/portability.md#os-neutral-implementation Node path/stat/process APIs resolve native tool identities; platform-specific executable suffixes and environment lookup are isolated in GoToolResolution, and Go supplies its own effective build settings.
|
|
49
54
|
* @evidence contracts/performance.md#efficient-algorithms SDK traversal sorts reached entry names and selected file paths, performs native stat/realpath/link queries and serializes topology/metadata/path text. Matching manifests share aggregate hashes; changes read full selected files via the caller adapter, retaining file buffers with population/topology data. Compiler misses capture at most three complete version/byte readings when native metadata moves during a reading; only a stable attempt enters the memo. Go env can run up to three times for its file witness, and command tokens can each perform executable searches/full reads. Environment sorting/framing and command substring parsing also process name/value/command bytes; E/D/F/B alone do not capture native lookup or text comparison costs.
|
|
50
|
-
* @evidence contracts/performance.md#reuse-equivalent-work Compiler memo identity includes selected path/file metadata and invocation cwd/environment; SDK aggregate reuse requires matching complete ordered metadata/topology.
|
|
51
|
-
* @evidence contracts/performance.md#bound-retention-and-release-resources Compiler, Go-environment-path and SDK maps retain historical distinct tool/context/root keys without eviction. SDK population/topology and full individual file reads contribute transient bytes; synchronous probes delegate process/capture lifetime to their owner and do not certify arbitrary descendants are gone. No independent entry/record/file/output byte ceiling is imposed here.
|
|
55
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Compiler memo identity includes selected path/file metadata and invocation cwd/environment; SDK aggregate reuse requires matching complete ordered metadata/topology. With a record store, a new process reuses the SDK aggregate and executable digests only while their signatures match the recorded ones and every stamp is separable from a freshly minted reference (#1722); `go version` and `go env` stay live because they report toolchain selection no file metadata shows. All of this relies on native metadata distinguishability and sequential observation premises. Physical directories expand once per selection policy while aliases retain edges; optional caller witnesses receive reached selected dependencies.
|
|
56
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources Compiler, Go-environment-path and SDK maps retain historical distinct tool/context/root keys without eviction; persisted records belong to the single-file collector. SDK population/topology and full individual file reads contribute transient bytes; synchronous probes delegate process/capture lifetime to their owner and do not certify arbitrary descendants are gone. No independent entry/record/file/output byte ceiling is imposed here.
|
|
52
57
|
*/
|
|
53
58
|
export function hashPluginBuildEnvironment(
|
|
54
59
|
hash: { update(data: string): unknown },
|
|
@@ -57,6 +62,7 @@ export function hashPluginBuildEnvironment(
|
|
|
57
62
|
env: NodeJS.ProcessEnv,
|
|
58
63
|
filesystem: SourceBuildFilesystemOperations,
|
|
59
64
|
witness?: PluginBuildEnvironmentWitness.Record,
|
|
65
|
+
identities?: PluginContentIdentities.Store,
|
|
60
66
|
): void {
|
|
61
67
|
// Fixed artifact policy must invalidate binaries and reported source states
|
|
62
68
|
// together, including binaries cached before this policy was introduced.
|
|
@@ -67,11 +73,25 @@ export function hashPluginBuildEnvironment(
|
|
|
67
73
|
hash.update(
|
|
68
74
|
JSON.stringify([
|
|
69
75
|
"go",
|
|
70
|
-
resolveGoCompilerIdentity(
|
|
76
|
+
resolveGoCompilerIdentity(
|
|
77
|
+
goBinary,
|
|
78
|
+
env,
|
|
79
|
+
directory,
|
|
80
|
+
witness,
|
|
81
|
+
identities,
|
|
82
|
+
),
|
|
71
83
|
]),
|
|
72
84
|
);
|
|
73
85
|
}
|
|
74
|
-
hashGoBuildEnvironment(
|
|
86
|
+
hashGoBuildEnvironment(
|
|
87
|
+
hash,
|
|
88
|
+
goBinary,
|
|
89
|
+
directory,
|
|
90
|
+
env,
|
|
91
|
+
filesystem,
|
|
92
|
+
witness,
|
|
93
|
+
identities,
|
|
94
|
+
);
|
|
75
95
|
hashExternalGoBuildEnvironment(hash, env);
|
|
76
96
|
}
|
|
77
97
|
|
|
@@ -172,6 +192,7 @@ function resolveGoCompilerIdentity(
|
|
|
172
192
|
env: NodeJS.ProcessEnv = process.env,
|
|
173
193
|
cwd: string = process.cwd(),
|
|
174
194
|
witness?: PluginBuildEnvironmentWitness.Record,
|
|
195
|
+
identities?: PluginContentIdentities.Store,
|
|
175
196
|
): string {
|
|
176
197
|
const selected = GoToolResolution.resolveGoToolForBuild(
|
|
177
198
|
goBinary,
|
|
@@ -216,7 +237,13 @@ function resolveGoCompilerIdentity(
|
|
|
216
237
|
compilerEnv,
|
|
217
238
|
cwd,
|
|
218
239
|
);
|
|
219
|
-
identity = computeGoCompilerIdentity(
|
|
240
|
+
identity = computeGoCompilerIdentity(
|
|
241
|
+
selected,
|
|
242
|
+
resolved,
|
|
243
|
+
compilerEnv,
|
|
244
|
+
cwd,
|
|
245
|
+
identities,
|
|
246
|
+
);
|
|
220
247
|
if (!PluginBuildEnvironmentWitness.holds(observed)) continue;
|
|
221
248
|
for (const [file, signature] of observed)
|
|
222
249
|
if (witness !== undefined && !witness.has(file))
|
|
@@ -268,10 +295,13 @@ function computeGoCompilerIdentity(
|
|
|
268
295
|
resolved: string,
|
|
269
296
|
env: NodeJS.ProcessEnv,
|
|
270
297
|
cwd: string,
|
|
298
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
271
299
|
): string {
|
|
272
300
|
if (!fs.existsSync(resolved)) {
|
|
273
301
|
return "missing";
|
|
274
302
|
}
|
|
303
|
+
// `go version` stays a live probe: it answers which toolchain the effective
|
|
304
|
+
// environment selects (GOTOOLCHAIN, go.mod), which no file metadata shows.
|
|
275
305
|
const version = spawnGoTool(goBinary, ["version"], {
|
|
276
306
|
cwd,
|
|
277
307
|
encoding: "utf8",
|
|
@@ -282,10 +312,27 @@ function computeGoCompilerIdentity(
|
|
|
282
312
|
version.error !== undefined
|
|
283
313
|
? ((version.error as NodeJS.ErrnoException).code ?? version.error.message)
|
|
284
314
|
: `${version.status ?? 0}:${version.stdout}${version.stderr}`;
|
|
285
|
-
const binaryHash =
|
|
315
|
+
const binaryHash = hashExecutable(resolved, identities);
|
|
286
316
|
return `sha256:${binaryHash}:${versionText}`;
|
|
287
317
|
}
|
|
288
318
|
|
|
319
|
+
/**
|
|
320
|
+
* The content digest of an executable, proven from its metadata in a process
|
|
321
|
+
* that has the record store (#1722) and read in full otherwise.
|
|
322
|
+
*/
|
|
323
|
+
function hashExecutable(
|
|
324
|
+
file: string,
|
|
325
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
326
|
+
): string {
|
|
327
|
+
return PluginContentIdentities.digest(
|
|
328
|
+
identities,
|
|
329
|
+
"executable",
|
|
330
|
+
file,
|
|
331
|
+
(references) => PluginContentIdentities.fileObservation(references, file),
|
|
332
|
+
() => hashFile(file),
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
289
336
|
function resolveExecutableIdentityPath(
|
|
290
337
|
binary: string,
|
|
291
338
|
env: NodeJS.ProcessEnv = process.env,
|
|
@@ -351,7 +398,8 @@ function hashGoBuildEnvironment(
|
|
|
351
398
|
cwd: string,
|
|
352
399
|
env: NodeJS.ProcessEnv,
|
|
353
400
|
filesystem: SourceBuildFilesystemOperations,
|
|
354
|
-
witness
|
|
401
|
+
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
402
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
355
403
|
): void {
|
|
356
404
|
const values = resolveGoBuildEnvironment(
|
|
357
405
|
goBinary,
|
|
@@ -359,6 +407,7 @@ function hashGoBuildEnvironment(
|
|
|
359
407
|
env,
|
|
360
408
|
filesystem,
|
|
361
409
|
witness,
|
|
410
|
+
identities,
|
|
362
411
|
);
|
|
363
412
|
for (const key of GO_BUILD_ENV_KEYS) {
|
|
364
413
|
const value = values.get(key);
|
|
@@ -373,11 +422,19 @@ function resolveGoBuildEnvironment(
|
|
|
373
422
|
cwd: string,
|
|
374
423
|
env: NodeJS.ProcessEnv,
|
|
375
424
|
filesystem: SourceBuildFilesystemOperations,
|
|
376
|
-
witness
|
|
425
|
+
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
426
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
377
427
|
): Map<string, string> {
|
|
378
428
|
const values = new Map<string, string>();
|
|
379
429
|
if (goBinary !== undefined) {
|
|
380
|
-
const parsed =
|
|
430
|
+
const parsed = GoEnvironmentReading.read(
|
|
431
|
+
goBinary,
|
|
432
|
+
cwd,
|
|
433
|
+
env,
|
|
434
|
+
GO_BUILD_ENV_KEYS,
|
|
435
|
+
witness,
|
|
436
|
+
identities,
|
|
437
|
+
);
|
|
381
438
|
if (parsed !== undefined) {
|
|
382
439
|
try {
|
|
383
440
|
for (const key of GO_BUILD_ENV_KEYS) {
|
|
@@ -385,7 +442,14 @@ function resolveGoBuildEnvironment(
|
|
|
385
442
|
if (typeof raw === "string" && raw !== "") {
|
|
386
443
|
values.set(
|
|
387
444
|
key,
|
|
388
|
-
normalizeGoBuildEnvValue(
|
|
445
|
+
normalizeGoBuildEnvValue(
|
|
446
|
+
key,
|
|
447
|
+
raw,
|
|
448
|
+
env,
|
|
449
|
+
filesystem,
|
|
450
|
+
witness,
|
|
451
|
+
identities,
|
|
452
|
+
),
|
|
389
453
|
);
|
|
390
454
|
}
|
|
391
455
|
}
|
|
@@ -401,94 +465,27 @@ function resolveGoBuildEnvironment(
|
|
|
401
465
|
if (value !== undefined && value !== "") {
|
|
402
466
|
values.set(
|
|
403
467
|
key,
|
|
404
|
-
normalizeGoBuildEnvValue(
|
|
468
|
+
normalizeGoBuildEnvValue(
|
|
469
|
+
key,
|
|
470
|
+
value,
|
|
471
|
+
env,
|
|
472
|
+
filesystem,
|
|
473
|
+
witness,
|
|
474
|
+
identities,
|
|
475
|
+
),
|
|
405
476
|
);
|
|
406
477
|
}
|
|
407
478
|
}
|
|
408
479
|
return values;
|
|
409
480
|
}
|
|
410
481
|
|
|
411
|
-
/**
|
|
412
|
-
* The Go environment file each Go tool and environment was last seen to name,
|
|
413
|
-
* which lets a reading witness that file before `go env` reads it.
|
|
414
|
-
*/
|
|
415
|
-
const goEnvironmentFiles = new Map<string, string | null>();
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Run `go env -json` for the build keys and `GOENV`, the file `go env -w`
|
|
419
|
-
* writes, witnessing that file before the run reads it.
|
|
420
|
-
*
|
|
421
|
-
* The file decides the reading without being part of it, so only its metadata
|
|
422
|
-
* is kept, and it must be taken before the read: metadata taken after would
|
|
423
|
-
* describe an edit that landed between the two, and a reading of the old
|
|
424
|
-
* content would pass as proven. Which file `go env` reads is known only from
|
|
425
|
-
* its answer, so the file this Go tool and environment named last is witnessed
|
|
426
|
-
* first, and a reading that names another is taken again with that one
|
|
427
|
-
* witnessed. A file that keeps moving between runs leaves the reading
|
|
428
|
-
* unwitnessable, so a kept reading or a build keyed on it never holds.
|
|
429
|
-
*
|
|
430
|
-
* @returns The parsed answer, or `undefined` when `go env` failed.
|
|
431
|
-
*/
|
|
432
|
-
function readGoEnvironment(
|
|
433
|
-
goBinary: string,
|
|
434
|
-
cwd: string,
|
|
435
|
-
env: NodeJS.ProcessEnv,
|
|
436
|
-
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
437
|
-
): Record<string, unknown> | undefined {
|
|
438
|
-
const memo = crypto.createHash("sha256").update(goBinary);
|
|
439
|
-
for (const [key, value] of Object.entries(env).sort(([left], [right]) =>
|
|
440
|
-
left < right ? -1 : left > right ? 1 : 0,
|
|
441
|
-
)) {
|
|
442
|
-
if (value !== undefined)
|
|
443
|
-
memo.update(`\0${key.length}:${key}${value.length}:${value}`);
|
|
444
|
-
}
|
|
445
|
-
const memoKey = memo.digest("hex");
|
|
446
|
-
let named = goEnvironmentFiles.get(memoKey);
|
|
447
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
448
|
-
if (named !== undefined && named !== null)
|
|
449
|
-
PluginBuildEnvironmentWitness.add(witness, named);
|
|
450
|
-
const result = spawnGoTool(
|
|
451
|
-
goBinary,
|
|
452
|
-
["env", "-json", ...GO_BUILD_ENV_KEYS, "GOENV"],
|
|
453
|
-
{
|
|
454
|
-
cwd,
|
|
455
|
-
encoding: "utf8",
|
|
456
|
-
env: GoSourceInputs.goBuildEnv(goBinary, undefined, env),
|
|
457
|
-
windowsHide: true,
|
|
458
|
-
},
|
|
459
|
-
);
|
|
460
|
-
if (result.error !== undefined || result.status !== 0) return undefined;
|
|
461
|
-
let parsed: Record<string, unknown>;
|
|
462
|
-
try {
|
|
463
|
-
parsed = JSON.parse(result.stdout) as Record<string, unknown>;
|
|
464
|
-
} catch {
|
|
465
|
-
return undefined;
|
|
466
|
-
}
|
|
467
|
-
const reported =
|
|
468
|
-
typeof parsed.GOENV === "string" &&
|
|
469
|
-
parsed.GOENV !== "" &&
|
|
470
|
-
parsed.GOENV !== "off"
|
|
471
|
-
? parsed.GOENV
|
|
472
|
-
: null;
|
|
473
|
-
if (reported === named) return parsed;
|
|
474
|
-
if (named !== undefined && named !== null) witness?.delete(named);
|
|
475
|
-
named = reported;
|
|
476
|
-
goEnvironmentFiles.set(memoKey, named);
|
|
477
|
-
// No file to witness: the reading depends on none.
|
|
478
|
-
if (named === null) return parsed;
|
|
479
|
-
if (witness === undefined) return parsed;
|
|
480
|
-
}
|
|
481
|
-
if (named !== undefined && named !== null)
|
|
482
|
-
PluginBuildEnvironmentWitness.refuse(witness, named);
|
|
483
|
-
return undefined;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
482
|
function normalizeGoBuildEnvValue(
|
|
487
483
|
key: string,
|
|
488
484
|
value: string,
|
|
489
485
|
env: NodeJS.ProcessEnv,
|
|
490
486
|
filesystem: SourceBuildFilesystemOperations,
|
|
491
|
-
witness
|
|
487
|
+
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
488
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
492
489
|
): string {
|
|
493
490
|
if (key === "GOROOT") {
|
|
494
491
|
// Root/version observations also cover a missing SDK. The complete SDK
|
|
@@ -496,10 +493,15 @@ function normalizeGoBuildEnvValue(
|
|
|
496
493
|
// nested content changes that do not move the root or VERSION metadata.
|
|
497
494
|
PluginBuildEnvironmentWitness.add(witness, value);
|
|
498
495
|
PluginBuildEnvironmentWitness.add(witness, path.join(value, "VERSION"));
|
|
499
|
-
return resolveGoRootCacheIdentity(value, filesystem, witness);
|
|
496
|
+
return resolveGoRootCacheIdentity(value, filesystem, witness, identities);
|
|
500
497
|
}
|
|
501
498
|
if (GO_BUILD_COMMAND_ENV_KEYS.has(key)) {
|
|
502
|
-
return `${value}\0${resolveCommandCacheIdentity(
|
|
499
|
+
return `${value}\0${resolveCommandCacheIdentity(
|
|
500
|
+
value,
|
|
501
|
+
env,
|
|
502
|
+
witness,
|
|
503
|
+
identities,
|
|
504
|
+
)}`;
|
|
503
505
|
}
|
|
504
506
|
return value;
|
|
505
507
|
}
|
|
@@ -519,7 +521,8 @@ function normalizeGoBuildEnvValue(
|
|
|
519
521
|
function resolveCommandCacheIdentity(
|
|
520
522
|
command: string,
|
|
521
523
|
env: NodeJS.ProcessEnv,
|
|
522
|
-
witness
|
|
524
|
+
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
525
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
523
526
|
): string {
|
|
524
527
|
const tokens = splitGoCommand(command);
|
|
525
528
|
if (tokens === null) {
|
|
@@ -541,7 +544,7 @@ function resolveCommandCacheIdentity(
|
|
|
541
544
|
}
|
|
542
545
|
let identity: string;
|
|
543
546
|
try {
|
|
544
|
-
identity = `command:sha256:${
|
|
547
|
+
identity = `command:sha256:${hashExecutable(resolved, identities)}`;
|
|
545
548
|
} catch {
|
|
546
549
|
return `command:unreadable:${resolved}`;
|
|
547
550
|
}
|
|
@@ -555,7 +558,7 @@ function resolveCommandCacheIdentity(
|
|
|
555
558
|
if (!GoToolResolution.isExecutableFile(operand)) return;
|
|
556
559
|
PluginBuildEnvironmentWitness.add(witness, operand);
|
|
557
560
|
try {
|
|
558
|
-
identity += `;${index + 1}:sha256:${
|
|
561
|
+
identity += `;${index + 1}:sha256:${hashExecutable(operand, identities)}`;
|
|
559
562
|
} catch {
|
|
560
563
|
identity += `;${index + 1}:unreadable:${operand}`;
|
|
561
564
|
}
|
|
@@ -612,6 +615,9 @@ interface GoRootIdentitySnapshot {
|
|
|
612
615
|
files: string[];
|
|
613
616
|
topology: string[];
|
|
614
617
|
signature: string;
|
|
618
|
+
|
|
619
|
+
/** Whether every file stamp precedes its device's minted reference. */
|
|
620
|
+
separable: boolean;
|
|
615
621
|
}
|
|
616
622
|
|
|
617
623
|
interface GoRootIdentityCacheEntry {
|
|
@@ -622,19 +628,31 @@ interface GoRootIdentityCacheEntry {
|
|
|
622
628
|
// GOROOT is usually stable but its selected source/tool payloads contribute to
|
|
623
629
|
// every plugin key. Retain only the final aggregate
|
|
624
630
|
// identity, guarded by a fresh metadata/topology manifest on every call. A
|
|
625
|
-
// changed or incomplete manifest falls through to
|
|
631
|
+
// changed or incomplete manifest falls through to the record store, then to a
|
|
632
|
+
// full content read.
|
|
633
|
+
//
|
|
634
|
+
// WARNING (#1186, #1722): this map lives only as long as the process. #1186
|
|
635
|
+
// stopped at it, so every CLI launch, worker and bundler restart still read the
|
|
636
|
+
// whole SDK (3,723 files, 135 MB for the bundled toolchain). The record store
|
|
637
|
+
// below is what carries the identity across processes; never drop it in favor
|
|
638
|
+
// of this memo alone.
|
|
626
639
|
const goRootIdentityCache = new Map<string, GoRootIdentityCacheEntry>();
|
|
627
640
|
|
|
628
641
|
function resolveGoRootCacheIdentity(
|
|
629
642
|
goRoot: string,
|
|
630
643
|
filesystem: SourceBuildFilesystemOperations,
|
|
631
|
-
witness
|
|
644
|
+
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
645
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
632
646
|
): string {
|
|
633
647
|
const resolved = resolveRealPath(goRoot);
|
|
634
648
|
if (!fs.existsSync(resolved)) {
|
|
635
649
|
return `missing:${goRoot}`;
|
|
636
650
|
}
|
|
637
|
-
const snapshot = collectGoRootIdentitySnapshot(
|
|
651
|
+
const snapshot = collectGoRootIdentitySnapshot(
|
|
652
|
+
resolved,
|
|
653
|
+
witness,
|
|
654
|
+
identities?.references,
|
|
655
|
+
);
|
|
638
656
|
if (!snapshot.complete)
|
|
639
657
|
PluginBuildEnvironmentWitness.refuse(witness, resolved);
|
|
640
658
|
if (snapshot.complete) {
|
|
@@ -643,15 +661,37 @@ function resolveGoRootCacheIdentity(
|
|
|
643
661
|
return cached.identity;
|
|
644
662
|
}
|
|
645
663
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
664
|
+
// The first observation is the snapshot the witness just recorded; a second
|
|
665
|
+
// one, after the content read, brackets that read for publication.
|
|
666
|
+
let observed = 0;
|
|
667
|
+
const identity = PluginContentIdentities.digest(
|
|
668
|
+
snapshot.complete ? identities : undefined,
|
|
669
|
+
"goroot",
|
|
670
|
+
resolved,
|
|
671
|
+
(references) => {
|
|
672
|
+
const current =
|
|
673
|
+
observed++ === 0
|
|
674
|
+
? snapshot
|
|
675
|
+
: collectGoRootIdentitySnapshot(resolved, undefined, references);
|
|
676
|
+
return current.complete
|
|
677
|
+
? { separable: current.separable, signature: current.signature }
|
|
678
|
+
: undefined;
|
|
679
|
+
},
|
|
680
|
+
() => {
|
|
681
|
+
const hash = crypto.createHash("sha256");
|
|
682
|
+
hash.update(JSON.stringify(snapshot.topology));
|
|
683
|
+
for (const file of snapshot.files) {
|
|
684
|
+
const relative = path
|
|
685
|
+
.relative(resolved, file)
|
|
686
|
+
.split(path.sep)
|
|
687
|
+
.join("/");
|
|
688
|
+
const contents = filesystem.readFile(file);
|
|
689
|
+
hash.update(JSON.stringify([relative, contents.length]));
|
|
690
|
+
hash.update(contents);
|
|
691
|
+
}
|
|
692
|
+
return `sha256:${hash.digest("hex")}`;
|
|
693
|
+
},
|
|
694
|
+
);
|
|
655
695
|
if (snapshot.complete) {
|
|
656
696
|
goRootIdentityCache.set(resolved, {
|
|
657
697
|
identity,
|
|
@@ -664,9 +704,10 @@ function resolveGoRootCacheIdentity(
|
|
|
664
704
|
function collectGoRootIdentitySnapshot(
|
|
665
705
|
root: string,
|
|
666
706
|
witness?: PluginBuildEnvironmentWitness.Record,
|
|
707
|
+
references?: ReadonlyMap<bigint, bigint>,
|
|
667
708
|
): GoRootIdentitySnapshot {
|
|
668
709
|
const out: string[] = [];
|
|
669
|
-
const state = { complete: true };
|
|
710
|
+
const state = { complete: true, separable: references !== undefined };
|
|
670
711
|
const topology: string[] = [];
|
|
671
712
|
walkGoRootIdentity(root, out, topology, state, witness);
|
|
672
713
|
out.sort();
|
|
@@ -681,6 +722,11 @@ function collectGoRootIdentitySnapshot(
|
|
|
681
722
|
state.complete = false;
|
|
682
723
|
continue;
|
|
683
724
|
}
|
|
725
|
+
if (
|
|
726
|
+
references === undefined ||
|
|
727
|
+
!PluginContentIdentities.separable(references, stats)
|
|
728
|
+
)
|
|
729
|
+
state.separable = false;
|
|
684
730
|
signature.update(
|
|
685
731
|
[
|
|
686
732
|
relative,
|
|
@@ -702,6 +748,7 @@ function collectGoRootIdentitySnapshot(
|
|
|
702
748
|
files: out,
|
|
703
749
|
topology,
|
|
704
750
|
signature: signature.digest("hex"),
|
|
751
|
+
separable: state.separable,
|
|
705
752
|
};
|
|
706
753
|
}
|
|
707
754
|
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
|
|
4
4
|
import { GoToolResolution } from "./GoToolResolution";
|
|
5
5
|
import type { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
6
|
+
import type { PluginContentIdentities } from "./PluginContentIdentities";
|
|
6
7
|
import { hashPluginBuildEnvironment } from "./hashPluginBuildEnvironment";
|
|
7
8
|
import { resolveGoCompiler } from "./resolveGoCompiler";
|
|
8
9
|
|
|
@@ -19,12 +20,16 @@ import { resolveGoCompiler } from "./resolveGoCompiler";
|
|
|
19
20
|
* GOROOT metadata. Witnessing a newly selected environment file can repeat the
|
|
20
21
|
* probe to observe it before reading. Compiler identity reuses a
|
|
21
22
|
* metadata-and-context memo; a changed SDK manifest rehashes all contributing
|
|
22
|
-
* content rather than only edited files.
|
|
23
|
+
* content rather than only edited files. A supplied record store lets a new
|
|
24
|
+
* process take the SDK and executable digests from records instead of their
|
|
25
|
+
* bytes, while `go env` and `go version` still run.
|
|
23
26
|
*
|
|
24
27
|
* @param directory The directory a build runs `go` in.
|
|
25
28
|
* @param env The effective environment, `process.env` by default.
|
|
26
29
|
* @param witness Receives the paths the reading depends on and no variable
|
|
27
30
|
* carries (`hashPluginBuildEnvironment`).
|
|
31
|
+
* @param identities Record store that lets a new process prove the SDK and
|
|
32
|
+
* executables from metadata (#1722).
|
|
28
33
|
* @evidence contracts/common.md#principled-implementation The digest uses the same resolved compiler and environment serializer as the binary key, so reported toolchain state denotes the inputs actually used by the build.
|
|
29
34
|
* @evidence contracts/common.md#clear-and-simple-design Compiler selection, executable resolution and hashing each stay with their owning helper; this function only composes one environment reading.
|
|
30
35
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts The result comes from real Go/environment inputs, not a fixed compiler version or a caller-specific substitute.
|
|
@@ -32,13 +37,14 @@ import { resolveGoCompiler } from "./resolveGoCompiler";
|
|
|
32
37
|
* @evidence contracts/portability.md#os-neutral-implementation Executable identity is resolved before probing, and Node filesystem/process boundaries implement native spelling rather than assuming Windows or POSIX paths are interchangeable.
|
|
33
38
|
* @evidence contracts/performance.md#efficient-algorithms The wrapper resolves the compiler/tool under the supplied environment before delegating full environment hashing. Native resolution/probes, SDK metadata/name/path sorting and changed-manifest full content reads contribute cost; the final hash output is fixed-width but streamed identity/input text and full-file buffers are not. No independent duplicate source-directory digest scan is added here.
|
|
34
39
|
*
|
|
35
|
-
* @
|
|
40
|
+
* @evidence contracts/performance.md#reuse-equivalent-work This is the fresh-reading boundary required by builds: the Go probes run on every call. Content digests of the SDK and executables are shared across processes only through the supplied record store's separable-signature proof; processPluginBuildEnvironment owns reuse of the whole reading when a consumer permits it.
|
|
36
41
|
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Its digest and filesystem adapter are call-local; retained compiler/content memo ownership belongs to the hasher.
|
|
37
42
|
*/
|
|
38
43
|
export function pluginBuildEnvironment(
|
|
39
44
|
directory: string,
|
|
40
45
|
env: NodeJS.ProcessEnv = process.env,
|
|
41
46
|
witness?: PluginBuildEnvironmentWitness.Record,
|
|
47
|
+
identities?: PluginContentIdentities.Store,
|
|
42
48
|
): string {
|
|
43
49
|
const goBinary = GoToolResolution.resolveGoToolForBuild(
|
|
44
50
|
resolveGoCompiler(env).binary,
|
|
@@ -54,6 +60,7 @@ export function pluginBuildEnvironment(
|
|
|
54
60
|
env,
|
|
55
61
|
{ readFile: (location) => fs.readFileSync(location) },
|
|
56
62
|
witness,
|
|
63
|
+
identities,
|
|
57
64
|
);
|
|
58
65
|
return hash.digest("hex");
|
|
59
66
|
}
|
|
@@ -2,24 +2,40 @@ import { parentPort } from "node:worker_threads";
|
|
|
2
2
|
|
|
3
3
|
import { serializeCompilerError } from "../../../internal/serializeCompilerError";
|
|
4
4
|
import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
5
|
+
import { PluginContentIdentities } from "./PluginContentIdentities";
|
|
5
6
|
import { pluginBuildEnvironment } from "./pluginBuildEnvironment";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Observe one requested native toolchain environment on the exclusive worker.
|
|
9
10
|
* Explicit environment values reach every Go/toolchain reader; no caller
|
|
10
11
|
* process globals are written. Only actual readings and pre-read witnesses
|
|
11
|
-
* cross the worker boundary, and failures retain their real error details.
|
|
12
|
+
* cross the worker boundary, and failures retain their real error details. A
|
|
13
|
+
* request that names its project lets this isolate prove the SDK and the
|
|
14
|
+
* executables from the plugin cache's records instead of their bytes (#1722).
|
|
12
15
|
*/
|
|
13
16
|
parentPort?.on(
|
|
14
17
|
"message",
|
|
15
|
-
(request: {
|
|
18
|
+
(request: {
|
|
19
|
+
directory: string;
|
|
20
|
+
env: NodeJS.ProcessEnv;
|
|
21
|
+
projectRoot?: string;
|
|
22
|
+
}) => {
|
|
16
23
|
try {
|
|
17
24
|
const witness: PluginBuildEnvironmentWitness.Record = new Map();
|
|
25
|
+
const identities =
|
|
26
|
+
request.projectRoot === undefined
|
|
27
|
+
? undefined
|
|
28
|
+
: PluginContentIdentities.open({
|
|
29
|
+
projectRoot: request.projectRoot,
|
|
30
|
+
env: request.env,
|
|
31
|
+
sources: [request.directory],
|
|
32
|
+
});
|
|
18
33
|
parentPort!.postMessage({
|
|
19
34
|
environment: pluginBuildEnvironment(
|
|
20
35
|
request.directory,
|
|
21
36
|
request.env,
|
|
22
37
|
witness,
|
|
38
|
+
identities,
|
|
23
39
|
),
|
|
24
40
|
witness,
|
|
25
41
|
});
|
|
@@ -2,6 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { ttscPackageVersion } from "./ttscPackageVersion";
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Package-version labels used by plugin build keys (`computeCacheKey`) and
|
|
7
9
|
* persisted transform identities. These are metadata inputs, not a measurement
|
|
@@ -21,42 +23,19 @@ import path from "node:path";
|
|
|
21
23
|
* @param projectRoot The project base used to resolve
|
|
22
24
|
* `typescript/package.json`.
|
|
23
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.
|
|
24
|
-
* @evidence contracts/common.md#clear-and-simple-design
|
|
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.
|
|
25
27
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts Node package resolution determines the actual project dependency; no particular project or fixture version is embedded.
|
|
26
28
|
* @evidence contracts/common.md#meaningful-documentation Native prose explains persisted-output consumers, metadata fallbacks and the process-stable host premise without claiming that version labels establish executable or output equivalence.
|
|
27
29
|
* @evidence contracts/portability.md#os-neutral-implementation Node createRequire and path APIs locate package metadata using native module resolution without manual separator or case normalization.
|
|
28
30
|
* @evidence contracts/performance.md#efficient-algorithms Each call performs project package resolution and, when successful, reads/parses its manifest bytes; module search/path work and metadata size are not fixed by one logical manifest lookup. The host manifest is attempted only on the first invocation, including native path and JSON costs.
|
|
29
31
|
* @evidence contracts/performance.md#reuse-equivalent-work All callers share the host installation's process-stable version; the project compiler version is resolved afresh so different project roots do not share a false identity.
|
|
30
|
-
* @evidence contracts/performance.md#bound-retention-and-release-resources
|
|
32
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources The host-version owner retains one label for the process lifetime, whose text follows its manifest value rather than a fixed byte limit; the retained label count is independent of project count. Native module-resolution caches belong to Node, and synchronous file reads leave no persistent descriptor here.
|
|
31
33
|
*/
|
|
32
34
|
export function pluginBuildVersions(projectRoot: string): {
|
|
33
35
|
tsgo: string;
|
|
34
36
|
ttsc: string;
|
|
35
37
|
} {
|
|
36
|
-
return { tsgo: readTsgoVersion(projectRoot), ttsc:
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let cachedTtscVersion: string | null = null;
|
|
40
|
-
|
|
41
|
-
function readTtscVersion(): string {
|
|
42
|
-
if (cachedTtscVersion !== null) return cachedTtscVersion;
|
|
43
|
-
try {
|
|
44
|
-
const file = path.resolve(
|
|
45
|
-
__dirname,
|
|
46
|
-
"..",
|
|
47
|
-
"..",
|
|
48
|
-
"..",
|
|
49
|
-
"..",
|
|
50
|
-
"package.json",
|
|
51
|
-
);
|
|
52
|
-
const pkg = JSON.parse(fs.readFileSync(file, "utf8")) as {
|
|
53
|
-
version?: string;
|
|
54
|
-
};
|
|
55
|
-
cachedTtscVersion = pkg.version ?? "0.0.0";
|
|
56
|
-
} catch {
|
|
57
|
-
cachedTtscVersion = "0.0.0";
|
|
58
|
-
}
|
|
59
|
-
return cachedTtscVersion;
|
|
38
|
+
return { tsgo: readTsgoVersion(projectRoot), ttsc: ttscPackageVersion() };
|
|
60
39
|
}
|
|
61
40
|
|
|
62
41
|
function readTsgoVersion(projectRoot: string): string {
|