@hublo/sentinel 1.4.0-alpha.10 → 1.4.0-alpha.12
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/dist/bin/sentinel.js
CHANGED
|
@@ -561,9 +561,9 @@ function binFromOwnInstall(packageName, binName) {
|
|
|
561
561
|
try {
|
|
562
562
|
const manifest = require2.resolve(`${packageName}/package.json`);
|
|
563
563
|
const bin = require2(manifest).bin;
|
|
564
|
-
const
|
|
565
|
-
if (!
|
|
566
|
-
const executable = join5(dirname(manifest),
|
|
564
|
+
const relative8 = typeof bin === "string" ? bin : bin?.[binName];
|
|
565
|
+
if (!relative8) return void 0;
|
|
566
|
+
const executable = join5(dirname(manifest), relative8);
|
|
567
567
|
return existsSync6(executable) ? executable : void 0;
|
|
568
568
|
} catch {
|
|
569
569
|
return void 0;
|
|
@@ -5482,7 +5482,8 @@ function testTarget(options) {
|
|
|
5482
5482
|
|
|
5483
5483
|
// src/roles/test/jest-migration.ts
|
|
5484
5484
|
import { existsSync as existsSync29, readFileSync as readFileSync28, readdirSync as readdirSync8 } from "fs";
|
|
5485
|
-
import {
|
|
5485
|
+
import { createRequire as createRequire6 } from "module";
|
|
5486
|
+
import { dirname as dirname7, join as join35, relative as relative7 } from "path";
|
|
5486
5487
|
|
|
5487
5488
|
// src/roles/test/codemod/migrate-module.ts
|
|
5488
5489
|
import { readdirSync as readdirSync6, readFileSync as readFileSync25, statSync as statSync6 } from "fs";
|
|
@@ -5614,11 +5615,11 @@ function readTestAdoption(cwd) {
|
|
|
5614
5615
|
// src/roles/test/support-file-name.ts
|
|
5615
5616
|
var JEST_PREFIX = "jest.";
|
|
5616
5617
|
var VITEST_PREFIX = "vitest.";
|
|
5617
|
-
function vitestSupportName(
|
|
5618
|
-
const cut =
|
|
5619
|
-
const directory = cut === -1 ? "" :
|
|
5620
|
-
const base =
|
|
5621
|
-
if (!base.startsWith(JEST_PREFIX)) return
|
|
5618
|
+
function vitestSupportName(relative8) {
|
|
5619
|
+
const cut = relative8.lastIndexOf("/");
|
|
5620
|
+
const directory = cut === -1 ? "" : relative8.slice(0, cut + 1);
|
|
5621
|
+
const base = relative8.slice(cut + 1);
|
|
5622
|
+
if (!base.startsWith(JEST_PREFIX)) return relative8;
|
|
5622
5623
|
return `${directory}${VITEST_PREFIX}${base.slice(JEST_PREFIX.length)}`;
|
|
5623
5624
|
}
|
|
5624
5625
|
|
|
@@ -7272,7 +7273,7 @@ function rewriteSpyInstanceType(source, program) {
|
|
|
7272
7273
|
if (args === void 0 || args.length === 0) return;
|
|
7273
7274
|
const text = (argument) => source.slice(argument.start, argument.end);
|
|
7274
7275
|
const returns = text(args[0]);
|
|
7275
|
-
const parameters = args.length > 1 ? `...args: ${text(args[1])}` : "...args:
|
|
7276
|
+
const parameters = args.length > 1 ? `...args: ${text(args[1])}` : "...args: never[]";
|
|
7276
7277
|
edits.push({
|
|
7277
7278
|
start: node.start,
|
|
7278
7279
|
end: node.end,
|
|
@@ -7583,16 +7584,16 @@ function migrateModuleTests(cwd, setupFiles = []) {
|
|
|
7583
7584
|
let hoisted = 0;
|
|
7584
7585
|
const defaultImported = defaultImportedSpecifiers(cwd);
|
|
7585
7586
|
const asyncHelpers = /* @__PURE__ */ new Set();
|
|
7586
|
-
for (const
|
|
7587
|
-
const source = read2(join31(cwd,
|
|
7587
|
+
for (const relative8 of listTestSupportFiles(cwd, setupFiles)) {
|
|
7588
|
+
const source = read2(join31(cwd, relative8));
|
|
7588
7589
|
if (source === void 0) {
|
|
7589
|
-
blockers.push({ file:
|
|
7590
|
+
blockers.push({ file: relative8, reason: "this file could not be read" });
|
|
7590
7591
|
continue;
|
|
7591
7592
|
}
|
|
7592
|
-
const migrated = migrateTestFile(join31(cwd,
|
|
7593
|
-
const renamedTo = vitestSupportName(
|
|
7594
|
-
if (!migrated.changed && renamedTo ===
|
|
7595
|
-
if (renamedTo !==
|
|
7593
|
+
const migrated = migrateTestFile(join31(cwd, relative8), source, defaultImported);
|
|
7594
|
+
const renamedTo = vitestSupportName(relative8);
|
|
7595
|
+
if (!migrated.changed && renamedTo === relative8) continue;
|
|
7596
|
+
if (renamedTo !== relative8) operations.push({ kind: "delete", path: relative8 });
|
|
7596
7597
|
const before = parse3(source);
|
|
7597
7598
|
const after = parse3(migrated.source);
|
|
7598
7599
|
if (before !== void 0 && after !== void 0) {
|
|
@@ -7605,27 +7606,27 @@ function migrateModuleTests(cwd, setupFiles = []) {
|
|
|
7605
7606
|
renamed += migrated.renamed;
|
|
7606
7607
|
operations.push({ kind: "write", path: renamedTo, contents: migrated.source });
|
|
7607
7608
|
}
|
|
7608
|
-
for (const
|
|
7609
|
-
const source = read2(join31(cwd,
|
|
7609
|
+
for (const relative8 of listTestFiles(cwd)) {
|
|
7610
|
+
const source = read2(join31(cwd, relative8));
|
|
7610
7611
|
if (source === void 0) {
|
|
7611
|
-
blockers.push({ file:
|
|
7612
|
+
blockers.push({ file: relative8, reason: "this file could not be read" });
|
|
7612
7613
|
continue;
|
|
7613
7614
|
}
|
|
7614
|
-
const migrated = migrateTestFile(join31(cwd,
|
|
7615
|
+
const migrated = migrateTestFile(join31(cwd, relative8), source, defaultImported, asyncHelpers);
|
|
7615
7616
|
for (const member of migrated.unhandled) {
|
|
7616
7617
|
blockers.push({
|
|
7617
|
-
file: `${
|
|
7618
|
+
file: `${relative8}:${member.line}`,
|
|
7618
7619
|
reason: member.rule === void 0 ? `${member.member} has no rule, and guessing one is how a suite goes green for the wrong reason` : `${member.member} needs ${member.rule}`
|
|
7619
7620
|
});
|
|
7620
7621
|
}
|
|
7621
7622
|
for (const refusal of migrated.refusals) {
|
|
7622
|
-
blockers.push({ file:
|
|
7623
|
+
blockers.push({ file: relative8, reason: refusal.reason });
|
|
7623
7624
|
}
|
|
7624
7625
|
if (!migrated.changed) continue;
|
|
7625
7626
|
files++;
|
|
7626
7627
|
renamed += migrated.renamed;
|
|
7627
7628
|
if (migrated.hoisted.length > 0) hoisted++;
|
|
7628
|
-
operations.push({ kind: "write", path:
|
|
7629
|
+
operations.push({ kind: "write", path: relative8, contents: migrated.source });
|
|
7629
7630
|
}
|
|
7630
7631
|
return blockers.length > 0 ? { operations: [], files, renamed, hoisted, blockers } : { operations, files, renamed, hoisted, blockers: [] };
|
|
7631
7632
|
}
|
|
@@ -8207,6 +8208,17 @@ function specTypesOperation(cwd) {
|
|
|
8207
8208
|
}
|
|
8208
8209
|
};
|
|
8209
8210
|
}
|
|
8211
|
+
function mockExtendedTypesPath(cwd) {
|
|
8212
|
+
try {
|
|
8213
|
+
const manifest = createRequire6(join35(cwd, "noop.cjs")).resolve("@hublo/sentinel/package.json");
|
|
8214
|
+
const file = join35(dirname7(manifest), "types", "mock-extended.d.ts");
|
|
8215
|
+
if (!existsSync29(file)) return void 0;
|
|
8216
|
+
const path = relative7(cwd, file);
|
|
8217
|
+
return path.startsWith(".") ? path : `./${path}`;
|
|
8218
|
+
} catch {
|
|
8219
|
+
return void 0;
|
|
8220
|
+
}
|
|
8221
|
+
}
|
|
8210
8222
|
function tsconfigFileListOperations(cwd) {
|
|
8211
8223
|
const operations = [];
|
|
8212
8224
|
let names;
|
|
@@ -8227,12 +8239,13 @@ function tsconfigFileListOperations(cwd) {
|
|
|
8227
8239
|
}
|
|
8228
8240
|
const include = Array.isArray(config?.include) ? rewriteFileList(config.include) : void 0;
|
|
8229
8241
|
const exclude = Array.isArray(config?.exclude) ? rewriteFileList(config.exclude) : void 0;
|
|
8230
|
-
|
|
8242
|
+
const withTypes = name === SPEC_TSCONFIG ? withMockExtendedTypes(cwd, include ?? config?.include) : void 0;
|
|
8243
|
+
if (include === void 0 && exclude === void 0 && withTypes === void 0) continue;
|
|
8231
8244
|
operations.push({
|
|
8232
8245
|
kind: "merge-json",
|
|
8233
8246
|
path: name,
|
|
8234
8247
|
value: {
|
|
8235
|
-
...include === void 0 ? {} : { include },
|
|
8248
|
+
...withTypes === void 0 ? include === void 0 ? {} : { include } : { include: withTypes },
|
|
8236
8249
|
...exclude === void 0 ? {} : { exclude }
|
|
8237
8250
|
}
|
|
8238
8251
|
});
|
|
@@ -8244,6 +8257,13 @@ function rewriteFileList(entries) {
|
|
|
8244
8257
|
const unchanged = rewritten.length === entries.length && rewritten.every((entry, index) => entry === entries[index]);
|
|
8245
8258
|
return unchanged ? void 0 : rewritten;
|
|
8246
8259
|
}
|
|
8260
|
+
function withMockExtendedTypes(cwd, include) {
|
|
8261
|
+
const path = mockExtendedTypesPath(cwd);
|
|
8262
|
+
if (path === void 0 || !Array.isArray(include)) return void 0;
|
|
8263
|
+
const entries = include;
|
|
8264
|
+
if (entries.some((entry) => entry.endsWith("types/mock-extended.d.ts"))) return void 0;
|
|
8265
|
+
return [...entries, path];
|
|
8266
|
+
}
|
|
8247
8267
|
var JEST_CONFIG_ENTRY = /(^|\/)jest\.[\w.-]*config\.[cm]?[jt]s$/;
|
|
8248
8268
|
var TSCONFIG_FILE = /^tsconfig[\w.-]*\.json$/;
|
|
8249
8269
|
function siblingScriptRewrites(cwd) {
|
|
@@ -8340,7 +8360,7 @@ function planJestMigration(context, adoption) {
|
|
|
8340
8360
|
);
|
|
8341
8361
|
}
|
|
8342
8362
|
notes.push(
|
|
8343
|
-
`run this module's own formatter
|
|
8363
|
+
`the files this rewrote were run through this module's own formatter afterwards. Two things needed it: the Vitest type imports land beside the existing ones, and a rewritten call is often SHORTER than the one it replaced, so the line breaks around it are no longer the ones your config would pick. Neither is a defect, and both would have failed your format check.`
|
|
8344
8364
|
);
|
|
8345
8365
|
notes.push(
|
|
8346
8366
|
`then run this module's TYPECHECK, not only its tests. The migration rewrites types as well as calls, and a wrong type is invisible to a green suite: measured on a real module, \`--run --test\` passed while \`--run --typescript\` reported ten errors it had not had before.`
|
|
@@ -8837,6 +8857,44 @@ var VitestAdapter = class extends BaseAdapter {
|
|
|
8837
8857
|
this.say(captureReference(ctx.cwd, config).message);
|
|
8838
8858
|
return Promise.resolve();
|
|
8839
8859
|
}
|
|
8860
|
+
/**
|
|
8861
|
+
* Hand what was just written to the module's OWN formatter.
|
|
8862
|
+
*
|
|
8863
|
+
* ⚠️ Not a style sentinel imposes: the module adopted `.oxfmtrc.json`, and this runs that
|
|
8864
|
+
* config over the files this role created. Without it, `--init --test` leaves a module whose
|
|
8865
|
+
* own `--run --format` fails on a file the module never wrote.
|
|
8866
|
+
*
|
|
8867
|
+
* Measured on `apps/nest/microservices/agency`, in this repository's CI:
|
|
8868
|
+
*
|
|
8869
|
+
* agency:format 12 files, including vitest.config.mts and
|
|
8870
|
+
* vitest.integration.config.mts, both generated here
|
|
8871
|
+
*
|
|
8872
|
+
* The generator cannot avoid it by wrapping at a fixed width, because the width is the
|
|
8873
|
+
* MODULE's decision: `printWidth` lives in its own config, and 12 modules already set their
|
|
8874
|
+
* own. Guessing it is how a tool starts owning a setting it does not own.
|
|
8875
|
+
*
|
|
8876
|
+
* The same delegation the build role asks a human to do by hand, done. A module that has not
|
|
8877
|
+
* adopted the format role has no formatter to invoke, and this says nothing rather than
|
|
8878
|
+
* choosing one for it.
|
|
8879
|
+
*/
|
|
8880
|
+
async afterInit(ctx) {
|
|
8881
|
+
const oxfmt = resolveOxfmt(ctx.cwd);
|
|
8882
|
+
if (oxfmt === void 0) {
|
|
8883
|
+
this.say(
|
|
8884
|
+
`no formatter adopted here, so what was written keeps the shape it was written in. Run your own formatter over it before committing.`
|
|
8885
|
+
);
|
|
8886
|
+
return Promise.resolve();
|
|
8887
|
+
}
|
|
8888
|
+
const pass = spawnSync5(oxfmt, ["--write", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
8889
|
+
this.say(
|
|
8890
|
+
pass.status === 0 ? `formatted what this wrote with the module's own oxfmt config, so its \`--run --format\` passes on files it did not write itself.` : (
|
|
8891
|
+
// Adoption SUCCEEDED; only the tidy-up did not. Failing here would revert a migration
|
|
8892
|
+
// over a formatting pass, which is the wrong trade.
|
|
8893
|
+
`the module's formatter could not run over what this wrote (${pass.status ?? "no exit code"}). Adoption itself is applied; run \`sentinel --run --format --fix\` before committing.`
|
|
8894
|
+
)
|
|
8895
|
+
);
|
|
8896
|
+
return Promise.resolve();
|
|
8897
|
+
}
|
|
8840
8898
|
plan(context) {
|
|
8841
8899
|
return plan3(context);
|
|
8842
8900
|
}
|
|
@@ -8953,7 +9011,7 @@ function registerTest() {
|
|
|
8953
9011
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
8954
9012
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
8955
9013
|
import { existsSync as existsSync31, readFileSync as readFileSync32 } from "fs";
|
|
8956
|
-
import { createRequire as
|
|
9014
|
+
import { createRequire as createRequire7 } from "module";
|
|
8957
9015
|
import { join as join39 } from "path";
|
|
8958
9016
|
|
|
8959
9017
|
// src/roles/typescript/presets/nest.json
|
|
@@ -9380,7 +9438,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
9380
9438
|
);
|
|
9381
9439
|
if (preset === void 0) return void 0;
|
|
9382
9440
|
try {
|
|
9383
|
-
|
|
9441
|
+
createRequire7(join39(cwd, "noop.js")).resolve(preset);
|
|
9384
9442
|
return void 0;
|
|
9385
9443
|
} catch {
|
|
9386
9444
|
return preset;
|
package/dist/index.js
CHANGED
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.12",
|
|
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",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
},
|
|
78
78
|
"files": [
|
|
79
79
|
"dist",
|
|
80
|
+
"types",
|
|
80
81
|
"docs",
|
|
81
82
|
"lint",
|
|
82
83
|
"oxlint",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type-level half of the `jest-mock-extended` alias.
|
|
3
|
+
*
|
|
4
|
+
* The generated Vitest config aliases `jest-mock-extended` to the fork sentinel ships, so at RUN
|
|
5
|
+
* time a migrated module has ONE copy. TypeScript does not read Vite's aliases, so it keeps
|
|
6
|
+
* resolving the jest package and a migrated spec ends up compiling two worlds at once:
|
|
7
|
+
*
|
|
8
|
+
* the value typeof mockDeep<Db>() -> CalledWithMock<…> extends jest.Mock
|
|
9
|
+
* the spec let spy: MockInstance -> vitest's own type
|
|
10
|
+
* TS2322 not assignable
|
|
11
|
+
*
|
|
12
|
+
* Measured on `apps/nest/microservices/agency`: one error, on a module whose typecheck was clean
|
|
13
|
+
* before it migrated. 34 files under `libs/` export a type built this way.
|
|
14
|
+
*
|
|
15
|
+
* ## Why here, and not in the module
|
|
16
|
+
*
|
|
17
|
+
* The declaration REPLACES the module's types with whatever it re-exports, so it has to name a
|
|
18
|
+
* package that resolves from wherever the file sits. `vitest-mock-extended` is sentinel's
|
|
19
|
+
* dependency, so a copy of this file placed in an adopting module would resolve nothing and empty
|
|
20
|
+
* the module out: measured, `Module '"jest-mock-extended"' has no exported member 'mockDeep'`.
|
|
21
|
+
*
|
|
22
|
+
* ## Why it does not couple modules to each other
|
|
23
|
+
*
|
|
24
|
+
* An ambient declaration applies to the PROGRAM that includes it, and nothing else. A module that
|
|
25
|
+
* includes it compiles against the Vitest types; a module still on jest, consuming the very same
|
|
26
|
+
* shared helper, keeps compiling against the jest ones. Proved on an isolated fixture: both
|
|
27
|
+
* programs green, over one unmodified shared lib.
|
|
28
|
+
*
|
|
29
|
+
* That is the whole point. Héla, 2026-09-24, on tying a module's migration to a library's:
|
|
30
|
+
* "ça crée des couplages et bloquent les migrations". lint, format and typescript each migrate one
|
|
31
|
+
* module at a time; this keeps the test role the same.
|
|
32
|
+
*/
|
|
33
|
+
declare module 'jest-mock-extended' {
|
|
34
|
+
export * from 'vitest-mock-extended'
|
|
35
|
+
}
|