@animus-ui/next-plugin 0.1.11 → 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.mjs CHANGED
@@ -1,17 +1,11 @@
1
- import { ENGINE_TRANSFORM_EXTENSIONS, assembleStylesheet, assertKnownOptionKeys, assertNoRetiredEngineSelection, buildPathAliasesJson, isPathWithinRoot, readTsconfigAliasPairs, resolveMode } from "@animus-ui/extract/pipeline";
2
- import { ANIMUS_CSS_MODULE_ID, ANIMUS_CSS_MODULE_ID as ANIMUS_CSS_MODULE_ID$1, ExtractionSession, STYLES_ARTIFACT, SYSTEM_PROPS_ARTIFACT, TURBOPACK_SYSTEM_PROPS_ID, getAnalysisPromise, getReplacementEpoch, getSharedCss, getSharedExternalDirs, getSharedExternalEntries, replacementEpochPath, runSessionPipeline, sessionArtifactDir, setAnalysisPromise, setSharedEngine, startTurbopackWatcher, stylesPath, systemPropsPath } from "@animus-ui/extract/session";
1
+ import { ENGINE_TRANSFORM_EXTENSIONS, assembleStylesheet, assertKnownOptionKeys, assertNoRetiredEngineSelection, buildPathAliasesJson, isEngineTransformExtension, isPathWithinRoot, readTsconfigAliasPairs, resolveMode } from "@animus-ui/extract/pipeline";
2
+ import { ANIMUS_CSS_MODULE_ID, ANIMUS_CSS_MODULE_ID as ANIMUS_CSS_MODULE_ID$1, ExtractionSession, STYLES_ARTIFACT, SYSTEM_PROPS_ARTIFACT, TURBOPACK_SYSTEM_PROPS_ID, getAnalysisStartedPromise, getReplacementEpoch, getSharedCss, getSharedExternalDirs, getSharedExternalEntries, replacementEpochPath, runSessionPipeline, sessionArtifactDir, setAnalysisStartedPromise, setSharedEngine, startTurbopackWatcher, stylesPath, systemPropsPath } from "@animus-ui/extract/session";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
4
4
  import { dirname, join, relative, resolve } from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  //#region src/loader-path.ts
