@animus-ui/extract 0.1.0-next.v7 → 0.1.1-beta.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,393 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let fs = require("fs");
3
+ let _animus_ui_properties = require("@animus-ui/properties");
4
+ //#region pipeline/assemble-stylesheet.ts
5
+ /**
6
+ * The 7 Animus cascade layers. Always prefixed with `anm-` to avoid
7
+ * collision with other frameworks' layer names (e.g., Tailwind's `base`).
8
+ */
9
+ const ANIMUS_LAYERS = [
10
+ "anm-global",
11
+ "anm-base",
12
+ "anm-variants",
13
+ "anm-compounds",
14
+ "anm-states",
15
+ "anm-system",
16
+ "anm-custom"
17
+ ];
18
+ /**
19
+ * Build the `@layer` declaration line.
20
+ *
21
+ * When `isCustom` is false (default layers), uses ANIMUS_LAYERS directly.
22
+ * When `isCustom` is true, passes names through as-is — the consumer already
23
+ * wrote the final names including the `anm-` prefixed entries.
24
+ */
25
+ function buildLayerDeclaration(layers, isCustom) {
26
+ return `@layer ${(isCustom ? layers : [...ANIMUS_LAYERS]).join(", ")};\n`;
27
+ }
28
+ /**
29
+ * Validate that a consumer `layers` array contains all 7 Animus layers
30
+ * in the correct relative order. Consumer layers may be interleaved.
31
+ *
32
+ * @throws Error with descriptive message on violation
33
+ */
34
+ function validateLayerOrder(layers) {
35
+ const expected = [...ANIMUS_LAYERS];
36
+ let cursor = 0;
37
+ for (const layer of layers) if (cursor < expected.length && layer === expected[cursor]) cursor++;
38
+ if (cursor < expected.length) {
39
+ const missing = expected.slice(cursor);
40
+ const found = expected.slice(0, cursor);
41
+ if (!expected.every((l) => layers.includes(l))) {
42
+ const absent = expected.filter((l) => !layers.includes(l));
43
+ throw new Error(`[animus-extract] Custom layers missing required layers: ${absent.join(", ")}. All 7 Animus layers must be present: ${expected.join(", ")}`);
44
+ }
45
+ throw new Error(`[animus-extract] Custom layers have wrong order. Found ${found.join(", ")} but then expected ${missing[0]}. Required order: (${expected.join(" < ")}). You may interleave custom layers but must preserve this subsequence.`);
46
+ }
47
+ }
48
+ /**
49
+ * Strip a leading `@layer ...;` declaration line from CSS if present.
50
+ * The Rust crate embeds this in prod-mode output; we strip it so the
51
+ * shared assembler controls placement.
52
+ */
53
+ function stripLeadingLayerDeclaration(css) {
54
+ return css.replace(/^@layer\s+[^;{]+;\s*\n?/, "");
55
+ }
56
+ function assembleStylesheet(options) {
57
+ const hasCustomLayers = !!options.layers;
58
+ const layers = options.layers ?? ANIMUS_LAYERS;
59
+ if (options.layers) validateLayerOrder(options.layers);
60
+ const declaration = buildLayerDeclaration(layers, hasCustomLayers);
61
+ const variables = options.variableCss || "";
62
+ const componentCss = options.componentCss ? stripLeadingLayerDeclaration(options.componentCss) : "";
63
+ const body = [options.globalCss || "", componentCss].filter(Boolean).join("\n");
64
+ if (options.split) return {
65
+ declaration,
66
+ variables,
67
+ body
68
+ };
69
+ return [
70
+ declaration,
71
+ variables,
72
+ body
73
+ ].filter(Boolean).join("\n");
74
+ }
75
+ //#endregion
76
+ //#region pipeline/discover-packages.ts
77
+ /**
78
+ * Extract external DS package names from `includes` declarations in the system file.
79
+ *
80
+ * Supports two forms:
81
+ * - Primary (1.0+): `createSystem({ includes: [identifier, ...] })` constructor arg
82
+ * - Legacy: `.includes([identifier, ...])` chain method (RC migration fallback)
83
+ *
84
+ * 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.
87
+ *
88
+ * Falls back to empty array if no `includes` declaration is found.
89
+ */
90
+ function extractSystemFilePackages(systemFilePath) {
91
+ let source;
92
+ try {
93
+ source = (0, fs.readFileSync)(systemFilePath, "utf-8");
94
+ } catch {
95
+ return [];
96
+ }
97
+ const identifiers = /* @__PURE__ */ new Set();
98
+ const constructorRegex = /createSystem\s*\(\s*\{[^}]*?\bincludes\s*:\s*\[([^\]]*)\]/gs;
99
+ const chainRegex = /\.includes\s*\(\s*\[([^\]]*)\]\s*\)/gs;
100
+ const collectIdentifiers = (regex) => {
101
+ let match;
102
+ while ((match = regex.exec(source)) !== null) {
103
+ const inner = match[1];
104
+ for (const token of inner.split(",")) {
105
+ const id = token.trim();
106
+ if (id && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(id)) identifiers.add(id);
107
+ }
108
+ }
109
+ };
110
+ collectIdentifiers(constructorRegex);
111
+ collectIdentifiers(chainRegex);
112
+ if (identifiers.size === 0) return [];
113
+ const importMap = /* @__PURE__ */ new Map();
114
+ const importRegex = /^\s*import\s+(?:([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,\s*)?(?:\{([^}]*)\}|([a-zA-Z_$][a-zA-Z0-9_$]*))\s+from\s+['"]([^'"]+)['"]/gm;
115
+ let importMatch;
116
+ while ((importMatch = importRegex.exec(source)) !== null) {
117
+ const [, comboDefault, namedImports, defaultImport, specifier] = importMatch;
118
+ if (comboDefault) importMap.set(comboDefault, specifier);
119
+ if (defaultImport) importMap.set(defaultImport, specifier);
120
+ if (namedImports) for (const binding of namedImports.split(",")) {
121
+ const parts = binding.trim().split(/\s+as\s+/);
122
+ const localName = (parts[1] || parts[0]).trim();
123
+ if (localName) importMap.set(localName, specifier);
124
+ }
125
+ }
126
+ const packages = /* @__PURE__ */ new Set();
127
+ for (const id of identifiers) {
128
+ const specifier = importMap.get(id);
129
+ if (specifier && !specifier.startsWith(".")) {
130
+ const pkgName = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
131
+ packages.add(pkgName);
132
+ }
133
+ }
134
+ return Array.from(packages);
135
+ }
136
+ //#endregion
137
+ //#region pipeline/mdx-preprocessor.ts
138
+ /**
139
+ * MDX source preprocessor for the extraction pipeline.
140
+ *
141
+ * MDX files are first-class consumers of ds-built components but are not
142
+ * directly parseable by OXC (which the scanner uses for .tsx/.jsx). This
143
+ * module compiles MDX sources to scanner-consumable JSX using @mdx-js/mdx.
144
+ *
145
+ * @mdx-js/mdx is declared as `peerDependenciesMeta.optional` on the
146
+ * plugin packages that import this module. The dynamic import with
147
+ * .catch() ensures non-MDX consumers (who configure `extensions` to
148
+ * exclude .mdx) never trigger the resolution — zero install-footprint
149
+ * cost for them.
150
+ *
151
+ * The `DEFAULT_EXTENSIONS` constant is the shared source of truth for
152
+ * both `@animus-ui/vite-plugin` and `@animus-ui/next-plugin`. Each plugin
153
+ * imports it directly; independent redeclaration of default extensions
154
+ * is considered a regression.
155
+ */
156
+ const DEFAULT_EXTENSIONS = [
157
+ ".ts",
158
+ ".tsx",
159
+ ".js",
160
+ ".jsx",
161
+ ".mdx"
162
+ ];
163
+ /**
164
+ * Preprocess an MDX source string into scanner-consumable JSX.
165
+ *
166
+ * - Returns `{ kind: 'ok', source }` with JSX-compiled output on success.
167
+ * - Returns `{ kind: 'missing-dep' }` if @mdx-js/mdx is not resolvable
168
+ * (consumer needs to install it).
169
+ * - Returns `{ kind: 'error', error }` on compile failure (e.g. malformed
170
+ * MDX syntax). Plugins SHALL warn + skip affected files; the build
171
+ * continues with remaining files.
172
+ */
173
+ async function preprocessMdx(source, filename) {
174
+ const mdxMod = await import("@mdx-js/mdx").catch(() => null);
175
+ if (mdxMod === null) return { kind: "missing-dep" };
176
+ try {
177
+ const vfile = await mdxMod.compile(source, {
178
+ outputFormat: "program",
179
+ development: false,
180
+ jsx: true
181
+ });
182
+ return {
183
+ kind: "ok",
184
+ source: `/* @mdx-source: ${filename} */\n${String(vfile)}`
185
+ };
186
+ } catch (err) {
187
+ return {
188
+ kind: "error",
189
+ error: err instanceof Error ? err.message : String(err)
190
+ };
191
+ }
192
+ }
193
+ //#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
+ //#region pipeline/utils.ts
232
+ /** Convert camelCase CSS property names to kebab-case. */
233
+ function camelToKebab(str) {
234
+ return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
235
+ }
236
+ //#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
+ }
307
+ /**
308
+ * Resolve global style blocks into CSS strings.
309
+ *
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;
321
+ }
322
+ //#endregion
323
+ //#region pipeline/resolve-transforms.ts
324
+ /**
325
+ * Resolve __TRANSFORM__ placeholders in extracted CSS.
326
+ *
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.
345
+ */
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
+ });
378
+ }
379
+ //#endregion
380
+ exports.ANIMUS_LAYERS = ANIMUS_LAYERS;
381
+ exports.DEFAULT_EXTENSIONS = DEFAULT_EXTENSIONS;
382
+ exports.applyPrefix = applyPrefix;
383
+ exports.applyUnitFallback = applyUnitFallback;
384
+ exports.assembleStylesheet = assembleStylesheet;
385
+ exports.camelToKebab = camelToKebab;
386
+ exports.extractSystemFilePackages = extractSystemFilePackages;
387
+ exports.preprocessMdx = preprocessMdx;
388
+ exports.resolveGlobalStyles = resolveGlobalStyles;
389
+ exports.resolveTokenAliases = resolveTokenAliases;
390
+ exports.resolveTransformPlaceholders = resolveTransformPlaceholders;
391
+ exports.resolveValue = resolveValue;
392
+ exports.stripLeadingLayerDeclaration = stripLeadingLayerDeclaration;
393
+ exports.validateLayerOrder = validateLayerOrder;
@@ -0,0 +1,11 @@
1
+ export type { AssembleStylesheetOptions, AssembleStylesheetParts, } from './assemble-stylesheet';
2
+ export { ANIMUS_LAYERS, assembleStylesheet, stripLeadingLayerDeclaration, validateLayerOrder, } from './assemble-stylesheet';
3
+ export { extractSystemFilePackages } from './discover-packages';
4
+ export type { DefaultExtension, PreprocessMdxResult } from './mdx-preprocessor';
5
+ export { DEFAULT_EXTENSIONS, preprocessMdx } from './mdx-preprocessor';
6
+ export { applyPrefix } from './prefix';
7
+ export { resolveGlobalStyles, resolveTokenAliases, resolveValue, } from './resolve-global-styles';
8
+ export { resolveTransformPlaceholders } from './resolve-transforms';
9
+ export { applyUnitFallback } from './unit-fallback';
10
+ export { camelToKebab } from './utils';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../pipeline/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,4BAA4B,EAC5B,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,YAAY,GACb,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"}