@fantastic.dev/repo-gates 0.2.1-bootstrap.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/LICENSE +21 -0
- package/README.md +499 -0
- package/dist/bin/repo-gates.d.ts +1 -0
- package/dist/bin/repo-gates.js +2318 -0
- package/dist/config.d.ts +147 -0
- package/dist/config.js +178 -0
- package/dist/design-system.d.ts +17 -0
- package/dist/design-system.js +14 -0
- package/dist/eslint-boundaries.d.ts +49 -0
- package/dist/eslint-boundaries.js +40 -0
- package/dist/index.d.ts +796 -0
- package/dist/index.js +1995 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kelly Kampen
|
|
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
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
# repo-gates
|
|
2
|
+
|
|
3
|
+
Config-driven quality gates for **turborepo** (and any) monorepos — one quiet
|
|
4
|
+
`check-all` command that runs your whole battery (lint, format, typecheck, tests)
|
|
5
|
+
alongside ratchet guards for file size, tech-debt markers, circular imports,
|
|
6
|
+
secret-shaped strings, shadcn UI quality, coverage, and bundle size, plus a
|
|
7
|
+
CI-parity drift detector and a PR docs-coverage gate. The engine is repo-agnostic; your policy
|
|
8
|
+
lives in a single `repo-gates.config.json`.
|
|
9
|
+
|
|
10
|
+
## What it does
|
|
11
|
+
|
|
12
|
+
`check-all` runs an ordered manifest of gates and reports them **quietly** — one
|
|
13
|
+
aligned line per gate, a tally, and (on failure) the parsed failure signature
|
|
14
|
+
instead of a wall of logs:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
✓ lint (2.5s)
|
|
18
|
+
✓ format:check (5.9s)
|
|
19
|
+
✓ typecheck (0.5s)
|
|
20
|
+
✓ check:size (0.3s)
|
|
21
|
+
✓ check:debt (0.3s)
|
|
22
|
+
✓ test (8.1s)
|
|
23
|
+
✓ check:coverage (9.4s)
|
|
24
|
+
✓ check:ci-parity (0.3s)
|
|
25
|
+
|
|
26
|
+
8/8 gates passed (24.3s)
|
|
27
|
+
|
|
28
|
+
Scores:
|
|
29
|
+
coverage lowest packages/api 81.2% lines (12 pkgs ≥ floor)
|
|
30
|
+
file-size tightest src/app.ts 512/512 (0 to spare)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The gates:
|
|
34
|
+
|
|
35
|
+
| Command | What it does |
|
|
36
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
37
|
+
| `check-all` | Runs the whole manifest quietly: aligned `✓/✗ gate (N.Ns)`, a tally, and parsed failure signatures (never the full log). On success prints a compact `Scores:` block. `--bail` stops at the first failure; `CHECK_ALL_VERBOSE=1` streams everything. |
|
|
38
|
+
| `check-ci-parity` | Fails when a `pnpm run <gate>` in `.github/workflows/ci*.yml` isn't reachable from `check-all` — kills CI/local drift. |
|
|
39
|
+
| `check-size` | Per-file line ceiling; large files are grandfathered and may only shrink. `--init` seeds baselines. |
|
|
40
|
+
| `check-debt` | `TODO/FIXME/HACK/XXX` must carry a tracker ref (`ABC-123` / `#123` / URL) or be allowlisted. `--init` seeds the allowlist. |
|
|
41
|
+
| `check-circular` | Flags new circular-import groups (relative imports within `scanRoots`, resolved into a graph, reduced to strongly-connected components). Existing cycles are grandfathered. `--init` seeds the allowlist. |
|
|
42
|
+
| `check-secrets` | Static scan of every **git-tracked** file for credential-shaped strings (AWS/GitHub/Slack/Stripe/npm/Google keys, PEM headers, userinfo-in-URL). Findings are reported as a redacted fingerprint — never the matched text. Not a substitute for a dedicated secret scanner (gitleaks/trufflehog): no entropy analysis, no git-history scan. `--init` seeds the allowlist — review every entry, it silences whatever it captures. |
|
|
43
|
+
| `check-agents` | Fails if a `pnpm run <x>` or a backticked path in `AGENTS.md` no longer resolves. |
|
|
44
|
+
| `check:design-system` | Optional consumer script using [@shadcn/lint](https://github.com/shadcn-ui/lint) to enforce Tailwind v4 design-system rules through ESLint or Oxlint. Defining the script includes it in `check-all`. |
|
|
45
|
+
| `check:shadscan` | Optional consumer script for React repositories using shadcn/ui. Runs a pinned [Shadscan](https://www.shadscan.com/docs) audit and fails below the repository's ratcheted score floor. Defining the script automatically adds it to `check-all`; non-shadcn repositories omit it. |
|
|
46
|
+
| `check-docs-coverage` | PR gate: a changed "surface" (config-defined glob) must come with a docs change, or a `docs: n/a - <reason>` opt-out in the PR body. Reads the changed-file list from the GitHub API (`GITHUB_REPOSITORY`/`PR_NUMBER`/`GITHUB_TOKEN`/`PR_BODY`); a no-op outside a PR context (safe to include in `check:all`). See [CI](#ci) for wiring it as its own `pull_request`-triggered job. |
|
|
47
|
+
| `check-coverage` | Holds **each package** to its own floor (no repo-wide aggregate — a high package can't mask a low one); unlisted packages must meet a `default`. Floors ratchet up. `--init` seeds; `--skip-run` reuses existing summaries. |
|
|
48
|
+
| `check-bundle-size` | Builds each configured target (turbo, cached), then ratchets raw+gzip totals AND the largest single chunk per bucket. `--init` re-baselines. |
|
|
49
|
+
| `report-test-timing` / `report-quality-metrics` | Non-gating dashboards → `$GITHUB_STEP_SUMMARY`. |
|
|
50
|
+
|
|
51
|
+
## Why
|
|
52
|
+
|
|
53
|
+
- **One quiet command.** `check-all` is the single entry point — aligned pass/fail, a tally, and parsed failure signatures instead of a wall of logs. `--bail` stops early; `CHECK_ALL_VERBOSE=1` streams everything.
|
|
54
|
+
- **Ratchets, not fixed limits.** File size, tech debt, circular imports, secret-shaped strings, shadcn UI quality, coverage, and bundle size only move in the right direction. Seed each baseline from your current tree, so day one is green — no big cleanup up front.
|
|
55
|
+
- **Per-package coverage floors.** Each package is held to its own floor, so a well-covered package can't mask a thin one.
|
|
56
|
+
- **Docs don't drift behind the product.** `check-docs-coverage` blocks a PR that changes a user-facing surface without touching docs — unless the author opts out on the record.
|
|
57
|
+
- **CI ↔ local parity.** `check-ci-parity` fails if your CI workflow drifts from the `check-all` manifest, so "green locally" means "green in CI."
|
|
58
|
+
- **Config-driven & reusable.** The engine ships no repo-specific assumptions; drop it into any repo and describe policy in one JSON file.
|
|
59
|
+
|
|
60
|
+
## How to use it
|
|
61
|
+
|
|
62
|
+
### Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pnpm add -D @fantastic.dev/repo-gates
|
|
66
|
+
# or: npm i -D @fantastic.dev/repo-gates / yarn add -D @fantastic.dev/repo-gates
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Ships compiled JS + types — no build step or Node type-stripping required in your
|
|
70
|
+
repo (Node ≥ 18).
|
|
71
|
+
|
|
72
|
+
### Migrating from @kellykampen/repo-gates
|
|
73
|
+
|
|
74
|
+
Maintenance has moved to [FantasticDevHQ/repo-gates](https://github.com/FantasticDevHQ/repo-gates)
|
|
75
|
+
and the npm package is now [@fantastic.dev/repo-gates](https://www.npmjs.com/package/@fantastic.dev/repo-gates).
|
|
76
|
+
Replace the dependency using your package manager, for example:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pnpm remove @kellykampen/repo-gates
|
|
80
|
+
pnpm add -D @fantastic.dev/repo-gates
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Update imports from `@kellykampen/repo-gates` to `@fantastic.dev/repo-gates`,
|
|
84
|
+
including the `/config`, `/design-system`, and `/eslint-boundaries` exports.
|
|
85
|
+
Check existing `eslint.design-system.config.mjs` files too: `init` preserves
|
|
86
|
+
existing configuration files, so it will not rewrite their imports for you.
|
|
87
|
+
The `repo-gates` executable, package scripts, `repo-gates.config.json`, and
|
|
88
|
+
existing baselines continue to work without changes. The old npm package remains
|
|
89
|
+
available for existing installs; new releases use the `@fantastic.dev` scope.
|
|
90
|
+
|
|
91
|
+
### Quickstart
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pnpm exec repo-gates init
|
|
95
|
+
pnpm run check:all
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`init` configures the repository, rather than only printing instructions. Run
|
|
99
|
+
it at the root containing `package.json`. It detects npm, pnpm, Yarn, or Bun
|
|
100
|
+
from `packageManager` or lockfiles, adds missing scripts, writes the gate
|
|
101
|
+
manifest, and installs applicable UI tools with that package manager.
|
|
102
|
+
Installation of repo-gates itself has no `postinstall` hook that modifies your
|
|
103
|
+
repository; initialization is an explicit command.
|
|
104
|
+
|
|
105
|
+
| Gate | What `init` does |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| File size, debt markers, circular imports | Adds scripts and seeds missing baselines from the current source. Existing baselines are never reseeded. |
|
|
108
|
+
| Secrets | Adds `check:secrets`, without automatically allowlisting findings. It scans Git-tracked files. |
|
|
109
|
+
| CI parity | Adds `check:ci-parity`. Run `check:all` from your CI workflow to enforce the generated manifest. |
|
|
110
|
+
| Design system | Detects Tailwind v4, installs pinned `@shadcn/lint` and a compatible linter/parser when absent, and configures all six rules as errors. |
|
|
111
|
+
| Shadscan | Detects `components.json` in root/workspace packages, installs `@shadscan/cli@0.7.0`, and checks each detected project with an initial floor of 80. |
|
|
112
|
+
| Agent docs | Enables `check:agents` when `AGENTS.md` files or configured targets exist. |
|
|
113
|
+
| Coverage | Enables `check:coverage` when `test:coverage` exists. Configure that script to emit `json-summary` reports, then run `repo-gates check-coverage --init` to measure the initial floors. |
|
|
114
|
+
| Docs coverage, bundle size | Enables their scripts when `docsCoverage.surfaces` or `bundleSize.targets` are configured. See the respective policy sections below. |
|
|
115
|
+
| Existing lint, format, typecheck, test, dependency and duplication checks | Includes existing scripts whose names match the default manifest. It does not invent framework-specific commands. Missing core scripts are listed at completion. |
|
|
116
|
+
|
|
117
|
+
A fresh config scans the repository root, excludes dependency/build directories,
|
|
118
|
+
and includes JS and TS source extensions. Workspace discovery reads
|
|
119
|
+
`package.json` workspaces or `pnpm-workspace.yaml`, supporting literal paths,
|
|
120
|
+
`*`, `**`, and `!` exclusions. It detects installed dependency versions or
|
|
121
|
+
ordinary declared semver ranges, including pnpm catalogs. Existing lint configs
|
|
122
|
+
remain untouched; the design-system pass gets a separate generated config.
|
|
123
|
+
ESLint is preferred when declared at the root; an existing root Oxlint setup
|
|
124
|
+
is used otherwise. If neither is present, initialization adds ESLint.
|
|
125
|
+
|
|
126
|
+
Re-running `init` preserves existing scripts, lint configs, explicit policy
|
|
127
|
+
values, and baselines, while adding missing applicable gates. Existing custom
|
|
128
|
+
manifests keep deliberate omissions of scripts that were already present;
|
|
129
|
+
newly added gate scripts are inserted into the manifest. Review the generated
|
|
130
|
+
files and commit them with your lockfile. An install failure exits nonzero and
|
|
131
|
+
leaves the generated files available for repair; run your package manager's
|
|
132
|
+
install command or rerun `init` to retry.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
repo-gates init --skip-install # write dependencies/configs; install later
|
|
136
|
+
repo-gates init --no-design-system # skip automatic Tailwind rule setup
|
|
137
|
+
repo-gates init --no-shadscan # skip automatic Shadscan setup
|
|
138
|
+
repo-gates init --shadscan-floor 70 # choose an initial integer floor, 0–100
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Opt-out flags skip setup; they do not remove existing scripts or dependencies.
|
|
142
|
+
To disable an existing gate, remove its entry from `repo-gates.config.json`.
|
|
143
|
+
The initial Shadscan floor is a policy choice, not a measured baseline. It may
|
|
144
|
+
fail on the first run. Raise it as findings are fixed, and never lower an
|
|
145
|
+
established floor to pass a regression. UI tooling needs Node 20.19+ for the
|
|
146
|
+
ESLint setup; Oxlint requires Node 20.19.x or 22.12+.
|
|
147
|
+
|
|
148
|
+
### Wire it up
|
|
149
|
+
|
|
150
|
+
```jsonc
|
|
151
|
+
{
|
|
152
|
+
"scripts": {
|
|
153
|
+
"check:all": "repo-gates check-all",
|
|
154
|
+
"check:size": "repo-gates check-size",
|
|
155
|
+
"check:debt": "repo-gates check-debt",
|
|
156
|
+
"check:shadscan": "pnpm dlx @shadscan/cli@0.7.0 ./apps/web --json --fail-under 40 --no-roast --no-interactive",
|
|
157
|
+
"check:coverage": "repo-gates check-coverage",
|
|
158
|
+
"check:ci-parity": "repo-gates check-ci-parity"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Add the Shadscan ratchet for shadcn repositories
|
|
164
|
+
|
|
165
|
+
`init` adds this gate automatically for detected shadcn/ui projects. For manual
|
|
166
|
+
setup, only define `check:shadscan` when the repository uses shadcn/ui. Run Shadscan
|
|
167
|
+
once against the React application package, choose a conservative floor below
|
|
168
|
+
or equal to the assessed score, and commit that floor as the starting ratchet:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pnpm dlx @shadscan/cli@0.7.0 ./apps/web --json --fail-under 40 --no-roast --no-interactive
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Keep the CLI version exact so the same source is evaluated by the same ruleset
|
|
175
|
+
locally and in CI. The default gate manifest treats `check:shadscan` as
|
|
176
|
+
conditional: defining the package script includes it in `check-all` (and any
|
|
177
|
+
pre-commit hook that runs `check-all`); omitting it leaves non-shadcn
|
|
178
|
+
repositories unaffected. Raise `--fail-under` as findings are remediated, and
|
|
179
|
+
never lower it to make a regression pass. See the [Shadscan pre-commit
|
|
180
|
+
documentation](https://www.shadscan.com/docs#pre-commit) for hook-manager-specific
|
|
181
|
+
wiring.
|
|
182
|
+
|
|
183
|
+
### Add design-system checks with @shadcn/lint
|
|
184
|
+
|
|
185
|
+
`repo-gates init` creates this setup automatically for detected Tailwind v4
|
|
186
|
+
repositories. For manual setup, define `check:design-system` to run a separate
|
|
187
|
+
design-system gate immediately after `lint`. The default manifest skips it
|
|
188
|
+
when the script is absent. This is a pass/fail check, with no score or ratchet.
|
|
189
|
+
Shadcn/ui is optional; custom Tailwind design systems are supported too.
|
|
190
|
+
|
|
191
|
+
Install the plugin in the consumer package that owns the lint configuration.
|
|
192
|
+
It requires Node **20.19+** and ESLint **9.30+**, or Oxlint **1.80+**.
|
|
193
|
+
The optional gate does not change repo-gates' own Node 18 requirement.
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pnpm add -D -E @shadcn/lint@0.1.5
|
|
197
|
+
# If ESLint and a TSX parser are not already installed:
|
|
198
|
+
pnpm add -D -E eslint@^9.30.0 @typescript-eslint/parser@^8.40.0
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Our default setup enables all six rules as errors. For a separate ESLint pass,
|
|
202
|
+
create `eslint.design-system.config.mjs`:
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
import { plugin as shadcn } from "@shadcn/lint";
|
|
206
|
+
import tsParser from "@typescript-eslint/parser";
|
|
207
|
+
import { designSystemRules } from "@fantastic.dev/repo-gates/design-system";
|
|
208
|
+
|
|
209
|
+
export default [
|
|
210
|
+
{ ignores: ["**/node_modules/**", "**/dist/**", "**/.next/**", "**/coverage/**"] },
|
|
211
|
+
{
|
|
212
|
+
files: ["**/*.{js,jsx,ts,tsx}"],
|
|
213
|
+
languageOptions: {
|
|
214
|
+
parser: tsParser,
|
|
215
|
+
parserOptions: { ecmaFeatures: { jsx: true } },
|
|
216
|
+
},
|
|
217
|
+
plugins: { shadcn },
|
|
218
|
+
rules: {
|
|
219
|
+
...designSystemRules,
|
|
220
|
+
// Add consumer overrides here, after the preset.
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
// Adjust to the directories where your design-system components are defined.
|
|
225
|
+
files: ["src/components/ui/**", "components/ui/**", "packages/ui/src/components/**"],
|
|
226
|
+
rules: { "shadcn/no-restyle": "off" },
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Add the consumer script, adjusting the source path to your application:
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"scripts": {
|
|
236
|
+
"check:design-system": "eslint --config eslint.design-system.config.mjs src --max-warnings 0"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Run `pnpm run check:design-system`, then `pnpm exec repo-gates check-all`.
|
|
242
|
+
If your repository overrides the `gates` array, add
|
|
243
|
+
`{ "name": "check:design-system", "conditional": true }` to that array;
|
|
244
|
+
arrays replace the defaults in full.
|
|
245
|
+
|
|
246
|
+
#### Update the rules
|
|
247
|
+
|
|
248
|
+
The shipped `@fantastic.dev/repo-gates/design-system` preset is the source of
|
|
249
|
+
ESLint defaults. Edit your consumer's `eslint.design-system.config.mjs` and add
|
|
250
|
+
rule overrides **after** `...designSystemRules`. Generated Oxlint setups use
|
|
251
|
+
`.oxlintrc.design-system.json`; edit the rules in its first `overrides` entry,
|
|
252
|
+
which contains a copy of the same defaults scoped to the detected UI packages.
|
|
253
|
+
The defaults are:
|
|
254
|
+
|
|
255
|
+
| Rule | What it checks |
|
|
256
|
+
| --- | --- |
|
|
257
|
+
| `shadcn/no-restyle` | Styling overrides on design-system components; layout classes are allowed. |
|
|
258
|
+
| `shadcn/no-raw-colors` | Colors that bypass the design-system theme. |
|
|
259
|
+
| `shadcn/no-arbitrary-values` | Arbitrary Tailwind values such as `p-[13px]`. |
|
|
260
|
+
| `shadcn/no-inline-styles` | Inline styles and `<style>` elements. |
|
|
261
|
+
| `shadcn/no-unknown-classes` | Classes that Tailwind cannot generate. |
|
|
262
|
+
| `shadcn/require-static-classes` | Component class values the linter cannot statically read. |
|
|
263
|
+
|
|
264
|
+
All six use `error`. The existing component-directory override turns off only
|
|
265
|
+
`no-restyle`, so components can define their own styles. The other five remain
|
|
266
|
+
active there too. Upstream recommends also exempting component definitions from
|
|
267
|
+
`no-arbitrary-values` and `require-static-classes` when needed; our default
|
|
268
|
+
keeps those checks enabled until you explicitly add an exception.
|
|
269
|
+
|
|
270
|
+
To disable a rule, set its value to `"off"`. To change its options, use an array
|
|
271
|
+
such as `["error", { allow: ["layout", "spacing"] }]` for `no-restyle`.
|
|
272
|
+
For a scoped exception, append a config entry after the defaults, for example:
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
{
|
|
276
|
+
files: ["src/components/ui/**"],
|
|
277
|
+
rules: {
|
|
278
|
+
"shadcn/no-arbitrary-values": "off",
|
|
279
|
+
"shadcn/require-static-classes": "off",
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Adjust the glob to your component directory. A later matching ESLint entry
|
|
285
|
+
replaces the earlier setting for that rule. `"warn"` is also supported, but
|
|
286
|
+
our script uses `--max-warnings 0`, so warnings still fail the gate. Remove
|
|
287
|
+
that flag or raise its limit if warnings should be non-blocking.
|
|
288
|
+
|
|
289
|
+
Component and theme discovery uses `components.json`; custom imports and shared
|
|
290
|
+
UI packages may need `settings.shadcn` configuration. See the
|
|
291
|
+
[shadcn/lint repository](https://github.com/shadcn-ui/lint),
|
|
292
|
+
[rule options](https://github.com/shadcn-ui/lint/blob/main/docs/rules.md), and
|
|
293
|
+
[settings](https://github.com/shadcn-ui/lint#settings).
|
|
294
|
+
After editing the configuration, run `pnpm run check:design-system`.
|
|
295
|
+
Existing consumers can run `repo-gates init` to add missing gate setup. Existing
|
|
296
|
+
design-system scripts and configurations are preserved; adopt the preset
|
|
297
|
+
explicitly if your config still lists rules individually. Upgrading repo-gates
|
|
298
|
+
can update the imported ESLint preset; Oxlint's copied rules remain unchanged
|
|
299
|
+
until you edit them. New upstream rules are not enabled merely by upgrading
|
|
300
|
+
`@shadcn/lint`. Review rule changes when upgrading repo-gates.
|
|
301
|
+
|
|
302
|
+
You can instead register the plugin in your existing ESLint or Oxlint config
|
|
303
|
+
and run it through the existing `lint` gate. In that case, omit the separate
|
|
304
|
+
script to avoid checking the same rules twice. Oxlint uses
|
|
305
|
+
`"jsPlugins": ["@shadcn/lint"]`; its JS plugin API is currently alpha.
|
|
306
|
+
See the upstream [setup instructions](https://github.com/shadcn-ui/lint#get-started).
|
|
307
|
+
Keep versions pinned and commit the consumer lockfile for consistent CI results.
|
|
308
|
+
|
|
309
|
+
## How it finds your repo
|
|
310
|
+
|
|
311
|
+
Every command resolves the repo root from `process.cwd()` and loads
|
|
312
|
+
`repo-gates.config.json` from there (falling back to the built-in `DEFAULT_CONFIG`).
|
|
313
|
+
The config is a **partial overlay** on the defaults — set only what differs.
|
|
314
|
+
|
|
315
|
+
## Configuration
|
|
316
|
+
|
|
317
|
+
`repo-gates.config.json` is a **partial overlay** on the built-in defaults — set only
|
|
318
|
+
what differs from your repo. JSON is parsed strictly (no `//` comments); use a
|
|
319
|
+
`"$comment"` key for inline notes, as below.
|
|
320
|
+
|
|
321
|
+
### Example
|
|
322
|
+
|
|
323
|
+
A realistic config for a pnpm + turbo monorepo (an Electron app, a web app, shared
|
|
324
|
+
packages) — the annotated version below is jsonc for readability; a copy-pasteable,
|
|
325
|
+
strictly-valid `repo-gates.config.json` (comments as `"$comment"` keys instead of
|
|
326
|
+
`//`) lives at [`examples/repo-gates.config.json`](./examples/repo-gates.config.json):
|
|
327
|
+
|
|
328
|
+
```jsonc
|
|
329
|
+
{
|
|
330
|
+
"$comment": "Anything omitted falls back to DEFAULT_CONFIG.",
|
|
331
|
+
"runner": "pnpm run",
|
|
332
|
+
|
|
333
|
+
// Each package/app's vitest coverage-summary.json — check-coverage holds each to
|
|
334
|
+
// its own floor (gates/coverage-budgets.json), seeded by `check-coverage --init`.
|
|
335
|
+
"coverage": {
|
|
336
|
+
"summaryGlobs": [
|
|
337
|
+
"apps/*/coverage/coverage-summary.json",
|
|
338
|
+
"packages/*/coverage/coverage-summary.json"
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
// Build + measure bundle budgets. Each target is built via
|
|
343
|
+
// `turbo run build --filter <filter>`, then dist is measured by bucket.
|
|
344
|
+
"bundleSize": {
|
|
345
|
+
"targets": [
|
|
346
|
+
{ "name": "web", "filter": "@acme/web", "distDir": "apps/web/dist",
|
|
347
|
+
"buckets": { "js": [".js"], "css": [".css"] } }
|
|
348
|
+
]
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
// check-agents: keep AGENTS.md's `pnpm run <script>` + backticked paths resolving.
|
|
352
|
+
"agents": { "targets": ["AGENTS.md"] },
|
|
353
|
+
|
|
354
|
+
// report-test-timing reads these junit files (non-gating dashboard).
|
|
355
|
+
"report": { "junitGlobs": ["apps/*/test-results/junit.xml", "packages/*/test-results/junit.xml"] },
|
|
356
|
+
|
|
357
|
+
// Architectural import rules → ESLint (see "Import boundaries" below).
|
|
358
|
+
"boundaries": [
|
|
359
|
+
{
|
|
360
|
+
// ONE cross-app rule for every app (message written once). Safe as long as
|
|
361
|
+
// apps don't import their own package by name.
|
|
362
|
+
"name": "no-cross-app",
|
|
363
|
+
"files": ["apps/**"],
|
|
364
|
+
"patterns": [
|
|
365
|
+
{ "forbid": ["@acme/web", "@acme/web/**", "@acme/desktop", "@acme/desktop/**"],
|
|
366
|
+
"message": "Apps must not import each other — share via packages/*." }
|
|
367
|
+
]
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
// Nested scope: inherits no-cross-app's patterns via `extends` (no copy-paste),
|
|
371
|
+
// and adds its own.
|
|
372
|
+
"name": "desktop-renderer",
|
|
373
|
+
"files": ["apps/desktop/src/renderer/**"],
|
|
374
|
+
"ignores": ["**/*.test.ts", "**/*.test.tsx"],
|
|
375
|
+
"extends": ["no-cross-app"],
|
|
376
|
+
"patterns": [
|
|
377
|
+
{ "forbid": ["node:*", "better-sqlite3", "**/main/**"],
|
|
378
|
+
"allowTypeImports": true,
|
|
379
|
+
"message": "Renderer is a browser context — reach main via IPC, not a value import (import type is fine)." }
|
|
380
|
+
]
|
|
381
|
+
}
|
|
382
|
+
]
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Reference
|
|
387
|
+
|
|
388
|
+
| Key | Default | Purpose |
|
|
389
|
+
| --- | --- | --- |
|
|
390
|
+
| `runner` | `"pnpm run"` | How a gate script is invoked (`"pnpm run"`, `"bun run"`, `"npm run"`). |
|
|
391
|
+
| `gates` | 18-gate manifest | Ordered `{ name, conditional }[]`. A non-conditional gate in the selected manifest is required: it runs even when its script is missing, so the run fails loudly. Conditional gates run only if their script exists. Overriding this array replaces the default policy, so it is valid to omit `test` when `check:coverage` runs the complete suite with coverage (avoids executing the suite twice). |
|
|
392
|
+
| `scanRoots` | `["apps","packages","scripts"]` | Roots the file-size + debt + circular-import walkers scan. |
|
|
393
|
+
| `excludeDirSegments` | `node_modules`, `dist`, `out`, `.turbo`, `coverage`, … | Directory names pruned from scans. |
|
|
394
|
+
| `excludePathPrefixes` | `[]` | Repo-relative path prefixes excluded from scans. |
|
|
395
|
+
| `sourceExtensions` | `[".ts",".tsx"]` | Extensions the size/debt/circular-import guards treat as source. |
|
|
396
|
+
| `fileSize.threshold` | `600` | Default per-file line ceiling (larger files are grandfathered in the budgets file). |
|
|
397
|
+
| `fileSize.budgetsPath` | `gates/file-size-budgets.json` | Grandfathered per-file budgets (`check-size --init` seeds). |
|
|
398
|
+
| `debt.markerTokens` | `["TODO","FIXME","HACK","XXX"]` | Tokens that must carry a tracker reference. |
|
|
399
|
+
| `debt.trackerPatterns` | `ABC-123`, `#123`, URL | Regex sources for a valid tracker reference. |
|
|
400
|
+
| `debt.allowlistPath` | `gates/debt-marker-allowlist.json` | Untracked-marker allowlist (`check-debt --init` seeds). |
|
|
401
|
+
| `circular.allowlistPath` | `gates/circular-imports-allowlist.json` | Grandfathered circular-import groups (`check-circular --init` seeds). |
|
|
402
|
+
| `secrets.patterns` | AWS/GitHub/Slack/Stripe/npm/Google key shapes, PEM headers, URL creds | Regex sources tested against every git-tracked line. |
|
|
403
|
+
| `secrets.binaryExtensions` | images, fonts, archives, media | Extensions skipped as non-text. |
|
|
404
|
+
| `secrets.allowlistPath` | `gates/secrets-allowlist.json` | Grandfathered findings (`check-secrets --init` seeds — review before trusting). |
|
|
405
|
+
| `docsCoverage.surfaces` | `[]` (no-op) | `[{ label, glob, on: "added"\|"changed" }]` — user-facing surfaces that require docs when changed. |
|
|
406
|
+
| `docsCoverage.docsGlobs` | `[]` | Globs a PR must touch for a triggered surface to count as documented. |
|
|
407
|
+
| `docsCoverage.exclude` | `[]` | Globs removed from both surface and docs matching (tests, fixtures). |
|
|
408
|
+
| `coverage.summaryGlobs` | `apps/*`, `packages/*` | Globs matching each package's `coverage-summary.json`. |
|
|
409
|
+
| `coverage.budgetsPath` | `gates/coverage-budgets.json` | Per-package floors (`check-coverage --init` seeds; floors ratchet up). |
|
|
410
|
+
| `bundleSize.targets` | `[]` (no-op) | `[{ name, filter, distDir, buckets }]` — built via turbo, then raw+gzip+largest-chunk ratcheted. |
|
|
411
|
+
| `bundleSize.budgetsPath` | `gates/bundle-size-budgets.json` | Bundle baselines. |
|
|
412
|
+
| `agents.targets` | `[]` | Agent docs (e.g. `["AGENTS.md"]`) whose `pnpm run <x>` + backticked paths must resolve. |
|
|
413
|
+
| `report.junitGlobs` | `[]` | junit files for the `report-test-timing` dashboard. |
|
|
414
|
+
| `report.topN` | `20` | Slowest-tests cutoff in that dashboard. |
|
|
415
|
+
| `ciParity.{configPath,rootGate,entryGates,workflowPrefix}` | `gates/ci-parity-config.json`, `check:all`, `["check:all","verify"]`, `ci` | Inputs to the CI-parity reachability graph. |
|
|
416
|
+
| `boundaries` | `[]` | Import-boundary rules → ESLint (below). |
|
|
417
|
+
|
|
418
|
+
The full `RepoGatesConfig` type is exported from the package for editor autocompletion.
|
|
419
|
+
|
|
420
|
+
### Import boundaries (ESLint)
|
|
421
|
+
|
|
422
|
+
Architectural import rules are _data_ in `repo-gates.config.json` under `boundaries`;
|
|
423
|
+
the transform `@fantastic.dev/repo-gates/eslint-boundaries` turns them into
|
|
424
|
+
`@typescript-eslint/no-restricted-imports` flat configs you spread into your
|
|
425
|
+
`eslint.config.mjs`:
|
|
426
|
+
|
|
427
|
+
```js
|
|
428
|
+
import { boundariesToEslintConfigs } from "@fantastic.dev/repo-gates/eslint-boundaries";
|
|
429
|
+
import repoGates from "./repo-gates.config.json" with { type: "json" };
|
|
430
|
+
|
|
431
|
+
export default [
|
|
432
|
+
// …your other flat configs…
|
|
433
|
+
...boundariesToEslintConfigs(repoGates.boundaries ?? []),
|
|
434
|
+
];
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Each boundary is one file scope carrying pattern groups; per-group `allowTypeImports`
|
|
438
|
+
lets a browser context still `import type` a Node-only module. `ignores` exempts files
|
|
439
|
+
(commonly tests, which run in Node).
|
|
440
|
+
|
|
441
|
+
Because ESLint flat config is **last-wins per rule**, a file matched by several
|
|
442
|
+
boundaries only keeps the *last* one's patterns — so a nested scope (`renderer/**` ⊂
|
|
443
|
+
`apps/**`) must carry the broader patterns too. Instead of copy-pasting them, use
|
|
444
|
+
**`extends`**: `{ "name": "desktop-renderer", "extends": ["no-cross-app"], … }` merges
|
|
445
|
+
the named boundaries' patterns in ahead of its own (resolved transitively, cycles
|
|
446
|
+
rejected). Author each rule — and its message — **once**, at its natural scope.
|
|
447
|
+
|
|
448
|
+
**Scores protocol:** any gate contributes a headline to `check-all`'s success
|
|
449
|
+
`Scores:` block by printing `SCORE: <label> — <value>` on success; `check-all`
|
|
450
|
+
collects and aligns them.
|
|
451
|
+
|
|
452
|
+
## CI
|
|
453
|
+
|
|
454
|
+
Run the battery as one job step (Node ≥ 18, deps installed):
|
|
455
|
+
|
|
456
|
+
```yaml
|
|
457
|
+
- run: pnpm exec repo-gates check-all
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Keep the CI workflow and the `check-all` manifest in lock-step with
|
|
461
|
+
`repo-gates check-ci-parity`.
|
|
462
|
+
|
|
463
|
+
Full copy-paste-able GitHub Actions workflows, from a single `check-all` step
|
|
464
|
+
up to a per-gate turborepo battery with remote caching, live in
|
|
465
|
+
[`examples/github-actions/`](./examples/github-actions):
|
|
466
|
+
|
|
467
|
+
- [`minimal.yml`](./examples/github-actions/minimal.yml) — one `check-all` step.
|
|
468
|
+
- [`single-package.yml`](./examples/github-actions/single-package.yml) — gates
|
|
469
|
+
broken into individual steps for a single-package (or lightly-workspaced) repo.
|
|
470
|
+
- [`monorepo-turborepo.yml`](./examples/github-actions/monorepo-turborepo.yml) —
|
|
471
|
+
the full battery (deps/dups/size/debt/circular/secrets/agents/bundle-size/coverage
|
|
472
|
+
ratchets + Turbo remote cache) for a pnpm + turbo monorepo.
|
|
473
|
+
- [`docs-coverage.yml`](./examples/github-actions/docs-coverage.yml) — `check-docs-coverage`
|
|
474
|
+
wired as its own `pull_request`-triggered job, passing `GITHUB_TOKEN`/`PR_NUMBER`/`PR_BODY`
|
|
475
|
+
from the event and checking out the PR's **base** commit (tamper-resistant — a PR can't
|
|
476
|
+
narrow its own docs-coverage policy to dodge the gate). Separate from the other examples
|
|
477
|
+
because it's a PR-diff gate, not part of the local `check:all` battery — see the config's
|
|
478
|
+
`docsCoverage` docs above.
|
|
479
|
+
|
|
480
|
+
## Programmatic use
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
import { loadContext, runCheckAll } from "@fantastic.dev/repo-gates";
|
|
484
|
+
|
|
485
|
+
process.exitCode = runCheckAll(loadContext(), { verbose: false });
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
## Development
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
pnpm install
|
|
492
|
+
pnpm test # vitest
|
|
493
|
+
pnpm run typecheck # tsc --noEmit
|
|
494
|
+
pnpm run build # tsup → dist/ (esm + d.ts)
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## License
|
|
498
|
+
|
|
499
|
+
[MIT](./LICENSE) © Kelly Kampen
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|