@brighterly/lib-fe-codestyle 1.0.0 → 1.0.2
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/README.md +234 -178
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +181 -0
- package/dist/config/define-config.d.ts +11 -0
- package/dist/config/define-config.js +33 -0
- package/dist/config/load-config.d.ts +15 -0
- package/dist/config/load-config.js +52 -0
- package/dist/config/schema.d.ts +58 -0
- package/dist/config/schema.js +139 -0
- package/dist/{invariants → engine}/ast.d.ts +8 -0
- package/dist/{invariants → engine}/ast.js +51 -0
- package/dist/engine/docs.d.ts +14 -0
- package/dist/engine/docs.js +72 -0
- package/dist/engine/meta.d.ts +32 -0
- package/dist/engine/meta.js +47 -0
- package/dist/engine/migrate.d.ts +22 -0
- package/dist/engine/migrate.js +175 -0
- package/dist/engine/overrides.d.ts +36 -0
- package/dist/engine/overrides.js +92 -0
- package/dist/engine/registry.d.ts +35 -0
- package/dist/engine/registry.js +100 -0
- package/dist/engine/report.d.ts +23 -0
- package/dist/engine/report.js +87 -0
- package/dist/engine/run.d.ts +57 -0
- package/dist/engine/run.js +353 -0
- package/dist/engine/self-test.d.ts +33 -0
- package/dist/engine/self-test.js +141 -0
- package/dist/engine/types.d.ts +232 -0
- package/dist/{invariants → engine}/types.js +2 -1
- package/dist/{invariants → engine}/util.d.ts +31 -39
- package/dist/{invariants → engine}/util.js +59 -86
- package/dist/index.d.ts +16 -0
- package/dist/index.js +14 -0
- package/dist/rules/convention.d.ts +20 -0
- package/dist/{invariants/rules → rules}/convention.js +103 -16
- package/dist/rules/index.d.ts +28 -0
- package/dist/{invariants/rules → rules}/index.js +40 -32
- package/dist/{invariants/rules → rules}/regression.d.ts +1 -1
- package/dist/{invariants/rules → rules}/regression.js +26 -6
- package/dist/{invariants/rules → rules}/shared.d.ts +1 -1
- package/dist/validate/index.d.ts +1 -1
- package/dist/validate/index.js +1 -1
- package/package.json +8 -13
- package/dist/invariants/index.d.ts +0 -7
- package/dist/invariants/index.js +0 -5
- package/dist/invariants/rules/convention.d.ts +0 -18
- package/dist/invariants/rules/fixtures.d.ts +0 -17
- package/dist/invariants/rules/fixtures.js +0 -89
- package/dist/invariants/rules/index.d.ts +0 -55
- package/dist/invariants/rules/linter.d.ts +0 -10
- package/dist/invariants/rules/linter.js +0 -9
- package/dist/invariants/run.d.ts +0 -53
- package/dist/invariants/run.js +0 -746
- package/dist/invariants/self-test.d.ts +0 -46
- package/dist/invariants/self-test.js +0 -84
- package/dist/invariants/types.d.ts +0 -222
- /package/dist/{invariants/rules → rules}/shared.js +0 -0
package/README.md
CHANGED
|
@@ -1,31 +1,214 @@
|
|
|
1
1
|
# `@brighterly/lib-fe-codestyle`
|
|
2
2
|
|
|
3
|
-
Shared, deterministic code
|
|
4
|
-
|
|
3
|
+
Shared, deterministic code review for the Brighterly Nuxt apps: a rule engine with a
|
|
4
|
+
binary (`codestyle`), eleven package rules, and a shared ESLint flat config. A consumer
|
|
5
|
+
declares one file, `codestyle.config.ts`; its own domain rules live next to it.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
**New here?** [docs/how-it-works.md](docs/how-it-works.md) explains what one run does,
|
|
8
|
+
the proof forms, overrides and tags. Moving a repo from 0.5.x:
|
|
9
|
+
[docs/migration-1.0.md](docs/migration-1.0.md).
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
code, what the gate can and cannot cover, what is already enforced, how to extend it,
|
|
10
|
-
and how to run it.
|
|
11
|
+
## Quick start
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add -D @brighterly/lib-fe-codestyle
|
|
15
|
+
```
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
`typescript` and `@vue/compiler-sfc` are **peers**: the engine parses your files with the
|
|
18
|
+
same compiler instances your build uses. Every Brighterly app already has both.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// codestyle.config.ts — the only required file
|
|
22
|
+
import { defineConfig } from '@brighterly/lib-fe-codestyle'
|
|
23
|
+
import repo from './codestyle/index.ts' // { custom: [...] } — this repo's own rules
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
settings: { // app landmarks the package rules read
|
|
27
|
+
scssTokensFile: 'app/assets/scss/_colors.scss',
|
|
28
|
+
typographySource: 'the font() mixin',
|
|
29
|
+
scriptAttrOrder: 'setup-first',
|
|
30
|
+
},
|
|
31
|
+
rules: { // one level map for package AND custom rules
|
|
32
|
+
'script-attr-order': 'error',
|
|
33
|
+
'vueuse-first': 'off',
|
|
34
|
+
},
|
|
35
|
+
...repo, // contributes `custom` only — levels stay in `rules`
|
|
36
|
+
overrides: [ // never "the rule is off" — always "this place, this reason"
|
|
37
|
+
{ files: ['app/components/UI/Buttons/BtnAuth*.vue'], rules: { 'inline-scss': 'off' },
|
|
38
|
+
reason: 'UI-kit exception, decided 2026-08-18' },
|
|
39
|
+
{ files: ['app/components/Views/ViewQuizExp132.vue'], rules: { 'journey-coverage': 'warn' },
|
|
40
|
+
reason: 'funnel still being built; until 2026-10-31' },
|
|
41
|
+
],
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A rule not named in `rules` runs at its own `level`. Keep the spread object to `{ custom }`:
|
|
46
|
+
object spread replaces a same-named key, so a `rules` map inside it and the one above would
|
|
47
|
+
silently overwrite each other.
|
|
48
|
+
|
|
49
|
+
Two loading facts, because Node runs this file with its own type stripping:
|
|
50
|
+
|
|
51
|
+
- **Relative imports spell `.ts`** (`./codestyle/index.ts`, and inside `codestyle/` too) —
|
|
52
|
+
Node does not rewrite `.js` to `.ts`. The tsconfig that covers these files needs
|
|
53
|
+
`allowImportingTsExtensions`.
|
|
54
|
+
- **Erasable syntax only** — no `enum`, `namespace` or parameter properties. `codestyle`
|
|
55
|
+
names the file and the reason if one slips in. Node 22.18+ is required for a `.ts`
|
|
56
|
+
config; `codestyle.config.mjs` works on older Node — unless it imports `.ts` rule files,
|
|
57
|
+
which still need 22.18+.
|
|
58
|
+
|
|
59
|
+
### Config keys
|
|
60
|
+
|
|
61
|
+
| Key | Default | What it is |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `settings` | `{}` | app landmarks for the package rules (`SharedRuleSettings` — see below) |
|
|
64
|
+
| `rules` | `{}` | `name → 'off' \| 'warn' \| 'error'` for package and custom rules. An unknown name, an engine rule (`IG0`–`IG2`) or an ESLint/test-owned declaration here is a config error |
|
|
65
|
+
| `custom` | `[]` | this repo's rules (each carries a proof) and check-less declarations owned by ESLint or a test. Shape-checked: a rule has no `owner` and needs `level` (`off`/`warn`/`error`), a non-empty `scope`, a `check` function and `title`/`why`/`fix`; a declaration has `owner: 'eslint' \| 'test'` and `ref`/`title`/`why`. Errors name the field (`custom[2].level`) |
|
|
66
|
+
| `overrides` | `[]` | `{ files, rules, reason }` — a level for some rules on some files, reason mandatory |
|
|
67
|
+
| `files` | `['app', 'config', 'tests', 'codestyle']` | scan roots: directories, files or globs |
|
|
68
|
+
| `ignores` | `['node_modules', '.nuxt', '.output', 'dist', 'coverage']` | pruned from the walk: names, paths or globs |
|
|
69
|
+
| `ruleSources` | `['codestyle/']` | where this repo's rules live: a staged change there triggers the self-test; their files are exempt from ignore-comment parsing (fixture strings carry ignore comments on purpose) |
|
|
70
|
+
| `docs` | `[]` | `{ template, output, tags? }` rendered by `codestyle docs` |
|
|
71
|
+
| `validate` | `[]` | `{ name, run }` steps for `codestyle validate` |
|
|
72
|
+
| `precommit` | `[]` | `{ name, run }` steps `codestyle precommit` runs after the staged check |
|
|
73
|
+
|
|
74
|
+
An unknown top-level key is a config error, so a typo never silently does nothing.
|
|
75
|
+
|
|
76
|
+
## Package rules
|
|
77
|
+
|
|
78
|
+
| Rule | Default | Catches |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `no-bare-storage` | warn* | bare `localStorage` / `sessionStorage` — the accessor itself throws on Safari ITP and in in-app webviews |
|
|
81
|
+
| `inline-scss` | error | SCSS rules inside a Vue `<style>` block instead of a referenced `.scss` file |
|
|
82
|
+
| `guarded-json-parse` | warn* | `JSON.parse` with no enclosing `try`/`catch` — a corrupt stored value breaks that path permanently |
|
|
83
|
+
| `font-face-single-family` | error | `@font-face` declaring a font *stack* — an invalid descriptor, so the browser drops the face silently |
|
|
84
|
+
| `vueuse-first` | warn | a hand-rolled browser API with a VueUse equivalent |
|
|
85
|
+
| `ui-kit-unique-names` | warn | a component whose name duplicates a UI-kit component |
|
|
86
|
+
| `scss-tokens` | warn | a literal colour in SCSS instead of a design token |
|
|
87
|
+
| `date-format-enum` | warn | a hand-rolled date format string, or the date part cut out of `toISOString()` |
|
|
88
|
+
| `types-layout` | warn | a type file outside `types/<entity>/{enums,interfaces,types}.ts` |
|
|
89
|
+
| `typography-source` | warn | a raw `font-family` / `font-size` / `font-weight` instead of the app's typography source |
|
|
90
|
+
| `script-attr-order` | warn | `<script>` attribute order differing from the app's canonical |
|
|
91
|
+
|
|
92
|
+
`error` is reserved for crash-class rules; conventions ship at `warn` so each app measures
|
|
93
|
+
its backlog before anything blocks. `no-bare-storage` and `guarded-json-parse` (marked *)
|
|
94
|
+
ship at `warn` as an interim state while the fleet backlog exists; their target is
|
|
95
|
+
`error`. Underscore-prefixed SCSS partials are declaration files and exempt from
|
|
96
|
+
`scss-tokens` and `typography-source`. Hover a `packageRuleNames` member for the rule's
|
|
97
|
+
contract.
|
|
98
|
+
|
|
99
|
+
### `settings`
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
settings: {
|
|
103
|
+
safeStorageModule: 'safeLocalStorage from @helpers/safe-local-storage', // no-bare-storage / guarded-json-parse messages
|
|
104
|
+
safeStorageFile: 'app/utils/helpers/safe-local-storage.ts', // no-bare-storage exempts the wrapper itself
|
|
105
|
+
safeJsonParse: 'parseStoredJson() from @helpers/safe-local-storage',
|
|
106
|
+
scssTokensFile: 'app/assets/scss/_colors.scss',
|
|
107
|
+
uiKitRoot: 'app/components/UI', // ui-kit-unique-names (default shown)
|
|
108
|
+
dateFormatModule: 'DateFormat from @ts-types/date/enums',
|
|
109
|
+
typesRoot: 'app/types', // types-layout (default shown)
|
|
110
|
+
typographySource: 'the font() mixin from @scss/_mixins.scss',
|
|
111
|
+
scriptAttrOrder: 'setup-first', // or 'lang-first' (default 'setup-first')
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A missing key degrades the rule's *message* or falls back to the documented default —
|
|
116
|
+
never silences the rule.
|
|
117
|
+
|
|
118
|
+
## Commands
|
|
119
|
+
|
|
120
|
+
| Command | What it does |
|
|
15
121
|
|---|---|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
122
|
+
| `codestyle check` | runs the rules. Flags: `--staged` (this commit), `--stdin --file=<path>` (check piped content as that file), `--json`, `--only=<a,b>` (by name; `--rule=<a,b>` is an alias, merged with `--only`), `--tags=<a,b>` (by tag — a tag no rule carries is exit 2), `--no-self-test`, `--no-color` |
|
|
123
|
+
| `codestyle list` | every rule: `name · level · owner · tags` |
|
|
124
|
+
| `codestyle self-test [--package]` | proves this repo's custom rules (`self-test: 0 rule(s) proven`, exit 0, when there are none); `--package` proves the package's own rules |
|
|
125
|
+
| `codestyle docs [--check]` | renders every `docs` entry; `--check` writes nothing and fails on a stale or missing output. No `docs` entries is exit 2 |
|
|
126
|
+
| `codestyle validate` | runs the `validate` steps — every step, one exit code. No steps is exit 2 |
|
|
127
|
+
| `codestyle precommit` | `check --staged` first; if it fails, the steps are skipped. Then the `precommit` steps run as one group — every step, one exit code. Any other argument is passed to `check` |
|
|
128
|
+
| `codestyle migrate [--write]` | moves a 0.5.x repo: suppression spelling, renamed ids, a config skeleton |
|
|
129
|
+
|
|
130
|
+
Every command takes `--root=<path>` (default: the working directory). Exit codes:
|
|
131
|
+
**0** clean or warnings only · **1** an error-level finding, a failed self-test, a stale doc
|
|
132
|
+
or a failed step · **2** the tool itself failed (bad flags, no or bad config — a malformed
|
|
133
|
+
`until` such as `until 2026-1-5` included — or a crash).
|
|
134
|
+
|
|
135
|
+
## Suppressions and overrides
|
|
136
|
+
|
|
137
|
+
One line, reviewed: `// codestyle-ignore: <name> — <reason>` suppresses that line and the
|
|
138
|
+
next. The reason is mandatory. Three engine rules police the escape hatch:
|
|
139
|
+
|
|
140
|
+
- **IG0** — an ignore without a reason;
|
|
141
|
+
- **IG1** — a name that is not a live rule (a renamed package rule says what it became), or
|
|
142
|
+
an override that no longer applies (a glob that matches no file, an `until` that has passed);
|
|
143
|
+
- **IG2** — an ignore naming a rule declared `noIgnore`; the comment suppresses nothing.
|
|
144
|
+
|
|
145
|
+
`// invariant-ignore:` is still accepted through 1.x with a one-line deprecation warning per
|
|
146
|
+
file; `codestyle migrate --write` rewrites it.
|
|
147
|
+
|
|
148
|
+
A place-wide exemption is an **override**: `{ files, rules, reason }`. An override can only
|
|
149
|
+
lower a level, never raise it. A `rules` entry is the base level of its rule and a ceiling
|
|
150
|
+
over a finding's own level; without one, a finding's own level wins. A reason may carry `until YYYY-MM-DD`: from `until − 7` days
|
|
151
|
+
through `until` itself (inclusive) the exemption reports at `warn`; after the date it stops
|
|
152
|
+
applying and is reported. A malformed date is a config error. A rule
|
|
153
|
+
may require that date on every override of it (`exemptionsRequireUntil`).
|
|
20
154
|
|
|
21
|
-
`
|
|
22
|
-
resolves deep paths that break the moment it is published.
|
|
155
|
+
## `--json`
|
|
23
156
|
|
|
24
|
-
|
|
157
|
+
```ts
|
|
158
|
+
{ schemaVersion: 1, ok, scanned, errors, warnings,
|
|
159
|
+
rules: [{ name, level, title, tags, owner: 'package' | 'custom' | 'eslint' | 'test' }],
|
|
160
|
+
findings: [{ rule, level, file, line, message }],
|
|
161
|
+
skipped?: { file, reason }, // --stdin: outside `files`, under `ignores`, or no rule scopes it
|
|
162
|
+
selfTest?: { ok, failures } } // present whenever the self-test ran
|
|
163
|
+
|
|
164
|
+
// a tool failure (exit 2) under --json:
|
|
165
|
+
{ schemaVersion: 1, ok: false, error }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`owner` is `package` (this package's rules), `custom` (the repo's own), `eslint` or `test`
|
|
169
|
+
(declarations: never run, here so their names stay live). `schemaVersion` changes only with
|
|
170
|
+
a breaking shape change. ESLint- and test-owned declarations appear in `rules` with no
|
|
171
|
+
findings. Output always drains before the process exits — a large report through a pipe
|
|
172
|
+
arrives whole.
|
|
173
|
+
|
|
174
|
+
## What checks what
|
|
25
175
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
176
|
+
| Tool | Answers | Runs |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| ESLint (+ this package's flat config and local rules) | "is this file well-formed by our style?" — one file's AST, IDE feedback, autofix | editor, pre-commit, CI |
|
|
179
|
+
| **This package** | "does the repo still satisfy its contracts?" — anything needing a second file, a registry, the test tree, SCSS/assets/config surfaces, project semantics, or an audited exemption | pre-commit (`--staged`), CI (whole repo), the AI write-time hook (`--stdin`) |
|
|
180
|
+
| Auditor | "what in the repo's state needs a human decision?" — dead code, dependency health, churn, LLM change review, and the trend of this package's warnings | explicit, human-triggered |
|
|
181
|
+
|
|
182
|
+
If one file's AST decides it and a plugin or a small shared rule can say it, it is ESLint —
|
|
183
|
+
declared in `custom` with `owner: 'eslint'` so there is one catalogue. If the author can fix
|
|
184
|
+
it before the commit lands, gate it here; if it needs history, external signals or judgment,
|
|
185
|
+
it belongs to the auditor, which reads `codestyle check --json` rather than grepping.
|
|
186
|
+
|
|
187
|
+
## Wiring a consumer
|
|
188
|
+
|
|
189
|
+
```jsonc
|
|
190
|
+
// package.json
|
|
191
|
+
"scripts": {
|
|
192
|
+
"codestyle": "codestyle check",
|
|
193
|
+
"codestyle:e2e": "codestyle check --tags=e2e",
|
|
194
|
+
"validate": "codestyle validate"
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- **Pre-commit**: the hook file is one line, `pnpm exec codestyle precommit`. Keep it in a
|
|
199
|
+
tracked directory and arm it from `postinstall` with `git config core.hooksPath <dir> || true`
|
|
200
|
+
(the `|| true` keeps installs without `.git`, such as Docker builds, green).
|
|
201
|
+
- **CI**: one step, `pnpm exec codestyle validate`, with the full chain declared in
|
|
202
|
+
`validate` (the codestyle check, lint, tests, typecheck).
|
|
203
|
+
- **AI write-time hook**: `codestyle check --stdin --file=<repo-relative path> [--tags=<x>]`
|
|
204
|
+
checks content before it reaches the disk; a path outside `files`, under `ignores`, or scoped
|
|
205
|
+
by no selected rule comes back as `skipped`.
|
|
206
|
+
- **Agent rules file**: declare `docs: [{ template, output, tags }]` and run `codestyle docs`;
|
|
207
|
+
the generated file says so in its first line and is never edited by hand. Put
|
|
208
|
+
`codestyle docs --check` in `validate` so drift fails the gate.
|
|
209
|
+
- **CLAUDE.md**: paste [docs/consumer-claude-md.md](docs/consumer-claude-md.md).
|
|
210
|
+
|
|
211
|
+
## The ESLint config — export data, never factories
|
|
29
212
|
|
|
30
213
|
```js
|
|
31
214
|
// an app that needs nothing extra
|
|
@@ -46,194 +229,67 @@ export default [
|
|
|
46
229
|
]
|
|
47
230
|
```
|
|
48
231
|
|
|
49
|
-
ESLint **replaces** rule options rather than merging them, so
|
|
50
|
-
|
|
51
|
-
exported as data.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
## What the rule pack ships
|
|
55
|
-
|
|
56
|
-
### Invariants — `@brighterly/lib-fe-codestyle/invariants/rules`
|
|
57
|
-
|
|
58
|
-
| Rule | Level | Checks |
|
|
59
|
-
|---|---|---|
|
|
60
|
-
| `core/R1` | warn* | bare `localStorage` / `sessionStorage` — the accessor itself throws on Safari ITP and in-app webviews |
|
|
61
|
-
| `core/R2` | error | SCSS rules inline in a Vue `<style>` block instead of a referenced `.scss` file |
|
|
62
|
-
| `core/R3` | warn* | `JSON.parse` with no enclosing `try`/`catch` — a corrupt entry breaks that path permanently |
|
|
63
|
-
| `core/R4` | error | `@font-face` declaring a font *stack* — an invalid descriptor, so the browser drops the face silently |
|
|
64
|
-
| `core/C1` | warn | hand-rolled browser API with a VueUse equivalent |
|
|
65
|
-
| `core/C2` | warn | a component duplicating an existing UI-kit component name |
|
|
66
|
-
| `core/C3` | warn | a literal colour in SCSS instead of a design token |
|
|
67
|
-
| `core/C4` | warn | a hand-rolled date format string, or `toISOString()` |
|
|
68
|
-
| `core/C5` | warn | a type file outside the `types/<entity>/{enums,interfaces,types}.ts` structure |
|
|
69
|
-
| `core/C6` | warn | a raw `font-family` / `font-size` / `font-weight` declaration instead of the app's typography source |
|
|
70
|
-
| `core/C7` | warn | `<script>` attribute order differing from the app's canonical (`setup lang` vs `lang setup`) |
|
|
71
|
-
|
|
72
|
-
Level discipline: `error` is for crash-class rules (`core/R*`); `warn` is for
|
|
73
|
-
conventions (`core/C*`). `core/R1`/`core/R3` (marked *) ship at `warn` **as an interim
|
|
74
|
-
state** (2026-09-25): their fleet backlog is real code nobody has bandwidth to migrate
|
|
75
|
-
and re-verify right now. Their target level is `error` — promote once the backlog
|
|
76
|
-
clears (a breaking-style bump). `core/R2`/`core/R4` stay `error`. A consumer's `levels`
|
|
77
|
-
ceiling is still never the tool for a backlog on an `error` rule — fix or allowlist.
|
|
78
|
-
Underscore-prefixed SCSS partials (`_extends.scss`, `_colors-*.scss`, …) are declaration
|
|
79
|
-
files by convention in every Brighterly app and are exempt from `core/C3`/`core/C6` wholesale.
|
|
80
|
-
|
|
81
|
-
`core/R*` were ported from land-app, where each was a production scar (as `R1`, `R7`, `R18`,
|
|
82
|
-
`R28` — renumbered sequentially when the prefix landed, pre-publish, while no released
|
|
83
|
-
version pinned the old tokens). `core/C*` are conventions with
|
|
84
|
-
no incident behind them *yet* — they ship at `warn` so a consuming app can measure what they
|
|
85
|
-
find before anyone decides to block on them. Promoting one to `error` is a **major** bump.
|
|
86
|
-
|
|
87
|
-
Fixtures ship with the rules (`commonFixtures`), so `--self-test` works in any consumer
|
|
88
|
-
without it having to write fixtures for rules it did not author.
|
|
89
|
-
|
|
90
|
-
### ESLint — `@brighterly/lib-fe-codestyle/eslint`
|
|
91
|
-
|
|
92
|
-
Seven local rules: `padding-after-macros`, `async-components-after-macros`,
|
|
232
|
+
ESLint **replaces** rule options rather than merging them, so an override that does not
|
|
233
|
+
spread the shared map silently discards every shared entry — that is why the maps are
|
|
234
|
+
exported as data. Seven local rules: `padding-after-macros`, `async-components-after-macros`,
|
|
93
235
|
`no-import-group-labels`, `ref-prefix-prop-mirrors`, `require-component-name`,
|
|
94
|
-
`on-prefixed-callbacks`,
|
|
95
|
-
invoked without `?.` throws for the first caller that omits it).
|
|
96
|
-
|
|
97
|
-
Plus the shared flat config: restricted syntax, restricted imports, the types-organization
|
|
98
|
-
blocks, and the `check-file` naming maps — all exported as data so a consumer can spread
|
|
99
|
-
and extend them.
|
|
100
|
-
|
|
101
|
-
## App-specific paths: `settings`, not parameters
|
|
236
|
+
`on-prefixed-callbacks`, `optional-callback-chaining`.
|
|
102
237
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
// invariants/config.mts
|
|
107
|
-
export const settings = {
|
|
108
|
-
safeStorageModule: 'safeLocalStorage from @helpers/safe-local-storage',
|
|
109
|
-
safeStorageFile: 'app/utils/helpers/safe-local-storage.ts', // core/R1 auto-exempts the wrapper itself
|
|
110
|
-
safeJsonParse: 'parseStoredJson() from @helpers/safe-local-storage',
|
|
111
|
-
scssTokensFile: 'app/assets/scss/_colors.scss',
|
|
112
|
-
uiKitRoot: 'app/components/UI',
|
|
113
|
-
dateFormatModule: 'DateFormat from @ts-types/date/enums',
|
|
114
|
-
typesRoot: 'app/types', // core/C5 (default shown)
|
|
115
|
-
typographySource: 'the font() mixin from @scss/_mixins.scss', // core/C6 — cs-app points this at its AppText component instead
|
|
116
|
-
scriptAttrOrder: 'setup-first', // core/C7 — or 'lang-first' (default 'setup-first')
|
|
117
|
-
selfTestFile: 'invariants/self-test.mts', // its ignore-strings are fixture DATA — skipped by IG0/IG1
|
|
118
|
-
}
|
|
119
|
-
```
|
|
238
|
+
## Imports
|
|
120
239
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
`
|
|
125
|
-
default above, so leaving them unset still never silences the rule.
|
|
126
|
-
|
|
127
|
-
## What consumers see in the IDE
|
|
128
|
-
|
|
129
|
-
Three hover surfaces, so "what rules do we have and what do they look for" never
|
|
130
|
-
requires opening this repo:
|
|
131
|
-
|
|
132
|
-
- **`sharedRuleIds`** (from `…/invariants/rules`) — the catalog: one constant per rule,
|
|
133
|
-
each JSDoc'd with level, what it checks, and the fix. Use them as `allowlists` /
|
|
134
|
-
`levels` keys (`[sharedRuleIds.core/C6]: 'off'`) and hover for the contract. A test pins
|
|
135
|
-
the catalog to the shipped rules, so it cannot drift.
|
|
136
|
-
- **`SharedRuleSettings`** (from `…/invariants`) — annotate the app's `settings` const
|
|
137
|
-
with it to get completion plus the per-key contract, and a type error on an invalid
|
|
138
|
-
`scriptAttrOrder` value.
|
|
139
|
-
- **ESLint squiggles** — every local rule ships `meta.docs.description`, which is what
|
|
140
|
-
the editor shows on hover over a violation.
|
|
141
|
-
|
|
142
|
-
PhpStorm / WebStorm notes: the IDE runs the **project's own ESLint binary** against the
|
|
143
|
-
flat `eslint.config.mjs`, so rules defined inside this package just work — nothing to
|
|
144
|
-
install or configure beyond the default "Automatic ESLint configuration". Two real
|
|
145
|
-
behaviours to know: the ESLint daemon caches the resolved config, so after upgrading
|
|
146
|
-
this package run *ESLint: Restart Service* (or reopen the project) to pick up new
|
|
147
|
-
rules; and the invariants engine is **not** an IDE inspection — its findings appear in
|
|
148
|
-
the terminal (`pnpm validate:invariants`) and the pre-commit hook only.
|
|
240
|
+
| Import | What it is |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `@brighterly/lib-fe-codestyle` | `defineConfig`; the rule contract types (`Rule`, `RuleContext`, the proofs, …); the helpers custom rules share (`eachLine`, `setting`, `stripCodeComments`, `matchesAny`, the AST helpers, …); `packageRuleNames` |
|
|
243
|
+
| `@brighterly/lib-fe-codestyle/eslint` | the shared flat config, its local rules and the maps behind them |
|
|
149
244
|
|
|
150
|
-
|
|
245
|
+
Everything else is reached through the `codestyle` binary. `src/` ships compiled as `dist/`
|
|
246
|
+
(Node does not strip types inside `node_modules`); `eslint/` ships as plain `.mjs`.
|
|
151
247
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
Every Brighterly Nuxt app already has both in the store — the devDep line adds a
|
|
157
|
-
symlink, not a download.
|
|
158
|
-
2. An `invariants/` directory with `cli.mts` (a ~20-line composition root — copy
|
|
159
|
-
land-app's), `config.mts` (allowlists, levels, retired, unmaintained, `settings` —
|
|
160
|
-
see above), your app-local rules, and `specs/` for checks needing real module
|
|
161
|
-
resolution.
|
|
162
|
-
3. A missing peer is loud, never silent: pnpm warns at install (and auto-installs by
|
|
163
|
-
default), and the engine's lazy `import()` throws on the first AST rule otherwise.
|
|
164
|
-
4. Arm the pre-commit hook from install, not by hand: keep the hook script in a tracked
|
|
165
|
-
directory and add `git config core.hooksPath <that-dir> || true` to `postinstall`
|
|
166
|
-
(the `|| true` keeps .git-less installs — Docker builds — green). Setup stays
|
|
167
|
-
`pnpm i && pnpm dev`; a hand-made `.git/hooks` symlink dies with every fresh clone.
|
|
168
|
-
|
|
169
|
-
An app with a pre-extraction fork of the engine (app-funnels) deletes its copied
|
|
170
|
-
`ast/util/run/self-test` plumbing and keeps only domain rules + config; its existing
|
|
171
|
-
compiler-sfc devDep already satisfies the peer.
|
|
172
|
-
|
|
173
|
-
## Why `src/` is compiled but `eslint/` is not
|
|
174
|
-
|
|
175
|
-
Node refuses to strip types inside `node_modules`
|
|
176
|
-
(`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), so the TypeScript half ships as `dist/`.
|
|
177
|
-
The ESLint config and its seven rules are already plain `.mjs` and ship as-is.
|
|
178
|
-
|
|
179
|
-
## Rule IDs
|
|
180
|
-
|
|
181
|
-
Shared rules carry the **`core/` prefix**: `core/R1`, `core/C3`. App-local rules stay bare:
|
|
182
|
-
`R1`, `B2`. The prefix marks provenance — a `core/`-id is shipped by this package, never
|
|
183
|
-
defined, renumbered or recycled in a consumer — and it keeps the two number lines
|
|
184
|
-
independent: every app allocates its own `R1, R2, …` naturally, with no risk of
|
|
185
|
-
colliding with a rule the package ships later. `run()` still exits 2 on a duplicate,
|
|
186
|
-
so a collision is loud, not silent.
|
|
187
|
-
|
|
188
|
-
Within the package each family is numbered sequentially with no gaps (`core/R1`–`core/R4`,
|
|
189
|
-
`core/C1`–`core/C7`) — enforced by `test/invariants/rule-ids.test.ts`. The family letter keeps
|
|
190
|
-
meaning provenance — `R` regression scar, `B` business, `C` convention, `L`
|
|
191
|
-
linter-owned — not topic. `core/` in a report means exactly one thing: this finding
|
|
192
|
-
comes from the library, and its numbering is the library's to own.
|
|
248
|
+
In the IDE: `defineConfig` types the whole config, a `packageRuleNames` member's hover is
|
|
249
|
+
that rule's contract, and ESLint squiggles carry each local rule's description. The rule
|
|
250
|
+
engine is not an IDE inspection: its findings appear in the terminal and the pre-commit
|
|
251
|
+
hook. After upgrading this package, run *ESLint: Restart Service*.
|
|
193
252
|
|
|
194
253
|
## Versioning is about blocking power
|
|
195
254
|
|
|
196
255
|
One question decides the bump: **can this change turn a green consumer red, or break
|
|
197
|
-
something a consumer wrote against a rule
|
|
256
|
+
something a consumer wrote against a rule name?** Yes → major. Adds signal without
|
|
198
257
|
blocking → minor. Everything else → patch.
|
|
199
258
|
|
|
200
259
|
| Change | Bump | Why |
|
|
201
260
|
|---|---|---|
|
|
202
|
-
| New
|
|
261
|
+
| New package rule at `error` | **major** | can turn a green repo red on install |
|
|
203
262
|
| Promoting a rule `warn` → `error` | **major** | same — new blocking power |
|
|
204
263
|
| Widening a rule's `scope`, narrowing its `exclude`, or making its match stricter | **major** | existing code that passed now fails |
|
|
205
|
-
| Renaming
|
|
264
|
+
| Renaming or removing a rule name | **major** | breaks every `codestyle-ignore:` comment, `rules`/`overrides` key and doc that names it — `IG1` starts firing in every consumer |
|
|
206
265
|
| New ESLint rule at `error`, or adding a selector to `restrictedSyntax` / `restrictedImports` / the `check-file` maps | **major** | the shared config is spread into every app's lint gate |
|
|
207
|
-
| Engine
|
|
208
|
-
| New
|
|
266
|
+
| Engine behaviour change (exit codes, the `--json` shape, the config or rule contract) | **major** | consumers' configs, hooks and CI are written against it |
|
|
267
|
+
| New package rule at `warn` | minor | new signal, blocks nothing |
|
|
209
268
|
| New ESLint rule at `warn` | minor | same |
|
|
210
269
|
| New export (a map, a helper, a settings key a rule *optionally* reads) | minor | additive surface |
|
|
211
270
|
| Demoting a rule `error` → `warn`, or narrowing its scope | patch | strictly relaxing |
|
|
212
|
-
| Bug fix that removes false positives, message/`why`/`fix` wording,
|
|
271
|
+
| Bug fix that removes false positives, message/`why`/`fix` wording, proof improvements | patch | nothing new fires |
|
|
213
272
|
|
|
214
|
-
Two edges worth naming: a bug fix that removes **false negatives** (the rule now
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
message, never the firing — see above).
|
|
273
|
+
Two edges worth naming: a bug fix that removes **false negatives** (the rule now catches what
|
|
274
|
+
it always claimed to) is still **major** — correctness of intent does not change what happens
|
|
275
|
+
in a consumer's CI. And a new `settings` key a rule *requires* is major; one it merely reads
|
|
276
|
+
for a better message is minor.
|
|
219
277
|
|
|
220
278
|
## Publishing
|
|
221
279
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
token. `publishConfig.access: "public"` in `package.json` makes the scoped publish
|
|
225
|
-
work without a paid org plan.
|
|
280
|
+
Published **publicly** to npmjs.org under the `@brighterly` org — the same channel as
|
|
281
|
+
`@brighterly/lib-lumee-ui`, so consumers need no registry config or token.
|
|
226
282
|
|
|
227
283
|
```bash
|
|
228
|
-
npm login
|
|
229
|
-
npm version <patch|minor|major>
|
|
230
|
-
npm publish
|
|
284
|
+
npm login # must be a member of the @brighterly org
|
|
285
|
+
npm version <patch|minor|major> # per the blocking-power table above
|
|
286
|
+
npm publish # prepublishOnly rebuilds dist and runs the tests first
|
|
231
287
|
git push && git push --tags
|
|
232
288
|
```
|
|
233
289
|
|
|
234
|
-
`dist/` is gitignored; `prepublishOnly` (`clean → build → test`) guarantees the
|
|
235
|
-
|
|
290
|
+
`dist/` is gitignored; `prepublishOnly` (`clean → build → test`) guarantees the tarball is
|
|
291
|
+
built from the current sources.
|
|
236
292
|
|
|
237
293
|
## Design
|
|
238
294
|
|
|
239
|
-
|
|
295
|
+
[docs/superpowers/specs/2026-10-16-consumer-architecture-v1-design.md](docs/superpowers/specs/2026-10-16-consumer-architecture-v1-design.md)
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `codestyle` — a thin argv switch over the engine. No engine logic lives here.
|
|
4
|
+
*
|
|
5
|
+
* Exit codes: 0 clean · 1 a finding at error, a failed proof, a stale doc or a failed step ·
|
|
6
|
+
* 2 the tool itself failed (usage, config).
|
|
7
|
+
*/
|
|
8
|
+
export {};
|