@hublo/sentinel 0.1.0-alpha.1 → 0.1.0-alpha.10
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 +131 -23
- package/dist/bin/sentinel.js +331 -80
- package/dist/bin/sentinel.js.map +1 -1
- package/dist/{chunk-TKMMUPGS.js → chunk-6N2CLPWE.js} +247 -85
- package/dist/chunk-6N2CLPWE.js.map +1 -0
- package/dist/index.d.ts +34 -11
- package/dist/index.js +1 -1
- package/dist/tsconfig/nest.json +2 -9
- package/dist/tsconfig/node.json +2 -9
- package/dist/tsconfig/react.json +0 -3
- package/package.json +4 -5
- package/dist/chunk-TKMMUPGS.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
`sentinel` is a standalone, semver-versioned package (published to a registry, consumed by a repo as a normal dependency) that unifies a repo's tooling, config, and quality checks into one place, so projects stop copying config files everywhere and stop carrying a pile of duplicated tooling dependencies.
|
|
6
6
|
|
|
7
|
-
> Status: **
|
|
7
|
+
> Status: **foundation + the TypeScript tool are shipped** (`@hublo/sentinel@0.1.0-alpha.x` on npm). The rest of this README is the design reference for the tools still to come, added one at a time on top of this foundation.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -43,6 +43,8 @@ A large monorepo accumulates:
|
|
|
43
43
|
|
|
44
44
|
sentinel writes **standard config files** into a project (each just `extends` a sentinel preset) and runs the checks. Your editor and the tools read those **normal files natively**, they never call sentinel at runtime, so nothing is coupled to it or brittle.
|
|
45
45
|
|
|
46
|
+
> **Shipped today:** only the **TypeScript** tool, so `--update` writes the `tsconfig` stub, and `--run`/`--report`/`--status` work for `--typescript`. The `eslint.config.js` / `--lint` / `--test` snippets below illustrate the end state; those subpaths (`@hublo/sentinel/lint/*`, …) land with their tool ticket.
|
|
47
|
+
|
|
46
48
|
**Step 1 — put a module on sentinel** (once per module, by a dev; the files are committed):
|
|
47
49
|
|
|
48
50
|
```bash
|
|
@@ -59,14 +61,17 @@ export default react
|
|
|
59
61
|
```
|
|
60
62
|
|
|
61
63
|
```jsonc
|
|
62
|
-
// tsconfig.json —
|
|
64
|
+
// tsconfig.json — COMPOSES the repo base with the sentinel preset (see "Composition
|
|
65
|
+
// & precedence"); only project-specific paths/include stay local
|
|
63
66
|
{
|
|
64
|
-
"extends": "@hublo/sentinel/
|
|
67
|
+
"extends": ["../../tsconfig.base.json", "@hublo/sentinel/tsconfig/react"],
|
|
65
68
|
"compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./src/*"] } },
|
|
66
69
|
"include": ["src"]
|
|
67
70
|
}
|
|
68
71
|
```
|
|
69
72
|
|
|
73
|
+
> A module can extend the preset alone (`"extends": "@hublo/sentinel/tsconfig/react"`) when there is no repo base. Compose with the base when the repo owns structure (a monorepo `paths` map) the preset should not, see "Composition & precedence".
|
|
74
|
+
|
|
70
75
|
And the app's `package.json` scripts route every check through the one CLI (run from the app dir, sentinel scopes to it):
|
|
71
76
|
|
|
72
77
|
```jsonc
|
|
@@ -96,6 +101,23 @@ And the app's `package.json` scripts route every check through the one CLI (run
|
|
|
96
101
|
|
|
97
102
|
The per-tool knowledge (eslint → `eslint.config.js`, tsc → `tsconfig`, …) lives **inside sentinel as an adapter**, swappable centrally, but never a runtime dependency of the project.
|
|
98
103
|
|
|
104
|
+
## Requirements & installing
|
|
105
|
+
|
|
106
|
+
**Node.** sentinel needs **Node >= 20.12** (its coloured output uses `util.styleText`, added in 20.12). It fails fast with a clear message on an older runtime rather than crashing. If a project runs on an older Node (e.g. a legacy app on Node 10), run sentinel with a modern Node via `fnm`/`nvm`; you do not need to change the project's own Node.
|
|
107
|
+
|
|
108
|
+
**Try it without installing.** A one-off run needs no auth and touches nothing:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pnpm dlx @hublo/sentinel@<exact-version> --inspect --typescript --module <name>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Installing a pre-release (`minimumReleaseAge`).** The monorepo enforces a 3-day `minimumReleaseAge` supply-chain gate (a freshly published version cannot be installed until it has aged 3 days). A brand-new `alpha` therefore cannot be added yet, so while testing pre-releases you either exclude the package (`pnpm-workspace.yaml` → `minimumReleaseAgeExclude`) or install with `--config.minimumReleaseAge=0`. This is a deliberate protection, not a bug: **always pin the exact version** (`@hublo/sentinel@0.1.0-alpha.9`) rather than `@latest`, so a run is reproducible and the gate stays meaningful.
|
|
115
|
+
|
|
116
|
+
## Docs & cheat sheets
|
|
117
|
+
|
|
118
|
+
- [`docs/typescript-adoption.md`](docs/typescript-adoption.md) — the adoption cheat sheet: the two adoption steps, the command model (verb x type x location), options, reading a report, and troubleshooting.
|
|
119
|
+
- [`docs/typescript-traces.md`](docs/typescript-traces.md) — a **generated, versioned** reference of live command + output traces (every verb, option, config result and edge case) against the mock monorepo. Regenerate after CLI changes with `pnpm docs:traces`.
|
|
120
|
+
|
|
99
121
|
## Architecture: `target → runner → flavour`
|
|
100
122
|
|
|
101
123
|
Every check is described by three layers:
|
|
@@ -104,12 +126,12 @@ Every check is described by three layers:
|
|
|
104
126
|
| -------------------- | --------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
105
127
|
| **target** (role) | `--lint`, `--typescript`, … | the _kind_ of check, stable | `lint` `format` `typescript` `build` `test` `static-analysis` `runtime-analysis` |
|
|
106
128
|
| **runner** (adapter) | `--runner=<tool>` | the _tool_ behind the target, swappable | lint: `eslint`/`biome`/`oxlint` · types: `tsc`/`tsgo` · build: `vite` · test: `vitest` |
|
|
107
|
-
| **flavour** (preset) |
|
|
129
|
+
| **flavour** (preset) | detected / `--flavour` | the _variant_ per stack (strict by default) | `react` `nest` `node` (svelte declared, preset deferred) |
|
|
108
130
|
|
|
109
131
|
A run is `target × runner × flavour`, e.g. `sentinel --run --lint --runner=eslint` from the `host-admin` dir.
|
|
110
132
|
|
|
111
133
|
- `--runner` is an **optional override on a central default**. `sentinel --lint` uses the configured default runner, so swapping a tool globally is a one-place change; `--runner=biome` overrides for a single run (great for benchmarking eslint vs biome vs oxlint, and for gradual migration).
|
|
112
|
-
- The **flavour is
|
|
134
|
+
- The **flavour is detected** from the module's dependencies (deterministic: a framework dep → its flavour, else `node`), and **`--flavour` overrides it**. Detection can be wrong where deps are hoisted at the repo root (it returns `node`), so pass `--flavour` for `--update` (that is where the preset is chosen). `--run`/`--report` don't depend on it, they run the tool on the committed config.
|
|
113
135
|
|
|
114
136
|
### Adapters & the engine (ports & adapters)
|
|
115
137
|
|
|
@@ -174,30 +196,115 @@ The rules live in `sentinel`. Each module keeps a **thin, generated stub** per t
|
|
|
174
196
|
- **Per-module install** — `@hublo/sentinel` is added per module, so adoption is **gradual** (migrate lot by lot; a module can adopt sentinel while its neighbour still uses the old config). Root configs are removed only once the **last** module has migrated.
|
|
175
197
|
- **Runner binaries** (`eslint`, `typescript`, `vite`, `vitest`, …) are **peer dependencies** so they install once and stay resolvable by the editor + nx, while sentinel still dictates their versions.
|
|
176
198
|
|
|
199
|
+
## Composition & precedence
|
|
200
|
+
|
|
201
|
+
A repo often has a base config that is **structural**, not just tooling, e.g. a
|
|
202
|
+
monorepo `tsconfig.base.json` carrying the workspace `paths` map. sentinel does **not**
|
|
203
|
+
replace it; it **composes** with it, so the structure survives and sentinel owns the
|
|
204
|
+
standards:
|
|
205
|
+
|
|
206
|
+
```jsonc
|
|
207
|
+
"extends": ["../../tsconfig.base.json", "@hublo/sentinel/tsconfig/react"]
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Precedence — sentinel wins.** With array `extends` (TS 5.0), entries merge in order
|
|
211
|
+
and the **last one wins**; the module's own `compilerOptions` win over both. So it is
|
|
212
|
+
always **base → sentinel → module-local**. sentinel is listed **last on purpose**:
|
|
213
|
+
it is a **reference** (a source of truth), not a suggestion the base can silently
|
|
214
|
+
veto. For any option sentinel declares, sentinel's value is the effective one.
|
|
215
|
+
|
|
216
|
+
For a module composing the base, the resolved config (`tsc --showConfig`) looks like:
|
|
217
|
+
|
|
218
|
+
| Option | Effective value | Owner |
|
|
219
|
+
| ----------------------------- | -------------------- | ------------------------------------------ |
|
|
220
|
+
| `strict`, `jsx`, decorators | preset's | **sentinel** (quality + framework) |
|
|
221
|
+
| `module` / `moduleResolution` | `esnext` / `bundler` | **sentinel** |
|
|
222
|
+
| `target` / `lib` / `paths` | the repo's | **base** (environment; sentinel won't set) |
|
|
223
|
+
| `noImplicitAny` | **off** (phase 1) | **base**, the preset defers it (see below) |
|
|
224
|
+
|
|
225
|
+
- **sentinel owns quality + framework semantics** (`strict`, `jsx`, decorators, module
|
|
226
|
+
system). The base can never quietly undo those, which is what makes sentinel a
|
|
227
|
+
reliable reference.
|
|
228
|
+
- **the base owns the environment** (`paths`, `target`, `lib`, project references).
|
|
229
|
+
sentinel does **not** set these, `extends` REPLACES arrays rather than merging, so
|
|
230
|
+
overriding `lib`/`target` would drop the repo's DOM libs or flip class-field emit.
|
|
231
|
+
|
|
232
|
+
**Least astonishment / phased strictness.** So a reference doesn't turn every rule on
|
|
233
|
+
the instant a module adopts it, sentinel **phases** the rules that would surface new
|
|
234
|
+
errors (`noImplicitAny`, `noUnusedLocals`, `noUnusedParameters`). They are kept
|
|
235
|
+
**commented** in the preset (visible, never silently dropped) and announced by a
|
|
236
|
+
runtime **warning**. `strict` stays on (so a standalone consumer still gets
|
|
237
|
+
`noImplicitAny`), but a repo base that relaxes `noImplicitAny` keeps it off until that
|
|
238
|
+
relaxation is removed. First adoption is therefore a **non-breaking lateral move**;
|
|
239
|
+
the rules are enabled **centrally** in a later wave.
|
|
240
|
+
|
|
241
|
+
**Removing the base is two decoupled moves, and neither loses quality:**
|
|
242
|
+
|
|
243
|
+
1. **Compose** (`extends: [base, preset]`). Quality flips to sentinel **immediately**,
|
|
244
|
+
because it wins. The base's own tooling options are still present but now **dead
|
|
245
|
+
config** (overridden).
|
|
246
|
+
2. **Slim the base later** to structure only (`paths` + references). This moves nothing
|
|
247
|
+
quality-wise, sentinel already won in step 1, so it is safe. The base is never fully
|
|
248
|
+
removed while it still owns `paths`.
|
|
249
|
+
|
|
250
|
+
**The discipline this demands:** sentinel must **explicitly declare every quality
|
|
251
|
+
option it means to own.** An option sentinel forgets to set falls through to the base,
|
|
252
|
+
a silent gap in the reference. (Be deliberate with array/replace options like `types`:
|
|
253
|
+
setting them **overrides** the base's rather than merging, so only own them when you
|
|
254
|
+
mean to.)
|
|
255
|
+
|
|
177
256
|
## CLI
|
|
178
257
|
|
|
258
|
+
A command **composes** three axes: **verb + type + location**.
|
|
259
|
+
|
|
179
260
|
```
|
|
180
|
-
sentinel <verb>
|
|
261
|
+
sentinel <verb> [type] [options]
|
|
181
262
|
|
|
182
263
|
VERBS --run execute the target's tool
|
|
183
|
-
--inspect show the resolved configuration
|
|
184
|
-
--update generate/apply the config stubs
|
|
264
|
+
--inspect show the resolved configuration (incl. deferred rules)
|
|
185
265
|
--report metrics and health
|
|
266
|
+
--status adoption + conformity (coverage + drift), read from configs
|
|
267
|
+
--update generate/apply the config stubs (writes; one module only)
|
|
186
268
|
|
|
187
|
-
|
|
188
|
-
--static-analysis --runtime-analysis --arch
|
|
269
|
+
TYPES --lint --format --typescript --build --test
|
|
270
|
+
--static-analysis --runtime-analysis --arch
|
|
271
|
+
(omit a type → ALL types; or --all)
|
|
189
272
|
|
|
190
|
-
|
|
191
|
-
--
|
|
273
|
+
LOCATION in a MODULE dir → that module (do NOT pass --module)
|
|
274
|
+
at the workspace ROOT → --module <name> (one) · --ci (affected) · else all
|
|
275
|
+
|
|
276
|
+
OPTIONS --module <name> from the root: scope to one module
|
|
277
|
+
--flavour <name> override the detected stack preset (react, nest, ...)
|
|
192
278
|
--runner <tool> override the default runner
|
|
193
|
-
--ci non-zero exit on failure
|
|
279
|
+
--ci from the root: affected only; non-zero exit on failure
|
|
194
280
|
--fix auto-fix where applicable
|
|
281
|
+
--dry-run preview a --update without writing
|
|
282
|
+
--json machine-readable output (report / inspect / status / --dry-run)
|
|
283
|
+
|
|
284
|
+
EXAMPLES sentinel --run --typescript # in a module → that module
|
|
285
|
+
sentinel --report --typescript --module bff-admin # from root → one module
|
|
286
|
+
sentinel --report # from root → all types, all modules
|
|
287
|
+
sentinel --status --typescript # from root → adoption coverage
|
|
288
|
+
sentinel --status --ci # from root → fail CI on drift
|
|
289
|
+
sentinel --update --typescript --flavour react # write stubs for the current module
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
`--run`/`--inspect`/`--report`/`--status` share one context rule (developer from a
|
|
293
|
+
module, or from the root for a name / affected / all); `--update` writes, so it targets
|
|
294
|
+
one module only (adopting every module at once is refused, adopt gradually).
|
|
195
295
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
296
|
+
**`--status` — adoption + conformity.** A cheap, workspace-wide read (no tool run, no
|
|
297
|
+
flavour guessing): for each module it reads the committed `tsconfig` `extends` chain and
|
|
298
|
+
reports whether it is **adopted** (extends a sentinel preset), which preset, and whether
|
|
299
|
+
it is **conformant** (drift-free, a re-`update` would change nothing), plus a coverage
|
|
300
|
+
footer. This is the **drift guard** as a command, `--status --ci` exits non-zero when an
|
|
301
|
+
adopted module has drifted.
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
✓ host-admin (react) typescript — adopted (react) conformant
|
|
305
|
+
✗ some-bff (nest) typescript — adopted (nest) drift: strict, target
|
|
306
|
+
· legacy-app (node) typescript — not adopted
|
|
307
|
+
coverage: 12/40 adopted · 11/12 conformant · 1 drifted
|
|
201
308
|
```
|
|
202
309
|
|
|
203
310
|
## Repository layout
|
|
@@ -222,11 +329,12 @@ src/
|
|
|
222
329
|
runners/ # one runner per sub-tool (duplication, complexity, ...)
|
|
223
330
|
shared/ # reusable utils (package-json, deep-merge, text)
|
|
224
331
|
tests/ # unit tests + tests/e2e (runs the built dist binary)
|
|
225
|
-
.
|
|
226
|
-
.github/workflows/ # ci.yml (PR checks) + release.yml (changesets publish)
|
|
332
|
+
.github/workflows/ # ci.yml (PR checks) + publish.yml (manual, version-input publish)
|
|
227
333
|
```
|
|
228
334
|
|
|
229
|
-
Subpath exports (in `package.json`) expose presets to consumers
|
|
335
|
+
Subpath exports (in `package.json`) expose presets to consumers. Shipped today:
|
|
336
|
+
`@hublo/sentinel/tsconfig/react` · `.../tsconfig/nest` · `.../tsconfig/node`. Other
|
|
337
|
+
subpaths (e.g. `.../lint/react`) land with their tool ticket.
|
|
230
338
|
|
|
231
339
|
## FAQ
|
|
232
340
|
|
|
@@ -252,8 +360,8 @@ nx is a **task runner**: it just runs the target's script. So `project.json` bar
|
|
|
252
360
|
|
|
253
361
|
This scaffold is the foundation; each tool is added one at a time on top of it:
|
|
254
362
|
|
|
255
|
-
1. **Foundation** — repo, exports, CLI skeleton, adapter contract, changesets, CI/release.
|
|
256
|
-
2. **TypeScript** (`--typescript
|
|
363
|
+
1. **Foundation** — repo, exports, CLI skeleton, adapter contract, changesets, CI/release. **✅ shipped**
|
|
364
|
+
2. **TypeScript** (`--typescript`) — runner `tsc` (later `tsgo`): presets react/nest/node, `--run`/`--inspect`/`--report`/`--update`, the composable grid, phased (non-breaking) strictness. **✅ shipped in `0.1.0-alpha.x`**
|
|
257
365
|
3. **Lint** (`--lint`) — benchmark `eslint` vs `biome` vs `oxlint`.
|
|
258
366
|
4. **Build** (`--build`) — `vite`.
|
|
259
367
|
5. **Test** (`--test`) — `vitest`, plus a11y / w3c setups.
|