@animus-ui/extract 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +12 -11
  2. package/crates/extract-v2/animus-extract-v2.darwin-arm64.node +0 -0
  3. package/crates/extract-v2/animus-extract-v2.linux-arm64-gnu.node +0 -0
  4. package/crates/extract-v2/animus-extract-v2.linux-x64-gnu.node +0 -0
  5. package/crates/extract-v2/index.d.ts +22 -0
  6. package/dist/analyze-project-args.d.ts +50 -0
  7. package/dist/analyze-project-args.d.ts.map +1 -0
  8. package/dist/content-hash.d.ts +7 -0
  9. package/dist/content-hash.d.ts.map +1 -0
  10. package/dist/discover-files.d.ts +10 -0
  11. package/dist/discover-files.d.ts.map +1 -0
  12. package/dist/discover-packages.d.ts +101 -2
  13. package/dist/discover-packages.d.ts.map +1 -1
  14. package/dist/dynamic-prop-config.d.ts +23 -0
  15. package/dist/dynamic-prop-config.d.ts.map +1 -0
  16. package/dist/engine-adapter.d.ts +95 -0
  17. package/dist/engine-adapter.d.ts.map +1 -0
  18. package/dist/engine-retirement.d.ts +22 -0
  19. package/dist/engine-retirement.d.ts.map +1 -0
  20. package/dist/index.cjs +939 -179
  21. package/dist/index.d.ts +26 -3
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.mjs +899 -177
  24. package/dist/manifest-diagnostics.d.ts +17 -0
  25. package/dist/manifest-diagnostics.d.ts.map +1 -0
  26. package/dist/path-aliases.d.ts +34 -0
  27. package/dist/path-aliases.d.ts.map +1 -0
  28. package/dist/post-process-css.d.ts +17 -0
  29. package/dist/post-process-css.d.ts.map +1 -0
  30. package/dist/run-analysis.d.ts +67 -0
  31. package/dist/run-analysis.d.ts.map +1 -0
  32. package/dist/static-css.d.ts +25 -0
  33. package/dist/static-css.d.ts.map +1 -0
  34. package/dist/system-config.d.ts +44 -0
  35. package/dist/system-config.d.ts.map +1 -0
  36. package/dist/system-props-module.d.ts +18 -0
  37. package/dist/system-props-module.d.ts.map +1 -0
  38. package/dist/timing-waterfall.d.ts +17 -0
  39. package/dist/timing-waterfall.d.ts.map +1 -0
  40. package/dist/tsconfig-paths.d.ts +8 -0
  41. package/dist/tsconfig-paths.d.ts.map +1 -0
  42. package/dist/watch-keys.d.ts +12 -0
  43. package/dist/watch-keys.d.ts.map +1 -0
  44. package/index-v2.js +6 -5
  45. package/package.json +14 -35
  46. package/animus-extract.darwin-arm64.node +0 -0
  47. package/animus-extract.linux-arm64-gnu.node +0 -0
  48. package/animus-extract.linux-x64-gnu.node +0 -0
  49. package/crates/extract-v2/index.js +0 -594
  50. package/dist/resolve-global-styles.d.ts +0 -25
  51. package/dist/resolve-global-styles.d.ts.map +0 -1
  52. package/dist/resolve-transforms.d.ts +0 -9
  53. package/dist/resolve-transforms.d.ts.map +0 -1
  54. package/index.d.ts +0 -65
  55. package/index.js +0 -594
package/dist/index.cjs CHANGED
@@ -1,6 +1,35 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let crypto = require("crypto");
2
25
  let fs = require("fs");
26
+ let path = require("path");
27
+ let browserslist = require("browserslist");
28
+ browserslist = __toESM(browserslist);
29
+ let lightningcss = require("lightningcss");
3
30
  let _animus_ui_properties = require("@animus-ui/properties");
31
+ let node_fs = require("node:fs");
32
+ let node_path = require("node:path");
4
33
  //#region pipeline/assemble-stylesheet.ts
