@ttsc/unplugin 0.32.0 → 0.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/core/options/ResolvedTtscUnpluginOptions.d.cts +2 -0
- package/lib/core/options/ResolvedTtscUnpluginOptions.d.mts +2 -0
- package/lib/core/options/ResolvedTtscUnpluginOptions.d.ts +2 -0
- package/lib/core/options/TtscUnpluginOptions.d.cts +12 -2
- package/lib/core/options/TtscUnpluginOptions.d.mts +12 -2
- package/lib/core/options/TtscUnpluginOptions.d.ts +12 -2
- package/lib/core/options/resolveOptions.d.cts +4 -3
- package/lib/core/options/resolveOptions.d.mts +4 -3
- package/lib/core/options/resolveOptions.d.ts +4 -3
- package/lib/core/options/resolveOptions.js +5 -3
- package/lib/core/options/resolveOptions.js.map +1 -1
- package/lib/core/options/resolveOptions.mjs +5 -3
- package/lib/core/options/resolveOptions.mjs.map +1 -1
- package/lib/core/transform/TtscPrecompilation.d.cts +52 -0
- package/lib/core/transform/TtscPrecompilation.d.mts +52 -0
- package/lib/core/transform/TtscPrecompilation.d.ts +52 -0
- package/lib/core/transform/TtscPrecompilation.js +101 -0
- package/lib/core/transform/TtscPrecompilation.js.map +1 -0
- package/lib/core/transform/TtscPrecompilation.mjs +101 -0
- package/lib/core/transform/TtscPrecompilation.mjs.map +1 -0
- package/lib/core/transform/cache/selectCachedGenerationAction.d.cts +2 -0
- package/lib/core/transform/cache/selectCachedGenerationAction.d.mts +2 -0
- package/lib/core/transform/cache/selectCachedGenerationAction.d.ts +2 -0
- package/lib/core/transform/cache/selectCachedGenerationAction.js +1 -1
- package/lib/core/transform/cache/selectCachedGenerationAction.js.map +1 -1
- package/lib/core/transform/cache/selectCachedGenerationAction.mjs +1 -1
- package/lib/core/transform/cache/selectCachedGenerationAction.mjs.map +1 -1
- package/lib/core/transform/cache/withdrawTtscTransformGenerations.js +2 -0
- package/lib/core/transform/cache/withdrawTtscTransformGenerations.js.map +1 -1
- package/lib/core/transform/cache/withdrawTtscTransformGenerations.mjs +2 -0
- package/lib/core/transform/cache/withdrawTtscTransformGenerations.mjs.map +1 -1
- package/lib/core/transform/generation/captureTransformGeneration.d.cts +2 -0
- package/lib/core/transform/generation/captureTransformGeneration.d.mts +2 -0
- package/lib/core/transform/generation/captureTransformGeneration.d.ts +2 -0
- package/lib/core/transform/generation/captureTransformGeneration.js +7 -2
- package/lib/core/transform/generation/captureTransformGeneration.js.map +1 -1
- package/lib/core/transform/generation/captureTransformGeneration.mjs +7 -2
- package/lib/core/transform/generation/captureTransformGeneration.mjs.map +1 -1
- package/lib/core/transform/generation/transformProject.d.cts +2 -0
- package/lib/core/transform/generation/transformProject.d.mts +2 -0
- package/lib/core/transform/generation/transformProject.d.ts +2 -0
- package/lib/core/transform/generation/transformProject.js.map +1 -1
- package/lib/core/transform/generation/transformProject.mjs.map +1 -1
- package/lib/core/transform/transformTtsc.d.cts +8 -24
- package/lib/core/transform/transformTtsc.d.mts +8 -24
- package/lib/core/transform/transformTtsc.d.ts +8 -24
- package/lib/core/transform/transformTtsc.js +51 -31
- package/lib/core/transform/transformTtsc.js.map +1 -1
- package/lib/core/transform/transformTtsc.mjs +51 -31
- package/lib/core/transform/transformTtsc.mjs.map +1 -1
- package/lib/core/transform/validation/captureUniversalHostInputValidation.d.cts +1 -1
- package/lib/core/transform/validation/captureUniversalHostInputValidation.d.mts +1 -1
- package/lib/core/transform/validation/captureUniversalHostInputValidation.d.ts +1 -1
- package/lib/core/transform/validation/captureUniversalHostInputValidation.js +12 -4
- package/lib/core/transform/validation/captureUniversalHostInputValidation.js.map +1 -1
- package/lib/core/transform/validation/captureUniversalHostInputValidation.mjs +12 -4
- package/lib/core/transform/validation/captureUniversalHostInputValidation.mjs.map +1 -1
- package/lib/core/transform/validation/matchesCachedSource.d.cts +9 -2
- package/lib/core/transform/validation/matchesCachedSource.d.mts +9 -2
- package/lib/core/transform/validation/matchesCachedSource.d.ts +9 -2
- package/lib/core/transform/validation/matchesCachedSource.js +11 -3
- package/lib/core/transform/validation/matchesCachedSource.js.map +1 -1
- package/lib/core/transform/validation/matchesCachedSource.mjs +11 -3
- package/lib/core/transform/validation/matchesCachedSource.mjs.map +1 -1
- package/lib/core/transform/watch/TtscTransformHooks.d.cts +9 -0
- package/lib/core/transform/watch/TtscTransformHooks.d.mts +9 -0
- package/lib/core/transform/watch/TtscTransformHooks.d.ts +9 -0
- package/lib/core/unplugin.js +14 -0
- package/lib/core/unplugin.js.map +1 -1
- package/lib/core/unplugin.mjs +14 -0
- package/lib/core/unplugin.mjs.map +1 -1
- package/lib/core/vite/installViteDependencyScanPlugins.d.cts +46 -0
- package/lib/core/vite/installViteDependencyScanPlugins.d.mts +46 -0
- package/lib/core/vite/installViteDependencyScanPlugins.d.ts +46 -0
- package/lib/core/vite/installViteDependencyScanPlugins.js +186 -0
- package/lib/core/vite/installViteDependencyScanPlugins.js.map +1 -0
- package/lib/core/vite/installViteDependencyScanPlugins.mjs +184 -0
- package/lib/core/vite/installViteDependencyScanPlugins.mjs.map +1 -0
- package/package.json +5 -4
- package/src/core/options/ResolvedTtscUnpluginOptions.ts +3 -0
- package/src/core/options/TtscUnpluginOptions.ts +13 -2
- package/src/core/options/resolveOptions.ts +6 -3
- package/src/core/transform/TtscPrecompilation.ts +103 -0
- package/src/core/transform/cache/selectCachedGenerationAction.ts +3 -0
- package/src/core/transform/cache/withdrawTtscTransformGenerations.ts +2 -0
- package/src/core/transform/generation/captureTransformGeneration.ts +12 -2
- package/src/core/transform/generation/transformProject.ts +3 -0
- package/src/core/transform/transformTtsc.ts +81 -36
- package/src/core/transform/validation/captureUniversalHostInputValidation.ts +12 -4
- package/src/core/transform/validation/matchesCachedSource.ts +9 -2
- package/src/core/transform/watch/TtscTransformHooks.ts +10 -0
- package/src/core/unplugin.ts +32 -0
- package/src/core/vite/installViteDependencyScanPlugins.ts +219 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { isTransformTarget } from "../isTransformTarget";
|
|
4
|
+
import type { ResolvedTtscUnpluginOptions } from "../options/ResolvedTtscUnpluginOptions";
|
|
5
|
+
import type { TtscTransformCache } from "./cache/TtscTransformCache";
|
|
6
|
+
import { transformCacheEpoch } from "./cache/transformCacheEpoch";
|
|
7
|
+
import { transformFilesystem } from "./cache/transformFilesystem";
|
|
8
|
+
import { pluginsAreDisabled } from "./utils/pluginsAreDisabled";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Schedule explicitly selected source entries before the host reaches their
|
|
12
|
+
* imports. Compilation and proof stay with the ordinary transform core.
|
|
13
|
+
*
|
|
14
|
+
* A host often discovers its next workspace project only after the preceding
|
|
15
|
+
* project's transform finishes (#1764). Starting known entries together avoids
|
|
16
|
+
* that dependency on host traversal order. Walking unrelated projects would
|
|
17
|
+
* execute plugins the user never selected, so this owner uses only the supplied
|
|
18
|
+
* entries. Preparation leaves module delivery and host registration to the
|
|
19
|
+
* actual request.
|
|
20
|
+
*
|
|
21
|
+
* @evidence contracts/common.md#principled-implementation Explicit entries authorize scheduling; the supplied core capture retains project selection, configuration identity and all generation proofs. The context contains the current working directory, options, aliases and delivery epoch, not new cache authority.
|
|
22
|
+
* @evidence contracts/common.md#clear-and-simple-design One scheduler owns per-cache startup deduplication and reset; the transform core owns compiled generations and their disposal.
|
|
23
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts No directory-wide discovery, plugin substitution or skipped proof is introduced; preparation never impersonates a host module delivery.
|
|
24
|
+
* @evidence contracts/common.md#meaningful-documentation Native prose explains host traversal order, explicit effectful selection and separation from delivery; members state context lifetime and failure ownership.
|
|
25
|
+
* @evidence contracts/portability.md#os-neutral-implementation Entries resolve through native path semantics from the current cwd and read through the cache's filesystem boundary; source eligibility uses the adapter's existing shared predicate.
|
|
26
|
+
* @evidence contracts/performance.md#efficient-algorithms Each new context serializes cwd, options and aliases, then resolves and deduplicates E entries and reads their source bytes once. Capture starts without waiting for other entries; native capture and proof retain their own costs. Repeated requests serialize the context but do not reread the entries.
|
|
27
|
+
* @evidence contracts/performance.md#reuse-equivalent-work One latest context per cache and epoch starts each entry once; ordinary project keys deduplicate entries in the same project. Context equality controls scheduling only, and retained results still need normal admission.
|
|
28
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources A WeakMap retains one context string per live cache. Startup rejection handlers consume each detached promise; running captures and native resources remain cache-owned and withdrawal disposes their fulfilled generations. No cancellation deadline or entry-count cap is added.
|
|
29
|
+
*/
|
|
30
|
+
export namespace TtscPrecompilation {
|
|
31
|
+
const contexts = new WeakMap<TtscTransformCache, string>();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Start every eligible explicit entry without waiting for its siblings.
|
|
35
|
+
* Failed speculative preparation is reported when its module is requested, so
|
|
36
|
+
* an unrelated delivered project's load can proceed.
|
|
37
|
+
*
|
|
38
|
+
* @evidence contracts/common.md#principled-implementation The context is installed before captures start, so recursive and concurrent deliveries cannot restart the same batch; only supported source entries reach normal core capture.
|
|
39
|
+
* @evidence contracts/common.md#clear-and-simple-design One synchronous loop separates startup ownership from asynchronous capture settlement.
|
|
40
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts Failed entry reads are not fabricated as empty source, and rejected captures do not become successful output. Actual delivery remains the error boundary.
|
|
41
|
+
* @evidence contracts/common.md#meaningful-documentation Prose states parallel startup and deferred error reporting; callback and context ownership remain explicit.
|
|
42
|
+
* @evidence contracts/portability.md#os-neutral-implementation Native absolute entry paths are passed as exact paths to capture and source reads use the same cache-owned filesystem as generation validation.
|
|
43
|
+
* @evidence contracts/performance.md#efficient-algorithms Context serialization precedes one E-entry deduplicating loop and actual source-byte reads; captures overlap their asynchronous work rather than being awaited sequentially.
|
|
44
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Identical latest cwd, options, aliases and epoch skip scheduling; project Promise identity and normal input proof decide compile reuse, independently of this scheduling record.
|
|
45
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources Context state follows its weak cache key. Every started capture receives a rejection handler immediately; its own core/cache owners release native resources, including withdrawal during capture.
|
|
46
|
+
*/
|
|
47
|
+
export function start(
|
|
48
|
+
cache: TtscTransformCache,
|
|
49
|
+
options: ResolvedTtscUnpluginOptions,
|
|
50
|
+
aliasPaths: Record<string, string[]> | undefined,
|
|
51
|
+
capture: (file: string, source: string) => Promise<unknown>,
|
|
52
|
+
): void {
|
|
53
|
+
if (
|
|
54
|
+
options.precompile?.length === 0 ||
|
|
55
|
+
options.precompile === undefined ||
|
|
56
|
+
pluginsAreDisabled(options.plugins)
|
|
57
|
+
)
|
|
58
|
+
return;
|
|
59
|
+
// Entry, project and projectRoot paths all use cwd. The same relative
|
|
60
|
+
// options under another directory select different work, even if the
|
|
61
|
+
// host retains its cache; capture coordinates must belong to this call.
|
|
62
|
+
const cwd = process.cwd();
|
|
63
|
+
const context = JSON.stringify([
|
|
64
|
+
cwd,
|
|
65
|
+
options,
|
|
66
|
+
aliasPaths,
|
|
67
|
+
transformCacheEpoch(cache),
|
|
68
|
+
]);
|
|
69
|
+
if (contexts.get(cache) === context) return;
|
|
70
|
+
contexts.set(cache, context);
|
|
71
|
+
const entries = new Set<string>();
|
|
72
|
+
const filesystem = transformFilesystem(cache);
|
|
73
|
+
for (const entry of options.precompile) {
|
|
74
|
+
if (!isTransformTarget(entry)) continue;
|
|
75
|
+
const file = path.resolve(cwd, entry);
|
|
76
|
+
if (entries.has(file)) continue;
|
|
77
|
+
entries.add(file);
|
|
78
|
+
try {
|
|
79
|
+
const source = filesystem.readFile(file).toString("utf8");
|
|
80
|
+
void capture(file, source).catch(() => undefined);
|
|
81
|
+
} catch {
|
|
82
|
+
// A missing entry must not poison another project's load. Its actual
|
|
83
|
+
// host delivery still owns the native read error and recovery.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* End startup ownership when the cache withdraws its generations.
|
|
90
|
+
*
|
|
91
|
+
* @evidence contracts/common.md#principled-implementation Withdrawal removes scheduling state with generation ownership so a later lifecycle can prepare the same entries again.
|
|
92
|
+
* @evidence contracts/common.md#clear-and-simple-design One weak-key deletion shares the cache's existing reset boundary.
|
|
93
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts Clearing scheduling permission does not admit or cancel any compiled result.
|
|
94
|
+
* @evidence contracts/common.md#meaningful-documentation The headline identifies the same generation withdrawal boundary used by callers.
|
|
95
|
+
* @evidenceExclude contracts/portability.md#os-neutral-implementation WeakMap deletion performs no native operation.
|
|
96
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms One identity deletion chooses no traversal or processing strategy.
|
|
97
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work This operation withdraws the previous scheduling record rather than reusing work.
|
|
98
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Deletion releases the single scheduling record; native resources remain owned by generation withdrawal.
|
|
99
|
+
*/
|
|
100
|
+
export function clear(cache: TtscTransformCache): void {
|
|
101
|
+
contexts.delete(cache);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -43,6 +43,8 @@ export function selectCachedGenerationAction(props: {
|
|
|
43
43
|
generation: Promise<TtscCachedProjectTransform>;
|
|
44
44
|
key: string;
|
|
45
45
|
source: string;
|
|
46
|
+
/** False only for speculative source reads before an actual module delivery. */
|
|
47
|
+
reportDivergence?: boolean;
|
|
46
48
|
proof?: TtscGenerationProof.Transaction;
|
|
47
49
|
}): "serve" | "retry" | "capture" {
|
|
48
50
|
if (
|
|
@@ -57,6 +59,7 @@ export function selectCachedGenerationAction(props: {
|
|
|
57
59
|
props.source,
|
|
58
60
|
props.epoch,
|
|
59
61
|
props.proof,
|
|
62
|
+
props.reportDivergence,
|
|
60
63
|
)
|
|
61
64
|
) {
|
|
62
65
|
return "serve";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TtscPrecompilation } from "../TtscPrecompilation";
|
|
1
2
|
import type { TtscTransformCache } from "./TtscTransformCache";
|
|
2
3
|
import { disposeCachedTransform } from "./disposeCachedTransform";
|
|
3
4
|
|
|
@@ -35,6 +36,7 @@ export function withdrawTtscTransformGenerations(
|
|
|
35
36
|
cache: TtscTransformCache,
|
|
36
37
|
): void {
|
|
37
38
|
const generations = [...cache.values()];
|
|
39
|
+
TtscPrecompilation.clear(cache);
|
|
38
40
|
cache.clear();
|
|
39
41
|
for (const generation of generations) {
|
|
40
42
|
void generation.then(disposeCachedTransform, () => undefined);
|
|
@@ -127,6 +127,9 @@ export async function captureTransformGeneration(props: {
|
|
|
127
127
|
/** Host-delivered text compared with the compiler's on-disk source baseline. */
|
|
128
128
|
currentSource: string;
|
|
129
129
|
|
|
130
|
+
/** False for preparation, whose source read is not a host module delivery. */
|
|
131
|
+
reportDivergence?: boolean;
|
|
132
|
+
|
|
130
133
|
/** Optional delivery epoch to which this captured observation belongs. */
|
|
131
134
|
deliveryEpoch?: number;
|
|
132
135
|
|
|
@@ -638,7 +641,7 @@ export async function captureTransformGeneration(props: {
|
|
|
638
641
|
// this generation. A delivered text that differs, because a plugin ordered
|
|
639
642
|
// before ttsc rewrote the module or the file changed after the host read
|
|
640
643
|
// it, must not replace them: every sibling delivery compares the disk and
|
|
641
|
-
// would recompile the project (samchon/ttsc#1394).
|
|
644
|
+
// would recompile the project (samchon/ttsc#1394). An actual delivery is reported once
|
|
642
645
|
// instead, and the file's own compile is served.
|
|
643
646
|
const currentFileKey = toProjectKey(
|
|
644
647
|
projectRoot,
|
|
@@ -691,7 +694,14 @@ export async function captureTransformGeneration(props: {
|
|
|
691
694
|
props.currentFile,
|
|
692
695
|
diskHash ?? deliveredHash,
|
|
693
696
|
);
|
|
694
|
-
|
|
697
|
+
// Preparation reads native source only to seed the ordinary capture. A
|
|
698
|
+
// later edit is handled by generation proof, not a warning about a host
|
|
699
|
+
// rewrite: no host delivered that speculative module (#1764).
|
|
700
|
+
if (
|
|
701
|
+
props.reportDivergence !== false &&
|
|
702
|
+
diskHash !== undefined &&
|
|
703
|
+
diskHash !== deliveredHash
|
|
704
|
+
) {
|
|
695
705
|
reportDivergentDelivery(cached, props.currentFile);
|
|
696
706
|
}
|
|
697
707
|
// An adopted compile was certified by its publisher, and is matched below
|
|
@@ -97,6 +97,9 @@ export async function transformProject(props: {
|
|
|
97
97
|
/** Delivered module text compared against the compiler's disk observation. */
|
|
98
98
|
currentSource: string;
|
|
99
99
|
|
|
100
|
+
/** False for preparation, whose source read is not a host module delivery. */
|
|
101
|
+
reportDivergence?: boolean;
|
|
102
|
+
|
|
100
103
|
/**
|
|
101
104
|
* Delivery pass this compile was started for; see
|
|
102
105
|
* {@link TtscCachedProjectTransform.deliveryEpoch}.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
3
|
import type { ResolvedTtscUnpluginOptions } from "../options/ResolvedTtscUnpluginOptions";
|
|
4
|
+
import { TtscPrecompilation } from "./TtscPrecompilation";
|
|
4
5
|
import type { TtscTransformResult } from "./TtscTransformResult";
|
|
5
6
|
import { createAliasPaths } from "./alias/createAliasPaths";
|
|
6
7
|
import { TERMINAL_TRANSFORM_GENERATIONS } from "./cache/TERMINAL_TRANSFORM_GENERATIONS";
|
|
@@ -68,38 +69,40 @@ import { recordedDependencyWitnesses } from "./watch/recordedDependencyWitnesses
|
|
|
68
69
|
* @param hooks - Optional adapter callbacks; see {@link TtscTransformHooks}.
|
|
69
70
|
* Dependency notifications fire on cache hits too; watch registrations are
|
|
70
71
|
* per build, not per compilation.
|
|
71
|
-
* @evidence contracts/common.md#principled-implementation Project selection and generation-qualified
|
|
72
|
-
* @evidence contracts/common.md#clear-and-simple-design One
|
|
73
|
-
* @evidence contracts/common.md#prohibited-implementation-shortcuts Wrapper modules cannot poison source baselines
|
|
74
|
-
* @evidence contracts/common.md#meaningful-documentation Native paragraphs and argument tags explain project scope, no-transform outcomes, cache epochs and per-build
|
|
75
|
-
* @evidence contracts/performance.md#efficient-algorithms
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* In-flight/valid completed reuse avoids compilation, not those checks or
|
|
80
|
-
* output/map/notification/record work. Capture misses pay whole native compile
|
|
81
|
-
* and input populations. The common proof owner skips redundant environment
|
|
82
|
-
* preparation for proven first deliveries and shares synchronous native proof
|
|
83
|
-
* queries; persistent/repeated delivery establishes fresh authority. Concurrent supersession can repeat the outer loop
|
|
84
|
-
* without a delivery-level retry/time bound here.
|
|
85
|
-
* @evidence contracts/performance.md#reuse-equivalent-work Cache identity covers config/options/plugins/aliases; current generations, pass-qualified terminal verdicts and in-flight Promises are shared only while their source and dependency proof remains valid.
|
|
86
|
-
* @evidence contracts/performance.md#bound-retention-and-release-resources
|
|
87
|
-
* Optional cache state retains current generation Promises/results and
|
|
88
|
-
* per-key dependency/case facts without a byte/key cap here. Native tasks
|
|
89
|
-
* belong to compiler/capture owners; uncached captures retain no notification
|
|
90
|
-
* or persistent clock probe. Fresh-only delivery releases capture resources,
|
|
91
|
-
* retaining only an explicitly admitted local pass result; rejected or failed
|
|
92
|
-
* incomplete handoff detaches its generation. Eviction/disposal attempt independent resource release
|
|
93
|
-
* and native failures need not close every handle or remove every probe.
|
|
94
|
-
* @evidence contracts/portability.md#os-neutral-implementation Native module paths and project coordinates use supported path/filesystem abstractions, while actual case policy and watcher capability come from generation proof rather than OS-name assumptions.
|
|
72
|
+
* @evidence contracts/common.md#principled-implementation Project selection and generation-qualified admission preserve compiler disk authority; preparation shares selection, keys, capture and input proofs with delivery, while host effects and first-delivery permission remain exclusive to actual delivery. Fresh-only success still requires an explicit nonwatching lifecycle, coherent project declaration and supported host-cache withdrawal because incomplete observation cannot establish watch invalidation.
|
|
73
|
+
* @evidence contracts/common.md#clear-and-simple-design One coordinator mode shares project selection, admission, compilation and output selection between preparation and delivery; dedicated owners retain proof and lifetime internals, and delivery alone performs host notification.
|
|
74
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts Wrapper modules cannot poison source baselines. Preparation skips handoff rather than postcompile proof, and superseded preparation stops without reviving withdrawn generations. Incomplete local results share only explicitly nonwatching first deliveries in their exact pass; unknown/watching lifecycles or unsupported withdrawal reject and evict without invented persistent records or dependency closure.
|
|
75
|
+
* @evidence contracts/common.md#meaningful-documentation Native paragraphs and argument tags explain project scope, no-transform outcomes, cache epochs and per-build notifications; coordinator prose identifies delivery-only effects and postpreparation admission, with maintained invalidation reference context.
|
|
76
|
+
* @evidence contracts/performance.md#efficient-algorithms Selection and keys pay ancestor/config/alias/option/path-text work. Each iteration may prepare toolchains, settle notifications and replay source/project/external/universal proofs; reuse avoids compilation rather than those checks or output/map/notification/record work. Misses pay native compilation and input populations. Proven first deliveries skip redundant environment preparation and share synchronous proof queries; repeated delivery establishes fresh authority. Startup schedules explicit entries concurrently and skips delivery output/map/registration work. Supersession can repeat the outer loop without a retry or time bound.
|
|
77
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Config/options/plugins/aliases key the ordinary project Promise shared by preparation, simultaneous loads and sibling entries; completed generations, pass-qualified terminal verdicts and in-flight work remain reusable only under current source and dependency proof.
|
|
78
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources Optional cache state retains generation Promises/results and per-key dependency/case facts without a byte/key cap. Native tasks belong to compiler/capture owners; uncached captures retain no notification or persistent clock probe. Fresh-only delivery releases capture resources while retaining an admitted local pass result; failed or rejected incomplete handoff detaches its generation. Superseded preparation preserves the new owner, and withdrawal schedules disposal of its pending fulfilled result. Eviction/disposal attempt independent release; native failure need not close every handle or remove every probe.
|
|
79
|
+
* @evidence contracts/portability.md#os-neutral-implementation Preparation and delivery use the same native project selection, path identity and cache-owned filesystem boundaries, with exact startup entry paths. Generation proof supplies case and watcher policy rather than OS-name assumptions.
|
|
95
80
|
*/
|
|
96
|
-
export
|
|
81
|
+
export function transformTtsc(
|
|
97
82
|
id: string,
|
|
98
83
|
source: string,
|
|
99
84
|
options: ResolvedTtscUnpluginOptions,
|
|
100
85
|
aliases?: unknown,
|
|
101
86
|
cache?: TtscTransformCache,
|
|
102
87
|
hooks?: TtscTransformHooks,
|
|
88
|
+
): Promise<TtscTransformResult | undefined> {
|
|
89
|
+
return runTransformTtsc(id, source, options, aliases, cache, hooks, false);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Coordinate capture and actual delivery through the same project cache.
|
|
94
|
+
* Preparation stops before handoff: it cannot mark a module served, publish a
|
|
95
|
+
* host record, acknowledge volatility or emit module diagnostics. A prepared
|
|
96
|
+
* generation must still pass ordinary admission when the host requests it.
|
|
97
|
+
*/
|
|
98
|
+
async function runTransformTtsc(
|
|
99
|
+
id: string,
|
|
100
|
+
source: string,
|
|
101
|
+
options: ResolvedTtscUnpluginOptions,
|
|
102
|
+
aliases: unknown,
|
|
103
|
+
cache: TtscTransformCache | undefined,
|
|
104
|
+
hooks: TtscTransformHooks | undefined,
|
|
105
|
+
preparing: boolean,
|
|
103
106
|
): Promise<TtscTransformResult | undefined> {
|
|
104
107
|
const filesystem = transformFilesystem(cache);
|
|
105
108
|
const exact = hooks?.exactPath === true;
|
|
@@ -126,7 +129,10 @@ export async function transformTtsc(
|
|
|
126
129
|
// Writing the delivered record must not create a new member of that proof.
|
|
127
130
|
prepareProjectRecordDirectories(hooks?.project);
|
|
128
131
|
|
|
129
|
-
const selection =
|
|
132
|
+
const selection =
|
|
133
|
+
hooks?.projectSelection === undefined
|
|
134
|
+
? resolveProjectSelection(file, options.project, filesystem)
|
|
135
|
+
: { tsconfig: path.resolve(hooks.projectSelection), consulted: [] };
|
|
130
136
|
const tsconfig = selection.tsconfig;
|
|
131
137
|
const selectedProjectRoot = path.resolve(
|
|
132
138
|
options.projectRoot ?? path.dirname(tsconfig),
|
|
@@ -143,6 +149,19 @@ export async function transformTtsc(
|
|
|
143
149
|
tsconfig,
|
|
144
150
|
};
|
|
145
151
|
const aliasPaths = createAliasPaths(aliases);
|
|
152
|
+
if (!preparing && cache !== undefined) {
|
|
153
|
+
TtscPrecompilation.start(cache, options, aliasPaths, (entry, text) =>
|
|
154
|
+
runTransformTtsc(
|
|
155
|
+
entry,
|
|
156
|
+
text,
|
|
157
|
+
options,
|
|
158
|
+
aliases,
|
|
159
|
+
cache,
|
|
160
|
+
{ ...hooks, exactPath: true, projectSelection: undefined },
|
|
161
|
+
true,
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
146
165
|
const key = createTransformCacheKey({
|
|
147
166
|
aliasPaths,
|
|
148
167
|
compilerOptions: options.compilerOptions,
|
|
@@ -169,7 +188,10 @@ export async function transformTtsc(
|
|
|
169
188
|
undefined,
|
|
170
189
|
terminal.validation.cached.projectRoot,
|
|
171
190
|
);
|
|
172
|
-
if (cache?.get(key) !== transformed)
|
|
191
|
+
if (cache?.get(key) !== transformed) {
|
|
192
|
+
if (preparing) return undefined;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
173
195
|
}
|
|
174
196
|
// A terminal verdict is an answer about one observed environment, not an
|
|
175
197
|
// invitation for every later module to repeat the whole compile.
|
|
@@ -180,7 +202,13 @@ export async function transformTtsc(
|
|
|
180
202
|
filesystem,
|
|
181
203
|
})
|
|
182
204
|
) {
|
|
183
|
-
|
|
205
|
+
if (!preparing)
|
|
206
|
+
notifyRejectedGenerationInputs(
|
|
207
|
+
hooks,
|
|
208
|
+
terminal,
|
|
209
|
+
file,
|
|
210
|
+
watchSelection,
|
|
211
|
+
);
|
|
184
212
|
throw terminal;
|
|
185
213
|
}
|
|
186
214
|
evictGeneration(cache, key, transformed);
|
|
@@ -193,12 +221,13 @@ export async function transformTtsc(
|
|
|
193
221
|
if (transformed !== undefined) {
|
|
194
222
|
const cached = await awaitOrEvict(cache, key, transformed).catch(
|
|
195
223
|
(rejection: unknown) => {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
224
|
+
if (!preparing)
|
|
225
|
+
notifyRejectedGenerationInputs(
|
|
226
|
+
hooks,
|
|
227
|
+
rejection,
|
|
228
|
+
file,
|
|
229
|
+
watchSelection,
|
|
230
|
+
);
|
|
202
231
|
throw rejection;
|
|
203
232
|
},
|
|
204
233
|
);
|
|
@@ -206,6 +235,7 @@ export async function transformTtsc(
|
|
|
206
235
|
// While this caller awaited the old Promise, another caller may have
|
|
207
236
|
// invalidated it and installed a newer authoritative generation.
|
|
208
237
|
if (cache?.get(key) !== transformed) {
|
|
238
|
+
if (preparing) return undefined;
|
|
209
239
|
continue;
|
|
210
240
|
}
|
|
211
241
|
// A generation captured while native notifications were trusted keeps
|
|
@@ -218,11 +248,15 @@ export async function transformTtsc(
|
|
|
218
248
|
if (epoch === undefined) {
|
|
219
249
|
await settleProjectMutationEvents(cached);
|
|
220
250
|
if (cache?.get(key) !== transformed) {
|
|
251
|
+
if (preparing) return undefined;
|
|
221
252
|
continue;
|
|
222
253
|
}
|
|
223
254
|
}
|
|
224
255
|
await TtscGenerationProof.prepare(cached, file, epoch);
|
|
225
|
-
if (cache?.get(key) !== transformed)
|
|
256
|
+
if (cache?.get(key) !== transformed) {
|
|
257
|
+
if (preparing) return undefined;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
226
260
|
// No await between this observation and synchronous admission.
|
|
227
261
|
const proof = TtscGenerationProof.create(cached, file, epoch);
|
|
228
262
|
const action = selectCachedGenerationAction({
|
|
@@ -234,11 +268,13 @@ export async function transformTtsc(
|
|
|
234
268
|
generation: transformed,
|
|
235
269
|
key,
|
|
236
270
|
source,
|
|
271
|
+
reportDivergence: !preparing,
|
|
237
272
|
});
|
|
238
273
|
if (action === "retry") {
|
|
239
274
|
continue;
|
|
240
275
|
}
|
|
241
276
|
if (action === "serve") {
|
|
277
|
+
if (preparing) return undefined;
|
|
242
278
|
return deliverCachedTransform({
|
|
243
279
|
cache,
|
|
244
280
|
cached,
|
|
@@ -261,6 +297,7 @@ export async function transformTtsc(
|
|
|
261
297
|
projectRoot: selectedProjectRoot,
|
|
262
298
|
currentFile: file,
|
|
263
299
|
currentSource: source,
|
|
300
|
+
reportDivergence: !preparing,
|
|
264
301
|
// Stamp the pass this compile was started for, not the one it happens
|
|
265
302
|
// to finish in: a boundary crossed mid-compile leaves the generation
|
|
266
303
|
// belonging to the earlier pass, so the next pass re-proves it.
|
|
@@ -296,11 +333,18 @@ export async function transformTtsc(
|
|
|
296
333
|
const generation = transformed;
|
|
297
334
|
const cached = await awaitOrEvict(cache, key, generation).catch(
|
|
298
335
|
(rejection: unknown) => {
|
|
299
|
-
|
|
336
|
+
if (!preparing)
|
|
337
|
+
notifyRejectedGenerationInputs(
|
|
338
|
+
hooks,
|
|
339
|
+
rejection,
|
|
340
|
+
file,
|
|
341
|
+
watchSelection,
|
|
342
|
+
);
|
|
300
343
|
throw rejection;
|
|
301
344
|
},
|
|
302
345
|
);
|
|
303
346
|
if (cache !== undefined && cache.get(key) !== generation) {
|
|
347
|
+
if (preparing) return undefined;
|
|
304
348
|
continue;
|
|
305
349
|
}
|
|
306
350
|
if (cache !== undefined) {
|
|
@@ -320,6 +364,7 @@ export async function transformTtsc(
|
|
|
320
364
|
policies.set(key, reported);
|
|
321
365
|
}
|
|
322
366
|
}
|
|
367
|
+
if (preparing) return undefined;
|
|
323
368
|
return deliverCachedTransform({
|
|
324
369
|
cache,
|
|
325
370
|
cached,
|
|
@@ -42,7 +42,7 @@ import { matchesRecordedInput } from "./matchesRecordedInput";
|
|
|
42
42
|
* @evidence contracts/common.md#meaningful-documentation Native prose explains failed admission and successful generation attachment; inline comments justify readable-state, blocker and tree distinctions.
|
|
43
43
|
* @evidence contracts/portability.md#os-neutral-implementation The generation's filesystem view qualifies metadata and exact native absence. Grouped candidates retain original spelling because directory case policy alone does not define Unicode or short-name equivalence; their joined path follows that view's platform dialect. Physical targets and aliases remain distinct from content identity, and native plugin environment authority is not established by arbitrary filesystem injection.
|
|
44
44
|
* @evidence contracts/performance.md#bound-retention-and-release-resources The generation retains manifest entries, lexical coverage, raw missing candidates and tree environments proportional to admitted inputs and their path/state text, without a total byte cap or native handle here. Failed attempt records are call-local and diagnostic witness counts are bounded by their owner; replacing or releasing the generation ends manifest ownership.
|
|
45
|
-
* @evidence contracts/performance.md#efficient-algorithms Input selection materializes the union of producer lists/proof keys and scans U universal inputs and T plugin source trees. Comparisons include content bytes, physical identity/ancestor/case queries, paired metadata and exact candidate stat; source proof includes tree population/bytes and environment validation. These delegated costs and temporary input/manifest populations are not constant-time map insertion work.
|
|
45
|
+
* @evidence contracts/performance.md#efficient-algorithms Input selection materializes the union of producer lists/proof keys and scans U universal inputs and T plugin source trees. Async-owned environments share one synchronous batch, qualifying common SDK paths once across plugin directories. Comparisons include content bytes, physical identity/ancestor/case queries, paired metadata and exact candidate stat; source proof includes tree population/bytes and environment validation. These delegated costs and temporary input/manifest populations are not constant-time map insertion work.
|
|
46
46
|
* @evidence contracts/performance.md#reuse-equivalent-work This shared generation manifest records exactly qualified lexical spellings, separable signatures and tree environments for later validators; changed proof requires new admission.
|
|
47
47
|
*/
|
|
48
48
|
export function captureUniversalHostInputValidation(
|
|
@@ -295,13 +295,21 @@ export function captureUniversalHostInputValidation(
|
|
|
295
295
|
// A plugin binary keyed on a source other than the disk's now, whether it
|
|
296
296
|
// was built here or adopted from another worker, is output for a state
|
|
297
297
|
// already gone (samchon/ttsc#1487).
|
|
298
|
-
|
|
298
|
+
const sources = selectPluginSourceInputs(cached.result);
|
|
299
|
+
const prepared = usesPreparedPluginBuildEnvironments(cached.result);
|
|
300
|
+
// Plugin directories commonly share the thousands-file Go SDK. Qualify it
|
|
301
|
+
// once for this synchronous proof, not once per directory (#1764). The map
|
|
302
|
+
// must stay inside this call: an await or later delivery needs fresh native
|
|
303
|
+
// authority, even when source watchers heard no change (#1712, #1516).
|
|
304
|
+
const environments = prepared
|
|
305
|
+
? PluginBuildEnvironmentReadings.cachedAll(sources.keys())
|
|
306
|
+
: undefined;
|
|
307
|
+
for (const [directory, digest] of sources) {
|
|
299
308
|
// The environment is recorded as read before the proof, so it is never a
|
|
300
309
|
// reading the proof did not see; one that moved meanwhile only makes the
|
|
301
310
|
// next delivery prove the tree again.
|
|
302
|
-
const prepared = usesPreparedPluginBuildEnvironments(cached.result);
|
|
303
311
|
const environment = prepared
|
|
304
|
-
?
|
|
312
|
+
? environments!.get(directory)
|
|
305
313
|
: processPluginBuildEnvironment(directory, false, cached.projectRoot);
|
|
306
314
|
if (
|
|
307
315
|
environment === undefined ||
|
|
@@ -37,7 +37,9 @@ import { notificationsProveProgramUnchanged } from "./notificationsProveProgramU
|
|
|
37
37
|
* rejects this generation for the delivery; its caller chooses replacement or
|
|
38
38
|
* capture. Delivered text that differs while the disk still holds the bytes the
|
|
39
39
|
* generation compiled is not one: it is reported and served, since the compile
|
|
40
|
-
* read the disk (samchon/ttsc#1394).
|
|
40
|
+
* read the disk (samchon/ttsc#1394). Preparation performs the same comparison
|
|
41
|
+
* without reporting a host rewrite, because its native source read is not a
|
|
42
|
+
* delivered module.
|
|
41
43
|
*
|
|
42
44
|
* Its place in the adapter's invalidation model, and the units beside it, are
|
|
43
45
|
* mapped in the maintainer page
|
|
@@ -73,6 +75,11 @@ export function matchesCachedSource(
|
|
|
73
75
|
/** Current host pass, or undefined for persistent input validation. */
|
|
74
76
|
epoch: number | undefined,
|
|
75
77
|
proof?: TtscGenerationProof.Transaction,
|
|
78
|
+
/**
|
|
79
|
+
* Preparation compares the same native bytes without reporting a host
|
|
80
|
+
* rewrite.
|
|
81
|
+
*/
|
|
82
|
+
reportDivergence = true,
|
|
76
83
|
): boolean {
|
|
77
84
|
if (!nativeInputPredicatesHold(cached, proof)) return false;
|
|
78
85
|
const pass = TtscGenerationProof.sharesPass(cached, file, epoch);
|
|
@@ -133,7 +140,7 @@ export function matchesCachedSource(
|
|
|
133
140
|
) {
|
|
134
141
|
return false;
|
|
135
142
|
}
|
|
136
|
-
reportDivergentDelivery(cached, file);
|
|
143
|
+
if (reportDivergence) reportDivergentDelivery(cached, file);
|
|
137
144
|
}
|
|
138
145
|
if (pass) return true;
|
|
139
146
|
if (epoch !== undefined && cached.projectSnapshotComplete === true) {
|
|
@@ -30,6 +30,16 @@ import type { TtscWatchInputEvidence } from "./TtscWatchInputEvidence";
|
|
|
30
30
|
* state at runtime.
|
|
31
31
|
*/
|
|
32
32
|
export interface TtscTransformHooks {
|
|
33
|
+
/**
|
|
34
|
+
* Native config already selected by the host for this delivered module.
|
|
35
|
+
*
|
|
36
|
+
* A host that freezes routing and its discovery inputs in one transaction
|
|
37
|
+
* supplies that config here and retains those inputs in its watch channel.
|
|
38
|
+
* Startup entries select their own projects from the user's
|
|
39
|
+
* `options.project`, independently of another module's discovered route.
|
|
40
|
+
*/
|
|
41
|
+
projectSelection?: string;
|
|
42
|
+
|
|
33
43
|
/**
|
|
34
44
|
* Invoked once per absolute watch-input path derived for the transformed file
|
|
35
45
|
* `F`: the plugin-reported `dependencies[F]` list unioned with the host-owned
|
package/src/core/unplugin.ts
CHANGED
|
@@ -42,6 +42,7 @@ import type { TtscProjectRegistration } from "./transform/watch/TtscProjectRegis
|
|
|
42
42
|
import { createViteBuildLifecycle } from "./vite/createViteBuildLifecycle";
|
|
43
43
|
import { createViteServeInputWatch } from "./vite/createViteServeInputWatch";
|
|
44
44
|
import { createViteServeWatchHooks } from "./vite/createViteServeWatchHooks";
|
|
45
|
+
import { installViteDependencyScanPlugins } from "./vite/installViteDependencyScanPlugins";
|
|
45
46
|
import { TTSC_SOURCE_MAP_STASH } from "./webpack/TTSC_SOURCE_MAP_STASH";
|
|
46
47
|
import { registerTtscSourceMapLoader } from "./webpack/registerTtscSourceMapLoader";
|
|
47
48
|
import { reportCompiledProjectRecords } from "./webpack/reportCompiledProjectRecords";
|
|
@@ -253,6 +254,37 @@ const unpluginFactory: UnpluginFactory<
|
|
|
253
254
|
transformCache,
|
|
254
255
|
hostDeclaresPolling(process.env, viteLifecycle.usePolling),
|
|
255
256
|
);
|
|
257
|
+
installViteDependencyScanPlugins(
|
|
258
|
+
config,
|
|
259
|
+
isTransformTarget,
|
|
260
|
+
(source, file) => {
|
|
261
|
+
const startedAt = viteLifecycle.watching
|
|
262
|
+
? serveInputs.begin()
|
|
263
|
+
: undefined;
|
|
264
|
+
return transformTtsc(
|
|
265
|
+
file,
|
|
266
|
+
source,
|
|
267
|
+
options,
|
|
268
|
+
aliases,
|
|
269
|
+
transformCache,
|
|
270
|
+
{
|
|
271
|
+
exactPath: true,
|
|
272
|
+
watching: viteLifecycle.watching,
|
|
273
|
+
...createViteServeWatchHooks(
|
|
274
|
+
"serve",
|
|
275
|
+
viteLifecycle.watching,
|
|
276
|
+
serveInputs,
|
|
277
|
+
file,
|
|
278
|
+
startedAt,
|
|
279
|
+
),
|
|
280
|
+
},
|
|
281
|
+
);
|
|
282
|
+
},
|
|
283
|
+
(this?.meta as { rolldownVersion?: string } | undefined)
|
|
284
|
+
?.rolldownVersion === undefined
|
|
285
|
+
? "esbuild"
|
|
286
|
+
: "rolldown",
|
|
287
|
+
);
|
|
256
288
|
},
|
|
257
289
|
// Compiler dependencies belong to the filesystem watch graph. Vite's
|
|
258
290
|
// transform-context addWatchFile also inserts runtime imports, so none
|