@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.
@@ -16,7 +16,7 @@ import {
16
16
  registerAdapters,
17
17
  resolve,
18
18
  resolveBin
19
- } from "../chunk-YHGCBMKJ.js";
19
+ } from "../chunk-BIDHDUUZ.js";
20
20
  import {
21
21
  WORKSPACE_ROOT_MARKER,
22
22
  ensureWorkspacePrep,
@@ -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 bare = written.startsWith("jest.") ? written.slice("jest.".length) : written;
7271
- if (!SPY_TYPES.has(bare)) return;
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: `MockInstance<(${parameters}) => ${returns}>`
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
- typeImports: edits.length > 0 ? ["MockInstance"] : []
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 mockExtendedTypesPath(cwd) {
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), MOCK_EXTENDED_TYPES);
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/${MOCK_EXTENDED_TYPES}`;
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 ? withMockExtendedTypes(cwd, files ?? config?.files) : void 0;
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 withMockExtendedTypes(cwd, files) {
8270
+ function withShippedTypes(cwd, files) {
8265
8271
  const entries = Array.isArray(files) ? files : [];
8266
- if (entries.some((entry) => entry.endsWith(MOCK_EXTENDED_TYPES))) return void 0;
8267
- return [...entries, mockExtendedTypesPath(cwd)];
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 MOCK_EXTENDED_TYPES = "types/mock-extended.d.ts";
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
@@ -7,7 +7,7 @@ import {
7
7
  registerAdapters,
8
8
  resolve,
9
9
  setDefaultRunner
10
- } from "./chunk-YHGCBMKJ.js";
10
+ } from "./chunk-BIDHDUUZ.js";
11
11
  import "./chunk-WLFE5RUU.js";
12
12
  export {
13
13
  BaseAdapter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hublo/sentinel",
3
- "version": "1.4.0-alpha.13",
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']
@@ -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>() -> CalledWithMock<…> extends jest.Mock
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 here, and not in the module
15
+ * ## Why it re-exports a SUBPATH of sentinel rather than `vitest-mock-extended`
16
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'`.
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 'vitest-mock-extended'
51
+ export * from '@hublo/sentinel/test/tools/mock-extended'
35
52
  }