7
- const __dirname$1 = dirname(fileURLToPath(import.meta.url));
8
- /**
9
- * The one loader-path derivation: first existing dist candidate under the
10
- * plugin dir, else the in-repo source fallback. Both loader resolvers below
11
- * are thin wrappers, so rule registration and any loader-chain predicate
12
- * can never drift (openspec: next-webpack-served-transform-coherence,
13
- * design D1).
14
- */
7
+ const __filename$1 = fileURLToPath(import.meta.url);
8
+ const __dirname$1 = dirname(__filename$1);
15
9
  function resolveLoaderPath(pluginDir, distCandidates, sourceFallback) {
16
10
  for (const candidate of distCandidates) {
17
11
  const distPath = resolve(pluginDir, candidate);
@@ -19,69 +13,38 @@ function resolveLoaderPath(pluginDir, distCandidates, sourceFallback) {
19
13
  }
20
14
  return resolve(pluginDir, sourceFallback);
21
15
  }
22
- /**
23
- * This package's webpack loader path, shared by the rule registration
24
- * (with-animus) and the needBuild loader-chain predicate (plugin). Compiled
25
- * dist ships `loader.mjs` next to this module; running from source falls
26
- * back to the TypeScript loader.
27
- */
28
16
  function resolveAnimusLoaderPath() {
29
17
  return resolveLoaderPath(__dirname$1, ["loader.mjs"], "loader.ts");
30
18
  }
31
19
  //#endregion
32
20
  //#region src/plugin.ts
33
- /** Loud unsupported-version failure (design D7): the coherence mechanism
34
- * cannot exist without the per-compilation needBuild hook, so a webpack
35
- * that lacks it fails immediately instead of serving stale transforms. */
36
- 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.";
21
+ /** The value arrives from a consumer's `next.config`, so the union above is a
22
+ * model of what webpack accepts, not a proof about the value in hand. */
23
+ const isWatchIgnoreString = (ignored) => Object.prototype.toString.call(ignored) === "[object String]";
24
+ const isWatchIgnoreMatcher = (ignored) => Object.prototype.toString.call(ignored) === "[object Function]";
25
+ 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.";
37
26
  const PLUGIN_NAME = "AnimusWebpackPlugin";
38
- /**
39
- * Webpack adapter for the extraction pipeline. All pipeline logic lives in
40
- * the bundler-agnostic ExtractionSession — this class owns only the webpack
41
- * wiring: hook registration, cross-compiler analysis dedup, alias
42
- * harvesting from the compiler config, watch-event translation, and
43
- * in-memory CSS asset replacement.
44
- */
45
27
  var AnimusWebpackPlugin = class {
46
28
  options;
47
29
  session;
48
30
  initialized = false;
49
31
  aliasesExtracted = false;
50
- /** Loader paths counting as "the animus loader" in a module's chain —
51
- * this package's own loader plus an optional harness override. */
52
32
  animusLoaderPaths;
53
- /** Epoch THIS compiler last proceeded with (set after every awaited
54
- * watchRun transaction — including joined ones). Null until the
55
- * initialization pipeline publishes. */
56
33
  lastBuiltEpoch = null;
57
- /** Armed by watchRun when the transaction moved the epoch; captured and
58
- * disarmed by the next compilation's needBuild wiring (design D1). */
59
34
  epochMovedForNextCompilation = false;
60
35
  constructor(options) {
61
36
  this.options = options;
62
37
  assertNoRetiredEngineSelection(options.engine);
63
38
  setSharedEngine(options.engine ?? "v2");
64
39
  this.session = new ExtractionSession(options);
65
- this.animusLoaderPaths = new Set([resolveAnimusLoaderPath()]);
40
+ this.animusLoaderPaths = /* @__PURE__ */ new Set([resolveAnimusLoaderPath()]);
66
41
  if (options.loaderPath) this.animusLoaderPaths.add(options.loaderPath);
67
42
  }
68
- /** True when the module's loader chain contains the animus loader — the
69
- * needBuild fan-out predicate (design D1: no historical records; covers
70
- * restored persistent-cache modules and raw passthroughs alike). */
71
43
  moduleUsesAnimusLoader(module) {
72
44
  const loaders = module?.loaders;
73
45
  if (!Array.isArray(loaders)) return false;
74
- return loaders.some((entry) => typeof entry?.loader === "string" && this.animusLoaderPaths.has(entry.loader));
46
+ return loaders.some((entry) => entry?.loader !== void 0 && this.animusLoaderPaths.has(entry.loader));
75
47
  }
76
- /** Append the exact epoch artifact path to `watchOptions.ignored`,
77
- * preserving the user's shape (design D2: the artifact participates in
78
- * module snapshots via addDependency but never triggers live watch
79
- * turns — no echo compilations). Session identity is process-claimed,
80
- * so every compiler's plugin instance derives the SAME session-scoped
81
- * path here regardless of which instance ends up owning the pipeline.
82
- * Derived from the ONE root (session.rootDir, set at config time by
83
- * with-animus); compiler.context is the fallback for bare-apply
84
- * harnesses — identical when compiler.context === cwd. */
85
48
  appendEpochWatchIgnore(compiler) {
86
49
  const options = compiler.options;
87
50
  if (!options) return;
@@ -92,25 +55,22 @@ var AnimusWebpackPlugin = class {
92
55
  if (ignored === void 0 || ignored === null) watchOptions.ignored = [epochPath];
93
56
  else if (Array.isArray(ignored)) {
94
57
  if (!ignored.includes(epochPath)) ignored.push(epochPath);
95
- } else if (typeof ignored === "string") watchOptions.ignored = [ignored, epochPath];
58
+ } else if (isWatchIgnoreString(ignored)) watchOptions.ignored = [ignored, epochPath];
96
59
  else if (ignored instanceof RegExp) watchOptions.ignored = (path) => path === epochPath || ignored.test(path);
97
- else if (typeof ignored === "function") {
98
- const original = ignored;
99
- watchOptions.ignored = (path) => path === epochPath || Boolean(original(path));
100
- }
60
+ else if (isWatchIgnoreMatcher(ignored)) watchOptions.ignored = (path) => path === epochPath || Boolean(ignored(path));
101
61
  }
102
- /** Extract path aliases from webpack's resolve.alias config. Runs once per
103
- * plugin instance — the resolve config is immutable after apply. */
104
62
  extractAliases(compiler) {
105
63
  if (this.aliasesExtracted) return;
106
64
  this.aliasesExtracted = true;
107
65
  const rootDir = compiler.context;
108
66
  const rawAlias = compiler.options?.resolve?.alias;
109
- if (!rawAlias || typeof rawAlias !== "object") return;
67
+ if (rawAlias === void 0) return;
68
+ if (Object.prototype.toString.call(rawAlias) !== "[object Object]") return;
110
69
  const pairs = [];
111
70
  for (const [key, value] of Object.entries(rawAlias)) {
112
- const target = Array.isArray(value) ? value[0] : value;
113
- if (typeof target !== "string") continue;
71
+ if (value === false) continue;
72
+ const target = Array.isArray(value) ? value.at(0) : value;
73
+ if (target === void 0) continue;
114
74
  if (key === ANIMUS_CSS_MODULE_ID) continue;
115
75
  pairs.push({
116
76
  pattern: key,
@@ -123,13 +83,13 @@ var AnimusWebpackPlugin = class {
123
83
  apply(compiler) {
124
84
  if (compiler.options?.name === "edge-server") return;
125
85
  const NormalModule = compiler.webpack?.NormalModule;
126
- if (typeof NormalModule?.getCompilationHooks !== "function") throw new Error(UNSUPPORTED_WEBPACK_MESSAGE);
86
+ if (NormalModule?.getCompilationHooks === void 0) throw new Error(UNSUPPORTED_WEBPACK_MESSAGE);
127
87
  this.appendEpochWatchIgnore(compiler);
128
88
  compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
129
89
  const epochMoved = this.epochMovedForNextCompilation;
130
90
  this.epochMovedForNextCompilation = false;
131
91
  const needBuild = NormalModule.getCompilationHooks(compilation).needBuild;
132
- if (typeof needBuild?.tapAsync !== "function") throw new Error(UNSUPPORTED_WEBPACK_MESSAGE);
92
+ if (needBuild === void 0) throw new Error(UNSUPPORTED_WEBPACK_MESSAGE);
133
93
  needBuild.tapAsync(PLUGIN_NAME, (module, _context, callback) => {
134
94
  if (epochMoved && this.moduleUsesAnimusLoader(module)) {
135
95
  callback(null, true);
@@ -156,7 +116,8 @@ var AnimusWebpackPlugin = class {
156
116
  registerSystemDependencies();
157
117
  const css = getSharedCss();
158
118
  if (!css || !RawSource) return;
159
- const cssPath = stylesPath(sessionArtifactDir(this.session.rootDir || compiler.context, this.session.sessionId));
119
+ const rootDir = this.session.rootDir || compiler.context;
120
+ const cssPath = stylesPath(sessionArtifactDir(rootDir, this.session.sessionId));
160
121
  if (compilation.getAsset(cssPath)) {
161
122
  compilation.updateAsset(cssPath, new RawSource(css));
162
123
  return;
@@ -167,24 +128,24 @@ var AnimusWebpackPlugin = class {
167
128
  compiler.hooks.run.tapPromise(PLUGIN_NAME, async (_compiler) => {
168
129
  this.adoptCompilerContext(_compiler);
169
130
  this.extractAliases(_compiler);
170
- const existing = getAnalysisPromise();
131
+ const existing = getAnalysisStartedPromise();
171
132
  if (existing) {
172
133
  await existing;
173
134
  return;
174
135
  }
175
136
  const promise = this.session.runFullPipeline();
176
- setAnalysisPromise(promise);
137
+ setAnalysisStartedPromise(promise);
177
138
  await promise;
178
139
  });
179
140
  compiler.hooks.watchRun.tapPromise(PLUGIN_NAME, async (_compiler) => {
180
141
  this.adoptCompilerContext(_compiler);
181
142
  this.extractAliases(_compiler);
182
143
  if (!this.initialized) {
183
- const existing = getAnalysisPromise();
144
+ const existing = getAnalysisStartedPromise();
184
145
  if (existing) await existing;
185
146
  else {
186
147
  const promise = this.session.runFullPipeline();
187
- setAnalysisPromise(promise);
148
+ setAnalysisStartedPromise(promise);
188
149
  await promise;
189
150
  }
190
151
  this.initialized = true;
@@ -200,35 +161,20 @@ var AnimusWebpackPlugin = class {
200
161
  if (epoch !== null) this.lastBuiltEpoch = epoch;
201
162
  });
202
163
  }
203
- /**
204
- * Reset analysis state for HMR geological reset.
205
- */
206
164
  resetForHmr() {
207
165
  this.session.resetForHmr();
208
166
  }
209
- /** Session identity (process-claimed) — with-animus derives the
210
- * session-scoped alias/stub paths from it (design D2). */
211
167
  get sessionId() {
212
168
  return this.session.sessionId;
213
169
  }
214
- /** Set the project root at CONFIG time (with-animus) — the ONE root all
215
- * session-path derivations use. The compiler hooks ADOPT
216
- * compiler.context only when no root was published (bare-apply
217
- * harnesses); they never overwrite this. */
218
170
  setRootDir(rootDir) {
219
171
  this.session.rootDir = rootDir;
220
172
  }
221
- /** One warning per plugin instance for a context/root mismatch. */
222
173
  warnedRootDivergence = false;
223
- /** The run/watchRun taps' root handling: adopt compiler.context when no
224
- * config-time root exists; otherwise KEEP the configured root — every
225
- * frozen config-time derivation (stub path, css alias target,
226
- * virtual:animus/system-props target, watch-ignore) came from it, and a
227
- * silent overwrite re-keys sessionDir so the pipeline publishes
228
- * artifacts where none of those consumers look. A disagreement is
229
- * surfaced loudly instead (with-animus derives the root from Next's own
230
- * `dir`, which equals compiler.context in real Next runs — a mismatch
231
- * means a custom-webpack setup wired the plugin differently). */
174
+ /** Adopts `compiler.context` only when no config-time root exists: every
175
+ * frozen derivation came from that root, so an overwrite re-keys it.
176
+ * Stub path, css alias, virtual system-props target and watch-ignore are
177
+ * all frozen from it, so a disagreement is warned, never reconciled. */
232
178
  adoptCompilerContext(compiler) {
233
179
  const context = compiler.context;
234
180
  if (!this.session.rootDir) {
@@ -240,73 +186,47 @@ var AnimusWebpackPlugin = class {
240
186
  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`);
241
187
  }
242
188
  }
243
- /** This session's artifact directory, derived from the one root. */
244
189
  get sessionDir() {
245
190
  return this.session.sessionDir;
246
191
  }
247
- /** Expose options for the loader */
248
192
  getOptions() {
249
193
  return this.options;
250
194
  }
251
- /** Expose external package directories for webpack loader allowlisting.
252
- * Falls back to shared globalThis state for non-owning compiler instances. */
253
195
  getExternalPackageDirs() {
254
196
  return this.session.externalPackageDirs.length > 0 ? this.session.externalPackageDirs : getSharedExternalDirs();
255
197
  }
256
- /** Expose external package source entries for webpack resolve alias.
257
- * Falls back to shared globalThis state for non-owning compiler instances. */
258
198
  getExternalSourceEntries() {
259
199
  return this.session.externalSourceEntries.size > 0 ? this.session.externalSourceEntries : getSharedExternalEntries();
260
200
  }
261
201
  };
262
202
  //#endregion
263
203
  //#region src/turbopack-config.ts
264
- /** The single glob the Animus loader registers under — derived from the
265
- * ONE shared engine extension set (also the unplugin transform gate's
266
- * source), so the two bundler families cannot drift. `.mjs` is in that
267
- * set for webpack parity: an external package without `src/` is ingested
268
- * via its resolved dist entry (often `dist/index.mjs`) and must still
269
- * reach the loader — manifest lookup remains the file-level gate. */
204
+ /** The one glob the loader registers under, derived from the shared engine
205
+ * extension set so the bundler families cannot drift. `.mjs` belongs in that
206
+ * set: an external package with no src/ is ingested through its dist entry
207
+ * and must still reach the loader, so do not trim it. */
270
208
  const ANIMUS_TURBOPACK_RULE_GLOB = `*.{${ENGINE_TRANSFORM_EXTENSIONS.join(",")}}`;
271
- /**
272
- * Resolve whether Turbopack wiring is active for this process. Default is
273
- * `'auto'`: active exactly when the TURBOPACK environment variable is set
274
- * (Next sets it for every Turbopack dev/build). The stable `turbopack`
275
- * option wins over the deprecated `unstable_turbopack` alias.
276
- */
277
209
  function resolveTurbopackMode(options, env = process.env) {
278
210
  const mode = options.turbopack?.mode ?? options.unstable_turbopack?.mode ?? "auto";
279
211
  if (mode === "on") return true;
280
212
  if (mode === "auto") return env.TURBOPACK !== void 0;
281
213
  return false;
282
214
  }
283
- /** rootDir-relative module request for an absolute path — always forward
284
- * slashes, even when path.relative produced Windows separators. */
285
215
  function rootRelativeRequest(rootDir, absPath) {
286
216
  return `./${relative(rootDir, absPath).replace(/\\/g, "/")}`;
287
217
  }
288
- /**
289
- * Build the `turbopack` config fragment: one glob-keyed loader rule
290
- * (file-level allowlisting lives in the loader via manifest lookup — Next 15
291
- * rules have no condition algebra) plus resolve aliases for the virtual
292
- * system-props id, the emitter's stylesheet id, and each collected external
293
- * package specifier redirected to its source entry. Loader options carry
294
- * the session identity (design D2: an options-borne, JSON-serializable
295
- * Turbopack task input — restart-cold by construction), and the artifact
296
- * aliases point into the session-scoped tree.
297
- */
298
218
  function buildTurbopackConfig(args) {
299
219
  const { rootDir, loaderPath, options, externalSourceEntries, sessionId, sessionDir } = args;
300
220
  const loaderOptions = {
301
221
  rootDir,
302
222
  sessionId,
303
- sessionDir,
304
- ...options.strict !== void 0 ? { strict: options.strict } : {},
305
- ...options.cssImportTarget !== void 0 ? { cssImportTarget: options.cssImportTarget } : {}
223
+ sessionDir
306
224
  };
225
+ if (options.strict !== void 0) loaderOptions.strict = options.strict;
226
+ if (options.cssImportTarget !== void 0) loaderOptions.cssImportTarget = options.cssImportTarget;
307
227
  const resolveAlias = {
308
228
  [TURBOPACK_SYSTEM_PROPS_ID]: rootRelativeRequest(rootDir, join(sessionDir, SYSTEM_PROPS_ARTIFACT)),
309
- ".animus/styles.css": rootRelativeRequest(rootDir, join(sessionDir, STYLES_ARTIFACT))
229
+ [ANIMUS_CSS_MODULE_ID]: rootRelativeRequest(rootDir, join(sessionDir, STYLES_ARTIFACT))
310
230
  };
311
231
  for (const [specifier, srcEntry] of externalSourceEntries) resolveAlias[specifier] = rootRelativeRequest(rootDir, srcEntry);
312
232
  return {
@@ -317,40 +237,25 @@ function buildTurbopackConfig(args) {
317
237
  resolveAlias
318
238
  };
319
239
  }
320
- /**
321
- * Locate the Turbopack loader module: built dist first, source fallback for
322
- * in-repo development. CJS preferred — Turbopack's loader-runner follows
323
- * the webpack loader contract (`module.exports = fn`), and the .cjs build
324
- * is loadable by every runner version; ESM default-export acceptance is
325
- * not guaranteed.
326
- */
327
240
  function resolveTurbopackLoaderPath(pluginDir) {
328
241
  return resolveLoaderPath(pluginDir, ["turbopack-loader.cjs", "turbopack-loader.mjs"], "turbopack-loader.ts");
329
242
  }
330
243
  //#endregion
331
244
  //#region src/with-animus.ts
332
- const __dirname = dirname(fileURLToPath(import.meta.url));
245
+ const __filename = fileURLToPath(import.meta.url);
246
+ const __dirname = dirname(__filename);
333
247
  let warnedGitignore = false;
334
248
  let warnedUnstableTurbopack = false;
335
- /**
336
- * Wrap a Next.js config to enable Animus static CSS extraction.
337
- *
338
- * ```ts
339
- * // next.config.ts
340
- * import { withAnimus } from '@animus-ui/next-plugin';
341
- * export default withAnimus({ system: './src/ds.ts' })({});
342
- * ```
343
- */
344
249
  function withAnimus(options) {
345
250
  if (!options.system) throw new Error("[animus-extract] Missing required option `system`. Provide the path to your SystemInstance module: withAnimus({ system: \"./src/ds.ts\" })");
346
- assertKnownOptionKeys(options, [
251
+ assertKnownOptionKeys({ ...options }, [
347
252
  "cssImportTarget",
348
253
  "turbopack",
349
254
  "unstable_turbopack",
350
255
  "loaderPath"
351
256
  ], [{
352
257
  key: "root",
353
- reason: "the Next driver's root is Next's `dir` at config time"
258
+ reason: "this driver derives its own root — Next's `dir` under webpack, `process.cwd()` under Turbopack (Next passes no dir to a config module)"
354
259
  }], {
355
260
  onUnknownKey: "warn",
356
261
  warn: (message) => console.warn(`[animus-extract] ${message}`)
@@ -365,8 +270,8 @@ function withAnimus(options) {
365
270
  return {
366
271
  ...nextConfig,
367
272
  webpack(config, context) {
368
- if (typeof existingWebpack === "function") config = existingWebpack(config, context);
369
- const rootDir = typeof context.dir === "string" && context.dir.length > 0 ? context.dir : process.cwd();
273
+ if (existingWebpack) config = existingWebpack(config, context);
274
+ const rootDir = context.dir?.length ? context.dir : process.cwd();
370
275
  const plugin = new AnimusWebpackPlugin(options);
371
276
  plugin.setRootDir(rootDir);
372
277
  const sessionDir = plugin.sessionDir;
@@ -393,8 +298,8 @@ function withAnimus(options) {
393
298
  const isExternalPackageFile = (filePath) => plugin.getExternalPackageDirs().some((dir) => isPathWithinRoot(dir, filePath));
394
299
  config.plugins = config.plugins || [];
395
300
  config.plugins.push(plugin);
396
- const { DefinePlugin } = context.webpack ?? {};
397
- if (typeof DefinePlugin === "function") config.plugins.push(new DefinePlugin({ __ANIMUS_DEV__: JSON.stringify(resolveMode(options.mode, () => context.dev === true ? "development" : "production").mode === "development") }));
301
+ const DefinePlugin = context.webpack?.DefinePlugin;
302
+ if (DefinePlugin) config.plugins.push(new DefinePlugin({ __ANIMUS_DEV__: JSON.stringify(resolveMode(options.mode, () => context.dev === true ? "development" : "production").mode === "development") }));
398
303
  config.resolve = config.resolve || {};
399
304
  config.resolve.alias = config.resolve.alias || {};
400
305
  config.resolve.alias[ANIMUS_CSS_MODULE_ID$1] = stylesPath(sessionDir);
@@ -413,10 +318,7 @@ function withAnimus(options) {
413
318
  config.module = config.module || {};
414
319
  config.module.rules = config.module.rules || [];
415
320
  config.module.rules.push({
416
- test: (filePath) => {
417
- if (/\.[jt]sx?$/.test(filePath)) return true;
418
- return /\.mjs$/.test(filePath) && isExternalPackageFile(filePath);
419
- },
321
+ test: (filePath) => isEngineTransformExtension(filePath),
420
322
  exclude: (filePath) => {
421
323
  if (!filePath.includes("node_modules")) return false;
422
324
  return !isExternalPackageFile(filePath);
@@ -435,20 +337,25 @@ function withAnimus(options) {
435
337
  };
436
338
  };
437
339
  }
438
- /**
439
- * Turbopack wiring: run the full extraction now (artifacts on disk before
440
- * bundling), start the dev watcher, and merge the generated rules/aliases
441
- * into `nextConfig.turbopack`. Consumer-managed rules for the same glob are
442
- * a hard error — silently stacking loaders would be undebuggable.
443
- */
340
+ function bindTurbopackWatchDeathReport(outcome, rootDir) {
341
+ if (outcome.kind !== "started") return;
342
+ const handle = outcome.handle;
343
+ handle.onDied = () => {
344
+ console.error(`[animus-extract] dev watcher for ${rootDir} died — source edits are no longer extracted; restart the dev server`);
345
+ };
346
+ }
347
+ let liveTurbopackSession = null;
444
348
  async function wireTurbopack(nextConfig, options) {
445
349
  const rootDir = process.cwd();
446
350
  const session = new ExtractionSession(options);
351
+ liveTurbopackSession?.close();
352
+ liveTurbopackSession = session;
447
353
  session.rootDir = rootDir;
448
- const builtAliases = buildPathAliasesJson(readTsconfigAliasPairs(rootDir), rootDir);
354
+ const aliasPairs = readTsconfigAliasPairs(rootDir);
355
+ const builtAliases = buildPathAliasesJson(aliasPairs, rootDir);
449
356
  if (builtAliases) session.pathAliasesJson = builtAliases.json;
450
357
  await runSessionPipeline(session);
451
- if (process.env.NODE_ENV === "development") startTurbopackWatcher(session, rootDir);
358
+ if (process.env.NODE_ENV === "development") bindTurbopackWatchDeathReport(startTurbopackWatcher(session, rootDir), rootDir);
452
359
  const fragment = buildTurbopackConfig({
453
360
  rootDir,
454
361
  loaderPath: resolveTurbopackLoaderPath(__dirname),
@@ -0,0 +1,47 @@
1
+ let _animus_ui_extract_session = require("@animus-ui/extract/session");
2
+ //#region src/loader-core.ts
3
+ /** Matches every already-emitted stylesheet import — arbitrary relative
4
+ * prefixes and the Vite emitter's virtual id — not this bundler's id.
5
+ * Never derive it from the shared module-id constant: this reads foreign
6
+ * output, and deriving it silently stops matching the other forms. */
7
+ const CSS_IMPORT_RE = /import\s+['"](?:[^'"]*\.animus\/styles\.css|virtual:animus\/styles\.css)['"];\n?/g;
8
+ const CSS_IMPORT_STATEMENT = `import '${_animus_ui_extract_session.ANIMUS_CSS_MODULE_ID}';\n`;
9
+ const ROOT_ENTRY_RE = /^(src\/)?(?:app\/layout|pages\/_app)\.[tj]sx?$/;
10
+ function normalizePath(p) {
11
+ return p.replace(/\\/g, "/").replace(/^\.\//, "");
12
+ }
13
+ function isCssImportTarget(filename, cssImportTarget) {
14
+ const normalized = normalizePath(filename);
15
+ if (cssImportTarget) return normalized === normalizePath(cssImportTarget);
16
+ return ROOT_ENTRY_RE.test(normalized);
17
+ }
18
+ function transformWithManifest(args) {
19
+ const { source, filename, manifestJson, engineApi, opts } = args;
20
+ const isRootEntry = isCssImportTarget(filename, opts.cssImportTarget);
21
+ try {
22
+ const { transformFile } = engineApi();
23
+ const result = transformFile(source, filename, manifestJson);
24
+ let code = result.hasComponents ? result.code : source;
25
+ code = code.replace(CSS_IMPORT_RE, "");
26
+ if (isRootEntry && !code.includes(_animus_ui_extract_session.ANIMUS_CSS_MODULE_ID)) {
27
+ if (code.startsWith("'use client'") || code.startsWith("\"use client\"")) {
28
+ const nl = code.indexOf("\n");
29
+ if (nl === -1) code = `${code}\n${CSS_IMPORT_STATEMENT}`;
30
+ else code = `${code.slice(0, nl + 1)}${CSS_IMPORT_STATEMENT}${code.slice(nl + 1)}`;
31
+ } else code = `${CSS_IMPORT_STATEMENT}${code}`;
32
+ }
33
+ return code;
34
+ } catch (e) {
35
+ const msg = e instanceof Error ? e.message : String(e);
36
+ if (opts.strict) throw new Error(`[animus-extract] Transform failed for ${filename}: ${msg}`, { cause: e });
37
+ console.warn(`[animus-extract] Transform failed for ${filename}:`, msg);
38
+ return source;
39
+ }
40
+ }
41
+ //#endregion
42
+ Object.defineProperty(exports, "transformWithManifest", {
43
+ enumerable: true,
44
+ get: function() {
45
+ return transformWithManifest;
46
+ }
47
+ });
@@ -0,0 +1,42 @@
1
+ import { ANIMUS_CSS_MODULE_ID } from "@animus-ui/extract/session";
2
+ //#region src/loader-core.ts
3
+ /** Matches every already-emitted stylesheet import — arbitrary relative
4
+ * prefixes and the Vite emitter's virtual id — not this bundler's id.
5
+ * Never derive it from the shared module-id constant: this reads foreign
6
+ * output, and deriving it silently stops matching the other forms. */
7
+ const CSS_IMPORT_RE = /import\s+['"](?:[^'"]*\.animus\/styles\.css|virtual:animus\/styles\.css)['"];\n?/g;
8
+ const CSS_IMPORT_STATEMENT = `import '${ANIMUS_CSS_MODULE_ID}';\n`;
9
+ const ROOT_ENTRY_RE = /^(src\/)?(?:app\/layout|pages\/_app)\.[tj]sx?$/;
10
+ function normalizePath(p) {
11
+ return p.replace(/\\/g, "/").replace(/^\.\//, "");
12
+ }
13
+ function isCssImportTarget(filename, cssImportTarget) {
14
+ const normalized = normalizePath(filename);
15
+ if (cssImportTarget) return normalized === normalizePath(cssImportTarget);
16
+ return ROOT_ENTRY_RE.test(normalized);
17
+ }
18
+ function transformWithManifest(args) {
19
+ const { source, filename, manifestJson, engineApi, opts } = args;
20
+ const isRootEntry = isCssImportTarget(filename, opts.cssImportTarget);
21
+ try {
22
+ const { transformFile } = engineApi();
23
+ const result = transformFile(source, filename, manifestJson);
24
+ let code = result.hasComponents ? result.code : source;
25
+ code = code.replace(CSS_IMPORT_RE, "");
26
+ if (isRootEntry && !code.includes(ANIMUS_CSS_MODULE_ID)) {
27
+ if (code.startsWith("'use client'") || code.startsWith("\"use client\"")) {
28
+ const nl = code.indexOf("\n");
29
+ if (nl === -1) code = `${code}\n${CSS_IMPORT_STATEMENT}`;
30
+ else code = `${code.slice(0, nl + 1)}${CSS_IMPORT_STATEMENT}${code.slice(nl + 1)}`;
31
+ } else code = `${CSS_IMPORT_STATEMENT}${code}`;
32
+ }
33
+ return code;
34
+ } catch (e) {
35
+ const msg = e instanceof Error ? e.message : String(e);
36
+ if (opts.strict) throw new Error(`[animus-extract] Transform failed for ${filename}: ${msg}`, { cause: e });
37
+ console.warn(`[animus-extract] Transform failed for ${filename}:`, msg);
38
+ return source;
39
+ }
40
+ }
41
+ //#endregion
42
+ export { transformWithManifest as t };
@@ -1,20 +1,13 @@
1
- /**
2
- * Bundler-neutral loader policy shared by the webpack loader (manifest from
3
- * the process singleton) and the Turbopack loader (manifest hydrated from
4
- * disk artifacts). Owns the transform invocation, strict-mode error
5
- * handling, and the single-stylesheet-import policy.
6
- */
7
1
  export interface LoaderPolicyOptions {
8
2
  strict?: boolean;
9
3
  cssImportTarget?: string;
10
4
  }
11
- /**
12
- * Transform one source file against a manifest: replace builder chains via
13
- * the engine, strip emitter-injected stylesheet imports everywhere, and
14
- * re-inject a single import at the root entry (after any leading
15
- * `'use client'` directive). On failure: throw in strict mode, otherwise
16
- * warn and return the source unchanged.
17
- */
5
+ export interface LoaderContextBase<O extends LoaderPolicyOptions = LoaderPolicyOptions> {
6
+ resourcePath: string;
7
+ rootContext: string;
8
+ getOptions: () => O;
9
+ addDependency?: (file: string) => void;
10
+ }
18
11
  export declare function transformWithManifest(args: {
19
12
  source: string;
20
13
  /** Project-root-relative path of the file being transformed. */
@@ -1 +1 @@
1
- {"version":3,"file":"loader-core.d.ts","sourceRoot":"","sources":["../src/loader-core.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA+CD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IAErB,SAAS,EAAE,MAAM,GAAG,CAAC;IACrB,IAAI,EAAE,mBAAmB,CAAC;CAC3B,GAAG,MAAM,CA4CT"}
1
+ {"version":3,"file":"loader-core.d.ts","sourceRoot":"","sources":["../src/loader-core.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB,CAChC,CAAC,SAAS,mBAAmB,GAAG,mBAAmB;IAEnD,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,CAAC;IACpB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AA4BD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IAErB,SAAS,EAAE,MAAM,GAAG,CAAC;IACrB,IAAI,EAAE,mBAAmB,CAAC;CAC3B,GAAG,MAAM,CAwCT"}
@@ -1,16 +1,3 @@
1
- /**
2
- * The one loader-path derivation: first existing dist candidate under the
3
- * plugin dir, else the in-repo source fallback. Both loader resolvers below
4
- * are thin wrappers, so rule registration and any loader-chain predicate
5
- * can never drift (openspec: next-webpack-served-transform-coherence,
6
- * design D1).
7
- */
8
1
  export declare function resolveLoaderPath(pluginDir: string, distCandidates: string[], sourceFallback: string): string;
9
- /**
10
- * This package's webpack loader path, shared by the rule registration
11
- * (with-animus) and the needBuild loader-chain predicate (plugin). Compiled
12
- * dist ships `loader.mjs` next to this module; running from source falls
13
- * back to the TypeScript loader.
14
- */
15
2
  export declare function resolveAnimusLoaderPath(): string;
16
3
  //# sourceMappingURL=loader-path.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader-path.d.ts","sourceRoot":"","sources":["../src/loader-path.ts"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EAAE,EACxB,cAAc,EAAE,MAAM,GACrB,MAAM,CAMR;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD"}
1
+ {"version":3,"file":"loader-path.d.ts","sourceRoot":"","sources":["../src/loader-path.ts"],"names":[],"mappings":"AAOA,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EAAE,EACxB,cAAc,EAAE,MAAM,GACrB,MAAM,CAMR;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD"}
package/dist/loader.cjs CHANGED
@@ -1,35 +1,12 @@
1
- const require_loader_core = require("./loader-core-D2rh--tX.cjs");
2
- require("./turbopack-loader.cjs");
1
+ const require_loader_core = require("./loader-core-BthLSJQF.cjs");
3
2
  let _animus_ui_extract_pipeline = require("@animus-ui/extract/pipeline");
4
3
  let _animus_ui_extract_session = require("@animus-ui/extract/session");
5
4
  let fs = require("fs");
6
5
  let path = require("path");
7
6
  //#region src/loader.ts
8
- /** Session dirs whose epoch artifact has been SEEN on disk. A sibling
9
- * session's reconciliation may delete the artifact, but the owning
10
- * session self-heals it on its next publish, so a positive existsSync
11
- * stays a sound basis for registering the dependency: a module built
12
- * during the missing window records a missing-file witness that the
13
- * recreation then invalidates (over-invalidation, never under).
14
- * Negatives always re-probe. */
15
7
  const epochSeenForSessionDir = /* @__PURE__ */ new Set();
16
- /**
17
- * Webpack loader for Animus source transformation.
18
- * Runs with enforce: 'pre' to see original source before Babel/SWC.
19
- * The manifest arrives via the process singleton (the webpack pipeline and
20
- * this loader share one process); the transform + CSS-import policy lives
21
- * in the shared loader-core.
22
- *
23
- * Coherence duties (openspec: next-webpack-served-transform-coherence):
24
- * every dev invocation registers the replacement-epoch artifact as a file
25
- * dependency (design D2 — restored persistent-cache modules snapshot it, so
26
- * offline epoch moves invalidate them), and an analyzed file whose current
27
- * source hash mismatches its analyzed hash fails with the stable diagnostic
28
- * `ANIMUS_ANALYSIS_CATCHING_UP` after one refreshed re-check (design D4 —
29
- * unconditional: a zero-entry stale manifest must never publish raw bytes).
30
- */
31
8
  function animusLoader(source) {
32
- if (this.mode !== "production" && typeof this.addDependency === "function") {
9
+ if (this.mode !== "production" && this.addDependency !== void 0) {
33
10
  const sessionDir = (0, _animus_ui_extract_session.getSessionArtifactDir)();
34
11
  if (sessionDir) {
35
12
  const epochPath = (0, _animus_ui_extract_session.replacementEpochPath)(sessionDir);
package/dist/loader.d.ts CHANGED
@@ -1,29 +1,7 @@
1
- import type { LoaderPolicyOptions } from './loader-core';
2
- type LoaderContext = {
3
- resourcePath: string;
4
- rootContext: string;
5
- getOptions: () => LoaderPolicyOptions;
6
- /** webpack file-dependency registration — optional so bare policy tests
7
- * can drive the loader without a full context. */
8
- addDependency?: (file: string) => void;
9
- /** webpack build mode; production invocations stay engine-verbatim. */
1
+ import type { LoaderContextBase } from './loader-core';
2
+ type LoaderContext = LoaderContextBase & {
10
3
  mode?: 'development' | 'production' | 'none';
11
4
  };
12
- /**
13
- * Webpack loader for Animus source transformation.
14
- * Runs with enforce: 'pre' to see original source before Babel/SWC.
15
- * The manifest arrives via the process singleton (the webpack pipeline and
16
- * this loader share one process); the transform + CSS-import policy lives
17
- * in the shared loader-core.
18
- *
19
- * Coherence duties (openspec: next-webpack-served-transform-coherence):
20
- * every dev invocation registers the replacement-epoch artifact as a file
21
- * dependency (design D2 — restored persistent-cache modules snapshot it, so
22
- * offline epoch moves invalidate them), and an analyzed file whose current
23
- * source hash mismatches its analyzed hash fails with the stable diagnostic
24
- * `ANIMUS_ANALYSIS_CATCHING_UP` after one refreshed re-check (design D4 —
25
- * unconditional: a zero-entry stale manifest must never publish raw bytes).
26
- */
27
5
  export default function animusLoader(this: LoaderContext, source: string): string;
28
6
  export {};
29
7
  //# sourceMappingURL=loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAWzD,KAAK,aAAa,GAAG;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,mBAAmB,CAAC;IACtC;uDACmD;IACnD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,uEAAuE;IACvE,IAAI,CAAC,EAAE,aAAa,GAAG,YAAY,GAAG,MAAM,CAAC;CAC9C,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAClC,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,MAAM,GACb,MAAM,CAmDR"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAIvD,KAAK,aAAa,GAAG,iBAAiB,GAAG;IACvC,IAAI,CAAC,EAAE,aAAa,GAAG,YAAY,GAAG,MAAM,CAAC;CAC9C,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,YAAY,CAClC,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,MAAM,GACb,MAAM,CAsCR"}