5
34
  /**
6
35
  * The 7 Animus cascade layers. Always prefixed with `anm-` to avoid
@@ -73,8 +102,319 @@ function assembleStylesheet(options) {
73
102
  ].filter(Boolean).join("\n");
74
103
  }
75
104
  //#endregion
105
+ //#region pipeline/analyze-project-args.ts
106
+ /** @internal */
107
+ function buildAnalyzeProjectArgs(inputs) {
108
+ return [
109
+ inputs.filesJson,
110
+ inputs.scalesJson,
111
+ inputs.variableMapJson,
112
+ inputs.contextualVarsJson,
113
+ inputs.propConfigJson,
114
+ inputs.groupRegistryJson,
115
+ inputs.packageResolutionJson,
116
+ inputs.devMode,
117
+ inputs.emitterConfigJson,
118
+ inputs.selectorAliasesJson,
119
+ null,
120
+ inputs.globalStyleBlocksJson,
121
+ inputs.pathAliasesJson,
122
+ inputs.keyframesJson,
123
+ inputs.staticCssJson,
124
+ inputs.conditionAliasesJson
125
+ ];
126
+ }
127
+ //#endregion
128
+ //#region pipeline/engine-adapter.ts
129
+ /**
130
+ * Build the engine-agnostic `engineApi()` both plugins call. Returns a function
131
+ * (invoked per call site) so `isV2()` and `loadNativeEngine()` re-evaluate each
132
+ * time — next reads its engine choice and native module dynamically from
133
+ * globalThis.
134
+ */
135
+ function createV2EngineApi(deps) {
136
+ const { label, isV2, loadNativeEngine, store } = deps;
137
+ return () => {
138
+ if (!isV2()) return loadNativeEngine();
139
+ const native = loadNativeEngine();
140
+ return {
141
+ loadSystemModule: (...args) => native.loadSystemModule(...args),
142
+ analyzeProject: (filesJsonRaw, scalesJson, variableMapJson, contextualVarsJson, propConfigJson, groupRegistryJson, packageResolutionJson, devMode, emitterConfigJson, selectorAliasesJson, _selectorOrderJson, globalStyleBlocksJson, pathAliasesJson, keyframesJson, staticCssJson, conditionAliasesJson) => {
143
+ const filesJson = deps.rehydrateFilesJson ? deps.rehydrateFilesJson(filesJsonRaw) : filesJsonRaw;
144
+ const sent = /* @__PURE__ */ new Map();
145
+ for (const entry of JSON.parse(filesJson)) sent.set(entry.path, entry.source);
146
+ store.setSentSources(sent);
147
+ const emitterConfig = emitterConfigJson ? JSON.parse(emitterConfigJson) : {};
148
+ store.setEngine(null);
149
+ const config = {
150
+ runtimeImport: emitterConfig.runtime_import ?? void 0,
151
+ cssModuleId: emitterConfig.css_module_id ?? void 0,
152
+ systemPropsModuleId: emitterConfig.system_props_module_id ?? void 0,
153
+ themeJson: scalesJson,
154
+ variableMapJson,
155
+ contextualVarsJson: contextualVarsJson ?? void 0,
156
+ configJson: propConfigJson,
157
+ groupRegistryJson,
158
+ selectorAliasesJson: selectorAliasesJson ?? void 0,
159
+ conditionAliasesJson: conditionAliasesJson ?? void 0,
160
+ globalStyleBlocksJson: globalStyleBlocksJson ?? void 0,
161
+ keyframesJson: keyframesJson ?? void 0,
162
+ packageResolutionJson: packageResolutionJson ?? void 0,
163
+ pathAliasesJson: pathAliasesJson ?? void 0,
164
+ staticCssJson: staticCssJson ?? void 0,
165
+ devMode
166
+ };
167
+ const engine = new native.ExtractEngine(config);
168
+ store.setEngine(engine);
169
+ return engine.analyze(filesJson);
170
+ },
171
+ transformFile: (source, path, _manifest) => {
172
+ const engine = store.getEngine();
173
+ if (!engine) throw new Error(`[${label}] v2 transform before analyze — engine instance not initialized`);
174
+ const sentMap = store.getSentSources();
175
+ if (deps.passThroughUnknownPaths && sentMap && !sentMap.has(path)) return {
176
+ code: source,
177
+ hasComponents: false
178
+ };
179
+ const sent = sentMap?.get(path);
180
+ if (sent !== void 0 && sent !== source && !store.getDriftWarned()) {
181
+ store.setDriftWarned(true);
182
+ console.warn(`[${label}] v2: transform-time source for ${path} differs from analyze-time source — an upstream transform may be reverted`);
183
+ }
184
+ return JSON.parse(engine.transformFile(path));
185
+ },
186
+ clearAnalysisCache: () => {
187
+ const engine = store.getEngine();
188
+ if (engine) engine.clearCache();
189
+ store.setEngine(null);
190
+ }
191
+ };
192
+ };
193
+ }
194
+ //#endregion
195
+ //#region pipeline/engine-retirement.ts
196
+ /**
197
+ * Loud rejection of the retired v1 extraction engine (openspec:
198
+ * retire-extract-v1).
199
+ *
200
+ * v2 is the only engine. Selecting v1 — via a plugin `engine: 'v1'` option or
201
+ * the `ANIMUS_ENGINE=v1` fixture env override — is a build-stopping error, never
202
+ * a silent upgrade to v2 (design D1: a silent flip would let a pinned consumer's
203
+ * build change engines without notice). Both extraction plugins call this before
204
+ * any engine selection so the rejection is uniform and the message is defined
205
+ * once.
206
+ */
207
+ /** The one canonical retirement message, shared verbatim by both plugins and
208
+ * the env path (tasks.md cross-cutting: define once, reuse). */
209
+ const RETIRED_ENGINE_MESSAGE = "animus: extraction engine 'v1' was retired (openspec: retire-extract-v1) — v2 is the only engine; remove the engine option / ANIMUS_ENGINE override";
210
+ /**
211
+ * Throw the canonical retirement error when v1 is selected through either
212
+ * surface. Accepts the raw engine option as a string because the option type no
213
+ * longer admits `'v1'` — callers cast it so a stale `'v1'` config still fails
214
+ * loud at runtime instead of being silently coerced.
215
+ */
216
+ function assertNoRetiredEngineSelection(engineOption) {
217
+ if (engineOption === "v1" || process.env.ANIMUS_ENGINE === "v1") throw new Error(RETIRED_ENGINE_MESSAGE);
218
+ }
219
+ //#endregion
220
+ //#region pipeline/content-hash.ts
221
+ /**
222
+ * MD5 content hash used for file-change detection (HMR diffing) by both
223
+ * extraction plugins. The algorithm/encoding is a cross-plugin contract:
224
+ * cache keys written by one build path must compare equal in the next.
225
+ */
226
+ function contentHash(source) {
227
+ return (0, crypto.createHash)("md5").update(source).digest("hex");
228
+ }
229
+ //#endregion
230
+ //#region pipeline/discover-files.ts
231
+ /**
232
+ * Recursively discover source files under `dir`, skipping any path that
233
+ * contains one of `excludePatterns` (matched as substrings of both the full
234
+ * and root-relative path) and keeping files whose extension is in
235
+ * `extensionsSet`.
236
+ *
237
+ * Single authoritative copy for both extraction plugins.
238
+ */
239
+ function discoverFiles(dir, rootDir, excludePatterns, extensionsSet) {
240
+ const results = [];
241
+ let entries;
242
+ try {
243
+ entries = (0, fs.readdirSync)(dir, { encoding: "utf8" });
244
+ } catch {
245
+ return results;
246
+ }
247
+ for (const entry of entries) {
248
+ const fullPath = (0, path.join)(dir, entry);
249
+ const relativePath = (0, path.relative)(rootDir, fullPath);
250
+ if (excludePatterns.some((pattern) => fullPath.includes(pattern) || relativePath.includes(pattern))) continue;
251
+ let stat;
252
+ try {
253
+ stat = (0, fs.statSync)(fullPath);
254
+ } catch {
255
+ continue;
256
+ }
257
+ if (stat.isDirectory()) results.push(...discoverFiles(fullPath, rootDir, excludePatterns, extensionsSet));
258
+ else if (extensionsSet.has((0, path.extname)(entry))) results.push(fullPath);
259
+ }
260
+ return results;
261
+ }
262
+ //#endregion
76
263
  //#region pipeline/discover-packages.ts
