@animus-ui/extract 0.1.0-next.9 → 0.1.0

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/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @animus-ui/extract
2
+
3
+ Rust/NAPI static CSS extraction pipeline for Animus. Analyzes TypeScript source files using OXC, resolves builder chains against serialized system config, and emits layered CSS.
4
+
5
+ This package is consumed by [`@animus-ui/vite-plugin`](../vite-plugin) and [`@animus-ui/next-plugin`](../next-plugin). You typically don't install it directly — the bundler plugins depend on it.
6
+
7
+ ## Platforms
8
+
9
+ Pre-built binaries for:
10
+
11
+ - `darwin-arm64` (macOS Apple Silicon)
12
+ - `linux-x64-gnu` (Linux x64)
13
+ - `linux-arm64-gnu` (Linux ARM64)
14
+
15
+ ## API
16
+
17
+ ```tsx
18
+ import { analyzeProject } from '@animus-ui/extract';
19
+
20
+ const manifest = analyzeProject(
21
+ fileEntriesJson,
22
+ scalesJson,
23
+ variableMapJson,
24
+ contextualVarsJson,
25
+ propConfig,
26
+ groupRegistry,
27
+ packageResolutionJson,
28
+ devMode,
29
+ prefix
30
+ );
31
+ ```
32
+
33
+ ## License
34
+
35
+ MIT
Binary file
Binary file
Binary file
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The 7 Animus cascade layers. Always prefixed with `anm-` to avoid
3
+ * collision with other frameworks' layer names (e.g., Tailwind's `base`).
4
+ */
5
+ export declare const ANIMUS_LAYERS: readonly ['anm-global', 'anm-base', 'anm-variants', 'anm-compounds', 'anm-states', 'anm-system', 'anm-custom'];
6
+ /**
7
+ * Validate that a consumer `layers` array contains all 7 Animus layers
8
+ * in the correct relative order. Consumer layers may be interleaved.
9
+ *
10
+ * @throws Error with descriptive message on violation
11
+ */
12
+ export declare function validateLayerOrder(layers: string[]): void;
13
+ /**
14
+ * Strip a leading `@layer ...;` declaration line from CSS if present.
15
+ * The Rust crate embeds this in prod-mode output; we strip it so the
16
+ * shared assembler controls placement.
17
+ */
18
+ export declare function stripLeadingLayerDeclaration(css: string): string;
19
+ export interface AssembleStylesheetParts {
20
+ declaration: string;
21
+ variables: string;
22
+ body: string;
23
+ }
24
+ export interface AssembleStylesheetOptions {
25
+ /**
26
+ * Custom layer order. Must contain all 7 Animus `anm-*` layers as a subsequence.
27
+ * Example: `['reset', 'anm-global', 'anm-base', ..., 'anm-custom', 'overrides']`
28
+ * Names are emitted as-is — this is the actual `@layer` declaration.
29
+ */
30
+ layers?: string[];
31
+ /** Variable CSS: `:root { --color-*: ... }` + color mode selectors */
32
+ variableCss?: string;
33
+ /** Global CSS: `@layer anm-global { reset + global styles }` */
34
+ globalCss?: string;
35
+ /** Component CSS from the Rust crate (may contain embedded @layer declaration) */
36
+ componentCss?: string;
37
+ /** When true, return structured `{ declaration, variables, body }` instead of a single string. */
38
+ split?: boolean;
39
+ }
40
+ /**
41
+ * Assemble the final stylesheet in canonical order:
42
+ *
43
+ * 1. `@layer` declaration (cascade ordering)
44
+ * 2. Emitted variables (`:root`, color mode selectors)
45
+ * 3. `@layer anm-global { ... }` (reset + global styles)
46
+ * 4. `@layer anm-base/variants/compounds/states/system/custom { ... }` (components)
47
+ *
48
+ * This is the single source of truth for stylesheet assembly.
49
+ * Both Vite and Next.js plugins must use this function.
50
+ */
51
+ export declare function assembleStylesheet(options: AssembleStylesheetOptions & {
52
+ split: true;
53
+ }): AssembleStylesheetParts;
54
+ export declare function assembleStylesheet(options: AssembleStylesheetOptions & {
55
+ split?: false;
56
+ }): string;
57
+ export declare function assembleStylesheet(options: AssembleStylesheetOptions): string | AssembleStylesheetParts;
58
+ //# sourceMappingURL=assemble-stylesheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assemble-stylesheet.d.ts","sourceRoot":"","sources":["../pipeline/assemble-stylesheet.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,aAAa,YACxB,YAAY,EACZ,UAAU,EACV,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,YAAY,CACJ,CAAC;AAiBX;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CA2BzD;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,yBAAyB,GAAG;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACnD,uBAAuB,CAAC;AAC3B,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,yBAAyB,GAAG;IAAE,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GACrD,MAAM,CAAC;AACV,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,yBAAyB,GACjC,MAAM,GAAG,uBAAuB,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Extract external DS package names from `includes` declarations in the system file.
3
+ *
4
+ * Supports two forms:
5
+ * - Primary (1.0+): `createSystem({ includes: [identifier, ...] })` constructor arg
6
+ * - Legacy: `.includes([identifier, ...])` chain method (RC migration fallback)
7
+ *
8
+ * For each identifier found, traces back to its import declaration and returns
9
+ * the import specifier. Only packages explicitly declared via `includes` are treated
10
+ * as external DS dependencies.
11
+ *
12
+ * Falls back to empty array if no `includes` declaration is found.
13
+ */
14
+ export declare function extractSystemFilePackages(systemFilePath: string): string[];
15
+ //# sourceMappingURL=discover-packages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover-packages.d.ts","sourceRoot":"","sources":["../pipeline/discover-packages.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAmF1E"}
@@ -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"}
package/dist/index.mjs ADDED
@@ -0,0 +1,379 @@
1
+ import { readFileSync } from "fs";
2
+ import { UNITLESS_PROPERTIES } from "@animus-ui/properties";
3
+ //#region pipeline/assemble-stylesheet.ts
4
+ /**
5
+ * The 7 Animus cascade layers. Always prefixed with `anm-` to avoid
6
+ * collision with other frameworks' layer names (e.g., Tailwind's `base`).
7
+ */
8
+ const ANIMUS_LAYERS = [
9
+ "anm-global",
10
+ "anm-base",
11
+ "anm-variants",
12
+ "anm-compounds",
13
+ "anm-states",
14
+ "anm-system",
15
+ "anm-custom"
16
+ ];
17
+ /**
18
+ * Build the `@layer` declaration line.
19
+ *
20
+ * When `isCustom` is false (default layers), uses ANIMUS_LAYERS directly.
21
+ * When `isCustom` is true, passes names through as-is — the consumer already
22
+ * wrote the final names including the `anm-` prefixed entries.
23
+ */
24
+ function buildLayerDeclaration(layers, isCustom) {
25
+ return `@layer ${(isCustom ? layers : [...ANIMUS_LAYERS]).join(", ")};\n`;
26
+ }
27
+ /**
28
+ * Validate that a consumer `layers` array contains all 7 Animus layers
29
+ * in the correct relative order. Consumer layers may be interleaved.
30
+ *
31
+ * @throws Error with descriptive message on violation
32
+ */
33
+ function validateLayerOrder(layers) {
34
+ const expected = [...ANIMUS_LAYERS];
35
+ let cursor = 0;
36
+ for (const layer of layers) if (cursor < expected.length && layer === expected[cursor]) cursor++;
37
+ if (cursor < expected.length) {
38
+ const missing = expected.slice(cursor);
39
+ const found = expected.slice(0, cursor);
40
+ if (!expected.every((l) => layers.includes(l))) {
41
+ const absent = expected.filter((l) => !layers.includes(l));
42
+ throw new Error(`[animus-extract] Custom layers missing required layers: ${absent.join(", ")}. All 7 Animus layers must be present: ${expected.join(", ")}`);
43
+ }
44
+ 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.`);
45
+ }
46
+ }
47
+ /**
48
+ * Strip a leading `@layer ...;` declaration line from CSS if present.
49
+ * The Rust crate embeds this in prod-mode output; we strip it so the
50
+ * shared assembler controls placement.
51
+ */
52
+ function stripLeadingLayerDeclaration(css) {
53
+ return css.replace(/^@layer\s+[^;{]+;\s*\n?/, "");
54
+ }
55
+ function assembleStylesheet(options) {
56
+ const hasCustomLayers = !!options.layers;
57
+ const layers = options.layers ?? ANIMUS_LAYERS;
58
+ if (options.layers) validateLayerOrder(options.layers);
59
+ const declaration = buildLayerDeclaration(layers, hasCustomLayers);
60
+ const variables = options.variableCss || "";
61
+ const componentCss = options.componentCss ? stripLeadingLayerDeclaration(options.componentCss) : "";
62
+ const body = [options.globalCss || "", componentCss].filter(Boolean).join("\n");
63
+ if (options.split) return {
64
+ declaration,
65
+ variables,
66
+ body
67
+ };
68
+ return [
69
+ declaration,
70
+ variables,
71
+ body
72
+ ].filter(Boolean).join("\n");
73
+ }
74
+ //#endregion
75
+ //#region pipeline/discover-packages.ts
76
+ /**
77
+ * Extract external DS package names from `includes` declarations in the system file.
78
+ *
79
+ * Supports two forms:
80
+ * - Primary (1.0+): `createSystem({ includes: [identifier, ...] })` constructor arg
81
+ * - Legacy: `.includes([identifier, ...])` chain method (RC migration fallback)
82
+ *
83
+ * For each identifier found, traces back to its import declaration and returns
84
+ * the import specifier. Only packages explicitly declared via `includes` are treated
85
+ * as external DS dependencies.
86
+ *
87
+ * Falls back to empty array if no `includes` declaration is found.
88
+ */
89
+ function extractSystemFilePackages(systemFilePath) {
90
+ let source;
91
+ try {
92
+ source = readFileSync(systemFilePath, "utf-8");
93
+ } catch {
94
+ return [];
95
+ }
96
+ const identifiers = /* @__PURE__ */ new Set();
97
+ const constructorRegex = /createSystem\s*\(\s*\{[^}]*?\bincludes\s*:\s*\[([^\]]*)\]/gs;
98
+ const chainRegex = /\.includes\s*\(\s*\[([^\]]*)\]\s*\)/gs;
99
+ const collectIdentifiers = (regex) => {
100
+ let match;
101
+ while ((match = regex.exec(source)) !== null) {
102
+ const inner = match[1];
103
+ for (const token of inner.split(",")) {
104
+ const id = token.trim();
105
+ if (id && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(id)) identifiers.add(id);
106
+ }
107
+ }
108
+ };
109
+ collectIdentifiers(constructorRegex);
110
+ collectIdentifiers(chainRegex);
111
+ if (identifiers.size === 0) return [];
112
+ const importMap = /* @__PURE__ */ new Map();
113
+ 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;
114
+ let importMatch;
115
+ while ((importMatch = importRegex.exec(source)) !== null) {
116
+ const [, comboDefault, namedImports, defaultImport, specifier] = importMatch;
117
+ if (comboDefault) importMap.set(comboDefault, specifier);
118
+ if (defaultImport) importMap.set(defaultImport, specifier);
119
+ if (namedImports) for (const binding of namedImports.split(",")) {
120
+ const parts = binding.trim().split(/\s+as\s+/);
121
+ const localName = (parts[1] || parts[0]).trim();
122
+ if (localName) importMap.set(localName, specifier);
123
+ }
124
+ }
125
+ const packages = /* @__PURE__ */ new Set();
126
+ for (const id of identifiers) {
127
+ const specifier = importMap.get(id);
128
+ if (specifier && !specifier.startsWith(".")) {
129
+ const pkgName = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
130
+ packages.add(pkgName);
131
+ }
132
+ }
133
+ return Array.from(packages);
134
+ }
135
+ //#endregion
136
+ //#region pipeline/mdx-preprocessor.ts
137
+ /**
138
+ * MDX source preprocessor for the extraction pipeline.
139
+ *
140
+ * MDX files are first-class consumers of ds-built components but are not
141
+ * directly parseable by OXC (which the scanner uses for .tsx/.jsx). This
142
+ * module compiles MDX sources to scanner-consumable JSX using @mdx-js/mdx.
143
+ *
144
+ * @mdx-js/mdx is declared as `peerDependenciesMeta.optional` on the
145
+ * plugin packages that import this module. The dynamic import with
146
+ * .catch() ensures non-MDX consumers (who configure `extensions` to
147
+ * exclude .mdx) never trigger the resolution — zero install-footprint
148
+ * cost for them.
149
+ *
150
+ * The `DEFAULT_EXTENSIONS` constant is the shared source of truth for
151
+ * both `@animus-ui/vite-plugin` and `@animus-ui/next-plugin`. Each plugin
152
+ * imports it directly; independent redeclaration of default extensions
153
+ * is considered a regression.
154
+ */
155
+ const DEFAULT_EXTENSIONS = [
156
+ ".ts",
157
+ ".tsx",
158
+ ".js",
159
+ ".jsx",
160
+ ".mdx"
161
+ ];
162
+ /**
163
+ * Preprocess an MDX source string into scanner-consumable JSX.
164
+ *
165
+ * - Returns `{ kind: 'ok', source }` with JSX-compiled output on success.
166
+ * - Returns `{ kind: 'missing-dep' }` if @mdx-js/mdx is not resolvable
167
+ * (consumer needs to install it).
168
+ * - Returns `{ kind: 'error', error }` on compile failure (e.g. malformed
169
+ * MDX syntax). Plugins SHALL warn + skip affected files; the build
170
+ * continues with remaining files.
171
+ */
172
+ async function preprocessMdx(source, filename) {
173
+ const mdxMod = await import("@mdx-js/mdx").catch(() => null);
174
+ if (mdxMod === null) return { kind: "missing-dep" };
175
+ try {
176
+ const vfile = await mdxMod.compile(source, {
177
+ outputFormat: "program",
178
+ development: false,
179
+ jsx: true
180
+ });
181
+ return {
182
+ kind: "ok",
183
+ source: `/* @mdx-source: ${filename} */\n${String(vfile)}`
184
+ };
185
+ } catch (err) {
186
+ return {
187
+ kind: "error",
188
+ error: err instanceof Error ? err.message : String(err)
189
+ };
190
+ }
191
+ }
192
+ //#endregion
193
+ //#region pipeline/prefix.ts
194
+ /**
195
+ * Apply namespace prefix to a variable map, CSS variable declarations, and theme JSON.
196
+ *
197
+ * Variable map: `{ "colors.ember": "--color-ember" }` -> `{ "colors.ember": "--prefix-color-ember" }`
198
+ * Variable CSS: `--color-ember: #FF2800` -> `--prefix-color-ember: #FF2800`
199
+ * `var(--color-ember)` -> `var(--prefix-color-ember)`
200
+ * Theme JSON: `{ "colors.ember": "var(--color-ember)" }` -> `{ "colors.ember": "var(--prefix-color-ember)" }`
201
+ */
202
+ function applyPrefix(prefix, variableMapJson, variableCss, themeJson, contextualVarsJson) {
203
+ if (!prefix) return {
204
+ variableMapJson,
205
+ variableCss,
206
+ themeJson,
207
+ contextualVarsJson
208
+ };
209
+ const varRefRe = /var\(--([a-zA-Z][\w-]*)\)/g;
210
+ const map = JSON.parse(variableMapJson);
211
+ const prefixed = {};
212
+ for (const [key, varName] of Object.entries(map)) prefixed[key] = varName.startsWith("--") ? `--${prefix}-${varName.slice(2)}` : varName;
213
+ let css = variableCss;
214
+ css = css.replace(/--([a-zA-Z][\w-]*)\s*:/g, `--${prefix}-$1:`);
215
+ css = css.replace(varRefRe, `var(--${prefix}-$1)`);
216
+ const result = {
217
+ variableMapJson: JSON.stringify(prefixed),
218
+ variableCss: css
219
+ };
220
+ if (themeJson) result.themeJson = themeJson.replace(varRefRe, `var(--${prefix}-$1)`);
221
+ if (contextualVarsJson) {
222
+ const ctxVars = JSON.parse(contextualVarsJson);
223
+ const prefixedCtx = {};
224
+ for (const [scale, names] of Object.entries(ctxVars)) prefixedCtx[scale] = names.map((name) => `${prefix}-${name}`);
225
+ result.contextualVarsJson = JSON.stringify(prefixedCtx);
226
+ }
227
+ return result;
228
+ }
229
+ //#endregion
230
+ //#region pipeline/utils.ts
231
+ /** Convert camelCase CSS property names to kebab-case. */
232
+ function camelToKebab(str) {
233
+ return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
234
+ }
235
+ //#endregion
236
+ //#region pipeline/resolve-global-styles.ts
237
+ /**
238
+ * Resolve {scale.path} and {scale.path/alpha} token aliases in a CSS value string.
239
+ * Mirrors the Rust theme_resolver's resolve_token_aliases logic.
240
+ */
241
+ function resolveTokenAliases(value, flat, variableMap) {
242
+ if (!value.includes("{")) return value;
243
+ return value.replace(/\{([^}]+)\}/g, (_match, content) => {
244
+ const slashIdx = content.indexOf("/");
245
+ const tokenPath = slashIdx >= 0 ? content.slice(0, slashIdx) : content;
246
+ const alpha = slashIdx >= 0 ? Number.parseInt(content.slice(slashIdx + 1), 10) : null;
247
+ const flatKey = tokenPath;
248
+ let resolved;
249
+ if (variableMap[flatKey]) resolved = `var(${variableMap[flatKey]})`;
250
+ else if (flat[flatKey] != null) resolved = flat[flatKey];
251
+ else return `{${content}}`;
252
+ if (alpha === 0) return "transparent";
253
+ if (alpha != null && alpha !== 100) return `color-mix(in srgb, ${resolved} ${alpha}%, transparent)`;
254
+ return resolved;
255
+ });
256
+ }
257
+ /**
258
+ * Resolve a single CSS value through prop config: scale lookup, transform, token aliases.
259
+ */
260
+ function resolveValue(raw, config, flat, variableMap, transforms) {
261
+ let resolved = String(raw);
262
+ if (config?.scale) {
263
+ const key = config.scale + "." + raw;
264
+ if (flat[key] != null) resolved = flat[key];
265
+ }
266
+ if (config?.transform && transforms[config.transform]) {
267
+ const fn = transforms[config.transform];
268
+ const input = typeof resolved === "string" && !Number.isNaN(Number(resolved)) ? Number(resolved) : resolved;
269
+ resolved = String(fn(input));
270
+ }
271
+ return resolveTokenAliases(resolved, flat, variableMap);
272
+ }
273
+ /**
274
+ * Resolve a block of selectors -> style objects using prop config + theme.
275
+ * @keyframes selectors are resolved with full prop config support.
276
+ */
277
+ function resolveBlock(selectors, propConfig, flat, variableMap, transforms) {
278
+ const rules = [];
279
+ for (const [selector, styleObj] of Object.entries(selectors)) {
280
+ if (selector.startsWith("@keyframes")) {
281
+ const frames = [];
282
+ for (const [pct, frameStyles] of Object.entries(styleObj)) if (typeof frameStyles === "object" && frameStyles !== null) {
283
+ const decls = [];
284
+ for (const [prop, raw] of Object.entries(frameStyles)) {
285
+ const cfg = propConfig[prop];
286
+ const cssProps = cfg?.properties?.length ? cfg.properties : cfg ? [cfg.property] : [prop];
287
+ const resolved = resolveValue(raw, cfg, flat, variableMap, transforms);
288
+ for (const cssProp of cssProps) decls.push(` ${camelToKebab(cssProp)}: ${resolved};`);
289
+ }
290
+ frames.push(` ${pct} {\n${decls.join("\n")}\n }`);
291
+ }
292
+ rules.push(`${selector} {\n${frames.join("\n")}\n}`);
293
+ continue;
294
+ }
295
+ const decls = [];
296
+ for (const [prop, raw] of Object.entries(styleObj)) {
297
+ const config = propConfig[prop];
298
+ const cssProps = config?.properties?.length ? config.properties : config ? [config.property] : [prop];
299
+ const resolved = resolveValue(raw, config, flat, variableMap, transforms);
300
+ for (const cssProp of cssProps) decls.push(` ${camelToKebab(cssProp)}: ${resolved};`);
301
+ }
302
+ if (decls.length) rules.push(`${selector} {\n${decls.join("\n")}\n}`);
303
+ }
304
+ return rules.join("\n\n");
305
+ }
306
+ /**
307
+ * Resolve global style blocks into CSS strings.
308
+ *
309
+ * Takes the global style map (block name -> selectors -> props -> values),
310
+ * resolves prop shorthand, scale lookups, transforms, and token aliases,
311
+ * and returns a map of block name -> resolved CSS string.
312
+ */
313
+ function resolveGlobalStyles(globalStyles, propConfig, flat, variableMap, transforms) {
314
+ const result = {};
315
+ for (const [name, block] of Object.entries(globalStyles)) {
316
+ const resolved = resolveBlock(block, propConfig, flat, variableMap, transforms);
317
+ if (resolved) result[name] = resolved;
318
+ }
319
+ return result;
320
+ }
321
+ //#endregion
322
+ //#region pipeline/resolve-transforms.ts
323
+ /**
324
+ * Resolve __TRANSFORM__ placeholders in extracted CSS.
325
+ *
326
+ * Pattern: `__TRANSFORM__name__rawValue__` -> transform function result.
327
+ * The Rust crate emits these placeholders for CSS values that require
328
+ * JS transform functions (e.g., size, grid) which can't be evaluated in Rust.
329
+ */
330
+ function resolveTransformPlaceholders(css, transforms) {
331
+ return css.replace(/__TRANSFORM__(\w+)__(.+?)__/g, (_, name, rawValue) => {
332
+ const fn = transforms[name];
333
+ if (!fn) return rawValue;
334
+ const result = fn(rawValue !== "" && !Number.isNaN(Number(rawValue)) ? Number(rawValue) : rawValue);
335
+ return typeof result === "object" ? JSON.stringify(result) : String(result);
336
+ });
337
+ }
338
+ //#endregion
339
+ //#region pipeline/unit-fallback.ts
340
+ /**
341
+ * Append `px` to bare numeric values in CSS declarations for properties
342
+ * that expect length units. Unitless properties are preserved as-is.
343
+ * Numbers inside CSS function calls (cubic-bezier, rgb, calc, etc.) are skipped.
344
+ */
345
+ function applyUnitFallback(css) {
346
+ return css.replace(/([a-z-]+)\s*:\s*([^;{}]+);/g, (match, prop, value) => {
347
+ if (UNITLESS_PROPERTIES.has(prop)) return match;
348
+ let depth = 0;
349
+ let fixed = "";
350
+ let i = 0;
351
+ while (i < value.length) if (value[i] === "(") {
352
+ depth++;
353
+ fixed += value[i];
354
+ i++;
355
+ } else if (value[i] === ")") {
356
+ depth--;
357
+ fixed += value[i];
358
+ i++;
359
+ } else if (depth > 0) {
360
+ fixed += value[i];
361
+ i++;
362
+ } else {
363
+ const numMatch = value.slice(i).match(/^(-?\d+\.?\d*)/);
364
+ if (numMatch) {
365
+ const num = numMatch[1];
366
+ const after = value[i + num.length];
367
+ if (after && /[a-z%]/i.test(after)) fixed += num;
368
+ else fixed += num + "px";
369
+ i += num.length;
370
+ } else {
371
+ fixed += value[i];
372
+ i++;
373
+ }
374
+ }
375
+ return fixed !== value ? `${prop}:${fixed};` : match;
376
+ });
377
+ }
378
+ //#endregion
379
+ export { ANIMUS_LAYERS, DEFAULT_EXTENSIONS, applyPrefix, applyUnitFallback, assembleStylesheet, camelToKebab, extractSystemFilePackages, preprocessMdx, resolveGlobalStyles, resolveTokenAliases, resolveTransformPlaceholders, resolveValue, stripLeadingLayerDeclaration, validateLayerOrder };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * MDX source preprocessor for the extraction pipeline.
3
+ *
4
+ * MDX files are first-class consumers of ds-built components but are not
5
+ * directly parseable by OXC (which the scanner uses for .tsx/.jsx). This
6
+ * module compiles MDX sources to scanner-consumable JSX using @mdx-js/mdx.
7
+ *
8
+ * @mdx-js/mdx is declared as `peerDependenciesMeta.optional` on the
9
+ * plugin packages that import this module. The dynamic import with
10
+ * .catch() ensures non-MDX consumers (who configure `extensions` to
11
+ * exclude .mdx) never trigger the resolution — zero install-footprint
12
+ * cost for them.
13
+ *
14
+ * The `DEFAULT_EXTENSIONS` constant is the shared source of truth for
15
+ * both `@animus-ui/vite-plugin` and `@animus-ui/next-plugin`. Each plugin
16
+ * imports it directly; independent redeclaration of default extensions
17
+ * is considered a regression.
18
+ */
19
+ export declare const DEFAULT_EXTENSIONS: readonly ['.ts', '.tsx', '.js', '.jsx', '.mdx'];
20
+ export type DefaultExtension = (typeof DEFAULT_EXTENSIONS)[number];
21
+ export interface PreprocessMdxResult {
22
+ kind: 'ok' | 'missing-dep' | 'error';
23
+ /** Preprocessed JSX source. Present when kind === 'ok'. */
24
+ source?: string;
25
+ /** Error message. Present when kind === 'error'. */
26
+ error?: string;
27
+ }
28
+ /**
29
+ * Preprocess an MDX source string into scanner-consumable JSX.
30
+ *
31
+ * - Returns `{ kind: 'ok', source }` with JSX-compiled output on success.
32
+ * - Returns `{ kind: 'missing-dep' }` if @mdx-js/mdx is not resolvable
33
+ * (consumer needs to install it).
34
+ * - Returns `{ kind: 'error', error }` on compile failure (e.g. malformed
35
+ * MDX syntax). Plugins SHALL warn + skip affected files; the build
36
+ * continues with remaining files.
37
+ */
38
+ export declare function preprocessMdx(source: string, filename: string): Promise<PreprocessMdxResult>;
39
+ //# sourceMappingURL=mdx-preprocessor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdx-preprocessor.d.ts","sourceRoot":"","sources":["../pipeline/mdx-preprocessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,kBAAkB,YAC7B,KAAK,EACL,MAAM,EACN,KAAK,EACL,MAAM,EACN,MAAM,CACE,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,GAAG,aAAa,GAAG,OAAO,CAAC;IACrC,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,mBAAmB,CAAC,CA4B9B"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Apply namespace prefix to a variable map, CSS variable declarations, and theme JSON.
3
+ *
4
+ * Variable map: `{ "colors.ember": "--color-ember" }` -> `{ "colors.ember": "--prefix-color-ember" }`
5
+ * Variable CSS: `--color-ember: #FF2800` -> `--prefix-color-ember: #FF2800`
6
+ * `var(--color-ember)` -> `var(--prefix-color-ember)`
7
+ * Theme JSON: `{ "colors.ember": "var(--color-ember)" }` -> `{ "colors.ember": "var(--prefix-color-ember)" }`
8
+ */
9
+ export declare function applyPrefix(prefix: string, variableMapJson: string, variableCss: string, themeJson?: string, contextualVarsJson?: string): {
10
+ variableMapJson: string;
11
+ variableCss: string;
12
+ themeJson?: string;
13
+ contextualVarsJson?: string;
14
+ };
15
+ //# sourceMappingURL=prefix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefix.d.ts","sourceRoot":"","sources":["../pipeline/prefix.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,EAClB,kBAAkB,CAAC,EAAE,MAAM,GAC1B;IACD,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CA0CA"}
@@ -0,0 +1,25 @@
1
+ interface PropConfigEntry {
2
+ property?: string;
3
+ properties?: string[];
4
+ scale?: string;
5
+ transform?: string;
6
+ }
7
+ /**
8
+ * Resolve {scale.path} and {scale.path/alpha} token aliases in a CSS value string.
9
+ * Mirrors the Rust theme_resolver's resolve_token_aliases logic.
10
+ */
11
+ export declare function resolveTokenAliases(value: string, flat: Record<string, string>, variableMap: Record<string, string>): string;
12
+ /**
13
+ * Resolve a single CSS value through prop config: scale lookup, transform, token aliases.
14
+ */
15
+ export declare function resolveValue(raw: unknown, config: PropConfigEntry | undefined, flat: Record<string, string>, variableMap: Record<string, string>, transforms: Record<string, (v: unknown) => unknown>): string;
16
+ /**
17
+ * Resolve global style blocks into CSS strings.
18
+ *
19
+ * Takes the global style map (block name -> selectors -> props -> values),
20
+ * resolves prop shorthand, scale lookups, transforms, and token aliases,
21
+ * and returns a map of block name -> resolved CSS string.
22
+ */
23
+ export declare function resolveGlobalStyles(globalStyles: Record<string, Record<string, Record<string, unknown>>>, propConfig: Record<string, PropConfigEntry>, flat: Record<string, string>, variableMap: Record<string, string>, transforms: Record<string, (v: unknown) => unknown>): Record<string, string>;
24
+ export {};
25
+ //# sourceMappingURL=resolve-global-styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-global-styles.d.ts","sourceRoot":"","sources":["../pipeline/resolve-global-styles.ts"],"names":[],"mappings":"AAEA,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,MAAM,CA2BR;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,eAAe,GAAG,SAAS,EACnC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,GAClD,MAAM,CAkBR;AAsED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EACrE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EAC3C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,GAClD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAexB"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Resolve __TRANSFORM__ placeholders in extracted CSS.
3
+ *
4
+ * Pattern: `__TRANSFORM__name__rawValue__` -> transform function result.
5
+ * The Rust crate emits these placeholders for CSS values that require
6
+ * JS transform functions (e.g., size, grid) which can't be evaluated in Rust.
7
+ */
8
+ export declare function resolveTransformPlaceholders(css: string, transforms: Record<string, (v: unknown) => unknown>): string;
9
+ //# sourceMappingURL=resolve-transforms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-transforms.d.ts","sourceRoot":"","sources":["../pipeline/resolve-transforms.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,GAClD,MAAM,CAgBR"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Append `px` to bare numeric values in CSS declarations for properties
3
+ * that expect length units. Unitless properties are preserved as-is.
4
+ * Numbers inside CSS function calls (cubic-bezier, rgb, calc, etc.) are skipped.
5
+ */
6
+ export declare function applyUnitFallback(css: string): string;
7
+ //# sourceMappingURL=unit-fallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unit-fallback.d.ts","sourceRoot":"","sources":["../pipeline/unit-fallback.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA2CrD"}
@@ -0,0 +1,3 @@
1
+ /** Convert camelCase CSS property names to kebab-case. */
2
+ export declare function camelToKebab(str: string): string;
3
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../pipeline/utils.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD"}
package/index.d.ts CHANGED
@@ -10,8 +10,13 @@
10
10
  * - `group_registry_json`: group registry JSON (`{ "space": ["p", "px", ...], ... }`).
11
11
  * - `package_resolution_json`: JSON object mapping package specifiers to entry-point paths,
12
12
  * e.g. `{ "@my-ui/components": "pkg-barrel/index.ts" }`. Pass `"{}"` when not needed.
13
+ * - `prefix`: optional namespace prefix for class names and CSS custom properties.
14
+ * When set, `animus-` is replaced with `{prefix}-` in all generated identifiers.
15
+ * - `emitter_config_json`: optional JSON `{ "runtime_import": "...", "css_module_id": "..." }`.
16
+ * Overrides hardcoded import paths in generated source. When `None`, defaults to
17
+ * `@animus-ui/system` and `virtual:animus/styles.css`.
13
18
  */
14
- export declare function analyzeProject(fileEntriesJson: string, themeJson: string, variableMapJson: string, configJson: string, groupRegistryJson: string, packageResolutionJson: string, devMode?: boolean | undefined | null): string
19
+ export declare function analyzeProject(fileEntriesJson: string, themeJson: string, variableMapJson: string, contextualVarsJson: string | undefined | null, configJson: string, groupRegistryJson: string, packageResolutionJson: string, devMode?: boolean | undefined | null, emitterConfigJson?: string | undefined | null, selectorAliasesJson?: string | undefined | null, selectorOrderJson?: string | undefined | null, globalStyleBlocksJson?: string | undefined | null, pathAliasesJson?: string | undefined | null, keyframesBlocksJson?: string | undefined | null): string
15
20
 
16
21
  /**
17
22
  * Clear the per-file extraction cache used by `analyze_project()`.
@@ -20,7 +25,7 @@ export declare function analyzeProject(fileEntriesJson: string, themeJson: strin
20
25
  */
21
26
  export declare function clearAnalysisCache(): void
22
27
 
23
- export declare function extract(source: string, filename: string, themeJson: string, variableMapJson: string, configJson: string, groupRegistryJson: string): ExtractionResult
28
+ export declare function extract(source: string, filename: string, themeJson: string, variableMapJson: string, configJson: string, groupRegistryJson: string, selectorAliasesJson?: string | undefined | null, selectorOrderJson?: string | undefined | null): ExtractionResult
24
29
 
25
30
  export interface ExtractionResult {
26
31
  css: string
@@ -30,6 +35,21 @@ export interface ExtractionResult {
30
35
  errors: Array<string>
31
36
  }
32
37
 
38
+ export declare function loadSystemModule(systemPath: string, rootDir: string, exportName?: string | undefined | null): NapiSystemConfig
39
+
40
+ export interface NapiSystemConfig {
41
+ propConfig: string
42
+ groupRegistry: string
43
+ scalesJson: string
44
+ variableMapJson: string
45
+ variableCss: string
46
+ contextualVarsJson: string
47
+ selectorAliases?: string
48
+ selectorOrder?: string
49
+ globalStyleBlocks?: string
50
+ keyframesBlocks?: string
51
+ }
52
+
33
53
  /**
34
54
  * Transform a single source file using a pre-built `UniverseManifest`.
35
55
  *
package/index.js CHANGED
@@ -579,4 +579,5 @@ module.exports = nativeBinding
579
579
  module.exports.analyzeProject = nativeBinding.analyzeProject
580
580
  module.exports.clearAnalysisCache = nativeBinding.clearAnalysisCache
581
581
  module.exports.extract = nativeBinding.extract
582
+ module.exports.loadSystemModule = nativeBinding.loadSystemModule
582
583
  module.exports.transformFile = nativeBinding.transformFile
package/package.json CHANGED
@@ -1,22 +1,75 @@
1
1
  {
2
2
  "name": "@animus-ui/extract",
3
- "version": "0.1.0-next.9",
3
+ "version": "0.1.0",
4
4
  "description": "Animus static CSS extraction pipeline (Rust/NAPI)",
5
- "author": "codecaaron <airrobb@gmail.com>",
5
+ "keywords": [
6
+ "animus",
7
+ "compiler",
8
+ "css",
9
+ "css-in-js",
10
+ "napi",
11
+ "rust",
12
+ "static-css",
13
+ "zero-runtime"
14
+ ],
15
+ "homepage": "https://github.com/codecaaron/animus/tree/main/packages/extract#readme",
6
16
  "license": "MIT",
7
- "main": "index.js",
8
- "types": "index.d.ts",
17
+ "author": "codecaaron <airrobb@gmail.com>",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/codecaaron/animus",
21
+ "directory": "packages/extract"
22
+ },
9
23
  "files": [
10
24
  "index.js",
11
25
  "index.d.ts",
12
- "*.node"
26
+ "*.node",
27
+ "dist"
13
28
  ],
29
+ "main": "index.js",
30
+ "types": "index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "import": "./index.js",
34
+ "require": "./index.js",
35
+ "default": "./index.js"
36
+ },
37
+ "./pipeline": {
38
+ "types": "./dist/index.d.ts",
39
+ "import": "./dist/index.mjs",
40
+ "require": "./dist/index.mjs",
41
+ "default": "./dist/index.mjs"
42
+ }
43
+ },
14
44
  "publishConfig": {
15
45
  "access": "public"
16
46
  },
17
- "repository": {
18
- "type": "git",
19
- "url": "git+https://github.com/codecaaron/animus"
47
+ "scripts": {
48
+ "build": "napi build --platform --release && bun run build:ts",
49
+ "build:ts": "tsdown && tsgo -p tsconfig.build.json",
50
+ "build:debug": "napi build --platform",
51
+ "compile": "tsgo -p tsconfig.build.json --noEmit"
52
+ },
53
+ "dependencies": {
54
+ "@animus-ui/properties": "0.1.0"
55
+ },
56
+ "devDependencies": {
57
+ "@animus-ui/system": "workspace:*",
58
+ "@mdx-js/mdx": "^3.0.0",
59
+ "@napi-rs/cli": "^3.0.0"
60
+ },
61
+ "peerDependencies": {
62
+ "@mdx-js/mdx": "^3.0.0"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "@mdx-js/mdx": {
66
+ "optional": true
67
+ }
68
+ },
69
+ "optionalDependencies": {
70
+ "@animus-ui/extract-darwin-arm64": "0.1.0",
71
+ "@animus-ui/extract-linux-arm64-gnu": "0.1.0",
72
+ "@animus-ui/extract-linux-x64-gnu": "0.1.0"
20
73
  },
21
74
  "napi": {
22
75
  "binaryName": "animus-extract",
@@ -25,17 +78,5 @@
25
78
  "x86_64-unknown-linux-gnu",
26
79
  "aarch64-unknown-linux-gnu"
27
80
  ]
28
- },
29
- "scripts": {
30
- "build": "napi build --platform --release",
31
- "build:debug": "napi build --platform"
32
- },
33
- "optionalDependencies": {
34
- "@animus-ui/extract-darwin-arm64": "0.1.0-next.9",
35
- "@animus-ui/extract-linux-x64-gnu": "0.1.0-next.9",
36
- "@animus-ui/extract-linux-arm64-gnu": "0.1.0-next.9"
37
- },
38
- "devDependencies": {
39
- "@napi-rs/cli": "^3.0.0"
40
81
  }
41
82
  }