@animus-ui/extract 0.1.5 → 0.1.7

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.
@@ -58,7 +58,7 @@ export interface EngineOptions {
58
58
  /** Selector aliases JSON (v1 `selector_aliases_json`). */
59
59
  selectorAliasesJson?: string
60
60
  /**
61
- * Condition aliases JSON (inc 03 — `conditionAliases` manifest field):
61
+ * Condition aliases JSON (the `conditionAliases` manifest field):
62
62
  * `{ "_motionReduce": { "value": "@media …", "order": 500, "kind":
63
63
  * "media" } }`. Absent = no registrations.
64
64
  */
@@ -98,10 +98,10 @@ export interface EngineOptions {
98
98
  export declare function engineVersion(): string
99
99
 
100
100
  /**
101
- * Full per-file fact extraction (increment 11): chains + eagerly evaluated
101
+ * Full per-file fact extraction: chains + eagerly evaluated
102
102
  * stages + statics + raw usage facts + compose families — one parse per
103
103
  * file. The store (and every AST) is dropped when this call returns; the
104
- * D4-relevant invariant is that no program() read happens after
104
+ * invariant is that no program() read happens after
105
105
  * cross-file facts resolve.
106
106
  */
107
107
  export declare function extractFacts(fileEntriesJson: string): string
@@ -118,8 +118,8 @@ export interface NapiSystemConfig {
118
118
  selectorAliases?: string
119
119
  selectorOrder?: string
120
120
  /**
121
- * Condition alias map JSON (inc 03 — `conditionAliases`): alias →
122
- * `{ value, order, kind }`. Absent when the system registers none.
121
+ * Condition alias map JSON (the `conditionAliases` manifest field):
122
+ * alias → `{ value, order, kind }`. Absent when the system registers none.
123
123
  */
124
124
  conditionAliases?: string
125
125
  globalStyleBlocks?: string
@@ -138,3 +138,14 @@ export interface NapiSystemConfig {
138
138
  */
139
139
  sourceThemeManifests?: string
140
140
  }
141
+
142
+ /**
143
+ * Scan one module entry for named `Keyframes` collection exports — the
144
+ * keyframes-only carve-out for external package entries. The
145
+ * entry evaluates through the same loader pipeline as a system module, but
146
+ * nothing except `__brand === 'Keyframes'` exports is read from it; the
147
+ * consumer's configured system remains the singular config authority.
148
+ * Returns the `{ exportName: { keyName: { name, frames } } }` JSON, or None
149
+ * when the entry exports no collections.
150
+ */
151
+ export declare function scanKeyframesExports(entryPath: string, rootDir: string): string | null
@@ -40,6 +40,8 @@ export interface TransformFileResult {
40
40
  */
41
41
  export interface EngineApi {
42
42
  loadSystemModule: (...args: unknown[]) => any;
43
+ /** Keyframes-only scan of an external package entry. */
44
+ scanKeyframesExports: (entryPath: string, rootDir: string) => string | null;
43
45
  analyzeProject: (filesJson: string, scalesJson: string, variableMapJson: string, contextualVarsJson: string | null, propConfigJson: string, groupRegistryJson: string, packageResolutionJson: string, devMode: boolean, emitterConfigJson: string | null, selectorAliasesJson: string | null, selectorOrderJson: string | null, globalStyleBlocksJson: string | null, pathAliasesJson: string | null, keyframesJson: string | null, staticCssJson: string | null, conditionAliasesJson: string | null, externalDirsJson: string | null) => string;
44
46
  transformFile: (source: string, path: string, manifest: string) => TransformFileResult;
45
47
  clearAnalysisCache: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"engine-adapter.d.ts","sourceRoot":"","sources":["../pipeline/engine-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,UAAU,IAAI,IAAI,CAAC;CACpB;AAED,6DAA6D;AAC7D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IAIxB,gBAAgB,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,GAAG,CAAC;IAC9C,cAAc,EAAE,CACd,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,MAAM,EACvB,kBAAkB,EAAE,MAAM,GAAG,IAAI,EACjC,cAAc,EAAE,MAAM,EACtB,iBAAiB,EAAE,MAAM,EACzB,qBAAqB,EAAE,MAAM,EAC7B,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,mBAAmB,EAAE,MAAM,GAAG,IAAI,EAClC,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,qBAAqB,EAAE,MAAM,GAAG,IAAI,EACpC,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,oBAAoB,EAAE,MAAM,GAAG,IAAI,EACnC,gBAAgB,EAAE,MAAM,GAAG,IAAI,KAC5B,MAAM,CAAC;IACZ,aAAa,EAAE,CACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,KACb,mBAAmB,CAAC;IACzB,kBAAkB,EAAE,MAAM,IAAI,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,IAAI,eAAe,GAAG,IAAI,CAAC;IACpC,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI,CAAC;IAChD,cAAc,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC7C,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1D,cAAc,IAAI,OAAO,CAAC;IAC1B,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACtC;AAED,4EAA4E;AAE5E,KAAK,kBAAkB,GAAG,GAAG,CAAC;AA2B9B,MAAM,WAAW,mBAAmB;IAClC;kDAC8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd;;2DAEuD;IACvD,IAAI,IAAI,OAAO,CAAC;IAChB;qDACiD;IACjD,gBAAgB,IAAI,kBAAkB,CAAC;IACvC,qEAAqE;IACrE,KAAK,EAAE,kBAAkB,CAAC;IAC1B;;;;gBAIY;IACZ,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;+CAG2C;IAC3C,kBAAkB,CAAC,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;CACnD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,SAAS,CAkH5E"}
1
+ {"version":3,"file":"engine-adapter.d.ts","sourceRoot":"","sources":["../pipeline/engine-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,UAAU,IAAI,IAAI,CAAC;CACpB;AAED,6DAA6D;AAC7D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IAIxB,gBAAgB,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,GAAG,CAAC;IAC9C,wDAAwD;IACxD,oBAAoB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC5E,cAAc,EAAE,CACd,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,MAAM,EACvB,kBAAkB,EAAE,MAAM,GAAG,IAAI,EACjC,cAAc,EAAE,MAAM,EACtB,iBAAiB,EAAE,MAAM,EACzB,qBAAqB,EAAE,MAAM,EAC7B,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,mBAAmB,EAAE,MAAM,GAAG,IAAI,EAClC,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,qBAAqB,EAAE,MAAM,GAAG,IAAI,EACpC,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,oBAAoB,EAAE,MAAM,GAAG,IAAI,EACnC,gBAAgB,EAAE,MAAM,GAAG,IAAI,KAC5B,MAAM,CAAC;IACZ,aAAa,EAAE,CACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,KACb,mBAAmB,CAAC;IACzB,kBAAkB,EAAE,MAAM,IAAI,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,IAAI,eAAe,GAAG,IAAI,CAAC;IACpC,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI,CAAC;IAChD,cAAc,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC7C,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1D,cAAc,IAAI,OAAO,CAAC;IAC1B,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACtC;AAED,4EAA4E;AAE5E,KAAK,kBAAkB,GAAG,GAAG,CAAC;AA2B9B,MAAM,WAAW,mBAAmB;IAClC;kDAC8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd;;2DAEuD;IACvD,IAAI,IAAI,OAAO,CAAC;IAChB;qDACiD;IACjD,gBAAgB,IAAI,kBAAkB,CAAC;IACvC,qEAAqE;IACrE,KAAK,EAAE,kBAAkB,CAAC;IAC1B;;;;gBAIY;IACZ,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;+CAG2C;IAC3C,kBAAkB,CAAC,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;CACnD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,SAAS,CAsH5E"}
@@ -0,0 +1,23 @@
1
+ import type { ManifestDiagnostic } from './manifest-diagnostics';
2
+ /** Stable codes for external keyframes discovery. */
3
+ export declare const KEYFRAMES_EXTERNAL_ENTRY_FAILED = "animus.keyframes.external-entry-failed";
4
+ export declare const KEYFRAMES_EXPORT_COLLISION = "animus.keyframes.export-collision";
5
+ export interface ExternalKeyframesMerge {
6
+ /** Consumer collections merged with every discovered external collection
7
+ * (consumer wins on name collisions); `null` when nothing exists. */
8
+ keyframesJson: string | null;
9
+ /** Warn-severity diagnostics for failed entries and name collisions —
10
+ * surfaced through the shared manifest-diagnostics policy point. */
11
+ diagnostics: ManifestDiagnostic[];
12
+ }
13
+ /**
14
+ * Merge `Keyframes` collections discovered from external package entries into
15
+ * the consumer system's collections. Keyframes are the sole carve-out from
16
+ * the consumer-config singular-authority rule: nothing but branded
17
+ * collections is read from an entry, an entry that fails to evaluate
18
+ * degrades to a coded diagnostic naming it, and a collection whose export
19
+ * name collides keeps the earlier (consumer-first) collection with a coded
20
+ * diagnostic instead of silently reordering names.
21
+ */
22
+ export declare function mergeExternalKeyframes(scan: (entryPath: string, rootDir: string) => string | null, consumerKeyframesJson: string | null | undefined, externalEntries: Iterable<string>, rootDir: string): ExternalKeyframesMerge;
23
+ //# sourceMappingURL=external-keyframes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-keyframes.d.ts","sourceRoot":"","sources":["../pipeline/external-keyframes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE,qDAAqD;AACrD,eAAO,MAAM,+BAA+B,2CACF,CAAC;AAC3C,eAAO,MAAM,0BAA0B,sCAAsC,CAAC;AAE9E,MAAM,WAAW,sBAAsB;IACrC;0EACsE;IACtE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;yEACqE;IACrE,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,EAC3D,qBAAqB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChD,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,EACjC,OAAO,EAAE,MAAM,GACd,sBAAsB,CAoExB"}
package/dist/index.cjs CHANGED
@@ -141,6 +141,7 @@ function createV2EngineApi(deps) {
141
141
  const native = loadNativeEngine();
142
142
  return {
143
143
  loadSystemModule: (...args) => native.loadSystemModule(...args),
144
+ scanKeyframesExports: (entryPath, rootDir) => native.scanKeyframesExports(entryPath, rootDir) ?? null,
144
145
  analyzeProject: (filesJsonRaw, scalesJson, variableMapJson, contextualVarsJson, propConfigJson, groupRegistryJson, packageResolutionJson, devMode, emitterConfigJson, selectorAliasesJson, _selectorOrderJson, globalStyleBlocksJson, pathAliasesJson, keyframesJson, staticCssJson, conditionAliasesJson, externalDirsJson) => {
145
146
  const filesJson = deps.rehydrateFilesJson ? deps.rehydrateFilesJson(filesJsonRaw) : filesJsonRaw;
146
147
  const sent = /* @__PURE__ */ new Map();
@@ -232,6 +233,77 @@ function contentHash(source) {
232
233
  return (0, crypto.createHash)("md5").update(source).digest("hex");
233
234
  }
234
235
  //#endregion
236
+ //#region pipeline/external-keyframes.ts
237
+ /** Stable codes for external keyframes discovery. */
238
+ const KEYFRAMES_EXTERNAL_ENTRY_FAILED = "animus.keyframes.external-entry-failed";
239
+ const KEYFRAMES_EXPORT_COLLISION = "animus.keyframes.export-collision";
240
+ /**
241
+ * Merge `Keyframes` collections discovered from external package entries into
242
+ * the consumer system's collections. Keyframes are the sole carve-out from
243
+ * the consumer-config singular-authority rule: nothing but branded
244
+ * collections is read from an entry, an entry that fails to evaluate
245
+ * degrades to a coded diagnostic naming it, and a collection whose export
246
+ * name collides keeps the earlier (consumer-first) collection with a coded
247
+ * diagnostic instead of silently reordering names.
248
+ */
249
+ function mergeExternalKeyframes(scan, consumerKeyframesJson, externalEntries, rootDir) {
250
+ const merged = {};
251
+ if (consumerKeyframesJson) try {
252
+ Object.assign(merged, JSON.parse(consumerKeyframesJson));
253
+ } catch {
254
+ return {
255
+ keyframesJson: consumerKeyframesJson,
256
+ diagnostics: []
257
+ };
258
+ }
259
+ const diagnostics = [];
260
+ const seenEntries = /* @__PURE__ */ new Set();
261
+ for (const entryPath of externalEntries) {
262
+ if (seenEntries.has(entryPath)) continue;
263
+ seenEntries.add(entryPath);
264
+ let scanned;
265
+ try {
266
+ scanned = scan(entryPath, rootDir);
267
+ } catch (error) {
268
+ diagnostics.push({
269
+ file: entryPath,
270
+ component: "keyframes",
271
+ kind: "warn",
272
+ message: `external package entry failed the keyframes scan — its collections are invisible to extraction: ${String(error)}. Keyframe collections must be reachable from the package's definition entry without evaluating framework re-exports: export them (directly or as a named re-export) from the definition entry, and avoid \`export *\` of framework packages there (${KEYFRAMES_EXTERNAL_ENTRY_FAILED})`,
273
+ code: KEYFRAMES_EXTERNAL_ENTRY_FAILED,
274
+ severity: "warn"
275
+ });
276
+ continue;
277
+ }
278
+ if (!scanned) continue;
279
+ let collections;
280
+ try {
281
+ collections = JSON.parse(scanned);
282
+ } catch {
283
+ continue;
284
+ }
285
+ for (const [exportName, collection] of Object.entries(collections)) {
286
+ const existing = merged[exportName];
287
+ if (existing !== void 0) {
288
+ if (JSON.stringify(existing) !== JSON.stringify(collection)) diagnostics.push({
289
+ file: entryPath,
290
+ component: exportName,
291
+ kind: "warn",
292
+ message: `keyframes collection export '${exportName}' collides with an earlier collection of the same name — the earlier one wins (${KEYFRAMES_EXPORT_COLLISION})`,
293
+ code: KEYFRAMES_EXPORT_COLLISION,
294
+ severity: "warn"
295
+ });
296
+ continue;
297
+ }
298
+ merged[exportName] = collection;
299
+ }
300
+ }
301
+ return {
302
+ keyframesJson: Object.keys(merged).length > 0 ? JSON.stringify(merged) : null,
303
+ diagnostics
304
+ };
305
+ }
306
+ //#endregion
235
307
  //#region pipeline/discover-files.ts
236
308
  /**
237
309
  * Recursively discover source files under `dir`, skipping any path that
@@ -1007,17 +1079,78 @@ function postProcessCss(css, opts) {
1007
1079
  }
1008
1080
  //#endregion
1009
1081
  //#region pipeline/manifest-diagnostics.ts
1082
+ /** Stable code for selector forms with no substitutable subject.
1083
+ * Mirrors the Rust constant in `extract-v2/src/eval.rs`. */
1084
+ const SELECTOR_UNSUPPORTED_SUBJECT = "animus.selector.unsupported-subject";
1085
+ /** True when the selector string carries at least one substitutable `&`
1086
+ * subject outside quoted text (mirrors the Rust `selector_subject` walk —
1087
+ * ancestor, leading, and repeated subjects all count; a `&` inside a
1088
+ * quoted attribute value does not). */
1089
+ function hasSelectorSubject(value) {
1090
+ let quote = null;
1091
+ for (const c of value) if (quote !== null) {
1092
+ if (c === quote) quote = null;
1093
+ } else if (c === "\"" || c === "'") quote = c;
1094
+ else if (c === "&") return true;
1095
+ return false;
1096
+ }
1097
+ /**
1098
+ * Synthesize coded diagnostics for registered selector-alias values that
1099
+ * look selector-shaped (`&` present) but carry no substitutable subject —
1100
+ * every `&` sits inside quoted text, so there is nothing to anchor the
1101
+ * class to. Ancestor-prefixed and repeated subjects are supported and pass
1102
+ * validation. The system-config boundary is where these must fail loud
1103
+ * (alias values never reach the evaluator's key guard).
1104
+ */
1105
+ function collectSelectorAliasDiagnostics(selectorAliasesJson) {
1106
+ if (!selectorAliasesJson) return [];
1107
+ let aliases;
1108
+ try {
1109
+ aliases = JSON.parse(selectorAliasesJson);
1110
+ } catch {
1111
+ return [];
1112
+ }
1113
+ const diagnostics = [];
1114
+ for (const [name, value] of Object.entries(aliases)) {
1115
+ if (typeof value !== "string") continue;
1116
+ if (value.includes("&") && !hasSelectorSubject(value)) diagnostics.push({
1117
+ file: "system",
1118
+ component: name,
1119
+ kind: "warn",
1120
+ message: `selector alias '${name}' value '${value}' has no substitutable '&' subject outside quoted text (${SELECTOR_UNSUPPORTED_SUBJECT})`,
1121
+ code: SELECTOR_UNSUPPORTED_SUBJECT,
1122
+ severity: "error"
1123
+ });
1124
+ }
1125
+ return diagnostics;
1126
+ }
1010
1127
  /**
1011
1128
  * Surface extraction-manifest diagnostics through a plugin's warn channel.
1012
1129
  *
1013
- * Single authoritative copy for both extraction plugins. Surfaces `bail`
1014
- * (component not extracted), `skip` (component skipped), and `warn` kinds;
1015
- * unknown kinds stay silent.
1130
+ * Single authoritative copy for both extraction plugins — and the single
1131
+ * strict-escalation policy point: error-severity diagnostics throw one
1132
+ * Error naming every offender when `policy.strict`, and print as warnings
1133
+ * otherwise. Surfaces `bail` (component not extracted), `skip` (component
1134
+ * skipped), and `warn` kinds; unknown kinds stay silent. Printed lines
1135
+ * include the diagnostic code when the message doesn't already carry it.
1016
1136
  */
1017
- function surfaceManifestDiagnostics(manifest, warn) {
1018
- for (const diagnostic of manifest.diagnostics ?? []) if (diagnostic.kind === "bail") warn(`⚠ ${diagnostic.component} not extracted: ${diagnostic.message}`);
1019
- else if (diagnostic.kind === "skip") warn(`⚠ ${diagnostic.component}: skipped ${diagnostic.message}`);
1020
- else if (diagnostic.kind === "warn") warn(`⚠ ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`);
1137
+ function surfaceManifestDiagnostics(manifest, warn, policy = {}) {
1138
+ const errors = [];
1139
+ const diagnostics = policy.prepend?.length ? [...policy.prepend, ...manifest.diagnostics ?? []] : manifest.diagnostics ?? [];
1140
+ for (const diagnostic of diagnostics) {
1141
+ let line = null;
1142
+ if (diagnostic.kind === "bail") line = `⚠ ${diagnostic.component} not extracted: ${diagnostic.message}`;
1143
+ else if (diagnostic.kind === "skip") line = `⚠ ${diagnostic.component}: skipped ${diagnostic.message}`;
1144
+ else if (diagnostic.kind === "warn") line = `⚠ ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`;
1145
+ if (line === null) continue;
1146
+ if (diagnostic.code && !diagnostic.message.includes(diagnostic.code)) line += ` [${diagnostic.code}]`;
1147
+ if (policy.strict && diagnostic.severity === "error") {
1148
+ errors.push(`${diagnostic.code ?? "error"} — ${diagnostic.component}: ${diagnostic.message}`);
1149
+ continue;
1150
+ }
1151
+ warn(line);
1152
+ }
1153
+ if (errors.length > 0) throw new Error(`[animus] strict: ${errors.length} error diagnostic(s):\n${errors.join("\n")}`);
1021
1154
  }
1022
1155
  //#endregion
1023
1156
  //#region pipeline/unit-fallback.ts
@@ -1113,7 +1246,10 @@ function runProjectAnalysis(engineApi, opts) {
1113
1246
  const extractMs = Math.round(performance.now() - t);
1114
1247
  t = performance.now();
1115
1248
  const manifest = JSON.parse(manifestJson);
1116
- surfaceManifestDiagnostics(manifest, opts.warn);
1249
+ surfaceManifestDiagnostics(manifest, opts.warn, {
1250
+ strict: opts.strict,
1251
+ prepend: [...collectSelectorAliasDiagnostics(opts.system.selectorAliasesJson), ...opts.extraDiagnostics ?? []]
1252
+ });
1117
1253
  const parseMs = Math.round(performance.now() - t);
1118
1254
  return {
1119
1255
  manifest,
@@ -1581,6 +1717,8 @@ function realpathThroughNearestExistingAncestor(path) {
1581
1717
  //#endregion
1582
1718
  exports.ANIMUS_LAYERS = ANIMUS_LAYERS;
1583
1719
  exports.DEFAULT_EXTENSIONS = DEFAULT_EXTENSIONS;
1720
+ exports.KEYFRAMES_EXPORT_COLLISION = KEYFRAMES_EXPORT_COLLISION;
1721
+ exports.KEYFRAMES_EXTERNAL_ENTRY_FAILED = KEYFRAMES_EXTERNAL_ENTRY_FAILED;
1584
1722
  exports.RETIRED_ENGINE_MESSAGE = RETIRED_ENGINE_MESSAGE;
1585
1723
  exports.applyPrefix = applyPrefix;
1586
1724
  exports.applyUnitFallback = applyUnitFallback;
@@ -1603,6 +1741,7 @@ exports.findAssetSpecifiers = findAssetSpecifiers;
1603
1741
  exports.findPackageRoot = findPackageRoot;
1604
1742
  exports.formatRustTimingWaterfall = formatRustTimingWaterfall;
1605
1743
  exports.loadSystemConfig = loadSystemConfig;
1744
+ exports.mergeExternalKeyframes = mergeExternalKeyframes;
1606
1745
  exports.postProcessCss = postProcessCss;
1607
1746
  exports.preprocessMdx = preprocessMdx;
1608
1747
  exports.readTsconfigAliasPairs = readTsconfigAliasPairs;
package/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ export type { EngineApi, TransformFileResult, V2EngineAdapterDeps, V2EngineState
6
6
  export { createV2EngineApi } from './engine-adapter';
7
7
  export { assertNoRetiredEngineSelection, RETIRED_ENGINE_MESSAGE, } from './engine-retirement';
8
8
  export { contentHash } from './content-hash';
9
+ export { KEYFRAMES_EXPORT_COLLISION, KEYFRAMES_EXTERNAL_ENTRY_FAILED, mergeExternalKeyframes, } from './external-keyframes';
10
+ export type { ExternalKeyframesMerge } from './external-keyframes';
9
11
  export { discoverFiles } from './discover-files';
10
12
  export type { CollectedExternalPackages, ExternalPackageOutcome, } from './discover-packages';
11
13
  export { collectExternalPackageSources, extractSystemFilePackages, findPackageRoot, staleDistIncludesMessage, unresolvableIncludesMessage, } from './discover-packages';
@@ -1 +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,YAAY,EACV,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,YAAY,EACV,SAAS,EACT,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EACL,8BAA8B,EAC9B,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,YAAY,EACV,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,6BAA6B,EAC7B,yBAAyB,EACzB,eAAe,EACf,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7E,YAAY,EACV,eAAe,EACf,aAAa,EACb,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,YAAY,EACV,sBAAsB,EACtB,eAAe,GAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,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,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../pipeline/index.ts"],"names":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -121,6 +121,7 @@ function createV2EngineApi(deps) {
121
121
  const native = loadNativeEngine();
122
122
  return {
123
123
  loadSystemModule: (...args) => native.loadSystemModule(...args),
124
+ scanKeyframesExports: (entryPath, rootDir) => native.scanKeyframesExports(entryPath, rootDir) ?? null,
124
125
  analyzeProject: (filesJsonRaw, scalesJson, variableMapJson, contextualVarsJson, propConfigJson, groupRegistryJson, packageResolutionJson, devMode, emitterConfigJson, selectorAliasesJson, _selectorOrderJson, globalStyleBlocksJson, pathAliasesJson, keyframesJson, staticCssJson, conditionAliasesJson, externalDirsJson) => {
125
126
  const filesJson = deps.rehydrateFilesJson ? deps.rehydrateFilesJson(filesJsonRaw) : filesJsonRaw;
126
127
  const sent = /* @__PURE__ */ new Map();
@@ -212,6 +213,77 @@ function contentHash(source) {
212
213
  return createHash("md5").update(source).digest("hex");
213
214
  }
214
215
  //#endregion
216
+ //#region pipeline/external-keyframes.ts
217
+ /** Stable codes for external keyframes discovery. */
218
+ const KEYFRAMES_EXTERNAL_ENTRY_FAILED = "animus.keyframes.external-entry-failed";
219
+ const KEYFRAMES_EXPORT_COLLISION = "animus.keyframes.export-collision";
220
+ /**
221
+ * Merge `Keyframes` collections discovered from external package entries into
222
+ * the consumer system's collections. Keyframes are the sole carve-out from
223
+ * the consumer-config singular-authority rule: nothing but branded
224
+ * collections is read from an entry, an entry that fails to evaluate
225
+ * degrades to a coded diagnostic naming it, and a collection whose export
226
+ * name collides keeps the earlier (consumer-first) collection with a coded
227
+ * diagnostic instead of silently reordering names.
228
+ */
229
+ function mergeExternalKeyframes(scan, consumerKeyframesJson, externalEntries, rootDir) {
230
+ const merged = {};
231
+ if (consumerKeyframesJson) try {
232
+ Object.assign(merged, JSON.parse(consumerKeyframesJson));
233
+ } catch {
234
+ return {
235
+ keyframesJson: consumerKeyframesJson,
236
+ diagnostics: []
237
+ };
238
+ }
239
+ const diagnostics = [];
240
+ const seenEntries = /* @__PURE__ */ new Set();
241
+ for (const entryPath of externalEntries) {
242
+ if (seenEntries.has(entryPath)) continue;
243
+ seenEntries.add(entryPath);
244
+ let scanned;
245
+ try {
246
+ scanned = scan(entryPath, rootDir);
247
+ } catch (error) {
248
+ diagnostics.push({
249
+ file: entryPath,
250
+ component: "keyframes",
251
+ kind: "warn",
252
+ message: `external package entry failed the keyframes scan — its collections are invisible to extraction: ${String(error)}. Keyframe collections must be reachable from the package's definition entry without evaluating framework re-exports: export them (directly or as a named re-export) from the definition entry, and avoid \`export *\` of framework packages there (${KEYFRAMES_EXTERNAL_ENTRY_FAILED})`,
253
+ code: KEYFRAMES_EXTERNAL_ENTRY_FAILED,
254
+ severity: "warn"
255
+ });
256
+ continue;
257
+ }
258
+ if (!scanned) continue;
259
+ let collections;
260
+ try {
261
+ collections = JSON.parse(scanned);
262
+ } catch {
263
+ continue;
264
+ }
265
+ for (const [exportName, collection] of Object.entries(collections)) {
266
+ const existing = merged[exportName];
267
+ if (existing !== void 0) {
268
+ if (JSON.stringify(existing) !== JSON.stringify(collection)) diagnostics.push({
269
+ file: entryPath,
270
+ component: exportName,
271
+ kind: "warn",
272
+ message: `keyframes collection export '${exportName}' collides with an earlier collection of the same name — the earlier one wins (${KEYFRAMES_EXPORT_COLLISION})`,
273
+ code: KEYFRAMES_EXPORT_COLLISION,
274
+ severity: "warn"
275
+ });
276
+ continue;
277
+ }
278
+ merged[exportName] = collection;
279
+ }
280
+ }
281
+ return {
282
+ keyframesJson: Object.keys(merged).length > 0 ? JSON.stringify(merged) : null,
283
+ diagnostics
284
+ };
285
+ }
286
+ //#endregion
215
287
  //#region pipeline/discover-files.ts
216
288
  /**
217
289
  * Recursively discover source files under `dir`, skipping any path that
@@ -987,17 +1059,78 @@ function postProcessCss(css, opts) {
987
1059
  }
988
1060
  //#endregion
989
1061
  //#region pipeline/manifest-diagnostics.ts
1062
+ /** Stable code for selector forms with no substitutable subject.
1063
+ * Mirrors the Rust constant in `extract-v2/src/eval.rs`. */
1064
+ const SELECTOR_UNSUPPORTED_SUBJECT = "animus.selector.unsupported-subject";
1065
+ /** True when the selector string carries at least one substitutable `&`
1066
+ * subject outside quoted text (mirrors the Rust `selector_subject` walk —
1067
+ * ancestor, leading, and repeated subjects all count; a `&` inside a
1068
+ * quoted attribute value does not). */
1069
+ function hasSelectorSubject(value) {
1070
+ let quote = null;
1071
+ for (const c of value) if (quote !== null) {
1072
+ if (c === quote) quote = null;
1073
+ } else if (c === "\"" || c === "'") quote = c;
1074
+ else if (c === "&") return true;
1075
+ return false;
1076
+ }
1077
+ /**
1078
+ * Synthesize coded diagnostics for registered selector-alias values that
1079
+ * look selector-shaped (`&` present) but carry no substitutable subject —
1080
+ * every `&` sits inside quoted text, so there is nothing to anchor the
1081
+ * class to. Ancestor-prefixed and repeated subjects are supported and pass
1082
+ * validation. The system-config boundary is where these must fail loud
1083
+ * (alias values never reach the evaluator's key guard).
1084
+ */
1085
+ function collectSelectorAliasDiagnostics(selectorAliasesJson) {
1086
+ if (!selectorAliasesJson) return [];
1087
+ let aliases;
1088
+ try {
1089
+ aliases = JSON.parse(selectorAliasesJson);
1090
+ } catch {
1091
+ return [];
1092
+ }
1093
+ const diagnostics = [];
1094
+ for (const [name, value] of Object.entries(aliases)) {
1095
+ if (typeof value !== "string") continue;
1096
+ if (value.includes("&") && !hasSelectorSubject(value)) diagnostics.push({
1097
+ file: "system",
1098
+ component: name,
1099
+ kind: "warn",
1100
+ message: `selector alias '${name}' value '${value}' has no substitutable '&' subject outside quoted text (${SELECTOR_UNSUPPORTED_SUBJECT})`,
1101
+ code: SELECTOR_UNSUPPORTED_SUBJECT,
1102
+ severity: "error"
1103
+ });
1104
+ }
1105
+ return diagnostics;
1106
+ }
990
1107
  /**
991
1108
  * Surface extraction-manifest diagnostics through a plugin's warn channel.
992
1109
  *
993
- * Single authoritative copy for both extraction plugins. Surfaces `bail`
994
- * (component not extracted), `skip` (component skipped), and `warn` kinds;
995
- * unknown kinds stay silent.
1110
+ * Single authoritative copy for both extraction plugins — and the single
1111
+ * strict-escalation policy point: error-severity diagnostics throw one
1112
+ * Error naming every offender when `policy.strict`, and print as warnings
1113
+ * otherwise. Surfaces `bail` (component not extracted), `skip` (component
1114
+ * skipped), and `warn` kinds; unknown kinds stay silent. Printed lines
1115
+ * include the diagnostic code when the message doesn't already carry it.
996
1116
  */
997
- function surfaceManifestDiagnostics(manifest, warn) {
998
- for (const diagnostic of manifest.diagnostics ?? []) if (diagnostic.kind === "bail") warn(`⚠ ${diagnostic.component} not extracted: ${diagnostic.message}`);
999
- else if (diagnostic.kind === "skip") warn(`⚠ ${diagnostic.component}: skipped ${diagnostic.message}`);
1000
- else if (diagnostic.kind === "warn") warn(`⚠ ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`);
1117
+ function surfaceManifestDiagnostics(manifest, warn, policy = {}) {
1118
+ const errors = [];
1119
+ const diagnostics = policy.prepend?.length ? [...policy.prepend, ...manifest.diagnostics ?? []] : manifest.diagnostics ?? [];
1120
+ for (const diagnostic of diagnostics) {
1121
+ let line = null;
1122
+ if (diagnostic.kind === "bail") line = `⚠ ${diagnostic.component} not extracted: ${diagnostic.message}`;
1123
+ else if (diagnostic.kind === "skip") line = `⚠ ${diagnostic.component}: skipped ${diagnostic.message}`;
1124
+ else if (diagnostic.kind === "warn") line = `⚠ ${diagnostic.file}: ${diagnostic.component}: ${diagnostic.message}`;
1125
+ if (line === null) continue;
1126
+ if (diagnostic.code && !diagnostic.message.includes(diagnostic.code)) line += ` [${diagnostic.code}]`;
1127
+ if (policy.strict && diagnostic.severity === "error") {
1128
+ errors.push(`${diagnostic.code ?? "error"} — ${diagnostic.component}: ${diagnostic.message}`);
1129
+ continue;
1130
+ }
1131
+ warn(line);
1132
+ }
1133
+ if (errors.length > 0) throw new Error(`[animus] strict: ${errors.length} error diagnostic(s):\n${errors.join("\n")}`);
1001
1134
  }
1002
1135
  //#endregion
1003
1136
  //#region pipeline/unit-fallback.ts
@@ -1093,7 +1226,10 @@ function runProjectAnalysis(engineApi, opts) {
1093
1226
  const extractMs = Math.round(performance.now() - t);
1094
1227
  t = performance.now();
1095
1228
  const manifest = JSON.parse(manifestJson);
1096
- surfaceManifestDiagnostics(manifest, opts.warn);
1229
+ surfaceManifestDiagnostics(manifest, opts.warn, {
1230
+ strict: opts.strict,
1231
+ prepend: [...collectSelectorAliasDiagnostics(opts.system.selectorAliasesJson), ...opts.extraDiagnostics ?? []]
1232
+ });
1097
1233
  const parseMs = Math.round(performance.now() - t);
1098
1234
  return {
1099
1235
  manifest,
@@ -1559,4 +1695,4 @@ function realpathThroughNearestExistingAncestor(path) {
1559
1695
  }
1560
1696
  }
1561
1697
  //#endregion
1562
- export { ANIMUS_LAYERS, DEFAULT_EXTENSIONS, RETIRED_ENGINE_MESSAGE, applyPrefix, applyUnitFallback, assembleStylesheet, assertNoRetiredEngineSelection, buildAnalysisInputs, buildAnalyzeProjectArgs, buildDynamicPropConfig, buildPathAliasesJson, buildSystemPropsModule, camelToKebab, clearEngineCache, collectExternalPackageSources, contentHash, createV2EngineApi, discoverFiles, enforceExternalTokenContracts, extractSystemFilePackages, findAssetSpecifiers, findPackageRoot, formatRustTimingWaterfall, loadSystemConfig, postProcessCss, preprocessMdx, readTsconfigAliasPairs, resolveAssetFile, resolveLightningTargets, resolveThroughPathAliases, runProjectAnalysis, serializeStaticCss, staleDistIncludesMessage, stripLeadingLayerDeclaration, substituteAssetPlaceholders, surfaceManifestDiagnostics, toWatchKeys, unresolvableIncludesMessage, validateLayerOrder };
1698
+ export { ANIMUS_LAYERS, DEFAULT_EXTENSIONS, KEYFRAMES_EXPORT_COLLISION, KEYFRAMES_EXTERNAL_ENTRY_FAILED, RETIRED_ENGINE_MESSAGE, applyPrefix, applyUnitFallback, assembleStylesheet, assertNoRetiredEngineSelection, buildAnalysisInputs, buildAnalyzeProjectArgs, buildDynamicPropConfig, buildPathAliasesJson, buildSystemPropsModule, camelToKebab, clearEngineCache, collectExternalPackageSources, contentHash, createV2EngineApi, discoverFiles, enforceExternalTokenContracts, extractSystemFilePackages, findAssetSpecifiers, findPackageRoot, formatRustTimingWaterfall, loadSystemConfig, mergeExternalKeyframes, postProcessCss, preprocessMdx, readTsconfigAliasPairs, resolveAssetFile, resolveLightningTargets, resolveThroughPathAliases, runProjectAnalysis, serializeStaticCss, staleDistIncludesMessage, stripLeadingLayerDeclaration, substituteAssetPlaceholders, surfaceManifestDiagnostics, toWatchKeys, unresolvableIncludesMessage, validateLayerOrder };
@@ -6,15 +6,47 @@ export type ManifestDiagnostic = {
6
6
  /** Structured token path (`scale.key`) — present only on
7
7
  * `external-token-candidate` diagnostics (cross-source correlation). */
8
8
  token?: string;
9
+ /** Stable diagnostic code (`animus.<namespace>.<slug>`). */
10
+ code?: string;
11
+ /** `"error"` fails strict builds at this policy point; `"warn"`/absent
12
+ * never does. */
13
+ severity?: string;
9
14
  };
15
+ /** Stable code for selector forms with no substitutable subject.
16
+ * Mirrors the Rust constant in `extract-v2/src/eval.rs`. */
17
+ export declare const SELECTOR_UNSUPPORTED_SUBJECT = "animus.selector.unsupported-subject";
18
+ export interface DiagnosticPolicy {
19
+ /** When true, error-severity diagnostics throw instead of warning. */
20
+ strict?: boolean;
21
+ /** System-level diagnostics (e.g. selector-alias validation) surfaced
22
+ * ahead of the manifest's own, through the same policy. */
23
+ prepend?: ManifestDiagnostic[];
24
+ }
25
+ /** True when the selector string carries at least one substitutable `&`
26
+ * subject outside quoted text (mirrors the Rust `selector_subject` walk —
27
+ * ancestor, leading, and repeated subjects all count; a `&` inside a
28
+ * quoted attribute value does not). */
29
+ export declare function hasSelectorSubject(value: string): boolean;
30
+ /**
31
+ * Synthesize coded diagnostics for registered selector-alias values that
32
+ * look selector-shaped (`&` present) but carry no substitutable subject —
33
+ * every `&` sits inside quoted text, so there is nothing to anchor the
34
+ * class to. Ancestor-prefixed and repeated subjects are supported and pass
35
+ * validation. The system-config boundary is where these must fail loud
36
+ * (alias values never reach the evaluator's key guard).
37
+ */
38
+ export declare function collectSelectorAliasDiagnostics(selectorAliasesJson: string | null | undefined): ManifestDiagnostic[];
10
39
  /**
11
40
  * Surface extraction-manifest diagnostics through a plugin's warn channel.
12
41
  *
13
- * Single authoritative copy for both extraction plugins. Surfaces `bail`
14
- * (component not extracted), `skip` (component skipped), and `warn` kinds;
15
- * unknown kinds stay silent.
42
+ * Single authoritative copy for both extraction plugins — and the single
43
+ * strict-escalation policy point: error-severity diagnostics throw one
44
+ * Error naming every offender when `policy.strict`, and print as warnings
45
+ * otherwise. Surfaces `bail` (component not extracted), `skip` (component
46
+ * skipped), and `warn` kinds; unknown kinds stay silent. Printed lines
47
+ * include the diagnostic code when the message doesn't already carry it.
16
48
  */
17
49
  export declare function surfaceManifestDiagnostics(manifest: {
18
50
  diagnostics?: ManifestDiagnostic[];
19
- }, warn: (message: string) => void): void;
51
+ }, warn: (message: string) => void, policy?: DiagnosticPolicy): void;
20
52
  //# sourceMappingURL=manifest-diagnostics.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-diagnostics.d.ts","sourceRoot":"","sources":["../pipeline/manifest-diagnostics.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;6EACyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE;IAAE,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAA;CAAE,EAChD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,IAAI,CAYN"}
1
+ {"version":3,"file":"manifest-diagnostics.d.ts","sourceRoot":"","sources":["../pipeline/manifest-diagnostics.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;6EACyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;sBACkB;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;6DAC6D;AAC7D,eAAO,MAAM,4BAA4B,wCACF,CAAC;AAExC,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;gEAC4D;IAC5D,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAChC;AAED;;;wCAGwC;AACxC,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAYzD;AAED;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,mBAAmB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7C,kBAAkB,EAAE,CAuBtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE;IAAE,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAA;CAAE,EAChD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAC/B,MAAM,GAAE,gBAAqB,GAC5B,IAAI,CA+BN"}
@@ -42,6 +42,10 @@ export interface AnalysisOptions {
42
42
  /** rootDir-relative external package dirs (external-token candidates). */
43
43
  externalDirs?: string[];
44
44
  devMode: boolean;
45
+ /** System-level diagnostics gathered outside analysis (e.g. external
46
+ * keyframes discovery) — surfaced through the single shared policy
47
+ * point alongside the manifest's own. */
48
+ extraDiagnostics?: import('./manifest-diagnostics').ManifestDiagnostic[];
45
49
  }
46
50
  /**
47
51
  * Build the named `analyzeProject` input set from analysis options. Also
@@ -59,6 +63,7 @@ export declare function buildAnalysisInputs(opts: AnalysisOptions): AnalyzeProje
59
63
  */
60
64
  export declare function runProjectAnalysis(engineApi: () => any, opts: AnalysisOptions & {
61
65
  warn: (message: string) => void;
66
+ strict?: boolean;
62
67
  }): ProjectAnalysisResult;
63
68
  /**
64
69
  * Clear the engine's per-file analysis cache so stale results from a prior
@@ -1 +1 @@
1
- {"version":3,"file":"run-analysis.d.ts","sourceRoot":"","sources":["../pipeline/run-analysis.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAqB;IAEpC,QAAQ,EAAE,GAAG,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB;6EACyE;IACzE,MAAM,EAAE,oBAAoB,CAAC;IAC7B,4DAA4D;IAC5D,OAAO,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACtE;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC;IACvB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,eAAe,GACpB,oBAAoB,CAiCtB;AAQD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAEhC,SAAS,EAAE,MAAM,GAAG,EACpB,IAAI,EAAE,eAAe,GAAG;IAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAC1D,qBAAqB,CA0BvB;AAED;;;;GAIG;AAEH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,IAAI,CAQ3D"}
1
+ {"version":3,"file":"run-analysis.d.ts","sourceRoot":"","sources":["../pipeline/run-analysis.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAqB;IAEpC,QAAQ,EAAE,GAAG,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB;6EACyE;IACzE,MAAM,EAAE,oBAAoB,CAAC;IAC7B,4DAA4D;IAC5D,OAAO,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACtE;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC;IACvB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB;;8CAE0C;IAC1C,gBAAgB,CAAC,EAAE,OAAO,wBAAwB,EAAE,kBAAkB,EAAE,CAAC;CAC1E;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,eAAe,GACpB,oBAAoB,CAiCtB;AAQD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAEhC,SAAS,EAAE,MAAM,GAAG,EACpB,IAAI,EAAE,eAAe,GAAG;IAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5E,qBAAqB,CAgCvB;AAED;;;;GAIG;AAEH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,IAAI,CAQ3D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animus-ui/extract",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Animus static CSS extraction pipeline (Rust/NAPI)",
5
5
  "keywords": [
6
6
  "animus",
@@ -51,12 +51,12 @@
51
51
  "build:v2:debug": "cd crates/extract-v2 && bunx napi build --platform"
52
52
  },
53
53
  "dependencies": {
54
- "@animus-ui/properties": "0.1.5",
54
+ "@animus-ui/properties": "0.1.7",
55
55
  "browserslist": "^4.24.0",
56
56
  "lightningcss": "^1.29.0"
57
57
  },
58
58
  "devDependencies": {
59
- "@animus-ui/system": "0.1.5",
59
+ "@animus-ui/system": "0.1.7",
60
60
  "@mdx-js/mdx": "3.1.1",
61
61
  "@napi-rs/cli": "^3.7.3"
62
62
  },