@animus-ui/extract 0.1.1-beta.2 → 0.1.2

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 +94 -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 +927 -179
  21. package/dist/index.d.ts +26 -3
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.mjs +888 -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,8 +422,11 @@ 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
  */
@@ -123,10 +466,13 @@ function extractSystemFilePackages(systemFilePath) {
123
466
  if (localName) importMap.set(localName, specifier);
124
467
  }
125
468
  }
469
+ const systemFileDir = (0, path.dirname)(systemFilePath);
126
470
  const packages = /* @__PURE__ */ new Set();
127
471
  for (const id of identifiers) {
128
472
  const specifier = importMap.get(id);
129
- if (specifier && !specifier.startsWith(".")) {
473
+ if (!specifier) continue;
474
+ if (specifier.startsWith(".")) packages.add((0, path.resolve)(systemFileDir, specifier));
475
+ else {
130
476
  const pkgName = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
131
477
  packages.add(pkgName);
132
478
  }
@@ -134,6 +480,525 @@ function extractSystemFilePackages(systemFilePath) {
134
480
  return Array.from(packages);
135
481
  }
136
482
  //#endregion
483
+ //#region pipeline/path-aliases.ts
484
+ /**
485
+ * Normalize alias pairs: strip a `rootDir` prefix from targets, classify
486
+ * exact (file) vs prefix (directory) matches, force trailing slashes on
487
+ * prefix entries, and sort longest pattern first for correct matching
488
+ * priority. Returns null when no entries survive.
489
+ */
490
+ function buildPathAliasesJson(pairs, rootDir) {
491
+ const entries = [];
492
+ for (const { pattern, target, kind } of pairs) {
493
+ const replacement = target.startsWith(rootDir) ? target.slice(rootDir.length + 1) : target;
494
+ if (kind ? kind === "exact" : /\.\w+$/.test(replacement)) entries.push({
495
+ pattern,
496
+ replacement,
497
+ type: "exact"
498
+ });
499
+ else entries.push({
500
+ pattern: pattern.endsWith("/") ? pattern : pattern + "/",
501
+ replacement: replacement.endsWith("/") ? replacement : replacement + "/",
502
+ type: "prefix"
503
+ });
504
+ }
505
+ entries.sort((a, b) => b.pattern.length - a.pattern.length);
506
+ if (entries.length === 0) return null;
507
+ return {
508
+ json: JSON.stringify({ aliases: entries }),
509
+ count: entries.length
510
+ };
511
+ }
512
+ //#endregion
513
+ //#region pipeline/post-process-css.ts
514
+ /**
515
+ * CSS post-processing (spec: css-post-processing) — Lightning CSS
516
+ * autoprefixing, syntax lowering, and optional minification against
517
+ * browserslist targets. The single implementation consumed by both the
518
+ * Vite plugin (virtual-module serve time) and the Next.js plugin
519
+ * (ExtractionSession artifact emission).
520
+ */
521
+ /**
522
+ * Resolve browser targets for Lightning CSS.
523
+ * Priority: explicit config → project browserslist → 'defaults' fallback.
524
+ */
525
+ function resolveLightningTargets(explicitTargets, rootDir) {
526
+ let queries;
527
+ if (explicitTargets) queries = Array.isArray(explicitTargets) ? explicitTargets : [explicitTargets];
528
+ else {
529
+ const detected = (0, browserslist.default)(void 0, { path: rootDir });
530
+ queries = detected.length > 0 ? detected : (0, browserslist.default)("defaults");
531
+ }
532
+ return (0, lightningcss.browserslistToTargets)(Array.isArray(queries) && typeof queries[0] === "string" && queries[0].includes(" ") ? (0, browserslist.default)(queries) : queries);
533
+ }
534
+ /**
535
+ * Post-process CSS with Lightning CSS: autoprefixing + optional minification.
536
+ * On failure, returns the original CSS and logs a warning.
537
+ */
538
+ function postProcessCss(css, opts) {
539
+ if (!css) return css;
540
+ try {
541
+ return (0, lightningcss.transform)({
542
+ filename: "animus-extracted.css",
543
+ code: Buffer.from(css),
544
+ minify: opts.minify,
545
+ targets: opts.targets
546
+ }).code.toString();
547
+ } catch (e) {
548
+ const msg = e instanceof Error ? e.message : String(e);
549
+ (opts.warnFn ?? console.warn)(`[animus] Lightning CSS post-processing failed: ${msg}`);
550
+ return css;
551
+ }
552
+ }
553
+ //#endregion
554
+ //#region pipeline/manifest-diagnostics.ts
555
+ /**
556
+ * Surface extraction-manifest diagnostics through a plugin's warn channel.
557
+ *
558
+ * Single authoritative copy for both extraction plugins. Surfaces `bail`
559
+ * (component not extracted), `skip` (component skipped), and `warn` kinds;
560
+ * unknown kinds stay silent.
561
+ */
562
+ function surfaceManifestDiagnostics(manifest, warn) {
563
+ for (const diagnostic of manifest.diagnostics ?? []) if (diagnostic.kind === "bail") warn(`⚠ ${diagnostic.component} not extracted: ${diagnostic.message}`);
564
+ else if (diagnostic.kind === "skip") warn(`⚠ ${diagnostic.component}: skipped ${diagnostic.message}`);
565
+ else if (diagnostic.kind === "warn") warn(`⚠ ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`);
566
+ }
567
+ //#endregion
568
+ //#region pipeline/unit-fallback.ts
569
+ /**
570
+ * Append `px` to bare numeric values in CSS declarations for properties
571
+ * that expect length units. Unitless properties are preserved as-is.
572
+ * Numbers inside CSS function calls (cubic-bezier, rgb, calc, etc.) are skipped.
573
+ */
574
+ function applyUnitFallback(css) {
575
+ return css.replace(/([a-z-]+)\s*:\s*([^;{}]+);/g, (match, prop, value) => {
576
+ if (_animus_ui_properties.UNITLESS_PROPERTIES.has(prop)) return match;
577
+ let depth = 0;
578
+ let fixed = "";
579
+ let i = 0;
580
+ while (i < value.length) if (value[i] === "(") {
581
+ depth++;
582
+ fixed += value[i];
583
+ i++;
584
+ } else if (value[i] === ")") {
585
+ depth--;
586
+ fixed += value[i];
587
+ i++;
588
+ } else if (depth > 0) {
589
+ fixed += value[i];
590
+ i++;
591
+ } else {
592
+ const numMatch = value.slice(i).match(/^(-?\d+\.?\d*)/);
593
+ if (numMatch) {
594
+ const num = numMatch[1];
595
+ const after = value[i + num.length];
596
+ if (after && /[a-z%]/i.test(after)) fixed += num;
597
+ else fixed += num + "px";
598
+ i += num.length;
599
+ } else {
600
+ fixed += value[i];
601
+ i++;
602
+ }
603
+ }
604
+ return fixed !== value ? `${prop}:${fixed};` : match;
605
+ });
606
+ }
607
+ //#endregion
608
+ //#region pipeline/run-analysis.ts
609
+ /**
610
+ * Build the named `analyzeProject` input set from analysis options. Also
611
+ * the persistence shape for `.animus/analysis-inputs.json` — an isolated
612
+ * process can replay the analysis from exactly this object
613
+ * (spec: next-turbopack-integration).
614
+ */
615
+ function buildAnalysisInputs(opts) {
616
+ return {
617
+ filesJson: JSON.stringify(opts.fileEntries),
618
+ scalesJson: opts.system.scalesJson,
619
+ variableMapJson: opts.system.variableMapJson,
620
+ contextualVarsJson: opts.system.contextualVarsJson,
621
+ propConfigJson: opts.system.propConfigJson,
622
+ groupRegistryJson: opts.system.groupRegistryJson,
623
+ packageResolutionJson: JSON.stringify(opts.packageMap),
624
+ devMode: opts.devMode,
625
+ emitterConfigJson: JSON.stringify({
626
+ runtime_import: opts.emitter.runtimeImport,
627
+ css_module_id: opts.emitter.cssModuleId,
628
+ ...opts.emitter.systemPropsModuleId ? { system_props_module_id: opts.emitter.systemPropsModuleId } : {}
629
+ }),
630
+ selectorAliasesJson: opts.system.selectorAliasesJson,
631
+ globalStyleBlocksJson: opts.system.globalStyleBlocksJson,
632
+ pathAliasesJson: opts.pathAliasesJson,
633
+ keyframesJson: opts.system.keyframesJson,
634
+ staticCssJson: opts.staticCssJson ?? null,
635
+ conditionAliasesJson: opts.system.conditionAliasesJson ?? null
636
+ };
637
+ }
638
+ /**
639
+ * The one analysis invocation both plugins share: build the emitter
640
+ * config, serialize inputs, call the NAPI `analyzeProject`, parse the
641
+ * manifest, surface its diagnostics, and resolve the CSS outputs.
642
+ *
643
+ * Error handling stays at the call site (strict-mode throw vs warn).
644
+ */
645
+ function runProjectAnalysis(engineApi, opts) {
646
+ const { analyzeProject } = engineApi();
647
+ let t = performance.now();
648
+ const inputs = buildAnalysisInputs(opts);
649
+ const serializeMs = Math.round(performance.now() - t);
650
+ t = performance.now();
651
+ const manifestJson = analyzeProject(...buildAnalyzeProjectArgs(inputs));
652
+ const extractMs = Math.round(performance.now() - t);
653
+ t = performance.now();
654
+ const manifest = JSON.parse(manifestJson);
655
+ surfaceManifestDiagnostics(manifest, opts.warn);
656
+ const parseMs = Math.round(performance.now() - t);
657
+ return {
658
+ manifest,
659
+ manifestJson,
660
+ globalCss: manifest?.sheets?.global || "",
661
+ componentCss: applyUnitFallback(manifest?.css || ""),
662
+ inputs,
663
+ timings: {
664
+ serializeMs,
665
+ extractMs,
666
+ parseMs
667
+ }
668
+ };
669
+ }
670
+ /**
671
+ * Clear the engine's per-file analysis cache so stale results from a prior
672
+ * build never bleed into a fresh run. Tolerates engines without the
673
+ * capability (older builds) — the probe is benign.
674
+ */
675
+ function clearEngineCache(engineApi) {
676
+ try {
677
+ const { clearAnalysisCache } = engineApi();
678
+ clearAnalysisCache();
679
+ } catch {}
680
+ }
681
+ //#endregion
682
+ //#region pipeline/static-css.ts
683
+ /** Key-sorted stringify so semantically identical configs serialize
684
+ * identically (stable analysis-inputs hashing). */
685
+ function stableStringify(value) {
686
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
687
+ 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(",")}}`;
688
+ return JSON.stringify(value);
689
+ }
690
+ /** Serialize a staticCss option for the engine; null when absent/empty. */
691
+ function serializeStaticCss(config) {
692
+ if (!config) return null;
693
+ const json = stableStringify(config);
694
+ return json === "{}" ? null : json;
695
+ }
696
+ //#endregion
697
+ //#region pipeline/prefix.ts
698
+ /**
699
+ * Apply namespace prefix to a variable map, CSS variable declarations, and theme JSON.
700
+ *
701
+ * Variable map: `{ "colors.ember": "--color-ember" }` -> `{ "colors.ember": "--prefix-color-ember" }`
702
+ * Variable CSS: `--color-ember: #FF2800` -> `--prefix-color-ember: #FF2800`
703
+ * `var(--color-ember)` -> `var(--prefix-color-ember)`
704
+ * Theme JSON: `{ "colors.ember": "var(--color-ember)" }` -> `{ "colors.ember": "var(--prefix-color-ember)" }`
705
+ */
706
+ function applyPrefix(prefix, variableMapJson, variableCss, themeJson, contextualVarsJson) {
707
+ if (!prefix) return {
708
+ variableMapJson,
709
+ variableCss,
710
+ themeJson,
711
+ contextualVarsJson
712
+ };
713
+ const varRefRe = /var\(--([a-zA-Z][\w-]*)\)/g;
714
+ const map = JSON.parse(variableMapJson);
715
+ const prefixed = {};
716
+ for (const [key, varName] of Object.entries(map)) prefixed[key] = varName.startsWith("--") ? `--${prefix}-${varName.slice(2)}` : varName;
717
+ let css = variableCss;
718
+ css = css.replace(/--([a-zA-Z][\w-]*)\s*:/g, `--${prefix}-$1:`);
719
+ css = css.replace(varRefRe, `var(--${prefix}-$1)`);
720
+ const result = {
721
+ variableMapJson: JSON.stringify(prefixed),
722
+ variableCss: css
723
+ };
724
+ if (themeJson) result.themeJson = themeJson.replace(varRefRe, `var(--${prefix}-$1)`);
725
+ if (contextualVarsJson) {
726
+ const ctxVars = JSON.parse(contextualVarsJson);
727
+ const prefixedCtx = {};
728
+ for (const [scale, names] of Object.entries(ctxVars)) prefixedCtx[scale] = names.map((name) => `${prefix}-${name}`);
729
+ result.contextualVarsJson = JSON.stringify(prefixedCtx);
730
+ }
731
+ return result;
732
+ }
733
+ //#endregion
734
+ //#region pipeline/system-config.ts
735
+ /**
736
+ * Load a SystemInstance via the engine's `loadSystemModule` and normalize
737
+ * it into a SystemConfig. When `prefix` is set, CSS variable names in the
738
+ * variable map/css (and theme + contextual vars when affected) are
739
+ * namespaced via the shared `applyPrefix`.
740
+ *
741
+ * Error handling stays at the call site — the Vite plugin warns (or throws
742
+ * in strict mode) and keeps its previous config, the Next plugin lets the
743
+ * failure propagate.
744
+ */
745
+ function loadSystemConfig(engineApi, opts) {
746
+ const { loadSystemModule } = engineApi();
747
+ const config = loadSystemModule(opts.systemPath, opts.rootDir);
748
+ let scalesJson = config.scalesJson;
749
+ let variableMapJson = config.variableMapJson;
750
+ let variableCss = config.variableCss;
751
+ let contextualVarsJson = config.contextualVarsJson || null;
752
+ if (opts.prefix) {
753
+ const prefixed = applyPrefix(opts.prefix, variableMapJson, variableCss, scalesJson, contextualVarsJson || void 0);
754
+ variableMapJson = prefixed.variableMapJson;
755
+ variableCss = prefixed.variableCss;
756
+ if (prefixed.themeJson) scalesJson = prefixed.themeJson;
757
+ if (prefixed.contextualVarsJson) contextualVarsJson = prefixed.contextualVarsJson;
758
+ }
759
+ return {
760
+ propConfigJson: config.propConfig,
761
+ groupRegistryJson: config.groupRegistry,
762
+ scalesJson,
763
+ variableMapJson,
764
+ variableCss,
765
+ contextualVarsJson,
766
+ selectorAliasesJson: config.selectorAliases || null,
767
+ conditionAliasesJson: config.conditionAliases || null,
768
+ globalStyleBlocksJson: config.globalStyleBlocks || null,
769
+ keyframesJson: config.keyframesBlocks || null,
770
+ dependencies: config.dependencies ?? []
771
+ };
772
+ }
773
+ //#endregion
774
+ //#region pipeline/dynamic-prop-config.ts
775
+ function buildDynamicPropConfig(dynamicProps) {
776
+ const configEntries = {};
777
+ for (const [propName, meta] of Object.entries(dynamicProps)) configEntries[propName] = {
778
+ varName: meta.var_name,
779
+ slotClass: meta.slot_class,
780
+ ...meta.transform_name ? { transformName: meta.transform_name } : {},
781
+ ...meta.scale_values && Object.keys(meta.scale_values).length > 0 ? { scaleValues: meta.scale_values } : {}
782
+ };
783
+ return configEntries;
784
+ }
785
+ //#endregion
786
+ //#region pipeline/system-props-module.ts
787
+ /**
788
+ * Build the source of the runtime system-props module — served as
789
+ * `virtual:animus/system-props` by the Vite plugin and written to
790
+ * `.animus/system-props.js` by the Next plugin. One generator so the two
791
+ * runtimes can never drift.
792
+ *
793
+ * Runtime transform functions for dynamic props are not yet supported
794
+ * (transforms are resolved at extraction time via boa_engine in Rust), so
795
+ * `transforms` defaults to an empty object literal.
796
+ */
797
+ function buildSystemPropsModule(opts) {
798
+ const dynamicPropConfig = buildDynamicPropConfig(opts.dynamicProps);
799
+ return `export const systemPropMap = ${opts.systemPropMapJson};\nexport const systemPropGroups = ${opts.groupRegistryJson};\nexport const dynamicPropConfig = ${JSON.stringify(dynamicPropConfig)};\nexport const transforms = ${opts.transformsSource ?? "{}"};\n`;
800
+ }
801
+ //#endregion
802
+ //#region pipeline/timing-waterfall.ts
803
+ /**
804
+ * Rust-side timing phases as reported in `manifest.timing`, in waterfall
805
+ * order, with their display labels. Mirrors the timing struct in the
806
+ * extract-v2 crate — a renamed or added Rust phase is updated here once,
807
+ * not per plugin.
808
+ */
809
+ const RUST_TIMING_PHASES = [
810
+ ["parseAndWalk", "parse+walk"],
811
+ ["importResolution", "imports"],
812
+ ["extensionProvenance", "provenance"],
813
+ ["topologicalSort", "topo-sort"],
814
+ ["chainEvaluation", "chains"],
815
+ ["jsxScanning", "jsx-scan"],
816
+ ["systemPropAggregation", "sys-props"],
817
+ ["usageLedger", "usage"],
818
+ ["reconciliation", "reconcile"],
819
+ ["cssGeneration", "css-gen"],
820
+ ["manifestSerialization", "serialize"]
821
+ ];
822
+ /**
823
+ * Format the Rust timing waterfall as log lines. `indent` and `labelWidth`
824
+ * let each plugin keep its established column layout; the phase table and
825
+ * the `(N files, M cached)` suffix are shared.
826
+ */
827
+ function formatRustTimingWaterfall(timing, opts) {
828
+ const lines = [];
829
+ for (const [key, label] of RUST_TIMING_PHASES) {
830
+ const ms = timing[key] ?? 0;
831
+ const pad = " ".repeat(Math.max(0, opts.labelWidth - label.length));
832
+ const extra = key === "parseAndWalk" ? ` (${timing.fileCount ?? 0} files, ${timing.cacheHits ?? 0} cached)` : "";
833
+ lines.push(`${opts.indent}${label}${pad}${String(ms).padStart(5)}ms${extra}`);
834
+ }
835
+ return lines;
836
+ }
837
+ //#endregion
838
+ //#region pipeline/tsconfig-paths.ts
839
+ /**
840
+ * tsconfig `paths` → alias pairs for the Rust `pathAliasesJson` contract
841
+ * (via `buildPathAliasesJson`). Used by drivers that cannot harvest aliases
842
+ * from a live bundler config — Turbopack exposes none.
843
+ *
844
+ * Semantics (deliberately the TypeScript subset that matters for import
845
+ * provenance): JSONC tolerated (comments + trailing commas); `extends`
846
+ * chains followed (string or array; missing parents and cycles skipped);
847
+ * the NEAREST config declaring `paths` wins wholesale (TS replaces the
848
+ * object, never merges); targets resolve against the nearest `baseUrl`
849
+ * (resolved from its declaring config's directory) or, absent one, the
850
+ * directory of the config declaring `paths`; first target per pattern.
851
+ * `x/*` patterns become prefix pairs, non-wildcard patterns exact pairs,
852
+ * bare `*` catch-alls and mid-pattern wildcards are skipped.
853
+ */
854
+ /** String-aware JSONC → JSON: strips // and block comments + trailing commas. */
855
+ function stripJsonc(text) {
856
+ let out = "";
857
+ let inString = false;
858
+ let inLine = false;
859
+ let inBlock = false;
860
+ for (let i = 0; i < text.length; i++) {
861
+ const ch = text[i];
862
+ const next = text[i + 1];
863
+ if (inLine) {
864
+ if (ch === "\n") {
865
+ inLine = false;
866
+ out += ch;
867
+ }
868
+ continue;
869
+ }
870
+ if (inBlock) {
871
+ if (ch === "*" && next === "/") {
872
+ inBlock = false;
873
+ i++;
874
+ }
875
+ continue;
876
+ }
877
+ if (inString) {
878
+ out += ch;
879
+ if (ch === "\\") {
880
+ out += next ?? "";
881
+ i++;
882
+ } else if (ch === "\"") inString = false;
883
+ continue;
884
+ }
885
+ if (ch === "\"") {
886
+ inString = true;
887
+ out += ch;
888
+ continue;
889
+ }
890
+ if (ch === "/" && next === "/") {
891
+ inLine = true;
892
+ i++;
893
+ continue;
894
+ }
895
+ if (ch === "/" && next === "*") {
896
+ inBlock = true;
897
+ i++;
898
+ continue;
899
+ }
900
+ out += ch;
901
+ }
902
+ return out.replace(/,(\s*[}\]])/g, "$1");
903
+ }
904
+ function readConfig(path$1) {
905
+ let raw;
906
+ try {
907
+ raw = (0, fs.readFileSync)(path$1, "utf-8");
908
+ } catch {
909
+ return null;
910
+ }
911
+ try {
912
+ const json = JSON.parse(stripJsonc(raw));
913
+ return {
914
+ dir: (0, path.dirname)(path$1),
915
+ compilerOptions: json.compilerOptions ?? {},
916
+ extends: json.extends
917
+ };
918
+ } catch {
919
+ return null;
920
+ }
921
+ }
922
+ function resolveExtendsTarget(specifier, fromDir) {
923
+ if (specifier.startsWith(".") || (0, path.isAbsolute)(specifier)) {
924
+ const base = (0, path.resolve)(fromDir, specifier);
925
+ for (const candidate of [
926
+ base,
927
+ `${base}.json`,
928
+ (0, path.join)(base, "tsconfig.json")
929
+ ]) if ((0, fs.existsSync)(candidate)) return candidate;
930
+ return null;
931
+ }
932
+ try {
933
+ return require.resolve(specifier.endsWith(".json") ? specifier : `${specifier}/tsconfig.json`, { paths: [fromDir] });
934
+ } catch {
935
+ try {
936
+ return require.resolve(specifier, { paths: [fromDir] });
937
+ } catch {
938
+ return null;
939
+ }
940
+ }
941
+ }
942
+ /** Child-first flattened extends chain starting at `entryPath`. */
943
+ function loadChain(entryPath) {
944
+ const chain = [];
945
+ const visited = /* @__PURE__ */ new Set();
946
+ const queue = [entryPath];
947
+ while (queue.length > 0) {
948
+ const key = (0, path.resolve)(queue.shift());
949
+ if (visited.has(key)) continue;
950
+ visited.add(key);
951
+ const node = readConfig(key);
952
+ if (!node) continue;
953
+ chain.push(node);
954
+ const parents = Array.isArray(node.extends) ? node.extends : node.extends !== void 0 ? [node.extends] : [];
955
+ for (const parent of parents) {
956
+ if (typeof parent !== "string") continue;
957
+ const resolved = resolveExtendsTarget(parent, node.dir);
958
+ if (resolved) queue.push(resolved);
959
+ }
960
+ }
961
+ return chain;
962
+ }
963
+ /**
964
+ * Read the project's tsconfig path aliases as pairs consumable by
965
+ * `buildPathAliasesJson`. Returns an empty array when no readable tsconfig
966
+ * (or no usable `paths`) exists.
967
+ */
968
+ function readTsconfigAliasPairs(rootDir) {
969
+ const chain = loadChain((0, path.join)(rootDir, "tsconfig.json"));
970
+ if (chain.length === 0) return [];
971
+ const pathsOwner = chain.find((node) => node.compilerOptions.paths && typeof node.compilerOptions.paths === "object");
972
+ if (!pathsOwner) return [];
973
+ const paths = pathsOwner.compilerOptions.paths;
974
+ const baseOwner = chain.find((node) => typeof node.compilerOptions.baseUrl === "string");
975
+ const base = baseOwner ? (0, path.resolve)(baseOwner.dir, baseOwner.compilerOptions.baseUrl) : pathsOwner.dir;
976
+ const pairs = [];
977
+ for (const [pattern, targets] of Object.entries(paths)) {
978
+ const target = Array.isArray(targets) ? targets[0] : void 0;
979
+ if (typeof target !== "string") continue;
980
+ const patternStars = pattern.split("*").length - 1;
981
+ const targetStars = target.split("*").length - 1;
982
+ if (pattern === "*" || patternStars > 1 || targetStars > 1) continue;
983
+ if (pattern.endsWith("/*")) {
984
+ if (!target.endsWith("/*")) continue;
985
+ pairs.push({
986
+ pattern: pattern.slice(0, -2),
987
+ target: (0, path.resolve)(base, target.slice(0, -2)),
988
+ kind: "prefix"
989
+ });
990
+ continue;
991
+ }
992
+ if (patternStars > 0 || targetStars > 0) continue;
993
+ pairs.push({
994
+ pattern,
995
+ target: (0, path.resolve)(base, target),
996
+ kind: "exact"
997
+ });
998
+ }
999
+ return pairs;
1000
+ }
1001
+ //#endregion
137
1002
  //#region pipeline/mdx-preprocessor.ts