77
264
  /**
265
+ * Walk up from a resolved package entry file to the nearest directory
266
+ * containing a package.json (the package root). Stops at the filesystem
267
+ * root and returns it if no package.json is found on the way up.
268
+ */
269
+ function findPackageRoot(absEntryPath) {
270
+ let pkgRoot = (0, path.dirname)(absEntryPath);
271
+ while (pkgRoot !== (0, path.dirname)(pkgRoot) && !(0, fs.existsSync)((0, path.join)(pkgRoot, "package.json"))) pkgRoot = (0, path.dirname)(pkgRoot);
272
+ return pkgRoot;
273
+ }
274
+ /**
275
+ * Exclusion fragments for discovery inside an external package's src/,
276
+ * matched against paths RELATIVE TO the src dir. Per spec
277
+ * external-package-file-discovery, the filters apply "within the package" —
278
+ * an npm-installed package whose own location is under node_modules (or a
279
+ * repo path containing 'dist') must still have its sources discovered.
280
+ */
281
+ const PACKAGE_SRC_EXCLUDES = [
282
+ "dist",
283
+ "node_modules",
284
+ ".test.",
285
+ ".spec."
286
+ ];
287
+ const isFile = (path$2) => {
288
+ try {
289
+ return (0, fs.statSync)(path$2).isFile();
290
+ } catch {
291
+ return false;
292
+ }
293
+ };
294
+ /**
295
+ * Resolve an absolute-path specifier (a relative `includes` entry already
296
+ * resolved against the system file) without a bundler: the path itself when it
297
+ * is a file, otherwise the extension and directory-index candidates built from
298
+ * the caller's extension set. Used only as a fallback — a bundler resolver that
299
+ * answers first still wins, since it also knows aliases and `.js`→`.ts` mapping.
300
+ */
301
+ function resolveAbsolutePathSpecifier(absSpecifier, extensionsSet) {
302
+ return [
303
+ absSpecifier,
304
+ ...Array.from(extensionsSet, (ext) => absSpecifier + ext),
305
+ ...Array.from(extensionsSet, (ext) => (0, path.join)(absSpecifier, `index${ext}`))
306
+ ].find(isFile) ?? null;
307
+ }
308
+ /**
309
+ * Shared external-package source collection (spec:
310
+ * external-package-file-discovery), consumed by both extraction plugins.
311
+ * For each specifier: resolve to an absolute entry (null/throw → silently
312
+ * skip; an absolute-path specifier falls back to a filesystem probe), walk up
313
+ * to the package root, then either discover sources under src/ (redirecting
314
+ * module resolution to src/index.ts when present) or fall back to ingesting
315
+ * the resolved entry file itself.
316
+ *
317
+ * The only bundler-specific seams are callbacks: specifier resolution,
318
+ * dedup against already-ingested files, per-file preprocessing (MDX), and
319
+ * the unreadable-file warning. Hashing and caching stay in the plugins —
320
+ * their cache policies legitimately differ.
321
+ *
322
+ * Alongside the aggregates, one `ExternalPackageOutcome` per declared
323
+ * specifier records what that specifier produced. Collection never reports on
324
+ * them — any warning or gate is the caller's policy.
325
+ */
326
+ async function collectExternalPackageSources(opts) {
327
+ const { specifiers, resolveSpecifier, rootDir, extensionsSet, hasEntry, preprocessFile, onUnreadable } = opts;
328
+ const entries = [];
329
+ const pushed = /* @__PURE__ */ new Set();
330
+ const packageMap = {};
331
+ const sourceEntries = /* @__PURE__ */ new Map();
332
+ const packageDirs = [];
333
+ const outcomes = [];
334
+ const alreadyIngested = (relPath) => hasEntry(relPath) || pushed.has(relPath);
335
+ for (const specifier of specifiers) {
336
+ let absEntry;
337
+ try {
338
+ absEntry = await resolveSpecifier(specifier);
339
+ } catch {
340
+ absEntry = null;
341
+ }
342
+ if (!absEntry && (0, path.isAbsolute)(specifier)) absEntry = resolveAbsolutePathSpecifier(specifier, extensionsSet);
343
+ if (!absEntry) {
344
+ outcomes.push({
345
+ specifier,
346
+ outcome: "unresolvable",
347
+ fileCount: 0
348
+ });
349
+ continue;
350
+ }
351
+ const srcDir = (0, path.join)(findPackageRoot(absEntry), "src");
352
+ let fileCount = 0;
353
+ if ((0, fs.existsSync)(srcDir)) {
354
+ packageDirs.push(srcDir);
355
+ const srcEntry = (0, path.join)(srcDir, "index.ts");
356
+ if ((0, fs.existsSync)(srcEntry)) {
357
+ packageMap[specifier] = (0, path.relative)(rootDir, srcEntry);
358
+ sourceEntries.set(specifier, srcEntry);
359
+ } else packageMap[specifier] = (0, path.relative)(rootDir, absEntry);
360
+ const pkgFiles = discoverFiles(srcDir, srcDir, [], extensionsSet).filter((absPath) => {
361
+ const inPkg = (0, path.relative)(srcDir, absPath);
362
+ return !PACKAGE_SRC_EXCLUDES.some((pattern) => inPkg.includes(pattern));
363
+ });
364
+ for (const pkgFile of pkgFiles) {
365
+ const relPath = (0, path.relative)(rootDir, pkgFile);
366
+ if (alreadyIngested(relPath)) {
367
+ fileCount++;
368
+ continue;
369
+ }
370
+ let source;
371
+ try {
372
+ source = (0, fs.readFileSync)(pkgFile, "utf-8");
373
+ } catch (err) {
374
+ onUnreadable(relPath, err);
375
+ continue;
376
+ }
377
+ const processed = await preprocessFile(source, relPath, pkgFile);
378
+ if (!processed) continue;
379
+ entries.push({
380
+ path: processed.relPath,
381
+ source: processed.source
382
+ });
383
+ pushed.add(processed.relPath);
384
+ fileCount++;
385
+ }
386
+ } else {
387
+ packageDirs.push((0, path.dirname)(absEntry));
388
+ const relPath = (0, path.relative)(rootDir, absEntry);
389
+ packageMap[specifier] = relPath;
390
+ if (alreadyIngested(relPath)) fileCount++;
391
+ else try {
392
+ const source = (0, fs.readFileSync)(absEntry, "utf-8");
393
+ entries.push({
394
+ path: relPath,
395
+ source
396
+ });
397
+ pushed.add(relPath);
398
+ fileCount++;
399
+ } catch (err) {
400
+ onUnreadable(relPath, err);
401
+ }
402
+ }
403
+ outcomes.push({
404
+ specifier,
405
+ outcome: fileCount > 0 ? "resolved" : "empty",
406
+ fileCount
407
+ });
408
+ }
409
+ return {
410
+ entries,
411
+ packageMap,
412
+ sourceEntries,
413
+ packageDirs,
414
+ outcomes
415
+ };
416
+ }
417
+ /**
78
418
  * Extract external DS package names from `includes` declarations in the system file.
79
419
  *
80
420
  * Supports two forms:
@@ -82,11 +422,25 @@ function assembleStylesheet(options) {
82
422
  * - Legacy: `.includes([identifier, ...])` chain method (RC migration fallback)
83
423
  *
84
424
  * For each identifier found, traces back to its import declaration and returns
85
- * the import specifier. Only packages explicitly declared via `includes` are treated
86
- * as external DS dependencies.
425
+ * the import specifier: a bare specifier normalized to its package name, a
426
+ * relative specifier resolved against the system file's directory into an
427
+ * absolute path (so a sibling package referenced by path contributes discovery
428
+ * too). Only packages explicitly declared via `includes` are treated as
429
+ * external DS dependencies.
87
430
  *
88
431
  * Falls back to empty array if no `includes` declaration is found.
89
432
  */
