@hublo/sentinel 1.4.0-alpha.31 → 1.4.0-alpha.32
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 +1 -0
- package/dist/bin/sentinel.js +3 -3
- package/dist/{chunk-XM65P73B.js → chunk-ORESQJAU.js} +15 -1
- package/dist/{chunk-QYIF3HXC.js → chunk-SPW4KIQX.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/{validate-YRLGMILJ.js → validate-5GABSOYH.js} +1 -1
- package/docs/test-adoption.md +27 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ A large monorepo accumulates:
|
|
|
24
24
|
- **One source of truth for config** — every project just `extends @hublo/sentinel/...`; the actual rules live in one versioned place. Change a rule once, everyone gets it on the next version bump.
|
|
25
25
|
- **One source of truth for tooling dependencies** — a project depends on `@hublo/sentinel`, not on a scattered pile of eslint / vitest / plugin devDeps. Bump one version and the whole toolchain moves, atomically, tested in isolation first.
|
|
26
26
|
- **`--init` sets a module up** — the one command generates the stubs the first time (**adopt**), regenerates them after a change like a runner swap (**refresh**), and applies the workspace prep the module needs. Run it module by module to roll out gradually. (`--migrate`, for changing an already-initialized setup, is a reserved future verb.)
|
|
27
|
+
- **`--validate` proves the move did not change anything** — a verb of its own, because what separates it from `--init` is what SURVIVES the run: `--init` and `--migrate` write state that REMAINS, `--validate` may instrument the module it is proving provided nothing it writes stays. Run before a migration it records what the module does today; run after, it compares and refuses a run that lost a test or invented one. It composes with every type, and answers "not available yet" for the ones without a proof rather than calling a valid command wrong.
|
|
27
28
|
- **Move one module at a time** — installed per module, so you adopt at your pace; a module can adopt sentinel while its neighbour keeps the old setup. No big-bang.
|
|
28
29
|
- **Swap tools without touching projects** — change eslint → biome (or benchmark them) in one place; `--init` regenerates the stubs.
|
|
29
30
|
- **No silent drift** — the guard keeps every project's config converged on the source of truth.
|
package/dist/bin/sentinel.js
CHANGED
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
palette,
|
|
16
16
|
registerAdapters,
|
|
17
17
|
resolve
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-SPW4KIQX.js";
|
|
19
19
|
import {
|
|
20
20
|
resolveContext
|
|
21
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-ORESQJAU.js";
|
|
22
22
|
import "../chunk-O7REVMOC.js";
|
|
23
23
|
import {
|
|
24
24
|
ensureWorkspacePrep,
|
|
@@ -144,7 +144,7 @@ async function runValidate(ctx) {
|
|
|
144
144
|
);
|
|
145
145
|
}
|
|
146
146
|
if (!ctx.targets.includes("test")) return 1;
|
|
147
|
-
const { validateModuleTests } = await import("../validate-
|
|
147
|
+
const { validateModuleTests } = await import("../validate-5GABSOYH.js");
|
|
148
148
|
const outcomes = modules.flatMap(
|
|
149
149
|
(module) => validateModuleTests(module.name, module.root)
|
|
150
150
|
);
|
|
@@ -1295,6 +1295,20 @@ function assertedMockedClass(source, node) {
|
|
|
1295
1295
|
if (object === void 0) return false;
|
|
1296
1296
|
return /\bMockedClass\b/.test(source.slice(object.start, object.end));
|
|
1297
1297
|
}
|
|
1298
|
+
function classValuedProperties(factories) {
|
|
1299
|
+
const found = [];
|
|
1300
|
+
for (const factory of factories) {
|
|
1301
|
+
walk(factory, (node) => {
|
|
1302
|
+
if (node.type !== "Property" && node.type !== "ObjectProperty") return;
|
|
1303
|
+
const key = node.key;
|
|
1304
|
+
const name = key?.type === "Identifier" ? key.name : key?.type === "StringLiteral" || key?.type === "Literal" ? String(key.value) : void 0;
|
|
1305
|
+
if (name === void 0 || !/^[A-Z]/.test(name)) return;
|
|
1306
|
+
const value = node.value;
|
|
1307
|
+
if (value !== void 0) found.push(value);
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
return found;
|
|
1311
|
+
}
|
|
1298
1312
|
function readsThis(node) {
|
|
1299
1313
|
let found = false;
|
|
1300
1314
|
walk(node, (inner) => {
|
|
@@ -1307,7 +1321,7 @@ function rewriteConstructibleMocks(source, program, factories) {
|
|
|
1307
1321
|
const refusals = [];
|
|
1308
1322
|
let rewritten = 0;
|
|
1309
1323
|
const seen = /* @__PURE__ */ new Set();
|
|
1310
|
-
const subtrees = [...factories];
|
|
1324
|
+
const subtrees = [...classValuedProperties(factories)];
|
|
1311
1325
|
walk(program, (node) => {
|
|
1312
1326
|
if (assertedMockedClass(source, node)) subtrees.push(node);
|
|
1313
1327
|
});
|
package/dist/index.js
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
registerAdapters,
|
|
7
7
|
resolve,
|
|
8
8
|
setDefaultRunner
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-SPW4KIQX.js";
|
|
10
10
|
import {
|
|
11
11
|
BaseAdapter
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-ORESQJAU.js";
|
|
13
13
|
import "./chunk-O7REVMOC.js";
|
|
14
14
|
import "./chunk-NW6UHNYX.js";
|
|
15
15
|
export {
|
package/docs/test-adoption.md
CHANGED
|
@@ -10,6 +10,7 @@ that decided a design choice say which choice.
|
|
|
10
10
|
- [The one line that makes a per-module migration possible](#the-one-line-that-makes-a-per-module-migration-possible)
|
|
11
11
|
- [What `--init --test` rewrites, and what it refuses](#what---init---test-rewrites-and-what-it-refuses)
|
|
12
12
|
- [Nest services](#nest-services)
|
|
13
|
+
- [The two DOM matchers, which a react module gets without asking](#the-two-dom-matchers-which-a-react-module-gets-without-asking)
|
|
13
14
|
- [The check that decides whether you are done](#the-check-that-decides-whether-you-are-done)
|
|
14
15
|
|
|
15
16
|
## What the ground actually looks like
|
|
@@ -292,16 +293,36 @@ member NAMES, which `any` gave up too. Applied only when the SAME file mocks tha
|
|
|
292
293
|
## The check that decides whether you are done
|
|
293
294
|
|
|
294
295
|
Not "the suite is green". **The suite runs the same tests as before**. You do not have to arrange
|
|
295
|
-
that comparison;
|
|
296
|
+
that comparison; `--validate` does it, on either side of the migration:
|
|
296
297
|
|
|
297
298
|
```console
|
|
298
|
-
$ sentinel --
|
|
299
|
-
$
|
|
300
|
-
$
|
|
299
|
+
$ sentinel --validate --test # on the JEST state: records every passing test name
|
|
300
|
+
$ sentinel --init --test # migrates
|
|
301
|
+
$ pnpm install # the module's dependencies changed
|
|
302
|
+
$ sentinel --validate --test # runs vitest, compares against that recording, then spends it
|
|
301
303
|
```
|
|
302
304
|
|
|
303
305
|
A codemod touching thousands of files cannot be reviewed by hand. This comparison is the review.
|
|
304
306
|
|
|
307
|
+
⚠️ **The first command comes first, and the tool enforces it.** Start with `--init` and the jest
|
|
308
|
+
config is already gone, so there is nothing left to record and nothing to compare against:
|
|
309
|
+
|
|
310
|
+
```console
|
|
311
|
+
✗ recruitment — already migrated, and no reference was recorded before it was, so there is
|
|
312
|
+
nothing to compare against. A proof has to be started BEFORE the migration:
|
|
313
|
+
`sentinel --validate --test` on the jest state, then `--init --test`, then this again.
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Why the proof is its own verb
|
|
317
|
+
|
|
318
|
+
Because of what SURVIVES a run. `--init` and `--migrate` write state that stays, which is their
|
|
319
|
+
whole purpose and why they are strict about what they touch. `--validate` may instrument the module
|
|
320
|
+
it is proving, provided nothing it writes remains. Those are different contracts, so they are
|
|
321
|
+
different verbs, and the proof can grow without touching a migration that is already proven.
|
|
322
|
+
|
|
323
|
+
It answers every type, too: `sentinel --validate --lint` is a well-formed command, so it says "not
|
|
324
|
+
available yet" rather than calling your sentence wrong.
|
|
325
|
+
|
|
305
326
|
### Why it cannot be one command
|
|
306
327
|
|
|
307
328
|
The reference only exists BEFORE the migration, and the thing that produces it, the jest config, is
|
|
@@ -321,7 +342,7 @@ shorter.
|
|
|
321
342
|
So the gate refuses a run that lost a name or invented one, and says which:
|
|
322
343
|
|
|
323
344
|
```console
|
|
324
|
-
$ sentinel --
|
|
345
|
+
$ sentinel --validate --test
|
|
325
346
|
1 test(s) no longer exist: useHublerNetworkProfilesQuery builds the URL with employment
|
|
326
347
|
statuses only. (reference: jest.config.ts, 2026-09-23T21:10:00.000Z) The reference is KEPT
|
|
327
348
|
until a run passes it, so this does not go green by running again. Fix the suite and re-run,
|
|
@@ -356,6 +377,7 @@ have read that improvement as a regression.
|
|
|
356
377
|
```console
|
|
357
378
|
$ nx run <module>:typecheck # BEFORE, and write that down too
|
|
358
379
|
$ sentinel --init --test
|
|
380
|
+
$ pnpm install
|
|
359
381
|
$ nx run <module>:typecheck # after
|
|
360
382
|
```
|
|
361
383
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hublo/sentinel",
|
|
3
|
-
"version": "1.4.0-alpha.
|
|
3
|
+
"version": "1.4.0-alpha.32",
|
|
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",
|