138
1003
  /**
139
1004
  * MDX source preprocessor for the extraction pipeline.
@@ -191,203 +1056,86 @@ async function preprocessMdx(source, filename) {
191
1056
  }
192
1057
  }
193
1058
  //#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
1059
  //#region pipeline/utils.ts
232
1060
  /** Convert camelCase CSS property names to kebab-case. */
233
1061
  function camelToKebab(str) {
234
1062
  return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
235
1063
  }
236
1064
  //#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
- }
1065
+ //#region pipeline/watch-keys.ts
307
1066
  /**
308
- * Resolve global style blocks into CSS strings.
1067
+ * Membership keys for a filesystem path in the system-dependency watch set.
309
1068
  *
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;
1069
+ * The loader reports symlink-resolved canonical paths; watcher events carry
1070
+ * lexical absolute paths that may traverse symlinks (bun workspace links) and
1071
+ * — for deletions — may no longer exist, so `realpathSync` on the event path
1072
+ * alone cannot produce the canonical form. Both insertion and lookup go
1073
+ * through this one normalizer: the lexical form plus a canonical form
1074
+ * reconstructed through the nearest existing ancestor.
1075
+ */
1076
+ function toWatchKeys(input) {
1077
+ const lexical = (0, node_path.normalize)(input);
1078
+ const canonical = realpathThroughNearestExistingAncestor(lexical);
1079
+ return canonical === lexical ? [lexical] : [lexical, canonical];
321
1080
  }
