Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/check-scope-and-summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'onsystem': minor
---

`check` and `check_ui` read only JS and TS files. A CSS, Markdown or JSON file named outright or matched by a glob, as `check $(git diff --name-only)`, lint-staged's `"*"` or `check "**/*"` passes them, was parsed as TSX and gave hundreds of syntax errors (209 for a shadcn/ui `app/globals.css`), so the run failed on files with no UI code in them. `check` now skips such a file and counts it in the summary line (`3 files that are not JS or TS skipped`). `check_ui` answers with an error for a `path` or `filename` that is not `.tsx`, `.jsx`, `.ts` or `.js` (or `.mts`, `.cts`, `.mjs`, `.cjs`): a `.vue` or `.css` file was checked as TSX before.

`--require-design-system` also fails (exit code 2) when the folders and globs it is given leave no file to check, so CI cannot pass by checking nothing: a JavaScript app whose pages are `.js` files, a job that runs in a design-system package, whose files are all its own components, or a glob that matches only styles. Files named outright that are not JS or TS still pass with nothing to check. This can fail a job that passed before.

Folders still stand for their `.tsx` and `.jsx` files, and `check` now names the `.js` files in them that have JSX (Next.js's JavaScript template writes `app/page.js`) with the command that checks them, such as `check . "**/*.js"`, in place of "no files match" for a folder of `.js` pages, and as the first line of the `--require-design-system` failure. In the GitHub Action, `paths: . **/*.js` checks them.

A run with 50 findings or more ends, in the default output, with the count per rule and the ten Tailwind palette classes it found most, each with the token to use. `--format json`, `sarif` and `github` print the findings alone, as before.
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ branding:

inputs:
paths:
description: Files, folders or globs to check, relative to working-directory, separated by spaces or new lines.
description: Files, folders or globs to check, relative to working-directory, separated by spaces or new lines, without quotes. A folder stands for its .tsx and .jsx files, so a JavaScript app lists its .js pages too, as in `. **/*.js`.
default: .
working-directory:
description: Where to run the check, such as one app of a monorepo. The config and the default baseline are read from there.
Expand All @@ -22,7 +22,7 @@ inputs:
description: On pull requests and merge groups, report only findings on the lines the change touches. false reports every finding in the checked files.
default: 'true'
require-design-system:
description: Fail when no components or no color tokens are found, so the check cannot pass by checking nothing. An app on a component library such as MUI passes with its components alone.
description: Fail when no components or no color tokens are found, or when paths leaves no file to check, so the check cannot pass by checking nothing. An app on a component library such as MUI passes with its components alone.
default: 'true'

outputs:
Expand Down
19 changes: 17 additions & 2 deletions docs/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

The check that runs after each edit in Claude Code also gates every pull request, whoever or whatever wrote it. `onsystem check` runs the same rules as `check_ui` and exits 1 on errors (or on more than `--max-warnings` warnings). It skips the design system's own component files, the ones `components` matches: they implement the scale and the primitives the rules enforce, so a fresh shadcn/ui project's `p-[3px]` is not a finding. `--include-design-system`, or `"includeDesignSystem": true` in the config, lints them too.

In folders and globs, `check` also leaves out tests and stories (`*.test.tsx`, `*.spec.tsx`, `*.stories.tsx`, and whatever is in `__tests__`, `__stories__` and `__mocks__` folders; `--include-tests` checks them), or what the config's `exclude` lists in their place ([a list replaces them](configuration.md#config-file)), and what git ignores (through `git check-ignore`, so nested `.gitignore` files count); a file named outright is always checked. A file that renders an image or an email is not checked, since tokens and classes don't apply there: one that imports `next/og`, `@vercel/og`, `satori` or `@react-email/*`, a Next.js `opengraph-image`, `twitter-image`, `icon` or `apple-icon` route, or a file in an `og` folder. The summary line counts what was left out. Code installed from a shadcn registry is checked as your own until the config sets it apart: [code from shadcn registries](configuration.md#code-from-shadcn-registries).
In folders and globs, `check` also leaves out tests and stories (`*.test.tsx`, `*.spec.tsx`, `*.stories.tsx`, and whatever is in `__tests__`, `__stories__` and `__mocks__` folders; `--include-tests` checks them), or what the config's `exclude` lists in their place ([a list replaces them](configuration.md#config-file)), and what git ignores (through `git check-ignore`, so nested `.gitignore` files count); a file named outright is always checked. Only JS and TS files are (`.tsx`, `.jsx`, `.ts`, `.js` and their `.mts`, `.cts`, `.mjs` and `.cjs` forms): the CSS, Markdown and JSON that `check $(git diff --name-only)` names or `check "**/*"` matches are skipped, as `check_ui` refuses them. A file that renders an image or an email is not checked, since tokens and classes don't apply there: one that imports `next/og`, `@vercel/og`, `satori` or `@react-email/*`, a Next.js `opengraph-image`, `twitter-image`, `icon` or `apple-icon` route, or a file in an `og` folder. The summary line counts what was left out. Code installed from a shadcn registry is checked as your own until the config sets it apart: [code from shadcn registries](configuration.md#code-from-shadcn-registries).

A folder stands for the `.tsx` and `.jsx` files in it. A JavaScript app can write its JSX in `.js` files (Next.js's JavaScript template has `app/page.js`): `check` names those it left out and gives the command that checks them, such as `check . "**/*.js"`. A `.js` file over 512 KB or named `*.min.js` is taken for a bundle and not looked at. With the [GitHub Action](required-check.md), list the glob in `paths` without quotes, `paths: . **/*.js`: the action passes each path as it is, with no shell.

Pin it as a dev dependency, so CI, the plugin's hook and everyone on the team run the same version:

Expand Down Expand Up @@ -32,7 +34,7 @@ Or use the GitHub Action, `uses: dgesteves/onsystem@v0`, which runs the same che

Install the dependencies first: without them, the props of components that wrap a library (Radix, React Aria) are unknown, and `no-unknown-prop` skips those components ([troubleshooting](troubleshooting.md#components-show-0-props)). `--format github` prints workflow commands, so findings show up as annotations on the pull request; their paths are relative to the repository root, so a job that runs in a folder (`working-directory: apps/web`) annotates the right files. `--format json` prints the raw results, and `--format sarif` a SARIF log for [code scanning](#code-scanning-and-other-sarif-tools). From a [monorepo root](configuration.md#monorepo-roots), `check .` checks each file against its own project's design system and keeps one baseline at the root; there, `--require-design-system` fails only when no workspace package has components and color tokens (or a component library such as MUI), so a job per app (`working-directory: apps/web`) is the stricter gate.

When no components or no color tokens are found, `check` and `check_ui` say which rules could not run and link to the [configuration docs](configuration.md), so a clean result is not mistaken for a checked one. In CI, `--require-design-system` turns that into a failure (exit code 2), for when the design system moves and the globs stop matching. An app whose components come from a [published library](configuration.md#published-libraries-mui) (MUI) has a design system even when its theme's colors cannot be read, as with a palette from Sass variables: the check runs and says that `no-hardcoded-color` did not.
When no components or no color tokens are found, `check` and `check_ui` say which rules could not run and link to the [configuration docs](configuration.md), so a clean result is not mistaken for a checked one. In CI, `--require-design-system` turns that into a failure (exit code 2), for when the design system moves and the globs stop matching. It also fails when the folders and globs it is given leave no file to check, as in a JavaScript app whose pages are `.js` files, in a design-system package whose files are all its own components, or with a glob that matches only styles. Files named outright that are not JS or TS leave nothing to check without failing: a pull request that changes only styles has no UI code to check. An app whose components come from a [published library](configuration.md#published-libraries-mui) (MUI) has a design system even when its theme's colors cannot be read, as with a palette from Sass variables: the check runs and says that `no-hardcoded-color` did not.

`check` and `check_ui` also say when the dependencies are not installed (no `node_modules`), since `no-unknown-prop` then skips the components whose props come from them, and plain `inspect` says it on stderr; `--require-design-system` does not fail on it. When a project without components of its own depends on a component library onsystem does not read yet (Chakra UI, Mantine, HeroUI, Radix Themes or Ant Design), they say that the library is not read, and `inspect` lists it on its `library` line.

Expand Down Expand Up @@ -80,3 +82,16 @@ npx -y onsystem check src --update-baseline
From then on, `check` reads `onsystem.baseline.json` from the root whenever it exists and fails only on new findings: `No new problems in 569 files (1,340 in the baseline).` Entries are keyed by file, rule and the offending text with a count, not by line, so edits elsewhere in a file do not invalidate them, while a second `bg-[#f7f7f7]` where the baseline accepts one is reported. When findings get fixed, `check` says so and prints the command that drops them, `check src --prune-baseline`, which locks in the progress: it only removes entries, while `--update-baseline` would also accept any new finding. Entries of a rule you turn off are kept rather than reported as fixed, a malformed baseline (a bad merge, say) is an error rather than something to overwrite, and paths are matched by their real spelling, so `APP/` on macOS or a linked checkout finds the same entries. `--ignore-baseline` shows everything, and `--baseline <file>` uses another path, creating its folder; the commands `check` prints repeat it, with `--root`, `--config` and the other options that decide what is found. The baseline applies to the CLI only: `check_ui` still shows an agent every finding in the file it is editing.

The baseline also records the onsystem version that wrote it and the rules that ran. When a rule that did not run then reports findings, as when an upgrade finds tokens an older version missed, `check` says they are new to the baseline rather than to the code, and how many other new findings `--update-baseline` would accept with them. A baseline written before these fields existed is read as before, without that line, until an update from the root that checks every file it lists, such as `check . --update-baseline`, records them: an update of one folder cannot tell which rules ran over the rest. Give the baseline to code owners ([CODEOWNERS](rollout.md#record-the-baseline)), so that a pull request that accepts findings by rewriting it needs their review.

Hundreds of findings scroll by faster than they can be read. A run with 50 findings or more ends with what they add up to: the count per rule, and the Tailwind palette classes it found most, each with the token to use, marked `(nearest token)` when that token is another color, which reads as a plan for moving to the tokens:

```text
By rule: no-hardcoded-color 48, prefer-design-system-component 12.
Tailwind palette classes: 48 in 12 files. The most common, with the token to use:
12× border-neutral-200 → border-border
12× hover:border-neutral-200 → hover:border-border
12× text-neutral-500 → text-muted-foreground
12× text-neutral-800 → text-primary (nearest token)
```

`--format json`, `sarif` and `github` print the findings alone.
20 changes: 10 additions & 10 deletions docs/required-check.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,16 @@ Install the dependencies first, as above: without them the props of components t

`@v0` follows the latest 0.x release, and each release of the action runs the CLI of the same version (`npx onsystem@<version>`). To stay on one release, pin a commit SHA.

| Input | Default | What it does |
| ----------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `paths` | `.` | Files, folders or globs to check, relative to `working-directory`, separated by spaces or new lines |
| `working-directory` | `.` | Where to run the check, such as one app of a monorepo; its config and baseline are used |
| `baseline` | | Baseline file, relative to `working-directory`; by default `onsystem.baseline.json` in the project root, whenever it exists |
| `fail-on` | `error` | `error`, `warning` (errors or warnings) or `none` |
| `only-changed-lines` | `true` | On pull requests and merge groups, report only findings on the changed lines; `false` reports every finding in the checked files |
| `require-design-system` | `true` | Fail when no components or no color tokens are found, so the check cannot pass by checking nothing ([CI and baselines](ci.md)) |

An app on a [published library](configuration.md#published-libraries-mui) (MUI) passes `require-design-system` with the library's components alone; when its theme's colors cannot be read, the step's log says that `no-hardcoded-color` did not run. The outputs `errors`, `warnings` and `outside-diff` count what was reported and what was left out. In a monorepo, add a job per app with `working-directory: apps/web`, each with its own design system and baseline.
| Input | Default | What it does |
| ----------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `paths` | `.` | Files, folders or globs to check, relative to `working-directory`, separated by spaces or new lines, without quotes |
| `working-directory` | `.` | Where to run the check, such as one app of a monorepo; its config and baseline are used |
| `baseline` | | Baseline file, relative to `working-directory`; by default `onsystem.baseline.json` in the project root, whenever it exists |
| `fail-on` | `error` | `error`, `warning` (errors or warnings) or `none` |
| `only-changed-lines` | `true` | On pull requests and merge groups, report only findings on the changed lines; `false` reports every finding in the checked files |
| `require-design-system` | `true` | Fail when no components or no color tokens are found, or when `paths` leaves no file to check, so the check cannot pass by checking nothing ([CI and baselines](ci.md)) |

A folder in `paths` stands for its `.tsx` and `.jsx` files. A JavaScript app whose pages are `.js` files (Next.js's JavaScript template) checks them with `paths: . **/*.js`, with no quotes, since the action passes each path as it is. With `paths: .` alone the step's log names them, and `require-design-system` fails when that leaves nothing to check. An app on a [published library](configuration.md#published-libraries-mui) (MUI) passes `require-design-system` with the library's components alone; when its theme's colors cannot be read, the step's log says that `no-hardcoded-color` did not run. The outputs `errors`, `warnings` and `outside-diff` count what was reported and what was left out. In a monorepo, add a job per app with `working-directory: apps/web`, each with its own design system and baseline.

## Make the check required

Expand Down
Loading
Loading