@hublo/sentinel 1.5.0 → 1.5.1-alpha.1
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 +9 -4
- package/dist/bin/sentinel.js +366 -156
- package/dist/{chunk-MOELAQPC.js → chunk-64H5HBPH.js} +580 -158
- package/dist/{chunk-43HQ6BVJ.js → chunk-J2USE3KV.js} +69 -19
- package/dist/{chunk-IL5PPLXE.js → chunk-JRZI5IIN.js} +1 -1
- package/dist/{chunk-IL5PPLXE.js.map → chunk-JRZI5IIN.js.map} +1 -1
- package/dist/{chunk-Y57KA4QS.js → chunk-LFNOBQZU.js} +2 -2
- package/dist/index.d.ts +48 -0
- package/dist/index.js +3 -3
- package/dist/roles/test/setup/jest-parity.js +2 -2
- package/dist/roles/test/setup/workspace-entry.js +2 -2
- package/dist/{validate-FRMENKHM.js → validate-TIPIECG6.js} +2 -2
- package/docs/format-adoption.md +40 -10
- package/docs/lint-adoption.md +73 -10
- package/docs/typescript-adoption.md +1 -0
- package/docs/typescript-traces.md +249 -0
- package/docs/using-sentinel.md +15 -3
- package/docs/validating-a-change.md +130 -0
- package/package.json +30 -23
- /package/dist/{chunk-Y57KA4QS.js.map → chunk-LFNOBQZU.js.map} +0 -0
package/docs/lint-adoption.md
CHANGED
|
@@ -17,21 +17,29 @@ pnpm install
|
|
|
17
17
|
|
|
18
18
|
Then run `pnpm run lint`.
|
|
19
19
|
|
|
20
|
-
`--init` with no target
|
|
20
|
+
`--init` with no target on a module that has adopted nothing adopts **every role sentinel
|
|
21
|
+
ships** (on a module that has, it re-inits the roles it has), and for lint that is the
|
|
21
22
|
intended way round: it runs the linter's autofix, then writes the format config and formats
|
|
22
|
-
the module, so the files it generated are already in your module's style.
|
|
23
|
-
|
|
23
|
+
the module, so the files it generated are already in your module's style.
|
|
24
|
+
|
|
25
|
+
**Lint and format are adopted together.** `--init --lint`, `--init --format` and
|
|
26
|
+
`--init --lint --format` do the same thing: oxlint AND oxfmt, and the module excluded from the
|
|
27
|
+
root `.prettierignore`, exactly as a bare `--init` does for those two roles. Since 1.5.1; before
|
|
28
|
+
it, lint alone left the module to the root Prettier, which then rejected the `.oxlintrc.json`
|
|
29
|
+
adoption had just written.
|
|
24
30
|
|
|
25
31
|
`--init` fixes what a machine can fix, once. It does not loop, and it does not edit your code
|
|
26
32
|
beyond what the tools' own fixers do. What is left afterwards is real debt: see
|
|
27
33
|
[Reading the numbers](#reading-the-numbers).
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
>
|
|
35
|
+
**Previewing the source rewrite.** `--init --dry-run` says the autofix can rewrite source, without
|
|
36
|
+
running it. `--init --dry-run --rehearse` runs it in a throwaway copy of the module and names the
|
|
37
|
+
files, at the cost of the real pass (minutes on a large nest module, nearly all of it
|
|
38
|
+
`@nx/enforce-module-boundaries`).
|
|
39
|
+
|
|
40
|
+
> **Order matters, and `--init` gets it right for you.** The linter's autofix rewrites code,
|
|
41
|
+
> and sentinel writes JSON in its own style, so **formatting has to come after linting**, never
|
|
42
|
+
> before. Whatever targets you name, format runs LAST, over everything every role wrote.
|
|
35
43
|
|
|
36
44
|
## What `--init` does
|
|
37
45
|
|
|
@@ -150,6 +158,11 @@ a **layer above the preset**, the way the planning team did:
|
|
|
150
158
|
Order matters, sentinel's preset first, or your layer cannot override anything. `--inspect`
|
|
151
159
|
reports the layer so it stays visible.
|
|
152
160
|
|
|
161
|
+
A rule set directly in `.oxlintrc.json` is still reported as drift, and a layer is the place for
|
|
162
|
+
it. Since 1.5.1 a re-`init` no longer deletes it, if the preset does not configure that rule: it
|
|
163
|
+
is kept, in your order, and `--init` says so. A rule the preset does configure is dropped, as
|
|
164
|
+
before, and named: set in the stub, it would override the preset and the baseline both.
|
|
165
|
+
|
|
153
166
|
Think of it as a queue rather than a fork: a rule promoted into the preset is deleted from the
|
|
154
167
|
layer. It is the exact mirror of `.oxlintrc.baseline.json`, which holds rules **below** the
|
|
155
168
|
preset and empties as debt is fixed, while a layer holds rules **above** it and empties as they
|
|
@@ -160,6 +173,56 @@ whatever `extends` it finds, so a layer already there survives and is respected
|
|
|
160
173
|
that follows. Added afterwards, the autofix has already run, and nothing undoes what it
|
|
161
174
|
rewrote. One team met this as twelve rewritten files.
|
|
162
175
|
|
|
176
|
+
### Where a team puts its own guards
|
|
177
|
+
|
|
178
|
+
Lint is always migrated, so a guard your ESLint carried and the preset does not is yours to add,
|
|
179
|
+
in your own config. A re-`init` keeps all of it (since 1.5.1): your `extends` layers in their
|
|
180
|
+
order after sentinel's preset, and in `.oxlintrc.json` your `rules`, `plugins`, `jsPlugins`,
|
|
181
|
+
`settings` and `overrides`. `--init` prints one line per block it kept.
|
|
182
|
+
|
|
183
|
+
`console` keeps two rules the react preset does not configure, directly in its stub:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"extends": ["./node_modules/@hublo/sentinel/lint/react.json"],
|
|
188
|
+
"rules": {
|
|
189
|
+
"import/no-cycle": "error",
|
|
190
|
+
"oxc/no-barrel-file": ["error", { "threshold": 0 }]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
A local plugin and file-scoped rules, the shape a planning layer takes:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"extends": ["./node_modules/@hublo/sentinel/lint/react.json"],
|
|
200
|
+
"jsPlugins": [{ "name": "planning", "specifier": "./lint/planning-plugin.mjs" }],
|
|
201
|
+
"overrides": [
|
|
202
|
+
{
|
|
203
|
+
"files": ["src/modules/planning/**/*.tsx"],
|
|
204
|
+
"rules": { "planning/no-raw-date": "error", "max-depth": ["error", 3] }
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Two limits, both the preset's governance:
|
|
211
|
+
|
|
212
|
+
- **An entry the preset already has is the preset's.** A rule it configures (top level or in one
|
|
213
|
+
of its overrides, at any severity), a `jsPlugins` entry it registers under the same name, a
|
|
214
|
+
`settings` key it sets: written in the stub, it would override sentinel's, so a re-`init` drops
|
|
215
|
+
it and names it. That includes `no-console` and `no-restricted-imports` in the react preset. To
|
|
216
|
+
set one of those for your module, put it in an `extends` layer after the preset (above): a
|
|
217
|
+
layer is reviewed as yours and `--inspect` shows it.
|
|
218
|
+
- **Your `overrides` blocks are kept as written**, with their `files`, and their rules go through
|
|
219
|
+
the same filter. Two of your blocks with the same `files` are merged into one on re-`init`; a
|
|
220
|
+
block is never merged into a preset override, which lives behind `extends` where the stub
|
|
221
|
+
cannot write. Measured on oxlint 1.77.0, the version sentinel ships: an `overrides` block in an
|
|
222
|
+
extended file DOES apply, and `plugins` and `jsPlugins` in the stub add to the extended ones
|
|
223
|
+
rather than replace them. The planning layer's notes say overrides do not cross `extends`; that
|
|
224
|
+
is not what 1.77.0 does.
|
|
225
|
+
|
|
163
226
|
## What a preset does not enforce
|
|
164
227
|
|
|
165
228
|
Every unenforced rule carries a reason, readable with `sentinel --inspect --lint`:
|
|
@@ -274,7 +337,7 @@ The numbers come from oxlint's own JSON, not from parsing what it prints for hum
|
|
|
274
337
|
|
|
275
338
|
| Symptom | Cause and fix |
|
|
276
339
|
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
277
|
-
| `pnpm run lint` fails on **Prettier** right after adopting |
|
|
340
|
+
| `pnpm run lint` fails on **Prettier** right after adopting | the module was adopted by a sentinel older than 1.5.1 with lint alone, so it is still formatted by the root Prettier. Re-run `sentinel --init --lint`: it adopts format with it |
|
|
278
341
|
| `Plugin 'x' not found` / `extends … does not exist` | the preset path assumes pnpm's isolated layout. Run `pnpm install` in the module; if the workspace switched to a hoisted linker, that breaks every adopted module at once |
|
|
279
342
|
| A file that was never linted now reports errors | your ESLint config ignored it. `--init` carries your module's `ignores` across, but it **cannot read a computed one** (e.g. `includeIgnoreFile(gitignorePath)`) and says so when it hits one. Add what you need to `ignorePatterns` |
|
|
280
343
|
| `error: Unexpected token` in a `.svelte` file | oxlint reads a `<script>` block as JavaScript unless it declares `lang="ts"`. Add it, or ignore the file |
|
|
@@ -76,6 +76,7 @@ sentinel --inspect --typescript # adoption + conformity coverage
|
|
|
76
76
|
| `--json` | machine-readable envelope on stdout, for every verb; progress goes to stderr |
|
|
77
77
|
| `--max-diagnostics <n>` | cap the diagnostics embedded per module in `--run --json` (`0` = no cap) |
|
|
78
78
|
| `--dry-run` | preview what `--init` would write, changing nothing |
|
|
79
|
+
| `--rehearse` | with `--dry-run`: run the autofix passes in a copy and name the source they rewrite |
|
|
79
80
|
| `--runner <tool>` | override the default runner |
|
|
80
81
|
| `-- <tool options>` | `--run` only, one type named: everything after `--` goes to tsc itself (see below) |
|
|
81
82
|
|
|
@@ -1298,10 +1298,259 @@ $ sentinel --init --dry-run
|
|
|
1298
1298
|
+ }
|
|
1299
1299
|
+ }
|
|
1300
1300
|
+ }
|
|
1301
|
+
dry run: the lint autofix runs on adoption and can rewrite source, as moving to another linter does; add --rehearse to name the files (it runs the pass in a throwaway copy, as long as the real one)
|
|
1302
|
+
dry run: the format autofix runs on adoption and can rewrite source, as moving to another formatter does; add --rehearse to name the files (it runs the pass in a throwaway copy, as long as the real one)
|
|
1301
1303
|
dry run: would ignore static-analysis-report/ in the root .gitignore
|
|
1302
1304
|
# exit: 0
|
|
1303
1305
|
```
|
|
1304
1306
|
|
|
1307
|
+
**--init --dry-run --rehearse (runs the autofixes in a copy, names what they rewrite)**
|
|
1308
|
+
|
|
1309
|
+
```console
|
|
1310
|
+
$ sentinel --init --dry-run --rehearse
|
|
1311
|
+
dry run: no files written
|
|
1312
|
+
wrote .oxlintrc.json extending ./node_modules/@hublo/sentinel/lint/nest.json (@hublo/sentinel@<version>)
|
|
1313
|
+
20 rule(s) not enforced by this preset (17 jest, 1 no-barrel-files, 1 core, 1 vitest). Run --inspect --lint for each rule and why.
|
|
1314
|
+
run `pnpm install`, then `pnpm run lint`
|
|
1315
|
+
|
|
1316
|
+
create .oxlintrc.json
|
|
1317
|
+
+ {
|
|
1318
|
+
+ "extends": [
|
|
1319
|
+
+ "./node_modules/@hublo/sentinel/lint/nest.json"
|
|
1320
|
+
+ ],
|
|
1321
|
+
+ "ignorePatterns": [
|
|
1322
|
+
+ "**/dist/**",
|
|
1323
|
+
+ "**/node_modules/**",
|
|
1324
|
+
+ "coverage/**",
|
|
1325
|
+
+ "**/jest.*.{ts,js,cjs,mjs}",
|
|
1326
|
+
+ "**/eslint.config.{js,cjs,mjs}",
|
|
1327
|
+
+ "**/*.timestamp*",
|
|
1328
|
+
+ "**/*.d.ts"
|
|
1329
|
+
+ ]
|
|
1330
|
+
+ }
|
|
1331
|
+
|
|
1332
|
+
update package.json
|
|
1333
|
+
- { "name": "bff-admin", "dependencies": { "@nestjs/core": "^10" } }
|
|
1334
|
+
+ {
|
|
1335
|
+
+ "name": "bff-admin",
|
|
1336
|
+
+ "dependencies": {
|
|
1337
|
+
+ "@nestjs/core": "^10"
|
|
1338
|
+
+ },
|
|
1339
|
+
+ "scripts": {
|
|
1340
|
+
+ "lint": "sentinel --run --lint"
|
|
1341
|
+
+ },
|
|
1342
|
+
+ "devDependencies": {
|
|
1343
|
+
+ "@hublo/sentinel": "<version>"
|
|
1344
|
+
+ },
|
|
1345
|
+
+ "nx": {
|
|
1346
|
+
+ "targets": {
|
|
1347
|
+
+ "lint": {
|
|
1348
|
+
+ "cache": true,
|
|
1349
|
+
+ "inputs": [
|
|
1350
|
+
+ "default",
|
|
1351
|
+
+ "^default",
|
|
1352
|
+
+ "{projectRoot}/.oxlintrc.json"
|
|
1353
|
+
+ ]
|
|
1354
|
+
+ }
|
|
1355
|
+
+ }
|
|
1356
|
+
+ }
|
|
1357
|
+
+ }
|
|
1358
|
+
dry run: no files written
|
|
1359
|
+
run `pnpm install` to fetch @hublo/sentinel (added to the module devDependencies) so `extends` and the typecheck script resolve
|
|
1360
|
+
|
|
1361
|
+
update tsconfig.json
|
|
1362
|
+
- { "extends": "../../tsconfig.base.json", "include": ["src"] }
|
|
1363
|
+
+ { "extends": [
|
|
1364
|
+
+ "../../tsconfig.base.json",
|
|
1365
|
+
+ "@hublo/sentinel/typescript/nest"
|
|
1366
|
+
+ ], "include": ["src"] }
|
|
1367
|
+
|
|
1368
|
+
update package.json
|
|
1369
|
+
- { "name": "bff-admin", "dependencies": { "@nestjs/core": "^10" } }
|
|
1370
|
+
+ {
|
|
1371
|
+
+ "name": "bff-admin",
|
|
1372
|
+
+ "dependencies": {
|
|
1373
|
+
+ "@nestjs/core": "^10"
|
|
1374
|
+
+ },
|
|
1375
|
+
+ "scripts": {
|
|
1376
|
+
+ "typecheck": "sentinel --run --typescript"
|
|
1377
|
+
+ },
|
|
1378
|
+
+ "devDependencies": {
|
|
1379
|
+
+ "@hublo/sentinel": "<version>"
|
|
1380
|
+
+ },
|
|
1381
|
+
+ "nx": {
|
|
1382
|
+
+ "targets": {
|
|
1383
|
+
+ "typecheck": {
|
|
1384
|
+
+ "cache": true,
|
|
1385
|
+
+ "inputs": [
|
|
1386
|
+
+ "default",
|
|
1387
|
+
+ "^default",
|
|
1388
|
+
+ "{projectRoot}/tsconfig.json"
|
|
1389
|
+
+ ]
|
|
1390
|
+
+ }
|
|
1391
|
+
+ }
|
|
1392
|
+
+ }
|
|
1393
|
+
+ }
|
|
1394
|
+
build: no Vite config in this module, so there is nothing to point at sentinel. This role moves an existing config's imports; it does not introduce a bundler. If this module builds another way (38 services here build through `@nx/webpack`), the build role does not apply to it.
|
|
1395
|
+
test: this module has no test and no jest leftovers, so there is nothing to do here.
|
|
1396
|
+
dry run: no files written
|
|
1397
|
+
added the three `static-analysis` scripts and static-analysis.config.mjs; declare this module's entry points there, everything else comes from the role
|
|
1398
|
+
|
|
1399
|
+
create static-analysis.config.mjs
|
|
1400
|
+
+ import { defineStaticAnalysis } from '@hublo/sentinel/static-analysis'
|
|
1401
|
+
+
|
|
1402
|
+
+ export default defineStaticAnalysis({
|
|
1403
|
+
+ root: import.meta.dirname,
|
|
1404
|
+
+
|
|
1405
|
+
+ // Files that RUN although nothing imports them: they are where reachability starts. Added to
|
|
1406
|
+
+ // the patterns sentinel ships, never replacing them.
|
|
1407
|
+
+ entry: [],
|
|
1408
|
+
+
|
|
1409
|
+
+ // ---------------------------------------------------------------------------------------------
|
|
1410
|
+
+ // What this module can move, with the role's value today. A setting you leave out keeps that
|
|
1411
|
+
+ // value; a setting you write REPLACES it, including inside a list. Anything you set is printed
|
|
1412
|
+
+ // under "measured with" in the report and marked `(module)`, because two modules measured with
|
|
1413
|
+
+ // different settings can no longer be compared.
|
|
1414
|
+
+ //
|
|
1415
|
+
+ // ignore 19 patterns
|
|
1416
|
+
+ // languages javascript, typescript, jsx, tsx, css, scss
|
|
1417
|
+
+ // duplication.minTokens 50
|
|
1418
|
+
+ // duplication.minLines 5
|
|
1419
|
+
+ // duplication.tests 3 patterns
|
|
1420
|
+
+ // deadCode.minConfidence 60
|
|
1421
|
+
+ // deadCode.categories unused-file, unused-export, unused-symbol, unused-import
|
|
1422
|
+
+ // graph.resolution 1
|
|
1423
|
+
+ //
|
|
1424
|
+
+ // A list shown as a count is not copyable, and replacing one means writing back what you keep,
|
|
1425
|
+
+ // so read the values in full with:
|
|
1426
|
+
+ //
|
|
1427
|
+
+ // pnpm exec sentinel --inspect --static-analysis --json
|
|
1428
|
+
+ //
|
|
1429
|
+
+ // duplication.kinds: only `exact` is supported: any other kind overlaps it and double-counts
|
|
1430
|
+
+ // tools: sentinel's own passthrough to the tool underneath; a module never names the vendor
|
|
1431
|
+
+ //
|
|
1432
|
+
+ // overrides: {
|
|
1433
|
+
+ // duplication: { minTokens: 100 },
|
|
1434
|
+
+ // },
|
|
1435
|
+
+ })
|
|
1436
|
+
|
|
1437
|
+
update package.json
|
|
1438
|
+
- { "name": "bff-admin", "dependencies": { "@nestjs/core": "^10" } }
|
|
1439
|
+
+ {
|
|
1440
|
+
+ "name": "bff-admin",
|
|
1441
|
+
+ "dependencies": {
|
|
1442
|
+
+ "@nestjs/core": "^10"
|
|
1443
|
+
+ },
|
|
1444
|
+
+ "scripts": {
|
|
1445
|
+
+ "static-analysis": "sentinel --run --static-analysis",
|
|
1446
|
+
+ "static-analysis:json": "sentinel --run --static-analysis --output json",
|
|
1447
|
+
+ "static-analysis:html": "sentinel --run --static-analysis --output html"
|
|
1448
|
+
+ },
|
|
1449
|
+
+ "devDependencies": {
|
|
1450
|
+
+ "@hublo/sentinel": "<version>"
|
|
1451
|
+
+ },
|
|
1452
|
+
+ "nx": {
|
|
1453
|
+
+ "targets": {
|
|
1454
|
+
+ "static-analysis": {
|
|
1455
|
+
+ "cache": true,
|
|
1456
|
+
+ "inputs": [
|
|
1457
|
+
+ "default",
|
|
1458
|
+
+ "{projectRoot}/static-analysis.config.mjs"
|
|
1459
|
+
+ ]
|
|
1460
|
+
+ }
|
|
1461
|
+
+ }
|
|
1462
|
+
+ }
|
|
1463
|
+
+ }
|
|
1464
|
+
dry run: no files written
|
|
1465
|
+
materialized the nest format preset into .oxfmtrc.json (@hublo/sentinel@<version>). oxfmt has no `extends`, so the values live here; `sentinel --status --format` reports modules left behind by a preset change
|
|
1466
|
+
|
|
1467
|
+
create .oxfmtrc.json
|
|
1468
|
+
+ {
|
|
1469
|
+
+ "$sentinel": {
|
|
1470
|
+
+ "preset": "nest",
|
|
1471
|
+
+ "version": "<version>",
|
|
1472
|
+
+ "local": []
|
|
1473
|
+
+ },
|
|
1474
|
+
+ "printWidth": 80,
|
|
1475
|
+
+ "semi": false,
|
|
1476
|
+
+ "singleQuote": true,
|
|
1477
|
+
+ "trailingComma": "all",
|
|
1478
|
+
+ "sortPackageJson": false,
|
|
1479
|
+
+ "sortImports": {
|
|
1480
|
+
+ "customGroups": [
|
|
1481
|
+
+ {
|
|
1482
|
+
+ "groupName": "workspace",
|
|
1483
|
+
+ "elementNamePattern": [
|
|
1484
|
+
+ "@hublo/**",
|
|
1485
|
+
+ "@shared/**",
|
|
1486
|
+
+ "@front/**",
|
|
1487
|
+
+ "@network/**"
|
|
1488
|
+
+ ]
|
|
1489
|
+
+ }
|
|
1490
|
+
+ ],
|
|
1491
|
+
+ "groups": [
|
|
1492
|
+
+ "builtin",
|
|
1493
|
+
+ "external",
|
|
1494
|
+
+ "workspace",
|
|
1495
|
+
+ [
|
|
1496
|
+
+ "parent",
|
|
1497
|
+
+ "index"
|
|
1498
|
+
+ ],
|
|
1499
|
+
+ "sibling"
|
|
1500
|
+
+ ],
|
|
1501
|
+
+ "newlinesBetween": true,
|
|
1502
|
+
+ "order": "asc",
|
|
1503
|
+
+ "ignoreCase": false
|
|
1504
|
+
+ },
|
|
1505
|
+
+ "ignorePatterns": [
|
|
1506
|
+
+ "**/*.toml"
|
|
1507
|
+
+ ]
|
|
1508
|
+
+ }
|
|
1509
|
+
|
|
1510
|
+
update package.json
|
|
1511
|
+
- { "name": "bff-admin", "dependencies": { "@nestjs/core": "^10" } }
|
|
1512
|
+
+ {
|
|
1513
|
+
+ "name": "bff-admin",
|
|
1514
|
+
+ "dependencies": {
|
|
1515
|
+
+ "@nestjs/core": "^10"
|
|
1516
|
+
+ },
|
|
1517
|
+
+ "scripts": {
|
|
1518
|
+
+ "format": "sentinel --run --format",
|
|
1519
|
+
+ "format:fix": "sentinel --run --format --fix"
|
|
1520
|
+
+ },
|
|
1521
|
+
+ "devDependencies": {
|
|
1522
|
+
+ "@hublo/sentinel": "<version>"
|
|
1523
|
+
+ },
|
|
1524
|
+
+ "nx": {
|
|
1525
|
+
+ "targets": {
|
|
1526
|
+
+ "format": {
|
|
1527
|
+
+ "cache": true,
|
|
1528
|
+
+ "inputs": [
|
|
1529
|
+
+ "default",
|
|
1530
|
+
+ "{projectRoot}/.oxfmtrc.json"
|
|
1531
|
+
+ ]
|
|
1532
|
+
+ },
|
|
1533
|
+
+ "format:fix": {
|
|
1534
|
+
+ "cache": false
|
|
1535
|
+
+ }
|
|
1536
|
+
+ }
|
|
1537
|
+
+ }
|
|
1538
|
+
+ }
|
|
1539
|
+
dry run: the lint autofix cannot be rehearsed yet: its preset is not installed in this module, which is expected before the first `pnpm install`. After it, this dry-run names what it rewrites
|
|
1540
|
+
dry run: the format autofix would rewrite 1 file beyond the ones above: project.json
|
|
1541
|
+
dry run: would ignore static-analysis-report/ in the root .gitignore
|
|
1542
|
+
# exit: 0
|
|
1543
|
+
```
|
|
1544
|
+
|
|
1545
|
+
**--rehearse without --dry-run**
|
|
1546
|
+
|
|
1547
|
+
```console
|
|
1548
|
+
$ sentinel --init --rehearse
|
|
1549
|
+
--rehearse only applies to --init --dry-run: the real --init runs the autofix passes themselves.
|
|
1550
|
+
(run "sentinel --help" for usage)
|
|
1551
|
+
# exit: 1
|
|
1552
|
+
```
|
|
1553
|
+
|
|
1305
1554
|
**no module and no workspace root**
|
|
1306
1555
|
|
|
1307
1556
|
```console
|
package/docs/using-sentinel.md
CHANGED
|
@@ -34,7 +34,15 @@ everywhere (`--run --json` and `--inspect` answer them), and `--migrate` is refu
|
|
|
34
34
|
| `--static-analysis` | the config stub + three scripts + nx metadata, and ignores the report directory at the repository root | measures and reports; never exits non-zero on a finding | runner, config file, resolved settings, what this module moved |
|
|
35
35
|
|
|
36
36
|
`--json` works for every verb, and stdout carries **only** the envelope, so `| jq` always
|
|
37
|
-
parses. `--dry-run` applies to `--init` and writes nothing.
|
|
37
|
+
parses. `--dry-run` applies to `--init` and writes nothing. Besides the config diffs, it says
|
|
38
|
+
that the lint autofix and the formatter run on adoption and can rewrite source, which is what
|
|
39
|
+
moving to another linter or formatter does. It does not run them: on a large module that costs
|
|
40
|
+
what the real pass costs (260 s measured on `network`).
|
|
41
|
+
|
|
42
|
+
`--init --dry-run --rehearse` runs them, and names the source files they would rewrite
|
|
43
|
+
(`rewrites` in `--json`, absent without `--rehearse`): both run in a throwaway copy of the
|
|
44
|
+
module, outside the workspace, and what changed is read back. `--rehearse` without `--dry-run`
|
|
45
|
+
is refused, because a real `--init` runs the passes itself.
|
|
38
46
|
|
|
39
47
|
`--output <shape>` is different, and only a role that WRITES a report reads it: `console` is the
|
|
40
48
|
default and writes no file, `html` is the page you open and send, `json` is for CI and for a
|
|
@@ -217,5 +225,9 @@ visible. Closing it in one action is [issue #17](https://github.com/hublo/sentin
|
|
|
217
225
|
today it is one command per module.
|
|
218
226
|
|
|
219
227
|
**Upgrading:** bump the pin, `pnpm install`, then `sentinel --init` in the module and commit
|
|
220
|
-
what changes.
|
|
221
|
-
|
|
228
|
+
what changes. On a module that has already adopted a role, a bare `--init` re-inits only the roles
|
|
229
|
+
it has adopted, the ones `sentinel --inspect` reports, and names them in one line. It does not
|
|
230
|
+
adopt a new role on the way: adopting the test role, for instance, is a migration of its own, so
|
|
231
|
+
it is asked for by name (`sentinel --init --test`), or every role at once with `--init --all`.
|
|
232
|
+
`--init` is idempotent, so on a version that changed nothing relevant it reports nothing to
|
|
233
|
+
change.
|
|
@@ -135,6 +135,136 @@ majority of the migration, and only a sweep over every real module found it.
|
|
|
135
135
|
|
|
136
136
|
---
|
|
137
137
|
|
|
138
|
+
## The `--init` matrix for 1.5.1, 29 October 2026
|
|
139
|
+
|
|
140
|
+
Six fixes, found by re-initializing the monorepo's 63 adopted modules with 1.5.1-alpha.0. Every
|
|
141
|
+
cell below is a test in this repo, against the built CLI unless it says unit, and every fix has a
|
|
142
|
+
negative control: the fix reverted in the source, the cells run again, and the cells that must
|
|
143
|
+
fail do. No existing cell changed: against the alpha, the test tree gains lines and loses none
|
|
144
|
+
(`git diff --numstat` on `tests/`: 0 deletions), in 10 new files and 3 existing ones.
|
|
145
|
+
|
|
146
|
+
### 1. a bare `--init` re-inits the roles a module has (`init-bare-on-adopted`)
|
|
147
|
+
|
|
148
|
+
| module state | from the module | from the root, `--module` |
|
|
149
|
+
| ---------------------------- | -------------------------------- | ------------------------- |
|
|
150
|
+
| nothing adopted | every role | every role |
|
|
151
|
+
| typescript adopted | typescript only | typescript only |
|
|
152
|
+
| lint adopted | lint and format | (pairing, same rule) |
|
|
153
|
+
| typescript adopted, `--all` | every role | |
|
|
154
|
+
| typescript adopted, real run | no lint or format config written | |
|
|
155
|
+
|
|
156
|
+
### 2. the lint baseline runs the lint script's leading commands (`init-lint-baseline-runs-script-prefix`)
|
|
157
|
+
|
|
158
|
+
| lint script | baseline |
|
|
159
|
+
| --------------------------------------- | ------------------------------------------ |
|
|
160
|
+
| `eslint .` (control) | holds `typescript/tsconfig-error` |
|
|
161
|
+
| `node generate.mjs && eslint .` | nothing held, the command is named |
|
|
162
|
+
| `node -e "process.exit(3)" && eslint .` | nothing written, says the command exited 3 |
|
|
163
|
+
|
|
164
|
+
Unit (`leading-commands`): `&&` chains only; `;`, `||` and a script that never reaches sentinel
|
|
165
|
+
return nothing.
|
|
166
|
+
|
|
167
|
+
### 3. the `$sentinel` stamp follows the content (unit, `format-adoption`)
|
|
168
|
+
|
|
169
|
+
| re-init | stamp |
|
|
170
|
+
| --------------------------------- | ------------------------ |
|
|
171
|
+
| same content | kept (no diff on a bump) |
|
|
172
|
+
| preset changed (`base` to `nest`) | this release |
|
|
173
|
+
| a carried ignore pattern added | this release |
|
|
174
|
+
|
|
175
|
+
### 4. two re-init messages (unit `build-init`, e2e `init-tools-defers-every-role`)
|
|
176
|
+
|
|
177
|
+
| case | said |
|
|
178
|
+
| --------------------------------------------------- | ---------------------------------------------------- |
|
|
179
|
+
| vite config rewritten, format adopted / not adopted | the format line, naming the rewritten files |
|
|
180
|
+
| vite config already adopted | no format line |
|
|
181
|
+
| lint config declares `tools`, `--init --typescript` | deferred, naming the lint config, tsconfig untouched |
|
|
182
|
+
| same module, bare `--init` | every role deferred |
|
|
183
|
+
| same module, `--preset node` | not deferred, the explicit preset decides |
|
|
184
|
+
|
|
185
|
+
### 5. a deliberate import order survives the sort (`init-format-import-order-matrix`, `init-format-keeps-deliberate-import-order`)
|
|
186
|
+
|
|
187
|
+
Each file is one cell, run on a first adoption AND on a re-init, nest preset:
|
|
188
|
+
|
|
189
|
+
| signal in the file | order kept | pins |
|
|
190
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -------------------------------------------------------- |
|
|
191
|
+
| `eslint-disable-next-line import/order` | yes | 1 |
|
|
192
|
+
| `eslint-disable import/order` region, then `eslint-enable` | inside yes, after it sorted | 2 |
|
|
193
|
+
| file-wide `/* eslint-disable import/order */` | yes | 3 |
|
|
194
|
+
| `eslint-disable-line import/first` | yes | 1 |
|
|
195
|
+
| `eslint-disable-next-line simple-import-sort/imports` | yes | 1 |
|
|
196
|
+
| first import: `dd-trace` (value) | yes | 1 |
|
|
197
|
+
| first import: `@opentelemetry/sdk-node` (value) | yes | 1 |
|
|
198
|
+
| first import, side effect: `dd-trace/init`, `reflect-metadata`, `source-map-support/register`, `core-js/stable`, `regenerator-runtime/runtime` | yes | 0: oxfmt 0.63.0 does not move a first side-effect import |
|
|
199
|
+
| side-effect `reflect-metadata` with an import the sort crosses | yes | 1 |
|
|
200
|
+
| two signals in one file, normal imports between them | pinned yes, the rest sorted | 2 |
|
|
201
|
+
| a `.tsx` file | yes | 1 |
|
|
202
|
+
| the tracer file of monorepo#23314 | yes | 3 |
|
|
203
|
+
| no signal at all (two files) | byte-identical to oxfmt alone | 0 |
|
|
204
|
+
|
|
205
|
+
And: the report names exactly the pinned files; `format:fix` then the format check keep every
|
|
206
|
+
order; a second re-init changes no file and adds no pin; a module that does not sort (react
|
|
207
|
+
preset) gets no pin. Unit (`deliberate-import-order`): a type-only import is never pinned, an
|
|
208
|
+
`eslint-disable` naming no order rule is ignored.
|
|
209
|
+
|
|
210
|
+
Negative control, `keepDeliberateImportOrder` disabled: 14 cells fail, the ten pinned files in
|
|
211
|
+
both modes, the report, `format:fix` and the tracer cells. The five first-position side-effect
|
|
212
|
+
cells pass without the fix, which is what the measurement above says.
|
|
213
|
+
|
|
214
|
+
### 6. a re-init keeps what a team added (`init-keeps-team-config-matrix`, `init-keeps-team-lint-config`)
|
|
215
|
+
|
|
216
|
+
| block in the module's config | after re-init |
|
|
217
|
+
| -------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
218
|
+
| a rule outside the preset | kept |
|
|
219
|
+
| a rule the preset configures | dropped, named |
|
|
220
|
+
| `jsPlugins`: a local path and a package entry | kept |
|
|
221
|
+
| `jsPlugins` under a name the preset registers | dropped, named |
|
|
222
|
+
| `overrides` on files no preset override names | kept |
|
|
223
|
+
| `overrides` on the preset override's own files | kept as the module block, governed rules dropped and named |
|
|
224
|
+
| `plugins` | kept, minus the preset's own |
|
|
225
|
+
| `settings` | kept |
|
|
226
|
+
| a second `extends` entry | kept, in order, after sentinel's |
|
|
227
|
+
| `.oxfmtrc.json` `sortImports` false / true / a groups object (nest) | kept, declared in `local`, stamp moves |
|
|
228
|
+
| `.oxfmtrc.json` module `ignorePatterns` | kept, stamp unchanged |
|
|
229
|
+
| every block at once | all kept; a second re-init is a no-op |
|
|
230
|
+
| every block at once, bare `--init` | re-inits lint and format only, all kept |
|
|
231
|
+
| the same from the workspace root, `--module` | the same |
|
|
232
|
+
| fixes 5 and 6 together: tracer imports, a team `sortImports`, a second `extends` | all three kept |
|
|
233
|
+
|
|
234
|
+
Unit (`lint-module-own-config`, `format-adoption`, `extends-preservation`): two module blocks
|
|
235
|
+
with the same `files` merge; a block left with only `files` is not written; a `sortImports`
|
|
236
|
+
equal to what the RECORDED preset sets is sentinel's, so the async-api-types libs corrected from
|
|
237
|
+
base to nest take nest's sort.
|
|
238
|
+
|
|
239
|
+
Negative controls, one per block, each reverting one read-back: plugins 5 cells fail, jsPlugins
|
|
240
|
+
6, settings 5, overrides 6, extends 5, rules 6, sortImports 7, format `ignorePatterns` 1. Each
|
|
241
|
+
fails its own cells plus the every-block cells, and nothing else.
|
|
242
|
+
|
|
243
|
+
### against the consumer, the 63 adopted modules
|
|
244
|
+
|
|
245
|
+
The packed branch, through a pnpm `file:` override, on a fresh worktree of the monorepo at
|
|
246
|
+
7177eb86038, nx graph built first, a BARE `--init` in each module:
|
|
247
|
+
|
|
248
|
+
| | result |
|
|
249
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
250
|
+
| `--init` exit 0 | 63 of 63 |
|
|
251
|
+
| roles re-inited = roles the alpha was given by name | 63 of 63 |
|
|
252
|
+
| files differing from the alpha's explicit-roles run | 8, all `.oxfmtrc.json` stamps (fix 3) |
|
|
253
|
+
| files differing from the fixes 1 to 4 run | 0 |
|
|
254
|
+
| the ten planning modules' second `extends` | byte for byte |
|
|
255
|
+
| lint / format / typecheck / build | 62 / 59 / 63 / 31, all green |
|
|
256
|
+
| test | 43 projects, 7 red under `--parallel=3`: the 4 environment reds known on origin/main (the `/private` checkout path in 3 jest suites, no Redis for bff-planning), host-admin's French-locale date, and two load reds that are green run alone (bff-worker, a jest worker SIGSEGV; regulation-balance, 15 s timeouts) |
|
|
257
|
+
|
|
258
|
+
Run with `--skip-nx-cache` from the workspace root after every `prisma:gen:*`, career's
|
|
259
|
+
`prepare-runtime-artifacts`, `svelte-kit sync` and `VITE_BRAND_ID=hublo`. Run alone, host-admin
|
|
260
|
+
fails only the locale test (1 806 of 1 807 files).
|
|
261
|
+
|
|
262
|
+
The two PR heads the fixes came from, each in a throwaway worktree, nothing pushed:
|
|
263
|
+
`libs/nest/tracer` at monorepo#23314's head keeps its import order byte for byte, and with the
|
|
264
|
+
pre-adoption `index.ts` restored, the bare `--init` pins it (3 pins) and the format check passes.
|
|
265
|
+
host-admin at monorepo#23342's head now adopts lint with its other roles, which is the rule:
|
|
266
|
+
ESLint is removed, and the guards the react preset does not carry are the team's to add.
|
|
267
|
+
|
|
138
268
|
## The static analysis role's validation matrix, 19 October 2026
|
|
139
269
|
|
|
140
270
|
Re-run from scratch because **53 commits landed on the role since 13 October**, and three of them
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hublo/sentinel",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.1-alpha.1",
|
|
4
4
|
"description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=24.15.0"
|
|
14
14
|
},
|
|
15
|
+
"packageManager": "pnpm@10.26.0",
|
|
15
16
|
"bin": {
|
|
16
17
|
"sentinel": "./dist/bin/sentinel.js"
|
|
17
18
|
},
|
|
@@ -107,6 +108,28 @@
|
|
|
107
108
|
"oxlint",
|
|
108
109
|
"plugins"
|
|
109
110
|
],
|
|
111
|
+
"scripts": {
|
|
112
|
+
"build": "tsup && tsx scripts/build-presets.ts",
|
|
113
|
+
"dev": "tsup --watch",
|
|
114
|
+
"typecheck": "tsc --noEmit",
|
|
115
|
+
"lint": "node dist/bin/sentinel.js --run --lint && node dist/bin/sentinel.js --run --format",
|
|
116
|
+
"lint:fix": "node dist/bin/sentinel.js --run --lint --fix && node dist/bin/sentinel.js --run --format --fix",
|
|
117
|
+
"format": "node dist/bin/sentinel.js --run --format",
|
|
118
|
+
"test": "vitest run",
|
|
119
|
+
"test:watch": "vitest",
|
|
120
|
+
"test:e2e": "vitest run tests/e2e",
|
|
121
|
+
"test:coverage": "vitest run --coverage --testTimeout=60000",
|
|
122
|
+
"cli": "node --import tsx bin/sentinel.ts",
|
|
123
|
+
"docs:traces": "pnpm build && bash scripts/gen-traces.sh",
|
|
124
|
+
"docs:traces:check": "pnpm build && bash scripts/gen-traces.sh --check",
|
|
125
|
+
"format:fix": "node dist/bin/sentinel.js --run --format --fix",
|
|
126
|
+
"bench": "node scripts/bench.mjs",
|
|
127
|
+
"deps:trust": "tsx scripts/check-trust-policy.ts",
|
|
128
|
+
"prepack": "tsx scripts/prune-sourcemaps.ts",
|
|
129
|
+
"static-analysis": "sentinel --run --static-analysis",
|
|
130
|
+
"static-analysis:json": "sentinel --run --static-analysis --output json",
|
|
131
|
+
"static-analysis:html": "sentinel --run --static-analysis --output html"
|
|
132
|
+
},
|
|
110
133
|
"dependencies": {
|
|
111
134
|
"@nx/eslint-plugin": "23.0.1",
|
|
112
135
|
"@tailwindcss/vite": "4.1.18",
|
|
@@ -155,6 +178,11 @@
|
|
|
155
178
|
"tsup": "^8.3.0",
|
|
156
179
|
"tsx": "^4.19.0"
|
|
157
180
|
},
|
|
181
|
+
"pnpm": {
|
|
182
|
+
"onlyBuiltDependencies": [
|
|
183
|
+
"esbuild"
|
|
184
|
+
]
|
|
185
|
+
},
|
|
158
186
|
"publishConfig": {
|
|
159
187
|
"registry": "https://registry.npmjs.org",
|
|
160
188
|
"access": "public"
|
|
@@ -191,26 +219,5 @@
|
|
|
191
219
|
"nx": {
|
|
192
220
|
"optional": true
|
|
193
221
|
}
|
|
194
|
-
},
|
|
195
|
-
"scripts": {
|
|
196
|
-
"build": "tsup && tsx scripts/build-presets.ts",
|
|
197
|
-
"dev": "tsup --watch",
|
|
198
|
-
"typecheck": "tsc --noEmit",
|
|
199
|
-
"lint": "node dist/bin/sentinel.js --run --lint && node dist/bin/sentinel.js --run --format",
|
|
200
|
-
"lint:fix": "node dist/bin/sentinel.js --run --lint --fix && node dist/bin/sentinel.js --run --format --fix",
|
|
201
|
-
"format": "node dist/bin/sentinel.js --run --format",
|
|
202
|
-
"test": "vitest run",
|
|
203
|
-
"test:watch": "vitest",
|
|
204
|
-
"test:e2e": "vitest run tests/e2e",
|
|
205
|
-
"test:coverage": "vitest run --coverage --testTimeout=60000",
|
|
206
|
-
"cli": "node --import tsx bin/sentinel.ts",
|
|
207
|
-
"docs:traces": "pnpm build && bash scripts/gen-traces.sh",
|
|
208
|
-
"docs:traces:check": "pnpm build && bash scripts/gen-traces.sh --check",
|
|
209
|
-
"format:fix": "node dist/bin/sentinel.js --run --format --fix",
|
|
210
|
-
"bench": "node scripts/bench.mjs",
|
|
211
|
-
"deps:trust": "tsx scripts/check-trust-policy.ts",
|
|
212
|
-
"static-analysis": "sentinel --run --static-analysis",
|
|
213
|
-
"static-analysis:json": "sentinel --run --static-analysis --output json",
|
|
214
|
-
"static-analysis:html": "sentinel --run --static-analysis --output html"
|
|
215
222
|
}
|
|
216
|
-
}
|
|
223
|
+
}
|
|
File without changes
|