322
- //#endregion
323
- //#region pipeline/resolve-transforms.ts
324
1081
  /**
325
- * Resolve __TRANSFORM__ placeholders in extracted CSS.
1082
+ * Canonicalize a path that may not exist: walk up to the nearest existing
1083
+ * ancestor, realpath it, and re-append the missing suffix. Handles deleted
1084
+ * files under still-existing (possibly symlinked) directories and atomic
1085
+ * unlink/rename saves. Falls back to the input when no ancestor resolves.
326
1086
  *
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.
1087
+ * Known limit: a symlink retargeted AFTER the system load canonicalizes to
1088
+ * the new target, which cannot match the recorded (old-target) dependency —
1089
+ * the membership test then fails open (no reset), identical to not having
1090
+ * this reconstruction at all. Retarget sensitivity is a resolution-input
1091
+ * concern, deliberately out of scope for the watch set.
345
1092
  */
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
- });
1093
+ function realpathThroughNearestExistingAncestor(path) {
1094
+ try {
1095
+ return (0, node_fs.realpathSync)(path);
1096
+ } catch {}
1097
+ const suffix = [];
1098
+ let current = path;
1099
+ for (;;) {
1100
+ const parent = (0, node_path.dirname)(current);
1101
+ if (parent === current) return path;
1102
+ suffix.unshift((0, node_path.basename)(current));
1103
+ current = parent;
1104
+ try {
1105
+ return (0, node_path.join)((0, node_fs.realpathSync)(current), ...suffix);
1106
+ } catch {}
1107
+ }
378
1108
  }
