@design-parity/catalog-export 0.1.20

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,77 @@
1
+ # @design-parity/catalog-export
2
+
3
+ Turn a rendered Compose **component system** into an importable **design-artifact
4
+ catalog** — the code → design-tool direction of design-parity.
5
+
6
+ Where the rest of design-parity proves a PR is at parity with a design, this
7
+ package goes the other way: it takes a whole component system (Compose M3, Wear
8
+ Compose M3, Glimmer, Glance, …), rendered by the upstream
9
+ [`compose-preview`](https://github.com/yschimke/compose-ai-tools) CLI, and lays
10
+ it out as a **sticker sheet** designers can import into Figma, Google Stitch, or
11
+ Claude Design.
12
+
13
+ The pipeline is **code-led**: every value comes from the renderer's own data
14
+ products, so the catalog is correct by construction. Published design kits are
15
+ seed/reference only — see
16
+ [`docs/design-artifacts/REFERENCE_KITS.md`](../../docs/design-artifacts/REFERENCE_KITS.md).
17
+
18
+ ## What it produces
19
+
20
+ For each component, in its primary modes (state × theme × size):
21
+
22
+ - **Two variants** — the `ideal` render (the capture PNG) and the `layout`
23
+ render (the `compose/semantics-wireframe` bordered view, so padding / gaps /
24
+ structure are visible).
25
+ - **A greenline layer** — accessibility annotations anchored to the render:
26
+ *issue* greenlines from the renderer's a11y / contrast / i18n findings, and
27
+ `info` *spec* greenlines documenting each interactive node's role and measured
28
+ touch-target size.
29
+ - **A token set** — the system's resolved `colors` / `typography` / `radius`
30
+ (shapes), exported as a W3C **DTCG** file plus a **Figma variable-collection**
31
+ projection (light/dark as modes).
32
+
33
+ The on-disk bundle is a superset of the `@design-parity/adapter-bundle`
34
+ `manifest.json`, so a catalog round-trips back through the parity flow.
35
+
36
+ ## Use
37
+
38
+ ```ts
39
+ import {
40
+ buildCatalog,
41
+ writeCatalog,
42
+ type ComponentSource,
43
+ } from "@design-parity/catalog-export";
44
+
45
+ // `sources` are normalized via @design-parity/candidate's data-product mappers
46
+ // (nativeFindings, semanticsToSemanticTree, composeThemeToTokens).
47
+ const catalog = buildCatalog(
48
+ { system: "compose-m3", title: "Compose Material 3" },
49
+ sources satisfies ComponentSource[],
50
+ );
51
+
52
+ await writeCatalog(catalog, ".design-artifacts/compose-m3", {
53
+ sourceRoot: "build/compose-previews",
54
+ });
55
+ ```
56
+
57
+ Output:
58
+
59
+ ```
60
+ catalog.json # the index: provenance, components, variants, greenlines
61
+ tokens.dtcg.json # the system token set (W3C DTCG)
62
+ figma-variables.json # Figma variable-collection projection
63
+ images/<component>/<variant>__<state>[__theme][__size].png
64
+ ```
65
+
66
+ ## Layout
67
+
68
+ | Module | Role |
69
+ | --- | --- |
70
+ | `types.ts` | The `Catalog` / `CatalogComponent` / `Greenline` model. |
71
+ | `ingest.ts` | `buildCatalog` / `buildComponent` from normalized inputs. |
72
+ | `greenlines.ts` | Findings + semantics → the greenline annotation layer. |
73
+ | `manifest.ts` | The pure `catalog.json` builder (paths only, no I/O). |
74
+ | `figma.ts` | `DesignTokens` → a Figma variable collection. |
75
+ | `write.ts` | The one I/O step: materialize the bundle to disk. |
76
+
77
+ Depends only on `@design-parity/core`.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Figma variables export — the "Figma second" target.
3
+ *
4
+ * The neutral DTCG file (see `dtcg.ts`) is the portable token export; this is
5
+ * the Figma-shaped projection of the same {@link DesignTokens}, ready to create
6
+ * a local **variable collection** (the structure the Figma plugin / REST
7
+ * `POST variables` endpoint consumes). Colours become `COLOR` variables, corner
8
+ * radii and spacing become `FLOAT` variables; typography is left to text styles
9
+ * (Figma has no composite type variable).
10
+ *
11
+ * Light/dark are expressed as Figma **modes**: a colour token keyed
12
+ * `<name>.<mode>` (the suffix `@design-parity/core` uses for themed palettes)
13
+ * contributes its value under that mode; an un-suffixed token applies to every
14
+ * mode. Pure data — the caller serializes or hands it to a writer.
15
+ */
16
+ import type { DesignTokens } from "@design-parity/core";
17
+ export type FigmaVariableType = "COLOR" | "FLOAT" | "STRING" | "BOOLEAN";
18
+ /** One Figma variable: a value per mode id. */
19
+ export interface FigmaVariable {
20
+ name: string;
21
+ resolvedType: FigmaVariableType;
22
+ /** Value keyed by mode id; a single `"value"` mode when the token isn't themed. */
23
+ valuesByMode: Record<string, string | number>;
24
+ }
25
+ /** A Figma variable collection (a design system) with its modes. */
26
+ export interface FigmaVariableCollection {
27
+ name: string;
28
+ /** Mode id → human mode name (`"light"`, `"dark"`, or the default `"value"`). */
29
+ modes: Record<string, string>;
30
+ defaultModeId: string;
31
+ variables: FigmaVariable[];
32
+ }
33
+ /**
34
+ * Project a {@link DesignTokens} bag onto a {@link FigmaVariableCollection}.
35
+ *
36
+ * @param name collection name (usually the catalog title).
37
+ */
38
+ export declare function toFigmaVariables(tokens: DesignTokens, name: string): FigmaVariableCollection;
39
+ //# sourceMappingURL=figma.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma.d.ts","sourceRoot":"","sources":["../src/figma.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEzE,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,iBAAiB,CAAC;IAChC,mFAAmF;IACnF,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;CAC/C;AAED,oEAAoE;AACpE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAaD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM,GACX,uBAAuB,CAiDzB"}
package/dist/figma.js ADDED
@@ -0,0 +1,68 @@
1
+ const DEFAULT_MODE = "value";
2
+ /** Split a `"name.mode"` token key into its base name and optional mode. */
3
+ function splitMode(key) {
4
+ const dot = key.lastIndexOf(".");
5
+ if (dot <= 0 || dot === key.length - 1)
6
+ return { name: key };
7
+ const mode = key.slice(dot + 1);
8
+ if (mode === "light" || mode === "dark")
9
+ return { name: key.slice(0, dot), mode };
10
+ return { name: key };
11
+ }
12
+ /**
13
+ * Project a {@link DesignTokens} bag onto a {@link FigmaVariableCollection}.
14
+ *
15
+ * @param name collection name (usually the catalog title).
16
+ */
17
+ export function toFigmaVariables(tokens, name) {
18
+ // Discover the modes present across colour keys; default to a single mode.
19
+ const modeIds = new Set();
20
+ for (const key of Object.keys(tokens.colors ?? {})) {
21
+ const { mode } = splitMode(key);
22
+ if (mode)
23
+ modeIds.add(mode);
24
+ }
25
+ const themed = modeIds.size > 0;
26
+ const modes = themed
27
+ ? Object.fromEntries([...modeIds].sort().map((m) => [m, m]))
28
+ : { [DEFAULT_MODE]: "Value" };
29
+ const defaultModeId = themed
30
+ ? (modes["light"] !== undefined ? "light" : [...modeIds].sort()[0])
31
+ : DEFAULT_MODE;
32
+ const allModes = Object.keys(modes);
33
+ // Merge themed colour keys (`name.light`, `name.dark`) into one variable.
34
+ const colorVars = new Map();
35
+ for (const [key, value] of Object.entries(tokens.colors ?? {})) {
36
+ const { name: base, mode } = splitMode(key);
37
+ let variable = colorVars.get(base);
38
+ if (!variable) {
39
+ variable = { name: `color/${base}`, resolvedType: "COLOR", valuesByMode: {} };
40
+ colorVars.set(base, variable);
41
+ }
42
+ if (mode) {
43
+ variable.valuesByMode[mode] = value;
44
+ }
45
+ else {
46
+ for (const m of allModes)
47
+ variable.valuesByMode[m] = value;
48
+ }
49
+ }
50
+ const floatVars = [];
51
+ const addFloats = (bag, prefix) => {
52
+ for (const [key, value] of Object.entries(bag ?? {})) {
53
+ const valuesByMode = {};
54
+ for (const m of allModes)
55
+ valuesByMode[m] = value;
56
+ floatVars.push({ name: `${prefix}/${key}`, resolvedType: "FLOAT", valuesByMode });
57
+ }
58
+ };
59
+ addFloats(tokens.radius, "radius");
60
+ addFloats(tokens.spacing, "spacing");
61
+ return {
62
+ name,
63
+ modes,
64
+ defaultModeId,
65
+ variables: [...colorVars.values(), ...floatVars],
66
+ };
67
+ }
68
+ //# sourceMappingURL=figma.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma.js","sourceRoot":"","sources":["../src/figma.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Build the accessibility **greenline** layer for a component.
3
+ *
4
+ * Two sources feed it:
5
+ *
6
+ * 1. **Issue greenlines** — the renderer's a11y/contrast/i18n {@link Finding}s
7
+ * (from `@design-parity/candidate`'s `nativeFindings`, or
8
+ * `@design-parity/checks`). Each becomes a greenline at the finding's bounds.
9
+ * 2. **Spec greenlines** — `info`-level annotations walked from the
10
+ * {@link SemanticTree}: every interactive node is annotated with its role and
11
+ * measured size, so the sheet documents the touch-target contract even when
12
+ * nothing fails. A node already covered by an issue greenline at the same
13
+ * bounds is not duplicated.
14
+ *
15
+ * Pure functions over core types — no I/O, trivially testable.
16
+ */
17
+ import type { Finding, SemanticTree } from "@design-parity/core";
18
+ import type { Greenline } from "./types.js";
19
+ /**
20
+ * Roles that must meet the touch-target contract. Mirrors
21
+ * `@design-parity/checks` `INTERACTIVE_ROLES` (kept inline so this package
22
+ * depends on `core` only).
23
+ */
24
+ export declare const INTERACTIVE_ROLES: readonly string[];
25
+ /** WCAG 2.5.8 / Material minimum touch target, in dp. */
26
+ export declare const MIN_TOUCH_TARGET_DP = 48;
27
+ /** Map one renderer {@link Finding} to an issue {@link Greenline}. */
28
+ export declare function findingToGreenline(finding: Finding): Greenline;
29
+ /** Map a component's findings to issue greenlines, in finding order. */
30
+ export declare function findingsToGreenlines(findings: readonly Finding[]): Greenline[];
31
+ /**
32
+ * Walk a {@link SemanticTree} and emit a spec greenline for every interactive
33
+ * node — its role, and its bounds when known — at `info` severity. These
34
+ * document the touch-target contract; the writer/board renders them in the same
35
+ * green annotation style as the issue greenlines, just non-blocking.
36
+ */
37
+ export declare function specGreenlines(tree: SemanticTree | undefined): Greenline[];
38
+ /**
39
+ * The full greenline layer for a component: issue greenlines first (they lead),
40
+ * then spec greenlines for interactive nodes not already annotated at the same
41
+ * bounds (so an interactive node that also has a finding isn't listed twice).
42
+ */
43
+ export declare function buildGreenlines(findings: readonly Finding[] | undefined, semantics: SemanticTree | undefined): Greenline[];
44
+ //# sourceMappingURL=greenlines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"greenlines.d.ts","sourceRoot":"","sources":["../src/greenlines.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAEV,OAAO,EAEP,YAAY,EACb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAU9C,CAAC;AAEF,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAkBtC,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAU9D;AAED,wEAAwE;AACxE,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,EAAE,CAE9E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,EAAE,CA0B1E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,EACxC,SAAS,EAAE,YAAY,GAAG,SAAS,GAClC,SAAS,EAAE,CAMb"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Roles that must meet the touch-target contract. Mirrors
3
+ * `@design-parity/checks` `INTERACTIVE_ROLES` (kept inline so this package
4
+ * depends on `core` only).
5
+ */
6
+ export const INTERACTIVE_ROLES = [
7
+ "button",
8
+ "link",
9
+ "switch",
10
+ "checkbox",
11
+ "radio",
12
+ "textfield",
13
+ "menuitem",
14
+ "tab",
15
+ "slider",
16
+ ];
17
+ /** WCAG 2.5.8 / Material minimum touch target, in dp. */
18
+ export const MIN_TOUCH_TARGET_DP = 48;
19
+ function isBounds(v) {
20
+ if (typeof v !== "object" || v === null)
21
+ return false;
22
+ const b = v;
23
+ return (typeof b.x === "number" &&
24
+ typeof b.y === "number" &&
25
+ typeof b.width === "number" &&
26
+ typeof b.height === "number");
27
+ }
28
+ function sameBounds(a, b) {
29
+ if (!a || !b)
30
+ return false;
31
+ return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
32
+ }
33
+ /** Map one renderer {@link Finding} to an issue {@link Greenline}. */
34
+ export function findingToGreenline(finding) {
35
+ const out = {
36
+ kind: finding.kind,
37
+ severity: finding.severity,
38
+ message: finding.message,
39
+ };
40
+ const bounds = finding.detail?.["bounds"];
41
+ if (isBounds(bounds))
42
+ out.bounds = bounds;
43
+ if (finding.detail)
44
+ out.detail = finding.detail;
45
+ return out;
46
+ }
47
+ /** Map a component's findings to issue greenlines, in finding order. */
48
+ export function findingsToGreenlines(findings) {
49
+ return findings.map(findingToGreenline);
50
+ }
51
+ /**
52
+ * Walk a {@link SemanticTree} and emit a spec greenline for every interactive
53
+ * node — its role, and its bounds when known — at `info` severity. These
54
+ * document the touch-target contract; the writer/board renders them in the same
55
+ * green annotation style as the issue greenlines, just non-blocking.
56
+ */
57
+ export function specGreenlines(tree) {
58
+ if (!tree)
59
+ return [];
60
+ const out = [];
61
+ const visit = (node) => {
62
+ const role = node.role?.toLowerCase();
63
+ if (role && INTERACTIVE_ROLES.includes(role)) {
64
+ const detail = { role };
65
+ if (node.label !== undefined)
66
+ detail["label"] = node.label;
67
+ const g = {
68
+ kind: "a11y",
69
+ severity: "info",
70
+ message: node.label
71
+ ? `${role} "${node.label}"`
72
+ : `${role}`,
73
+ detail,
74
+ };
75
+ if (node.bounds) {
76
+ g.bounds = node.bounds;
77
+ detail["bounds"] = node.bounds;
78
+ }
79
+ out.push(g);
80
+ }
81
+ for (const child of node.children ?? [])
82
+ visit(child);
83
+ };
84
+ visit(tree.root);
85
+ return out;
86
+ }
87
+ /**
88
+ * The full greenline layer for a component: issue greenlines first (they lead),
89
+ * then spec greenlines for interactive nodes not already annotated at the same
90
+ * bounds (so an interactive node that also has a finding isn't listed twice).
91
+ */
92
+ export function buildGreenlines(findings, semantics) {
93
+ const issues = findingsToGreenlines(findings ?? []);
94
+ const specs = specGreenlines(semantics).filter((spec) => !issues.some((issue) => sameBounds(issue.bounds, spec.bounds)));
95
+ return [...issues, ...specs];
96
+ }
97
+ //# sourceMappingURL=greenlines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"greenlines.js","sourceRoot":"","sources":["../src/greenlines.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `@design-parity/catalog-export` — code → importable design artifacts.
3
+ *
4
+ * Turns a rendered Compose component system into a sticker-sheet **catalog**:
5
+ * per-component renders in two variants (ideal + layout), a W3C DTCG token set,
6
+ * a Figma variable-collection projection, and an accessibility **greenline**
7
+ * annotation layer — laid out on disk for Figma / Stitch / Claude Design import.
8
+ *
9
+ * The pipeline is code-led: every value comes from the renderer's own data
10
+ * products (via `@design-parity/candidate`'s mappers), so the catalog is correct
11
+ * by construction. Published design kits are seed/reference only (see
12
+ * `docs/design-artifacts/REFERENCE_KITS.md`). Depends only on
13
+ * `@design-parity/core`.
14
+ */
15
+ export type { Catalog, CatalogComponent, CatalogMeta, ComponentReference, ComponentVariants, Greenline, } from "./types.js";
16
+ export { buildCatalog, buildComponent, } from "./ingest.js";
17
+ export type { ComponentSource } from "./ingest.js";
18
+ export { INTERACTIVE_ROLES, MIN_TOUCH_TARGET_DP, buildGreenlines, findingToGreenline, findingsToGreenlines, specGreenlines, } from "./greenlines.js";
19
+ export { imagePath, slug, toCatalogManifest, } from "./manifest.js";
20
+ export type { CatalogManifest, CatalogManifestComponent, CatalogManifestImage, ManifestOptions, VariantKind, } from "./manifest.js";
21
+ export { toFigmaVariables } from "./figma.js";
22
+ export type { FigmaVariable, FigmaVariableCollection, FigmaVariableType, } from "./figma.js";
23
+ export { writeCatalog } from "./write.js";
24
+ export type { WriteOptions, WriteResult } from "./write.js";
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,YAAY,EACV,OAAO,EACP,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,YAAY,EACZ,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,SAAS,EACT,IAAI,EACJ,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,eAAe,EACf,wBAAwB,EACxB,oBAAoB,EACpB,eAAe,EACf,WAAW,GACZ,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EACV,aAAa,EACb,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { buildCatalog, buildComponent, } from "./ingest.js";
2
+ export { INTERACTIVE_ROLES, MIN_TOUCH_TARGET_DP, buildGreenlines, findingToGreenline, findingsToGreenlines, specGreenlines, } from "./greenlines.js";
3
+ export { imagePath, slug, toCatalogManifest, } from "./manifest.js";
4
+ export { toFigmaVariables } from "./figma.js";
5
+ export { writeCatalog } from "./write.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAuBA,OAAO,EACL,YAAY,EACZ,cAAc,GACf,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,SAAS,EACT,IAAI,EACJ,iBAAiB,GAClB,MAAM,eAAe,CAAC;AASvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAO9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Build a {@link Catalog} from normalized, per-component inputs.
3
+ *
4
+ * The heavy lifting — turning raw `compose-preview` data products into
5
+ * {@link Finding}s, a {@link SemanticTree}, and {@link DesignTokens} — is the job
6
+ * of `@design-parity/candidate` (`nativeFindings`, `semanticsToSemanticTree`,
7
+ * `composeThemeToTokens`). This module takes those already-normalized pieces and
8
+ * assembles the catalog: it attaches each component's greenline layer and, when
9
+ * the catalog's system token set isn't supplied, lifts it from the richest
10
+ * component semantics' `themeTokens`. Pure — no I/O, no renderer.
11
+ */
12
+ import type { DesignTokens, Finding, Image, SemanticTree } from "@design-parity/core";
13
+ import type { Catalog, CatalogComponent, CatalogMeta, ComponentReference } from "./types.js";
14
+ /** Normalized input for one component (the output of candidate's mappers). */
15
+ export interface ComponentSource {
16
+ componentId: string;
17
+ group?: string;
18
+ caption?: string;
19
+ reference?: ComponentReference;
20
+ /** Ideal capture image(s) — one per state/theme/size. */
21
+ ideal: Image[];
22
+ /** Layout/wireframe image(s) from `compose/semantics-wireframe`. */
23
+ layout?: Image[];
24
+ /** Per-component resolved tokens (padding / radius / type used). */
25
+ tokens?: DesignTokens;
26
+ /** Component semantic tree (carries bounds + per-node tokens + themeTokens). */
27
+ semantics?: SemanticTree;
28
+ /** Renderer-native a11y / contrast / i18n findings → issue greenlines. */
29
+ findings?: Finding[];
30
+ }
31
+ /** Assemble one {@link CatalogComponent}, computing its greenline layer. */
32
+ export declare function buildComponent(source: ComponentSource): CatalogComponent;
33
+ /**
34
+ * Assemble a whole {@link Catalog}. When `themeTokens` is omitted, it is lifted
35
+ * from the first component whose semantics carry `themeTokens` (the resolved
36
+ * `compose/theme` of the system) — so the system token set comes from the code,
37
+ * not a hand-maintained list.
38
+ */
39
+ export declare function buildCatalog(meta: CatalogMeta, sources: readonly ComponentSource[], themeTokens?: DesignTokens): Catalog;
40
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../src/ingest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EACV,YAAY,EACZ,OAAO,EACP,KAAK,EACL,YAAY,EACb,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EACV,OAAO,EACP,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,yDAAyD;IACzD,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,oEAAoE;IACpE,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,oEAAoE;IACpE,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,gFAAgF;IAChF,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CAmBxE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,WAAW,CAAC,EAAE,YAAY,GACzB,OAAO,CAQT"}
package/dist/ingest.js ADDED
@@ -0,0 +1,41 @@
1
+ import { buildGreenlines } from "./greenlines.js";
2
+ /** Assemble one {@link CatalogComponent}, computing its greenline layer. */
3
+ export function buildComponent(source) {
4
+ if (source.ideal.length === 0) {
5
+ throw new Error(`catalog-export: component '${source.componentId}' has no ideal images`);
6
+ }
7
+ const component = {
8
+ componentId: source.componentId,
9
+ variants: source.layout
10
+ ? { ideal: source.ideal, layout: source.layout }
11
+ : { ideal: source.ideal },
12
+ greenlines: buildGreenlines(source.findings, source.semantics),
13
+ };
14
+ if (source.group !== undefined)
15
+ component.group = source.group;
16
+ if (source.caption !== undefined)
17
+ component.caption = source.caption;
18
+ if (source.reference !== undefined)
19
+ component.reference = source.reference;
20
+ if (source.tokens !== undefined)
21
+ component.tokens = source.tokens;
22
+ if (source.semantics !== undefined)
23
+ component.semantics = source.semantics;
24
+ return component;
25
+ }
26
+ /**
27
+ * Assemble a whole {@link Catalog}. When `themeTokens` is omitted, it is lifted
28
+ * from the first component whose semantics carry `themeTokens` (the resolved
29
+ * `compose/theme` of the system) — so the system token set comes from the code,
30
+ * not a hand-maintained list.
31
+ */
32
+ export function buildCatalog(meta, sources, themeTokens) {
33
+ const components = sources.map(buildComponent);
34
+ const resolvedTokens = themeTokens ??
35
+ components.find((c) => c.semantics?.themeTokens)?.semantics?.themeTokens;
36
+ const catalog = { meta, components };
37
+ if (resolvedTokens)
38
+ catalog.themeTokens = resolvedTokens;
39
+ return catalog;
40
+ }
41
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../src/ingest.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AA0BlD,4EAA4E;AAC5E,MAAM,UAAU,cAAc,CAAC,MAAuB;IACpD,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,8BAA8B,MAAM,CAAC,WAAW,uBAAuB,CACxE,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAqB;QAClC,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM;YACrB,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;YAChD,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE;QAC3B,UAAU,EAAE,eAAe,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC;KAC/D,CAAC;IACF,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,SAAS,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC/D,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;QAAE,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IACrE,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;QAAE,SAAS,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IAC3E,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;QAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAClE,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;QAAE,SAAS,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IAC3E,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAiB,EACjB,OAAmC,EACnC,WAA0B;IAE1B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC/C,MAAM,cAAc,GAClB,WAAW;QACX,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC;IAC3E,MAAM,OAAO,GAAY,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAC9C,IAAI,cAAc;QAAE,OAAO,CAAC,WAAW,GAAG,cAAc,CAAC;IACzD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The importable **catalog manifest** — `catalog.json`.
3
+ *
4
+ * This is the index a design tool reads: provenance, a pointer to the DTCG
5
+ * token file, and one entry per component carrying every variant image (ideal +
6
+ * layout) with a stable bundle-relative path, the component's resolved tokens,
7
+ * its greenline layer, and its seed-kit reference. It is a superset of the
8
+ * `@design-parity/adapter-bundle` `manifest.json` shape (same `path` / `state` /
9
+ * `theme` / `size` image keys) so a catalog component is also a valid parity
10
+ * bundle — the same artifact round-trips back through the parity flow.
11
+ *
12
+ * {@link toCatalogManifest} is **pure** (it only computes paths, no I/O); the
13
+ * {@link writeCatalog} step materializes the bytes those paths point at.
14
+ */
15
+ import type { DesignTokens, Image, Theme } from "@design-parity/core";
16
+ import type { Catalog, ComponentReference, Greenline } from "./types.js";
17
+ /** Which sticker-sheet variant an image belongs to. */
18
+ export type VariantKind = "ideal" | "layout";
19
+ /** One image entry in the manifest — bundle-relative path + variant keys. */
20
+ export interface CatalogManifestImage {
21
+ variant: VariantKind;
22
+ /** Bundle-relative PNG path (forward slashes). */
23
+ path: string;
24
+ state: string;
25
+ theme?: Theme;
26
+ size?: string;
27
+ width: number;
28
+ height: number;
29
+ }
30
+ /** One component entry in the manifest. */
31
+ export interface CatalogManifestComponent {
32
+ componentId: string;
33
+ group?: string;
34
+ caption?: string;
35
+ reference?: ComponentReference;
36
+ images: CatalogManifestImage[];
37
+ tokens?: DesignTokens;
38
+ greenlines: Greenline[];
39
+ }
40
+ /** The parsed/serializable `catalog.json`. */
41
+ export interface CatalogManifest {
42
+ schema: "design-parity-catalog/v1";
43
+ system: string;
44
+ title: string;
45
+ library?: string[];
46
+ renderer?: string;
47
+ generatedAt?: string;
48
+ /** Bundle-relative path to the DTCG token file, when tokens were exported. */
49
+ tokensFile?: string;
50
+ components: CatalogManifestComponent[];
51
+ }
52
+ export interface ManifestOptions {
53
+ /** Bundle-relative DTCG token filename. Default `"tokens.dtcg.json"`. */
54
+ tokensFile?: string;
55
+ }
56
+ /** Filesystem-safe slug for a component id / variant key segment. */
57
+ export declare function slug(value: string): string;
58
+ /**
59
+ * Bundle-relative path for one variant image of a component. Encodes the
60
+ * component id and every variant key so distinct states/themes/sizes never
61
+ * collide:
62
+ * `images/<component>/<variant>__<state>[__<theme>][__<size>].png`.
63
+ */
64
+ export declare function imagePath(componentId: string, variant: VariantKind, image: Image): string;
65
+ /** Build the serializable {@link CatalogManifest} for a {@link Catalog} (pure). */
66
+ export declare function toCatalogManifest(catalog: Catalog, opts?: ManifestOptions): CatalogManifest;
67
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEtE,OAAO,KAAK,EAAE,OAAO,EAAoB,kBAAkB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE3F,uDAAuD;AACvD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE7C,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,WAAW,CAAC;IACrB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,2CAA2C;AAC3C,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAED,8CAA8C;AAC9C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,0BAA0B,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,wBAAwB,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,qEAAqE;AACrE,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO1C;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,KAAK,GACX,MAAM,CAMR;AAoCD,mFAAmF;AACnF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,EAChB,IAAI,GAAE,eAAoB,GACzB,eAAe,CAcjB"}
@@ -0,0 +1,81 @@
1
+ const DEFAULT_TOKENS_FILE = "tokens.dtcg.json";
2
+ /** Filesystem-safe slug for a component id / variant key segment. */
3
+ export function slug(value) {
4
+ return (value
5
+ .replace(/[^a-zA-Z0-9._-]+/g, "-")
6
+ .replace(/^-+|-+$/g, "")
7
+ .toLowerCase() || "x");
8
+ }
9
+ /**
10
+ * Bundle-relative path for one variant image of a component. Encodes the
11
+ * component id and every variant key so distinct states/themes/sizes never
12
+ * collide:
13
+ * `images/<component>/<variant>__<state>[__<theme>][__<size>].png`.
14
+ */
15
+ export function imagePath(componentId, variant, image) {
16
+ const parts = [variant, image.state];
17
+ if (image.theme)
18
+ parts.push(image.theme);
19
+ if (image.size)
20
+ parts.push(image.size);
21
+ const file = parts.map(slug).join("__");
22
+ return `images/${slug(componentId)}/${file}.png`;
23
+ }
24
+ function manifestImages(component) {
25
+ const out = [];
26
+ const push = (variant, images) => {
27
+ for (const image of images ?? []) {
28
+ const entry = {
29
+ variant,
30
+ path: imagePath(component.componentId, variant, image),
31
+ state: image.state,
32
+ width: image.width,
33
+ height: image.height,
34
+ };
35
+ if (image.theme)
36
+ entry.theme = image.theme;
37
+ if (image.size)
38
+ entry.size = image.size;
39
+ out.push(entry);
40
+ }
41
+ };
42
+ push("ideal", component.variants.ideal);
43
+ push("layout", component.variants.layout);
44
+ return out;
45
+ }
46
+ function manifestComponent(component) {
47
+ const out = {
48
+ componentId: component.componentId,
49
+ images: manifestImages(component),
50
+ greenlines: component.greenlines,
51
+ };
52
+ if (component.group !== undefined)
53
+ out.group = component.group;
54
+ if (component.caption !== undefined)
55
+ out.caption = component.caption;
56
+ if (component.reference !== undefined)
57
+ out.reference = component.reference;
58
+ if (component.tokens !== undefined)
59
+ out.tokens = component.tokens;
60
+ return out;
61
+ }
62
+ /** Build the serializable {@link CatalogManifest} for a {@link Catalog} (pure). */
63
+ export function toCatalogManifest(catalog, opts = {}) {
64
+ const manifest = {
65
+ schema: "design-parity-catalog/v1",
66
+ system: catalog.meta.system,
67
+ title: catalog.meta.title,
68
+ components: catalog.components.map(manifestComponent),
69
+ };
70
+ if (catalog.meta.library)
71
+ manifest.library = catalog.meta.library;
72
+ if (catalog.meta.renderer)
73
+ manifest.renderer = catalog.meta.renderer;
74
+ if (catalog.meta.generatedAt)
75
+ manifest.generatedAt = catalog.meta.generatedAt;
76
+ if (catalog.themeTokens) {
77
+ manifest.tokensFile = opts.tokensFile ?? DEFAULT_TOKENS_FILE;
78
+ }
79
+ return manifest;
80
+ }
81
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The catalog model — a design system rendered into importable design artifacts.
3
+ *
4
+ * A {@link Catalog} is the code → design-tool counterpart of the parity flow: a
5
+ * component system (Compose M3, Wear M3, Glimmer, Glance, …) is rendered by the
6
+ * upstream `compose-preview` CLI, and its data products (captures,
7
+ * `compose/semantics-wireframe`, `compose/theme`, the a11y findings) are folded
8
+ * into a sticker-sheet catalog that exports as a {@link CatalogManifest} + a W3C
9
+ * DTCG token file + a per-component **greenline** (accessibility annotation)
10
+ * layer. Every type here is source-agnostic — it carries normalized
11
+ * {@link DesignTokens}, {@link Image}, {@link SemanticTree}, and {@link Finding}
12
+ * from `@design-parity/core`, so the export is the same whatever rendered it.
13
+ */
14
+ import type { Bounds, DesignTokens, FindingKind, Image, SemanticTree, Severity } from "@design-parity/core";
15
+ /** Provenance for a catalog: which system, library, and renderer produced it. */
16
+ export interface CatalogMeta {
17
+ /** Stable system id, kebab-case, e.g. `"compose-m3"`. */
18
+ system: string;
19
+ /** Human title, e.g. `"Compose Material 3"`. */
20
+ title: string;
21
+ /** Library coordinate(s) rendered, e.g. `["androidx.compose.material3:material3"]`. */
22
+ library?: string[];
23
+ /** `compose-preview` CLI version used, for provenance. */
24
+ renderer?: string;
25
+ /** ISO-8601 timestamp the catalog was generated, for provenance. */
26
+ generatedAt?: string;
27
+ }
28
+ /**
29
+ * The two rendered variants the sticker sheet shows for each component:
30
+ *
31
+ * - `ideal` — the component as shipped (the capture PNGs), one image per
32
+ * state / theme / size.
33
+ * - `layout` — the same component with every composable bounded, from the
34
+ * renderer's `compose/semantics-wireframe` product, so a designer sees the
35
+ * padding / gaps / structure behind the pixels.
36
+ */
37
+ export interface ComponentVariants {
38
+ ideal: Image[];
39
+ layout?: Image[];
40
+ }
41
+ /**
42
+ * One accessibility **greenline**: an annotation anchored (when bounds are
43
+ * known) to a region of the ideal render. Issue greenlines come from the
44
+ * renderer's a11y findings (touch target below minimum, contrast fail, missing
45
+ * label, text overflow); spec greenlines (`info`) annotate interactive nodes
46
+ * with their role and measured size so the sheet documents the a11y contract
47
+ * even when nothing is wrong.
48
+ */
49
+ export interface Greenline {
50
+ kind: FindingKind;
51
+ severity: Severity;
52
+ /** One-line, human-readable. */
53
+ message: string;
54
+ /** Region in the ideal render's pixel space, when the finding/node carries one. */
55
+ bounds?: Bounds;
56
+ /** Structured payload (measured size, ratio, role, …) for machine consumers. */
57
+ detail?: Record<string, unknown>;
58
+ }
59
+ /**
60
+ * A link to the component's published-kit reference frame, for the **one-off
61
+ * seed import** only. The catalog is code-led: this reference exists so the
62
+ * import lines our authoritative render up with the kit's frame (name,
63
+ * position), never to override our values. A divergence is a bug in the kit.
64
+ */
65
+ export interface ComponentReference {
66
+ /** Design source the reference frame lives in (usually `"figma"`). */
67
+ source?: string;
68
+ /** Source handle, e.g. `"figma:<fileKey>/<nodeId>"`. */
69
+ ref?: string;
70
+ /** Human URL to the kit / frame. */
71
+ url?: string;
72
+ }
73
+ /** One component on the sticker sheet, in its primary modes. */
74
+ export interface CatalogComponent {
75
+ /** Stable component id, e.g. `"Button/Filled"`. */
76
+ componentId: string;
77
+ /** Human group for sheet layout, e.g. `"Buttons"`. */
78
+ group?: string;
79
+ /** Optional one-line intent / usage note. */
80
+ caption?: string;
81
+ /** Published-kit reference for the seed import (code stays authoritative). */
82
+ reference?: ComponentReference;
83
+ variants: ComponentVariants;
84
+ /** Per-component resolved tokens (the padding / radius / type actually used). */
85
+ tokens?: DesignTokens;
86
+ /** Accessibility annotation layer for this component. */
87
+ greenlines: Greenline[];
88
+ /**
89
+ * The component's semantic tree — bounds, roles, and per-node tokens — so the
90
+ * layout is identified, not just pictured. Optional; absent for hand-authored
91
+ * components that carry only images.
92
+ */
93
+ semantics?: SemanticTree;
94
+ }
95
+ /** A whole design-system catalog: provenance + token set + components. */
96
+ export interface Catalog {
97
+ meta: CatalogMeta;
98
+ /**
99
+ * The design system's resolved token set — the full `colors` palette,
100
+ * `typography` scale, and corner `radius` (shapes) — keyed by code token name
101
+ * (`onSurface`, `bodyLarge`, `medium`). Exported as the catalog's DTCG file.
102
+ */
103
+ themeTokens?: DesignTokens;
104
+ components: CatalogComponent[];
105
+ }
106
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EACZ,WAAW,EACX,KAAK,EACL,YAAY,EACZ,QAAQ,EACT,MAAM,qBAAqB,CAAC;AAE7B,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,QAAQ,CAAC;IACnB,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,gEAAgE;AAChE,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,iFAAiF;IACjF,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,yDAAyD;IACzD,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC;CAC1B;AAED,0EAA0E;AAC1E,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,gBAAgB,EAAE,CAAC;CAChC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import { type ManifestOptions } from "./manifest.js";
2
+ import type { Catalog } from "./types.js";
3
+ export interface WriteOptions extends ManifestOptions {
4
+ /** Root that relative image `uri`s resolve against. Default: `process.cwd()`. */
5
+ sourceRoot?: string;
6
+ /** Also emit the Figma variable-collection projection. Default `true`. */
7
+ figmaVariables?: boolean;
8
+ /** Pretty-print JSON with this indent. Default `2`. */
9
+ indent?: number;
10
+ }
11
+ export interface WriteResult {
12
+ /** Absolute path to the written `catalog.json`. */
13
+ manifestPath: string;
14
+ /** Absolute path to the DTCG token file, when tokens were exported. */
15
+ tokensPath?: string;
16
+ /** Absolute path to the Figma variables file, when written. */
17
+ figmaPath?: string;
18
+ /** Number of image files written. */
19
+ imageCount: number;
20
+ }
21
+ /** Write the full importable catalog bundle to `outDir`. */
22
+ export declare function writeCatalog(catalog: Catalog, outDir: string, opts?: WriteOptions): Promise<WriteResult>;
23
+ //# sourceMappingURL=write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../src/write.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAgC,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACnF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,YAAa,SAAQ,eAAe;IACnD,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;CACpB;AA2BD,4DAA4D;AAC5D,wBAAsB,YAAY,CAChC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,WAAW,CAAC,CA8CtB"}
package/dist/write.js ADDED
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Materialize a {@link Catalog} into an importable on-disk bundle.
3
+ *
4
+ * Layout written under `outDir`:
5
+ *
6
+ * ```
7
+ * catalog.json # the CatalogManifest index
8
+ * tokens.dtcg.json # the system token set, W3C DTCG (when present)
9
+ * figma-variables.json # the Figma variable-collection projection (when present)
10
+ * images/<component>/<variant>__<state>[__theme][__size].png
11
+ * ```
12
+ *
13
+ * Image bytes are resolved from each {@link Image.uri}: a `data:` URI is decoded
14
+ * inline; any other value is read as a path (absolute, or relative to
15
+ * `sourceRoot`). This is the only module in the package that does I/O — the
16
+ * manifest, token, and Figma projections it writes are all built by the pure
17
+ * functions in the sibling modules.
18
+ */
19
+ import { mkdir, writeFile } from "node:fs/promises";
20
+ import { readFile } from "node:fs/promises";
21
+ import { dirname, isAbsolute, join, resolve } from "node:path";
22
+ import { tokensToDtcg } from "@design-parity/core";
23
+ import { toFigmaVariables } from "./figma.js";
24
+ import { imagePath, toCatalogManifest } from "./manifest.js";
25
+ const DATA_URI = /^data:([^;,]*)?(;base64)?,(.*)$/s;
26
+ /** Resolve the bytes for an {@link Image}: inline `data:` URI or a file path. */
27
+ async function imageBytes(image, sourceRoot) {
28
+ const match = DATA_URI.exec(image.uri);
29
+ if (match) {
30
+ const isBase64 = match[2] === ";base64";
31
+ const data = match[3] ?? "";
32
+ return isBase64
33
+ ? Buffer.from(data, "base64")
34
+ : Buffer.from(decodeURIComponent(data), "utf8");
35
+ }
36
+ const path = isAbsolute(image.uri) ? image.uri : resolve(sourceRoot, image.uri);
37
+ return readFile(path);
38
+ }
39
+ async function writeJson(path, value, indent) {
40
+ await mkdir(dirname(path), { recursive: true });
41
+ await writeFile(path, `${JSON.stringify(value, null, indent)}\n`, "utf8");
42
+ }
43
+ /** Write the full importable catalog bundle to `outDir`. */
44
+ export async function writeCatalog(catalog, outDir, opts = {}) {
45
+ const sourceRoot = opts.sourceRoot ?? process.cwd();
46
+ const indent = opts.indent ?? 2;
47
+ const out = resolve(outDir);
48
+ await mkdir(out, { recursive: true });
49
+ const manifest = toCatalogManifest(catalog, opts);
50
+ const manifestPath = join(out, "catalog.json");
51
+ await writeJson(manifestPath, manifest, indent);
52
+ const result = { manifestPath, imageCount: 0 };
53
+ if (catalog.themeTokens && manifest.tokensFile) {
54
+ const tokensPath = join(out, manifest.tokensFile);
55
+ await writeJson(tokensPath, tokensToDtcg(catalog.themeTokens), indent);
56
+ result.tokensPath = tokensPath;
57
+ if (opts.figmaVariables !== false) {
58
+ const figmaPath = join(out, "figma-variables.json");
59
+ await writeJson(figmaPath, toFigmaVariables(catalog.themeTokens, catalog.meta.title), indent);
60
+ result.figmaPath = figmaPath;
61
+ }
62
+ }
63
+ // Image bytes, keyed by the same paths the manifest computed.
64
+ for (const component of catalog.components) {
65
+ const variants = [
66
+ ["ideal", component.variants.ideal],
67
+ ["layout", component.variants.layout],
68
+ ];
69
+ for (const [variant, images] of variants) {
70
+ for (const image of images ?? []) {
71
+ const rel = imagePath(component.componentId, variant, image);
72
+ const dest = join(out, rel);
73
+ await mkdir(dirname(dest), { recursive: true });
74
+ await writeFile(dest, await imageBytes(image, sourceRoot));
75
+ result.imageCount += 1;
76
+ }
77
+ }
78
+ }
79
+ return result;
80
+ }
81
+ //# sourceMappingURL=write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.js","sourceRoot":"","sources":["../src/write.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@design-parity/catalog-export",
3
+ "version": "0.1.20",
4
+ "description": "Turn a rendered Compose component system (compose-preview data products) into an importable design-artifact catalog: per-component sticker sheets in two variants (ideal + layout), a W3C DTCG token set, and an accessibility 'greenline' annotation layer — laid out for Figma / Stitch / Claude Design import.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": "./dist/index.js"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc --build",
17
+ "test": "vitest run"
18
+ },
19
+ "dependencies": {
20
+ "@design-parity/core": "^0.1.20"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/yschimke/design-parity.git",
25
+ "directory": "packages/catalog-export"
26
+ },
27
+ "homepage": "https://github.com/yschimke/design-parity/tree/main/packages/catalog-export#readme",
28
+ "bugs": "https://github.com/yschimke/design-parity/issues"
29
+ }