@designtools/adherence 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @designtools/adherence
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f16f305: First release. `designtools-adherence` reports how closely a React product follows its design system, reading the `@designtools/manifest` JSON and nothing else: the share of replaceable elements that come from the system (system component uses against the raw elements a component `replaces`), classes that bypass the tokens (palette steps and arbitrary values, through eslint-plugin-better-tailwindcss), and classNames that override a system component's colour, spacing or radius. Per file, per Next route and in total, as markdown for a pull request or deterministic JSON for docs. Warnings only: it exits 0 whatever it finds.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Made by Many Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,99 @@
1
- # Temporary Holding Version
1
+ # @designtools/adherence
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Reports how closely a React product follows its design system: how much of what a system component could render comes from the system, which classes bypass the tokens, and where a component's `className` fights its variants. It reads the JSON that [`@designtools/manifest`](../manifest) writes, so it needs no configuration of its own.
4
+
5
+ ```bash
6
+ npx @designtools/adherence # markdown, for a pull request comment
7
+ npx @designtools/adherence --format json # the JSON report
8
+ npx @designtools/adherence --json adherence.json # markdown to stdout, JSON to a file
9
+ ```
10
+
11
+ A report, never a gate. Every finding is a warning, and it exits 0 whatever it finds; 2 means it could not run at all. Use it to see where a product drifts and which components are missing a variant, not to fail a build.
12
+
13
+ Deterministic: the same source gives the same bytes, sorted throughout, with no timestamps.
14
+
15
+ ## Setup
16
+
17
+ None beyond the manifest. It finds `components.json` from `designtools.json` at the project root, as the other tools do, and reads `tokens.json` beside it:
18
+
19
+ ```json
20
+ {
21
+ "manifest": { "system": "src/ds" },
22
+ "adherence": { "exclude": ["app/legacy/**"] }
23
+ }
24
+ ```
25
+
26
+ `--manifest <components.json>` reads a manifest from elsewhere, and `--root` sets the project root.
27
+
28
+ ## What it scans
29
+
30
+ The product: `app/` and `src/`, by default. Never the system folder, the docs pages under `app/design/`, tests, `__tests__` folders, fixtures, examples, stories or `node_modules`. `--include <glob>` replaces the product globs, `--exclude <glob>` adds to what is left out; repeat either, or record them under `adherence` in `designtools.json`. A Next folder name in a glob means the folder: `--exclude "app/(app)/system/**"` leaves out that route group.
31
+
32
+ ## What it reports
33
+
34
+ **Component use.** Every JSX use of a system component, with its props, and every raw element that some component `replaces`. A component declares what it stands in for with `@replaces` in its JSDoc (`@replaces button`, `@replaces input[type=checkbox]`, `@replaces a[href]`), which the manifest records. Which imports are the system comes from the manifest too: an import that resolves, relatively or through a tsconfig `paths` alias, to a component's source file is that component, and one that resolves to another file in the system folder, such as a barrel `index.ts`, is matched by export name. Namespace imports (`<DS.Button>`) and sub-components (`<Select.Trigger>`) count.
35
+
36
+ From these comes the share: system component uses ÷ (system component uses + raw replaceable elements). Until a component declares `@replaces`, there are no raw elements to count and the report says so.
37
+
38
+ **Off-system values.** Classes that bypass the tokens:
39
+
40
+ | Kind | Examples |
41
+ | --- | --- |
42
+ | palette | `bg-blue-500`, `hover:text-primary-500/80`, `bg-(--color-primary-500)`: any `--color-<family>-<step>` ramp step in `tokens.json`, and Tailwind's default palette, used directly |
43
+ | arbitrary | `p-[13px]`, `md:bg-[#fff]`, `grid-cols-[1fr_2fr]`, `[mask-type:alpha]`. A token reference in brackets, `w-[var(--size-md)]`, is not one |
44
+
45
+ They are found wherever Tailwind classes live, `className`, `cn()`, `clsx()`, `tv()`, `cva()` and the like, by [eslint-plugin-better-tailwindcss](https://github.com/schoero/eslint-plugin-better-tailwindcss)'s `no-restricted-classes` rule, run through ESLint's Node API with an in-memory config generated from `tokens.json`. The project's own ESLint setup is neither read nor changed.
46
+
47
+ **Overrides.** A `className` on a system component that changes its colour (`bg-`, `text-`, `border-`, `ring-` … with a colour), spacing (`p-`, `m-`, `gap-`, `space-`) or radius (`rounded-`). That usually means someone fought the component instead of choosing a variant, or that the component is missing one. Classes inside `cn()` count; layout and size (`w-full`, `flex-1`, `text-sm`) do not.
48
+
49
+ All of it per file, per route and in total. A route is a Next app router page, `app/**/page.tsx`, with route groups dropped (`app/(shop)/checkout/page.tsx` is `/checkout`). A route counts its page, the files beside it, and files in folders under it that lead to no other page, such as a private `_components` folder. A layout shared by several routes counts per file only. Per component, it also counts which props are passed and which variant options are picked, and lists the components the product never uses.
50
+
51
+ ## The report
52
+
53
+ The markdown is a pull request comment: the summary, a table of routes, then each file's findings with line numbers, and the components by use. It stays under GitHub's comment limit; past it, the remaining files are in the JSON.
54
+
55
+ The JSON, abridged:
56
+
57
+ ```json
58
+ {
59
+ "generator": "@designtools/adherence@0.1.0",
60
+ "schema": "designtools.adherence/1",
61
+ "manifest": "src/ds/manifest/components.json",
62
+ "system": "src/ds",
63
+ "summary": { "systemUses": 10, "rawElements": 5, "share": 0.6667, "offSystemValues": 6, "overrides": 2, "files": 7, "filesWithFindings": 5 },
64
+ "routes": [
65
+ { "route": "/checkout", "page": "app/(shop)/checkout/page.tsx", "files": ["app/(shop)/checkout/_parts/summary.tsx", "app/(shop)/checkout/page.tsx"], "counts": { "systemUses": 4, "rawElements": 1, "share": 0.8, "offSystemValues": 4, "overrides": 1 } }
66
+ ],
67
+ "files": [
68
+ {
69
+ "file": "app/(shop)/checkout/page.tsx",
70
+ "route": "/checkout",
71
+ "raw": [{ "element": "input", "selector": "input[type=checkbox]", "replacedBy": ["Checkbox"], "line": 10, "column": 10 }],
72
+ "offSystem": [{ "class": "p-[13px]", "kind": "arbitrary", "line": 7, "column": 22 }],
73
+ "overrides": [{ "component": "Button", "classes": [{ "class": "bg-destructive", "kind": "colour" }, { "class": "px-6", "kind": "spacing" }], "line": 13, "column": 8 }]
74
+ }
75
+ ]
76
+ }
77
+ ```
78
+
79
+ The full schema is in [`src/types.ts`](src/types.ts), which has no imports so it can be copied, for the "Adherence summary" docs block in [`@designtools/blocks`](../blocks) to read the report through the same types.
80
+
81
+ ## Programmatic use
82
+
83
+ ```ts
84
+ import { buildReport, formatMarkdown, loadConfig } from "@designtools/adherence";
85
+
86
+ const { report } = await buildReport(loadConfig({ root: "." }));
87
+ report.summary.share; // 0.6667
88
+ formatMarkdown(report); // the pull request comment
89
+ ```
90
+
91
+ ## How it is built
92
+
93
+ It wraps existing tools rather than reimplementing them. Component use comes from [react-scanner](https://github.com/moroshko/react-scanner), kept behind one small module (`src/scanner.ts`) because it has had no release since October 2024 and pins TypeScript 5.6 for its parser: anything that produces the same raw report can replace it. Off-system values come from eslint-plugin-better-tailwindcss under ESLint 10. The override check is the only rule of its own, and it reads react-scanner's raw report.
94
+
95
+ A file either scanner cannot parse is listed under `skipped`, and its numbers are missing; it never fails the run.
96
+
97
+ ## Licence
98
+
99
+ [MIT](LICENSE), © Made by Many Ltd. Its dependencies are MIT too, except TypeScript, which typescript-eslint's parser needs: Apache-2.0.