@animus-ui/next-plugin 0.1.9 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +88 -1742
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +78 -1731
- package/dist/loader-core-BthLSJQF.cjs +47 -0
- package/dist/loader-core-x6cOzF9v.mjs +42 -0
- package/dist/loader-core.d.ts +6 -13
- package/dist/loader-core.d.ts.map +1 -1
- package/dist/loader-path.d.ts +0 -13
- package/dist/loader-path.d.ts.map +1 -1
- package/dist/loader.cjs +10 -34
- package/dist/loader.d.ts +2 -24
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.mjs +3 -26
- package/dist/plugin.d.ts +18 -70
- package/dist/plugin.d.ts.map +1 -1
- package/dist/turbopack-config.d.ts +11 -38
- package/dist/turbopack-config.d.ts.map +1 -1
- package/dist/turbopack-loader.cjs +44 -64
- package/dist/turbopack-loader.d.ts +12 -24
- package/dist/turbopack-loader.d.ts.map +1 -1
- package/dist/turbopack-loader.mjs +40 -56
- package/dist/types.d.ts +25 -79
- package/dist/types.d.ts.map +1 -1
- package/dist/with-animus.d.ts +39 -24
- package/dist/with-animus.d.ts.map +1 -1
- package/package.json +8 -2
- package/dist/extraction-session.d.ts +0 -378
- package/dist/extraction-session.d.ts.map +0 -1
- package/dist/loader-core-C40WbC6s.cjs +0 -71
- package/dist/loader-core-D6Tm_b8F.mjs +0 -66
- package/dist/resolve-packages.d.ts +0 -8
- package/dist/resolve-packages.d.ts.map +0 -1
- package/dist/session-paths-CZoi19NW.cjs +0 -229
- package/dist/session-paths-DfYIPDcM.mjs +0 -107
- package/dist/session-paths.d.ts +0 -107
- package/dist/session-paths.d.ts.map +0 -1
- package/dist/singleton-BjSsGjsg.mjs +0 -217
- package/dist/singleton-BjZyw_8b.cjs +0 -366
- package/dist/singleton.d.ts +0 -96
- package/dist/singleton.d.ts.map +0 -1
- package/dist/timing.d.ts +0 -7
- package/dist/timing.d.ts.map +0 -1
- package/dist/turbopack-orchestrator.d.ts +0 -34
- package/dist/turbopack-orchestrator.d.ts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,1384 +1,12 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_session_paths = require("./session-paths-CZoi19NW.cjs");
|
|
3
|
-
const require_singleton = require("./singleton-BjZyw_8b.cjs");
|
|
4
|
-
require("./turbopack-loader.cjs");
|
|
5
2
|
let _animus_ui_extract_pipeline = require("@animus-ui/extract/pipeline");
|
|
3
|
+
let _animus_ui_extract_session = require("@animus-ui/extract/session");
|
|
6
4
|
let fs = require("fs");
|
|
7
5
|
let path = require("path");
|
|
8
6
|
let url = require("url");
|
|
9
|
-
//#region src/resolve-packages.ts
|
|
10
|
-
/**
|
|
11
|
-
* Resolve external DS package specifiers to rootDir-relative entry paths.
|
|
12
|
-
* Node-generic (no webpack APIs): Pass 1 walks the consumer's workspaces for
|
|
13
|
-
* matching package names; Pass 2 falls back to `require.resolve`. Specifiers
|
|
14
|
-
* that resolve nowhere are simply omitted (spec: silent skip).
|
|
15
|
-
*/
|
|
16
|
-
function resolvePackagesByName(rootDir, names) {
|
|
17
|
-
if (names.length === 0) return {};
|
|
18
|
-
const nameSet = new Set(names);
|
|
19
|
-
const resolved = /* @__PURE__ */ new Set();
|
|
20
|
-
const packageMap = {};
|
|
21
|
-
try {
|
|
22
|
-
const rootPkg = JSON.parse((0, fs.readFileSync)((0, path.join)(rootDir, "package.json"), "utf-8"));
|
|
23
|
-
const workspaces = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces?.packages ?? [];
|
|
24
|
-
for (const ws of workspaces) {
|
|
25
|
-
const wsDir = (0, path.resolve)(rootDir, ws);
|
|
26
|
-
if (!(0, fs.existsSync)(wsDir)) continue;
|
|
27
|
-
try {
|
|
28
|
-
const pkg = JSON.parse((0, fs.readFileSync)((0, path.join)(wsDir, "package.json"), "utf-8"));
|
|
29
|
-
const name = pkg.name || "";
|
|
30
|
-
if (nameSet.has(name)) {
|
|
31
|
-
const entryPath = (0, path.resolve)(wsDir, pkg.main || pkg.module || "index.ts");
|
|
32
|
-
if ((0, fs.existsSync)(entryPath)) {
|
|
33
|
-
packageMap[name] = (0, path.relative)(rootDir, entryPath);
|
|
34
|
-
resolved.add(name);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
} catch {}
|
|
38
|
-
}
|
|
39
|
-
} catch {}
|
|
40
|
-
for (const name of nameSet) {
|
|
41
|
-
if (resolved.has(name)) continue;
|
|
42
|
-
try {
|
|
43
|
-
packageMap[name] = (0, path.relative)(rootDir, require.resolve(name, { paths: [rootDir] }));
|
|
44
|
-
} catch {}
|
|
45
|
-
}
|
|
46
|
-
return packageMap;
|
|
47
|
-
}
|
|
48
|
-
//#endregion
|
|
49
|
-
//#region src/timing.ts
|
|
50
|
-
/**
|
|
51
|
-
* Verbose build-timing waterfall: JS phases, analysis sub-phases, and the
|
|
52
|
-
* shared Rust phase table. `ANIMUS_TIMING_JSON=1` additionally dumps a
|
|
53
|
-
* machine-readable merged record.
|
|
54
|
-
*/
|
|
55
|
-
function logBuildTimings(bt, rustTiming, log, verbose) {
|
|
56
|
-
if (!verbose) return;
|
|
57
|
-
const pad = (label, width) => label + " ".repeat(Math.max(0, width - label.length));
|
|
58
|
-
for (const [key, label] of [
|
|
59
|
-
["systemLoad", "system-load"],
|
|
60
|
-
["fileDiscovery", "file-discovery"],
|
|
61
|
-
["fileRead", "file-read+hash"],
|
|
62
|
-
["packageResolve", "pkg-resolve"],
|
|
63
|
-
["analysis", "analysis"]
|
|
64
|
-
]) {
|
|
65
|
-
const ms = bt[key] ?? 0;
|
|
66
|
-
if (ms === 0 && !Object.hasOwn(bt, key)) continue;
|
|
67
|
-
log(` ${pad(label, 17)}${String(ms).padStart(5)}ms`);
|
|
68
|
-
if (key === "analysis") for (const [sk, sl] of [
|
|
69
|
-
["jsonSerialize", "json-serialize"],
|
|
70
|
-
["rustExtract", "rust-extract"],
|
|
71
|
-
["jsonParse", "json-parse"]
|
|
72
|
-
]) {
|
|
73
|
-
const sms = bt[sk] ?? 0;
|
|
74
|
-
log(` ${pad(sl, 15)}${String(sms).padStart(5)}ms`);
|
|
75
|
-
if (sk === "rustExtract" && rustTiming) for (const line of (0, _animus_ui_extract_pipeline.formatRustTimingWaterfall)(rustTiming, {
|
|
76
|
-
indent: " ",
|
|
77
|
-
labelWidth: 13
|
|
78
|
-
})) log(line);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
log(` total ${String(bt.total).padStart(5)}ms`);
|
|
82
|
-
if (process.env.ANIMUS_TIMING_JSON === "1") {
|
|
83
|
-
const merged = {};
|
|
84
|
-
for (const [k, v] of Object.entries(bt)) merged[`buildStart.${k}`] = v;
|
|
85
|
-
if (rustTiming) {
|
|
86
|
-
for (const [k, v] of Object.entries(rustTiming)) if (typeof v === "number") merged[`rust.${k}`] = v;
|
|
87
|
-
}
|
|
88
|
-
console.info(`[animus:timing] ${JSON.stringify(merged)}`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
//#endregion
|
|
92
|
-
//#region src/extraction-session.ts
|
|
93
|
-
/**
|
|
94
|
-
* Module id the Rust emitter injects for the extracted stylesheet — also the
|
|
95
|
-
* exact resolve.alias key with-animus registers for it, which the adapter's
|
|
96
|
-
* alias harvesting must skip.
|
|
97
|
-
*/
|
|
98
|
-
const ANIMUS_CSS_MODULE_ID = ".animus/styles.css";
|
|
99
|
-
/** Retention window for sibling session directories (design D2). */
|
|
100
|
-
const SESSION_DIR_MAX_AGE_MS = 1440 * 60 * 1e3;
|
|
101
|
-
/** Watchdog added to the debounce ceiling for status deadlines (D3). */
|
|
102
|
-
const STATUS_WATCHDOG_MS = 2e3;
|
|
103
|
-
/** Flat legacy `.animus/` artifacts removed at session start — unreachable
|
|
104
|
-
* by session-scoped loaders and no longer written by anyone. */
|
|
105
|
-
const LEGACY_FLAT_ARTIFACTS = [
|
|
106
|
-
require_session_paths.MANIFEST_ARTIFACT,
|
|
107
|
-
require_session_paths.ANALYSIS_INPUTS_ARTIFACT,
|
|
108
|
-
require_session_paths.STYLES_ARTIFACT,
|
|
109
|
-
require_session_paths.SYSTEM_PROPS_ARTIFACT,
|
|
110
|
-
require_session_paths.REPLACEMENT_EPOCH_ARTIFACT,
|
|
111
|
-
require_session_paths.ANALYSIS_COMMIT_ARTIFACT,
|
|
112
|
-
require_session_paths.ANALYSIS_STATUS_ARTIFACT
|
|
113
|
-
];
|
|
114
|
-
/** Default path fragments excluded from source discovery (full + watch). */
|
|
115
|
-
const DEFAULT_EXCLUDE = [
|
|
116
|
-
"node_modules",
|
|
117
|
-
"dist",
|
|
118
|
-
".test.",
|
|
119
|
-
".spec.",
|
|
120
|
-
".next",
|
|
121
|
-
".animus"
|
|
122
|
-
];
|
|
123
|
-
/**
|
|
124
|
-
* Bundler-agnostic extraction pipeline: system loading, source discovery
|
|
125
|
-
* and ingestion, external-package collection, analysis, stylesheet and
|
|
126
|
-
* system-props emission, and watch-cycle diffing.
|
|
127
|
-
*
|
|
128
|
-
* The webpack adapter (plugin.ts) owns only bundler wiring: hook
|
|
129
|
-
* registration, alias harvesting from the compiler config, in-memory asset
|
|
130
|
-
* replacement, and translating watch events into `handleWatchUpdate`
|
|
131
|
-
* change sets. Keeping this class free of webpack types is deliberate — a
|
|
132
|
-
* future Turbopack integration must drive the same session from outside
|
|
133
|
-
* the bundler (Turbopack has no compiler-hook surface), reusing everything
|
|
134
|
-
* here unchanged.
|
|
135
|
-
*
|
|
136
|
-
* Outputs are published through the package singleton (shared CSS,
|
|
137
|
-
* manifest, system props) and written to `.animus/` on disk.
|
|
138
|
-
*/
|
|
139
|
-
var ExtractionSession = class {
|
|
140
|
-
/** Set by the adapter before any pipeline run. */
|
|
141
|
-
rootDir = null;
|
|
142
|
-
/** Serialized path aliases, harvested by the adapter from bundler config. */
|
|
143
|
-
pathAliasesJson = null;
|
|
144
|
-
assetCopyCache = /* @__PURE__ */ new Map();
|
|
145
|
-
/** Physical asset files registered with the host watcher. */
|
|
146
|
-
assetDependencyPaths = /* @__PURE__ */ new Set();
|
|
147
|
-
assetDependencyKeys = /* @__PURE__ */ new Set();
|
|
148
|
-
/** Emitter identity override for the system-props module id. Webpack mode
|
|
149
|
-
* (null) injects the absolute `.animus/system-props.js` path, resolved by
|
|
150
|
-
* NormalModuleReplacement; Turbopack rejects absolute-path imports, so
|
|
151
|
-
* its driver sets the virtual id that `resolveAlias` maps to disk. */
|
|
152
|
-
systemPropsModuleId = null;
|
|
153
|
-
/** Whether the analysis-inputs hydration corpus is serialized + written.
|
|
154
|
-
* False (webpack mode): the loader shares the pipeline process and reads
|
|
155
|
-
* the manifest from memory — the corpus is never persisted. True is set
|
|
156
|
-
* by the Turbopack orchestration driver, whose isolated loader workers
|
|
157
|
-
* replay it (spec: next-turbopack-integration, "Manifest disk artifact" /
|
|
158
|
-
* "Webpack mode skips the hydration corpus"). */
|
|
159
|
-
persistAnalysisInputs = false;
|
|
160
|
-
/** Absolute directory prefixes for external DS packages (loader allowlisting). */
|
|
161
|
-
externalPackageDirs = [];
|
|
162
|
-
/** Absolute package dir → owning specifier (cross-source correlation). */
|
|
163
|
-
externalDirOwners = {};
|
|
164
|
-
/** rootDir-relative external file → owning specifier (correlation join). */
|
|
165
|
-
externalFileOwners = {};
|
|
166
|
-
/** External package specifier → absolute source entry path. */
|
|
167
|
-
externalSourceEntries = /* @__PURE__ */ new Map();
|
|
168
|
-
/** SourceId derivation authority for the current generation (openspec:
|
|
169
|
-
* external-source-watch-ingestion, design D2) — canonical roots
|
|
170
|
-
* realpath'd once per full pipeline; alias→SourceId associations
|
|
171
|
-
* recorded while files exist so deletion resolves through the cache. */
|
|
172
|
-
sourceIdentity = null;
|
|
173
|
-
/** Canonical admitted external root → declared specifiers (set-valued
|
|
174
|
-
* ownership, design D2; duplicate specifiers resolving to one canonical
|
|
175
|
-
* root share it). Engine watch wiring and tests read this. */
|
|
176
|
-
externalRootOwners = /* @__PURE__ */ new Map();
|
|
177
|
-
/** Canonical external root → the extension set its collection walk used
|
|
178
|
-
* (widened for dist-only roots); rewalks and classification share it. */
|
|
179
|
-
externalRootExtensions = /* @__PURE__ */ new Map();
|
|
180
|
-
/** Canonical admitted external watch roots — the engine-side watch
|
|
181
|
-
* surface (webpack contextDependencies / Turbopack watchers). */
|
|
182
|
-
externalWatchRoots = [];
|
|
183
|
-
/** Canonical external root → (raw sourceKey → {raw content hash, recorded
|
|
184
|
-
* abs spelling}): the previous-generation inventory a dirty-root rewalk
|
|
185
|
-
* diffs against (design D3). Raw hashes — MDX preprocessing never skews
|
|
186
|
-
* the diff; the recorded abs path is a registered deletion alias, so a
|
|
187
|
-
* reconstructed deletion always resolves through cached identity. */
|
|
188
|
-
externalInventory = /* @__PURE__ */ new Map();
|
|
189
|
-
/** Sticky diagnostics (design D5/D7): stable key → message, re-surfaced
|
|
190
|
-
* on every full pipeline until the underlying condition clears. */
|
|
191
|
-
stickyDiagnostics = /* @__PURE__ */ new Map();
|
|
192
|
-
/** Volume-membership predicate for the cross-volume gate (design D5).
|
|
193
|
-
* Injected seam: the shared predicate only discriminates on Windows
|
|
194
|
-
* (win32 semantics are unit-tested via path.win32), so runtime tests
|
|
195
|
-
* drive the gate through this. */
|
|
196
|
-
sharesProjectVolume = _animus_ui_extract_pipeline.sharesVolumeRoot;
|
|
197
|
-
/** Orchestrator seams (openspec: external-source-watch-ingestion, design
|
|
198
|
-
* D4). `onExternalRootResolved` fires per volume-admitted external root
|
|
199
|
-
* DURING collection — after resolution, BEFORE that root's sources are
|
|
200
|
-
* walked — so a watching host can open its handle with no blind gap
|
|
201
|
-
* between scan and watch. `onExternalRootsCommitted` fires once after a
|
|
202
|
-
* successful full-pipeline publication with the complete admitted
|
|
203
|
-
* canonical root set (the host's promote/replay/close-old phase; a
|
|
204
|
-
* failed pipeline never fires it, so the host rolls back). */
|
|
205
|
-
onExternalRootResolved = null;
|
|
206
|
-
onExternalRootsCommitted = null;
|
|
207
|
-
options;
|
|
208
|
-
staticCssJson;
|
|
209
|
-
system = null;
|
|
210
|
-
/** Discovery-time keyframes diagnostics awaiting the shared surfacing pass. */
|
|
211
|
-
externalKeyframesDiagnostics = [];
|
|
212
|
-
/** Full package-resolution map from the last full pipeline — replayed by
|
|
213
|
-
* incremental passes (sourceEntries alone omits dist-resolved packages). */
|
|
214
|
-
lastPackageMap = {};
|
|
215
|
-
fileCache = /* @__PURE__ */ new Map();
|
|
216
|
-
systemDependencyKeys = /* @__PURE__ */ new Set();
|
|
217
|
-
/** Loader-reported dependency paths for bundler watch registration. */
|
|
218
|
-
systemDependencyPaths = [];
|
|
219
|
-
systemDependencyHashes = /* @__PURE__ */ new Map();
|
|
220
|
-
lastSystemPropsHash = null;
|
|
221
|
-
/** Per-payload write guards: payloadHash gates rewrites (byte-identical
|
|
222
|
-
* payloads leave disk untouched); diskHash is the hash of the CURRENT
|
|
223
|
-
* disk bytes (envelope included) recorded in the analysis-commit. null =
|
|
224
|
-
* not yet seeded (first publication reads the artifact's envelope so a
|
|
225
|
-
* same-session restart never rewrites byte-identical artifacts). */
|
|
226
|
-
artifactRecords = {
|
|
227
|
-
manifest: null,
|
|
228
|
-
inputs: null,
|
|
229
|
-
styles: null
|
|
230
|
-
};
|
|
231
|
-
/** Last written analysis-commit; null = seeded from disk on first use. */
|
|
232
|
-
lastCommit = null;
|
|
233
|
-
/** Session identity — claimed once per PROCESS (one Next invocation),
|
|
234
|
-
* adopted by every subsequent session instance so all compilers share
|
|
235
|
-
* one artifact tree (design D2: `next dev`/`next build` co-writing are
|
|
236
|
-
* separate processes and therefore separate sessions). */
|
|
237
|
-
sessionId = require_singleton.claimProcessSessionId();
|
|
238
|
-
/** Epoch value of the last published analysis; null = not yet known
|
|
239
|
-
* (seeded from the session dir's artifact on first publication so a
|
|
240
|
-
* same-session restart with unchanged plans never rewrites bytes). */
|
|
241
|
-
lastEpochValue = null;
|
|
242
|
-
/** Watcher debounce ceiling feeding status deadlines (design D3). */
|
|
243
|
-
debounceCeilingMs = 75;
|
|
244
|
-
/** Test seam: observes every session-artifact write (name, content)
|
|
245
|
-
* post-rename — write ORDER is part of the transaction contract. */
|
|
246
|
-
onArtifactWrite = null;
|
|
247
|
-
/** Status attempt bookkeeping (design D3). */
|
|
248
|
-
statusAttemptId = 0;
|
|
249
|
-
statusAttemptOpen = false;
|
|
250
|
-
/** Debounce-window observations pending analysis (sourceKey → hash). */
|
|
251
|
-
debouncePending = /* @__PURE__ */ new Map();
|
|
252
|
-
/** Session-start hygiene (pruning + legacy cleanup) runs once. */
|
|
253
|
-
sessionStartHygieneDone = false;
|
|
254
|
-
lcssTargets = null;
|
|
255
|
-
constructor(options) {
|
|
256
|
-
this.options = options;
|
|
257
|
-
this.staticCssJson = (0, _animus_ui_extract_pipeline.serializeStaticCss)(options.staticCss);
|
|
258
|
-
}
|
|
259
|
-
/** This session's artifact directory. Requires `rootDir` to be set. */
|
|
260
|
-
get sessionDir() {
|
|
261
|
-
return require_session_paths.sessionArtifactDir(this.rootDir, this.sessionId);
|
|
262
|
-
}
|
|
263
|
-
get verbose() {
|
|
264
|
-
return this.options.verbose === true || process.env.ANIMUS_DEBUG === "1" || process.env.ANIMUS_DEBUG === "true";
|
|
265
|
-
}
|
|
266
|
-
log(msg) {
|
|
267
|
-
if (this.verbose) console.info(`[animus] ${msg}`);
|
|
268
|
-
}
|
|
269
|
-
warn(msg) {
|
|
270
|
-
console.warn(`[animus] ${msg}`);
|
|
271
|
-
}
|
|
272
|
-
now() {
|
|
273
|
-
return this.verbose ? performance.now() : 0;
|
|
274
|
-
}
|
|
275
|
-
elapsed(t) {
|
|
276
|
-
return this.verbose ? Math.round(performance.now() - t) : 0;
|
|
277
|
-
}
|
|
278
|
-
/** Lazily-computed scan config (options are constructor-fixed, so the
|
|
279
|
-
* derivation is stable for the session's lifetime). */
|
|
280
|
-
scanConfigMemo = null;
|
|
281
|
-
/** Resolve the scan configuration from options — the single source of the
|
|
282
|
-
* exclude/extension policy shared by the full and incremental pipelines. */
|
|
283
|
-
resolveScanConfig() {
|
|
284
|
-
if (this.scanConfigMemo === null) {
|
|
285
|
-
const extensionsSet = new Set(this.options.extensions ?? _animus_ui_extract_pipeline.DEFAULT_EXTENSIONS);
|
|
286
|
-
this.scanConfigMemo = {
|
|
287
|
-
excludePatterns: this.options.exclude ?? DEFAULT_EXCLUDE,
|
|
288
|
-
extensionsSet,
|
|
289
|
-
shouldHandleMdx: extensionsSet.has(".mdx")
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
return this.scanConfigMemo;
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Watch entry point: one single-flight analysis transaction per event
|
|
296
|
-
* batch (openspec: next-webpack-served-transform-coherence, design D3).
|
|
297
|
-
* The first entering session runs the transaction; every concurrent
|
|
298
|
-
* entry — same session re-entered, or another compiler's session
|
|
299
|
-
* (client/server/RSC each hold their own instance) — joins the in-flight
|
|
300
|
-
* promise, so no compiler proceeds against a generation older than the
|
|
301
|
-
* one the transaction publishes. A rejected transaction rejects every
|
|
302
|
-
* joiner; the gate always clears.
|
|
303
|
-
*/
|
|
304
|
-
async handleWatchUpdate(changes) {
|
|
305
|
-
const inflight = require_singleton.getWatchTransaction();
|
|
306
|
-
if (inflight) {
|
|
307
|
-
await inflight;
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (!this.system) {
|
|
311
|
-
const owner = require_singleton.getOwningWatchSession();
|
|
312
|
-
const hasBatch = (changes.modifiedFiles?.size ?? 0) > 0 || (changes.removedFiles?.size ?? 0) > 0;
|
|
313
|
-
if (owner && owner !== this && hasBatch) await owner.ingestForwardedBatch(changes);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const transaction = this.processWatchUpdate(changes);
|
|
317
|
-
require_singleton.setWatchTransaction(transaction);
|
|
318
|
-
try {
|
|
319
|
-
await transaction;
|
|
320
|
-
} finally {
|
|
321
|
-
require_singleton.setWatchTransaction(null);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Owner-side entry for a batch observed by a NON-OWNING compiler's
|
|
326
|
-
* watcher. Serializes behind any in-flight transaction, then runs the
|
|
327
|
-
* forwarded batch as its own transaction — never drops it (a forwarded
|
|
328
|
-
* batch has no other delivery; content-hash diffing makes an
|
|
329
|
-
* already-covered batch a cheap no-op).
|
|
330
|
-
*/
|
|
331
|
-
async ingestForwardedBatch(changes) {
|
|
332
|
-
if (!this.system) return;
|
|
333
|
-
for (;;) {
|
|
334
|
-
const inflight = require_singleton.getWatchTransaction();
|
|
335
|
-
if (!inflight) break;
|
|
336
|
-
await inflight;
|
|
337
|
-
}
|
|
338
|
-
const transaction = this.processWatchUpdate(changes);
|
|
339
|
-
require_singleton.setWatchTransaction(transaction);
|
|
340
|
-
try {
|
|
341
|
-
await transaction;
|
|
342
|
-
} finally {
|
|
343
|
-
require_singleton.setWatchTransaction(null);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Incremental watch pass: geological reset when the system file changed,
|
|
348
|
-
* otherwise content-hash diffing restricted to the watcher's change sets
|
|
349
|
-
* (falling back to a full discovery walk when no sets are provided).
|
|
350
|
-
*/
|
|
351
|
-
async processWatchUpdate(changes) {
|
|
352
|
-
const rootDir = this.rootDir;
|
|
353
|
-
try {
|
|
354
|
-
changes = this.reconcileExternalRoots(changes);
|
|
355
|
-
} catch (err) {
|
|
356
|
-
this.warn(`external root reconciliation failed: ${String(err)}`);
|
|
357
|
-
}
|
|
358
|
-
const changed = [...changes.modifiedFiles ?? [], ...changes.removedFiles ?? []];
|
|
359
|
-
let assetChanged = false;
|
|
360
|
-
try {
|
|
361
|
-
let systemHit;
|
|
362
|
-
if (!changes.modifiedFiles && !changes.removedFiles) for (const [dep, hash] of this.systemDependencyHashes) {
|
|
363
|
-
let current = "";
|
|
364
|
-
try {
|
|
365
|
-
current = (0, _animus_ui_extract_pipeline.contentHash)((0, fs.readFileSync)(dep, "utf-8"));
|
|
366
|
-
} catch {}
|
|
367
|
-
if (current !== hash) {
|
|
368
|
-
systemHit = dep;
|
|
369
|
-
break;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
else systemHit = changed.find((path$2) => (0, _animus_ui_extract_pipeline.toWatchKeys)(path$2).some((key) => this.systemDependencyKeys.has(key)));
|
|
373
|
-
if (systemHit) {
|
|
374
|
-
this.log(`geological reset: system dependency changed (${(0, path.relative)(rootDir, systemHit)})`);
|
|
375
|
-
this.resetForHmr();
|
|
376
|
-
const promise = this.runFullPipeline(this.pendingFromBatch(changes));
|
|
377
|
-
require_singleton.setAnalysisPromise(promise);
|
|
378
|
-
await promise;
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
if (changed.some((path$3) => (0, _animus_ui_extract_pipeline.toWatchKeys)(path$3).some((key) => this.assetDependencyKeys.has(key)))) {
|
|
382
|
-
this.assetCopyCache.clear();
|
|
383
|
-
assetChanged = true;
|
|
384
|
-
}
|
|
385
|
-
} catch (err) {
|
|
386
|
-
this.warn(`HMR geological-reset check failed: ${String(err)}`);
|
|
387
|
-
}
|
|
388
|
-
const { excludePatterns, extensionsSet, shouldHandleMdx } = this.resolveScanConfig();
|
|
389
|
-
const priorCacheEntries = /* @__PURE__ */ new Map();
|
|
390
|
-
const recordPrior = (key) => {
|
|
391
|
-
if (!priorCacheEntries.has(key)) priorCacheEntries.set(key, this.fileCache.get(key) ?? null);
|
|
392
|
-
};
|
|
393
|
-
const inventoryUpdates = [];
|
|
394
|
-
const ownerRemovals = [];
|
|
395
|
-
let removedAny = false;
|
|
396
|
-
if (changes.removedFiles) for (const removedPath of changes.removedFiles) {
|
|
397
|
-
let key;
|
|
398
|
-
let owningRoot = null;
|
|
399
|
-
if ((0, _animus_ui_extract_pipeline.isPathWithinRoot)(rootDir, removedPath)) key = (0, path.relative)(rootDir, removedPath);
|
|
400
|
-
else {
|
|
401
|
-
const resolved = this.sourceIdentity?.resolveDeletedSourceId(removedPath);
|
|
402
|
-
if (!resolved) continue;
|
|
403
|
-
key = resolved.sourceKey;
|
|
404
|
-
owningRoot = resolved.owningRoot;
|
|
405
|
-
}
|
|
406
|
-
recordPrior(key);
|
|
407
|
-
recordPrior(key + ".tsx");
|
|
408
|
-
if (this.fileCache.delete(key) || this.fileCache.delete(key + ".tsx")) {
|
|
409
|
-
removedAny = true;
|
|
410
|
-
if (owningRoot) {
|
|
411
|
-
inventoryUpdates.push({
|
|
412
|
-
root: owningRoot,
|
|
413
|
-
key,
|
|
414
|
-
hash: null,
|
|
415
|
-
abs: removedPath
|
|
416
|
-
});
|
|
417
|
-
ownerRemovals.push(key, key + ".tsx");
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
let targets;
|
|
422
|
-
if (changes.modifiedFiles) {
|
|
423
|
-
targets = [];
|
|
424
|
-
for (const modifiedPath of changes.modifiedFiles) {
|
|
425
|
-
const classified = this.classifyWatchPath(modifiedPath, {
|
|
426
|
-
excludePatterns,
|
|
427
|
-
extensionsSet
|
|
428
|
-
});
|
|
429
|
-
if (classified) targets.push({
|
|
430
|
-
abs: modifiedPath,
|
|
431
|
-
...classified
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
} else targets = (0, _animus_ui_extract_pipeline.discoverFiles)(rootDir, rootDir, excludePatterns, extensionsSet).map((abs) => ({
|
|
435
|
-
abs,
|
|
436
|
-
key: (0, path.relative)(rootDir, abs),
|
|
437
|
-
owningRoot: null
|
|
438
|
-
}));
|
|
439
|
-
const changedPaths = [];
|
|
440
|
-
for (const target of targets) {
|
|
441
|
-
let relPath = target.key;
|
|
442
|
-
let source;
|
|
443
|
-
try {
|
|
444
|
-
source = (0, fs.readFileSync)(target.abs, "utf-8");
|
|
445
|
-
} catch {
|
|
446
|
-
continue;
|
|
447
|
-
}
|
|
448
|
-
const rawHash = (0, _animus_ui_extract_pipeline.contentHash)(source);
|
|
449
|
-
if (shouldHandleMdx && (0, path.extname)(target.abs) === ".mdx") {
|
|
450
|
-
const processed = await this.preprocessMdxEntry(source, relPath, { warn: false });
|
|
451
|
-
if (!processed) continue;
|
|
452
|
-
source = processed.source;
|
|
453
|
-
relPath = processed.relPath;
|
|
454
|
-
}
|
|
455
|
-
const cached = this.fileCache.get(relPath);
|
|
456
|
-
const hash = relPath === target.key ? rawHash : (0, _animus_ui_extract_pipeline.contentHash)(source);
|
|
457
|
-
if (!cached || cached.hash !== hash) {
|
|
458
|
-
changedPaths.push(relPath);
|
|
459
|
-
recordPrior(relPath);
|
|
460
|
-
this.fileCache.set(relPath, {
|
|
461
|
-
hash,
|
|
462
|
-
source
|
|
463
|
-
});
|
|
464
|
-
if (target.owningRoot) {
|
|
465
|
-
const owner = this.externalRootOwners.get(target.owningRoot)?.values().next().value;
|
|
466
|
-
if (owner !== void 0) this.externalFileOwners[relPath] ??= owner;
|
|
467
|
-
inventoryUpdates.push({
|
|
468
|
-
root: target.owningRoot,
|
|
469
|
-
key: target.key,
|
|
470
|
-
hash: rawHash,
|
|
471
|
-
abs: target.abs
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (changedPaths.length > 0 || removedAny || assetChanged) {
|
|
477
|
-
const fileEntries = this.buildFileEntriesFromCache();
|
|
478
|
-
const pending = changedPaths.map((rel) => [rel, this.fileCache.get(rel).hash]);
|
|
479
|
-
require_singleton.resetAnalysisPromise();
|
|
480
|
-
const promise = this.runIncrementalPipeline(fileEntries, pending);
|
|
481
|
-
require_singleton.setAnalysisPromise(promise);
|
|
482
|
-
try {
|
|
483
|
-
await promise;
|
|
484
|
-
} catch (err) {
|
|
485
|
-
for (const [key, prior] of priorCacheEntries) if (prior === null) this.fileCache.delete(key);
|
|
486
|
-
else this.fileCache.set(key, prior);
|
|
487
|
-
throw err;
|
|
488
|
-
}
|
|
489
|
-
for (const update of inventoryUpdates) {
|
|
490
|
-
let inventory = this.externalInventory.get(update.root);
|
|
491
|
-
if (!inventory) {
|
|
492
|
-
inventory = /* @__PURE__ */ new Map();
|
|
493
|
-
this.externalInventory.set(update.root, inventory);
|
|
494
|
-
}
|
|
495
|
-
if (update.hash === null) inventory.delete(update.key);
|
|
496
|
-
else inventory.set(update.key, {
|
|
497
|
-
hash: update.hash,
|
|
498
|
-
abs: update.abs
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
for (const key of ownerRemovals) delete this.externalFileOwners[key];
|
|
502
|
-
} else if (this.statusAttemptOpen) {
|
|
503
|
-
this.debouncePending.clear();
|
|
504
|
-
this.writeAnalysisStatus("idle", []);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
/**
|
|
508
|
-
* Preprocess one `.mdx` entry into scanner-consumable tsx. Returns the
|
|
509
|
-
* rewritten source plus the `relPath + '.tsx'` path on success, or null
|
|
510
|
-
* when the file must be skipped.
|
|
511
|
-
*
|
|
512
|
-
* `warn: false` (incremental watch pass) skips silently — the full
|
|
513
|
-
* pipeline already surfaced the warning. `warn: true` (full pipeline)
|
|
514
|
-
* warns ONCE for a missing @mdx-js/mdx dependency via the shared
|
|
515
|
-
* `missingDepFlag` holder, and every time for a preprocessing error.
|
|
516
|
-
*/
|
|
517
|
-
async preprocessMdxEntry(source, relPath, opts) {
|
|
518
|
-
const result = await (0, _animus_ui_extract_pipeline.preprocessMdx)(source, relPath);
|
|
519
|
-
if (result.kind === "missing-dep") {
|
|
520
|
-
if (opts.warn && opts.missingDepFlag && !opts.missingDepFlag.warned) {
|
|
521
|
-
console.warn("[animus] ⚠ .mdx in extensions but @mdx-js/mdx not installed; MDX files skipped");
|
|
522
|
-
opts.missingDepFlag.warned = true;
|
|
523
|
-
}
|
|
524
|
-
return null;
|
|
525
|
-
}
|
|
526
|
-
if (result.kind === "error") {
|
|
527
|
-
if (opts.warn) console.warn(`[animus] ⚠ MDX preprocessing failed for ${relPath}: ${result.error}`);
|
|
528
|
-
return null;
|
|
529
|
-
}
|
|
530
|
-
return {
|
|
531
|
-
source: result.source,
|
|
532
|
-
relPath: relPath + ".tsx"
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
async runFullPipeline(pending = []) {
|
|
536
|
-
const pipelineStart = this.now();
|
|
537
|
-
const bt = {};
|
|
538
|
-
this.runSessionStartHygiene();
|
|
539
|
-
require_singleton.setSessionArtifactDir(this.sessionDir);
|
|
540
|
-
(0, _animus_ui_extract_pipeline.clearEngineCache)(require_singleton.engineApi);
|
|
541
|
-
const rootDir = this.rootDir;
|
|
542
|
-
const resolvedSystemPath = (0, path.resolve)(rootDir, this.options.system);
|
|
543
|
-
let t = this.now();
|
|
544
|
-
this.system = (0, _animus_ui_extract_pipeline.loadSystemConfig)(require_singleton.engineApi, {
|
|
545
|
-
systemPath: resolvedSystemPath,
|
|
546
|
-
rootDir,
|
|
547
|
-
prefix: this.options.prefix
|
|
548
|
-
});
|
|
549
|
-
this.assetCopyCache.clear();
|
|
550
|
-
this.assetDependencyPaths.clear();
|
|
551
|
-
this.assetDependencyKeys.clear();
|
|
552
|
-
{
|
|
553
|
-
const deps = this.system.dependencies ?? [];
|
|
554
|
-
const keys = /* @__PURE__ */ new Set();
|
|
555
|
-
for (const key of (0, _animus_ui_extract_pipeline.toWatchKeys)(resolvedSystemPath)) keys.add(key);
|
|
556
|
-
for (const dep of deps) for (const key of (0, _animus_ui_extract_pipeline.toWatchKeys)(dep)) keys.add(key);
|
|
557
|
-
this.systemDependencyKeys = keys;
|
|
558
|
-
this.systemDependencyPaths = deps;
|
|
559
|
-
const hashes = /* @__PURE__ */ new Map();
|
|
560
|
-
for (const dep of deps.length > 0 ? deps : [resolvedSystemPath]) try {
|
|
561
|
-
hashes.set(dep, (0, _animus_ui_extract_pipeline.contentHash)((0, fs.readFileSync)(dep, "utf-8")));
|
|
562
|
-
} catch {
|
|
563
|
-
hashes.set(dep, "");
|
|
564
|
-
}
|
|
565
|
-
this.systemDependencyHashes = hashes;
|
|
566
|
-
}
|
|
567
|
-
bt.systemLoad = this.elapsed(t);
|
|
568
|
-
t = this.now();
|
|
569
|
-
const { excludePatterns, extensionsSet, shouldHandleMdx } = this.resolveScanConfig();
|
|
570
|
-
const missingDepFlag = { warned: false };
|
|
571
|
-
const files = (0, _animus_ui_extract_pipeline.discoverFiles)(rootDir, rootDir, excludePatterns, extensionsSet);
|
|
572
|
-
bt.fileDiscovery = this.elapsed(t);
|
|
573
|
-
t = this.now();
|
|
574
|
-
const fileEntries = [];
|
|
575
|
-
for (const filePath of files) {
|
|
576
|
-
let source = (0, fs.readFileSync)(filePath, "utf-8");
|
|
577
|
-
let relPath = (0, path.relative)(rootDir, filePath);
|
|
578
|
-
if (shouldHandleMdx && (0, path.extname)(filePath) === ".mdx") {
|
|
579
|
-
const processed = await this.preprocessMdxEntry(source, relPath, {
|
|
580
|
-
warn: true,
|
|
581
|
-
missingDepFlag
|
|
582
|
-
});
|
|
583
|
-
if (!processed) continue;
|
|
584
|
-
source = processed.source;
|
|
585
|
-
relPath = processed.relPath;
|
|
586
|
-
}
|
|
587
|
-
const hash = (0, _animus_ui_extract_pipeline.contentHash)(source);
|
|
588
|
-
this.fileCache.set(relPath, {
|
|
589
|
-
hash,
|
|
590
|
-
source
|
|
591
|
-
});
|
|
592
|
-
fileEntries.push({
|
|
593
|
-
path: relPath,
|
|
594
|
-
source,
|
|
595
|
-
hash
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
bt.fileRead = this.elapsed(t);
|
|
599
|
-
bt.fileCount = fileEntries.length;
|
|
600
|
-
t = this.now();
|
|
601
|
-
const packageNames = (0, _animus_ui_extract_pipeline.extractSystemFilePackages)(resolvedSystemPath);
|
|
602
|
-
const preResolved = resolvePackagesByName(rootDir, packageNames);
|
|
603
|
-
const rawExternalFiles = /* @__PURE__ */ new Map();
|
|
604
|
-
const collected = await (0, _animus_ui_extract_pipeline.collectExternalPackageSources)({
|
|
605
|
-
specifiers: packageNames,
|
|
606
|
-
resolveSpecifier: (name) => preResolved[name] ? (0, path.resolve)(rootDir, preResolved[name]) : null,
|
|
607
|
-
rootDir,
|
|
608
|
-
extensionsSet,
|
|
609
|
-
hasEntry: (relPath) => fileEntries.some((e) => e.path === relPath),
|
|
610
|
-
preprocessFile: async (source, relPath, absPath) => {
|
|
611
|
-
rawExternalFiles.set(absPath, (0, _animus_ui_extract_pipeline.contentHash)(source));
|
|
612
|
-
if (shouldHandleMdx && (0, path.extname)(absPath) === ".mdx") return this.preprocessMdxEntry(source, relPath, {
|
|
613
|
-
warn: true,
|
|
614
|
-
missingDepFlag
|
|
615
|
-
});
|
|
616
|
-
return {
|
|
617
|
-
source,
|
|
618
|
-
relPath
|
|
619
|
-
};
|
|
620
|
-
},
|
|
621
|
-
onUnreadable: (relPath, err) => this.warn(`skipped unreadable package file ${relPath}: ${String(err)}`),
|
|
622
|
-
onPackageResolved: (_specifier, packageDir) => {
|
|
623
|
-
if (!this.onExternalRootResolved) return;
|
|
624
|
-
if (!this.sharesProjectVolume(rootDir, packageDir)) return;
|
|
625
|
-
let canonical = packageDir;
|
|
626
|
-
try {
|
|
627
|
-
canonical = (0, fs.realpathSync)(packageDir);
|
|
628
|
-
} catch {}
|
|
629
|
-
this.onExternalRootResolved(canonical);
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
|
-
for (const record of collected.outcomes) if (record.outcome === "empty") this.warn(`include '${record.specifier}' resolved but discovered no component sources`);
|
|
633
|
-
const unresolvableMessage = (0, _animus_ui_extract_pipeline.unresolvableIncludesMessage)(collected.outcomes);
|
|
634
|
-
if (unresolvableMessage !== null) {
|
|
635
|
-
if (this.options.strict) throw new Error(unresolvableMessage);
|
|
636
|
-
this.warn(unresolvableMessage);
|
|
637
|
-
}
|
|
638
|
-
const staleDistMessage = (0, _animus_ui_extract_pipeline.staleDistIncludesMessage)(collected.outcomes);
|
|
639
|
-
if (staleDistMessage !== null) {
|
|
640
|
-
if (this.options.strict) throw new Error(staleDistMessage);
|
|
641
|
-
this.warn(staleDistMessage);
|
|
642
|
-
}
|
|
643
|
-
const rejectedSpecifiers = /* @__PURE__ */ new Set();
|
|
644
|
-
const crossVolumeDetails = [];
|
|
645
|
-
for (const [dir, specifiers] of Object.entries(collected.dirOwnerSets)) {
|
|
646
|
-
if (this.sharesProjectVolume(rootDir, dir)) continue;
|
|
647
|
-
for (const specifier of specifiers) rejectedSpecifiers.add(specifier);
|
|
648
|
-
crossVolumeDetails.push(`${specifiers.join(", ")} → ${dir}`);
|
|
649
|
-
}
|
|
650
|
-
this.stickyDiagnostics.delete("cross-volume");
|
|
651
|
-
if (rejectedSpecifiers.size > 0) {
|
|
652
|
-
const message = `ANIMUS_EXTERNAL_CROSS_VOLUME_UNSUPPORTED: external package source root(s) on a different volume than the project root (${rootDir}): ${crossVolumeDetails.join("; ")} — cross-volume workspace sources are unsupported; the package(s) are excluded from extraction and watching`;
|
|
653
|
-
if (this.options.strict) throw new Error(`[animus-next] ${message}`);
|
|
654
|
-
this.stickyDiagnostics.set("cross-volume", message);
|
|
655
|
-
}
|
|
656
|
-
for (const message of this.stickyDiagnostics.values()) this.warn(message);
|
|
657
|
-
const admitted = (0, _animus_ui_extract_pipeline.excludeCollectedPackages)(collected, rejectedSpecifiers, rootDir);
|
|
658
|
-
const identity = (0, _animus_ui_extract_pipeline.createSourceIdentity)(rootDir);
|
|
659
|
-
this.externalRootOwners = /* @__PURE__ */ new Map();
|
|
660
|
-
this.externalRootExtensions = /* @__PURE__ */ new Map();
|
|
661
|
-
for (const [dir, specifiers] of Object.entries(admitted.dirOwnerSets)) {
|
|
662
|
-
const canonical = identity.registerExternalRoot(dir);
|
|
663
|
-
const owners = this.externalRootOwners.get(canonical) ?? /* @__PURE__ */ new Set();
|
|
664
|
-
for (const specifier of specifiers) owners.add(specifier);
|
|
665
|
-
this.externalRootOwners.set(canonical, owners);
|
|
666
|
-
const exts = admitted.dirExtensions[dir];
|
|
667
|
-
if (exts) this.externalRootExtensions.set(canonical, new Set(exts));
|
|
668
|
-
}
|
|
669
|
-
this.sourceIdentity = identity;
|
|
670
|
-
this.externalWatchRoots = identity.externalRoots();
|
|
671
|
-
this.externalInventory = /* @__PURE__ */ new Map();
|
|
672
|
-
for (const [absPath, rawHash] of rawExternalFiles) {
|
|
673
|
-
const resolved = identity.resolveSourceId(absPath);
|
|
674
|
-
if (!resolved?.owningRoot) continue;
|
|
675
|
-
let inventory = this.externalInventory.get(resolved.owningRoot);
|
|
676
|
-
if (!inventory) {
|
|
677
|
-
inventory = /* @__PURE__ */ new Map();
|
|
678
|
-
this.externalInventory.set(resolved.owningRoot, inventory);
|
|
679
|
-
}
|
|
680
|
-
inventory.set(resolved.sourceKey, {
|
|
681
|
-
hash: rawHash,
|
|
682
|
-
abs: absPath
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
const packageMap = admitted.packageMap;
|
|
686
|
-
this.lastPackageMap = packageMap;
|
|
687
|
-
this.externalDirOwners = (0, _animus_ui_extract_pipeline.firstOwners)(admitted.dirOwnerSets);
|
|
688
|
-
this.externalFileOwners = admitted.fileOwners;
|
|
689
|
-
this.externalSourceEntries = admitted.sourceEntries;
|
|
690
|
-
for (const entry of admitted.entries) {
|
|
691
|
-
const hash = (0, _animus_ui_extract_pipeline.contentHash)(entry.source);
|
|
692
|
-
this.fileCache.set(entry.path, {
|
|
693
|
-
hash,
|
|
694
|
-
source: entry.source
|
|
695
|
-
});
|
|
696
|
-
fileEntries.push({
|
|
697
|
-
path: entry.path,
|
|
698
|
-
source: entry.source,
|
|
699
|
-
hash
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
this.externalPackageDirs = admitted.packageDirs;
|
|
703
|
-
require_singleton.setSharedExternalDirs(admitted.packageDirs);
|
|
704
|
-
require_singleton.setSharedExternalEntries(admitted.sourceEntries);
|
|
705
|
-
if (this.system && admitted.keyframesScanEntries.size > 0) {
|
|
706
|
-
const api = require_singleton.engineApi();
|
|
707
|
-
const merge = (0, _animus_ui_extract_pipeline.mergeExternalKeyframes)((entry, root) => api.scanKeyframesExports(entry, root), this.system.keyframesJson, admitted.keyframesScanEntries.values(), this.rootDir);
|
|
708
|
-
this.system.keyframesJson = merge.keyframesJson;
|
|
709
|
-
this.externalKeyframesDiagnostics = merge.diagnostics;
|
|
710
|
-
} else this.externalKeyframesDiagnostics = [];
|
|
711
|
-
bt.packageResolve = this.elapsed(t);
|
|
712
|
-
const universe = new Set(fileEntries.map((entry) => entry.path));
|
|
713
|
-
for (const key of [...this.fileCache.keys()]) if (!universe.has(key)) this.fileCache.delete(key);
|
|
714
|
-
await this.analyzeAndEmit(fileEntries, packageMap, false, bt, pipelineStart, pending);
|
|
715
|
-
this.onExternalRootsCommitted?.(this.externalWatchRoots);
|
|
716
|
-
require_singleton.setOwningWatchSession(this);
|
|
717
|
-
}
|
|
718
|
-
/**
|
|
719
|
-
* Reset analysis state for HMR geological reset. Payload write guards go
|
|
720
|
-
* back to null so the next publication reseeds them from the disk
|
|
721
|
-
* envelopes — a byte-identical post-reset artifact is still not
|
|
722
|
-
* rewritten.
|
|
723
|
-
*/
|
|
724
|
-
resetForHmr() {
|
|
725
|
-
require_singleton.resetAnalysisPromise();
|
|
726
|
-
this.artifactRecords = {
|
|
727
|
-
manifest: null,
|
|
728
|
-
inputs: null,
|
|
729
|
-
styles: null
|
|
730
|
-
};
|
|
731
|
-
this.lastSystemPropsHash = null;
|
|
732
|
-
(0, _animus_ui_extract_pipeline.clearEngineCache)(require_singleton.engineApi);
|
|
733
|
-
}
|
|
734
|
-
/**
|
|
735
|
-
* The (sourceKey, observedSourceHash) pairs of a watch batch — cheap
|
|
736
|
-
* evidence for the status file's pending set on the geological-reset
|
|
737
|
-
* path, where the batch's component edits ride along with the system
|
|
738
|
-
* edit (design D3: loaders wait only on observed inputs).
|
|
739
|
-
*/
|
|
740
|
-
pendingFromBatch(changes) {
|
|
741
|
-
const { excludePatterns, extensionsSet } = this.resolveScanConfig();
|
|
742
|
-
const pending = [];
|
|
743
|
-
for (const path$4 of changes.modifiedFiles ?? []) {
|
|
744
|
-
const classified = this.classifyWatchPath(path$4, {
|
|
745
|
-
excludePatterns,
|
|
746
|
-
extensionsSet
|
|
747
|
-
});
|
|
748
|
-
if (!classified) continue;
|
|
749
|
-
try {
|
|
750
|
-
pending.push([classified.key, (0, _animus_ui_extract_pipeline.contentHash)((0, fs.readFileSync)(path$4, "utf-8"))]);
|
|
751
|
-
} catch {}
|
|
752
|
-
}
|
|
753
|
-
return pending;
|
|
754
|
-
}
|
|
755
|
-
/**
|
|
756
|
-
* Root-dirty inventory reconciliation (design D3): partition the
|
|
757
|
-
* watcher's change sets into explicit FILE events and external ROOT hits
|
|
758
|
-
* (a reported path that IS an admitted root, or a directory inside one),
|
|
759
|
-
* rewalk each dirty root through the shared discovery policy, diff the
|
|
760
|
-
* SourceId+raw-hash inventory against the previous generation, and merge
|
|
761
|
-
* the reconstructed created/edited/deleted deltas with the explicit
|
|
762
|
-
* events (identity-level dedup happens downstream at the cache gate).
|
|
763
|
-
* Deletion falls out of the inventory diff — webpack may report only the
|
|
764
|
-
* directory (probe-proven).
|
|
765
|
-
*/
|
|
766
|
-
reconcileExternalRoots(changes) {
|
|
767
|
-
const identity = this.sourceIdentity;
|
|
768
|
-
if (!identity) return changes;
|
|
769
|
-
if (!changes.modifiedFiles && !changes.removedFiles) return changes;
|
|
770
|
-
if (this.externalWatchRoots.length === 0) return changes;
|
|
771
|
-
const { extensionsSet } = this.resolveScanConfig();
|
|
772
|
-
const dirtyRoots = /* @__PURE__ */ new Set();
|
|
773
|
-
const modified = /* @__PURE__ */ new Set();
|
|
774
|
-
const removed = /* @__PURE__ */ new Set();
|
|
775
|
-
const rootHitFor = (path$5) => {
|
|
776
|
-
const containing = identity.containingExternalRoot(path$5);
|
|
777
|
-
if (!containing) return null;
|
|
778
|
-
try {
|
|
779
|
-
return (0, fs.statSync)(path$5).isDirectory() ? containing : null;
|
|
780
|
-
} catch {
|
|
781
|
-
return identity.resolveDeletedSourceId(path$5) ? null : containing;
|
|
782
|
-
}
|
|
783
|
-
};
|
|
784
|
-
for (const path$6 of changes.modifiedFiles ?? []) {
|
|
785
|
-
const root = rootHitFor(path$6);
|
|
786
|
-
if (root) dirtyRoots.add(root);
|
|
787
|
-
else modified.add(path$6);
|
|
788
|
-
}
|
|
789
|
-
for (const path$7 of changes.removedFiles ?? []) {
|
|
790
|
-
const root = rootHitFor(path$7);
|
|
791
|
-
if (root) dirtyRoots.add(root);
|
|
792
|
-
else removed.add(path$7);
|
|
793
|
-
}
|
|
794
|
-
for (const root of dirtyRoots) {
|
|
795
|
-
const previous = this.externalInventory.get(root) ?? /* @__PURE__ */ new Map();
|
|
796
|
-
const seen = /* @__PURE__ */ new Set();
|
|
797
|
-
const walked = (0, _animus_ui_extract_pipeline.walkPackageSources)(root, this.externalRootExtensions.get(root) ?? extensionsSet);
|
|
798
|
-
if (walked.length === 0 && previous.size > 0) this.warn(`external root rewalk found no files under ${root} while its inventory holds ${previous.size} — reconciling as full deletion`);
|
|
799
|
-
for (const abs of walked) {
|
|
800
|
-
const resolved = identity.resolveSourceId(abs);
|
|
801
|
-
if (!resolved || resolved.owningRoot !== root) continue;
|
|
802
|
-
seen.add(resolved.sourceKey);
|
|
803
|
-
let rawHash;
|
|
804
|
-
try {
|
|
805
|
-
rawHash = (0, _animus_ui_extract_pipeline.contentHash)((0, fs.readFileSync)(abs, "utf-8"));
|
|
806
|
-
} catch {
|
|
807
|
-
continue;
|
|
808
|
-
}
|
|
809
|
-
if (previous.get(resolved.sourceKey)?.hash !== rawHash) modified.add(abs);
|
|
810
|
-
}
|
|
811
|
-
for (const [key, entry] of previous) if (!seen.has(key)) removed.add(entry.abs);
|
|
812
|
-
}
|
|
813
|
-
return {
|
|
814
|
-
modifiedFiles: modified,
|
|
815
|
-
removedFiles: removed
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
/**
|
|
819
|
-
* Route one watcher-reported path (design D1/D2): a project-root member
|
|
820
|
-
* (lexical containment — existing local semantics preserved; consumer
|
|
821
|
-
* exclude patterns apply) or an admitted external member (identity
|
|
822
|
-
* resolution with symlink-escape rejection; package-relative excludes
|
|
823
|
-
* mirror the collection walk's filters — guardrail G1, one policy).
|
|
824
|
-
* Returns null for dropped paths.
|
|
825
|
-
*/
|
|
826
|
-
classifyWatchPath(absPath, scan) {
|
|
827
|
-
const ext = (0, path.extname)(absPath);
|
|
828
|
-
const rootDir = this.rootDir;
|
|
829
|
-
if ((0, _animus_ui_extract_pipeline.isPathWithinRoot)(rootDir, absPath)) {
|
|
830
|
-
if (!scan.extensionsSet.has(ext)) return null;
|
|
831
|
-
const rel = (0, path.relative)(rootDir, absPath);
|
|
832
|
-
if (scan.excludePatterns.some((pattern) => absPath.includes(pattern) || rel.includes(pattern))) return null;
|
|
833
|
-
return {
|
|
834
|
-
key: rel,
|
|
835
|
-
owningRoot: null
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
const resolved = this.sourceIdentity?.resolveSourceId(absPath);
|
|
839
|
-
if (!resolved) return null;
|
|
840
|
-
if (resolved.owningRoot === null) {
|
|
841
|
-
if (!scan.extensionsSet.has(ext)) return null;
|
|
842
|
-
if (scan.excludePatterns.some((pattern) => resolved.sourceKey.includes(pattern))) return null;
|
|
843
|
-
return {
|
|
844
|
-
key: resolved.sourceKey,
|
|
845
|
-
owningRoot: null
|
|
846
|
-
};
|
|
847
|
-
}
|
|
848
|
-
if (!(this.externalRootExtensions.get(resolved.owningRoot) ?? scan.extensionsSet).has(ext)) return null;
|
|
849
|
-
if ((0, _animus_ui_extract_pipeline.isExcludedPackageRelativePath)(resolved.pathInRoot)) return null;
|
|
850
|
-
return {
|
|
851
|
-
key: resolved.sourceKey,
|
|
852
|
-
owningRoot: resolved.owningRoot
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
/**
|
|
856
|
-
* Build file entries from cache: every cached file rides with full source.
|
|
857
|
-
* The v2 engine has NO Rust-side cache (arch-extract-v2-spine: uncached
|
|
858
|
-
* re-analysis beats a cache-hit path), so it must always receive full sources
|
|
859
|
-
* (openspec: retire-extract-v1 removed the v1 empty-source cache contract).
|
|
860
|
-
*/
|
|
861
|
-
buildFileEntriesFromCache() {
|
|
862
|
-
const entries = [];
|
|
863
|
-
for (const [path$8, { hash, source }] of this.fileCache) entries.push({
|
|
864
|
-
path: path$8,
|
|
865
|
-
source,
|
|
866
|
-
hash
|
|
867
|
-
});
|
|
868
|
-
return entries;
|
|
869
|
-
}
|
|
870
|
-
/**
|
|
871
|
-
* Run incremental pipeline with cache-aware file entries.
|
|
872
|
-
* Reuses system config from the last full pipeline run.
|
|
873
|
-
*/
|
|
874
|
-
async runIncrementalPipeline(fileEntries, pending = []) {
|
|
875
|
-
const bt = {};
|
|
876
|
-
const pipelineStart = this.now();
|
|
877
|
-
await this.analyzeAndEmit(fileEntries, this.lastPackageMap, true, bt, pipelineStart, pending);
|
|
878
|
-
}
|
|
879
|
-
/**
|
|
880
|
-
* Shared analysis + emit core for both pipelines — the single call site
|
|
881
|
-
* that routes every manifest through the shared `runProjectAnalysis`,
|
|
882
|
-
* reachable from both runFullPipeline (production) and
|
|
883
|
-
* runIncrementalPipeline (HMR).
|
|
884
|
-
*
|
|
885
|
-
* Owns diagnostic surfacing, CSS assembly + styles.css write guard,
|
|
886
|
-
* system-props module emit, and the timing log. The `devMode` flag is the
|
|
887
|
-
* ONLY behavioral fork:
|
|
888
|
-
*
|
|
889
|
-
* - `false` (production): computes bt.analysis + logs the extraction
|
|
890
|
-
* report, and writes system-props.js UNCONDITIONALLY (no
|
|
891
|
-
* lastSystemPropsHash guard).
|
|
892
|
-
* - `true` (HMR): skips the report log, and guards the system-props.js
|
|
893
|
-
* write by lastSystemPropsHash.
|
|
894
|
-
*
|
|
895
|
-
* The disk artifacts land under the session directory in transaction
|
|
896
|
-
* order (design D1): manifest → analysis-inputs (Turbopack orchestration
|
|
897
|
-
* only) → styles.css → system-props → analysis-commit →
|
|
898
|
-
* replacements-epoch (last, only when moved). The analysis-status file walks starting → analyzing →
|
|
899
|
-
* committing → idle around the attempt, landing in `failed` (with the
|
|
900
|
-
* diagnostic) on any throw (design D3).
|
|
901
|
-
*/
|
|
902
|
-
async analyzeAndEmit(fileEntries, packageMap, devMode, bt, pipelineStart, pending = []) {
|
|
903
|
-
this.beginStatusAttempt();
|
|
904
|
-
this.debouncePending.clear();
|
|
905
|
-
this.writeAnalysisStatus("starting", pending);
|
|
906
|
-
try {
|
|
907
|
-
await this.analyzeAndEmitAttempt(fileEntries, packageMap, devMode, bt, pipelineStart, pending);
|
|
908
|
-
this.writeAnalysisStatus("idle", []);
|
|
909
|
-
} catch (err) {
|
|
910
|
-
this.writeAnalysisStatus("failed", pending, String(err));
|
|
911
|
-
throw err;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
async analyzeAndEmitAttempt(fileEntries, packageMap, devMode, bt, pipelineStart, pending) {
|
|
915
|
-
const system = this.system;
|
|
916
|
-
const analysisOptions = {
|
|
917
|
-
fileEntries,
|
|
918
|
-
packageMap,
|
|
919
|
-
system,
|
|
920
|
-
emitter: {
|
|
921
|
-
runtimeImport: "@animus-ui/system/runtime",
|
|
922
|
-
cssModuleId: ANIMUS_CSS_MODULE_ID,
|
|
923
|
-
systemPropsModuleId: this.systemPropsModuleId ?? require_session_paths.systemPropsPath(this.sessionDir)
|
|
924
|
-
},
|
|
925
|
-
pathAliasesJson: this.pathAliasesJson,
|
|
926
|
-
staticCssJson: this.staticCssJson,
|
|
927
|
-
externalDirs: this.externalPackageDirs.map((dir) => (0, path.relative)(this.rootDir, dir)),
|
|
928
|
-
devMode
|
|
929
|
-
};
|
|
930
|
-
this.writeAnalysisStatus("analyzing", pending);
|
|
931
|
-
const result = (0, _animus_ui_extract_pipeline.runProjectAnalysis)(require_singleton.engineApi, {
|
|
932
|
-
...analysisOptions,
|
|
933
|
-
warn: (message) => this.warn(message),
|
|
934
|
-
strict: this.options.strict,
|
|
935
|
-
extraDiagnostics: this.externalKeyframesDiagnostics
|
|
936
|
-
});
|
|
937
|
-
(0, _animus_ui_extract_pipeline.assertNoErrorDiagnostics)(result.manifest?.diagnostics);
|
|
938
|
-
(0, _animus_ui_extract_pipeline.enforceExternalTokenContracts)({
|
|
939
|
-
diagnostics: result.manifest?.diagnostics,
|
|
940
|
-
fileOwners: this.externalFileOwners,
|
|
941
|
-
dirOwners: this.externalDirOwners,
|
|
942
|
-
sourceThemeManifestsJson: system.sourceThemeManifestsJson,
|
|
943
|
-
strict: this.options.strict,
|
|
944
|
-
prefix: "[animus-next]",
|
|
945
|
-
warn: (message) => this.warn(message)
|
|
946
|
-
});
|
|
947
|
-
bt.jsonSerialize = result.timings.serializeMs;
|
|
948
|
-
bt.rustExtract = result.timings.extractMs;
|
|
949
|
-
bt.jsonParse = result.timings.parseMs;
|
|
950
|
-
const manifest = result.manifest;
|
|
951
|
-
if (!devMode) {
|
|
952
|
-
bt.analysis = (bt.jsonSerialize ?? 0) + (bt.rustExtract ?? 0) + (bt.jsonParse ?? 0);
|
|
953
|
-
if (manifest?.report) this.log(`Extracted ${manifest.report.components_extracted ?? "?"}/${manifest.report.components_total ?? "?"} components (${bt.analysis}ms)`);
|
|
954
|
-
}
|
|
955
|
-
const globalCss = this.substituteAssetReferences(result.globalCss, devMode);
|
|
956
|
-
const { declaration, variables, body } = (0, _animus_ui_extract_pipeline.assembleStylesheet)({
|
|
957
|
-
layers: this.options.layers,
|
|
958
|
-
variableCss: system.variableCss,
|
|
959
|
-
globalCss,
|
|
960
|
-
componentCss: result.componentCss,
|
|
961
|
-
split: true
|
|
962
|
-
});
|
|
963
|
-
if (this.lcssTargets === null) this.lcssTargets = (0, _animus_ui_extract_pipeline.resolveLightningTargets)(this.options.targets, this.rootDir);
|
|
964
|
-
const fullCss = [
|
|
965
|
-
declaration,
|
|
966
|
-
variables,
|
|
967
|
-
(0, _animus_ui_extract_pipeline.postProcessCss)(body, {
|
|
968
|
-
minify: this.options.minify ?? process.env.NODE_ENV === "production",
|
|
969
|
-
targets: this.lcssTargets,
|
|
970
|
-
warnFn: (msg) => this.warn(msg)
|
|
971
|
-
})
|
|
972
|
-
].filter(Boolean).join("\n");
|
|
973
|
-
require_singleton.setSharedCss(fullCss);
|
|
974
|
-
const systemPropsContent = (0, _animus_ui_extract_pipeline.buildSystemPropsModule)({
|
|
975
|
-
systemPropMapJson: JSON.stringify(manifest?.system_prop_map ?? {}),
|
|
976
|
-
groupRegistryJson: system.groupRegistryJson,
|
|
977
|
-
dynamicProps: manifest?.dynamic_props ?? {}
|
|
978
|
-
});
|
|
979
|
-
require_singleton.setSharedSystemProps(systemPropsContent);
|
|
980
|
-
require_singleton.setManifestJson(result.manifestJson);
|
|
981
|
-
require_singleton.setAnalyzedHashes(new Map(fileEntries.map((entry) => [entry.path, entry.hash])));
|
|
982
|
-
this.writeAnalysisStatus("committing", pending);
|
|
983
|
-
const epoch = (0, _animus_ui_extract_pipeline.hashReplacementPlans)((0, _animus_ui_extract_pipeline.snapshotFilePlans)(manifest), systemPropsContent);
|
|
984
|
-
if (this.lastCommit === null) this.lastCommit = this.seedCommitFromDisk();
|
|
985
|
-
const generation = (this.lastCommit?.generation ?? 0) + 1;
|
|
986
|
-
this.publishPayloadArtifact("manifest", require_session_paths.MANIFEST_ARTIFACT, result.manifestJson, epoch, generation, require_session_paths.envelopeJsonArtifact);
|
|
987
|
-
if (this.persistAnalysisInputs) this.publishPayloadArtifact("inputs", require_session_paths.ANALYSIS_INPUTS_ARTIFACT, JSON.stringify({
|
|
988
|
-
analyzedHashes: Object.fromEntries(fileEntries.map((entry) => [entry.path, entry.hash])),
|
|
989
|
-
...result.inputs
|
|
990
|
-
}), epoch, generation, require_session_paths.envelopeJsonArtifact);
|
|
991
|
-
this.publishPayloadArtifact("styles", require_session_paths.STYLES_ARTIFACT, fullCss, epoch, generation, require_session_paths.envelopeCssArtifact);
|
|
992
|
-
if (devMode) {
|
|
993
|
-
const systemPropsHash = (0, _animus_ui_extract_pipeline.contentHash)(systemPropsContent);
|
|
994
|
-
if (systemPropsHash !== this.lastSystemPropsHash) {
|
|
995
|
-
this.writeSessionArtifact(require_session_paths.SYSTEM_PROPS_ARTIFACT, systemPropsContent);
|
|
996
|
-
this.lastSystemPropsHash = systemPropsHash;
|
|
997
|
-
}
|
|
998
|
-
} else this.writeSessionArtifact(require_session_paths.SYSTEM_PROPS_ARTIFACT, systemPropsContent);
|
|
999
|
-
this.publishAnalysisCommit(epoch, generation);
|
|
1000
|
-
this.publishReplacementEpoch(epoch);
|
|
1001
|
-
bt.total = this.elapsed(pipelineStart);
|
|
1002
|
-
logBuildTimings(bt, manifest?.timing, (msg) => this.log(msg), this.verbose);
|
|
1003
|
-
}
|
|
1004
|
-
/** Open a status attempt (attemptId increments once per burst — a
|
|
1005
|
-
* debouncing pre-write and the analysis that follows share one id). */
|
|
1006
|
-
beginStatusAttempt() {
|
|
1007
|
-
if (!this.statusAttemptOpen) {
|
|
1008
|
-
this.statusAttemptId += 1;
|
|
1009
|
-
this.statusAttemptOpen = true;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
/** Write the session's analysis-status artifact (design D3). Terminal
|
|
1013
|
-
* states (idle/failed) close the attempt. */
|
|
1014
|
-
writeAnalysisStatus(state, pending, diagnostic) {
|
|
1015
|
-
const status = {
|
|
1016
|
-
schema: 1,
|
|
1017
|
-
sessionId: this.sessionId,
|
|
1018
|
-
attemptId: this.statusAttemptId,
|
|
1019
|
-
state,
|
|
1020
|
-
pending,
|
|
1021
|
-
deadlineAt: Date.now() + this.debounceCeilingMs + STATUS_WATCHDOG_MS,
|
|
1022
|
-
...diagnostic !== void 0 ? { diagnostic } : {}
|
|
1023
|
-
};
|
|
1024
|
-
this.writeSessionArtifact(require_session_paths.ANALYSIS_STATUS_ARTIFACT, JSON.stringify(status));
|
|
1025
|
-
if (state === "idle" || state === "failed") this.statusAttemptOpen = false;
|
|
1026
|
-
}
|
|
1027
|
-
/**
|
|
1028
|
-
* Orchestrator seam (Turbopack watcher): record watch events observed
|
|
1029
|
-
* during the debounce window so a loader running ahead of the analysis
|
|
1030
|
-
* has positive evidence to wait on (design D3 'debouncing'). Paths are
|
|
1031
|
-
* filtered by the scan config and hashed at observation time.
|
|
1032
|
-
*/
|
|
1033
|
-
noteDebouncedWatchEvents(absPaths) {
|
|
1034
|
-
if (!this.rootDir) return;
|
|
1035
|
-
const additions = this.pendingFromBatch({ modifiedFiles: new Set(absPaths) });
|
|
1036
|
-
if (additions.length === 0) return;
|
|
1037
|
-
this.beginStatusAttempt();
|
|
1038
|
-
for (const [key, hash] of additions) this.debouncePending.set(key, hash);
|
|
1039
|
-
if (this.debounceStatusWriteScheduled) return;
|
|
1040
|
-
this.debounceStatusWriteScheduled = true;
|
|
1041
|
-
queueMicrotask(() => {
|
|
1042
|
-
this.debounceStatusWriteScheduled = false;
|
|
1043
|
-
if (this.debouncePending.size === 0) return;
|
|
1044
|
-
try {
|
|
1045
|
-
this.writeAnalysisStatus("debouncing", [...this.debouncePending.entries()]);
|
|
1046
|
-
} catch (err) {
|
|
1047
|
-
this.warn(`debounce status write failed: ${String(err)}`);
|
|
1048
|
-
}
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
/** One pending microtask flushes a burst of debounce observations. */
|
|
1052
|
-
debounceStatusWriteScheduled = false;
|
|
1053
|
-
/**
|
|
1054
|
-
* Write one payload artifact into the session directory, enveloped with
|
|
1055
|
-
* `{sessionId, generation, replacementEpoch, payloadHash}` and rewritten
|
|
1056
|
-
* only when the PAYLOAD bytes changed (byte-identical re-analyses leave
|
|
1057
|
-
* disk untouched — spec: "Unchanged manifest is not rewritten"). The
|
|
1058
|
-
* recorded diskHash (hash of the enveloped bytes) feeds the
|
|
1059
|
-
* analysis-commit.
|
|
1060
|
-
*/
|
|
1061
|
-
publishPayloadArtifact(key, name, payload, epoch, generation, wrap) {
|
|
1062
|
-
const payloadHash = (0, _animus_ui_extract_pipeline.contentHash)(payload);
|
|
1063
|
-
if (this.artifactRecords[key] === null) this.artifactRecords[key] = this.seedPayloadRecord(key, name);
|
|
1064
|
-
const record = this.artifactRecords[key];
|
|
1065
|
-
if (record !== null && record.payloadHash === payloadHash) return;
|
|
1066
|
-
const envelope = {
|
|
1067
|
-
sessionId: this.sessionId,
|
|
1068
|
-
generation,
|
|
1069
|
-
replacementEpoch: epoch,
|
|
1070
|
-
payloadHash
|
|
1071
|
-
};
|
|
1072
|
-
const bytes = wrap(payload, JSON.stringify(envelope));
|
|
1073
|
-
this.writeSessionArtifact(name, bytes);
|
|
1074
|
-
this.artifactRecords[key] = {
|
|
1075
|
-
payloadHash,
|
|
1076
|
-
diskHash: (0, _animus_ui_extract_pipeline.contentHash)(bytes)
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
/** Reconstruct a payload write guard from the on-disk artifact's envelope
|
|
1080
|
-
* (same-session restart: a byte-identical payload must not rewrite). */
|
|
1081
|
-
seedPayloadRecord(key, name) {
|
|
1082
|
-
let bytes;
|
|
1083
|
-
try {
|
|
1084
|
-
bytes = (0, fs.readFileSync)((0, path.join)(this.sessionDir, name), "utf-8");
|
|
1085
|
-
} catch {
|
|
1086
|
-
return null;
|
|
1087
|
-
}
|
|
1088
|
-
let envelope;
|
|
1089
|
-
try {
|
|
1090
|
-
envelope = key === "styles" ? require_session_paths.readCssEnvelope(bytes) : require_session_paths.readJsonEnvelope(bytes);
|
|
1091
|
-
} catch {
|
|
1092
|
-
return null;
|
|
1093
|
-
}
|
|
1094
|
-
if (!envelope || typeof envelope.payloadHash !== "string") return null;
|
|
1095
|
-
return {
|
|
1096
|
-
payloadHash: envelope.payloadHash,
|
|
1097
|
-
diskHash: (0, _animus_ui_extract_pipeline.contentHash)(bytes)
|
|
1098
|
-
};
|
|
1099
|
-
}
|
|
1100
|
-
/** Last analysis-commit persisted in this session's directory, or null. */
|
|
1101
|
-
seedCommitFromDisk() {
|
|
1102
|
-
try {
|
|
1103
|
-
const parsed = JSON.parse((0, fs.readFileSync)(require_session_paths.analysisCommitPath(this.sessionDir), "utf-8"));
|
|
1104
|
-
return parsed.schema === 1 && parsed.sessionId === this.sessionId && typeof parsed.generation === "number" ? parsed : null;
|
|
1105
|
-
} catch {
|
|
1106
|
-
return null;
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
/**
|
|
1110
|
-
* Publish the analysis-commit — the transaction identity (design D1).
|
|
1111
|
-
* Written AFTER every payload and BEFORE the epoch; skipped entirely when
|
|
1112
|
-
* the payload set (disk hashes) and epoch are unchanged, so a no-op
|
|
1113
|
-
* re-analysis neither re-keys loader-worker hydration nor burns a
|
|
1114
|
-
* generation.
|
|
1115
|
-
*/
|
|
1116
|
-
publishAnalysisCommit(epoch, generation) {
|
|
1117
|
-
const manifestHash = this.artifactRecords.manifest?.diskHash ?? "";
|
|
1118
|
-
const inputsHash = this.persistAnalysisInputs ? this.artifactRecords.inputs?.diskHash ?? "" : void 0;
|
|
1119
|
-
const stylesHash = this.artifactRecords.styles?.diskHash ?? "";
|
|
1120
|
-
const prev = this.lastCommit;
|
|
1121
|
-
if (prev !== null && prev.manifestHash === manifestHash && prev.inputsHash === inputsHash && prev.stylesHash === stylesHash && prev.replacementEpoch === epoch) return;
|
|
1122
|
-
const commit = {
|
|
1123
|
-
schema: 1,
|
|
1124
|
-
sessionId: this.sessionId,
|
|
1125
|
-
generation,
|
|
1126
|
-
replacementEpoch: epoch,
|
|
1127
|
-
manifestHash,
|
|
1128
|
-
...inputsHash !== void 0 ? { inputsHash } : {},
|
|
1129
|
-
stylesHash
|
|
1130
|
-
};
|
|
1131
|
-
this.writeSessionArtifact(require_session_paths.ANALYSIS_COMMIT_ARTIFACT, JSON.stringify(commit));
|
|
1132
|
-
this.lastCommit = commit;
|
|
1133
|
-
}
|
|
1134
|
-
/**
|
|
1135
|
-
* Publish the canonical replacement epoch: maintain the session-scoped
|
|
1136
|
-
* disk witness `{schema, sessionId, epoch}`, rewritten ONLY when the
|
|
1137
|
-
* epoch VALUE changes so style-only analyses and same-session restarts
|
|
1138
|
-
* leave bytes and mtime untouched, then expose the value through the
|
|
1139
|
-
* singleton for the plugin's needBuild fan-out and the loader's catch-up
|
|
1140
|
-
* re-check. Every VALUE move also reconciles sibling sessions' epoch
|
|
1141
|
-
* artifacts (below) — the webpack cold-cache validity witness.
|
|
1142
|
-
*/
|
|
1143
|
-
publishReplacementEpoch(epoch) {
|
|
1144
|
-
const onDisk = this.diskEpochValue();
|
|
1145
|
-
if (this.lastEpochValue === null) this.lastEpochValue = onDisk;
|
|
1146
|
-
if (epoch !== onDisk) this.writeSessionArtifact(require_session_paths.REPLACEMENT_EPOCH_ARTIFACT, JSON.stringify({
|
|
1147
|
-
schema: 1,
|
|
1148
|
-
sessionId: this.sessionId,
|
|
1149
|
-
epoch
|
|
1150
|
-
}));
|
|
1151
|
-
if (epoch !== this.lastEpochValue) {
|
|
1152
|
-
this.lastEpochValue = epoch;
|
|
1153
|
-
this.reconcileSiblingEpochs(epoch);
|
|
1154
|
-
}
|
|
1155
|
-
require_singleton.setReplacementEpoch(epoch);
|
|
1156
|
-
}
|
|
1157
|
-
/**
|
|
1158
|
-
* Sibling epoch reconciliation — the cross-session half of the webpack
|
|
1159
|
-
* persistent-cache witness (spec: dev-served-transform-coherence,
|
|
1160
|
-
* "Offline change invalidates restored modules"). Restored-module
|
|
1161
|
-
* snapshots reference the epoch artifact of the session that BUILT them;
|
|
1162
|
-
* with session-scoped trees that file would otherwise sit untouched
|
|
1163
|
-
* forever, keeping stale snapshots valid. Whenever this session's epoch
|
|
1164
|
-
* value moves, delete every sibling epoch artifact that disagrees (their
|
|
1165
|
-
* snapshots must invalidate — restore-on-demand then rebuilds from the
|
|
1166
|
-
* current generation) and leave agreeing siblings byte-untouched (warm
|
|
1167
|
-
* restores stay valid). Deleting a stale artifact is pruning, not a
|
|
1168
|
-
* foreign-session write; races with concurrent sessions are tolerated
|
|
1169
|
-
* (S14).
|
|
1170
|
-
*/
|
|
1171
|
-
/** Sibling ids already reconciled away (artifact deleted or absent) —
|
|
1172
|
-
* nothing left to invalidate for them on later moves. */
|
|
1173
|
-
reconciledSiblingIds = /* @__PURE__ */ new Set();
|
|
1174
|
-
/** sessions-root listing memo, keyed by the root dir's mtime — a new or
|
|
1175
|
-
* pruned sibling DIRECTORY moves it; agreeing siblings stay listed. */
|
|
1176
|
-
siblingListing = null;
|
|
1177
|
-
reconcileSiblingEpochs(epoch) {
|
|
1178
|
-
const rootPath = require_session_paths.sessionsRootDir(this.rootDir);
|
|
1179
|
-
let entries;
|
|
1180
|
-
try {
|
|
1181
|
-
const mtimeMs = (0, fs.statSync)(rootPath).mtimeMs;
|
|
1182
|
-
if (this.siblingListing?.mtimeMs === mtimeMs) entries = this.siblingListing.entries;
|
|
1183
|
-
else {
|
|
1184
|
-
entries = (0, fs.readdirSync)(rootPath);
|
|
1185
|
-
this.siblingListing = {
|
|
1186
|
-
mtimeMs,
|
|
1187
|
-
entries
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
|
-
} catch {
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
for (const entry of entries) {
|
|
1194
|
-
if (entry === this.sessionId) continue;
|
|
1195
|
-
if (this.reconciledSiblingIds.has(entry)) continue;
|
|
1196
|
-
const siblingEpochPath = (0, path.join)(rootPath, entry, require_session_paths.REPLACEMENT_EPOCH_ARTIFACT);
|
|
1197
|
-
try {
|
|
1198
|
-
if (JSON.parse((0, fs.readFileSync)(siblingEpochPath, "utf-8")).epoch === epoch) continue;
|
|
1199
|
-
} catch (err) {
|
|
1200
|
-
if (err?.code === "ENOENT") {
|
|
1201
|
-
this.reconciledSiblingIds.add(entry);
|
|
1202
|
-
continue;
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
try {
|
|
1206
|
-
(0, fs.unlinkSync)(siblingEpochPath);
|
|
1207
|
-
} catch {}
|
|
1208
|
-
this.reconciledSiblingIds.add(entry);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
/** Epoch value held by this session's on-disk artifact, or null when
|
|
1212
|
-
* absent, unreadable, or not the expected schema. */
|
|
1213
|
-
diskEpochValue() {
|
|
1214
|
-
try {
|
|
1215
|
-
const parsed = JSON.parse((0, fs.readFileSync)(require_session_paths.replacementEpochPath(this.sessionDir), "utf-8"));
|
|
1216
|
-
return parsed.schema === 1 && typeof parsed.epoch === "string" ? parsed.epoch : null;
|
|
1217
|
-
} catch {
|
|
1218
|
-
return null;
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
/** Session-start hygiene (design D2): delete legacy flat artifacts
|
|
1222
|
-
* (unreachable by session-scoped loaders) and prune sibling session
|
|
1223
|
-
* directories older than the retention window — never the own dir,
|
|
1224
|
-
* tolerating races with concurrently-pruning sessions (S14). */
|
|
1225
|
-
runSessionStartHygiene() {
|
|
1226
|
-
if (this.sessionStartHygieneDone) return;
|
|
1227
|
-
this.sessionStartHygieneDone = true;
|
|
1228
|
-
const animusDir = (0, path.join)(this.rootDir, ".animus");
|
|
1229
|
-
for (const name of LEGACY_FLAT_ARTIFACTS) try {
|
|
1230
|
-
(0, fs.unlinkSync)((0, path.join)(animusDir, name));
|
|
1231
|
-
} catch {}
|
|
1232
|
-
let entries;
|
|
1233
|
-
try {
|
|
1234
|
-
entries = (0, fs.readdirSync)(require_session_paths.sessionsRootDir(this.rootDir));
|
|
1235
|
-
} catch {
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
const cutoff = Date.now() - SESSION_DIR_MAX_AGE_MS;
|
|
1239
|
-
for (const entry of entries) {
|
|
1240
|
-
if (entry === this.sessionId) continue;
|
|
1241
|
-
const dir = (0, path.join)(require_session_paths.sessionsRootDir(this.rootDir), entry);
|
|
1242
|
-
try {
|
|
1243
|
-
if ((0, fs.statSync)(dir).mtimeMs < cutoff) (0, fs.rmSync)(dir, {
|
|
1244
|
-
recursive: true,
|
|
1245
|
-
force: true
|
|
1246
|
-
});
|
|
1247
|
-
} catch {}
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
/** Ensure the session directory exists and write one artifact into it.
|
|
1251
|
-
* Write-then-rename so cross-process readers (Turbopack loader workers)
|
|
1252
|
-
* can never observe a torn half-written file. The tmp name carries the
|
|
1253
|
-
* pid — Next dev evaluates the config in more than one process, and two
|
|
1254
|
-
* sessions writing the same artifact must not race on one tmp path. */
|
|
1255
|
-
writeSessionArtifact(name, content) {
|
|
1256
|
-
const dir = this.sessionDir;
|
|
1257
|
-
(0, fs.mkdirSync)(dir, { recursive: true });
|
|
1258
|
-
const tmpPath = (0, path.join)(dir, `.${name}.${process.pid}.tmp`);
|
|
1259
|
-
(0, fs.writeFileSync)(tmpPath, content);
|
|
1260
|
-
(0, fs.renameSync)(tmpPath, (0, path.join)(dir, name));
|
|
1261
|
-
this.onArtifactWrite?.(name, content);
|
|
1262
|
-
}
|
|
1263
|
-
/**
|
|
1264
|
-
* asset() placeholder substitution (global-styles-system): resolve each
|
|
1265
|
-
* referenced specifier through Node resolution, copy the bytes into the
|
|
1266
|
-
* session directory's `assets/` under a content-hashed name, and
|
|
1267
|
-
* substitute a RELATIVE url — relative to the session-scoped styles.css,
|
|
1268
|
-
* which sits beside `assets/`, so the emitted `./assets/<file>` form is
|
|
1269
|
-
* unchanged. The stylesheet is processed by Next's own CSS pipeline
|
|
1270
|
-
* (webpack and Turbopack alike), which applies its native asset handling
|
|
1271
|
-
* — publicPath and output hashing — to relative url() references.
|
|
1272
|
-
* Unsubstitutable specifiers warn and emit literally in non-strict mode,
|
|
1273
|
-
* fail the build under `strict: true`.
|
|
1274
|
-
*/
|
|
1275
|
-
substituteAssetReferences(globalCss, devMode) {
|
|
1276
|
-
const specifiers = (0, _animus_ui_extract_pipeline.findAssetSpecifiers)(globalCss);
|
|
1277
|
-
const assetsDir = (0, path.join)(this.sessionDir, require_session_paths.SESSION_ASSETS_DIR);
|
|
1278
|
-
const expected = /* @__PURE__ */ new Set();
|
|
1279
|
-
this.assetDependencyPaths.clear();
|
|
1280
|
-
this.assetDependencyKeys.clear();
|
|
1281
|
-
const urlBySpecifier = /* @__PURE__ */ new Map();
|
|
1282
|
-
for (const specifier of specifiers) {
|
|
1283
|
-
const cached = this.assetCopyCache.get(specifier);
|
|
1284
|
-
if (cached && (0, fs.existsSync)((0, path.join)(assetsDir, cached.fileName))) try {
|
|
1285
|
-
const current = (0, fs.statSync)(cached.sourcePath);
|
|
1286
|
-
if (current.mtimeMs === cached.mtimeMs && current.size === cached.size) {
|
|
1287
|
-
this.trackAssetDependency(cached.sourcePath);
|
|
1288
|
-
expected.add(cached.fileName);
|
|
1289
|
-
urlBySpecifier.set(specifier, cached.url);
|
|
1290
|
-
continue;
|
|
1291
|
-
}
|
|
1292
|
-
} catch {}
|
|
1293
|
-
const resolvedPath = this.resolveAssetSpecifier(specifier);
|
|
1294
|
-
if (!resolvedPath) {
|
|
1295
|
-
const message = `unresolvable asset() specifier: ${specifier}`;
|
|
1296
|
-
if (this.options.strict) throw new Error(`[animus-next] ${message}`);
|
|
1297
|
-
this.warn(message);
|
|
1298
|
-
urlBySpecifier.set(specifier, specifier);
|
|
1299
|
-
continue;
|
|
1300
|
-
}
|
|
1301
|
-
const bytes = (0, fs.readFileSync)(resolvedPath);
|
|
1302
|
-
const sourceStat = (0, fs.statSync)(resolvedPath);
|
|
1303
|
-
this.trackAssetDependency(resolvedPath);
|
|
1304
|
-
const ext = (0, path.extname)(resolvedPath);
|
|
1305
|
-
const fileName = `${(0, path.basename)(resolvedPath, ext)}.${(0, _animus_ui_extract_pipeline.contentHash)(bytes).slice(0, 8)}${ext}`;
|
|
1306
|
-
expected.add(fileName);
|
|
1307
|
-
if (!(0, fs.existsSync)(assetsDir)) (0, fs.mkdirSync)(assetsDir, { recursive: true });
|
|
1308
|
-
const assetPath = (0, path.join)(assetsDir, fileName);
|
|
1309
|
-
if (!(0, fs.existsSync)(assetPath)) (0, fs.writeFileSync)(assetPath, bytes);
|
|
1310
|
-
const url = `./assets/${fileName}`;
|
|
1311
|
-
urlBySpecifier.set(specifier, url);
|
|
1312
|
-
this.assetCopyCache.set(specifier, {
|
|
1313
|
-
sourcePath: resolvedPath,
|
|
1314
|
-
mtimeMs: sourceStat.mtimeMs,
|
|
1315
|
-
size: sourceStat.size,
|
|
1316
|
-
fileName,
|
|
1317
|
-
url
|
|
1318
|
-
});
|
|
1319
|
-
}
|
|
1320
|
-
if (!devMode) pruneStaleAssets(assetsDir, expected);
|
|
1321
|
-
return (0, _animus_ui_extract_pipeline.substituteAssetPlaceholders)(globalCss, urlBySpecifier);
|
|
1322
|
-
}
|
|
1323
|
-
trackAssetDependency(path$9) {
|
|
1324
|
-
this.assetDependencyPaths.add(path$9);
|
|
1325
|
-
for (const key of (0, _animus_ui_extract_pipeline.toWatchKeys)(path$9)) this.assetDependencyKeys.add(key);
|
|
1326
|
-
}
|
|
1327
|
-
/**
|
|
1328
|
-
* Resolve an asset specifier to an absolute file via the shared pipeline
|
|
1329
|
-
* resolver (host aliases → Node resolution → package root), with one
|
|
1330
|
-
* session-local last resort: an already-discovered source entry's package
|
|
1331
|
-
* root (dist-less workspace kits the shared resolver cannot see).
|
|
1332
|
-
*/
|
|
1333
|
-
resolveAssetSpecifier(specifier) {
|
|
1334
|
-
const resolved = (0, _animus_ui_extract_pipeline.resolveAssetFile)(specifier, this.rootDir, this.pathAliasesJson);
|
|
1335
|
-
if (resolved) return resolved;
|
|
1336
|
-
const segments = specifier.split("/");
|
|
1337
|
-
const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
|
|
1338
|
-
const subpath = specifier.slice(packageName.length + 1);
|
|
1339
|
-
if (!subpath) return null;
|
|
1340
|
-
const sourceEntry = this.externalSourceEntries.get(packageName) ?? [...this.externalSourceEntries].find(([declared]) => declared === packageName || declared.startsWith(`${packageName}/`))?.[1];
|
|
1341
|
-
if (sourceEntry) {
|
|
1342
|
-
const candidate = (0, path.join)((0, _animus_ui_extract_pipeline.findPackageRoot)(sourceEntry), subpath);
|
|
1343
|
-
if ((0, fs.existsSync)(candidate)) return candidate;
|
|
1344
|
-
}
|
|
1345
|
-
return null;
|
|
1346
|
-
}
|
|
1347
|
-
};
|
|
1348
|
-
/**
|
|
1349
|
-
* Sync `.animus/assets/` to the current build's content-hashed file set:
|
|
1350
|
-
* anything else in the directory is a superseded revision (the copies are
|
|
1351
|
-
* content-addressed and never overwritten) or the leftover of an asset()
|
|
1352
|
-
* reference that no longer exists. Failures are tolerated per entry — Next
|
|
1353
|
-
* dev evaluates the config in more than one process, and a concurrent
|
|
1354
|
-
* session may have removed (or be about to rewrite) the same file; every
|
|
1355
|
-
* pass rewrites whatever of its own set is missing, so races self-heal.
|
|
1356
|
-
*/
|
|
1357
|
-
function pruneStaleAssets(assetsDir, expected) {
|
|
1358
|
-
if (!(0, fs.existsSync)(assetsDir)) return;
|
|
1359
|
-
let entries;
|
|
1360
|
-
try {
|
|
1361
|
-
entries = (0, fs.readdirSync)(assetsDir);
|
|
1362
|
-
} catch {
|
|
1363
|
-
return;
|
|
1364
|
-
}
|
|
1365
|
-
for (const entry of entries) {
|
|
1366
|
-
if (expected.has(entry)) continue;
|
|
1367
|
-
try {
|
|
1368
|
-
(0, fs.unlinkSync)((0, path.join)(assetsDir, entry));
|
|
1369
|
-
} catch {}
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
//#endregion
|
|
1373
7
|
//#region src/loader-path.ts
|
|
1374
|
-
const
|
|
1375
|
-
|
|
1376
|
-
* The one loader-path derivation: first existing dist candidate under the
|
|
1377
|
-
* plugin dir, else the in-repo source fallback. Both loader resolvers below
|
|
1378
|
-
* are thin wrappers, so rule registration and any loader-chain predicate
|
|
1379
|
-
* can never drift (openspec: next-webpack-served-transform-coherence,
|
|
1380
|
-
* design D1).
|
|
1381
|
-
*/
|
|
8
|
+
const __filename$2 = (0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
|
|
9
|
+
const __dirname$2 = (0, path.dirname)(__filename$2);
|
|
1382
10
|
function resolveLoaderPath(pluginDir, distCandidates, sourceFallback) {
|
|
1383
11
|
for (const candidate of distCandidates) {
|
|
1384
12
|
const distPath = (0, path.resolve)(pluginDir, candidate);
|
|
@@ -1386,99 +14,65 @@ function resolveLoaderPath(pluginDir, distCandidates, sourceFallback) {
|
|
|
1386
14
|
}
|
|
1387
15
|
return (0, path.resolve)(pluginDir, sourceFallback);
|
|
1388
16
|
}
|
|
1389
|
-
/**
|
|
1390
|
-
* This package's webpack loader path, shared by the rule registration
|
|
1391
|
-
* (with-animus) and the needBuild loader-chain predicate (plugin). Compiled
|
|
1392
|
-
* dist ships `loader.mjs` next to this module; running from source falls
|
|
1393
|
-
* back to the TypeScript loader.
|
|
1394
|
-
*/
|
|
1395
17
|
function resolveAnimusLoaderPath() {
|
|
1396
18
|
return resolveLoaderPath(__dirname$2, ["loader.mjs"], "loader.ts");
|
|
1397
19
|
}
|
|
1398
20
|
//#endregion
|
|
1399
21
|
//#region src/plugin.ts
|
|
1400
|
-
/**
|
|
1401
|
-
*
|
|
1402
|
-
|
|
1403
|
-
const
|
|
22
|
+
/** The value arrives from a consumer's `next.config`, so the union above is a
|
|
23
|
+
* model of what webpack accepts, not a proof about the value in hand. */
|
|
24
|
+
const isWatchIgnoreString = (ignored) => Object.prototype.toString.call(ignored) === "[object String]";
|
|
25
|
+
const isWatchIgnoreMatcher = (ignored) => Object.prototype.toString.call(ignored) === "[object Function]";
|
|
26
|
+
const UNSUPPORTED_WEBPACK_MESSAGE = "[animus-extract] Unsupported webpack: NormalModule.getCompilationHooks(compilation).needBuild is required for dev transform coherence and this webpack does not expose it. Use a Next.js version whose webpack exposes this hook.";
|
|
1404
27
|
const PLUGIN_NAME = "AnimusWebpackPlugin";
|
|
1405
|
-
/**
|
|
1406
|
-
* Webpack adapter for the extraction pipeline. All pipeline logic lives in
|
|
1407
|
-
* the bundler-agnostic ExtractionSession — this class owns only the webpack
|
|
1408
|
-
* wiring: hook registration, cross-compiler analysis dedup, alias
|
|
1409
|
-
* harvesting from the compiler config, watch-event translation, and
|
|
1410
|
-
* in-memory CSS asset replacement.
|
|
1411
|
-
*/
|
|
1412
28
|
var AnimusWebpackPlugin = class {
|
|
1413
29
|
options;
|
|
1414
30
|
session;
|
|
1415
31
|
initialized = false;
|
|
1416
32
|
aliasesExtracted = false;
|
|
1417
|
-
/** Loader paths counting as "the animus loader" in a module's chain —
|
|
1418
|
-
* this package's own loader plus an optional harness override. */
|
|
1419
33
|
animusLoaderPaths;
|
|
1420
|
-
/** Epoch THIS compiler last proceeded with (set after every awaited
|
|
1421
|
-
* watchRun transaction — including joined ones). Null until the
|
|
1422
|
-
* initialization pipeline publishes. */
|
|
1423
34
|
lastBuiltEpoch = null;
|
|
1424
|
-
/** Armed by watchRun when the transaction moved the epoch; captured and
|
|
1425
|
-
* disarmed by the next compilation's needBuild wiring (design D1). */
|
|
1426
35
|
epochMovedForNextCompilation = false;
|
|
1427
36
|
constructor(options) {
|
|
1428
37
|
this.options = options;
|
|
1429
38
|
(0, _animus_ui_extract_pipeline.assertNoRetiredEngineSelection)(options.engine);
|
|
1430
|
-
|
|
1431
|
-
this.session = new ExtractionSession(options);
|
|
1432
|
-
this.animusLoaderPaths = new Set([resolveAnimusLoaderPath()]);
|
|
39
|
+
(0, _animus_ui_extract_session.setSharedEngine)(options.engine ?? "v2");
|
|
40
|
+
this.session = new _animus_ui_extract_session.ExtractionSession(options);
|
|
41
|
+
this.animusLoaderPaths = /* @__PURE__ */ new Set([resolveAnimusLoaderPath()]);
|
|
1433
42
|
if (options.loaderPath) this.animusLoaderPaths.add(options.loaderPath);
|
|
1434
43
|
}
|
|
1435
|
-
/** True when the module's loader chain contains the animus loader — the
|
|
1436
|
-
* needBuild fan-out predicate (design D1: no historical records; covers
|
|
1437
|
-
* restored persistent-cache modules and raw passthroughs alike). */
|
|
1438
44
|
moduleUsesAnimusLoader(module) {
|
|
1439
45
|
const loaders = module?.loaders;
|
|
1440
46
|
if (!Array.isArray(loaders)) return false;
|
|
1441
|
-
return loaders.some((entry) =>
|
|
47
|
+
return loaders.some((entry) => entry?.loader !== void 0 && this.animusLoaderPaths.has(entry.loader));
|
|
1442
48
|
}
|
|
1443
|
-
/** Append the exact epoch artifact path to `watchOptions.ignored`,
|
|
1444
|
-
* preserving the user's shape (design D2: the artifact participates in
|
|
1445
|
-
* module snapshots via addDependency but never triggers live watch
|
|
1446
|
-
* turns — no echo compilations). Session identity is process-claimed,
|
|
1447
|
-
* so every compiler's plugin instance derives the SAME session-scoped
|
|
1448
|
-
* path here regardless of which instance ends up owning the pipeline.
|
|
1449
|
-
* Derived from the ONE root (session.rootDir, set at config time by
|
|
1450
|
-
* with-animus); compiler.context is the fallback for bare-apply
|
|
1451
|
-
* harnesses — identical when compiler.context === cwd. */
|
|
1452
49
|
appendEpochWatchIgnore(compiler) {
|
|
1453
50
|
const options = compiler.options;
|
|
1454
51
|
if (!options) return;
|
|
1455
|
-
const epochPath =
|
|
52
|
+
const epochPath = (0, _animus_ui_extract_session.replacementEpochPath)((0, _animus_ui_extract_session.sessionArtifactDir)(this.session.rootDir ?? compiler.context, this.session.sessionId));
|
|
1456
53
|
options.watchOptions ??= {};
|
|
1457
54
|
const watchOptions = options.watchOptions;
|
|
1458
55
|
const ignored = watchOptions.ignored;
|
|
1459
56
|
if (ignored === void 0 || ignored === null) watchOptions.ignored = [epochPath];
|
|
1460
57
|
else if (Array.isArray(ignored)) {
|
|
1461
58
|
if (!ignored.includes(epochPath)) ignored.push(epochPath);
|
|
1462
|
-
} else if (
|
|
59
|
+
} else if (isWatchIgnoreString(ignored)) watchOptions.ignored = [ignored, epochPath];
|
|
1463
60
|
else if (ignored instanceof RegExp) watchOptions.ignored = (path) => path === epochPath || ignored.test(path);
|
|
1464
|
-
else if (
|
|
1465
|
-
const original = ignored;
|
|
1466
|
-
watchOptions.ignored = (path) => path === epochPath || Boolean(original(path));
|
|
1467
|
-
}
|
|
61
|
+
else if (isWatchIgnoreMatcher(ignored)) watchOptions.ignored = (path) => path === epochPath || Boolean(ignored(path));
|
|
1468
62
|
}
|
|
1469
|
-
/** Extract path aliases from webpack's resolve.alias config. Runs once per
|
|
1470
|
-
* plugin instance — the resolve config is immutable after apply. */
|
|
1471
63
|
extractAliases(compiler) {
|
|
1472
64
|
if (this.aliasesExtracted) return;
|
|
1473
65
|
this.aliasesExtracted = true;
|
|
1474
66
|
const rootDir = compiler.context;
|
|
1475
67
|
const rawAlias = compiler.options?.resolve?.alias;
|
|
1476
|
-
if (
|
|
68
|
+
if (rawAlias === void 0) return;
|
|
69
|
+
if (Object.prototype.toString.call(rawAlias) !== "[object Object]") return;
|
|
1477
70
|
const pairs = [];
|
|
1478
71
|
for (const [key, value] of Object.entries(rawAlias)) {
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
if (
|
|
72
|
+
if (value === false) continue;
|
|
73
|
+
const target = Array.isArray(value) ? value.at(0) : value;
|
|
74
|
+
if (target === void 0) continue;
|
|
75
|
+
if (key === _animus_ui_extract_session.ANIMUS_CSS_MODULE_ID) continue;
|
|
1482
76
|
pairs.push({
|
|
1483
77
|
pattern: key,
|
|
1484
78
|
target
|
|
@@ -1490,13 +84,13 @@ var AnimusWebpackPlugin = class {
|
|
|
1490
84
|
apply(compiler) {
|
|
1491
85
|
if (compiler.options?.name === "edge-server") return;
|
|
1492
86
|
const NormalModule = compiler.webpack?.NormalModule;
|
|
1493
|
-
if (
|
|
87
|
+
if (NormalModule?.getCompilationHooks === void 0) throw new Error(UNSUPPORTED_WEBPACK_MESSAGE);
|
|
1494
88
|
this.appendEpochWatchIgnore(compiler);
|
|
1495
89
|
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
|
|
1496
90
|
const epochMoved = this.epochMovedForNextCompilation;
|
|
1497
91
|
this.epochMovedForNextCompilation = false;
|
|
1498
92
|
const needBuild = NormalModule.getCompilationHooks(compilation).needBuild;
|
|
1499
|
-
if (
|
|
93
|
+
if (needBuild === void 0) throw new Error(UNSUPPORTED_WEBPACK_MESSAGE);
|
|
1500
94
|
needBuild.tapAsync(PLUGIN_NAME, (module, _context, callback) => {
|
|
1501
95
|
if (epochMoved && this.moduleUsesAnimusLoader(module)) {
|
|
1502
96
|
callback(null, true);
|
|
@@ -1521,81 +115,67 @@ var AnimusWebpackPlugin = class {
|
|
|
1521
115
|
stage
|
|
1522
116
|
}, () => {
|
|
1523
117
|
registerSystemDependencies();
|
|
1524
|
-
const css =
|
|
118
|
+
const css = (0, _animus_ui_extract_session.getSharedCss)();
|
|
1525
119
|
if (!css || !RawSource) return;
|
|
1526
|
-
const
|
|
120
|
+
const rootDir = this.session.rootDir || compiler.context;
|
|
121
|
+
const cssPath = (0, _animus_ui_extract_session.stylesPath)((0, _animus_ui_extract_session.sessionArtifactDir)(rootDir, this.session.sessionId));
|
|
1527
122
|
if (compilation.getAsset(cssPath)) {
|
|
1528
123
|
compilation.updateAsset(cssPath, new RawSource(css));
|
|
1529
124
|
return;
|
|
1530
125
|
}
|
|
1531
|
-
if (compilation.getAsset(
|
|
126
|
+
if (compilation.getAsset(_animus_ui_extract_session.ANIMUS_CSS_MODULE_ID)) compilation.updateAsset(_animus_ui_extract_session.ANIMUS_CSS_MODULE_ID, new RawSource(css));
|
|
1532
127
|
});
|
|
1533
128
|
});
|
|
1534
129
|
compiler.hooks.run.tapPromise(PLUGIN_NAME, async (_compiler) => {
|
|
1535
130
|
this.adoptCompilerContext(_compiler);
|
|
1536
131
|
this.extractAliases(_compiler);
|
|
1537
|
-
const existing =
|
|
132
|
+
const existing = (0, _animus_ui_extract_session.getAnalysisStartedPromise)();
|
|
1538
133
|
if (existing) {
|
|
1539
134
|
await existing;
|
|
1540
135
|
return;
|
|
1541
136
|
}
|
|
1542
137
|
const promise = this.session.runFullPipeline();
|
|
1543
|
-
|
|
138
|
+
(0, _animus_ui_extract_session.setAnalysisStartedPromise)(promise);
|
|
1544
139
|
await promise;
|
|
1545
140
|
});
|
|
1546
141
|
compiler.hooks.watchRun.tapPromise(PLUGIN_NAME, async (_compiler) => {
|
|
1547
142
|
this.adoptCompilerContext(_compiler);
|
|
1548
143
|
this.extractAliases(_compiler);
|
|
1549
144
|
if (!this.initialized) {
|
|
1550
|
-
const existing =
|
|
145
|
+
const existing = (0, _animus_ui_extract_session.getAnalysisStartedPromise)();
|
|
1551
146
|
if (existing) await existing;
|
|
1552
147
|
else {
|
|
1553
148
|
const promise = this.session.runFullPipeline();
|
|
1554
|
-
|
|
149
|
+
(0, _animus_ui_extract_session.setAnalysisStartedPromise)(promise);
|
|
1555
150
|
await promise;
|
|
1556
151
|
}
|
|
1557
152
|
this.initialized = true;
|
|
1558
|
-
this.lastBuiltEpoch =
|
|
153
|
+
this.lastBuiltEpoch = (0, _animus_ui_extract_session.getReplacementEpoch)();
|
|
1559
154
|
return;
|
|
1560
155
|
}
|
|
1561
156
|
await this.session.handleWatchUpdate({
|
|
1562
157
|
modifiedFiles: _compiler.modifiedFiles,
|
|
1563
158
|
removedFiles: _compiler.removedFiles
|
|
1564
159
|
});
|
|
1565
|
-
const epoch =
|
|
160
|
+
const epoch = (0, _animus_ui_extract_session.getReplacementEpoch)();
|
|
1566
161
|
this.epochMovedForNextCompilation = this.lastBuiltEpoch !== null && epoch !== null && epoch !== this.lastBuiltEpoch;
|
|
1567
162
|
if (epoch !== null) this.lastBuiltEpoch = epoch;
|
|
1568
163
|
});
|
|
1569
164
|
}
|
|
1570
|
-
/**
|
|
1571
|
-
* Reset analysis state for HMR geological reset.
|
|
1572
|
-
*/
|
|
1573
165
|
resetForHmr() {
|
|
1574
166
|
this.session.resetForHmr();
|
|
1575
167
|
}
|
|
1576
|
-
/** Session identity (process-claimed) — with-animus derives the
|
|
1577
|
-
* session-scoped alias/stub paths from it (design D2). */
|
|
1578
168
|
get sessionId() {
|
|
1579
169
|
return this.session.sessionId;
|
|
1580
170
|
}
|
|
1581
|
-
/** Set the project root at CONFIG time (with-animus) — the ONE root all
|
|
1582
|
-
* session-path derivations use. The compiler hooks ADOPT
|
|
1583
|
-
* compiler.context only when no root was published (bare-apply
|
|
1584
|
-
* harnesses); they never overwrite this. */
|
|
1585
171
|
setRootDir(rootDir) {
|
|
1586
172
|
this.session.rootDir = rootDir;
|
|
1587
173
|
}
|
|
1588
|
-
/** One warning per plugin instance for a context/root mismatch. */
|
|
1589
174
|
warnedRootDivergence = false;
|
|
1590
|
-
/**
|
|
1591
|
-
*
|
|
1592
|
-
*
|
|
1593
|
-
*
|
|
1594
|
-
* silent overwrite re-keys sessionDir so the pipeline publishes
|
|
1595
|
-
* artifacts where none of those consumers look. A disagreement is
|
|
1596
|
-
* surfaced loudly instead (with-animus derives the root from Next's own
|
|
1597
|
-
* `dir`, which equals compiler.context in real Next runs — a mismatch
|
|
1598
|
-
* means a custom-webpack setup wired the plugin differently). */
|
|
175
|
+
/** Adopts `compiler.context` only when no config-time root exists: every
|
|
176
|
+
* frozen derivation came from that root, so an overwrite re-keys it.
|
|
177
|
+
* Stub path, css alias, virtual system-props target and watch-ignore are
|
|
178
|
+
* all frozen from it, so a disagreement is warned, never reconciled. */
|
|
1599
179
|
adoptCompilerContext(compiler) {
|
|
1600
180
|
const context = compiler.context;
|
|
1601
181
|
if (!this.session.rootDir) {
|
|
@@ -1607,75 +187,47 @@ var AnimusWebpackPlugin = class {
|
|
|
1607
187
|
console.warn(`[animus-extract] compiler.context (${context}) differs from the configured project root (${this.session.rootDir}); keeping the configured root — run next against the app directory (or align your custom webpack context) so both agree`);
|
|
1608
188
|
}
|
|
1609
189
|
}
|
|
1610
|
-
/** This session's artifact directory, derived from the one root. */
|
|
1611
190
|
get sessionDir() {
|
|
1612
191
|
return this.session.sessionDir;
|
|
1613
192
|
}
|
|
1614
|
-
/** Expose options for the loader */
|
|
1615
193
|
getOptions() {
|
|
1616
194
|
return this.options;
|
|
1617
195
|
}
|
|
1618
|
-
/** Expose external package directories for webpack loader allowlisting.
|
|
1619
|
-
* Falls back to shared globalThis state for non-owning compiler instances. */
|
|
1620
196
|
getExternalPackageDirs() {
|
|
1621
|
-
return this.session.externalPackageDirs.length > 0 ? this.session.externalPackageDirs :
|
|
197
|
+
return this.session.externalPackageDirs.length > 0 ? this.session.externalPackageDirs : (0, _animus_ui_extract_session.getSharedExternalDirs)();
|
|
1622
198
|
}
|
|
1623
|
-
/** Expose external package source entries for webpack resolve alias.
|
|
1624
|
-
* Falls back to shared globalThis state for non-owning compiler instances. */
|
|
1625
199
|
getExternalSourceEntries() {
|
|
1626
|
-
return this.session.externalSourceEntries.size > 0 ? this.session.externalSourceEntries :
|
|
200
|
+
return this.session.externalSourceEntries.size > 0 ? this.session.externalSourceEntries : (0, _animus_ui_extract_session.getSharedExternalEntries)();
|
|
1627
201
|
}
|
|
1628
202
|
};
|
|
1629
203
|
//#endregion
|
|
1630
204
|
//#region src/turbopack-config.ts
|
|
1631
|
-
/** The
|
|
1632
|
-
*
|
|
1633
|
-
*
|
|
1634
|
-
* the loader
|
|
1635
|
-
const ANIMUS_TURBOPACK_RULE_GLOB = "
|
|
1636
|
-
/** Virtual system-props id emitted into transformed sources under Turbopack
|
|
1637
|
-
* (absolute-path imports are rejected there); resolveAlias maps it to the
|
|
1638
|
-
* on-disk artifact. */
|
|
1639
|
-
const TURBOPACK_SYSTEM_PROPS_ID = "virtual:animus/system-props";
|
|
1640
|
-
/**
|
|
1641
|
-
* Resolve whether Turbopack wiring is active for this process. Default is
|
|
1642
|
-
* `'auto'`: active exactly when the TURBOPACK environment variable is set
|
|
1643
|
-
* (Next sets it for every Turbopack dev/build). The stable `turbopack`
|
|
1644
|
-
* option wins over the deprecated `unstable_turbopack` alias.
|
|
1645
|
-
*/
|
|
205
|
+
/** The one glob the loader registers under, derived from the shared engine
|
|
206
|
+
* extension set so the bundler families cannot drift. `.mjs` belongs in that
|
|
207
|
+
* set: an external package with no src/ is ingested through its dist entry
|
|
208
|
+
* and must still reach the loader, so do not trim it. */
|
|
209
|
+
const ANIMUS_TURBOPACK_RULE_GLOB = `*.{${_animus_ui_extract_pipeline.ENGINE_TRANSFORM_EXTENSIONS.join(",")}}`;
|
|
1646
210
|
function resolveTurbopackMode(options, env = process.env) {
|
|
1647
211
|
const mode = options.turbopack?.mode ?? options.unstable_turbopack?.mode ?? "auto";
|
|
1648
212
|
if (mode === "on") return true;
|
|
1649
213
|
if (mode === "auto") return env.TURBOPACK !== void 0;
|
|
1650
214
|
return false;
|
|
1651
215
|
}
|
|
1652
|
-
/** rootDir-relative module request for an absolute path — always forward
|
|
1653
|
-
* slashes, even when path.relative produced Windows separators. */
|
|
1654
216
|
function rootRelativeRequest(rootDir, absPath) {
|
|
1655
217
|
return `./${(0, path.relative)(rootDir, absPath).replace(/\\/g, "/")}`;
|
|
1656
218
|
}
|
|
1657
|
-
/**
|
|
1658
|
-
* Build the `turbopack` config fragment: one glob-keyed loader rule
|
|
1659
|
-
* (file-level allowlisting lives in the loader via manifest lookup — Next 15
|
|
1660
|
-
* rules have no condition algebra) plus resolve aliases for the virtual
|
|
1661
|
-
* system-props id, the emitter's stylesheet id, and each collected external
|
|
1662
|
-
* package specifier redirected to its source entry. Loader options carry
|
|
1663
|
-
* the session identity (design D2: an options-borne, JSON-serializable
|
|
1664
|
-
* Turbopack task input — restart-cold by construction), and the artifact
|
|
1665
|
-
* aliases point into the session-scoped tree.
|
|
1666
|
-
*/
|
|
1667
219
|
function buildTurbopackConfig(args) {
|
|
1668
220
|
const { rootDir, loaderPath, options, externalSourceEntries, sessionId, sessionDir } = args;
|
|
1669
221
|
const loaderOptions = {
|
|
1670
222
|
rootDir,
|
|
1671
223
|
sessionId,
|
|
1672
|
-
sessionDir
|
|
1673
|
-
...options.strict !== void 0 ? { strict: options.strict } : {},
|
|
1674
|
-
...options.cssImportTarget !== void 0 ? { cssImportTarget: options.cssImportTarget } : {}
|
|
224
|
+
sessionDir
|
|
1675
225
|
};
|
|
226
|
+
if (options.strict !== void 0) loaderOptions.strict = options.strict;
|
|
227
|
+
if (options.cssImportTarget !== void 0) loaderOptions.cssImportTarget = options.cssImportTarget;
|
|
1676
228
|
const resolveAlias = {
|
|
1677
|
-
[TURBOPACK_SYSTEM_PROPS_ID]: rootRelativeRequest(rootDir, (0, path.join)(sessionDir,
|
|
1678
|
-
|
|
229
|
+
[_animus_ui_extract_session.TURBOPACK_SYSTEM_PROPS_ID]: rootRelativeRequest(rootDir, (0, path.join)(sessionDir, _animus_ui_extract_session.SYSTEM_PROPS_ARTIFACT)),
|
|
230
|
+
[_animus_ui_extract_session.ANIMUS_CSS_MODULE_ID]: rootRelativeRequest(rootDir, (0, path.join)(sessionDir, _animus_ui_extract_session.STYLES_ARTIFACT))
|
|
1679
231
|
};
|
|
1680
232
|
for (const [specifier, srcEntry] of externalSourceEntries) resolveAlias[specifier] = rootRelativeRequest(rootDir, srcEntry);
|
|
1681
233
|
return {
|
|
@@ -1686,237 +238,29 @@ function buildTurbopackConfig(args) {
|
|
|
1686
238
|
resolveAlias
|
|
1687
239
|
};
|
|
1688
240
|
}
|
|
1689
|
-
/**
|
|
1690
|
-
* Locate the Turbopack loader module: built dist first, source fallback for
|
|
1691
|
-
* in-repo development. CJS preferred — Turbopack's loader-runner follows
|
|
1692
|
-
* the webpack loader contract (`module.exports = fn`), and the .cjs build
|
|
1693
|
-
* is loadable by every runner version; ESM default-export acceptance is
|
|
1694
|
-
* not guaranteed.
|
|
1695
|
-
*/
|
|
1696
241
|
function resolveTurbopackLoaderPath(pluginDir) {
|
|
1697
242
|
return resolveLoaderPath(pluginDir, ["turbopack-loader.cjs", "turbopack-loader.mjs"], "turbopack-loader.ts");
|
|
1698
243
|
}
|
|
1699
244
|
//#endregion
|
|
1700
|
-
//#region src/turbopack-orchestrator.ts
|
|
1701
|
-
/**
|
|
1702
|
-
* Out-of-band pipeline orchestration for Turbopack (spec:
|
|
1703
|
-
* next-turbopack-integration). Turbopack exposes no compiler hooks, so the
|
|
1704
|
-
* pipeline runs during next.config resolution and, in dev, from a
|
|
1705
|
-
* filesystem watcher — the bundler only ever sees the `.animus/` disk
|
|
1706
|
-
* artifacts.
|
|
1707
|
-
*/
|
|
1708
|
-
/** Run the full pipeline with the Turbopack emitter identity: the virtual
|
|
1709
|
-
* system-props id (Turbopack rejects the webpack path's absolute-path
|
|
1710
|
-
* imports). Turbopack orchestration persists the analysis-inputs
|
|
1711
|
-
* hydration corpus — its isolated loader workers replay it (spec:
|
|
1712
|
-
* next-turbopack-integration, "Manifest disk artifact"; webpack mode
|
|
1713
|
-
* skips the corpus). */
|
|
1714
|
-
async function runTurbopackPipeline(session) {
|
|
1715
|
-
session.systemPropsModuleId = TURBOPACK_SYSTEM_PROPS_ID;
|
|
1716
|
-
session.persistAnalysisInputs = true;
|
|
1717
|
-
await session.runFullPipeline();
|
|
1718
|
-
}
|
|
1719
|
-
const activeWatcherRoots = /* @__PURE__ */ new Set();
|
|
1720
|
-
const IGNORED_SEGMENTS = new Set([
|
|
1721
|
-
".animus",
|
|
1722
|
-
".next",
|
|
1723
|
-
"node_modules"
|
|
1724
|
-
]);
|
|
1725
|
-
/**
|
|
1726
|
-
* Start the dev watcher: fs.watch per eligible top-level directory (plus a
|
|
1727
|
-
* non-recursive watch on the root itself), debounced into
|
|
1728
|
-
* existence-partitioned modified/removed sets feeding
|
|
1729
|
-
* `session.handleWatchUpdate` (serialized — updates never overlap).
|
|
1730
|
-
* Generated and vendored trees (`.animus`, `.next`, node_modules) are never
|
|
1731
|
-
* registered with the OS — recursive registration of those trees exhausts
|
|
1732
|
-
* inotify/kqueue descriptors (EMFILE/ENOSPC) on large projects.
|
|
1733
|
-
* Idempotent per project root; unref'd so it never holds the process open.
|
|
1734
|
-
* Asynchronous FSWatcher errors degrade to no-watch with a warning instead
|
|
1735
|
-
* of crashing the dev server. Returns a close handle, or null when this
|
|
1736
|
-
* root is already watched or the platform lacks recursive fs.watch (Linux
|
|
1737
|
-
* before Node 20 — degrades to no-watch with a warning).
|
|
1738
|
-
*/
|
|
1739
|
-
function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.watch) {
|
|
1740
|
-
if (activeWatcherRoots.has(rootDir)) return null;
|
|
1741
|
-
activeWatcherRoots.add(rootDir);
|
|
1742
|
-
if (typeof session.noteDebouncedWatchEvents === "function") session.debounceCeilingMs = debounceMs;
|
|
1743
|
-
const pendingPaths = /* @__PURE__ */ new Set();
|
|
1744
|
-
const watchers = /* @__PURE__ */ new Map();
|
|
1745
|
-
let timer = null;
|
|
1746
|
-
let updateChain = Promise.resolve();
|
|
1747
|
-
let closed = false;
|
|
1748
|
-
const externalWatchers = /* @__PURE__ */ new Map();
|
|
1749
|
-
let pendingOpened = /* @__PURE__ */ new Map();
|
|
1750
|
-
let capturedDuringSnapshot = [];
|
|
1751
|
-
const externalDiagnosticKey = (root) => `external-watch:${root}`;
|
|
1752
|
-
const failureReason = (err) => {
|
|
1753
|
-
const code = err?.code;
|
|
1754
|
-
if (code === "EMFILE" || code === "ENFILE" || code === "ENOSPC" || code === "EPERM") return `capacity(${code})`;
|
|
1755
|
-
if (/inotify|too many/i.test(String(err))) return "capacity";
|
|
1756
|
-
return code ?? "error";
|
|
1757
|
-
};
|
|
1758
|
-
const degradeExternalRoot = (root, err) => {
|
|
1759
|
-
externalWatchers.get(root)?.close();
|
|
1760
|
-
externalWatchers.delete(root);
|
|
1761
|
-
pendingOpened.get(root)?.close();
|
|
1762
|
-
pendingOpened.delete(root);
|
|
1763
|
-
const message = `ANIMUS_EXTERNAL_WATCH_UNAVAILABLE root=${(0, path.relative)(rootDir, root)} reason=${failureReason(err)} effect=changes in this workspace source may require restart`;
|
|
1764
|
-
session.stickyDiagnostics?.set(externalDiagnosticKey(root), message);
|
|
1765
|
-
console.warn(`[animus-extract] ${message}`);
|
|
1766
|
-
};
|
|
1767
|
-
const flush = () => {
|
|
1768
|
-
timer = null;
|
|
1769
|
-
const modifiedFiles = /* @__PURE__ */ new Set();
|
|
1770
|
-
const removedFiles = /* @__PURE__ */ new Set();
|
|
1771
|
-
for (const path$1 of pendingPaths) ((0, fs.existsSync)(path$1) ? modifiedFiles : removedFiles).add(path$1);
|
|
1772
|
-
pendingPaths.clear();
|
|
1773
|
-
updateChain = updateChain.then(() => session.handleWatchUpdate({
|
|
1774
|
-
modifiedFiles,
|
|
1775
|
-
removedFiles
|
|
1776
|
-
}).catch((err) => {
|
|
1777
|
-
console.warn(`[animus-extract] Turbopack watch update failed: ${String(err)}`);
|
|
1778
|
-
}).then(() => {
|
|
1779
|
-
if (pendingOpened.size > 0 || capturedDuringSnapshot.length > 0) rollbackPendingExternal();
|
|
1780
|
-
}));
|
|
1781
|
-
};
|
|
1782
|
-
const rollbackPendingExternal = () => {
|
|
1783
|
-
for (const watcher of pendingOpened.values()) watcher.close();
|
|
1784
|
-
pendingOpened = /* @__PURE__ */ new Map();
|
|
1785
|
-
capturedDuringSnapshot = [];
|
|
1786
|
-
};
|
|
1787
|
-
const closeAll = () => {
|
|
1788
|
-
if (closed) return;
|
|
1789
|
-
closed = true;
|
|
1790
|
-
if (timer) clearTimeout(timer);
|
|
1791
|
-
timer = null;
|
|
1792
|
-
for (const watcher of watchers.values()) watcher.close();
|
|
1793
|
-
watchers.clear();
|
|
1794
|
-
for (const watcher of externalWatchers.values()) watcher.close();
|
|
1795
|
-
externalWatchers.clear();
|
|
1796
|
-
rollbackPendingExternal();
|
|
1797
|
-
session.onExternalRootResolved = null;
|
|
1798
|
-
session.onExternalRootsCommitted = null;
|
|
1799
|
-
activeWatcherRoots.delete(rootDir);
|
|
1800
|
-
};
|
|
1801
|
-
const onWatcherError = (err) => {
|
|
1802
|
-
closeAll();
|
|
1803
|
-
console.warn(`[animus-extract] Turbopack dev watcher failed (${String(err)}); source edits require a dev-server restart`);
|
|
1804
|
-
};
|
|
1805
|
-
const enqueuePath = (abs) => {
|
|
1806
|
-
pendingPaths.add(abs);
|
|
1807
|
-
try {
|
|
1808
|
-
session.noteDebouncedWatchEvents?.([abs]);
|
|
1809
|
-
} catch (err) {
|
|
1810
|
-
console.warn(`[animus-extract] Turbopack debounce status update failed: ${String(err)}`);
|
|
1811
|
-
}
|
|
1812
|
-
if (!timer) {
|
|
1813
|
-
timer = setTimeout(flush, debounceMs);
|
|
1814
|
-
timer.unref?.();
|
|
1815
|
-
}
|
|
1816
|
-
};
|
|
1817
|
-
const onEvent = (baseDir, filename) => {
|
|
1818
|
-
if (closed || !filename) return;
|
|
1819
|
-
const rel = filename.toString();
|
|
1820
|
-
if (rel.split(/[\\/]/).some((segment) => IGNORED_SEGMENTS.has(segment))) return;
|
|
1821
|
-
const abs = (0, path.join)(baseDir, rel);
|
|
1822
|
-
if (baseDir === rootDir && !watchers.has(abs)) try {
|
|
1823
|
-
if ((0, fs.statSync)(abs).isDirectory()) addWatcher(abs, true);
|
|
1824
|
-
} catch {}
|
|
1825
|
-
enqueuePath(abs);
|
|
1826
|
-
};
|
|
1827
|
-
/** External-root event path: fenced by watcher identity ({watcherRootId,
|
|
1828
|
-
* generation} — a handle that is neither current nor pending belongs to
|
|
1829
|
-
* a retired generation and its events are rejected), `filename == null`
|
|
1830
|
-
* marks the ROOT dirty for rediscovery, and events observed while the
|
|
1831
|
-
* root's generation is still snapshotting are buffered for replay. */
|
|
1832
|
-
const onExternalEvent = (root, self, filename) => {
|
|
1833
|
-
if (closed) return;
|
|
1834
|
-
const watcher = self();
|
|
1835
|
-
const active = watcher !== void 0 && externalWatchers.get(root) === watcher;
|
|
1836
|
-
const isPending = watcher !== void 0 && pendingOpened.get(root) === watcher;
|
|
1837
|
-
if (!active && !isPending) return;
|
|
1838
|
-
let abs;
|
|
1839
|
-
if (filename == null) abs = root;
|
|
1840
|
-
else {
|
|
1841
|
-
const rel = filename.toString();
|
|
1842
|
-
if (rel.split(/[\\/]/).some((segment) => IGNORED_SEGMENTS.has(segment))) return;
|
|
1843
|
-
abs = rel === "" ? root : (0, path.join)(root, rel);
|
|
1844
|
-
}
|
|
1845
|
-
if (isPending && !active) {
|
|
1846
|
-
capturedDuringSnapshot.push(abs);
|
|
1847
|
-
return;
|
|
1848
|
-
}
|
|
1849
|
-
enqueuePath(abs);
|
|
1850
|
-
};
|
|
1851
|
-
/** Open one external-root watcher into `into` (the committed set at
|
|
1852
|
-
* startup, the pending set during reset reconciliation). Failures
|
|
1853
|
-
* degrade ONLY this root (design D7); node_modules-resident roots are
|
|
1854
|
-
* documented unwatchable and never registered (guardrail G2). */
|
|
1855
|
-
const openExternalWatcher = (root, into) => {
|
|
1856
|
-
if (root.split(/[\\/]/).includes("node_modules")) return;
|
|
1857
|
-
try {
|
|
1858
|
-
let watcher;
|
|
1859
|
-
watcher = watchFn(root, { recursive: true }, (_event, filename) => onExternalEvent(root, () => watcher, filename));
|
|
1860
|
-
watcher.on("error", (err) => degradeExternalRoot(root, err));
|
|
1861
|
-
watcher.unref?.();
|
|
1862
|
-
into.set(root, watcher);
|
|
1863
|
-
session.stickyDiagnostics?.delete(externalDiagnosticKey(root));
|
|
1864
|
-
} catch (err) {
|
|
1865
|
-
degradeExternalRoot(root, err);
|
|
1866
|
-
}
|
|
1867
|
-
};
|
|
1868
|
-
const addWatcher = (dir, recursive) => {
|
|
1869
|
-
const watcher = watchFn(dir, { recursive }, (_event, filename) => onEvent(dir, filename));
|
|
1870
|
-
watcher.on("error", onWatcherError);
|
|
1871
|
-
watcher.unref?.();
|
|
1872
|
-
watchers.set(dir, watcher);
|
|
1873
|
-
};
|
|
1874
|
-
try {
|
|
1875
|
-
addWatcher(rootDir, false);
|
|
1876
|
-
for (const entry of (0, fs.readdirSync)(rootDir, { withFileTypes: true })) {
|
|
1877
|
-
if (!entry.isDirectory() || IGNORED_SEGMENTS.has(entry.name)) continue;
|
|
1878
|
-
addWatcher((0, path.join)(rootDir, entry.name), true);
|
|
1879
|
-
}
|
|
1880
|
-
} catch (err) {
|
|
1881
|
-
closeAll();
|
|
1882
|
-
console.warn(`[animus-extract] Turbopack dev watcher unavailable (${String(err)}); source edits require a dev-server restart`);
|
|
1883
|
-
return null;
|
|
1884
|
-
}
|
|
1885
|
-
for (const root of session.externalWatchRoots ?? []) openExternalWatcher(root, externalWatchers);
|
|
1886
|
-
session.onExternalRootResolved = (root) => {
|
|
1887
|
-
if (closed) return;
|
|
1888
|
-
if (externalWatchers.has(root) || pendingOpened.has(root)) return;
|
|
1889
|
-
openExternalWatcher(root, pendingOpened);
|
|
1890
|
-
};
|
|
1891
|
-
session.onExternalRootsCommitted = (roots) => {
|
|
1892
|
-
if (closed) return;
|
|
1893
|
-
const admitted = new Set(roots);
|
|
1894
|
-
for (const [root, watcher] of pendingOpened) externalWatchers.set(root, watcher);
|
|
1895
|
-
pendingOpened = /* @__PURE__ */ new Map();
|
|
1896
|
-
for (const [root, watcher] of [...externalWatchers]) if (!admitted.has(root)) {
|
|
1897
|
-
watcher.close();
|
|
1898
|
-
externalWatchers.delete(root);
|
|
1899
|
-
}
|
|
1900
|
-
for (const abs of capturedDuringSnapshot.splice(0)) enqueuePath(abs);
|
|
1901
|
-
};
|
|
1902
|
-
return { close: closeAll };
|
|
1903
|
-
}
|
|
1904
|
-
//#endregion
|
|
1905
245
|
//#region src/with-animus.ts
|
|
1906
|
-
const
|
|
246
|
+
const __filename$1 = (0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
|
|
247
|
+
const __dirname$1 = (0, path.dirname)(__filename$1);
|
|
1907
248
|
let warnedGitignore = false;
|
|
1908
249
|
let warnedUnstableTurbopack = false;
|
|
1909
|
-
/**
|
|
1910
|
-
* Wrap a Next.js config to enable Animus static CSS extraction.
|
|
1911
|
-
*
|
|
1912
|
-
* ```ts
|
|
1913
|
-
* // next.config.ts
|
|
1914
|
-
* import { withAnimus } from '@animus-ui/next-plugin';
|
|
1915
|
-
* export default withAnimus({ system: './src/ds.ts' })({});
|
|
1916
|
-
* ```
|
|
1917
|
-
*/
|
|
1918
250
|
function withAnimus(options) {
|
|
1919
251
|
if (!options.system) throw new Error("[animus-extract] Missing required option `system`. Provide the path to your SystemInstance module: withAnimus({ system: \"./src/ds.ts\" })");
|
|
252
|
+
(0, _animus_ui_extract_pipeline.assertKnownOptionKeys)({ ...options }, [
|
|
253
|
+
"cssImportTarget",
|
|
254
|
+
"turbopack",
|
|
255
|
+
"unstable_turbopack",
|
|
256
|
+
"loaderPath"
|
|
257
|
+
], [{
|
|
258
|
+
key: "root",
|
|
259
|
+
reason: "this driver derives its own root — Next's `dir` under webpack, `process.cwd()` under Turbopack (Next passes no dir to a config module)"
|
|
260
|
+
}], {
|
|
261
|
+
onUnknownKey: "warn",
|
|
262
|
+
warn: (message) => console.warn(`[animus-extract] ${message}`)
|
|
263
|
+
});
|
|
1920
264
|
if (options.unstable_turbopack && !options.turbopack && !warnedUnstableTurbopack) {
|
|
1921
265
|
warnedUnstableTurbopack = true;
|
|
1922
266
|
console.warn("[animus-extract] `unstable_turbopack` is deprecated — rename it to `turbopack` (same shape)");
|
|
@@ -1927,13 +271,13 @@ function withAnimus(options) {
|
|
|
1927
271
|
return {
|
|
1928
272
|
...nextConfig,
|
|
1929
273
|
webpack(config, context) {
|
|
1930
|
-
if (
|
|
1931
|
-
const rootDir =
|
|
274
|
+
if (existingWebpack) config = existingWebpack(config, context);
|
|
275
|
+
const rootDir = context.dir?.length ? context.dir : process.cwd();
|
|
1932
276
|
const plugin = new AnimusWebpackPlugin(options);
|
|
1933
277
|
plugin.setRootDir(rootDir);
|
|
1934
278
|
const sessionDir = plugin.sessionDir;
|
|
1935
279
|
if (!(0, fs.existsSync)(sessionDir)) (0, fs.mkdirSync)(sessionDir, { recursive: true });
|
|
1936
|
-
const stubCssPath =
|
|
280
|
+
const stubCssPath = (0, _animus_ui_extract_session.stylesPath)(sessionDir);
|
|
1937
281
|
if (!(0, fs.existsSync)(stubCssPath)) {
|
|
1938
282
|
const { declaration } = (0, _animus_ui_extract_pipeline.assembleStylesheet)({
|
|
1939
283
|
layers: options.layers,
|
|
@@ -1955,17 +299,17 @@ function withAnimus(options) {
|
|
|
1955
299
|
const isExternalPackageFile = (filePath) => plugin.getExternalPackageDirs().some((dir) => (0, _animus_ui_extract_pipeline.isPathWithinRoot)(dir, filePath));
|
|
1956
300
|
config.plugins = config.plugins || [];
|
|
1957
301
|
config.plugins.push(plugin);
|
|
1958
|
-
const
|
|
1959
|
-
if (
|
|
302
|
+
const DefinePlugin = context.webpack?.DefinePlugin;
|
|
303
|
+
if (DefinePlugin) config.plugins.push(new DefinePlugin({ __ANIMUS_DEV__: JSON.stringify((0, _animus_ui_extract_pipeline.resolveMode)(options.mode, () => context.dev === true ? "development" : "production").mode === "development") }));
|
|
1960
304
|
config.resolve = config.resolve || {};
|
|
1961
305
|
config.resolve.alias = config.resolve.alias || {};
|
|
1962
|
-
config.resolve.alias[ANIMUS_CSS_MODULE_ID] =
|
|
1963
|
-
const sessionSystemPropsPath =
|
|
306
|
+
config.resolve.alias[_animus_ui_extract_session.ANIMUS_CSS_MODULE_ID] = (0, _animus_ui_extract_session.stylesPath)(sessionDir);
|
|
307
|
+
const sessionSystemPropsPath = (0, _animus_ui_extract_session.systemPropsPath)(sessionDir);
|
|
1964
308
|
config.plugins.push({ apply(compiler) {
|
|
1965
309
|
compiler.hooks.normalModuleFactory.tap("AnimusVirtualResolve", (nmf) => {
|
|
1966
310
|
nmf.hooks.beforeResolve.tap("AnimusVirtualResolve", (resolveData) => {
|
|
1967
311
|
if (resolveData.request === "virtual:animus/system-props") resolveData.request = sessionSystemPropsPath;
|
|
1968
|
-
if (resolveData.request === "virtual:animus/styles.css") resolveData.request =
|
|
312
|
+
if (resolveData.request === "virtual:animus/styles.css") resolveData.request = (0, _animus_ui_extract_session.stylesPath)(sessionDir);
|
|
1969
313
|
const srcEntry = plugin.getExternalSourceEntries().get(resolveData.request);
|
|
1970
314
|
if (srcEntry) resolveData.request = srcEntry;
|
|
1971
315
|
});
|
|
@@ -1975,10 +319,7 @@ function withAnimus(options) {
|
|
|
1975
319
|
config.module = config.module || {};
|
|
1976
320
|
config.module.rules = config.module.rules || [];
|
|
1977
321
|
config.module.rules.push({
|
|
1978
|
-
test: (filePath) =>
|
|
1979
|
-
if (/\.[jt]sx?$/.test(filePath)) return true;
|
|
1980
|
-
return /\.mjs$/.test(filePath) && isExternalPackageFile(filePath);
|
|
1981
|
-
},
|
|
322
|
+
test: (filePath) => (0, _animus_ui_extract_pipeline.isEngineTransformExtension)(filePath),
|
|
1982
323
|
exclude: (filePath) => {
|
|
1983
324
|
if (!filePath.includes("node_modules")) return false;
|
|
1984
325
|
return !isExternalPackageFile(filePath);
|
|
@@ -1997,20 +338,25 @@ function withAnimus(options) {
|
|
|
1997
338
|
};
|
|
1998
339
|
};
|
|
1999
340
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
341
|
+
function bindTurbopackWatchDeathReport(outcome, rootDir) {
|
|
342
|
+
if (outcome.kind !== "started") return;
|
|
343
|
+
const handle = outcome.handle;
|
|
344
|
+
handle.onDied = () => {
|
|
345
|
+
console.error(`[animus-extract] dev watcher for ${rootDir} died — source edits are no longer extracted; restart the dev server`);
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
let liveTurbopackSession = null;
|
|
2006
349
|
async function wireTurbopack(nextConfig, options) {
|
|
2007
350
|
const rootDir = process.cwd();
|
|
2008
|
-
const session = new ExtractionSession(options);
|
|
351
|
+
const session = new _animus_ui_extract_session.ExtractionSession(options);
|
|
352
|
+
liveTurbopackSession?.close();
|
|
353
|
+
liveTurbopackSession = session;
|
|
2009
354
|
session.rootDir = rootDir;
|
|
2010
|
-
const
|
|
355
|
+
const aliasPairs = (0, _animus_ui_extract_pipeline.readTsconfigAliasPairs)(rootDir);
|
|
356
|
+
const builtAliases = (0, _animus_ui_extract_pipeline.buildPathAliasesJson)(aliasPairs, rootDir);
|
|
2011
357
|
if (builtAliases) session.pathAliasesJson = builtAliases.json;
|
|
2012
|
-
await
|
|
2013
|
-
if (process.env.NODE_ENV === "development") startTurbopackWatcher(session, rootDir);
|
|
358
|
+
await (0, _animus_ui_extract_session.runSessionPipeline)(session);
|
|
359
|
+
if (process.env.NODE_ENV === "development") bindTurbopackWatchDeathReport((0, _animus_ui_extract_session.startTurbopackWatcher)(session, rootDir), rootDir);
|
|
2014
360
|
const fragment = buildTurbopackConfig({
|
|
2015
361
|
rootDir,
|
|
2016
362
|
loaderPath: resolveTurbopackLoaderPath(__dirname$1),
|
|
@@ -2020,7 +366,7 @@ async function wireTurbopack(nextConfig, options) {
|
|
|
2020
366
|
sessionDir: session.sessionDir
|
|
2021
367
|
});
|
|
2022
368
|
const existing = nextConfig.turbopack ?? {};
|
|
2023
|
-
if (existing.rules &&
|
|
369
|
+
if (existing.rules && ANIMUS_TURBOPACK_RULE_GLOB in existing.rules) throw new Error(`[animus-extract] turbopack.rules['${ANIMUS_TURBOPACK_RULE_GLOB}'] is already configured — remove the consumer rule or disable unstable_turbopack`);
|
|
2024
370
|
return {
|
|
2025
371
|
...nextConfig,
|
|
2026
372
|
turbopack: {
|