433
+ /**
434
+ * The message for the strict/warn gate over unresolvable includes, or null
435
+ * when every declared specifier resolved (external-package-file-discovery:
436
+ * silence is never an outcome — non-strict consumers warn with this line,
437
+ * strict consumers throw it).
438
+ */
439
+ function unresolvableIncludesMessage(outcomes) {
440
+ const unresolvable = outcomes.filter((record) => record.outcome === "unresolvable").map((record) => record.specifier);
441
+ if (unresolvable.length === 0) return null;
442
+ return `[animus-extract] unresolvable include specifier(s): ${unresolvable.join(", ")}`;
443
+ }
90
444
  function extractSystemFilePackages(systemFilePath) {
91
445
  let source;
92
446
  try {
@@ -123,10 +477,13 @@ function extractSystemFilePackages(systemFilePath) {
123
477
  if (localName) importMap.set(localName, specifier);
124
478
  }
125
479
  }
480
+ const systemFileDir = (0, path.dirname)(systemFilePath);
126
481
  const packages = /* @__PURE__ */ new Set();
127
482
  for (const id of identifiers) {
128
483
  const specifier = importMap.get(id);
129
- if (specifier && !specifier.startsWith(".")) {
484
+ if (!specifier) continue;
485
+ if (specifier.startsWith(".")) packages.add((0, path.resolve)(systemFileDir, specifier));
486
+ else {
130
487
  const pkgName = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
131
488
  packages.add(pkgName);
132
489
  }
@@ -134,6 +491,525 @@ function extractSystemFilePackages(systemFilePath) {
134
491
  return Array.from(packages);
135
492
  }
136
493
  //#endregion
494
+ //#region pipeline/path-aliases.ts
495
+ /**
496
+ * Normalize alias pairs: strip a `rootDir` prefix from targets, classify
497
+ * exact (file) vs prefix (directory) matches, force trailing slashes on
498
+ * prefix entries, and sort longest pattern first for correct matching
499
+ * priority. Returns null when no entries survive.
500
+ */
501
+ function buildPathAliasesJson(pairs, rootDir) {
502
+ const entries = [];
503
+ for (const { pattern, target, kind } of pairs) {
504
+ const replacement = target.startsWith(rootDir) ? target.slice(rootDir.length + 1) : target;
505
+ if (kind ? kind === "exact" : /\.\w+$/.test(replacement)) entries.push({
506
+ pattern,
507
+ replacement,
508
+ type: "exact"
509
+ });
510
+ else entries.push({
511
+ pattern: pattern.endsWith("/") ? pattern : pattern + "/",
512
+ replacement: replacement.endsWith("/") ? replacement : replacement + "/",
513
+ type: "prefix"
514
+ });
515
+ }
516
+ entries.sort((a, b) => b.pattern.length - a.pattern.length);
517
+ if (entries.length === 0) return null;
518
+ return {
519
+ json: JSON.stringify({ aliases: entries }),
520
+ count: entries.length
521
+ };
522
+ }
523
+ //#endregion
524
+ //#region pipeline/post-process-css.ts
525
+ /**
526
+ * CSS post-processing (spec: css-post-processing) — Lightning CSS
527
+ * autoprefixing, syntax lowering, and optional minification against
528
+ * browserslist targets. The single implementation consumed by both the
529
+ * Vite plugin (virtual-module serve time) and the Next.js plugin
530
+ * (ExtractionSession artifact emission).
531
+ */
532
+ /**
533
+ * Resolve browser targets for Lightning CSS.
534
+ * Priority: explicit config → project browserslist → 'defaults' fallback.
535
+ */
536
+ function resolveLightningTargets(explicitTargets, rootDir) {
537
+ let queries;
538
+ if (explicitTargets) queries = Array.isArray(explicitTargets) ? explicitTargets : [explicitTargets];
539
+ else {
540
+ const detected = (0, browserslist.default)(void 0, { path: rootDir });
541
+ queries = detected.length > 0 ? detected : (0, browserslist.default)("defaults");
542
+ }
543
+ return (0, lightningcss.browserslistToTargets)(Array.isArray(queries) && typeof queries[0] === "string" && queries[0].includes(" ") ? (0, browserslist.default)(queries) : queries);
544
+ }
545
+ /**
546
+ * Post-process CSS with Lightning CSS: autoprefixing + optional minification.
547
+ * On failure, returns the original CSS and logs a warning.
548
+ */
549
+ function postProcessCss(css, opts) {
550
+ if (!css) return css;
551
+ try {
552
+ return (0, lightningcss.transform)({
553
+ filename: "animus-extracted.css",
554
+ code: Buffer.from(css),
555
+ minify: opts.minify,
556
+ targets: opts.targets
557
+ }).code.toString();
558
+ } catch (e) {
559
+ const msg = e instanceof Error ? e.message : String(e);
560
+ (opts.warnFn ?? console.warn)(`[animus] Lightning CSS post-processing failed: ${msg}`);
561
+ return css;
562
+ }
563
+ }
564
+ //#endregion
565
+ //#region pipeline/manifest-diagnostics.ts
566
+ /**
567
+ * Surface extraction-manifest diagnostics through a plugin's warn channel.
568
+ *
569
+ * Single authoritative copy for both extraction plugins. Surfaces `bail`
570
+ * (component not extracted), `skip` (component skipped), and `warn` kinds;
571
+ * unknown kinds stay silent.
572
+ */
573
+ function surfaceManifestDiagnostics(manifest, warn) {
574
+ for (const diagnostic of manifest.diagnostics ?? []) if (diagnostic.kind === "bail") warn(`⚠ ${diagnostic.component} not extracted: ${diagnostic.message}`);
575
+ else if (diagnostic.kind === "skip") warn(`⚠ ${diagnostic.component}: skipped ${diagnostic.message}`);
576
+ else if (diagnostic.kind === "warn") warn(`⚠ ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`);
577
+ }
578
+ //#endregion
579
+ //#region pipeline/unit-fallback.ts
580
+ /**
581
+ * Append `px` to bare numeric values in CSS declarations for properties
582
+ * that expect length units. Unitless properties are preserved as-is.
583
+ * Numbers inside CSS function calls (cubic-bezier, rgb, calc, etc.) are skipped.
584
+ */
585
+ function applyUnitFallback(css) {
586
+ return css.replace(/([a-z-]+)\s*:\s*([^;{}]+);/g, (match, prop, value) => {
587
+ if (_animus_ui_properties.UNITLESS_PROPERTIES.has(prop)) return match;
588
+ let depth = 0;
589
+ let fixed = "";
590
+ let i = 0;
591
+ while (i < value.length) if (value[i] === "(") {
592
+ depth++;
593
+ fixed += value[i];
594
+ i++;
595
+ } else if (value[i] === ")") {
596
+ depth--;
597
+ fixed += value[i];
598
+ i++;
599
+ } else if (depth > 0) {
600
+ fixed += value[i];
601
+ i++;
602
+ } else {
603
+ const numMatch = value.slice(i).match(/^(-?\d+\.?\d*)/);
604
+ if (numMatch) {
605
+ const num = numMatch[1];
606
+ const after = value[i + num.length];
607
+ if (after && /[a-z%]/i.test(after)) fixed += num;
608
+ else fixed += num + "px";
609
+ i += num.length;
610
+ } else {
611
+ fixed += value[i];
612
+ i++;
613
+ }
614
+ }
615
+ return fixed !== value ? `${prop}:${fixed};` : match;
616
+ });
617
+ }
618
+ //#endregion
619
+ //#region pipeline/run-analysis.ts
620
+ /**
621
+ * Build the named `analyzeProject` input set from analysis options. Also
622
+ * the persistence shape for `.animus/analysis-inputs.json` — an isolated
623
+ * process can replay the analysis from exactly this object
624
+ * (spec: next-turbopack-integration).
625
+ */
626
+ function buildAnalysisInputs(opts) {
627
+ return {
628
+ filesJson: JSON.stringify(opts.fileEntries),
629
+ scalesJson: opts.system.scalesJson,
630
+ variableMapJson: opts.system.variableMapJson,
631
+ contextualVarsJson: opts.system.contextualVarsJson,
632
+ propConfigJson: opts.system.propConfigJson,
633
+ groupRegistryJson: opts.system.groupRegistryJson,
634
+ packageResolutionJson: JSON.stringify(opts.packageMap),
635
+ devMode: opts.devMode,
636
+ emitterConfigJson: JSON.stringify({
637
+ runtime_import: opts.emitter.runtimeImport,
638
+ css_module_id: opts.emitter.cssModuleId,
639
+ ...opts.emitter.systemPropsModuleId ? { system_props_module_id: opts.emitter.systemPropsModuleId } : {}
640
+ }),
641
+ selectorAliasesJson: opts.system.selectorAliasesJson,
642
+ globalStyleBlocksJson: opts.system.globalStyleBlocksJson,
643
+ pathAliasesJson: opts.pathAliasesJson,
644
+ keyframesJson: opts.system.keyframesJson,
645
+ staticCssJson: opts.staticCssJson ?? null,
646
+ conditionAliasesJson: opts.system.conditionAliasesJson ?? null
647
+ };
648
+ }
649
+ /**
650
+ * The one analysis invocation both plugins share: build the emitter
651
+ * config, serialize inputs, call the NAPI `analyzeProject`, parse the
652
+ * manifest, surface its diagnostics, and resolve the CSS outputs.
653
+ *
654
+ * Error handling stays at the call site (strict-mode throw vs warn).
655
+ */
656
+ function runProjectAnalysis(engineApi, opts) {
657
+ const { analyzeProject } = engineApi();
658
+ let t = performance.now();
659
+ const inputs = buildAnalysisInputs(opts);
660
+ const serializeMs = Math.round(performance.now() - t);
661
+ t = performance.now();
662
+ const manifestJson = analyzeProject(...buildAnalyzeProjectArgs(inputs));
663
+ const extractMs = Math.round(performance.now() - t);
664
+ t = performance.now();
665
+ const manifest = JSON.parse(manifestJson);
666
+ surfaceManifestDiagnostics(manifest, opts.warn);
667
+ const parseMs = Math.round(performance.now() - t);
668
+ return {
669
+ manifest,
670
+ manifestJson,
671
+ globalCss: manifest?.sheets?.global || "",
672
+ componentCss: applyUnitFallback(manifest?.css || ""),
673
+ inputs,
674
+ timings: {
675
+ serializeMs,
676
+ extractMs,
677
+ parseMs
678
+ }
679
+ };
680
+ }
681
+ /**
682
+ * Clear the engine's per-file analysis cache so stale results from a prior
683
+ * build never bleed into a fresh run. Tolerates engines without the
684
+ * capability (older builds) — the probe is benign.
685
+ */
686
+ function clearEngineCache(engineApi) {
687
+ try {
688
+ const { clearAnalysisCache } = engineApi();
689
+ clearAnalysisCache();
690
+ } catch {}
691
+ }
692
+ //#endregion
693
+ //#region pipeline/static-css.ts
694
+ /** Key-sorted stringify so semantically identical configs serialize
695
+ * identically (stable analysis-inputs hashing). */
696
+ function stableStringify(value) {
697
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
698
+ if (value !== null && typeof value === "object") return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
699
+ return JSON.stringify(value);
700
+ }
701
+ /** Serialize a staticCss option for the engine; null when absent/empty. */
702
+ function serializeStaticCss(config) {
703
+ if (!config) return null;
704
+ const json = stableStringify(config);
705
+ return json === "{}" ? null : json;
706
+ }
707
+ //#endregion
708
+ //#region pipeline/prefix.ts
709
+ /**
710
+ * Apply namespace prefix to a variable map, CSS variable declarations, and theme JSON.
711
+ *
712
+ * Variable map: `{ "colors.ember": "--color-ember" }` -> `{ "colors.ember": "--prefix-color-ember" }`
713
+ * Variable CSS: `--color-ember: #FF2800` -> `--prefix-color-ember: #FF2800`
714
+ * `var(--color-ember)` -> `var(--prefix-color-ember)`
715
+ * Theme JSON: `{ "colors.ember": "var(--color-ember)" }` -> `{ "colors.ember": "var(--prefix-color-ember)" }`
716
+ */
717
+ function applyPrefix(prefix, variableMapJson, variableCss, themeJson, contextualVarsJson) {
718
+ if (!prefix) return {
719
+ variableMapJson,
720
+ variableCss,
721
+ themeJson,
722
+ contextualVarsJson
723
+ };
724
+ const varRefRe = /var\(--([a-zA-Z][\w-]*)\)/g;
725
+ const map = JSON.parse(variableMapJson);
726
+ const prefixed = {};
727
+ for (const [key, varName] of Object.entries(map)) prefixed[key] = varName.startsWith("--") ? `--${prefix}-${varName.slice(2)}` : varName;
728
+ let css = variableCss;
729
+ css = css.replace(/--([a-zA-Z][\w-]*)\s*:/g, `--${prefix}-$1:`);
730
+ css = css.replace(varRefRe, `var(--${prefix}-$1)`);
731
+ const result = {
732
+ variableMapJson: JSON.stringify(prefixed),
733
+ variableCss: css
734
+ };
735
+ if (themeJson) result.themeJson = themeJson.replace(varRefRe, `var(--${prefix}-$1)`);
736
+ if (contextualVarsJson) {
737
+ const ctxVars = JSON.parse(contextualVarsJson);
738
+ const prefixedCtx = {};
739
+ for (const [scale, names] of Object.entries(ctxVars)) prefixedCtx[scale] = names.map((name) => `${prefix}-${name}`);
740
+ result.contextualVarsJson = JSON.stringify(prefixedCtx);
741
+ }
742
+ return result;
743
+ }
744
+ //#endregion
745
+ //#region pipeline/system-config.ts
746
+ /**
747
+ * Load a SystemInstance via the engine's `loadSystemModule` and normalize
748
+ * it into a SystemConfig. When `prefix` is set, CSS variable names in the
749
+ * variable map/css (and theme + contextual vars when affected) are
750
+ * namespaced via the shared `applyPrefix`.
751
+ *
752
+ * Error handling stays at the call site — the Vite plugin warns (or throws
753
+ * in strict mode) and keeps its previous config, the Next plugin lets the
754
+ * failure propagate.
755
+ */
756
+ function loadSystemConfig(engineApi, opts) {
757
+ const { loadSystemModule } = engineApi();
758
+ const config = loadSystemModule(opts.systemPath, opts.rootDir);
759
+ let scalesJson = config.scalesJson;
760
+ let variableMapJson = config.variableMapJson;
761
+ let variableCss = config.variableCss;
762
+ let contextualVarsJson = config.contextualVarsJson || null;
763
+ if (opts.prefix) {
764
+ const prefixed = applyPrefix(opts.prefix, variableMapJson, variableCss, scalesJson, contextualVarsJson || void 0);
765
+ variableMapJson = prefixed.variableMapJson;
766
+ variableCss = prefixed.variableCss;
767
+ if (prefixed.themeJson) scalesJson = prefixed.themeJson;
768
+ if (prefixed.contextualVarsJson) contextualVarsJson = prefixed.contextualVarsJson;
769
+ }
770
+ return {
771
+ propConfigJson: config.propConfig,
772
+ groupRegistryJson: config.groupRegistry,
773
+ scalesJson,
774
+ variableMapJson,
775
+ variableCss,
776
+ contextualVarsJson,
777
+ selectorAliasesJson: config.selectorAliases || null,
778
+ conditionAliasesJson: config.conditionAliases || null,
779
+ globalStyleBlocksJson: config.globalStyleBlocks || null,
780
+ keyframesJson: config.keyframesBlocks || null,
781
+ dependencies: config.dependencies ?? []
782
+ };
783
+ }
784
+ //#endregion
785
+ //#region pipeline/dynamic-prop-config.ts
786
+ function buildDynamicPropConfig(dynamicProps) {
787
+ const configEntries = {};
788
+ for (const [propName, meta] of Object.entries(dynamicProps)) configEntries[propName] = {
789
+ varName: meta.var_name,
790
+ slotClass: meta.slot_class,
791
+ ...meta.transform_name ? { transformName: meta.transform_name } : {},
792
+ ...meta.scale_values && Object.keys(meta.scale_values).length > 0 ? { scaleValues: meta.scale_values } : {}
793
+ };
794
+ return configEntries;
795
+ }
796
+ //#endregion
797
+ //#region pipeline/system-props-module.ts
798
+ /**
799
+ * Build the source of the runtime system-props module — served as
800
+ * `virtual:animus/system-props` by the Vite plugin and written to
801
+ * `.animus/system-props.js` by the Next plugin. One generator so the two
802
+ * runtimes can never drift.
803
+ *
804
+ * Runtime transform functions for dynamic props are not yet supported
805
+ * (transforms are resolved at extraction time via boa_engine in Rust), so
806
+ * `transforms` defaults to an empty object literal.
807
+ */
808
+ function buildSystemPropsModule(opts) {
809
+ const dynamicPropConfig = buildDynamicPropConfig(opts.dynamicProps);
810
+ return `export const systemPropMap = ${opts.systemPropMapJson};\nexport const systemPropGroups = ${opts.groupRegistryJson};\nexport const dynamicPropConfig = ${JSON.stringify(dynamicPropConfig)};\nexport const transforms = ${opts.transformsSource ?? "{}"};\n`;
811
+ }
812
+ //#endregion
813
+ //#region pipeline/timing-waterfall.ts
814
+ /**
815
+ * Rust-side timing phases as reported in `manifest.timing`, in waterfall
816
+ * order, with their display labels. Mirrors the timing struct in the
817
+ * extract-v2 crate — a renamed or added Rust phase is updated here once,
818
+ * not per plugin.
819
+ */
820
+ const RUST_TIMING_PHASES = [
821
+ ["parseAndWalk", "parse+walk"],
822
+ ["importResolution", "imports"],
823
+ ["extensionProvenance", "provenance"],
824
+ ["topologicalSort", "topo-sort"],
825
+ ["chainEvaluation", "chains"],
826
+ ["jsxScanning", "jsx-scan"],
827
+ ["systemPropAggregation", "sys-props"],
828
+ ["usageLedger", "usage"],
829
+ ["reconciliation", "reconcile"],
830
+ ["cssGeneration", "css-gen"],
831
+ ["manifestSerialization", "serialize"]
832
+ ];
833
+ /**
834
+ * Format the Rust timing waterfall as log lines. `indent` and `labelWidth`
835
+ * let each plugin keep its established column layout; the phase table and
836
+ * the `(N files, M cached)` suffix are shared.
837
+ */
838
+ function formatRustTimingWaterfall(timing, opts) {
839
+ const lines = [];
840
+ for (const [key, label] of RUST_TIMING_PHASES) {
841
+ const ms = timing[key] ?? 0;
842
+ const pad = " ".repeat(Math.max(0, opts.labelWidth - label.length));
843
+ const extra = key === "parseAndWalk" ? ` (${timing.fileCount ?? 0} files, ${timing.cacheHits ?? 0} cached)` : "";
844
+ lines.push(`${opts.indent}${label}${pad}${String(ms).padStart(5)}ms${extra}`);
845
+ }
846
+ return lines;
847
+ }
848
+ //#endregion
849
+ //#region pipeline/tsconfig-paths.ts
850
+ /**
851
+ * tsconfig `paths` → alias pairs for the Rust `pathAliasesJson` contract
852
+ * (via `buildPathAliasesJson`). Used by drivers that cannot harvest aliases
853
+ * from a live bundler config — Turbopack exposes none.
854
+ *
855
+ * Semantics (deliberately the TypeScript subset that matters for import
856
+ * provenance): JSONC tolerated (comments + trailing commas); `extends`
857
+ * chains followed (string or array; missing parents and cycles skipped);
858
+ * the NEAREST config declaring `paths` wins wholesale (TS replaces the
859
+ * object, never merges); targets resolve against the nearest `baseUrl`
860
+ * (resolved from its declaring config's directory) or, absent one, the
861
+ * directory of the config declaring `paths`; first target per pattern.
862
+ * `x/*` patterns become prefix pairs, non-wildcard patterns exact pairs,
863
+ * bare `*` catch-alls and mid-pattern wildcards are skipped.
864
+ */
865
+ /** String-aware JSONC → JSON: strips // and block comments + trailing commas. */
866
+ function stripJsonc(text) {
867
+ let out = "";
868
+ let inString = false;
869
+ let inLine = false;
870
+ let inBlock = false;
871
+ for (let i = 0; i < text.length; i++) {
872
+ const ch = text[i];
873
+ const next = text[i + 1];
874
+ if (inLine) {
875
+ if (ch === "\n") {
876
+ inLine = false;
877
+ out += ch;
878
+ }
879
+ continue;
880
+ }
881
+ if (inBlock) {
882
+ if (ch === "*" && next === "/") {
883
+ inBlock = false;
884
+ i++;
885
+ }
886
+ continue;
887
+ }
888
+ if (inString) {
889
+ out += ch;
890
+ if (ch === "\\") {
891
+ out += next ?? "";
892
+ i++;
893
+ } else if (ch === "\"") inString = false;
894
+ continue;
895
+ }
896
+ if (ch === "\"") {
897
+ inString = true;
898
+ out += ch;
899
+ continue;
900
+ }
901
+ if (ch === "/" && next === "/") {
902
+ inLine = true;
903
+ i++;
904
+ continue;
905
+ }
906
+ if (ch === "/" && next === "*") {
907
+ inBlock = true;
908
+ i++;
909
+ continue;
910
+ }
911
+ out += ch;
912
+ }
913
+ return out.replace(/,(\s*[}\]])/g, "$1");
914
+ }
915
+ function readConfig(path$1) {
916
+ let raw;
917
+ try {
918
+ raw = (0, fs.readFileSync)(path$1, "utf-8");
919
+ } catch {
920
+ return null;
921
+ }
922
+ try {
923
+ const json = JSON.parse(stripJsonc(raw));
924
+ return {
925
+ dir: (0, path.dirname)(path$1),
926
+ compilerOptions: json.compilerOptions ?? {},
927
+ extends: json.extends
928
+ };
929
+ } catch {
930
+ return null;
931
+ }
932
+ }
933
+ function resolveExtendsTarget(specifier, fromDir) {
934
+ if (specifier.startsWith(".") || (0, path.isAbsolute)(specifier)) {
935
+ const base = (0, path.resolve)(fromDir, specifier);
936
+ for (const candidate of [
937
+ base,
938
+ `${base}.json`,
939
+ (0, path.join)(base, "tsconfig.json")
940
+ ]) if ((0, fs.existsSync)(candidate)) return candidate;
941
+ return null;
942
+ }
943
+ try {
944
+ return require.resolve(specifier.endsWith(".json") ? specifier : `${specifier}/tsconfig.json`, { paths: [fromDir] });
945
+ } catch {
946
+ try {
947
+ return require.resolve(specifier, { paths: [fromDir] });
948
+ } catch {
949
+ return null;
950
+ }
951
+ }
952
+ }
953
+ /** Child-first flattened extends chain starting at `entryPath`. */
954
+ function loadChain(entryPath) {
955
+ const chain = [];
956
+ const visited = /* @__PURE__ */ new Set();
957
+ const queue = [entryPath];
958
+ while (queue.length > 0) {
959
+ const key = (0, path.resolve)(queue.shift());
960
+ if (visited.has(key)) continue;
961
+ visited.add(key);
962
+ const node = readConfig(key);
963
+ if (!node) continue;
964
+ chain.push(node);
965
+ const parents = Array.isArray(node.extends) ? node.extends : node.extends !== void 0 ? [node.extends] : [];
966
+ for (const parent of parents) {
967
+ if (typeof parent !== "string") continue;
968
+ const resolved = resolveExtendsTarget(parent, node.dir);
969
+ if (resolved) queue.push(resolved);
970
+ }
971
+ }
972
+ return chain;
973
+ }
974
+ /**
975
+ * Read the project's tsconfig path aliases as pairs consumable by
976
+ * `buildPathAliasesJson`. Returns an empty array when no readable tsconfig
977
+ * (or no usable `paths`) exists.
978
+ */
979
+ function readTsconfigAliasPairs(rootDir) {
980
+ const chain = loadChain((0, path.join)(rootDir, "tsconfig.json"));
981
+ if (chain.length === 0) return [];
982
+ const pathsOwner = chain.find((node) => node.compilerOptions.paths && typeof node.compilerOptions.paths === "object");
983
+ if (!pathsOwner) return [];
984
+ const paths = pathsOwner.compilerOptions.paths;
985
+ const baseOwner = chain.find((node) => typeof node.compilerOptions.baseUrl === "string");
986
+ const base = baseOwner ? (0, path.resolve)(baseOwner.dir, baseOwner.compilerOptions.baseUrl) : pathsOwner.dir;
987
+ const pairs = [];
988
+ for (const [pattern, targets] of Object.entries(paths)) {
989
+ const target = Array.isArray(targets) ? targets[0] : void 0;
990
+ if (typeof target !== "string") continue;
991
+ const patternStars = pattern.split("*").length - 1;
992
+ const targetStars = target.split("*").length - 1;
993
+ if (pattern === "*" || patternStars > 1 || targetStars > 1) continue;
994
+ if (pattern.endsWith("/*")) {
995
+ if (!target.endsWith("/*")) continue;
996
+ pairs.push({
997
+ pattern: pattern.slice(0, -2),
998
+ target: (0, path.resolve)(base, target.slice(0, -2)),
999
+ kind: "prefix"
1000
+ });
1001
+ continue;
1002
+ }
1003
+ if (patternStars > 0 || targetStars > 0) continue;
1004
+ pairs.push({
1005
+ pattern,
1006
+ target: (0, path.resolve)(base, target),
1007
+ kind: "exact"
1008
+ });
1009
+ }
1010
+ return pairs;
1011
+ }
1012
+ //#endregion
137
1013
  //#region pipeline/mdx-preprocessor.ts
138
1014
  /**
139
1015
  * MDX source preprocessor for the extraction pipeline.
@@ -191,203 +1067,87 @@ async function preprocessMdx(source, filename) {
191
1067
  }
192
1068
  }
193
1069
  //#endregion
194
- //#region pipeline/prefix.ts
195
- /**
196
- * Apply namespace prefix to a variable map, CSS variable declarations, and theme JSON.
197
- *
198
- * Variable map: `{ "colors.ember": "--color-ember" }` -> `{ "colors.ember": "--prefix-color-ember" }`
199
- * Variable CSS: `--color-ember: #FF2800` -> `--prefix-color-ember: #FF2800`
200
- * `var(--color-ember)` -> `var(--prefix-color-ember)`
201
- * Theme JSON: `{ "colors.ember": "var(--color-ember)" }` -> `{ "colors.ember": "var(--prefix-color-ember)" }`
202
- */
203
- function applyPrefix(prefix, variableMapJson, variableCss, themeJson, contextualVarsJson) {
204
- if (!prefix) return {
205
- variableMapJson,
206
- variableCss,
207
- themeJson,
208
- contextualVarsJson
209
- };
210
- const varRefRe = /var\(--([a-zA-Z][\w-]*)\)/g;
211
- const map = JSON.parse(variableMapJson);
212
- const prefixed = {};
213
- for (const [key, varName] of Object.entries(map)) prefixed[key] = varName.startsWith("--") ? `--${prefix}-${varName.slice(2)}` : varName;
214
- let css = variableCss;
215
- css = css.replace(/--([a-zA-Z][\w-]*)\s*:/g, `--${prefix}-$1:`);
216
- css = css.replace(varRefRe, `var(--${prefix}-$1)`);
217
- const result = {
218
- variableMapJson: JSON.stringify(prefixed),
219
- variableCss: css
220
- };
221
- if (themeJson) result.themeJson = themeJson.replace(varRefRe, `var(--${prefix}-$1)`);
222
- if (contextualVarsJson) {
223
- const ctxVars = JSON.parse(contextualVarsJson);
224
- const prefixedCtx = {};
225
- for (const [scale, names] of Object.entries(ctxVars)) prefixedCtx[scale] = names.map((name) => `${prefix}-${name}`);
226
- result.contextualVarsJson = JSON.stringify(prefixedCtx);
227
- }
228
- return result;
229
- }
230
- //#endregion
231
1070
  //#region pipeline/utils.ts
232
1071
  /** Convert camelCase CSS property names to kebab-case. */
233
1072
  function camelToKebab(str) {
234
1073
  return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
235
1074
  }
236
1075
  //#endregion
237
- //#region pipeline/resolve-global-styles.ts
238
- /**
239
- * Resolve {scale.path} and {scale.path/alpha} token aliases in a CSS value string.
240
- * Mirrors the Rust theme_resolver's resolve_token_aliases logic.
241
- */
242
- function resolveTokenAliases(value, flat, variableMap) {
243
- if (!value.includes("{")) return value;
244
- return value.replace(/\{([^}]+)\}/g, (_match, content) => {
245
- const slashIdx = content.indexOf("/");
246
- const tokenPath = slashIdx >= 0 ? content.slice(0, slashIdx) : content;
247
- const alpha = slashIdx >= 0 ? Number.parseInt(content.slice(slashIdx + 1), 10) : null;
248
- const flatKey = tokenPath;
249
- let resolved;
250
- if (variableMap[flatKey]) resolved = `var(${variableMap[flatKey]})`;
251
- else if (flat[flatKey] != null) resolved = flat[flatKey];
252
- else return `{${content}}`;
253
- if (alpha === 0) return "transparent";
254
- if (alpha != null && alpha !== 100) return `color-mix(in srgb, ${resolved} ${alpha}%, transparent)`;
255
- return resolved;
256
- });
257
- }
258
- /**
259
- * Resolve a single CSS value through prop config: scale lookup, transform, token aliases.
260
- */
261
- function resolveValue(raw, config, flat, variableMap, transforms) {
262
- let resolved = String(raw);
263
- if (config?.scale) {
264
- const key = config.scale + "." + raw;
265
- if (flat[key] != null) resolved = flat[key];
266
- }
267
- if (config?.transform && transforms[config.transform]) {
268
- const fn = transforms[config.transform];
269
- const input = typeof resolved === "string" && !Number.isNaN(Number(resolved)) ? Number(resolved) : resolved;
270
- resolved = String(fn(input));
271
- }
272
- return resolveTokenAliases(resolved, flat, variableMap);
273
- }
274
- /**
275
- * Resolve a block of selectors -> style objects using prop config + theme.
276
- * @keyframes selectors are resolved with full prop config support.
277
- */
278
- function resolveBlock(selectors, propConfig, flat, variableMap, transforms) {
279
- const rules = [];
280
- for (const [selector, styleObj] of Object.entries(selectors)) {
281
- if (selector.startsWith("@keyframes")) {
282
- const frames = [];
283
- for (const [pct, frameStyles] of Object.entries(styleObj)) if (typeof frameStyles === "object" && frameStyles !== null) {
284
- const decls = [];
285
- for (const [prop, raw] of Object.entries(frameStyles)) {
286
- const cfg = propConfig[prop];
287
- const cssProps = cfg?.properties?.length ? cfg.properties : cfg ? [cfg.property] : [prop];
288
- const resolved = resolveValue(raw, cfg, flat, variableMap, transforms);
289
- for (const cssProp of cssProps) decls.push(` ${camelToKebab(cssProp)}: ${resolved};`);
290
- }
291
- frames.push(` ${pct} {\n${decls.join("\n")}\n }`);
292
- }
293
- rules.push(`${selector} {\n${frames.join("\n")}\n}`);
294
- continue;
295
- }
296
- const decls = [];
297
- for (const [prop, raw] of Object.entries(styleObj)) {
298
- const config = propConfig[prop];
299
- const cssProps = config?.properties?.length ? config.properties : config ? [config.property] : [prop];
300
- const resolved = resolveValue(raw, config, flat, variableMap, transforms);
301
- for (const cssProp of cssProps) decls.push(` ${camelToKebab(cssProp)}: ${resolved};`);
302
- }
303
- if (decls.length) rules.push(`${selector} {\n${decls.join("\n")}\n}`);
304
- }
305
- return rules.join("\n\n");
306
- }
1076
+ //#region pipeline/watch-keys.ts
307
1077
  /**
308
- * Resolve global style blocks into CSS strings.
1078
+ * Membership keys for a filesystem path in the system-dependency watch set.
309
1079
  *
310
- * Takes the global style map (block name -> selectors -> props -> values),
311
- * resolves prop shorthand, scale lookups, transforms, and token aliases,
312
- * and returns a map of block name -> resolved CSS string.
313
- */
314
- function resolveGlobalStyles(globalStyles, propConfig, flat, variableMap, transforms) {
315
- const result = {};
316
- for (const [name, block] of Object.entries(globalStyles)) {
317
- const resolved = resolveBlock(block, propConfig, flat, variableMap, transforms);
318
- if (resolved) result[name] = resolved;
319
- }
320
- return result;
1080
+ * The loader reports symlink-resolved canonical paths; watcher events carry
1081
+ * lexical absolute paths that may traverse symlinks (bun workspace links) and
1082
+ * — for deletions — may no longer exist, so `realpathSync` on the event path
1083
+ * alone cannot produce the canonical form. Both insertion and lookup go
1084
+ * through this one normalizer: the lexical form plus a canonical form
1085
+ * reconstructed through the nearest existing ancestor.
1086
+ */
1087
+ function toWatchKeys(input) {
1088
+ const lexical = (0, node_path.normalize)(input);
1089
+ const canonical = realpathThroughNearestExistingAncestor(lexical);
1090
+ return canonical === lexical ? [lexical] : [lexical, canonical];
321
1091
  }
322
- //#endregion
323
- //#region pipeline/resolve-transforms.ts
324
1092
  /**
325
- * Resolve __TRANSFORM__ placeholders in extracted CSS.
1093
+ * Canonicalize a path that may not exist: walk up to the nearest existing
1094
+ * ancestor, realpath it, and re-append the missing suffix. Handles deleted
1095
+ * files under still-existing (possibly symlinked) directories and atomic
1096
+ * unlink/rename saves. Falls back to the input when no ancestor resolves.
326
1097
  *
327
- * Pattern: `__TRANSFORM__name__rawValue__` -> transform function result.
328
- * The Rust crate emits these placeholders for CSS values that require
329
- * JS transform functions (e.g., size, grid) which can't be evaluated in Rust.
330
- */
331
- function resolveTransformPlaceholders(css, transforms) {
332
- return css.replace(/__TRANSFORM__(\w+)__(.+?)__/g, (_, name, rawValue) => {
333
- const fn = transforms[name];
334
- if (!fn) return rawValue;
335
- const result = fn(rawValue !== "" && !Number.isNaN(Number(rawValue)) ? Number(rawValue) : rawValue);
336
- return typeof result === "object" ? JSON.stringify(result) : String(result);
337
- });
338
- }
339
- //#endregion
340
- //#region pipeline/unit-fallback.ts
341
- /**
342
- * Append `px` to bare numeric values in CSS declarations for properties
343
- * that expect length units. Unitless properties are preserved as-is.
344
- * Numbers inside CSS function calls (cubic-bezier, rgb, calc, etc.) are skipped.
1098
+ * Known limit: a symlink retargeted AFTER the system load canonicalizes to
1099
+ * the new target, which cannot match the recorded (old-target) dependency —
1100
+ * the membership test then fails open (no reset), identical to not having
1101
+ * this reconstruction at all. Retarget sensitivity is a resolution-input
1102
+ * concern, deliberately out of scope for the watch set.
345
1103
  */
346
- function applyUnitFallback(css) {
347
- return css.replace(/([a-z-]+)\s*:\s*([^;{}]+);/g, (match, prop, value) => {
348
- if (_animus_ui_properties.UNITLESS_PROPERTIES.has(prop)) return match;
349
- let depth = 0;
350
- let fixed = "";
351
- let i = 0;
352
- while (i < value.length) if (value[i] === "(") {
353
- depth++;
354
- fixed += value[i];
355
- i++;
356
- } else if (value[i] === ")") {
357
- depth--;
358
- fixed += value[i];
359
- i++;
360
- } else if (depth > 0) {
361
- fixed += value[i];
362
- i++;
363
- } else {
364
- const numMatch = value.slice(i).match(/^(-?\d+\.?\d*)/);
365
- if (numMatch) {
366
- const num = numMatch[1];
367
- const after = value[i + num.length];
368
- if (after && /[a-z%]/i.test(after)) fixed += num;
369
- else fixed += num + "px";
370
- i += num.length;
371
- } else {
372
- fixed += value[i];
373
- i++;
374
- }
375
- }
376
- return fixed !== value ? `${prop}:${fixed};` : match;
377
- });
1104
+ function realpathThroughNearestExistingAncestor(path) {
1105
+ try {
1106
+ return (0, node_fs.realpathSync)(path);
1107
+ } catch {}
1108
+ const suffix = [];
1109
+ let current = path;
1110
+ for (;;) {
1111
+ const parent = (0, node_path.dirname)(current);
1112
+ if (parent === current) return path;
1113
+ suffix.unshift((0, node_path.basename)(current));
1114
+ current = parent;
1115
+ try {
1116
+ return (0, node_path.join)((0, node_fs.realpathSync)(current), ...suffix);
1117
+ } catch {}
1118
+ }
378
1119
  }
379
1120
  //#endregion
380
1121
  exports.ANIMUS_LAYERS = ANIMUS_LAYERS;
381
1122
  exports.DEFAULT_EXTENSIONS = DEFAULT_EXTENSIONS;
1123
+ exports.RETIRED_ENGINE_MESSAGE = RETIRED_ENGINE_MESSAGE;
382
1124
  exports.applyPrefix = applyPrefix;
383
1125
  exports.applyUnitFallback = applyUnitFallback;
384
1126
  exports.assembleStylesheet = assembleStylesheet;
1127
+ exports.assertNoRetiredEngineSelection = assertNoRetiredEngineSelection;
1128
+ exports.buildAnalysisInputs = buildAnalysisInputs;
1129
+ exports.buildAnalyzeProjectArgs = buildAnalyzeProjectArgs;
1130
+ exports.buildDynamicPropConfig = buildDynamicPropConfig;
1131
+ exports.buildPathAliasesJson = buildPathAliasesJson;
1132
+ exports.buildSystemPropsModule = buildSystemPropsModule;
385
1133
  exports.camelToKebab = camelToKebab;
1134
+ exports.clearEngineCache = clearEngineCache;
1135
+ exports.collectExternalPackageSources = collectExternalPackageSources;
1136
+ exports.contentHash = contentHash;
1137
+ exports.createV2EngineApi = createV2EngineApi;
1138
+ exports.discoverFiles = discoverFiles;
386
1139
  exports.extractSystemFilePackages = extractSystemFilePackages;
1140
+ exports.findPackageRoot = findPackageRoot;
1141
+ exports.formatRustTimingWaterfall = formatRustTimingWaterfall;
1142
+ exports.loadSystemConfig = loadSystemConfig;
1143
+ exports.postProcessCss = postProcessCss;
387
1144
  exports.preprocessMdx = preprocessMdx;
388
- exports.resolveGlobalStyles = resolveGlobalStyles;
389
- exports.resolveTokenAliases = resolveTokenAliases;
390
- exports.resolveTransformPlaceholders = resolveTransformPlaceholders;
391
- exports.resolveValue = resolveValue;
1145
+ exports.readTsconfigAliasPairs = readTsconfigAliasPairs;
1146
+ exports.resolveLightningTargets = resolveLightningTargets;
1147
+ exports.runProjectAnalysis = runProjectAnalysis;
1148
+ exports.serializeStaticCss = serializeStaticCss;
392
1149
  exports.stripLeadingLayerDeclaration = stripLeadingLayerDeclaration;
1150
+ exports.surfaceManifestDiagnostics = surfaceManifestDiagnostics;
1151
+ exports.toWatchKeys = toWatchKeys;
1152
+ exports.unresolvableIncludesMessage = unresolvableIncludesMessage;
393
1153
  exports.validateLayerOrder = validateLayerOrder;