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