@animus-ui/extract 0.1.25 → 0.1.27
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/crates/extract-v2/animus-extract-v2.darwin-arm64.node +0 -0
- package/crates/extract-v2/animus-extract-v2.linux-arm64-gnu.node +0 -0
- package/crates/extract-v2/animus-extract-v2.linux-x64-gnu.node +0 -0
- package/crates/extract-v2/index.d.ts +101 -6
- package/dist/index.cjs +48 -5
- package/dist/index.mjs +2 -2
- package/dist/pipeline/alias-agreement.d.ts +107 -0
- package/dist/pipeline/alias-agreement.d.ts.map +1 -0
- package/dist/pipeline/analyze-project-args.d.ts +7 -2
- package/dist/pipeline/analyze-project-args.d.ts.map +1 -1
- package/dist/pipeline/asset-copies.d.ts +20 -0
- package/dist/pipeline/asset-copies.d.ts.map +1 -0
- package/dist/pipeline/built-in-asset-types.d.ts +9 -0
- package/dist/pipeline/built-in-asset-types.d.ts.map +1 -0
- package/dist/pipeline/core-options.d.ts +36 -7
- package/dist/pipeline/core-options.d.ts.map +1 -1
- package/dist/pipeline/discover-packages.d.ts +98 -12
- package/dist/pipeline/discover-packages.d.ts.map +1 -1
- package/dist/pipeline/engine-adapter.d.ts +41 -3
- package/dist/pipeline/engine-adapter.d.ts.map +1 -1
- package/dist/pipeline/engine-retirement.d.ts +7 -1
- package/dist/pipeline/engine-retirement.d.ts.map +1 -1
- package/dist/pipeline/extension-policy.d.ts +95 -0
- package/dist/pipeline/extension-policy.d.ts.map +1 -0
- package/dist/pipeline/host-assets.d.ts +32 -0
- package/dist/pipeline/host-assets.d.ts.map +1 -0
- package/dist/pipeline/host-expansions.d.ts +92 -0
- package/dist/pipeline/host-expansions.d.ts.map +1 -0
- package/dist/pipeline/host-extensions.d.ts +64 -0
- package/dist/pipeline/host-extensions.d.ts.map +1 -0
- package/dist/pipeline/host-package-resolver.d.ts +40 -0
- package/dist/pipeline/host-package-resolver.d.ts.map +1 -0
- package/dist/pipeline/index.d.ts +32 -8
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/kit-publication.d.ts.map +1 -1
- package/dist/pipeline/manifest-diagnostics.d.ts +25 -4
- package/dist/pipeline/manifest-diagnostics.d.ts.map +1 -1
- package/dist/pipeline/manifest-schema.d.ts +16 -0
- package/dist/pipeline/manifest-schema.d.ts.map +1 -1
- package/dist/pipeline/output-targets.d.ts +52 -0
- package/dist/pipeline/output-targets.d.ts.map +1 -0
- package/dist/pipeline/probe-evidence.d.ts +61 -0
- package/dist/pipeline/probe-evidence.d.ts.map +1 -0
- package/dist/pipeline/registry-packages.d.ts +27 -0
- package/dist/pipeline/registry-packages.d.ts.map +1 -0
- package/dist/pipeline/run-analysis.d.ts +26 -0
- package/dist/pipeline/run-analysis.d.ts.map +1 -1
- package/dist/pipeline/runtime-import.d.ts +10 -0
- package/dist/pipeline/runtime-import.d.ts.map +1 -0
- package/dist/pipeline/served-files.d.ts +16 -0
- package/dist/pipeline/served-files.d.ts.map +1 -0
- package/dist/pipeline/source-corpus.d.ts +4 -0
- package/dist/pipeline/source-corpus.d.ts.map +1 -1
- package/dist/pipeline/source-ingestion.d.ts +37 -5
- package/dist/pipeline/source-ingestion.d.ts.map +1 -1
- package/dist/pipeline/system-config.d.ts +18 -0
- package/dist/pipeline/system-config.d.ts.map +1 -1
- package/dist/pipeline/system-instances.d.ts +27 -0
- package/dist/pipeline/system-instances.d.ts.map +1 -0
- package/dist/pipeline/tsconfig-paths.d.ts +4 -0
- package/dist/pipeline/tsconfig-paths.d.ts.map +1 -1
- package/dist/pipeline/vite-expansions.d.ts +40 -0
- package/dist/pipeline/vite-expansions.d.ts.map +1 -0
- package/dist/pipeline/webpack-expansions.d.ts +55 -0
- package/dist/pipeline/webpack-expansions.d.ts.map +1 -0
- package/dist/pipeline/workspace-packages.d.ts +13 -0
- package/dist/pipeline/workspace-packages.d.ts.map +1 -0
- package/dist/{pipeline-YGWpNr_Z.mjs → pipeline-C0AcBXuQ.mjs} +4244 -2595
- package/dist/{pipeline-DOgKR4VZ.cjs → pipeline-CPSPw522.cjs} +4562 -2637
- package/dist/session/coalescing-window.d.ts +34 -0
- package/dist/session/coalescing-window.d.ts.map +1 -0
- package/dist/session/environment-builds.d.ts +26 -0
- package/dist/session/environment-builds.d.ts.map +1 -0
- package/dist/session/extraction-session.d.ts +338 -38
- package/dist/session/extraction-session.d.ts.map +1 -1
- package/dist/session/index.d.ts +9 -3
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/published-set.d.ts +6 -1
- package/dist/session/published-set.d.ts.map +1 -1
- package/dist/session/resolve-packages.d.ts +9 -5
- package/dist/session/resolve-packages.d.ts.map +1 -1
- package/dist/session/singleton.d.ts +216 -54
- package/dist/session/singleton.d.ts.map +1 -1
- package/dist/session/turbopack-orchestrator.d.ts.map +1 -1
- package/dist/session.cjs +1539 -525
- package/dist/session.mjs +1505 -504
- package/package.json +4 -4
package/dist/session.mjs
CHANGED
|
@@ -1,7 +1,150 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as writeAssetCopy, $t as createExcludeMatcher, A as postProcessCss, At as declaredWorkspacePackages, Bn as __require, C as serializeStaticCss, Ct as nativePackageLocator, D as clearEngineCache, Dn as findSheetAssetSpecifiers, Et as staleDistIncludesMessage, Ft as readTsconfigAliasPairs, G as discoveryResolver, Gt as discoverFiles, I as createSourceCorpus, It as tsconfigChainPaths, J as packageInstanceFinding, K as AliasAgreementCache, Ln as assembleStylesheet, Lt as BUILT_IN_EXTENSION_POLICY, M as buildPathAliasesJson, N as enforceExternalTokenContracts, Nn as contentHash, O as runProjectAnalysis, On as generatedModuleCode, Ot as unresolvableIncludesMessage, P as resolveAssetFile, Pn as parseInternalWire, Q as pruneStaleAssets, Sn as surfaceManifestDiagnostics, St as nativeKitSourceRoot, T as stableStringify, Y as packageReadRecord, Z as assertRuntimeImportSuppliesTerminals, _ as buildSystemPropsModule, _t as importedKitDiagnostics, an as unmatchedExcludeLines, b as loadSystemConfig, cn as sharesVolumeRoot, ct as adaptedOriginals, d as toWatchKeys, dn as isEngineTransformExtension, f as diffFilePlans, ft as excludeCollectedPackages, gn as SYSTEM_INSTANCE_MISMATCH, gt as firstOwners, ht as findPackageRoot, in as resolveVerbosity, j as resolveLightningTargets, jn as substituteSheetAssets, kn as reportSurvivingAssetPlaceholders, kt as walkPackageSources, ln as DEFAULT_EXTENSIONS, lt as bareSpecifierPackageName, m as snapshotFilePlans, nn as outputSourceMessage, nt as createV2EngineApi, on as createSourceIdentity, ot as projectExternalFileOwners, p as hashReplacementPlans, pt as extractSystemFilePackages, q as compareAliases, rn as resolveMode, s as builtInAssetClassification, sn as isPathWithinRoot, tn as outputSourceGuard, ut as collectExternalPackageSources, vt as isExcludedPackageRelativePath, wn as unreadableSourceDiagnostic, x as systemLoadReads, xn as severityFor, yn as isDeletedSource, yt as isPackageSpecifier, zt as extensionPolicyJson } from "./pipeline-C0AcBXuQ.mjs";
|
|
2
2
|
import { randomUUID } from "crypto";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "fs";
|
|
4
|
-
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "path";
|
|
4
|
+
import { basename, dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "path";
|
|
5
|
+
//#region session/coalescing-window.ts
|
|
6
|
+
/**
|
|
7
|
+
* The one coalescing window (SA19). It opens on the first pending event and
|
|
8
|
+
* fires a fixed interval later: a later event joins the open window and never
|
|
9
|
+
* pushes it back, so a steady stream of edits cannot starve the work it
|
|
10
|
+
* coalesces. Builds, startup and explicit flushes bypass it through `flush`.
|
|
11
|
+
* Hosts add no timers of their own.
|
|
12
|
+
*/
|
|
13
|
+
const COALESCING_WINDOW_MS = 75;
|
|
14
|
+
const SYSTEM_CLOCK = {
|
|
15
|
+
schedule: (fn, ms) => {
|
|
16
|
+
const timer = setTimeout(fn, ms);
|
|
17
|
+
timer.unref?.();
|
|
18
|
+
return timer;
|
|
19
|
+
},
|
|
20
|
+
cancel: (timer) => clearTimeout(timer)
|
|
21
|
+
};
|
|
22
|
+
var CoalescingWindow = class {
|
|
23
|
+
fire;
|
|
24
|
+
windowMs;
|
|
25
|
+
clock;
|
|
26
|
+
timer = null;
|
|
27
|
+
constructor(fire, windowMs = 75, clock = SYSTEM_CLOCK) {
|
|
28
|
+
this.fire = fire;
|
|
29
|
+
this.windowMs = windowMs;
|
|
30
|
+
this.clock = clock;
|
|
31
|
+
}
|
|
32
|
+
/** A pending event: opens the window, or joins the open one. */
|
|
33
|
+
note() {
|
|
34
|
+
if (this.timer !== null) return;
|
|
35
|
+
this.timer = this.clock.schedule(() => {
|
|
36
|
+
this.timer = null;
|
|
37
|
+
return this.fire();
|
|
38
|
+
}, this.windowMs);
|
|
39
|
+
}
|
|
40
|
+
/** Fires now, closing any open window: the bypass. */
|
|
41
|
+
flush() {
|
|
42
|
+
this.cancel();
|
|
43
|
+
return this.fire();
|
|
44
|
+
}
|
|
45
|
+
/** Closes an open window without firing it. */
|
|
46
|
+
cancel() {
|
|
47
|
+
if (this.timer === null) return;
|
|
48
|
+
this.clock.cancel(this.timer);
|
|
49
|
+
this.timer = null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region session/environment-builds.ts
|
|
54
|
+
/**
|
|
55
|
+
* Each environment build that started and has not ended, by name. A host
|
|
56
|
+
* that builds a shared plugin's environments in parallel (a Vite `buildApp`)
|
|
57
|
+
* shares one build among them, retired by the last to end. A build under a
|
|
58
|
+
* name whose build is open (a reused builder, or a second environment under
|
|
59
|
+
* one name) waits for that build's end, so each build ends only its own
|
|
60
|
+
* generation, once.
|
|
61
|
+
*/
|
|
62
|
+
var EnvironmentBuilds = class {
|
|
63
|
+
/** What a host without environments builds as. */
|
|
64
|
+
single = { name: "build" };
|
|
65
|
+
builds = /* @__PURE__ */ new Map();
|
|
66
|
+
/** Opens `environment`'s build once no build under its name is open;
|
|
67
|
+
* whether another environment's build is open beside it. */
|
|
68
|
+
async open(environment) {
|
|
69
|
+
for (;;) {
|
|
70
|
+
const open = this.builds.get(environment.name);
|
|
71
|
+
if (open === void 0) break;
|
|
72
|
+
await open.ended;
|
|
73
|
+
}
|
|
74
|
+
let end = () => {};
|
|
75
|
+
const ended = new Promise((settle) => {
|
|
76
|
+
end = settle;
|
|
77
|
+
});
|
|
78
|
+
this.builds.set(environment.name, {
|
|
79
|
+
environment,
|
|
80
|
+
ended,
|
|
81
|
+
end
|
|
82
|
+
});
|
|
83
|
+
return this.builds.size > 1;
|
|
84
|
+
}
|
|
85
|
+
/** Ends `environment`'s build if this environment opened it; whether that
|
|
86
|
+
* left none open. A host can end an environment it never opened (a dev
|
|
87
|
+
* server ends every environment, started or not), which ends nothing. */
|
|
88
|
+
end(environment) {
|
|
89
|
+
const open = this.builds.get(environment.name);
|
|
90
|
+
if (open === void 0 || open.environment !== environment) return false;
|
|
91
|
+
this.builds.delete(environment.name);
|
|
92
|
+
open.end();
|
|
93
|
+
return this.builds.size === 0;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region pipeline/system-instances.ts
|
|
98
|
+
/**
|
|
99
|
+
* The system and the analysis read one instance of each kit (spec §2.2
|
|
100
|
+
* A4). Discovery asks the host which instance a kit specifier names; the
|
|
101
|
+
* system loader resolves the system file's imports itself. Until the loader
|
|
102
|
+
* takes the host's instance, a kit whose analysed instance differs from the
|
|
103
|
+
* one the system evaluated is refused: its components would be extracted
|
|
104
|
+
* against another instance's vocabulary.
|
|
105
|
+
*/
|
|
106
|
+
function realPath(path) {
|
|
107
|
+
try {
|
|
108
|
+
return realpathSync(path);
|
|
109
|
+
} catch {
|
|
110
|
+
return path;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* One error for each system-file kit specifier whose analysed package
|
|
115
|
+
* instance is not the one the system load evaluated: the file the loader's
|
|
116
|
+
* own resolution names must be among the files the load read, and lie in
|
|
117
|
+
* another package root than the instance discovery read for its package.
|
|
118
|
+
*/
|
|
119
|
+
function systemInstanceMismatches(input) {
|
|
120
|
+
const evaluated = new Set(input.evaluated.map(realPath));
|
|
121
|
+
const shown = (path) => relative(input.rootDir, path).split(sep).join("/");
|
|
122
|
+
const diagnostics = [];
|
|
123
|
+
for (const specifier of input.specifiers) {
|
|
124
|
+
if (!isPackageSpecifier(specifier)) continue;
|
|
125
|
+
const analysed = input.instances[bareSpecifierPackageName(specifier)];
|
|
126
|
+
if (analysed === void 0) continue;
|
|
127
|
+
let file;
|
|
128
|
+
try {
|
|
129
|
+
file = input.loaderResolve(specifier);
|
|
130
|
+
} catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (file === null || !evaluated.has(realPath(file))) continue;
|
|
134
|
+
const system = realPath(findPackageRoot(realPath(file)));
|
|
135
|
+
if (system === analysed) continue;
|
|
136
|
+
diagnostics.push({
|
|
137
|
+
file: specifier,
|
|
138
|
+
component: "kit",
|
|
139
|
+
kind: "error",
|
|
140
|
+
message: `the system evaluates '${specifier}' from ${shown(system)}, but the host bundler resolves it to ${shown(analysed)}, which extraction analyses, so its components would be extracted against another instance's vocabulary — make the bundler and the system resolve '${specifier}' to one instance`,
|
|
141
|
+
code: SYSTEM_INSTANCE_MISMATCH,
|
|
142
|
+
severity: severityFor(SYSTEM_INSTANCE_MISMATCH)
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return diagnostics;
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
5
148
|
//#region session/session-paths.ts
|
|
6
149
|
/**
|
|
7
150
|
* Session artifact names, path derivations, and artifact shapes. Imports no
|
|
@@ -168,9 +311,11 @@ function readCliLockRecord(dir) {
|
|
|
168
311
|
if (!isPublishedSetRecord(candidate) || !isPublishedSetNumber(candidate.pid)) return { kind: "indeterminate" };
|
|
169
312
|
const startedAt = candidate.startedAt;
|
|
170
313
|
const heartbeatAt = candidate.heartbeatAt;
|
|
314
|
+
const build = candidate.build;
|
|
171
315
|
const record = { pid: candidate.pid };
|
|
172
316
|
if (isPublishedSetString(startedAt)) record.startedAt = startedAt;
|
|
173
317
|
if (isPublishedSetString(heartbeatAt)) record.heartbeatAt = heartbeatAt;
|
|
318
|
+
if (isPublishedSetString(build)) record.build = build;
|
|
174
319
|
return {
|
|
175
320
|
kind: "held",
|
|
176
321
|
record
|
|
@@ -201,12 +346,14 @@ function checkLockLiveness(record) {
|
|
|
201
346
|
reason: `holder pid ${record.pid} is running but its lock heartbeat (${record.heartbeatAt}) is ${Math.round(ageMs / 1e3)}s old, past the ${CLI_LOCK_STALE_AFTER_MS / 1e3}s staleness window — the pid names a different process now`
|
|
202
347
|
};
|
|
203
348
|
}
|
|
204
|
-
function lockRecordBytes(startedAt) {
|
|
205
|
-
|
|
349
|
+
function lockRecordBytes(startedAt, build) {
|
|
350
|
+
const record = {
|
|
206
351
|
pid: process.pid,
|
|
207
352
|
startedAt,
|
|
208
353
|
heartbeatAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
209
|
-
}
|
|
354
|
+
};
|
|
355
|
+
if (build !== void 0) record.build = build;
|
|
356
|
+
return JSON.stringify(record);
|
|
210
357
|
}
|
|
211
358
|
const HEARTBEAT_TEMP_PREFIX = ".lock-heartbeat-";
|
|
212
359
|
function holdDirectoryClaim(dir, options = {}) {
|
|
@@ -224,9 +371,9 @@ function holdDirectoryClaim(dir, options = {}) {
|
|
|
224
371
|
const stamp = () => {
|
|
225
372
|
try {
|
|
226
373
|
if (rename) {
|
|
227
|
-
writeFileSync(tempPath, lockRecordBytes(startedAt));
|
|
374
|
+
writeFileSync(tempPath, lockRecordBytes(startedAt, options.build));
|
|
228
375
|
renameSync(tempPath, path);
|
|
229
|
-
} else writeFileSync(path, lockRecordBytes(startedAt));
|
|
376
|
+
} else writeFileSync(path, lockRecordBytes(startedAt, options.build));
|
|
230
377
|
} catch (error) {
|
|
231
378
|
stop();
|
|
232
379
|
if (isMissingFile(error)) return;
|
|
@@ -256,320 +403,395 @@ function collectSessionAssets(sessionDir) {
|
|
|
256
403
|
}));
|
|
257
404
|
}
|
|
258
405
|
//#endregion
|
|
259
|
-
//#region session/resolve-packages.ts
|
|
260
|
-
/** An exports target under the `import` condition, then `default`, as the
|
|
261
|
-
* system loader reads it; an array offers its targets in order. */
|
|
262
|
-
function importTarget(value) {
|
|
263
|
-
if (isJsonString(value)) return value;
|
|
264
|
-
if (Array.isArray(value)) {
|
|
265
|
-
for (const entry of value) {
|
|
266
|
-
const target = importTarget(entry);
|
|
267
|
-
if (target !== null) return target;
|
|
268
|
-
}
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
if (!isJsonBlock(value)) return null;
|
|
272
|
-
return importTarget(value.import) ?? importTarget(value.default);
|
|
273
|
-
}
|
|
274
|
-
/** The exports entry for `subpath`: an exact key answers alone; otherwise
|
|
275
|
-
* the pattern with the longest prefix, then the longest suffix, wins. */
|
|
276
|
-
function exportsTarget(exports, subpath) {
|
|
277
|
-
const map = isJsonBlock(exports) && Object.keys(exports).some((k) => k.startsWith(".")) ? exports : { ".": exports };
|
|
278
|
-
if (subpath in map) return importTarget(map[subpath]);
|
|
279
|
-
let best = null;
|
|
280
|
-
for (const [pattern, value] of Object.entries(map)) {
|
|
281
|
-
const star = pattern.indexOf("*");
|
|
282
|
-
if (star === -1 || pattern.indexOf("*", star + 1) !== -1) continue;
|
|
283
|
-
const prefix = pattern.slice(0, star);
|
|
284
|
-
const suffix = pattern.slice(star + 1);
|
|
285
|
-
if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix) || subpath.length < prefix.length + suffix.length) continue;
|
|
286
|
-
if (!best || prefix.length > best.prefix.length || prefix.length === best.prefix.length && suffix.length > best.suffix.length) best = {
|
|
287
|
-
prefix,
|
|
288
|
-
suffix,
|
|
289
|
-
value
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
if (!best) return null;
|
|
293
|
-
const matched = subpath.slice(best.prefix.length, subpath.length - best.suffix.length);
|
|
294
|
-
return importTarget(best.value)?.replaceAll("*", matched) ?? null;
|
|
295
|
-
}
|
|
296
|
-
/** The entry an ES import of `name` resolves to through the package's
|
|
297
|
-
* `exports`, under the `import` condition, then `default`: what the Vite
|
|
298
|
-
* host and the system loader resolve. Null without an `exports` field, or
|
|
299
|
-
* when no target exists. */
|
|
300
|
-
function resolveImportEntry(rootDir, name) {
|
|
301
|
-
const packageName = name.split("/").slice(0, name.startsWith("@") ? 2 : 1).join("/");
|
|
302
|
-
const subpath = `.${name.slice(packageName.length)}`;
|
|
303
|
-
for (let dir = rootDir;; dir = dirname(dir)) {
|
|
304
|
-
const manifest = join(dir, "node_modules", packageName, "package.json");
|
|
305
|
-
if (existsSync(manifest)) {
|
|
306
|
-
let exports;
|
|
307
|
-
try {
|
|
308
|
-
const parsed = parseInternalWire(readFileSync(manifest, "utf-8"), `${manifest} (a package manifest)`);
|
|
309
|
-
exports = isJsonBlock(parsed) ? parsed.exports : void 0;
|
|
310
|
-
} catch {
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
313
|
-
if (exports === void 0 || exports === null) return null;
|
|
314
|
-
const target = exportsTarget(exports, subpath);
|
|
315
|
-
if (target === null) return null;
|
|
316
|
-
const entryPath = resolve(dirname(manifest), target);
|
|
317
|
-
return existsSync(entryPath) ? realpathSync(entryPath) : null;
|
|
318
|
-
}
|
|
319
|
-
if (dir === dirname(dir)) return null;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
function resolvePackagesByName(rootDir, names) {
|
|
323
|
-
if (names.length === 0) return {};
|
|
324
|
-
const nameSet = new Set(names);
|
|
325
|
-
const resolved = /* @__PURE__ */ new Set();
|
|
326
|
-
const packageMap = {};
|
|
327
|
-
try {
|
|
328
|
-
const rootPkg = JSON.parse(readFileSync(join(rootDir, "package.json"), "utf-8"));
|
|
329
|
-
const workspaces = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces?.packages ?? [];
|
|
330
|
-
for (const ws of workspaces) {
|
|
331
|
-
const wsDir = resolve(rootDir, ws);
|
|
332
|
-
if (!existsSync(wsDir)) continue;
|
|
333
|
-
try {
|
|
334
|
-
const pkg = JSON.parse(readFileSync(join(wsDir, "package.json"), "utf-8"));
|
|
335
|
-
const name = pkg.name || "";
|
|
336
|
-
if (nameSet.has(name)) {
|
|
337
|
-
const main = pkg.main || pkg.module || "index.ts";
|
|
338
|
-
const entryPath = resolve(wsDir, main);
|
|
339
|
-
if (existsSync(entryPath)) {
|
|
340
|
-
packageMap[name] = relative(rootDir, entryPath);
|
|
341
|
-
resolved.add(name);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
} catch {}
|
|
345
|
-
}
|
|
346
|
-
} catch {}
|
|
347
|
-
for (const name of nameSet) {
|
|
348
|
-
if (resolved.has(name)) continue;
|
|
349
|
-
const importEntry = resolveImportEntry(rootDir, name);
|
|
350
|
-
if (importEntry !== null) {
|
|
351
|
-
packageMap[name] = relative(rootDir, importEntry);
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
try {
|
|
355
|
-
const entryPath = __require.resolve(name, { paths: [rootDir] });
|
|
356
|
-
packageMap[name] = relative(rootDir, entryPath);
|
|
357
|
-
} catch {}
|
|
358
|
-
}
|
|
359
|
-
return packageMap;
|
|
360
|
-
}
|
|
361
|
-
//#endregion
|
|
362
406
|
//#region session/singleton.ts
|
|
363
407
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
408
|
+
* A build's shared state lives in one registry on `globalThis`, keyed by its
|
|
409
|
+
* `SessionKey`, so the ESM plugin copy and the CJS webpack-loader copy of
|
|
410
|
+
* this module find the same entry, and two builds of one root never share
|
|
411
|
+
* one. A reader of a build no participant has joined gets the empty value;
|
|
412
|
+
* a writer must have joined it.
|
|
366
413
|
*/
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const SESSION_ARTIFACT_DIR_KEY = "__animus_session_artifact_dir__";
|
|
379
|
-
const OWNING_WATCH_SESSION_KEY = "__animus_owning_watch_session__";
|
|
380
|
-
const EXCLUSIVE_SESSION_OWNER_KEY = "__animus_exclusive_session_owner__";
|
|
381
|
-
const ENGINE_KEY = "__animus_engine__";
|
|
382
|
-
const V2_ENGINE_KEY = "__animus_v2_engine__";
|
|
383
|
-
const V2_SENT_SOURCES_KEY = "__animus_v2_sent_sources__";
|
|
384
|
-
const V2_DRIFT_WARNED_KEY = "__animus_v2_drift_warned__";
|
|
385
|
-
const ENGINE_API_OVERRIDE_KEY = "__animus_engine_api_override__";
|
|
386
|
-
const singletonStore = globalThis;
|
|
387
|
-
const isSingletonString = (value) => {
|
|
388
|
-
if (Object(value) === value) return false;
|
|
389
|
-
try {
|
|
390
|
-
String.prototype.valueOf.call(value);
|
|
391
|
-
return true;
|
|
392
|
-
} catch {
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
function getManifestJson() {
|
|
397
|
-
return singletonStore[MANIFEST_KEY];
|
|
414
|
+
function joined(key) {
|
|
415
|
+
const state = findSession(key);
|
|
416
|
+
if (state === void 0) throw new Error(`[animus] no participant has joined build ${key.build} of ${key.root}`);
|
|
417
|
+
return state;
|
|
418
|
+
}
|
|
419
|
+
/** Commits the build's publication, the one write of everything below. */
|
|
420
|
+
function setPublication(key, publication) {
|
|
421
|
+
joined(key).publication = publication;
|
|
422
|
+
}
|
|
423
|
+
function publicationOf(key) {
|
|
424
|
+
return findSession(key)?.publication ?? null;
|
|
398
425
|
}
|
|
399
|
-
function
|
|
400
|
-
|
|
426
|
+
function getManifestJson(key) {
|
|
427
|
+
return publicationOf(key)?.manifestJson ?? null;
|
|
401
428
|
}
|
|
402
429
|
/**
|
|
403
430
|
* A latch over the most recently STARTED analysis, never cleared on settle:
|
|
404
431
|
* non-null means one was scheduled. In-flight lives in getWatchTransaction.
|
|
405
432
|
*/
|
|
406
|
-
function getAnalysisStartedPromise() {
|
|
407
|
-
return
|
|
408
|
-
}
|
|
409
|
-
function setAnalysisStartedPromise(promise) {
|
|
410
|
-
singletonStore[ANALYSIS_STARTED_KEY] = promise;
|
|
411
|
-
}
|
|
412
|
-
function resetAnalysisStartedPromise() {
|
|
413
|
-
singletonStore[ANALYSIS_STARTED_KEY] = null;
|
|
433
|
+
function getAnalysisStartedPromise(key) {
|
|
434
|
+
return findSession(key)?.analysisStarted ?? null;
|
|
414
435
|
}
|
|
415
|
-
function
|
|
416
|
-
|
|
436
|
+
function setAnalysisStartedPromise(key, promise) {
|
|
437
|
+
joined(key).analysisStarted = promise;
|
|
417
438
|
}
|
|
418
|
-
function
|
|
419
|
-
|
|
439
|
+
function resetAnalysisStartedPromise(key) {
|
|
440
|
+
const state = findSession(key);
|
|
441
|
+
if (state !== void 0) state.analysisStarted = null;
|
|
420
442
|
}
|
|
421
|
-
|
|
422
|
-
|
|
443
|
+
/** True for the build's first caller only: the one that reports its
|
|
444
|
+
* unmatched user excludes. */
|
|
445
|
+
function claimUnmatchedExcludeReport(key) {
|
|
446
|
+
const state = joined(key);
|
|
447
|
+
if (state.unmatchedExcludesReported) return false;
|
|
448
|
+
state.unmatchedExcludesReported = true;
|
|
449
|
+
return true;
|
|
423
450
|
}
|
|
424
|
-
function
|
|
425
|
-
|
|
451
|
+
function getSharedCss(key) {
|
|
452
|
+
return publicationOf(key)?.styles ?? "";
|
|
426
453
|
}
|
|
427
|
-
function
|
|
428
|
-
return
|
|
454
|
+
function getSharedSystemProps(key) {
|
|
455
|
+
return publicationOf(key)?.systemPropsModule ?? "";
|
|
429
456
|
}
|
|
430
|
-
function
|
|
431
|
-
|
|
457
|
+
function getSharedExternalDirs(key) {
|
|
458
|
+
return publicationOf(key)?.externalPackageDirs ?? [];
|
|
432
459
|
}
|
|
433
|
-
function getSharedExternalEntries() {
|
|
434
|
-
return
|
|
435
|
-
}
|
|
436
|
-
function setSharedExternalEntries(entries) {
|
|
437
|
-
singletonStore[SHARED_EXTERNAL_ENTRIES_KEY] = entries;
|
|
460
|
+
function getSharedExternalEntries(key) {
|
|
461
|
+
return publicationOf(key)?.externalSourceEntries ?? /* @__PURE__ */ new Map();
|
|
438
462
|
}
|
|
439
463
|
/** Specifier → its source entry's side effects, published with the shared
|
|
440
464
|
* entries; absent leaves the bundler's own classification. */
|
|
441
|
-
function getSharedExternalSideEffects() {
|
|
442
|
-
return
|
|
443
|
-
}
|
|
444
|
-
function setSharedExternalSideEffects(sideEffects) {
|
|
445
|
-
singletonStore[SHARED_EXTERNAL_SIDE_EFFECTS_KEY] = sideEffects;
|
|
465
|
+
function getSharedExternalSideEffects(key) {
|
|
466
|
+
return publicationOf(key)?.externalSourceSideEffects ?? /* @__PURE__ */ new Map();
|
|
446
467
|
}
|
|
447
|
-
function globalSlot(key) {
|
|
448
|
-
return {
|
|
449
|
-
get: () => singletonStore[key] ?? null,
|
|
450
|
-
set: (value) => {
|
|
451
|
-
singletonStore[key] = value;
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
const analyzedHashesSlot = globalSlot(ANALYZED_HASHES_KEY);
|
|
456
|
-
const replacementEpochSlot = globalSlot(REPLACEMENT_EPOCH_KEY);
|
|
457
|
-
const watchTransactionSlot = globalSlot(WATCH_TRANSACTION_KEY);
|
|
458
|
-
const sessionArtifactDirSlot = globalSlot(SESSION_ARTIFACT_DIR_KEY);
|
|
459
|
-
const owningWatchSessionSlot = globalSlot(OWNING_WATCH_SESSION_KEY);
|
|
460
468
|
/**
|
|
461
|
-
* The only session that can run watch analysis. A non-owning
|
|
462
|
-
*
|
|
469
|
+
* The only session of a build that can run watch analysis. A non-owning
|
|
470
|
+
* session's observations queue for it; dropping them loses files only that
|
|
471
|
+
* watcher sees.
|
|
463
472
|
*/
|
|
464
|
-
function getOwningWatchSession() {
|
|
465
|
-
return
|
|
473
|
+
function getOwningWatchSession(key) {
|
|
474
|
+
return findSession(key)?.authority ?? null;
|
|
466
475
|
}
|
|
467
|
-
function setOwningWatchSession(session) {
|
|
468
|
-
|
|
476
|
+
function setOwningWatchSession(key, session) {
|
|
477
|
+
joined(key).authority = session;
|
|
469
478
|
}
|
|
470
479
|
/**
|
|
471
480
|
* Per-file content hashes of the last published analysis (relPath → hash of
|
|
472
481
|
* the exact bytes analyzed), including files that produced zero entries.
|
|
473
482
|
*/
|
|
474
|
-
function getAnalyzedHashes() {
|
|
475
|
-
return
|
|
476
|
-
}
|
|
477
|
-
function setAnalyzedHashes(hashes) {
|
|
478
|
-
analyzedHashesSlot.set(hashes);
|
|
483
|
+
function getAnalyzedHashes(key) {
|
|
484
|
+
return publicationOf(key)?.analyzedHashes ?? null;
|
|
479
485
|
}
|
|
480
|
-
/**
|
|
481
|
-
*
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
function claimProcessSessionId() {
|
|
485
|
-
const existing = singletonStore[PROCESS_SESSION_ID_KEY];
|
|
486
|
-
if (isSingletonString(existing) && existing.length > 0) return existing;
|
|
487
|
-
const fresh = randomUUID();
|
|
488
|
-
singletonStore[PROCESS_SESSION_ID_KEY] = fresh;
|
|
489
|
-
return fresh;
|
|
486
|
+
/** What the build's last published analysis read of the discovered
|
|
487
|
+
* packages (spec §2.2 A4); null before one publishes. */
|
|
488
|
+
function getSharedPackageReadRecord(key) {
|
|
489
|
+
return publicationOf(key)?.packageReadRecord ?? null;
|
|
490
490
|
}
|
|
491
491
|
/**
|
|
492
|
-
*
|
|
493
|
-
*
|
|
492
|
+
* One participant publishes a build's analysis, holding this claim until it
|
|
493
|
+
* closes: a second publisher would clobber its artifacts. Two builds never
|
|
494
|
+
* exclude each other; sequential claim and release cycles are legal.
|
|
494
495
|
*/
|
|
495
|
-
function claimExclusiveSessionOwner(label) {
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
496
|
+
function claimExclusiveSessionOwner(key, label) {
|
|
497
|
+
const state = joined(key);
|
|
498
|
+
const active = state.activeAttempt;
|
|
499
|
+
if (active !== null) throw new Error(`[animus] "${label}" started an analysis of build ${key.build} of ${key.root}, which "${active}" publishes. A build's participants share one analysis; run it once and let the others read its results.`);
|
|
500
|
+
state.activeAttempt = label;
|
|
499
501
|
return () => {
|
|
500
|
-
if (
|
|
502
|
+
if (state.activeAttempt === label) state.activeAttempt = null;
|
|
501
503
|
};
|
|
502
504
|
}
|
|
503
505
|
/**
|
|
504
|
-
* Absolute artifact directory of the
|
|
506
|
+
* Absolute artifact directory of the build's last publication. The
|
|
505
507
|
* Turbopack loader runs out of process and receives it via options instead.
|
|
506
508
|
*/
|
|
507
|
-
function getSessionArtifactDir() {
|
|
508
|
-
return
|
|
509
|
+
function getSessionArtifactDir(key) {
|
|
510
|
+
return findSession(key)?.artifactDir ?? null;
|
|
509
511
|
}
|
|
510
|
-
function setSessionArtifactDir(dir) {
|
|
511
|
-
|
|
512
|
+
function setSessionArtifactDir(key, dir) {
|
|
513
|
+
joined(key).artifactDir = dir;
|
|
512
514
|
}
|
|
513
|
-
/**
|
|
514
|
-
*
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
function getReplacementEpoch() {
|
|
518
|
-
return replacementEpochSlot.get();
|
|
519
|
-
}
|
|
520
|
-
function setReplacementEpoch(epoch) {
|
|
521
|
-
replacementEpochSlot.set(epoch);
|
|
515
|
+
/** Replacement epoch of the last published analysis: committed in the same
|
|
516
|
+
* record as its manifest, so an epoch reader sees that generation's. */
|
|
517
|
+
function getReplacementEpoch(key) {
|
|
518
|
+
return publicationOf(key)?.replacementEpoch ?? null;
|
|
522
519
|
}
|
|
523
520
|
/**
|
|
524
|
-
* The one in-flight watch-analysis transaction: a
|
|
525
|
-
* it joins this promise rather than read a pre-transaction
|
|
521
|
+
* The build's one in-flight watch-analysis transaction: a participant that
|
|
522
|
+
* did not start it joins this promise rather than read a pre-transaction
|
|
523
|
+
* generation.
|
|
526
524
|
*/
|
|
527
|
-
function getWatchTransaction() {
|
|
528
|
-
return
|
|
525
|
+
function getWatchTransaction(key) {
|
|
526
|
+
return findSession(key)?.watchTransaction ?? null;
|
|
529
527
|
}
|
|
530
|
-
function
|
|
531
|
-
|
|
528
|
+
function observationQueue(key) {
|
|
529
|
+
return joined(key).observations;
|
|
532
530
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
function setSharedEngine(engine) {
|
|
536
|
-
singletonStore[ENGINE_KEY] = engine;
|
|
531
|
+
function getSystemAuthority(key) {
|
|
532
|
+
return findSession(key)?.systemAuthority ?? null;
|
|
537
533
|
}
|
|
538
|
-
function
|
|
539
|
-
|
|
534
|
+
function setSystemAuthority(key, authority) {
|
|
535
|
+
joined(key).systemAuthority = authority;
|
|
536
|
+
}
|
|
537
|
+
function setWatchTransaction(key, transaction) {
|
|
538
|
+
joined(key).watchTransaction = transaction;
|
|
540
539
|
}
|
|
541
540
|
/** Single choke-point for every native extraction call; the NAPI module's
|
|
542
541
|
* own `.d.ts` is the authoritative surface for what it returns. */
|
|
543
542
|
function requireEngine() {
|
|
544
543
|
return __require("@animus-ui/extract");
|
|
545
544
|
}
|
|
545
|
+
const ENGINE_API_OVERRIDE_KEY = "__animus_engine_api_override__";
|
|
546
|
+
const overrideGlobal = globalThis;
|
|
547
|
+
/** The native engine module could not be loaded: an environment failure,
|
|
548
|
+
* never answered by another engine. */
|
|
549
|
+
var EnvironmentFailure = class extends Error {
|
|
550
|
+
code = "EnvironmentFailure";
|
|
551
|
+
constructor(cause) {
|
|
552
|
+
super(`[animus] EnvironmentFailure: the native extraction engine could not be loaded: ${String(cause)}`, { cause });
|
|
553
|
+
this.name = "EnvironmentFailure";
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
let loadEngineFactory = requireEngine;
|
|
557
|
+
/** The native factory, loaded once for every build in the process. A load
|
|
558
|
+
* failure is an EnvironmentFailure, and is not cached: the next use tries
|
|
559
|
+
* the load again. */
|
|
560
|
+
function engineFactory() {
|
|
561
|
+
const registry = sessionRegistry();
|
|
562
|
+
if (registry.factory === null) try {
|
|
563
|
+
registry.factory = loadEngineFactory();
|
|
564
|
+
} catch (error) {
|
|
565
|
+
throw new EnvironmentFailure(error instanceof Error ? error : String(error));
|
|
566
|
+
}
|
|
567
|
+
return registry.factory;
|
|
568
|
+
}
|
|
546
569
|
/**
|
|
547
|
-
*
|
|
548
|
-
*
|
|
570
|
+
* One engine-handle store (SA18): the engine API over `key`'s own engine
|
|
571
|
+
* state, made once per key in the registry every module copy joins. The
|
|
572
|
+
* native factory is shared; each key's engine instance, analyze-time
|
|
573
|
+
* sources and drift flag are its own, and its build's retirement releases
|
|
574
|
+
* them. A key need not be a joined build's: a Vite context or a Turbopack
|
|
575
|
+
* worker keys its own.
|
|
549
576
|
*/
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
577
|
+
function engineHandle(key, options) {
|
|
578
|
+
const engines = sessionRegistry().engines;
|
|
579
|
+
const id = sessionKeyId(key);
|
|
580
|
+
let api = engines.get(id);
|
|
581
|
+
if (api === void 0) {
|
|
582
|
+
let engine = null;
|
|
583
|
+
let sentSources = null;
|
|
584
|
+
let driftWarned = false;
|
|
585
|
+
api = createV2EngineApi({
|
|
586
|
+
...options,
|
|
587
|
+
isV2: () => true,
|
|
588
|
+
loadNativeEngine: engineFactory,
|
|
589
|
+
store: {
|
|
590
|
+
getEngine: () => engine,
|
|
591
|
+
setEngine: (next) => {
|
|
592
|
+
engine = next;
|
|
593
|
+
},
|
|
594
|
+
getSentSources: () => sentSources,
|
|
595
|
+
setSentSources: (sources) => {
|
|
596
|
+
sentSources = sources;
|
|
597
|
+
},
|
|
598
|
+
getDriftWarned: () => driftWarned,
|
|
599
|
+
setDriftWarned: (value) => {
|
|
600
|
+
driftWarned = value;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
engines.set(id, api);
|
|
605
|
+
}
|
|
606
|
+
return api;
|
|
607
|
+
}
|
|
608
|
+
/** Releases a standalone handle, one no joined build retires: its host
|
|
609
|
+
* calls this when it closes. */
|
|
610
|
+
function releaseEngineHandle(key) {
|
|
611
|
+
sessionRegistry().engines.delete(sessionKeyId(key));
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* The build's engine API, from the one engine-handle store.
|
|
615
|
+
*/
|
|
616
|
+
function engineApi(key) {
|
|
617
|
+
const override = overrideGlobal[ENGINE_API_OVERRIDE_KEY];
|
|
618
|
+
if (override) return override();
|
|
619
|
+
joined(key);
|
|
620
|
+
return engineHandle(key, {
|
|
621
|
+
label: "animus",
|
|
622
|
+
passThroughUnknownPaths: true
|
|
623
|
+
})();
|
|
624
|
+
}
|
|
625
|
+
/** A fresh build id, created once by the owning host wrapper. */
|
|
626
|
+
function createBuildId() {
|
|
627
|
+
return randomUUID();
|
|
628
|
+
}
|
|
629
|
+
/** The key of build `build` of `root`, by the root's canonical path. */
|
|
630
|
+
function sessionKey(build, root) {
|
|
631
|
+
const absolute = resolve(root);
|
|
632
|
+
let canonical = absolute;
|
|
633
|
+
try {
|
|
634
|
+
canonical = realpathSync(absolute);
|
|
635
|
+
} catch {}
|
|
636
|
+
return {
|
|
637
|
+
build,
|
|
638
|
+
root: canonical,
|
|
639
|
+
protocol: 1
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function sessionKeyId(key) {
|
|
643
|
+
return JSON.stringify([
|
|
644
|
+
key.protocol,
|
|
645
|
+
key.build,
|
|
646
|
+
key.root
|
|
647
|
+
]);
|
|
648
|
+
}
|
|
649
|
+
/** A participant joined a session with semantic options that differ from
|
|
650
|
+
* those the session was created with. */
|
|
651
|
+
var SessionConfigurationConflict = class extends Error {
|
|
652
|
+
key;
|
|
653
|
+
established;
|
|
654
|
+
joining;
|
|
655
|
+
differing;
|
|
656
|
+
code = "SessionConfigurationConflict";
|
|
657
|
+
constructor(key, established, joining, differing, requirement = "Every participant of one build must use the same Animus options.") {
|
|
658
|
+
super(`[animus] SessionConfigurationConflict: "${joining}" joined build ${key.build} of ${key.root} with ${differing.join(", ")} differing from "${established}". ${requirement}`);
|
|
659
|
+
this.key = key;
|
|
660
|
+
this.established = established;
|
|
661
|
+
this.joining = joining;
|
|
662
|
+
this.differing = differing;
|
|
663
|
+
this.name = "SessionConfigurationConflict";
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
function digestOf(field) {
|
|
667
|
+
return {
|
|
668
|
+
field,
|
|
669
|
+
text: (options) => {
|
|
670
|
+
const value = options[field];
|
|
671
|
+
return value === void 0 ? void 0 : stableStringify(value);
|
|
567
672
|
}
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Every option, classified: one a build's analysis depends on, which every
|
|
677
|
+
* participant must agree on, or one deliberately excluded. `verbose` changes
|
|
678
|
+
* no output, and the root is the key's. The type checker rejects an option
|
|
679
|
+
* added to `AnimusCoreOptions` until it is classified here.
|
|
680
|
+
*/
|
|
681
|
+
const OPTION_CLASSES = {
|
|
682
|
+
system: digestOf("system"),
|
|
683
|
+
exclude: digestOf("exclude"),
|
|
684
|
+
extensions: digestOf("extensions"),
|
|
685
|
+
strict: digestOf("strict"),
|
|
686
|
+
diagnostics: digestOf("diagnostics"),
|
|
687
|
+
verbose: "excluded",
|
|
688
|
+
prefix: digestOf("prefix"),
|
|
689
|
+
prefixContextualVars: digestOf("prefixContextualVars"),
|
|
690
|
+
targets: digestOf("targets"),
|
|
691
|
+
minify: digestOf("minify"),
|
|
692
|
+
staticCss: digestOf("staticCss"),
|
|
693
|
+
layers: digestOf("layers"),
|
|
694
|
+
engine: digestOf("engine"),
|
|
695
|
+
mode: digestOf("mode"),
|
|
696
|
+
runtimeImport: digestOf("runtimeImport"),
|
|
697
|
+
root: "excluded"
|
|
698
|
+
};
|
|
699
|
+
/** The options a build's analysis depends on, each as stable text; each is
|
|
700
|
+
* data by its type, so equal text is an equal value. A host's own options
|
|
701
|
+
* are not the build's. */
|
|
702
|
+
function semanticOptions(options) {
|
|
703
|
+
const present = [];
|
|
704
|
+
for (const [name, digest] of Object.entries(OPTION_CLASSES)) {
|
|
705
|
+
if (digest === "excluded") continue;
|
|
706
|
+
const text = digest.text(options);
|
|
707
|
+
if (text !== void 0) present.push([name, text]);
|
|
708
|
+
}
|
|
709
|
+
return new Map(present);
|
|
710
|
+
}
|
|
711
|
+
const SESSION_REGISTRY = Symbol.for("animus.session-registry.v1");
|
|
712
|
+
const registryGlobal = globalThis;
|
|
713
|
+
function sessionRegistry() {
|
|
714
|
+
return registryGlobal[SESSION_REGISTRY] ??= {
|
|
715
|
+
sessions: /* @__PURE__ */ new Map(),
|
|
716
|
+
engines: /* @__PURE__ */ new Map(),
|
|
717
|
+
factory: null
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Joins `participant` to the session of `key`, creating it with `options` on
|
|
722
|
+
* the first join. A later participant must bring the same semantic options.
|
|
723
|
+
*/
|
|
724
|
+
function joinSession(key, participant, options) {
|
|
725
|
+
const sessions = sessionRegistry().sessions;
|
|
726
|
+
const id = sessionKeyId(key);
|
|
727
|
+
const joining = semanticOptions(options);
|
|
728
|
+
const existing = sessions.get(id);
|
|
729
|
+
if (existing === void 0) {
|
|
730
|
+
const state = {
|
|
731
|
+
key,
|
|
732
|
+
established: participant,
|
|
733
|
+
options: joining,
|
|
734
|
+
participants: /* @__PURE__ */ new Set([participant]),
|
|
735
|
+
activeAttempt: null,
|
|
736
|
+
authority: null,
|
|
737
|
+
analysisStarted: null,
|
|
738
|
+
watchTransaction: null,
|
|
739
|
+
observations: {
|
|
740
|
+
revision: 0,
|
|
741
|
+
rescan: false,
|
|
742
|
+
untaken: /* @__PURE__ */ new Map(),
|
|
743
|
+
suppliedSources: /* @__PURE__ */ new Map(),
|
|
744
|
+
revisions: /* @__PURE__ */ new Map(),
|
|
745
|
+
active: null
|
|
746
|
+
},
|
|
747
|
+
systemAuthority: null,
|
|
748
|
+
claimRecord: null,
|
|
749
|
+
publication: null,
|
|
750
|
+
artifactDir: null,
|
|
751
|
+
unmatchedExcludesReported: false
|
|
752
|
+
};
|
|
753
|
+
sessions.set(id, state);
|
|
754
|
+
return state;
|
|
755
|
+
}
|
|
756
|
+
const differing = [.../* @__PURE__ */ new Set([...existing.options.keys(), ...joining.keys()])].filter((name) => existing.options.get(name) !== joining.get(name));
|
|
757
|
+
if (differing.length > 0) throw new SessionConfigurationConflict(key, existing.established, participant, differing.sort());
|
|
758
|
+
existing.participants.add(participant);
|
|
759
|
+
return existing;
|
|
760
|
+
}
|
|
761
|
+
/** The session of `key`, if a participant has joined it. */
|
|
762
|
+
function findSession(key) {
|
|
763
|
+
return sessionRegistry().sessions.get(sessionKeyId(key));
|
|
764
|
+
}
|
|
765
|
+
/** The build's on-disk claim record, which stops another process pruning
|
|
766
|
+
* its tree: held once by the build, whichever participant publishes, and
|
|
767
|
+
* released only when the build retires. */
|
|
768
|
+
function holdBuildClaimRecord(key, hold) {
|
|
769
|
+
const state = joined(key);
|
|
770
|
+
state.claimRecord ??= hold();
|
|
771
|
+
}
|
|
772
|
+
/** `participant` leaves the build of `key`. The build retires with its
|
|
773
|
+
* last participant, once no attempt is in flight: it leaves the registry
|
|
774
|
+
* and releases its claim record, so its tree is pruned once stale. */
|
|
775
|
+
function leaveSession(key, participant) {
|
|
776
|
+
const state = findSession(key);
|
|
777
|
+
if (state === void 0) return;
|
|
778
|
+
state.participants.delete(participant);
|
|
779
|
+
retireIfUnused(sessionKeyId(key), state);
|
|
780
|
+
}
|
|
781
|
+
function retireIfUnused(id, state) {
|
|
782
|
+
if (state.participants.size > 0) return;
|
|
783
|
+
const inflight = state.watchTransaction;
|
|
784
|
+
if (inflight !== null) {
|
|
785
|
+
const retry = () => retireIfUnused(id, state);
|
|
786
|
+
inflight.then(retry, retry);
|
|
787
|
+
return;
|
|
568
788
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
789
|
+
const sessions = sessionRegistry().sessions;
|
|
790
|
+
if (sessions.get(id) !== state) return;
|
|
791
|
+
sessions.delete(id);
|
|
792
|
+
sessionRegistry().engines.delete(id);
|
|
793
|
+
state.claimRecord?.();
|
|
794
|
+
state.claimRecord = null;
|
|
573
795
|
}
|
|
574
796
|
//#endregion
|
|
575
797
|
//#region session/timing.ts
|
|
@@ -605,6 +827,23 @@ function logBuildTimings(bt, rustTiming, log, verbose) {
|
|
|
605
827
|
}
|
|
606
828
|
//#endregion
|
|
607
829
|
//#region session/extraction-session.ts
|
|
830
|
+
/** An observation's state when its file is gone, or cannot be read; any
|
|
831
|
+
* other state is the content hash it was observed with. */
|
|
832
|
+
const REMOVED_OBSERVATION = "removed";
|
|
833
|
+
const UNREADABLE_OBSERVATION = "unreadable";
|
|
834
|
+
/** A loaded system's identity: its evaluated modules and a fingerprint of
|
|
835
|
+
* the whole load. */
|
|
836
|
+
function systemLoad(system) {
|
|
837
|
+
return {
|
|
838
|
+
identity: contentHash(JSON.stringify(system)),
|
|
839
|
+
dependencies: system.dependencies ?? []
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
const CONSOLE_LOGGER = {
|
|
843
|
+
info: (line) => console.error(line),
|
|
844
|
+
warn: (line) => console.warn(line),
|
|
845
|
+
error: (line) => console.error(line)
|
|
846
|
+
};
|
|
608
847
|
const SESSION_DIR_MAX_AGE_MS = 864e5;
|
|
609
848
|
const STATUS_WATCHDOG_MS = 2e3;
|
|
610
849
|
const LEGACY_FLAT_ARTIFACTS = [
|
|
@@ -623,29 +862,120 @@ function isDiskString(value) {
|
|
|
623
862
|
}
|
|
624
863
|
/** Rejection of an attempt whose corpus holds an aborted parse. */
|
|
625
864
|
var AbortedParseRejection = class extends Error {};
|
|
865
|
+
/** A reload refused because its full read would replace host-supplied
|
|
866
|
+
* bytes with the disk's: a system reload a parse hold deferred, or a full
|
|
867
|
+
* read that reaches a host-supplied file only under another spelling (a
|
|
868
|
+
* case-insensitive volume). Like a hold, it keeps the prior publication
|
|
869
|
+
* and the observed sources. */
|
|
870
|
+
var DeferredReloadRefusal = class extends Error {};
|
|
871
|
+
/** The file's canonical path, or null when it cannot be resolved. */
|
|
872
|
+
function canonicalPath(path) {
|
|
873
|
+
try {
|
|
874
|
+
return realpathSync.native(path);
|
|
875
|
+
} catch {
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
/** The host sources a full read reaches only under another spelling: a key
|
|
880
|
+
* the read did not reach exactly, whose file is one the read reached by
|
|
881
|
+
* another path (a case-insensitive volume, a symlinked alias). A key with
|
|
882
|
+
* no such file is not read either way, and is left alone. */
|
|
883
|
+
function misspelledHostSources(hostSources, readPaths) {
|
|
884
|
+
const misses = [...hostSources.keys()].filter((path) => !readPaths.has(path));
|
|
885
|
+
if (misses.length === 0) return [];
|
|
886
|
+
const readByCanonical = /* @__PURE__ */ new Map();
|
|
887
|
+
for (const readPath of readPaths) {
|
|
888
|
+
const canonical = canonicalPath(readPath);
|
|
889
|
+
if (canonical === null) continue;
|
|
890
|
+
readByCanonical.set(canonical, [...readByCanonical.get(canonical) ?? [], readPath]);
|
|
891
|
+
}
|
|
892
|
+
return misses.flatMap((path) => {
|
|
893
|
+
const canonical = canonicalPath(path);
|
|
894
|
+
const read = canonical === null ? void 0 : readByCanonical.get(canonical);
|
|
895
|
+
return read === void 0 ? [] : [{
|
|
896
|
+
path,
|
|
897
|
+
read
|
|
898
|
+
}];
|
|
899
|
+
});
|
|
900
|
+
}
|
|
626
901
|
function isDiskNumber(value) {
|
|
627
902
|
return Object(value) !== value && Object.prototype.toString.call(value) === "[object Number]";
|
|
628
903
|
}
|
|
904
|
+
/** The session's own system-props file, imported by absolute path: what a
|
|
905
|
+
* host that serves no system-props module (the CLI) delivers. */
|
|
906
|
+
const SESSION_ARTIFACT = Symbol("animus.session-artifact");
|
|
907
|
+
/** A file's content hash, or '' when it cannot be read. */
|
|
908
|
+
function fileHashOrEmpty(path) {
|
|
909
|
+
try {
|
|
910
|
+
return contentHash(readFileSync(path, "utf-8"));
|
|
911
|
+
} catch {
|
|
912
|
+
return "";
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/** The first file whose content no longer has its recorded hash: an
|
|
916
|
+
* unreadable or deleted file reads as '', which mismatches a real one. */
|
|
917
|
+
function firstChangedFile(hashes) {
|
|
918
|
+
for (const [path, hash] of hashes) if (fileHashOrEmpty(path) !== hash) return path;
|
|
919
|
+
}
|
|
629
920
|
/** Bundler-agnostic extraction pipeline: system load, source ingestion,
|
|
630
921
|
* analysis, emission, and watch-cycle diffing. Carries no bundler types. */
|
|
631
922
|
var ExtractionSession = class {
|
|
632
923
|
host;
|
|
633
924
|
/** Set by the adapter before any pipeline run. */
|
|
634
925
|
rootDir = null;
|
|
926
|
+
/** A host that resolves imports itself (Vite's `this.resolve`): package
|
|
927
|
+
* discovery asks it first, the loader only for a declared workspace it
|
|
928
|
+
* cannot resolve. Null resolves through the loader alone. */
|
|
929
|
+
hostResolve = null;
|
|
930
|
+
/** Each file's alias comparison, kept by content hash across analyses. */
|
|
931
|
+
aliasCache = new AliasAgreementCache();
|
|
635
932
|
/** Serialized path aliases, harvested by the adapter from bundler config. */
|
|
636
933
|
pathAliasesJson = null;
|
|
934
|
+
/** The host's aliases are the project's tsconfig `paths`: each full pass
|
|
935
|
+
* re-reads them, so a tsconfig edit takes effect. */
|
|
936
|
+
aliasesFromTsconfig = false;
|
|
937
|
+
/** The root package.json and the tsconfig chain, absent files included:
|
|
938
|
+
* a change to any is a configuration change, which runs the full pass. */
|
|
939
|
+
configInputPaths = [];
|
|
940
|
+
configInputKeys = /* @__PURE__ */ new Set();
|
|
941
|
+
/** Each configuration input's content hash at the last full pass, ''
|
|
942
|
+
* for an absent one: the probe's basis when the host reports nothing. */
|
|
943
|
+
configInputHashes = /* @__PURE__ */ new Map();
|
|
637
944
|
/** The host's export conditions, in its order, for the system loader. */
|
|
638
945
|
conditions = [];
|
|
946
|
+
/** The host's extension order, as its adapter captured it; the built-in
|
|
947
|
+
* policy where no host resolves. */
|
|
948
|
+
extensionPolicy = BUILT_IN_EXTENSION_POLICY;
|
|
949
|
+
/** Which files the host serves as assets (spec §2.2 A8); the built-in
|
|
950
|
+
* classification where no host's can be read. */
|
|
951
|
+
assetClassification = builtInAssetClassification("noHost");
|
|
952
|
+
/** How the host's bundler expands computed loads (spec §6.3); without
|
|
953
|
+
* one, every computed load keeps its widest reading. */
|
|
954
|
+
loadExpander;
|
|
639
955
|
assetCopyCache = /* @__PURE__ */ new Map();
|
|
640
956
|
/** Physical asset files registered with the host watcher. */
|
|
641
957
|
assetDependencyPaths = /* @__PURE__ */ new Set();
|
|
642
958
|
assetDependencyKeys = /* @__PURE__ */ new Set();
|
|
643
|
-
/**
|
|
644
|
-
*
|
|
645
|
-
|
|
959
|
+
/** What generated code imports; a host that serves its own modules
|
|
960
|
+
* declares them before planning. */
|
|
961
|
+
delivery = {
|
|
962
|
+
cssModuleId: ANIMUS_CSS_MODULE_ID,
|
|
963
|
+
systemPropsModuleId: SESSION_ARTIFACT
|
|
964
|
+
};
|
|
646
965
|
/** Whether the analysis-inputs hydration corpus is written to disk. Only
|
|
647
966
|
* isolated loader workers replay it; an in-process loader reads memory. */
|
|
648
967
|
persistAnalysisInputs = false;
|
|
968
|
+
/** The host substitutes asset() URLs itself (Vite: dev copies, build
|
|
969
|
+
* emitFile markers): the published sheets keep their placeholders, and
|
|
970
|
+
* the session makes no copy and tracks no asset file. */
|
|
971
|
+
hostSubstitutesAssets = false;
|
|
972
|
+
/** Whether the session keeps its artifacts on disk: status, commit,
|
|
973
|
+
* manifest, styles, the epoch witness, asset copies and its directory
|
|
974
|
+
* claim, with the sibling reconciliation they need. A host that serves
|
|
975
|
+
* every result from memory (Vite) turns it off; the shared state still
|
|
976
|
+
* publishes. Such a host also substitutes assets (above): asset copies
|
|
977
|
+
* are artifacts. */
|
|
978
|
+
persistArtifacts = true;
|
|
649
979
|
/** Whether this session serves development (a watch), where an error-level
|
|
650
980
|
* diagnostic is reported and the session keeps running. Only a build
|
|
651
981
|
* fails on one. */
|
|
@@ -693,29 +1023,90 @@ var ExtractionSession = class {
|
|
|
693
1023
|
/** Fires exactly once per settled `handleWatchUpdate` — the failure cause,
|
|
694
1024
|
* or null when clean. Observation never alters delivery. */
|
|
695
1025
|
onCycleSettled = null;
|
|
1026
|
+
/** Fires once per published generation, and per held attempt. */
|
|
1027
|
+
onOutcome = null;
|
|
1028
|
+
/** The last published generation; null before the first. */
|
|
1029
|
+
publication = null;
|
|
1030
|
+
/** The original sources (raw, before adaptation) the session holds, by
|
|
1031
|
+
* root-relative path: the published generation's, plus any that an
|
|
1032
|
+
* attempt in flight or held by an aborted parse has read. */
|
|
1033
|
+
get heldSources() {
|
|
1034
|
+
return this.fileCache;
|
|
1035
|
+
}
|
|
1036
|
+
/** The generation an emit produced, until it commits with the
|
|
1037
|
+
* ingestion's ownership and the external package state it analysed. */
|
|
1038
|
+
stagedPublication = null;
|
|
696
1039
|
options;
|
|
697
1040
|
staticCssJson;
|
|
698
1041
|
system = null;
|
|
699
|
-
/** Configured
|
|
700
|
-
*
|
|
1042
|
+
/** Configured files that could not be read. Incremental passes replay
|
|
1043
|
+
* these instead of passing strict, and add a watched change they cannot
|
|
1044
|
+
* read. */
|
|
701
1045
|
ingestionFailureDiagnostics = [];
|
|
702
1046
|
/** Whether the corpus being analyzed left a file out, whose renders the
|
|
703
1047
|
* analysis cannot see. */
|
|
704
1048
|
skippedSources = [];
|
|
1049
|
+
/** Resolver calls ingestion ended under another spelling. */
|
|
1050
|
+
ingestionOpenings = [];
|
|
1051
|
+
/** The adapted originals of the ingestion being analysed: it publishes
|
|
1052
|
+
* only after its analysis does. */
|
|
1053
|
+
ingestionAdapted = /* @__PURE__ */ new Map();
|
|
705
1054
|
/** Full package-resolution map from the last full pipeline — replayed by
|
|
706
1055
|
* incremental passes (sourceEntries alone omits dist-resolved packages). */
|
|
707
1056
|
lastPackageMap = {};
|
|
708
1057
|
fileCache = /* @__PURE__ */ new Map();
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1058
|
+
/** Held sources whose bytes the host supplied (WatchChanges.sources),
|
|
1059
|
+
* by root-relative path; a read from disk drops the path. */
|
|
1060
|
+
hostSuppliedKeys = /* @__PURE__ */ new Set();
|
|
1061
|
+
corpus = createSourceCorpus(this.corpusHost());
|
|
1062
|
+
/** The corpus reads the driver label when it reports, so a host that
|
|
1063
|
+
* sets `driverLabel` after construction is named in its messages. */
|
|
1064
|
+
corpusHost() {
|
|
1065
|
+
const label = () => this.driverLabel;
|
|
1066
|
+
return {
|
|
1067
|
+
engineApi: () => this.engine(),
|
|
1068
|
+
get prefix() {
|
|
1069
|
+
return `[${label()}]`;
|
|
1070
|
+
},
|
|
1071
|
+
strict: () => !!this.options.strict,
|
|
1072
|
+
warn: (message) => this.warn(message),
|
|
1073
|
+
reportErrors: () => this.reportErrors(),
|
|
1074
|
+
extensions: () => this.extensionPolicy,
|
|
1075
|
+
rootDir: () => this.rootDir
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
716
1078
|
systemDependencyKeys = /* @__PURE__ */ new Set();
|
|
1079
|
+
/** What a host watches for this Session, one list for every host: the
|
|
1080
|
+
* files whose change it acts on (the system module and the modules it
|
|
1081
|
+
* read, asset() sources, the configuration inputs), the paths the system
|
|
1082
|
+
* load found absent, whose creation reloads it, and the external package
|
|
1083
|
+
* roots, watched recursively. An absent path inside `covered` (roots the
|
|
1084
|
+
* host already watches recursively) is left out: a host's recursive
|
|
1085
|
+
* watch reports its creation, and watching an absent file inside a
|
|
1086
|
+
* watched directory can stop that directory's events (inotify). */
|
|
1087
|
+
watchPlan(covered = []) {
|
|
1088
|
+
const system = this.rootDir === null ? [] : [resolve(this.rootDir, this.options.system)];
|
|
1089
|
+
return {
|
|
1090
|
+
files: [.../* @__PURE__ */ new Set([
|
|
1091
|
+
...system,
|
|
1092
|
+
...this.systemDependencyPaths,
|
|
1093
|
+
...this.assetDependencyPaths,
|
|
1094
|
+
...this.configInputPaths
|
|
1095
|
+
])],
|
|
1096
|
+
missing: this.systemMissingPaths.filter((path) => !covered.some((root) => isPathWithinRoot(root, path))),
|
|
1097
|
+
directories: [...this.externalWatchRoots]
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
/** Whether `path` is the system module, one of its dependencies, or a
|
|
1101
|
+
* path its load found absent: a change to it reloads the system. */
|
|
1102
|
+
isSystemDependency(path) {
|
|
1103
|
+
return toWatchKeys(path).some((key) => this.systemDependencyKeys.has(key));
|
|
1104
|
+
}
|
|
717
1105
|
/** Loader-reported dependency paths for bundler watch registration. */
|
|
718
1106
|
systemDependencyPaths = [];
|
|
1107
|
+
/** Paths the system load looked for and found absent: a host registers
|
|
1108
|
+
* them for creation, which reloads the system. */
|
|
1109
|
+
systemMissingPaths = [];
|
|
719
1110
|
systemDependencyHashes = /* @__PURE__ */ new Map();
|
|
720
1111
|
lastSystemPropsHash = null;
|
|
721
1112
|
artifactRecords = {
|
|
@@ -729,9 +1120,20 @@ var ExtractionSession = class {
|
|
|
729
1120
|
/** Release handle for the in-process owner slot AND the directory's
|
|
730
1121
|
* on-disk owner record, taken and released as one; null when unheld. */
|
|
731
1122
|
releasePublicationClaim = null;
|
|
732
|
-
/**
|
|
733
|
-
*
|
|
734
|
-
|
|
1123
|
+
/** The build this session takes part in: every participant of one build
|
|
1124
|
+
* shares its artifact tree and state; another build of the root never
|
|
1125
|
+
* does. */
|
|
1126
|
+
sessionId;
|
|
1127
|
+
/** The log tier, from the option and `ANIMUS_DEBUG` at construction. */
|
|
1128
|
+
verbosity;
|
|
1129
|
+
/** The emission mode at construction: explicit, else `NODE_ENV`'s. It
|
|
1130
|
+
* defaults minify. */
|
|
1131
|
+
startupMode;
|
|
1132
|
+
joinedKey = null;
|
|
1133
|
+
/** This instance as a participant of its build, once joined. */
|
|
1134
|
+
participant = null;
|
|
1135
|
+
/** Closed: it left its build and starts no attempt until it reattaches. */
|
|
1136
|
+
detached = false;
|
|
735
1137
|
/** Epoch of the last published analysis; null seeds from disk so a
|
|
736
1138
|
* same-session restart with unchanged plans rewrites no bytes. */
|
|
737
1139
|
lastEpochValue = null;
|
|
@@ -746,10 +1148,23 @@ var ExtractionSession = class {
|
|
|
746
1148
|
* the cache keeps every observed source so the repair publishes them in
|
|
747
1149
|
* one generation; a successful publication clears it. */
|
|
748
1150
|
heldRejection = null;
|
|
749
|
-
/**
|
|
750
|
-
*
|
|
751
|
-
*
|
|
1151
|
+
/** Reload debt: a full pass that failed with a committed system to
|
|
1152
|
+
* hold, in development or under an aborted parse, owed until one
|
|
1153
|
+
* succeeds. runFullPipeline alone sets and clears it. While it stands, every batch retries the full reload and
|
|
1154
|
+
* nothing publishes against the stale system. Under an aborted parse's
|
|
1155
|
+
* hold, the first batch that passes the parse check pays it instead. */
|
|
752
1156
|
systemReloadOwed = false;
|
|
1157
|
+
/** The system dependencies the last committed full pass recorded: a
|
|
1158
|
+
* failed reload watches these and its own, never an earlier failure's. */
|
|
1159
|
+
lastGoodSystemDependencies = {
|
|
1160
|
+
keys: /* @__PURE__ */ new Set(),
|
|
1161
|
+
paths: [],
|
|
1162
|
+
missing: [],
|
|
1163
|
+
hashes: /* @__PURE__ */ new Map()
|
|
1164
|
+
};
|
|
1165
|
+
/** Whether the current full pass's system load recorded its
|
|
1166
|
+
* dependencies. */
|
|
1167
|
+
attemptRecordedSystemDependencies = false;
|
|
753
1168
|
/** Monotonic readiness witness: flips true on the first complete
|
|
754
1169
|
* publication and never regresses; every later status write carries it. */
|
|
755
1170
|
firstEmissionComplete = false;
|
|
@@ -760,27 +1175,57 @@ var ExtractionSession = class {
|
|
|
760
1175
|
constructor(options, host = {}) {
|
|
761
1176
|
this.host = host;
|
|
762
1177
|
this.options = options;
|
|
1178
|
+
this.sessionId = host.build ?? createBuildId();
|
|
763
1179
|
this.staticCssJson = serializeStaticCss(options.staticCss);
|
|
1180
|
+
this.verbosity = resolveVerbosity(options.verbose, process.env.ANIMUS_DEBUG);
|
|
1181
|
+
this.startupMode = resolveMode(options.mode, () => process.env.NODE_ENV === "production" ? "production" : "development").mode;
|
|
764
1182
|
}
|
|
765
1183
|
/** Forgets that `relPath` could not be read once an incremental pass reads
|
|
766
|
-
* or deletes it, so its cost stops holding pruning off.
|
|
1184
|
+
* or deletes it, so its cost stops holding pruning off. Whether one was
|
|
1185
|
+
* recorded. */
|
|
767
1186
|
clearIngestionFailure(relPath) {
|
|
1187
|
+
const before = this.ingestionFailureDiagnostics.length;
|
|
768
1188
|
this.ingestionFailureDiagnostics = this.ingestionFailureDiagnostics.filter((diagnostic) => diagnostic.file !== relPath);
|
|
1189
|
+
return this.ingestionFailureDiagnostics.length !== before;
|
|
1190
|
+
}
|
|
1191
|
+
/** The module extracted terminals import their factories from. */
|
|
1192
|
+
get runtimeImport() {
|
|
1193
|
+
return this.options.runtimeImport ?? "@animus-ui/system/runtime";
|
|
769
1194
|
}
|
|
770
1195
|
get sessionDir() {
|
|
771
1196
|
return sessionArtifactDir(this.rootDir, this.sessionId);
|
|
772
1197
|
}
|
|
1198
|
+
/** This session's build of its root, joined on first use: a participant
|
|
1199
|
+
* whose options differ from the build's fails with
|
|
1200
|
+
* `SessionConfigurationConflict`. */
|
|
1201
|
+
get key() {
|
|
1202
|
+
if (this.joinedKey === null) {
|
|
1203
|
+
const key = sessionKey(this.sessionId, this.rootDir);
|
|
1204
|
+
const participant = `${this.driverLabel}#${randomUUID()}`;
|
|
1205
|
+
joinSession(key, participant, this.options);
|
|
1206
|
+
this.participant = participant;
|
|
1207
|
+
this.joinedKey = key;
|
|
1208
|
+
}
|
|
1209
|
+
return this.joinedKey;
|
|
1210
|
+
}
|
|
773
1211
|
get verbose() {
|
|
774
|
-
return
|
|
1212
|
+
return this.verbosity !== "quiet";
|
|
1213
|
+
}
|
|
1214
|
+
/** The engine of this session's build. */
|
|
1215
|
+
engine() {
|
|
1216
|
+
return this.host.engine?.() ?? engineApi(this.key);
|
|
1217
|
+
}
|
|
1218
|
+
get logger() {
|
|
1219
|
+
return this.host.logger ?? CONSOLE_LOGGER;
|
|
775
1220
|
}
|
|
776
1221
|
log(msg) {
|
|
777
|
-
if (this.verbose)
|
|
1222
|
+
if (this.verbose) this.logger.info(`[animus] ${msg}`);
|
|
778
1223
|
}
|
|
779
1224
|
warn(msg) {
|
|
780
|
-
|
|
1225
|
+
this.logger.warn(`[animus] ${msg}`);
|
|
781
1226
|
}
|
|
782
1227
|
reportErrors() {
|
|
783
|
-
return this.development ? (message) =>
|
|
1228
|
+
return this.development ? (message) => this.logger.error(message) : void 0;
|
|
784
1229
|
}
|
|
785
1230
|
/** A build-strictness check that failed: a build throws `message`, and
|
|
786
1231
|
* development reports it at error level and takes the non-strict path. */
|
|
@@ -789,6 +1234,11 @@ var ExtractionSession = class {
|
|
|
789
1234
|
if (!report) throw new Error(message);
|
|
790
1235
|
report(message);
|
|
791
1236
|
}
|
|
1237
|
+
/** A finding `strict` turns into a strict failure; otherwise a warning. */
|
|
1238
|
+
strictOrWarn(message, strictMessage = message) {
|
|
1239
|
+
if (this.options.strict) this.strictFailure(strictMessage);
|
|
1240
|
+
else this.warn(message);
|
|
1241
|
+
}
|
|
792
1242
|
now() {
|
|
793
1243
|
return this.verbose ? performance.now() : 0;
|
|
794
1244
|
}
|
|
@@ -803,6 +1253,11 @@ var ExtractionSession = class {
|
|
|
803
1253
|
lastExternalOutcomes = [];
|
|
804
1254
|
/** The analysed kits' descriptors, from the last discovery. */
|
|
805
1255
|
kitDescriptors = [];
|
|
1256
|
+
/** Package name → the real root of the instance discovery read. */
|
|
1257
|
+
packageInstances = {};
|
|
1258
|
+
/** What this attempt's analysis read of the discovered packages; the
|
|
1259
|
+
* build's shared record once the analysis publishes. */
|
|
1260
|
+
packageReadRecord = null;
|
|
806
1261
|
/** Component count of the last published analysis, null before the first —
|
|
807
1262
|
* read by drivers instead of re-parsing the manifest JSON every cycle. */
|
|
808
1263
|
lastComponentCount = null;
|
|
@@ -813,18 +1268,15 @@ var ExtractionSession = class {
|
|
|
813
1268
|
/** When superseded asset copies leave `assets/`: `'full-pipeline'` suits a
|
|
814
1269
|
* driver serving the dir in place, `'every-cycle'` one republishing it. */
|
|
815
1270
|
staleAssetPruning = "full-pipeline";
|
|
816
|
-
/**
|
|
817
|
-
*
|
|
1271
|
+
/** The host's output directories (`outputExclusion`): root-relative,
|
|
1272
|
+
* excluded as literal path prefixes from discovery and from package
|
|
1273
|
+
* collection, whatever the user's list. */
|
|
818
1274
|
structuralExclude = [];
|
|
819
1275
|
scanConfigMemo = null;
|
|
820
|
-
/** Per-pattern exclusion hit counts from this session's matcher —
|
|
821
|
-
* driver-consumed reporting surface (dead-pattern visibility). */
|
|
822
|
-
getExcludeStats() {
|
|
823
|
-
return this.resolveScanConfig().excludeMatcher.stats();
|
|
824
|
-
}
|
|
825
1276
|
/** Resolve the scan configuration from options — the single source of the
|
|
826
|
-
* exclude/extension policy shared by the full and incremental pipelines
|
|
827
|
-
|
|
1277
|
+
* exclude/extension policy shared by the full and incremental pipelines,
|
|
1278
|
+
* and by a host classifying its own file events. */
|
|
1279
|
+
get scanConfig() {
|
|
828
1280
|
if (this.scanConfigMemo === null) {
|
|
829
1281
|
const extensionsSet = new Set(this.options.extensions ?? DEFAULT_EXTENSIONS);
|
|
830
1282
|
this.scanConfigMemo = {
|
|
@@ -834,8 +1286,8 @@ var ExtractionSession = class {
|
|
|
834
1286
|
}
|
|
835
1287
|
return this.scanConfigMemo;
|
|
836
1288
|
}
|
|
837
|
-
/**
|
|
838
|
-
*
|
|
1289
|
+
/** Records the batch before any await, so an attempt that rejects never
|
|
1290
|
+
* erases it, and settles with the attempt that takes it (SA20). */
|
|
839
1291
|
async handleWatchUpdate(changes) {
|
|
840
1292
|
try {
|
|
841
1293
|
await this.routeWatchUpdate(changes);
|
|
@@ -846,36 +1298,131 @@ var ExtractionSession = class {
|
|
|
846
1298
|
this.onCycleSettled?.(null);
|
|
847
1299
|
}
|
|
848
1300
|
async routeWatchUpdate(changes) {
|
|
849
|
-
|
|
850
|
-
|
|
1301
|
+
if ((changes.modifiedFiles?.size ?? 0) > 0 || (changes.removedFiles?.size ?? 0) > 0) {
|
|
1302
|
+
await this.awaitObservation(this.recordObservation(changes));
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const inflight = getWatchTransaction(this.key);
|
|
851
1306
|
if (inflight) {
|
|
852
1307
|
await inflight;
|
|
853
|
-
if (!hasBatch) return;
|
|
854
|
-
}
|
|
855
|
-
if (!this.system) {
|
|
856
|
-
const owner = getOwningWatchSession();
|
|
857
|
-
if (owner && owner !== this && hasBatch) await owner.ingestForwardedBatch(changes);
|
|
858
1308
|
return;
|
|
859
1309
|
}
|
|
860
|
-
await this.
|
|
1310
|
+
if (this.system && !this.detached) await this.runWatchAttempt(changes);
|
|
1311
|
+
}
|
|
1312
|
+
/** Queues the batch's observations under a new revision, synchronously.
|
|
1313
|
+
* A batch equivalent to what the active attempt took joins that attempt:
|
|
1314
|
+
* a second compiler's report of the same edit never analyzes it twice. */
|
|
1315
|
+
recordObservation(changes) {
|
|
1316
|
+
const queue = observationQueue(this.key);
|
|
1317
|
+
const observed = /* @__PURE__ */ new Map();
|
|
1318
|
+
for (const path of changes.modifiedFiles ?? []) {
|
|
1319
|
+
let state;
|
|
1320
|
+
try {
|
|
1321
|
+
state = contentHash(changes.sources?.get(path) ?? readFileSync(path, "utf-8"));
|
|
1322
|
+
} catch {
|
|
1323
|
+
state = UNREADABLE_OBSERVATION;
|
|
1324
|
+
}
|
|
1325
|
+
observed.set(path, state);
|
|
1326
|
+
}
|
|
1327
|
+
for (const path of changes.removedFiles ?? []) observed.set(path, REMOVED_OBSERVATION);
|
|
1328
|
+
const revision = ++queue.revision;
|
|
1329
|
+
const active = queue.active;
|
|
1330
|
+
if (active !== null && [...observed].every(([path, state]) => !queue.untaken.has(path) && active.taken.get(path) === state)) {
|
|
1331
|
+
queue.revisions.set(revision, active.transaction);
|
|
1332
|
+
return revision;
|
|
1333
|
+
}
|
|
1334
|
+
for (const [path, state] of observed) {
|
|
1335
|
+
queue.untaken.set(path, state);
|
|
1336
|
+
const supplied = changes.sources?.get(path);
|
|
1337
|
+
if (supplied === void 0) queue.suppliedSources.delete(path);
|
|
1338
|
+
else queue.suppliedSources.set(path, supplied);
|
|
1339
|
+
}
|
|
1340
|
+
queue.revisions.set(revision, null);
|
|
1341
|
+
return revision;
|
|
861
1342
|
}
|
|
862
|
-
/**
|
|
863
|
-
*
|
|
864
|
-
async
|
|
865
|
-
|
|
1343
|
+
/** Settles with the attempt that takes `revision`. Another attempt's
|
|
1344
|
+
* rejection is its own callers': this observation runs after it. */
|
|
1345
|
+
async awaitObservation(revision) {
|
|
1346
|
+
const queue = observationQueue(this.key);
|
|
866
1347
|
for (;;) {
|
|
867
|
-
const
|
|
868
|
-
if (
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1348
|
+
const taken = queue.revisions.get(revision);
|
|
1349
|
+
if (taken !== null && taken !== void 0) {
|
|
1350
|
+
queue.revisions.delete(revision);
|
|
1351
|
+
await taken;
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
const inflight = getWatchTransaction(this.key);
|
|
1355
|
+
if (inflight) {
|
|
1356
|
+
await inflight.catch(() => void 0);
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
const owner = this.system && !this.detached ? this : getOwningWatchSession(this.key);
|
|
1360
|
+
if (owner === null && !this.detached && getManifestJson(this.key) !== null) {
|
|
1361
|
+
await this.runFullPipeline();
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
if (owner === null || !owner.startObservedAttempt()) {
|
|
1365
|
+
queue.revisions.delete(revision);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
877
1368
|
}
|
|
878
1369
|
}
|
|
1370
|
+
/** Starts one attempt over every untaken observation of the build. */
|
|
1371
|
+
startObservedAttempt() {
|
|
1372
|
+
if (!this.system || this.detached) return false;
|
|
1373
|
+
const { taken, rescan, sources } = this.takeObservations();
|
|
1374
|
+
const modifiedFiles = /* @__PURE__ */ new Set();
|
|
1375
|
+
const removedFiles = /* @__PURE__ */ new Set();
|
|
1376
|
+
for (const [path, state] of taken) (state === REMOVED_OBSERVATION ? removedFiles : modifiedFiles).add(path);
|
|
1377
|
+
this.settleTaken(taken, this.runWatchAttempt({
|
|
1378
|
+
modifiedFiles,
|
|
1379
|
+
removedFiles,
|
|
1380
|
+
sources
|
|
1381
|
+
}, rescan));
|
|
1382
|
+
return true;
|
|
1383
|
+
}
|
|
1384
|
+
/** Every untaken observation of the build, and whether a full rescan is
|
|
1385
|
+
* requested, which the caller's attempt now covers. */
|
|
1386
|
+
takeObservations() {
|
|
1387
|
+
const queue = observationQueue(this.key);
|
|
1388
|
+
const taken = new Map(queue.untaken);
|
|
1389
|
+
const rescan = queue.rescan;
|
|
1390
|
+
const sources = new Map(queue.suppliedSources);
|
|
1391
|
+
queue.untaken.clear();
|
|
1392
|
+
queue.suppliedSources.clear();
|
|
1393
|
+
queue.rescan = false;
|
|
1394
|
+
return {
|
|
1395
|
+
taken,
|
|
1396
|
+
rescan,
|
|
1397
|
+
sources
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
/** `transaction` settles every waiting revision, and is the active
|
|
1401
|
+
* attempt equivalent observations join until it settles. */
|
|
1402
|
+
settleTaken(taken, transaction) {
|
|
1403
|
+
const queue = observationQueue(this.key);
|
|
1404
|
+
for (const [revision, attempt] of queue.revisions) if (attempt === null) queue.revisions.set(revision, transaction);
|
|
1405
|
+
queue.active = {
|
|
1406
|
+
taken,
|
|
1407
|
+
transaction
|
|
1408
|
+
};
|
|
1409
|
+
const settle = () => {
|
|
1410
|
+
if (queue.active?.transaction === transaction) queue.active = null;
|
|
1411
|
+
};
|
|
1412
|
+
transaction.then(settle, settle);
|
|
1413
|
+
}
|
|
1414
|
+
/** Registers one attempt in the build's single-flight slot before it
|
|
1415
|
+
* processes, so a nested full system reload inherits the gate rather
|
|
1416
|
+
* than clear it, and clears the slot when it settles either way. */
|
|
1417
|
+
runWatchAttempt(changes, rescan = false) {
|
|
1418
|
+
const transaction = Promise.resolve().then(() => rescan ? this.runFullPipeline([], changes.sources) : this.processWatchUpdate(changes));
|
|
1419
|
+
setWatchTransaction(this.key, transaction);
|
|
1420
|
+
const settle = () => {
|
|
1421
|
+
if (getWatchTransaction(this.key) === transaction) setWatchTransaction(this.key, null);
|
|
1422
|
+
};
|
|
1423
|
+
transaction.then(settle, settle);
|
|
1424
|
+
return transaction;
|
|
1425
|
+
}
|
|
879
1426
|
async processWatchUpdate(changes) {
|
|
880
1427
|
const rootDir = this.rootDir;
|
|
881
1428
|
try {
|
|
@@ -886,31 +1433,25 @@ var ExtractionSession = class {
|
|
|
886
1433
|
const changed = [...changes.modifiedFiles ?? [], ...changes.removedFiles ?? []];
|
|
887
1434
|
let assetChanged = false;
|
|
888
1435
|
let systemHit;
|
|
1436
|
+
let configHit;
|
|
889
1437
|
try {
|
|
890
|
-
if (
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
} catch {}
|
|
895
|
-
if (current !== hash) {
|
|
896
|
-
systemHit = dep;
|
|
897
|
-
break;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
else systemHit = changed.find((path) => toWatchKeys(path).some((key) => this.systemDependencyKeys.has(key)));
|
|
1438
|
+
if (changed.length === 0) {
|
|
1439
|
+
systemHit = firstChangedFile(this.systemDependencyHashes);
|
|
1440
|
+
if (systemHit === void 0) configHit = firstChangedFile(this.configInputHashes);
|
|
1441
|
+
} else systemHit = changed.find((path) => this.isSystemDependency(path));
|
|
901
1442
|
} catch (err) {
|
|
902
1443
|
this.warn(`HMR system-reload check failed: ${String(err)}`);
|
|
903
1444
|
}
|
|
904
|
-
|
|
905
|
-
|
|
1445
|
+
configHit ??= changed.find((path) => toWatchKeys(path).some((key) => this.configInputKeys.has(key)));
|
|
1446
|
+
const debtRetry = this.systemReloadOwed && this.heldRejection === null;
|
|
1447
|
+
if (systemHit || configHit || debtRetry) {
|
|
1448
|
+
this.log(systemHit ? `system reload: system dependency changed (${relative(rootDir, systemHit)})` : configHit ? `system reload: configuration changed (${relative(rootDir, configHit)})` : "system reload: retrying the reload a failure left owed");
|
|
906
1449
|
this.resetForHmr();
|
|
907
1450
|
try {
|
|
908
|
-
const promise = this.runFullPipeline(this.pendingFromBatch(changes));
|
|
909
|
-
setAnalysisStartedPromise(promise);
|
|
1451
|
+
const promise = this.runFullPipeline(this.pendingFromBatch(changes), changes.sources);
|
|
1452
|
+
setAnalysisStartedPromise(this.key, promise);
|
|
910
1453
|
await promise;
|
|
911
|
-
this.systemReloadOwed = false;
|
|
912
1454
|
} catch (err) {
|
|
913
|
-
if (err instanceof AbortedParseRejection) this.systemReloadOwed = true;
|
|
914
1455
|
try {
|
|
915
1456
|
this.beginStatusAttempt();
|
|
916
1457
|
this.writeAnalysisStatus("failed", this.pendingFromBatch(changes), String(err));
|
|
@@ -925,14 +1466,16 @@ var ExtractionSession = class {
|
|
|
925
1466
|
this.assetCopyCache.clear();
|
|
926
1467
|
assetChanged = true;
|
|
927
1468
|
}
|
|
928
|
-
const { excludeMatcher, extensionsSet } = this.
|
|
1469
|
+
const { excludeMatcher, extensionsSet } = this.scanConfig;
|
|
929
1470
|
const priorCacheEntries = /* @__PURE__ */ new Map();
|
|
930
1471
|
const priorExternalFileOwners = { ...this.externalFileOwners };
|
|
1472
|
+
const priorHostSuppliedKeys = new Set(this.hostSuppliedKeys);
|
|
931
1473
|
const recordPrior = (key) => {
|
|
932
1474
|
if (!priorCacheEntries.has(key)) priorCacheEntries.set(key, this.fileCache.get(key) ?? null);
|
|
933
1475
|
};
|
|
934
1476
|
const inventoryUpdates = [];
|
|
935
1477
|
const ownerRemovals = [];
|
|
1478
|
+
const removedProjectFiles = /* @__PURE__ */ new Map();
|
|
936
1479
|
let removedAny = false;
|
|
937
1480
|
if (changes.removedFiles) for (const removedPath of changes.removedFiles) {
|
|
938
1481
|
let key;
|
|
@@ -946,8 +1489,10 @@ var ExtractionSession = class {
|
|
|
946
1489
|
}
|
|
947
1490
|
recordPrior(key);
|
|
948
1491
|
this.clearIngestionFailure(key);
|
|
1492
|
+
this.hostSuppliedKeys.delete(key);
|
|
949
1493
|
if (this.fileCache.delete(key)) {
|
|
950
1494
|
removedAny = true;
|
|
1495
|
+
if (!owningRoot) removedProjectFiles.set(key, removedPath);
|
|
951
1496
|
if (owningRoot) {
|
|
952
1497
|
inventoryUpdates.push({
|
|
953
1498
|
root: owningRoot,
|
|
@@ -978,15 +1523,29 @@ var ExtractionSession = class {
|
|
|
978
1523
|
owningRoot: null
|
|
979
1524
|
}));
|
|
980
1525
|
const changedPaths = [];
|
|
1526
|
+
const inOutput = outputSourceGuard(rootDir, this.structuralExclude);
|
|
1527
|
+
let readFailuresChanged = false;
|
|
981
1528
|
for (const target of targets) {
|
|
982
1529
|
const relPath = target.key;
|
|
1530
|
+
const outputDir = inOutput(target.abs);
|
|
1531
|
+
if (outputDir !== null) {
|
|
1532
|
+
this.warn(outputSourceMessage(relPath, outputDir));
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
983
1535
|
let source;
|
|
984
1536
|
try {
|
|
985
|
-
|
|
986
|
-
|
|
1537
|
+
const supplied = changes.sources?.get(target.abs);
|
|
1538
|
+
source = supplied ?? readFileSync(target.abs, "utf-8");
|
|
1539
|
+
if (supplied === void 0) this.hostSuppliedKeys.delete(relPath);
|
|
1540
|
+
else this.hostSuppliedKeys.add(relPath);
|
|
1541
|
+
} catch (err) {
|
|
1542
|
+
if (isDeletedSource(err)) continue;
|
|
1543
|
+
this.clearIngestionFailure(relPath);
|
|
1544
|
+
this.ingestionFailureDiagnostics = [...this.ingestionFailureDiagnostics, unreadableSourceDiagnostic(relPath, err)];
|
|
1545
|
+
readFailuresChanged = true;
|
|
987
1546
|
continue;
|
|
988
1547
|
}
|
|
989
|
-
this.clearIngestionFailure(relPath);
|
|
1548
|
+
if (this.clearIngestionFailure(relPath)) readFailuresChanged = true;
|
|
990
1549
|
const rawHash = contentHash(source);
|
|
991
1550
|
const cached = this.fileCache.get(relPath);
|
|
992
1551
|
const hash = rawHash;
|
|
@@ -1009,7 +1568,7 @@ var ExtractionSession = class {
|
|
|
1009
1568
|
}
|
|
1010
1569
|
}
|
|
1011
1570
|
}
|
|
1012
|
-
if (changedPaths.length > 0 || removedAny || assetChanged) {
|
|
1571
|
+
if (changedPaths.length > 0 || removedAny || readFailuresChanged || assetChanged) {
|
|
1013
1572
|
const pending = changedPaths.map((rel) => [rel, this.fileCache.get(rel).hash]);
|
|
1014
1573
|
const commitObservedBatch = () => {
|
|
1015
1574
|
for (const update of inventoryUpdates) {
|
|
@@ -1035,24 +1594,32 @@ var ExtractionSession = class {
|
|
|
1035
1594
|
});
|
|
1036
1595
|
this.throwOnAbortedParse(ingested);
|
|
1037
1596
|
this.skippedSources = ingested.skippedOriginals ?? [];
|
|
1597
|
+
this.ingestionOpenings = ingested.openings ?? [];
|
|
1598
|
+
this.ingestionAdapted = adaptedOriginals(ingested.ownership);
|
|
1038
1599
|
if (this.systemReloadOwed) {
|
|
1039
1600
|
this.log("system reload: publishing the reload a parse hold deferred");
|
|
1601
|
+
const stale = this.staleHostSuppliedSources(changes.sources);
|
|
1602
|
+
if (stale.length > 0) throw new DeferredReloadRefusal(`[${this.driverLabel}] system reload not published: its full read would take ${stale.join(", ")} from disk, which does not hold the bytes the host supplied; the last generation stays served, and the reload publishes with the next change once the disk holds them`);
|
|
1040
1603
|
this.resetForHmr();
|
|
1041
|
-
await this.runFullPipeline(pending);
|
|
1042
|
-
this.systemReloadOwed = false;
|
|
1604
|
+
await this.runFullPipeline(pending, changes.sources);
|
|
1043
1605
|
return;
|
|
1044
1606
|
}
|
|
1045
1607
|
this.externalFileOwners = projectExternalFileOwners(ingested, this.externalFileOwners);
|
|
1046
|
-
resetAnalysisStartedPromise();
|
|
1608
|
+
resetAnalysisStartedPromise(this.key);
|
|
1047
1609
|
const promise = this.runIncrementalPipeline(ingested.analysisEntries, pending);
|
|
1048
|
-
setAnalysisStartedPromise(promise);
|
|
1610
|
+
setAnalysisStartedPromise(this.key, promise);
|
|
1049
1611
|
await promise;
|
|
1050
1612
|
} catch (err) {
|
|
1051
|
-
if (err instanceof AbortedParseRejection) commitObservedBatch();
|
|
1613
|
+
if (err instanceof AbortedParseRejection || err instanceof DeferredReloadRefusal) commitObservedBatch();
|
|
1052
1614
|
else {
|
|
1053
|
-
for (const [key, prior] of priorCacheEntries)
|
|
1054
|
-
|
|
1615
|
+
for (const [key, prior] of priorCacheEntries) {
|
|
1616
|
+
const removed = removedProjectFiles.get(key);
|
|
1617
|
+
if (removed !== void 0 && !existsSync(removed)) continue;
|
|
1618
|
+
if (prior === null) this.fileCache.delete(key);
|
|
1619
|
+
else this.fileCache.set(key, prior);
|
|
1620
|
+
}
|
|
1055
1621
|
this.externalFileOwners = priorExternalFileOwners;
|
|
1622
|
+
this.hostSuppliedKeys = priorHostSuppliedKeys;
|
|
1056
1623
|
}
|
|
1057
1624
|
if (this.statusAttemptOpen) {
|
|
1058
1625
|
this.debouncePending.clear();
|
|
@@ -1060,7 +1627,7 @@ var ExtractionSession = class {
|
|
|
1060
1627
|
}
|
|
1061
1628
|
throw err;
|
|
1062
1629
|
}
|
|
1063
|
-
this.publishSourceIngestion(ingested);
|
|
1630
|
+
this.publishSourceIngestion(ingested, "incremental", (path) => this.fileCache.get(path));
|
|
1064
1631
|
commitObservedBatch();
|
|
1065
1632
|
} else if (this.statusAttemptOpen) {
|
|
1066
1633
|
this.debouncePending.clear();
|
|
@@ -1075,88 +1642,251 @@ var ExtractionSession = class {
|
|
|
1075
1642
|
const rejection = this.corpus.rejection(prepared);
|
|
1076
1643
|
if (!rejection) return;
|
|
1077
1644
|
this.heldRejection = rejection.message;
|
|
1645
|
+
this.onOutcome?.({
|
|
1646
|
+
kind: "held",
|
|
1647
|
+
abortedOriginals: rejection.originals
|
|
1648
|
+
});
|
|
1078
1649
|
throw new AbortedParseRejection(rejection.message);
|
|
1079
1650
|
}
|
|
1080
|
-
|
|
1651
|
+
/** Holds a held full read's entries: what it read stays the held bytes,
|
|
1652
|
+
* never overwriting held host bytes with the disk's (a key the host
|
|
1653
|
+
* supplied before that this read took from disk keeps its bytes). */
|
|
1654
|
+
holdObservedEntries(entries, supplied) {
|
|
1655
|
+
for (const entry of entries) {
|
|
1656
|
+
const fromHost = supplied.has(entry.path);
|
|
1657
|
+
if (this.hostSuppliedKeys.has(entry.path) && !fromHost) continue;
|
|
1658
|
+
this.fileCache.set(entry.path, {
|
|
1659
|
+
hash: entry.hash,
|
|
1660
|
+
source: entry.source
|
|
1661
|
+
});
|
|
1662
|
+
if (fromHost) this.hostSuppliedKeys.add(entry.path);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
/** Held sources whose bytes the host supplied and which the disk no
|
|
1666
|
+
* longer matches: a full read not carrying them (`carried`) would
|
|
1667
|
+
* publish the disk's bytes in their place. */
|
|
1668
|
+
staleHostSuppliedSources(carried) {
|
|
1669
|
+
const stale = [];
|
|
1670
|
+
for (const key of this.hostSuppliedKeys) {
|
|
1671
|
+
const held = this.fileCache.get(key);
|
|
1672
|
+
const abs = resolve(this.rootDir, key);
|
|
1673
|
+
if (held === void 0 || carried?.has(abs)) continue;
|
|
1674
|
+
let onDisk;
|
|
1675
|
+
try {
|
|
1676
|
+
onDisk = readFileSync(abs, "utf-8");
|
|
1677
|
+
} catch {
|
|
1678
|
+
onDisk = null;
|
|
1679
|
+
}
|
|
1680
|
+
if (onDisk === null || contentHash(onDisk) !== held.hash) stale.push(key);
|
|
1681
|
+
}
|
|
1682
|
+
return stale.sort();
|
|
1683
|
+
}
|
|
1684
|
+
/** `raw` is the attempt's input by original path, from which a
|
|
1685
|
+
* quarantined original stays held: every later pass re-ingests it, so it
|
|
1686
|
+
* keeps counting as skipped (nothing it renders is pruned) and its fix
|
|
1687
|
+
* edit compares against its bytes. */
|
|
1688
|
+
publishSourceIngestion(result, pass, raw) {
|
|
1689
|
+
const endedHold = this.heldRejection !== null;
|
|
1690
|
+
const previousOwnership = this.corpus.published.ownership;
|
|
1081
1691
|
this.heldRejection = null;
|
|
1082
|
-
|
|
1692
|
+
const held = new Map(result.originalEntries.map((entry) => [entry.path, {
|
|
1083
1693
|
hash: entry.hash,
|
|
1084
1694
|
source: entry.source
|
|
1085
1695
|
}]));
|
|
1696
|
+
for (const path of result.skippedOriginals ?? []) {
|
|
1697
|
+
const entry = raw(path);
|
|
1698
|
+
if (entry) held.set(path, {
|
|
1699
|
+
hash: entry.hash ?? contentHash(entry.source),
|
|
1700
|
+
source: entry.source
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
this.fileCache = held;
|
|
1086
1704
|
this.corpus.publish(result);
|
|
1705
|
+
const staged = this.stagedPublication;
|
|
1706
|
+
if (staged === null) return;
|
|
1707
|
+
this.stagedPublication = null;
|
|
1708
|
+
const previous = this.publication;
|
|
1709
|
+
const publication = {
|
|
1710
|
+
...staged,
|
|
1711
|
+
ownership: result.ownership,
|
|
1712
|
+
externalPackageDirs: this.externalPackageDirs,
|
|
1713
|
+
externalSourceEntries: this.externalSourceEntries,
|
|
1714
|
+
externalSourceSideEffects: this.externalSourceSideEffects
|
|
1715
|
+
};
|
|
1716
|
+
this.publication = publication;
|
|
1717
|
+
setPublication(this.key, publication);
|
|
1718
|
+
this.onOutcome?.({
|
|
1719
|
+
kind: "published",
|
|
1720
|
+
publication,
|
|
1721
|
+
pass,
|
|
1722
|
+
planChanged: diffFilePlans(snapshotFilePlans(previous?.manifest ?? null), snapshotFilePlans(publication.manifest)),
|
|
1723
|
+
endedHold,
|
|
1724
|
+
redeliver: endedHold ? Object.values(result.ownership).filter((owner) => previousOwnership[owner.originalPath]?.originalHash !== owner.originalHash).map((owner) => owner.originalPath) : []
|
|
1725
|
+
});
|
|
1087
1726
|
}
|
|
1088
1727
|
/** Full analysis + publication, registered in the one in-flight
|
|
1089
|
-
* transaction slot; a nested reload leaves the enclosing registration.
|
|
1090
|
-
|
|
1728
|
+
* transaction slot; a nested reload leaves the enclosing registration.
|
|
1729
|
+
* `sources` are bytes a host already read (see WatchChanges.sources):
|
|
1730
|
+
* the full read uses them, and those of the observations it takes, in
|
|
1731
|
+
* place of the disk. */
|
|
1732
|
+
async runFullPipeline(pending = [], sources = /* @__PURE__ */ new Map()) {
|
|
1091
1733
|
if (this.releasePublicationClaim === null) {
|
|
1092
|
-
const releaseOwner = claimExclusiveSessionOwner(`${this.driverLabel}:${this.sessionDir}`);
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
};
|
|
1734
|
+
const releaseOwner = claimExclusiveSessionOwner(this.key, `${this.driverLabel}:${this.sessionDir}`);
|
|
1735
|
+
if (this.persistArtifacts) {
|
|
1736
|
+
mkdirSync(this.sessionDir, { recursive: true });
|
|
1737
|
+
holdBuildClaimRecord(this.key, () => holdDirectoryClaim(this.sessionDir, { build: this.sessionId }));
|
|
1738
|
+
}
|
|
1739
|
+
this.releasePublicationClaim = releaseOwner;
|
|
1099
1740
|
}
|
|
1100
1741
|
const priorSystem = this.system;
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1742
|
+
const priorRegistration = this.snapshotWatchRegistration();
|
|
1743
|
+
this.attemptRecordedSystemDependencies = false;
|
|
1744
|
+
const nested = getWatchTransaction(this.key) !== null;
|
|
1745
|
+
const observations = nested ? null : this.takeObservations();
|
|
1746
|
+
const taken = observations?.taken ?? null;
|
|
1747
|
+
const transaction = this.runPipelineTransaction(pending, new Map([...observations?.sources ?? [], ...sources]));
|
|
1748
|
+
if (taken !== null) {
|
|
1749
|
+
setWatchTransaction(this.key, transaction);
|
|
1750
|
+
this.settleTaken(taken, transaction);
|
|
1751
|
+
}
|
|
1104
1752
|
try {
|
|
1105
1753
|
await transaction;
|
|
1106
1754
|
} catch (error) {
|
|
1107
1755
|
this.system = priorSystem;
|
|
1756
|
+
this.restoreWatchRegistration(priorRegistration);
|
|
1757
|
+
if (priorSystem !== null && (this.development || error instanceof AbortedParseRejection)) this.systemReloadOwed = true;
|
|
1108
1758
|
throw error;
|
|
1109
1759
|
} finally {
|
|
1110
|
-
if (!nested) setWatchTransaction(null);
|
|
1760
|
+
if (!nested) setWatchTransaction(this.key, null);
|
|
1761
|
+
}
|
|
1762
|
+
this.systemReloadOwed = false;
|
|
1763
|
+
this.lastGoodSystemDependencies = {
|
|
1764
|
+
keys: this.systemDependencyKeys,
|
|
1765
|
+
paths: this.systemDependencyPaths,
|
|
1766
|
+
missing: this.systemMissingPaths,
|
|
1767
|
+
hashes: this.systemDependencyHashes
|
|
1768
|
+
};
|
|
1769
|
+
this.commitSystemAuthority();
|
|
1770
|
+
}
|
|
1771
|
+
/** This attempt's system dependencies: the files its load read, and the
|
|
1772
|
+
* paths it found absent, whose creation is a system change. */
|
|
1773
|
+
recordSystemDependencies(systemPath, dependencies, missing) {
|
|
1774
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1775
|
+
for (const path of [
|
|
1776
|
+
systemPath,
|
|
1777
|
+
...dependencies,
|
|
1778
|
+
...missing
|
|
1779
|
+
]) for (const key of toWatchKeys(path)) keys.add(key);
|
|
1780
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
1781
|
+
for (const dep of dependencies.length > 0 ? dependencies : [systemPath]) try {
|
|
1782
|
+
hashes.set(dep, contentHash(readFileSync(dep, "utf-8")));
|
|
1783
|
+
} catch {
|
|
1784
|
+
hashes.set(dep, "");
|
|
1785
|
+
}
|
|
1786
|
+
for (const path of missing) hashes.set(path, "");
|
|
1787
|
+
this.systemDependencyKeys = keys;
|
|
1788
|
+
this.systemDependencyPaths = dependencies;
|
|
1789
|
+
this.systemMissingPaths = missing;
|
|
1790
|
+
this.systemDependencyHashes = hashes;
|
|
1791
|
+
this.attemptRecordedSystemDependencies = true;
|
|
1792
|
+
}
|
|
1793
|
+
/** Records the configuration inputs and, for a tsconfig-aliased host,
|
|
1794
|
+
* re-reads its aliases. */
|
|
1795
|
+
readConfigInputs(rootDir) {
|
|
1796
|
+
this.configInputPaths = [join(rootDir, "package.json"), ...tsconfigChainPaths(rootDir)];
|
|
1797
|
+
this.configInputKeys = new Set(this.configInputPaths.flatMap((path) => toWatchKeys(path)));
|
|
1798
|
+
this.configInputHashes = new Map(this.configInputPaths.map((path) => [path, fileHashOrEmpty(path)]));
|
|
1799
|
+
if (this.aliasesFromTsconfig) this.pathAliasesJson = buildPathAliasesJson(readTsconfigAliasPairs(rootDir), rootDir)?.json ?? null;
|
|
1800
|
+
}
|
|
1801
|
+
/** What hosts watch for assets, and what the last-good generation
|
|
1802
|
+
* reported about its kits and packages, which a failed full pass must
|
|
1803
|
+
* leave in place. */
|
|
1804
|
+
snapshotWatchRegistration() {
|
|
1805
|
+
return {
|
|
1806
|
+
assetDependencyPaths: new Set(this.assetDependencyPaths),
|
|
1807
|
+
assetDependencyKeys: new Set(this.assetDependencyKeys),
|
|
1808
|
+
kitDescriptors: this.kitDescriptors,
|
|
1809
|
+
lastExternalOutcomes: this.lastExternalOutcomes
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
/** Restores the last-good registration. The system dependencies are the
|
|
1813
|
+
* last committed pass's and, when its load got that far, this failed
|
|
1814
|
+
* attempt's (spec §2.3): a fix to any of them must reach the owed reload,
|
|
1815
|
+
* and an earlier failure's are replaced, never accumulated. */
|
|
1816
|
+
restoreWatchRegistration(prior) {
|
|
1817
|
+
Object.assign(this, prior);
|
|
1818
|
+
const lastGood = this.lastGoodSystemDependencies;
|
|
1819
|
+
if (!this.attemptRecordedSystemDependencies) {
|
|
1820
|
+
this.systemDependencyKeys = lastGood.keys;
|
|
1821
|
+
this.systemDependencyPaths = lastGood.paths;
|
|
1822
|
+
this.systemMissingPaths = lastGood.missing;
|
|
1823
|
+
this.systemDependencyHashes = lastGood.hashes;
|
|
1824
|
+
return;
|
|
1111
1825
|
}
|
|
1826
|
+
this.systemMissingPaths = [.../* @__PURE__ */ new Set([...lastGood.missing, ...this.systemMissingPaths])];
|
|
1827
|
+
this.systemDependencyKeys = /* @__PURE__ */ new Set([...lastGood.keys, ...this.systemDependencyKeys]);
|
|
1828
|
+
this.systemDependencyPaths = [.../* @__PURE__ */ new Set([...lastGood.paths, ...this.systemDependencyPaths])];
|
|
1829
|
+
this.systemDependencyHashes = new Map([...this.systemDependencyHashes, ...lastGood.hashes]);
|
|
1830
|
+
}
|
|
1831
|
+
/** The build's authority becomes the system this session committed, with
|
|
1832
|
+
* it and never before: a failed reload leaves the committed one, and the
|
|
1833
|
+
* context proofs made against it. A committed reload discards every
|
|
1834
|
+
* other context's proof, even for an unchanged system: the reload may
|
|
1835
|
+
* have changed what another context resolves, so each loads again. */
|
|
1836
|
+
commitSystemAuthority() {
|
|
1837
|
+
setSystemAuthority(this.key, {
|
|
1838
|
+
...systemLoad(this.system),
|
|
1839
|
+
context: this.resolutionContextText(),
|
|
1840
|
+
label: this.resolutionContextLabel(),
|
|
1841
|
+
others: /* @__PURE__ */ new Map()
|
|
1842
|
+
});
|
|
1112
1843
|
}
|
|
1113
|
-
async runPipelineTransaction(pending) {
|
|
1844
|
+
async runPipelineTransaction(pending, hostSources) {
|
|
1114
1845
|
const pipelineStart = this.now();
|
|
1115
1846
|
const bt = {};
|
|
1116
1847
|
this.runSessionStartHygiene();
|
|
1117
|
-
setSessionArtifactDir(this.sessionDir);
|
|
1848
|
+
setSessionArtifactDir(this.key, this.sessionDir);
|
|
1118
1849
|
const rootDir = this.rootDir;
|
|
1119
1850
|
const priorSourceState = this.snapshotSourceState();
|
|
1120
1851
|
const resolvedSystemPath = resolve(rootDir, this.options.system);
|
|
1852
|
+
this.readConfigInputs(rootDir);
|
|
1853
|
+
const context = {
|
|
1854
|
+
conditions: this.conditions,
|
|
1855
|
+
workspaces: declaredWorkspacePackages(rootDir),
|
|
1856
|
+
extensions: this.extensionPolicy
|
|
1857
|
+
};
|
|
1858
|
+
this.log(`extension order: ${context.extensions.resolverVersion}`);
|
|
1121
1859
|
let t = this.now();
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
}
|
|
1860
|
+
try {
|
|
1861
|
+
this.system = this.loadSystemUnder(context);
|
|
1862
|
+
} catch (error) {
|
|
1863
|
+
const reads = systemLoadReads(error);
|
|
1864
|
+
this.recordSystemDependencies(resolvedSystemPath, reads.dependencies, reads.missing);
|
|
1865
|
+
throw error;
|
|
1866
|
+
}
|
|
1129
1867
|
this.assetCopyCache.clear();
|
|
1130
1868
|
this.assetDependencyPaths.clear();
|
|
1131
1869
|
this.assetDependencyKeys.clear();
|
|
1132
|
-
|
|
1133
|
-
const deps = this.system.dependencies ?? [];
|
|
1134
|
-
const keys = /* @__PURE__ */ new Set();
|
|
1135
|
-
for (const key of toWatchKeys(resolvedSystemPath)) keys.add(key);
|
|
1136
|
-
for (const dep of deps) for (const key of toWatchKeys(dep)) keys.add(key);
|
|
1137
|
-
this.systemDependencyKeys = keys;
|
|
1138
|
-
this.systemDependencyPaths = deps;
|
|
1139
|
-
const hashes = /* @__PURE__ */ new Map();
|
|
1140
|
-
for (const dep of deps.length > 0 ? deps : [resolvedSystemPath]) try {
|
|
1141
|
-
hashes.set(dep, contentHash(readFileSync(dep, "utf-8")));
|
|
1142
|
-
} catch {
|
|
1143
|
-
hashes.set(dep, "");
|
|
1144
|
-
}
|
|
1145
|
-
this.systemDependencyHashes = hashes;
|
|
1146
|
-
}
|
|
1870
|
+
this.recordSystemDependencies(resolvedSystemPath, this.system.dependencies ?? [], this.system.missing ?? []);
|
|
1147
1871
|
bt.systemLoad = this.elapsed(t);
|
|
1148
1872
|
t = this.now();
|
|
1149
|
-
const { excludeMatcher, extensionsSet } = this.
|
|
1873
|
+
const { excludeMatcher, extensionsSet } = this.scanConfig;
|
|
1150
1874
|
const files = discoverFiles(rootDir, rootDir, excludeMatcher, extensionsSet);
|
|
1875
|
+
if (claimUnmatchedExcludeReport(this.key)) for (const line of unmatchedExcludeLines(excludeMatcher)) this.logger.error(line);
|
|
1151
1876
|
bt.fileDiscovery = this.elapsed(t);
|
|
1152
1877
|
t = this.now();
|
|
1153
1878
|
const rawEntries = [];
|
|
1154
1879
|
const ingestionFailures = [];
|
|
1880
|
+
const readPaths = /* @__PURE__ */ new Set();
|
|
1881
|
+
const supplied = /* @__PURE__ */ new Set();
|
|
1155
1882
|
for (const filePath of files) {
|
|
1156
1883
|
const relPath = relative(rootDir, filePath);
|
|
1884
|
+
readPaths.add(filePath);
|
|
1157
1885
|
let source;
|
|
1158
1886
|
try {
|
|
1159
|
-
|
|
1887
|
+
const hostSource = hostSources.get(filePath);
|
|
1888
|
+
source = hostSource ?? readFileSync(filePath, "utf-8");
|
|
1889
|
+
if (hostSource !== void 0) supplied.add(relPath);
|
|
1160
1890
|
} catch (err) {
|
|
1161
1891
|
if (!isDeletedSource(err)) ingestionFailures.push(unreadableSourceDiagnostic(relPath, err));
|
|
1162
1892
|
continue;
|
|
@@ -1168,21 +1898,36 @@ var ExtractionSession = class {
|
|
|
1168
1898
|
});
|
|
1169
1899
|
}
|
|
1170
1900
|
bt.fileRead = this.elapsed(t);
|
|
1901
|
+
const rawPaths = new Set(rawEntries.map((entry) => entry.path));
|
|
1171
1902
|
t = this.now();
|
|
1172
|
-
const
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1903
|
+
const resolver = discoveryResolver(this.hostResolve, this.engine(), {
|
|
1904
|
+
rootDir,
|
|
1905
|
+
conditions: context.conditions,
|
|
1906
|
+
workspaces: context.workspaces,
|
|
1907
|
+
extensions: context.extensions
|
|
1176
1908
|
});
|
|
1177
|
-
discoveryDiagnostics
|
|
1178
|
-
const
|
|
1909
|
+
const discoveryDiagnostics = [];
|
|
1910
|
+
const systemPackageRoot = findPackageRoot(resolvedSystemPath);
|
|
1911
|
+
const kitMembers = /* @__PURE__ */ new Map();
|
|
1912
|
+
const packageNames = await extractSystemFilePackages(resolvedSystemPath, { systemMembership: this.engine().systemMembership ?? requireEngine().systemMembership }, (diagnostic) => discoveryDiagnostics.push(diagnostic), (specifier, importer) => findPackageRoot(importer) === systemPackageRoot ? resolver.resolveSpecifier(specifier) : resolver.loaderResolve(specifier, dirname(importer)), context.extensions, kitMembers);
|
|
1913
|
+
discoveryDiagnostics.push(...importedKitDiagnostics(rawEntries, this.engine(), rootDir, packageNames, (entries) => this.corpus.facts(entries)));
|
|
1179
1914
|
const rawExternalFiles = /* @__PURE__ */ new Map();
|
|
1180
1915
|
const collected = await collectExternalPackageSources({
|
|
1916
|
+
extensions: context.extensions,
|
|
1917
|
+
kitSourceRoot: nativeKitSourceRoot(this.engine(), rootDir, context.conditions, context.workspaces, extensionPolicyJson(context.extensions)),
|
|
1918
|
+
locatePackage: nativePackageLocator(this.engine(), context.workspaces),
|
|
1181
1919
|
specifiers: packageNames,
|
|
1182
|
-
resolveSpecifier: (
|
|
1920
|
+
resolveSpecifier: async (specifier) => kitMembers.get(specifier) ?? await resolver.resolveSpecifier(specifier),
|
|
1921
|
+
locateFrom: (specifier) => {
|
|
1922
|
+
const file = kitMembers.get(specifier);
|
|
1923
|
+
return file === void 0 ? rootDir : dirname(file);
|
|
1924
|
+
},
|
|
1925
|
+
unresolvedReason: resolver.unresolvedReason,
|
|
1926
|
+
requireSourceEntry: resolver.requireSourceEntry,
|
|
1183
1927
|
rootDir,
|
|
1184
1928
|
extensionsSet,
|
|
1185
|
-
hasEntry: (relPath) =>
|
|
1929
|
+
hasEntry: (relPath) => rawPaths.has(relPath),
|
|
1930
|
+
hostOutputDirs: this.structuralExclude.map((dir) => resolve(rootDir, dir)),
|
|
1186
1931
|
onSourceRead: (source, _relPath, absPath) => {
|
|
1187
1932
|
rawExternalFiles.set(absPath, contentHash(source));
|
|
1188
1933
|
},
|
|
@@ -1201,21 +1946,22 @@ var ExtractionSession = class {
|
|
|
1201
1946
|
});
|
|
1202
1947
|
this.lastExternalOutcomes = collected.outcomes;
|
|
1203
1948
|
this.ingestionFailureDiagnostics = ingestionFailures;
|
|
1949
|
+
if (resolver.hostFirst) discoveryDiagnostics.push(...systemInstanceMismatches({
|
|
1950
|
+
rootDir,
|
|
1951
|
+
specifiers: packageNames,
|
|
1952
|
+
instances: collected.packageInstances,
|
|
1953
|
+
evaluated: this.systemDependencyPaths,
|
|
1954
|
+
loaderResolve: (specifier) => resolver.loaderResolve(specifier, dirname(resolvedSystemPath))
|
|
1955
|
+
}));
|
|
1204
1956
|
surfaceManifestDiagnostics({ diagnostics: [...discoveryDiagnostics, ...collected.diagnostics] }, (message) => this.warn(message), {
|
|
1205
1957
|
levels: this.options.diagnostics,
|
|
1206
1958
|
strict: this.options.strict,
|
|
1207
1959
|
reportErrors: this.reportErrors()
|
|
1208
1960
|
});
|
|
1209
|
-
const unresolvableMessage = unresolvableIncludesMessage(collected.outcomes);
|
|
1210
|
-
if (unresolvableMessage !== null)
|
|
1211
|
-
if (this.options.strict) this.strictFailure(unresolvableMessage);
|
|
1212
|
-
else this.warn(unresolvableMessage);
|
|
1213
|
-
}
|
|
1961
|
+
const unresolvableMessage = unresolvableIncludesMessage(collected.outcomes, relative(rootDir, resolvedSystemPath));
|
|
1962
|
+
if (unresolvableMessage !== null) this.strictOrWarn(unresolvableMessage);
|
|
1214
1963
|
const staleDistMessage = staleDistIncludesMessage(collected.outcomes);
|
|
1215
|
-
if (staleDistMessage !== null)
|
|
1216
|
-
if (this.options.strict) this.strictFailure(staleDistMessage);
|
|
1217
|
-
else this.warn(staleDistMessage);
|
|
1218
|
-
}
|
|
1964
|
+
if (staleDistMessage !== null) this.strictOrWarn(staleDistMessage);
|
|
1219
1965
|
const rejectedSpecifiers = /* @__PURE__ */ new Set();
|
|
1220
1966
|
const crossVolumeDetails = [];
|
|
1221
1967
|
for (const [dir, specifiers] of Object.entries(collected.dirOwnerSets)) {
|
|
@@ -1232,6 +1978,7 @@ var ExtractionSession = class {
|
|
|
1232
1978
|
for (const message of this.stickyDiagnostics.values()) this.warn(message);
|
|
1233
1979
|
const admitted = excludeCollectedPackages(collected, rejectedSpecifiers, rootDir);
|
|
1234
1980
|
this.kitDescriptors = admitted.kitDescriptors;
|
|
1981
|
+
this.packageInstances = admitted.packageInstances;
|
|
1235
1982
|
try {
|
|
1236
1983
|
const identity = createSourceIdentity(rootDir);
|
|
1237
1984
|
this.externalRootOwners = /* @__PURE__ */ new Map();
|
|
@@ -1267,43 +2014,62 @@ var ExtractionSession = class {
|
|
|
1267
2014
|
this.externalSourceEntries = admitted.sourceEntries;
|
|
1268
2015
|
this.externalSourceSideEffects = admitted.sourceEntrySideEffects;
|
|
1269
2016
|
for (const entry of admitted.entries) {
|
|
1270
|
-
const
|
|
2017
|
+
const entryPath = resolve(rootDir, entry.path);
|
|
2018
|
+
readPaths.add(entryPath);
|
|
2019
|
+
const hostSource = hostSources.get(entryPath);
|
|
2020
|
+
if (hostSource !== void 0) supplied.add(entry.path);
|
|
2021
|
+
const source = hostSource ?? entry.source;
|
|
1271
2022
|
rawEntries.push({
|
|
1272
2023
|
path: entry.path,
|
|
1273
|
-
source
|
|
1274
|
-
hash
|
|
2024
|
+
source,
|
|
2025
|
+
hash: contentHash(source)
|
|
1275
2026
|
});
|
|
1276
2027
|
}
|
|
1277
2028
|
this.externalPackageDirs = admitted.packageDirs;
|
|
1278
2029
|
this.externalLinkedDirs = admitted.linkedDirs;
|
|
1279
2030
|
this.externalOutputDirs = admitted.outputDirs;
|
|
1280
2031
|
bt.packageResolve = this.elapsed(t);
|
|
2032
|
+
const misspelled = misspelledHostSources(hostSources, readPaths).map(({ path, read }) => `${relative(rootDir, path)} (read as ${read.map((readPath) => relative(rootDir, readPath)).join(", ")})`);
|
|
2033
|
+
if (misspelled.length > 0) throw new DeferredReloadRefusal(`[${this.driverLabel}] reload not published: the host supplied bytes for ${misspelled.join(", ")}, which the full read reaches only under another spelling; the last generation stays served`);
|
|
1281
2034
|
this.beginStatusAttempt();
|
|
1282
2035
|
let accepted;
|
|
2036
|
+
const readEntries = this.withoutOutputSources(rawEntries);
|
|
1283
2037
|
try {
|
|
1284
|
-
accepted = await this.corpus.prepare(
|
|
2038
|
+
accepted = await this.corpus.prepare(readEntries);
|
|
1285
2039
|
this.throwOnAbortedParse(accepted);
|
|
1286
2040
|
this.skippedSources = accepted.skippedOriginals ?? [];
|
|
2041
|
+
this.ingestionOpenings = accepted.openings ?? [];
|
|
2042
|
+
this.ingestionAdapted = adaptedOriginals(accepted.ownership);
|
|
1287
2043
|
} catch (err) {
|
|
2044
|
+
if (err instanceof AbortedParseRejection) this.holdObservedEntries(readEntries, supplied);
|
|
1288
2045
|
this.debouncePending.clear();
|
|
1289
2046
|
this.writeAnalysisStatus("failed", pending, String(err));
|
|
1290
2047
|
throw err;
|
|
1291
2048
|
}
|
|
1292
2049
|
this.externalFileOwners = projectExternalFileOwners(accepted, this.externalFileOwners);
|
|
1293
2050
|
bt.fileCount = accepted.analysisEntries.length;
|
|
1294
|
-
clearEngineCache(
|
|
2051
|
+
clearEngineCache(() => this.engine());
|
|
1295
2052
|
await this.analyzeAndEmit(accepted.analysisEntries, packageMap, false, bt, pipelineStart, pending);
|
|
1296
|
-
this.publishSourceIngestion(accepted);
|
|
1297
|
-
|
|
1298
|
-
setSharedExternalEntries(admitted.sourceEntries);
|
|
1299
|
-
setSharedExternalSideEffects(admitted.sourceEntrySideEffects);
|
|
2053
|
+
this.publishSourceIngestion(accepted, "full", (path) => rawEntries.find((entry) => entry.path === path));
|
|
2054
|
+
this.hostSuppliedKeys = supplied;
|
|
1300
2055
|
this.onExternalRootsCommitted?.(this.externalWatchRoots);
|
|
1301
|
-
setOwningWatchSession(this);
|
|
2056
|
+
setOwningWatchSession(this.key, this);
|
|
1302
2057
|
} catch (err) {
|
|
1303
2058
|
this.restoreSourceState(priorSourceState);
|
|
1304
2059
|
throw err;
|
|
1305
2060
|
}
|
|
1306
2061
|
}
|
|
2062
|
+
/** The full pass's analysis inputs, less any file whose real path lies in
|
|
2063
|
+
* an output directory, each reported. */
|
|
2064
|
+
withoutOutputSources(entries) {
|
|
2065
|
+
const inOutput = outputSourceGuard(this.rootDir, this.structuralExclude);
|
|
2066
|
+
return entries.filter((entry) => {
|
|
2067
|
+
const outputDir = inOutput(resolve(this.rootDir, entry.path));
|
|
2068
|
+
if (outputDir === null) return true;
|
|
2069
|
+
this.warn(outputSourceMessage(entry.path, outputDir));
|
|
2070
|
+
return false;
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
1307
2073
|
/** Per-generation source state captured as ONE object so the restore
|
|
1308
2074
|
* cannot omit a member; deep-copied fields are mutated in place. */
|
|
1309
2075
|
snapshotSourceState() {
|
|
@@ -1325,16 +2091,93 @@ var ExtractionSession = class {
|
|
|
1325
2091
|
restoreSourceState(snapshot) {
|
|
1326
2092
|
Object.assign(this, snapshot);
|
|
1327
2093
|
}
|
|
1328
|
-
/**
|
|
1329
|
-
*
|
|
2094
|
+
/** The system under `context`: the one load path for the build's
|
|
2095
|
+
* authority and for a participant checking its own context. */
|
|
2096
|
+
loadSystemUnder(context) {
|
|
2097
|
+
return loadSystemConfig(() => this.engine(), {
|
|
2098
|
+
systemPath: resolve(this.rootDir, this.options.system),
|
|
2099
|
+
rootDir: this.rootDir,
|
|
2100
|
+
prefix: this.options.prefix,
|
|
2101
|
+
prefixContextualVars: this.options.prefixContextualVars,
|
|
2102
|
+
conditions: context.conditions,
|
|
2103
|
+
workspaces: context.workspaces,
|
|
2104
|
+
extensions: context.extensions
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
/** What the system loader reads of this participant besides the root, as
|
|
2108
|
+
* stable text: its conditions and extension order. */
|
|
2109
|
+
resolutionContextText() {
|
|
2110
|
+
return JSON.stringify([this.conditions, extensionPolicyJson(this.extensionPolicy)]);
|
|
2111
|
+
}
|
|
2112
|
+
resolutionContextLabel() {
|
|
2113
|
+
return `${this.driverLabel} with conditions [${this.conditions.join(", ")}] and extension order ${this.extensionPolicy.resolverVersion}`;
|
|
2114
|
+
}
|
|
2115
|
+
/** One agreed system authority (SA5): a participant whose resolution
|
|
2116
|
+
* context differs from the one the build's system was loaded under loads
|
|
2117
|
+
* it once under its own, and proceeds only on the same system. Otherwise
|
|
2118
|
+
* it throws, naming both contexts, whichever participant came first. */
|
|
2119
|
+
agreeWithSystemAuthority() {
|
|
2120
|
+
const authority = getSystemAuthority(this.key);
|
|
2121
|
+
if (authority === null) return;
|
|
2122
|
+
const context = this.resolutionContextText();
|
|
2123
|
+
if (context === authority.context) return;
|
|
2124
|
+
let own = authority.others.get(context);
|
|
2125
|
+
if (own === void 0) {
|
|
2126
|
+
own = systemLoad(this.loadSystemUnder({
|
|
2127
|
+
conditions: this.conditions,
|
|
2128
|
+
workspaces: declaredWorkspacePackages(this.rootDir),
|
|
2129
|
+
extensions: this.extensionPolicy
|
|
2130
|
+
}));
|
|
2131
|
+
authority.others.set(context, own);
|
|
2132
|
+
}
|
|
2133
|
+
if (own.identity === authority.identity) return;
|
|
2134
|
+
const shared = new Set(authority.dependencies);
|
|
2135
|
+
const mine = new Set(own.dependencies);
|
|
2136
|
+
const differing = [...authority.dependencies.filter((path) => !mine.has(path)), ...own.dependencies.filter((path) => !shared.has(path))].map((path) => relative(this.rootDir, path));
|
|
2137
|
+
throw new SessionConfigurationConflict(this.key, authority.label, this.resolutionContextLabel(), differing.length > 0 ? [`system modules ${differing.join(", ")}`] : ["the evaluated system"], "Every participant of one build must evaluate the same system: give each compiler conditions that select the same system modules.");
|
|
2138
|
+
}
|
|
2139
|
+
/** Release this session's publication claim so a successor may publish,
|
|
2140
|
+
* and leave the build, which retires with its last participant.
|
|
2141
|
+
* Idempotent; `reattach` joins the build again. */
|
|
1330
2142
|
close() {
|
|
1331
2143
|
this.releasePublicationClaim?.();
|
|
1332
2144
|
this.releasePublicationClaim = null;
|
|
2145
|
+
this.hostSuppliedKeys.clear();
|
|
2146
|
+
if (this.joinedKey === null || this.participant === null) return;
|
|
2147
|
+
if (getOwningWatchSession(this.joinedKey) === this) setOwningWatchSession(this.joinedKey, null);
|
|
2148
|
+
leaveSession(this.joinedKey, this.participant);
|
|
2149
|
+
this.participant = null;
|
|
2150
|
+
this.detached = true;
|
|
2151
|
+
}
|
|
2152
|
+
/** A closed participant attaching again: its private analysis is stale,
|
|
2153
|
+
* so it is discarded, and it joins its build again. Idempotent. */
|
|
2154
|
+
reattach() {
|
|
2155
|
+
if (!this.detached) return;
|
|
2156
|
+
this.detached = false;
|
|
2157
|
+
this.system = null;
|
|
2158
|
+
this.joinedKey = null;
|
|
2159
|
+
}
|
|
2160
|
+
/** Brings a reattached participant up to date with a full rescan of a
|
|
2161
|
+
* live build, which reconciles deletions too; true then. A retired build
|
|
2162
|
+
* is false: the host starts it as at first. */
|
|
2163
|
+
async catchUp() {
|
|
2164
|
+
if (getManifestJson(this.key) === null) return false;
|
|
2165
|
+
await this.awaitObservation(this.recordRescan());
|
|
2166
|
+
return true;
|
|
2167
|
+
}
|
|
2168
|
+
/** Queues a full rescan under a new revision. It is never equivalent to
|
|
2169
|
+
* an active attempt: only a full read covers it. */
|
|
2170
|
+
recordRescan() {
|
|
2171
|
+
const queue = observationQueue(this.key);
|
|
2172
|
+
queue.rescan = true;
|
|
2173
|
+
const revision = ++queue.revision;
|
|
2174
|
+
queue.revisions.set(revision, null);
|
|
2175
|
+
return revision;
|
|
1333
2176
|
}
|
|
1334
2177
|
/** Reset analysis state for a system reload: write guards go back to null
|
|
1335
2178
|
* and reseed from the disk envelopes on the next publication. */
|
|
1336
2179
|
resetForHmr() {
|
|
1337
|
-
resetAnalysisStartedPromise();
|
|
2180
|
+
resetAnalysisStartedPromise(this.key);
|
|
1338
2181
|
this.artifactRecords = {
|
|
1339
2182
|
manifest: null,
|
|
1340
2183
|
inputs: null,
|
|
@@ -1345,7 +2188,7 @@ var ExtractionSession = class {
|
|
|
1345
2188
|
/** The (sourceKey, observed hash) pairs of a watch batch — the status
|
|
1346
2189
|
* file's pending set; loaders wait only on observed inputs. */
|
|
1347
2190
|
pendingFromBatch(changes) {
|
|
1348
|
-
const { excludeMatcher, extensionsSet } = this.
|
|
2191
|
+
const { excludeMatcher, extensionsSet } = this.scanConfig;
|
|
1349
2192
|
const pending = [];
|
|
1350
2193
|
for (const path of changes.modifiedFiles ?? []) {
|
|
1351
2194
|
const classified = this.classifyWatchPath(path, {
|
|
@@ -1354,7 +2197,7 @@ var ExtractionSession = class {
|
|
|
1354
2197
|
});
|
|
1355
2198
|
if (!classified) continue;
|
|
1356
2199
|
try {
|
|
1357
|
-
pending.push([classified.key, contentHash(readFileSync(path, "utf-8"))]);
|
|
2200
|
+
pending.push([classified.key, contentHash(changes.sources?.get(path) ?? readFileSync(path, "utf-8"))]);
|
|
1358
2201
|
} catch {}
|
|
1359
2202
|
}
|
|
1360
2203
|
return pending;
|
|
@@ -1366,7 +2209,7 @@ var ExtractionSession = class {
|
|
|
1366
2209
|
if (!identity) return changes;
|
|
1367
2210
|
if (!changes.modifiedFiles && !changes.removedFiles) return changes;
|
|
1368
2211
|
if (this.externalWatchRoots.length === 0) return changes;
|
|
1369
|
-
const { extensionsSet } = this.
|
|
2212
|
+
const { extensionsSet } = this.scanConfig;
|
|
1370
2213
|
const dirtyRoots = /* @__PURE__ */ new Set();
|
|
1371
2214
|
const modified = /* @__PURE__ */ new Set();
|
|
1372
2215
|
const removed = /* @__PURE__ */ new Set();
|
|
@@ -1404,13 +2247,14 @@ var ExtractionSession = class {
|
|
|
1404
2247
|
} catch {
|
|
1405
2248
|
continue;
|
|
1406
2249
|
}
|
|
1407
|
-
if (previous.get(resolved.sourceKey)?.hash !== rawHash) modified.add(abs);
|
|
2250
|
+
if (previous.get(resolved.sourceKey)?.hash !== rawHash || this.ingestionFailureDiagnostics.some((diagnostic) => diagnostic.file === resolved.sourceKey)) modified.add(abs);
|
|
1408
2251
|
}
|
|
1409
2252
|
for (const [key, entry] of previous) if (!seen.has(key)) removed.add(entry.abs);
|
|
1410
2253
|
}
|
|
1411
2254
|
return {
|
|
1412
2255
|
modifiedFiles: modified,
|
|
1413
|
-
removedFiles: removed
|
|
2256
|
+
removedFiles: removed,
|
|
2257
|
+
sources: changes.sources
|
|
1414
2258
|
};
|
|
1415
2259
|
}
|
|
1416
2260
|
/** Route one watcher path: a project-root member (consumer excludes apply)
|
|
@@ -1477,33 +2321,40 @@ var ExtractionSession = class {
|
|
|
1477
2321
|
packageMap,
|
|
1478
2322
|
system,
|
|
1479
2323
|
emitter: {
|
|
1480
|
-
runtimeImport:
|
|
1481
|
-
cssModuleId:
|
|
1482
|
-
systemPropsModuleId: this.systemPropsModuleId
|
|
2324
|
+
runtimeImport: this.runtimeImport,
|
|
2325
|
+
cssModuleId: this.delivery.cssModuleId,
|
|
2326
|
+
systemPropsModuleId: this.delivery.systemPropsModuleId === SESSION_ARTIFACT ? systemPropsPath(this.sessionDir) : this.delivery.systemPropsModuleId
|
|
1483
2327
|
},
|
|
1484
2328
|
pathAliasesJson: this.pathAliasesJson,
|
|
1485
2329
|
staticCssJson: this.staticCssJson,
|
|
1486
2330
|
externalDirs: this.externalPackageDirs.map((dir) => relative(this.rootDir, dir)),
|
|
2331
|
+
rootDir: this.rootDir,
|
|
2332
|
+
assets: this.assetClassification,
|
|
2333
|
+
adapted: this.ingestionAdapted,
|
|
2334
|
+
loadExpander: this.loadExpander,
|
|
1487
2335
|
devMode: this.engineDevMode(devMode),
|
|
1488
2336
|
analysisContext: {
|
|
1489
2337
|
skippedSources: [...this.skippedSources, ...this.ingestionFailureDiagnostics.map((d) => d.file)].map((file) => isAbsolute(file) ? relative(this.rootDir, file) : file),
|
|
1490
2338
|
unbundledComputedImports: this.host.unbundledComputedImports,
|
|
2339
|
+
openings: [...this.ingestionOpenings],
|
|
1491
2340
|
packageDirs: this.externalPackageDirs.map((dir) => relative(this.rootDir, dir)),
|
|
1492
2341
|
linkedDirs: this.externalLinkedDirs.map((dir) => relative(this.rootDir, dir)),
|
|
1493
|
-
outputDirs: this.externalOutputDirs.map((dir) => relative(this.rootDir, dir))
|
|
2342
|
+
outputDirs: this.externalOutputDirs.map((dir) => relative(this.rootDir, dir)),
|
|
2343
|
+
extensions: this.extensionPolicy
|
|
1494
2344
|
}
|
|
1495
2345
|
};
|
|
1496
2346
|
this.writeAnalysisStatus("analyzing", pending);
|
|
1497
|
-
const result = runProjectAnalysis(
|
|
2347
|
+
const result = runProjectAnalysis(() => this.engine(), {
|
|
1498
2348
|
...analysisOptions,
|
|
1499
2349
|
warn: (message) => this.warn(message),
|
|
1500
2350
|
info: (message) => this.log(message),
|
|
1501
2351
|
strict: this.options.strict,
|
|
1502
2352
|
diagnostics: this.options.diagnostics,
|
|
1503
2353
|
reportErrors: this.reportErrors(),
|
|
1504
|
-
extraDiagnostics: this.ingestionFailureDiagnostics,
|
|
2354
|
+
extraDiagnostics: [...this.ingestionFailureDiagnostics, ...await this.compareAliasesWithHost(fileEntries)],
|
|
1505
2355
|
kitDescriptors: this.kitDescriptors
|
|
1506
2356
|
});
|
|
2357
|
+
if (result.manifest) assertRuntimeImportSuppliesTerminals(this.runtimeImport, result.manifest);
|
|
1507
2358
|
enforceExternalTokenContracts({
|
|
1508
2359
|
diagnostics: result.manifest?.diagnostics,
|
|
1509
2360
|
fileOwners: this.externalFileOwners,
|
|
@@ -1538,12 +2389,12 @@ var ExtractionSession = class {
|
|
|
1538
2389
|
declaration,
|
|
1539
2390
|
variables,
|
|
1540
2391
|
postProcessCss(body, {
|
|
1541
|
-
minify: this.options.minify ??
|
|
2392
|
+
minify: this.options.minify ?? this.startupMode === "production",
|
|
1542
2393
|
targets: this.lcssTargets,
|
|
1543
2394
|
warnFn: (msg) => this.warn(msg)
|
|
1544
2395
|
})
|
|
1545
2396
|
].filter(Boolean).join("\n");
|
|
1546
|
-
reportSurvivingAssetPlaceholders(fullCss, {
|
|
2397
|
+
if (!this.hostSubstitutesAssets) reportSurvivingAssetPlaceholders(fullCss, {
|
|
1547
2398
|
strict: this.options.strict,
|
|
1548
2399
|
warn: (message) => this.warn(message),
|
|
1549
2400
|
reportErrors: this.reportErrors(),
|
|
@@ -1556,7 +2407,6 @@ var ExtractionSession = class {
|
|
|
1556
2407
|
prefix: `[${this.driverLabel}]`,
|
|
1557
2408
|
surface: "generated runtime modules"
|
|
1558
2409
|
});
|
|
1559
|
-
setSharedCss(fullCss);
|
|
1560
2410
|
const systemPropsContent = buildSystemPropsModule({
|
|
1561
2411
|
systemPropMapJson: JSON.stringify(manifest?.system_prop_map ?? {}),
|
|
1562
2412
|
groupRegistryJson: system.groupRegistryJson,
|
|
@@ -1564,9 +2414,6 @@ var ExtractionSession = class {
|
|
|
1564
2414
|
admittedTransforms: manifest?.admitted_transforms ?? {},
|
|
1565
2415
|
typedSystemProps: manifest?.typed_system_props ?? []
|
|
1566
2416
|
});
|
|
1567
|
-
setSharedSystemProps(systemPropsContent);
|
|
1568
|
-
setManifestJson(result.manifestJson);
|
|
1569
|
-
setAnalyzedHashes(new Map(fileEntries.map((entry) => [entry.path, entry.hash])));
|
|
1570
2417
|
this.writeAnalysisStatus("committing", pending);
|
|
1571
2418
|
const epoch = hashReplacementPlans(snapshotFilePlans(manifest), systemPropsContent);
|
|
1572
2419
|
if (this.lastCommit === null) this.lastCommit = this.seedCommitFromDisk();
|
|
@@ -1586,6 +2433,19 @@ var ExtractionSession = class {
|
|
|
1586
2433
|
} else this.writeSessionArtifact(SYSTEM_PROPS_ARTIFACT, systemPropsContent);
|
|
1587
2434
|
this.publishAnalysisCommit(epoch, generation);
|
|
1588
2435
|
this.publishReplacementEpoch(epoch);
|
|
2436
|
+
this.stagedPublication = {
|
|
2437
|
+
manifest,
|
|
2438
|
+
manifestJson: result.manifestJson,
|
|
2439
|
+
analysisGlobalCss: sheets.globalCss,
|
|
2440
|
+
analysisComponentCss: sheets.componentCss,
|
|
2441
|
+
styles: fullCss,
|
|
2442
|
+
systemPropsModule: systemPropsContent,
|
|
2443
|
+
system,
|
|
2444
|
+
packageMap,
|
|
2445
|
+
analyzedHashes: new Map(fileEntries.map((entry) => [entry.path, entry.hash])),
|
|
2446
|
+
packageReadRecord: this.packageReadRecord,
|
|
2447
|
+
replacementEpoch: epoch
|
|
2448
|
+
};
|
|
1589
2449
|
bt.total = this.elapsed(pipelineStart);
|
|
1590
2450
|
logBuildTimings(bt, manifest?.timing, (msg) => this.log(msg), this.verbose);
|
|
1591
2451
|
}
|
|
@@ -1599,6 +2459,86 @@ var ExtractionSession = class {
|
|
|
1599
2459
|
}
|
|
1600
2460
|
/** Write the session's analysis-status artifact. Terminal states
|
|
1601
2461
|
* (idle/failed) close the attempt. */
|
|
2462
|
+
/** Where the host resolves a path alias the analysis reads to another
|
|
2463
|
+
* file (spec §2.2 A7, report only), and how the two were compared. */
|
|
2464
|
+
async compareAliasesWithHost(fileEntries) {
|
|
2465
|
+
const record = packageReadRecord({
|
|
2466
|
+
rootDir: this.rootDir,
|
|
2467
|
+
roots: this.packageInstances,
|
|
2468
|
+
redirected: new Set(this.externalSourceEntries.keys()),
|
|
2469
|
+
analysed: fileEntries.map((entry) => entry.path),
|
|
2470
|
+
rendersBuilder: (file) => this.rendersBuilder(file)
|
|
2471
|
+
});
|
|
2472
|
+
this.packageReadRecord = record;
|
|
2473
|
+
const { moduleSpecifiers } = this.engine();
|
|
2474
|
+
if (this.host.noBundler) return [];
|
|
2475
|
+
if (this.hostResolve === null || moduleSpecifiers === void 0) {
|
|
2476
|
+
this.log("path aliases: no host resolver, comparison unavailable");
|
|
2477
|
+
return [];
|
|
2478
|
+
}
|
|
2479
|
+
try {
|
|
2480
|
+
const { findings, unavailable } = await compareAliases({
|
|
2481
|
+
rootDir: this.rootDir,
|
|
2482
|
+
fileEntries,
|
|
2483
|
+
pathAliasesJson: this.pathAliasesJson,
|
|
2484
|
+
order: this.extensionPolicy,
|
|
2485
|
+
readings: {
|
|
2486
|
+
isAsset: this.assetClassification.isAsset,
|
|
2487
|
+
adapted: this.ingestionAdapted
|
|
2488
|
+
},
|
|
2489
|
+
moduleSpecifiers,
|
|
2490
|
+
resolve: this.hostResolve,
|
|
2491
|
+
cache: this.aliasCache,
|
|
2492
|
+
packages: record
|
|
2493
|
+
});
|
|
2494
|
+
for (const finding of findings) record.reported.add(`${finding.file}\0${finding.component}`);
|
|
2495
|
+
if (unavailable > 0) this.log(`path aliases: ${unavailable} aliased import(s) the host's resolver failed on are unavailable, not agreeing`);
|
|
2496
|
+
return findings;
|
|
2497
|
+
} catch (error) {
|
|
2498
|
+
this.log(`path aliases: comparison unavailable (${String(error)})`);
|
|
2499
|
+
return [];
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
/** Whether the file at a real path declares builder chains, by the
|
|
2503
|
+
* engine's own parse: a module that does, served unread, renders with
|
|
2504
|
+
* empty classes. One the engine cannot read counts as one that does. */
|
|
2505
|
+
rendersBuilder(file) {
|
|
2506
|
+
if (!isEngineTransformExtension(file)) return false;
|
|
2507
|
+
const { extractFacts } = this.engine();
|
|
2508
|
+
if (extractFacts === void 0) return true;
|
|
2509
|
+
const path = relative(this.rootDir, file);
|
|
2510
|
+
try {
|
|
2511
|
+
const facts = parseInternalWire(extractFacts(JSON.stringify([{
|
|
2512
|
+
path,
|
|
2513
|
+
source: readFileSync(file, "utf-8")
|
|
2514
|
+
}])), "extractFacts").files[path];
|
|
2515
|
+
return facts === void 0 || facts.parsePanicked || facts.chains.length > 0;
|
|
2516
|
+
} catch {
|
|
2517
|
+
return true;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
/**
|
|
2521
|
+
* An import the host resolved while bundling (spec §2.2 A4), reported as
|
|
2522
|
+
* the comparison reports it: webpack resolves each module's requests with
|
|
2523
|
+
* the resolve options of the rules that module matches, which its
|
|
2524
|
+
* resolver, asked outside a compilation, does not apply. `importer` and
|
|
2525
|
+
* `resolved` are absolute. A finding the comparison already reported is
|
|
2526
|
+
* not repeated; an error-level one throws in a build.
|
|
2527
|
+
*/
|
|
2528
|
+
reportHostResolution(importer, request, resolved) {
|
|
2529
|
+
const record = getSharedPackageReadRecord(this.key);
|
|
2530
|
+
if (record === null) return;
|
|
2531
|
+
const finding = packageInstanceFinding(record, relative(record.rootDir, importer).split(sep).join(posix.sep), request, resolved);
|
|
2532
|
+
if (finding === null) return;
|
|
2533
|
+
const key = `${finding.file}\0${finding.component}`;
|
|
2534
|
+
if (record.reported.has(key)) return;
|
|
2535
|
+
record.reported.add(key);
|
|
2536
|
+
surfaceManifestDiagnostics({ diagnostics: [finding] }, (message) => this.warn(message), {
|
|
2537
|
+
levels: this.options.diagnostics,
|
|
2538
|
+
strict: this.options.strict,
|
|
2539
|
+
reportErrors: this.reportErrors()
|
|
2540
|
+
});
|
|
2541
|
+
}
|
|
1602
2542
|
writeAnalysisStatus(state, pending, diagnostic) {
|
|
1603
2543
|
const status = {
|
|
1604
2544
|
schema: 2,
|
|
@@ -1708,6 +2648,10 @@ var ExtractionSession = class {
|
|
|
1708
2648
|
/** Maintain the session's epoch witness, rewritten ONLY when the value
|
|
1709
2649
|
* changes, then expose it through the singleton. */
|
|
1710
2650
|
publishReplacementEpoch(epoch) {
|
|
2651
|
+
if (!this.persistArtifacts) {
|
|
2652
|
+
this.lastEpochValue = epoch;
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
1711
2655
|
const onDisk = this.diskEpochValue();
|
|
1712
2656
|
if (this.lastEpochValue === null) this.lastEpochValue = onDisk;
|
|
1713
2657
|
if (epoch !== onDisk) this.writeSessionArtifact(REPLACEMENT_EPOCH_ARTIFACT, JSON.stringify({
|
|
@@ -1719,7 +2663,6 @@ var ExtractionSession = class {
|
|
|
1719
2663
|
this.lastEpochValue = epoch;
|
|
1720
2664
|
this.reconcileSiblingEpochs(epoch);
|
|
1721
2665
|
}
|
|
1722
|
-
setReplacementEpoch(epoch);
|
|
1723
2666
|
}
|
|
1724
2667
|
/** sessions-root listing memo, keyed by the root dir's mtime — a new or
|
|
1725
2668
|
* pruned sibling DIRECTORY moves it; agreeing siblings stay listed. */
|
|
@@ -1764,10 +2707,14 @@ var ExtractionSession = class {
|
|
|
1764
2707
|
}
|
|
1765
2708
|
}
|
|
1766
2709
|
/** The pid of `siblingDir`'s live owner, or null when nothing proves one.
|
|
1767
|
-
*
|
|
1768
|
-
*
|
|
1769
|
-
*
|
|
2710
|
+
* A tree whose build is registered in this process is ours and live,
|
|
2711
|
+
* claim or no claim: a publisher's close() releases the claim while the
|
|
2712
|
+
* build's other participants still serve from it. Otherwise a missing,
|
|
2713
|
+
* undecodable or unreadable claim, or one naming our own pid, means no
|
|
2714
|
+
* owner: the opposite default from the CLI lock check on purpose, so do
|
|
2715
|
+
* not unify the two. */
|
|
1770
2716
|
liveSiblingOwnerPid(siblingDir) {
|
|
2717
|
+
if (findSession(sessionKey(basename(siblingDir), this.rootDir)) !== void 0) return process.pid;
|
|
1771
2718
|
let claim;
|
|
1772
2719
|
try {
|
|
1773
2720
|
claim = readCliLockRecord(siblingDir);
|
|
@@ -1798,7 +2745,7 @@ var ExtractionSession = class {
|
|
|
1798
2745
|
/** Delete legacy flat artifacts and prune sibling session directories past
|
|
1799
2746
|
* the retention window — never this session's own dir. */
|
|
1800
2747
|
runSessionStartHygiene() {
|
|
1801
|
-
if (this.sessionStartHygieneDone) return;
|
|
2748
|
+
if (this.sessionStartHygieneDone || !this.persistArtifacts) return;
|
|
1802
2749
|
this.sessionStartHygieneDone = true;
|
|
1803
2750
|
const animusDir = join(this.rootDir, ANIMUS_ARTIFACT_DIR);
|
|
1804
2751
|
const isCliPublishedSet = this.cliWriterHoldsLock(animusDir) || verifyCommitRecord(animusDir).length === 0;
|
|
@@ -1839,13 +2786,29 @@ var ExtractionSession = class {
|
|
|
1839
2786
|
/** Write one artifact write-then-rename, so a cross-process reader never
|
|
1840
2787
|
* sees a torn file; the tmp name carries the pid to keep writers apart. */
|
|
1841
2788
|
writeSessionArtifact(name, content) {
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
2789
|
+
if (this.persistArtifacts) {
|
|
2790
|
+
const dir = this.sessionDir;
|
|
2791
|
+
mkdirSync(dir, { recursive: true });
|
|
2792
|
+
const tmpPath = join(dir, `.${name}.${process.pid}.tmp`);
|
|
2793
|
+
writeFileSync(tmpPath, content);
|
|
2794
|
+
renameSync(tmpPath, join(dir, name));
|
|
2795
|
+
}
|
|
1847
2796
|
this.onArtifactWrite?.(name, content);
|
|
1848
2797
|
}
|
|
2798
|
+
/** How an asset's bytes are fingerprinted, of a file or of bytes read:
|
|
2799
|
+
* the engine's fast fingerprint where it has both, the content hash
|
|
2800
|
+
* otherwise, so the two always agree. */
|
|
2801
|
+
assetFingerprints() {
|
|
2802
|
+
const { fingerprintFile, fingerprintBytes } = this.engine();
|
|
2803
|
+
if (fingerprintFile && fingerprintBytes) return {
|
|
2804
|
+
ofFile: (path) => fingerprintFile(path) ?? contentHash(readFileSync(path)),
|
|
2805
|
+
ofBytes: fingerprintBytes
|
|
2806
|
+
};
|
|
2807
|
+
return {
|
|
2808
|
+
ofFile: (path) => contentHash(readFileSync(path)),
|
|
2809
|
+
ofBytes: contentHash
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
1849
2812
|
/** Copy each asset() specifier's bytes, from every sheet, into `assets/`
|
|
1850
2813
|
* under a content-hashed name and substitute a url relative to
|
|
1851
2814
|
* styles.css. */
|
|
@@ -1855,12 +2818,12 @@ var ExtractionSession = class {
|
|
|
1855
2818
|
const expected = /* @__PURE__ */ new Set();
|
|
1856
2819
|
this.assetDependencyPaths.clear();
|
|
1857
2820
|
this.assetDependencyKeys.clear();
|
|
2821
|
+
if (this.hostSubstitutesAssets) return sheets;
|
|
1858
2822
|
const urlBySpecifier = /* @__PURE__ */ new Map();
|
|
1859
2823
|
for (const specifier of specifiers) {
|
|
1860
2824
|
const cached = this.assetCopyCache.get(specifier);
|
|
1861
2825
|
if (cached && existsSync(join(assetsDir, cached.fileName))) try {
|
|
1862
|
-
|
|
1863
|
-
if (current.mtimeMs === cached.mtimeMs && current.size === cached.size) {
|
|
2826
|
+
if (this.assetFingerprints().ofFile(cached.sourcePath) === cached.fingerprint) {
|
|
1864
2827
|
this.trackAssetDependency(cached.sourcePath);
|
|
1865
2828
|
expected.add(cached.fileName);
|
|
1866
2829
|
urlBySpecifier.set(specifier, cached.url);
|
|
@@ -1869,27 +2832,18 @@ var ExtractionSession = class {
|
|
|
1869
2832
|
} catch {}
|
|
1870
2833
|
const resolvedPath = this.resolveAssetSpecifier(specifier);
|
|
1871
2834
|
if (!resolvedPath) {
|
|
1872
|
-
|
|
1873
|
-
if (this.options.strict) this.strictFailure(`[${this.driverLabel}] ${message}`);
|
|
1874
|
-
else this.warn(message);
|
|
2835
|
+
this.reportUnresolvableAsset(specifier);
|
|
1875
2836
|
urlBySpecifier.set(specifier, specifier);
|
|
1876
2837
|
continue;
|
|
1877
2838
|
}
|
|
1878
|
-
const bytes =
|
|
1879
|
-
const sourceStat = statSync(resolvedPath);
|
|
2839
|
+
const { fileName, bytes } = writeAssetCopy(resolvedPath, assetsDir);
|
|
1880
2840
|
this.trackAssetDependency(resolvedPath);
|
|
1881
|
-
const ext = extname(resolvedPath);
|
|
1882
|
-
const fileName = `${basename(resolvedPath, ext)}.${contentHash(bytes).slice(0, 8)}${ext}`;
|
|
1883
2841
|
expected.add(fileName);
|
|
1884
|
-
if (!existsSync(assetsDir)) mkdirSync(assetsDir, { recursive: true });
|
|
1885
|
-
const assetPath = join(assetsDir, fileName);
|
|
1886
|
-
if (!existsSync(assetPath)) writeFileSync(assetPath, bytes);
|
|
1887
2842
|
const url = `./${SESSION_ASSETS_DIR}/${fileName}`;
|
|
1888
2843
|
urlBySpecifier.set(specifier, url);
|
|
1889
2844
|
this.assetCopyCache.set(specifier, {
|
|
1890
2845
|
sourcePath: resolvedPath,
|
|
1891
|
-
|
|
1892
|
-
size: sourceStat.size,
|
|
2846
|
+
fingerprint: this.assetFingerprints().ofBytes(bytes),
|
|
1893
2847
|
fileName,
|
|
1894
2848
|
url
|
|
1895
2849
|
});
|
|
@@ -1897,6 +2851,10 @@ var ExtractionSession = class {
|
|
|
1897
2851
|
if (pruneSuperseded) pruneStaleAssets(assetsDir, expected);
|
|
1898
2852
|
return substituteSheetAssets(sheets, urlBySpecifier);
|
|
1899
2853
|
}
|
|
2854
|
+
reportUnresolvableAsset(specifier) {
|
|
2855
|
+
const message = `unresolvable asset() specifier: ${specifier}`;
|
|
2856
|
+
this.strictOrWarn(message, `[${this.driverLabel}] ${message}`);
|
|
2857
|
+
}
|
|
1900
2858
|
trackAssetDependency(path) {
|
|
1901
2859
|
this.assetDependencyPaths.add(path);
|
|
1902
2860
|
for (const key of toWatchKeys(path)) this.assetDependencyKeys.add(key);
|
|
@@ -1918,30 +2876,17 @@ var ExtractionSession = class {
|
|
|
1918
2876
|
return null;
|
|
1919
2877
|
}
|
|
1920
2878
|
};
|
|
1921
|
-
/** Sync `assets/` to the current build's file set; per-entry failures are
|
|
1922
|
-
* tolerated — a concurrent session may have removed the same file. */
|
|
1923
|
-
function pruneStaleAssets(assetsDir, expected) {
|
|
1924
|
-
if (!existsSync(assetsDir)) return;
|
|
1925
|
-
let entries;
|
|
1926
|
-
try {
|
|
1927
|
-
entries = readdirSync(assetsDir);
|
|
1928
|
-
} catch {
|
|
1929
|
-
return;
|
|
1930
|
-
}
|
|
1931
|
-
for (const entry of entries) {
|
|
1932
|
-
if (expected.has(entry)) continue;
|
|
1933
|
-
try {
|
|
1934
|
-
unlinkSync(join(assetsDir, entry));
|
|
1935
|
-
} catch {}
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
2879
|
//#endregion
|
|
1939
2880
|
//#region session/turbopack-orchestrator.ts
|
|
1940
2881
|
async function runSessionPipeline(session) {
|
|
1941
|
-
session.
|
|
2882
|
+
session.delivery = {
|
|
2883
|
+
...session.delivery,
|
|
2884
|
+
systemPropsModuleId: TURBOPACK_SYSTEM_PROPS_ID
|
|
2885
|
+
};
|
|
1942
2886
|
session.persistAnalysisInputs = true;
|
|
1943
2887
|
await session.runFullPipeline();
|
|
1944
2888
|
}
|
|
2889
|
+
/** Each build's watched root: another build of the root watches it too. */
|
|
1945
2890
|
const activeWatcherRoots = /* @__PURE__ */ new Set();
|
|
1946
2891
|
const IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
|
|
1947
2892
|
ANIMUS_ARTIFACT_DIR,
|
|
@@ -1968,17 +2913,18 @@ function isIntrinsicString(value) {
|
|
|
1968
2913
|
}
|
|
1969
2914
|
function startTurbopackWatcher(session, rootDir, options = {}) {
|
|
1970
2915
|
const { debounceMs = 75, watchFn = watch, holdEvents = false } = options;
|
|
1971
|
-
|
|
1972
|
-
activeWatcherRoots.
|
|
2916
|
+
const watched = JSON.stringify([session.sessionId, rootDir]);
|
|
2917
|
+
if (activeWatcherRoots.has(watched)) return { kind: "already-watched" };
|
|
2918
|
+
activeWatcherRoots.add(watched);
|
|
1973
2919
|
session.debounceCeilingMs = debounceMs;
|
|
1974
2920
|
const pendingPaths = /* @__PURE__ */ new Set();
|
|
1975
2921
|
const watchers = /* @__PURE__ */ new Map();
|
|
1976
|
-
let timer = null;
|
|
1977
2922
|
let updateChain = Promise.resolve();
|
|
1978
2923
|
let closed = false;
|
|
1979
2924
|
let died = false;
|
|
1980
2925
|
let holdingEvents = holdEvents;
|
|
1981
2926
|
const externalWatchers = /* @__PURE__ */ new Map();
|
|
2927
|
+
const dependencyWatchers = /* @__PURE__ */ new Map();
|
|
1982
2928
|
let pendingOpened = /* @__PURE__ */ new Map();
|
|
1983
2929
|
let capturedDuringSnapshot = [];
|
|
1984
2930
|
const externalDiagnosticKey = (root) => `external-watch:${root}`;
|
|
@@ -1997,24 +2943,28 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
|
|
|
1997
2943
|
session.stickyDiagnostics?.set(externalDiagnosticKey(root), message);
|
|
1998
2944
|
console.warn(`[animus-extract] ${message}`);
|
|
1999
2945
|
};
|
|
2946
|
+
let cyclesInFlight = 0;
|
|
2000
2947
|
const flush = () => {
|
|
2001
|
-
timer = null;
|
|
2002
2948
|
const modifiedFiles = /* @__PURE__ */ new Set();
|
|
2003
2949
|
const removedFiles = /* @__PURE__ */ new Set();
|
|
2004
2950
|
for (const path of pendingPaths) (existsSync(path) ? modifiedFiles : removedFiles).add(path);
|
|
2005
2951
|
pendingPaths.clear();
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2952
|
+
if (closed) return;
|
|
2953
|
+
cyclesInFlight++;
|
|
2954
|
+
const cycle = session.handleWatchUpdate({
|
|
2955
|
+
modifiedFiles,
|
|
2956
|
+
removedFiles
|
|
2957
|
+
}).catch((err) => {
|
|
2958
|
+
console.warn(`[animus-extract] watch update failed: ${String(err)}`);
|
|
2959
|
+
}).finally(() => {
|
|
2960
|
+
cyclesInFlight--;
|
|
2961
|
+
if (cyclesInFlight > 0) return;
|
|
2962
|
+
if (pendingOpened.size > 0 || capturedDuringSnapshot.length > 0) rollbackPendingExternal();
|
|
2963
|
+
syncDependencyWatchers();
|
|
2016
2964
|
});
|
|
2965
|
+
updateChain = updateChain.then(() => cycle);
|
|
2017
2966
|
};
|
|
2967
|
+
const coalescing = new CoalescingWindow(flush, debounceMs);
|
|
2018
2968
|
const rollbackPendingExternal = () => {
|
|
2019
2969
|
for (const watcher of pendingOpened.values()) watcher.close();
|
|
2020
2970
|
pendingOpened = /* @__PURE__ */ new Map();
|
|
@@ -2023,16 +2973,17 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
|
|
|
2023
2973
|
const closeAll = () => {
|
|
2024
2974
|
if (closed) return;
|
|
2025
2975
|
closed = true;
|
|
2026
|
-
|
|
2027
|
-
timer = null;
|
|
2976
|
+
coalescing.cancel();
|
|
2028
2977
|
for (const watcher of watchers.values()) watcher.close();
|
|
2029
2978
|
watchers.clear();
|
|
2979
|
+
for (const { watcher } of dependencyWatchers.values()) watcher.close();
|
|
2980
|
+
dependencyWatchers.clear();
|
|
2030
2981
|
for (const watcher of externalWatchers.values()) watcher.close();
|
|
2031
2982
|
externalWatchers.clear();
|
|
2032
2983
|
rollbackPendingExternal();
|
|
2033
2984
|
session.onExternalRootResolved = null;
|
|
2034
2985
|
session.onExternalRootsCommitted = null;
|
|
2035
|
-
activeWatcherRoots.delete(
|
|
2986
|
+
activeWatcherRoots.delete(watched);
|
|
2036
2987
|
};
|
|
2037
2988
|
const onWatcherError = (err) => {
|
|
2038
2989
|
died = true;
|
|
@@ -2048,10 +2999,7 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
|
|
|
2048
2999
|
} catch (err) {
|
|
2049
3000
|
console.warn(`[animus-extract] Turbopack debounce status update failed: ${String(err)}`);
|
|
2050
3001
|
}
|
|
2051
|
-
|
|
2052
|
-
timer = setTimeout(flush, debounceMs);
|
|
2053
|
-
timer.unref?.();
|
|
2054
|
-
}
|
|
3002
|
+
coalescing.note();
|
|
2055
3003
|
};
|
|
2056
3004
|
const onEvent = (baseDir, filename) => {
|
|
2057
3005
|
if (closed || !filename) return;
|
|
@@ -2095,6 +3043,57 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
|
|
|
2095
3043
|
degradeExternalRoot(root, err);
|
|
2096
3044
|
}
|
|
2097
3045
|
};
|
|
3046
|
+
/** Whether the root or an external root watcher already reports `path`. */
|
|
3047
|
+
const watchedByRoot = (path) => {
|
|
3048
|
+
if (isPathWithinRoot(rootDir, path) && !relative(rootDir, path).split(/[\\/]/).some((segment) => IGNORED_SEGMENTS.has(segment))) return true;
|
|
3049
|
+
return [...externalWatchers.keys()].some((root) => isPathWithinRoot(root, path));
|
|
3050
|
+
};
|
|
3051
|
+
/** Watches the Session's plan (its files, and the absent paths whose
|
|
3052
|
+
* creation reloads the system) where no root watcher reports them, by
|
|
3053
|
+
* their directories, for those paths only. */
|
|
3054
|
+
const syncDependencyWatchers = () => {
|
|
3055
|
+
if (closed) return;
|
|
3056
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
3057
|
+
const plan = session.watchPlan();
|
|
3058
|
+
for (const path of [...plan.files, ...plan.missing]) {
|
|
3059
|
+
if (watchedByRoot(path)) continue;
|
|
3060
|
+
const files = wanted.get(dirname(path)) ?? /* @__PURE__ */ new Set();
|
|
3061
|
+
files.add(path);
|
|
3062
|
+
wanted.set(dirname(path), files);
|
|
3063
|
+
}
|
|
3064
|
+
for (const [dir, { watcher }] of [...dependencyWatchers]) {
|
|
3065
|
+
if (wanted.has(dir)) continue;
|
|
3066
|
+
watcher.close();
|
|
3067
|
+
dependencyWatchers.delete(dir);
|
|
3068
|
+
}
|
|
3069
|
+
for (const [dir, files] of wanted) {
|
|
3070
|
+
const existing = dependencyWatchers.get(dir);
|
|
3071
|
+
if (existing) {
|
|
3072
|
+
existing.files = files;
|
|
3073
|
+
continue;
|
|
3074
|
+
}
|
|
3075
|
+
try {
|
|
3076
|
+
const watcher = watchFn(dir, { recursive: false }, (_event, name) => {
|
|
3077
|
+
if (closed || !name) return;
|
|
3078
|
+
const abs = join(dir, name.toString());
|
|
3079
|
+
if (dependencyWatchers.get(dir)?.files.has(abs)) enqueuePath(abs);
|
|
3080
|
+
});
|
|
3081
|
+
watcher.on("error", (err) => {
|
|
3082
|
+
watcher.close();
|
|
3083
|
+
dependencyWatchers.delete(dir);
|
|
3084
|
+
console.warn(`[animus-extract] dependency watch on ${dir} failed (${String(err)}); its changes require a dev-server restart`);
|
|
3085
|
+
});
|
|
3086
|
+
watcher.unref?.();
|
|
3087
|
+
dependencyWatchers.set(dir, {
|
|
3088
|
+
watcher,
|
|
3089
|
+
files
|
|
3090
|
+
});
|
|
3091
|
+
} catch (err) {
|
|
3092
|
+
if (errnoCode(err) === "ENOENT") continue;
|
|
3093
|
+
console.warn(`[animus-extract] dependency watch on ${dir} unavailable (${String(err)}); its changes require a dev-server restart`);
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
};
|
|
2098
3097
|
const addWatcher = (dir, recursive) => {
|
|
2099
3098
|
const watcher = watchFn(dir, { recursive }, (_event, filename) => onEvent(dir, filename));
|
|
2100
3099
|
watcher.on("error", onWatcherError);
|
|
@@ -2113,6 +3112,7 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
|
|
|
2113
3112
|
return { kind: "unavailable" };
|
|
2114
3113
|
}
|
|
2115
3114
|
for (const root of session.externalWatchRoots ?? []) openExternalWatcher(root, externalWatchers);
|
|
3115
|
+
syncDependencyWatchers();
|
|
2116
3116
|
session.onExternalRootResolved = (root) => {
|
|
2117
3117
|
if (closed) return;
|
|
2118
3118
|
if (externalWatchers.has(root) || pendingOpened.has(root)) return;
|
|
@@ -2137,7 +3137,8 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
|
|
|
2137
3137
|
onDied: null,
|
|
2138
3138
|
deliverHeldEvents: () => {
|
|
2139
3139
|
holdingEvents = false;
|
|
2140
|
-
|
|
3140
|
+
syncDependencyWatchers();
|
|
3141
|
+
if (pendingPaths.size > 0) coalescing.flush();
|
|
2141
3142
|
return updateChain;
|
|
2142
3143
|
},
|
|
2143
3144
|
settle: () => updateChain
|
|
@@ -2148,4 +3149,4 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
|
|
|
2148
3149
|
};
|
|
2149
3150
|
}
|
|
2150
3151
|
//#endregion
|
|
2151
|
-
export { ANIMUS_ARTIFACT_DIR, ANIMUS_CSS_MODULE_ID, CLI_COMMIT_ARTIFACT, CLI_LOCK_ARTIFACT, CLI_LOCK_HEARTBEAT_INTERVAL_MS, CLI_LOCK_STALE_AFTER_MS, ExtractionSession, MANIFEST_ARTIFACT, SESSION_ASSETS_DIR, STYLES_ARTIFACT, SYSTEM_PROPS_ARTIFACT, TURBOPACK_SYSTEM_PROPS_ID, analysisCommitPath, analysisInputsPath, analysisStatusPath, checkLockLiveness, collectSessionAssets, decodeCommitRecord, engineApi, getAnalysisStartedPromise, getAnalyzedHashes, getManifestJson, getReplacementEpoch, getSessionArtifactDir, getSharedCss, getSharedExternalDirs, getSharedExternalEntries, getSharedExternalSideEffects, getSharedSystemProps, holdDirectoryClaim, isLockHolderAlive, lockRecordBytes, manifestPath, pruneStaleAssets, readCliLockRecord, readJsonEnvelope, replacementEpochPath, resetAnalysisStartedPromise, runSessionPipeline, sessionArtifactDir,
|
|
3152
|
+
export { ANIMUS_ARTIFACT_DIR, ANIMUS_CSS_MODULE_ID, CLI_COMMIT_ARTIFACT, CLI_LOCK_ARTIFACT, CLI_LOCK_HEARTBEAT_INTERVAL_MS, CLI_LOCK_STALE_AFTER_MS, COALESCING_WINDOW_MS, CoalescingWindow, EnvironmentBuilds, EnvironmentFailure, ExtractionSession, MANIFEST_ARTIFACT, SESSION_ARTIFACT, SESSION_ASSETS_DIR, STYLES_ARTIFACT, SYSTEM_PROPS_ARTIFACT, SessionConfigurationConflict, TURBOPACK_SYSTEM_PROPS_ID, analysisCommitPath, analysisInputsPath, analysisStatusPath, checkLockLiveness, collectSessionAssets, createBuildId, decodeCommitRecord, engineApi, engineFactory, engineHandle, findSession, getAnalysisStartedPromise, getAnalyzedHashes, getManifestJson, getReplacementEpoch, getSessionArtifactDir, getSharedCss, getSharedExternalDirs, getSharedExternalEntries, getSharedExternalSideEffects, getSharedSystemProps, holdDirectoryClaim, isLockHolderAlive, joinSession, lockRecordBytes, manifestPath, pruneStaleAssets, readCliLockRecord, readJsonEnvelope, releaseEngineHandle, replacementEpochPath, requireEngine, resetAnalysisStartedPromise, runSessionPipeline, sessionArtifactDir, sessionKey, setAnalysisStartedPromise, startTurbopackWatcher, stylesPath, systemPropsPath, verifyCommitRecord };
|