design-constraint-validator 1.0.0 → 1.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/LICENSE +21 -21
- package/README.md +215 -659
- package/adapters/README.md +46 -46
- package/adapters/css.ts +116 -116
- package/adapters/js.ts +14 -14
- package/adapters/json.ts +45 -45
- package/cli/build-css.ts +32 -32
- package/cli/commands/build.ts +65 -65
- package/cli/commands/graph.d.ts.map +1 -1
- package/cli/commands/graph.js +26 -10
- package/cli/commands/graph.ts +180 -137
- package/cli/commands/index.ts +7 -7
- package/cli/commands/patch-apply.ts +80 -80
- package/cli/commands/patch.ts +22 -22
- package/cli/commands/set.d.ts.map +1 -1
- package/cli/commands/set.js +12 -4
- package/cli/commands/set.ts +239 -225
- package/cli/commands/utils.ts +50 -50
- package/cli/commands/validate.d.ts.map +1 -1
- package/cli/commands/validate.js +86 -33
- package/cli/commands/validate.ts +176 -115
- package/cli/commands/why.d.ts.map +1 -1
- package/cli/commands/why.js +86 -20
- package/cli/commands/why.ts +158 -46
- package/cli/config-schema.ts +27 -27
- package/cli/config.ts +35 -35
- package/cli/constraint-registry.d.ts +101 -0
- package/cli/constraint-registry.d.ts.map +1 -0
- package/cli/constraint-registry.js +225 -0
- package/cli/constraint-registry.ts +304 -0
- package/cli/constraints-loader.d.ts +30 -0
- package/cli/constraints-loader.d.ts.map +1 -0
- package/cli/constraints-loader.js +58 -0
- package/cli/constraints-loader.ts +83 -0
- package/cli/cross-axis-loader.d.ts +91 -0
- package/cli/cross-axis-loader.d.ts.map +1 -0
- package/cli/cross-axis-loader.js +222 -0
- package/cli/cross-axis-loader.ts +289 -0
- package/cli/dcv.js +4 -0
- package/cli/dcv.ts +111 -107
- package/cli/engine-helpers.d.ts +33 -0
- package/cli/engine-helpers.d.ts.map +1 -1
- package/cli/engine-helpers.js +87 -22
- package/cli/engine-helpers.ts +133 -61
- package/cli/graph-poset.ts +74 -74
- package/cli/json-output.d.ts +64 -0
- package/cli/json-output.d.ts.map +1 -0
- package/cli/json-output.js +107 -0
- package/cli/json-output.ts +177 -0
- package/cli/result.ts +27 -27
- package/cli/run.ts +54 -54
- package/cli/smoke-test.ts +40 -40
- package/cli/types.d.ts +6 -0
- package/cli/types.d.ts.map +1 -1
- package/cli/types.ts +84 -78
- package/core/breakpoints.ts +50 -50
- package/core/cli-format.ts +31 -31
- package/core/color.ts +148 -148
- package/core/constraints/cross-axis.ts +114 -114
- package/core/constraints/monotonic-lightness.ts +38 -38
- package/core/constraints/monotonic.ts +74 -74
- package/core/constraints/threshold.ts +43 -43
- package/core/constraints/wcag.ts +70 -70
- package/core/cross-axis-config.d.ts +29 -0
- package/core/cross-axis-config.d.ts.map +1 -1
- package/core/cross-axis-config.js +29 -0
- package/core/cross-axis-config.ts +181 -151
- package/core/engine.d.ts +95 -0
- package/core/engine.d.ts.map +1 -1
- package/core/engine.js +22 -0
- package/core/engine.ts +167 -65
- package/core/flatten.ts +116 -116
- package/core/image-export.ts +48 -48
- package/core/index.d.ts +9 -30
- package/core/index.d.ts.map +1 -1
- package/core/index.js +7 -54
- package/core/index.ts +10 -72
- package/core/patch.ts +134 -134
- package/core/poset.ts +311 -311
- package/core/why.ts +63 -63
- package/package.json +96 -90
- package/themes/color.lg.order.json +15 -15
- package/themes/color.md.order.json +15 -15
- package/themes/color.order.json +15 -15
- package/themes/color.sm.order.json +15 -15
- package/themes/cross-axis.rules.json +35 -35
- package/themes/cross-axis.sm.rules.json +12 -12
- package/themes/layout.lg.order.json +18 -18
- package/themes/layout.md.order.json +18 -18
- package/themes/layout.order.json +18 -18
- package/themes/layout.sm.order.json +18 -18
- package/themes/spacing.order.json +14 -14
- package/themes/typography.lg.order.json +15 -15
- package/themes/typography.md.order.json +15 -15
- package/themes/typography.order.json +15 -15
- package/themes/typography.sm.order.json +15 -15
- package/dist/test-overrides-removal.json +0 -4
- package/dist/tmp.patch.json +0 -35
- package/tokens/overrides/base.json +0 -22
- package/tokens/overrides/lg.json +0 -20
- package/tokens/overrides/md.json +0 -16
- package/tokens/overrides/sm.json +0 -16
- package/tokens/overrides/viol.color.json +0 -6
- package/tokens/overrides/viol.typography.json +0 -6
- package/tokens/tokens.demo-violations.json +0 -116
- package/tokens/tokens.example.json +0 -128
- package/tokens/tokens.json +0 -67
- package/tokens/tokens.multi-violations.json +0 -21
- package/tokens/tokens.schema.d.ts +0 -2298
- package/tokens/tokens.schema.d.ts.map +0 -1
- package/tokens/tokens.schema.js +0 -148
- package/tokens/tokens.schema.ts +0 -196
- package/tokens/tokens.test.json +0 -38
- package/tokens/tokens.touch-violation.json +0 -8
- package/tokens/typography.classes.css +0 -11
- package/tokens/typography.css +0 -20
package/adapters/README.md
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
# Adapters
|
|
2
|
-
|
|
3
|
-
DCV supports multiple **input shapes** via lightweight adapters that normalize external token formats into DCV's internal model.
|
|
4
|
-
|
|
5
|
-
## Goals
|
|
6
|
-
- Accept common design-token ecosystems with zero/low friction.
|
|
7
|
-
- Preserve **IDs** and **provenance** so diagnostics remain meaningful.
|
|
8
|
-
- Avoid mutation: adapters are **pure mappings**.
|
|
9
|
-
|
|
10
|
-
## Currently targeted
|
|
11
|
-
- **Style Dictionary** (`*.json`)
|
|
12
|
-
- Map `tokens.foo.bar.value` → DCV node id `tokens.foo.bar`
|
|
13
|
-
- Preserve `type`, `description` if present
|
|
14
|
-
- **Tokens Studio JSON** (Figma plugin export)
|
|
15
|
-
- Map `$value`, `$type`, `$description` to DCV fields
|
|
16
|
-
- **DTCG** schema
|
|
17
|
-
- Normalize `value`, `type`, `extensions` to DCV keys
|
|
18
|
-
|
|
19
|
-
## Minimal internal shape (simplified)
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
type DCVToken = {
|
|
23
|
-
id: string; // stable dot-path id
|
|
24
|
-
type?: string; // 'color' | 'dimension' | ...
|
|
25
|
-
value: unknown; // normalized primitive (hex, number, etc.)
|
|
26
|
-
meta?: Record<string, any>; // passthrough fields (description, file, source)
|
|
27
|
-
};
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Writing a custom adapter
|
|
31
|
-
|
|
32
|
-
```ts
|
|
33
|
-
export function fromMyFormat(json: any): DCVToken[] {
|
|
34
|
-
// 1) walk your input
|
|
35
|
-
// 2) produce DCVToken[] with stable ids & normalized values
|
|
36
|
-
// 3) do not throw on unknown fields; pass through to meta
|
|
37
|
-
return tokens;
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
**Tip:** Keep **IDs stable**; diagnostics and graphs depend on them.
|
|
42
|
-
|
|
43
|
-
## Policy inputs
|
|
44
|
-
|
|
45
|
-
Policies are separate JSONs (e.g., AA, AAA, org presets).
|
|
46
|
-
Adapters do **not** alter policy files; they only map tokens.
|
|
1
|
+
# Adapters
|
|
2
|
+
|
|
3
|
+
DCV supports multiple **input shapes** via lightweight adapters that normalize external token formats into DCV's internal model.
|
|
4
|
+
|
|
5
|
+
## Goals
|
|
6
|
+
- Accept common design-token ecosystems with zero/low friction.
|
|
7
|
+
- Preserve **IDs** and **provenance** so diagnostics remain meaningful.
|
|
8
|
+
- Avoid mutation: adapters are **pure mappings**.
|
|
9
|
+
|
|
10
|
+
## Currently targeted
|
|
11
|
+
- **Style Dictionary** (`*.json`)
|
|
12
|
+
- Map `tokens.foo.bar.value` → DCV node id `tokens.foo.bar`
|
|
13
|
+
- Preserve `type`, `description` if present
|
|
14
|
+
- **Tokens Studio JSON** (Figma plugin export)
|
|
15
|
+
- Map `$value`, `$type`, `$description` to DCV fields
|
|
16
|
+
- **DTCG** schema
|
|
17
|
+
- Normalize `value`, `type`, `extensions` to DCV keys
|
|
18
|
+
|
|
19
|
+
## Minimal internal shape (simplified)
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
type DCVToken = {
|
|
23
|
+
id: string; // stable dot-path id
|
|
24
|
+
type?: string; // 'color' | 'dimension' | ...
|
|
25
|
+
value: unknown; // normalized primitive (hex, number, etc.)
|
|
26
|
+
meta?: Record<string, any>; // passthrough fields (description, file, source)
|
|
27
|
+
};
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Writing a custom adapter
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
export function fromMyFormat(json: any): DCVToken[] {
|
|
34
|
+
// 1) walk your input
|
|
35
|
+
// 2) produce DCVToken[] with stable ids & normalized values
|
|
36
|
+
// 3) do not throw on unknown fields; pass through to meta
|
|
37
|
+
return tokens;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Tip:** Keep **IDs stable**; diagnostics and graphs depend on them.
|
|
42
|
+
|
|
43
|
+
## Policy inputs
|
|
44
|
+
|
|
45
|
+
Policies are separate JSONs (e.g., AA, AAA, org presets).
|
|
46
|
+
Adapters do **not** alter policy files; they only map tokens.
|
package/adapters/css.ts
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
// adapters/css.ts
|
|
2
|
-
export type TokenId = string;
|
|
3
|
-
export type TokenValue = string | number;
|
|
4
|
-
|
|
5
|
-
// Turn a token ID into a CSS custom property name.
|
|
6
|
-
// Default: "--dimension-spacing-scale-4" (neutral, reversible).
|
|
7
|
-
export type VarMapper = (id: TokenId) => string | null;
|
|
8
|
-
|
|
9
|
-
export const defaultVarMapper: VarMapper = (id) =>
|
|
10
|
-
`--${id.replace(/[^a-z0-9.]/gi, "-").replace(/\.+/g, "-").toLowerCase()}`;
|
|
11
|
-
|
|
12
|
-
// Manifest driven mapping allowing canonical + legacy aliases.
|
|
13
|
-
export type ManifestRow = { id: string; canonicalVar?: string | null; legacyVars?: string[] };
|
|
14
|
-
|
|
15
|
-
export interface VarMapping { canonical: string; aliases: string[] };
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Build a mapping of token id -> {canonical, aliases} from an optional manifest.
|
|
19
|
-
* If no manifest provided, falls back to defaultVarMapper with no aliases.
|
|
20
|
-
*/
|
|
21
|
-
export function buildVarMapping(ids: Iterable<string>, manifest?: ManifestRow[]): Map<string, VarMapping> {
|
|
22
|
-
const map = new Map<string, VarMapping>();
|
|
23
|
-
const byId: Map<string, ManifestRow> = new Map();
|
|
24
|
-
if (manifest) {
|
|
25
|
-
for (const row of manifest) {
|
|
26
|
-
if (!row || !row.id) continue;
|
|
27
|
-
byId.set(row.id, row);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
for (const id of ids) {
|
|
31
|
-
const row = byId.get(id);
|
|
32
|
-
// Always fall back to generated name so canonical is never null.
|
|
33
|
-
const canonical = ((row?.canonicalVar ? row.canonicalVar.trim() : "") || defaultVarMapper(id)) as string;
|
|
34
|
-
const aliasSet = new Set<string>();
|
|
35
|
-
(row?.legacyVars || []).forEach(a => {
|
|
36
|
-
if (!a) return;
|
|
37
|
-
const alias = a.trim();
|
|
38
|
-
if (alias && alias !== canonical) aliasSet.add(alias);
|
|
39
|
-
});
|
|
40
|
-
map.set(id, { canonical, aliases: Array.from(aliasSet) });
|
|
41
|
-
}
|
|
42
|
-
return map;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function makeManifestVarMapper(manifest: ManifestRow[]): VarMapper {
|
|
46
|
-
const byId = new Map<string, string>();
|
|
47
|
-
for (const m of manifest) if (m.canonicalVar) byId.set(m.id, m.canonicalVar);
|
|
48
|
-
return (id) => byId.get(id) ?? defaultVarMapper(id);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Build a CSS block from a map of token IDs to values.
|
|
52
|
-
function buildCssBlock(
|
|
53
|
-
values: Record<TokenId, TokenValue>,
|
|
54
|
-
opts: { mapVar?: VarMapper; manifest?: ManifestRow[] }
|
|
55
|
-
): string {
|
|
56
|
-
const decls: string[] = [];
|
|
57
|
-
if (opts.manifest) {
|
|
58
|
-
const mapping = buildVarMapping(Object.keys(values), opts.manifest);
|
|
59
|
-
for (const [id, val] of Object.entries(values)) {
|
|
60
|
-
const m = mapping.get(id);
|
|
61
|
-
if (!m) continue;
|
|
62
|
-
const v = String(val).trim();
|
|
63
|
-
if (!v) continue;
|
|
64
|
-
decls.push(`${m.canonical}: ${v};`);
|
|
65
|
-
for (const alias of m.aliases) {
|
|
66
|
-
decls.push(`${alias}: var(${m.canonical});`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
const mapVar = opts.mapVar ?? defaultVarMapper;
|
|
71
|
-
for (const [id, val] of Object.entries(values)) {
|
|
72
|
-
const cssVar = mapVar(id);
|
|
73
|
-
if (!cssVar) continue; // skip if intentionally unmapped
|
|
74
|
-
const v = String(val).trim();
|
|
75
|
-
if (v) decls.push(`${cssVar}: ${v};`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return decls.join("\n ");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Generate CSS for a *full* :root block (use for initial build from flat values).
|
|
83
|
-
*/
|
|
84
|
-
export function valuesToCss(
|
|
85
|
-
values: Record<TokenId, TokenValue>,
|
|
86
|
-
opts?: { selector?: string; layer?: string; mapVar?: VarMapper; manifest?: ManifestRow[] }
|
|
87
|
-
): string {
|
|
88
|
-
const selector = opts?.selector ?? ":root";
|
|
89
|
-
const layer = opts?.layer ?? "tokens";
|
|
90
|
-
const body = buildCssBlock(values, { mapVar: opts?.mapVar, manifest: opts?.manifest });
|
|
91
|
-
return `@layer ${layer} {\n ${selector} {\n ${body}\n }\n}`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Generate CSS for a *patch* (small overlay you can inject at runtime).
|
|
96
|
-
*/
|
|
97
|
-
export function patchToCss(
|
|
98
|
-
patch: Record<TokenId, TokenValue>,
|
|
99
|
-
opts?: { selector?: string; layer?: string; mapVar?: VarMapper; manifest?: ManifestRow[] }
|
|
100
|
-
): string {
|
|
101
|
-
const selector = opts?.selector ?? ":root";
|
|
102
|
-
const layer = opts?.layer ?? "tokens-overrides";
|
|
103
|
-
const body = buildCssBlock(patch, { mapVar: opts?.mapVar, manifest: opts?.manifest });
|
|
104
|
-
return `@layer ${layer} {\n ${selector} {\n ${body}\n }\n}`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* (Browser only) Replace the contents of a <style> element with the given CSS.
|
|
109
|
-
*/
|
|
110
|
-
export function applyCssToStyleEl(styleEl: { textContent: string | null }, cssText: string) {
|
|
111
|
-
styleEl.textContent = cssText;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// NOTE: The design studio -> catalog preview iframe token sync uses a simplified
|
|
115
|
-
// :root { --token: value } block (no @layer) built ad-hoc. For a layered build
|
|
116
|
-
// prefer valuesToCss / patchToCss helpers above.
|
|
1
|
+
// adapters/css.ts
|
|
2
|
+
export type TokenId = string;
|
|
3
|
+
export type TokenValue = string | number;
|
|
4
|
+
|
|
5
|
+
// Turn a token ID into a CSS custom property name.
|
|
6
|
+
// Default: "--dimension-spacing-scale-4" (neutral, reversible).
|
|
7
|
+
export type VarMapper = (id: TokenId) => string | null;
|
|
8
|
+
|
|
9
|
+
export const defaultVarMapper: VarMapper = (id) =>
|
|
10
|
+
`--${id.replace(/[^a-z0-9.]/gi, "-").replace(/\.+/g, "-").toLowerCase()}`;
|
|
11
|
+
|
|
12
|
+
// Manifest driven mapping allowing canonical + legacy aliases.
|
|
13
|
+
export type ManifestRow = { id: string; canonicalVar?: string | null; legacyVars?: string[] };
|
|
14
|
+
|
|
15
|
+
export interface VarMapping { canonical: string; aliases: string[] };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build a mapping of token id -> {canonical, aliases} from an optional manifest.
|
|
19
|
+
* If no manifest provided, falls back to defaultVarMapper with no aliases.
|
|
20
|
+
*/
|
|
21
|
+
export function buildVarMapping(ids: Iterable<string>, manifest?: ManifestRow[]): Map<string, VarMapping> {
|
|
22
|
+
const map = new Map<string, VarMapping>();
|
|
23
|
+
const byId: Map<string, ManifestRow> = new Map();
|
|
24
|
+
if (manifest) {
|
|
25
|
+
for (const row of manifest) {
|
|
26
|
+
if (!row || !row.id) continue;
|
|
27
|
+
byId.set(row.id, row);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const id of ids) {
|
|
31
|
+
const row = byId.get(id);
|
|
32
|
+
// Always fall back to generated name so canonical is never null.
|
|
33
|
+
const canonical = ((row?.canonicalVar ? row.canonicalVar.trim() : "") || defaultVarMapper(id)) as string;
|
|
34
|
+
const aliasSet = new Set<string>();
|
|
35
|
+
(row?.legacyVars || []).forEach(a => {
|
|
36
|
+
if (!a) return;
|
|
37
|
+
const alias = a.trim();
|
|
38
|
+
if (alias && alias !== canonical) aliasSet.add(alias);
|
|
39
|
+
});
|
|
40
|
+
map.set(id, { canonical, aliases: Array.from(aliasSet) });
|
|
41
|
+
}
|
|
42
|
+
return map;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function makeManifestVarMapper(manifest: ManifestRow[]): VarMapper {
|
|
46
|
+
const byId = new Map<string, string>();
|
|
47
|
+
for (const m of manifest) if (m.canonicalVar) byId.set(m.id, m.canonicalVar);
|
|
48
|
+
return (id) => byId.get(id) ?? defaultVarMapper(id);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Build a CSS block from a map of token IDs to values.
|
|
52
|
+
function buildCssBlock(
|
|
53
|
+
values: Record<TokenId, TokenValue>,
|
|
54
|
+
opts: { mapVar?: VarMapper; manifest?: ManifestRow[] }
|
|
55
|
+
): string {
|
|
56
|
+
const decls: string[] = [];
|
|
57
|
+
if (opts.manifest) {
|
|
58
|
+
const mapping = buildVarMapping(Object.keys(values), opts.manifest);
|
|
59
|
+
for (const [id, val] of Object.entries(values)) {
|
|
60
|
+
const m = mapping.get(id);
|
|
61
|
+
if (!m) continue;
|
|
62
|
+
const v = String(val).trim();
|
|
63
|
+
if (!v) continue;
|
|
64
|
+
decls.push(`${m.canonical}: ${v};`);
|
|
65
|
+
for (const alias of m.aliases) {
|
|
66
|
+
decls.push(`${alias}: var(${m.canonical});`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
const mapVar = opts.mapVar ?? defaultVarMapper;
|
|
71
|
+
for (const [id, val] of Object.entries(values)) {
|
|
72
|
+
const cssVar = mapVar(id);
|
|
73
|
+
if (!cssVar) continue; // skip if intentionally unmapped
|
|
74
|
+
const v = String(val).trim();
|
|
75
|
+
if (v) decls.push(`${cssVar}: ${v};`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return decls.join("\n ");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generate CSS for a *full* :root block (use for initial build from flat values).
|
|
83
|
+
*/
|
|
84
|
+
export function valuesToCss(
|
|
85
|
+
values: Record<TokenId, TokenValue>,
|
|
86
|
+
opts?: { selector?: string; layer?: string; mapVar?: VarMapper; manifest?: ManifestRow[] }
|
|
87
|
+
): string {
|
|
88
|
+
const selector = opts?.selector ?? ":root";
|
|
89
|
+
const layer = opts?.layer ?? "tokens";
|
|
90
|
+
const body = buildCssBlock(values, { mapVar: opts?.mapVar, manifest: opts?.manifest });
|
|
91
|
+
return `@layer ${layer} {\n ${selector} {\n ${body}\n }\n}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generate CSS for a *patch* (small overlay you can inject at runtime).
|
|
96
|
+
*/
|
|
97
|
+
export function patchToCss(
|
|
98
|
+
patch: Record<TokenId, TokenValue>,
|
|
99
|
+
opts?: { selector?: string; layer?: string; mapVar?: VarMapper; manifest?: ManifestRow[] }
|
|
100
|
+
): string {
|
|
101
|
+
const selector = opts?.selector ?? ":root";
|
|
102
|
+
const layer = opts?.layer ?? "tokens-overrides";
|
|
103
|
+
const body = buildCssBlock(patch, { mapVar: opts?.mapVar, manifest: opts?.manifest });
|
|
104
|
+
return `@layer ${layer} {\n ${selector} {\n ${body}\n }\n}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* (Browser only) Replace the contents of a <style> element with the given CSS.
|
|
109
|
+
*/
|
|
110
|
+
export function applyCssToStyleEl(styleEl: { textContent: string | null }, cssText: string) {
|
|
111
|
+
styleEl.textContent = cssText;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// NOTE: The design studio -> catalog preview iframe token sync uses a simplified
|
|
115
|
+
// :root { --token: value } block (no @layer) built ad-hoc. For a layered build
|
|
116
|
+
// prefer valuesToCss / patchToCss helpers above.
|
package/adapters/js.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// adapters/js.ts
|
|
2
|
-
import { buildVarMapping, defaultVarMapper, type ManifestRow } from './css.js';
|
|
3
|
-
|
|
4
|
-
export function emitJS(values: Record<string, any>, manifest?: ManifestRow[]): string {
|
|
5
|
-
const ids = Object.keys(values).sort();
|
|
6
|
-
let mapping: Map<string, { canonical: string; aliases: string[] }> | undefined;
|
|
7
|
-
if (manifest) mapping = buildVarMapping(ids, manifest);
|
|
8
|
-
const lines: string[] = [];
|
|
9
|
-
for (const id of ids) {
|
|
10
|
-
const canonical = mapping ? mapping.get(id)!.canonical : defaultVarMapper(id);
|
|
11
|
-
if (canonical) lines.push(` "${canonical}": ${JSON.stringify(values[id])}`);
|
|
12
|
-
}
|
|
13
|
-
return `// Generated by DCV. Do not edit.\nexport default {\n${lines.join(',\n')}\n};\n`;
|
|
14
|
-
}
|
|
1
|
+
// adapters/js.ts
|
|
2
|
+
import { buildVarMapping, defaultVarMapper, type ManifestRow } from './css.js';
|
|
3
|
+
|
|
4
|
+
export function emitJS(values: Record<string, any>, manifest?: ManifestRow[]): string {
|
|
5
|
+
const ids = Object.keys(values).sort();
|
|
6
|
+
let mapping: Map<string, { canonical: string; aliases: string[] }> | undefined;
|
|
7
|
+
if (manifest) mapping = buildVarMapping(ids, manifest);
|
|
8
|
+
const lines: string[] = [];
|
|
9
|
+
for (const id of ids) {
|
|
10
|
+
const canonical = mapping ? mapping.get(id)!.canonical : defaultVarMapper(id);
|
|
11
|
+
if (canonical) lines.push(` "${canonical}": ${JSON.stringify(values[id])}`);
|
|
12
|
+
}
|
|
13
|
+
return `// Generated by DCV. Do not edit.\nexport default {\n${lines.join(',\n')}\n};\n`;
|
|
14
|
+
}
|
package/adapters/json.ts
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
// adapters/json.ts
|
|
2
|
-
import { buildVarMapping, defaultVarMapper, type ManifestRow } from './css.js';
|
|
3
|
-
|
|
4
|
-
export function emitJSON(values: Record<string, any>, manifest?: ManifestRow[]): string {
|
|
5
|
-
const ids = Object.keys(values).sort();
|
|
6
|
-
let mapping: Map<string, { canonical: string; aliases: string[] }> | undefined;
|
|
7
|
-
if (manifest) mapping = buildVarMapping(ids, manifest);
|
|
8
|
-
const out: Record<string, any> = {};
|
|
9
|
-
for (const id of ids) {
|
|
10
|
-
const canonical = mapping ? mapping.get(id)!.canonical : defaultVarMapper(id);
|
|
11
|
-
if (canonical) out[canonical] = values[id];
|
|
12
|
-
}
|
|
13
|
-
return JSON.stringify(out, null, 2) + '\n';
|
|
14
|
-
}
|
|
15
|
-
// adapters/json.ts
|
|
16
|
-
import type { TokenId, TokenValue, FlatToken } from '../core/flatten.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Generates a JSON representation of the entire token set and dependency graph.
|
|
20
|
-
*/
|
|
21
|
-
export function valuesToJson(data: {
|
|
22
|
-
flat: Record<TokenId, FlatToken>;
|
|
23
|
-
edges: [TokenId, TokenId][];
|
|
24
|
-
}): string {
|
|
25
|
-
const values = Object.fromEntries(Object.entries(data.flat).map(([id, token]) => [id, token.value]));
|
|
26
|
-
const edges = data.edges.map(([from, to]) => ({ from, to }));
|
|
27
|
-
|
|
28
|
-
return JSON.stringify({
|
|
29
|
-
tokens: values,
|
|
30
|
-
edges: edges,
|
|
31
|
-
}, null, 2);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Generates a JSON representation of a patch from an engine commit.
|
|
36
|
-
*/
|
|
37
|
-
export function patchToJson(patch: {
|
|
38
|
-
patch: Record<TokenId, TokenValue>;
|
|
39
|
-
affected: TokenId[];
|
|
40
|
-
}): string {
|
|
41
|
-
return JSON.stringify({
|
|
42
|
-
changed: patch.patch,
|
|
43
|
-
affected: patch.affected,
|
|
44
|
-
}, null, 2);
|
|
45
|
-
}
|
|
1
|
+
// adapters/json.ts
|
|
2
|
+
import { buildVarMapping, defaultVarMapper, type ManifestRow } from './css.js';
|
|
3
|
+
|
|
4
|
+
export function emitJSON(values: Record<string, any>, manifest?: ManifestRow[]): string {
|
|
5
|
+
const ids = Object.keys(values).sort();
|
|
6
|
+
let mapping: Map<string, { canonical: string; aliases: string[] }> | undefined;
|
|
7
|
+
if (manifest) mapping = buildVarMapping(ids, manifest);
|
|
8
|
+
const out: Record<string, any> = {};
|
|
9
|
+
for (const id of ids) {
|
|
10
|
+
const canonical = mapping ? mapping.get(id)!.canonical : defaultVarMapper(id);
|
|
11
|
+
if (canonical) out[canonical] = values[id];
|
|
12
|
+
}
|
|
13
|
+
return JSON.stringify(out, null, 2) + '\n';
|
|
14
|
+
}
|
|
15
|
+
// adapters/json.ts
|
|
16
|
+
import type { TokenId, TokenValue, FlatToken } from '../core/flatten.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generates a JSON representation of the entire token set and dependency graph.
|
|
20
|
+
*/
|
|
21
|
+
export function valuesToJson(data: {
|
|
22
|
+
flat: Record<TokenId, FlatToken>;
|
|
23
|
+
edges: [TokenId, TokenId][];
|
|
24
|
+
}): string {
|
|
25
|
+
const values = Object.fromEntries(Object.entries(data.flat).map(([id, token]) => [id, token.value]));
|
|
26
|
+
const edges = data.edges.map(([from, to]) => ({ from, to }));
|
|
27
|
+
|
|
28
|
+
return JSON.stringify({
|
|
29
|
+
tokens: values,
|
|
30
|
+
edges: edges,
|
|
31
|
+
}, null, 2);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generates a JSON representation of a patch from an engine commit.
|
|
36
|
+
*/
|
|
37
|
+
export function patchToJson(patch: {
|
|
38
|
+
patch: Record<TokenId, TokenValue>;
|
|
39
|
+
affected: TokenId[];
|
|
40
|
+
}): string {
|
|
41
|
+
return JSON.stringify({
|
|
42
|
+
changed: patch.patch,
|
|
43
|
+
affected: patch.affected,
|
|
44
|
+
}, null, 2);
|
|
45
|
+
}
|
package/cli/build-css.ts
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
// cli/build-css.ts
|
|
2
|
-
import { writeFileSync, readFileSync, mkdirSync } from "node:fs";
|
|
3
|
-
import { dirname } from "node:path";
|
|
4
|
-
import { flattenTokens } from "../core/flatten.js";
|
|
5
|
-
import { valuesToCss } from "../adapters/css.js";
|
|
6
|
-
|
|
7
|
-
const tokens = JSON.parse(readFileSync("tokens/tokens.example.json", "utf8"));
|
|
8
|
-
const { flat } = flattenTokens(tokens);
|
|
9
|
-
|
|
10
|
-
const allValues: Record<string, string|number> =
|
|
11
|
-
Object.fromEntries(Object.values(flat).map(t => [t.id, t.value]));
|
|
12
|
-
|
|
13
|
-
import yargs from "yargs";
|
|
14
|
-
import { hideBin } from "yargs/helpers";
|
|
15
|
-
|
|
16
|
-
const argv = yargs(hideBin(process.argv)).option('out', {
|
|
17
|
-
alias: 'o',
|
|
18
|
-
type: 'string',
|
|
19
|
-
description: 'Output file path',
|
|
20
|
-
default: 'dist/tokens.css'
|
|
21
|
-
}).parseSync();
|
|
22
|
-
|
|
23
|
-
// ... existing code ...
|
|
24
|
-
|
|
25
|
-
const outFile = argv.out;
|
|
26
|
-
const css = valuesToCss(allValues, { layer: "tokens", selector: ":root" });
|
|
27
|
-
// ... existing code ...
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
mkdirSync(dirname(outFile), { recursive: true });
|
|
31
|
-
writeFileSync(outFile, css);
|
|
32
|
-
console.log(`Wrote ${outFile}`);
|
|
1
|
+
// cli/build-css.ts
|
|
2
|
+
import { writeFileSync, readFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { flattenTokens } from "../core/flatten.js";
|
|
5
|
+
import { valuesToCss } from "../adapters/css.js";
|
|
6
|
+
|
|
7
|
+
const tokens = JSON.parse(readFileSync("tokens/tokens.example.json", "utf8"));
|
|
8
|
+
const { flat } = flattenTokens(tokens);
|
|
9
|
+
|
|
10
|
+
const allValues: Record<string, string|number> =
|
|
11
|
+
Object.fromEntries(Object.values(flat).map(t => [t.id, t.value]));
|
|
12
|
+
|
|
13
|
+
import yargs from "yargs";
|
|
14
|
+
import { hideBin } from "yargs/helpers";
|
|
15
|
+
|
|
16
|
+
const argv = yargs(hideBin(process.argv)).option('out', {
|
|
17
|
+
alias: 'o',
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Output file path',
|
|
20
|
+
default: 'dist/tokens.css'
|
|
21
|
+
}).parseSync();
|
|
22
|
+
|
|
23
|
+
// ... existing code ...
|
|
24
|
+
|
|
25
|
+
const outFile = argv.out;
|
|
26
|
+
const css = valuesToCss(allValues, { layer: "tokens", selector: ":root" });
|
|
27
|
+
// ... existing code ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
mkdirSync(dirname(outFile), { recursive: true });
|
|
31
|
+
writeFileSync(outFile, css);
|
|
32
|
+
console.log(`Wrote ${outFile}`);
|
package/cli/commands/build.ts
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import { join, dirname, resolve } from 'node:path';
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { flattenTokens, type FlatToken } from '../../core/flatten.js';
|
|
4
|
-
import { valuesToCss, type ManifestRow } from '../../adapters/css.js';
|
|
5
|
-
import { emitJSON } from '../../adapters/json.js';
|
|
6
|
-
import { emitJS } from '../../adapters/js.js';
|
|
7
|
-
import type { BuildOptions } from '../types.js';
|
|
8
|
-
|
|
9
|
-
export async function buildCommand(options: BuildOptions & { [k: string]: any }): Promise<void> {
|
|
10
|
-
const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
|
|
11
|
-
const tokens = loadTokensWithBreakpoint();
|
|
12
|
-
const { flat } = flattenTokens(tokens);
|
|
13
|
-
let allValues = Object.fromEntries(Object.values(flat).map(t => [t.id, (t as FlatToken).value]));
|
|
14
|
-
if (options.theme) {
|
|
15
|
-
const themePath = join('tokens/themes', `${options.theme}.json`);
|
|
16
|
-
if (existsSync(themePath)) {
|
|
17
|
-
const themeTokens = JSON.parse(readFileSync(themePath, 'utf8'));
|
|
18
|
-
Object.assign(allValues, themeTokens);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
const format = options.format || 'css';
|
|
22
|
-
const defaultOutput = `dist/tokens.${format}`;
|
|
23
|
-
let manifest: ManifestRow[] | undefined;
|
|
24
|
-
if (options.mapper) {
|
|
25
|
-
try {
|
|
26
|
-
const mp = resolve(options.mapper);
|
|
27
|
-
if (!existsSync(mp)) throw new Error(`mapper file not found: ${mp}`);
|
|
28
|
-
manifest = JSON.parse(readFileSync(mp, 'utf8')) as ManifestRow[];
|
|
29
|
-
if (!Array.isArray(manifest)) throw new Error('mapper manifest must be an array');
|
|
30
|
-
} catch (e) {
|
|
31
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
32
|
-
console.error(`Failed to load mapper manifest: ${msg}`);
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
const allFormats = options.allFormats ?? options['all-formats'];
|
|
37
|
-
if (allFormats) {
|
|
38
|
-
const dir = 'dist'; if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
39
|
-
const css = valuesToCss(allValues, { manifest });
|
|
40
|
-
writeFileSync('dist/tokens.css', css, 'utf8');
|
|
41
|
-
if (options.dryRun) console.log(css);
|
|
42
|
-
writeFileSync('dist/tokens.json', emitJSON(allValues, manifest), 'utf8');
|
|
43
|
-
writeFileSync('dist/tokens.js', emitJS(allValues, manifest), 'utf8');
|
|
44
|
-
console.log(`Tokens written (all formats) to dist/ (css/json/js)${manifest ? ' with mapper' : ''}`);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const dryRun = options.dryRun ?? options['dry-run'];
|
|
48
|
-
if (format === 'css') {
|
|
49
|
-
const css = valuesToCss(allValues, { manifest });
|
|
50
|
-
if (dryRun) { console.log(css); return; }
|
|
51
|
-
const outPath = options.output || defaultOutput; const dir = dirname(outPath); if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
52
|
-
writeFileSync(outPath, css, 'utf8');
|
|
53
|
-
console.log(`CSS tokens written to ${outPath}${manifest ? ' (manifest mapper applied)' : ''}`);
|
|
54
|
-
} else if (format === 'json') {
|
|
55
|
-
const outPath = options.output || defaultOutput; const dir = dirname(outPath); if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
56
|
-
if (dryRun) { console.log(emitJSON(allValues, manifest)); return; }
|
|
57
|
-
writeFileSync(outPath, emitJSON(allValues, manifest), 'utf8');
|
|
58
|
-
console.log(`JSON tokens written to ${outPath}${manifest ? ' (manifest mapper applied)' : ''}`);
|
|
59
|
-
} else if (format === 'js') {
|
|
60
|
-
const outPath = options.output || defaultOutput; const dir = dirname(outPath); if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
61
|
-
if (dryRun) { console.log(emitJS(allValues, manifest)); return; }
|
|
62
|
-
writeFileSync(outPath, emitJS(allValues, manifest), 'utf8');
|
|
63
|
-
console.log(`JS tokens written to ${outPath}${manifest ? ' (manifest mapper applied)' : ''}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
1
|
+
import { join, dirname, resolve } from 'node:path';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { flattenTokens, type FlatToken } from '../../core/flatten.js';
|
|
4
|
+
import { valuesToCss, type ManifestRow } from '../../adapters/css.js';
|
|
5
|
+
import { emitJSON } from '../../adapters/json.js';
|
|
6
|
+
import { emitJS } from '../../adapters/js.js';
|
|
7
|
+
import type { BuildOptions } from '../types.js';
|
|
8
|
+
|
|
9
|
+
export async function buildCommand(options: BuildOptions & { [k: string]: any }): Promise<void> {
|
|
10
|
+
const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
|
|
11
|
+
const tokens = loadTokensWithBreakpoint();
|
|
12
|
+
const { flat } = flattenTokens(tokens);
|
|
13
|
+
let allValues = Object.fromEntries(Object.values(flat).map(t => [t.id, (t as FlatToken).value]));
|
|
14
|
+
if (options.theme) {
|
|
15
|
+
const themePath = join('tokens/themes', `${options.theme}.json`);
|
|
16
|
+
if (existsSync(themePath)) {
|
|
17
|
+
const themeTokens = JSON.parse(readFileSync(themePath, 'utf8'));
|
|
18
|
+
Object.assign(allValues, themeTokens);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const format = options.format || 'css';
|
|
22
|
+
const defaultOutput = `dist/tokens.${format}`;
|
|
23
|
+
let manifest: ManifestRow[] | undefined;
|
|
24
|
+
if (options.mapper) {
|
|
25
|
+
try {
|
|
26
|
+
const mp = resolve(options.mapper);
|
|
27
|
+
if (!existsSync(mp)) throw new Error(`mapper file not found: ${mp}`);
|
|
28
|
+
manifest = JSON.parse(readFileSync(mp, 'utf8')) as ManifestRow[];
|
|
29
|
+
if (!Array.isArray(manifest)) throw new Error('mapper manifest must be an array');
|
|
30
|
+
} catch (e) {
|
|
31
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
32
|
+
console.error(`Failed to load mapper manifest: ${msg}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const allFormats = options.allFormats ?? options['all-formats'];
|
|
37
|
+
if (allFormats) {
|
|
38
|
+
const dir = 'dist'; if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
39
|
+
const css = valuesToCss(allValues, { manifest });
|
|
40
|
+
writeFileSync('dist/tokens.css', css, 'utf8');
|
|
41
|
+
if (options.dryRun) console.log(css);
|
|
42
|
+
writeFileSync('dist/tokens.json', emitJSON(allValues, manifest), 'utf8');
|
|
43
|
+
writeFileSync('dist/tokens.js', emitJS(allValues, manifest), 'utf8');
|
|
44
|
+
console.log(`Tokens written (all formats) to dist/ (css/json/js)${manifest ? ' with mapper' : ''}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const dryRun = options.dryRun ?? options['dry-run'];
|
|
48
|
+
if (format === 'css') {
|
|
49
|
+
const css = valuesToCss(allValues, { manifest });
|
|
50
|
+
if (dryRun) { console.log(css); return; }
|
|
51
|
+
const outPath = options.output || defaultOutput; const dir = dirname(outPath); if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
52
|
+
writeFileSync(outPath, css, 'utf8');
|
|
53
|
+
console.log(`CSS tokens written to ${outPath}${manifest ? ' (manifest mapper applied)' : ''}`);
|
|
54
|
+
} else if (format === 'json') {
|
|
55
|
+
const outPath = options.output || defaultOutput; const dir = dirname(outPath); if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
56
|
+
if (dryRun) { console.log(emitJSON(allValues, manifest)); return; }
|
|
57
|
+
writeFileSync(outPath, emitJSON(allValues, manifest), 'utf8');
|
|
58
|
+
console.log(`JSON tokens written to ${outPath}${manifest ? ' (manifest mapper applied)' : ''}`);
|
|
59
|
+
} else if (format === 'js') {
|
|
60
|
+
const outPath = options.output || defaultOutput; const dir = dirname(outPath); if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
61
|
+
if (dryRun) { console.log(emitJS(allValues, manifest)); return; }
|
|
62
|
+
writeFileSync(outPath, emitJS(allValues, manifest), 'utf8');
|
|
63
|
+
console.log(`JS tokens written to ${outPath}${manifest ? ' (manifest mapper applied)' : ''}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["graph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["graph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA2BhD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAuJvE"}
|