ttsc 0.31.0 → 0.31.1
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/internal/nativeBinary.d.ts +3 -3
- package/lib/internal/nativeBinary.js +3 -3
- package/lib/plugin/internal/load/loadProjectPlugins.d.ts +12 -13
- package/lib/plugin/internal/load/loadProjectPlugins.js +61 -36
- package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
- package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +42 -0
- package/lib/plugin/internal/source/GoEnvironmentReading.js +110 -0
- package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -0
- package/lib/plugin/internal/source/NativeSourcePackages.d.ts +7 -7
- package/lib/plugin/internal/source/NativeSourcePackages.js +33 -20
- package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +21 -0
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +36 -0
- 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/SourcePluginWorkspace.d.ts +50 -19
- package/lib/plugin/internal/source/SourcePluginWorkspace.js +109 -22
- package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
- package/lib/plugin/internal/source/buildSourcePlugin.d.ts +19 -16
- package/lib/plugin/internal/source/buildSourcePlugin.js +27 -22
- package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/source/computeCacheKey.d.ts +4 -2
- package/lib/plugin/internal/source/computeCacheKey.js +15 -6
- package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +2 -68
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.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/package.json +8 -8
- package/src/internal/nativeBinary.ts +3 -3
- package/src/plugin/internal/load/loadProjectPlugins.ts +209 -117
- package/src/plugin/internal/source/GoEnvironmentReading.ts +109 -0
- package/src/plugin/internal/source/NativeSourcePackages.ts +103 -43
- package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +42 -0
- package/src/plugin/internal/source/PluginBuildEnvironmentWitness.ts +18 -8
- package/src/plugin/internal/source/SourcePluginWorkspace.ts +143 -25
- package/src/plugin/internal/source/buildSourcePlugin.ts +62 -34
- package/src/plugin/internal/source/computeCacheKey.ts +26 -8
- package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +8 -76
- package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +13 -25
|
@@ -23,12 +23,12 @@ import { pluginDescriptorFailureReason } from "../pluginDescriptorFailureReason"
|
|
|
23
23
|
import { pluginDescriptorProcessFailure } from "../pluginDescriptorProcessFailure";
|
|
24
24
|
import { NativeSourcePackages } from "../source/NativeSourcePackages";
|
|
25
25
|
import { SourcePluginWorkspace } from "../source/SourcePluginWorkspace";
|
|
26
|
-
import { resolvePluginGoModule } from "../source/resolvePluginGoModule";
|
|
27
26
|
import { buildSourcePlugin } from "../source/buildSourcePlugin";
|
|
28
27
|
import { isPathWithin } from "../source/isPathWithin";
|
|
29
28
|
import { pluginBuildVersions } from "../source/pluginBuildVersions";
|
|
30
29
|
import { pluginModuleReplaceDirectories } from "../source/pluginModuleReplaceDirectories";
|
|
31
30
|
import { pluginSourceState } from "../source/pluginSourceState";
|
|
31
|
+
import { resolvePluginGoModule } from "../source/resolvePluginGoModule";
|
|
32
32
|
import { COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE } from "./COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE";
|
|
33
33
|
import { PLUGIN_DESCRIPTOR_SHIM_SOURCE } from "./PLUGIN_DESCRIPTOR_SHIM_SOURCE";
|
|
34
34
|
import { PluginDescriptorAdmission } from "./PluginDescriptorAdmission";
|
|
@@ -61,19 +61,18 @@ import { visitImportMappedCandidates } from "./visitImportMappedCandidates";
|
|
|
61
61
|
* under generic host manifests while preserving source-relative input layout.
|
|
62
62
|
* Main proposals must agree in their full owning module; linked proposals must
|
|
63
63
|
* agree inside every selected transform host. Errors or disagreement terminate
|
|
64
|
-
* the load rather than changing the proposed kind.
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* linked-plugin host, are keyed but omitted from this report under the
|
|
64
|
+
* the load rather than changing the proposed kind. Watch callers first receive
|
|
65
|
+
* structurally selected source/module/contributor repair interests, before
|
|
66
|
+
* metadata can reject them. Valid records then narrow those interests to the
|
|
67
|
+
* exact build directories; interests alone never admit a plugin, certify a kind
|
|
68
|
+
* or supply reusable source-state proof. Returns the ordered native plugins,
|
|
69
|
+
* parsed project config, recorded JavaScript-host inputs and unresolved
|
|
70
|
+
* selection candidates, and the keyed state of reported Go source directories
|
|
71
|
+
* supplied to the builds (`pluginSources`): each plugin's module root and each
|
|
72
|
+
* contributor's source, with its state (`pluginSourceState`), the sources as
|
|
73
|
+
* the build read them together with the environment a build there is keyed on.
|
|
74
|
+
* Directories within ttsc's installed package, including its overlays and the
|
|
75
|
+
* fallback linked-plugin host, are keyed but omitted from this report under the
|
|
77
76
|
* installed-package/version ownership policy. That policy does not prove the
|
|
78
77
|
* installation cannot be edited in place. Descriptor completeness relies on the
|
|
79
78
|
* runtime recorder's status and the descriptor's explicit external-read
|
|
@@ -329,30 +328,41 @@ export function loadProjectPlugins(options: {
|
|
|
329
328
|
const { ttsc: ttscVersion, tsgo: tsgoVersion } = pluginBuildVersions(
|
|
330
329
|
context.projectRoot,
|
|
331
330
|
);
|
|
332
|
-
const candidates = plugins
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
331
|
+
const candidates = plugins
|
|
332
|
+
.map((plugin, index) => {
|
|
333
|
+
const stage = PluginDescriptorAdmission.stage(plugin);
|
|
334
|
+
validatePluginSource(plugin);
|
|
335
|
+
const contributors = validatePluginContributors(plugin);
|
|
336
|
+
const source = resolvePluginSource(plugin.source, context.projectRoot);
|
|
337
|
+
const label = pluginLabel(plugin, entries[index]!.config, index);
|
|
338
|
+
requirePluginSource(source, label);
|
|
339
|
+
return { plugin, index, stage, contributors, source, label };
|
|
340
|
+
})
|
|
341
|
+
.map((candidate) => ({
|
|
342
|
+
...candidate,
|
|
343
|
+
...resolvePluginGoModule(candidate.source, candidate.label),
|
|
344
|
+
}));
|
|
345
|
+
// One fixed-effective-environment load owns manifest syntax sharing from the
|
|
346
|
+
// first watch query through metadata and builds. Do not allocate this after
|
|
347
|
+
// watch projection: that silently repeats selected-Go work (#1712).
|
|
348
|
+
const packageReaders = new Map<string, SourcePluginWorkspace.GoModReader>();
|
|
349
|
+
const watchReplacements =
|
|
350
|
+
options.onWatchInputs === undefined
|
|
351
|
+
? undefined
|
|
352
|
+
: new Map<string, readonly string[]>();
|
|
347
353
|
if (options.onWatchInputs !== undefined) {
|
|
348
354
|
// The prospective context reads the admitted module layout and anchors its
|
|
349
355
|
// tool there before kind exists. Keep both possible final-root baselines:
|
|
350
356
|
// a linked package keeps packageDir, and an executable keeps moduleRoot.
|
|
351
|
-
const interests = new Set(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
357
|
+
const interests = new Set(
|
|
358
|
+
candidates
|
|
359
|
+
.flatMap((candidate) => [
|
|
360
|
+
candidate.moduleRoot,
|
|
361
|
+
candidate.packageDir,
|
|
362
|
+
...(candidate.contributors ?? []).map((input) => input.source),
|
|
363
|
+
])
|
|
364
|
+
.filter(reportsPluginSource),
|
|
365
|
+
);
|
|
356
366
|
options.onWatchInputs([...interests].sort());
|
|
357
367
|
const initialSize = interests.size;
|
|
358
368
|
for (const candidate of candidates) {
|
|
@@ -360,77 +370,102 @@ export function loadProjectPlugins(options: {
|
|
|
360
370
|
// activate their irrelevant module graph just to discover watch inputs.
|
|
361
371
|
if (candidate.stage === "transform" && candidate.entry !== ".") continue;
|
|
362
372
|
for (const directory of pluginReplacementWatchDirectories(
|
|
363
|
-
candidate.moduleRoot,
|
|
364
|
-
|
|
373
|
+
candidate.moduleRoot,
|
|
374
|
+
effectiveEnv,
|
|
375
|
+
watchReplacements,
|
|
376
|
+
packageReaders,
|
|
377
|
+
))
|
|
378
|
+
if (reportsPluginSource(directory)) interests.add(directory);
|
|
365
379
|
}
|
|
366
380
|
if (interests.size !== initialSize)
|
|
367
381
|
options.onWatchInputs([...interests].sort());
|
|
368
382
|
}
|
|
369
|
-
const packageReaders = new Map<string, SourcePluginWorkspace.GoModReader>();
|
|
370
383
|
const proposals = NativeSourcePackages.propose(
|
|
371
384
|
candidates.map((candidate) => ({
|
|
372
|
-
...candidate,
|
|
373
|
-
|
|
385
|
+
...candidate,
|
|
386
|
+
ownModule: candidate.stage !== "transform",
|
|
387
|
+
})),
|
|
388
|
+
effectiveEnv,
|
|
389
|
+
packageReaders,
|
|
374
390
|
);
|
|
375
|
-
const records = candidates.map(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
391
|
+
const records = candidates.map(
|
|
392
|
+
({ plugin, index, stage, contributors, source, packageDir }) => {
|
|
393
|
+
const { kind, moduleRoot } = resolveNativeSource(
|
|
394
|
+
source,
|
|
395
|
+
plugin,
|
|
396
|
+
entries[index]!.config,
|
|
397
|
+
index,
|
|
398
|
+
{
|
|
399
|
+
env: effectiveEnv,
|
|
400
|
+
observation: proposals[index]!.observation,
|
|
401
|
+
},
|
|
382
402
|
);
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
hostInputs,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
index,
|
|
397
|
-
hostInputs,
|
|
398
|
-
);
|
|
399
|
-
return {
|
|
400
|
-
capabilities: plugin.capabilities,
|
|
401
|
-
contributors,
|
|
402
|
-
config: entries[index]!.config,
|
|
403
|
-
kind,
|
|
404
|
-
label: pluginLabel(plugin, entries[index]!.config, index),
|
|
405
|
-
linkedContributorName,
|
|
406
|
-
name: plugin.name,
|
|
407
|
-
reportsTypeScriptDiagnostics:
|
|
408
|
-
plugin.reportsTypeScriptDiagnostics === true,
|
|
409
|
-
request: loadedEntries[index]!.request,
|
|
410
|
-
hostInputHashes: mergePluginHostInputHashes(
|
|
411
|
-
loadedEntries[index]!.hostInputHashes,
|
|
412
|
-
pluginHostInputHashes,
|
|
413
|
-
loadedEntries[index]!.hostInputs,
|
|
403
|
+
if (kind === "linked" && stage !== "transform") {
|
|
404
|
+
throw new Error(
|
|
405
|
+
`ttsc: plugin "${pluginLabel(plugin, entries[index]!.config, index)}" source is a linked Go package, but only transform-stage plugins can be linked into a compiler host`,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
const linkedContributorName =
|
|
409
|
+
kind === "linked"
|
|
410
|
+
? `linked_${String(index).padStart(6, "0")}`
|
|
411
|
+
: undefined;
|
|
412
|
+
const hostInputs = validatePluginHostInputs(plugin, index);
|
|
413
|
+
const pluginHostInputHashes = validatePluginHostInputHashes(
|
|
414
|
+
plugin,
|
|
415
|
+
index,
|
|
414
416
|
hostInputs,
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
loadedEntries[index]!.hostInputs,
|
|
417
|
+
);
|
|
418
|
+
const pluginHostInputRealpaths = validatePluginHostInputRealpaths(
|
|
419
|
+
plugin,
|
|
420
|
+
index,
|
|
420
421
|
hostInputs,
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
422
|
+
);
|
|
423
|
+
return {
|
|
424
|
+
capabilities: plugin.capabilities,
|
|
425
|
+
contributors,
|
|
426
|
+
config: entries[index]!.config,
|
|
427
|
+
kind,
|
|
428
|
+
label: pluginLabel(plugin, entries[index]!.config, index),
|
|
429
|
+
linkedContributorName,
|
|
430
|
+
name: plugin.name,
|
|
431
|
+
reportsTypeScriptDiagnostics:
|
|
432
|
+
plugin.reportsTypeScriptDiagnostics === true,
|
|
433
|
+
request: loadedEntries[index]!.request,
|
|
434
|
+
hostInputHashes: mergePluginHostInputHashes(
|
|
435
|
+
loadedEntries[index]!.hostInputHashes,
|
|
436
|
+
pluginHostInputHashes,
|
|
437
|
+
loadedEntries[index]!.hostInputs,
|
|
438
|
+
hostInputs,
|
|
439
|
+
),
|
|
440
|
+
hostInputRealpaths: mergePluginHostInputHashes(
|
|
441
|
+
loadedEntries[index]!.hostInputRealpaths,
|
|
442
|
+
pluginHostInputRealpaths,
|
|
443
|
+
loadedEntries[index]!.hostInputs,
|
|
444
|
+
hostInputs,
|
|
445
|
+
),
|
|
446
|
+
hostInputs: [...loadedEntries[index]!.hostInputs, ...hostInputs],
|
|
447
|
+
moduleRoot,
|
|
448
|
+
packageDir,
|
|
449
|
+
source,
|
|
450
|
+
stage,
|
|
451
|
+
};
|
|
452
|
+
},
|
|
453
|
+
);
|
|
454
|
+
const executableCandidates = candidates.filter(
|
|
455
|
+
(candidate) =>
|
|
456
|
+
records[candidate.index]!.kind === "executable" &&
|
|
457
|
+
!proposals[candidate.index]!.ownModule,
|
|
458
|
+
);
|
|
431
459
|
// Admission can now narrow the provisional interests without restamping the
|
|
432
460
|
// retained package/module baselines. Final host errors still keep these roots.
|
|
433
|
-
options.onWatchInputs?.(
|
|
461
|
+
options.onWatchInputs?.(
|
|
462
|
+
pluginBuildDirectories(
|
|
463
|
+
records,
|
|
464
|
+
effectiveEnv,
|
|
465
|
+
watchReplacements,
|
|
466
|
+
packageReaders,
|
|
467
|
+
),
|
|
468
|
+
);
|
|
434
469
|
const linkedContributors = records
|
|
435
470
|
.filter((record) => record.stage === "transform")
|
|
436
471
|
.flatMap((record) =>
|
|
@@ -448,59 +483,96 @@ export function loadProjectPlugins(options: {
|
|
|
448
483
|
// a second executable host can select a genuinely different Go context.
|
|
449
484
|
const admittedExecutables = new Set<string>();
|
|
450
485
|
if (linkedContributors.length !== 0) {
|
|
451
|
-
const hosts =
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
486
|
+
const hosts =
|
|
487
|
+
transformHosts.length === 0
|
|
488
|
+
? [
|
|
489
|
+
{
|
|
490
|
+
source: path.join(ttscPackageRoot(), "cmd", "utility-host"),
|
|
491
|
+
label: "linked-plugin-host",
|
|
492
|
+
},
|
|
493
|
+
]
|
|
494
|
+
: transformHosts;
|
|
495
|
+
const contexts = new Map<
|
|
496
|
+
string,
|
|
497
|
+
{ source: string; label: string; records: typeof transformHosts }
|
|
498
|
+
>();
|
|
455
499
|
for (const host of hosts) {
|
|
456
|
-
const moduleRoot = resolvePluginGoModule(
|
|
500
|
+
const moduleRoot = resolvePluginGoModule(
|
|
501
|
+
host.source,
|
|
502
|
+
host.label,
|
|
503
|
+
).moduleRoot;
|
|
457
504
|
let context = contexts.get(moduleRoot);
|
|
458
505
|
if (context === undefined) {
|
|
459
506
|
context = { source: host.source, label: host.label, records: [] };
|
|
460
507
|
contexts.set(moduleRoot, context);
|
|
461
508
|
}
|
|
462
509
|
}
|
|
463
|
-
for (const record of transformHosts)
|
|
510
|
+
for (const record of transformHosts)
|
|
511
|
+
contexts.get(record.moduleRoot)!.records.push(record);
|
|
464
512
|
for (const host of contexts.values()) {
|
|
465
513
|
const moduleHosts = host.records;
|
|
466
514
|
const hostEntries = moduleHosts.map((record) => ({
|
|
467
515
|
entry: resolvePluginGoModule(record.source, record.label).entry,
|
|
468
516
|
}));
|
|
469
517
|
const observations = NativeSourcePackages.inspect({
|
|
470
|
-
source: host.source,
|
|
518
|
+
source: host.source,
|
|
519
|
+
pluginName: host.label,
|
|
520
|
+
env: effectiveEnv,
|
|
471
521
|
readers: packageReaders,
|
|
472
522
|
packages: [
|
|
473
523
|
...hostEntries,
|
|
474
524
|
...linkedContributors.map((input) => ({
|
|
475
|
-
...input,
|
|
525
|
+
...input,
|
|
526
|
+
entry: `./contrib/${input.name}`,
|
|
476
527
|
})),
|
|
477
528
|
],
|
|
478
529
|
});
|
|
479
530
|
moduleHosts.forEach((record, index) => {
|
|
480
|
-
NativeSourcePackages.kind(
|
|
531
|
+
NativeSourcePackages.kind(
|
|
532
|
+
observations[index]!,
|
|
533
|
+
record.label,
|
|
534
|
+
"executable",
|
|
535
|
+
);
|
|
481
536
|
admittedExecutables.add(record.source);
|
|
482
537
|
});
|
|
483
538
|
linkedContributors.forEach((input, index) => {
|
|
484
|
-
NativeSourcePackages.kind(
|
|
539
|
+
NativeSourcePackages.kind(
|
|
540
|
+
observations[hostEntries.length + index]!,
|
|
541
|
+
`${input.name} inside host ${host.label}`,
|
|
542
|
+
"linked",
|
|
543
|
+
);
|
|
485
544
|
});
|
|
486
545
|
}
|
|
487
546
|
}
|
|
488
547
|
// A nested main proposal still needs its actual owning manifests. Hosts
|
|
489
548
|
// with linked contributors were admitted together above; other equivalent
|
|
490
549
|
// entries share one standalone observation, with no repeated host copy.
|
|
491
|
-
const standaloneCandidates = executableCandidates.filter(
|
|
492
|
-
!admittedExecutables.has(candidate.source)
|
|
550
|
+
const standaloneCandidates = executableCandidates.filter(
|
|
551
|
+
(candidate) => !admittedExecutables.has(candidate.source),
|
|
552
|
+
);
|
|
493
553
|
const executablePackages = NativeSourcePackages.ownPackages(
|
|
494
|
-
standaloneCandidates,
|
|
554
|
+
standaloneCandidates,
|
|
555
|
+
effectiveEnv,
|
|
556
|
+
packageReaders,
|
|
495
557
|
);
|
|
496
558
|
standaloneCandidates.forEach((candidate, index) => {
|
|
497
|
-
NativeSourcePackages.kind(
|
|
559
|
+
NativeSourcePackages.kind(
|
|
560
|
+
executablePackages[index]!,
|
|
561
|
+
candidate.label,
|
|
562
|
+
"executable",
|
|
563
|
+
);
|
|
498
564
|
});
|
|
499
565
|
const packageOwnership = (source: string, label: string, linked: boolean) => [
|
|
500
|
-
{
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
566
|
+
{
|
|
567
|
+
entry: resolvePluginGoModule(source, label).entry,
|
|
568
|
+
kind: "executable" as const,
|
|
569
|
+
},
|
|
570
|
+
...(linked
|
|
571
|
+
? linkedContributors.map((input) => ({
|
|
572
|
+
entry: `./contrib/${input.name}`,
|
|
573
|
+
kind: "linked" as const,
|
|
574
|
+
}))
|
|
575
|
+
: []),
|
|
504
576
|
];
|
|
505
577
|
// One reading of each source directory, shared by every build below and
|
|
506
578
|
// reported as the state the binaries were keyed on.
|
|
@@ -517,6 +589,7 @@ export function loadProjectPlugins(options: {
|
|
|
517
589
|
cacheDir: options.cacheDir,
|
|
518
590
|
contributors: mergeContributors(record.contributors, hostContributors),
|
|
519
591
|
env: effectiveEnv,
|
|
592
|
+
goModReaders: packageReaders,
|
|
520
593
|
pluginName: record.label,
|
|
521
594
|
packageOwnership: packageOwnership(record.source, record.label, true),
|
|
522
595
|
source: record.source,
|
|
@@ -534,11 +607,14 @@ export function loadProjectPlugins(options: {
|
|
|
534
607
|
cacheDir: options.cacheDir,
|
|
535
608
|
contributors: linkedContributors,
|
|
536
609
|
env: effectiveEnv,
|
|
610
|
+
goModReaders: packageReaders,
|
|
537
611
|
label: "linked plugin host",
|
|
538
612
|
pluginName: "linked-plugin-host",
|
|
539
613
|
source: path.join(ttscPackageRoot(), "cmd", "utility-host"),
|
|
540
614
|
packageOwnership: packageOwnership(
|
|
541
|
-
path.join(ttscPackageRoot(), "cmd", "utility-host"),
|
|
615
|
+
path.join(ttscPackageRoot(), "cmd", "utility-host"),
|
|
616
|
+
"linked-plugin-host",
|
|
617
|
+
true,
|
|
542
618
|
),
|
|
543
619
|
environmentDigests,
|
|
544
620
|
sourceDigests,
|
|
@@ -561,8 +637,13 @@ export function loadProjectPlugins(options: {
|
|
|
561
637
|
cacheDir: options.cacheDir,
|
|
562
638
|
contributors: record.contributors,
|
|
563
639
|
env: effectiveEnv,
|
|
640
|
+
goModReaders: packageReaders,
|
|
564
641
|
pluginName: record.label,
|
|
565
|
-
packageOwnership: packageOwnership(
|
|
642
|
+
packageOwnership: packageOwnership(
|
|
643
|
+
record.source,
|
|
644
|
+
record.label,
|
|
645
|
+
false,
|
|
646
|
+
),
|
|
566
647
|
source: record.source,
|
|
567
648
|
environmentDigests,
|
|
568
649
|
sourceDigests,
|
|
@@ -2136,6 +2217,7 @@ function pluginBuildDirectories(
|
|
|
2136
2217
|
}[],
|
|
2137
2218
|
env: NodeJS.ProcessEnv,
|
|
2138
2219
|
replacements?: Map<string, readonly string[]>,
|
|
2220
|
+
readers?: Map<string, SourcePluginWorkspace.GoModReader>,
|
|
2139
2221
|
): string[] {
|
|
2140
2222
|
const directories = new Set<string>();
|
|
2141
2223
|
for (const record of records) {
|
|
@@ -2146,8 +2228,12 @@ function pluginBuildDirectories(
|
|
|
2146
2228
|
);
|
|
2147
2229
|
if (record.kind === "executable") {
|
|
2148
2230
|
for (const directory of pluginReplacementWatchDirectories(
|
|
2149
|
-
record.moduleRoot,
|
|
2150
|
-
|
|
2231
|
+
record.moduleRoot,
|
|
2232
|
+
env,
|
|
2233
|
+
replacements,
|
|
2234
|
+
readers,
|
|
2235
|
+
))
|
|
2236
|
+
directories.add(directory);
|
|
2151
2237
|
}
|
|
2152
2238
|
for (const contributor of record.contributors ?? [])
|
|
2153
2239
|
directories.add(path.resolve(contributor.source));
|
|
@@ -2164,13 +2250,19 @@ function pluginReplacementWatchDirectories(
|
|
|
2164
2250
|
moduleRoot: string,
|
|
2165
2251
|
env: NodeJS.ProcessEnv,
|
|
2166
2252
|
observed?: Map<string, readonly string[]>,
|
|
2253
|
+
readers?: Map<string, SourcePluginWorkspace.GoModReader>,
|
|
2167
2254
|
): readonly string[] {
|
|
2168
2255
|
const previous = observed?.get(moduleRoot);
|
|
2169
2256
|
if (previous !== undefined) return previous;
|
|
2170
2257
|
let directories: readonly string[];
|
|
2171
2258
|
try {
|
|
2172
|
-
directories = pluginModuleReplaceDirectories(
|
|
2173
|
-
|
|
2259
|
+
directories = pluginModuleReplaceDirectories(
|
|
2260
|
+
moduleRoot,
|
|
2261
|
+
env,
|
|
2262
|
+
undefined,
|
|
2263
|
+
undefined,
|
|
2264
|
+
readers,
|
|
2265
|
+
).map((replacement) => replacement.directory);
|
|
2174
2266
|
} catch {
|
|
2175
2267
|
// An unreadable manifest/tool is not valid metadata. The already observed
|
|
2176
2268
|
// module retains its repair path; actual proposal/build refusal propagates.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { GoSourceInputs } from "./GoSourceInputs";
|
|
4
|
+
import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
5
|
+
import { spawnGoTool } from "./spawnGoTool";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Acquire current Go values together with their environment-file evidence.
|
|
9
|
+
* Build digests and syntax readers share this owner; a file-location hint is
|
|
10
|
+
* never a retained positive environment verdict (#1516, #1712).
|
|
11
|
+
*
|
|
12
|
+
* @evidence contracts/common.md#principled-implementation The acquisition pairs actual Go output with native pre-read file evidence; callers retain their own completeness and reuse policy.
|
|
13
|
+
* @evidence contracts/common.md#clear-and-simple-design One namespace owns environment-file discovery and native acquisition for build and manifest clients.
|
|
14
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts No handwritten default GOENV path or process-permanent environment success replaces native Go.
|
|
15
|
+
* @evidence contracts/common.md#meaningful-documentation Prose distinguishes observation from a discovery hint and identifies both consumers.
|
|
16
|
+
* @evidenceExclude contracts/portability.md#os-neutral-implementation The read member owns native observation and execution.
|
|
17
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms The read member owns acquisition and identity costs.
|
|
18
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work The read member owns hint sharing; no result cache is exposed.
|
|
19
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources The read member describes the private hint map and delegated native lifetime.
|
|
20
|
+
*/
|
|
21
|
+
export namespace GoEnvironmentReading {
|
|
22
|
+
/**
|
|
23
|
+
* The Go environment file each Go tool and environment was last seen to name,
|
|
24
|
+
* which lets a reading witness that file before `go env` reads it.
|
|
25
|
+
*/
|
|
26
|
+
const goEnvironmentFiles = new Map<string, string | null>();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Run `go env -json` for the requested keys and `GOENV`, the file `go env -w`
|
|
30
|
+
* writes, witnessing that file before the run reads it.
|
|
31
|
+
*
|
|
32
|
+
* The file decides the reading without being part of it, so only its metadata
|
|
33
|
+
* is kept, and it must be taken before the read: metadata taken after would
|
|
34
|
+
* describe an edit that landed between the two, and a reading of the old
|
|
35
|
+
* content would pass as proven. Which file `go env` reads is known only from
|
|
36
|
+
* its answer, so the file this Go tool and environment named last is
|
|
37
|
+
* witnessed first, and a reading that names another is taken again with that
|
|
38
|
+
* one witnessed. A file that keeps moving between runs leaves the reading
|
|
39
|
+
* unwitnessable, so a kept reading or a build keyed on it never holds.
|
|
40
|
+
*
|
|
41
|
+
* @returns The parsed object, or `undefined` when native acquisition or JSON
|
|
42
|
+
* object decoding failed.
|
|
43
|
+
* @evidence contracts/common.md#principled-implementation Actual Go supplies values and the environment-file path; pre-read metadata and bounded rediscovery preserve the earliest file witness instead of attaching a later state to an earlier read.
|
|
44
|
+
* @evidence contracts/common.md#clear-and-simple-design One acquisition serves build hashing and manifest syntax qualification; callers choose keys and own interpretation and cache admission.
|
|
45
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts A remembered file name is only a discovery hint. Every call executes Go, and unresolved movement marks its witness unproven rather than lending a historical positive verdict.
|
|
46
|
+
* @evidence contracts/common.md#meaningful-documentation Native paragraphs explain why file discovery can repeat and distinguish unavailable acquisition from current authority.
|
|
47
|
+
* @evidence contracts/portability.md#os-neutral-implementation Go reports its actual environment-file location; native path metadata and the existing Go process owner supply observation and execution semantics.
|
|
48
|
+
* @evidence contracts/performance.md#efficient-algorithms Hashing the complete tool/environment identity and up to three native Go queries process full name/value/output bytes. Requested keys contribute argument bytes; file discovery uses metadata rather than an SDK walk.
|
|
49
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Only file-location discovery hints are shared across equal selected-tool/environment identities. Current Go values and file authority are reacquired for every call.
|
|
50
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources The module retains one file-location hint per historical tool/environment identity, without eviction, but no positive reading or native handle. Command capture/retirement belongs to spawnGoTool and the caller owns the transferred witness.
|
|
51
|
+
*/
|
|
52
|
+
export function read(
|
|
53
|
+
goBinary: string,
|
|
54
|
+
cwd: string,
|
|
55
|
+
env: NodeJS.ProcessEnv,
|
|
56
|
+
keys: readonly string[],
|
|
57
|
+
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
58
|
+
): Record<string, unknown> | undefined {
|
|
59
|
+
const memo = crypto.createHash("sha256").update(goBinary);
|
|
60
|
+
for (const [key, value] of Object.entries(env).sort(([left], [right]) =>
|
|
61
|
+
left < right ? -1 : left > right ? 1 : 0,
|
|
62
|
+
)) {
|
|
63
|
+
if (value !== undefined)
|
|
64
|
+
memo.update(`\0${key.length}:${key}${value.length}:${value}`);
|
|
65
|
+
}
|
|
66
|
+
const memoKey = memo.digest("hex");
|
|
67
|
+
let named = goEnvironmentFiles.get(memoKey);
|
|
68
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
69
|
+
if (named !== undefined && named !== null)
|
|
70
|
+
PluginBuildEnvironmentWitness.add(witness, named);
|
|
71
|
+
const result = spawnGoTool(goBinary, ["env", "-json", ...keys, "GOENV"], {
|
|
72
|
+
cwd,
|
|
73
|
+
encoding: "utf8",
|
|
74
|
+
env: GoSourceInputs.goBuildEnv(goBinary, undefined, env),
|
|
75
|
+
windowsHide: true,
|
|
76
|
+
});
|
|
77
|
+
if (result.error !== undefined || result.status !== 0) return undefined;
|
|
78
|
+
let parsed: Record<string, unknown>;
|
|
79
|
+
try {
|
|
80
|
+
const decoded: unknown = JSON.parse(result.stdout);
|
|
81
|
+
if (
|
|
82
|
+
decoded === null ||
|
|
83
|
+
typeof decoded !== "object" ||
|
|
84
|
+
Array.isArray(decoded)
|
|
85
|
+
)
|
|
86
|
+
return undefined;
|
|
87
|
+
parsed = decoded as Record<string, unknown>;
|
|
88
|
+
} catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
const reported =
|
|
92
|
+
typeof parsed.GOENV === "string" &&
|
|
93
|
+
parsed.GOENV !== "" &&
|
|
94
|
+
parsed.GOENV !== "off"
|
|
95
|
+
? parsed.GOENV
|
|
96
|
+
: null;
|
|
97
|
+
if (reported === named) return parsed;
|
|
98
|
+
if (named !== undefined && named !== null) witness?.delete(named);
|
|
99
|
+
named = reported;
|
|
100
|
+
goEnvironmentFiles.set(memoKey, named);
|
|
101
|
+
// No file to witness: the reading depends on none.
|
|
102
|
+
if (named === null) return parsed;
|
|
103
|
+
if (witness === undefined) return parsed;
|
|
104
|
+
}
|
|
105
|
+
if (named !== undefined && named !== null)
|
|
106
|
+
PluginBuildEnvironmentWitness.refuse(witness, named);
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|