@bridge-ds/core 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +367 -2
- package/audit-report.schema.json +732 -0
- package/bridge.config.schema.json +669 -0
- package/dist/audit/code.d.ts +16 -0
- package/dist/audit/color.d.ts +18 -0
- package/dist/audit/components.d.ts +12 -0
- package/dist/audit/css.d.ts +24 -0
- package/dist/audit/ds-index.d.ts +40 -0
- package/dist/audit/index.d.ts +9 -0
- package/dist/audit/index.js +40 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/report.d.ts +53 -0
- package/dist/audit/scan-value.d.ts +30 -0
- package/dist/audit/tailwind.d.ts +14 -0
- package/dist/audit/types.d.ts +181 -0
- package/dist/brand.d.ts +152 -0
- package/dist/change-drafts.d.ts +55 -0
- package/dist/changeset.d.ts +24 -0
- package/dist/component-spec-IRFGJ7XK.js +3130 -0
- package/dist/component-spec-IRFGJ7XK.js.map +1 -0
- package/dist/component-spec.d.ts +164 -0
- package/dist/compose.d.ts +15 -0
- package/dist/config.d.ts +232 -0
- package/dist/diff-engine.d.ts +41 -0
- package/dist/docs-ai.d.ts +116 -0
- package/dist/docs-draft.d.ts +153 -0
- package/dist/draft-exchange.d.ts +259 -0
- package/dist/dtcg-codec.d.ts +46 -0
- package/dist/explorations-github.d.ts +119 -0
- package/dist/explorations.d.ts +162 -0
- package/dist/github-client.d.ts +497 -0
- package/dist/governance.d.ts +13 -0
- package/dist/history-demo.d.ts +8 -0
- package/dist/history-github.d.ts +14 -0
- package/dist/history.d.ts +339 -0
- package/dist/id-map.d.ts +31 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +3959 -0
- package/dist/index.js.map +1 -0
- package/dist/json-ast.d.ts +64 -0
- package/dist/layered-DGate4nv.js +500 -0
- package/dist/layered-DGate4nv.js.map +1 -0
- package/dist/layered-review.d.ts +88 -0
- package/dist/layered.d.ts +93 -0
- package/dist/node/audit.d.ts +43 -0
- package/dist/node/component-specs.d.ts +47 -0
- package/dist/node/git-history.d.ts +14 -0
- package/dist/node/git-local.d.ts +31 -0
- package/dist/node/index.d.ts +5 -0
- package/dist/node/index.js +1765 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/spec-css.d.ts +87 -0
- package/dist/pr-composer.d.ts +55 -0
- package/dist/previews-github.d.ts +22 -0
- package/dist/previews.d.ts +181 -0
- package/dist/protection-github.d.ts +25 -0
- package/dist/protection.d.ts +139 -0
- package/dist/ramps.d.ts +29 -0
- package/dist/report-tNzTriGF.js +1454 -0
- package/dist/report-tNzTriGF.js.map +1 -0
- package/dist/requests-github.d.ts +21 -0
- package/dist/requests.d.ts +124 -0
- package/dist/resolver.d.ts +39 -0
- package/dist/rights.d.ts +63 -0
- package/dist/risk.d.ts +41 -0
- package/dist/scopes.d.ts +10 -0
- package/dist/teams.d.ts +149 -0
- package/dist/token-edits.d.ts +24 -0
- package/dist/token-set.d.ts +51 -0
- package/dist/types.d.ts +86 -0
- package/dist/usage.d.ts +84 -0
- package/dist/validators.d.ts +44 -0
- package/dist/values.d.ts +30 -0
- package/dist/wcag.d.ts +92 -0
- package/package.json +64 -4
package/README.md
CHANGED
|
@@ -1,3 +1,368 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @bridge-ds/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Pure design-token logic for Bridge: load DTCG 2025.10 tokens across tiers, resolve aliases per brand,
|
|
4
|
+
theme and density, check WCAG contrast, diff three versions of a token set, edit `*.tokens.json` files
|
|
5
|
+
without reformatting them, build the pull request (body, risk tier, changeset) and talk to GitHub.
|
|
6
|
+
|
|
7
|
+
No Figma API, no DOM, no Node-only API in the main entry (Node helpers live in `@bridge-ds/core/node`): it runs in a Figma plugin sandbox, a browser, Node or an MCP
|
|
8
|
+
server. The DS Bridge Figma plugin (`apps/figma-plugin`) is built on it; a CLI and an MCP server can be
|
|
9
|
+
too. Nothing here hardcodes a repository: callers pass a `BridgeConfig` (usually `bridge.config.json`).
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { loadConfig, buildTokenSet, layoutOptions, validateSet, parseContrastChecks } from '@bridge-ds/core';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Published builds ship ES modules and type declarations from `dist/` (`npm run build:packages`). Inside this
|
|
16
|
+
monorepo the `source` export condition resolves to the TypeScript sources instead, so nothing needs building
|
|
17
|
+
first. Tests: `npx vitest run --project core` (part of the root `npm test`).
|
|
18
|
+
Typecheck: `npx tsc --noEmit -p packages/core`.
|
|
19
|
+
|
|
20
|
+
## Tiers
|
|
21
|
+
|
|
22
|
+
| Tier | Files (under `tokens.root`) | Modes | May alias |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| `primitive` | `primitives/*.tokens.json` | single (`"value"`) | primitives |
|
|
25
|
+
| `brand` | `brands/<brand>.tokens.json` | one per brand | primitives |
|
|
26
|
+
| `base` | `semantic/base.tokens.json` | single | primitives (literals allowed) |
|
|
27
|
+
| `semantic` | `semantic/<theme>.tokens.json` | one per theme | colors: `palette.*` (or `color.alpha.*`); others: brand roles or primitives |
|
|
28
|
+
| `density` | `density/<density>.tokens.json` | one per density | primitives |
|
|
29
|
+
| `component` | `components/*.tokens.json` | single | semantic, base, density, brand roles, primitive scale steps; never a palette role or a raw color |
|
|
30
|
+
| overrides | `brands/<brand>.<theme>.tokens.json` | brand x theme | same as semantic; must override an existing semantic token |
|
|
31
|
+
|
|
32
|
+
## config
|
|
33
|
+
|
|
34
|
+
`bridge.config.json` at the repository root: one file and one schema (`packages/core/bridge.config.schema.json`,
|
|
35
|
+
also exported as `@bridge-ds/core/bridge.config.schema.json`) for the plugin, the docs site, the CLI and the MCP
|
|
36
|
+
server. Canonical form: `repo`, `tokens` (`root`, `include`, `exclude`, `tiers`, ordered `layers` with `{mode}`
|
|
37
|
+
placeholders and `optional`), `modes` (name -> `{ values, default, parity }`), `contrast` (`path`, `modes`),
|
|
38
|
+
`components`, `docs`, `changesets`, `cssVar`, `governance` (`protectedBranches`, `branchPrefix`, `labels`,
|
|
39
|
+
`guardrails`, `owners: { design, tech }`). The older flat form (`tokens.primitives`, `modes.brands`...) still loads.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
const CONFIG_FILE = 'bridge.config.json';
|
|
43
|
+
interface BridgeConfig {
|
|
44
|
+
repo: { owner; name; defaultBranch } | undefined;
|
|
45
|
+
tokens: TokenPaths & { include; exclude; tiers: Record<string, string[]>; layers: { files; optional }[] };
|
|
46
|
+
axes: Record<string, { values; default; parity }>; // canonical modes
|
|
47
|
+
modes: { brands; themes; densities; defaults }; // brand/theme/density view for the tier model
|
|
48
|
+
contrast: { path; modes? } | undefined;
|
|
49
|
+
components?: { dir; docsSuffix; styleSuffix; reference?; index? };
|
|
50
|
+
docs; changesets: { dir; package } | undefined; changeset?: { package };
|
|
51
|
+
cssVar: { prefix; separator };
|
|
52
|
+
governance: { protectedBranches; branchPrefix; labels; guardrails; owners: { design: string[]; tech: string[] } };
|
|
53
|
+
}
|
|
54
|
+
function defineConfig(input): BridgeConfig; function loadConfig(source): BridgeConfig; // ConfigError { problems }
|
|
55
|
+
function toConfigJson(config): object; // canonical JSON (what `bridge init` writes)
|
|
56
|
+
function protectedBranchesOf(config); function baseBranchOf(config); function tokensPath(config, rel?);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## governance
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
function assertSafeBranch(branch, { protectedBranches?, base?, branchPrefix? }): void; // ProtectedBranchError
|
|
63
|
+
const DEFAULT_PROTECTED_BRANCHES = ['main', 'master'];
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The GitHub client and the local git writer both call it: no Bridge surface writes to a protected branch.
|
|
67
|
+
|
|
68
|
+
## layered sets (CLI and MCP)
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
new LayeredTokenSet(config, files: Map<relPath, text>); // combos(), view(combo), resolve(path, combo), tierOf(path), cssVarName(path)
|
|
72
|
+
function validateLayered(set): LayerIssue[]; // DTCG shapes, names, aliases per combo, missing layers, mode parity
|
|
73
|
+
function runLayeredContrast(set, pairs, modes?); function contrastDelta(before, after);
|
|
74
|
+
function applyTokenEdits(set, edits: TokenEdit[]): { files; touched; notes }; // set, add, remove, rename; minimal diffs
|
|
75
|
+
function coerceValue(input, type?, existing?); // "#ff5500", "12px", "150ms" -> the repo's DTCG form
|
|
76
|
+
function diffLayered(before, after); function layeredImpact(before, after, changes);
|
|
77
|
+
function classifyLayered(changes, otherFiles?, config?); // classifyRisk underneath
|
|
78
|
+
function composeProposalBody(input); function changeTable(changes); function contrastDeltaTable(deltas);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`@bridge-ds/core/node` (Node only): `git`, `isGitRepo`, `currentBranch`, `branchExists`, `showFile`, `listFilesAt`,
|
|
82
|
+
`originRepo`, `commitToNewLocalBranch` (plumbing commit on a new branch; working tree, index and checkout untouched).
|
|
83
|
+
|
|
84
|
+
## types
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
type Tier = 'primitive' | 'brand' | 'base' | 'semantic' | 'density' | 'component';
|
|
88
|
+
const TIER_ORDER: Tier[]; // dependency order
|
|
89
|
+
const TIER_AXIS: Record<Tier, 'brand' | 'theme' | 'density' | undefined>;
|
|
90
|
+
const TIER_NAMES: Record<Tier, string>; // "Primitives", "Brand", "Base", "Semantic", "Density", "Component"
|
|
91
|
+
const SINGLE_MODE = 'value'; // mode key of single-mode tiers (alias PRIMITIVE_MODE)
|
|
92
|
+
interface LogicalToken { id; path; type; tier; values: Record<mode, unknown>; description; scopes; files: Record<mode, file> }
|
|
93
|
+
interface ThemeOverride { brand; theme; path; type; value; file }
|
|
94
|
+
interface RepoLayout { primitiveFiles; brands; baseFiles; themes; overrides; densities; componentFiles }
|
|
95
|
+
function isMultiMode(tier: Tier): boolean;
|
|
96
|
+
function modeDisplayName(mode: string): string; // "light" -> "Light"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## json-ast and dtcg-codec (minimal-diff edits)
|
|
100
|
+
|
|
101
|
+
A format-preserving JSON parser: an untouched file serializes byte for byte; an edited token keeps key
|
|
102
|
+
order, indentation, inline or multi-line layout and number spelling.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
function parseJson(text: string): JsonDocument;
|
|
106
|
+
function serializeJson(doc: JsonDocument): string;
|
|
107
|
+
function parseTokenFile(file: string, text: string): TokenDocument;
|
|
108
|
+
function serializeTokenFile(doc: TokenDocument): string;
|
|
109
|
+
function flattenTokens(doc: TokenDocument): FlatToken[]; // $type inherited from groups
|
|
110
|
+
function readTokenAt(doc, path): FlatToken | undefined;
|
|
111
|
+
function upsertToken(doc, path, fields: { type; value; description; extensions? }, template?): boolean; // false when nothing changed
|
|
112
|
+
function removeToken(doc, path): boolean; // prunes empty groups
|
|
113
|
+
function moveToken(doc, from, to): boolean; // keeps every field
|
|
114
|
+
function emptyTokenFile(file): TokenDocument;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## values and scopes
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
function isAlias(v): v is string; function aliasTarget(v: string): string; function toAlias(path): string;
|
|
121
|
+
function parseColor(v): Rgba | undefined; function rgbaToDtcg(c: Rgba): object; function rgbaToHex(c): string;
|
|
122
|
+
function parseDimension(v): { value: number; unit: string } | undefined;
|
|
123
|
+
function valuesEqual(type, a, b): boolean; // colors by 8-bit hex + alpha, numbers to 1e-4
|
|
124
|
+
function formatValue(type, v): string; // "palette.primary.600", "#4b5562", "16px"
|
|
125
|
+
function defaultScopes(path, type, tier): string[]; // Figma scopes inferred from path and type
|
|
126
|
+
function effectiveScopes(t): string[];
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## token-set (load)
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
function buildTokenSet(files: Record<relPath, text>, options?: LayoutOptions): TokenSet;
|
|
133
|
+
function layoutOptions(config: BridgeConfig): LayoutOptions; // tier folders + mode order from the config
|
|
134
|
+
function detectLayout(files: string[], options?: LayoutOptions): RepoLayout;
|
|
135
|
+
function modesOf(layout, tier): string[]; // ["bridge", "ocean"], ["light", "dark"], ["value"]...
|
|
136
|
+
function modeFile(layout, tier, mode): string | undefined;
|
|
137
|
+
function defaultFile(tier, path, dirs?): string; // where a new single-mode token goes
|
|
138
|
+
function defaultContext(set): ResolveContext; // first brand, theme, density, with overrides
|
|
139
|
+
function contextFor(set, tier, mode): ResolveContext;
|
|
140
|
+
interface TokenSet { tokens; byPath; docs; layout; options; overrides: ThemeOverride[]; overrideIndex; problems: string[] }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## resolver (aliases across tiers)
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
interface ResolveContext { brand?; theme?; density?; overrides?: Map<string, unknown> }
|
|
147
|
+
function resolve(index: Map<path, LogicalToken>, path, ctx): { value?; chain: string[]; via: ('token' | 'override')[]; error?: 'cycle' | 'broken' | 'missing-mode'; at? };
|
|
148
|
+
function valueIn(t: LogicalToken, ctx): { value; via };
|
|
149
|
+
function findReferenceProblems(index, axes: { brand?: string[]; theme?: string[]; density?: string[] }): ReferenceProblem[];
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Semantic tokens take a brand override when the context's brand x theme has one.
|
|
153
|
+
|
|
154
|
+
## wcag (contrast)
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const CONTRAST_CHECKS_FILE = 'contrast.checks.json';
|
|
158
|
+
function parseContrastChecks(text): ContrastPair[]; // { pairs: [[fg, bg, min], ...] }; kind = min >= 4.5 ? 'text' : 'ui'
|
|
159
|
+
function contrastScope(set): ContrastScope; // every brand x theme, default density, overrides
|
|
160
|
+
function evaluatePairs(index, pairs, scope): ContrastResult[]; // { pair, brand, theme, ratio, level, error?, steps }
|
|
161
|
+
function contrastRatio(fg, bg): number; function contrastLevel(ratio, kind, min?): 'AAA' | 'AA' | 'fail';
|
|
162
|
+
function renderedColor(v): Rgba | undefined; // hex wins over components, like tokens.css
|
|
163
|
+
function contextLabel(r): string; // "Ocean dark"
|
|
164
|
+
function pairKey(r): string;
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`steps` lists every token both colors resolve through, which is how a change finds the pairs it
|
|
168
|
+
affects (see `affectedContrast`).
|
|
169
|
+
|
|
170
|
+
## validators (guardrails)
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
function validateSet(set, pairs): { issues: Issue[]; contrast: ContrastResult[] };
|
|
174
|
+
function validateProposal(proposed, head, pairs, typeChanges?): { blocking; warnings; contrastBefore; contrastAfter };
|
|
175
|
+
function shadowedEdits(set, edits: { path; tier; modes }[]): Issue[]; // semantic edits a brand override hides
|
|
176
|
+
type IssueCode = 'file-problem' | 'raw-value' | 'alias-tier' | 'broken-reference' | 'alias-cycle' | 'all-scopes'
|
|
177
|
+
| 'missing-mode' | 'override-orphan' | 'override-shadowed' | 'contrast' | 'contrast-unresolved' | 'type-change';
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Problems already on the base branch come back as warnings, so only what a change introduces blocks.
|
|
181
|
+
|
|
182
|
+
## id-map and diff-engine (three-way diff)
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
function threeWayDiff(base?: Map<id, LogicalToken>, edited: Map, head: Map): { local; remote; conflicts; unchanged };
|
|
186
|
+
function toMap(tokens): Map<id, LogicalToken>;
|
|
187
|
+
function fieldChanges(a, b): FieldChange[];
|
|
188
|
+
function reconcileIds(edited: LogicalToken[], git: LogicalToken[]): { tokens; adoptedByName; suggestions: RenameSuggestion[] };
|
|
189
|
+
function applyConfirmedRenames(tokens, confirmed): LogicalToken[];
|
|
190
|
+
function idExtensionFor(t): string | undefined; // "com.dsbridge.id" only after a rename
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## compose (write changes back)
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
interface AcceptedChange { target?: LogicalToken; current?: LogicalToken } // target undefined = remove
|
|
197
|
+
function composeFiles(head: TokenSet, originals: Record<relPath, text>, changes): { files; allFiles };
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Each value goes to the file of its tier and mode; only changed files are returned.
|
|
201
|
+
|
|
202
|
+
## risk
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
type RiskTier = 1 | 2 | 3;
|
|
206
|
+
function classifyRisk(input: { tokenChanges?; files?: { path; status? }[]; config? }): { tier; label; reasons: string[]; bump?: 'patch' | 'minor' | 'major' };
|
|
207
|
+
function tokenBump(changes); function isGuardrail(path, config?);
|
|
208
|
+
const RISK_LABELS: Record<RiskTier, string>;
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The one classifier for the plugin, the docs site, the CLI and the MCP server. Rules (from
|
|
212
|
+
`docs/GOVERNANCE.md`, highest wins): Tier 3 for any token added, changed (value, type, scopes), renamed or
|
|
213
|
+
removed in any tier or override, `*.tokens.json` files when their token changes are unknown, and guardrail
|
|
214
|
+
files (`governance.guardrails`, the contrast checks, `bridge.config.json`, `.github/**`). Tier 2 for component
|
|
215
|
+
sources under `components.dir` and unknown source files. Tier 1 for a description-only token edit, docs,
|
|
216
|
+
`*.md`, stories, `*.docs.json`, tests and changesets. Bumps: removals and renames `major`, other token
|
|
217
|
+
changes `minor`, description-only `patch`. Token changes can be `AcceptedChange`s or `{ kind, path,
|
|
218
|
+
descriptionOnly? }` summaries.
|
|
219
|
+
|
|
220
|
+
## pr-composer and changeset
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
function composePrBody(input: { changes; head; proposed; validation; baseBranch; headSha; baseSha; origin: PrOrigin; title; notes; upstreamPaths; resolvedConflicts }): string;
|
|
224
|
+
interface PrOrigin { tool: string; surface: string; location?: string; authoredBy: string }
|
|
225
|
+
function affectedContrast(change, before: ContrastResult[], after: ContrastResult[]): { before?; after? }[];
|
|
226
|
+
function changedModes(change): Set<string> | undefined;
|
|
227
|
+
function defaultTitle(changes): string; function branchName(slug, date): string; function slugify(text, max?): string;
|
|
228
|
+
function commitMessage(title, changes, footer?): string;
|
|
229
|
+
function changesetBump(changes): 'minor' | 'major';
|
|
230
|
+
function composeChangeset(changes, { title, branch, packageName, source? }): { path; content };
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The body has a Changes table (token, tier, old -> new per mode, contrast before -> after), a Contrast
|
|
234
|
+
impact table, the guardrail checklist and the risk tier. A pair is listed under a changed token when its
|
|
235
|
+
foreground or background resolves through that token, directly or through aliases, in a brand x theme
|
|
236
|
+
where the changed mode is used (PR #4 regression: changing `color.bg.default` lists every pair on it).
|
|
237
|
+
|
|
238
|
+
## github-client
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
new GitHubClient(repo: 'owner/name', token: string, fetchImpl?: FetchLike, retry?: RetryPolicy);
|
|
242
|
+
// refs and git data
|
|
243
|
+
getRepo(); getBranchSha(branch); getCommitTree(sha); getTree(treeSha, recursive?); getBlobText(sha);
|
|
244
|
+
createBlob(text); createTree(baseTree, files); createCommit(message, tree, parents); createBranch(name, sha); updateBranch(name, sha);
|
|
245
|
+
compare(base, head);
|
|
246
|
+
// contents API
|
|
247
|
+
getFileText(path, ref?): Promise<string | undefined>; putFile({ path, content, branch, message, sha? }): Promise<commitSha>;
|
|
248
|
+
// pull requests, reviews, checks, labels
|
|
249
|
+
createPullRequest({ title, head, base, body, draft }); getPullRequest(n); findOpenPullRequest(branch); updatePullRequest(n, patch);
|
|
250
|
+
listPullRequestFiles(n); listReviews(n); createReview(n, { event, body });
|
|
251
|
+
listCheckRuns(ref); createCheckRun({ name, headSha, status?, conclusion?, output? }); // check runs need a GitHub App token
|
|
252
|
+
addLabels(n, labels); removeLabel(n, label); addComment(n, body);
|
|
253
|
+
// whole token set at a commit, honoring bridge.config.json in the repo
|
|
254
|
+
loadRepoFiles(client, commitSha, fallbackTokensPath): Promise<{ files; contrastChecks; config?; tokensPath; warnings }>;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Reads and content-addressed writes retry on network errors, 5xx and secondary rate limits; errors
|
|
258
|
+
become `GitHubError { status, message, detail }` with plain-language messages.
|
|
259
|
+
|
|
260
|
+
## history (history.ts, history-github.ts, history-demo.ts)
|
|
261
|
+
|
|
262
|
+
What changed in the tokens, when, who changed it and why, and how to undo it safely.
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
// model
|
|
266
|
+
interface HistoryEntry { id; sha; baseSha?; date; author; title; why; pr?; changes: LayeredChange[]; counts; tier; bump; breaking; reasons; changesets; version?; bulk?; demo? }
|
|
267
|
+
function buildHistoryEntry({ config, commit, before, after, changesets? }): HistoryEntry | null;
|
|
268
|
+
function collectHistory(config, backend: HistoryBackend, { ref, limit }): Promise<HistoryEntry[]>;
|
|
269
|
+
// backends: GitHub REST (browser or Node) and local git (Node, @bridge-ds/core/node)
|
|
270
|
+
function gitHubHistoryBackend(client, config); function loadGitHubHistory(client, config, { ref?, limit? }): Promise<HistoryReport>;
|
|
271
|
+
function localGitHistoryBackend(root, config); function loadLocalHistory(root, config, { ref?, limit? }): Promise<HistoryReport>;
|
|
272
|
+
// versions: CHANGELOG sections and tags; the timeline of design system versions
|
|
273
|
+
function parseChangelog(text); function releasesFromTags(tags, pkg?); function assignVersions(entries, releases); function versionTimeline(entries, releases?);
|
|
274
|
+
// reading
|
|
275
|
+
function filterHistory(entries, { token?, author?, tier?, version?, breaking?, since?, until?, query? });
|
|
276
|
+
function tokenHistory(entries, path, config?): TokenHistoryItem[]; // every change of one token, renames followed
|
|
277
|
+
function fileModes(config, file); function modeLabel(modes); // semantic/dark.tokens.json -> { theme: "dark" } -> "Dark"
|
|
278
|
+
// planning (never writes)
|
|
279
|
+
function planRevert({ current, entry, later?, pairs?, resolutions? }): ChangePlan;
|
|
280
|
+
function planRestore({ current, path, file?, value, label?, item?, pairs? }): ChangePlan;
|
|
281
|
+
function planToJson(plan); // a patch-like JSON for people without a token
|
|
282
|
+
function demoHistory(config, currentFiles): HistoryReport; // fixture entries, flagged demo, never real
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Local history follows the first parent of the ref, so a merged branch is one entry (the merge commit against
|
|
286
|
+
the base it merged into); GitHub history asks each commit for its pull request and diffs the PR's merge commit.
|
|
287
|
+
Bulk entries (more than 60 token changes: an import or a restructure) are flagged.
|
|
288
|
+
|
|
289
|
+
A revert is not a blind `git revert`. `planRevert` computes the inverse token edits against the current base
|
|
290
|
+
branch: a token that changed again later (`changed-later`), was removed (`removed-later`), came back
|
|
291
|
+
(`recreated-later`), moved (`renamed-later`) or that other tokens now alias (`still-referenced`) is a conflict,
|
|
292
|
+
listed with the later changes that explain it and kept as it is unless `resolutions[key]` is `"use-before"`.
|
|
293
|
+
Changes already undone are skipped. The rest goes through `applyTokenEdits` (minimal diffs), validation, the
|
|
294
|
+
contrast gate, `classifyRisk` and a changeset, like any proposal: `status` is `ready`, `blocked` (new
|
|
295
|
+
validation errors or a failing contrast pair) or `nothing-to-do`. The PR body starts with "Reverts #n" and the
|
|
296
|
+
conflicts table.
|
|
297
|
+
## audit (`@bridge-ds/core/audit`)
|
|
298
|
+
|
|
299
|
+
Design system adoption and drift in a product codebase that consumes the DS. Pure (file texts in, report
|
|
300
|
+
out); it reads TSX/JSX with the TypeScript compiler API (syntax only), which is why it is its own entry and the
|
|
301
|
+
Figma plugin never loads it. `@bridge-ds/core/node` adds the disk side.
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
import { DesignSystemIndex, buildAuditReport, compareWithBaseline, auditMarkdown, planCssFixes, auditFile } from '@bridge-ds/core/audit';
|
|
305
|
+
import { loadDesignSystem, runAudit, collectAuditFiles, fixCss, findDesignSystemRoot } from '@bridge-ds/core/node';
|
|
306
|
+
|
|
307
|
+
const ds = loadDesignSystem('/path/to/ds-repo'); // bridge.config.json: tokens in every mode, components, packages
|
|
308
|
+
const report = runAudit('apps/admin/src', { ds: ds.root, label: 'admin', baseline }); // AuditReport, schemaVersion 1
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
- **Scanned**: CSS, SCSS, Less, CSS modules; `style`, MUI `sx`, `styled()`, `makeStyles`, `createTheme` and theme
|
|
312
|
+
objects, styled-components / emotion templates; Tailwind class strings (`className`, `cn()`, `clsx()`, `cva()`,
|
|
313
|
+
`@apply`); any color literal in code.
|
|
314
|
+
- **Hardcoded values**: colors (hex, rgb, hsl, named, bare HSL channels), spacing, sizes, radii, border widths,
|
|
315
|
+
shadows, durations, easings, z-index, font family, size, weight and line height. Each gets the closest token:
|
|
316
|
+
colors by deltaE in OKLab across every mode (semantic tokens and tokens that fit the property win near-ties),
|
|
317
|
+
lengths by nearest scale step, with a confidence (`exact`, `high`, `medium`, `low`).
|
|
318
|
+
- **Token references**: `var(--x)` of a DS token (also in Tailwind arbitrary values). Other variables, MUI palette
|
|
319
|
+
paths, `theme.spacing()` and Tailwind scale classes count as references to another theme, not as hardcoded.
|
|
320
|
+
- **Components**: JSX instances imported from the DS packages (`audit.packages`, default: the package that holds
|
|
321
|
+
`components.dir`, plus `@bridge-ds/react`) versus foreign UI libraries (MUI, react-admin, antd, Chakra, Radix,
|
|
322
|
+
Headless UI, Mantine, shadcn/ui local components...), native elements that should be DS components
|
|
323
|
+
(`button`, `input`, `select`, `textarea`, `dialog`, `table`, `a` with `href`, `img`), router links (react-router,
|
|
324
|
+
ra-core `LinkBase`, next/link) unless passed as a DS component's `render` element, chart libraries (recharts,
|
|
325
|
+
Nivo, Chart.js...: foreign with no DS equivalent) and local components named like a DS component. Providers and
|
|
326
|
+
other non-visual parts never count, DS ones included.
|
|
327
|
+
- **Metrics**: token adoption = token references / (token references + hardcoded values); component adoption =
|
|
328
|
+
DS instances / all UI instances. By category, top files, top fixes, per-file findings with line numbers.
|
|
329
|
+
- **Baseline**: `compareWithBaseline(report, previous)` adds deltas per metric, category and file, and carries the
|
|
330
|
+
history. `auditMarkdown(report)` renders the summary. `planCssFixes(text, lang, ds)` is the safe autofix: only
|
|
331
|
+
CSS values equal to a token become `var(--token)`.
|
|
332
|
+
|
|
333
|
+
The report schema is `packages/core/audit-report.schema.json` (also exported as
|
|
334
|
+
`@bridge-ds/core/audit-report.schema.json`). `bridge.config.json` takes an optional `audit` section:
|
|
335
|
+
`packages`, `foreignLibraries`, `nativeElements`, `exclude`.
|
|
336
|
+
|
|
337
|
+
## component specs (`component-spec.ts`, generator in `@bridge-ds/core/node`)
|
|
338
|
+
|
|
339
|
+
A declarative description of a coded component that a design tool can rebuild: variant axes, TEXT and BOOLEAN
|
|
340
|
+
properties, parts (frames, text, icons with auto layout) and, per variant, the token path behind every fill, stroke,
|
|
341
|
+
radius, padding, gap, size and text property. The DS Bridge Figma plugin pulls it as component sets bound to variables.
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
import { parseComponentSpecs, variantKeyOf, type ComponentSpec } from '@bridge-ds/core';
|
|
345
|
+
import { generateRepoComponentSpecs, serializeComponentSpecs } from '@bridge-ds/core/node';
|
|
346
|
+
|
|
347
|
+
const file = generateRepoComponentSpecs('/path/to/repo', ['Button']); // reads bridge.config.json, tokens, TSX, CSS modules
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Derived from code, never hand-maintained: `npm run specs` writes `packages/react/component-specs.json` (path:
|
|
351
|
+
`components.specs` in the config), and `packages/core/test/component-specs.test.ts` fails when it is out of date.
|
|
352
|
+
The generator reads the component with the TypeScript parser (props interface, defaults, the JSX it returns for given
|
|
353
|
+
props, nested DS components such as Icon) and runs a small CSS cascade (`spec-css.ts`: classes, attributes, `:hover`,
|
|
354
|
+
`:active`, `:not()`, `:has()`, combinators, `@media (hover: hover)`, specificity, inheritance, custom properties).
|
|
355
|
+
Union props that change the styles become axes; a `State` axis lists the states (Hover, Pressed, Disabled) that look
|
|
356
|
+
different. Values that are not a token (`calc()`, percentages) and props it does not draw are listed in `unmapped`.
|
|
357
|
+
|
|
358
|
+
Swappable parts carry a `swap` (INSTANCE_SWAP property name, default component, options) and each variant lists the
|
|
359
|
+
component it shows in `slots`:
|
|
360
|
+
|
|
361
|
+
- Icon parts (`LucideIcon` props) default to the icon the docs preview, then the `AllStates` story, passes to the
|
|
362
|
+
prop (else Plus, Info for Badge). The file's `icons` section holds the normalized 24x24 SVG (`stroke="currentColor"`)
|
|
363
|
+
of every icon a slot defaults to, every icon the components' docs and stories reference, and a common set
|
|
364
|
+
(`COMMON_ICONS`), read from the installed `lucide-react`; aliases of one Lucide icon fold into the first name seen.
|
|
365
|
+
`iconStyle` names the tokens of the icon components themselves (size, stroke color, stroke width).
|
|
366
|
+
- A compound part's text that code draws full-bleed in some variant (no inline padding: Card `padding="none"`) is a
|
|
367
|
+
content slot: `Slot/<Component> <part> text` or `Slot/Media`, Media where it is full-bleed. The `slots` section
|
|
368
|
+
describes both components.
|