@ttsc/unplugin 0.28.5 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -11
- package/lib/api.js +10 -0
- package/lib/api.js.map +1 -1
- package/lib/api.mjs +3 -2
- package/lib/api.mjs.map +1 -1
- package/lib/bun-register.d.cts +10 -7
- package/lib/bun-register.d.mts +10 -7
- package/lib/bun-register.d.ts +10 -7
- package/lib/bun-register.js +90 -24
- package/lib/bun-register.js.map +1 -1
- package/lib/bun-register.mjs +90 -24
- package/lib/bun-register.mjs.map +1 -1
- package/lib/bun.d.cts +10 -8
- package/lib/bun.d.mts +10 -8
- package/lib/bun.d.ts +10 -8
- package/lib/bun.js +19 -17
- package/lib/bun.js.map +1 -1
- package/lib/bun.mjs +20 -18
- package/lib/bun.mjs.map +1 -1
- package/lib/core/index.d.cts +10 -8
- package/lib/core/index.d.mts +10 -8
- package/lib/core/index.d.ts +10 -8
- package/lib/core/index.js +88 -16
- package/lib/core/index.js.map +1 -1
- package/lib/core/index.mjs +81 -18
- package/lib/core/index.mjs.map +1 -1
- package/lib/core/projectDiscovery.d.cts +55 -0
- package/lib/core/projectDiscovery.d.mts +55 -0
- package/lib/core/projectDiscovery.d.ts +55 -0
- package/lib/core/projectDiscovery.js +187 -0
- package/lib/core/projectDiscovery.js.map +1 -0
- package/lib/core/projectDiscovery.mjs +182 -0
- package/lib/core/projectDiscovery.mjs.map +1 -0
- package/lib/core/sourceExtensions.d.cts +10 -0
- package/lib/core/sourceExtensions.d.mts +10 -0
- package/lib/core/sourceExtensions.d.ts +10 -0
- package/lib/core/sourceExtensions.js +38 -0
- package/lib/core/sourceExtensions.js.map +1 -0
- package/lib/core/sourceExtensions.mjs +32 -0
- package/lib/core/sourceExtensions.mjs.map +1 -0
- package/lib/core/transform.d.cts +96 -16
- package/lib/core/transform.d.mts +96 -16
- package/lib/core/transform.d.ts +96 -16
- package/lib/core/transform.js +1295 -309
- package/lib/core/transform.js.map +1 -1
- package/lib/core/transform.mjs +1290 -310
- package/lib/core/transform.mjs.map +1 -1
- package/lib/core/tsconfigPaths.d.cts +55 -3
- package/lib/core/tsconfigPaths.d.mts +55 -3
- package/lib/core/tsconfigPaths.d.ts +55 -3
- package/lib/core/tsconfigPaths.js +260 -46
- package/lib/core/tsconfigPaths.js.map +1 -1
- package/lib/core/tsconfigPaths.mjs +255 -47
- package/lib/core/tsconfigPaths.mjs.map +1 -1
- package/lib/core/viteServe.d.cts +18 -21
- package/lib/core/viteServe.d.mts +18 -21
- package/lib/core/viteServe.d.ts +18 -21
- package/lib/core/viteServe.js +88 -47
- package/lib/core/viteServe.js.map +1 -1
- package/lib/core/viteServe.mjs +89 -49
- package/lib/core/viteServe.mjs.map +1 -1
- package/lib/next.d.cts +7 -0
- package/lib/next.d.mts +7 -0
- package/lib/next.d.ts +7 -0
- package/lib/next.js +186 -54
- package/lib/next.js.map +1 -1
- package/lib/next.mjs +186 -55
- package/lib/next.mjs.map +1 -1
- package/lib/turbopack.d.cts +6 -4
- package/lib/turbopack.d.mts +6 -4
- package/lib/turbopack.d.ts +6 -4
- package/lib/turbopack.js +11 -10
- package/lib/turbopack.js.map +1 -1
- package/lib/turbopack.mjs +11 -10
- package/lib/turbopack.mjs.map +1 -1
- package/package.json +7 -4
- package/src/bun-register.ts +112 -25
- package/src/bun.ts +60 -50
- package/src/core/index.ts +114 -19
- package/src/core/projectDiscovery.ts +256 -0
- package/src/core/sourceExtensions.ts +47 -0
- package/src/core/transform.ts +1671 -361
- package/src/core/tsconfigPaths.ts +406 -46
- package/src/core/viteServe.ts +125 -74
- package/src/next.ts +230 -54
- package/src/turbopack.ts +11 -10
package/lib/core/transform.mjs
CHANGED
|
@@ -4,9 +4,11 @@ import fs from 'node:fs';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { TtscCompiler } from 'ttsc';
|
|
7
|
-
import { createFilesystemPathIdentityContext } from 'ttsc/path-identity';
|
|
8
|
-
import {
|
|
7
|
+
import { createFilesystemPathIdentityContext, resolveFilesystemPath } from 'ttsc/path-identity';
|
|
8
|
+
import { findNearestProjectTsconfig, isIgnoredProjectDirectory } from './projectDiscovery.mjs';
|
|
9
|
+
import { mergeMembershipPolicyOverlay, PERMISSIVE_PROJECT_MEMBERSHIP_POLICY, readProjectMembershipPolicy, readEffectiveTsconfigPaths, readEffectiveTsconfigTemplateCompilerOptions, readEffectiveTsconfigTemplateFileSpecs, readTsconfigSourceSnapshot, CONFIG_DIR_TEMPLATE_SCALAR_OPTIONS, resolveConfigDirTemplatePath, CONFIG_DIR_TEMPLATE_LIST_OPTIONS, absolutizePathsTarget } from './tsconfigPaths.mjs';
|
|
9
10
|
|
|
11
|
+
const TTSC_SEMANTIC_CONFIG_PATH = "TTSC_SEMANTIC_CONFIG_PATH";
|
|
10
12
|
/**
|
|
11
13
|
* A verdict about one generation that later deliveries replay instead of
|
|
12
14
|
* repeating the whole compile behind it.
|
|
@@ -108,6 +110,8 @@ class TtscPassVerdictError extends TtscTerminalGenerationError {
|
|
|
108
110
|
const TRANSFORM_GENERATION_FAILURES = new WeakMap();
|
|
109
111
|
/** Retry baselines retained only for attempts that could not be published. */
|
|
110
112
|
const TRANSFORM_FAILED_GENERATION_VALIDATIONS = new WeakMap();
|
|
113
|
+
/** Internal retained clock-probe ownership for published generations. */
|
|
114
|
+
const TRANSFORM_CLOCK_REFERENCE_DIRECTORIES = new WeakMap();
|
|
111
115
|
/** Cache promises whose unchanged terminal verdict may be replayed. */
|
|
112
116
|
const TERMINAL_TRANSFORM_GENERATIONS = new WeakMap();
|
|
113
117
|
/** Maximum witnesses printed and retained for each failed transform attempt. */
|
|
@@ -243,8 +247,7 @@ function clearTtscTransformCache(cache) {
|
|
|
243
247
|
* @param id - Bundler module id (may carry a query string or virtual prefix).
|
|
244
248
|
* @param source - Current file content supplied by the bundler.
|
|
245
249
|
* @param options - Resolved plugin options.
|
|
246
|
-
* @param aliases - Raw
|
|
247
|
-
* object).
|
|
250
|
+
* @param aliases - Raw Vite alias configuration (object or array).
|
|
248
251
|
* @param cache - Optional project cache. Callers with a real `buildStart`
|
|
249
252
|
* boundary declare it through {@link beginTtscTransformBuild}; other hosts
|
|
250
253
|
* retain persistent validation.
|
|
@@ -344,9 +347,11 @@ async function transformTtsc(id, source, options, aliases, cache, hooks) {
|
|
|
344
347
|
}
|
|
345
348
|
// The compile is fine and simply has nothing for this module, so the
|
|
346
349
|
// module goes back to the host untransformed rather than failing the
|
|
347
|
-
// build (samchon/ttsc#1308).
|
|
348
|
-
//
|
|
350
|
+
// build (samchon/ttsc#1308). Its project config and selection inputs
|
|
351
|
+
// still decide whether a later generation will contain this module,
|
|
352
|
+
// so hosts must receive the same universal watch-input batch.
|
|
349
353
|
reportMissingProgramOutput(cached, error, epoch);
|
|
354
|
+
notifyWatchInputs(hooks, cached, file);
|
|
350
355
|
markCachedSourceServed(cached, file);
|
|
351
356
|
return undefined;
|
|
352
357
|
}
|
|
@@ -402,6 +407,7 @@ async function transformTtsc(id, source, options, aliases, cache, hooks) {
|
|
|
402
407
|
throw error;
|
|
403
408
|
}
|
|
404
409
|
reportMissingProgramOutput(cached, error, epoch);
|
|
410
|
+
notifyWatchInputs(hooks, cached, file);
|
|
405
411
|
markCachedSourceServed(cached, file);
|
|
406
412
|
return undefined;
|
|
407
413
|
}
|
|
@@ -553,7 +559,7 @@ function evictGeneration(cache, key, generation) {
|
|
|
553
559
|
void generation.then(disposeCachedTransform, () => undefined);
|
|
554
560
|
}
|
|
555
561
|
}
|
|
556
|
-
/**
|
|
562
|
+
/** Release one generation's watchers and retained clock probe exactly once. */
|
|
557
563
|
function disposeCachedTransform(cached) {
|
|
558
564
|
const trackers = [
|
|
559
565
|
cached.projectMutationTracker,
|
|
@@ -563,8 +569,21 @@ function disposeCachedTransform(cached) {
|
|
|
563
569
|
cached.projectMutationTracker = undefined;
|
|
564
570
|
cached.hostInputMutationTracker = undefined;
|
|
565
571
|
cached.candidateMutationTracker = undefined;
|
|
566
|
-
|
|
567
|
-
|
|
572
|
+
const clockReferenceDirectory = TRANSFORM_CLOCK_REFERENCE_DIRECTORIES.get(cached);
|
|
573
|
+
TRANSFORM_CLOCK_REFERENCE_DIRECTORIES.delete(cached);
|
|
574
|
+
for (const tracker of trackers) {
|
|
575
|
+
try {
|
|
576
|
+
tracker?.close();
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
// Disposal is scheduled behind fulfilled generation Promises, so it has
|
|
580
|
+
// no caller that can recover from a close failure. Keep releasing every
|
|
581
|
+
// independent resource and leave no rejected cleanup Promise behind.
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (clockReferenceDirectory !== undefined) {
|
|
585
|
+
disposeFilesystemClockReference(clockReferenceDirectory);
|
|
586
|
+
}
|
|
568
587
|
}
|
|
569
588
|
/**
|
|
570
589
|
* Derivation states keyed by the compiler result object. One result object is
|
|
@@ -595,17 +614,22 @@ function envelopeGraphIndexes(state, props) {
|
|
|
595
614
|
if (state.graph !== undefined) {
|
|
596
615
|
return state.graph;
|
|
597
616
|
}
|
|
617
|
+
const platform = resultFilesystem(props.result).platform ?? process.platform;
|
|
618
|
+
const pathApi = platform === "win32" ? path.win32 : path.posix;
|
|
598
619
|
const built = {
|
|
599
620
|
edges: new Map(),
|
|
600
621
|
spellings: new Map(),
|
|
601
622
|
candidates: [],
|
|
602
623
|
globals: [],
|
|
603
624
|
configs: [],
|
|
604
|
-
|
|
625
|
+
resolutionInputs: [],
|
|
626
|
+
memberSpellings: new Set(),
|
|
605
627
|
speculative: new Set(),
|
|
606
628
|
inputProofs: new Map(),
|
|
607
629
|
inputProofFailures: new Map(),
|
|
608
630
|
inputProofConflicts: new Set(),
|
|
631
|
+
inputObservations: new Map(),
|
|
632
|
+
inputObservationConflicts: new Set(),
|
|
609
633
|
};
|
|
610
634
|
const graph = props.result.type === "exception" ? undefined : props.result.graph;
|
|
611
635
|
if (graph !== undefined) {
|
|
@@ -615,7 +639,7 @@ function envelopeGraphIndexes(state, props) {
|
|
|
615
639
|
}
|
|
616
640
|
const absolute = path.resolve(props.projectRoot, source);
|
|
617
641
|
const identity = derivationIdentity(state, absolute);
|
|
618
|
-
built.
|
|
642
|
+
built.memberSpellings.add(absolute);
|
|
619
643
|
built.spellings.set(identity, absolute);
|
|
620
644
|
const entries = built.edges.get(identity) ?? [];
|
|
621
645
|
entries.push(...targets
|
|
@@ -623,7 +647,7 @@ function envelopeGraphIndexes(state, props) {
|
|
|
623
647
|
.map((target) => {
|
|
624
648
|
const absoluteTarget = path.resolve(props.projectRoot, target);
|
|
625
649
|
const targetIdentity = derivationIdentity(state, absoluteTarget);
|
|
626
|
-
built.
|
|
650
|
+
built.memberSpellings.add(absoluteTarget);
|
|
627
651
|
if (!built.spellings.has(targetIdentity)) {
|
|
628
652
|
built.spellings.set(targetIdentity, absoluteTarget);
|
|
629
653
|
}
|
|
@@ -635,7 +659,7 @@ function envelopeGraphIndexes(state, props) {
|
|
|
635
659
|
built.configs.push(...selectListedFiles(props.projectRoot, graph.configs));
|
|
636
660
|
for (const input of [...built.globals, ...built.configs]) {
|
|
637
661
|
const identity = derivationIdentity(state, input);
|
|
638
|
-
built.
|
|
662
|
+
built.memberSpellings.add(path.resolve(input));
|
|
639
663
|
if (!built.spellings.has(identity))
|
|
640
664
|
built.spellings.set(identity, input);
|
|
641
665
|
}
|
|
@@ -647,12 +671,22 @@ function envelopeGraphIndexes(state, props) {
|
|
|
647
671
|
for (const [source] of candidateEntries) {
|
|
648
672
|
const absoluteSource = path.resolve(props.projectRoot, source);
|
|
649
673
|
const identity = derivationIdentity(state, absoluteSource);
|
|
650
|
-
built.
|
|
674
|
+
built.memberSpellings.add(absoluteSource);
|
|
651
675
|
if (!built.spellings.has(identity)) {
|
|
652
676
|
built.spellings.set(identity, absoluteSource);
|
|
653
677
|
}
|
|
654
678
|
}
|
|
655
|
-
const realized = new Set(built.
|
|
679
|
+
const realized = new Set(built.memberSpellings);
|
|
680
|
+
built.resolutionInputs.push(...selectListedFiles(props.projectRoot, graph.resolutionInputs));
|
|
681
|
+
for (const input of built.resolutionInputs) {
|
|
682
|
+
const spelling = path.resolve(input);
|
|
683
|
+
const identity = derivationIdentity(state, input);
|
|
684
|
+
if (!realized.has(spelling))
|
|
685
|
+
built.speculative.add(spelling);
|
|
686
|
+
built.memberSpellings.add(spelling);
|
|
687
|
+
if (!built.spellings.has(identity))
|
|
688
|
+
built.spellings.set(identity, input);
|
|
689
|
+
}
|
|
656
690
|
for (const [source, candidates] of candidateEntries) {
|
|
657
691
|
built.candidates.push({
|
|
658
692
|
source: derivationIdentity(state, path.resolve(props.projectRoot, source)),
|
|
@@ -661,26 +695,61 @@ function envelopeGraphIndexes(state, props) {
|
|
|
661
695
|
for (const candidate of candidates) {
|
|
662
696
|
if (typeof candidate !== "string" || candidate.length === 0)
|
|
663
697
|
continue;
|
|
664
|
-
const
|
|
698
|
+
const absoluteCandidate = path.resolve(props.projectRoot, candidate);
|
|
699
|
+
const identity = derivationIdentity(state, absoluteCandidate);
|
|
665
700
|
// Edges, globals, configs, and every candidate source are folded in
|
|
666
701
|
// above, so a path absent from that set is one the envelope reported
|
|
667
702
|
// only as a candidate.
|
|
668
|
-
if (!realized.has(
|
|
669
|
-
built.speculative.add(
|
|
670
|
-
|
|
703
|
+
if (!realized.has(absoluteCandidate)) {
|
|
704
|
+
built.speculative.add(absoluteCandidate);
|
|
705
|
+
}
|
|
706
|
+
built.memberSpellings.add(absoluteCandidate);
|
|
671
707
|
if (!built.spellings.has(identity)) {
|
|
672
|
-
built.spellings.set(identity,
|
|
708
|
+
built.spellings.set(identity, absoluteCandidate);
|
|
673
709
|
}
|
|
674
710
|
}
|
|
675
711
|
}
|
|
676
|
-
const
|
|
712
|
+
const transformSourceSpellings = new Set();
|
|
677
713
|
if (props.result.type === "success") {
|
|
678
714
|
for (const output of Object.keys(props.result.typescript)) {
|
|
679
715
|
if (!isDeclarationFile(output)) {
|
|
680
|
-
|
|
716
|
+
const absoluteOutput = path.resolve(props.projectRoot, output);
|
|
717
|
+
transformSourceSpellings.add(absoluteOutput);
|
|
681
718
|
}
|
|
682
719
|
}
|
|
683
720
|
}
|
|
721
|
+
for (const [input, reported] of Object.entries(graph.inputObservations ?? {})) {
|
|
722
|
+
if (input.length === 0)
|
|
723
|
+
continue;
|
|
724
|
+
const absolute = path.resolve(props.projectRoot, input);
|
|
725
|
+
const spelling = path.resolve(absolute);
|
|
726
|
+
if (!built.memberSpellings.has(spelling) &&
|
|
727
|
+
!transformSourceSpellings.has(spelling)) {
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
const normalized = normalizeGraphInputObservation(reported, platform);
|
|
731
|
+
if (normalized === undefined) {
|
|
732
|
+
built.inputObservationConflicts.add(spelling);
|
|
733
|
+
if (!built.inputProofFailures.has(spelling)) {
|
|
734
|
+
built.inputProofFailures.set(spelling, "malformed-observation");
|
|
735
|
+
}
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const previous = built.inputObservations.get(spelling);
|
|
739
|
+
const merged = previous === undefined
|
|
740
|
+
? normalized
|
|
741
|
+
: mergeGraphInputObservations(previous, normalized);
|
|
742
|
+
if (merged === undefined) {
|
|
743
|
+
built.inputObservations.delete(spelling);
|
|
744
|
+
built.inputObservationConflicts.add(spelling);
|
|
745
|
+
if (!built.inputProofFailures.has(spelling)) {
|
|
746
|
+
built.inputProofFailures.set(spelling, "conflicting-observation");
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
else if (!built.inputObservationConflicts.has(spelling)) {
|
|
750
|
+
built.inputObservations.set(spelling, merged);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
684
753
|
for (const [input, hash] of Object.entries(graph.inputHashes ?? {})) {
|
|
685
754
|
if (hash !== null &&
|
|
686
755
|
(typeof hash !== "string" || !/^[0-9a-f]{64}$/.test(hash))) {
|
|
@@ -693,28 +762,31 @@ function envelopeGraphIndexes(state, props) {
|
|
|
693
762
|
const reportedRealpath = graph.inputRealpaths[input];
|
|
694
763
|
if (reportedRealpath !== null &&
|
|
695
764
|
(typeof reportedRealpath !== "string" ||
|
|
696
|
-
!
|
|
765
|
+
!pathApi.isAbsolute(reportedRealpath))) {
|
|
697
766
|
continue;
|
|
698
767
|
}
|
|
699
768
|
const absolute = path.resolve(props.projectRoot, input);
|
|
700
|
-
const
|
|
701
|
-
if (!built.
|
|
769
|
+
const spelling = path.resolve(absolute);
|
|
770
|
+
if (!built.memberSpellings.has(spelling) &&
|
|
771
|
+
!transformSourceSpellings.has(spelling)) {
|
|
702
772
|
continue;
|
|
703
773
|
}
|
|
704
774
|
const proof = {
|
|
705
775
|
hash,
|
|
706
776
|
path: absolute,
|
|
707
|
-
realpath: reportedRealpath === null
|
|
777
|
+
realpath: reportedRealpath === null
|
|
778
|
+
? null
|
|
779
|
+
: resolveFilesystemPath(reportedRealpath, platform),
|
|
708
780
|
};
|
|
709
|
-
const previous = built.inputProofs.get(
|
|
781
|
+
const previous = built.inputProofs.get(spelling);
|
|
710
782
|
if (previous !== undefined &&
|
|
711
783
|
(previous.hash !== proof.hash ||
|
|
712
784
|
!sameHostInputRealpath(previous.realpath, proof.realpath, state.identityContext))) {
|
|
713
|
-
built.inputProofs.delete(
|
|
714
|
-
built.inputProofConflicts.add(
|
|
785
|
+
built.inputProofs.delete(spelling);
|
|
786
|
+
built.inputProofConflicts.add(spelling);
|
|
715
787
|
}
|
|
716
|
-
else if (!built.inputProofConflicts.has(
|
|
717
|
-
built.inputProofs.set(
|
|
788
|
+
else if (!built.inputProofConflicts.has(spelling)) {
|
|
789
|
+
built.inputProofs.set(spelling, proof);
|
|
718
790
|
}
|
|
719
791
|
}
|
|
720
792
|
for (const [input, reason] of Object.entries(graph.inputProofFailures ?? {})) {
|
|
@@ -722,22 +794,213 @@ function envelopeGraphIndexes(state, props) {
|
|
|
722
794
|
continue;
|
|
723
795
|
}
|
|
724
796
|
const absolute = path.resolve(props.projectRoot, input);
|
|
725
|
-
const
|
|
726
|
-
if (!built.
|
|
797
|
+
const spelling = path.resolve(absolute);
|
|
798
|
+
if (!built.memberSpellings.has(spelling) &&
|
|
799
|
+
!transformSourceSpellings.has(spelling)) {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
const observation = built.inputObservations.get(spelling);
|
|
803
|
+
const legacyProjection = observation === undefined
|
|
804
|
+
? undefined
|
|
805
|
+
: legacyProjectionOfGraphInputObservation(observation);
|
|
806
|
+
if (built.speculative.has(spelling) &&
|
|
807
|
+
!built.inputProofs.has(spelling) &&
|
|
808
|
+
legacyProjection?.failure === reason) {
|
|
727
809
|
continue;
|
|
728
810
|
}
|
|
729
|
-
if (built.
|
|
730
|
-
built.
|
|
731
|
-
built.
|
|
811
|
+
if (built.inputObservations.has(spelling)) {
|
|
812
|
+
built.inputObservations.delete(spelling);
|
|
813
|
+
built.inputObservationConflicts.add(spelling);
|
|
732
814
|
}
|
|
733
|
-
if (
|
|
734
|
-
built.
|
|
815
|
+
if (built.inputProofs.has(spelling)) {
|
|
816
|
+
built.inputProofs.delete(spelling);
|
|
817
|
+
built.inputProofConflicts.add(spelling);
|
|
818
|
+
}
|
|
819
|
+
if (!built.inputProofFailures.has(spelling)) {
|
|
820
|
+
built.inputProofFailures.set(spelling, reason);
|
|
735
821
|
}
|
|
736
822
|
}
|
|
737
823
|
}
|
|
738
824
|
state.graph = built;
|
|
739
825
|
return built;
|
|
740
826
|
}
|
|
827
|
+
/** Normalize one untrusted predicate proof without filling missing predicates. */
|
|
828
|
+
function normalizeGraphInputObservation(value, platform = process.platform) {
|
|
829
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
830
|
+
return undefined;
|
|
831
|
+
}
|
|
832
|
+
const entry = value;
|
|
833
|
+
const observation = {};
|
|
834
|
+
if (Object.prototype.hasOwnProperty.call(entry, "accessibleEntries")) {
|
|
835
|
+
const accessible = entry.accessibleEntries;
|
|
836
|
+
if (typeof accessible !== "object" ||
|
|
837
|
+
accessible === null ||
|
|
838
|
+
Array.isArray(accessible)) {
|
|
839
|
+
return undefined;
|
|
840
|
+
}
|
|
841
|
+
const lists = accessible;
|
|
842
|
+
if (!Array.isArray(lists.directories) ||
|
|
843
|
+
!lists.directories.every((name) => typeof name === "string") ||
|
|
844
|
+
!Array.isArray(lists.files) ||
|
|
845
|
+
!lists.files.every((name) => typeof name === "string")) {
|
|
846
|
+
return undefined;
|
|
847
|
+
}
|
|
848
|
+
observation.accessibleEntries = {
|
|
849
|
+
directories: [...lists.directories],
|
|
850
|
+
files: [...lists.files],
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
if (Object.prototype.hasOwnProperty.call(entry, "fileExists")) {
|
|
854
|
+
if (typeof entry.fileExists !== "boolean")
|
|
855
|
+
return undefined;
|
|
856
|
+
observation.fileExists = entry.fileExists;
|
|
857
|
+
}
|
|
858
|
+
if (Object.prototype.hasOwnProperty.call(entry, "directoryExists")) {
|
|
859
|
+
if (typeof entry.directoryExists !== "boolean")
|
|
860
|
+
return undefined;
|
|
861
|
+
observation.directoryExists = entry.directoryExists;
|
|
862
|
+
}
|
|
863
|
+
if (Object.prototype.hasOwnProperty.call(entry, "stat")) {
|
|
864
|
+
if (entry.stat !== "missing" &&
|
|
865
|
+
entry.stat !== "file" &&
|
|
866
|
+
entry.stat !== "directory") {
|
|
867
|
+
return undefined;
|
|
868
|
+
}
|
|
869
|
+
observation.stat = entry.stat;
|
|
870
|
+
}
|
|
871
|
+
if (Object.prototype.hasOwnProperty.call(entry, "readFile")) {
|
|
872
|
+
const read = entry.readFile;
|
|
873
|
+
if (typeof read !== "object" || read === null || Array.isArray(read)) {
|
|
874
|
+
return undefined;
|
|
875
|
+
}
|
|
876
|
+
const result = read;
|
|
877
|
+
if (result.ok === false && result.hash === undefined) {
|
|
878
|
+
observation.readFile = { ok: false };
|
|
879
|
+
}
|
|
880
|
+
else if (result.ok === true &&
|
|
881
|
+
typeof result.hash === "string" &&
|
|
882
|
+
/^[0-9a-f]{64}$/.test(result.hash)) {
|
|
883
|
+
observation.readFile = { hash: result.hash, ok: true };
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
return undefined;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (Object.prototype.hasOwnProperty.call(entry, "realpath")) {
|
|
890
|
+
const realpath = entry.realpath;
|
|
891
|
+
if (typeof realpath !== "object" ||
|
|
892
|
+
realpath === null ||
|
|
893
|
+
Array.isArray(realpath)) {
|
|
894
|
+
return undefined;
|
|
895
|
+
}
|
|
896
|
+
const result = realpath;
|
|
897
|
+
if (result.ok === false && result.path === undefined) {
|
|
898
|
+
observation.realpath = { ok: false };
|
|
899
|
+
}
|
|
900
|
+
else if (result.ok === true &&
|
|
901
|
+
typeof result.path === "string" &&
|
|
902
|
+
(platform === "win32" ? path.win32 : path.posix).isAbsolute(result.path)) {
|
|
903
|
+
observation.realpath = {
|
|
904
|
+
ok: true,
|
|
905
|
+
path: resolveFilesystemPath(result.path, platform),
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
return undefined;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return Object.keys(observation).length !== 0 &&
|
|
913
|
+
graphInputObservationCompatible(observation)
|
|
914
|
+
? observation
|
|
915
|
+
: undefined;
|
|
916
|
+
}
|
|
917
|
+
/** Join duplicate lexical keys only when every repeated predicate agrees. */
|
|
918
|
+
function mergeGraphInputObservations(left, right) {
|
|
919
|
+
for (const property of [
|
|
920
|
+
"accessibleEntries",
|
|
921
|
+
"directoryExists",
|
|
922
|
+
"fileExists",
|
|
923
|
+
"readFile",
|
|
924
|
+
"realpath",
|
|
925
|
+
"stat",
|
|
926
|
+
]) {
|
|
927
|
+
if (left[property] !== undefined &&
|
|
928
|
+
right[property] !== undefined &&
|
|
929
|
+
JSON.stringify(left[property]) !== JSON.stringify(right[property])) {
|
|
930
|
+
return undefined;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
const merged = { ...left, ...right };
|
|
934
|
+
return graphInputObservationCompatible(merged) ? merged : undefined;
|
|
935
|
+
}
|
|
936
|
+
/** Whether one predicate set can describe a single stable filesystem state. */
|
|
937
|
+
function graphInputObservationCompatible(observation) {
|
|
938
|
+
const { accessibleEntries, directoryExists, fileExists, readFile, stat } = observation;
|
|
939
|
+
const hasAccessibleEntries = accessibleEntries !== undefined &&
|
|
940
|
+
(accessibleEntries.directories.length !== 0 ||
|
|
941
|
+
accessibleEntries.files.length !== 0);
|
|
942
|
+
if (hasAccessibleEntries &&
|
|
943
|
+
(fileExists === true ||
|
|
944
|
+
directoryExists === false ||
|
|
945
|
+
(stat !== undefined && stat !== "directory") ||
|
|
946
|
+
readFile?.ok === true)) {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
if (fileExists === true && directoryExists === true)
|
|
950
|
+
return false;
|
|
951
|
+
if (stat === "directory" &&
|
|
952
|
+
(fileExists === true || directoryExists === false)) {
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
if (stat === "file" && (fileExists === false || directoryExists === true)) {
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
if (stat === "missing" && (fileExists === true || directoryExists === true)) {
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
if (readFile?.ok === true &&
|
|
962
|
+
(fileExists === false ||
|
|
963
|
+
directoryExists === true ||
|
|
964
|
+
(stat !== undefined && stat !== "file"))) {
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
return true;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Reconstruct a legacy hash/realpath pair only when the rich predicates prove
|
|
971
|
+
* that projection without collapsing an unreadable file or failed file probe
|
|
972
|
+
* into generic absence.
|
|
973
|
+
*/
|
|
974
|
+
function legacyProjectionOfGraphInputObservation(observation) {
|
|
975
|
+
if (observation.readFile?.ok === true) {
|
|
976
|
+
return observation.realpath?.ok === true
|
|
977
|
+
? {
|
|
978
|
+
hash: observation.readFile.hash,
|
|
979
|
+
realpath: observation.realpath.path,
|
|
980
|
+
}
|
|
981
|
+
: { failure: "realpath-unavailable" };
|
|
982
|
+
}
|
|
983
|
+
const directory = observation.stat === "directory" || observation.directoryExists === true;
|
|
984
|
+
if (directory) {
|
|
985
|
+
return observation.realpath?.ok === true
|
|
986
|
+
? {
|
|
987
|
+
hash: hashText("ttsc:host-input:directory\0"),
|
|
988
|
+
realpath: observation.realpath.path,
|
|
989
|
+
}
|
|
990
|
+
: { failure: "realpath-unavailable" };
|
|
991
|
+
}
|
|
992
|
+
const file = observation.stat === "file" || observation.fileExists === true;
|
|
993
|
+
if (file) {
|
|
994
|
+
return { failure: "content-unavailable" };
|
|
995
|
+
}
|
|
996
|
+
const missing = observation.stat === "missing" ||
|
|
997
|
+
observation.fileExists === false ||
|
|
998
|
+
observation.directoryExists === false ||
|
|
999
|
+
observation.readFile?.ok === false;
|
|
1000
|
+
return missing
|
|
1001
|
+
? { hash: null, realpath: null }
|
|
1002
|
+
: { failure: "unsupported-input-kind" };
|
|
1003
|
+
}
|
|
741
1004
|
/**
|
|
742
1005
|
* {@link pathIdentityKey} memoized inside one envelope's derivation state.
|
|
743
1006
|
* Callers always pass already-resolved absolute paths, so the input string is a
|
|
@@ -772,21 +1035,8 @@ function collectDeclaredIdentities(state, projectRoot, listed) {
|
|
|
772
1035
|
return output;
|
|
773
1036
|
}
|
|
774
1037
|
/**
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
* host-owned reference graph's contribution (`reach(edges, file)`, `globals`,
|
|
778
|
-
* `configs`, and resolution candidates).
|
|
779
|
-
*
|
|
780
|
-
* Envelope keys mirror the `typescript` keys (project-relative); values may be
|
|
781
|
-
* project-relative or absolute. Every path is absolutized against the project
|
|
782
|
-
* root and deduplicated; the file itself is dropped (the bundler already
|
|
783
|
-
* watches the module it transforms), and so is every path in the disposed
|
|
784
|
-
* transform scratch tree (see
|
|
785
|
-
* {@link TtscCachedProjectTransform.scratchDirectory}).
|
|
786
|
-
*/
|
|
787
|
-
/**
|
|
788
|
-
* Register the failed generation's own project inputs so the host can observe
|
|
789
|
-
* the fix.
|
|
1038
|
+
* Register the failed generation's project and external inputs so the host can
|
|
1039
|
+
* observe the fix.
|
|
790
1040
|
*
|
|
791
1041
|
* A successful delivery registers the derived watch inputs, which is how a
|
|
792
1042
|
* type-only file that no bundler graph contains still invalidates the modules
|
|
@@ -797,67 +1047,168 @@ function collectDeclaredIdentities(state, projectRoot, listed) {
|
|
|
797
1047
|
* arrive: the user repairs a file the bundler does not track, nothing is
|
|
798
1048
|
* invalidated, and the error stays on screen (samchon/ttsc#1312).
|
|
799
1049
|
*
|
|
800
|
-
*
|
|
801
|
-
*
|
|
802
|
-
*
|
|
803
|
-
*
|
|
1050
|
+
* A failure envelope can retain exact external input spellings from its graph
|
|
1051
|
+
* and host metadata. A pre-transform typecheck failure may have no graph yet;
|
|
1052
|
+
* its structured diagnostics, or the host's standard diagnostic lines when it
|
|
1053
|
+
* could return only an exception, still name the external files that need a
|
|
1054
|
+
* repair. The cost is paid only on a failure, and only until the next compile
|
|
1055
|
+
* succeeds and narrows the set back to the derived inputs.
|
|
804
1056
|
*/
|
|
805
1057
|
function notifyFailedGenerationInputs(hooks, cached) {
|
|
806
1058
|
const addWatchFile = hooks?.addWatchFile;
|
|
807
|
-
|
|
1059
|
+
const addWatchFiles = hooks?.addWatchFiles;
|
|
1060
|
+
if (addWatchFile === undefined && addWatchFiles === undefined) {
|
|
808
1061
|
return;
|
|
809
1062
|
}
|
|
1063
|
+
const inputs = [];
|
|
1064
|
+
const seen = new Set();
|
|
1065
|
+
const append = (input) => {
|
|
1066
|
+
const spelling = path.resolve(input);
|
|
1067
|
+
if (seen.has(spelling) ||
|
|
1068
|
+
isTransformScratchInput(spelling, cached.scratchDirectory)) {
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
seen.add(spelling);
|
|
1072
|
+
// No evidence, deliberately. A failed generation is replayed for the rest
|
|
1073
|
+
// of its pass without re-proving its inputs, so the adapter must observe
|
|
1074
|
+
// the current availability itself.
|
|
1075
|
+
inputs.push({ file: spelling });
|
|
1076
|
+
};
|
|
810
1077
|
for (const key of Object.keys(cached.inputHashes)) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1078
|
+
append(path.resolve(cached.projectRoot, key));
|
|
1079
|
+
}
|
|
1080
|
+
// The project walk deliberately excludes node_modules and cannot reach
|
|
1081
|
+
// sibling-project or out-of-root inputs. The generation already retained
|
|
1082
|
+
// their exact lexical spellings, so keep that ownership on the failure path
|
|
1083
|
+
// instead of deriving a narrower second answer.
|
|
1084
|
+
for (const input of cached.externalInputPaths ?? []) {
|
|
1085
|
+
append(input);
|
|
1086
|
+
}
|
|
1087
|
+
if (cached.result.type === "failure") {
|
|
1088
|
+
for (const diagnostic of cached.result.diagnostics) {
|
|
1089
|
+
if (typeof diagnostic.file === "string" && diagnostic.file.length !== 0) {
|
|
1090
|
+
append(path.resolve(cached.projectRoot, diagnostic.file));
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
else if (cached.result.type === "exception") {
|
|
1095
|
+
for (const diagnostic of selectExceptionDiagnosticFiles(cached.result.error)) {
|
|
1096
|
+
append(path.resolve(cached.projectRoot, diagnostic));
|
|
814
1097
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
// watch on a path that does not exist registers nothing, and no module
|
|
823
|
-
// graph carries a type-only input. It costs one `existsSync` per input,
|
|
824
|
-
// and only where the adapter reads evidence at all, which is Vite serve.
|
|
825
|
-
addWatchFile(input);
|
|
1098
|
+
}
|
|
1099
|
+
if (addWatchFiles !== undefined) {
|
|
1100
|
+
addWatchFiles(inputs);
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
for (const input of inputs) {
|
|
1104
|
+
addWatchFile(input.file);
|
|
826
1105
|
}
|
|
827
1106
|
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Extract file spellings only from the two standard TypeScript diagnostic
|
|
1109
|
+
* forms.
|
|
1110
|
+
*/
|
|
1111
|
+
function selectExceptionDiagnosticFiles(error) {
|
|
1112
|
+
const files = [];
|
|
1113
|
+
for (const line of formatUnknownError(error).split(/\r?\n/)) {
|
|
1114
|
+
const colon = /^(.+):\d+:\d+\s+-\s+(?:error|warning|suggestion|message)\s+TS\d+:\s+.+$/i.exec(line);
|
|
1115
|
+
const parenthesized = /^(.+?)\(\d+,\d+\):\s+(?:error|warning|suggestion|message)\s+TS\d+:\s+.+$/i.exec(line);
|
|
1116
|
+
const file = colon?.[1] ?? parenthesized?.[1];
|
|
1117
|
+
if (file !== undefined && file.trim().length !== 0) {
|
|
1118
|
+
files.push(file.trim());
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return files;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Forward every derived watch input for `file` to the adapter's `addWatchFile`
|
|
1125
|
+
* hook: the plugin-reported `dependencies[file]` list unioned with the
|
|
1126
|
+
* host-owned reference graph's contribution (`reach(edges, file)`, `globals`,
|
|
1127
|
+
* `configs`, and resolution candidates).
|
|
1128
|
+
*
|
|
1129
|
+
* Envelope keys mirror the `typescript` keys (project-relative); values may be
|
|
1130
|
+
* project-relative or absolute. Every path is absolutized against the project
|
|
1131
|
+
* root and deduplicated; the file itself is dropped (the bundler already
|
|
1132
|
+
* watches the module it transforms), and so is every path in the disposed
|
|
1133
|
+
* transform scratch tree (see
|
|
1134
|
+
* {@link TtscCachedProjectTransform.scratchDirectory}).
|
|
1135
|
+
*/
|
|
828
1136
|
function notifyWatchInputs(hooks, cached, file) {
|
|
829
1137
|
const addWatchFile = hooks?.addWatchFile;
|
|
830
|
-
|
|
1138
|
+
const addWatchFiles = hooks?.addWatchFiles;
|
|
1139
|
+
if (addWatchFile === undefined && addWatchFiles === undefined) {
|
|
831
1140
|
return;
|
|
832
1141
|
}
|
|
833
1142
|
const state = envelopeDerivation(cached);
|
|
834
1143
|
const external = cached.externalInputHashes ?? {};
|
|
835
|
-
|
|
1144
|
+
const inputs = selectWatchInputs({
|
|
836
1145
|
file,
|
|
837
1146
|
projectRoot: cached.projectRoot,
|
|
838
1147
|
result: cached.result,
|
|
839
1148
|
scratchDirectory: cached.scratchDirectory,
|
|
840
1149
|
temporaryTsconfig: cached.temporaryTsconfig,
|
|
841
|
-
})) {
|
|
1150
|
+
}).map((input) => {
|
|
842
1151
|
// Hand the adapter the identity this generation already resolved and the
|
|
843
|
-
//
|
|
844
|
-
//
|
|
845
|
-
//
|
|
846
|
-
// (samchon/ttsc#1246).
|
|
1152
|
+
// exact state it already recorded. Both are memoized per generation, while
|
|
1153
|
+
// an adapter deriving them itself pays repeated filesystem reads and can
|
|
1154
|
+
// accidentally attach a later state to an earlier transform.
|
|
847
1155
|
const identity = derivationIdentity(state, input);
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1156
|
+
const spelling = path.resolve(input);
|
|
1157
|
+
const observation = cached.externalInputObservations?.[spelling];
|
|
1158
|
+
const missing = observation?.fileExists === false ||
|
|
1159
|
+
state.graph?.inputProofs.get(spelling)?.hash === null ||
|
|
1160
|
+
external[identity] === MISSING_INPUT_STATE;
|
|
1161
|
+
const projectKey = toProjectKey(cached.projectRoot, input, state.identityContext);
|
|
1162
|
+
const externalHash = Object.prototype.hasOwnProperty.call(external, identity)
|
|
1163
|
+
? external[identity]
|
|
1164
|
+
: undefined;
|
|
1165
|
+
const projectHash = Object.prototype.hasOwnProperty.call(cached.inputHashes, projectKey)
|
|
1166
|
+
? cached.inputHashes[projectKey]
|
|
1167
|
+
: undefined;
|
|
1168
|
+
const graphRealpaths = cached.externalInputRealpaths ?? {};
|
|
1169
|
+
const stateEvidence = observation !== undefined
|
|
1170
|
+
? { codec: "predicates", observation }
|
|
1171
|
+
: externalHash !== undefined &&
|
|
1172
|
+
Object.prototype.hasOwnProperty.call(graphRealpaths, identity)
|
|
1173
|
+
? {
|
|
1174
|
+
codec: "graph",
|
|
1175
|
+
hash: externalHash,
|
|
1176
|
+
realpath: graphRealpaths[identity] ?? null,
|
|
1177
|
+
}
|
|
1178
|
+
: externalHash !== undefined
|
|
1179
|
+
? { codec: "host", hash: externalHash }
|
|
1180
|
+
: projectHash !== undefined
|
|
1181
|
+
? { codec: "host", hash: projectHash }
|
|
1182
|
+
: undefined;
|
|
1183
|
+
return {
|
|
1184
|
+
file: input,
|
|
1185
|
+
evidence: {
|
|
1186
|
+
identity,
|
|
1187
|
+
missing,
|
|
1188
|
+
...(stateEvidence === undefined ? {} : { state: stateEvidence }),
|
|
1189
|
+
unavailable: observation?.fileExists === false
|
|
1190
|
+
? "not-file"
|
|
1191
|
+
: missing
|
|
1192
|
+
? "missing"
|
|
1193
|
+
: undefined,
|
|
1194
|
+
},
|
|
1195
|
+
};
|
|
1196
|
+
});
|
|
1197
|
+
if (addWatchFiles !== undefined) {
|
|
1198
|
+
addWatchFiles(inputs);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
for (const input of inputs) {
|
|
1202
|
+
addWatchFile(input.file, input.evidence);
|
|
852
1203
|
}
|
|
853
1204
|
}
|
|
854
1205
|
/**
|
|
855
1206
|
* Derive the absolute, deduplicated watch-input list for a single file.
|
|
856
1207
|
*
|
|
857
|
-
* By default the derivation
|
|
858
|
-
*
|
|
859
|
-
*
|
|
860
|
-
* remain
|
|
1208
|
+
* By default the derivation unions plugin dependencies, the reachable graph,
|
|
1209
|
+
* globals, configs, importer resolver inputs, and universal resolver inputs.
|
|
1210
|
+
* The plugin-reported list can only widen the host-owned language-semantic
|
|
1211
|
+
* bound, never narrow it. Resolver inputs remain in that bound in both modes.
|
|
861
1212
|
*
|
|
862
1213
|
* An envelope that lists `file` in `dependenciesComplete` narrows it to
|
|
863
1214
|
* `dependencies[file] ∪ configs`: the plugin declared its reported list the
|
|
@@ -869,10 +1220,12 @@ function notifyWatchInputs(hooks, cached, file) {
|
|
|
869
1220
|
* volatile keeps the baseline: the two declarations contradict, so the
|
|
870
1221
|
* conservative one wins.
|
|
871
1222
|
*
|
|
872
|
-
* The derived list is a pure function of the envelope and the file's
|
|
873
|
-
*
|
|
874
|
-
*
|
|
875
|
-
*
|
|
1223
|
+
* The derived list is a pure function of the envelope and the delivered file's
|
|
1224
|
+
* normalized lexical spelling. Graph traversal uses its filesystem identity,
|
|
1225
|
+
* but lexical inputs exclude only that exact spelling, so two aliases of one
|
|
1226
|
+
* source require distinct lists. Repeated deliveries of one spelling replay the
|
|
1227
|
+
* per-envelope memo ({@link envelopeDerivation}) instead of re-walking the
|
|
1228
|
+
* graph. Returns an empty list on exceptions.
|
|
876
1229
|
*/
|
|
877
1230
|
function selectWatchInputs(props) {
|
|
878
1231
|
if (props.result.type === "exception") {
|
|
@@ -880,12 +1233,13 @@ function selectWatchInputs(props) {
|
|
|
880
1233
|
}
|
|
881
1234
|
const state = envelopeDerivation(props);
|
|
882
1235
|
const fileIdentity = derivationIdentity(state, props.file);
|
|
883
|
-
const
|
|
1236
|
+
const fileSpelling = path.resolve(props.file);
|
|
1237
|
+
const memoized = state.watchInputs.get(fileSpelling);
|
|
884
1238
|
if (memoized !== undefined) {
|
|
885
1239
|
return memoized;
|
|
886
1240
|
}
|
|
887
1241
|
const derived = deriveWatchInputs(state, props, fileIdentity);
|
|
888
|
-
state.watchInputs.set(
|
|
1242
|
+
state.watchInputs.set(fileSpelling, derived);
|
|
889
1243
|
return derived;
|
|
890
1244
|
}
|
|
891
1245
|
/** Compute one file's watch-input list over the shared per-envelope state. */
|
|
@@ -932,7 +1286,7 @@ function deriveWatchInputs(state, props, fileIdentity) {
|
|
|
932
1286
|
!isVolatileFile(state, props),
|
|
933
1287
|
}))
|
|
934
1288
|
appendPhysical(input);
|
|
935
|
-
//
|
|
1289
|
+
// Resolver inputs, plugin dependencies, and universal host inputs
|
|
936
1290
|
// preserve lexical aliases. Physical deduplication would collapse
|
|
937
1291
|
// `alias/selection.cjs` into the selected target path, so a bundler would
|
|
938
1292
|
// watch only the target and miss a symlink/junction retarget.
|
|
@@ -949,22 +1303,23 @@ function selectHostInputs(props) {
|
|
|
949
1303
|
: selectListedFiles(props.projectRoot, props.result.hostInputs);
|
|
950
1304
|
}
|
|
951
1305
|
/**
|
|
952
|
-
* Return
|
|
953
|
-
*
|
|
954
|
-
*
|
|
955
|
-
*
|
|
1306
|
+
* Return exact importer-owned and universal resolver inputs for `file`. They
|
|
1307
|
+
* remain host-owned even when a plugin declares `dependenciesComplete`: plugin
|
|
1308
|
+
* code cannot vouch for a compiler resolution or automatic type change that
|
|
1309
|
+
* occurs without any plugin input changing.
|
|
956
1310
|
*
|
|
957
|
-
*
|
|
958
|
-
* per-envelope state, so one delivery scans only the
|
|
959
|
-
*
|
|
1311
|
+
* Importer entries and their source identities come from the shared
|
|
1312
|
+
* per-envelope state, so one delivery scans only the recorded inputs instead of
|
|
1313
|
+
* re-resolving every source.
|
|
960
1314
|
*/
|
|
961
1315
|
function selectResolutionCandidateInputs(graph, state, props) {
|
|
962
|
-
if (props.result.type === "exception" ||
|
|
963
|
-
props.result.graph?.candidates === undefined) {
|
|
1316
|
+
if (props.result.type === "exception" || props.result.graph === undefined) {
|
|
964
1317
|
return [];
|
|
965
1318
|
}
|
|
966
1319
|
const reachable = new Set(selectReachableSources(graph, state, props.file).map((source) => derivationIdentity(state, source)));
|
|
967
|
-
const output = [];
|
|
1320
|
+
const output = [...graph.resolutionInputs];
|
|
1321
|
+
if (props.result.graph.candidates === undefined)
|
|
1322
|
+
return output;
|
|
968
1323
|
for (const entry of graph.candidates) {
|
|
969
1324
|
if (!reachable.has(entry.source)) {
|
|
970
1325
|
continue;
|
|
@@ -1093,9 +1448,11 @@ function declaresCompleteDependencies(state, props) {
|
|
|
1093
1448
|
return declared.has(derivationIdentity(state, props.file));
|
|
1094
1449
|
}
|
|
1095
1450
|
/**
|
|
1096
|
-
* Extract the absolute, deduplicated dependency list for a
|
|
1097
|
-
* compiler result. Mirrors
|
|
1098
|
-
*
|
|
1451
|
+
* Extract the absolute, lexical-spelling-deduplicated dependency list for a
|
|
1452
|
+
* single file from the compiler result. Mirrors
|
|
1453
|
+
* {@link selectTransformedSource}'s key lookup: fast project-relative match
|
|
1454
|
+
* first, then a per-envelope identity index. Distinct lexical aliases must
|
|
1455
|
+
* survive so bundlers observe a later symlink or junction retarget. Returns an
|
|
1099
1456
|
* empty list on exceptions or when the plugin reported nothing.
|
|
1100
1457
|
*/
|
|
1101
1458
|
function selectFileDependencies(props) {
|
|
@@ -1118,17 +1475,15 @@ function selectFileDependencies(props) {
|
|
|
1118
1475
|
}
|
|
1119
1476
|
const output = [];
|
|
1120
1477
|
const seen = new Set();
|
|
1121
|
-
const fileIdentity = derivationIdentity(state, props.file);
|
|
1122
1478
|
for (const entry of entries) {
|
|
1123
1479
|
if (typeof entry !== "string" || entry.length === 0) {
|
|
1124
1480
|
continue;
|
|
1125
1481
|
}
|
|
1126
1482
|
const absolute = path.resolve(props.projectRoot, entry);
|
|
1127
|
-
|
|
1128
|
-
if (identity === fileIdentity || seen.has(identity)) {
|
|
1483
|
+
if (seen.has(absolute)) {
|
|
1129
1484
|
continue;
|
|
1130
1485
|
}
|
|
1131
|
-
seen.add(
|
|
1486
|
+
seen.add(absolute);
|
|
1132
1487
|
output.push(absolute);
|
|
1133
1488
|
}
|
|
1134
1489
|
return output;
|
|
@@ -1231,6 +1586,7 @@ function matchesCachedSource(cached, file, source, epoch) {
|
|
|
1231
1586
|
// out-of-walk snapshot — before any of this pass's deliveries may be
|
|
1232
1587
|
// settled against it. That proof is what a per-pass recompile used to buy
|
|
1233
1588
|
// (samchon/ttsc#1300), at a walk instead of a compile.
|
|
1589
|
+
refreshFilesystemClockReference(TRANSFORM_CLOCK_REFERENCE_DIRECTORIES.get(cached), resultFilesystem(cached.result));
|
|
1234
1590
|
if (!matchesCompleteInputSnapshot(cached, currentKey, source)) {
|
|
1235
1591
|
return false;
|
|
1236
1592
|
}
|
|
@@ -1242,6 +1598,7 @@ function matchesCachedSource(cached, file, source, epoch) {
|
|
|
1242
1598
|
return true;
|
|
1243
1599
|
}
|
|
1244
1600
|
}
|
|
1601
|
+
refreshFilesystemClockReference(TRANSFORM_CLOCK_REFERENCE_DIRECTORIES.get(cached), resultFilesystem(cached.result));
|
|
1245
1602
|
if (cached.result.type !== "exception" &&
|
|
1246
1603
|
cached.result.graph !== undefined &&
|
|
1247
1604
|
cached.projectSnapshotComplete === true &&
|
|
@@ -1308,26 +1665,36 @@ function matchesNarrowPersistentInputs(cached, file) {
|
|
|
1308
1665
|
}
|
|
1309
1666
|
/**
|
|
1310
1667
|
* Validate one derived input against the generation, skipping the content read
|
|
1311
|
-
* while the recorded metadata signature still holds
|
|
1668
|
+
* while the recorded metadata signature still holds and its freshly minted
|
|
1669
|
+
* clock reference remains safe.
|
|
1312
1670
|
*
|
|
1313
1671
|
* Sibling deliveries of one generation share most of their derived inputs, and
|
|
1314
1672
|
* `graph.globals` is shared by every one of them, so re-reading and re-hashing
|
|
1315
1673
|
* the whole derived set per delivery multiplies one generation's proven bytes
|
|
1316
1674
|
* by the module count. The derived set is proven the same way the universal
|
|
1317
1675
|
* descriptor inputs are ({@link matchesUniversalHostInputs}), under the same
|
|
1318
|
-
* rules:
|
|
1319
|
-
* signature change falls back to the full
|
|
1320
|
-
* only around a read nothing raced, only
|
|
1321
|
-
*
|
|
1322
|
-
* filesystem's own clock has provably
|
|
1323
|
-
* ({@link stampSeparable}), so a same-length rewrite
|
|
1324
|
-
* hide behind an unchanged signature.
|
|
1676
|
+
* rules: a currently separable unchanged signature stands in for the content
|
|
1677
|
+
* comparison, and any signature or clock-ordering change falls back to the full
|
|
1678
|
+
* comparison. A signature is recorded only around a read nothing raced, only
|
|
1679
|
+
* for a recorded state that came from reading the input rather than from
|
|
1680
|
+
* failing to, and only while the observed filesystem's own clock has provably
|
|
1681
|
+
* left the stamp's tick ({@link stampSeparable}), so a same-length rewrite
|
|
1682
|
+
* inside that tick cannot hide behind an unchanged signature.
|
|
1325
1683
|
*
|
|
1326
1684
|
* The signature carries the physical identity of both the lexical path and its
|
|
1327
1685
|
* link target ({@link inputMetadataSignature}), so retargeting a symlink or
|
|
1328
1686
|
* junction moves it and the skipped realpath comparison cannot be evaded.
|
|
1329
1687
|
*/
|
|
1330
1688
|
function matchesProvenInput(cached, state, input) {
|
|
1689
|
+
const observation = cached.externalInputObservations?.[path.resolve(input)];
|
|
1690
|
+
if (observation !== undefined) {
|
|
1691
|
+
if (observation.fileExists === false &&
|
|
1692
|
+
Object.keys(observation).length === 1 &&
|
|
1693
|
+
notifiesAbsence(cached, input)) {
|
|
1694
|
+
return true;
|
|
1695
|
+
}
|
|
1696
|
+
return matchesRecordedInput(cached, input);
|
|
1697
|
+
}
|
|
1331
1698
|
const slot = inputSignatureSlot(cached, state, input);
|
|
1332
1699
|
if (slot === undefined) {
|
|
1333
1700
|
return matchesRecordedInput(cached, input);
|
|
@@ -1342,7 +1709,9 @@ function matchesProvenInput(cached, state, input) {
|
|
|
1342
1709
|
}
|
|
1343
1710
|
const filesystem = resultFilesystem(cached.result);
|
|
1344
1711
|
const before = inputMetadataEvidence(input, filesystem);
|
|
1345
|
-
if (before !== undefined &&
|
|
1712
|
+
if (before !== undefined &&
|
|
1713
|
+
before.separable &&
|
|
1714
|
+
slot.signatures[slot.key] === before.signature) {
|
|
1346
1715
|
return true;
|
|
1347
1716
|
}
|
|
1348
1717
|
if (!matchesRecordedInput(cached, input)) {
|
|
@@ -1391,6 +1760,9 @@ function notifiesAbsence(cached, input) {
|
|
|
1391
1760
|
*/
|
|
1392
1761
|
function inputSignatureSlot(cached, state, input) {
|
|
1393
1762
|
const identity = derivationIdentity(state, input);
|
|
1763
|
+
if (cached.externalInputObservations?.[path.resolve(input)] !== undefined) {
|
|
1764
|
+
return undefined;
|
|
1765
|
+
}
|
|
1394
1766
|
const external = cached.externalInputHashes ?? {};
|
|
1395
1767
|
if (Object.prototype.hasOwnProperty.call(external, identity)) {
|
|
1396
1768
|
// The recorded hash is identity-keyed because aliases of one physical file
|
|
@@ -1435,7 +1807,8 @@ function matchesUniversalHostInputEntries(cached, validation) {
|
|
|
1435
1807
|
for (const entry of validation.entries.values()) {
|
|
1436
1808
|
const evidence = inputMetadataEvidence(entry.path, filesystem);
|
|
1437
1809
|
if (entry.signature !== undefined &&
|
|
1438
|
-
evidence?.signature === entry.signature
|
|
1810
|
+
evidence?.signature === entry.signature &&
|
|
1811
|
+
(entry.strict === true || evidence.separable))
|
|
1439
1812
|
continue;
|
|
1440
1813
|
if (entry.strict === true)
|
|
1441
1814
|
return false;
|
|
@@ -1664,8 +2037,8 @@ function captureUniversalHostInputValidation(cached, currentFile) {
|
|
|
1664
2037
|
*/
|
|
1665
2038
|
const MISSING_INPUT_STATE = "missing";
|
|
1666
2039
|
/**
|
|
1667
|
-
* The
|
|
1668
|
-
*
|
|
2040
|
+
* The current stamp an adapter-owned probe has minted, keyed by the operations
|
|
2041
|
+
* object that observes it and, inside, by reporting device.
|
|
1669
2042
|
*
|
|
1670
2043
|
* A filesystem stamps a write once per clock tick, so two same-length writes
|
|
1671
2044
|
* inside one tick are indistinguishable by metadata alone. A signature may
|
|
@@ -1673,99 +2046,99 @@ const MISSING_INPUT_STATE = "missing";
|
|
|
1673
2046
|
* it, and that guarantee needs a reference instant the observed filesystem
|
|
1674
2047
|
* itself produced: once some stamp on the same device is strictly newer than an
|
|
1675
2048
|
* input's modification stamp, that input's tick is provably over, so any later
|
|
1676
|
-
* write must mint a newer stamp and move the signature.
|
|
1677
|
-
* racily-clean index rule
|
|
1678
|
-
*
|
|
1679
|
-
* stamp the cache-owned operations report, seeded per generation by
|
|
1680
|
-
* {@link mintFilesystemClockReference}.
|
|
1681
|
-
*
|
|
1682
|
-
* The process clock never participates: both sides of every comparison are
|
|
1683
|
-
* stamps the same filesystem clock minted, at the same granularity, so a
|
|
1684
|
-
* filesystem clock running behind (or ahead of) the host process changes
|
|
1685
|
-
* nothing.
|
|
2049
|
+
* write must mint a newer stamp and move the signature. This adapts git's
|
|
2050
|
+
* racily-clean index rule: both sides of the comparison come from the same
|
|
2051
|
+
* reporting device, and the newer side is a write the adapter itself made.
|
|
1686
2052
|
*
|
|
1687
|
-
*
|
|
1688
|
-
*
|
|
1689
|
-
*
|
|
1690
|
-
*
|
|
1691
|
-
*
|
|
1692
|
-
*
|
|
1693
|
-
*
|
|
1694
|
-
*
|
|
1695
|
-
* floor above the present the same way, a different hazard from the constant
|
|
1696
|
-
* offset the paragraph above is about: an offset moves both operands together
|
|
1697
|
-
* and changes nothing, a jump moves only the present.
|
|
2053
|
+
* A timestamp merely observed on another input cannot advance the reference.
|
|
2054
|
+
* Tools may assign modification times, and a filesystem clock can move
|
|
2055
|
+
* backwards independently of the process clock. Neither a passive historical
|
|
2056
|
+
* maximum nor `Date.now()` proves what stamp a write would mint now. The probe
|
|
2057
|
+
* is therefore rewritten immediately before every validation that may reuse a
|
|
2058
|
+
* content signature, and its previous reference is cleared before the write. A
|
|
2059
|
+
* failed refresh or a different reporting device declines the optimization and
|
|
2060
|
+
* retains the content comparison (samchon/ttsc#1344).
|
|
1698
2061
|
*
|
|
1699
|
-
* The
|
|
1700
|
-
*
|
|
1701
|
-
*
|
|
1702
|
-
*
|
|
1703
|
-
*
|
|
1704
|
-
* because it proves a kind rather than content. Observed stamps keep the common
|
|
1705
|
-
* case working; the probe covers the case they cannot, a tree whose files were
|
|
1706
|
-
* all written inside one tick.
|
|
2062
|
+
* The probe lives in an adapter-owned temporary directory outside the project.
|
|
2063
|
+
* That directory may be on another volume, such as `C:` when a project lives on
|
|
2064
|
+
* `D:`. Such a split-volume generation safely keeps reading content because no
|
|
2065
|
+
* same-device reference exists; it never substitutes a process-clock guess or
|
|
2066
|
+
* writes a probe into the user's project.
|
|
1707
2067
|
*/
|
|
1708
|
-
const
|
|
1709
|
-
/** Return one observed filesystem's per-device
|
|
1710
|
-
function
|
|
1711
|
-
let
|
|
1712
|
-
if (
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
}
|
|
1716
|
-
return floors;
|
|
1717
|
-
}
|
|
1718
|
-
/** Raise a device's clock floor with the stamps one observation reported. */
|
|
1719
|
-
function observeFilesystemClock(filesystem, stats) {
|
|
1720
|
-
const floors = filesystemClockFloors(filesystem);
|
|
1721
|
-
const stamp = stats.mtimeNs > stats.ctimeNs ? stats.mtimeNs : stats.ctimeNs;
|
|
1722
|
-
const current = floors.get(stats.dev);
|
|
1723
|
-
if (current === undefined || stamp > current) {
|
|
1724
|
-
floors.set(stats.dev, stamp);
|
|
2068
|
+
const FILESYSTEM_CLOCK_REFERENCES = new WeakMap();
|
|
2069
|
+
/** Return one observed filesystem's current per-device references. */
|
|
2070
|
+
function filesystemClockReferences(filesystem) {
|
|
2071
|
+
let references = FILESYSTEM_CLOCK_REFERENCES.get(filesystem);
|
|
2072
|
+
if (references === undefined) {
|
|
2073
|
+
references = new Map();
|
|
2074
|
+
FILESYSTEM_CLOCK_REFERENCES.set(filesystem, references);
|
|
1725
2075
|
}
|
|
2076
|
+
return references;
|
|
1726
2077
|
}
|
|
1727
2078
|
/**
|
|
1728
2079
|
* Report whether a later write to the observed path is guaranteed to move its
|
|
1729
|
-
* modification stamp: the device's
|
|
1730
|
-
* the tick that minted the stamp is provably over. The
|
|
2080
|
+
* modification stamp: the device's current probe holds a stamp strictly newer,
|
|
2081
|
+
* so the tick that minted the stamp is provably over. The probe was written
|
|
1731
2082
|
* before the caller's content read began, which is the ordering the guarantee
|
|
1732
2083
|
* needs — a stamp minted before the read proves every post-read write lands in
|
|
1733
2084
|
* a newer tick.
|
|
1734
2085
|
*/
|
|
1735
2086
|
function stampSeparable(filesystem, stats) {
|
|
1736
|
-
const
|
|
1737
|
-
return
|
|
2087
|
+
const reference = filesystemClockReferences(filesystem).get(stats.dev);
|
|
2088
|
+
return reference !== undefined && stats.mtimeNs < reference;
|
|
1738
2089
|
}
|
|
1739
2090
|
/**
|
|
1740
|
-
*
|
|
1741
|
-
* filesystem's clock floor.
|
|
2091
|
+
* Replace every prior clock proof with one reference minted by a fresh write.
|
|
1742
2092
|
*
|
|
1743
|
-
* The
|
|
2093
|
+
* The reference directory is storage the adapter already owns, deliberately
|
|
1744
2094
|
* outside the project root, so stamping a probe file there produces a
|
|
1745
2095
|
* freshly-minted "now" without touching the user's project — the analogue of
|
|
1746
2096
|
* git writing its index. The probe is observed through the cache-owned
|
|
1747
2097
|
* operations and keyed by the device those operations report, so it only ever
|
|
1748
|
-
* separates stamps on the filesystem that actually minted it
|
|
1749
|
-
*
|
|
1750
|
-
*
|
|
1751
|
-
* declined until passively observed stamps separate an input on their own.
|
|
2098
|
+
* separates stamps on the filesystem that actually minted it. When its volume
|
|
2099
|
+
* differs from the inputs' volume, or the observed filesystem cannot see the
|
|
2100
|
+
* probe at all, nothing is proven and content comparison continues.
|
|
1752
2101
|
*
|
|
1753
|
-
*
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
* property of this rule, so the cross-volume case degrades to more reads here
|
|
1758
|
-
* rather than being bought with it.
|
|
2102
|
+
* Forcing the reference onto the inputs' volume would require writing into the
|
|
2103
|
+
* user's project or an otherwise unowned neighboring directory. That is not a
|
|
2104
|
+
* valid price for this optimization, so the cross-volume case degrades to more
|
|
2105
|
+
* reads instead.
|
|
1759
2106
|
*/
|
|
1760
|
-
function
|
|
2107
|
+
function refreshFilesystemClockReference(referenceDirectory, filesystem) {
|
|
2108
|
+
const references = filesystemClockReferences(filesystem);
|
|
2109
|
+
references.clear();
|
|
2110
|
+
if (referenceDirectory === undefined)
|
|
2111
|
+
return;
|
|
1761
2112
|
try {
|
|
1762
|
-
const probe = path.join(
|
|
1763
|
-
fs.writeFileSync(probe,
|
|
1764
|
-
|
|
2113
|
+
const probe = path.join(referenceDirectory, "clock-reference");
|
|
2114
|
+
fs.writeFileSync(probe, `${process.hrtime.bigint()}\n`);
|
|
2115
|
+
const stats = filesystem.lstat(probe);
|
|
2116
|
+
if (stats.isFile()) {
|
|
2117
|
+
references.set(stats.dev, stats.mtimeNs);
|
|
2118
|
+
}
|
|
1765
2119
|
}
|
|
1766
2120
|
catch {
|
|
1767
|
-
//
|
|
1768
|
-
|
|
2121
|
+
// A failed refresh leaves no reference, so content comparison continues.
|
|
2122
|
+
references.clear();
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
/** Remove only the known probe and its now-empty owned directory. */
|
|
2126
|
+
function disposeFilesystemClockReference(referenceDirectory) {
|
|
2127
|
+
try {
|
|
2128
|
+
fs.rmSync(path.join(referenceDirectory, "clock-reference"), {
|
|
2129
|
+
force: true,
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
catch {
|
|
2133
|
+
// The probe may already have disappeared; the directory removal below is
|
|
2134
|
+
// still safe because it is deliberately non-recursive.
|
|
2135
|
+
}
|
|
2136
|
+
try {
|
|
2137
|
+
fs.rmdirSync(referenceDirectory);
|
|
2138
|
+
}
|
|
2139
|
+
catch {
|
|
2140
|
+
// Eviction schedules cleanup without awaiting its Promise. A foreign entry
|
|
2141
|
+
// or a concurrent removal leaves, at worst, an empty temporary directory.
|
|
1769
2142
|
}
|
|
1770
2143
|
}
|
|
1771
2144
|
/** Metadata identity whose stability lets a generation reuse a content hash. */
|
|
@@ -1776,12 +2149,10 @@ function inputMetadataSignature(file, filesystem = DEFAULT_FILESYSTEM_OPERATIONS
|
|
|
1776
2149
|
function inputMetadataEvidence(file, filesystem = DEFAULT_FILESYSTEM_OPERATIONS) {
|
|
1777
2150
|
try {
|
|
1778
2151
|
const link = filesystem.lstat(file);
|
|
1779
|
-
observeFilesystemClock(filesystem, link);
|
|
1780
2152
|
let target = link;
|
|
1781
2153
|
if (link.isSymbolicLink()) {
|
|
1782
2154
|
try {
|
|
1783
2155
|
target = filesystem.statBigInt(file);
|
|
1784
|
-
observeFilesystemClock(filesystem, target);
|
|
1785
2156
|
}
|
|
1786
2157
|
catch {
|
|
1787
2158
|
// Keep a broken link in the existing-input manifest. Its own metadata
|
|
@@ -1847,7 +2218,18 @@ function hostInputStateHash(file, filesystem = DEFAULT_FILESYSTEM_OPERATIONS) {
|
|
|
1847
2218
|
}
|
|
1848
2219
|
/** Fingerprint the text/kind state returned by TypeScript-Go's filesystem. */
|
|
1849
2220
|
function graphInputStateHash(file, filesystem = DEFAULT_FILESYSTEM_OPERATIONS) {
|
|
2221
|
+
const kind = compilerStatKind(file, filesystem);
|
|
2222
|
+
const readHash = graphInputReadHash(file, filesystem, kind);
|
|
2223
|
+
if (readHash !== null)
|
|
2224
|
+
return readHash;
|
|
2225
|
+
return kind === "directory" ? hashText("ttsc:host-input:directory\0") : null;
|
|
2226
|
+
}
|
|
2227
|
+
/** Hash only a successful compiler-style ReadFile result, without kind fallback. */
|
|
2228
|
+
function graphInputReadHash(file, filesystem = DEFAULT_FILESYSTEM_OPERATIONS, observedKind) {
|
|
2229
|
+
const kind = observedKind ?? compilerStatKind(file, filesystem);
|
|
1850
2230
|
try {
|
|
2231
|
+
if (kind === "directory")
|
|
2232
|
+
return null;
|
|
1851
2233
|
const bytes = filesystem.readFile(file);
|
|
1852
2234
|
if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xfe) {
|
|
1853
2235
|
const even = bytes.subarray(2, 2 + Math.floor((bytes.length - 2) / 2) * 2);
|
|
@@ -1867,15 +2249,131 @@ function graphInputStateHash(file, filesystem = DEFAULT_FILESYSTEM_OPERATIONS) {
|
|
|
1867
2249
|
return hashText(content);
|
|
1868
2250
|
}
|
|
1869
2251
|
catch {
|
|
2252
|
+
return null;
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
/** Current result of TypeScript-Go's Stat predicate. */
|
|
2256
|
+
function compilerStatKind(file, filesystem) {
|
|
2257
|
+
try {
|
|
2258
|
+
return filesystem.stat(file).isDirectory() ? "directory" : "file";
|
|
2259
|
+
}
|
|
2260
|
+
catch {
|
|
2261
|
+
return "missing";
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
/** Return the exact recorded predicates that no longer hold for one spelling. */
|
|
2265
|
+
function graphInputObservationFailures(file, observation, filesystem, identities) {
|
|
2266
|
+
const failures = [];
|
|
2267
|
+
if (observation.accessibleEntries !== undefined) {
|
|
2268
|
+
const current = compilerAccessibleEntries(file, filesystem);
|
|
2269
|
+
if (JSON.stringify(current) !== JSON.stringify(observation.accessibleEntries)) {
|
|
2270
|
+
failures.push("accessible-entries-changed");
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
const kind = observation.fileExists !== undefined ||
|
|
2274
|
+
observation.directoryExists !== undefined ||
|
|
2275
|
+
observation.stat !== undefined ||
|
|
2276
|
+
observation.readFile !== undefined
|
|
2277
|
+
? compilerStatKind(file, filesystem)
|
|
2278
|
+
: undefined;
|
|
2279
|
+
if (observation.fileExists !== undefined &&
|
|
2280
|
+
(kind === "file") !== observation.fileExists) {
|
|
2281
|
+
failures.push("file-exists-changed");
|
|
2282
|
+
}
|
|
2283
|
+
if (observation.directoryExists !== undefined &&
|
|
2284
|
+
(kind === "directory") !== observation.directoryExists) {
|
|
2285
|
+
failures.push("directory-exists-changed");
|
|
2286
|
+
}
|
|
2287
|
+
if (observation.stat !== undefined && kind !== observation.stat) {
|
|
2288
|
+
failures.push("stat-changed");
|
|
2289
|
+
}
|
|
2290
|
+
if (observation.readFile !== undefined) {
|
|
2291
|
+
const currentHash = graphInputReadHash(file, filesystem, kind);
|
|
2292
|
+
if ((observation.readFile.ok && currentHash !== observation.readFile.hash) ||
|
|
2293
|
+
(!observation.readFile.ok && currentHash !== null)) {
|
|
2294
|
+
failures.push("read-file-changed");
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
if (observation.realpath !== undefined) {
|
|
2298
|
+
const currentRealpath = compilerInputRealpathObservation(file, filesystem);
|
|
2299
|
+
if (observation.realpath.ok !== currentRealpath.ok ||
|
|
2300
|
+
(observation.realpath.ok &&
|
|
2301
|
+
currentRealpath.ok &&
|
|
2302
|
+
!sameHostInputRealpath(observation.realpath.path, currentRealpath.path, identities))) {
|
|
2303
|
+
failures.push("realpath-changed");
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
return failures;
|
|
2307
|
+
}
|
|
2308
|
+
/** Replay TypeScript-Go's accessible-entry classification and sorted order. */
|
|
2309
|
+
function compilerAccessibleEntries(directory, filesystem) {
|
|
2310
|
+
const directories = [];
|
|
2311
|
+
const files = [];
|
|
2312
|
+
const pathApi = filesystem.platform === "win32" ? path.win32 : path.posix;
|
|
2313
|
+
let entries;
|
|
2314
|
+
try {
|
|
2315
|
+
entries = filesystem.readdir(directory);
|
|
2316
|
+
}
|
|
2317
|
+
catch {
|
|
2318
|
+
return { directories, files };
|
|
2319
|
+
}
|
|
2320
|
+
for (const entry of entries) {
|
|
2321
|
+
if (entry.isDirectory()) {
|
|
2322
|
+
directories.push(entry.name);
|
|
2323
|
+
continue;
|
|
2324
|
+
}
|
|
2325
|
+
if (entry.isFile()) {
|
|
2326
|
+
files.push(entry.name);
|
|
2327
|
+
continue;
|
|
2328
|
+
}
|
|
2329
|
+
if (!entry.isSymbolicLink())
|
|
2330
|
+
continue;
|
|
1870
2331
|
try {
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
2332
|
+
const target = filesystem.stat(pathApi.join(directory, entry.name));
|
|
2333
|
+
if (target.isDirectory())
|
|
2334
|
+
directories.push(entry.name);
|
|
2335
|
+
else if (target.isFile())
|
|
2336
|
+
files.push(entry.name);
|
|
1874
2337
|
}
|
|
1875
2338
|
catch {
|
|
1876
|
-
|
|
2339
|
+
// TypeScript-Go omits inaccessible and broken linked entries.
|
|
1877
2340
|
}
|
|
1878
2341
|
}
|
|
2342
|
+
directories.sort();
|
|
2343
|
+
files.sort();
|
|
2344
|
+
return { directories, files };
|
|
2345
|
+
}
|
|
2346
|
+
/** Validate one predicate-preserving graph proof against a filesystem view. */
|
|
2347
|
+
function validateGraphInputObservation(file, observation, filesystem = DEFAULT_FILESYSTEM_OPERATIONS) {
|
|
2348
|
+
const normalized = normalizeGraphInputObservation(observation, filesystem.platform);
|
|
2349
|
+
if (normalized === undefined)
|
|
2350
|
+
return ["proof-conflict"];
|
|
2351
|
+
return graphInputObservationFailures(file, normalized, filesystem, createHostPathIdentityContext(filesystem));
|
|
2352
|
+
}
|
|
2353
|
+
/** Whether every compiler-time predicate still returns its recorded value. */
|
|
2354
|
+
function matchesGraphInputObservation(file, observation, filesystem, identities) {
|
|
2355
|
+
return (graphInputObservationFailures(file, observation, filesystem, identities)
|
|
2356
|
+
.length === 0);
|
|
2357
|
+
}
|
|
2358
|
+
/** Replay TypeScript-Go's Realpath result, including its lexical fallback. */
|
|
2359
|
+
function compilerInputRealpathObservation(file, filesystem) {
|
|
2360
|
+
try {
|
|
2361
|
+
const realpath = filesystem.realpath(file);
|
|
2362
|
+
return realpath.length === 0
|
|
2363
|
+
? { ok: false }
|
|
2364
|
+
: {
|
|
2365
|
+
ok: true,
|
|
2366
|
+
path: resolveFilesystemPath(realpath, filesystem.platform),
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
catch {
|
|
2370
|
+
// TypeScript-Go's OS and io filesystems return the cleaned input spelling
|
|
2371
|
+
// when native realpath resolution fails; they do not expose the failure.
|
|
2372
|
+
return {
|
|
2373
|
+
ok: true,
|
|
2374
|
+
path: resolveFilesystemPath(file, filesystem.platform),
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
1879
2377
|
}
|
|
1880
2378
|
/** Physical target selected by a lexical host-input path. */
|
|
1881
2379
|
function hostInputRealpath(file, filesystem = DEFAULT_FILESYSTEM_OPERATIONS) {
|
|
@@ -2019,6 +2517,9 @@ function matchesExternalInputRealpaths(cached) {
|
|
|
2019
2517
|
const state = envelopeDerivation(cached);
|
|
2020
2518
|
const filesystem = resultFilesystem(cached.result);
|
|
2021
2519
|
for (const input of cached.externalInputPaths ?? []) {
|
|
2520
|
+
if (cached.externalInputObservations?.[path.resolve(input)] !== undefined) {
|
|
2521
|
+
continue;
|
|
2522
|
+
}
|
|
2022
2523
|
const identity = derivationIdentity(state, input);
|
|
2023
2524
|
if (!Object.prototype.hasOwnProperty.call(expected, identity))
|
|
2024
2525
|
continue;
|
|
@@ -2042,15 +2543,16 @@ function captureExternalInputSnapshot(cached, paths) {
|
|
|
2042
2543
|
// A non-declaration transform output is a compiler-realized source even when
|
|
2043
2544
|
// a malformed or legacy graph omitted its node. Its output was computed from
|
|
2044
2545
|
// compiler-time bytes, so a post-compile host read cannot prove coherence.
|
|
2045
|
-
const
|
|
2546
|
+
const transformSourceSpellings = new Set();
|
|
2046
2547
|
if (cached.result.type === "success") {
|
|
2047
2548
|
for (const output of Object.keys(cached.result.typescript)) {
|
|
2048
2549
|
if (!isDeclarationFile(output)) {
|
|
2049
|
-
|
|
2550
|
+
transformSourceSpellings.add(path.resolve(cached.projectRoot, output));
|
|
2050
2551
|
}
|
|
2051
2552
|
}
|
|
2052
2553
|
}
|
|
2053
2554
|
const hashes = {};
|
|
2555
|
+
const observations = {};
|
|
2054
2556
|
const realpaths = {};
|
|
2055
2557
|
const signatures = {};
|
|
2056
2558
|
const failures = createGenerationProofFailures();
|
|
@@ -2067,26 +2569,55 @@ function captureExternalInputSnapshot(cached, paths) {
|
|
|
2067
2569
|
};
|
|
2068
2570
|
for (const input of paths) {
|
|
2069
2571
|
const identity = derivationIdentity(state, input);
|
|
2070
|
-
|
|
2572
|
+
const spelling = path.resolve(input);
|
|
2573
|
+
const predicateObservation = graph.inputObservations.get(spelling);
|
|
2574
|
+
const predicateConflict = graph.inputObservationConflicts.has(spelling);
|
|
2575
|
+
if (graph.speculative.has(spelling) &&
|
|
2576
|
+
(predicateObservation !== undefined || predicateConflict)) {
|
|
2577
|
+
if (predicateConflict || predicateObservation === undefined) {
|
|
2578
|
+
complete = false;
|
|
2579
|
+
recordGenerationProofFailure(failures, {
|
|
2580
|
+
domain: "external",
|
|
2581
|
+
kind: "graph-proof-conflict",
|
|
2582
|
+
detail: graph.inputProofFailures.get(spelling),
|
|
2583
|
+
path: input,
|
|
2584
|
+
});
|
|
2585
|
+
continue;
|
|
2586
|
+
}
|
|
2587
|
+
const mismatches = graphInputObservationFailures(input, predicateObservation, filesystem, state.identityContext);
|
|
2588
|
+
if (mismatches.length !== 0)
|
|
2589
|
+
complete = false;
|
|
2590
|
+
for (const kind of mismatches) {
|
|
2591
|
+
recordGenerationProofFailure(failures, {
|
|
2592
|
+
domain: "external",
|
|
2593
|
+
kind: `graph-${kind}`,
|
|
2594
|
+
path: input,
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
observations[spelling] = predicateObservation;
|
|
2598
|
+
continue;
|
|
2599
|
+
}
|
|
2600
|
+
// A member the envelope reported only as a resolver input falls
|
|
2071
2601
|
// through to the recorded-state branch below, the same evidence a
|
|
2072
2602
|
// plugin-declared dependency path carries. Its absence still invalidates
|
|
2073
2603
|
// the generation when it appears, because `missing` is recorded state.
|
|
2074
|
-
const realizedTransformSource =
|
|
2604
|
+
const realizedTransformSource = transformSourceSpellings.has(spelling);
|
|
2075
2605
|
const speculativeOnly = !realizedTransformSource &&
|
|
2076
|
-
graph.speculative.has(
|
|
2077
|
-
!graph.inputProofs.has(
|
|
2078
|
-
!graph.inputProofConflicts.has(
|
|
2079
|
-
|
|
2606
|
+
graph.speculative.has(spelling) &&
|
|
2607
|
+
!graph.inputProofs.has(spelling) &&
|
|
2608
|
+
!graph.inputProofConflicts.has(spelling) &&
|
|
2609
|
+
!graph.inputProofFailures.has(spelling);
|
|
2610
|
+
if ((realizedTransformSource || graph.memberSpellings.has(spelling)) &&
|
|
2080
2611
|
!speculativeOnly) {
|
|
2081
|
-
const proof = graph.inputProofs.get(
|
|
2082
|
-
if (proof === undefined || graph.inputProofConflicts.has(
|
|
2612
|
+
const proof = graph.inputProofs.get(spelling);
|
|
2613
|
+
if (proof === undefined || graph.inputProofConflicts.has(spelling)) {
|
|
2083
2614
|
complete = false;
|
|
2084
2615
|
recordGenerationProofFailure(failures, {
|
|
2085
2616
|
domain: "external",
|
|
2086
|
-
kind: graph.inputProofConflicts.has(
|
|
2617
|
+
kind: graph.inputProofConflicts.has(spelling)
|
|
2087
2618
|
? "graph-proof-conflict"
|
|
2088
2619
|
: "graph-proof-missing",
|
|
2089
|
-
detail: graph.inputProofFailures.get(
|
|
2620
|
+
detail: graph.inputProofFailures.get(spelling),
|
|
2090
2621
|
path: input,
|
|
2091
2622
|
});
|
|
2092
2623
|
continue;
|
|
@@ -2131,7 +2662,14 @@ function captureExternalInputSnapshot(cached, paths) {
|
|
|
2131
2662
|
if (hash !== null)
|
|
2132
2663
|
record(input, before, after);
|
|
2133
2664
|
}
|
|
2134
|
-
return {
|
|
2665
|
+
return {
|
|
2666
|
+
complete,
|
|
2667
|
+
failures,
|
|
2668
|
+
hashes,
|
|
2669
|
+
observations,
|
|
2670
|
+
realpaths,
|
|
2671
|
+
signatures,
|
|
2672
|
+
};
|
|
2135
2673
|
}
|
|
2136
2674
|
/** Explain every graph member that no longer matches the compiler's state. */
|
|
2137
2675
|
function compilerGraphInputProofFailures(cached) {
|
|
@@ -2139,6 +2677,7 @@ function compilerGraphInputProofFailures(cached) {
|
|
|
2139
2677
|
if (cached.result.type === "exception" ||
|
|
2140
2678
|
cached.result.graph === undefined ||
|
|
2141
2679
|
(cached.result.graph.inputHashes === undefined &&
|
|
2680
|
+
cached.result.graph.inputObservations === undefined &&
|
|
2142
2681
|
cached.result.graph.inputRealpaths === undefined &&
|
|
2143
2682
|
cached.result.graph.inputProofFailures === undefined)) {
|
|
2144
2683
|
// Legacy sidecars remain compatible for ordinary in-project graphs. Their
|
|
@@ -2149,34 +2688,89 @@ function compilerGraphInputProofFailures(cached) {
|
|
|
2149
2688
|
const state = envelopeDerivation(cached);
|
|
2150
2689
|
const filesystem = resultFilesystem(cached.result);
|
|
2151
2690
|
const graph = envelopeGraphIndexes(state, cached);
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2691
|
+
const predicateSpellings = new Set();
|
|
2692
|
+
const predicateConflictSpellings = new Set();
|
|
2693
|
+
for (const [spelling, observation] of graph.inputObservations) {
|
|
2694
|
+
predicateSpellings.add(spelling);
|
|
2695
|
+
for (const kind of graphInputObservationFailures(spelling, observation, filesystem, state.identityContext)) {
|
|
2696
|
+
recordGenerationProofFailure(failures, {
|
|
2697
|
+
domain: "graph",
|
|
2698
|
+
kind,
|
|
2699
|
+
path: spelling,
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
for (const spelling of graph.inputObservationConflicts) {
|
|
2704
|
+
predicateSpellings.add(spelling);
|
|
2705
|
+
predicateConflictSpellings.add(spelling);
|
|
2706
|
+
recordGenerationProofFailure(failures, {
|
|
2707
|
+
domain: "graph",
|
|
2708
|
+
kind: "proof-conflict",
|
|
2709
|
+
detail: graph.inputProofFailures.get(spelling),
|
|
2710
|
+
path: spelling,
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
for (const spelling of graph.memberSpellings) {
|
|
2714
|
+
const proof = graph.inputProofs.get(spelling);
|
|
2155
2715
|
if (isTransformScratchInput(spelling, cached.scratchDirectory)) {
|
|
2156
2716
|
continue;
|
|
2157
2717
|
}
|
|
2158
|
-
|
|
2159
|
-
// would void every generation of every project whose resolution passes over
|
|
2160
|
-
// a higher-priority spelling, which is every project with a dependency
|
|
2161
|
-
// typed by a declaration file (samchon/ttsc#1245). It is validated instead
|
|
2162
|
-
// against the state {@link captureExternalInputSnapshot} recorded for it.
|
|
2163
|
-
if (proof === undefined && graph.speculative.has(identity)) {
|
|
2718
|
+
if (predicateConflictSpellings.has(spelling)) {
|
|
2164
2719
|
continue;
|
|
2165
2720
|
}
|
|
2166
|
-
if (graph.inputProofConflicts.has(
|
|
2721
|
+
if (graph.inputProofConflicts.has(spelling)) {
|
|
2167
2722
|
recordGenerationProofFailure(failures, {
|
|
2168
2723
|
domain: "graph",
|
|
2169
2724
|
kind: "proof-conflict",
|
|
2170
|
-
detail: graph.inputProofFailures.get(
|
|
2725
|
+
detail: graph.inputProofFailures.get(spelling),
|
|
2171
2726
|
path: spelling,
|
|
2172
2727
|
});
|
|
2173
2728
|
continue;
|
|
2174
2729
|
}
|
|
2730
|
+
if (graph.inputProofFailures.has(spelling)) {
|
|
2731
|
+
recordGenerationProofFailure(failures, {
|
|
2732
|
+
domain: "graph",
|
|
2733
|
+
kind: "proof-missing",
|
|
2734
|
+
detail: graph.inputProofFailures.get(spelling),
|
|
2735
|
+
path: spelling,
|
|
2736
|
+
});
|
|
2737
|
+
continue;
|
|
2738
|
+
}
|
|
2739
|
+
const observation = graph.inputObservations.get(spelling);
|
|
2740
|
+
if (observation !== undefined && proof !== undefined) {
|
|
2741
|
+
const projection = legacyProjectionOfGraphInputObservation(observation);
|
|
2742
|
+
if (projection.failure !== undefined ||
|
|
2743
|
+
projection.hash !== proof.hash ||
|
|
2744
|
+
!sameHostInputRealpath(projection.realpath, proof.realpath, state.identityContext)) {
|
|
2745
|
+
recordGenerationProofFailure(failures, {
|
|
2746
|
+
domain: "graph",
|
|
2747
|
+
kind: "proof-conflict",
|
|
2748
|
+
detail: projection.failure,
|
|
2749
|
+
path: spelling,
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
// The rich predicates were already replayed above. Their legacy
|
|
2753
|
+
// projection is an internal producer-consistency check, never a reason
|
|
2754
|
+
// to read the same filesystem input again. A projection that cannot
|
|
2755
|
+
// represent the observation is itself inconsistent with a supplied
|
|
2756
|
+
// legacy proof, rather than permission to trust either representation.
|
|
2757
|
+
continue;
|
|
2758
|
+
}
|
|
2759
|
+
if (graph.speculative.has(spelling) &&
|
|
2760
|
+
predicateSpellings.has(spelling) &&
|
|
2761
|
+
!graph.inputProofFailures.has(spelling)) {
|
|
2762
|
+
continue;
|
|
2763
|
+
}
|
|
2764
|
+
// A legacy sidecar can report a resolver candidate without a compiler
|
|
2765
|
+
// predicate. Validate it against the generation snapshot instead.
|
|
2766
|
+
if (proof === undefined && graph.speculative.has(spelling)) {
|
|
2767
|
+
continue;
|
|
2768
|
+
}
|
|
2175
2769
|
if (proof === undefined) {
|
|
2176
2770
|
recordGenerationProofFailure(failures, {
|
|
2177
2771
|
domain: "graph",
|
|
2178
2772
|
kind: "proof-missing",
|
|
2179
|
-
detail: graph.inputProofFailures.get(
|
|
2773
|
+
detail: graph.inputProofFailures.get(spelling),
|
|
2180
2774
|
path: spelling,
|
|
2181
2775
|
});
|
|
2182
2776
|
continue;
|
|
@@ -2208,6 +2802,10 @@ function matchesRecordedInput(cached, input) {
|
|
|
2208
2802
|
? cached.inputHashes[projectKey]
|
|
2209
2803
|
: undefined;
|
|
2210
2804
|
const identity = derivationIdentity(state, input);
|
|
2805
|
+
const observation = cached.externalInputObservations?.[path.resolve(input)];
|
|
2806
|
+
if (observation !== undefined) {
|
|
2807
|
+
return matchesGraphInputObservation(input, observation, filesystem, state.identityContext);
|
|
2808
|
+
}
|
|
2211
2809
|
const externalHash = (cached.externalInputHashes ?? {})[identity];
|
|
2212
2810
|
const externalRealpaths = cached.externalInputRealpaths;
|
|
2213
2811
|
const graphInput = externalRealpaths !== undefined &&
|
|
@@ -2245,9 +2843,20 @@ function markCachedSourceServed(cached, file) {
|
|
|
2245
2843
|
* can fold the identical input universe into their own cache fingerprints.
|
|
2246
2844
|
*/
|
|
2247
2845
|
function collectProjectInputHashes(projectRoot, identities = createHostPathIdentityContext(), filesystem = DEFAULT_FILESYSTEM_OPERATIONS, policy) {
|
|
2248
|
-
return
|
|
2846
|
+
return collectProjectInputHashSnapshot(projectRoot, identities, filesystem, policy).hashes;
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Hash the project walk and retain whether every attempted directory and file
|
|
2850
|
+
* was observed coherently. Cache-key hosts must reject an incomplete set.
|
|
2851
|
+
*/
|
|
2852
|
+
function collectProjectInputHashSnapshot(projectRoot, identities = createHostPathIdentityContext(), filesystem = DEFAULT_FILESYSTEM_OPERATIONS, policy) {
|
|
2853
|
+
const snapshot = collectProjectInputSnapshot(projectRoot, identities, filesystem, undefined, {
|
|
2249
2854
|
policy,
|
|
2250
|
-
})
|
|
2855
|
+
});
|
|
2856
|
+
return {
|
|
2857
|
+
complete: snapshot.complete && snapshot.directoryComplete,
|
|
2858
|
+
hashes: snapshot.hashes,
|
|
2859
|
+
};
|
|
2251
2860
|
}
|
|
2252
2861
|
/** Hash project files and snapshot the directory topology in one walk. */
|
|
2253
2862
|
function collectProjectInputSnapshot(projectRoot, identities, filesystem = DEFAULT_FILESYSTEM_OPERATIONS, proven, options) {
|
|
@@ -2276,10 +2885,11 @@ function collectProjectInputSnapshot(projectRoot, identities, filesystem = DEFAU
|
|
|
2276
2885
|
const before = inputMetadataEvidence(file, filesystem);
|
|
2277
2886
|
// A file whose signature still equals the one captured around the read
|
|
2278
2887
|
// that produced the recorded hash carries that content, so the whole
|
|
2279
|
-
// project does not have to be re-read to prove one delivery.
|
|
2280
|
-
//
|
|
2281
|
-
//
|
|
2888
|
+
// project does not have to be re-read to prove one delivery. Recheck the
|
|
2889
|
+
// clock ordering as well: rollback can make a formerly safe floor unable
|
|
2890
|
+
// to answer for a new write (samchon/ttsc#1344).
|
|
2282
2891
|
if (before !== undefined &&
|
|
2892
|
+
before.separable &&
|
|
2283
2893
|
proven !== undefined &&
|
|
2284
2894
|
proven.signatures[key] === before.signature &&
|
|
2285
2895
|
Object.prototype.hasOwnProperty.call(proven.hashes, key)) {
|
|
@@ -2470,7 +3080,7 @@ function walkProjectInputs(root, filesystem = DEFAULT_FILESYSTEM_OPERATIONS, pol
|
|
|
2470
3080
|
}
|
|
2471
3081
|
/**
|
|
2472
3082
|
* Return a directory's metadata stamp, used to detect that its membership moved
|
|
2473
|
-
* _while_ the walk was enumerating it
|
|
3083
|
+
* _while_ the walk was enumerating it.
|
|
2474
3084
|
*
|
|
2475
3085
|
* This is the right instrument for that job and the wrong one for comparing two
|
|
2476
3086
|
* generations: it moves for ignored entries too. {@link walkProjectInputs}
|
|
@@ -2479,9 +3089,6 @@ function walkProjectInputs(root, filesystem = DEFAULT_FILESYSTEM_OPERATIONS, pol
|
|
|
2479
3089
|
function projectDirectorySignature(directory, filesystem = DEFAULT_FILESYSTEM_OPERATIONS) {
|
|
2480
3090
|
try {
|
|
2481
3091
|
const stats = filesystem.statBigInt(directory);
|
|
2482
|
-
// Directory stamps are minted by the same clock as file stamps, so every
|
|
2483
|
-
// walk observation also raises the clock floor that separates them.
|
|
2484
|
-
observeFilesystemClock(filesystem, stats);
|
|
2485
3092
|
if (!stats.isDirectory()) {
|
|
2486
3093
|
return undefined;
|
|
2487
3094
|
}
|
|
@@ -2533,6 +3140,25 @@ function openDirectoryWatch(filesystem, directory, listener, onError) {
|
|
|
2533
3140
|
watcher.on("error", onError);
|
|
2534
3141
|
return { close: () => watcher.close() };
|
|
2535
3142
|
}
|
|
3143
|
+
/** Detach and close every watcher, then preserve the first cleanup failure. */
|
|
3144
|
+
function closeDirectoryWatches(watchers) {
|
|
3145
|
+
const owned = watchers.splice(0);
|
|
3146
|
+
let failed = false;
|
|
3147
|
+
let failure;
|
|
3148
|
+
for (const watcher of owned) {
|
|
3149
|
+
try {
|
|
3150
|
+
watcher.close();
|
|
3151
|
+
}
|
|
3152
|
+
catch (error) {
|
|
3153
|
+
if (!failed) {
|
|
3154
|
+
failed = true;
|
|
3155
|
+
failure = error;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
if (failed)
|
|
3160
|
+
throw failure;
|
|
3161
|
+
}
|
|
2536
3162
|
/** Watch every walked directory for membership changes after generation. */
|
|
2537
3163
|
async function createProjectMutationTracker(directories, filesystem = DEFAULT_FILESYSTEM_OPERATIONS, policy = PERMISSIVE_PROJECT_MEMBERSHIP_POLICY) {
|
|
2538
3164
|
const tracker = {
|
|
@@ -2547,11 +3173,7 @@ async function createProjectMutationTracker(directories, filesystem = DEFAULT_FI
|
|
|
2547
3173
|
return tracker;
|
|
2548
3174
|
}
|
|
2549
3175
|
const watchers = [];
|
|
2550
|
-
tracker.close = () =>
|
|
2551
|
-
for (const watcher of watchers)
|
|
2552
|
-
watcher.close();
|
|
2553
|
-
watchers.length = 0;
|
|
2554
|
-
};
|
|
3176
|
+
tracker.close = () => closeDirectoryWatches(watchers);
|
|
2555
3177
|
for (const directory of directories) {
|
|
2556
3178
|
try {
|
|
2557
3179
|
watchers.push(openDirectoryWatch(filesystem, directory.path, (eventType, filename) => {
|
|
@@ -2618,11 +3240,7 @@ async function createHostInputMutationTracker(inputs, filesystem, covered, event
|
|
|
2618
3240
|
return tracker;
|
|
2619
3241
|
}
|
|
2620
3242
|
const watchers = [];
|
|
2621
|
-
tracker.close = () =>
|
|
2622
|
-
for (const watcher of watchers)
|
|
2623
|
-
watcher.close();
|
|
2624
|
-
watchers.length = 0;
|
|
2625
|
-
};
|
|
3243
|
+
tracker.close = () => closeDirectoryWatches(watchers);
|
|
2626
3244
|
for (const location of locations) {
|
|
2627
3245
|
try {
|
|
2628
3246
|
const names = new Set(location.names);
|
|
@@ -2793,14 +3411,40 @@ membership) {
|
|
|
2793
3411
|
return;
|
|
2794
3412
|
broker.trackers.delete(id);
|
|
2795
3413
|
active.ready();
|
|
2796
|
-
|
|
3414
|
+
let failed = false;
|
|
3415
|
+
let failure;
|
|
3416
|
+
try {
|
|
3417
|
+
broker.child.send?.({ id, op: "remove" });
|
|
3418
|
+
}
|
|
3419
|
+
catch (error) {
|
|
3420
|
+
failed = true;
|
|
3421
|
+
failure = error;
|
|
3422
|
+
}
|
|
2797
3423
|
if (broker.trackers.size === 0) {
|
|
2798
|
-
broker.child.disconnect?.();
|
|
2799
|
-
broker.child.kill();
|
|
2800
3424
|
if (windowsProjectMutationBroker === broker) {
|
|
2801
3425
|
windowsProjectMutationBroker = undefined;
|
|
2802
3426
|
}
|
|
3427
|
+
try {
|
|
3428
|
+
broker.child.disconnect?.();
|
|
3429
|
+
}
|
|
3430
|
+
catch (error) {
|
|
3431
|
+
if (!failed) {
|
|
3432
|
+
failed = true;
|
|
3433
|
+
failure = error;
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
try {
|
|
3437
|
+
broker.child.kill();
|
|
3438
|
+
}
|
|
3439
|
+
catch (error) {
|
|
3440
|
+
if (!failed) {
|
|
3441
|
+
failed = true;
|
|
3442
|
+
failure = error;
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
2803
3445
|
}
|
|
3446
|
+
if (failed)
|
|
3447
|
+
throw failure;
|
|
2804
3448
|
};
|
|
2805
3449
|
broker.child.send?.({
|
|
2806
3450
|
allEvents,
|
|
@@ -3142,10 +3786,197 @@ function collectExternalInputHashes(paths, filesystem = DEFAULT_FILESYSTEM_OPERA
|
|
|
3142
3786
|
}
|
|
3143
3787
|
return hashes;
|
|
3144
3788
|
}
|
|
3789
|
+
/** Capture the stable file predicate used by implicit project discovery. */
|
|
3790
|
+
function captureWatchInputFileBaseline(file, filesystem = DEFAULT_FILESYSTEM_OPERATIONS) {
|
|
3791
|
+
const capture = () => {
|
|
3792
|
+
const identities = createHostPathIdentityContext(filesystem);
|
|
3793
|
+
let fileExists = false;
|
|
3794
|
+
try {
|
|
3795
|
+
fileExists = filesystem.stat(file).isFile();
|
|
3796
|
+
}
|
|
3797
|
+
catch {
|
|
3798
|
+
// Project discovery rejects every candidate not proven to be a file.
|
|
3799
|
+
}
|
|
3800
|
+
return {
|
|
3801
|
+
fileExists,
|
|
3802
|
+
identity: pathIdentityKey(file, identities),
|
|
3803
|
+
};
|
|
3804
|
+
};
|
|
3805
|
+
try {
|
|
3806
|
+
const before = capture();
|
|
3807
|
+
const after = capture();
|
|
3808
|
+
return stableStringify(before) === stableStringify(after)
|
|
3809
|
+
? after
|
|
3810
|
+
: undefined;
|
|
3811
|
+
}
|
|
3812
|
+
catch {
|
|
3813
|
+
return undefined;
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
/**
|
|
3817
|
+
* Capture one stable main-process baseline that can be compared with any
|
|
3818
|
+
* generation-owned watch-input evidence. Two equal broad observations are
|
|
3819
|
+
* required so a cache key never publishes a torn path state.
|
|
3820
|
+
*/
|
|
3821
|
+
function captureWatchInputBaseline(file, filesystem = DEFAULT_FILESYSTEM_OPERATIONS) {
|
|
3822
|
+
const capture = () => {
|
|
3823
|
+
const identities = createHostPathIdentityContext(filesystem);
|
|
3824
|
+
const stat = compilerStatKind(file, filesystem);
|
|
3825
|
+
return {
|
|
3826
|
+
directoryExists: stat === "directory",
|
|
3827
|
+
fileExists: stat === "file",
|
|
3828
|
+
graphHash: graphInputStateHash(file, filesystem) ?? MISSING_INPUT_STATE,
|
|
3829
|
+
graphReadHash: graphInputReadHash(file, filesystem),
|
|
3830
|
+
hostHash: hostInputStateHash(file, filesystem) ?? MISSING_INPUT_STATE,
|
|
3831
|
+
identity: pathIdentityKey(file, identities),
|
|
3832
|
+
realpath: compilerInputRealpathObservation(file, filesystem),
|
|
3833
|
+
stat,
|
|
3834
|
+
};
|
|
3835
|
+
};
|
|
3836
|
+
try {
|
|
3837
|
+
const before = capture();
|
|
3838
|
+
const after = capture();
|
|
3839
|
+
return stableStringify(before) === stableStringify(after)
|
|
3840
|
+
? after
|
|
3841
|
+
: undefined;
|
|
3842
|
+
}
|
|
3843
|
+
catch {
|
|
3844
|
+
return undefined;
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
/** Compare generation evidence with the main process's exact key baseline. */
|
|
3848
|
+
function watchInputEvidenceMatchesBaseline(evidence, baseline) {
|
|
3849
|
+
if (!isWatchInputKeyBaseline(baseline) ||
|
|
3850
|
+
evidence.identity !== baseline.identity ||
|
|
3851
|
+
evidence.state === undefined) {
|
|
3852
|
+
return false;
|
|
3853
|
+
}
|
|
3854
|
+
const broad = broadWatchInputBaseline(baseline);
|
|
3855
|
+
if (evidence.state.codec === "host") {
|
|
3856
|
+
return broad !== undefined && evidence.state.hash === broad.hostHash;
|
|
3857
|
+
}
|
|
3858
|
+
const identities = createHostPathIdentityContext();
|
|
3859
|
+
if (evidence.state.codec === "graph") {
|
|
3860
|
+
return (broad !== undefined &&
|
|
3861
|
+
evidence.state.hash === broad.graphHash &&
|
|
3862
|
+
sameHostInputRealpath(evidence.state.realpath, broad.realpath.ok ? broad.realpath.path : null, identities));
|
|
3863
|
+
}
|
|
3864
|
+
const observation = evidence.state.observation;
|
|
3865
|
+
if (observation.fileExists !== undefined &&
|
|
3866
|
+
observation.fileExists !== baseline.fileExists) {
|
|
3867
|
+
return false;
|
|
3868
|
+
}
|
|
3869
|
+
if (observation.directoryExists !== undefined &&
|
|
3870
|
+
(broad === undefined ||
|
|
3871
|
+
observation.directoryExists !== broad.directoryExists)) {
|
|
3872
|
+
return false;
|
|
3873
|
+
}
|
|
3874
|
+
if (observation.stat !== undefined &&
|
|
3875
|
+
(broad === undefined || observation.stat !== broad.stat)) {
|
|
3876
|
+
return false;
|
|
3877
|
+
}
|
|
3878
|
+
if (observation.readFile !== undefined &&
|
|
3879
|
+
(broad === undefined ||
|
|
3880
|
+
(observation.readFile.ok &&
|
|
3881
|
+
observation.readFile.hash !== broad.graphReadHash) ||
|
|
3882
|
+
(!observation.readFile.ok && broad.graphReadHash !== null))) {
|
|
3883
|
+
return false;
|
|
3884
|
+
}
|
|
3885
|
+
if (observation.realpath !== undefined) {
|
|
3886
|
+
if (broad === undefined) {
|
|
3887
|
+
return false;
|
|
3888
|
+
}
|
|
3889
|
+
if (observation.realpath.ok !== broad.realpath.ok) {
|
|
3890
|
+
return false;
|
|
3891
|
+
}
|
|
3892
|
+
if (observation.realpath.ok &&
|
|
3893
|
+
broad.realpath.ok &&
|
|
3894
|
+
!sameHostInputRealpath(observation.realpath.path, broad.realpath.path, identities)) {
|
|
3895
|
+
return false;
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
return true;
|
|
3899
|
+
}
|
|
3900
|
+
/** Validate the complete narrow or broad shape stored in a key baseline. */
|
|
3901
|
+
function isWatchInputKeyBaseline(baseline) {
|
|
3902
|
+
if (!isPlainRecord(baseline))
|
|
3903
|
+
return false;
|
|
3904
|
+
const keys = Object.keys(baseline).sort();
|
|
3905
|
+
if (typeof baseline.fileExists !== "boolean" ||
|
|
3906
|
+
typeof baseline.identity !== "string" ||
|
|
3907
|
+
!isAbsoluteFilesystemPath(baseline.identity)) {
|
|
3908
|
+
return false;
|
|
3909
|
+
}
|
|
3910
|
+
if (stableStringify(keys) === stableStringify(["fileExists", "identity"])) {
|
|
3911
|
+
return true;
|
|
3912
|
+
}
|
|
3913
|
+
if (stableStringify(keys) !==
|
|
3914
|
+
stableStringify([
|
|
3915
|
+
"directoryExists",
|
|
3916
|
+
"fileExists",
|
|
3917
|
+
"graphHash",
|
|
3918
|
+
"graphReadHash",
|
|
3919
|
+
"hostHash",
|
|
3920
|
+
"identity",
|
|
3921
|
+
"realpath",
|
|
3922
|
+
"stat",
|
|
3923
|
+
])) {
|
|
3924
|
+
return false;
|
|
3925
|
+
}
|
|
3926
|
+
if (typeof baseline.directoryExists !== "boolean" ||
|
|
3927
|
+
!isWatchInputStateHash(baseline.graphHash) ||
|
|
3928
|
+
!(baseline.graphReadHash === null || isContentHash(baseline.graphReadHash)) ||
|
|
3929
|
+
!isWatchInputStateHash(baseline.hostHash) ||
|
|
3930
|
+
!isWatchInputRealpathBaseline(baseline.realpath) ||
|
|
3931
|
+
(baseline.stat !== "directory" &&
|
|
3932
|
+
baseline.stat !== "file" &&
|
|
3933
|
+
baseline.stat !== "missing")) {
|
|
3934
|
+
return false;
|
|
3935
|
+
}
|
|
3936
|
+
return (baseline.fileExists === (baseline.stat === "file") &&
|
|
3937
|
+
baseline.directoryExists === (baseline.stat === "directory"));
|
|
3938
|
+
}
|
|
3939
|
+
/** Whether an unknown value is one ordinary JSON object. */
|
|
3940
|
+
function isPlainRecord(value) {
|
|
3941
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3942
|
+
return false;
|
|
3943
|
+
}
|
|
3944
|
+
const prototype = Object.getPrototypeOf(value);
|
|
3945
|
+
return prototype === Object.prototype || prototype === null;
|
|
3946
|
+
}
|
|
3947
|
+
/** Whether one identity can name an absolute path on either supported syntax. */
|
|
3948
|
+
function isAbsoluteFilesystemPath(value) {
|
|
3949
|
+
return path.posix.isAbsolute(value) || path.win32.isAbsolute(value);
|
|
3950
|
+
}
|
|
3951
|
+
/** Whether a serialized hash is a content digest. */
|
|
3952
|
+
function isContentHash(value) {
|
|
3953
|
+
return typeof value === "string" && /^[0-9a-f]{64}$/.test(value);
|
|
3954
|
+
}
|
|
3955
|
+
/** Whether a baseline state is either a digest or the missing marker. */
|
|
3956
|
+
function isWatchInputStateHash(value) {
|
|
3957
|
+
return value === MISSING_INPUT_STATE || isContentHash(value);
|
|
3958
|
+
}
|
|
3959
|
+
/** Validate the serialized Realpath predicate as an exact discriminated union. */
|
|
3960
|
+
function isWatchInputRealpathBaseline(value) {
|
|
3961
|
+
if (!isPlainRecord(value) || typeof value.ok !== "boolean")
|
|
3962
|
+
return false;
|
|
3963
|
+
const keys = Object.keys(value).sort();
|
|
3964
|
+
if (value.ok === false) {
|
|
3965
|
+
return stableStringify(keys) === stableStringify(["ok"]);
|
|
3966
|
+
}
|
|
3967
|
+
return (stableStringify(keys) === stableStringify(["ok", "path"]) &&
|
|
3968
|
+
typeof value.path === "string" &&
|
|
3969
|
+
isAbsoluteFilesystemPath(value.path));
|
|
3970
|
+
}
|
|
3971
|
+
/** Return a broad baseline after the complete entry has been validated. */
|
|
3972
|
+
function broadWatchInputBaseline(baseline) {
|
|
3973
|
+
return "directoryExists" in baseline ? baseline : undefined;
|
|
3974
|
+
}
|
|
3145
3975
|
/**
|
|
3146
3976
|
* Re-check a cached mixed graph/dependency input set with its owning codec,
|
|
3147
3977
|
* reusing the recorded hash of any input whose metadata signature still holds
|
|
3148
|
-
* and reporting the signatures
|
|
3978
|
+
* under a freshly minted same-device reference and reporting the signatures
|
|
3979
|
+
* this pass captured.
|
|
3149
3980
|
*
|
|
3150
3981
|
* The caller adopts those signatures only once every input is proven unchanged,
|
|
3151
3982
|
* so a signature never outlives the content comparison that justified it.
|
|
@@ -3168,6 +3999,13 @@ function matchesCachedExternalInputs(cached) {
|
|
|
3168
3999
|
Object.keys(cached.externalInputHashes ?? {})) {
|
|
3169
4000
|
const identity = derivationIdentity(state, file);
|
|
3170
4001
|
const spelling = path.resolve(file);
|
|
4002
|
+
const observation = cached.externalInputObservations?.[spelling];
|
|
4003
|
+
if (observation !== undefined) {
|
|
4004
|
+
if (!matchesGraphInputObservation(file, observation, filesystem, state.identityContext)) {
|
|
4005
|
+
matches = false;
|
|
4006
|
+
}
|
|
4007
|
+
continue;
|
|
4008
|
+
}
|
|
3171
4009
|
// Reuse the recorded hash of an out-of-walk input whose signature still
|
|
3172
4010
|
// equals the one captured around the read that proved it. The signature is
|
|
3173
4011
|
// keyed by this exact spelling, so an alias of the same physical file
|
|
@@ -3176,6 +4014,7 @@ function matchesCachedExternalInputs(cached) {
|
|
|
3176
4014
|
if (before !== undefined &&
|
|
3177
4015
|
Object.prototype.hasOwnProperty.call(recordedSignatures, spelling) &&
|
|
3178
4016
|
Object.prototype.hasOwnProperty.call(recordedHashes, identity) &&
|
|
4017
|
+
before.separable &&
|
|
3179
4018
|
before.signature === recordedSignatures[spelling]) {
|
|
3180
4019
|
continue;
|
|
3181
4020
|
}
|
|
@@ -3221,6 +4060,13 @@ function selectExternalInputPaths(props) {
|
|
|
3221
4060
|
const identities = createHostPathIdentityContext(filesystem);
|
|
3222
4061
|
const resolutionCandidates = new Set();
|
|
3223
4062
|
const graph = props.result.graph;
|
|
4063
|
+
const graphState = envelopeDerivation({
|
|
4064
|
+
result: props.result,
|
|
4065
|
+
});
|
|
4066
|
+
const graphIndexes = envelopeGraphIndexes(graphState, {
|
|
4067
|
+
projectRoot: props.projectRoot,
|
|
4068
|
+
result: props.result,
|
|
4069
|
+
});
|
|
3224
4070
|
// Every transform output key names the source file whose transformed text it
|
|
3225
4071
|
// carries. Keep an out-of-walk source in the external snapshot instead of
|
|
3226
4072
|
// injecting it into the project-walk key universe (samchon/ttsc#252).
|
|
@@ -3232,7 +4078,11 @@ function selectExternalInputPaths(props) {
|
|
|
3232
4078
|
members.push(...targets);
|
|
3233
4079
|
}
|
|
3234
4080
|
}
|
|
3235
|
-
for (const listed of [
|
|
4081
|
+
for (const listed of [
|
|
4082
|
+
graph.globals,
|
|
4083
|
+
graph.configs,
|
|
4084
|
+
graph.resolutionInputs,
|
|
4085
|
+
]) {
|
|
3236
4086
|
if (Array.isArray(listed)) {
|
|
3237
4087
|
members.push(...listed);
|
|
3238
4088
|
}
|
|
@@ -3247,7 +4097,12 @@ function selectExternalInputPaths(props) {
|
|
|
3247
4097
|
}
|
|
3248
4098
|
const absolute = path.resolve(props.projectRoot, candidate);
|
|
3249
4099
|
members.push(candidate);
|
|
3250
|
-
resolutionCandidates.add(
|
|
4100
|
+
resolutionCandidates.add(path.resolve(absolute));
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
for (const input of graph.resolutionInputs ?? []) {
|
|
4104
|
+
if (typeof input === "string" && input.length !== 0) {
|
|
4105
|
+
resolutionCandidates.add(path.resolve(props.projectRoot, input));
|
|
3251
4106
|
}
|
|
3252
4107
|
}
|
|
3253
4108
|
}
|
|
@@ -3263,7 +4118,7 @@ function selectExternalInputPaths(props) {
|
|
|
3263
4118
|
// Plugin discovery inputs deliberately include absent config and
|
|
3264
4119
|
// resolution probes. A project walk cannot snapshot a path that does
|
|
3265
4120
|
// not exist yet, even when its spelling lies below projectRoot.
|
|
3266
|
-
resolutionCandidates.add(
|
|
4121
|
+
resolutionCandidates.add(path.resolve(props.projectRoot, input));
|
|
3267
4122
|
}
|
|
3268
4123
|
}
|
|
3269
4124
|
}
|
|
@@ -3279,7 +4134,10 @@ function selectExternalInputPaths(props) {
|
|
|
3279
4134
|
const absolute = path.resolve(props.projectRoot, member);
|
|
3280
4135
|
const spelling = path.resolve(absolute);
|
|
3281
4136
|
const identity = pathIdentityKey(absolute, identities);
|
|
3282
|
-
const
|
|
4137
|
+
const observation = graphIndexes.inputObservations.get(spelling);
|
|
4138
|
+
const missingCandidate = resolutionCandidates.has(spelling) &&
|
|
4139
|
+
(observation?.fileExists === false ||
|
|
4140
|
+
(observation === undefined && !filesystem.exists(absolute)));
|
|
3283
4141
|
if (identity === excluded ||
|
|
3284
4142
|
isTransformScratchInput(absolute, props.scratchDirectory) ||
|
|
3285
4143
|
seen.has(spelling) ||
|
|
@@ -3322,6 +4180,13 @@ function selectNotifiableAbsentInputs(props) {
|
|
|
3322
4180
|
return empty;
|
|
3323
4181
|
}
|
|
3324
4182
|
const identities = createHostPathIdentityContext(props.filesystem);
|
|
4183
|
+
const graphState = envelopeDerivation({
|
|
4184
|
+
result: props.result,
|
|
4185
|
+
});
|
|
4186
|
+
const graphIndexes = envelopeGraphIndexes(graphState, {
|
|
4187
|
+
projectRoot: props.projectRoot,
|
|
4188
|
+
result: props.result,
|
|
4189
|
+
});
|
|
3325
4190
|
const excluded = props.temporaryTsconfig === undefined
|
|
3326
4191
|
? undefined
|
|
3327
4192
|
: pathIdentityKey(props.temporaryTsconfig, identities);
|
|
@@ -3334,7 +4199,10 @@ function selectNotifiableAbsentInputs(props) {
|
|
|
3334
4199
|
// them. Sharing a set would let one silently answer for the other.
|
|
3335
4200
|
const seen = new Set();
|
|
3336
4201
|
const chain = new Set();
|
|
3337
|
-
for (const candidates of
|
|
4202
|
+
for (const candidates of [
|
|
4203
|
+
...Object.values(graph.candidates ?? {}),
|
|
4204
|
+
graph.resolutionInputs ?? [],
|
|
4205
|
+
]) {
|
|
3338
4206
|
if (!Array.isArray(candidates)) {
|
|
3339
4207
|
continue;
|
|
3340
4208
|
}
|
|
@@ -3344,11 +4212,14 @@ function selectNotifiableAbsentInputs(props) {
|
|
|
3344
4212
|
}
|
|
3345
4213
|
const absolute = path.resolve(props.projectRoot, candidate);
|
|
3346
4214
|
const spelling = path.resolve(absolute);
|
|
4215
|
+
const observation = graphIndexes.inputObservations.get(spelling);
|
|
4216
|
+
const absentAsFile = observation?.fileExists === false ||
|
|
4217
|
+
(observation === undefined && !props.filesystem.exists(absolute));
|
|
3347
4218
|
if (seen.has(spelling) ||
|
|
3348
4219
|
isTransformScratchInput(absolute, props.scratchDirectory) ||
|
|
3349
4220
|
(excluded !== undefined &&
|
|
3350
4221
|
pathIdentityKey(absolute, identities) === excluded) ||
|
|
3351
|
-
|
|
4222
|
+
!absentAsFile) {
|
|
3352
4223
|
continue;
|
|
3353
4224
|
}
|
|
3354
4225
|
seen.add(spelling);
|
|
@@ -3446,19 +4317,6 @@ function insideProject(directory, projectRoot) {
|
|
|
3446
4317
|
* number; the bound stays sound and is merely not tight.
|
|
3447
4318
|
*/
|
|
3448
4319
|
const NOTIFIABLE_ABSENCE_DIRECTORY_LIMIT = 512;
|
|
3449
|
-
function isIgnoredProjectDirectory(name) {
|
|
3450
|
-
// The residue of what used to be a fifteen-name list, kept to the three
|
|
3451
|
-
// directories no tsconfig can name and no program can contain: the VCS
|
|
3452
|
-
// store, the package manager's tree (TypeScript's own default `exclude`
|
|
3453
|
-
// carries it too), and ttsc's own plugin cache. Everything else the old list
|
|
3454
|
-
// guessed at, and guessing was wrong in both directions: a bundler writing
|
|
3455
|
-
// to an unnamed directory changed project membership with its own output,
|
|
3456
|
-
// while a real source directory named `build` or `temp` was dropped from the
|
|
3457
|
-
// walk and its new files were never seen (samchon/ttsc#1307). Those are now
|
|
3458
|
-
// decided by `ITtscProjectMembershipPolicy`, which reads the configuration
|
|
3459
|
-
// that actually knows.
|
|
3460
|
-
return name === ".git" || name === ".ttsc" || name === "node_modules";
|
|
3461
|
-
}
|
|
3462
4320
|
/**
|
|
3463
4321
|
* Whether the resolved configuration keeps this directory out of the program.
|
|
3464
4322
|
*
|
|
@@ -3683,6 +4541,9 @@ function selectDeclaredProjectInputKeys(props) {
|
|
|
3683
4541
|
if (Array.isArray(graph.configs))
|
|
3684
4542
|
for (const input of graph.configs)
|
|
3685
4543
|
add(input);
|
|
4544
|
+
if (Array.isArray(graph.resolutionInputs))
|
|
4545
|
+
for (const input of graph.resolutionInputs)
|
|
4546
|
+
add(input);
|
|
3686
4547
|
for (const [source, candidates] of Object.entries(graph.candidates ?? {})) {
|
|
3687
4548
|
add(source);
|
|
3688
4549
|
if (Array.isArray(candidates))
|
|
@@ -3896,6 +4757,10 @@ function createUnstableGenerationError(projectRoot, attempts, validation) {
|
|
|
3896
4757
|
}
|
|
3897
4758
|
});
|
|
3898
4759
|
lines.push(" Stop writes to the listed inputs before compilation, or fix the producer that omitted or contradicted the listed proof.");
|
|
4760
|
+
// The failed snapshot remains useful as immutable retry evidence, but it can
|
|
4761
|
+
// never serve a module. Release every live resource before its terminal
|
|
4762
|
+
// verdict is retained in the cache.
|
|
4763
|
+
disposeCachedTransform(validation.cached);
|
|
3899
4764
|
return new TtscUnstableGenerationError(lines.join("\n"), validation);
|
|
3900
4765
|
}
|
|
3901
4766
|
function hashText(input) {
|
|
@@ -3905,9 +4770,10 @@ async function transformProject(props) {
|
|
|
3905
4770
|
const attempts = [];
|
|
3906
4771
|
for (let attempt = 0; attempt < TRANSFORM_GENERATION_ATTEMPTS; attempt += 1) {
|
|
3907
4772
|
const cached = await captureTransformGeneration(props);
|
|
3908
|
-
if (
|
|
3909
|
-
|
|
3910
|
-
|
|
4773
|
+
if (cached.configStateComplete !== false &&
|
|
4774
|
+
(!props.trackProjectMembership ||
|
|
4775
|
+
cached.result.type !== "success" ||
|
|
4776
|
+
cached.projectSnapshotComplete === true)) {
|
|
3911
4777
|
return cached;
|
|
3912
4778
|
}
|
|
3913
4779
|
attempts.push(TRANSFORM_GENERATION_FAILURES.get(cached.result) ??
|
|
@@ -3928,21 +4794,43 @@ async function transformProject(props) {
|
|
|
3928
4794
|
async function captureTransformGeneration(props) {
|
|
3929
4795
|
const projectRoot = path.dirname(props.tsconfig);
|
|
3930
4796
|
const scratchDirectory = createTransformScratchDirectory(projectRoot, props.filesystem);
|
|
4797
|
+
let clockReferenceDirectory;
|
|
4798
|
+
let retainClockReferenceDirectory = false;
|
|
3931
4799
|
let tracker;
|
|
3932
4800
|
let retainTracker = false;
|
|
3933
4801
|
let hostInputTracker;
|
|
3934
4802
|
let candidateTracker;
|
|
3935
4803
|
let retainHostInputTracker = false;
|
|
3936
4804
|
let retainCandidateTracker = false;
|
|
4805
|
+
let captured;
|
|
3937
4806
|
try {
|
|
3938
|
-
|
|
4807
|
+
if (props.trackProjectMembership) {
|
|
4808
|
+
try {
|
|
4809
|
+
clockReferenceDirectory = createTransformScratchDirectory(projectRoot, props.filesystem);
|
|
4810
|
+
}
|
|
4811
|
+
catch {
|
|
4812
|
+
// A retained probe is an optimization. The live compiler scratch can
|
|
4813
|
+
// still authorize capture, and later validations will compare bytes.
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
const materializesConfig = Object.keys(props.compilerOptions).length !== 0 ||
|
|
4817
|
+
Object.keys(props.aliasPaths).length !== 0;
|
|
4818
|
+
const tsconfigState = readTransformTsconfigState(props.tsconfig, materializesConfig);
|
|
4819
|
+
const configured = createTransformTsconfig(props, scratchDirectory, tsconfigState);
|
|
3939
4820
|
const temporaryTsconfig = configured.path === props.tsconfig ? undefined : configured.path;
|
|
4821
|
+
const compilerEnvironment = transformScratchEnvironment(scratchDirectory);
|
|
4822
|
+
if (temporaryTsconfig === undefined) {
|
|
4823
|
+
delete compilerEnvironment[TTSC_SEMANTIC_CONFIG_PATH];
|
|
4824
|
+
}
|
|
4825
|
+
else {
|
|
4826
|
+
compilerEnvironment[TTSC_SEMANTIC_CONFIG_PATH] = props.tsconfig;
|
|
4827
|
+
}
|
|
3940
4828
|
const identities = createHostPathIdentityContext(props.filesystem);
|
|
3941
4829
|
// Read from the project's own tsconfig rather than the generated one: a
|
|
3942
4830
|
// relative `outDir` is anchored at the config that declares it, and the
|
|
3943
4831
|
// generated config lives in a system temp directory. The caller's
|
|
3944
4832
|
// compiler-options overlay still wins, since it wins for the compile too.
|
|
3945
|
-
const membershipPolicy = mergeMembershipPolicyOverlay(
|
|
4833
|
+
const membershipPolicy = mergeMembershipPolicyOverlay(tsconfigState.membershipPolicy, props.compilerOptions, projectRoot);
|
|
3946
4834
|
const before = collectProjectInputSnapshot(projectRoot, identities, props.filesystem, undefined, { policy: membershipPolicy });
|
|
3947
4835
|
tracker = props.trackProjectMembership
|
|
3948
4836
|
? await createProjectMutationTracker(before.projectDirectories, props.filesystem, membershipPolicy)
|
|
@@ -3959,13 +4847,16 @@ async function captureTransformGeneration(props) {
|
|
|
3959
4847
|
plugins: props.plugins,
|
|
3960
4848
|
projectRoot,
|
|
3961
4849
|
tsconfig: configured.path,
|
|
3962
|
-
env:
|
|
4850
|
+
env: compilerEnvironment,
|
|
3963
4851
|
}).transform());
|
|
3964
4852
|
TRANSFORM_RESULT_FILESYSTEM.set(result, props.filesystem);
|
|
4853
|
+
const configStable = tsconfigState.signature === undefined ||
|
|
4854
|
+
tsconfigState.signature ===
|
|
4855
|
+
readTransformTsconfigState(props.tsconfig, true).signature;
|
|
3965
4856
|
// Mint the generation's clock reference after the compile and before any
|
|
3966
4857
|
// signature-recording read below, so every input written before the
|
|
3967
4858
|
// compile sits in a provably finished tick when its signature is captured.
|
|
3968
|
-
|
|
4859
|
+
refreshFilesystemClockReference(clockReferenceDirectory ?? scratchDirectory, props.filesystem);
|
|
3969
4860
|
const persistentHostInputs = selectPersistentHostInputs({
|
|
3970
4861
|
filesystem: props.filesystem,
|
|
3971
4862
|
projectRoot,
|
|
@@ -4028,7 +4919,8 @@ async function captureTransformGeneration(props) {
|
|
|
4028
4919
|
result,
|
|
4029
4920
|
scratchDirectory,
|
|
4030
4921
|
});
|
|
4031
|
-
const walkStable =
|
|
4922
|
+
const walkStable = configStable &&
|
|
4923
|
+
walkSnapshotComplete(before, declaredInputs) &&
|
|
4032
4924
|
walkSnapshotComplete(inputSnapshot, declaredInputs) &&
|
|
4033
4925
|
sameHashes(before.hashes, inputSnapshot.hashes, declaredInputs) &&
|
|
4034
4926
|
sameHashes(before.fileSignatures, inputSnapshot.fileSignatures, declaredInputs) &&
|
|
@@ -4064,6 +4956,7 @@ async function captureTransformGeneration(props) {
|
|
|
4064
4956
|
externalInputHashes: {},
|
|
4065
4957
|
externalInputRealpaths: {},
|
|
4066
4958
|
externalInputPaths,
|
|
4959
|
+
configStateComplete: configStable,
|
|
4067
4960
|
inputHashes: inputSnapshot.hashes,
|
|
4068
4961
|
inputSignatures: inputSnapshot.provenSignatures,
|
|
4069
4962
|
membershipPolicy,
|
|
@@ -4082,6 +4975,7 @@ async function captureTransformGeneration(props) {
|
|
|
4082
4975
|
cached.sourceHashes = captureTransformSourceHashes(cached, props.currentFile, currentSourceHash);
|
|
4083
4976
|
const externalInputSnapshot = captureExternalInputSnapshot(cached, externalInputPaths);
|
|
4084
4977
|
cached.externalInputHashes = externalInputSnapshot.hashes;
|
|
4978
|
+
cached.externalInputObservations = externalInputSnapshot.observations;
|
|
4085
4979
|
cached.externalInputRealpaths = externalInputSnapshot.realpaths;
|
|
4086
4980
|
cached.externalInputSignatures = externalInputSnapshot.signatures;
|
|
4087
4981
|
// Evaluate every half, rather than short-circuiting, so a generation that
|
|
@@ -4089,6 +4983,13 @@ async function captureTransformGeneration(props) {
|
|
|
4089
4983
|
// only on the failing path, where the alternative is recompiling the whole
|
|
4090
4984
|
// project for every remaining module.
|
|
4091
4985
|
const failures = createGenerationProofFailures();
|
|
4986
|
+
if (!configStable) {
|
|
4987
|
+
recordGenerationProofFailure(failures, {
|
|
4988
|
+
domain: "project",
|
|
4989
|
+
kind: "config-state-changed",
|
|
4990
|
+
path: props.tsconfig,
|
|
4991
|
+
});
|
|
4992
|
+
}
|
|
4092
4993
|
if (!walkStable) {
|
|
4093
4994
|
recordProjectSnapshotFailures(failures, {
|
|
4094
4995
|
before,
|
|
@@ -4144,32 +5045,80 @@ async function captureTransformGeneration(props) {
|
|
|
4144
5045
|
retainTracker = notifying && tracker !== undefined;
|
|
4145
5046
|
retainHostInputTracker = notifying && hostInputTracker !== undefined;
|
|
4146
5047
|
retainCandidateTracker = notifying && candidateTracker !== undefined;
|
|
4147
|
-
|
|
5048
|
+
if (clockReferenceDirectory !== undefined) {
|
|
5049
|
+
retainClockReferenceDirectory = true;
|
|
5050
|
+
}
|
|
5051
|
+
captured = cached;
|
|
4148
5052
|
}
|
|
4149
5053
|
finally {
|
|
5054
|
+
let cleanupFailed = false;
|
|
5055
|
+
let cleanupFailure;
|
|
4150
5056
|
try {
|
|
4151
|
-
if (!retainTracker && tracker !== undefined) {
|
|
4152
|
-
tracker.close();
|
|
4153
|
-
}
|
|
4154
|
-
}
|
|
4155
|
-
finally {
|
|
4156
5057
|
try {
|
|
4157
|
-
if (!
|
|
4158
|
-
|
|
5058
|
+
if (!retainTracker && tracker !== undefined) {
|
|
5059
|
+
tracker.close();
|
|
4159
5060
|
}
|
|
4160
5061
|
}
|
|
4161
5062
|
finally {
|
|
4162
5063
|
try {
|
|
4163
|
-
if (!
|
|
4164
|
-
|
|
5064
|
+
if (!retainHostInputTracker && hostInputTracker !== undefined) {
|
|
5065
|
+
hostInputTracker.close();
|
|
4165
5066
|
}
|
|
4166
5067
|
}
|
|
4167
5068
|
finally {
|
|
4168
|
-
|
|
5069
|
+
try {
|
|
5070
|
+
if (!retainCandidateTracker && candidateTracker !== undefined) {
|
|
5071
|
+
candidateTracker.close();
|
|
5072
|
+
}
|
|
5073
|
+
}
|
|
5074
|
+
finally {
|
|
5075
|
+
try {
|
|
5076
|
+
fs.rmSync(scratchDirectory, { force: true, recursive: true });
|
|
5077
|
+
}
|
|
5078
|
+
finally {
|
|
5079
|
+
if (!retainClockReferenceDirectory &&
|
|
5080
|
+
clockReferenceDirectory !== undefined) {
|
|
5081
|
+
disposeFilesystemClockReference(clockReferenceDirectory);
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
4169
5085
|
}
|
|
4170
5086
|
}
|
|
4171
5087
|
}
|
|
5088
|
+
catch (error) {
|
|
5089
|
+
cleanupFailed = true;
|
|
5090
|
+
cleanupFailure = error;
|
|
5091
|
+
}
|
|
5092
|
+
if (cleanupFailed) {
|
|
5093
|
+
// The generation never leaves this function when local cleanup fails.
|
|
5094
|
+
// Close everything that was waiting to transfer, without letting a
|
|
5095
|
+
// secondary teardown error replace the first failure.
|
|
5096
|
+
for (const retained of [
|
|
5097
|
+
retainTracker ? tracker : undefined,
|
|
5098
|
+
retainHostInputTracker ? hostInputTracker : undefined,
|
|
5099
|
+
retainCandidateTracker ? candidateTracker : undefined,
|
|
5100
|
+
]) {
|
|
5101
|
+
try {
|
|
5102
|
+
retained?.close();
|
|
5103
|
+
}
|
|
5104
|
+
catch {
|
|
5105
|
+
// Continue releasing the other generation-owned resources.
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
if (retainClockReferenceDirectory &&
|
|
5109
|
+
clockReferenceDirectory !== undefined) {
|
|
5110
|
+
disposeFilesystemClockReference(clockReferenceDirectory);
|
|
5111
|
+
}
|
|
5112
|
+
throw cleanupFailure;
|
|
5113
|
+
}
|
|
4172
5114
|
}
|
|
5115
|
+
if (captured === undefined) {
|
|
5116
|
+
throw new Error("ttsc: transform generation capture produced no result");
|
|
5117
|
+
}
|
|
5118
|
+
if (clockReferenceDirectory !== undefined) {
|
|
5119
|
+
TRANSFORM_CLOCK_REFERENCE_DIRECTORIES.set(captured, clockReferenceDirectory);
|
|
5120
|
+
}
|
|
5121
|
+
return captured;
|
|
4173
5122
|
}
|
|
4174
5123
|
/** Exclude disposed transform scratch from live host-input tracking. */
|
|
4175
5124
|
function selectPersistentHostInputs(props) {
|
|
@@ -4189,17 +5138,55 @@ function selectPersistentHostInputs(props) {
|
|
|
4189
5138
|
return pathIdentityKey(input, identities) !== temporary;
|
|
4190
5139
|
});
|
|
4191
5140
|
}
|
|
4192
|
-
|
|
4193
|
-
|
|
5141
|
+
/** Read every wrapper-dependent view and bind it to one config-chain state. */
|
|
5142
|
+
function readTransformTsconfigState(tsconfig, materializesConfig) {
|
|
5143
|
+
const membershipPolicy = readProjectMembershipPolicy(tsconfig);
|
|
5144
|
+
if (!materializesConfig) {
|
|
5145
|
+
return {
|
|
5146
|
+
effectivePaths: {},
|
|
5147
|
+
membershipPolicy,
|
|
5148
|
+
templateCompilerOptions: {},
|
|
5149
|
+
templateFileSpecs: {},
|
|
5150
|
+
};
|
|
5151
|
+
}
|
|
5152
|
+
const effectivePaths = readEffectiveTsconfigPaths(tsconfig);
|
|
5153
|
+
const templateCompilerOptions = readEffectiveTsconfigTemplateCompilerOptions(tsconfig);
|
|
5154
|
+
const templateFileSpecs = readEffectiveTsconfigTemplateFileSpecs(tsconfig);
|
|
5155
|
+
const sources = readTsconfigSourceSnapshot(tsconfig);
|
|
5156
|
+
return {
|
|
5157
|
+
effectivePaths,
|
|
5158
|
+
membershipPolicy,
|
|
5159
|
+
signature: hashText(JSON.stringify({
|
|
5160
|
+
effectivePaths,
|
|
5161
|
+
membershipPolicy,
|
|
5162
|
+
sources,
|
|
5163
|
+
templateCompilerOptions,
|
|
5164
|
+
templateFileSpecs,
|
|
5165
|
+
})),
|
|
5166
|
+
templateCompilerOptions,
|
|
5167
|
+
templateFileSpecs,
|
|
5168
|
+
};
|
|
5169
|
+
}
|
|
5170
|
+
function createTransformTsconfig(props, scratchDirectory, state) {
|
|
5171
|
+
const overlay = normalizeCompilerOptionsForGeneratedTsconfig({
|
|
4194
5172
|
...props.compilerOptions,
|
|
4195
|
-
...createAliasCompilerOptions(props),
|
|
5173
|
+
...createAliasCompilerOptions(props, state.effectivePaths),
|
|
4196
5174
|
}, path.dirname(props.tsconfig));
|
|
4197
|
-
if (Object.keys(
|
|
5175
|
+
if (Object.keys(overlay).length === 0) {
|
|
4198
5176
|
return { path: props.tsconfig };
|
|
4199
5177
|
}
|
|
5178
|
+
// A `${configDir}` value survives every `extends` hop and binds only at the
|
|
5179
|
+
// final consumer. The scratch wrapper would therefore move it out of the
|
|
5180
|
+
// project even for an unrelated overlay. Re-state only those inherited
|
|
5181
|
+
// values as absolute paths so the wrapper remains semantically transparent.
|
|
5182
|
+
const compilerOptions = {
|
|
5183
|
+
...state.templateCompilerOptions,
|
|
5184
|
+
...overlay,
|
|
5185
|
+
};
|
|
4200
5186
|
const file = path.join(scratchDirectory, "tsconfig.json");
|
|
4201
5187
|
fs.writeFileSync(file, JSON.stringify({
|
|
4202
5188
|
extends: normalizePath(props.tsconfig),
|
|
5189
|
+
...state.templateFileSpecs,
|
|
4203
5190
|
compilerOptions,
|
|
4204
5191
|
}, null, 2), "utf8");
|
|
4205
5192
|
return { path: file };
|
|
@@ -4332,15 +5319,17 @@ function withTransformScratchEnvironment(scratchDirectory, callback) {
|
|
|
4332
5319
|
function normalizeCompilerOptionsForGeneratedTsconfig(compilerOptions, tsconfigDir) {
|
|
4333
5320
|
const output = { ...compilerOptions };
|
|
4334
5321
|
// Scalar path fields: resolve each against the original tsconfig directory.
|
|
4335
|
-
for (const key of
|
|
5322
|
+
for (const key of CONFIG_DIR_TEMPLATE_SCALAR_OPTIONS) {
|
|
4336
5323
|
if (typeof output[key] === "string") {
|
|
4337
|
-
output[key] =
|
|
5324
|
+
output[key] = resolveConfigDirTemplatePath(tsconfigDir, output[key]);
|
|
4338
5325
|
}
|
|
4339
5326
|
}
|
|
4340
5327
|
// Array path fields: resolve each element individually.
|
|
4341
|
-
for (const key of
|
|
5328
|
+
for (const key of CONFIG_DIR_TEMPLATE_LIST_OPTIONS) {
|
|
4342
5329
|
if (Array.isArray(output[key])) {
|
|
4343
|
-
output[key] = output[key].map((entry) => typeof entry === "string"
|
|
5330
|
+
output[key] = output[key].map((entry) => typeof entry === "string"
|
|
5331
|
+
? resolveConfigDirTemplatePath(tsconfigDir, entry)
|
|
5332
|
+
: entry);
|
|
4344
5333
|
}
|
|
4345
5334
|
}
|
|
4346
5335
|
const paths = readPaths(output.paths);
|
|
@@ -4390,13 +5379,13 @@ function normalizePluginConfigForGeneratedTsconfig(entry, tsconfigDir) {
|
|
|
4390
5379
|
* No `baseUrl` is emitted: TypeScript-Go removed the option (TS5102), and all
|
|
4391
5380
|
* targets are absolute so none is needed.
|
|
4392
5381
|
*/
|
|
4393
|
-
function createAliasCompilerOptions(props) {
|
|
5382
|
+
function createAliasCompilerOptions(props, effectivePaths) {
|
|
4394
5383
|
if (Object.keys(props.aliasPaths).length === 0) {
|
|
4395
5384
|
return {};
|
|
4396
5385
|
}
|
|
4397
5386
|
return {
|
|
4398
5387
|
paths: {
|
|
4399
|
-
...
|
|
5388
|
+
...effectivePaths,
|
|
4400
5389
|
...readPaths(props.compilerOptions.paths),
|
|
4401
5390
|
...props.aliasPaths,
|
|
4402
5391
|
},
|
|
@@ -4718,9 +5707,9 @@ function stripTerminalEscapes(text) {
|
|
|
4718
5707
|
*
|
|
4719
5708
|
* If `tsconfig` is supplied it is returned as-is (absolute) or resolved from
|
|
4720
5709
|
* `process.cwd()` (relative). Otherwise the function walks ancestor directories
|
|
4721
|
-
* starting at `file`'s directory, returning the first `tsconfig.json`
|
|
4722
|
-
* Falls back to `<cwd>/tsconfig.json` when no ancestor contains one;
|
|
4723
|
-
* compiler will error if that file does not exist, which is the correct
|
|
5710
|
+
* starting at `file`'s directory, returning the first `tsconfig.json` proven to
|
|
5711
|
+
* be a file. Falls back to `<cwd>/tsconfig.json` when no ancestor contains one;
|
|
5712
|
+
* the compiler will error if that file does not exist, which is the correct
|
|
4724
5713
|
* behavior for a mis-configured project.
|
|
4725
5714
|
*/
|
|
4726
5715
|
function resolveTsconfig(file, tsconfig, filesystem = DEFAULT_FILESYSTEM_OPERATIONS) {
|
|
@@ -4729,18 +5718,9 @@ function resolveTsconfig(file, tsconfig, filesystem = DEFAULT_FILESYSTEM_OPERATI
|
|
|
4729
5718
|
? tsconfig
|
|
4730
5719
|
: path.resolve(process.cwd(), tsconfig);
|
|
4731
5720
|
}
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
if (filesystem.exists(candidate)) {
|
|
4736
|
-
return candidate;
|
|
4737
|
-
}
|
|
4738
|
-
const parent = path.dirname(current);
|
|
4739
|
-
// Reached filesystem root, stop walking.
|
|
4740
|
-
if (parent === current) {
|
|
4741
|
-
break;
|
|
4742
|
-
}
|
|
4743
|
-
current = parent;
|
|
5721
|
+
const discovered = findNearestProjectTsconfig(path.dirname(file), filesystem);
|
|
5722
|
+
if (discovered !== undefined) {
|
|
5723
|
+
return discovered;
|
|
4744
5724
|
}
|
|
4745
5725
|
return path.resolve(process.cwd(), "tsconfig.json");
|
|
4746
5726
|
}
|
|
@@ -4764,5 +5744,5 @@ function normalizePath(file) {
|
|
|
4764
5744
|
return file.replace(/\\/g, "/");
|
|
4765
5745
|
}
|
|
4766
5746
|
|
|
4767
|
-
export { beginTtscTransformBuild, collectExternalInputHashes, collectProjectInputHashes, createTransformResult, createTtscTransformCache, isDeclarationFile, isProjectWalkPath, normalizeHostInputName, pathIdentityKey, resetTtscTransformCache, stripQuery, transformTtsc };
|
|
5747
|
+
export { beginTtscTransformBuild, captureWatchInputBaseline, captureWatchInputFileBaseline, collectExternalInputHashes, collectProjectInputHashSnapshot, collectProjectInputHashes, createTransformResult, createTtscTransformCache, isDeclarationFile, isProjectWalkPath, isWatchInputKeyBaseline, normalizeHostInputName, pathIdentityKey, resetTtscTransformCache, stripQuery, transformTtsc, validateGraphInputObservation, watchInputEvidenceMatchesBaseline };
|
|
4768
5748
|
//# sourceMappingURL=transform.mjs.map
|