@actioneer/ads-lint
v0.2.0
Published
Design linter for the Actioneer Design System (ADS 1.0). Flags deviations from ADS principles on changed files.
Readme
@actioneer/ads-lint
A design linter for the Actioneer Design System (ADS 1.0). It flags deviations from ADS principles on changed files, so any repo that consumes ADS stays on-system without a human catching every stray hex or icon import in review.
It runs in two tiers: the TypeScript compiler API when typescript is
resolvable (exact import analysis), degrading to a regex parser when it
isn't — so it works in a fresh repo before npm i.
What it checks
| Rule | Severity | Principle |
|---|---|---|
| external-ui-import — importing radix/mui/assistant-ui/etc. directly | error | P7 ADS-first |
| deep-import — reaching past the barrel (@actioneer/ads/button) | error | P7 |
| barrel-default-import / -namespace-import / -aliased-import | error | P7 |
| icon-package-import — importing @hugeicons/lucide outside the registry | error | P4 icons |
| hardcoded-hex — a raw #rrggbb in code | error | tokens-only |
| arbitrary-color — bg-[#fff] / text-[rgb(...)] | error | tokens-only |
| arbitrary-dimension — w-[13px] / gap-[1.5rem] | warn | tokens-only |
| Motion: unbounded-transition, sluggish-easing, zero-scale-entrance, undersized-zoom-entrance, hardcoded-duration, layout-property-transition, layout-keyframe-disclosure | error | P3 motion |
Errors fail the run (exit 1); warnings don't unless --strict.
Usage
ads-lint [files...] # lint specific files (what lint-staged passes)
ads-lint --staged # files staged for commit (pre-commit hook)
ads-lint --diff origin/main # files changed vs a ref (CI on a PR)
ads-lint --all # everything under the configured root
ads-lint --strict # fail on warnings tooConfig — ads-lint.config.mjs at the repo root
Deep-merged over the defaults. A consumer repo needs almost nothing — just point at the published package:
export default {
imports: { libraryPackage: "@actioneer/ads" }, // libraryRoot stays null
};The ADS library repo itself runs in library-dev mode (see this repo's
ads-lint.config.mjs): libraryRoot: "src/components" exempts the library's own
internals, and libraryPackage: "@/components" matches the local alias.
Wiring into a consumer repo
Pre-commit (lint-staged) — lint only what's staged:
// package.json
"lint-staged": {
"src/**/*.{ts,tsx,css}": "ads-lint"
}CI (GitHub Actions) — lint only the PR diff, so legacy code doesn't block.
--diff diffs from the merge-base, so the base branch must be present — set
fetch-depth: 0 (the default shallow clone omits it):
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: npx ads-lint --diff origin/${{ github.base_ref }}Programmatic API
import { loadConfig, resolveFiles, lintFiles } from "@actioneer/ads-lint";
const cfg = await loadConfig();
const { findings } = lintFiles(resolveFiles({ files: myFiles }, cfg, cwd), cfg, cwd);