@animus-ui/extract 0.1.25 → 0.1.26

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.
Files changed (83) hide show
  1. package/crates/extract-v2/animus-extract-v2.darwin-arm64.node +0 -0
  2. package/crates/extract-v2/animus-extract-v2.linux-arm64-gnu.node +0 -0
  3. package/crates/extract-v2/animus-extract-v2.linux-x64-gnu.node +0 -0
  4. package/crates/extract-v2/index.d.ts +101 -6
  5. package/dist/index.cjs +48 -5
  6. package/dist/index.mjs +2 -2
  7. package/dist/pipeline/alias-agreement.d.ts +107 -0
  8. package/dist/pipeline/alias-agreement.d.ts.map +1 -0
  9. package/dist/pipeline/analyze-project-args.d.ts +7 -2
  10. package/dist/pipeline/analyze-project-args.d.ts.map +1 -1
  11. package/dist/pipeline/asset-copies.d.ts +20 -0
  12. package/dist/pipeline/asset-copies.d.ts.map +1 -0
  13. package/dist/pipeline/built-in-asset-types.d.ts +9 -0
  14. package/dist/pipeline/built-in-asset-types.d.ts.map +1 -0
  15. package/dist/pipeline/core-options.d.ts +31 -7
  16. package/dist/pipeline/core-options.d.ts.map +1 -1
  17. package/dist/pipeline/discover-packages.d.ts +98 -12
  18. package/dist/pipeline/discover-packages.d.ts.map +1 -1
  19. package/dist/pipeline/engine-adapter.d.ts +41 -3
  20. package/dist/pipeline/engine-adapter.d.ts.map +1 -1
  21. package/dist/pipeline/engine-retirement.d.ts +7 -1
  22. package/dist/pipeline/engine-retirement.d.ts.map +1 -1
  23. package/dist/pipeline/extension-policy.d.ts +95 -0
  24. package/dist/pipeline/extension-policy.d.ts.map +1 -0
  25. package/dist/pipeline/host-assets.d.ts +32 -0
  26. package/dist/pipeline/host-assets.d.ts.map +1 -0
  27. package/dist/pipeline/host-expansions.d.ts +92 -0
  28. package/dist/pipeline/host-expansions.d.ts.map +1 -0
  29. package/dist/pipeline/host-extensions.d.ts +64 -0
  30. package/dist/pipeline/host-extensions.d.ts.map +1 -0
  31. package/dist/pipeline/host-package-resolver.d.ts +40 -0
  32. package/dist/pipeline/host-package-resolver.d.ts.map +1 -0
  33. package/dist/pipeline/index.d.ts +32 -8
  34. package/dist/pipeline/index.d.ts.map +1 -1
  35. package/dist/pipeline/kit-publication.d.ts.map +1 -1
  36. package/dist/pipeline/manifest-diagnostics.d.ts +25 -4
  37. package/dist/pipeline/manifest-diagnostics.d.ts.map +1 -1
  38. package/dist/pipeline/manifest-schema.d.ts +16 -0
  39. package/dist/pipeline/manifest-schema.d.ts.map +1 -1
  40. package/dist/pipeline/output-targets.d.ts +52 -0
  41. package/dist/pipeline/output-targets.d.ts.map +1 -0
  42. package/dist/pipeline/probe-evidence.d.ts +58 -0
  43. package/dist/pipeline/probe-evidence.d.ts.map +1 -0
  44. package/dist/pipeline/run-analysis.d.ts +26 -0
  45. package/dist/pipeline/run-analysis.d.ts.map +1 -1
  46. package/dist/pipeline/runtime-import.d.ts +10 -0
  47. package/dist/pipeline/runtime-import.d.ts.map +1 -0
  48. package/dist/pipeline/served-files.d.ts +16 -0
  49. package/dist/pipeline/served-files.d.ts.map +1 -0
  50. package/dist/pipeline/source-corpus.d.ts +4 -0
  51. package/dist/pipeline/source-corpus.d.ts.map +1 -1
  52. package/dist/pipeline/source-ingestion.d.ts +37 -5
  53. package/dist/pipeline/source-ingestion.d.ts.map +1 -1
  54. package/dist/pipeline/system-config.d.ts +18 -0
  55. package/dist/pipeline/system-config.d.ts.map +1 -1
  56. package/dist/pipeline/system-instances.d.ts +27 -0
  57. package/dist/pipeline/system-instances.d.ts.map +1 -0
  58. package/dist/pipeline/tsconfig-paths.d.ts +4 -0
  59. package/dist/pipeline/tsconfig-paths.d.ts.map +1 -1
  60. package/dist/pipeline/vite-expansions.d.ts +40 -0
  61. package/dist/pipeline/vite-expansions.d.ts.map +1 -0
  62. package/dist/pipeline/webpack-expansions.d.ts +55 -0
  63. package/dist/pipeline/webpack-expansions.d.ts.map +1 -0
  64. package/dist/pipeline/workspace-packages.d.ts +13 -0
  65. package/dist/pipeline/workspace-packages.d.ts.map +1 -0
  66. package/dist/{pipeline-YGWpNr_Z.mjs → pipeline-BzMQ_Z0M.mjs} +2285 -773
  67. package/dist/{pipeline-DOgKR4VZ.cjs → pipeline-DyUf-eKZ.cjs} +2556 -768
  68. package/dist/session/coalescing-window.d.ts +34 -0
  69. package/dist/session/coalescing-window.d.ts.map +1 -0
  70. package/dist/session/extraction-session.d.ts +297 -35
  71. package/dist/session/extraction-session.d.ts.map +1 -1
  72. package/dist/session/index.d.ts +7 -3
  73. package/dist/session/index.d.ts.map +1 -1
  74. package/dist/session/published-set.d.ts +6 -1
  75. package/dist/session/published-set.d.ts.map +1 -1
  76. package/dist/session/resolve-packages.d.ts +9 -5
  77. package/dist/session/resolve-packages.d.ts.map +1 -1
  78. package/dist/session/singleton.d.ts +218 -53
  79. package/dist/session/singleton.d.ts.map +1 -1
  80. package/dist/session/turbopack-orchestrator.d.ts.map +1 -1
  81. package/dist/session.cjs +1462 -510
  82. package/dist/session.mjs +1430 -489
  83. package/package.json +4 -4