379
1109
  //#endregion
380
1110
  exports.ANIMUS_LAYERS = ANIMUS_LAYERS;
381
1111
  exports.DEFAULT_EXTENSIONS = DEFAULT_EXTENSIONS;
1112
+ exports.RETIRED_ENGINE_MESSAGE = RETIRED_ENGINE_MESSAGE;
382
1113
  exports.applyPrefix = applyPrefix;
383
1114
  exports.applyUnitFallback = applyUnitFallback;
384
1115
  exports.assembleStylesheet = assembleStylesheet;
1116
+ exports.assertNoRetiredEngineSelection = assertNoRetiredEngineSelection;
1117
+ exports.buildAnalysisInputs = buildAnalysisInputs;
1118
+ exports.buildAnalyzeProjectArgs = buildAnalyzeProjectArgs;
1119
+ exports.buildDynamicPropConfig = buildDynamicPropConfig;
1120
+ exports.buildPathAliasesJson = buildPathAliasesJson;
1121
+ exports.buildSystemPropsModule = buildSystemPropsModule;
385
1122
  exports.camelToKebab = camelToKebab;
1123
+ exports.clearEngineCache = clearEngineCache;
1124
+ exports.collectExternalPackageSources = collectExternalPackageSources;
1125
+ exports.contentHash = contentHash;
1126
+ exports.createV2EngineApi = createV2EngineApi;
1127
+ exports.discoverFiles = discoverFiles;
386
1128
  exports.extractSystemFilePackages = extractSystemFilePackages;
1129
+ exports.findPackageRoot = findPackageRoot;
1130
+ exports.formatRustTimingWaterfall = formatRustTimingWaterfall;
1131
+ exports.loadSystemConfig = loadSystemConfig;
1132
+ exports.postProcessCss = postProcessCss;
387
1133
  exports.preprocessMdx = preprocessMdx;
388
- exports.resolveGlobalStyles = resolveGlobalStyles;
389
- exports.resolveTokenAliases = resolveTokenAliases;
390
- exports.resolveTransformPlaceholders = resolveTransformPlaceholders;
391
- exports.resolveValue = resolveValue;
1134
+ exports.readTsconfigAliasPairs = readTsconfigAliasPairs;
1135
+ exports.resolveLightningTargets = resolveLightningTargets;
1136
+ exports.runProjectAnalysis = runProjectAnalysis;
1137
+ exports.serializeStaticCss = serializeStaticCss;
392
1138
  exports.stripLeadingLayerDeclaration = stripLeadingLayerDeclaration;
1139
+ exports.surfaceManifestDiagnostics = surfaceManifestDiagnostics;
1140
+ exports.toWatchKeys = toWatchKeys;
393
1141
  exports.validateLayerOrder = validateLayerOrder;