@animus-ui/next-plugin 0.1.6 → 0.1.8
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/extraction-session.d.ts +216 -26
- package/dist/extraction-session.d.ts.map +1 -1
- package/dist/index.cjs +980 -144
- package/dist/index.mjs +977 -143
- package/dist/loader-path.d.ts +16 -0
- package/dist/loader-path.d.ts.map +1 -0
- package/dist/loader.cjs +40 -4
- package/dist/loader.d.ts +13 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.mjs +38 -3
- package/dist/plugin.d.ts +65 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/session-paths-CZoi19NW.cjs +229 -0
- package/dist/session-paths-DfYIPDcM.mjs +107 -0
- package/dist/session-paths.d.ts +107 -0
- package/dist/session-paths.d.ts.map +1 -0
- package/dist/singleton-372wiSRr.mjs +202 -0
- package/dist/{singleton-DH7yeWZa.cjs → singleton-D4vhawsW.cjs} +149 -0
- package/dist/singleton.d.ts +55 -0
- package/dist/singleton.d.ts.map +1 -1
- package/dist/turbopack-config.d.ts +6 -1
- package/dist/turbopack-config.d.ts.map +1 -1
- package/dist/turbopack-loader.cjs +231 -39
- package/dist/turbopack-loader.d.ts +29 -6
- package/dist/turbopack-loader.d.ts.map +1 -1
- package/dist/turbopack-loader.mjs +203 -42
- package/dist/turbopack-orchestrator.d.ts +6 -3
- package/dist/turbopack-orchestrator.d.ts.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/with-animus.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-y_GBKt04.mjs +0 -5
- package/dist/singleton-BKALu7Mk.mjs +0 -108
- /package/dist/{loader-core-CbaYNhBu.cjs → loader-core-C40WbC6s.cjs} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
2
|
+
const require_session_paths = require("./session-paths-CZoi19NW.cjs");
|
|
3
|
+
const require_singleton = require("./singleton-D4vhawsW.cjs");
|
|
4
|
+
require("./turbopack-loader.cjs");
|
|
3
5
|
let _animus_ui_extract_pipeline = require("@animus-ui/extract/pipeline");
|
|
4
6
|
let fs = require("fs");
|
|
5
7
|
let path = require("path");
|
|
@@ -94,6 +96,21 @@ function logBuildTimings(bt, rustTiming, log, verbose) {
|
|
|
94
96
|
* alias harvesting must skip.
|
|
95
97
|
*/
|
|
96
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
|
+
];
|
|
97
114
|
/** Default path fragments excluded from source discovery (full + watch). */
|
|
98
115
|
const DEFAULT_EXCLUDE = [
|
|
99
116
|
"node_modules",
|
|
@@ -128,15 +145,18 @@ var ExtractionSession = class {
|
|
|
128
145
|
/** Physical asset files registered with the host watcher. */
|
|
129
146
|
assetDependencyPaths = /* @__PURE__ */ new Set();
|
|
130
147
|
assetDependencyKeys = /* @__PURE__ */ new Set();
|
|
131
|
-
/** When set (Turbopack orchestration), every analysis also persists
|
|
132
|
-
* `.animus/analysis-inputs.json` so isolated loader workers can hydrate.
|
|
133
|
-
* Webpack mode leaves this off — its loader shares the process. */
|
|
134
|
-
persistAnalysisInputs = false;
|
|
135
148
|
/** Emitter identity override for the system-props module id. Webpack mode
|
|
136
149
|
* (null) injects the absolute `.animus/system-props.js` path, resolved by
|
|
137
150
|
* NormalModuleReplacement; Turbopack rejects absolute-path imports, so
|
|
138
151
|
* its driver sets the virtual id that `resolveAlias` maps to disk. */
|
|
139
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;
|
|
140
160
|
/** Absolute directory prefixes for external DS packages (loader allowlisting). */
|
|
141
161
|
externalPackageDirs = [];
|
|
142
162
|
/** Absolute package dir → owning specifier (cross-source correlation). */
|
|
@@ -145,6 +165,42 @@ var ExtractionSession = class {
|
|
|
145
165
|
externalFileOwners = {};
|
|
146
166
|
/** External package specifier → absolute source entry path. */
|
|
147
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 admitted external watch roots — the engine-side watch
|
|
178
|
+
* surface (webpack contextDependencies / Turbopack watchers). */
|
|
179
|
+
externalWatchRoots = [];
|
|
180
|
+
/** Canonical external root → (raw sourceKey → {raw content hash, recorded
|
|
181
|
+
* abs spelling}): the previous-generation inventory a dirty-root rewalk
|
|
182
|
+
* diffs against (design D3). Raw hashes — MDX preprocessing never skews
|
|
183
|
+
* the diff; the recorded abs path is a registered deletion alias, so a
|
|
184
|
+
* reconstructed deletion always resolves through cached identity. */
|
|
185
|
+
externalInventory = /* @__PURE__ */ new Map();
|
|
186
|
+
/** Sticky diagnostics (design D5/D7): stable key → message, re-surfaced
|
|
187
|
+
* on every full pipeline until the underlying condition clears. */
|
|
188
|
+
stickyDiagnostics = /* @__PURE__ */ new Map();
|
|
189
|
+
/** Volume-membership predicate for the cross-volume gate (design D5).
|
|
190
|
+
* Injected seam: the shared predicate only discriminates on Windows
|
|
191
|
+
* (win32 semantics are unit-tested via path.win32), so runtime tests
|
|
192
|
+
* drive the gate through this. */
|
|
193
|
+
sharesProjectVolume = _animus_ui_extract_pipeline.sharesVolumeRoot;
|
|
194
|
+
/** Orchestrator seams (openspec: external-source-watch-ingestion, design
|
|
195
|
+
* D4). `onExternalRootResolved` fires per volume-admitted external root
|
|
196
|
+
* DURING collection — after resolution, BEFORE that root's sources are
|
|
197
|
+
* walked — so a watching host can open its handle with no blind gap
|
|
198
|
+
* between scan and watch. `onExternalRootsCommitted` fires once after a
|
|
199
|
+
* successful full-pipeline publication with the complete admitted
|
|
200
|
+
* canonical root set (the host's promote/replay/close-old phase; a
|
|
201
|
+
* failed pipeline never fires it, so the host rolls back). */
|
|
202
|
+
onExternalRootResolved = null;
|
|
203
|
+
onExternalRootsCommitted = null;
|
|
148
204
|
options;
|
|
149
205
|
staticCssJson;
|
|
150
206
|
system = null;
|
|
@@ -158,15 +214,49 @@ var ExtractionSession = class {
|
|
|
158
214
|
/** Loader-reported dependency paths for bundler watch registration. */
|
|
159
215
|
systemDependencyPaths = [];
|
|
160
216
|
systemDependencyHashes = /* @__PURE__ */ new Map();
|
|
161
|
-
lastCssHash = null;
|
|
162
217
|
lastSystemPropsHash = null;
|
|
163
|
-
|
|
164
|
-
|
|
218
|
+
/** Per-payload write guards: payloadHash gates rewrites (byte-identical
|
|
219
|
+
* payloads leave disk untouched); diskHash is the hash of the CURRENT
|
|
220
|
+
* disk bytes (envelope included) recorded in the analysis-commit. null =
|
|
221
|
+
* not yet seeded (first publication reads the artifact's envelope so a
|
|
222
|
+
* same-session restart never rewrites byte-identical artifacts). */
|
|
223
|
+
artifactRecords = {
|
|
224
|
+
manifest: null,
|
|
225
|
+
inputs: null,
|
|
226
|
+
styles: null
|
|
227
|
+
};
|
|
228
|
+
/** Last written analysis-commit; null = seeded from disk on first use. */
|
|
229
|
+
lastCommit = null;
|
|
230
|
+
/** Session identity — claimed once per PROCESS (one Next invocation),
|
|
231
|
+
* adopted by every subsequent session instance so all compilers share
|
|
232
|
+
* one artifact tree (design D2: `next dev`/`next build` co-writing are
|
|
233
|
+
* separate processes and therefore separate sessions). */
|
|
234
|
+
sessionId = require_singleton.claimProcessSessionId();
|
|
235
|
+
/** Epoch value of the last published analysis; null = not yet known
|
|
236
|
+
* (seeded from the session dir's artifact on first publication so a
|
|
237
|
+
* same-session restart with unchanged plans never rewrites bytes). */
|
|
238
|
+
lastEpochValue = null;
|
|
239
|
+
/** Watcher debounce ceiling feeding status deadlines (design D3). */
|
|
240
|
+
debounceCeilingMs = 75;
|
|
241
|
+
/** Test seam: observes every session-artifact write (name, content)
|
|
242
|
+
* post-rename — write ORDER is part of the transaction contract. */
|
|
243
|
+
onArtifactWrite = null;
|
|
244
|
+
/** Status attempt bookkeeping (design D3). */
|
|
245
|
+
statusAttemptId = 0;
|
|
246
|
+
statusAttemptOpen = false;
|
|
247
|
+
/** Debounce-window observations pending analysis (sourceKey → hash). */
|
|
248
|
+
debouncePending = /* @__PURE__ */ new Map();
|
|
249
|
+
/** Session-start hygiene (pruning + legacy cleanup) runs once. */
|
|
250
|
+
sessionStartHygieneDone = false;
|
|
165
251
|
lcssTargets = null;
|
|
166
252
|
constructor(options) {
|
|
167
253
|
this.options = options;
|
|
168
254
|
this.staticCssJson = (0, _animus_ui_extract_pipeline.serializeStaticCss)(options.staticCss);
|
|
169
255
|
}
|
|
256
|
+
/** This session's artifact directory. Requires `rootDir` to be set. */
|
|
257
|
+
get sessionDir() {
|
|
258
|
+
return require_session_paths.sessionArtifactDir(this.rootDir, this.sessionId);
|
|
259
|
+
}
|
|
170
260
|
get verbose() {
|
|
171
261
|
return this.options.verbose === true || process.env.ANIMUS_DEBUG === "1" || process.env.ANIMUS_DEBUG === "true";
|
|
172
262
|
}
|
|
@@ -182,24 +272,60 @@ var ExtractionSession = class {
|
|
|
182
272
|
elapsed(t) {
|
|
183
273
|
return this.verbose ? Math.round(performance.now() - t) : 0;
|
|
184
274
|
}
|
|
275
|
+
/** Lazily-computed scan config (options are constructor-fixed, so the
|
|
276
|
+
* derivation is stable for the session's lifetime). */
|
|
277
|
+
scanConfigMemo = null;
|
|
185
278
|
/** Resolve the scan configuration from options — the single source of the
|
|
186
279
|
* exclude/extension policy shared by the full and incremental pipelines. */
|
|
187
280
|
resolveScanConfig() {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
281
|
+
if (this.scanConfigMemo === null) {
|
|
282
|
+
const extensionsSet = new Set(this.options.extensions ?? _animus_ui_extract_pipeline.DEFAULT_EXTENSIONS);
|
|
283
|
+
this.scanConfigMemo = {
|
|
284
|
+
excludePatterns: this.options.exclude ?? DEFAULT_EXCLUDE,
|
|
285
|
+
extensionsSet,
|
|
286
|
+
shouldHandleMdx: extensionsSet.has(".mdx")
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return this.scanConfigMemo;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Watch entry point: one single-flight analysis transaction per event
|
|
293
|
+
* batch (openspec: next-webpack-served-transform-coherence, design D3).
|
|
294
|
+
* The first entering session runs the transaction; every concurrent
|
|
295
|
+
* entry — same session re-entered, or another compiler's session
|
|
296
|
+
* (client/server/RSC each hold their own instance) — joins the in-flight
|
|
297
|
+
* promise, so no compiler proceeds against a generation older than the
|
|
298
|
+
* one the transaction publishes. A rejected transaction rejects every
|
|
299
|
+
* joiner; the gate always clears.
|
|
300
|
+
*/
|
|
301
|
+
async handleWatchUpdate(changes) {
|
|
302
|
+
const inflight = require_singleton.getWatchTransaction();
|
|
303
|
+
if (inflight) {
|
|
304
|
+
await inflight;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (!this.system) return;
|
|
308
|
+
const transaction = this.processWatchUpdate(changes);
|
|
309
|
+
require_singleton.setWatchTransaction(transaction);
|
|
310
|
+
try {
|
|
311
|
+
await transaction;
|
|
312
|
+
} finally {
|
|
313
|
+
require_singleton.setWatchTransaction(null);
|
|
314
|
+
}
|
|
194
315
|
}
|
|
195
316
|
/**
|
|
196
317
|
* Incremental watch pass: geological reset when the system file changed,
|
|
197
318
|
* otherwise content-hash diffing restricted to the watcher's change sets
|
|
198
319
|
* (falling back to a full discovery walk when no sets are provided).
|
|
199
320
|
*/
|
|
200
|
-
async
|
|
201
|
-
if (!this.system) return;
|
|
321
|
+
async processWatchUpdate(changes) {
|
|
202
322
|
const rootDir = this.rootDir;
|
|
323
|
+
try {
|
|
324
|
+
changes = this.reconcileExternalRoots(changes);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
this.warn(`external root reconciliation failed: ${String(err)}`);
|
|
327
|
+
}
|
|
328
|
+
const changed = [...changes.modifiedFiles ?? [], ...changes.removedFiles ?? []];
|
|
203
329
|
let assetChanged = false;
|
|
204
330
|
try {
|
|
205
331
|
let systemHit;
|
|
@@ -213,16 +339,16 @@ var ExtractionSession = class {
|
|
|
213
339
|
break;
|
|
214
340
|
}
|
|
215
341
|
}
|
|
216
|
-
else systemHit =
|
|
342
|
+
else systemHit = changed.find((path$2) => (0, _animus_ui_extract_pipeline.toWatchKeys)(path$2).some((key) => this.systemDependencyKeys.has(key)));
|
|
217
343
|
if (systemHit) {
|
|
218
344
|
this.log(`geological reset: system dependency changed (${(0, path.relative)(rootDir, systemHit)})`);
|
|
219
345
|
this.resetForHmr();
|
|
220
|
-
const promise = this.runFullPipeline();
|
|
346
|
+
const promise = this.runFullPipeline(this.pendingFromBatch(changes));
|
|
221
347
|
require_singleton.setAnalysisPromise(promise);
|
|
222
348
|
await promise;
|
|
223
349
|
return;
|
|
224
350
|
}
|
|
225
|
-
if (
|
|
351
|
+
if (changed.some((path$3) => (0, _animus_ui_extract_pipeline.toWatchKeys)(path$3).some((key) => this.assetDependencyKeys.has(key)))) {
|
|
226
352
|
this.assetCopyCache.clear();
|
|
227
353
|
assetChanged = true;
|
|
228
354
|
}
|
|
@@ -230,53 +356,121 @@ var ExtractionSession = class {
|
|
|
230
356
|
this.warn(`HMR geological-reset check failed: ${String(err)}`);
|
|
231
357
|
}
|
|
232
358
|
const { excludePatterns, extensionsSet, shouldHandleMdx } = this.resolveScanConfig();
|
|
359
|
+
const priorCacheEntries = /* @__PURE__ */ new Map();
|
|
360
|
+
const recordPrior = (key) => {
|
|
361
|
+
if (!priorCacheEntries.has(key)) priorCacheEntries.set(key, this.fileCache.get(key) ?? null);
|
|
362
|
+
};
|
|
363
|
+
const inventoryUpdates = [];
|
|
233
364
|
let removedAny = false;
|
|
234
365
|
if (changes.removedFiles) for (const removedPath of changes.removedFiles) {
|
|
235
|
-
|
|
236
|
-
|
|
366
|
+
let key;
|
|
367
|
+
let owningRoot = null;
|
|
368
|
+
if ((0, _animus_ui_extract_pipeline.isPathWithinRoot)(rootDir, removedPath)) key = (0, path.relative)(rootDir, removedPath);
|
|
369
|
+
else {
|
|
370
|
+
const resolved = this.sourceIdentity?.resolveDeletedSourceId(removedPath);
|
|
371
|
+
if (!resolved) continue;
|
|
372
|
+
key = resolved.sourceKey;
|
|
373
|
+
owningRoot = resolved.owningRoot;
|
|
374
|
+
}
|
|
375
|
+
recordPrior(key);
|
|
376
|
+
recordPrior(key + ".tsx");
|
|
377
|
+
if (this.fileCache.delete(key) || this.fileCache.delete(key + ".tsx")) {
|
|
378
|
+
removedAny = true;
|
|
379
|
+
if (owningRoot) {
|
|
380
|
+
inventoryUpdates.push({
|
|
381
|
+
root: owningRoot,
|
|
382
|
+
key,
|
|
383
|
+
hash: null,
|
|
384
|
+
abs: removedPath
|
|
385
|
+
});
|
|
386
|
+
delete this.externalFileOwners[key];
|
|
387
|
+
delete this.externalFileOwners[key + ".tsx"];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
237
390
|
}
|
|
238
|
-
let
|
|
391
|
+
let targets;
|
|
239
392
|
if (changes.modifiedFiles) {
|
|
240
|
-
|
|
393
|
+
targets = [];
|
|
241
394
|
for (const modifiedPath of changes.modifiedFiles) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
395
|
+
const classified = this.classifyWatchPath(modifiedPath, {
|
|
396
|
+
excludePatterns,
|
|
397
|
+
extensionsSet
|
|
398
|
+
});
|
|
399
|
+
if (classified) targets.push({
|
|
400
|
+
abs: modifiedPath,
|
|
401
|
+
...classified
|
|
402
|
+
});
|
|
247
403
|
}
|
|
248
|
-
} else
|
|
404
|
+
} else targets = (0, _animus_ui_extract_pipeline.discoverFiles)(rootDir, rootDir, excludePatterns, extensionsSet).map((abs) => ({
|
|
405
|
+
abs,
|
|
406
|
+
key: (0, path.relative)(rootDir, abs),
|
|
407
|
+
owningRoot: null
|
|
408
|
+
}));
|
|
249
409
|
const changedPaths = [];
|
|
250
|
-
for (const
|
|
251
|
-
let relPath =
|
|
410
|
+
for (const target of targets) {
|
|
411
|
+
let relPath = target.key;
|
|
252
412
|
let source;
|
|
253
413
|
try {
|
|
254
|
-
source = (0, fs.readFileSync)(
|
|
414
|
+
source = (0, fs.readFileSync)(target.abs, "utf-8");
|
|
255
415
|
} catch {
|
|
256
416
|
continue;
|
|
257
417
|
}
|
|
258
|
-
|
|
418
|
+
const rawHash = (0, _animus_ui_extract_pipeline.contentHash)(source);
|
|
419
|
+
if (shouldHandleMdx && (0, path.extname)(target.abs) === ".mdx") {
|
|
259
420
|
const processed = await this.preprocessMdxEntry(source, relPath, { warn: false });
|
|
260
421
|
if (!processed) continue;
|
|
261
422
|
source = processed.source;
|
|
262
423
|
relPath = processed.relPath;
|
|
263
424
|
}
|
|
264
425
|
const cached = this.fileCache.get(relPath);
|
|
265
|
-
const hash = (0, _animus_ui_extract_pipeline.contentHash)(source);
|
|
426
|
+
const hash = relPath === target.key ? rawHash : (0, _animus_ui_extract_pipeline.contentHash)(source);
|
|
266
427
|
if (!cached || cached.hash !== hash) {
|
|
267
428
|
changedPaths.push(relPath);
|
|
429
|
+
recordPrior(relPath);
|
|
268
430
|
this.fileCache.set(relPath, {
|
|
269
431
|
hash,
|
|
270
432
|
source
|
|
271
433
|
});
|
|
434
|
+
if (target.owningRoot) {
|
|
435
|
+
const owner = this.externalRootOwners.get(target.owningRoot)?.values().next().value;
|
|
436
|
+
if (owner !== void 0) this.externalFileOwners[relPath] ??= owner;
|
|
437
|
+
inventoryUpdates.push({
|
|
438
|
+
root: target.owningRoot,
|
|
439
|
+
key: target.key,
|
|
440
|
+
hash: rawHash,
|
|
441
|
+
abs: target.abs
|
|
442
|
+
});
|
|
443
|
+
}
|
|
272
444
|
}
|
|
273
445
|
}
|
|
274
446
|
if (changedPaths.length > 0 || removedAny || assetChanged) {
|
|
275
447
|
const fileEntries = this.buildFileEntriesFromCache();
|
|
448
|
+
const pending = changedPaths.map((rel) => [rel, this.fileCache.get(rel).hash]);
|
|
276
449
|
require_singleton.resetAnalysisPromise();
|
|
277
|
-
const promise = this.runIncrementalPipeline(fileEntries);
|
|
450
|
+
const promise = this.runIncrementalPipeline(fileEntries, pending);
|
|
278
451
|
require_singleton.setAnalysisPromise(promise);
|
|
279
|
-
|
|
452
|
+
try {
|
|
453
|
+
await promise;
|
|
454
|
+
} catch (err) {
|
|
455
|
+
for (const [key, prior] of priorCacheEntries) if (prior === null) this.fileCache.delete(key);
|
|
456
|
+
else this.fileCache.set(key, prior);
|
|
457
|
+
throw err;
|
|
458
|
+
}
|
|
459
|
+
for (const update of inventoryUpdates) {
|
|
460
|
+
let inventory = this.externalInventory.get(update.root);
|
|
461
|
+
if (!inventory) {
|
|
462
|
+
inventory = /* @__PURE__ */ new Map();
|
|
463
|
+
this.externalInventory.set(update.root, inventory);
|
|
464
|
+
}
|
|
465
|
+
if (update.hash === null) inventory.delete(update.key);
|
|
466
|
+
else inventory.set(update.key, {
|
|
467
|
+
hash: update.hash,
|
|
468
|
+
abs: update.abs
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
} else if (this.statusAttemptOpen) {
|
|
472
|
+
this.debouncePending.clear();
|
|
473
|
+
this.writeAnalysisStatus("idle", []);
|
|
280
474
|
}
|
|
281
475
|
}
|
|
282
476
|
/**
|
|
@@ -307,9 +501,11 @@ var ExtractionSession = class {
|
|
|
307
501
|
relPath: relPath + ".tsx"
|
|
308
502
|
};
|
|
309
503
|
}
|
|
310
|
-
async runFullPipeline() {
|
|
504
|
+
async runFullPipeline(pending = []) {
|
|
311
505
|
const pipelineStart = this.now();
|
|
312
506
|
const bt = {};
|
|
507
|
+
this.runSessionStartHygiene();
|
|
508
|
+
require_singleton.setSessionArtifactDir(this.sessionDir);
|
|
313
509
|
(0, _animus_ui_extract_pipeline.clearEngineCache)(require_singleton.engineApi);
|
|
314
510
|
const rootDir = this.rootDir;
|
|
315
511
|
const resolvedSystemPath = (0, path.resolve)(rootDir, this.options.system);
|
|
@@ -373,6 +569,7 @@ var ExtractionSession = class {
|
|
|
373
569
|
t = this.now();
|
|
374
570
|
const packageNames = (0, _animus_ui_extract_pipeline.extractSystemFilePackages)(resolvedSystemPath);
|
|
375
571
|
const preResolved = resolvePackagesByName(rootDir, packageNames);
|
|
572
|
+
const rawExternalFiles = /* @__PURE__ */ new Map();
|
|
376
573
|
const collected = await (0, _animus_ui_extract_pipeline.collectExternalPackageSources)({
|
|
377
574
|
specifiers: packageNames,
|
|
378
575
|
resolveSpecifier: (name) => preResolved[name] ? (0, path.resolve)(rootDir, preResolved[name]) : null,
|
|
@@ -380,6 +577,7 @@ var ExtractionSession = class {
|
|
|
380
577
|
extensionsSet,
|
|
381
578
|
hasEntry: (relPath) => fileEntries.some((e) => e.path === relPath),
|
|
382
579
|
preprocessFile: async (source, relPath, absPath) => {
|
|
580
|
+
rawExternalFiles.set(absPath, (0, _animus_ui_extract_pipeline.contentHash)(source));
|
|
383
581
|
if (shouldHandleMdx && (0, path.extname)(absPath) === ".mdx") return this.preprocessMdxEntry(source, relPath, {
|
|
384
582
|
warn: true,
|
|
385
583
|
missingDepFlag
|
|
@@ -389,7 +587,16 @@ var ExtractionSession = class {
|
|
|
389
587
|
relPath
|
|
390
588
|
};
|
|
391
589
|
},
|
|
392
|
-
onUnreadable: (relPath, err) => this.warn(`skipped unreadable package file ${relPath}: ${String(err)}`)
|
|
590
|
+
onUnreadable: (relPath, err) => this.warn(`skipped unreadable package file ${relPath}: ${String(err)}`),
|
|
591
|
+
onPackageResolved: (_specifier, packageDir) => {
|
|
592
|
+
if (!this.onExternalRootResolved) return;
|
|
593
|
+
if (!this.sharesProjectVolume(rootDir, packageDir)) return;
|
|
594
|
+
let canonical = packageDir;
|
|
595
|
+
try {
|
|
596
|
+
canonical = (0, fs.realpathSync)(packageDir);
|
|
597
|
+
} catch {}
|
|
598
|
+
this.onExternalRootResolved(canonical);
|
|
599
|
+
}
|
|
393
600
|
});
|
|
394
601
|
for (const record of collected.outcomes) if (record.outcome === "empty") this.warn(`include '${record.specifier}' resolved but discovered no component sources`);
|
|
395
602
|
const unresolvableMessage = (0, _animus_ui_extract_pipeline.unresolvableIncludesMessage)(collected.outcomes);
|
|
@@ -402,12 +609,51 @@ var ExtractionSession = class {
|
|
|
402
609
|
if (this.options.strict) throw new Error(staleDistMessage);
|
|
403
610
|
this.warn(staleDistMessage);
|
|
404
611
|
}
|
|
405
|
-
const
|
|
612
|
+
const rejectedSpecifiers = /* @__PURE__ */ new Set();
|
|
613
|
+
const crossVolumeDetails = [];
|
|
614
|
+
for (const [dir, specifiers] of Object.entries(collected.dirOwnerSets)) {
|
|
615
|
+
if (this.sharesProjectVolume(rootDir, dir)) continue;
|
|
616
|
+
for (const specifier of specifiers) rejectedSpecifiers.add(specifier);
|
|
617
|
+
crossVolumeDetails.push(`${specifiers.join(", ")} → ${dir}`);
|
|
618
|
+
}
|
|
619
|
+
this.stickyDiagnostics.delete("cross-volume");
|
|
620
|
+
if (rejectedSpecifiers.size > 0) {
|
|
621
|
+
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`;
|
|
622
|
+
if (this.options.strict) throw new Error(`[animus-next] ${message}`);
|
|
623
|
+
this.stickyDiagnostics.set("cross-volume", message);
|
|
624
|
+
}
|
|
625
|
+
for (const message of this.stickyDiagnostics.values()) this.warn(message);
|
|
626
|
+
const admitted = (0, _animus_ui_extract_pipeline.excludeCollectedPackages)(collected, rejectedSpecifiers, rootDir);
|
|
627
|
+
const identity = (0, _animus_ui_extract_pipeline.createSourceIdentity)(rootDir);
|
|
628
|
+
this.externalRootOwners = /* @__PURE__ */ new Map();
|
|
629
|
+
for (const [dir, specifiers] of Object.entries(admitted.dirOwnerSets)) {
|
|
630
|
+
const canonical = identity.registerExternalRoot(dir);
|
|
631
|
+
const owners = this.externalRootOwners.get(canonical) ?? /* @__PURE__ */ new Set();
|
|
632
|
+
for (const specifier of specifiers) owners.add(specifier);
|
|
633
|
+
this.externalRootOwners.set(canonical, owners);
|
|
634
|
+
}
|
|
635
|
+
this.sourceIdentity = identity;
|
|
636
|
+
this.externalWatchRoots = identity.externalRoots();
|
|
637
|
+
this.externalInventory = /* @__PURE__ */ new Map();
|
|
638
|
+
for (const [absPath, rawHash] of rawExternalFiles) {
|
|
639
|
+
const resolved = identity.resolveSourceId(absPath);
|
|
640
|
+
if (!resolved?.owningRoot) continue;
|
|
641
|
+
let inventory = this.externalInventory.get(resolved.owningRoot);
|
|
642
|
+
if (!inventory) {
|
|
643
|
+
inventory = /* @__PURE__ */ new Map();
|
|
644
|
+
this.externalInventory.set(resolved.owningRoot, inventory);
|
|
645
|
+
}
|
|
646
|
+
inventory.set(resolved.sourceKey, {
|
|
647
|
+
hash: rawHash,
|
|
648
|
+
abs: absPath
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
const packageMap = admitted.packageMap;
|
|
406
652
|
this.lastPackageMap = packageMap;
|
|
407
|
-
this.externalDirOwners =
|
|
408
|
-
this.externalFileOwners =
|
|
409
|
-
this.externalSourceEntries =
|
|
410
|
-
for (const entry of
|
|
653
|
+
this.externalDirOwners = (0, _animus_ui_extract_pipeline.firstOwners)(admitted.dirOwnerSets);
|
|
654
|
+
this.externalFileOwners = admitted.fileOwners;
|
|
655
|
+
this.externalSourceEntries = admitted.sourceEntries;
|
|
656
|
+
for (const entry of admitted.entries) {
|
|
411
657
|
const hash = (0, _animus_ui_extract_pipeline.contentHash)(entry.source);
|
|
412
658
|
this.fileCache.set(entry.path, {
|
|
413
659
|
hash,
|
|
@@ -419,30 +665,155 @@ var ExtractionSession = class {
|
|
|
419
665
|
hash
|
|
420
666
|
});
|
|
421
667
|
}
|
|
422
|
-
this.externalPackageDirs =
|
|
423
|
-
require_singleton.setSharedExternalDirs(
|
|
424
|
-
require_singleton.setSharedExternalEntries(
|
|
425
|
-
if (this.system &&
|
|
668
|
+
this.externalPackageDirs = admitted.packageDirs;
|
|
669
|
+
require_singleton.setSharedExternalDirs(admitted.packageDirs);
|
|
670
|
+
require_singleton.setSharedExternalEntries(admitted.sourceEntries);
|
|
671
|
+
if (this.system && admitted.keyframesScanEntries.size > 0) {
|
|
426
672
|
const api = require_singleton.engineApi();
|
|
427
|
-
const merge = (0, _animus_ui_extract_pipeline.mergeExternalKeyframes)((entry, root) => api.scanKeyframesExports(entry, root), this.system.keyframesJson,
|
|
673
|
+
const merge = (0, _animus_ui_extract_pipeline.mergeExternalKeyframes)((entry, root) => api.scanKeyframesExports(entry, root), this.system.keyframesJson, admitted.keyframesScanEntries.values(), this.rootDir);
|
|
428
674
|
this.system.keyframesJson = merge.keyframesJson;
|
|
429
675
|
this.externalKeyframesDiagnostics = merge.diagnostics;
|
|
430
676
|
}
|
|
431
677
|
bt.packageResolve = this.elapsed(t);
|
|
432
|
-
|
|
678
|
+
const universe = new Set(fileEntries.map((entry) => entry.path));
|
|
679
|
+
for (const key of [...this.fileCache.keys()]) if (!universe.has(key)) this.fileCache.delete(key);
|
|
680
|
+
await this.analyzeAndEmit(fileEntries, packageMap, false, bt, pipelineStart, pending);
|
|
681
|
+
this.onExternalRootsCommitted?.(this.externalWatchRoots);
|
|
433
682
|
}
|
|
434
683
|
/**
|
|
435
|
-
* Reset analysis state for HMR geological reset.
|
|
684
|
+
* Reset analysis state for HMR geological reset. Payload write guards go
|
|
685
|
+
* back to null so the next publication reseeds them from the disk
|
|
686
|
+
* envelopes — a byte-identical post-reset artifact is still not
|
|
687
|
+
* rewritten.
|
|
436
688
|
*/
|
|
437
689
|
resetForHmr() {
|
|
438
690
|
require_singleton.resetAnalysisPromise();
|
|
439
|
-
this.
|
|
691
|
+
this.artifactRecords = {
|
|
692
|
+
manifest: null,
|
|
693
|
+
inputs: null,
|
|
694
|
+
styles: null
|
|
695
|
+
};
|
|
440
696
|
this.lastSystemPropsHash = null;
|
|
441
|
-
this.lastManifestHash = null;
|
|
442
|
-
this.lastAnalysisInputsHash = null;
|
|
443
697
|
(0, _animus_ui_extract_pipeline.clearEngineCache)(require_singleton.engineApi);
|
|
444
698
|
}
|
|
445
699
|
/**
|
|
700
|
+
* The (sourceKey, observedSourceHash) pairs of a watch batch — cheap
|
|
701
|
+
* evidence for the status file's pending set on the geological-reset
|
|
702
|
+
* path, where the batch's component edits ride along with the system
|
|
703
|
+
* edit (design D3: loaders wait only on observed inputs).
|
|
704
|
+
*/
|
|
705
|
+
pendingFromBatch(changes) {
|
|
706
|
+
const { excludePatterns, extensionsSet } = this.resolveScanConfig();
|
|
707
|
+
const pending = [];
|
|
708
|
+
for (const path$4 of changes.modifiedFiles ?? []) {
|
|
709
|
+
const classified = this.classifyWatchPath(path$4, {
|
|
710
|
+
excludePatterns,
|
|
711
|
+
extensionsSet
|
|
712
|
+
});
|
|
713
|
+
if (!classified) continue;
|
|
714
|
+
try {
|
|
715
|
+
pending.push([classified.key, (0, _animus_ui_extract_pipeline.contentHash)((0, fs.readFileSync)(path$4, "utf-8"))]);
|
|
716
|
+
} catch {}
|
|
717
|
+
}
|
|
718
|
+
return pending;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Root-dirty inventory reconciliation (design D3): partition the
|
|
722
|
+
* watcher's change sets into explicit FILE events and external ROOT hits
|
|
723
|
+
* (a reported path that IS an admitted root, or a directory inside one),
|
|
724
|
+
* rewalk each dirty root through the shared discovery policy, diff the
|
|
725
|
+
* SourceId+raw-hash inventory against the previous generation, and merge
|
|
726
|
+
* the reconstructed created/edited/deleted deltas with the explicit
|
|
727
|
+
* events (identity-level dedup happens downstream at the cache gate).
|
|
728
|
+
* Deletion falls out of the inventory diff — webpack may report only the
|
|
729
|
+
* directory (probe-proven).
|
|
730
|
+
*/
|
|
731
|
+
reconcileExternalRoots(changes) {
|
|
732
|
+
const identity = this.sourceIdentity;
|
|
733
|
+
if (!identity) return changes;
|
|
734
|
+
if (!changes.modifiedFiles && !changes.removedFiles) return changes;
|
|
735
|
+
if (this.externalWatchRoots.length === 0) return changes;
|
|
736
|
+
const { extensionsSet } = this.resolveScanConfig();
|
|
737
|
+
const dirtyRoots = /* @__PURE__ */ new Set();
|
|
738
|
+
const modified = /* @__PURE__ */ new Set();
|
|
739
|
+
const removed = /* @__PURE__ */ new Set();
|
|
740
|
+
const rootHitFor = (path$5) => {
|
|
741
|
+
const containing = identity.containingExternalRoot(path$5);
|
|
742
|
+
if (!containing) return null;
|
|
743
|
+
try {
|
|
744
|
+
return (0, fs.statSync)(path$5).isDirectory() ? containing : null;
|
|
745
|
+
} catch {
|
|
746
|
+
return identity.resolveDeletedSourceId(path$5) ? null : containing;
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
for (const path$6 of changes.modifiedFiles ?? []) {
|
|
750
|
+
const root = rootHitFor(path$6);
|
|
751
|
+
if (root) dirtyRoots.add(root);
|
|
752
|
+
else modified.add(path$6);
|
|
753
|
+
}
|
|
754
|
+
for (const path$7 of changes.removedFiles ?? []) {
|
|
755
|
+
const root = rootHitFor(path$7);
|
|
756
|
+
if (root) dirtyRoots.add(root);
|
|
757
|
+
else removed.add(path$7);
|
|
758
|
+
}
|
|
759
|
+
for (const root of dirtyRoots) {
|
|
760
|
+
const previous = this.externalInventory.get(root) ?? /* @__PURE__ */ new Map();
|
|
761
|
+
const seen = /* @__PURE__ */ new Set();
|
|
762
|
+
const walked = (0, _animus_ui_extract_pipeline.walkPackageSources)(root, extensionsSet);
|
|
763
|
+
for (const abs of walked) {
|
|
764
|
+
const resolved = identity.resolveSourceId(abs);
|
|
765
|
+
if (!resolved || resolved.owningRoot !== root) continue;
|
|
766
|
+
seen.add(resolved.sourceKey);
|
|
767
|
+
let rawHash;
|
|
768
|
+
try {
|
|
769
|
+
rawHash = (0, _animus_ui_extract_pipeline.contentHash)((0, fs.readFileSync)(abs, "utf-8"));
|
|
770
|
+
} catch {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if (previous.get(resolved.sourceKey)?.hash !== rawHash) modified.add(abs);
|
|
774
|
+
}
|
|
775
|
+
for (const [key, entry] of previous) if (!seen.has(key)) removed.add(entry.abs);
|
|
776
|
+
}
|
|
777
|
+
return {
|
|
778
|
+
modifiedFiles: modified,
|
|
779
|
+
removedFiles: removed
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Route one watcher-reported path (design D1/D2): a project-root member
|
|
784
|
+
* (lexical containment — existing local semantics preserved; consumer
|
|
785
|
+
* exclude patterns apply) or an admitted external member (identity
|
|
786
|
+
* resolution with symlink-escape rejection; package-relative excludes
|
|
787
|
+
* mirror the collection walk's filters — guardrail G1, one policy).
|
|
788
|
+
* Returns null for dropped paths.
|
|
789
|
+
*/
|
|
790
|
+
classifyWatchPath(absPath, scan) {
|
|
791
|
+
if (!scan.extensionsSet.has((0, path.extname)(absPath))) return null;
|
|
792
|
+
const rootDir = this.rootDir;
|
|
793
|
+
if ((0, _animus_ui_extract_pipeline.isPathWithinRoot)(rootDir, absPath)) {
|
|
794
|
+
const rel = (0, path.relative)(rootDir, absPath);
|
|
795
|
+
if (scan.excludePatterns.some((pattern) => absPath.includes(pattern) || rel.includes(pattern))) return null;
|
|
796
|
+
return {
|
|
797
|
+
key: rel,
|
|
798
|
+
owningRoot: null
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
const resolved = this.sourceIdentity?.resolveSourceId(absPath);
|
|
802
|
+
if (!resolved) return null;
|
|
803
|
+
if (resolved.owningRoot === null) {
|
|
804
|
+
if (scan.excludePatterns.some((pattern) => resolved.sourceKey.includes(pattern))) return null;
|
|
805
|
+
return {
|
|
806
|
+
key: resolved.sourceKey,
|
|
807
|
+
owningRoot: null
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
if ((0, _animus_ui_extract_pipeline.isExcludedPackageRelativePath)(resolved.pathInRoot)) return null;
|
|
811
|
+
return {
|
|
812
|
+
key: resolved.sourceKey,
|
|
813
|
+
owningRoot: resolved.owningRoot
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
446
817
|
* Build file entries from cache: every cached file rides with full source.
|
|
447
818
|
* The v2 engine has NO Rust-side cache (arch-extract-v2-spine: uncached
|
|
448
819
|
* re-analysis beats a cache-hit path), so it must always receive full sources
|
|
@@ -450,8 +821,8 @@ var ExtractionSession = class {
|
|
|
450
821
|
*/
|
|
451
822
|
buildFileEntriesFromCache() {
|
|
452
823
|
const entries = [];
|
|
453
|
-
for (const [path$
|
|
454
|
-
path: path$
|
|
824
|
+
for (const [path$8, { hash, source }] of this.fileCache) entries.push({
|
|
825
|
+
path: path$8,
|
|
455
826
|
source,
|
|
456
827
|
hash
|
|
457
828
|
});
|
|
@@ -461,10 +832,10 @@ var ExtractionSession = class {
|
|
|
461
832
|
* Run incremental pipeline with cache-aware file entries.
|
|
462
833
|
* Reuses system config from the last full pipeline run.
|
|
463
834
|
*/
|
|
464
|
-
async runIncrementalPipeline(fileEntries) {
|
|
835
|
+
async runIncrementalPipeline(fileEntries, pending = []) {
|
|
465
836
|
const bt = {};
|
|
466
837
|
const pipelineStart = this.now();
|
|
467
|
-
await this.analyzeAndEmit(fileEntries, this.lastPackageMap, true, bt, pipelineStart);
|
|
838
|
+
await this.analyzeAndEmit(fileEntries, this.lastPackageMap, true, bt, pipelineStart, pending);
|
|
468
839
|
}
|
|
469
840
|
/**
|
|
470
841
|
* Shared analysis + emit core for both pipelines — the single call site
|
|
@@ -482,23 +853,44 @@ var ExtractionSession = class {
|
|
|
482
853
|
* - `true` (HMR): skips the report log, and guards the system-props.js
|
|
483
854
|
* write by lastSystemPropsHash.
|
|
484
855
|
*
|
|
485
|
-
* The
|
|
856
|
+
* The disk artifacts land under the session directory in transaction
|
|
857
|
+
* order (design D1): manifest → analysis-inputs (Turbopack orchestration
|
|
858
|
+
* only) → styles.css → system-props → analysis-commit →
|
|
859
|
+
* replacements-epoch (last, only when moved). The analysis-status file walks starting → analyzing →
|
|
860
|
+
* committing → idle around the attempt, landing in `failed` (with the
|
|
861
|
+
* diagnostic) on any throw (design D3).
|
|
486
862
|
*/
|
|
487
|
-
async analyzeAndEmit(fileEntries, packageMap, devMode, bt, pipelineStart) {
|
|
863
|
+
async analyzeAndEmit(fileEntries, packageMap, devMode, bt, pipelineStart, pending = []) {
|
|
864
|
+
this.beginStatusAttempt();
|
|
865
|
+
this.debouncePending.clear();
|
|
866
|
+
this.writeAnalysisStatus("starting", pending);
|
|
867
|
+
try {
|
|
868
|
+
await this.analyzeAndEmitAttempt(fileEntries, packageMap, devMode, bt, pipelineStart, pending);
|
|
869
|
+
this.writeAnalysisStatus("idle", []);
|
|
870
|
+
} catch (err) {
|
|
871
|
+
this.writeAnalysisStatus("failed", pending, String(err));
|
|
872
|
+
throw err;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
async analyzeAndEmitAttempt(fileEntries, packageMap, devMode, bt, pipelineStart, pending) {
|
|
488
876
|
const system = this.system;
|
|
489
|
-
const
|
|
877
|
+
const analysisOptions = {
|
|
490
878
|
fileEntries,
|
|
491
879
|
packageMap,
|
|
492
880
|
system,
|
|
493
881
|
emitter: {
|
|
494
882
|
runtimeImport: "@animus-ui/system/runtime",
|
|
495
883
|
cssModuleId: ANIMUS_CSS_MODULE_ID,
|
|
496
|
-
systemPropsModuleId: this.systemPropsModuleId ??
|
|
884
|
+
systemPropsModuleId: this.systemPropsModuleId ?? require_session_paths.systemPropsPath(this.sessionDir)
|
|
497
885
|
},
|
|
498
886
|
pathAliasesJson: this.pathAliasesJson,
|
|
499
887
|
staticCssJson: this.staticCssJson,
|
|
500
888
|
externalDirs: this.externalPackageDirs.map((dir) => (0, path.relative)(this.rootDir, dir)),
|
|
501
|
-
devMode
|
|
889
|
+
devMode
|
|
890
|
+
};
|
|
891
|
+
this.writeAnalysisStatus("analyzing", pending);
|
|
892
|
+
const result = (0, _animus_ui_extract_pipeline.runProjectAnalysis)(require_singleton.engineApi, {
|
|
893
|
+
...analysisOptions,
|
|
502
894
|
warn: (message) => this.warn(message),
|
|
503
895
|
strict: this.options.strict,
|
|
504
896
|
extraDiagnostics: this.externalKeyframesDiagnostics
|
|
@@ -539,80 +931,305 @@ var ExtractionSession = class {
|
|
|
539
931
|
})
|
|
540
932
|
].filter(Boolean).join("\n");
|
|
541
933
|
require_singleton.setSharedCss(fullCss);
|
|
542
|
-
const cssHash = (0, _animus_ui_extract_pipeline.contentHash)(fullCss);
|
|
543
|
-
if (cssHash !== this.lastCssHash) {
|
|
544
|
-
this.writeAnimusFile("styles.css", fullCss);
|
|
545
|
-
this.lastCssHash = cssHash;
|
|
546
|
-
}
|
|
547
934
|
const systemPropsContent = (0, _animus_ui_extract_pipeline.buildSystemPropsModule)({
|
|
548
935
|
systemPropMapJson: JSON.stringify(manifest?.system_prop_map ?? {}),
|
|
549
936
|
groupRegistryJson: system.groupRegistryJson,
|
|
550
937
|
dynamicProps: manifest?.dynamic_props ?? {}
|
|
551
938
|
});
|
|
552
939
|
require_singleton.setSharedSystemProps(systemPropsContent);
|
|
940
|
+
require_singleton.setManifestJson(result.manifestJson);
|
|
941
|
+
require_singleton.setAnalyzedHashes(new Map(fileEntries.map((entry) => [entry.path, entry.hash])));
|
|
942
|
+
this.writeAnalysisStatus("committing", pending);
|
|
943
|
+
const epoch = (0, _animus_ui_extract_pipeline.hashReplacementPlans)((0, _animus_ui_extract_pipeline.snapshotFilePlans)(manifest), systemPropsContent);
|
|
944
|
+
if (this.lastCommit === null) this.lastCommit = this.seedCommitFromDisk();
|
|
945
|
+
const generation = (this.lastCommit?.generation ?? 0) + 1;
|
|
946
|
+
this.publishPayloadArtifact("manifest", require_session_paths.MANIFEST_ARTIFACT, result.manifestJson, epoch, generation, require_session_paths.envelopeJsonArtifact);
|
|
947
|
+
if (this.persistAnalysisInputs) this.publishPayloadArtifact("inputs", require_session_paths.ANALYSIS_INPUTS_ARTIFACT, JSON.stringify({
|
|
948
|
+
analyzedHashes: Object.fromEntries(fileEntries.map((entry) => [entry.path, entry.hash])),
|
|
949
|
+
...result.inputs
|
|
950
|
+
}), epoch, generation, require_session_paths.envelopeJsonArtifact);
|
|
951
|
+
this.publishPayloadArtifact("styles", require_session_paths.STYLES_ARTIFACT, fullCss, epoch, generation, require_session_paths.envelopeCssArtifact);
|
|
553
952
|
if (devMode) {
|
|
554
953
|
const systemPropsHash = (0, _animus_ui_extract_pipeline.contentHash)(systemPropsContent);
|
|
555
954
|
if (systemPropsHash !== this.lastSystemPropsHash) {
|
|
556
|
-
this.
|
|
955
|
+
this.writeSessionArtifact(require_session_paths.SYSTEM_PROPS_ARTIFACT, systemPropsContent);
|
|
557
956
|
this.lastSystemPropsHash = systemPropsHash;
|
|
558
957
|
}
|
|
559
|
-
} else this.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
958
|
+
} else this.writeSessionArtifact(require_session_paths.SYSTEM_PROPS_ARTIFACT, systemPropsContent);
|
|
959
|
+
this.publishAnalysisCommit(epoch, generation);
|
|
960
|
+
this.publishReplacementEpoch(epoch);
|
|
961
|
+
bt.total = this.elapsed(pipelineStart);
|
|
962
|
+
logBuildTimings(bt, manifest?.timing, (msg) => this.log(msg), this.verbose);
|
|
963
|
+
}
|
|
964
|
+
/** Open a status attempt (attemptId increments once per burst — a
|
|
965
|
+
* debouncing pre-write and the analysis that follows share one id). */
|
|
966
|
+
beginStatusAttempt() {
|
|
967
|
+
if (!this.statusAttemptOpen) {
|
|
968
|
+
this.statusAttemptId += 1;
|
|
969
|
+
this.statusAttemptOpen = true;
|
|
566
970
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
971
|
+
}
|
|
972
|
+
/** Write the session's analysis-status artifact (design D3). Terminal
|
|
973
|
+
* states (idle/failed) close the attempt. */
|
|
974
|
+
writeAnalysisStatus(state, pending, diagnostic) {
|
|
975
|
+
const status = {
|
|
976
|
+
schema: 1,
|
|
977
|
+
sessionId: this.sessionId,
|
|
978
|
+
attemptId: this.statusAttemptId,
|
|
979
|
+
state,
|
|
980
|
+
pending,
|
|
981
|
+
deadlineAt: Date.now() + this.debounceCeilingMs + STATUS_WATCHDOG_MS,
|
|
982
|
+
...diagnostic !== void 0 ? { diagnostic } : {}
|
|
983
|
+
};
|
|
984
|
+
this.writeSessionArtifact(require_session_paths.ANALYSIS_STATUS_ARTIFACT, JSON.stringify(status));
|
|
985
|
+
if (state === "idle" || state === "failed") this.statusAttemptOpen = false;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Orchestrator seam (Turbopack watcher): record watch events observed
|
|
989
|
+
* during the debounce window so a loader running ahead of the analysis
|
|
990
|
+
* has positive evidence to wait on (design D3 'debouncing'). Paths are
|
|
991
|
+
* filtered by the scan config and hashed at observation time.
|
|
992
|
+
*/
|
|
993
|
+
noteDebouncedWatchEvents(absPaths) {
|
|
994
|
+
if (!this.rootDir) return;
|
|
995
|
+
const additions = this.pendingFromBatch({ modifiedFiles: new Set(absPaths) });
|
|
996
|
+
if (additions.length === 0) return;
|
|
997
|
+
this.beginStatusAttempt();
|
|
998
|
+
for (const [key, hash] of additions) this.debouncePending.set(key, hash);
|
|
999
|
+
if (this.debounceStatusWriteScheduled) return;
|
|
1000
|
+
this.debounceStatusWriteScheduled = true;
|
|
1001
|
+
queueMicrotask(() => {
|
|
1002
|
+
this.debounceStatusWriteScheduled = false;
|
|
1003
|
+
if (this.debouncePending.size === 0) return;
|
|
1004
|
+
this.writeAnalysisStatus("debouncing", [...this.debouncePending.entries()]);
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
/** One pending microtask flushes a burst of debounce observations. */
|
|
1008
|
+
debounceStatusWriteScheduled = false;
|
|
1009
|
+
/**
|
|
1010
|
+
* Write one payload artifact into the session directory, enveloped with
|
|
1011
|
+
* `{sessionId, generation, replacementEpoch, payloadHash}` and rewritten
|
|
1012
|
+
* only when the PAYLOAD bytes changed (byte-identical re-analyses leave
|
|
1013
|
+
* disk untouched — spec: "Unchanged manifest is not rewritten"). The
|
|
1014
|
+
* recorded diskHash (hash of the enveloped bytes) feeds the
|
|
1015
|
+
* analysis-commit.
|
|
1016
|
+
*/
|
|
1017
|
+
publishPayloadArtifact(key, name, payload, epoch, generation, wrap) {
|
|
1018
|
+
const payloadHash = (0, _animus_ui_extract_pipeline.contentHash)(payload);
|
|
1019
|
+
if (this.artifactRecords[key] === null) this.artifactRecords[key] = this.seedPayloadRecord(key, name);
|
|
1020
|
+
const record = this.artifactRecords[key];
|
|
1021
|
+
if (record !== null && record.payloadHash === payloadHash) return;
|
|
1022
|
+
const envelope = {
|
|
1023
|
+
sessionId: this.sessionId,
|
|
1024
|
+
generation,
|
|
1025
|
+
replacementEpoch: epoch,
|
|
1026
|
+
payloadHash
|
|
1027
|
+
};
|
|
1028
|
+
const bytes = wrap(payload, JSON.stringify(envelope));
|
|
1029
|
+
this.writeSessionArtifact(name, bytes);
|
|
1030
|
+
this.artifactRecords[key] = {
|
|
1031
|
+
payloadHash,
|
|
1032
|
+
diskHash: (0, _animus_ui_extract_pipeline.contentHash)(bytes)
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
/** Reconstruct a payload write guard from the on-disk artifact's envelope
|
|
1036
|
+
* (same-session restart: a byte-identical payload must not rewrite). */
|
|
1037
|
+
seedPayloadRecord(key, name) {
|
|
1038
|
+
let bytes;
|
|
1039
|
+
try {
|
|
1040
|
+
bytes = (0, fs.readFileSync)((0, path.join)(this.sessionDir, name), "utf-8");
|
|
1041
|
+
} catch {
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
let envelope;
|
|
1045
|
+
try {
|
|
1046
|
+
envelope = key === "styles" ? require_session_paths.readCssEnvelope(bytes) : require_session_paths.readJsonEnvelope(bytes);
|
|
1047
|
+
} catch {
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
if (!envelope || typeof envelope.payloadHash !== "string") return null;
|
|
1051
|
+
return {
|
|
1052
|
+
payloadHash: envelope.payloadHash,
|
|
1053
|
+
diskHash: (0, _animus_ui_extract_pipeline.contentHash)(bytes)
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
/** Last analysis-commit persisted in this session's directory, or null. */
|
|
1057
|
+
seedCommitFromDisk() {
|
|
1058
|
+
try {
|
|
1059
|
+
const parsed = JSON.parse((0, fs.readFileSync)(require_session_paths.analysisCommitPath(this.sessionDir), "utf-8"));
|
|
1060
|
+
return parsed.schema === 1 && parsed.sessionId === this.sessionId && typeof parsed.generation === "number" ? parsed : null;
|
|
1061
|
+
} catch {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Publish the analysis-commit — the transaction identity (design D1).
|
|
1067
|
+
* Written AFTER every payload and BEFORE the epoch; skipped entirely when
|
|
1068
|
+
* the payload set (disk hashes) and epoch are unchanged, so a no-op
|
|
1069
|
+
* re-analysis neither re-keys loader-worker hydration nor burns a
|
|
1070
|
+
* generation.
|
|
1071
|
+
*/
|
|
1072
|
+
publishAnalysisCommit(epoch, generation) {
|
|
1073
|
+
const manifestHash = this.artifactRecords.manifest?.diskHash ?? "";
|
|
1074
|
+
const inputsHash = this.persistAnalysisInputs ? this.artifactRecords.inputs?.diskHash ?? "" : void 0;
|
|
1075
|
+
const stylesHash = this.artifactRecords.styles?.diskHash ?? "";
|
|
1076
|
+
const prev = this.lastCommit;
|
|
1077
|
+
if (prev !== null && prev.manifestHash === manifestHash && prev.inputsHash === inputsHash && prev.stylesHash === stylesHash && prev.replacementEpoch === epoch) return;
|
|
1078
|
+
const commit = {
|
|
1079
|
+
schema: 1,
|
|
1080
|
+
sessionId: this.sessionId,
|
|
1081
|
+
generation,
|
|
1082
|
+
replacementEpoch: epoch,
|
|
1083
|
+
manifestHash,
|
|
1084
|
+
...inputsHash !== void 0 ? { inputsHash } : {},
|
|
1085
|
+
stylesHash
|
|
1086
|
+
};
|
|
1087
|
+
this.writeSessionArtifact(require_session_paths.ANALYSIS_COMMIT_ARTIFACT, JSON.stringify(commit));
|
|
1088
|
+
this.lastCommit = commit;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Publish the canonical replacement epoch: maintain the session-scoped
|
|
1092
|
+
* disk witness `{schema, sessionId, epoch}`, rewritten ONLY when the
|
|
1093
|
+
* epoch VALUE changes so style-only analyses and same-session restarts
|
|
1094
|
+
* leave bytes and mtime untouched, then expose the value through the
|
|
1095
|
+
* singleton for the plugin's needBuild fan-out and the loader's catch-up
|
|
1096
|
+
* re-check. Every VALUE move also reconciles sibling sessions' epoch
|
|
1097
|
+
* artifacts (below) — the webpack cold-cache validity witness.
|
|
1098
|
+
*/
|
|
1099
|
+
publishReplacementEpoch(epoch) {
|
|
1100
|
+
if (this.lastEpochValue === null) this.lastEpochValue = this.diskEpochValue();
|
|
1101
|
+
if (epoch !== this.lastEpochValue) {
|
|
1102
|
+
this.writeSessionArtifact(require_session_paths.REPLACEMENT_EPOCH_ARTIFACT, JSON.stringify({
|
|
1103
|
+
schema: 1,
|
|
1104
|
+
sessionId: this.sessionId,
|
|
1105
|
+
epoch
|
|
1106
|
+
}));
|
|
1107
|
+
this.lastEpochValue = epoch;
|
|
1108
|
+
this.reconcileSiblingEpochs(epoch);
|
|
1109
|
+
}
|
|
1110
|
+
require_singleton.setReplacementEpoch(epoch);
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Sibling epoch reconciliation — the cross-session half of the webpack
|
|
1114
|
+
* persistent-cache witness (spec: dev-served-transform-coherence,
|
|
1115
|
+
* "Offline change invalidates restored modules"). Restored-module
|
|
1116
|
+
* snapshots reference the epoch artifact of the session that BUILT them;
|
|
1117
|
+
* with session-scoped trees that file would otherwise sit untouched
|
|
1118
|
+
* forever, keeping stale snapshots valid. Whenever this session's epoch
|
|
1119
|
+
* value moves, delete every sibling epoch artifact that disagrees (their
|
|
1120
|
+
* snapshots must invalidate — restore-on-demand then rebuilds from the
|
|
1121
|
+
* current generation) and leave agreeing siblings byte-untouched (warm
|
|
1122
|
+
* restores stay valid). Deleting a stale artifact is pruning, not a
|
|
1123
|
+
* foreign-session write; races with concurrent sessions are tolerated
|
|
1124
|
+
* (S14).
|
|
1125
|
+
*/
|
|
1126
|
+
/** Sibling ids already reconciled away (artifact deleted or absent) —
|
|
1127
|
+
* nothing left to invalidate for them on later moves. */
|
|
1128
|
+
reconciledSiblingIds = /* @__PURE__ */ new Set();
|
|
1129
|
+
/** sessions-root listing memo, keyed by the root dir's mtime — a new or
|
|
1130
|
+
* pruned sibling DIRECTORY moves it; agreeing siblings stay listed. */
|
|
1131
|
+
siblingListing = null;
|
|
1132
|
+
reconcileSiblingEpochs(epoch) {
|
|
1133
|
+
const rootPath = require_session_paths.sessionsRootDir(this.rootDir);
|
|
1134
|
+
let entries;
|
|
1135
|
+
try {
|
|
1136
|
+
const mtimeMs = (0, fs.statSync)(rootPath).mtimeMs;
|
|
1137
|
+
if (this.siblingListing?.mtimeMs === mtimeMs) entries = this.siblingListing.entries;
|
|
1138
|
+
else {
|
|
1139
|
+
entries = (0, fs.readdirSync)(rootPath);
|
|
1140
|
+
this.siblingListing = {
|
|
1141
|
+
mtimeMs,
|
|
1142
|
+
entries
|
|
1143
|
+
};
|
|
574
1144
|
}
|
|
1145
|
+
} catch {
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
for (const entry of entries) {
|
|
1149
|
+
if (entry === this.sessionId) continue;
|
|
1150
|
+
if (this.reconciledSiblingIds.has(entry)) continue;
|
|
1151
|
+
const siblingEpochPath = (0, path.join)(rootPath, entry, require_session_paths.REPLACEMENT_EPOCH_ARTIFACT);
|
|
1152
|
+
try {
|
|
1153
|
+
if (JSON.parse((0, fs.readFileSync)(siblingEpochPath, "utf-8")).epoch === epoch) continue;
|
|
1154
|
+
} catch (err) {
|
|
1155
|
+
if (err?.code === "ENOENT") {
|
|
1156
|
+
this.reconciledSiblingIds.add(entry);
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
try {
|
|
1161
|
+
(0, fs.unlinkSync)(siblingEpochPath);
|
|
1162
|
+
} catch {}
|
|
1163
|
+
this.reconciledSiblingIds.add(entry);
|
|
575
1164
|
}
|
|
576
|
-
bt.total = this.elapsed(pipelineStart);
|
|
577
|
-
logBuildTimings(bt, manifest?.timing, (msg) => this.log(msg), this.verbose);
|
|
578
1165
|
}
|
|
579
|
-
/**
|
|
580
|
-
* unreadable
|
|
581
|
-
|
|
582
|
-
* changes mtime/inode and needlessly re-keys Turbopack loader-worker
|
|
583
|
-
* hydration). */
|
|
584
|
-
diskArtifactHash(name) {
|
|
1166
|
+
/** Epoch value held by this session's on-disk artifact, or null when
|
|
1167
|
+
* absent, unreadable, or not the expected schema. */
|
|
1168
|
+
diskEpochValue() {
|
|
585
1169
|
try {
|
|
586
|
-
|
|
1170
|
+
const parsed = JSON.parse((0, fs.readFileSync)(require_session_paths.replacementEpochPath(this.sessionDir), "utf-8"));
|
|
1171
|
+
return parsed.schema === 1 && typeof parsed.epoch === "string" ? parsed.epoch : null;
|
|
587
1172
|
} catch {
|
|
588
1173
|
return null;
|
|
589
1174
|
}
|
|
590
1175
|
}
|
|
591
|
-
/**
|
|
1176
|
+
/** Session-start hygiene (design D2): delete legacy flat artifacts
|
|
1177
|
+
* (unreachable by session-scoped loaders) and prune sibling session
|
|
1178
|
+
* directories older than the retention window — never the own dir,
|
|
1179
|
+
* tolerating races with concurrently-pruning sessions (S14). */
|
|
1180
|
+
runSessionStartHygiene() {
|
|
1181
|
+
if (this.sessionStartHygieneDone) return;
|
|
1182
|
+
this.sessionStartHygieneDone = true;
|
|
1183
|
+
const animusDir = (0, path.join)(this.rootDir, ".animus");
|
|
1184
|
+
for (const name of LEGACY_FLAT_ARTIFACTS) try {
|
|
1185
|
+
(0, fs.unlinkSync)((0, path.join)(animusDir, name));
|
|
1186
|
+
} catch {}
|
|
1187
|
+
let entries;
|
|
1188
|
+
try {
|
|
1189
|
+
entries = (0, fs.readdirSync)(require_session_paths.sessionsRootDir(this.rootDir));
|
|
1190
|
+
} catch {
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
const cutoff = Date.now() - SESSION_DIR_MAX_AGE_MS;
|
|
1194
|
+
for (const entry of entries) {
|
|
1195
|
+
if (entry === this.sessionId) continue;
|
|
1196
|
+
const dir = (0, path.join)(require_session_paths.sessionsRootDir(this.rootDir), entry);
|
|
1197
|
+
try {
|
|
1198
|
+
if ((0, fs.statSync)(dir).mtimeMs < cutoff) (0, fs.rmSync)(dir, {
|
|
1199
|
+
recursive: true,
|
|
1200
|
+
force: true
|
|
1201
|
+
});
|
|
1202
|
+
} catch {}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
/** Ensure the session directory exists and write one artifact into it.
|
|
592
1206
|
* Write-then-rename so cross-process readers (Turbopack loader workers)
|
|
593
1207
|
* can never observe a torn half-written file. The tmp name carries the
|
|
594
1208
|
* pid — Next dev evaluates the config in more than one process, and two
|
|
595
1209
|
* sessions writing the same artifact must not race on one tmp path. */
|
|
596
|
-
|
|
597
|
-
const dir =
|
|
598
|
-
|
|
1210
|
+
writeSessionArtifact(name, content) {
|
|
1211
|
+
const dir = this.sessionDir;
|
|
1212
|
+
(0, fs.mkdirSync)(dir, { recursive: true });
|
|
599
1213
|
const tmpPath = (0, path.join)(dir, `.${name}.${process.pid}.tmp`);
|
|
600
1214
|
(0, fs.writeFileSync)(tmpPath, content);
|
|
601
1215
|
(0, fs.renameSync)(tmpPath, (0, path.join)(dir, name));
|
|
1216
|
+
this.onArtifactWrite?.(name, content);
|
|
602
1217
|
}
|
|
603
1218
|
/**
|
|
604
1219
|
* asset() placeholder substitution (global-styles-system): resolve each
|
|
605
|
-
* referenced specifier through Node resolution, copy the bytes into
|
|
606
|
-
*
|
|
607
|
-
* RELATIVE url
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
1220
|
+
* referenced specifier through Node resolution, copy the bytes into the
|
|
1221
|
+
* session directory's `assets/` under a content-hashed name, and
|
|
1222
|
+
* substitute a RELATIVE url — relative to the session-scoped styles.css,
|
|
1223
|
+
* which sits beside `assets/`, so the emitted `./assets/<file>` form is
|
|
1224
|
+
* unchanged. The stylesheet is processed by Next's own CSS pipeline
|
|
1225
|
+
* (webpack and Turbopack alike), which applies its native asset handling
|
|
1226
|
+
* — publicPath and output hashing — to relative url() references.
|
|
1227
|
+
* Unsubstitutable specifiers warn and emit literally in non-strict mode,
|
|
1228
|
+
* fail the build under `strict: true`.
|
|
612
1229
|
*/
|
|
613
1230
|
substituteAssetReferences(globalCss, devMode) {
|
|
614
1231
|
const specifiers = (0, _animus_ui_extract_pipeline.findAssetSpecifiers)(globalCss);
|
|
615
|
-
const assetsDir = (0, path.join)(this.
|
|
1232
|
+
const assetsDir = (0, path.join)(this.sessionDir, require_session_paths.SESSION_ASSETS_DIR);
|
|
616
1233
|
const expected = /* @__PURE__ */ new Set();
|
|
617
1234
|
this.assetDependencyPaths.clear();
|
|
618
1235
|
this.assetDependencyKeys.clear();
|
|
@@ -658,9 +1275,9 @@ var ExtractionSession = class {
|
|
|
658
1275
|
if (!devMode) pruneStaleAssets(assetsDir, expected);
|
|
659
1276
|
return (0, _animus_ui_extract_pipeline.substituteAssetPlaceholders)(globalCss, urlBySpecifier);
|
|
660
1277
|
}
|
|
661
|
-
trackAssetDependency(path$
|
|
662
|
-
this.assetDependencyPaths.add(path$
|
|
663
|
-
for (const key of (0, _animus_ui_extract_pipeline.toWatchKeys)(path$
|
|
1278
|
+
trackAssetDependency(path$9) {
|
|
1279
|
+
this.assetDependencyPaths.add(path$9);
|
|
1280
|
+
for (const key of (0, _animus_ui_extract_pipeline.toWatchKeys)(path$9)) this.assetDependencyKeys.add(key);
|
|
664
1281
|
}
|
|
665
1282
|
/**
|
|
666
1283
|
* Resolve an asset specifier to an absolute file via the shared pipeline
|
|
@@ -708,7 +1325,37 @@ function pruneStaleAssets(assetsDir, expected) {
|
|
|
708
1325
|
}
|
|
709
1326
|
}
|
|
710
1327
|
//#endregion
|
|
1328
|
+
//#region src/loader-path.ts
|
|
1329
|
+
const __dirname$2 = (0, path.dirname)((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
1330
|
+
/**
|
|
1331
|
+
* The one loader-path derivation: first existing dist candidate under the
|
|
1332
|
+
* plugin dir, else the in-repo source fallback. Both loader resolvers below
|
|
1333
|
+
* are thin wrappers, so rule registration and any loader-chain predicate
|
|
1334
|
+
* can never drift (openspec: next-webpack-served-transform-coherence,
|
|
1335
|
+
* design D1).
|
|
1336
|
+
*/
|
|
1337
|
+
function resolveLoaderPath(pluginDir, distCandidates, sourceFallback) {
|
|
1338
|
+
for (const candidate of distCandidates) {
|
|
1339
|
+
const distPath = (0, path.resolve)(pluginDir, candidate);
|
|
1340
|
+
if ((0, fs.existsSync)(distPath)) return distPath;
|
|
1341
|
+
}
|
|
1342
|
+
return (0, path.resolve)(pluginDir, sourceFallback);
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* This package's webpack loader path, shared by the rule registration
|
|
1346
|
+
* (with-animus) and the needBuild loader-chain predicate (plugin). Compiled
|
|
1347
|
+
* dist ships `loader.mjs` next to this module; running from source falls
|
|
1348
|
+
* back to the TypeScript loader.
|
|
1349
|
+
*/
|
|
1350
|
+
function resolveAnimusLoaderPath() {
|
|
1351
|
+
return resolveLoaderPath(__dirname$2, ["loader.mjs"], "loader.ts");
|
|
1352
|
+
}
|
|
1353
|
+
//#endregion
|
|
711
1354
|
//#region src/plugin.ts
|
|
1355
|
+
/** Loud unsupported-version failure (design D7): the coherence mechanism
|
|
1356
|
+
* cannot exist without the per-compilation needBuild hook, so a webpack
|
|
1357
|
+
* that lacks it fails immediately instead of serving stale transforms. */
|
|
1358
|
+
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 covered by the animus webpack gauntlet.";
|
|
712
1359
|
const PLUGIN_NAME = "AnimusWebpackPlugin";
|
|
713
1360
|
/**
|
|
714
1361
|
* Webpack adapter for the extraction pipeline. All pipeline logic lives in
|
|
@@ -722,11 +1369,57 @@ var AnimusWebpackPlugin = class {
|
|
|
722
1369
|
session;
|
|
723
1370
|
initialized = false;
|
|
724
1371
|
aliasesExtracted = false;
|
|
1372
|
+
/** Loader paths counting as "the animus loader" in a module's chain —
|
|
1373
|
+
* this package's own loader plus an optional harness override. */
|
|
1374
|
+
animusLoaderPaths;
|
|
1375
|
+
/** Epoch THIS compiler last proceeded with (set after every awaited
|
|
1376
|
+
* watchRun transaction — including joined ones). Null until the
|
|
1377
|
+
* initialization pipeline publishes. */
|
|
1378
|
+
lastBuiltEpoch = null;
|
|
1379
|
+
/** Armed by watchRun when the transaction moved the epoch; captured and
|
|
1380
|
+
* disarmed by the next compilation's needBuild wiring (design D1). */
|
|
1381
|
+
epochMovedForNextCompilation = false;
|
|
725
1382
|
constructor(options) {
|
|
726
1383
|
this.options = options;
|
|
727
1384
|
(0, _animus_ui_extract_pipeline.assertNoRetiredEngineSelection)(options.engine);
|
|
728
1385
|
require_singleton.setSharedEngine(options.engine ?? "v2");
|
|
729
1386
|
this.session = new ExtractionSession(options);
|
|
1387
|
+
this.animusLoaderPaths = new Set([resolveAnimusLoaderPath()]);
|
|
1388
|
+
if (options.loaderPath) this.animusLoaderPaths.add(options.loaderPath);
|
|
1389
|
+
}
|
|
1390
|
+
/** True when the module's loader chain contains the animus loader — the
|
|
1391
|
+
* needBuild fan-out predicate (design D1: no historical records; covers
|
|
1392
|
+
* restored persistent-cache modules and raw passthroughs alike). */
|
|
1393
|
+
moduleUsesAnimusLoader(module) {
|
|
1394
|
+
const loaders = module?.loaders;
|
|
1395
|
+
if (!Array.isArray(loaders)) return false;
|
|
1396
|
+
return loaders.some((entry) => typeof entry?.loader === "string" && this.animusLoaderPaths.has(entry.loader));
|
|
1397
|
+
}
|
|
1398
|
+
/** Append the exact epoch artifact path to `watchOptions.ignored`,
|
|
1399
|
+
* preserving the user's shape (design D2: the artifact participates in
|
|
1400
|
+
* module snapshots via addDependency but never triggers live watch
|
|
1401
|
+
* turns — no echo compilations). Session identity is process-claimed,
|
|
1402
|
+
* so every compiler's plugin instance derives the SAME session-scoped
|
|
1403
|
+
* path here regardless of which instance ends up owning the pipeline.
|
|
1404
|
+
* Derived from the ONE root (session.rootDir, set at config time by
|
|
1405
|
+
* with-animus); compiler.context is the fallback for bare-apply
|
|
1406
|
+
* harnesses — identical when compiler.context === cwd. */
|
|
1407
|
+
appendEpochWatchIgnore(compiler) {
|
|
1408
|
+
const options = compiler.options;
|
|
1409
|
+
if (!options) return;
|
|
1410
|
+
const epochPath = require_session_paths.replacementEpochPath(require_session_paths.sessionArtifactDir(this.session.rootDir ?? compiler.context, this.session.sessionId));
|
|
1411
|
+
options.watchOptions ??= {};
|
|
1412
|
+
const watchOptions = options.watchOptions;
|
|
1413
|
+
const ignored = watchOptions.ignored;
|
|
1414
|
+
if (ignored === void 0 || ignored === null) watchOptions.ignored = [epochPath];
|
|
1415
|
+
else if (Array.isArray(ignored)) {
|
|
1416
|
+
if (!ignored.includes(epochPath)) ignored.push(epochPath);
|
|
1417
|
+
} else if (typeof ignored === "string") watchOptions.ignored = [ignored, epochPath];
|
|
1418
|
+
else if (ignored instanceof RegExp) watchOptions.ignored = (path) => path === epochPath || ignored.test(path);
|
|
1419
|
+
else if (typeof ignored === "function") {
|
|
1420
|
+
const original = ignored;
|
|
1421
|
+
watchOptions.ignored = (path) => path === epochPath || Boolean(original(path));
|
|
1422
|
+
}
|
|
730
1423
|
}
|
|
731
1424
|
/** Extract path aliases from webpack's resolve.alias config. Runs once per
|
|
732
1425
|
* plugin instance — the resolve config is immutable after apply. */
|
|
@@ -751,12 +1444,29 @@ var AnimusWebpackPlugin = class {
|
|
|
751
1444
|
}
|
|
752
1445
|
apply(compiler) {
|
|
753
1446
|
if (compiler.options?.name === "edge-server") return;
|
|
1447
|
+
const NormalModule = compiler.webpack?.NormalModule;
|
|
1448
|
+
if (typeof NormalModule?.getCompilationHooks !== "function") throw new Error(UNSUPPORTED_WEBPACK_MESSAGE);
|
|
1449
|
+
this.appendEpochWatchIgnore(compiler);
|
|
1450
|
+
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
|
|
1451
|
+
const epochMoved = this.epochMovedForNextCompilation;
|
|
1452
|
+
this.epochMovedForNextCompilation = false;
|
|
1453
|
+
const needBuild = NormalModule.getCompilationHooks(compilation).needBuild;
|
|
1454
|
+
if (typeof needBuild?.tapAsync !== "function") throw new Error(UNSUPPORTED_WEBPACK_MESSAGE);
|
|
1455
|
+
needBuild.tapAsync(PLUGIN_NAME, (module, _context, callback) => {
|
|
1456
|
+
if (epochMoved && this.moduleUsesAnimusLoader(module)) {
|
|
1457
|
+
callback(null, true);
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
callback();
|
|
1461
|
+
});
|
|
1462
|
+
});
|
|
754
1463
|
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
|
|
755
1464
|
const registerSystemDependencies = () => {
|
|
756
1465
|
for (const dep of this.session.systemDependencyPaths) if ((0, fs.existsSync)(dep)) compilation.fileDependencies.add(dep);
|
|
757
1466
|
else compilation.missingDependencies.add(dep);
|
|
758
1467
|
for (const dep of this.session.assetDependencyPaths) if ((0, fs.existsSync)(dep)) compilation.fileDependencies.add(dep);
|
|
759
1468
|
else compilation.missingDependencies.add(dep);
|
|
1469
|
+
for (const root of this.session.externalWatchRoots) compilation.contextDependencies.add(root);
|
|
760
1470
|
};
|
|
761
1471
|
registerSystemDependencies();
|
|
762
1472
|
const stage = compiler.webpack?.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL ?? -2e3;
|
|
@@ -768,7 +1478,7 @@ var AnimusWebpackPlugin = class {
|
|
|
768
1478
|
registerSystemDependencies();
|
|
769
1479
|
const css = require_singleton.getSharedCss();
|
|
770
1480
|
if (!css || !RawSource) return;
|
|
771
|
-
const cssPath = (
|
|
1481
|
+
const cssPath = require_session_paths.stylesPath(require_session_paths.sessionArtifactDir(this.session.rootDir || compiler.context, this.session.sessionId));
|
|
772
1482
|
if (compilation.getAsset(cssPath)) {
|
|
773
1483
|
compilation.updateAsset(cssPath, new RawSource(css));
|
|
774
1484
|
return;
|
|
@@ -793,21 +1503,23 @@ var AnimusWebpackPlugin = class {
|
|
|
793
1503
|
this.extractAliases(_compiler);
|
|
794
1504
|
if (!this.initialized) {
|
|
795
1505
|
const existing = require_singleton.getAnalysisPromise();
|
|
796
|
-
if (existing)
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1506
|
+
if (existing) await existing;
|
|
1507
|
+
else {
|
|
1508
|
+
const promise = this.session.runFullPipeline();
|
|
1509
|
+
require_singleton.setAnalysisPromise(promise);
|
|
1510
|
+
await promise;
|
|
800
1511
|
}
|
|
801
|
-
const promise = this.session.runFullPipeline();
|
|
802
|
-
require_singleton.setAnalysisPromise(promise);
|
|
803
|
-
await promise;
|
|
804
1512
|
this.initialized = true;
|
|
1513
|
+
this.lastBuiltEpoch = require_singleton.getReplacementEpoch();
|
|
805
1514
|
return;
|
|
806
1515
|
}
|
|
807
1516
|
await this.session.handleWatchUpdate({
|
|
808
1517
|
modifiedFiles: _compiler.modifiedFiles,
|
|
809
1518
|
removedFiles: _compiler.removedFiles
|
|
810
1519
|
});
|
|
1520
|
+
const epoch = require_singleton.getReplacementEpoch();
|
|
1521
|
+
this.epochMovedForNextCompilation = this.lastBuiltEpoch !== null && epoch !== null && epoch !== this.lastBuiltEpoch;
|
|
1522
|
+
if (epoch !== null) this.lastBuiltEpoch = epoch;
|
|
811
1523
|
});
|
|
812
1524
|
}
|
|
813
1525
|
/**
|
|
@@ -816,6 +1528,21 @@ var AnimusWebpackPlugin = class {
|
|
|
816
1528
|
resetForHmr() {
|
|
817
1529
|
this.session.resetForHmr();
|
|
818
1530
|
}
|
|
1531
|
+
/** Session identity (process-claimed) — with-animus derives the
|
|
1532
|
+
* session-scoped alias/stub paths from it (design D2). */
|
|
1533
|
+
get sessionId() {
|
|
1534
|
+
return this.session.sessionId;
|
|
1535
|
+
}
|
|
1536
|
+
/** Set the project root at CONFIG time (with-animus) — the ONE root all
|
|
1537
|
+
* session-path derivations use. The compiler hooks re-assert it from
|
|
1538
|
+
* compiler.context (identical when compiler.context === cwd). */
|
|
1539
|
+
setRootDir(rootDir) {
|
|
1540
|
+
this.session.rootDir = rootDir;
|
|
1541
|
+
}
|
|
1542
|
+
/** This session's artifact directory, derived from the one root. */
|
|
1543
|
+
get sessionDir() {
|
|
1544
|
+
return this.session.sessionDir;
|
|
1545
|
+
}
|
|
819
1546
|
/** Expose options for the loader */
|
|
820
1547
|
getOptions() {
|
|
821
1548
|
return this.options;
|
|
@@ -854,25 +1581,35 @@ function resolveTurbopackMode(options, env = process.env) {
|
|
|
854
1581
|
if (mode === "auto") return env.TURBOPACK !== void 0;
|
|
855
1582
|
return false;
|
|
856
1583
|
}
|
|
1584
|
+
/** rootDir-relative module request for an absolute path — always forward
|
|
1585
|
+
* slashes, even when path.relative produced Windows separators. */
|
|
1586
|
+
function rootRelativeRequest(rootDir, absPath) {
|
|
1587
|
+
return `./${(0, path.relative)(rootDir, absPath).replace(/\\/g, "/")}`;
|
|
1588
|
+
}
|
|
857
1589
|
/**
|
|
858
1590
|
* Build the `turbopack` config fragment: one glob-keyed loader rule
|
|
859
1591
|
* (file-level allowlisting lives in the loader via manifest lookup — Next 15
|
|
860
1592
|
* rules have no condition algebra) plus resolve aliases for the virtual
|
|
861
1593
|
* system-props id, the emitter's stylesheet id, and each collected external
|
|
862
|
-
* package specifier redirected to its source entry.
|
|
1594
|
+
* package specifier redirected to its source entry. Loader options carry
|
|
1595
|
+
* the session identity (design D2: an options-borne, JSON-serializable
|
|
1596
|
+
* Turbopack task input — restart-cold by construction), and the artifact
|
|
1597
|
+
* aliases point into the session-scoped tree.
|
|
863
1598
|
*/
|
|
864
1599
|
function buildTurbopackConfig(args) {
|
|
865
|
-
const { rootDir, loaderPath, options, externalSourceEntries } = args;
|
|
1600
|
+
const { rootDir, loaderPath, options, externalSourceEntries, sessionId, sessionDir } = args;
|
|
866
1601
|
const loaderOptions = {
|
|
867
1602
|
rootDir,
|
|
1603
|
+
sessionId,
|
|
1604
|
+
sessionDir,
|
|
868
1605
|
...options.strict !== void 0 ? { strict: options.strict } : {},
|
|
869
1606
|
...options.cssImportTarget !== void 0 ? { cssImportTarget: options.cssImportTarget } : {}
|
|
870
1607
|
};
|
|
871
1608
|
const resolveAlias = {
|
|
872
|
-
[TURBOPACK_SYSTEM_PROPS_ID]:
|
|
873
|
-
".animus/styles.css":
|
|
1609
|
+
[TURBOPACK_SYSTEM_PROPS_ID]: rootRelativeRequest(rootDir, (0, path.join)(sessionDir, require_session_paths.SYSTEM_PROPS_ARTIFACT)),
|
|
1610
|
+
".animus/styles.css": rootRelativeRequest(rootDir, (0, path.join)(sessionDir, require_session_paths.STYLES_ARTIFACT))
|
|
874
1611
|
};
|
|
875
|
-
for (const [specifier, srcEntry] of externalSourceEntries) resolveAlias[specifier] =
|
|
1612
|
+
for (const [specifier, srcEntry] of externalSourceEntries) resolveAlias[specifier] = rootRelativeRequest(rootDir, srcEntry);
|
|
876
1613
|
return {
|
|
877
1614
|
rules: { [ANIMUS_TURBOPACK_RULE_GLOB]: { loaders: [{
|
|
878
1615
|
loader: loaderPath,
|
|
@@ -889,11 +1626,7 @@ function buildTurbopackConfig(args) {
|
|
|
889
1626
|
* not guaranteed.
|
|
890
1627
|
*/
|
|
891
1628
|
function resolveTurbopackLoaderPath(pluginDir) {
|
|
892
|
-
|
|
893
|
-
const distPath = (0, path.resolve)(pluginDir, candidate);
|
|
894
|
-
if ((0, fs.existsSync)(distPath)) return distPath;
|
|
895
|
-
}
|
|
896
|
-
return (0, path.resolve)(pluginDir, "turbopack-loader.ts");
|
|
1629
|
+
return resolveLoaderPath(pluginDir, ["turbopack-loader.cjs", "turbopack-loader.mjs"], "turbopack-loader.ts");
|
|
897
1630
|
}
|
|
898
1631
|
//#endregion
|
|
899
1632
|
//#region src/turbopack-orchestrator.ts
|
|
@@ -904,12 +1637,15 @@ function resolveTurbopackLoaderPath(pluginDir) {
|
|
|
904
1637
|
* filesystem watcher — the bundler only ever sees the `.animus/` disk
|
|
905
1638
|
* artifacts.
|
|
906
1639
|
*/
|
|
907
|
-
/** Run the full pipeline with the Turbopack emitter identity:
|
|
908
|
-
*
|
|
909
|
-
*
|
|
1640
|
+
/** Run the full pipeline with the Turbopack emitter identity: the virtual
|
|
1641
|
+
* system-props id (Turbopack rejects the webpack path's absolute-path
|
|
1642
|
+
* imports). Turbopack orchestration persists the analysis-inputs
|
|
1643
|
+
* hydration corpus — its isolated loader workers replay it (spec:
|
|
1644
|
+
* next-turbopack-integration, "Manifest disk artifact"; webpack mode
|
|
1645
|
+
* skips the corpus). */
|
|
910
1646
|
async function runTurbopackPipeline(session) {
|
|
911
|
-
session.persistAnalysisInputs = true;
|
|
912
1647
|
session.systemPropsModuleId = TURBOPACK_SYSTEM_PROPS_ID;
|
|
1648
|
+
session.persistAnalysisInputs = true;
|
|
913
1649
|
await session.runFullPipeline();
|
|
914
1650
|
}
|
|
915
1651
|
const activeWatcherRoots = /* @__PURE__ */ new Set();
|
|
@@ -935,11 +1671,31 @@ const IGNORED_SEGMENTS = new Set([
|
|
|
935
1671
|
function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.watch) {
|
|
936
1672
|
if (activeWatcherRoots.has(rootDir)) return null;
|
|
937
1673
|
activeWatcherRoots.add(rootDir);
|
|
1674
|
+
if (typeof session.noteDebouncedWatchEvents === "function") session.debounceCeilingMs = debounceMs;
|
|
938
1675
|
const pendingPaths = /* @__PURE__ */ new Set();
|
|
939
1676
|
const watchers = /* @__PURE__ */ new Map();
|
|
940
1677
|
let timer = null;
|
|
941
1678
|
let updateChain = Promise.resolve();
|
|
942
1679
|
let closed = false;
|
|
1680
|
+
const externalWatchers = /* @__PURE__ */ new Map();
|
|
1681
|
+
let pendingOpened = /* @__PURE__ */ new Map();
|
|
1682
|
+
let capturedDuringSnapshot = [];
|
|
1683
|
+
const externalDiagnosticKey = (root) => `external-watch:${root}`;
|
|
1684
|
+
const failureReason = (err) => {
|
|
1685
|
+
const code = err?.code;
|
|
1686
|
+
if (code === "EMFILE" || code === "ENFILE" || code === "ENOSPC" || code === "EPERM") return `capacity(${code})`;
|
|
1687
|
+
if (/inotify|too many/i.test(String(err))) return "capacity";
|
|
1688
|
+
return code ?? "error";
|
|
1689
|
+
};
|
|
1690
|
+
const degradeExternalRoot = (root, err) => {
|
|
1691
|
+
externalWatchers.get(root)?.close();
|
|
1692
|
+
externalWatchers.delete(root);
|
|
1693
|
+
pendingOpened.get(root)?.close();
|
|
1694
|
+
pendingOpened.delete(root);
|
|
1695
|
+
const message = `ANIMUS_EXTERNAL_WATCH_UNAVAILABLE root=${(0, path.relative)(rootDir, root)} reason=${failureReason(err)} effect=changes in this workspace source may require restart`;
|
|
1696
|
+
session.stickyDiagnostics?.set(externalDiagnosticKey(root), message);
|
|
1697
|
+
console.warn(`[animus-extract] ${message}`);
|
|
1698
|
+
};
|
|
943
1699
|
const flush = () => {
|
|
944
1700
|
timer = null;
|
|
945
1701
|
const modifiedFiles = /* @__PURE__ */ new Set();
|
|
@@ -951,8 +1707,15 @@ function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.w
|
|
|
951
1707
|
removedFiles
|
|
952
1708
|
}).catch((err) => {
|
|
953
1709
|
console.warn(`[animus-extract] Turbopack watch update failed: ${String(err)}`);
|
|
1710
|
+
}).then(() => {
|
|
1711
|
+
if (pendingOpened.size > 0 || capturedDuringSnapshot.length > 0) rollbackPendingExternal();
|
|
954
1712
|
}));
|
|
955
1713
|
};
|
|
1714
|
+
const rollbackPendingExternal = () => {
|
|
1715
|
+
for (const watcher of pendingOpened.values()) watcher.close();
|
|
1716
|
+
pendingOpened = /* @__PURE__ */ new Map();
|
|
1717
|
+
capturedDuringSnapshot = [];
|
|
1718
|
+
};
|
|
956
1719
|
const closeAll = () => {
|
|
957
1720
|
if (closed) return;
|
|
958
1721
|
closed = true;
|
|
@@ -960,12 +1723,29 @@ function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.w
|
|
|
960
1723
|
timer = null;
|
|
961
1724
|
for (const watcher of watchers.values()) watcher.close();
|
|
962
1725
|
watchers.clear();
|
|
1726
|
+
for (const watcher of externalWatchers.values()) watcher.close();
|
|
1727
|
+
externalWatchers.clear();
|
|
1728
|
+
rollbackPendingExternal();
|
|
1729
|
+
session.onExternalRootResolved = null;
|
|
1730
|
+
session.onExternalRootsCommitted = null;
|
|
963
1731
|
activeWatcherRoots.delete(rootDir);
|
|
964
1732
|
};
|
|
965
1733
|
const onWatcherError = (err) => {
|
|
966
1734
|
closeAll();
|
|
967
1735
|
console.warn(`[animus-extract] Turbopack dev watcher failed (${String(err)}); source edits require a dev-server restart`);
|
|
968
1736
|
};
|
|
1737
|
+
const enqueuePath = (abs) => {
|
|
1738
|
+
pendingPaths.add(abs);
|
|
1739
|
+
try {
|
|
1740
|
+
session.noteDebouncedWatchEvents?.([abs]);
|
|
1741
|
+
} catch (err) {
|
|
1742
|
+
console.warn(`[animus-extract] Turbopack debounce status update failed: ${String(err)}`);
|
|
1743
|
+
}
|
|
1744
|
+
if (!timer) {
|
|
1745
|
+
timer = setTimeout(flush, debounceMs);
|
|
1746
|
+
timer.unref?.();
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
969
1749
|
const onEvent = (baseDir, filename) => {
|
|
970
1750
|
if (closed || !filename) return;
|
|
971
1751
|
const rel = filename.toString();
|
|
@@ -974,10 +1754,47 @@ function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.w
|
|
|
974
1754
|
if (baseDir === rootDir && !watchers.has(abs)) try {
|
|
975
1755
|
if ((0, fs.statSync)(abs).isDirectory()) addWatcher(abs, true);
|
|
976
1756
|
} catch {}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1757
|
+
enqueuePath(abs);
|
|
1758
|
+
};
|
|
1759
|
+
/** External-root event path: fenced by watcher identity ({watcherRootId,
|
|
1760
|
+
* generation} — a handle that is neither current nor pending belongs to
|
|
1761
|
+
* a retired generation and its events are rejected), `filename == null`
|
|
1762
|
+
* marks the ROOT dirty for rediscovery, and events observed while the
|
|
1763
|
+
* root's generation is still snapshotting are buffered for replay. */
|
|
1764
|
+
const onExternalEvent = (root, self, filename) => {
|
|
1765
|
+
if (closed) return;
|
|
1766
|
+
const watcher = self();
|
|
1767
|
+
const active = watcher !== void 0 && externalWatchers.get(root) === watcher;
|
|
1768
|
+
const isPending = watcher !== void 0 && pendingOpened.get(root) === watcher;
|
|
1769
|
+
if (!active && !isPending) return;
|
|
1770
|
+
let abs;
|
|
1771
|
+
if (filename == null) abs = root;
|
|
1772
|
+
else {
|
|
1773
|
+
const rel = filename.toString();
|
|
1774
|
+
if (rel.split(/[\\/]/).some((segment) => IGNORED_SEGMENTS.has(segment))) return;
|
|
1775
|
+
abs = rel === "" ? root : (0, path.join)(root, rel);
|
|
1776
|
+
}
|
|
1777
|
+
if (isPending && !active) {
|
|
1778
|
+
capturedDuringSnapshot.push(abs);
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
enqueuePath(abs);
|
|
1782
|
+
};
|
|
1783
|
+
/** Open one external-root watcher into `into` (the committed set at
|
|
1784
|
+
* startup, the pending set during reset reconciliation). Failures
|
|
1785
|
+
* degrade ONLY this root (design D7); node_modules-resident roots are
|
|
1786
|
+
* documented unwatchable and never registered (guardrail G2). */
|
|
1787
|
+
const openExternalWatcher = (root, into) => {
|
|
1788
|
+
if (root.split(/[\\/]/).includes("node_modules")) return;
|
|
1789
|
+
try {
|
|
1790
|
+
let watcher;
|
|
1791
|
+
watcher = watchFn(root, { recursive: true }, (_event, filename) => onExternalEvent(root, () => watcher, filename));
|
|
1792
|
+
watcher.on("error", (err) => degradeExternalRoot(root, err));
|
|
1793
|
+
watcher.unref?.();
|
|
1794
|
+
into.set(root, watcher);
|
|
1795
|
+
session.stickyDiagnostics?.delete(externalDiagnosticKey(root));
|
|
1796
|
+
} catch (err) {
|
|
1797
|
+
degradeExternalRoot(root, err);
|
|
981
1798
|
}
|
|
982
1799
|
};
|
|
983
1800
|
const addWatcher = (dir, recursive) => {
|
|
@@ -997,6 +1814,23 @@ function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.w
|
|
|
997
1814
|
console.warn(`[animus-extract] Turbopack dev watcher unavailable (${String(err)}); source edits require a dev-server restart`);
|
|
998
1815
|
return null;
|
|
999
1816
|
}
|
|
1817
|
+
for (const root of session.externalWatchRoots ?? []) openExternalWatcher(root, externalWatchers);
|
|
1818
|
+
session.onExternalRootResolved = (root) => {
|
|
1819
|
+
if (closed) return;
|
|
1820
|
+
if (externalWatchers.has(root) || pendingOpened.has(root)) return;
|
|
1821
|
+
openExternalWatcher(root, pendingOpened);
|
|
1822
|
+
};
|
|
1823
|
+
session.onExternalRootsCommitted = (roots) => {
|
|
1824
|
+
if (closed) return;
|
|
1825
|
+
const admitted = new Set(roots);
|
|
1826
|
+
for (const [root, watcher] of pendingOpened) externalWatchers.set(root, watcher);
|
|
1827
|
+
pendingOpened = /* @__PURE__ */ new Map();
|
|
1828
|
+
for (const [root, watcher] of [...externalWatchers]) if (!admitted.has(root)) {
|
|
1829
|
+
watcher.close();
|
|
1830
|
+
externalWatchers.delete(root);
|
|
1831
|
+
}
|
|
1832
|
+
for (const abs of capturedDuringSnapshot.splice(0)) enqueuePath(abs);
|
|
1833
|
+
};
|
|
1000
1834
|
return { close: closeAll };
|
|
1001
1835
|
}
|
|
1002
1836
|
//#endregion
|
|
@@ -1027,9 +1861,11 @@ function withAnimus(options) {
|
|
|
1027
1861
|
webpack(config, context) {
|
|
1028
1862
|
if (typeof existingWebpack === "function") config = existingWebpack(config, context);
|
|
1029
1863
|
const rootDir = process.cwd();
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
const
|
|
1864
|
+
const plugin = new AnimusWebpackPlugin(options);
|
|
1865
|
+
plugin.setRootDir(rootDir);
|
|
1866
|
+
const sessionDir = plugin.sessionDir;
|
|
1867
|
+
if (!(0, fs.existsSync)(sessionDir)) (0, fs.mkdirSync)(sessionDir, { recursive: true });
|
|
1868
|
+
const stubCssPath = require_session_paths.stylesPath(sessionDir);
|
|
1033
1869
|
if (!(0, fs.existsSync)(stubCssPath)) {
|
|
1034
1870
|
const { declaration } = (0, _animus_ui_extract_pipeline.assembleStylesheet)({
|
|
1035
1871
|
layers: options.layers,
|
|
@@ -1048,28 +1884,26 @@ function withAnimus(options) {
|
|
|
1048
1884
|
}
|
|
1049
1885
|
} catch {}
|
|
1050
1886
|
}
|
|
1051
|
-
const
|
|
1052
|
-
const isExternalPackageFile = (filePath) => plugin.getExternalPackageDirs().some((dir) => filePath.startsWith(dir + path.sep) || filePath === dir);
|
|
1887
|
+
const isExternalPackageFile = (filePath) => plugin.getExternalPackageDirs().some((dir) => (0, _animus_ui_extract_pipeline.isPathWithinRoot)(dir, filePath));
|
|
1053
1888
|
config.plugins = config.plugins || [];
|
|
1054
1889
|
config.plugins.push(plugin);
|
|
1055
1890
|
const { DefinePlugin } = context.webpack ?? {};
|
|
1056
1891
|
if (typeof DefinePlugin === "function") config.plugins.push(new DefinePlugin({ __ANIMUS_DEV__: JSON.stringify(context.dev === true) }));
|
|
1057
1892
|
config.resolve = config.resolve || {};
|
|
1058
1893
|
config.resolve.alias = config.resolve.alias || {};
|
|
1059
|
-
config.resolve.alias[ANIMUS_CSS_MODULE_ID] =
|
|
1060
|
-
const
|
|
1894
|
+
config.resolve.alias[ANIMUS_CSS_MODULE_ID] = require_session_paths.stylesPath(sessionDir);
|
|
1895
|
+
const sessionSystemPropsPath = require_session_paths.systemPropsPath(sessionDir);
|
|
1061
1896
|
config.plugins.push({ apply(compiler) {
|
|
1062
1897
|
compiler.hooks.normalModuleFactory.tap("AnimusVirtualResolve", (nmf) => {
|
|
1063
1898
|
nmf.hooks.beforeResolve.tap("AnimusVirtualResolve", (resolveData) => {
|
|
1064
|
-
if (resolveData.request === "virtual:animus/system-props") resolveData.request =
|
|
1065
|
-
if (resolveData.request === "virtual:animus/styles.css") resolveData.request =
|
|
1899
|
+
if (resolveData.request === "virtual:animus/system-props") resolveData.request = sessionSystemPropsPath;
|
|
1900
|
+
if (resolveData.request === "virtual:animus/styles.css") resolveData.request = require_session_paths.stylesPath(sessionDir);
|
|
1066
1901
|
const srcEntry = plugin.getExternalSourceEntries().get(resolveData.request);
|
|
1067
1902
|
if (srcEntry) resolveData.request = srcEntry;
|
|
1068
1903
|
});
|
|
1069
1904
|
});
|
|
1070
1905
|
} });
|
|
1071
|
-
const
|
|
1072
|
-
const actualLoaderPath = (0, fs.existsSync)(loaderPath) ? loaderPath : (0, path.resolve)(__dirname$1, "loader.ts");
|
|
1906
|
+
const actualLoaderPath = resolveAnimusLoaderPath();
|
|
1073
1907
|
config.module = config.module || {};
|
|
1074
1908
|
config.module.rules = config.module.rules || [];
|
|
1075
1909
|
config.module.rules.push({
|
|
@@ -1113,7 +1947,9 @@ async function wireTurbopack(nextConfig, options) {
|
|
|
1113
1947
|
rootDir,
|
|
1114
1948
|
loaderPath: resolveTurbopackLoaderPath(__dirname$1),
|
|
1115
1949
|
options,
|
|
1116
|
-
externalSourceEntries: session.externalSourceEntries
|
|
1950
|
+
externalSourceEntries: session.externalSourceEntries,
|
|
1951
|
+
sessionId: session.sessionId,
|
|
1952
|
+
sessionDir: session.sessionDir
|
|
1117
1953
|
});
|
|
1118
1954
|
const existing = nextConfig.turbopack ?? {};
|
|
1119
1955
|
if (existing.rules && "*.{ts,tsx,js,jsx,mjs}" 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`);
|