@bridge-ds/core 0.12.0 → 0.15.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 +26 -2
- package/bridge.config.schema.json +22 -0
- package/dist/audit/index.js +3 -3
- package/dist/component-scan.d.ts +158 -0
- package/dist/config.d.ts +22 -0
- package/dist/contrast-model.d.ts +43 -0
- package/dist/github-client.d.ts +166 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1377 -336
- package/dist/index.js.map +1 -1
- package/dist/node/fs-source.d.ts +5 -0
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +53 -4
- package/dist/node/index.js.map +1 -1
- package/dist/{prod-branch-BdMPKffE.js → prod-branch-BjXFIz74.js} +145 -115
- package/dist/prod-branch-BjXFIz74.js.map +1 -0
- package/dist/repo-access.d.ts +66 -0
- package/dist/repo-model.d.ts +142 -0
- package/dist/{suggestion-apply-DxXRGQrO.js → repo-source-CNZLq_xK.js} +483 -208
- package/dist/repo-source-CNZLq_xK.js.map +1 -0
- package/dist/repo-source-github.d.ts +43 -0
- package/dist/repo-source.d.ts +38 -0
- package/dist/{report-DOnKUw8G.js → report-DCPfvvoq.js} +2 -2
- package/dist/{report-DOnKUw8G.js.map → report-DCPfvvoq.js.map} +1 -1
- package/dist/review-gate.d.ts +8 -1
- package/dist/rights.d.ts +20 -0
- package/dist/snapshot.d.ts +132 -0
- package/dist/suggestion-review.d.ts +4 -1
- package/dist/token-rows.d.ts +122 -0
- package/dist/try-in-product.d.ts +4 -1
- package/package.json +1 -1
- package/dist/prod-branch-BdMPKffE.js.map +0 -1
- package/dist/suggestion-apply-DxXRGQrO.js.map +0 -1
package/README.md
CHANGED
|
@@ -83,7 +83,8 @@ function composeProposalBody(input); function changeTable(changes); function con
|
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
`@bridge-ds/core/node` (Node only): `git`, `isGitRepo`, `currentBranch`, `branchExists`, `showFile`, `listFilesAt`,
|
|
86
|
-
`originRepo`, `commitToNewLocalBranch` (plumbing commit on a new branch; working tree, index and checkout untouched)
|
|
86
|
+
`originRepo`, `commitToNewLocalBranch` (plumbing commit on a new branch; working tree, index and checkout untouched),
|
|
87
|
+
`fsSource(root, ref?)` and `localRepoRef(root)` (a `RepoSource` over a checkout, see repo model).
|
|
87
88
|
|
|
88
89
|
## types
|
|
89
90
|
|
|
@@ -275,7 +276,7 @@ parseSuggestionIssue(body, { comments?, author? }): { ok: true; payload; sha; ma
|
|
|
275
276
|
suggestionPayloadSha(payload); canonicalJson(value); sha256Hex(text); gitBlobSha(text);
|
|
276
277
|
suggestionLabels(payload, { state?, labels? }); suggestionIssueTitle(payload); suggestionStatus({ state, stateReason?, linkedPrs?, review?, applied?, outdated? });
|
|
277
278
|
suggestionPathProblems(payload, config): string[]; unsafePathProblem(path): string | null;
|
|
278
|
-
summarizeSuggestionApprovals(comments, { owners, rights?, teams? }, { payloadSha, author? }); suggestionReviewState(tier, approvals);
|
|
279
|
+
summarizeSuggestionApprovals(comments, { owners, rights?, teams?, soloOwnerCanApproveBoth? }, { payloadSha, author? }); suggestionReviewState(tier, approvals);
|
|
279
280
|
composeSuggestionReviewComment(record); parseSuggestionReviewComment(body); composeSuggestionAppliedComment(record); parseSuggestionAppliedComment(body);
|
|
280
281
|
// GitHub (Issues read and write only)
|
|
281
282
|
createSuggestionOnGitHub(gh, payload, opts); listSuggestionsOnGitHub(gh, { state? }); getSuggestionOnGitHub(gh, n, gov, { outdated? });
|
|
@@ -299,6 +300,29 @@ normalizeStorybookUrl(pasted): string; // https only (http on localhost), drop
|
|
|
299
300
|
storybookIframeUrl(base, id, { viewMode?, globals?, args? }); storybookManagerUrl(base, id, viewMode?); storybookIndexUrls(base);
|
|
300
301
|
```
|
|
301
302
|
|
|
303
|
+
## repo model (repo-source.ts, repo-model.ts, token-rows.ts, contrast-model.ts, component-scan.ts)
|
|
304
|
+
|
|
305
|
+
Everything the docs site and Studio read from a design system repo, from one `RepoSource`, so the build (a local
|
|
306
|
+
checkout) and the browser (any team's repo on GitHub) run the same code. `bridge.config.json` is parsed once; every
|
|
307
|
+
folder comes from it (`tokens.root` and tier folders, `contrast.path`, `components.dir|index|specs`,
|
|
308
|
+
`tokens.usage`), with this repo's former paths as defaults (`REPO_MODEL_DEFAULTS`). Repo content never throws: a bad
|
|
309
|
+
config, docs.json or alias, or a file the source refuses as too large (413), lands in `problems`; a rate limit or
|
|
310
|
+
network error still throws so the caller can retry.
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
interface RepoSource { ref: { owner; repo; sha }; list(prefix?): Promise<string[]>; text(path): Promise<string | null> }
|
|
314
|
+
memorySource(files, ref?); readRepoTexts(source, paths, concurrency?, onError?); // fsSource(root, ref?) in @bridge-ds/core/node
|
|
315
|
+
// gitHubSource(client, sha, opts?) (repo-source-github.ts) is the same RepoSource over GitHub; every list() is sorted.
|
|
316
|
+
loadRepoModel(source, { components?, href?, concurrency? }): Promise<RepoModel>;
|
|
317
|
+
// RepoModel: { ref, configText, config, site: SiteRepo, problems, tokens: { root, files, rows, usage },
|
|
318
|
+
// contrast: { path, specs, pairs }, components: { dir, index, families, list } | null, specs: { path, file, coverage } }
|
|
319
|
+
siteRepoFromConfig(config, raw?, fallback?); parseModelConfig(text);
|
|
320
|
+
buildTokenRows(config, readText, listFiles); tokenRowIndex(rows); tokenRowAxes(config, readText?); tokenRowFiles(config);
|
|
321
|
+
parseContrastSpecs(text); evaluateContrastPairs(specs, rows); hexContrastRatio(a, b);
|
|
322
|
+
scanComponents(input); parseComponentTsx(name, src); parseComponentCss(src, labels, describeVar);
|
|
323
|
+
parseComponentStories(src); parseComponentFamilies(indexSrc, componentsRel?); resolveInheritedTokens(list);
|
|
324
|
+
```
|
|
325
|
+
|
|
302
326
|
## history (history.ts, history-github.ts, history-demo.ts)
|
|
303
327
|
|
|
304
328
|
What changed in the tokens, when, who changed it and why, and how to undo it safely.
|
|
@@ -129,6 +129,11 @@
|
|
|
129
129
|
]
|
|
130
130
|
}
|
|
131
131
|
},
|
|
132
|
+
"usage": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"description": "Optional notes per token group ({ \"color.action\": \"Buttons and links\" }), relative to root. Bridge reads usage.json under root when this is not set.",
|
|
135
|
+
"minLength": 1
|
|
136
|
+
},
|
|
132
137
|
"primitives": {
|
|
133
138
|
"type": "string",
|
|
134
139
|
"description": "Flat form: primitive tier directory."
|
|
@@ -437,6 +442,11 @@
|
|
|
437
442
|
}
|
|
438
443
|
}
|
|
439
444
|
},
|
|
445
|
+
"soloOwnerCanApproveBoth": {
|
|
446
|
+
"type": "boolean",
|
|
447
|
+
"description": "For a design system run by one person. When true and the owners resolve to exactly one person who holds both roles, that person's approval counts as the design and the tech approval, so Tier 2 and Tier 3 changes can be approved. The author of a change still never approves it. Default false: one person never covers both approvals alone.",
|
|
448
|
+
"default": false
|
|
449
|
+
},
|
|
440
450
|
"rights": {
|
|
441
451
|
"type": "object",
|
|
442
452
|
"additionalProperties": false,
|
|
@@ -673,6 +683,18 @@
|
|
|
673
683
|
}
|
|
674
684
|
}
|
|
675
685
|
},
|
|
686
|
+
"library": {
|
|
687
|
+
"type": "object",
|
|
688
|
+
"additionalProperties": false,
|
|
689
|
+
"description": "The team's hosted Bridge Library (/library/<owner>/<repo>/), read from the bridge-data snapshot.",
|
|
690
|
+
"properties": {
|
|
691
|
+
"public": {
|
|
692
|
+
"type": "boolean",
|
|
693
|
+
"default": false,
|
|
694
|
+
"description": "true lets search engines index the hosted Library of a public repository. Default false: every Library page says noindex."
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
},
|
|
676
698
|
"visualImpact": {
|
|
677
699
|
"type": "object",
|
|
678
700
|
"additionalProperties": false,
|
package/dist/audit/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as isAuditable, a as auditFile } from "../report-
|
|
2
|
-
import { A, b, c, C, D, d, F, I, L, e, M, N, f, g, h, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, B, E, G, H, J, K, O, P, Q, R, S, T, U, V, W, X, Y, Z } from "../report-
|
|
3
|
-
import { p as pieceId, a as proposedTokenPath, t as tokenLabel } from "../prod-branch-
|
|
1
|
+
import { i as isAuditable, a as auditFile } from "../report-DCPfvvoq.js";
|
|
2
|
+
import { A, b, c, C, D, d, F, I, L, e, M, N, f, g, h, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, B, E, G, H, J, K, O, P, Q, R, S, T, U, V, W, X, Y, Z } from "../report-DCPfvvoq.js";
|
|
3
|
+
import { p as pieceId, a as proposedTokenPath, t as tokenLabel } from "../prod-branch-BjXFIz74.js";
|
|
4
4
|
const lines = (text) => text.split(/\r?\n/);
|
|
5
5
|
function lineBag(text) {
|
|
6
6
|
const bag = /* @__PURE__ */ new Map();
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { ProseKey } from './docs-draft';
|
|
2
|
+
import type { TokenRowTier } from './token-rows';
|
|
3
|
+
export type ExampleNode = string | {
|
|
4
|
+
c: string;
|
|
5
|
+
p?: Record<string, unknown>;
|
|
6
|
+
children?: ExampleNode[];
|
|
7
|
+
};
|
|
8
|
+
export interface GuidanceItem {
|
|
9
|
+
title: string;
|
|
10
|
+
text: string;
|
|
11
|
+
example?: ExampleNode;
|
|
12
|
+
}
|
|
13
|
+
/** <Name>.docs.json. */
|
|
14
|
+
export interface ComponentDocs {
|
|
15
|
+
summary: string;
|
|
16
|
+
/** Navigation group. Optional; components without one are listed under "Core". */
|
|
17
|
+
category?: string;
|
|
18
|
+
/** Lifecycle shown on the component page and in the nav: stable (default), beta, deprecated. */
|
|
19
|
+
status?: 'stable' | 'beta' | 'deprecated';
|
|
20
|
+
preview?: ExampleNode;
|
|
21
|
+
whenToUse: string[];
|
|
22
|
+
whenNotToUse: string[];
|
|
23
|
+
dos: GuidanceItem[];
|
|
24
|
+
donts: GuidanceItem[];
|
|
25
|
+
accessibility: string[];
|
|
26
|
+
contentGuidelines?: string[];
|
|
27
|
+
anatomy?: Record<string, string>;
|
|
28
|
+
/** "claude" when an AI drafted the prose (docs-draft Action, bridge_draft_docs). */
|
|
29
|
+
draftedBy?: string;
|
|
30
|
+
/** Prose sections Claude drafted that wait for review, when a draft covered only some (missing: the whole file). */
|
|
31
|
+
draftedSections?: ProseKey[];
|
|
32
|
+
/** True while drafted prose waits for a reviewer. */
|
|
33
|
+
needsReview?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface ComponentProp {
|
|
36
|
+
name: string;
|
|
37
|
+
type: string;
|
|
38
|
+
required: boolean;
|
|
39
|
+
defaultValue?: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
}
|
|
42
|
+
/** What the scan knows about a CSS variable: the token it is, and an optional docs link (the caller's routing). */
|
|
43
|
+
export interface ComponentVar {
|
|
44
|
+
name: string;
|
|
45
|
+
href?: string;
|
|
46
|
+
known: boolean;
|
|
47
|
+
path?: string;
|
|
48
|
+
kind?: TokenRowTier;
|
|
49
|
+
}
|
|
50
|
+
export interface ComponentCssRule {
|
|
51
|
+
selector: string;
|
|
52
|
+
/** First class in the selector, used for the anatomy label. */
|
|
53
|
+
className: string;
|
|
54
|
+
label: string;
|
|
55
|
+
vars: ComponentVar[];
|
|
56
|
+
}
|
|
57
|
+
export interface ScannedComponent {
|
|
58
|
+
name: string;
|
|
59
|
+
slug: string;
|
|
60
|
+
/** Repo-relative folder. */
|
|
61
|
+
dir: string;
|
|
62
|
+
/** Repo-relative files (they may not all exist). */
|
|
63
|
+
files: {
|
|
64
|
+
tsx: string;
|
|
65
|
+
css: string;
|
|
66
|
+
stories: string;
|
|
67
|
+
docs: string;
|
|
68
|
+
};
|
|
69
|
+
description?: string;
|
|
70
|
+
props: ComponentProp[];
|
|
71
|
+
inherits?: string;
|
|
72
|
+
omitted: string[];
|
|
73
|
+
subcomponents: string[];
|
|
74
|
+
rules: ComponentCssRule[];
|
|
75
|
+
tokensUsed: {
|
|
76
|
+
name: string;
|
|
77
|
+
href?: string;
|
|
78
|
+
known: boolean;
|
|
79
|
+
kind?: string;
|
|
80
|
+
}[];
|
|
81
|
+
/** Paths of the tokens its own stylesheet reads, in every tier. */
|
|
82
|
+
tokenPaths: string[];
|
|
83
|
+
/**
|
|
84
|
+
* Tokens it reads through another component's stylesheet it imports (IconButton imports Button.module.css), by
|
|
85
|
+
* that component, without the ones its own stylesheet already reads. Followed transitively.
|
|
86
|
+
*/
|
|
87
|
+
inherited: {
|
|
88
|
+
from: string;
|
|
89
|
+
slug: string;
|
|
90
|
+
paths: string[];
|
|
91
|
+
}[];
|
|
92
|
+
/** Own and inherited token paths: what the component actually paints with. */
|
|
93
|
+
allTokenPaths: string[];
|
|
94
|
+
stories: {
|
|
95
|
+
name: string;
|
|
96
|
+
id: string;
|
|
97
|
+
}[];
|
|
98
|
+
docs: ComponentDocs | null;
|
|
99
|
+
/** Family from docs.json `category`, else from the grouped exports of the barrel file. */
|
|
100
|
+
category: string;
|
|
101
|
+
}
|
|
102
|
+
/** "IconButton" -> "icon-button". */
|
|
103
|
+
export declare const componentSlug: (s: string) => string;
|
|
104
|
+
/**
|
|
105
|
+
* Families in the order the barrel file lists them ("// Actions" then its exports). `componentsRel` is the components
|
|
106
|
+
* folder relative to the barrel's folder ("components"); exports from elsewhere are not members.
|
|
107
|
+
*/
|
|
108
|
+
export declare function parseComponentFamilies(indexSrc: string, componentsRel?: string): {
|
|
109
|
+
name: string;
|
|
110
|
+
members: string[];
|
|
111
|
+
}[];
|
|
112
|
+
/** Props (with defaults and JSDoc), the native element it extends, its description and sub-components, from <Name>.tsx. */
|
|
113
|
+
export declare function parseComponentTsx(name: string, src: string): {
|
|
114
|
+
props: ComponentProp[];
|
|
115
|
+
inherits: string | undefined;
|
|
116
|
+
omitted: string[];
|
|
117
|
+
description: string | undefined;
|
|
118
|
+
subcomponents: string[];
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Minimal CSS reader: selector -> var(--*) list, labelled with the docs.json anatomy. Skips @keyframes, flattens
|
|
122
|
+
* @media. `describe` says what a variable is (token path, tier, docs link); undefined: not a token.
|
|
123
|
+
*/
|
|
124
|
+
export declare function parseComponentCss(src: string, labels: Record<string, string>, describe: (cssVar: string) => Omit<ComponentVar, 'name' | 'known'> | undefined): ComponentCssRule[];
|
|
125
|
+
/** Story names and Storybook ids of a <Name>.stories.tsx (CSF with a `title`). */
|
|
126
|
+
export declare function parseComponentStories(src: string): {
|
|
127
|
+
name: string;
|
|
128
|
+
id: string;
|
|
129
|
+
}[];
|
|
130
|
+
/**
|
|
131
|
+
* Fills `inherited` and `allTokenPaths` from the stylesheets each component imports from another component,
|
|
132
|
+
* transitively (A imports B's stylesheet, B imports C's: A inherits from B and C). Removes `reuses`.
|
|
133
|
+
*/
|
|
134
|
+
export declare function resolveInheritedTokens(list: (Pick<ScannedComponent, 'name' | 'slug' | 'tokenPaths' | 'inherited' | 'allTokenPaths'> & {
|
|
135
|
+
reuses?: string[];
|
|
136
|
+
})[]): void;
|
|
137
|
+
export interface ComponentScanInput {
|
|
138
|
+
/** Components folder, repo-relative (bridge.config.json components.dir). */
|
|
139
|
+
dir: string;
|
|
140
|
+
/** Every repo-relative file under `dir` (RepoSource.list(dir)). */
|
|
141
|
+
files: string[];
|
|
142
|
+
/** Body of a repo-relative file, undefined when missing. */
|
|
143
|
+
readText: (path: string) => string | undefined;
|
|
144
|
+
/** Barrel file body (components.index), for families. Undefined: no families. */
|
|
145
|
+
indexText?: string | undefined;
|
|
146
|
+
/** Components folder relative to the barrel's folder. Default "components". */
|
|
147
|
+
indexRel?: string;
|
|
148
|
+
docsSuffix?: string;
|
|
149
|
+
styleSuffix?: string;
|
|
150
|
+
/** What a CSS variable is (token path, tier, docs link). Undefined: not a token. */
|
|
151
|
+
describeVar: (cssVar: string) => Omit<ComponentVar, 'name' | 'known'> | undefined;
|
|
152
|
+
/** Problems found (a docs.json that does not parse) are reported here instead of failing the scan. */
|
|
153
|
+
problems?: string[];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Every component of the folder: a sub-folder <Name> holding <Name>.tsx. Sorted by family (barrel order), then name.
|
|
157
|
+
*/
|
|
158
|
+
export declare function scanComponents(input: ComponentScanInput): ScannedComponent[];
|
package/dist/config.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface StorybookConfig {
|
|
|
17
17
|
/** Root URL (where index.json and iframe.html are served), https only, no trailing slash. */
|
|
18
18
|
url: string;
|
|
19
19
|
}
|
|
20
|
+
/** The team's hosted Library (/library/<owner>/<repo>/). */
|
|
21
|
+
export interface LibraryConfig {
|
|
22
|
+
/** True lets search engines index the hosted Library of a public repository. Default false: noindex. */
|
|
23
|
+
public?: boolean;
|
|
24
|
+
}
|
|
20
25
|
export interface RepoRef {
|
|
21
26
|
owner: string;
|
|
22
27
|
name: string;
|
|
@@ -48,6 +53,8 @@ export interface TokenConfig extends TokenPaths {
|
|
|
48
53
|
/** Tier name -> directories or files relative to root. First match wins. */
|
|
49
54
|
tiers: Record<string, string[]>;
|
|
50
55
|
layers: TokenLayer[];
|
|
56
|
+
/** Optional notes per token group ({ "color.action": "Buttons and links" }), relative to root. Default usage.json when present. */
|
|
57
|
+
usage?: string | undefined;
|
|
51
58
|
}
|
|
52
59
|
export interface ModeAxis {
|
|
53
60
|
values: string[];
|
|
@@ -114,6 +121,11 @@ export interface GovernanceConfig {
|
|
|
114
121
|
* and anyone with write access may ship, as before the field existed.
|
|
115
122
|
*/
|
|
116
123
|
rights?: Rights | undefined;
|
|
124
|
+
/**
|
|
125
|
+
* A design system run by one person: when the owners resolve to exactly one person holding both roles, that
|
|
126
|
+
* person's approval counts for design and tech. Default false: one person never covers both approvals alone.
|
|
127
|
+
*/
|
|
128
|
+
soloOwnerCanApproveBoth?: boolean | undefined;
|
|
117
129
|
}
|
|
118
130
|
/** `bridge audit` settings: how product code that consumes the design system is measured. */
|
|
119
131
|
export interface AuditConfig {
|
|
@@ -171,6 +183,8 @@ export interface BridgeConfig {
|
|
|
171
183
|
changes: ChangesMode;
|
|
172
184
|
/** The Storybook set in the file. Read it through storybookUrl(), which falls back to previews.production.storybook. */
|
|
173
185
|
storybook?: StorybookConfig | undefined;
|
|
186
|
+
/** The hosted Library. Read it through libraryIsPublic(). */
|
|
187
|
+
library?: LibraryConfig | undefined;
|
|
174
188
|
}
|
|
175
189
|
type Partialish<T> = {
|
|
176
190
|
[K in keyof T]?: T[K] | undefined;
|
|
@@ -193,6 +207,7 @@ export interface BridgeConfigInput {
|
|
|
193
207
|
files: string;
|
|
194
208
|
optional?: boolean;
|
|
195
209
|
})[];
|
|
210
|
+
usage?: string;
|
|
196
211
|
};
|
|
197
212
|
modes?: (Partial<Omit<ModeConfig, 'defaults'>> & {
|
|
198
213
|
defaults?: ModeConfig['defaults'];
|
|
@@ -235,6 +250,7 @@ export interface BridgeConfigInput {
|
|
|
235
250
|
storybook?: {
|
|
236
251
|
url: string;
|
|
237
252
|
};
|
|
253
|
+
library?: LibraryConfig;
|
|
238
254
|
}
|
|
239
255
|
export declare const DEFAULT_TOKEN_PATHS: TokenPaths;
|
|
240
256
|
export declare const DEFAULT_GOVERNANCE: GovernanceConfig;
|
|
@@ -259,6 +275,12 @@ export declare const changesMode: (config: Pick<BridgeConfig, "changes"> | null
|
|
|
259
275
|
* when neither is set. No trailing slash.
|
|
260
276
|
*/
|
|
261
277
|
export declare function storybookUrl(config: Pick<BridgeConfig, 'storybook' | 'previews'> | null | undefined): string | undefined;
|
|
278
|
+
/**
|
|
279
|
+
* True when the team opted in to search engines for its hosted Library (`"library": { "public": true }`). Off by
|
|
280
|
+
* default: a Library page under the Bridge domain shows content Bridge does not control, so it is noindex unless the
|
|
281
|
+
* team asks.
|
|
282
|
+
*/
|
|
283
|
+
export declare const libraryIsPublic: (config: Pick<BridgeConfig, "library"> | null | undefined) => boolean;
|
|
262
284
|
/** Plain-JSON form of a config, in the canonical shape (what `bridge init` writes). */
|
|
263
285
|
export declare function toConfigJson(config: BridgeConfig, schema?: string): Record<string, unknown>;
|
|
264
286
|
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { TokenRow, TokenRows } from './token-rows';
|
|
2
|
+
export interface ContrastPairSpec {
|
|
3
|
+
fg: string;
|
|
4
|
+
bg: string;
|
|
5
|
+
usage: 'text' | 'ui';
|
|
6
|
+
/** Minimum ratio the build enforces for this pair. */
|
|
7
|
+
min: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ContrastModeResult {
|
|
10
|
+
fgHex: string;
|
|
11
|
+
bgHex: string;
|
|
12
|
+
/** Rounded down to 2 decimals, so 4.496 never displays as a passing 4.50. */
|
|
13
|
+
ratio: number;
|
|
14
|
+
aa: boolean;
|
|
15
|
+
/** null when AAA is not defined (non-text UI). */
|
|
16
|
+
aaa: boolean | null;
|
|
17
|
+
}
|
|
18
|
+
export interface ContrastPairResult extends ContrastPairSpec {
|
|
19
|
+
fgToken: TokenRow;
|
|
20
|
+
bgToken: TokenRow;
|
|
21
|
+
/** Default brand, per theme. */
|
|
22
|
+
modes: Record<string, ContrastModeResult>;
|
|
23
|
+
brands: Record<string, Record<string, ContrastModeResult>>;
|
|
24
|
+
}
|
|
25
|
+
/** WCAG thresholds: body text 4.5:1 (AA) and 7:1 (AAA); UI parts and graphics 3:1 (AA, 1.4.11). */
|
|
26
|
+
export declare const CONTRAST_THRESHOLDS: {
|
|
27
|
+
readonly text: {
|
|
28
|
+
readonly aa: 4.5;
|
|
29
|
+
readonly aaa: 7;
|
|
30
|
+
};
|
|
31
|
+
readonly ui: {
|
|
32
|
+
readonly aa: 3;
|
|
33
|
+
readonly aaa: null;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
/** Relative luminance of a #rrggbb color. */
|
|
37
|
+
export declare function hexLuminance(hex: string): number;
|
|
38
|
+
/** Contrast ratio of two opaque #rrggbb colors. */
|
|
39
|
+
export declare function hexContrastRatio(a: string, b: string): number;
|
|
40
|
+
/** The pairs of a contrast checks file ({ "pairs": [[fg, bg, min] | { fg, bg, min }] }). Empty when there is no file. */
|
|
41
|
+
export declare function parseContrastSpecs(text: string | null | undefined): ContrastPairSpec[];
|
|
42
|
+
/** Resolves pair specs against the token rows in every brand and theme. Throws on a pair naming a missing token. */
|
|
43
|
+
export declare function evaluateContrastPairs(specs: ContrastPairSpec[], rows: Pick<TokenRows, 'all' | 'brands' | 'themes' | 'defaultBrand'>): ContrastPairResult[];
|
package/dist/github-client.d.ts
CHANGED
|
@@ -60,7 +60,9 @@ export interface RepoInfo {
|
|
|
60
60
|
private: boolean;
|
|
61
61
|
permissions: {
|
|
62
62
|
admin?: boolean;
|
|
63
|
+
maintain?: boolean;
|
|
63
64
|
push?: boolean;
|
|
65
|
+
triage?: boolean;
|
|
64
66
|
pull?: boolean;
|
|
65
67
|
} | undefined;
|
|
66
68
|
}
|
|
@@ -69,6 +71,8 @@ export interface TreeEntry {
|
|
|
69
71
|
type: 'blob' | 'tree' | 'commit';
|
|
70
72
|
sha: string;
|
|
71
73
|
mode: string;
|
|
74
|
+
/** Bytes, for blobs (GitHub sends it in tree listings). */
|
|
75
|
+
size?: number | undefined;
|
|
72
76
|
}
|
|
73
77
|
export interface CompareResult {
|
|
74
78
|
status: string;
|
|
@@ -123,7 +127,119 @@ export interface ClientOptions {
|
|
|
123
127
|
* the MCP server config"). Default: "add a GitHub token".
|
|
124
128
|
*/
|
|
125
129
|
tokenHint?: string;
|
|
130
|
+
/**
|
|
131
|
+
* Where conditional GET responses are kept (ETag + body). Default: one in-memory store shared by every client in
|
|
132
|
+
* this process or page, keyed by the token's fingerprint, so two accounts never share an entry. `false` turns
|
|
133
|
+
* conditional requests off. A 304 answer does not count against GitHub's primary rate limit.
|
|
134
|
+
*/
|
|
135
|
+
etags?: EtagStore | false;
|
|
136
|
+
/**
|
|
137
|
+
* Who the cached responses belong to, when a stable name is known (the signed-in login). Default: a fingerprint of
|
|
138
|
+
* the token, which changes when the token is refreshed.
|
|
139
|
+
*/
|
|
140
|
+
etagScope?: string;
|
|
141
|
+
/**
|
|
142
|
+
* When GitHub's hourly limit is used up and a cached response exists for that request, return it instead of
|
|
143
|
+
* throwing. Off by default: the caller then knows the data may be stale only from `rateLimit`.
|
|
144
|
+
*/
|
|
145
|
+
serveStaleOnRateLimit?: boolean;
|
|
146
|
+
}
|
|
147
|
+
/** One cached conditional response: what GitHub sent with this ETag. */
|
|
148
|
+
export interface EtagEntry {
|
|
149
|
+
etag: string;
|
|
150
|
+
body: unknown;
|
|
151
|
+
/** When it was stored or last confirmed by a 304 (ms since epoch). */
|
|
152
|
+
at: number;
|
|
153
|
+
}
|
|
154
|
+
/** Storage for conditional requests. Keys never contain the token. */
|
|
155
|
+
export interface EtagStore {
|
|
156
|
+
get(key: string): EtagEntry | undefined;
|
|
157
|
+
set(key: string, entry: EtagEntry): void;
|
|
158
|
+
/** Forgets every entry (sign-out). */
|
|
159
|
+
clear(): void;
|
|
160
|
+
}
|
|
161
|
+
/** An in-memory ETag store that keeps the `max` most recently used entries. */
|
|
162
|
+
export declare function memoryEtagStore(max?: number): EtagStore;
|
|
163
|
+
/** The subset of the Web Storage API (localStorage, sessionStorage) an ETag store needs. */
|
|
164
|
+
export interface StorageLike {
|
|
165
|
+
getItem(key: string): string | null;
|
|
166
|
+
setItem(key: string, value: string): void;
|
|
167
|
+
removeItem(key: string): void;
|
|
168
|
+
key(index: number): string | null;
|
|
169
|
+
readonly length: number;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* An ETag store over Web Storage, so conditional requests survive a page load. Every access is guarded: private
|
|
173
|
+
* windows, quota errors and blocked storage turn into cache misses, never into failures. Entries live under
|
|
174
|
+
* `prefix` (default "bridge.gh.etag:"); `clear()` removes only those. Bodies of private repositories land here
|
|
175
|
+
* too, so a sign-out must call `clear()`.
|
|
176
|
+
*/
|
|
177
|
+
export declare function webStorageEtagStore(storage: StorageLike, opts?: {
|
|
178
|
+
prefix?: string;
|
|
179
|
+
maxEntryBytes?: number;
|
|
180
|
+
}): EtagStore;
|
|
181
|
+
/** Clears the default in-memory ETag store every client shares (sign-out, account switch). */
|
|
182
|
+
export declare function clearSharedEtags(): void;
|
|
183
|
+
/** A short, non-reversible name for a token, used in cache keys instead of the token itself (two FNV-1a passes). */
|
|
184
|
+
export declare function tokenFingerprint(token: string): string;
|
|
185
|
+
/** GitHub's rate-limit headers from the last API response. */
|
|
186
|
+
export interface RateLimitInfo {
|
|
187
|
+
limit: number | undefined;
|
|
188
|
+
remaining: number | undefined;
|
|
189
|
+
resetsAt: Date | undefined;
|
|
190
|
+
/** "core", "search", "graphql"... */
|
|
191
|
+
resource: string | undefined;
|
|
192
|
+
}
|
|
193
|
+
/** What the conditional request cache did, for tests and diagnostics. */
|
|
194
|
+
export interface EtagStats {
|
|
195
|
+
/** Requests sent with If-None-Match. */
|
|
196
|
+
conditional: number;
|
|
197
|
+
/** 304 answers served from the cache (free for the primary rate limit). */
|
|
198
|
+
notModified: number;
|
|
199
|
+
/** Cached bodies returned because the hourly limit was used up (serveStaleOnRateLimit). */
|
|
200
|
+
stale: number;
|
|
201
|
+
}
|
|
202
|
+
/** An installation of a GitHub App the signed-in user can reach (GET /user/installations). */
|
|
203
|
+
export interface Installation {
|
|
204
|
+
id: number;
|
|
205
|
+
account: {
|
|
206
|
+
login: string;
|
|
207
|
+
type: string;
|
|
208
|
+
} | null;
|
|
209
|
+
/** "all" repositories of the account, or "selected" ones. */
|
|
210
|
+
repositorySelection: 'all' | 'selected' | string;
|
|
211
|
+
appSlug: string | undefined;
|
|
212
|
+
}
|
|
213
|
+
/** One repository of an installation (GET /user/installations/{id}/repositories). */
|
|
214
|
+
export interface InstallationRepo {
|
|
215
|
+
owner: string;
|
|
216
|
+
name: string;
|
|
217
|
+
fullName: string;
|
|
218
|
+
private: boolean;
|
|
219
|
+
}
|
|
220
|
+
/** One installation of the GitHub App, as GET /user/installations lists it (raw or mapped). */
|
|
221
|
+
export interface InstallationLike {
|
|
222
|
+
id: number;
|
|
223
|
+
account?: {
|
|
224
|
+
login?: string;
|
|
225
|
+
} | null;
|
|
226
|
+
repository_selection?: 'all' | 'selected' | string;
|
|
227
|
+
repositorySelection?: 'all' | 'selected' | string;
|
|
126
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Whether the signed-in account reaches a repository of `owner` through the app: 'yes' when an installation on the
|
|
231
|
+
* owner covers all its repositories, 'check' (with its id) when it covers selected ones (list them to know), 'no'
|
|
232
|
+
* when there is none. Same contract as the site's `installationFor` (lib/github-signin.ts).
|
|
233
|
+
*/
|
|
234
|
+
export declare function installationFor(list: InstallationLike[], owner: string): {
|
|
235
|
+
reach: 'yes';
|
|
236
|
+
id: number;
|
|
237
|
+
} | {
|
|
238
|
+
reach: 'check';
|
|
239
|
+
id: number;
|
|
240
|
+
} | {
|
|
241
|
+
reach: 'no';
|
|
242
|
+
};
|
|
127
243
|
/**
|
|
128
244
|
* The sentence for GitHub's exhausted hourly limit. With a token, the token's own limit is used up: wait. Without one,
|
|
129
245
|
* the shared limit for anonymous reads is (60 an hour per address): say how to get a higher one.
|
|
@@ -199,7 +315,14 @@ export declare class GitHubClient {
|
|
|
199
315
|
private readonly blobCache;
|
|
200
316
|
private readonly api;
|
|
201
317
|
private readonly protectedBranches;
|
|
318
|
+
private readonly etags;
|
|
319
|
+
private readonly etagScope;
|
|
320
|
+
private rate;
|
|
321
|
+
/** What the conditional request cache did for this client. */
|
|
322
|
+
readonly etagStats: EtagStats;
|
|
202
323
|
constructor(repo: string, token: string, fetchImpl?: FetchLike, retry?: RetryPolicy, options?: ClientOptions);
|
|
324
|
+
/** GitHub's rate-limit headers from the last API response, or undefined before any response carried them. */
|
|
325
|
+
get rateLimit(): RateLimitInfo | undefined;
|
|
203
326
|
/** "owner/name". */
|
|
204
327
|
get fullName(): string;
|
|
205
328
|
/** Whether a token was given (without one, every write is refused before any request). */
|
|
@@ -209,7 +332,13 @@ export declare class GitHubClient {
|
|
|
209
332
|
private assertToken;
|
|
210
333
|
private get repoPath();
|
|
211
334
|
private request;
|
|
335
|
+
private noteRateLimit;
|
|
212
336
|
private once;
|
|
337
|
+
/**
|
|
338
|
+
* Every item of a paged listing (`per_page=100`), at most 10 pages. `pick` takes the items out of one page (the
|
|
339
|
+
* installation endpoints wrap them in an object with `total_count`).
|
|
340
|
+
*/
|
|
341
|
+
private paged;
|
|
213
342
|
getRepo(): Promise<RepoInfo>;
|
|
214
343
|
/** Commit sha a branch points to, or undefined when the branch does not exist. */
|
|
215
344
|
getBranchSha(branch: string): Promise<string | undefined>;
|
|
@@ -408,6 +537,43 @@ export declare class GitHubClient {
|
|
|
408
537
|
* hourly API limit); with one, through the contents API (private repos work).
|
|
409
538
|
*/
|
|
410
539
|
readFile(path: string, ref: string): Promise<string | undefined>;
|
|
540
|
+
/**
|
|
541
|
+
* Text of a file from the raw host (raw.githubusercontent.com), sent without any header so the browser makes a
|
|
542
|
+
* simple CORS request (no preflight) and nothing counts against the API limit. Works for public repositories only:
|
|
543
|
+
* a private file answers 404 there, like a missing one. Undefined on 404.
|
|
544
|
+
*/
|
|
545
|
+
readRawFile(path: string, ref: string): Promise<string | undefined>;
|
|
546
|
+
/**
|
|
547
|
+
* Text of a file at a ref through the contents API with the raw media type: the way to read a private file (the raw
|
|
548
|
+
* host cannot take the token without a preflight). Counts against the API limit; conditional when cached.
|
|
549
|
+
* Undefined when the file does not exist.
|
|
550
|
+
*/
|
|
551
|
+
getRawText(path: string, ref: string): Promise<string | undefined>;
|
|
552
|
+
/**
|
|
553
|
+
* Text of a blob by its sha through the git blobs API with the raw media type: how a signed-in reader loads a file
|
|
554
|
+
* listed by a tree (the raw host refuses any request that carries a token). A blob sha names its content, so the
|
|
555
|
+
* answer is cached for the client's life and never revalidated. Undefined on 404.
|
|
556
|
+
*/
|
|
557
|
+
getBlobRaw(sha: string): Promise<string | undefined>;
|
|
558
|
+
/**
|
|
559
|
+
* Installations of the signing-in GitHub App that the signed-in user can reach (GET /user/installations, every
|
|
560
|
+
* page). Needs a user-to-server token of that same App: a personal or OAuth token is refused (403), and each App
|
|
561
|
+
* sees only its own installations. GitHub lists only installations on accounts and repositories the user has
|
|
562
|
+
* explicit permission on: a public repository where the user is not a collaborator is never listed, installed or
|
|
563
|
+
* not. (GET /repos/{o}/{r}/installation would say, but it needs the App's private key, which Bridge never holds.)
|
|
564
|
+
*/
|
|
565
|
+
listUserInstallations(): Promise<Installation[]>;
|
|
566
|
+
/** Repositories of one installation the signed-in user can reach (GET /user/installations/{id}/repositories, every page). */
|
|
567
|
+
listInstallationRepos(installationId: number): Promise<InstallationRepo[]>;
|
|
568
|
+
/**
|
|
569
|
+
* The App installation that gives the signed-in user this repository (this client's by default): its id, or null
|
|
570
|
+
* when the App is not installed there or the user cannot reach it. One call when the installation covers all of
|
|
571
|
+
* the owner's repositories, two (paged) when it covers selected ones.
|
|
572
|
+
*/
|
|
573
|
+
findInstallation(owner?: string, repo?: string): Promise<{
|
|
574
|
+
id: number;
|
|
575
|
+
selection: 'all' | 'selected';
|
|
576
|
+
} | null>;
|
|
411
577
|
/** The repository's default branch. */
|
|
412
578
|
getDefaultBranch(): Promise<string>;
|
|
413
579
|
/** Every file path at a ref (branch, tag or sha), one API call. */
|
package/dist/index.d.ts
CHANGED
|
@@ -64,3 +64,11 @@ export * from './suggestion-review';
|
|
|
64
64
|
export * from './suggestions-github';
|
|
65
65
|
export * from './suggestion-apply';
|
|
66
66
|
export * from './storybook';
|
|
67
|
+
export * from './repo-source';
|
|
68
|
+
export * from './token-rows';
|
|
69
|
+
export * from './contrast-model';
|
|
70
|
+
export * from './component-scan';
|
|
71
|
+
export * from './repo-model';
|
|
72
|
+
export * from './repo-source-github';
|
|
73
|
+
export * from './repo-access';
|
|
74
|
+
export * from './snapshot';
|