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