@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 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.
@@ -15,10 +15,10 @@ import {
15
15
  palette,
16
16
  registerAdapters,
17
17
  resolve
18
- } from "../chunk-QYIF3HXC.js";
18
+ } from "../chunk-SPW4KIQX.js";
19
19
  import {
20
20
  resolveContext
21
- } from "../chunk-XM65P73B.js";
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-YRLGMILJ.js");
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
  });
@@ -27,7 +27,7 @@ import {
27
27
  selfCommand,
28
28
  splitToolArgs,
29
29
  walk
30
- } from "./chunk-XM65P73B.js";
30
+ } from "./chunk-ORESQJAU.js";
31
31
  import {
32
32
  DEFAULT_MAX_DIAGNOSTICS,
33
33
  findWorkspaceRoot,
package/dist/index.js CHANGED
@@ -6,10 +6,10 @@ import {
6
6
  registerAdapters,
7
7
  resolve,
8
8
  setDefaultRunner
9
- } from "./chunk-QYIF3HXC.js";
9
+ } from "./chunk-SPW4KIQX.js";
10
10
  import {
11
11
  BaseAdapter
12
- } from "./chunk-XM65P73B.js";
12
+ } from "./chunk-ORESQJAU.js";
13
13
  import "./chunk-O7REVMOC.js";
14
14
  import "./chunk-NW6UHNYX.js";
15
15
  export {
@@ -7,7 +7,7 @@ import {
7
7
  readTestAdoption,
8
8
  resolveVitest,
9
9
  suiteOf
10
- } from "./chunk-XM65P73B.js";
10
+ } from "./chunk-ORESQJAU.js";
11
11
  import "./chunk-O7REVMOC.js";
12
12
  import "./chunk-NW6UHNYX.js";
13
13
 
@@ -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; the two commands do it between them:
296
+ that comparison; `--validate` does it, on either side of the migration:
296
297
 
297
298
  ```console
298
- $ sentinel --init --test # runs jest ONCE first, records every test name, then migrates
299
- $ pnpm install # the module's dependencies changed
300
- $ sentinel --run --test # runs vitest, compares against that recording, then spends it
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 --run --test
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.31",
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",