@hublo/sentinel 1.4.0-alpha.13 → 1.4.0-alpha.15
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
|
@@ -7256,6 +7256,8 @@ function repointMswServer(fileName, source) {
|
|
|
7256
7256
|
|
|
7257
7257
|
// src/roles/test/codemod/spy-instance-type.ts
|
|
7258
7258
|
var SPY_TYPES = /* @__PURE__ */ new Set(["SpyInstance"]);
|
|
7259
|
+
var QUALIFIED_ONLY = /* @__PURE__ */ new Set(["Mock"]);
|
|
7260
|
+
var VITEST_NAME = { SpyInstance: "MockInstance", Mock: "Mock" };
|
|
7259
7261
|
function typeNameOf(source, node) {
|
|
7260
7262
|
const name = node.typeName;
|
|
7261
7263
|
if (name === void 0) return void 0;
|
|
@@ -7267,17 +7269,20 @@ function rewriteSpyInstanceType(source, program) {
|
|
|
7267
7269
|
if (node.type !== "TSTypeReference") return;
|
|
7268
7270
|
const written = typeNameOf(source, node);
|
|
7269
7271
|
if (written === void 0) return;
|
|
7270
|
-
const
|
|
7271
|
-
|
|
7272
|
+
const qualified = written.startsWith("jest.");
|
|
7273
|
+
const bare = qualified ? written.slice("jest.".length) : written;
|
|
7274
|
+
if (!SPY_TYPES.has(bare) && !(qualified && QUALIFIED_ONLY.has(bare))) return;
|
|
7272
7275
|
const args = (node.typeArguments ?? node.typeParameters)?.params;
|
|
7273
7276
|
if (args === void 0 || args.length === 0) return;
|
|
7274
7277
|
const text = (argument) => source.slice(argument.start, argument.end);
|
|
7275
7278
|
const returns = text(args[0]);
|
|
7276
7279
|
const parameters = args.length > 1 ? `...args: ${text(args[1])}` : "...args: never[]";
|
|
7280
|
+
const target = VITEST_NAME[bare];
|
|
7277
7281
|
edits.push({
|
|
7278
7282
|
start: node.start,
|
|
7279
7283
|
end: node.end,
|
|
7280
|
-
text:
|
|
7284
|
+
text: `${target}<(${parameters}) => ${returns}>`,
|
|
7285
|
+
imports: target
|
|
7281
7286
|
});
|
|
7282
7287
|
});
|
|
7283
7288
|
let result = source;
|
|
@@ -7287,7 +7292,8 @@ function rewriteSpyInstanceType(source, program) {
|
|
|
7287
7292
|
return {
|
|
7288
7293
|
source: result,
|
|
7289
7294
|
rewritten: edits.length,
|
|
7290
|
-
|
|
7295
|
+
// Only the names actually written, so a file does not import a type it never uses.
|
|
7296
|
+
typeImports: [...new Set(edits.map((edit) => edit.imports))]
|
|
7291
7297
|
};
|
|
7292
7298
|
}
|
|
7293
7299
|
|
|
@@ -8208,17 +8214,17 @@ function specTypesOperation(cwd) {
|
|
|
8208
8214
|
}
|
|
8209
8215
|
};
|
|
8210
8216
|
}
|
|
8211
|
-
function
|
|
8217
|
+
function shippedTypesPath(cwd, declaration) {
|
|
8212
8218
|
try {
|
|
8213
8219
|
const manifest = createRequire6(join35(cwd, "noop.cjs")).resolve("@hublo/sentinel/package.json");
|
|
8214
|
-
const file = join35(dirname7(manifest),
|
|
8220
|
+
const file = join35(dirname7(manifest), declaration);
|
|
8215
8221
|
if (existsSync29(file)) {
|
|
8216
8222
|
const path = relative7(cwd, file);
|
|
8217
8223
|
return path.startsWith(".") ? path : `./${path}`;
|
|
8218
8224
|
}
|
|
8219
8225
|
} catch {
|
|
8220
8226
|
}
|
|
8221
|
-
return `./node_modules/@hublo/sentinel/${
|
|
8227
|
+
return `./node_modules/@hublo/sentinel/${declaration}`;
|
|
8222
8228
|
}
|
|
8223
8229
|
function tsconfigFileListOperations(cwd) {
|
|
8224
8230
|
const operations = [];
|
|
@@ -8241,7 +8247,7 @@ function tsconfigFileListOperations(cwd) {
|
|
|
8241
8247
|
const include = Array.isArray(config?.include) ? rewriteFileList(config.include) : void 0;
|
|
8242
8248
|
const exclude = Array.isArray(config?.exclude) ? rewriteFileList(config.exclude) : void 0;
|
|
8243
8249
|
const files = Array.isArray(config?.files) ? rewriteFileList(config.files) : void 0;
|
|
8244
|
-
const withTypes = name === SPEC_TSCONFIG ?
|
|
8250
|
+
const withTypes = name === SPEC_TSCONFIG ? withShippedTypes(cwd, files ?? config?.files) : void 0;
|
|
8245
8251
|
if (include === void 0 && exclude === void 0 && files === void 0 && withTypes === void 0)
|
|
8246
8252
|
continue;
|
|
8247
8253
|
operations.push({
|
|
@@ -8261,12 +8267,15 @@ function rewriteFileList(entries) {
|
|
|
8261
8267
|
const unchanged = rewritten.length === entries.length && rewritten.every((entry, index) => entry === entries[index]);
|
|
8262
8268
|
return unchanged ? void 0 : rewritten;
|
|
8263
8269
|
}
|
|
8264
|
-
function
|
|
8270
|
+
function withShippedTypes(cwd, files) {
|
|
8265
8271
|
const entries = Array.isArray(files) ? files : [];
|
|
8266
|
-
|
|
8267
|
-
|
|
8272
|
+
const missing = SHIPPED_TYPES.filter(
|
|
8273
|
+
(declaration) => !entries.some((entry) => entry.endsWith(declaration))
|
|
8274
|
+
);
|
|
8275
|
+
if (missing.length === 0) return void 0;
|
|
8276
|
+
return [...entries, ...missing.map((declaration) => shippedTypesPath(cwd, declaration))];
|
|
8268
8277
|
}
|
|
8269
|
-
var
|
|
8278
|
+
var SHIPPED_TYPES = ["types/mock-extended.d.ts", "types/jest-global.d.ts"];
|
|
8270
8279
|
var JEST_CONFIG_ENTRY = /(^|\/)jest\.[\w.-]*config\.[cm]?[jt]s$/;
|
|
8271
8280
|
var TSCONFIG_FILE = /^tsconfig[\w.-]*\.json$/;
|
|
8272
8281
|
function siblingScriptRewrites(cwd) {
|
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.15",
|
|
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",
|
|
@@ -66,6 +66,10 @@
|
|
|
66
66
|
"types": "./dist/roles/test/tools/msw.d.ts",
|
|
67
67
|
"import": "./dist/roles/test/tools/msw.js"
|
|
68
68
|
},
|
|
69
|
+
"./test/tools/mock-extended": {
|
|
70
|
+
"types": "./dist/roles/test/setup/mock-extended.d.ts",
|
|
71
|
+
"import": "./dist/roles/test/setup/mock-extended.js"
|
|
72
|
+
},
|
|
69
73
|
"./test/setup/msw-lifecycle": {
|
|
70
74
|
"types": "./dist/roles/test/setup/msw-lifecycle.d.ts",
|
|
71
75
|
"import": "./dist/roles/test/setup/msw-lifecycle.js"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type-level half of the `jest` global the setup installs.
|
|
3
|
+
*
|
|
4
|
+
* ## What it pairs with
|
|
5
|
+
*
|
|
6
|
+
* The migration rewrites SPEC files and deliberately leaves helpers alone, because the setup puts
|
|
7
|
+
* Vitest's `vi` on `globalThis` under the name `jest`:
|
|
8
|
+
*
|
|
9
|
+
* // setup/jest-global.ts
|
|
10
|
+
* ;(globalThis as Record<string, unknown>).jest = vi
|
|
11
|
+
*
|
|
12
|
+
* That is what lets a module migrate on its own while a shared helper still calls `jest.fn()`, and
|
|
13
|
+
* it is why the suite of a migrated module is green with untouched helpers.
|
|
14
|
+
*
|
|
15
|
+
* The shim answers the RUNTIME. It says nothing to TypeScript, and the migration also replaces
|
|
16
|
+
* `"jest"` with `"vitest/globals"` in the module's `tsconfig.spec.json`, so the name stops being a
|
|
17
|
+
* value at compile time while it still is one at run time.
|
|
18
|
+
*
|
|
19
|
+
* Measured on `apps/nest/microservices/institution` with alpha.14, a module whose typecheck was
|
|
20
|
+
* green before it migrated and whose 2958 tests all pass after:
|
|
21
|
+
*
|
|
22
|
+
* src/app/institution.test-wrapper.ts(83,22)
|
|
23
|
+
* error TS2708: Cannot use namespace 'jest' as a value.
|
|
24
|
+
* tests/support/institution-prisma.test-wrapper.ts(58,22)
|
|
25
|
+
* error TS2708: Cannot use namespace 'jest' as a value.
|
|
26
|
+
*
|
|
27
|
+
* Both are helpers, neither is a spec, and both run correctly. This file states, for the type
|
|
28
|
+
* checker, exactly what the setup states for the runtime.
|
|
29
|
+
*
|
|
30
|
+
* ## Why `typeof vi` and not a hand-written subset
|
|
31
|
+
*
|
|
32
|
+
* The shim assigns the whole `vi`, so anything narrower would describe something the module does
|
|
33
|
+
* not run. Writing the truth costs one line and cannot drift.
|
|
34
|
+
*
|
|
35
|
+
* ## In a program that still carries `@types/jest`
|
|
36
|
+
*
|
|
37
|
+
* No conflict, and this one wins: measured on a fixture holding both, `jest.hoisted(() => 1)`
|
|
38
|
+
* compiles, and `hoisted` exists only on `vi`. So a module part-way through its migration gets the
|
|
39
|
+
* type of what actually executes rather than the type of the runner it is leaving.
|
|
40
|
+
*/
|
|
41
|
+
declare const jest: (typeof import('vitest'))['vi']
|
package/types/mock-extended.d.ts
CHANGED
|
@@ -5,19 +5,36 @@
|
|
|
5
5
|
* time a migrated module has ONE copy. TypeScript does not read Vite's aliases, so it keeps
|
|
6
6
|
* resolving the jest package and a migrated spec ends up compiling two worlds at once:
|
|
7
7
|
*
|
|
8
|
-
* the value typeof mockDeep<Db>()
|
|
8
|
+
* the value typeof mockDeep<Db>() -> CalledWithMock<…> extends jest.Mock
|
|
9
9
|
* the spec let spy: MockInstance -> vitest's own type
|
|
10
10
|
* TS2322 not assignable
|
|
11
11
|
*
|
|
12
12
|
* Measured on `apps/nest/microservices/agency`: one error, on a module whose typecheck was clean
|
|
13
13
|
* before it migrated. 34 files under `libs/` export a type built this way.
|
|
14
14
|
*
|
|
15
|
-
* ## Why
|
|
15
|
+
* ## Why it re-exports a SUBPATH of sentinel rather than `vitest-mock-extended`
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* Because a bare `vitest-mock-extended` does not resolve from here, which was measured with
|
|
18
|
+
* `--traceResolution` after the first attempt emptied the module out:
|
|
19
|
+
*
|
|
20
|
+
* Resolving module 'vitest-mock-extended' from '…/agency/node_modules/@hublo/sentinel/types/…'
|
|
21
|
+
* Module name 'vitest-mock-extended' was not resolved.
|
|
22
|
+
*
|
|
23
|
+
* The module's `tsconfig.spec.json` names this file by PATH, so TypeScript keeps the symlink path
|
|
24
|
+
* pnpm installed and walks up from there: `…/sentinel/node_modules`, then the module's, then the
|
|
25
|
+
* workspace's. Sentinel's own dependency sits in the store beside its real directory, and none of
|
|
26
|
+
* those three is it. A file reached by RESOLUTION is realpathed into the store instead, and its
|
|
27
|
+
* dependencies are found: same trace, `@hublo/sentinel/test/tools/msw` → `msw` resolved.
|
|
28
|
+
*
|
|
29
|
+
* `skipLibCheck` is on in every module here, so the failed `export *` was silent and what surfaced
|
|
30
|
+
* was an empty module: 18 × `Module '"jest-mock-extended"' has no exported member 'mockDeep'`.
|
|
31
|
+
*
|
|
32
|
+
* ## Why THIS subpath
|
|
33
|
+
*
|
|
34
|
+
* `@hublo/sentinel/test/tools/mock-extended` is the very module the runtime alias points at, so the
|
|
35
|
+
* types a spec compiles against are the ones it runs against, down to the lifecycle helpers the
|
|
36
|
+
* fork adds. Re-exporting `vitest-mock-extended` directly would describe something adjacent to what
|
|
37
|
+
* executes.
|
|
21
38
|
*
|
|
22
39
|
* ## Why it does not couple modules to each other
|
|
23
40
|
*
|
|
@@ -31,5 +48,5 @@
|
|
|
31
48
|
* module at a time; this keeps the test role the same.
|
|
32
49
|
*/
|
|
33
50
|
declare module 'jest-mock-extended' {
|
|
34
|
-
export * from '
|
|
51
|
+
export * from '@hublo/sentinel/test/tools/mock-extended'
|
|
35
52
|
}
|