@motion-proto/live-tokens 0.70.0 → 0.71.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/.claude/skills/live-tokens-check-compliance/SKILL.md +72 -0
- package/.claude/skills/live-tokens-fix-findings/SKILL.md +5 -2
- package/CHANGELOG.md +23 -0
- package/README.md +8 -1
- package/bin/check-page.mjs +1 -1
- package/bin/cli.mjs +27 -0
- package/bin/lib/report.mjs +158 -0
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: live-tokens-check-compliance
|
|
3
|
+
description: Check an existing @motion-proto/live-tokens project against its design system and report, without changing a file: which tokens each component reads, which page renders which component, what the two checkers find, and a list of recommended fixes handed to live-tokens-fix-findings. Use when the user asks to check, audit, validate, or review the project, a page, or a component against the design system; asks how compliant it is, what is off, or what it would take to make the build pass; or wants a look before an upgrade. Not for making the changes (live-tokens-fix-findings), and not for a single token (use the editor).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Checking a project against its design system
|
|
7
|
+
|
|
8
|
+
The answer to "check this project" is a report, and every fact in it comes
|
|
9
|
+
from one command. This skill runs it, reads it, and says what the facts mean
|
|
10
|
+
and what fixing them would involve. It edits nothing. When the user wants the
|
|
11
|
+
changes made, that is **live-tokens-fix-findings**, and the report is what it
|
|
12
|
+
starts from.
|
|
13
|
+
|
|
14
|
+
## Run the report
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npx live-tokens report --json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
It always exits 0: it is a reading, not a gate. Unknown command means the
|
|
21
|
+
installed package predates it; upgrade `@motion-proto/live-tokens` first. The
|
|
22
|
+
sections, in the order the report gives them:
|
|
23
|
+
|
|
24
|
+
| Section | Fact | What it means when it is not clean |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `migrations` | Whether `tokens.css` is behind the installed package | A stale file shows up downstream as unknown tokens. This is the first fix, and it is one command: `npx live-tokens migrate --check`, then `--write` (`--tokens <path>` for a tokens.css in an unusual place). |
|
|
27
|
+
| `components[].unread` | Tokens a component declares that nothing in its file reads | An editor row that edits nothing. Each is a token to wire into the CSS or to remove. |
|
|
28
|
+
| `components[].registered` | A component file with no `bootLiveTokens` or `registerComponent` entry | It renders on the page but has no editor. |
|
|
29
|
+
| `components[].described` | Whether the runtime file has the header comment the picker reads | Without one, `live-tokens components` cannot say what it is for. |
|
|
30
|
+
| `usage.byPage` | Which catalogue component each page renders, and how many times | A page rendering none is either chrome or hand-rolled markup that a shipped component covers. |
|
|
31
|
+
| `usage.unusedShipped` | Shipped components no page renders | Information, not a finding. |
|
|
32
|
+
| `usage.customUnregistered`, `usage.customUnused` | The project's own components that are unregistered or unused | Dead or half-wired work. |
|
|
33
|
+
| `findings.pages`, `findings.components` | Both checkers' findings by rule, under the project's severities and again under `--strict` | The errors are what fails the build today; the strict count is what a fully tokenized project would fail. |
|
|
34
|
+
|
|
35
|
+
`npx live-tokens components <id>` and `npx live-tokens tokens --family <name>`
|
|
36
|
+
(both take `--json`) answer any question the report raises about one
|
|
37
|
+
component or one scale.
|
|
38
|
+
|
|
39
|
+
## Read it
|
|
40
|
+
|
|
41
|
+
Facts are the report's; the reading is yours. For each rule with findings, say
|
|
42
|
+
in a line what the rule holds and which of two kinds the fix is:
|
|
43
|
+
|
|
44
|
+
- **Mechanical**: a spacing literal to its nearest `--space-*` step, a stroke
|
|
45
|
+
to `--border-width-*`, a hardcoded column count to `var(--columns-count)`,
|
|
46
|
+
`site.css` moved out of `main.ts`, a route given its `source`. Name any
|
|
47
|
+
visible shift, such as a `14px` margin becoming `16px`.
|
|
48
|
+
- **Judgement**: a colour literal mapped by the role it plays rather than its
|
|
49
|
+
hue, a raw type axis set from a text style, a prop the component does not
|
|
50
|
+
declare mapped or dropped. Say what the choice is, not what you would pick.
|
|
51
|
+
|
|
52
|
+
Where a finding looks deliberate, a translucent overlay on an app shell or a
|
|
53
|
+
layout size the project owns, say so and name the config entry that would
|
|
54
|
+
record the decision: `"checks": { "rules": { "<rule>": "warn" } }` in
|
|
55
|
+
`live-tokens.config.json`. Recording it is the user's call, not yours.
|
|
56
|
+
|
|
57
|
+
## Report
|
|
58
|
+
|
|
59
|
+
In this order, each line carrying its count:
|
|
60
|
+
|
|
61
|
+
1. Migrations pending, and the one command that clears them.
|
|
62
|
+
2. What fails the build now: errors by rule, with the files.
|
|
63
|
+
3. What `--strict` would add: warnings by rule.
|
|
64
|
+
4. Components: unread tokens, unregistered, undescribed.
|
|
65
|
+
5. Usage: what each page renders, and what is used nowhere.
|
|
66
|
+
6. Recommended fixes, in the order **live-tokens-fix-findings** would take
|
|
67
|
+
them: migrations, then the largest group of errors, then the rest, then
|
|
68
|
+
warnings. Mark each as mechanical or judgement.
|
|
69
|
+
|
|
70
|
+
End with the hand-off: "Run live-tokens-fix-findings to apply these", or the
|
|
71
|
+
subset the user chooses. Do not start applying them here, even when the fix is
|
|
72
|
+
one line, because the user asked how things stand.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: live-tokens-fix-findings
|
|
3
|
-
description: Bring an existing @motion-proto/live-tokens project into line with its design system by running check-page and check-component, reading the findings, and fixing each by rule until both exit 0. Use when the user asks to make the build pass, fix the design-system errors or warnings, clean up the literals, replace hex or pixel values with tokens, make a page or component themeable,
|
|
3
|
+
description: Bring an existing @motion-proto/live-tokens project into line with its design system by running check-page and check-component, reading the findings, and fixing each by rule until both exit 0. Use when the user asks to make the build pass, fix the design-system errors or warnings, clean up the literals, replace hex or pixel values with tokens, make a page or component themeable, or apply what a check reported. Not for the check itself (live-tokens-check-compliance reports and edits nothing), not for building a new page (live-tokens-build-page) or a new component (live-tokens-create-component), which run the same gate as their last step, and not for a single token edit (use the editor).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Fixing what the checkers report
|
|
@@ -14,7 +14,10 @@ passes repaints when the theme changes. One that does not has opted out of the
|
|
|
14
14
|
system silently, and these findings are where.
|
|
15
15
|
|
|
16
16
|
This skill is the loop for code that already exists. Run the checker, fix one
|
|
17
|
-
rule at a time, run it again, and stop only when both exit 0.
|
|
17
|
+
rule at a time, run it again, and stop only when both exit 0. When the user has
|
|
18
|
+
not seen the state of the project yet, `npx live-tokens report --json` is the
|
|
19
|
+
reading to start from, and **live-tokens-check-compliance** is the skill that
|
|
20
|
+
presents it without editing; this one edits.
|
|
18
21
|
|
|
19
22
|
## Reach the checkers
|
|
20
23
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.71.0 — Check this project
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`npx live-tokens report` is the project as facts.** Pending `tokens.css`
|
|
8
|
+
migrations, the tokens each component declares and how many its own CSS
|
|
9
|
+
reads (a read counts a `var()`, a `style:` directive, a padding mixin's
|
|
10
|
+
string, or an SCSS-interpolated pattern), whether a component is registered
|
|
11
|
+
and carries the description comment the picker reads, which page renders
|
|
12
|
+
which component and how many times, the shipped and custom components used
|
|
13
|
+
nowhere, and both checkers' findings by rule under the project's severities
|
|
14
|
+
and again under `--strict`. It always exits 0: a reading, not a gate. `--json`
|
|
15
|
+
for data.
|
|
16
|
+
|
|
17
|
+
- **`live-tokens-check-compliance`, the eighth skill.** "Check this project"
|
|
18
|
+
runs the report and presents it without editing a file: what fails the
|
|
19
|
+
build now, what `--strict` would add, the components and usage facts, and a
|
|
20
|
+
list of recommended fixes marked mechanical or judgement with any visible
|
|
21
|
+
shift named, handed to `live-tokens-fix-findings`. A finding that looks
|
|
22
|
+
deliberate is flagged with the config entry that would record the decision,
|
|
23
|
+
which stays the user's call. `live-tokens-fix-findings` no longer claims the
|
|
24
|
+
audit wording, and starts from the report when the user has not seen it.
|
|
25
|
+
|
|
3
26
|
## 0.70.0 — The registry is a query
|
|
4
27
|
|
|
5
28
|
### Added
|
package/README.md
CHANGED
|
@@ -325,6 +325,7 @@ npx @motion-proto/live-tokens <command>
|
|
|
325
325
|
| `setup-claude [--force]` | Install the bundled Claude Code skills into `./.claude/skills/`. |
|
|
326
326
|
| `components [id] [--json]` | List every component the project has, shipped and its own, with the props each takes; with an id, its props, variants, tokens, and defaults. |
|
|
327
327
|
| `tokens [--family <name>] [--json]` | List every theme token the project's `tokens.css` declares, by family, with its value. |
|
|
328
|
+
| `report [--json]` | The project as facts: pending migrations, tokens each component reads, which page renders which component, and both checkers' findings by rule. Always exits 0. |
|
|
328
329
|
| `check-component [id]` | Validate a component's runtime, editor, and registration against the authoring contract; with no id, every component authored under `src/system/components`. |
|
|
329
330
|
| `check-page [paths...]` | Validate pages against the build-page contract: catalogue components and their props, theme tokens over literals, route wiring. |
|
|
330
331
|
| `generate-theme <brief.json> [--no-activate] [--dry-run] [--carry-from <name>]` | Build a full theme from a 10-seed OKLCH brief, enforce AA contrast, write `themes/<slug>.json`, and open it. |
|
|
@@ -336,7 +337,7 @@ Once installed in a project, the same commands are available as `npx live-tokens
|
|
|
336
337
|
|
|
337
338
|
## Claude Code skills
|
|
338
339
|
|
|
339
|
-
The package bundles
|
|
340
|
+
The package bundles eight Claude Code skills. They encode the conventions this README cannot carry in full: which component fits a need, how a page is wired, what a valid theme looks like in OKLCH, how two typefaces sit together, how geometry moves along the token scales, how a project is checked against all of that, and how an existing page or component is brought back into line. Each triggers from an ordinary request, so there are no slash commands to learn.
|
|
340
341
|
|
|
341
342
|
### Install
|
|
342
343
|
|
|
@@ -400,6 +401,12 @@ Ask for something the catalogue lacks: "author a Rating component", "make my Chi
|
|
|
400
401
|
|
|
401
402
|
The skill covers the recipe: the runtime `.svelte` file with its `:global(:root)` token block, the editor `.svelte` file exporting `allTokens` and its variant groups, the `registerComponent()` call, and the catalogue entry that keeps `live-tokens-pick-component` current. It carries the naming scheme, the token suffix vocabulary, the state model (component states such as selected and disabled are separate from interaction states such as hover), and the public-imports rule, and points at the shipped `Toggle` in `node_modules` as the worked example. Linked siblings, intrinsics, and the fixed-overlay portal rule sit in reference files the skill reads only when a component needs them.
|
|
402
403
|
|
|
404
|
+
### `live-tokens-check-compliance`
|
|
405
|
+
|
|
406
|
+
Ask how things stand: "check this project against the design system", "audit the pricing page", "what would it take to make the build pass?", "review this before I upgrade".
|
|
407
|
+
|
|
408
|
+
The skill runs `npx live-tokens report --json`, which is the project as facts: pending `tokens.css` migrations, the tokens each component declares and reads, which page renders which component, and both checkers' findings by rule under the project's severities and under `--strict`. It presents the report, says what each rule holds, marks each recommended fix as mechanical or a judgement call, names any visible shift, and flags a finding that looks deliberate together with the config entry that would record the decision. It edits nothing and ends by handing the list to `live-tokens-fix-findings`.
|
|
409
|
+
|
|
403
410
|
### `live-tokens-fix-findings`
|
|
404
411
|
|
|
405
412
|
Ask for the existing code to catch up: "make check:design pass", "fix the design-system warnings", "replace the hex and pixel values with tokens", "why is check-page failing on the pricing page?".
|
package/bin/check-page.mjs
CHANGED
|
@@ -35,7 +35,7 @@ export const PAGE_RULES = {
|
|
|
35
35
|
// Directories that hold the system, not pages built on it.
|
|
36
36
|
const NOT_PAGES = ['src/system', 'src/editor', 'src/lib', 'src/live-tokens'];
|
|
37
37
|
|
|
38
|
-
const COMPONENT_IMPORT =
|
|
38
|
+
export const COMPONENT_IMPORT =
|
|
39
39
|
/(?:@motion-proto\/live-tokens\/components|[./][^'"]*\/system\/components)\/([A-Za-z0-9]+)\.svelte$/;
|
|
40
40
|
|
|
41
41
|
const DEEP_IMPORT_PATTERNS = [
|
package/bin/cli.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// setup-claude [--force] Copy bundled Claude Code skills into ./.claude/skills/.
|
|
6
6
|
// components [id] List every component the project has, shipped and its own, with props and tokens.
|
|
7
7
|
// tokens [--family <name>] List every theme token by family, with its value.
|
|
8
|
+
// report The project as facts: tokens read, components used, findings by rule. Always exits 0.
|
|
8
9
|
// check-component [id] Validate a component (or every authored one) against the create-component skill contract.
|
|
9
10
|
// check-page [paths...] Validate pages against the build-page skill contract.
|
|
10
11
|
// generate-theme <brief> Build a theme from a 10-seed OKLCH brief and open it.
|
|
@@ -19,6 +20,7 @@ import process from 'node:process';
|
|
|
19
20
|
import { COMPONENT_RULES, checkComponent, discoverComponents, formatReport } from './check-component.mjs';
|
|
20
21
|
import { PAGE_RULES, checkPages, discoverPages } from './check-page.mjs';
|
|
21
22
|
import { describeComponents, describeTokens, formatComponents, formatTokens } from './lib/catalogue.mjs';
|
|
23
|
+
import { buildReport, formatReport as formatProjectReport } from './lib/report.mjs';
|
|
22
24
|
import { loadVocabulary } from './lib/tokenVocabulary.mjs';
|
|
23
25
|
import {
|
|
24
26
|
applySeverity,
|
|
@@ -54,6 +56,12 @@ Commands:
|
|
|
54
56
|
tokens [--family <name>] [--json]
|
|
55
57
|
List every theme token the project's tokens.css
|
|
56
58
|
declares, by family, with its value
|
|
59
|
+
report [--json] The project as facts: pending migrations, tokens
|
|
60
|
+
each component declares and reads, which page
|
|
61
|
+
renders which component, and both checkers'
|
|
62
|
+
findings by rule under the project's severities
|
|
63
|
+
and under --strict. A reading, not a gate: always
|
|
64
|
+
exits 0
|
|
57
65
|
check-component [id] Validate <id>'s runtime, editor, and registration
|
|
58
66
|
against the live-tokens-create-component contract
|
|
59
67
|
check-page [paths...] Validate pages against the live-tokens-build-page
|
|
@@ -190,6 +198,24 @@ if (command === 'tokens') {
|
|
|
190
198
|
process.exit(0);
|
|
191
199
|
}
|
|
192
200
|
|
|
201
|
+
if (command === 'report') {
|
|
202
|
+
const opts = parseCheckFlags(rest);
|
|
203
|
+
const report = buildReport(loadVocabulary());
|
|
204
|
+
try {
|
|
205
|
+
const plan = await runMigrate({ check: true });
|
|
206
|
+
report.migrations =
|
|
207
|
+
plan.status === 'no-path'
|
|
208
|
+
? { status: 'no tokens.css' }
|
|
209
|
+
: plan.status === 'would-change'
|
|
210
|
+
? { status: 'pending', pending: plan.applied ?? plan.migrations ?? [] }
|
|
211
|
+
: { status: 'none pending' };
|
|
212
|
+
} catch {
|
|
213
|
+
report.migrations = { status: 'unavailable (compiled engine not built)' };
|
|
214
|
+
}
|
|
215
|
+
writeOut(opts.json ? JSON.stringify(report, null, 2) : formatProjectReport(report));
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
|
|
193
219
|
if (command === 'check-component') {
|
|
194
220
|
const opts = parseCheckFlags(rest);
|
|
195
221
|
const ids = opts.rest.length > 0 ? [opts.rest[0]] : discoverComponents();
|
|
@@ -368,6 +394,7 @@ const SAMPLE_PROMPTS = {
|
|
|
368
394
|
'live-tokens-adjust-geometry': 'make the buttons pill shaped',
|
|
369
395
|
'live-tokens-pair-fonts': 'pair some fonts for this theme',
|
|
370
396
|
'live-tokens-fix-findings': 'make check:design pass',
|
|
397
|
+
'live-tokens-check-compliance': 'check this project against the design system',
|
|
371
398
|
};
|
|
372
399
|
|
|
373
400
|
const installedSamples = skills
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// The project as facts. Every section is deterministic: what the tokens.css
|
|
2
|
+
// declares, what each component reads, which component each page renders, and
|
|
3
|
+
// what the two checkers report under the project's severities and under
|
|
4
|
+
// --strict. Nothing here interprets; the check skill narrates it and the fix
|
|
5
|
+
// skill acts on it. Reads files only, like the vocabulary it is built on.
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { relative } from 'node:path';
|
|
9
|
+
import { COMPONENT_RULES, checkComponent, discoverComponents } from '../check-component.mjs';
|
|
10
|
+
import { COMPONENT_IMPORT, PAGE_RULES, checkPages, discoverPages } from '../check-page.mjs';
|
|
11
|
+
import { applySeverity, readChecksConfig } from './findings.mjs';
|
|
12
|
+
import { extractGlobalRootBlocks } from './tokenVocabulary.mjs';
|
|
13
|
+
|
|
14
|
+
const SIDES = ['-top', '-right', '-bottom', '-left'];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Tokens a component declares that nothing in its file reads. A read is the
|
|
18
|
+
* name appearing outside the `:global(:root)` block: in a `var()`, in a `style:`
|
|
19
|
+
* directive, or as the string a padding mixin takes. SCSS interpolation
|
|
20
|
+
* (`--badge-#{$v}-surface`) reads every token the pattern covers. A per-side
|
|
21
|
+
* padding is read through its parent.
|
|
22
|
+
*/
|
|
23
|
+
export function unreadTokens(source, tokens) {
|
|
24
|
+
let body = source.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/<!--[\s\S]*?-->/g, ' ');
|
|
25
|
+
for (const block of extractGlobalRootBlocks(body)) body = body.replace(block, ' ');
|
|
26
|
+
const patterns = [...body.matchAll(/--[a-z0-9-]*(?:#\{[^}]*\}[a-z0-9-]*)+/g)].map(
|
|
27
|
+
(m) => new RegExp(`^${m[0].replace(/[.*+?^()|[\]\\]/g, '\\$&').replace(/#\{[^}]*\}/g, '[a-z0-9-]+')}$`),
|
|
28
|
+
);
|
|
29
|
+
const isRead = (name) => body.includes(name) || patterns.some((re) => re.test(name));
|
|
30
|
+
return [...tokens].filter((name) => {
|
|
31
|
+
const side = SIDES.find((s) => name.endsWith(s));
|
|
32
|
+
return !isRead(name) && !(side && isRead(name.slice(0, -side.length)));
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function countByRule(findings) {
|
|
37
|
+
const out = {};
|
|
38
|
+
for (const f of findings) out[f.rule] = (out[f.rule] ?? 0) + 1;
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function summarise(findings, rules, config) {
|
|
43
|
+
const resolved = applySeverity(findings, rules, {}, config);
|
|
44
|
+
const strict = applySeverity(findings, rules, { strict: true }, config);
|
|
45
|
+
return {
|
|
46
|
+
errors: resolved.filter((f) => f.severity === 'error').length,
|
|
47
|
+
warnings: resolved.filter((f) => f.severity === 'warn').length,
|
|
48
|
+
strictErrors: strict.filter((f) => f.severity === 'error').length,
|
|
49
|
+
byRule: countByRule(resolved),
|
|
50
|
+
items: resolved.map((f) => ({ rule: f.rule, severity: f.severity, file: f.file, line: f.line, message: f.message })),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildReport(vocab, { root = process.cwd() } = {}) {
|
|
55
|
+
const config = readChecksConfig(root);
|
|
56
|
+
|
|
57
|
+
const components = [...vocab.components.values()].map((c) => {
|
|
58
|
+
const source = readFileSync(c.file, 'utf8');
|
|
59
|
+
return {
|
|
60
|
+
id: c.id,
|
|
61
|
+
name: c.name,
|
|
62
|
+
origin: c.origin,
|
|
63
|
+
file: relative(root, c.file),
|
|
64
|
+
registered: c.origin === 'shipped' || vocab.registered.has(c.id),
|
|
65
|
+
described: /^\s*<!--[\s\S]*?-->/.test(source),
|
|
66
|
+
tokens: c.tokens.size,
|
|
67
|
+
unread: unreadTokens(source, c.tokens.keys()),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const pageFiles = discoverPages(root);
|
|
72
|
+
const byPage = [];
|
|
73
|
+
const pagesOf = new Map();
|
|
74
|
+
for (const file of pageFiles) {
|
|
75
|
+
if (!file.endsWith('.svelte')) continue;
|
|
76
|
+
const text = readFileSync(file, 'utf8').replace(/<style[^>]*>[\s\S]*?<\/style>/g, ' ');
|
|
77
|
+
const used = [];
|
|
78
|
+
for (const m of text.matchAll(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g)) {
|
|
79
|
+
const comp = m[2].match(COMPONENT_IMPORT);
|
|
80
|
+
if (!comp) continue;
|
|
81
|
+
const id = comp[1].toLowerCase();
|
|
82
|
+
const rendered = [...text.matchAll(new RegExp(`<${m[1]}(?=[\\s/>])`, 'g'))].length;
|
|
83
|
+
used.push({ id, rendered });
|
|
84
|
+
if (!pagesOf.has(id)) pagesOf.set(id, []);
|
|
85
|
+
pagesOf.get(id).push(relative(root, file));
|
|
86
|
+
}
|
|
87
|
+
byPage.push({ file: relative(root, file), components: used });
|
|
88
|
+
}
|
|
89
|
+
const byComponent = components.map((c) => ({ id: c.id, origin: c.origin, pages: pagesOf.get(c.id) ?? [] }));
|
|
90
|
+
|
|
91
|
+
const pageFindings = checkPages(pageFiles, { root, vocabulary: vocab }).findings;
|
|
92
|
+
const authored = discoverComponents(root);
|
|
93
|
+
const componentFindings = authored.flatMap((id) => checkComponent(id, root, { vocabulary: vocab }).findings);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
project: {
|
|
97
|
+
root,
|
|
98
|
+
tokensCss: vocab.tokensCssPath ? relative(root, vocab.tokensCssPath) : null,
|
|
99
|
+
themeTokens: vocab.themeTokens.size,
|
|
100
|
+
components: components.length,
|
|
101
|
+
pages: pageFiles.length,
|
|
102
|
+
},
|
|
103
|
+
components,
|
|
104
|
+
usage: {
|
|
105
|
+
byPage,
|
|
106
|
+
byComponent,
|
|
107
|
+
unusedShipped: byComponent.filter((c) => c.origin === 'shipped' && c.pages.length === 0).map((c) => c.id),
|
|
108
|
+
customUnregistered: components.filter((c) => c.origin === 'custom' && !c.registered).map((c) => c.id),
|
|
109
|
+
customUnused: byComponent.filter((c) => c.origin === 'custom' && c.pages.length === 0).map((c) => c.id),
|
|
110
|
+
},
|
|
111
|
+
findings: {
|
|
112
|
+
pages: summarise(pageFindings, PAGE_RULES, config),
|
|
113
|
+
components: { checked: authored, ...summarise(componentFindings, COMPONENT_RULES, config) },
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const list = (items, max = 20) =>
|
|
119
|
+
items.length <= max ? items.join(', ') : `${items.slice(0, max).join(', ')}, +${items.length - max} more`;
|
|
120
|
+
|
|
121
|
+
export function formatReport(r) {
|
|
122
|
+
const out = [];
|
|
123
|
+
out.push(`Project: ${r.project.pages} page file(s), ${r.project.components} component(s), ${r.project.themeTokens} theme tokens from ${r.project.tokensCss ?? '(no tokens.css)'}`);
|
|
124
|
+
if (r.migrations) {
|
|
125
|
+
out.push('');
|
|
126
|
+
out.push(`Migrations: ${r.migrations.status}${r.migrations.pending?.length ? ` (${list(r.migrations.pending)})` : ''}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
out.push('');
|
|
130
|
+
out.push('Components');
|
|
131
|
+
const unread = r.components.filter((c) => c.unread.length);
|
|
132
|
+
out.push(` tokens declared and read by their own CSS: ${r.components.reduce((n, c) => n + c.tokens - c.unread.length, 0)} of ${r.components.reduce((n, c) => n + c.tokens, 0)}`);
|
|
133
|
+
for (const c of unread) out.push(` ${c.id}: ${c.unread.length} unread (${list(c.unread, 6)})`);
|
|
134
|
+
const custom = r.components.filter((c) => c.origin === 'custom');
|
|
135
|
+
out.push(` custom: ${custom.length}${custom.length ? ` (${list(custom.map((c) => c.id))})` : ''}`);
|
|
136
|
+
if (r.usage.customUnregistered.length) out.push(` not registered: ${list(r.usage.customUnregistered)}`);
|
|
137
|
+
const undescribed = custom.filter((c) => !c.described).map((c) => c.id);
|
|
138
|
+
if (undescribed.length) out.push(` no description comment: ${list(undescribed)}`);
|
|
139
|
+
|
|
140
|
+
out.push('');
|
|
141
|
+
out.push('Usage');
|
|
142
|
+
for (const p of r.usage.byPage) {
|
|
143
|
+
if (p.components.length === 0) continue;
|
|
144
|
+
out.push(` ${p.file}: ${p.components.map((c) => `${c.id}×${c.rendered}`).join(', ')}`);
|
|
145
|
+
}
|
|
146
|
+
out.push(` pages rendering no catalogue component: ${r.usage.byPage.filter((p) => p.components.length === 0).length}`);
|
|
147
|
+
out.push(` shipped components used nowhere: ${r.usage.unusedShipped.length}${r.usage.unusedShipped.length ? ` (${list(r.usage.unusedShipped)})` : ''}`);
|
|
148
|
+
if (r.usage.customUnused.length) out.push(` custom components used nowhere: ${list(r.usage.customUnused)}`);
|
|
149
|
+
|
|
150
|
+
const section = (label, s) => {
|
|
151
|
+
out.push('');
|
|
152
|
+
out.push(`${label}: ${s.errors} error(s), ${s.warnings} warning(s); ${s.strictErrors} under --strict`);
|
|
153
|
+
for (const [rule, n] of Object.entries(s.byRule).sort((a, b) => b[1] - a[1])) out.push(` ${rule}: ${n}`);
|
|
154
|
+
};
|
|
155
|
+
section('check-page', r.findings.pages);
|
|
156
|
+
section(`check-component (${r.findings.components.checked.length} authored)`, r.findings.components);
|
|
157
|
+
return out.join('\n');
|
|
158
|
+
}
|