package/dist/session.cjs CHANGED
@@ -1,8 +1,107 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_pipeline = require("./pipeline-DOgKR4VZ.cjs");
2
+ const require_pipeline = require("./pipeline-DyUf-eKZ.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 pipeline/system-instances.ts
55
+ /**
56
+ * The system and the analysis read one instance of each kit (spec §2.2
57
+ * A4). Discovery asks the host which instance a kit specifier names; the
58
+ * system loader resolves the system file's imports itself. Until the loader
59
+ * takes the host's instance, a kit whose analysed instance differs from the
60
+ * one the system evaluated is refused: its components would be extracted
61
+ * against another instance's vocabulary.
62
+ */
63
+ function realPath(path$38) {
64
+ try {
65
+ return (0, fs.realpathSync)(path$38);
66
+ } catch {
67
+ return path$38;
68
+ }
69
+ }
70
+ /**
71
+ * One error for each system-file kit specifier whose analysed package
72
+ * instance is not the one the system load evaluated: the file the loader's
73
+ * own resolution names must be among the files the load read, and lie in
74
+ * another package root than the instance discovery read for its package.
75
+ */
76
+ function systemInstanceMismatches(input) {
77
+ const evaluated = new Set(input.evaluated.map(realPath));
78
+ const shown = (path$39) => (0, path.relative)(input.rootDir, path$39).split(path.sep).join("/");
79
+ const diagnostics = [];
80
+ for (const specifier of input.specifiers) {
81
+ if (!require_pipeline.isPackageSpecifier(specifier)) continue;
82
+ const analysed = input.instances[require_pipeline.bareSpecifierPackageName(specifier)];
83
+ if (analysed === void 0) continue;
84
+ let file;
85
+ try {
86
+ file = input.loaderResolve(specifier);
87
+ } catch {
88
+ continue;
89
+ }
90
+ if (file === null || !evaluated.has(realPath(file))) continue;
91
+ const system = realPath(require_pipeline.findPackageRoot(realPath(file)));
92
+ if (system === analysed) continue;
93
+ diagnostics.push({
94
+ file: specifier,
95
+ component: "kit",
96
+ kind: "error",
97
+ 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`,
98
+ code: require_pipeline.SYSTEM_INSTANCE_MISMATCH,
99
+ severity: require_pipeline.severityFor(require_pipeline.SYSTEM_INSTANCE_MISMATCH)
100
+ });
101
+ }
102
+ return diagnostics;
103
+ }
104
+ //#endregion
6
105
  //#region session/session-paths.ts
7
106
  /**
8
107
  * Session artifact names, path derivations, and artifact shapes. Imports no
@@ -169,9 +268,11 @@ function readCliLockRecord(dir) {
169
268
  if (!isPublishedSetRecord(candidate) || !isPublishedSetNumber(candidate.pid)) return { kind: "indeterminate" };
170
269
  const startedAt = candidate.startedAt;
171
270
  const heartbeatAt = candidate.heartbeatAt;
271
+ const build = candidate.build;
172
272
  const record = { pid: candidate.pid };
173
273
  if (isPublishedSetString(startedAt)) record.startedAt = startedAt;
174
274
  if (isPublishedSetString(heartbeatAt)) record.heartbeatAt = heartbeatAt;
275
+ if (isPublishedSetString(build)) record.build = build;
175
276
  return {
176
277
  kind: "held",
177
278
  record
@@ -202,17 +303,19 @@ function checkLockLiveness(record) {
202
303
  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
304
  };
204
305
  }
205
- function lockRecordBytes(startedAt) {
206
- return JSON.stringify({
306
+ function lockRecordBytes(startedAt, build) {
307
+ const record = {
207
308
  pid: process.pid,
208
309
  startedAt,
209
310
  heartbeatAt: (/* @__PURE__ */ new Date()).toISOString()
210
- });
311
+ };
312
+ if (build !== void 0) record.build = build;
313
+ return JSON.stringify(record);
211
314
  }
212
315
  const HEARTBEAT_TEMP_PREFIX = ".lock-heartbeat-";
213
316
  function holdDirectoryClaim(dir, options = {}) {
214
317
  const startedAt = options.startedAt ?? (/* @__PURE__ */ new Date()).toISOString();
215
- const path$9 = (0, path.join)(dir, CLI_LOCK_ARTIFACT);
318
+ const path$37 = (0, path.join)(dir, CLI_LOCK_ARTIFACT);
216
319
  const tempPath = (0, path.join)(dir, `${HEARTBEAT_TEMP_PREFIX}${process.pid}`);
217
320
  const rename = options.write === "rename";
218
321
  let refresh = null;
@@ -225,13 +328,13 @@ function holdDirectoryClaim(dir, options = {}) {
225
328
  const stamp = () => {
226
329
  try {
227
330
  if (rename) {
228
- (0, fs.writeFileSync)(tempPath, lockRecordBytes(startedAt));
229
- (0, fs.renameSync)(tempPath, path$9);
230
- } else (0, fs.writeFileSync)(path$9, lockRecordBytes(startedAt));
331
+ (0, fs.writeFileSync)(tempPath, lockRecordBytes(startedAt, options.build));
332
+ (0, fs.renameSync)(tempPath, path$37);
333
+ } else (0, fs.writeFileSync)(path$37, lockRecordBytes(startedAt, options.build));
231
334
  } catch (error) {
232
335
  stop();
233
336
  if (isMissingFile(error)) return;
234
- console.error(`[animus] Could not write the directory claim ${path$9}: ${String(error)}`);
337
+ console.error(`[animus] Could not write the directory claim ${path$37}: ${String(error)}`);
235
338
  }
236
339
  };
237
340
  stamp();
@@ -242,7 +345,7 @@ function holdDirectoryClaim(dir, options = {}) {
242
345
  return () => {
243
346
  stop();
244
347
  if (rename) (0, fs.rmSync)(tempPath, { force: true });
245
- (0, fs.rmSync)(path$9, { force: true });
348
+ (0, fs.rmSync)(path$37, { force: true });
246
349
  };
247
350
  }
248
351
  /** Snapshot the session's copied asset() files, sorted, BEFORE the session
@@ -257,320 +360,424 @@ function collectSessionAssets(sessionDir) {
257
360
  }));
258
361
  }
259
362
  //#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
363
  //#region session/singleton.ts
364
364
  /**
365
- * globalThis-backed so the ESM plugin copy and the CJS webpack-loader copy
366
- * share one store; module scope alone gives each copy its own instance.
365
+ * A build's shared state lives in one registry on `globalThis`, keyed by its
366
+ * `SessionKey`, so the ESM plugin copy and the CJS webpack-loader copy of
367
+ * this module find the same entry, and two builds of one root never share
368
+ * one. A reader of a build no participant has joined gets the empty value;
369
+ * a writer must have joined it.
367
370
  */
368
- const MANIFEST_KEY = "__animus_manifest_json__";
369
- const ANALYSIS_STARTED_KEY = "__animus_analysis_started__";
370
- const SHARED_CSS_KEY = "__animus_shared_css__";
371
- const SHARED_SYSTEM_PROPS_KEY = "__animus_shared_system_props__";
372
- const SHARED_EXTERNAL_DIRS_KEY = "__animus_external_pkg_dirs__";
373
- const SHARED_EXTERNAL_ENTRIES_KEY = "__animus_external_source_entries__";
374
- const SHARED_EXTERNAL_SIDE_EFFECTS_KEY = "__animus_external_side_effects__";
375
- const ANALYZED_HASHES_KEY = "__animus_analyzed_hashes__";
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];
371
+ function joined(key) {
372
+ const state = findSession(key);
373
+ if (state === void 0) throw new Error(`[animus] no participant has joined build ${key.build} of ${key.root}`);
374
+ return state;
399
375
  }
400
- function setManifestJson(json) {
401
- singletonStore[MANIFEST_KEY] = json;
376
+ function getManifestJson(key) {
377
+ return findSession(key)?.manifestJson ?? null;
378
+ }
379
+ function setManifestJson(key, json) {
380
+ joined(key).manifestJson = json;
402
381
  }
403
382
  /**
404
383
  * A latch over the most recently STARTED analysis, never cleared on settle:
405
384
  * non-null means one was scheduled. In-flight lives in getWatchTransaction.
406
385
  */
407
- function getAnalysisStartedPromise() {
408
- return singletonStore[ANALYSIS_STARTED_KEY];
386
+ function getAnalysisStartedPromise(key) {
387
+ return findSession(key)?.analysisStarted ?? null;
388
+ }
389
+ function setAnalysisStartedPromise(key, promise) {
390
+ joined(key).analysisStarted = promise;
409
391
  }
410
- function setAnalysisStartedPromise(promise) {
411
- singletonStore[ANALYSIS_STARTED_KEY] = promise;
392
+ function resetAnalysisStartedPromise(key) {
393
+ const state = findSession(key);
394
+ if (state !== void 0) state.analysisStarted = null;
412
395
  }
413
- function resetAnalysisStartedPromise() {
414
- singletonStore[ANALYSIS_STARTED_KEY] = null;
396
+ /** True for the build's first caller only: the one that reports its
397
+ * unmatched user excludes. */
398
+ function claimUnmatchedExcludeReport(key) {
399
+ const state = joined(key);
400
+ if (state.unmatchedExcludesReported) return false;
401
+ state.unmatchedExcludesReported = true;
402
+ return true;
415
403
  }
416
- function getSharedCss() {
417
- return singletonStore[SHARED_CSS_KEY] || "";
404
+ function getSharedCss(key) {
405
+ return findSession(key)?.sharedCss ?? "";
418
406
  }
419
- function setSharedCss(css) {
420
- singletonStore[SHARED_CSS_KEY] = css;
407
+ function setSharedCss(key, css) {
408
+ joined(key).sharedCss = css;
421
409
  }
422
- function getSharedSystemProps() {
423
- return singletonStore[SHARED_SYSTEM_PROPS_KEY] || "";
410
+ function getSharedSystemProps(key) {
411
+ return findSession(key)?.sharedSystemProps ?? "";
424
412
  }
425
- function setSharedSystemProps(content) {
426
- singletonStore[SHARED_SYSTEM_PROPS_KEY] = content;
413
+ function setSharedSystemProps(key, content) {
414
+ joined(key).sharedSystemProps = content;
427
415
  }
428
- function getSharedExternalDirs() {
429
- return singletonStore[SHARED_EXTERNAL_DIRS_KEY] || [];
416
+ function getSharedExternalDirs(key) {
417
+ return findSession(key)?.externalDirs ?? [];
430
418
  }
431
- function setSharedExternalDirs(dirs) {
432
- singletonStore[SHARED_EXTERNAL_DIRS_KEY] = dirs;
419
+ function setSharedExternalDirs(key, dirs) {
420
+ joined(key).externalDirs = dirs;
433
421
  }
434
- function getSharedExternalEntries() {
435
- return singletonStore[SHARED_EXTERNAL_ENTRIES_KEY] || /* @__PURE__ */ new Map();
422
+ function getSharedExternalEntries(key) {
423
+ return findSession(key)?.externalEntries ?? /* @__PURE__ */ new Map();
436
424
  }
437
- function setSharedExternalEntries(entries) {
438
- singletonStore[SHARED_EXTERNAL_ENTRIES_KEY] = entries;
425
+ function setSharedExternalEntries(key, entries) {
426
+ joined(key).externalEntries = entries;
439
427
  }
440
428
  /** Specifier → its source entry's side effects, published with the shared
441
429
  * entries; absent leaves the bundler's own classification. */
442
- function getSharedExternalSideEffects() {
443
- return singletonStore[SHARED_EXTERNAL_SIDE_EFFECTS_KEY] || /* @__PURE__ */ new Map();
430
+ function getSharedExternalSideEffects(key) {
431
+ return findSession(key)?.externalSideEffects ?? /* @__PURE__ */ new Map();
444
432
  }
445
- function setSharedExternalSideEffects(sideEffects) {
446
- singletonStore[SHARED_EXTERNAL_SIDE_EFFECTS_KEY] = sideEffects;
447
- }
448
- function globalSlot(key) {
449
- return {
450
- get: () => singletonStore[key] ?? null,
451
- set: (value) => {
452
- singletonStore[key] = value;
453
- }
454
- };
433
+ function setSharedExternalSideEffects(key, sideEffects) {
434
+ joined(key).externalSideEffects = sideEffects;
455
435
  }
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
436
  /**
462
- * The only session that can run watch analysis. A non-owning session forwards
463
- * its batch here; dropping it loses files only that watcher sees.
437
+ * The only session of a build that can run watch analysis. A non-owning
438
+ * session's observations queue for it; dropping them loses files only that
439
+ * watcher sees.
464
440
  */
465
- function getOwningWatchSession() {
466
- return owningWatchSessionSlot.get();
441
+ function getOwningWatchSession(key) {
442
+ return findSession(key)?.authority ?? null;
467
443
  }
468
- function setOwningWatchSession(session) {
469
- owningWatchSessionSlot.set(session);
444
+ function setOwningWatchSession(key, session) {
445
+ joined(key).authority = session;
470
446
  }
471
447
  /**
472
448
  * Per-file content hashes of the last published analysis (relPath → hash of
473
449
  * the exact bytes analyzed), including files that produced zero entries.
474
450
  */
475
- function getAnalyzedHashes() {
476
- return analyzedHashesSlot.get();
451
+ function getAnalyzedHashes(key) {
452
+ return findSession(key)?.analyzedHashes ?? null;
477
453
  }
478
- function setAnalyzedHashes(hashes) {
479
- analyzedHashesSlot.set(hashes);
454
+ function setAnalyzedHashes(key, hashes) {
455
+ joined(key).analyzedHashes = hashes;
480
456
  }
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;
457
+ /** What the build's last published analysis read of the discovered
458
+ * packages (spec §2.2 A4); null before one publishes. */
459
+ function getSharedPackageReadRecord(key) {
460
+ return findSession(key)?.packageReadRecord ?? null;
461
+ }
462
+ function setSharedPackageReadRecord(key, record) {
463
+ joined(key).packageReadRecord = record;
491
464
  }
492
465
  /**
493
- * Two CONCURRENT drive loops in one process would share one session tree and
494
- * clobber each other; sequential claim/release cycles are legal.
466
+ * One participant publishes a build's analysis, holding this claim until it
467
+ * closes: a second publisher would clobber its artifacts. Two builds never
468
+ * exclude each other; sequential claim and release cycles are legal.
495
469
  */
496
- function claimExclusiveSessionOwner(label) {
497
- const active = singletonStore[EXCLUSIVE_SESSION_OWNER_KEY];
498
- if (isSingletonString(active)) throw new Error(`[animus] a second Animus host ("${label}") started while "${active}" is still active in this process. The extraction session is process-global (one session tree, one manifest), so concurrent hosts would clobber each other's analysis. Run one Animus-enabled config per process, or make the builds sequential.`);
499
- singletonStore[EXCLUSIVE_SESSION_OWNER_KEY] = label;
470
+ function claimExclusiveSessionOwner(key, label) {
471
+ const state = joined(key);
472
+ const active = state.activeAttempt;
473
+ 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.`);
474
+ state.activeAttempt = label;
500
475
  return () => {
501
- if (singletonStore[EXCLUSIVE_SESSION_OWNER_KEY] === label) delete singletonStore[EXCLUSIVE_SESSION_OWNER_KEY];
476
+ if (state.activeAttempt === label) state.activeAttempt = null;
502
477
  };
503
478
  }
504
479
  /**
505
- * Absolute artifact directory of the OWNING session's last publication. The
480
+ * Absolute artifact directory of the build's last publication. The
506
481
  * Turbopack loader runs out of process and receives it via options instead.
507
482
  */
508
- function getSessionArtifactDir() {
509
- return sessionArtifactDirSlot.get();
483
+ function getSessionArtifactDir(key) {
484
+ return findSession(key)?.artifactDir ?? null;
510
485
  }
511
- function setSessionArtifactDir(dir) {
512
- sessionArtifactDirSlot.set(dir);
486
+ function setSessionArtifactDir(key, dir) {
487
+ joined(key).artifactDir = dir;
513
488
  }
514
489
  /**
515
490
  * Replacement epoch of the last published analysis, written AFTER the
516
491
  * manifest so an epoch reader always sees at least that generation's manifest.
517
492
  */
518
- function getReplacementEpoch() {
519
- return replacementEpochSlot.get();
493
+ function getReplacementEpoch(key) {
494
+ return findSession(key)?.replacementEpoch ?? null;
520
495
  }
521
- function setReplacementEpoch(epoch) {
522
- replacementEpochSlot.set(epoch);
496
+ function setReplacementEpoch(key, epoch) {
497
+ joined(key).replacementEpoch = epoch;
523
498
  }
524
499
  /**
525
- * The one in-flight watch-analysis transaction: a compiler that did not start
526
- * it joins this promise rather than read a pre-transaction generation.
500
+ * The build's one in-flight watch-analysis transaction: a participant that
501
+ * did not start it joins this promise rather than read a pre-transaction
502
+ * generation.
527
503
  */
528
- function getWatchTransaction() {
529
- return watchTransactionSlot.get();
504
+ function getWatchTransaction(key) {
505
+ return findSession(key)?.watchTransaction ?? null;
506
+ }
507
+ function observationQueue(key) {
508
+ return joined(key).observations;
530
509
  }
531
- function setWatchTransaction(transaction) {
532
- watchTransactionSlot.set(transaction);
510
+ function getSystemAuthority(key) {
511
+ return findSession(key)?.systemAuthority ?? null;
533
512
  }
534
- /** Engine selection travels through the singleton so non-owning compilers
535
- * and the webpack loader honor the owner's choice. */
536
- function setSharedEngine(engine) {
537
- singletonStore[ENGINE_KEY] = engine;
513
+ function setSystemAuthority(key, authority) {
514
+ joined(key).systemAuthority = authority;
538
515
  }
539
- function getSharedEngine() {
540
- return singletonStore[ENGINE_KEY] || "v2";
516
+ function setWatchTransaction(key, transaction) {
517
+ joined(key).watchTransaction = transaction;
541
518
  }
542
519
  /** Single choke-point for every native extraction call; the NAPI module's
543
520
  * own `.d.ts` is the authoritative surface for what it returns. */
544
521
  function requireEngine() {
545
522
  return require("@animus-ui/extract");
546
523
  }
524
+ const ENGINE_API_OVERRIDE_KEY = "__animus_engine_api_override__";
525
+ const overrideGlobal = globalThis;
526
+ /** The native engine module could not be loaded: an environment failure,
527
+ * never answered by another engine. */
528
+ var EnvironmentFailure = class extends Error {
529
+ code = "EnvironmentFailure";
530
+ constructor(cause) {
531
+ super(`[animus] EnvironmentFailure: the native extraction engine could not be loaded: ${String(cause)}`, { cause });
532
+ this.name = "EnvironmentFailure";
533
+ }
534
+ };
535
+ let loadEngineFactory = requireEngine;
536
+ /** The native factory, loaded once for every build in the process. A load
537
+ * failure is an EnvironmentFailure, and is not cached: the next use tries
538
+ * the load again. */
539
+ function engineFactory() {
540
+ const registry = sessionRegistry();
541
+ if (registry.factory === null) try {
542
+ registry.factory = loadEngineFactory();
543
+ } catch (error) {
544
+ throw new EnvironmentFailure(error instanceof Error ? error : String(error));
545
+ }
546
+ return registry.factory;
547
+ }
547
548
  /**
548
- * The one engine API for this process: the instance, its analyze-time
549
- * sources, and the drift flag live on globalThis so both copies share them.
549
+ * One engine-handle store (SA18): the engine API over `key`'s own engine
550
+ * state, made once per key in the registry every module copy joins. The
551
+ * native factory is shared; each key's engine instance, analyze-time
552
+ * sources and drift flag are its own, and its build's retirement releases
553
+ * them. A key need not be a joined build's: a Vite context or a Turbopack
554
+ * worker keys its own.
550
555
  */
551
- const v2EngineApi = require_pipeline.createV2EngineApi({
552
- label: "animus",
553
- isV2: () => getSharedEngine() === "v2",
554
- loadNativeEngine: requireEngine,
555
- passThroughUnknownPaths: true,
556
- store: {
557
- getEngine: () => singletonStore[V2_ENGINE_KEY],
558
- setEngine: (engine) => {
559
- singletonStore[V2_ENGINE_KEY] = engine;
560
- },
561
- getSentSources: () => singletonStore[V2_SENT_SOURCES_KEY] ?? null,
562
- setSentSources: (sources) => {
563
- singletonStore[V2_SENT_SOURCES_KEY] = sources;
564
- },
565
- getDriftWarned: () => Boolean(singletonStore[V2_DRIFT_WARNED_KEY]),
566
- setDriftWarned: (value) => {
567
- singletonStore[V2_DRIFT_WARNED_KEY] = value;
556
+ function engineHandle(key, options) {
557
+ const engines = sessionRegistry().engines;
558
+ const id = sessionKeyId(key);
559
+ let api = engines.get(id);
560
+ if (api === void 0) {
561
+ let engine = null;
562
+ let sentSources = null;
563
+ let driftWarned = false;
564
+ api = require_pipeline.createV2EngineApi({
565
+ ...options,
566
+ isV2: () => true,
567
+ loadNativeEngine: engineFactory,
568
+ store: {
569
+ getEngine: () => engine,
570
+ setEngine: (next) => {
571
+ engine = next;
572
+ },
573
+ getSentSources: () => sentSources,
574
+ setSentSources: (sources) => {
575
+ sentSources = sources;
576
+ },
577
+ getDriftWarned: () => driftWarned,
578
+ setDriftWarned: (value) => {
579
+ driftWarned = value;
580
+ }
581
+ }
582
+ });
583
+ engines.set(id, api);
584
+ }
585
+ return api;
586
+ }
587
+ /** Releases a standalone handle, one no joined build retires: its host
588
+ * calls this when it closes. */
589
+ function releaseEngineHandle(key) {
590
+ sessionRegistry().engines.delete(sessionKeyId(key));
591
+ }
592
+ /**
593
+ * The build's engine API, from the one engine-handle store.
594
+ */
595
+ function engineApi(key) {
596
+ const override = overrideGlobal[ENGINE_API_OVERRIDE_KEY];
597
+ if (override) return override();
598
+ joined(key);
599
+ return engineHandle(key, {
600
+ label: "animus",
601
+ passThroughUnknownPaths: true
602
+ })();
603
+ }
604
+ /** A fresh build id, created once by the owning host wrapper. */
605
+ function createBuildId() {
606
+ return (0, crypto.randomUUID)();
607
+ }
608
+ /** The key of build `build` of `root`, by the root's canonical path. */
609
+ function sessionKey(build, root) {
610
+ const absolute = (0, path.resolve)(root);
611
+ let canonical = absolute;
612
+ try {
613
+ canonical = (0, fs.realpathSync)(absolute);
614
+ } catch {}
615
+ return {
616
+ build,
617
+ root: canonical,
618
+ protocol: 1
619
+ };
620
+ }
621
+ function sessionKeyId(key) {
622
+ return JSON.stringify([
623
+ key.protocol,
624
+ key.build,
625
+ key.root
626
+ ]);
627
+ }
628
+ /** A participant joined a session with semantic options that differ from
629
+ * those the session was created with. */
630
+ var SessionConfigurationConflict = class extends Error {
631
+ key;
632
+ established;
633
+ joining;
634
+ differing;
635
+ code = "SessionConfigurationConflict";
636
+ constructor(key, established, joining, differing, requirement = "Every participant of one build must use the same Animus options.") {
637
+ super(`[animus] SessionConfigurationConflict: "${joining}" joined build ${key.build} of ${key.root} with ${differing.join(", ")} differing from "${established}". ${requirement}`);
638
+ this.key = key;
639
+ this.established = established;
640
+ this.joining = joining;
641
+ this.differing = differing;
642
+ this.name = "SessionConfigurationConflict";
643
+ }
644
+ };
645
+ function digestOf(field) {
646
+ return {
647
+ field,
648
+ text: (options) => {
649
+ const value = options[field];
650
+ return value === void 0 ? void 0 : require_pipeline.stableStringify(value);
568
651
  }
652
+ };
653
+ }
654
+ /**
655
+ * Every option, classified: one a build's analysis depends on, which every
656
+ * participant must agree on, or one deliberately excluded. `verbose` changes
657
+ * no output, and the root is the key's. The type checker rejects an option
658
+ * added to `AnimusCoreOptions` until it is classified here.
659
+ */
660
+ const OPTION_CLASSES = {
661
+ system: digestOf("system"),
662
+ exclude: digestOf("exclude"),
663
+ extensions: digestOf("extensions"),
664
+ strict: digestOf("strict"),
665
+ diagnostics: digestOf("diagnostics"),
666
+ verbose: "excluded",
667
+ prefix: digestOf("prefix"),
668
+ prefixContextualVars: digestOf("prefixContextualVars"),
669
+ targets: digestOf("targets"),
670
+ minify: digestOf("minify"),
671
+ staticCss: digestOf("staticCss"),
672
+ layers: digestOf("layers"),
673
+ engine: digestOf("engine"),
674
+ mode: digestOf("mode"),
675
+ root: "excluded"
676
+ };
677
+ /** The options a build's analysis depends on, each as stable text; each is
678
+ * data by its type, so equal text is an equal value. A host's own options
679
+ * are not the build's. */
680
+ function semanticOptions(options) {
681
+ const present = [];
682
+ for (const [name, digest] of Object.entries(OPTION_CLASSES)) {
683
+ if (digest === "excluded") continue;
684
+ const text = digest.text(options);
685
+ if (text !== void 0) present.push([name, text]);
686
+ }
687
+ return new Map(present);
688
+ }
689
+ const SESSION_REGISTRY = Symbol.for("animus.session-registry.v1");
690
+ const registryGlobal = globalThis;
691
+ function sessionRegistry() {
692
+ return registryGlobal[SESSION_REGISTRY] ??= {
693
+ sessions: /* @__PURE__ */ new Map(),
694
+ engines: /* @__PURE__ */ new Map(),
695
+ factory: null
696
+ };
697
+ }
698
+ /**
699
+ * Joins `participant` to the session of `key`, creating it with `options` on
700
+ * the first join. A later participant must bring the same semantic options.
701
+ */
702
+ function joinSession(key, participant, options) {
703
+ const sessions = sessionRegistry().sessions;
704
+ const id = sessionKeyId(key);
705
+ const joining = semanticOptions(options);
706
+ const existing = sessions.get(id);
707
+ if (existing === void 0) {
708
+ const state = {
709
+ key,
710
+ established: participant,
711
+ options: joining,
712
+ participants: /* @__PURE__ */ new Set([participant]),
713
+ activeAttempt: null,
714
+ authority: null,
715
+ analysisStarted: null,
716
+ watchTransaction: null,
717
+ observations: {
718
+ revision: 0,
719
+ rescan: false,
720
+ untaken: /* @__PURE__ */ new Map(),
721
+ suppliedSources: /* @__PURE__ */ new Map(),
722
+ revisions: /* @__PURE__ */ new Map(),
723
+ active: null
724
+ },
725
+ systemAuthority: null,
726
+ claimRecord: null,
727
+ manifestJson: null,
728
+ sharedCss: "",
729
+ sharedSystemProps: "",
730
+ externalDirs: [],
731
+ externalEntries: /* @__PURE__ */ new Map(),
732
+ externalSideEffects: /* @__PURE__ */ new Map(),
733
+ analyzedHashes: null,
734
+ packageReadRecord: null,
735
+ replacementEpoch: null,
736
+ artifactDir: null,
737
+ unmatchedExcludesReported: false
738
+ };
739
+ sessions.set(id, state);
740
+ return state;
741
+ }
742
+ const differing = [.../* @__PURE__ */ new Set([...existing.options.keys(), ...joining.keys()])].filter((name) => existing.options.get(name) !== joining.get(name));
743
+ if (differing.length > 0) throw new SessionConfigurationConflict(key, existing.established, participant, differing.sort());
744
+ existing.participants.add(participant);
745
+ return existing;
746
+ }
747
+ /** The session of `key`, if a participant has joined it. */
748
+ function findSession(key) {
749
+ return sessionRegistry().sessions.get(sessionKeyId(key));
750
+ }
751
+ /** The build's on-disk claim record, which stops another process pruning
752
+ * its tree: held once by the build, whichever participant publishes, and
753
+ * released only when the build retires. */
754
+ function holdBuildClaimRecord(key, hold) {
755
+ const state = joined(key);
756
+ state.claimRecord ??= hold();
757
+ }
758
+ /** `participant` leaves the build of `key`. The build retires with its
759
+ * last participant, once no attempt is in flight: it leaves the registry
760
+ * and releases its claim record, so its tree is pruned once stale. */
761
+ function leaveSession(key, participant) {
762
+ const state = findSession(key);
763
+ if (state === void 0) return;
764
+ state.participants.delete(participant);
765
+ retireIfUnused(sessionKeyId(key), state);
766
+ }
767
+ function retireIfUnused(id, state) {
768
+ if (state.participants.size > 0) return;
769
+ const inflight = state.watchTransaction;
770
+ if (inflight !== null) {
771
+ const retry = () => retireIfUnused(id, state);
772
+ inflight.then(retry, retry);
773
+ return;
569
774
  }
570
- });
571
- function engineApi() {
572
- const override = singletonStore[ENGINE_API_OVERRIDE_KEY];
573
- return override ? override() : v2EngineApi();
775
+ const sessions = sessionRegistry().sessions;
776
+ if (sessions.get(id) !== state) return;
777
+ sessions.delete(id);
778
+ sessionRegistry().engines.delete(id);
779
+ state.claimRecord?.();
780
+ state.claimRecord = null;
574
781
  }
575
782
  //#endregion
576
783
  //#region session/timing.ts
@@ -606,6 +813,23 @@ function logBuildTimings(bt, rustTiming, log, verbose) {
606
813
  }
607
814
  //#endregion
608
815
  //#region session/extraction-session.ts
816
+ /** An observation's state when its file is gone, or cannot be read; any
817
+ * other state is the content hash it was observed with. */
818
+ const REMOVED_OBSERVATION = "removed";
819
+ const UNREADABLE_OBSERVATION = "unreadable";
820
+ /** A loaded system's identity: its evaluated modules and a fingerprint of
821
+ * the whole load. */
822
+ function systemLoad(system) {
823
+ return {
824
+ identity: require_pipeline.contentHash(JSON.stringify(system)),
825
+ dependencies: system.dependencies ?? []
826
+ };
827
+ }
828
+ const CONSOLE_LOGGER = {
829
+ info: (line) => console.error(line),
830
+ warn: (line) => console.warn(line),
831
+ error: (line) => console.error(line)
832
+ };
609
833
  const SESSION_DIR_MAX_AGE_MS = 864e5;
610
834
  const STATUS_WATCHDOG_MS = 2e3;
611
835
  const LEGACY_FLAT_ARTIFACTS = [
@@ -624,19 +848,96 @@ function isDiskString(value) {
624
848
  }
625
849
  /** Rejection of an attempt whose corpus holds an aborted parse. */
626
850
  var AbortedParseRejection = class extends Error {};
851
+ /** A reload refused because its full read would replace host-supplied
852
+ * bytes with the disk's: a system reload a parse hold deferred, or a full
853
+ * read that reaches a host-supplied file only under another spelling (a
854
+ * case-insensitive volume). Like a hold, it keeps the prior publication
855
+ * and the observed sources. */
856
+ var DeferredReloadRefusal = class extends Error {};
857
+ /** The file's canonical path, or null when it cannot be resolved. */
858
+ function canonicalPath(path$4) {
859
+ try {
860
+ return fs.realpathSync.native(path$4);
861
+ } catch {
862
+ return null;
863
+ }
864
+ }
865
+ /** The host sources a full read reaches only under another spelling: a key
866
+ * the read did not reach exactly, whose file is one the read reached by
867
+ * another path (a case-insensitive volume, a symlinked alias). A key with
868
+ * no such file is not read either way, and is left alone. */
869
+ function misspelledHostSources(hostSources, readPaths) {
870
+ const misses = [...hostSources.keys()].filter((path$5) => !readPaths.has(path$5));
871
+ if (misses.length === 0) return [];
872
+ const readByCanonical = /* @__PURE__ */ new Map();
873
+ for (const readPath of readPaths) {
874
+ const canonical = canonicalPath(readPath);
875
+ if (canonical === null) continue;
876
+ readByCanonical.set(canonical, [...readByCanonical.get(canonical) ?? [], readPath]);
877
+ }
878
+ return misses.flatMap((path$6) => {
879
+ const canonical = canonicalPath(path$6);
880
+ const read = canonical === null ? void 0 : readByCanonical.get(canonical);
881
+ return read === void 0 ? [] : [{
882
+ path: path$6,
883
+ read
884
+ }];
885
+ });
886
+ }
627
887
  function isDiskNumber(value) {
628
888
  return Object(value) !== value && Object.prototype.toString.call(value) === "[object Number]";
629
889
  }
890
+ /** A file's content hash, or '' when it cannot be read. */
891
+ function fileHashOrEmpty(path$7) {
892
+ try {
893
+ return require_pipeline.contentHash((0, fs.readFileSync)(path$7, "utf-8"));
894
+ } catch {
895
+ return "";
896
+ }
897
+ }
898
+ /** The first file whose content no longer has its recorded hash: an
899
+ * unreadable or deleted file reads as '', which mismatches a real one. */
900
+ function firstChangedFile(hashes) {
901
+ for (const [path$8, hash] of hashes) if (fileHashOrEmpty(path$8) !== hash) return path$8;
902
+ }
630
903
  /** Bundler-agnostic extraction pipeline: system load, source ingestion,
631
904
  * analysis, emission, and watch-cycle diffing. Carries no bundler types. */
632
905
  var ExtractionSession = class {
633
906
  host;
634
907
  /** Set by the adapter before any pipeline run. */
635
908
  rootDir = null;
909
+ /** The module extracted terminals import their factories from; an
910
+ * override must supply every terminal the analysis extracts. */
911
+ runtimeImport = require_pipeline.DEFAULT_RUNTIME_IMPORT;
912
+ /** A host that resolves imports itself (Vite's `this.resolve`): package
913
+ * discovery asks it first, the loader only for a declared workspace it
914
+ * cannot resolve. Null resolves through the loader alone. */
915
+ hostResolve = null;
916
+ /** Each file's alias comparison, kept by content hash across analyses. */
917
+ aliasCache = new require_pipeline.AliasAgreementCache();
636
918
  /** Serialized path aliases, harvested by the adapter from bundler config. */
637
919
  pathAliasesJson = null;
920
+ /** The host's aliases are the project's tsconfig `paths`: each full pass
921
+ * re-reads them, so a tsconfig edit takes effect. */
922
+ aliasesFromTsconfig = false;
923
+ /** The root package.json and the tsconfig chain, absent files included:
924
+ * a change to any is a configuration change, which runs the full pass. */
925
+ configInputPaths = [];
926
+ configInputKeys = /* @__PURE__ */ new Set();
927
+ /** Each configuration input's content hash at the last full pass, ''
928
+ * for an absent one: the probe's basis when the host reports nothing. */
929
+ configInputHashes = /* @__PURE__ */ new Map();
638
930
  /** The host's export conditions, in its order, for the system loader. */
639
931
  conditions = [];
932
+ /** The host's extension order, as its adapter captured it; the built-in
933
+ * policy where no host resolves. */
934
+ extensionPolicy = require_pipeline.BUILT_IN_EXTENSION_POLICY;
935
+ /** Which files the host serves as assets (spec §2.2 A8); the built-in
936
+ * classification where no host's can be read. */
937
+ assetClassification = require_pipeline.builtInAssetClassification("noHost");
938
+ /** How the host's bundler expands computed loads (spec §6.3); without
939
+ * one, every computed load keeps its widest reading. */
940
+ loadExpander;
640
941
  assetCopyCache = /* @__PURE__ */ new Map();
641
942
  /** Physical asset files registered with the host watcher. */
642
943
  assetDependencyPaths = /* @__PURE__ */ new Set();
@@ -644,9 +945,23 @@ var ExtractionSession = class {
644
945
  /** System-props module id override. Turbopack rejects absolute-path
645
946
  * imports, so its driver sets a virtual id; null injects the abs path. */
646
947
  systemPropsModuleId = null;
948
+ /** The stylesheet module generated code imports; a host that serves the
949
+ * stylesheet itself names its own. */
950
+ cssModuleId = ANIMUS_CSS_MODULE_ID;
647
951
  /** Whether the analysis-inputs hydration corpus is written to disk. Only
648
952
  * isolated loader workers replay it; an in-process loader reads memory. */
649
953
  persistAnalysisInputs = false;
954
+ /** The host substitutes asset() URLs itself (Vite: dev copies, build
955
+ * emitFile markers): the published sheets keep their placeholders, and
956
+ * the session makes no copy and tracks no asset file. */
957
+ hostSubstitutesAssets = false;
958
+ /** Whether the session keeps its artifacts on disk: status, commit,
959
+ * manifest, styles, the epoch witness, asset copies and its directory
960
+ * claim, with the sibling reconciliation they need. A host that serves
961
+ * every result from memory (Vite) turns it off; the shared state still
962
+ * publishes. Such a host also substitutes assets (above): asset copies
963
+ * are artifacts. */
964
+ persistArtifacts = true;
650
965
  /** Whether this session serves development (a watch), where an error-level
651
966
  * diagnostic is reported and the session keeps running. Only a build
652
967
  * fails on one. */
@@ -694,29 +1009,69 @@ var ExtractionSession = class {
694
1009
  /** Fires exactly once per settled `handleWatchUpdate` — the failure cause,
695
1010
  * or null when clean. Observation never alters delivery. */
696
1011
  onCycleSettled = null;
1012
+ /** Fires once per published generation, and per held attempt. */
1013
+ onOutcome = null;
1014
+ /** The last published generation; null before the first. */
1015
+ publication = null;
1016
+ /** The original sources (raw, before adaptation) the session holds, by
1017
+ * root-relative path: the published generation's, plus any that an
1018
+ * attempt in flight or held by an aborted parse has read. */
1019
+ get heldSources() {
1020
+ return this.fileCache;
1021
+ }
1022
+ /** The generation an emit produced, current once its ingestion
1023
+ * publishes. */
1024
+ stagedPublication = null;
697
1025
  options;
698
1026
  staticCssJson;
699
1027
  system = null;
700
- /** Configured external files that could not be read. Incremental passes
701
- * re-collect nothing, so they replay these instead of passing strict. */
1028
+ /** Configured files that could not be read. Incremental passes replay
1029
+ * these instead of passing strict, and add a watched change they cannot
1030
+ * read. */
702
1031
  ingestionFailureDiagnostics = [];
703
1032
  /** Whether the corpus being analyzed left a file out, whose renders the
704
1033
  * analysis cannot see. */
705
1034
  skippedSources = [];
1035
+ /** Resolver calls ingestion ended under another spelling. */
1036
+ ingestionOpenings = [];
1037
+ /** The adapted originals of the ingestion being analysed: it publishes
1038
+ * only after its analysis does. */
1039
+ ingestionAdapted = /* @__PURE__ */ new Map();
706
1040
  /** Full package-resolution map from the last full pipeline — replayed by
707
1041
  * incremental passes (sourceEntries alone omits dist-resolved packages). */
708
1042
  lastPackageMap = {};
709
1043
  fileCache = /* @__PURE__ */ new Map();
710
- corpus = require_pipeline.createSourceCorpus({
711
- engineApi: () => engineApi(),
712
- prefix: "[animus-next]",
713
- strict: () => !!this.options.strict,
714
- warn: (message) => this.warn(message),
715
- reportErrors: () => this.reportErrors()
716
- });
1044
+ /** Held sources whose bytes the host supplied (WatchChanges.sources),
1045
+ * by root-relative path; a read from disk drops the path. */
1046
+ hostSuppliedKeys = /* @__PURE__ */ new Set();
1047
+ corpus = require_pipeline.createSourceCorpus(this.corpusHost());
1048
+ /** The corpus reads the driver label when it reports, so a host that
1049
+ * sets `driverLabel` after construction is named in its messages. */
1050
+ corpusHost() {
1051
+ const label = () => this.driverLabel;
1052
+ return {
1053
+ engineApi: () => this.engine(),
1054
+ get prefix() {
1055
+ return `[${label()}]`;
1056
+ },
1057
+ strict: () => !!this.options.strict,
1058
+ warn: (message) => this.warn(message),
1059
+ reportErrors: () => this.reportErrors(),
1060
+ extensions: () => this.extensionPolicy,
1061
+ rootDir: () => this.rootDir
1062
+ };
1063
+ }
717
1064
  systemDependencyKeys = /* @__PURE__ */ new Set();
1065
+ /** Whether `path` is the system module, one of its dependencies, or a
1066
+ * path its load found absent: a change to it reloads the system. */
1067
+ isSystemDependency(path$9) {
1068
+ return require_pipeline.toWatchKeys(path$9).some((key) => this.systemDependencyKeys.has(key));
1069
+ }
718
1070
  /** Loader-reported dependency paths for bundler watch registration. */
719
1071
  systemDependencyPaths = [];
1072
+ /** Paths the system load looked for and found absent: a host registers
1073
+ * them for creation, which reloads the system. */
1074
+ systemMissingPaths = [];
720
1075
  systemDependencyHashes = /* @__PURE__ */ new Map();
721
1076
  lastSystemPropsHash = null;
722
1077
  artifactRecords = {
@@ -730,9 +1085,20 @@ var ExtractionSession = class {
730
1085
  /** Release handle for the in-process owner slot AND the directory's
731
1086
  * on-disk owner record, taken and released as one; null when unheld. */
732
1087
  releasePublicationClaim = null;
733
- /** Session identity, claimed once per PROCESS and adopted by every later
734
- * session instance so all compilers share one artifact tree. */
735
- sessionId = claimProcessSessionId();
1088
+ /** The build this session takes part in: every participant of one build
1089
+ * shares its artifact tree and state; another build of the root never
1090
+ * does. */
1091
+ sessionId;
1092
+ /** The log tier, from the option and `ANIMUS_DEBUG` at construction. */
1093
+ verbosity;
1094
+ /** The emission mode at construction: explicit, else `NODE_ENV`'s. It
1095
+ * defaults minify. */
1096
+ startupMode;
1097
+ joinedKey = null;
1098
+ /** This instance as a participant of its build, once joined. */
1099
+ participant = null;
1100
+ /** Closed: it left its build and starts no attempt until it reattaches. */
1101
+ detached = false;
736
1102
  /** Epoch of the last published analysis; null seeds from disk so a
737
1103
  * same-session restart with unchanged plans rewrites no bytes. */
738
1104
  lastEpochValue = null;
@@ -747,10 +1113,23 @@ var ExtractionSession = class {
747
1113
  * the cache keeps every observed source so the repair publishes them in
748
1114
  * one generation; a successful publication clears it. */
749
1115
  heldRejection = null;
750
- /** A system reload an aborted parse rejected. Its dependency hashes already
751
- * advanced, so the first batch that passes the parse check publishes
752
- * through the full pipeline, which loads the latest system. */
1116
+ /** Reload debt: a full pass that failed with a committed system to
1117
+ * hold, in development or under an aborted parse, owed until one
1118
+ * succeeds. runFullPipeline alone sets and clears it. While it stands, every batch retries the full reload and
1119
+ * nothing publishes against the stale system. Under an aborted parse's
1120
+ * hold, the first batch that passes the parse check pays it instead. */
753
1121
  systemReloadOwed = false;
1122
+ /** The system dependencies the last committed full pass recorded: a
1123
+ * failed reload watches these and its own, never an earlier failure's. */
1124
+ lastGoodSystemDependencies = {
1125
+ keys: /* @__PURE__ */ new Set(),
1126
+ paths: [],
1127
+ missing: [],
1128
+ hashes: /* @__PURE__ */ new Map()
1129
+ };
1130
+ /** Whether the current full pass's system load recorded its
1131
+ * dependencies. */
1132
+ attemptRecordedSystemDependencies = false;
754
1133
  /** Monotonic readiness witness: flips true on the first complete
755
1134
  * publication and never regresses; every later status write carries it. */
756
1135
  firstEmissionComplete = false;
@@ -761,27 +1140,53 @@ var ExtractionSession = class {
761
1140
  constructor(options, host = {}) {
762
1141
  this.host = host;
763
1142
  this.options = options;
1143
+ this.sessionId = host.build ?? createBuildId();
764
1144
  this.staticCssJson = require_pipeline.serializeStaticCss(options.staticCss);
1145
+ this.verbosity = require_pipeline.resolveVerbosity(options.verbose, process.env.ANIMUS_DEBUG);
1146
+ this.startupMode = require_pipeline.resolveMode(options.mode, () => process.env.NODE_ENV === "production" ? "production" : "development").mode;
765
1147
  }
766
1148
  /** Forgets that `relPath` could not be read once an incremental pass reads
767
- * or deletes it, so its cost stops holding pruning off. */
1149
+ * or deletes it, so its cost stops holding pruning off. Whether one was
1150
+ * recorded. */
768
1151
  clearIngestionFailure(relPath) {
1152
+ const before = this.ingestionFailureDiagnostics.length;
769
1153
  this.ingestionFailureDiagnostics = this.ingestionFailureDiagnostics.filter((diagnostic) => diagnostic.file !== relPath);
1154
+ return this.ingestionFailureDiagnostics.length !== before;
770
1155
  }
771
1156
  get sessionDir() {
772
1157
  return sessionArtifactDir(this.rootDir, this.sessionId);
773
1158
  }
1159
+ /** This session's build of its root, joined on first use: a participant
1160
+ * whose options differ from the build's fails with
1161
+ * `SessionConfigurationConflict`. */
1162
+ get key() {
1163
+ if (this.joinedKey === null) {
1164
+ const key = sessionKey(this.sessionId, this.rootDir);
1165
+ const participant = `${this.driverLabel}#${(0, crypto.randomUUID)()}`;
1166
+ joinSession(key, participant, this.options);
1167
+ this.participant = participant;
1168
+ this.joinedKey = key;
1169
+ }
1170
+ return this.joinedKey;
1171
+ }
774
1172
  get verbose() {
775
- return require_pipeline.resolveVerbosity(this.options.verbose, process.env.ANIMUS_DEBUG) !== "quiet";
1173
+ return this.verbosity !== "quiet";
1174
+ }
1175
+ /** The engine of this session's build. */
1176
+ engine() {
1177
+ return this.host.engine?.() ?? engineApi(this.key);
1178
+ }
1179
+ get logger() {
1180
+ return this.host.logger ?? CONSOLE_LOGGER;
776
1181
  }
777
1182
  log(msg) {
778
- if (this.verbose) console.error(`[animus] ${msg}`);
1183
+ if (this.verbose) this.logger.info(`[animus] ${msg}`);
779
1184
  }
780
1185
  warn(msg) {
781
- console.warn(`[animus] ${msg}`);
1186
+ this.logger.warn(`[animus] ${msg}`);
782
1187
  }
783
1188
  reportErrors() {
784
- return this.development ? (message) => console.error(message) : void 0;
1189
+ return this.development ? (message) => this.logger.error(message) : void 0;
785
1190
  }
786
1191
  /** A build-strictness check that failed: a build throws `message`, and
787
1192
  * development reports it at error level and takes the non-strict path. */
@@ -790,6 +1195,11 @@ var ExtractionSession = class {
790
1195
  if (!report) throw new Error(message);
791
1196
  report(message);
792
1197
  }
1198
+ /** A finding `strict` turns into a strict failure; otherwise a warning. */
1199
+ strictOrWarn(message, strictMessage = message) {
1200
+ if (this.options.strict) this.strictFailure(strictMessage);
1201
+ else this.warn(message);
1202
+ }
793
1203
  now() {
794
1204
  return this.verbose ? performance.now() : 0;
795
1205
  }
@@ -804,6 +1214,11 @@ var ExtractionSession = class {
804
1214
  lastExternalOutcomes = [];
805
1215
  /** The analysed kits' descriptors, from the last discovery. */
806
1216
  kitDescriptors = [];
1217
+ /** Package name → the real root of the instance discovery read. */
1218
+ packageInstances = {};
1219
+ /** What this attempt's analysis read of the discovered packages; the
1220
+ * build's shared record once the analysis publishes. */
1221
+ packageReadRecord = null;
807
1222
  /** Component count of the last published analysis, null before the first —
808
1223
  * read by drivers instead of re-parsing the manifest JSON every cycle. */
809
1224
  lastComponentCount = null;
@@ -814,18 +1229,15 @@ var ExtractionSession = class {
814
1229
  /** When superseded asset copies leave `assets/`: `'full-pipeline'` suits a
815
1230
  * driver serving the dir in place, `'every-cycle'` one republishing it. */
816
1231
  staleAssetPruning = "full-pipeline";
817
- /** Driver-owned STRUCTURAL exclusions, joined to the never-replaceable set
818
- * — never the user list, whose presence flips the replace semantics. */
1232
+ /** The host's output directories (`outputExclusion`): root-relative,
1233
+ * excluded as literal path prefixes from discovery and from package
1234
+ * collection, whatever the user's list. */
819
1235
  structuralExclude = [];
820
1236
  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
1237
  /** Resolve the scan configuration from options — the single source of the
827
- * exclude/extension policy shared by the full and incremental pipelines. */
828
- resolveScanConfig() {
1238
+ * exclude/extension policy shared by the full and incremental pipelines,
1239
+ * and by a host classifying its own file events. */
1240
+ get scanConfig() {
829
1241
  if (this.scanConfigMemo === null) {
830
1242
  const extensionsSet = new Set(this.options.extensions ?? require_pipeline.DEFAULT_EXTENSIONS);
831
1243
  this.scanConfigMemo = {
@@ -835,8 +1247,8 @@ var ExtractionSession = class {
835
1247
  }
836
1248
  return this.scanConfigMemo;
837
1249
  }
838
- /** Concurrent entries wait for publication, then ingest their own batch;
839
- * content hashes coalesce observations the prior analysis covered. */
1250
+ /** Records the batch before any await, so an attempt that rejects never
1251
+ * erases it, and settles with the attempt that takes it (SA20). */
840
1252
  async handleWatchUpdate(changes) {
841
1253
  try {
842
1254
  await this.routeWatchUpdate(changes);
@@ -847,36 +1259,131 @@ var ExtractionSession = class {
847
1259
  this.onCycleSettled?.(null);
848
1260
  }
849
1261
  async routeWatchUpdate(changes) {
850
- const hasBatch = (changes.modifiedFiles?.size ?? 0) > 0 || (changes.removedFiles?.size ?? 0) > 0;
851
- const inflight = getWatchTransaction();
1262
+ if ((changes.modifiedFiles?.size ?? 0) > 0 || (changes.removedFiles?.size ?? 0) > 0) {
1263
+ await this.awaitObservation(this.recordObservation(changes));
1264
+ return;
1265
+ }
1266
+ const inflight = getWatchTransaction(this.key);
852
1267
  if (inflight) {
853
1268
  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
1269
  return;
860
1270
  }
861
- await this.ingestForwardedBatch(changes);
1271
+ if (this.system && !this.detached) await this.runWatchAttempt(changes);
1272
+ }
1273
+ /** Queues the batch's observations under a new revision, synchronously.
1274
+ * A batch equivalent to what the active attempt took joins that attempt:
1275
+ * a second compiler's report of the same edit never analyzes it twice. */
1276
+ recordObservation(changes) {
1277
+ const queue = observationQueue(this.key);
1278
+ const observed = /* @__PURE__ */ new Map();
1279
+ for (const path$10 of changes.modifiedFiles ?? []) {
1280
+ let state;
1281
+ try {
1282
+ state = require_pipeline.contentHash(changes.sources?.get(path$10) ?? (0, fs.readFileSync)(path$10, "utf-8"));
1283
+ } catch {
1284
+ state = UNREADABLE_OBSERVATION;
1285
+ }
1286
+ observed.set(path$10, state);
1287
+ }
1288
+ for (const path$11 of changes.removedFiles ?? []) observed.set(path$11, REMOVED_OBSERVATION);
1289
+ const revision = ++queue.revision;
1290
+ const active = queue.active;
1291
+ if (active !== null && [...observed].every(([path$12, state]) => !queue.untaken.has(path$12) && active.taken.get(path$12) === state)) {
1292
+ queue.revisions.set(revision, active.transaction);
1293
+ return revision;
1294
+ }
1295
+ for (const [path$13, state] of observed) {
1296
+ queue.untaken.set(path$13, state);
1297
+ const supplied = changes.sources?.get(path$13);
1298
+ if (supplied === void 0) queue.suppliedSources.delete(path$13);
1299
+ else queue.suppliedSources.set(path$13, supplied);
1300
+ }
1301
+ queue.revisions.set(revision, null);
1302
+ return revision;
862
1303
  }
863
- /** The shared owner gate for local and forwarded batches. Every waiter
864
- * rechecks the slot so distinct batches publish in arrival order. */
865
- async ingestForwardedBatch(changes) {
866
- if (!this.system) return;
1304
+ /** Settles with the attempt that takes `revision`. Another attempt's
1305
+ * rejection is its own callers': this observation runs after it. */
1306
+ async awaitObservation(revision) {
1307
+ const queue = observationQueue(this.key);
867
1308
  for (;;) {
868
- const inflight = getWatchTransaction();
869
- if (!inflight) break;
870
- await inflight;
871
- }
872
- const transaction = Promise.resolve().then(() => this.processWatchUpdate(changes));
873
- setWatchTransaction(transaction);
874
- try {
875
- await transaction;
876
- } finally {
877
- setWatchTransaction(null);
1309
+ const taken = queue.revisions.get(revision);
1310
+ if (taken !== null && taken !== void 0) {
1311
+ queue.revisions.delete(revision);
1312
+ await taken;
1313
+ return;
1314
+ }
1315
+ const inflight = getWatchTransaction(this.key);
1316
+ if (inflight) {
1317
+ await inflight.catch(() => void 0);
1318
+ continue;
1319
+ }
1320
+ const owner = this.system && !this.detached ? this : getOwningWatchSession(this.key);
1321
+ if (owner === null && !this.detached && getManifestJson(this.key) !== null) {
1322
+ await this.runFullPipeline();
1323
+ continue;
1324
+ }
1325
+ if (owner === null || !owner.startObservedAttempt()) {
1326
+ queue.revisions.delete(revision);
1327
+ return;
1328
+ }
878
1329
  }
879
1330
  }
1331
+ /** Starts one attempt over every untaken observation of the build. */
1332
+ startObservedAttempt() {
1333
+ if (!this.system || this.detached) return false;
1334
+ const { taken, rescan, sources } = this.takeObservations();
1335
+ const modifiedFiles = /* @__PURE__ */ new Set();
1336
+ const removedFiles = /* @__PURE__ */ new Set();
1337
+ for (const [path$14, state] of taken) (state === REMOVED_OBSERVATION ? removedFiles : modifiedFiles).add(path$14);
1338
+ this.settleTaken(taken, this.runWatchAttempt({
1339
+ modifiedFiles,
1340
+ removedFiles,
1341
+ sources
1342
+ }, rescan));
1343
+ return true;
1344
+ }
1345
+ /** Every untaken observation of the build, and whether a full rescan is
1346
+ * requested, which the caller's attempt now covers. */
1347
+ takeObservations() {
1348
+ const queue = observationQueue(this.key);
1349
+ const taken = new Map(queue.untaken);
1350
+ const rescan = queue.rescan;
1351
+ const sources = new Map(queue.suppliedSources);
1352
+ queue.untaken.clear();
1353
+ queue.suppliedSources.clear();
1354
+ queue.rescan = false;
1355
+ return {
1356
+ taken,
1357
+ rescan,
1358
+ sources
1359
+ };
1360
+ }
1361
+ /** `transaction` settles every waiting revision, and is the active
1362
+ * attempt equivalent observations join until it settles. */
1363
+ settleTaken(taken, transaction) {
1364
+ const queue = observationQueue(this.key);
1365
+ for (const [revision, attempt] of queue.revisions) if (attempt === null) queue.revisions.set(revision, transaction);
1366
+ queue.active = {
1367
+ taken,
1368
+ transaction
1369
+ };
1370
+ const settle = () => {
1371
+ if (queue.active?.transaction === transaction) queue.active = null;
1372
+ };
1373
+ transaction.then(settle, settle);
1374
+ }
1375
+ /** Registers one attempt in the build's single-flight slot before it
1376
+ * processes, so a nested full system reload inherits the gate rather
1377
+ * than clear it, and clears the slot when it settles either way. */
1378
+ runWatchAttempt(changes, rescan = false) {
1379
+ const transaction = Promise.resolve().then(() => rescan ? this.runFullPipeline([], changes.sources) : this.processWatchUpdate(changes));
1380
+ setWatchTransaction(this.key, transaction);
1381
+ const settle = () => {
1382
+ if (getWatchTransaction(this.key) === transaction) setWatchTransaction(this.key, null);
1383
+ };
1384
+ transaction.then(settle, settle);
1385
+ return transaction;
1386
+ }
880
1387
  async processWatchUpdate(changes) {
881
1388
  const rootDir = this.rootDir;
882
1389
  try {
@@ -887,31 +1394,25 @@ var ExtractionSession = class {
887
1394
  const changed = [...changes.modifiedFiles ?? [], ...changes.removedFiles ?? []];
888
1395
  let assetChanged = false;
889
1396
  let systemHit;
1397
+ let configHit;
890
1398
  try {
891
- if (!changes.modifiedFiles && !changes.removedFiles) for (const [dep, hash] of this.systemDependencyHashes) {
892
- let current = "";
893
- try {
894
- current = require_pipeline.contentHash((0, fs.readFileSync)(dep, "utf-8"));
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)));
1399
+ if (changed.length === 0) {
1400
+ systemHit = firstChangedFile(this.systemDependencyHashes);
1401
+ if (systemHit === void 0) configHit = firstChangedFile(this.configInputHashes);
1402
+ } else systemHit = changed.find((path$15) => this.isSystemDependency(path$15));
902
1403
  } catch (err) {
903
1404
  this.warn(`HMR system-reload check failed: ${String(err)}`);
904
1405
  }
905
- if (systemHit) {
906
- this.log(`system reload: system dependency changed (${(0, path.relative)(rootDir, systemHit)})`);
1406
+ configHit ??= changed.find((path$16) => require_pipeline.toWatchKeys(path$16).some((key) => this.configInputKeys.has(key)));
1407
+ const debtRetry = this.systemReloadOwed && this.heldRejection === null;
1408
+ if (systemHit || configHit || debtRetry) {
1409
+ 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
1410
  this.resetForHmr();
908
1411
  try {
909
- const promise = this.runFullPipeline(this.pendingFromBatch(changes));
910
- setAnalysisStartedPromise(promise);
1412
+ const promise = this.runFullPipeline(this.pendingFromBatch(changes), changes.sources);
1413
+ setAnalysisStartedPromise(this.key, promise);
911
1414
  await promise;
912
- this.systemReloadOwed = false;
913
1415
  } catch (err) {
914
- if (err instanceof AbortedParseRejection) this.systemReloadOwed = true;
915
1416
  try {
916
1417
  this.beginStatusAttempt();
917
1418
  this.writeAnalysisStatus("failed", this.pendingFromBatch(changes), String(err));
@@ -922,11 +1423,11 @@ var ExtractionSession = class {
922
1423
  }
923
1424
  return;
924
1425
  }
925
- if (changed.some((path$3) => require_pipeline.toWatchKeys(path$3).some((key) => this.assetDependencyKeys.has(key)))) {
1426
+ if (changed.some((path$17) => require_pipeline.toWatchKeys(path$17).some((key) => this.assetDependencyKeys.has(key)))) {
926
1427
  this.assetCopyCache.clear();
927
1428
  assetChanged = true;
928
1429
  }
929
- const { excludeMatcher, extensionsSet } = this.resolveScanConfig();
1430
+ const { excludeMatcher, extensionsSet } = this.scanConfig;
930
1431
  const priorCacheEntries = /* @__PURE__ */ new Map();
931
1432
  const priorExternalFileOwners = { ...this.externalFileOwners };
932
1433
  const recordPrior = (key) => {
@@ -934,6 +1435,7 @@ var ExtractionSession = class {
934
1435
  };
935
1436
  const inventoryUpdates = [];
936
1437
  const ownerRemovals = [];
1438
+ const removedProjectFiles = /* @__PURE__ */ new Map();
937
1439
  let removedAny = false;
938
1440
  if (changes.removedFiles) for (const removedPath of changes.removedFiles) {
939
1441
  let key;
@@ -947,8 +1449,10 @@ var ExtractionSession = class {
947
1449
  }
948
1450
  recordPrior(key);
949
1451
  this.clearIngestionFailure(key);
1452
+ this.hostSuppliedKeys.delete(key);
950
1453
  if (this.fileCache.delete(key)) {
951
1454
  removedAny = true;
1455
+ if (!owningRoot) removedProjectFiles.set(key, removedPath);
952
1456
  if (owningRoot) {
953
1457
  inventoryUpdates.push({
954
1458
  root: owningRoot,
@@ -979,15 +1483,29 @@ var ExtractionSession = class {
979
1483
  owningRoot: null
980
1484
  }));
981
1485
  const changedPaths = [];
1486
+ const inOutput = require_pipeline.outputSourceGuard(rootDir, this.structuralExclude);
1487
+ let readFailuresChanged = false;
982
1488
  for (const target of targets) {
983
1489
  const relPath = target.key;
1490
+ const outputDir = inOutput(target.abs);
1491
+ if (outputDir !== null) {
1492
+ this.warn(require_pipeline.outputSourceMessage(relPath, outputDir));
1493
+ continue;
1494
+ }
984
1495
  let source;
985
1496
  try {
986
- source = (0, fs.readFileSync)(target.abs, "utf-8");
987
- } catch {
1497
+ const supplied = changes.sources?.get(target.abs);
1498
+ source = supplied ?? (0, fs.readFileSync)(target.abs, "utf-8");
1499
+ if (supplied === void 0) this.hostSuppliedKeys.delete(relPath);
1500
+ else this.hostSuppliedKeys.add(relPath);
1501
+ } catch (err) {
1502
+ if (require_pipeline.isDeletedSource(err)) continue;
1503
+ this.clearIngestionFailure(relPath);
1504
+ this.ingestionFailureDiagnostics = [...this.ingestionFailureDiagnostics, require_pipeline.unreadableSourceDiagnostic(relPath, err)];
1505
+ readFailuresChanged = true;
988
1506
  continue;
989
1507
  }
990
- this.clearIngestionFailure(relPath);
1508
+ if (this.clearIngestionFailure(relPath)) readFailuresChanged = true;
991
1509
  const rawHash = require_pipeline.contentHash(source);
992
1510
  const cached = this.fileCache.get(relPath);
993
1511
  const hash = rawHash;
@@ -1010,7 +1528,7 @@ var ExtractionSession = class {
1010
1528
  }
1011
1529
  }
1012
1530
  }
1013
- if (changedPaths.length > 0 || removedAny || assetChanged) {
1531
+ if (changedPaths.length > 0 || removedAny || readFailuresChanged || assetChanged) {
1014
1532
  const pending = changedPaths.map((rel) => [rel, this.fileCache.get(rel).hash]);
1015
1533
  const commitObservedBatch = () => {
1016
1534
  for (const update of inventoryUpdates) {
@@ -1036,23 +1554,30 @@ var ExtractionSession = class {
1036
1554
  });
1037
1555
  this.throwOnAbortedParse(ingested);
1038
1556
  this.skippedSources = ingested.skippedOriginals ?? [];
1557
+ this.ingestionOpenings = ingested.openings ?? [];
1558
+ this.ingestionAdapted = require_pipeline.adaptedOriginals(ingested.ownership);
1039
1559
  if (this.systemReloadOwed) {
1040
1560
  this.log("system reload: publishing the reload a parse hold deferred");
1561
+ const stale = this.staleHostSuppliedSources(changes.sources);
1562
+ 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
1563
  this.resetForHmr();
1042
- await this.runFullPipeline(pending);
1043
- this.systemReloadOwed = false;
1564
+ await this.runFullPipeline(pending, changes.sources);
1044
1565
  return;
1045
1566
  }
1046
1567
  this.externalFileOwners = require_pipeline.projectExternalFileOwners(ingested, this.externalFileOwners);
1047
- resetAnalysisStartedPromise();
1568
+ resetAnalysisStartedPromise(this.key);
1048
1569
  const promise = this.runIncrementalPipeline(ingested.analysisEntries, pending);
1049
- setAnalysisStartedPromise(promise);
1570
+ setAnalysisStartedPromise(this.key, promise);
1050
1571
  await promise;
1051
1572
  } catch (err) {
1052
- if (err instanceof AbortedParseRejection) commitObservedBatch();
1573
+ if (err instanceof AbortedParseRejection || err instanceof DeferredReloadRefusal) commitObservedBatch();
1053
1574
  else {
1054
- for (const [key, prior] of priorCacheEntries) if (prior === null) this.fileCache.delete(key);
1055
- else this.fileCache.set(key, prior);
1575
+ for (const [key, prior] of priorCacheEntries) {
1576
+ const removed = removedProjectFiles.get(key);
1577
+ if (removed !== void 0 && !(0, fs.existsSync)(removed)) continue;
1578
+ if (prior === null) this.fileCache.delete(key);
1579
+ else this.fileCache.set(key, prior);
1580
+ }
1056
1581
  this.externalFileOwners = priorExternalFileOwners;
1057
1582
  }
1058
1583
  if (this.statusAttemptOpen) {
@@ -1061,7 +1586,7 @@ var ExtractionSession = class {
1061
1586
  }
1062
1587
  throw err;
1063
1588
  }
1064
- this.publishSourceIngestion(ingested);
1589
+ this.publishSourceIngestion(ingested, "incremental", (path$18) => this.fileCache.get(path$18));
1065
1590
  commitObservedBatch();
1066
1591
  } else if (this.statusAttemptOpen) {
1067
1592
  this.debouncePending.clear();
@@ -1076,88 +1601,233 @@ var ExtractionSession = class {
1076
1601
  const rejection = this.corpus.rejection(prepared);
1077
1602
  if (!rejection) return;
1078
1603
  this.heldRejection = rejection.message;
1604
+ this.onOutcome?.({
1605
+ kind: "held",
1606
+ abortedOriginals: rejection.originals
1607
+ });
1079
1608
  throw new AbortedParseRejection(rejection.message);
1080
1609
  }
1081
- publishSourceIngestion(result) {
1610
+ /** Held sources whose bytes the host supplied and which the disk no
1611
+ * longer matches: a full read not carrying them (`carried`) would
1612
+ * publish the disk's bytes in their place. */
1613
+ staleHostSuppliedSources(carried) {
1614
+ const stale = [];
1615
+ for (const key of this.hostSuppliedKeys) {
1616
+ const held = this.fileCache.get(key);
1617
+ const abs = (0, path.resolve)(this.rootDir, key);
1618
+ if (held === void 0 || carried?.has(abs)) continue;
1619
+ let onDisk;
1620
+ try {
1621
+ onDisk = (0, fs.readFileSync)(abs, "utf-8");
1622
+ } catch {
1623
+ onDisk = null;
1624
+ }
1625
+ if (onDisk === null || require_pipeline.contentHash(onDisk) !== held.hash) stale.push(key);
1626
+ }
1627
+ return stale.sort();
1628
+ }
1629
+ /** `raw` is the attempt's input by original path, from which a
1630
+ * quarantined original stays held: every later pass re-ingests it, so it
1631
+ * keeps counting as skipped (nothing it renders is pruned) and its fix
1632
+ * edit compares against its bytes. */
1633
+ publishSourceIngestion(result, pass, raw) {
1634
+ const endedHold = this.heldRejection !== null;
1635
+ const previousOwnership = this.corpus.published.ownership;
1082
1636
  this.heldRejection = null;
1083
- this.fileCache = new Map(result.originalEntries.map((entry) => [entry.path, {
1637
+ const held = new Map(result.originalEntries.map((entry) => [entry.path, {
1084
1638
  hash: entry.hash,
1085
1639
  source: entry.source
1086
1640
  }]));
1641
+ for (const path$19 of result.skippedOriginals ?? []) {
1642
+ const entry = raw(path$19);
1643
+ if (entry) held.set(path$19, {
1644
+ hash: entry.hash ?? require_pipeline.contentHash(entry.source),
1645
+ source: entry.source
1646
+ });
1647
+ }
1648
+ this.fileCache = held;
1087
1649
  this.corpus.publish(result);
1650
+ const staged = this.stagedPublication;
1651
+ if (staged === null) return;
1652
+ this.stagedPublication = null;
1653
+ const previous = this.publication;
1654
+ const publication = {
1655
+ ...staged,
1656
+ ownership: result.ownership
1657
+ };
1658
+ this.publication = publication;
1659
+ this.onOutcome?.({
1660
+ kind: "published",
1661
+ publication,
1662
+ pass,
1663
+ planChanged: require_pipeline.diffFilePlans(require_pipeline.snapshotFilePlans(previous?.manifest ?? null), require_pipeline.snapshotFilePlans(publication.manifest)),
1664
+ endedHold,
1665
+ redeliver: endedHold ? Object.values(result.ownership).filter((owner) => previousOwnership[owner.originalPath]?.originalHash !== owner.originalHash).map((owner) => owner.originalPath) : []
1666
+ });
1088
1667
  }
1089
1668
  /** Full analysis + publication, registered in the one in-flight
1090
- * transaction slot; a nested reload leaves the enclosing registration. */
1091
- async runFullPipeline(pending = []) {
1669
+ * transaction slot; a nested reload leaves the enclosing registration.
1670
+ * `sources` are bytes a host already read (see WatchChanges.sources):
1671
+ * the full read uses them, and those of the observations it takes, in
1672
+ * place of the disk. */
1673
+ async runFullPipeline(pending = [], sources = /* @__PURE__ */ new Map()) {
1092
1674
  if (this.releasePublicationClaim === null) {
1093
- const releaseOwner = claimExclusiveSessionOwner(`${this.driverLabel}:${this.sessionDir}`);
1094
- (0, fs.mkdirSync)(this.sessionDir, { recursive: true });
1095
- const releaseClaimRecord = holdDirectoryClaim(this.sessionDir);
1096
- this.releasePublicationClaim = () => {
1097
- releaseOwner();
1098
- releaseClaimRecord();
1099
- };
1675
+ const releaseOwner = claimExclusiveSessionOwner(this.key, `${this.driverLabel}:${this.sessionDir}`);
1676
+ if (this.persistArtifacts) {
1677
+ (0, fs.mkdirSync)(this.sessionDir, { recursive: true });
1678
+ holdBuildClaimRecord(this.key, () => holdDirectoryClaim(this.sessionDir, { build: this.sessionId }));
1679
+ }
1680
+ this.releasePublicationClaim = releaseOwner;
1100
1681
  }
1101
1682
  const priorSystem = this.system;
1102
- const nested = getWatchTransaction() !== null;
1103
- const transaction = this.runPipelineTransaction(pending);
1104
- if (!nested) setWatchTransaction(transaction);
1683
+ const priorRegistration = this.snapshotWatchRegistration();
1684
+ this.attemptRecordedSystemDependencies = false;
1685
+ const nested = getWatchTransaction(this.key) !== null;
1686
+ const observations = nested ? null : this.takeObservations();
1687
+ const taken = observations?.taken ?? null;
1688
+ const transaction = this.runPipelineTransaction(pending, new Map([...observations?.sources ?? [], ...sources]));
1689
+ if (taken !== null) {
1690
+ setWatchTransaction(this.key, transaction);
1691
+ this.settleTaken(taken, transaction);
1692
+ }
1105
1693
  try {
1106
1694
  await transaction;
1107
1695
  } catch (error) {
1108
1696
  this.system = priorSystem;
1697
+ this.restoreWatchRegistration(priorRegistration);
1698
+ if (priorSystem !== null && (this.development || error instanceof AbortedParseRejection)) this.systemReloadOwed = true;
1109
1699
  throw error;
1110
1700
  } finally {
1111
- if (!nested) setWatchTransaction(null);
1701
+ if (!nested) setWatchTransaction(this.key, null);
1702
+ }
1703
+ this.systemReloadOwed = false;
1704
+ this.lastGoodSystemDependencies = {
1705
+ keys: this.systemDependencyKeys,
1706
+ paths: this.systemDependencyPaths,
1707
+ missing: this.systemMissingPaths,
1708
+ hashes: this.systemDependencyHashes
1709
+ };
1710
+ this.commitSystemAuthority();
1711
+ }
1712
+ /** This attempt's system dependencies: the files its load read, and the
1713
+ * paths it found absent, whose creation is a system change. */
1714
+ recordSystemDependencies(systemPath, dependencies, missing) {
1715
+ const keys = /* @__PURE__ */ new Set();
1716
+ for (const path$20 of [
1717
+ systemPath,
1718
+ ...dependencies,
1719
+ ...missing
1720
+ ]) for (const key of require_pipeline.toWatchKeys(path$20)) keys.add(key);
1721
+ const hashes = /* @__PURE__ */ new Map();
1722
+ for (const dep of dependencies.length > 0 ? dependencies : [systemPath]) try {
1723
+ hashes.set(dep, require_pipeline.contentHash((0, fs.readFileSync)(dep, "utf-8")));
1724
+ } catch {
1725
+ hashes.set(dep, "");
1726
+ }
1727
+ for (const path$21 of missing) hashes.set(path$21, "");
1728
+ this.systemDependencyKeys = keys;
1729
+ this.systemDependencyPaths = dependencies;
1730
+ this.systemMissingPaths = missing;
1731
+ this.systemDependencyHashes = hashes;
1732
+ this.attemptRecordedSystemDependencies = true;
1733
+ }
1734
+ /** Records the configuration inputs and, for a tsconfig-aliased host,
1735
+ * re-reads its aliases. */
1736
+ readConfigInputs(rootDir) {
1737
+ this.configInputPaths = [(0, path.join)(rootDir, "package.json"), ...require_pipeline.tsconfigChainPaths(rootDir)];
1738
+ this.configInputKeys = new Set(this.configInputPaths.flatMap((path$22) => require_pipeline.toWatchKeys(path$22)));
1739
+ this.configInputHashes = new Map(this.configInputPaths.map((path$23) => [path$23, fileHashOrEmpty(path$23)]));
1740
+ if (this.aliasesFromTsconfig) this.pathAliasesJson = require_pipeline.buildPathAliasesJson(require_pipeline.readTsconfigAliasPairs(rootDir), rootDir)?.json ?? null;
1741
+ }
1742
+ /** What hosts watch for assets, and what the last-good generation
1743
+ * reported about its kits and packages, which a failed full pass must
1744
+ * leave in place. */
1745
+ snapshotWatchRegistration() {
1746
+ return {
1747
+ assetDependencyPaths: new Set(this.assetDependencyPaths),
1748
+ assetDependencyKeys: new Set(this.assetDependencyKeys),
1749
+ kitDescriptors: this.kitDescriptors,
1750
+ lastExternalOutcomes: this.lastExternalOutcomes
1751
+ };
1752
+ }
1753
+ /** Restores the last-good registration. The system dependencies are the
1754
+ * last committed pass's and, when its load got that far, this failed
1755
+ * attempt's (spec §2.3): a fix to any of them must reach the owed reload,
1756
+ * and an earlier failure's are replaced, never accumulated. */
1757
+ restoreWatchRegistration(prior) {
1758
+ Object.assign(this, prior);
1759
+ const lastGood = this.lastGoodSystemDependencies;
1760
+ if (!this.attemptRecordedSystemDependencies) {
1761
+ this.systemDependencyKeys = lastGood.keys;
1762
+ this.systemDependencyPaths = lastGood.paths;
1763
+ this.systemMissingPaths = lastGood.missing;
1764
+ this.systemDependencyHashes = lastGood.hashes;
1765
+ return;
1112
1766
  }
1767
+ this.systemMissingPaths = [.../* @__PURE__ */ new Set([...lastGood.missing, ...this.systemMissingPaths])];
1768
+ this.systemDependencyKeys = /* @__PURE__ */ new Set([...lastGood.keys, ...this.systemDependencyKeys]);
1769
+ this.systemDependencyPaths = [.../* @__PURE__ */ new Set([...lastGood.paths, ...this.systemDependencyPaths])];
1770
+ this.systemDependencyHashes = new Map([...this.systemDependencyHashes, ...lastGood.hashes]);
1771
+ }
1772
+ /** The build's authority becomes the system this session committed, with
1773
+ * it and never before: a failed reload leaves the committed one, and the
1774
+ * context proofs made against it. A committed reload discards every
1775
+ * other context's proof, even for an unchanged system: the reload may
1776
+ * have changed what another context resolves, so each loads again. */
1777
+ commitSystemAuthority() {
1778
+ setSystemAuthority(this.key, {
1779
+ ...systemLoad(this.system),
1780
+ context: this.resolutionContextText(),
1781
+ label: this.resolutionContextLabel(),
1782
+ others: /* @__PURE__ */ new Map()
1783
+ });
1113
1784
  }
1114
- async runPipelineTransaction(pending) {
1785
+ async runPipelineTransaction(pending, hostSources) {
1115
1786
  const pipelineStart = this.now();
1116
1787
  const bt = {};
1117
1788
  this.runSessionStartHygiene();
1118
- setSessionArtifactDir(this.sessionDir);
1789
+ setSessionArtifactDir(this.key, this.sessionDir);
1119
1790
  const rootDir = this.rootDir;
1120
1791
  const priorSourceState = this.snapshotSourceState();
1121
1792
  const resolvedSystemPath = (0, path.resolve)(rootDir, this.options.system);
1793
+ this.readConfigInputs(rootDir);
1794
+ const context = {
1795
+ conditions: this.conditions,
1796
+ workspaces: require_pipeline.declaredWorkspacePackages(rootDir),
1797
+ extensions: this.extensionPolicy
1798
+ };
1799
+ this.log(`extension order: ${context.extensions.resolverVersion}`);
1122
1800
  let t = this.now();
1123
- this.system = require_pipeline.loadSystemConfig(engineApi, {
1124
- systemPath: resolvedSystemPath,
1125
- rootDir,
1126
- prefix: this.options.prefix,
1127
- prefixContextualVars: this.options.prefixContextualVars,
1128
- conditions: this.conditions
1129
- });
1801
+ try {
1802
+ this.system = this.loadSystemUnder(context);
1803
+ } catch (error) {
1804
+ const reads = require_pipeline.systemLoadReads(error);
1805
+ this.recordSystemDependencies(resolvedSystemPath, reads.dependencies, reads.missing);
1806
+ throw error;
1807
+ }
1130
1808
  this.assetCopyCache.clear();
1131
1809
  this.assetDependencyPaths.clear();
1132
1810
  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
- }
1811
+ this.recordSystemDependencies(resolvedSystemPath, this.system.dependencies ?? [], this.system.missing ?? []);
1148
1812
  bt.systemLoad = this.elapsed(t);
1149
1813
  t = this.now();
1150
- const { excludeMatcher, extensionsSet } = this.resolveScanConfig();
1814
+ const { excludeMatcher, extensionsSet } = this.scanConfig;
1151
1815
  const files = require_pipeline.discoverFiles(rootDir, rootDir, excludeMatcher, extensionsSet);
1816
+ if (claimUnmatchedExcludeReport(this.key)) for (const line of require_pipeline.unmatchedExcludeLines(excludeMatcher)) this.logger.error(line);
1152
1817
  bt.fileDiscovery = this.elapsed(t);
1153
1818
  t = this.now();
1154
1819
  const rawEntries = [];
1155
1820
  const ingestionFailures = [];
1821
+ const readPaths = /* @__PURE__ */ new Set();
1156
1822
  for (const filePath of files) {
1157
1823
  const relPath = (0, path.relative)(rootDir, filePath);
1824
+ readPaths.add(filePath);
1158
1825
  let source;
1159
1826
  try {
1160
- source = (0, fs.readFileSync)(filePath, "utf-8");
1827
+ const supplied = hostSources.get(filePath);
1828
+ source = supplied ?? (0, fs.readFileSync)(filePath, "utf-8");
1829
+ if (supplied === void 0) this.hostSuppliedKeys.delete(relPath);
1830
+ else this.hostSuppliedKeys.add(relPath);
1161
1831
  } catch (err) {
1162
1832
  if (!require_pipeline.isDeletedSource(err)) ingestionFailures.push(require_pipeline.unreadableSourceDiagnostic(relPath, err));
1163
1833
  continue;
@@ -1169,21 +1839,36 @@ var ExtractionSession = class {
1169
1839
  });
1170
1840
  }
1171
1841
  bt.fileRead = this.elapsed(t);
1842
+ const rawPaths = new Set(rawEntries.map((entry) => entry.path));
1172
1843
  t = this.now();
1173
- const discoveryDiagnostics = [];
1174
- const packageNames = await require_pipeline.extractSystemFilePackages(resolvedSystemPath, require_pipeline.engineModuleParser(engineApi()), (diagnostic) => discoveryDiagnostics.push(diagnostic), (name) => {
1175
- const entry = resolvePackagesByName(rootDir, [name])[name];
1176
- return entry ? (0, path.resolve)(rootDir, entry) : null;
1844
+ const resolver = require_pipeline.discoveryResolver(this.hostResolve, this.engine(), {
1845
+ rootDir,
1846
+ conditions: context.conditions,
1847
+ workspaces: context.workspaces,
1848
+ extensions: context.extensions
1177
1849
  });
1178
- discoveryDiagnostics.push(...require_pipeline.importedKitDiagnostics(rawEntries, engineApi(), rootDir, packageNames));
1179
- const preResolved = resolvePackagesByName(rootDir, packageNames);
1850
+ const discoveryDiagnostics = [];
1851
+ const systemPackageRoot = require_pipeline.findPackageRoot(resolvedSystemPath);
1852
+ const kitMembers = /* @__PURE__ */ new Map();
1853
+ 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);
1854
+ discoveryDiagnostics.push(...require_pipeline.importedKitDiagnostics(rawEntries, this.engine(), rootDir, packageNames, (entries) => this.corpus.facts(entries)));
1180
1855
  const rawExternalFiles = /* @__PURE__ */ new Map();
1181
1856
  const collected = await require_pipeline.collectExternalPackageSources({
1857
+ extensions: context.extensions,
1858
+ kitSourceRoot: require_pipeline.nativeKitSourceRoot(this.engine(), rootDir, context.conditions, context.workspaces, require_pipeline.extensionPolicyJson(context.extensions)),
1859
+ locatePackage: require_pipeline.nativePackageLocator(this.engine(), context.workspaces),
1182
1860
  specifiers: packageNames,
1183
- resolveSpecifier: (name) => preResolved[name] ? (0, path.resolve)(rootDir, preResolved[name]) : null,
1861
+ resolveSpecifier: async (specifier) => kitMembers.get(specifier) ?? await resolver.resolveSpecifier(specifier),
1862
+ locateFrom: (specifier) => {
1863
+ const file = kitMembers.get(specifier);
1864
+ return file === void 0 ? rootDir : (0, path.dirname)(file);
1865
+ },
1866
+ unresolvedReason: resolver.unresolvedReason,
1867
+ requireSourceEntry: resolver.requireSourceEntry,
1184
1868
  rootDir,
1185
1869
  extensionsSet,
1186
- hasEntry: (relPath) => rawEntries.some((e) => e.path === relPath),
1870
+ hasEntry: (relPath) => rawPaths.has(relPath),
1871
+ hostOutputDirs: this.structuralExclude.map((dir) => (0, path.resolve)(rootDir, dir)),
1187
1872
  onSourceRead: (source, _relPath, absPath) => {
1188
1873
  rawExternalFiles.set(absPath, require_pipeline.contentHash(source));
1189
1874
  },
@@ -1202,21 +1887,22 @@ var ExtractionSession = class {
1202
1887
  });
1203
1888
  this.lastExternalOutcomes = collected.outcomes;
1204
1889
  this.ingestionFailureDiagnostics = ingestionFailures;
1890
+ if (resolver.hostFirst) discoveryDiagnostics.push(...systemInstanceMismatches({
1891
+ rootDir,
1892
+ specifiers: packageNames,
1893
+ instances: collected.packageInstances,
1894
+ evaluated: this.systemDependencyPaths,
1895
+ loaderResolve: (specifier) => resolver.loaderResolve(specifier, (0, path.dirname)(resolvedSystemPath))
1896
+ }));
1205
1897
  require_pipeline.surfaceManifestDiagnostics({ diagnostics: [...discoveryDiagnostics, ...collected.diagnostics] }, (message) => this.warn(message), {
1206
1898
  levels: this.options.diagnostics,
1207
1899
  strict: this.options.strict,
1208
1900
  reportErrors: this.reportErrors()
1209
1901
  });
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
- }
1902
+ const unresolvableMessage = require_pipeline.unresolvableIncludesMessage(collected.outcomes, (0, path.relative)(rootDir, resolvedSystemPath));
1903
+ if (unresolvableMessage !== null) this.strictOrWarn(unresolvableMessage);
1215
1904
  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
- }
1905
+ if (staleDistMessage !== null) this.strictOrWarn(staleDistMessage);
1220
1906
  const rejectedSpecifiers = /* @__PURE__ */ new Set();
1221
1907
  const crossVolumeDetails = [];
1222
1908
  for (const [dir, specifiers] of Object.entries(collected.dirOwnerSets)) {
@@ -1233,6 +1919,7 @@ var ExtractionSession = class {
1233
1919
  for (const message of this.stickyDiagnostics.values()) this.warn(message);
1234
1920
  const admitted = require_pipeline.excludeCollectedPackages(collected, rejectedSpecifiers, rootDir);
1235
1921
  this.kitDescriptors = admitted.kitDescriptors;
1922
+ this.packageInstances = admitted.packageInstances;
1236
1923
  try {
1237
1924
  const identity = require_pipeline.createSourceIdentity(rootDir);
1238
1925
  this.externalRootOwners = /* @__PURE__ */ new Map();
@@ -1268,23 +1955,29 @@ var ExtractionSession = class {
1268
1955
  this.externalSourceEntries = admitted.sourceEntries;
1269
1956
  this.externalSourceSideEffects = admitted.sourceEntrySideEffects;
1270
1957
  for (const entry of admitted.entries) {
1271
- const hash = require_pipeline.contentHash(entry.source);
1958
+ const entryPath = (0, path.resolve)(rootDir, entry.path);
1959
+ readPaths.add(entryPath);
1960
+ const source = hostSources.get(entryPath) ?? entry.source;
1272
1961
  rawEntries.push({
1273
1962
  path: entry.path,
1274
- source: entry.source,
1275
- hash
1963
+ source,
1964
+ hash: require_pipeline.contentHash(source)
1276
1965
  });
1277
1966
  }
1278
1967
  this.externalPackageDirs = admitted.packageDirs;
1279
1968
  this.externalLinkedDirs = admitted.linkedDirs;
1280
1969
  this.externalOutputDirs = admitted.outputDirs;
1281
1970
  bt.packageResolve = this.elapsed(t);
1971
+ const misspelled = misspelledHostSources(hostSources, readPaths).map(({ path: path$24, read }) => `${(0, path.relative)(rootDir, path$24)} (read as ${read.map((readPath) => (0, path.relative)(rootDir, readPath)).join(", ")})`);
1972
+ 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
1973
  this.beginStatusAttempt();
1283
1974
  let accepted;
1284
1975
  try {
1285
- accepted = await this.corpus.prepare(rawEntries);
1976
+ accepted = await this.corpus.prepare(this.withoutOutputSources(rawEntries));
1286
1977
  this.throwOnAbortedParse(accepted);
1287
1978
  this.skippedSources = accepted.skippedOriginals ?? [];
1979
+ this.ingestionOpenings = accepted.openings ?? [];
1980
+ this.ingestionAdapted = require_pipeline.adaptedOriginals(accepted.ownership);
1288
1981
  } catch (err) {
1289
1982
  this.debouncePending.clear();
1290
1983
  this.writeAnalysisStatus("failed", pending, String(err));
@@ -1292,19 +1985,30 @@ var ExtractionSession = class {
1292
1985
  }
1293
1986
  this.externalFileOwners = require_pipeline.projectExternalFileOwners(accepted, this.externalFileOwners);
1294
1987
  bt.fileCount = accepted.analysisEntries.length;
1295
- require_pipeline.clearEngineCache(engineApi);
1988
+ require_pipeline.clearEngineCache(() => this.engine());
1296
1989
  await this.analyzeAndEmit(accepted.analysisEntries, packageMap, false, bt, pipelineStart, pending);
1297
- this.publishSourceIngestion(accepted);
1298
- setSharedExternalDirs(admitted.packageDirs);
1299
- setSharedExternalEntries(admitted.sourceEntries);
1300
- setSharedExternalSideEffects(admitted.sourceEntrySideEffects);
1990
+ this.publishSourceIngestion(accepted, "full", (path$25) => rawEntries.find((entry) => entry.path === path$25));
1991
+ setSharedExternalDirs(this.key, admitted.packageDirs);
1992
+ setSharedExternalEntries(this.key, admitted.sourceEntries);
1993
+ setSharedExternalSideEffects(this.key, admitted.sourceEntrySideEffects);
1301
1994
  this.onExternalRootsCommitted?.(this.externalWatchRoots);
1302
- setOwningWatchSession(this);
1995
+ setOwningWatchSession(this.key, this);
1303
1996
  } catch (err) {
1304
1997
  this.restoreSourceState(priorSourceState);
1305
1998
  throw err;
1306
1999
  }
1307
2000
  }
2001
+ /** The full pass's analysis inputs, less any file whose real path lies in
2002
+ * an output directory, each reported. */
2003
+ withoutOutputSources(entries) {
2004
+ const inOutput = require_pipeline.outputSourceGuard(this.rootDir, this.structuralExclude);
2005
+ return entries.filter((entry) => {
2006
+ const outputDir = inOutput((0, path.resolve)(this.rootDir, entry.path));
2007
+ if (outputDir === null) return true;
2008
+ this.warn(require_pipeline.outputSourceMessage(entry.path, outputDir));
2009
+ return false;
2010
+ });
2011
+ }
1308
2012
  /** Per-generation source state captured as ONE object so the restore
1309
2013
  * cannot omit a member; deep-copied fields are mutated in place. */
1310
2014
  snapshotSourceState() {
@@ -1326,16 +2030,92 @@ var ExtractionSession = class {
1326
2030
  restoreSourceState(snapshot) {
1327
2031
  Object.assign(this, snapshot);
1328
2032
  }
1329
- /** Release this session's publication claim so a successor may publish.
1330
- * Idempotent, and scoped to this session's own claim. */
2033
+ /** The system under `context`: the one load path for the build's
2034
+ * authority and for a participant checking its own context. */
2035
+ loadSystemUnder(context) {
2036
+ return require_pipeline.loadSystemConfig(() => this.engine(), {
2037
+ systemPath: (0, path.resolve)(this.rootDir, this.options.system),
2038
+ rootDir: this.rootDir,
2039
+ prefix: this.options.prefix,
2040
+ prefixContextualVars: this.options.prefixContextualVars,
2041
+ conditions: context.conditions,
2042
+ workspaces: context.workspaces,
2043
+ extensions: context.extensions
2044
+ });
2045
+ }
2046
+ /** What the system loader reads of this participant besides the root, as
2047
+ * stable text: its conditions and extension order. */
2048
+ resolutionContextText() {
2049
+ return JSON.stringify([this.conditions, require_pipeline.extensionPolicyJson(this.extensionPolicy)]);
2050
+ }
2051
+ resolutionContextLabel() {
2052
+ return `${this.driverLabel} with conditions [${this.conditions.join(", ")}] and extension order ${this.extensionPolicy.resolverVersion}`;
2053
+ }
2054
+ /** One agreed system authority (SA5): a participant whose resolution
2055
+ * context differs from the one the build's system was loaded under loads
2056
+ * it once under its own, and proceeds only on the same system. Otherwise
2057
+ * it throws, naming both contexts, whichever participant came first. */
2058
+ agreeWithSystemAuthority() {
2059
+ const authority = getSystemAuthority(this.key);
2060
+ if (authority === null) return;
2061
+ const context = this.resolutionContextText();
2062
+ if (context === authority.context) return;
2063
+ let own = authority.others.get(context);
2064
+ if (own === void 0) {
2065
+ own = systemLoad(this.loadSystemUnder({
2066
+ conditions: this.conditions,
2067
+ workspaces: require_pipeline.declaredWorkspacePackages(this.rootDir),
2068
+ extensions: this.extensionPolicy
2069
+ }));
2070
+ authority.others.set(context, own);
2071
+ }
2072
+ if (own.identity === authority.identity) return;
2073
+ const shared = new Set(authority.dependencies);
2074
+ const mine = new Set(own.dependencies);
2075
+ const differing = [...authority.dependencies.filter((path$26) => !mine.has(path$26)), ...own.dependencies.filter((path$27) => !shared.has(path$27))].map((path$28) => (0, path.relative)(this.rootDir, path$28));
2076
+ 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.");
2077
+ }
2078
+ /** Release this session's publication claim so a successor may publish,
2079
+ * and leave the build, which retires with its last participant.
2080
+ * Idempotent; `reattach` joins the build again. */
1331
2081
  close() {
1332
2082
  this.releasePublicationClaim?.();
1333
2083
  this.releasePublicationClaim = null;
2084
+ if (this.joinedKey === null || this.participant === null) return;
2085
+ if (getOwningWatchSession(this.joinedKey) === this) setOwningWatchSession(this.joinedKey, null);
2086
+ leaveSession(this.joinedKey, this.participant);
2087
+ this.participant = null;
2088
+ this.detached = true;
2089
+ }
2090
+ /** A closed participant attaching again: its private analysis is stale,
2091
+ * so it is discarded, and it joins its build again. Idempotent. */
2092
+ reattach() {
2093
+ if (!this.detached) return;
2094
+ this.detached = false;
2095
+ this.system = null;
2096
+ this.joinedKey = null;
2097
+ }
2098
+ /** Brings a reattached participant up to date with a full rescan of a
2099
+ * live build, which reconciles deletions too; true then. A retired build
2100
+ * is false: the host starts it as at first. */
2101
+ async catchUp() {
2102
+ if (getManifestJson(this.key) === null) return false;
2103
+ await this.awaitObservation(this.recordRescan());
2104
+ return true;
2105
+ }
2106
+ /** Queues a full rescan under a new revision. It is never equivalent to
2107
+ * an active attempt: only a full read covers it. */
2108
+ recordRescan() {
2109
+ const queue = observationQueue(this.key);
2110
+ queue.rescan = true;
2111
+ const revision = ++queue.revision;
2112
+ queue.revisions.set(revision, null);
2113
+ return revision;
1334
2114
  }
1335
2115
  /** Reset analysis state for a system reload: write guards go back to null
1336
2116
  * and reseed from the disk envelopes on the next publication. */
1337
2117
  resetForHmr() {
1338
- resetAnalysisStartedPromise();
2118
+ resetAnalysisStartedPromise(this.key);
1339
2119
  this.artifactRecords = {
1340
2120
  manifest: null,
1341
2121
  inputs: null,
@@ -1346,16 +2126,16 @@ var ExtractionSession = class {
1346
2126
  /** The (sourceKey, observed hash) pairs of a watch batch — the status
1347
2127
  * file's pending set; loaders wait only on observed inputs. */
1348
2128
  pendingFromBatch(changes) {
1349
- const { excludeMatcher, extensionsSet } = this.resolveScanConfig();
2129
+ const { excludeMatcher, extensionsSet } = this.scanConfig;
1350
2130
  const pending = [];
1351
- for (const path$4 of changes.modifiedFiles ?? []) {
1352
- const classified = this.classifyWatchPath(path$4, {
2131
+ for (const path$29 of changes.modifiedFiles ?? []) {
2132
+ const classified = this.classifyWatchPath(path$29, {
1353
2133
  excludeMatcher,
1354
2134
  extensionsSet
1355
2135
  });
1356
2136
  if (!classified) continue;
1357
2137
  try {
1358
- pending.push([classified.key, require_pipeline.contentHash((0, fs.readFileSync)(path$4, "utf-8"))]);
2138
+ pending.push([classified.key, require_pipeline.contentHash(changes.sources?.get(path$29) ?? (0, fs.readFileSync)(path$29, "utf-8"))]);
1359
2139
  } catch {}
1360
2140
  }
1361
2141
  return pending;
@@ -1367,28 +2147,28 @@ var ExtractionSession = class {
1367
2147
  if (!identity) return changes;
1368
2148
  if (!changes.modifiedFiles && !changes.removedFiles) return changes;
1369
2149
  if (this.externalWatchRoots.length === 0) return changes;
1370
- const { extensionsSet } = this.resolveScanConfig();
2150
+ const { extensionsSet } = this.scanConfig;
1371
2151
  const dirtyRoots = /* @__PURE__ */ new Set();
1372
2152
  const modified = /* @__PURE__ */ new Set();
1373
2153
  const removed = /* @__PURE__ */ new Set();
1374
- const rootHitFor = (path$5) => {
1375
- const containing = identity.containingExternalRoot(path$5);
2154
+ const rootHitFor = (path$30) => {
2155
+ const containing = identity.containingExternalRoot(path$30);
1376
2156
  if (!containing) return null;
1377
2157
  try {
1378
- return (0, fs.statSync)(path$5).isDirectory() ? containing : null;
2158
+ return (0, fs.statSync)(path$30).isDirectory() ? containing : null;
1379
2159
  } catch {
1380
- return identity.resolveDeletedSourceId(path$5) ? null : containing;
2160
+ return identity.resolveDeletedSourceId(path$30) ? null : containing;
1381
2161
  }
1382
2162
  };
1383
- for (const path$6 of changes.modifiedFiles ?? []) {
1384
- const root = rootHitFor(path$6);
2163
+ for (const path$31 of changes.modifiedFiles ?? []) {
2164
+ const root = rootHitFor(path$31);
1385
2165
  if (root) dirtyRoots.add(root);
1386
- else modified.add(path$6);
2166
+ else modified.add(path$31);
1387
2167
  }
1388
- for (const path$7 of changes.removedFiles ?? []) {
1389
- const root = rootHitFor(path$7);
2168
+ for (const path$32 of changes.removedFiles ?? []) {
2169
+ const root = rootHitFor(path$32);
1390
2170
  if (root) dirtyRoots.add(root);
1391
- else removed.add(path$7);
2171
+ else removed.add(path$32);
1392
2172
  }
1393
2173
  for (const root of dirtyRoots) {
1394
2174
  const previous = this.externalInventory.get(root) ?? /* @__PURE__ */ new Map();
@@ -1405,13 +2185,14 @@ var ExtractionSession = class {
1405
2185
  } catch {
1406
2186
  continue;
1407
2187
  }
1408
- if (previous.get(resolved.sourceKey)?.hash !== rawHash) modified.add(abs);
2188
+ if (previous.get(resolved.sourceKey)?.hash !== rawHash || this.ingestionFailureDiagnostics.some((diagnostic) => diagnostic.file === resolved.sourceKey)) modified.add(abs);
1409
2189
  }
1410
2190
  for (const [key, entry] of previous) if (!seen.has(key)) removed.add(entry.abs);
1411
2191
  }
1412
2192
  return {
1413
2193
  modifiedFiles: modified,
1414
- removedFiles: removed
2194
+ removedFiles: removed,
2195
+ sources: changes.sources
1415
2196
  };
1416
2197
  }
1417
2198
  /** Route one watcher path: a project-root member (consumer excludes apply)
@@ -1478,33 +2259,40 @@ var ExtractionSession = class {
1478
2259
  packageMap,
1479
2260
  system,
1480
2261
  emitter: {
1481
- runtimeImport: "@animus-ui/system/runtime",
1482
- cssModuleId: ANIMUS_CSS_MODULE_ID,
2262
+ runtimeImport: this.runtimeImport,
2263
+ cssModuleId: this.cssModuleId,
1483
2264
  systemPropsModuleId: this.systemPropsModuleId ?? systemPropsPath(this.sessionDir)
1484
2265
  },
1485
2266
  pathAliasesJson: this.pathAliasesJson,
1486
2267
  staticCssJson: this.staticCssJson,
1487
2268
  externalDirs: this.externalPackageDirs.map((dir) => (0, path.relative)(this.rootDir, dir)),
2269
+ rootDir: this.rootDir,
2270
+ assets: this.assetClassification,
2271
+ adapted: this.ingestionAdapted,
2272
+ loadExpander: this.loadExpander,
1488
2273
  devMode: this.engineDevMode(devMode),
1489
2274
  analysisContext: {
1490
2275
  skippedSources: [...this.skippedSources, ...this.ingestionFailureDiagnostics.map((d) => d.file)].map((file) => (0, path.isAbsolute)(file) ? (0, path.relative)(this.rootDir, file) : file),
1491
2276
  unbundledComputedImports: this.host.unbundledComputedImports,
2277
+ openings: [...this.ingestionOpenings],
1492
2278
  packageDirs: this.externalPackageDirs.map((dir) => (0, path.relative)(this.rootDir, dir)),
1493
2279
  linkedDirs: this.externalLinkedDirs.map((dir) => (0, path.relative)(this.rootDir, dir)),
1494
- outputDirs: this.externalOutputDirs.map((dir) => (0, path.relative)(this.rootDir, dir))
2280
+ outputDirs: this.externalOutputDirs.map((dir) => (0, path.relative)(this.rootDir, dir)),
2281
+ extensions: this.extensionPolicy
1495
2282
  }
1496
2283
  };
1497
2284
  this.writeAnalysisStatus("analyzing", pending);
1498
- const result = require_pipeline.runProjectAnalysis(engineApi, {
2285
+ const result = require_pipeline.runProjectAnalysis(() => this.engine(), {
1499
2286
  ...analysisOptions,
1500
2287
  warn: (message) => this.warn(message),
1501
2288
  info: (message) => this.log(message),
1502
2289
  strict: this.options.strict,
1503
2290
  diagnostics: this.options.diagnostics,
1504
2291
  reportErrors: this.reportErrors(),
1505
- extraDiagnostics: this.ingestionFailureDiagnostics,
2292
+ extraDiagnostics: [...this.ingestionFailureDiagnostics, ...await this.compareAliasesWithHost(fileEntries)],
1506
2293
  kitDescriptors: this.kitDescriptors
1507
2294
  });
2295
+ if (result.manifest) require_pipeline.assertRuntimeImportSuppliesTerminals(this.runtimeImport, result.manifest);
1508
2296
  require_pipeline.enforceExternalTokenContracts({
1509
2297
  diagnostics: result.manifest?.diagnostics,
1510
2298
  fileOwners: this.externalFileOwners,
@@ -1539,12 +2327,12 @@ var ExtractionSession = class {
1539
2327
  declaration,
1540
2328
  variables,
1541
2329
  require_pipeline.postProcessCss(body, {
1542
- minify: this.options.minify ?? require_pipeline.resolveMode(this.options.mode, () => process.env.NODE_ENV === "production" ? "production" : "development").mode === "production",
2330
+ minify: this.options.minify ?? this.startupMode === "production",
1543
2331
  targets: this.lcssTargets,
1544
2332
  warnFn: (msg) => this.warn(msg)
1545
2333
  })
1546
2334
  ].filter(Boolean).join("\n");
1547
- require_pipeline.reportSurvivingAssetPlaceholders(fullCss, {
2335
+ if (!this.hostSubstitutesAssets) require_pipeline.reportSurvivingAssetPlaceholders(fullCss, {
1548
2336
  strict: this.options.strict,
1549
2337
  warn: (message) => this.warn(message),
1550
2338
  reportErrors: this.reportErrors(),
@@ -1557,7 +2345,7 @@ var ExtractionSession = class {
1557
2345
  prefix: `[${this.driverLabel}]`,
1558
2346
  surface: "generated runtime modules"
1559
2347
  });
1560
- setSharedCss(fullCss);
2348
+ setSharedCss(this.key, fullCss);
1561
2349
  const systemPropsContent = require_pipeline.buildSystemPropsModule({
1562
2350
  systemPropMapJson: JSON.stringify(manifest?.system_prop_map ?? {}),
1563
2351
  groupRegistryJson: system.groupRegistryJson,
@@ -1565,9 +2353,20 @@ var ExtractionSession = class {
1565
2353
  admittedTransforms: manifest?.admitted_transforms ?? {},
1566
2354
  typedSystemProps: manifest?.typed_system_props ?? []
1567
2355
  });
1568
- setSharedSystemProps(systemPropsContent);
1569
- setManifestJson(result.manifestJson);
1570
- setAnalyzedHashes(new Map(fileEntries.map((entry) => [entry.path, entry.hash])));
2356
+ setSharedSystemProps(this.key, systemPropsContent);
2357
+ setManifestJson(this.key, result.manifestJson);
2358
+ if (manifest) this.stagedPublication = {
2359
+ manifest,
2360
+ manifestJson: result.manifestJson,
2361
+ analysisGlobalCss: sheets.globalCss,
2362
+ analysisComponentCss: sheets.componentCss,
2363
+ styles: fullCss,
2364
+ systemPropsModule: systemPropsContent,
2365
+ system,
2366
+ packageMap
2367
+ };
2368
+ setAnalyzedHashes(this.key, new Map(fileEntries.map((entry) => [entry.path, entry.hash])));
2369
+ if (this.packageReadRecord !== null) setSharedPackageReadRecord(this.key, this.packageReadRecord);
1571
2370
  this.writeAnalysisStatus("committing", pending);
1572
2371
  const epoch = require_pipeline.hashReplacementPlans(require_pipeline.snapshotFilePlans(manifest), systemPropsContent);
1573
2372
  if (this.lastCommit === null) this.lastCommit = this.seedCommitFromDisk();
@@ -1600,6 +2399,86 @@ var ExtractionSession = class {
1600
2399
  }
1601
2400
  /** Write the session's analysis-status artifact. Terminal states
1602
2401
  * (idle/failed) close the attempt. */
2402
+ /** Where the host resolves a path alias the analysis reads to another
2403
+ * file (spec §2.2 A7, report only), and how the two were compared. */
2404
+ async compareAliasesWithHost(fileEntries) {
2405
+ const record = require_pipeline.packageReadRecord({
2406
+ rootDir: this.rootDir,
2407
+ roots: this.packageInstances,
2408
+ redirected: new Set(this.externalSourceEntries.keys()),
2409
+ analysed: fileEntries.map((entry) => entry.path),
2410
+ rendersBuilder: (file) => this.rendersBuilder(file)
2411
+ });
2412
+ this.packageReadRecord = record;
2413
+ const { moduleSpecifiers } = this.engine();
2414
+ if (this.host.noBundler) return [];
2415
+ if (this.hostResolve === null || moduleSpecifiers === void 0) {
2416
+ this.log("path aliases: no host resolver, comparison unavailable");
2417
+ return [];
2418
+ }
2419
+ try {
2420
+ const { findings, unavailable } = await require_pipeline.compareAliases({
2421
+ rootDir: this.rootDir,
2422
+ fileEntries,
2423
+ pathAliasesJson: this.pathAliasesJson,
2424
+ order: this.extensionPolicy,
2425
+ readings: {
2426
+ isAsset: this.assetClassification.isAsset,
2427
+ adapted: this.ingestionAdapted
2428
+ },
2429
+ moduleSpecifiers,
2430
+ resolve: this.hostResolve,
2431
+ cache: this.aliasCache,
2432
+ packages: record
2433
+ });
2434
+ for (const finding of findings) record.reported.add(`${finding.file}\0${finding.component}`);
2435
+ if (unavailable > 0) this.log(`path aliases: ${unavailable} aliased import(s) the host's resolver failed on are unavailable, not agreeing`);
2436
+ return findings;
2437
+ } catch (error) {
2438
+ this.log(`path aliases: comparison unavailable (${String(error)})`);
2439
+ return [];
2440
+ }
2441
+ }
2442
+ /** Whether the file at a real path declares builder chains, by the
2443
+ * engine's own parse: a module that does, served unread, renders with
2444
+ * empty classes. One the engine cannot read counts as one that does. */
2445
+ rendersBuilder(file) {
2446
+ if (!require_pipeline.isEngineTransformExtension(file)) return false;
2447
+ const { extractFacts } = this.engine();
2448
+ if (extractFacts === void 0) return true;
2449
+ const path$33 = (0, path.relative)(this.rootDir, file);
2450
+ try {
2451
+ const facts = require_pipeline.parseInternalWire(extractFacts(JSON.stringify([{
2452
+ path: path$33,
2453
+ source: (0, fs.readFileSync)(file, "utf-8")
2454
+ }])), "extractFacts").files[path$33];
2455
+ return facts === void 0 || facts.parsePanicked || facts.chains.length > 0;
2456
+ } catch {
2457
+ return true;
2458
+ }
2459
+ }
2460
+ /**
2461
+ * An import the host resolved while bundling (spec §2.2 A4), reported as
2462
+ * the comparison reports it: webpack resolves each module's requests with
2463
+ * the resolve options of the rules that module matches, which its
2464
+ * resolver, asked outside a compilation, does not apply. `importer` and
2465
+ * `resolved` are absolute. A finding the comparison already reported is
2466
+ * not repeated; an error-level one throws in a build.
2467
+ */
2468
+ reportHostResolution(importer, request, resolved) {
2469
+ const record = getSharedPackageReadRecord(this.key);
2470
+ if (record === null) return;
2471
+ const finding = require_pipeline.packageInstanceFinding(record, (0, path.relative)(record.rootDir, importer).split(path.sep).join(path.posix.sep), request, resolved);
2472
+ if (finding === null) return;
2473
+ const key = `${finding.file}\0${finding.component}`;
2474
+ if (record.reported.has(key)) return;
2475
+ record.reported.add(key);
2476
+ require_pipeline.surfaceManifestDiagnostics({ diagnostics: [finding] }, (message) => this.warn(message), {
2477
+ levels: this.options.diagnostics,
2478
+ strict: this.options.strict,
2479
+ reportErrors: this.reportErrors()
2480
+ });
2481
+ }
1603
2482
  writeAnalysisStatus(state, pending, diagnostic) {
1604
2483
  const status = {
1605
2484
  schema: 2,
@@ -1709,6 +2588,11 @@ var ExtractionSession = class {
1709
2588
  /** Maintain the session's epoch witness, rewritten ONLY when the value
1710
2589
  * changes, then expose it through the singleton. */
1711
2590
  publishReplacementEpoch(epoch) {
2591
+ if (!this.persistArtifacts) {
2592
+ this.lastEpochValue = epoch;
2593
+ setReplacementEpoch(this.key, epoch);
2594
+ return;
2595
+ }
1712
2596
  const onDisk = this.diskEpochValue();
1713
2597
  if (this.lastEpochValue === null) this.lastEpochValue = onDisk;
1714
2598
  if (epoch !== onDisk) this.writeSessionArtifact(REPLACEMENT_EPOCH_ARTIFACT, JSON.stringify({
@@ -1720,7 +2604,7 @@ var ExtractionSession = class {
1720
2604
  this.lastEpochValue = epoch;
1721
2605
  this.reconcileSiblingEpochs(epoch);
1722
2606
  }
1723
- setReplacementEpoch(epoch);
2607
+ setReplacementEpoch(this.key, epoch);
1724
2608
  }
1725
2609
  /** sessions-root listing memo, keyed by the root dir's mtime — a new or
1726
2610
  * pruned sibling DIRECTORY moves it; agreeing siblings stay listed. */
@@ -1765,10 +2649,14 @@ var ExtractionSession = class {
1765
2649
  }
1766
2650
  }
1767
2651
  /** The pid of `siblingDir`'s live owner, or null when nothing proves one.
1768
- * Our own pid is never one — publication is process-exclusive. A missing,
1769
- * undecodable or unreadable claim all mean no owner: the opposite default
1770
- * from the CLI lock check on purpose, so do not unify the two. */
2652
+ * A tree whose build is registered in this process is ours and live,
2653
+ * claim or no claim: a publisher's close() releases the claim while the
2654
+ * build's other participants still serve from it. Otherwise a missing,
2655
+ * undecodable or unreadable claim, or one naming our own pid, means no
2656
+ * owner: the opposite default from the CLI lock check on purpose, so do
2657
+ * not unify the two. */
1771
2658
  liveSiblingOwnerPid(siblingDir) {
2659
+ if (findSession(sessionKey((0, path.basename)(siblingDir), this.rootDir)) !== void 0) return process.pid;
1772
2660
  let claim;
1773
2661
  try {
1774
2662
  claim = readCliLockRecord(siblingDir);
@@ -1799,7 +2687,7 @@ var ExtractionSession = class {
1799
2687
  /** Delete legacy flat artifacts and prune sibling session directories past
1800
2688
  * the retention window — never this session's own dir. */
1801
2689
  runSessionStartHygiene() {
1802
- if (this.sessionStartHygieneDone) return;
2690
+ if (this.sessionStartHygieneDone || !this.persistArtifacts) return;
1803
2691
  this.sessionStartHygieneDone = true;
1804
2692
  const animusDir = (0, path.join)(this.rootDir, ANIMUS_ARTIFACT_DIR);
1805
2693
  const isCliPublishedSet = this.cliWriterHoldsLock(animusDir) || verifyCommitRecord(animusDir).length === 0;
@@ -1840,13 +2728,29 @@ var ExtractionSession = class {
1840
2728
  /** Write one artifact write-then-rename, so a cross-process reader never
1841
2729
  * sees a torn file; the tmp name carries the pid to keep writers apart. */
1842
2730
  writeSessionArtifact(name, content) {
1843
- const dir = this.sessionDir;
1844
- (0, fs.mkdirSync)(dir, { recursive: true });
1845
- const tmpPath = (0, path.join)(dir, `.${name}.${process.pid}.tmp`);
1846
- (0, fs.writeFileSync)(tmpPath, content);
1847
- (0, fs.renameSync)(tmpPath, (0, path.join)(dir, name));
2731
+ if (this.persistArtifacts) {
2732
+ const dir = this.sessionDir;
2733
+ (0, fs.mkdirSync)(dir, { recursive: true });
2734
+ const tmpPath = (0, path.join)(dir, `.${name}.${process.pid}.tmp`);
2735
+ (0, fs.writeFileSync)(tmpPath, content);
2736
+ (0, fs.renameSync)(tmpPath, (0, path.join)(dir, name));
2737
+ }
1848
2738
  this.onArtifactWrite?.(name, content);
1849
2739
  }
2740
+ /** How an asset's bytes are fingerprinted, of a file or of bytes read:
2741
+ * the engine's fast fingerprint where it has both, the content hash
2742
+ * otherwise, so the two always agree. */
2743
+ assetFingerprints() {
2744
+ const { fingerprintFile, fingerprintBytes } = this.engine();
2745
+ if (fingerprintFile && fingerprintBytes) return {
2746
+ ofFile: (path$34) => fingerprintFile(path$34) ?? require_pipeline.contentHash((0, fs.readFileSync)(path$34)),
2747
+ ofBytes: fingerprintBytes
2748
+ };
2749
+ return {
2750
+ ofFile: (path$35) => require_pipeline.contentHash((0, fs.readFileSync)(path$35)),
2751
+ ofBytes: require_pipeline.contentHash
2752
+ };
2753
+ }
1850
2754
  /** Copy each asset() specifier's bytes, from every sheet, into `assets/`
1851
2755
  * under a content-hashed name and substitute a url relative to
1852
2756
  * styles.css. */
@@ -1856,12 +2760,12 @@ var ExtractionSession = class {
1856
2760
  const expected = /* @__PURE__ */ new Set();
1857
2761
  this.assetDependencyPaths.clear();
1858
2762
  this.assetDependencyKeys.clear();
2763
+ if (this.hostSubstitutesAssets) return sheets;
1859
2764
  const urlBySpecifier = /* @__PURE__ */ new Map();
1860
2765
  for (const specifier of specifiers) {
1861
2766
  const cached = this.assetCopyCache.get(specifier);
1862
2767
  if (cached && (0, fs.existsSync)((0, path.join)(assetsDir, cached.fileName))) try {
1863
- const current = (0, fs.statSync)(cached.sourcePath);
1864
- if (current.mtimeMs === cached.mtimeMs && current.size === cached.size) {
2768
+ if (this.assetFingerprints().ofFile(cached.sourcePath) === cached.fingerprint) {
1865
2769
  this.trackAssetDependency(cached.sourcePath);
1866
2770
  expected.add(cached.fileName);
1867
2771
  urlBySpecifier.set(specifier, cached.url);
@@ -1870,37 +2774,32 @@ var ExtractionSession = class {
1870
2774
  } catch {}
1871
2775
  const resolvedPath = this.resolveAssetSpecifier(specifier);
1872
2776
  if (!resolvedPath) {
1873
- const message = `unresolvable asset() specifier: ${specifier}`;
1874
- if (this.options.strict) this.strictFailure(`[${this.driverLabel}] ${message}`);
1875
- else this.warn(message);
2777
+ this.reportUnresolvableAsset(specifier);
1876
2778
  urlBySpecifier.set(specifier, specifier);
1877
2779
  continue;
1878
2780
  }
1879
- const bytes = (0, fs.readFileSync)(resolvedPath);
1880
- const sourceStat = (0, fs.statSync)(resolvedPath);
2781
+ const { fileName, bytes } = require_pipeline.writeAssetCopy(resolvedPath, assetsDir);
1881
2782
  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
2783
  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
2784
  const url = `./${SESSION_ASSETS_DIR}/${fileName}`;
1889
2785
  urlBySpecifier.set(specifier, url);
1890
2786
  this.assetCopyCache.set(specifier, {
1891
2787
  sourcePath: resolvedPath,
1892
- mtimeMs: sourceStat.mtimeMs,
1893
- size: sourceStat.size,
2788
+ fingerprint: this.assetFingerprints().ofBytes(bytes),
1894
2789
  fileName,
1895
2790
  url
1896
2791
  });
1897
2792
  }
1898
- if (pruneSuperseded) pruneStaleAssets(assetsDir, expected);
2793
+ if (pruneSuperseded) require_pipeline.pruneStaleAssets(assetsDir, expected);
1899
2794
  return require_pipeline.substituteSheetAssets(sheets, urlBySpecifier);
1900
2795
  }
1901
- trackAssetDependency(path$8) {
1902
- this.assetDependencyPaths.add(path$8);
1903
- for (const key of require_pipeline.toWatchKeys(path$8)) this.assetDependencyKeys.add(key);
2796
+ reportUnresolvableAsset(specifier) {
2797
+ const message = `unresolvable asset() specifier: ${specifier}`;
2798
+ this.strictOrWarn(message, `[${this.driverLabel}] ${message}`);
2799
+ }
2800
+ trackAssetDependency(path$36) {
2801
+ this.assetDependencyPaths.add(path$36);
2802
+ for (const key of require_pipeline.toWatchKeys(path$36)) this.assetDependencyKeys.add(key);
1904
2803
  }
1905
2804
  /** Resolve an asset specifier through the shared resolver, with one
1906
2805
  * last resort: a discovered source entry's package root. */
@@ -1919,23 +2818,6 @@ var ExtractionSession = class {
1919
2818
  return null;
1920
2819
  }
1921
2820
  };
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
2821
  //#endregion
1940
2822
  //#region session/turbopack-orchestrator.ts
1941
2823
  async function runSessionPipeline(session) {
@@ -1943,6 +2825,7 @@ async function runSessionPipeline(session) {
1943
2825
  session.persistAnalysisInputs = true;
1944
2826
  await session.runFullPipeline();
1945
2827
  }
2828
+ /** Each build's watched root: another build of the root watches it too. */
1946
2829
  const activeWatcherRoots = /* @__PURE__ */ new Set();
1947
2830
  const IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
1948
2831
  ANIMUS_ARTIFACT_DIR,
@@ -1969,17 +2852,18 @@ function isIntrinsicString(value) {
1969
2852
  }
1970
2853
  function startTurbopackWatcher(session, rootDir, options = {}) {
1971
2854
  const { debounceMs = 75, watchFn = fs.watch, holdEvents = false } = options;
1972
- if (activeWatcherRoots.has(rootDir)) return { kind: "already-watched" };
1973
- activeWatcherRoots.add(rootDir);
2855
+ const watched = JSON.stringify([session.sessionId, rootDir]);
2856
+ if (activeWatcherRoots.has(watched)) return { kind: "already-watched" };
2857
+ activeWatcherRoots.add(watched);
1974
2858
  session.debounceCeilingMs = debounceMs;
1975
2859
  const pendingPaths = /* @__PURE__ */ new Set();
1976
2860
  const watchers = /* @__PURE__ */ new Map();
1977
- let timer = null;
1978
2861
  let updateChain = Promise.resolve();
1979
2862
  let closed = false;
1980
2863
  let died = false;
1981
2864
  let holdingEvents = holdEvents;
1982
2865
  const externalWatchers = /* @__PURE__ */ new Map();
2866
+ const dependencyWatchers = /* @__PURE__ */ new Map();
1983
2867
  let pendingOpened = /* @__PURE__ */ new Map();
1984
2868
  let capturedDuringSnapshot = [];
1985
2869
  const externalDiagnosticKey = (root) => `external-watch:${root}`;
@@ -1998,24 +2882,28 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
1998
2882
  session.stickyDiagnostics?.set(externalDiagnosticKey(root), message);
1999
2883
  console.warn(`[animus-extract] ${message}`);
2000
2884
  };
2885
+ let cyclesInFlight = 0;
2001
2886
  const flush = () => {
2002
- timer = null;
2003
2887
  const modifiedFiles = /* @__PURE__ */ new Set();
2004
2888
  const removedFiles = /* @__PURE__ */ new Set();
2005
2889
  for (const path$1 of pendingPaths) ((0, fs.existsSync)(path$1) ? modifiedFiles : removedFiles).add(path$1);
2006
2890
  pendingPaths.clear();
2007
- updateChain = updateChain.then(() => {
2008
- if (closed) return;
2009
- return session.handleWatchUpdate({
2010
- modifiedFiles,
2011
- removedFiles
2012
- }).catch((err) => {
2013
- console.warn(`[animus-extract] watch update failed: ${String(err)}`);
2014
- }).then(() => {
2015
- if (pendingOpened.size > 0 || capturedDuringSnapshot.length > 0) rollbackPendingExternal();
2016
- });
2891
+ if (closed) return;
2892
+ cyclesInFlight++;
2893
+ const cycle = session.handleWatchUpdate({
2894
+ modifiedFiles,
2895
+ removedFiles
2896
+ }).catch((err) => {
2897
+ console.warn(`[animus-extract] watch update failed: ${String(err)}`);
2898
+ }).finally(() => {
2899
+ cyclesInFlight--;
2900
+ if (cyclesInFlight > 0) return;
2901
+ if (pendingOpened.size > 0 || capturedDuringSnapshot.length > 0) rollbackPendingExternal();
2902
+ syncDependencyWatchers();
2017
2903
  });
2904
+ updateChain = updateChain.then(() => cycle);
2018
2905
  };
2906
+ const coalescing = new CoalescingWindow(flush, debounceMs);
2019
2907
  const rollbackPendingExternal = () => {
2020
2908
  for (const watcher of pendingOpened.values()) watcher.close();
2021
2909
  pendingOpened = /* @__PURE__ */ new Map();
@@ -2024,16 +2912,17 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
2024
2912
  const closeAll = () => {
2025
2913
  if (closed) return;
2026
2914
  closed = true;
2027
- if (timer) clearTimeout(timer);
2028
- timer = null;
2915
+ coalescing.cancel();
2029
2916
  for (const watcher of watchers.values()) watcher.close();
2030
2917
  watchers.clear();
2918
+ for (const { watcher } of dependencyWatchers.values()) watcher.close();
2919
+ dependencyWatchers.clear();
2031
2920
  for (const watcher of externalWatchers.values()) watcher.close();
2032
2921
  externalWatchers.clear();
2033
2922
  rollbackPendingExternal();
2034
2923
  session.onExternalRootResolved = null;
2035
2924
  session.onExternalRootsCommitted = null;
2036
- activeWatcherRoots.delete(rootDir);
2925
+ activeWatcherRoots.delete(watched);
2037
2926
  };
2038
2927
  const onWatcherError = (err) => {
2039
2928
  died = true;
@@ -2049,10 +2938,7 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
2049
2938
  } catch (err) {
2050
2939
  console.warn(`[animus-extract] Turbopack debounce status update failed: ${String(err)}`);
2051
2940
  }
2052
- if (!timer) {
2053
- timer = setTimeout(flush, debounceMs);
2054
- timer.unref?.();
2055
- }
2941
+ coalescing.note();
2056
2942
  };
2057
2943
  const onEvent = (baseDir, filename) => {
2058
2944
  if (closed || !filename) return;
@@ -2096,6 +2982,59 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
2096
2982
  degradeExternalRoot(root, err);
2097
2983
  }
2098
2984
  };
2985
+ /** Whether the root or an external root watcher already reports `path`. */
2986
+ const watchedByRoot = (path$2) => {
2987
+ if (require_pipeline.isPathWithinRoot(rootDir, path$2) && !(0, path.relative)(rootDir, path$2).split(/[\\/]/).some((segment) => IGNORED_SEGMENTS.has(segment))) return true;
2988
+ return [...externalWatchers.keys()].some((root) => require_pipeline.isPathWithinRoot(root, path$2));
2989
+ };
2990
+ /** Watches the system, asset and configuration dependencies no root
2991
+ * watcher reports, by their directories, for those files only. */
2992
+ const syncDependencyWatchers = () => {
2993
+ if (closed) return;
2994
+ const wanted = /* @__PURE__ */ new Map();
2995
+ for (const path$3 of [
2996
+ ...session.systemDependencyPaths,
2997
+ ...session.assetDependencyPaths,
2998
+ ...session.configInputPaths
2999
+ ]) {
3000
+ if (watchedByRoot(path$3)) continue;
3001
+ const files = wanted.get((0, path.dirname)(path$3)) ?? /* @__PURE__ */ new Set();
3002
+ files.add(path$3);
3003
+ wanted.set((0, path.dirname)(path$3), files);
3004
+ }
3005
+ for (const [dir, { watcher }] of [...dependencyWatchers]) {
3006
+ if (wanted.has(dir)) continue;
3007
+ watcher.close();
3008
+ dependencyWatchers.delete(dir);
3009
+ }
3010
+ for (const [dir, files] of wanted) {
3011
+ const existing = dependencyWatchers.get(dir);
3012
+ if (existing) {
3013
+ existing.files = files;
3014
+ continue;
3015
+ }
3016
+ try {
3017
+ const watcher = watchFn(dir, { recursive: false }, (_event, name) => {
3018
+ if (closed || !name) return;
3019
+ const abs = (0, path.join)(dir, name.toString());
3020
+ if (dependencyWatchers.get(dir)?.files.has(abs)) enqueuePath(abs);
3021
+ });
3022
+ watcher.on("error", (err) => {
3023
+ watcher.close();
3024
+ dependencyWatchers.delete(dir);
3025
+ console.warn(`[animus-extract] dependency watch on ${dir} failed (${String(err)}); its changes require a dev-server restart`);
3026
+ });
3027
+ watcher.unref?.();
3028
+ dependencyWatchers.set(dir, {
3029
+ watcher,
3030
+ files
3031
+ });
3032
+ } catch (err) {
3033
+ if (errnoCode(err) === "ENOENT") continue;
3034
+ console.warn(`[animus-extract] dependency watch on ${dir} unavailable (${String(err)}); its changes require a dev-server restart`);
3035
+ }
3036
+ }
3037
+ };
2099
3038
  const addWatcher = (dir, recursive) => {
2100
3039
  const watcher = watchFn(dir, { recursive }, (_event, filename) => onEvent(dir, filename));
2101
3040
  watcher.on("error", onWatcherError);
@@ -2114,6 +3053,7 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
2114
3053
  return { kind: "unavailable" };
2115
3054
  }
2116
3055
  for (const root of session.externalWatchRoots ?? []) openExternalWatcher(root, externalWatchers);
3056
+ syncDependencyWatchers();
2117
3057
  session.onExternalRootResolved = (root) => {
2118
3058
  if (closed) return;
2119
3059
  if (externalWatchers.has(root) || pendingOpened.has(root)) return;
@@ -2138,7 +3078,8 @@ function startTurbopackWatcher(session, rootDir, options = {}) {
2138
3078
  onDied: null,
2139
3079
  deliverHeldEvents: () => {
2140
3080
  holdingEvents = false;
2141
- if (pendingPaths.size > 0) flush();
3081
+ syncDependencyWatchers();
3082
+ if (pendingPaths.size > 0) coalescing.flush();
2142
3083
  return updateChain;
2143
3084
  },
2144
3085
  settle: () => updateChain
@@ -2155,19 +3096,27 @@ exports.CLI_COMMIT_ARTIFACT = CLI_COMMIT_ARTIFACT;
2155
3096
  exports.CLI_LOCK_ARTIFACT = CLI_LOCK_ARTIFACT;
2156
3097
  exports.CLI_LOCK_HEARTBEAT_INTERVAL_MS = CLI_LOCK_HEARTBEAT_INTERVAL_MS;
2157
3098
  exports.CLI_LOCK_STALE_AFTER_MS = CLI_LOCK_STALE_AFTER_MS;
3099
+ exports.COALESCING_WINDOW_MS = COALESCING_WINDOW_MS;
3100
+ exports.CoalescingWindow = CoalescingWindow;
3101
+ exports.EnvironmentFailure = EnvironmentFailure;
2158
3102
  exports.ExtractionSession = ExtractionSession;
2159
3103
  exports.MANIFEST_ARTIFACT = MANIFEST_ARTIFACT;
2160
3104
  exports.SESSION_ASSETS_DIR = SESSION_ASSETS_DIR;
2161
3105
  exports.STYLES_ARTIFACT = STYLES_ARTIFACT;
2162
3106
  exports.SYSTEM_PROPS_ARTIFACT = SYSTEM_PROPS_ARTIFACT;
3107
+ exports.SessionConfigurationConflict = SessionConfigurationConflict;
2163
3108
  exports.TURBOPACK_SYSTEM_PROPS_ID = TURBOPACK_SYSTEM_PROPS_ID;
2164
3109
  exports.analysisCommitPath = analysisCommitPath;
2165
3110
  exports.analysisInputsPath = analysisInputsPath;
2166
3111
  exports.analysisStatusPath = analysisStatusPath;
2167
3112
  exports.checkLockLiveness = checkLockLiveness;
2168
3113
  exports.collectSessionAssets = collectSessionAssets;
3114
+ exports.createBuildId = createBuildId;
2169
3115
  exports.decodeCommitRecord = decodeCommitRecord;
2170
3116
  exports.engineApi = engineApi;
3117
+ exports.engineFactory = engineFactory;
3118
+ exports.engineHandle = engineHandle;
3119
+ exports.findSession = findSession;
2171
3120
  exports.getAnalysisStartedPromise = getAnalysisStartedPromise;
2172
3121
  exports.getAnalyzedHashes = getAnalyzedHashes;
2173
3122
  exports.getManifestJson = getManifestJson;
@@ -2180,17 +3129,20 @@ exports.getSharedExternalSideEffects = getSharedExternalSideEffects;
2180
3129
  exports.getSharedSystemProps = getSharedSystemProps;
2181
3130
  exports.holdDirectoryClaim = holdDirectoryClaim;
2182
3131
  exports.isLockHolderAlive = isLockHolderAlive;
3132
+ exports.joinSession = joinSession;
2183
3133
  exports.lockRecordBytes = lockRecordBytes;
2184
3134
  exports.manifestPath = manifestPath;
2185
- exports.pruneStaleAssets = pruneStaleAssets;
3135
+ exports.pruneStaleAssets = require_pipeline.pruneStaleAssets;
2186
3136
  exports.readCliLockRecord = readCliLockRecord;
2187
3137
  exports.readJsonEnvelope = readJsonEnvelope;
3138
+ exports.releaseEngineHandle = releaseEngineHandle;
2188
3139
  exports.replacementEpochPath = replacementEpochPath;
3140
+ exports.requireEngine = requireEngine;
2189
3141
  exports.resetAnalysisStartedPromise = resetAnalysisStartedPromise;
2190
3142
  exports.runSessionPipeline = runSessionPipeline;
2191
3143
  exports.sessionArtifactDir = sessionArtifactDir;
3144
+ exports.sessionKey = sessionKey;
2192
3145
  exports.setAnalysisStartedPromise = setAnalysisStartedPromise;
2193
- exports.setSharedEngine = setSharedEngine;
2194
3146
  exports.startTurbopackWatcher = startTurbopackWatcher;
2195
3147
  exports.stylesPath = stylesPath;
2196
3148
  exports.systemPropsPath = systemPropsPath;