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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
@@ -7,9 +8,14 @@ import { SourceNativeRetirement } from "../../../internal/SourceNativeRetirement
|
|
|
7
8
|
import { createCanonicalTempDirectory } from "../../../internal/createCanonicalTempDirectory";
|
|
8
9
|
import { GoSourceInputs } from "./GoSourceInputs";
|
|
9
10
|
import { GoToolResolution } from "./GoToolResolution";
|
|
11
|
+
import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
12
|
+
import { PluginContentIdentities } from "./PluginContentIdentities";
|
|
13
|
+
import { PluginLoadAnswers } from "./PluginLoadAnswers";
|
|
10
14
|
import { SourcePluginAdmission } from "./SourcePluginAdmission";
|
|
11
15
|
import { SourcePluginWorkspace } from "./SourcePluginWorkspace";
|
|
12
16
|
import { ensureExecutableGoToolchain } from "./ensureExecutableGoToolchain";
|
|
17
|
+
import { hashPluginBuildEnvironment } from "./hashPluginBuildEnvironment";
|
|
18
|
+
import { pluginSourceDigest } from "./pluginSourceDigest";
|
|
13
19
|
import { resolveGoCompiler } from "./resolveGoCompiler";
|
|
14
20
|
import { resolvePluginGoModule } from "./resolvePluginGoModule";
|
|
15
21
|
import { spawnGoTool } from "./spawnGoTool";
|
|
@@ -17,8 +23,8 @@ import { spawnGoTool } from "./spawnGoTool";
|
|
|
17
23
|
/**
|
|
18
24
|
* Observe Go-selected packages under the native builder's workspace policy.
|
|
19
25
|
*
|
|
20
|
-
* Metadata does not compile dependencies or certify a later build. A cold
|
|
21
|
-
*
|
|
26
|
+
* Metadata does not compile dependencies or certify a later build. A cold build
|
|
27
|
+
* repeats ownership admission in its proven, materialized workspace.
|
|
22
28
|
*
|
|
23
29
|
* @evidence contracts/common.md#principled-implementation The selected Go tool, effective environment, shared workspace writer and actual copied package inputs determine package names and errors; Go remains the build-constraint and package-validity authority.
|
|
24
30
|
* @evidence contracts/common.md#clear-and-simple-design One namespace separates contextual observation, metadata framing and ownership admission; source-only contributions are observed inside their selected host module.
|
|
@@ -26,7 +32,7 @@ import { spawnGoTool } from "./spawnGoTool";
|
|
|
26
32
|
* @evidence contracts/common.md#meaningful-documentation Native prose distinguishes metadata selection from compilation and later materialized admission.
|
|
27
33
|
* @evidence contracts/portability.md#os-neutral-implementation Native path/fs operations and the shared Go tool selector preserve environment-name and path identity without blanket case folding; Go itself applies target platform and cgo selection.
|
|
28
34
|
* @evidence contracts/performance.md#efficient-algorithms One list command observes all distinct package entries in each prepared context. Context materialization traverses and copies admitted host/contributor files; workspace setup delegates manifest commands and list skips dependency loading. Framing and JSON parsing process complete output bytes, and retained results scale with selected packages/files.
|
|
29
|
-
* @evidence contracts/performance.md#reuse-equivalent-work Entries are deduplicated within a contextual observation; callers group equivalent module contexts for one load.
|
|
35
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Entries are deduplicated within a contextual observation; callers group equivalent module contexts for one load. With a record store, a successful selection is answered across loads and processes under an identity built from the content of everything Go reads, so any source, overlay, contributor, manifest, Go variable or toolchain change observes Go again (#1721); erroneous selections are never recorded. Cold admission uses the already prepared real build workspace.
|
|
30
36
|
* @evidence contracts/performance.md#bound-retention-and-release-resources Each metadata scratch has finally-based retirement-aware cleanup, and capture resources belong to spawnGoTool. Unknown native retirement retains scratch under the existing scope; results and complete output remain caller-owned with no independent byte ceiling.
|
|
31
37
|
*/
|
|
32
38
|
export namespace NativeSourcePackages {
|
|
@@ -59,6 +65,35 @@ export namespace NativeSourcePackages {
|
|
|
59
65
|
Error?: { Err: string };
|
|
60
66
|
}
|
|
61
67
|
|
|
68
|
+
/**
|
|
69
|
+
* One load's readings, shared by its package selections and its builds:
|
|
70
|
+
* source directory digests and module build-environment digests, each by
|
|
71
|
+
* absolute directory. The builds fill and report the same maps, so a load
|
|
72
|
+
* reads each directory and each module's toolchain once.
|
|
73
|
+
*
|
|
74
|
+
* @evidence contracts/common.md#principled-implementation Both maps carry readings already taken in this load under its fixed effective environment; builds still take their own environment reading with a witness.
|
|
75
|
+
* @evidence contracts/common.md#clear-and-simple-design Two maps already owned by the loader travel together.
|
|
76
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts The maps never outlive the load; cross-process reuse belongs to the record store.
|
|
77
|
+
* @evidence contracts/common.md#meaningful-documentation Members state their keys and values.
|
|
78
|
+
* @evidence contracts/portability.md#os-neutral-implementation Keys are native absolute paths.
|
|
79
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms A record type runs no algorithm.
|
|
80
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Selections and builds of one load share one reading per directory and module.
|
|
81
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources The loader owns both maps for one load.
|
|
82
|
+
*/
|
|
83
|
+
export interface SelectionReadings {
|
|
84
|
+
/** `pluginSourceDigest` of each directory this load read. */
|
|
85
|
+
sources?: Map<string, string>;
|
|
86
|
+
|
|
87
|
+
/** Build-environment digest of each module root this load read. */
|
|
88
|
+
environments?: Map<string, string>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Witness of each reading in `environments`, so a build of the same
|
|
92
|
+
* directory keys on that reading instead of probing Go again.
|
|
93
|
+
*/
|
|
94
|
+
witnesses?: Map<string, PluginBuildEnvironmentWitness.Record>;
|
|
95
|
+
}
|
|
96
|
+
|
|
62
97
|
/**
|
|
63
98
|
* Observe entries together in a private module layout for their context.
|
|
64
99
|
*
|
|
@@ -68,7 +103,7 @@ export namespace NativeSourcePackages {
|
|
|
68
103
|
* @evidence contracts/common.md#meaningful-documentation Native prose states this operation's authority and delegated boundaries; fields retain separate native comments.
|
|
69
104
|
* @evidence contracts/portability.md#os-neutral-implementation Native copy/path and selected tool/environment owners preserve platform identity; contribution target containment is checked.
|
|
70
105
|
* @evidence contracts/performance.md#efficient-algorithms Copies admitted host/contributor bytes and enumerates entries; workspace setup reads manifests and one list command skips dependencies.
|
|
71
|
-
* @evidence contracts/performance.md#reuse-equivalent-work All entries share one prepared context; optional readers are scoped to one load and the same selected tool/effective environment.
|
|
106
|
+
* @evidence contracts/performance.md#reuse-equivalent-work All entries share one prepared context; optional readers are scoped to one load and the same selected tool/effective environment. A supplied record store answers an equal selection without copying or running Go, under the content identity of the module, contributors, overlays, proposal manifests, toolchain/environment digest, Go variables, entries and command; an answer is recorded only when the copies matched their keyed digests, no package reported an error and the overlays still held their digests after Go ran.
|
|
72
107
|
* @evidence contracts/performance.md#bound-retention-and-release-resources Finally releases owned scratch through retirement authority; unknown native closure defers removal and capture cleanup remains delegated.
|
|
73
108
|
*/
|
|
74
109
|
export function inspect(opts: {
|
|
@@ -78,6 +113,18 @@ export namespace NativeSourcePackages {
|
|
|
78
113
|
packages: readonly { entry: string; source?: string; name?: string }[];
|
|
79
114
|
readers?: Map<string, SourcePluginWorkspace.GoModReader>;
|
|
80
115
|
proposal?: boolean;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Record store under which an equal selection is answered without Go
|
|
119
|
+
* (#1721); without it every load runs the full observation.
|
|
120
|
+
*/
|
|
121
|
+
identities?: PluginContentIdentities.Store;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The load's source and environment readings, shared with its builds so one
|
|
125
|
+
* load reads each directory and each module's toolchain once.
|
|
126
|
+
*/
|
|
127
|
+
readings?: SelectionReadings;
|
|
81
128
|
}): Package[] {
|
|
82
129
|
const env = SidecarEnvironment.merge(opts.env ?? process.env);
|
|
83
130
|
const { moduleRoot } = resolvePluginGoModule(opts.source, opts.pluginName);
|
|
@@ -88,6 +135,22 @@ export namespace NativeSourcePackages {
|
|
|
88
135
|
moduleRoot,
|
|
89
136
|
);
|
|
90
137
|
ensureExecutableGoToolchain(goBinary, compiler.bundled);
|
|
138
|
+
// WARNING (#1721): never drop this lookup or narrow its identity. Without
|
|
139
|
+
// it every warm launch and every unplugin generation copied the module
|
|
140
|
+
// twice and ran `go list` twice (6 s+ on Windows) to rediscover an answer
|
|
141
|
+
// its unchanged inputs already determined.
|
|
142
|
+
const identity =
|
|
143
|
+
opts.identities === undefined
|
|
144
|
+
? undefined
|
|
145
|
+
: selectionIdentity(opts, moduleRoot, goBinary, env, opts.identities);
|
|
146
|
+
if (identity !== undefined) {
|
|
147
|
+
const recalled = recallSelection(
|
|
148
|
+
PluginLoadAnswers.read(opts.identities, SELECTION_ANSWER, identity),
|
|
149
|
+
moduleRoot,
|
|
150
|
+
opts.packages.length,
|
|
151
|
+
);
|
|
152
|
+
if (recalled !== undefined) return recalled;
|
|
153
|
+
}
|
|
91
154
|
const scratch = createCanonicalTempDirectory("ttsc-package-selection-");
|
|
92
155
|
let taskFailure: { error: unknown } | undefined;
|
|
93
156
|
try {
|
|
@@ -96,10 +159,19 @@ export namespace NativeSourcePackages {
|
|
|
96
159
|
retainedPaths: [scratch],
|
|
97
160
|
});
|
|
98
161
|
SourcePluginWorkspace.materialize(moduleRoot, scratch);
|
|
162
|
+
// An answer is recorded only for the copy its identity describes: a
|
|
163
|
+
// module edited between the identity reading and the copy observed
|
|
164
|
+
// other bytes than the key names.
|
|
165
|
+
let copiesKeyed =
|
|
166
|
+
identity === undefined ||
|
|
167
|
+
pluginSourceDigest(scratch) === identity.module;
|
|
99
168
|
if (opts.proposal === true) {
|
|
100
169
|
// Only root manifests change. Relative Go inputs retain the source
|
|
101
170
|
// module layout, while its irrelevant graph/toolchain stays inactive.
|
|
102
|
-
const hostModule = resolvePluginGoModule(
|
|
171
|
+
const hostModule = resolvePluginGoModule(
|
|
172
|
+
genericHostSource(),
|
|
173
|
+
opts.pluginName,
|
|
174
|
+
).moduleRoot;
|
|
103
175
|
for (const name of ["go.mod", "go.sum"]) {
|
|
104
176
|
const manifest = path.join(hostModule, name);
|
|
105
177
|
const target = path.join(scratch, name);
|
|
@@ -110,19 +182,33 @@ export namespace NativeSourcePackages {
|
|
|
110
182
|
for (const input of opts.packages) {
|
|
111
183
|
if (input.source === undefined) continue;
|
|
112
184
|
const contributor = { source: input.source, name: input.name! };
|
|
113
|
-
SourcePluginAdmission.requireContributorPackage(
|
|
185
|
+
SourcePluginAdmission.requireContributorPackage(
|
|
186
|
+
opts.pluginName,
|
|
187
|
+
contributor,
|
|
188
|
+
);
|
|
114
189
|
const target = path.resolve(scratch, input.entry);
|
|
115
|
-
if (
|
|
190
|
+
if (
|
|
191
|
+
path.relative(scratch, target).startsWith("..") ||
|
|
192
|
+
target === scratch
|
|
193
|
+
)
|
|
116
194
|
throw new Error("ttsc: invalid package selection target");
|
|
117
195
|
if (fs.existsSync(target))
|
|
118
|
-
throw new Error(
|
|
196
|
+
throw new Error(
|
|
197
|
+
`ttsc: package selection target already exists: ${target}`,
|
|
198
|
+
);
|
|
119
199
|
SourcePluginWorkspace.materialize(input.source, target);
|
|
200
|
+
if (identity !== undefined)
|
|
201
|
+
copiesKeyed &&=
|
|
202
|
+
pluginSourceDigest(target) ===
|
|
203
|
+
identity.entries.find((entry) => entry[0] === input.entry)?.[2];
|
|
120
204
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
205
|
+
const reader = SourcePluginWorkspace.createGoModReader(
|
|
206
|
+
goBinary,
|
|
207
|
+
opts.pluginName,
|
|
208
|
+
env,
|
|
209
|
+
opts.readers,
|
|
210
|
+
opts.identities,
|
|
211
|
+
);
|
|
126
212
|
SourcePluginWorkspace.writeGoWork(
|
|
127
213
|
scratch,
|
|
128
214
|
SourcePluginWorkspace.findTtscOverlayDirs(),
|
|
@@ -131,20 +217,44 @@ export namespace NativeSourcePackages {
|
|
|
131
217
|
env,
|
|
132
218
|
reader,
|
|
133
219
|
);
|
|
134
|
-
|
|
220
|
+
const observed = read({
|
|
135
221
|
cwd: scratch,
|
|
136
222
|
entries: opts.packages.map((input) => input.entry),
|
|
137
223
|
env: GoSourceInputs.goBuildEnv(goBinary, undefined, env),
|
|
138
224
|
goBinary,
|
|
139
225
|
pluginName: opts.pluginName,
|
|
140
226
|
});
|
|
227
|
+
if (
|
|
228
|
+
identity !== undefined &&
|
|
229
|
+
copiesKeyed &&
|
|
230
|
+
observed.every((input) => input.Error === undefined) &&
|
|
231
|
+
JSON.stringify(overlayDigests(opts.identities)) ===
|
|
232
|
+
JSON.stringify(identity.overlays)
|
|
233
|
+
)
|
|
234
|
+
PluginLoadAnswers.write(
|
|
235
|
+
opts.identities,
|
|
236
|
+
SELECTION_ANSWER,
|
|
237
|
+
identity,
|
|
238
|
+
observed.map((input) => ({
|
|
239
|
+
...input,
|
|
240
|
+
Dir:
|
|
241
|
+
input.Dir === undefined
|
|
242
|
+
? undefined
|
|
243
|
+
: path.relative(scratch, input.Dir),
|
|
244
|
+
})),
|
|
245
|
+
);
|
|
246
|
+
return observed;
|
|
141
247
|
} catch (error) {
|
|
142
248
|
taskFailure = { error };
|
|
143
249
|
throw error;
|
|
144
250
|
} finally {
|
|
145
|
-
SourceNativeRetirement.releaseResource(
|
|
146
|
-
|
|
147
|
-
|
|
251
|
+
SourceNativeRetirement.releaseResource(
|
|
252
|
+
scratch,
|
|
253
|
+
() => {
|
|
254
|
+
fs.rmSync(scratch, { recursive: true, force: true });
|
|
255
|
+
},
|
|
256
|
+
taskFailure,
|
|
257
|
+
);
|
|
148
258
|
}
|
|
149
259
|
}
|
|
150
260
|
|
|
@@ -157,13 +267,15 @@ export namespace NativeSourcePackages {
|
|
|
157
267
|
* @evidence contracts/common.md#meaningful-documentation Native prose states this operation's authority and delegated boundaries; fields retain separate native comments.
|
|
158
268
|
* @evidence contracts/portability.md#os-neutral-implementation Native module roots retain their selected spelling, without blanket case folding.
|
|
159
269
|
* @evidence contracts/performance.md#efficient-algorithms One grouping pass plus contextual copies and metadata work for each distinct module; output storage scales with input population.
|
|
160
|
-
* @evidence contracts/performance.md#reuse-equivalent-work Equivalent entries in a module share one list; overlay module observations share the load's selected-tool readers,
|
|
270
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Equivalent entries in a module share one list; overlay module observations share the load's selected-tool readers, and the load's readings and record store reach each contextual observation so equal selections are answered from their content identity.
|
|
161
271
|
* @evidence contracts/performance.md#bound-retention-and-release-resources Grouping/maps/results live for this call/load; scratch and native captures remain with inspect/read.
|
|
162
272
|
*/
|
|
163
273
|
export function ownPackages(
|
|
164
274
|
inputs: readonly { source: string; label: string }[],
|
|
165
275
|
env: NodeJS.ProcessEnv,
|
|
166
276
|
readers = new Map<string, SourcePluginWorkspace.GoModReader>(),
|
|
277
|
+
identities?: PluginContentIdentities.Store,
|
|
278
|
+
readings?: SelectionReadings,
|
|
167
279
|
): Package[] {
|
|
168
280
|
const groups = new Map<string, Array<{ index: number; entry: string }>>();
|
|
169
281
|
inputs.forEach((input, index) => {
|
|
@@ -180,9 +292,13 @@ export namespace NativeSourcePackages {
|
|
|
180
292
|
pluginName: first.label,
|
|
181
293
|
env,
|
|
182
294
|
readers,
|
|
295
|
+
identities,
|
|
296
|
+
readings,
|
|
183
297
|
packages: group.map(({ entry }) => ({ entry })),
|
|
184
298
|
});
|
|
185
|
-
group.forEach(({ index }, offset) => {
|
|
299
|
+
group.forEach(({ index }, offset) => {
|
|
300
|
+
output[index] = observations[offset]!;
|
|
301
|
+
});
|
|
186
302
|
}
|
|
187
303
|
return output;
|
|
188
304
|
}
|
|
@@ -190,11 +306,11 @@ export namespace NativeSourcePackages {
|
|
|
190
306
|
/**
|
|
191
307
|
* Propose ownership with the candidate-selected tool before final admission.
|
|
192
308
|
*
|
|
193
|
-
* Nested transform sources retain their admitted source-module layout but
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
309
|
+
* Nested transform sources retain their admitted source-module layout but use
|
|
310
|
+
* unchanged generic host root manifests in scratch. Their outer module graph
|
|
311
|
+
* and toolchain are not source-only build authority. Module-root and check
|
|
312
|
+
* sources retain their actual owning module. Executable proposals must agree
|
|
313
|
+
* in the actual own module; linked proposals in each chosen actual host.
|
|
198
314
|
*
|
|
199
315
|
* @evidence contracts/common.md#principled-implementation Candidate tool selection occurs at the original owning module and effective environment; only prospective nested source-only manifests change in owned scratch before Go interprets the normal generated workspace.
|
|
200
316
|
* @evidence contracts/common.md#clear-and-simple-design One grouping pass shares each module/proposal context and returns whether actual own-module observation already occurred.
|
|
@@ -202,15 +318,20 @@ export namespace NativeSourcePackages {
|
|
|
202
318
|
* @evidence contracts/common.md#meaningful-documentation Native prose states deterministic proposal authority and mandatory final ownership confirmation.
|
|
203
319
|
* @evidence contracts/portability.md#os-neutral-implementation Native module/tool/environment owners preserve candidate-relative tool and source layout identity.
|
|
204
320
|
* @evidence contracts/performance.md#efficient-algorithms Groups inputs by module and actual/prospective mode, copies each admitted module once per context, and batches entries through Go metadata; retained work scales with source bytes and distinct contexts.
|
|
205
|
-
* @evidence contracts/performance.md#reuse-equivalent-work Equivalent entries share one observation and per-load manifest readers;
|
|
321
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Equivalent entries share one observation and per-load manifest readers; a result crosses loads only through the record store's content identity, so a changed context observes Go again.
|
|
206
322
|
* @evidence contracts/performance.md#bound-retention-and-release-resources Group records live for this call; scratch/process cleanup remains with inspect and no independent output byte cap is imposed.
|
|
207
323
|
*/
|
|
208
324
|
export function propose(
|
|
209
325
|
inputs: readonly { source: string; label: string; ownModule?: boolean }[],
|
|
210
326
|
env: NodeJS.ProcessEnv,
|
|
211
327
|
readers = new Map<string, SourcePluginWorkspace.GoModReader>(),
|
|
328
|
+
identities?: PluginContentIdentities.Store,
|
|
329
|
+
readings?: SelectionReadings,
|
|
212
330
|
): Array<{ observation: Package; ownModule: boolean }> {
|
|
213
|
-
const groups = new Map<
|
|
331
|
+
const groups = new Map<
|
|
332
|
+
string,
|
|
333
|
+
Array<{ index: number; entry: string; ownModule: boolean }>
|
|
334
|
+
>();
|
|
214
335
|
inputs.forEach((input, index) => {
|
|
215
336
|
const target = resolvePluginGoModule(input.source, input.label);
|
|
216
337
|
const ownModule = input.ownModule === true || target.entry === ".";
|
|
@@ -219,11 +340,17 @@ export namespace NativeSourcePackages {
|
|
|
219
340
|
group.push({ index, entry: target.entry, ownModule });
|
|
220
341
|
groups.set(key, group);
|
|
221
342
|
});
|
|
222
|
-
const output: Array<{ observation: Package; ownModule: boolean }> =
|
|
343
|
+
const output: Array<{ observation: Package; ownModule: boolean }> =
|
|
344
|
+
new Array(inputs.length);
|
|
223
345
|
for (const group of groups.values()) {
|
|
224
346
|
const first = inputs[group[0]!.index]!;
|
|
225
347
|
const observations = inspect({
|
|
226
|
-
source: first.source,
|
|
348
|
+
source: first.source,
|
|
349
|
+
pluginName: first.label,
|
|
350
|
+
env,
|
|
351
|
+
readers,
|
|
352
|
+
identities,
|
|
353
|
+
readings,
|
|
227
354
|
proposal: !group[0]!.ownModule,
|
|
228
355
|
packages: group.map(({ entry }) => ({ entry })),
|
|
229
356
|
});
|
|
@@ -258,21 +385,38 @@ export namespace NativeSourcePackages {
|
|
|
258
385
|
OwnedSynchronousProcess.checkpoint();
|
|
259
386
|
const result = spawnGoTool(
|
|
260
387
|
opts.goBinary,
|
|
261
|
-
[
|
|
388
|
+
[
|
|
389
|
+
"list",
|
|
390
|
+
...GoSourceInputs.BUILD_FLAGS,
|
|
391
|
+
"-find",
|
|
392
|
+
"-e",
|
|
393
|
+
"-json=Dir,Name,GoFiles,CgoFiles,Error",
|
|
394
|
+
...entries,
|
|
395
|
+
],
|
|
262
396
|
{ cwd: opts.cwd, encoding: "utf8", env: opts.env, windowsHide: true },
|
|
263
397
|
);
|
|
264
398
|
OwnedSynchronousProcess.checkpoint();
|
|
265
399
|
if (result.error !== undefined)
|
|
266
|
-
throw new Error(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
400
|
+
throw new Error(
|
|
401
|
+
SourcePluginWorkspace.goSpawnFailureMessage(
|
|
402
|
+
`selecting Go packages for plugin "${opts.pluginName}"`,
|
|
403
|
+
opts.pluginName,
|
|
404
|
+
opts.goBinary,
|
|
405
|
+
opts.cwd,
|
|
406
|
+
result.error,
|
|
407
|
+
),
|
|
408
|
+
);
|
|
270
409
|
if (result.status !== 0)
|
|
271
|
-
throw new Error(
|
|
410
|
+
throw new Error(
|
|
411
|
+
`ttsc: selecting Go packages for plugin "${opts.pluginName}" failed:\n${result.stderr || result.stdout}`,
|
|
412
|
+
);
|
|
272
413
|
const packages = parse(result.stdout);
|
|
273
|
-
const selected = new Map(
|
|
274
|
-
|
|
275
|
-
|
|
414
|
+
const selected = new Map(
|
|
415
|
+
packages.map((input) => [
|
|
416
|
+
input.Dir === undefined ? undefined : fs.realpathSync.native(input.Dir),
|
|
417
|
+
input,
|
|
418
|
+
]),
|
|
419
|
+
);
|
|
276
420
|
return opts.entries.map((entry) => {
|
|
277
421
|
const dir = fs.realpathSync.native(path.resolve(opts.cwd, entry));
|
|
278
422
|
const input = selected.get(dir);
|
|
@@ -300,12 +444,21 @@ export namespace NativeSourcePackages {
|
|
|
300
444
|
expected?: "executable" | "linked",
|
|
301
445
|
): "executable" | "linked" {
|
|
302
446
|
if (input.Error !== undefined)
|
|
303
|
-
throw new Error(
|
|
304
|
-
|
|
305
|
-
|
|
447
|
+
throw new Error(
|
|
448
|
+
`ttsc: plugin "${label}" Go package selection failed: ${input.Error.Err}`,
|
|
449
|
+
);
|
|
450
|
+
if (
|
|
451
|
+
input.Name === undefined ||
|
|
452
|
+
(input.GoFiles?.length ?? 0) + (input.CgoFiles?.length ?? 0) === 0
|
|
453
|
+
)
|
|
454
|
+
throw new Error(
|
|
455
|
+
`ttsc: plugin "${label}" source has no Go-selected production files: ${input.Dir ?? "unknown package"}`,
|
|
456
|
+
);
|
|
306
457
|
const actual = input.Name === "main" ? "executable" : "linked";
|
|
307
458
|
if (expected !== undefined && actual !== expected)
|
|
308
|
-
throw new Error(
|
|
459
|
+
throw new Error(
|
|
460
|
+
`ttsc: plugin "${label}" Go package ownership disagrees with its proposal: selected ${actual}, expected ${expected} (${input.Dir ?? "unknown package"})`,
|
|
461
|
+
);
|
|
309
462
|
return actual;
|
|
310
463
|
}
|
|
311
464
|
|
|
@@ -331,7 +484,8 @@ export namespace NativeSourcePackages {
|
|
|
331
484
|
const token = output[offset]!;
|
|
332
485
|
if (start < 0) {
|
|
333
486
|
if (/\s/.test(token)) continue;
|
|
334
|
-
if (token !== "{")
|
|
487
|
+
if (token !== "{")
|
|
488
|
+
throw new Error("ttsc: invalid Go package metadata stream");
|
|
335
489
|
start = offset;
|
|
336
490
|
depth = 1;
|
|
337
491
|
continue;
|
|
@@ -347,17 +501,25 @@ export namespace NativeSourcePackages {
|
|
|
347
501
|
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
348
502
|
throw new Error("ttsc: invalid Go package metadata object");
|
|
349
503
|
const record = input as Package;
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
(
|
|
504
|
+
if (
|
|
505
|
+
(record.Dir !== undefined && typeof record.Dir !== "string") ||
|
|
506
|
+
(record.Name !== undefined && typeof record.Name !== "string") ||
|
|
507
|
+
[record.GoFiles, record.CgoFiles].some(
|
|
508
|
+
(files) =>
|
|
509
|
+
files !== undefined &&
|
|
510
|
+
(!Array.isArray(files) ||
|
|
511
|
+
files.some((file) => typeof file !== "string")),
|
|
512
|
+
) ||
|
|
513
|
+
(record.Error !== undefined &&
|
|
514
|
+
(record.Error === null || typeof record.Error.Err !== "string"))
|
|
515
|
+
)
|
|
355
516
|
throw new Error("ttsc: invalid Go package metadata fields");
|
|
356
517
|
packages.push(record);
|
|
357
518
|
start = -1;
|
|
358
519
|
}
|
|
359
520
|
}
|
|
360
|
-
if (start >= 0)
|
|
521
|
+
if (start >= 0)
|
|
522
|
+
throw new Error("ttsc: incomplete Go package metadata stream");
|
|
361
523
|
return packages;
|
|
362
524
|
}
|
|
363
525
|
}
|
|
@@ -366,3 +528,158 @@ export namespace NativeSourcePackages {
|
|
|
366
528
|
function genericHostSource(): string {
|
|
367
529
|
return path.resolve(__dirname, "../../../..", "cmd/utility-host");
|
|
368
530
|
}
|
|
531
|
+
|
|
532
|
+
/** Answer family of package selections (`PluginLoadAnswers`). */
|
|
533
|
+
const SELECTION_ANSWER = "go-package-selection";
|
|
534
|
+
|
|
535
|
+
/** Fields `read` asks Go for; part of the selection identity. */
|
|
536
|
+
const SELECTION_FIELDS = "-json=Dir,Name,GoFiles,CgoFiles,Error";
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Everything a package selection's answer depends on, by content: the copied
|
|
540
|
+
* module, each contributor copied into it, every ttsc overlay the generated
|
|
541
|
+
* workspace uses, the generic host manifests a proposal substitutes, the
|
|
542
|
+
* toolchain and build environment Go reads, every `GO*`/`CGO_*` variable the
|
|
543
|
+
* `go list` process receives, the entries and the fixed command. Paths stay
|
|
544
|
+
* out, so equal content in another location shares the answer, as the binary
|
|
545
|
+
* key does.
|
|
546
|
+
*/
|
|
547
|
+
interface ISelectionIdentity {
|
|
548
|
+
format: 1;
|
|
549
|
+
command: readonly string[];
|
|
550
|
+
mode: "proposal" | "actual";
|
|
551
|
+
module: string;
|
|
552
|
+
environment: string;
|
|
553
|
+
variables: [string, string][];
|
|
554
|
+
manifests: (string | null)[] | null;
|
|
555
|
+
overlays: string[];
|
|
556
|
+
entries: [string, string | null, string | null][];
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function selectionIdentity(
|
|
560
|
+
opts: Parameters<typeof NativeSourcePackages.inspect>[0],
|
|
561
|
+
moduleRoot: string,
|
|
562
|
+
goBinary: string,
|
|
563
|
+
env: NodeJS.ProcessEnv,
|
|
564
|
+
identities: PluginContentIdentities.Store,
|
|
565
|
+
): ISelectionIdentity | undefined {
|
|
566
|
+
try {
|
|
567
|
+
const readings = opts.readings ?? {};
|
|
568
|
+
const directory = (root: string): string => {
|
|
569
|
+
const resolved = path.resolve(root);
|
|
570
|
+
let digest = readings.sources?.get(resolved);
|
|
571
|
+
if (digest === undefined) {
|
|
572
|
+
digest = PluginContentIdentities.sourceDirectory(identities, resolved);
|
|
573
|
+
readings.sources?.set(resolved, digest);
|
|
574
|
+
}
|
|
575
|
+
return digest;
|
|
576
|
+
};
|
|
577
|
+
const resolvedRoot = path.resolve(moduleRoot);
|
|
578
|
+
let environment = readings.environments?.get(resolvedRoot);
|
|
579
|
+
if (environment === undefined) {
|
|
580
|
+
const hash = crypto.createHash("sha256");
|
|
581
|
+
const witness: PluginBuildEnvironmentWitness.Record = new Map();
|
|
582
|
+
hashPluginBuildEnvironment(
|
|
583
|
+
hash,
|
|
584
|
+
goBinary,
|
|
585
|
+
moduleRoot,
|
|
586
|
+
env,
|
|
587
|
+
{ readFile: (location) => fs.readFileSync(location) },
|
|
588
|
+
witness,
|
|
589
|
+
identities,
|
|
590
|
+
);
|
|
591
|
+
environment = hash.digest("hex");
|
|
592
|
+
readings.environments?.set(resolvedRoot, environment);
|
|
593
|
+
readings.witnesses?.set(resolvedRoot, witness);
|
|
594
|
+
}
|
|
595
|
+
const host =
|
|
596
|
+
opts.proposal === true
|
|
597
|
+
? resolvePluginGoModule(genericHostSource(), opts.pluginName).moduleRoot
|
|
598
|
+
: undefined;
|
|
599
|
+
return {
|
|
600
|
+
format: 1,
|
|
601
|
+
command: [
|
|
602
|
+
"list",
|
|
603
|
+
...GoSourceInputs.BUILD_FLAGS,
|
|
604
|
+
"-find",
|
|
605
|
+
"-e",
|
|
606
|
+
SELECTION_FIELDS,
|
|
607
|
+
],
|
|
608
|
+
mode: opts.proposal === true ? "proposal" : "actual",
|
|
609
|
+
module: directory(moduleRoot),
|
|
610
|
+
environment,
|
|
611
|
+
variables: Object.entries(
|
|
612
|
+
GoSourceInputs.goBuildEnv(goBinary, undefined, env),
|
|
613
|
+
)
|
|
614
|
+
.filter(
|
|
615
|
+
(entry): entry is [string, string] =>
|
|
616
|
+
entry[1] !== undefined && /^(GO|CGO_)/i.test(entry[0]),
|
|
617
|
+
)
|
|
618
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)),
|
|
619
|
+
manifests:
|
|
620
|
+
host === undefined
|
|
621
|
+
? null
|
|
622
|
+
: ["go.mod", "go.sum"].map((name) =>
|
|
623
|
+
fileDigest(path.join(host, name)),
|
|
624
|
+
),
|
|
625
|
+
overlays: SourcePluginWorkspace.findTtscOverlayDirs().map(directory),
|
|
626
|
+
entries: opts.packages.map((input) => [
|
|
627
|
+
input.entry,
|
|
628
|
+
input.name ?? null,
|
|
629
|
+
input.source === undefined ? null : directory(input.source),
|
|
630
|
+
]),
|
|
631
|
+
};
|
|
632
|
+
} catch {
|
|
633
|
+
// An identity that cannot be formed only costs the real observation.
|
|
634
|
+
return undefined;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/** The digest of every overlay module, in the generated workspace's order. */
|
|
639
|
+
function overlayDigests(
|
|
640
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
641
|
+
): string[] {
|
|
642
|
+
return SourcePluginWorkspace.findTtscOverlayDirs().map((directory) =>
|
|
643
|
+
PluginContentIdentities.sourceDirectory(identities, directory),
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/** A small manifest's content digest, or null when it does not exist. */
|
|
648
|
+
function fileDigest(file: string): string | null {
|
|
649
|
+
try {
|
|
650
|
+
return crypto
|
|
651
|
+
.createHash("sha256")
|
|
652
|
+
.update(fs.readFileSync(file))
|
|
653
|
+
.digest("hex");
|
|
654
|
+
} catch (error) {
|
|
655
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* A recorded selection rebased onto the module it now describes, or `undefined`
|
|
662
|
+
* when the record does not have the shape `read` produces.
|
|
663
|
+
*/
|
|
664
|
+
function recallSelection(
|
|
665
|
+
value: unknown,
|
|
666
|
+
moduleRoot: string,
|
|
667
|
+
count: number,
|
|
668
|
+
): NativeSourcePackages.Package[] | undefined {
|
|
669
|
+
if (!Array.isArray(value) || value.length !== count) return undefined;
|
|
670
|
+
try {
|
|
671
|
+
const packages = NativeSourcePackages.parse(
|
|
672
|
+
value.map((input) => JSON.stringify(input)).join("\n"),
|
|
673
|
+
);
|
|
674
|
+
if (packages.length !== count) return undefined;
|
|
675
|
+
return packages.map((input) => ({
|
|
676
|
+
...input,
|
|
677
|
+
Dir:
|
|
678
|
+
input.Dir === undefined
|
|
679
|
+
? undefined
|
|
680
|
+
: path.resolve(moduleRoot, input.Dir),
|
|
681
|
+
}));
|
|
682
|
+
} catch {
|
|
683
|
+
return undefined;
|
|
684
|
+
}
|
|
685
|
+
}
|