@hublo/sentinel 1.4.0-alpha.1 → 1.4.0-alpha.2

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.
@@ -5456,12 +5456,12 @@ function rewriteVitestScript(command) {
5456
5456
  });
5457
5457
  }
5458
5458
  function testTarget(options) {
5459
- const { configFiles, outputs, dependsOn, configurations } = options;
5459
+ const { name, configFiles, outputs, dependsOn, configurations } = options;
5460
5460
  const cached = outputs !== void 0 && outputs.length > 0;
5461
5461
  return {
5462
- [TEST_SCRIPT_NAME]: {
5462
+ [name ?? TEST_SCRIPT_NAME]: {
5463
5463
  cache: cached,
5464
- inputs: ["default", "^default", ...configFiles.map((name) => `{projectRoot}/${name}`)],
5464
+ inputs: ["default", "^default", ...configFiles.map((name2) => `{projectRoot}/${name2}`)],
5465
5465
  ...cached ? { outputs: [...outputs] } : {},
5466
5466
  ...dependsOn !== void 0 && dependsOn.length > 0 ? { dependsOn: [...dependsOn] } : {},
5467
5467
  ...configurations !== void 0 && Object.keys(configurations).length > 0 ? { configurations: { ...configurations } } : {}
@@ -5470,12 +5470,12 @@ function testTarget(options) {
5470
5470
  }
5471
5471
 
5472
5472
  // src/roles/test/jest-migration.ts
5473
- import { existsSync as existsSync26, readFileSync as readFileSync26 } from "fs";
5474
- import { join as join32 } from "path";
5473
+ import { existsSync as existsSync28, readFileSync as readFileSync28 } from "fs";
5474
+ import { join as join35 } from "path";
5475
5475
 
5476
5476
  // src/roles/test/codemod/migrate-module.ts
5477
- import { readdirSync as readdirSync5, readFileSync as readFileSync23, statSync as statSync5 } from "fs";
5478
- import { join as join29 } from "path";
5477
+ import { readdirSync as readdirSync6, readFileSync as readFileSync25, statSync as statSync6 } from "fs";
5478
+ import { join as join31 } from "path";
5479
5479
  import { parseSync as parseSync8 } from "oxc-parser";
5480
5480
 
5481
5481
  // src/roles/test/read-adoption.ts
@@ -5933,6 +5933,104 @@ function rewriteEmptyMockImplementation(source, program) {
5933
5933
  return { source: result, rewritten: insertAt.length };
5934
5934
  }
5935
5935
 
5936
+ // src/roles/test/codemod/fake-timer-options.ts
5937
+ var MEMBER_TYPES6 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
5938
+ var TIMER_CALLS = /* @__PURE__ */ new Set(["useFakeTimers"]);
5939
+ function timerOptions(node) {
5940
+ if (node.type !== "CallExpression") return void 0;
5941
+ const callee = node.callee;
5942
+ if (callee === void 0 || !MEMBER_TYPES6.has(callee.type)) return void 0;
5943
+ const object = callee.object;
5944
+ const property = callee.property;
5945
+ if (object?.type !== "Identifier") return void 0;
5946
+ if (object.name !== "jest" && object.name !== "vi") return void 0;
5947
+ if (property?.type !== "Identifier" || !TIMER_CALLS.has(property.name)) return void 0;
5948
+ const [options] = node.arguments ?? [];
5949
+ return options?.type === "ObjectExpression" ? options : void 0;
5950
+ }
5951
+ var FAKEABLE = [
5952
+ "setTimeout",
5953
+ "clearTimeout",
5954
+ "setImmediate",
5955
+ "clearImmediate",
5956
+ "setInterval",
5957
+ "clearInterval",
5958
+ "Date",
5959
+ "hrtime",
5960
+ "requestAnimationFrame",
5961
+ "cancelAnimationFrame",
5962
+ "requestIdleCallback",
5963
+ "cancelIdleCallback",
5964
+ "performance"
5965
+ ];
5966
+ function stringLiterals2(node) {
5967
+ if (node.type !== "ArrayExpression") return void 0;
5968
+ const values = [];
5969
+ for (const element of node.elements ?? []) {
5970
+ if (element?.type !== "Literal" || typeof element.value !== "string") return void 0;
5971
+ values.push(element.value);
5972
+ }
5973
+ return values;
5974
+ }
5975
+ function rewriteFakeTimerOptions(source, program) {
5976
+ const edits = [];
5977
+ const translated = [];
5978
+ const refusals = [];
5979
+ walk(program, (node) => {
5980
+ const options = timerOptions(node);
5981
+ if (options === void 0) return;
5982
+ for (const property of options.properties ?? []) {
5983
+ const key = property.key;
5984
+ if (key?.type !== "Identifier") continue;
5985
+ const value = property.value;
5986
+ if (value === void 0) continue;
5987
+ const span = { start: property.start, end: property.end };
5988
+ if (key.name === "advanceTimers") {
5989
+ if (value.type === "Literal" && value.value === true) {
5990
+ edits.push({ ...span, text: "shouldAdvanceTime: true" });
5991
+ translated.push("advanceTimers -> shouldAdvanceTime");
5992
+ continue;
5993
+ }
5994
+ if (value.type === "Literal" && typeof value.value === "number") {
5995
+ edits.push({
5996
+ ...span,
5997
+ text: `shouldAdvanceTime: true, advanceTimeDelta: ${value.value}`
5998
+ });
5999
+ translated.push("advanceTimers -> shouldAdvanceTime + advanceTimeDelta");
6000
+ continue;
6001
+ }
6002
+ refusals.push({
6003
+ binding: "advanceTimers",
6004
+ reason: "`useFakeTimers({ advanceTimers })` translates to sinon's `shouldAdvanceTime`, but only when the value can be read here: this one is neither `true` nor a number, and inventing a delta would be inventing behaviour."
6005
+ });
6006
+ continue;
6007
+ }
6008
+ if (key.name === "doNotFake") {
6009
+ const excluded = stringLiterals2(value);
6010
+ if (excluded === void 0) {
6011
+ refusals.push({
6012
+ binding: "doNotFake",
6013
+ reason: "`useFakeTimers({ doNotFake })` becomes sinon's `toFake`, which is its COMPLEMENT, so the list has to be readable here. This one is not a list of string literals."
6014
+ });
6015
+ continue;
6016
+ }
6017
+ const kept = FAKEABLE.filter((name) => !excluded.includes(name));
6018
+ edits.push({
6019
+ ...span,
6020
+ text: `toFake: [${kept.map((name) => `'${name}'`).join(", ")}]`
6021
+ });
6022
+ translated.push("doNotFake -> toFake");
6023
+ }
6024
+ }
6025
+ });
6026
+ if (edits.length === 0) return { source, translated, refusals };
6027
+ let result = source;
6028
+ for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
6029
+ result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
6030
+ }
6031
+ return { source: result, translated, refusals };
6032
+ }
6033
+
5936
6034
  // src/roles/test/codemod/scope.ts
5937
6035
  function readIdentifiers(node, runsLater) {
5938
6036
  const names = [];
@@ -6098,7 +6196,7 @@ function hoistCapturedBindings(source, program, factories, bindings, imported) {
6098
6196
  if (hoistable(name, bindings, imported)) continue;
6099
6197
  refusals.push({
6100
6198
  binding: name,
6101
- reason: bindings.get(name) === void 0 ? `it is declared without an initializer, so there is nothing for vi.hoisted to carry` : `its initializer reads something imported, so it cannot live above the import graph`
6199
+ reason: bindings.get(name) === void 0 ? `\`${name}\` cannot move into vi.hoisted: it is declared without an initializer, so there is nothing for vi.hoisted to carry` : `\`${name}\` cannot move into vi.hoisted: its initializer reads something imported, so it cannot live above the import graph`
6102
6200
  });
6103
6201
  }
6104
6202
  const declarations = simpleDeclarations(program);
@@ -6156,7 +6254,7 @@ function hoistCapturedBindings(source, program, factories, bindings, imported) {
6156
6254
 
6157
6255
  // src/roles/test/codemod/hook-return-value.ts
6158
6256
  var HOOKS = /* @__PURE__ */ new Set(["beforeEach", "afterEach", "beforeAll", "afterAll"]);
6159
- var MEMBER_TYPES6 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6257
+ var MEMBER_TYPES7 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6160
6258
  function hookCallback(node) {
6161
6259
  if (node.type !== "CallExpression") return void 0;
6162
6260
  const callee = node.callee;
@@ -6167,7 +6265,7 @@ function hookCallback(node) {
6167
6265
  function configuresAMock(body) {
6168
6266
  if (body.type !== "CallExpression") return false;
6169
6267
  const callee = body.callee;
6170
- if (callee === void 0 || !MEMBER_TYPES6.has(callee.type)) return false;
6268
+ if (callee === void 0 || !MEMBER_TYPES7.has(callee.type)) return false;
6171
6269
  const property = callee.property;
6172
6270
  return property?.type === "Identifier" && /^mock[A-Z]/.test(property.name);
6173
6271
  }
@@ -6352,7 +6450,37 @@ ${source}`;
6352
6450
  }
6353
6451
 
6354
6452
  // src/roles/test/codemod/named-rules.ts
6355
- var MEMBER_TYPES7 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6453
+ import { existsSync as existsSync24, readFileSync as readFileSync22 } from "fs";
6454
+ import { createRequire as createRequire4 } from "module";
6455
+ import { dirname as dirname4, join as join28 } from "path";
6456
+ function isCommonJsPackage(fromFile, specifier) {
6457
+ if (specifier.startsWith(".") || specifier.startsWith("/")) return false;
6458
+ let resolved;
6459
+ try {
6460
+ resolved = createRequire4(fromFile).resolve(specifier);
6461
+ } catch {
6462
+ return false;
6463
+ }
6464
+ if (!resolved.includes(`${"/"}node_modules${"/"}`)) return false;
6465
+ if (resolved.endsWith(".mjs")) return false;
6466
+ if (resolved.endsWith(".cjs")) return true;
6467
+ let dir = dirname4(resolved);
6468
+ for (let depth = 0; depth < 12; depth += 1) {
6469
+ const manifest = join28(dir, "package.json");
6470
+ if (existsSync24(manifest)) {
6471
+ try {
6472
+ return JSON.parse(readFileSync22(manifest, "utf8")).type !== "module";
6473
+ } catch {
6474
+ return false;
6475
+ }
6476
+ }
6477
+ const parent = dirname4(dir);
6478
+ if (parent === dir) break;
6479
+ dir = parent;
6480
+ }
6481
+ return false;
6482
+ }
6483
+ var MEMBER_TYPES8 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6356
6484
  var ASYNC_REPLACEMENTS = {
6357
6485
  requireActual: "importActual",
6358
6486
  requireMock: "importMock"
@@ -6362,7 +6490,7 @@ function mockedSpecifiers(program) {
6362
6490
  walk(program, (node) => {
6363
6491
  if (node.type !== "CallExpression") return;
6364
6492
  const callee = node.callee;
6365
- if (callee === void 0 || !MEMBER_TYPES7.has(callee.type)) return;
6493
+ if (callee === void 0 || !MEMBER_TYPES8.has(callee.type)) return;
6366
6494
  const object = callee.object;
6367
6495
  const property = callee.property;
6368
6496
  if (object?.type !== "Identifier") return;
@@ -6396,6 +6524,33 @@ function bareRequire(node) {
6396
6524
  const args = node.arguments ?? [];
6397
6525
  return args.length === 1 && args[0] !== void 0;
6398
6526
  }
6527
+ function mockFactoryReturn(node) {
6528
+ if (node.type !== "CallExpression") return void 0;
6529
+ const callee = node.callee;
6530
+ if (callee === void 0 || !MEMBER_TYPES8.has(callee.type)) return void 0;
6531
+ const object = callee.object;
6532
+ const property = callee.property;
6533
+ if (object?.type !== "Identifier") return void 0;
6534
+ if (!["jest", "vi", "vitest"].includes(object.name)) return void 0;
6535
+ if (property?.name !== "mock") return void 0;
6536
+ const factory = (node.arguments ?? [])[1];
6537
+ if (factory?.type !== "ArrowFunctionExpression" && factory?.type !== "FunctionExpression") {
6538
+ return void 0;
6539
+ }
6540
+ let body = factory.body;
6541
+ while (body?.type === "ParenthesizedExpression") body = body.expression;
6542
+ if (body === void 0) return void 0;
6543
+ if (body.type !== "BlockStatement") return body;
6544
+ const statements = body.body ?? [];
6545
+ if (statements.length !== 1) return void 0;
6546
+ const only = statements[0];
6547
+ if (only?.type !== "ReturnStatement") return void 0;
6548
+ let argument = only.argument;
6549
+ while (argument?.type === "ParenthesizedExpression") {
6550
+ argument = argument.expression;
6551
+ }
6552
+ return argument;
6553
+ }
6399
6554
  function ownedCall(node) {
6400
6555
  if (node.type !== "CallExpression") return void 0;
6401
6556
  const callee = node.callee;
@@ -6421,11 +6576,36 @@ function applyEdits(source, edits) {
6421
6576
  }
6422
6577
  return result;
6423
6578
  }
6424
- function applyNamedRules(source, program) {
6579
+ function applyNamedRules(source, program, fileName = "") {
6425
6580
  const edits = [];
6426
6581
  const applied = /* @__PURE__ */ new Set();
6427
6582
  const asyncify = /* @__PURE__ */ new Set();
6428
6583
  const mocked = mockedSpecifiers(program);
6584
+ const wholeStatement = /* @__PURE__ */ new Set();
6585
+ walk(program, (node) => {
6586
+ if (node.type !== "ExpressionStatement") return;
6587
+ const expression = node.expression;
6588
+ if (expression !== void 0) wholeStatement.add(expression);
6589
+ });
6590
+ const handedToTheRunnerAsAModule = /* @__PURE__ */ new Set();
6591
+ walk(program, (node) => {
6592
+ const returned = mockFactoryReturn(node);
6593
+ if (returned !== void 0) handedToTheRunnerAsAModule.add(returned);
6594
+ });
6595
+ const readsANameOffIt = /* @__PURE__ */ new Set();
6596
+ walk(program, (node) => {
6597
+ if (node.type === "VariableDeclarator") {
6598
+ const id = node.id;
6599
+ const init = node.init ?? void 0;
6600
+ if (id !== void 0 && id.type !== "Identifier" && init !== void 0) {
6601
+ readsANameOffIt.add(init);
6602
+ }
6603
+ return;
6604
+ }
6605
+ if (!MEMBER_TYPES8.has(node.type)) return;
6606
+ const object = node.object;
6607
+ if (object !== void 0) readsANameOffIt.add(object);
6608
+ });
6429
6609
  walkWithEnclosingFunction(program, void 0, (node, enclosing) => {
6430
6610
  const member = ownedCall(node);
6431
6611
  if (member === void 0 && !bareRequire(node)) return;
@@ -6455,10 +6635,13 @@ function applyNamedRules(source, program) {
6455
6635
  return;
6456
6636
  }
6457
6637
  if (bareRequire(node)) {
6638
+ const [specifier] = node.arguments ?? [];
6639
+ const literal = specifier?.type === "StringLiteral" || specifier?.type === "Literal" ? String(specifier.value) : void 0;
6640
+ const interop = literal !== void 0 && !readsANameOffIt.has(node) && !handedToTheRunnerAsAModule.has(node) && !wholeStatement.has(node) && isCommonJsPackage(fileName, literal) ? ".default" : "";
6458
6641
  edits.push({
6459
6642
  start: node.start,
6460
6643
  end: node.end,
6461
- text: `(await import(${argumentText(source, node)}))`
6644
+ text: wholeStatement.has(node) ? `await import(${argumentText(source, node)});` : `(await import(${argumentText(source, node)}))${interop}`
6462
6645
  });
6463
6646
  applied.add("require");
6464
6647
  if (enclosing !== void 0 && enclosing.async !== true) asyncify.add(enclosing);
@@ -6490,13 +6673,13 @@ function applyNamedRules(source, program) {
6490
6673
  }
6491
6674
 
6492
6675
  // src/roles/test/codemod/isolate-modules.ts
6493
- var MEMBER_TYPES8 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6676
+ var MEMBER_TYPES9 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6494
6677
  var ISOLATORS = /* @__PURE__ */ new Set(["isolateModules", "isolateModulesAsync"]);
6495
6678
  var FUNCTIONS2 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression"]);
6496
6679
  function isolatingCall(node) {
6497
6680
  if (node.type !== "CallExpression") return void 0;
6498
6681
  const callee = node.callee;
6499
- if (callee === void 0 || !MEMBER_TYPES8.has(callee.type)) return void 0;
6682
+ if (callee === void 0 || !MEMBER_TYPES9.has(callee.type)) return void 0;
6500
6683
  const object = callee.object;
6501
6684
  const property = callee.property;
6502
6685
  if (object?.type !== "Identifier" || object.name !== "jest") return void 0;
@@ -6507,7 +6690,7 @@ function isolatingCall(node) {
6507
6690
  function unusableIsolatingCall(node) {
6508
6691
  if (node.type !== "CallExpression") return void 0;
6509
6692
  const callee = node.callee;
6510
- if (callee === void 0 || !MEMBER_TYPES8.has(callee.type)) return void 0;
6693
+ if (callee === void 0 || !MEMBER_TYPES9.has(callee.type)) return void 0;
6511
6694
  const object = callee.object;
6512
6695
  const property = callee.property;
6513
6696
  if (object?.type !== "Identifier" || object.name !== "jest") return void 0;
@@ -6558,11 +6741,11 @@ ${indent}await (${callback})()`
6558
6741
  }
6559
6742
 
6560
6743
  // src/roles/test/codemod/mock-as-value.ts
6561
- var MEMBER_TYPES9 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6744
+ var MEMBER_TYPES10 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6562
6745
  function isViMockCall(node) {
6563
6746
  if (node?.type !== "CallExpression") return false;
6564
6747
  const callee = node.callee;
6565
- if (callee === void 0 || !MEMBER_TYPES9.has(callee.type)) return false;
6748
+ if (callee === void 0 || !MEMBER_TYPES10.has(callee.type)) return false;
6566
6749
  const object = callee.object;
6567
6750
  const property = callee.property;
6568
6751
  return object?.type === "Identifier" && object.name === "vi" && property?.type === "Identifier" && (property.name === "mock" || property.name === "doMock");
@@ -6587,7 +6770,7 @@ function rewriteMockAsValue(source, program) {
6587
6770
  const memberUses = /* @__PURE__ */ new Map();
6588
6771
  const otherUses = /* @__PURE__ */ new Map();
6589
6772
  walk(program, (node) => {
6590
- if (MEMBER_TYPES9.has(node.type)) {
6773
+ if (MEMBER_TYPES10.has(node.type)) {
6591
6774
  const object = node.object;
6592
6775
  if (object?.type === "Identifier" && bindings.has(object.name)) {
6593
6776
  const list = memberUses.get(object.name) ?? [];
@@ -6633,12 +6816,12 @@ function rewriteMockAsValue(source, program) {
6633
6816
  }
6634
6817
 
6635
6818
  // src/roles/test/codemod/mock-factory-default.ts
6636
- var MEMBER_TYPES10 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6819
+ var MEMBER_TYPES11 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6637
6820
  var BINDING = "sentinelMockedModule";
6638
6821
  function mockCall(node) {
6639
6822
  if (node.type !== "CallExpression") return void 0;
6640
6823
  const callee = node.callee;
6641
- if (callee === void 0 || !MEMBER_TYPES10.has(callee.type)) return void 0;
6824
+ if (callee === void 0 || !MEMBER_TYPES11.has(callee.type)) return void 0;
6642
6825
  const property = callee.property;
6643
6826
  if (property?.type !== "Identifier" || property.name !== "mock") return void 0;
6644
6827
  const object = callee.object;
@@ -6657,7 +6840,7 @@ function returnedExpression(factory) {
6657
6840
  let body = factory.body;
6658
6841
  while (body?.type === "ParenthesizedExpression") body = body.expression;
6659
6842
  if (body === void 0) return void 0;
6660
- if (body.type !== "BlockStatement") return { node: body, isObject: false };
6843
+ if (body.type !== "BlockStatement") return { node: body, isObject: false, concise: true };
6661
6844
  const statements = body.body ?? [];
6662
6845
  if (statements.length !== 1) return void 0;
6663
6846
  const only = statements[0];
@@ -6681,6 +6864,20 @@ function returnedObject(factory) {
6681
6864
  }
6682
6865
  return void 0;
6683
6866
  }
6867
+ function isModuleNamespace(node) {
6868
+ let current = node;
6869
+ while (current?.type === "ParenthesizedExpression") {
6870
+ current = current.expression;
6871
+ }
6872
+ if (current?.type === "AwaitExpression") {
6873
+ let argument = current.argument;
6874
+ while (argument?.type === "ParenthesizedExpression") {
6875
+ argument = argument.expression;
6876
+ }
6877
+ current = argument;
6878
+ }
6879
+ return current?.type === "ImportExpression";
6880
+ }
6684
6881
  function namesDefault(object) {
6685
6882
  return (object.properties ?? []).some((property) => {
6686
6883
  const key = property.key;
@@ -6698,12 +6895,14 @@ function rewriteMockFactoryDefault(source, program, defaultImported) {
6698
6895
  if (returned === void 0) return;
6699
6896
  if (returned.isObject && namesDefault(returned.node)) return;
6700
6897
  if (returned.isObject && !defaultImported.has(call.specifier)) return;
6898
+ if (!returned.isObject && isModuleNamespace(returned.node)) return;
6701
6899
  if (!returned.isObject) {
6702
6900
  const expression = source.slice(returned.node.start, returned.node.end);
6703
6901
  edits.push({
6704
6902
  start: returned.node.start,
6705
6903
  end: returned.node.end,
6706
- literal: `{ default: ${expression} }`,
6904
+ // Parenthesised when it is the arrow's own body, or it is read as a block, not an object.
6905
+ literal: returned.concise === true ? `({ default: ${expression} })` : `{ default: ${expression} }`,
6707
6906
  specifier: call.specifier,
6708
6907
  wrap: false
6709
6908
  });
@@ -6736,7 +6935,7 @@ function rewriteMockFactoryDefault(source, program, defaultImported) {
6736
6935
  // src/roles/test/codemod/propagate-await.ts
6737
6936
  import { parseSync as parseSync5 } from "oxc-parser";
6738
6937
  var FUNCTIONS3 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration"]);
6739
- var MEMBER_TYPES11 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6938
+ var MEMBER_TYPES12 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
6740
6939
  var ALREADY_A_PROMISE = /* @__PURE__ */ new Set(["then", "catch", "finally"]);
6741
6940
  function asyncFunctionNames(program) {
6742
6941
  const names = /* @__PURE__ */ new Set();
@@ -6810,7 +7009,7 @@ function alreadyHandled(call, parent) {
6810
7009
  if (parent === void 0) return false;
6811
7010
  if (parent.type === "AwaitExpression") return true;
6812
7011
  if (parent.type === "ReturnStatement" || parent.type === "ArrowFunctionExpression") return true;
6813
- if (MEMBER_TYPES11.has(parent.type) && parent.object === call) {
7012
+ if (MEMBER_TYPES12.has(parent.type) && parent.object === call) {
6814
7013
  const property = parent.property;
6815
7014
  return property?.type === "Identifier" && ALREADY_A_PROMISE.has(property.name);
6816
7015
  }
@@ -6859,6 +7058,105 @@ function propagateAwait(fileName, source, original, elsewhere = /* @__PURE__ */
6859
7058
  return { source: current, rewritten };
6860
7059
  }
6861
7060
 
7061
+ // src/roles/test/codemod/reexport-barrel-mock.ts
7062
+ import { readFileSync as readFileSync23, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
7063
+ import { createRequire as createRequire5 } from "module";
7064
+ import { dirname as dirname5, join as join29 } from "path";
7065
+ var MEMBER_TYPES13 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
7066
+ var PACKAGE_SUBPATH = /^(@[^/]+\/[^/]+|[^@.][^/]*)\/(.+)$/;
7067
+ var PURE_REEXPORT = /^module\.exports\s*=\s*require\(\s*(['"])([^'"]+)\1\s*\)\s*;?$/;
7068
+ function mockCalls(program) {
7069
+ const found = [];
7070
+ walk(program, (node) => {
7071
+ if (node.type !== "CallExpression") return;
7072
+ const callee = node.callee;
7073
+ if (callee === void 0 || !MEMBER_TYPES13.has(callee.type)) return;
7074
+ const object = callee.object;
7075
+ const property = callee.property;
7076
+ if (object?.type !== "Identifier") return;
7077
+ if (object.name !== "vi" && object.name !== "jest") return;
7078
+ if (property?.name !== "mock") return;
7079
+ const first = (node.arguments ?? [])[0];
7080
+ if (first?.type !== "Literal" || typeof first.value !== "string") return;
7081
+ found.push({
7082
+ specifier: first.value,
7083
+ start: node.start,
7084
+ end: node.end,
7085
+ literalStart: first.start,
7086
+ literalEnd: first.end
7087
+ });
7088
+ });
7089
+ return found;
7090
+ }
7091
+ function packageRoot(fromFile, name) {
7092
+ try {
7093
+ return dirname5(createRequire5(fromFile).resolve(`${name}/package.json`));
7094
+ } catch {
7095
+ return void 0;
7096
+ }
7097
+ }
7098
+ function resolveFrom(fromFile, specifier) {
7099
+ try {
7100
+ return createRequire5(fromFile).resolve(specifier);
7101
+ } catch {
7102
+ return void 0;
7103
+ }
7104
+ }
7105
+ function barrelsFor(fromFile, name, target) {
7106
+ const root = packageRoot(fromFile, name);
7107
+ if (root === void 0) return [];
7108
+ let entries;
7109
+ try {
7110
+ entries = readdirSync4(root);
7111
+ } catch {
7112
+ return [];
7113
+ }
7114
+ const aliases = [];
7115
+ for (const entry of entries) {
7116
+ if (!entry.endsWith(".js")) continue;
7117
+ const path = join29(root, entry);
7118
+ let body;
7119
+ try {
7120
+ if (!statSync4(path).isFile()) continue;
7121
+ body = readFileSync23(path, "utf8");
7122
+ } catch {
7123
+ continue;
7124
+ }
7125
+ const match = PURE_REEXPORT.exec(body.trim());
7126
+ if (match === null) continue;
7127
+ if (resolveFrom(path, match[2]) !== target) continue;
7128
+ aliases.push(`${name}/${entry.slice(0, -".js".length)}`);
7129
+ }
7130
+ return aliases.sort();
7131
+ }
7132
+ function rewriteReexportBarrelMocks(fromFile, source, program) {
7133
+ const calls = mockCalls(program);
7134
+ if (calls.length === 0) return { source, added: [] };
7135
+ const alreadyMocked = new Set(calls.map((call) => call.specifier));
7136
+ const added = [];
7137
+ const insertions = [];
7138
+ for (const call of calls) {
7139
+ const parts = PACKAGE_SUBPATH.exec(call.specifier);
7140
+ if (parts === null) continue;
7141
+ const target = resolveFrom(fromFile, call.specifier);
7142
+ if (target === void 0) continue;
7143
+ for (const alias of barrelsFor(fromFile, parts[1], target)) {
7144
+ if (alreadyMocked.has(alias)) continue;
7145
+ alreadyMocked.add(alias);
7146
+ added.push(alias);
7147
+ const text = source.slice(call.start, call.literalStart) + `'${alias}'` + source.slice(call.literalEnd, call.end);
7148
+ insertions.push({ at: call.end, text: `
7149
+ ${text}` });
7150
+ }
7151
+ }
7152
+ if (insertions.length === 0) return { source, added: [] };
7153
+ let rewritten = source;
7154
+ for (const insertion of insertions.sort((a, b) => b.at - a.at)) {
7155
+ rewritten = rewritten.slice(0, insertion.at) + insertion.text + rewritten.slice(insertion.at);
7156
+ }
7157
+ return { source: rewritten, added };
7158
+ }
7159
+
6862
7160
  // src/roles/test/codemod/repoint-msw.ts
6863
7161
  import { parseSync as parseSync6 } from "oxc-parser";
6864
7162
  var SENTINEL_MSW_SERVER = "@hublo/sentinel/test/msw";
@@ -6896,6 +7194,43 @@ function repointMswServer(fileName, source) {
6896
7194
  return { source: rewritten, repointed: true };
6897
7195
  }
6898
7196
 
7197
+ // src/roles/test/codemod/spy-instance-type.ts
7198
+ var SPY_TYPES = /* @__PURE__ */ new Set(["SpyInstance"]);
7199
+ function typeNameOf(source, node) {
7200
+ const name = node.typeName;
7201
+ if (name === void 0) return void 0;
7202
+ return source.slice(name.start, name.end);
7203
+ }
7204
+ function rewriteSpyInstanceType(source, program) {
7205
+ const edits = [];
7206
+ walk(program, (node) => {
7207
+ if (node.type !== "TSTypeReference") return;
7208
+ const written = typeNameOf(source, node);
7209
+ if (written === void 0) return;
7210
+ const bare = written.startsWith("jest.") ? written.slice("jest.".length) : written;
7211
+ if (!SPY_TYPES.has(bare)) return;
7212
+ const args = (node.typeArguments ?? node.typeParameters)?.params;
7213
+ if (args === void 0 || args.length === 0) return;
7214
+ const text = (argument) => source.slice(argument.start, argument.end);
7215
+ const returns = text(args[0]);
7216
+ const parameters = args.length > 1 ? `...args: ${text(args[1])}` : "...args: any[]";
7217
+ edits.push({
7218
+ start: node.start,
7219
+ end: node.end,
7220
+ text: `MockInstance<(${parameters}) => ${returns}>`
7221
+ });
7222
+ });
7223
+ let result = source;
7224
+ for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
7225
+ result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
7226
+ }
7227
+ return {
7228
+ source: result,
7229
+ rewritten: edits.length,
7230
+ typeImports: edits.length > 0 ? ["MockInstance"] : []
7231
+ };
7232
+ }
7233
+
6899
7234
  // src/roles/test/codemod/migrate-file.ts
6900
7235
  function mockFactories(program) {
6901
7236
  const factories = [];
@@ -6928,46 +7263,61 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
6928
7263
  rulesApplied: [],
6929
7264
  mswRepointed: false
6930
7265
  };
7266
+ const unparsable = (step) => ({
7267
+ ...untouched,
7268
+ refusals: [
7269
+ {
7270
+ binding: step,
7271
+ reason: `this file no longer parses after the \`${step}\` step, so nothing was written for it. That is a defect in sentinel, not in the file: please report it with the file name.`
7272
+ }
7273
+ ]
7274
+ });
6931
7275
  const original = parse2(fileName, source);
6932
- if (original === void 0) return untouched;
7276
+ if (original === void 0) return unparsable("reading the file");
6933
7277
  const titles = rewriteEachTitles(source, original);
6934
7278
  const tables = rewriteEachTableRows(titles.source, original);
6935
7279
  const afterTables = parse2(fileName, tables.source);
6936
- if (afterTables === void 0) return untouched;
6937
- const ruled = applyNamedRules(tables.source, afterTables);
7280
+ if (afterTables === void 0) return unparsable("each table rows");
7281
+ const spyTypes = rewriteSpyInstanceType(tables.source, afterTables);
7282
+ const afterSpyTypes = parse2(fileName, spyTypes.source);
7283
+ if (afterSpyTypes === void 0) return unparsable("SpyInstance types");
7284
+ const ruled = applyNamedRules(spyTypes.source, afterSpyTypes, fileName);
6938
7285
  const renamed = renameJestToVitest(fileName, ruled.source);
6939
7286
  const afterRename = parse2(fileName, renamed.source);
6940
- if (afterRename === void 0) return untouched;
6941
- const mocks = rewriteMockAsValue(renamed.source, afterRename);
7287
+ if (afterRename === void 0) return unparsable("jest to vi rename");
7288
+ const fakeTimers = rewriteFakeTimerOptions(renamed.source, afterRename);
7289
+ const afterFakeTimers = parse2(fileName, fakeTimers.source);
7290
+ if (afterFakeTimers === void 0) return unparsable("fake timer options");
7291
+ const mocks = rewriteMockAsValue(fakeTimers.source, afterFakeTimers);
6942
7292
  const afterMocks = parse2(fileName, mocks.source);
6943
- if (afterMocks === void 0) return untouched;
7293
+ if (afterMocks === void 0) return unparsable("mock as a value");
6944
7294
  const constructible = rewriteConstructibleMocks(
6945
7295
  mocks.source,
6946
7296
  afterMocks,
6947
7297
  mockFactories(afterMocks)
6948
7298
  );
6949
7299
  const afterConstructible = parse2(fileName, constructible.source);
6950
- if (afterConstructible === void 0) return untouched;
7300
+ if (afterConstructible === void 0) return unparsable("constructible mocks");
6951
7301
  const factoryDefault = rewriteMockFactoryDefault(
6952
7302
  constructible.source,
6953
7303
  afterConstructible,
6954
7304
  defaultImported
6955
7305
  );
6956
7306
  const beforeReturnValue = parse2(fileName, factoryDefault.source);
6957
- if (beforeReturnValue === void 0) return untouched;
7307
+ if (beforeReturnValue === void 0) return unparsable("mock factory default");
6958
7308
  const returnValue = rewriteAssertionReturnValue(factoryDefault.source, beforeReturnValue);
6959
7309
  const afterReturnValue = parse2(fileName, returnValue.source);
6960
- if (afterReturnValue === void 0) return untouched;
7310
+ if (afterReturnValue === void 0) return unparsable("assertion return value");
6961
7311
  const isolated = rewriteIsolateModules(returnValue.source, afterReturnValue);
6962
7312
  const afterIsolate = parse2(fileName, isolated.source);
6963
- if (afterIsolate === void 0) return untouched;
7313
+ if (afterIsolate === void 0) return unparsable("isolate modules");
6964
7314
  const emptyImplementations = rewriteEmptyMockImplementation(isolated.source, afterIsolate);
6965
7315
  const afterEmpty = parse2(fileName, emptyImplementations.source);
6966
- if (afterEmpty === void 0) return untouched;
7316
+ if (afterEmpty === void 0) return unparsable("empty mock implementation");
6967
7317
  const hooks = rewriteHookReturnValue(emptyImplementations.source, afterEmpty);
6968
7318
  const awaited = propagateAwait(fileName, hooks.source, original, asyncHelpers);
6969
7319
  const program = parse2(fileName, awaited.source);
6970
- if (program === void 0) return untouched;
7320
+ if (program === void 0) return unparsable("hook body");
6971
7321
  const hoist = hoistCapturedBindings(
6972
7322
  awaited.source,
6973
7323
  program,
@@ -6975,12 +7325,18 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
6975
7325
  moduleBindings(program),
6976
7326
  importedNames(program)
6977
7327
  );
6978
- const withImport = injectTypeImport(fileName, hoist.source, renamed.typeImports);
7328
+ const withImport = injectTypeImport(fileName, hoist.source, [
7329
+ ...renamed.typeImports,
7330
+ ...spyTypes.typeImports
7331
+ ]);
6979
7332
  const deep = normaliseDeepImports(withImport);
6980
7333
  const repointed = repointMswServer(fileName, deep.source);
7334
+ const finalProgram = parse2(fileName, repointed.source);
7335
+ if (finalProgram === void 0) return unparsable("msw server import");
7336
+ const barrels = rewriteReexportBarrelMocks(fileName, repointed.source, finalProgram);
6981
7337
  return {
6982
- source: repointed.source,
6983
- changed: repointed.source !== source,
7338
+ source: barrels.source,
7339
+ changed: barrels.source !== source,
6984
7340
  renamed: renamed.rewritten,
6985
7341
  hoisted: hoist.hoisted,
6986
7342
  unhandled: renamed.unhandled,
@@ -6993,9 +7349,13 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
6993
7349
  ...isolated.rewritten > 0 ? ["jest.isolateModules"] : [],
6994
7350
  ...emptyImplementations.rewritten > 0 ? ["empty mockImplementation"] : [],
6995
7351
  ...hooks.rewritten > 0 ? ["hook body, so it does not return its mock"] : [],
6996
- ...awaited.rewritten > 0 ? ["await propagated to a helper that became async"] : []
7352
+ ...spyTypes.rewritten > 0 ? ["SpyInstance<T> as a signature, not a return type"] : [],
7353
+ ...awaited.rewritten > 0 ? ["await propagated to a helper that became async"] : [],
7354
+ ...barrels.added.map((alias) => `mock carried to ${alias}, which re-exports the mocked file`),
7355
+ ...fakeTimers.translated
6997
7356
  ].sort(),
6998
7357
  refusals: [
7358
+ ...fakeTimers.refusals,
6999
7359
  ...hoist.refusals,
7000
7360
  ...mocks.refusals.map((refusal) => ({ binding: refusal.binding, reason: refusal.reason })),
7001
7361
  ...constructible.refusals,
@@ -7006,8 +7366,8 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
7006
7366
  }
7007
7367
 
7008
7368
  // src/roles/test/codemod/test-support-files.ts
7009
- import { existsSync as existsSync24, readFileSync as readFileSync22, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
7010
- import { dirname as dirname4, join as join28, relative as relative5, resolve as resolve4 } from "path";
7369
+ import { existsSync as existsSync25, readFileSync as readFileSync24, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
7370
+ import { dirname as dirname6, join as join30, relative as relative5, resolve as resolve4 } from "path";
7011
7371
  var SKIPPED = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".git", ".vite"]);
7012
7372
  var TEST_FILE2 = /\.(spec|test)\.[cm]?[jt]sx?$/;
7013
7373
  var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
@@ -7026,14 +7386,14 @@ function relativeSpecifiers(source) {
7026
7386
  return found;
7027
7387
  }
7028
7388
  function resolveSpecifier(fromFile, specifier) {
7029
- const base = resolve4(dirname4(fromFile), specifier);
7030
- if (existsSync24(base) && statSync4(base).isFile()) return base;
7389
+ const base = resolve4(dirname6(fromFile), specifier);
7390
+ if (existsSync25(base) && statSync5(base).isFile()) return base;
7031
7391
  for (const extension of EXTENSIONS) {
7032
- if (existsSync24(`${base}${extension}`)) return `${base}${extension}`;
7392
+ if (existsSync25(`${base}${extension}`)) return `${base}${extension}`;
7033
7393
  }
7034
7394
  for (const extension of EXTENSIONS) {
7035
- const index = join28(base, `index${extension}`);
7036
- if (existsSync24(index)) return index;
7395
+ const index = join30(base, `index${extension}`);
7396
+ if (existsSync25(index)) return index;
7037
7397
  }
7038
7398
  return void 0;
7039
7399
  }
@@ -7044,15 +7404,15 @@ function moduleSources(cwd) {
7044
7404
  const dir = stack.pop();
7045
7405
  let entries;
7046
7406
  try {
7047
- entries = readdirSync4(dir, { withFileTypes: true });
7407
+ entries = readdirSync5(dir, { withFileTypes: true });
7048
7408
  } catch {
7049
7409
  continue;
7050
7410
  }
7051
7411
  for (const entry of entries) {
7052
7412
  if (entry.isDirectory()) {
7053
- if (!SKIPPED.has(entry.name)) stack.push(join28(dir, entry.name));
7413
+ if (!SKIPPED.has(entry.name)) stack.push(join30(dir, entry.name));
7054
7414
  } else if (SOURCE_FILE.test(entry.name)) {
7055
- found.push(join28(dir, entry.name));
7415
+ found.push(join30(dir, entry.name));
7056
7416
  }
7057
7417
  }
7058
7418
  }
@@ -7060,12 +7420,24 @@ function moduleSources(cwd) {
7060
7420
  }
7061
7421
  function read(path) {
7062
7422
  try {
7063
- return readFileSync22(path, "utf8");
7423
+ return readFileSync24(path, "utf8");
7064
7424
  } catch {
7065
7425
  return void 0;
7066
7426
  }
7067
7427
  }
7068
- function listTestSupportFiles(cwd) {
7428
+ function setupFilesInside(cwd, named) {
7429
+ const kept = [];
7430
+ for (const raw of named) {
7431
+ const withoutToken = raw.replaceAll("<rootDir>/", "").replaceAll("<rootDir>", ".");
7432
+ const full = resolve4(cwd, withoutToken);
7433
+ const inside = relative5(cwd, full);
7434
+ if (inside.startsWith("..") || inside === "") continue;
7435
+ if (!existsSync25(full)) continue;
7436
+ kept.push(inside.replaceAll("\\", "/"));
7437
+ }
7438
+ return kept;
7439
+ }
7440
+ function listTestSupportFiles(cwd, setupFiles = []) {
7069
7441
  const sources = moduleSources(cwd);
7070
7442
  const contents = /* @__PURE__ */ new Map();
7071
7443
  for (const file of sources) {
@@ -7095,7 +7467,8 @@ function listTestSupportFiles(cwd) {
7095
7467
  queue.push(target);
7096
7468
  }
7097
7469
  }
7098
- return [...testOnly].filter((file) => /\brequire\s*\(/.test(contents.get(file) ?? "")).map((file) => relative5(cwd, file).replaceAll("\\", "/")).sort();
7470
+ const reachable = [...testOnly].filter((file) => /\brequire\s*\(/.test(contents.get(file) ?? "")).map((file) => relative5(cwd, file).replaceAll("\\", "/"));
7471
+ return [.../* @__PURE__ */ new Set([...reachable, ...setupFilesInside(cwd, setupFiles)])].sort();
7099
7472
  }
7100
7473
 
7101
7474
  // src/roles/test/codemod/migrate-module.ts
@@ -7105,7 +7478,7 @@ function parse3(source) {
7105
7478
  }
7106
7479
  function read2(path) {
7107
7480
  try {
7108
- return readFileSync23(path, "utf8");
7481
+ return readFileSync25(path, "utf8");
7109
7482
  } catch {
7110
7483
  return void 0;
7111
7484
  }
@@ -7117,16 +7490,16 @@ function defaultImportedSpecifiers(cwd) {
7117
7490
  const walk2 = (dir) => {
7118
7491
  let entries;
7119
7492
  try {
7120
- entries = readdirSync5(dir);
7493
+ entries = readdirSync6(dir);
7121
7494
  } catch {
7122
7495
  return;
7123
7496
  }
7124
7497
  for (const entry of entries) {
7125
7498
  if (SKIPPED2.has(entry)) continue;
7126
- const path = join29(dir, entry);
7499
+ const path = join31(dir, entry);
7127
7500
  let directory;
7128
7501
  try {
7129
- directory = statSync5(path).isDirectory();
7502
+ directory = statSync6(path).isDirectory();
7130
7503
  } catch {
7131
7504
  continue;
7132
7505
  }
@@ -7143,7 +7516,7 @@ function defaultImportedSpecifiers(cwd) {
7143
7516
  walk2(cwd);
7144
7517
  return found;
7145
7518
  }
7146
- function migrateModuleTests(cwd) {
7519
+ function migrateModuleTests(cwd, setupFiles = []) {
7147
7520
  const operations = [];
7148
7521
  const blockers = [];
7149
7522
  let files = 0;
@@ -7151,13 +7524,13 @@ function migrateModuleTests(cwd) {
7151
7524
  let hoisted = 0;
7152
7525
  const defaultImported = defaultImportedSpecifiers(cwd);
7153
7526
  const asyncHelpers = /* @__PURE__ */ new Set();
7154
- for (const relative7 of listTestSupportFiles(cwd)) {
7155
- const source = read2(join29(cwd, relative7));
7527
+ for (const relative7 of listTestSupportFiles(cwd, setupFiles)) {
7528
+ const source = read2(join31(cwd, relative7));
7156
7529
  if (source === void 0) {
7157
7530
  blockers.push({ file: relative7, reason: "this file could not be read" });
7158
7531
  continue;
7159
7532
  }
7160
- const migrated = migrateTestFile(join29(cwd, relative7), source, defaultImported);
7533
+ const migrated = migrateTestFile(join31(cwd, relative7), source, defaultImported);
7161
7534
  if (!migrated.changed) continue;
7162
7535
  const before = parse3(source);
7163
7536
  const after = parse3(migrated.source);
@@ -7172,12 +7545,12 @@ function migrateModuleTests(cwd) {
7172
7545
  operations.push({ kind: "write", path: relative7, contents: migrated.source });
7173
7546
  }
7174
7547
  for (const relative7 of listTestFiles(cwd)) {
7175
- const source = read2(join29(cwd, relative7));
7548
+ const source = read2(join31(cwd, relative7));
7176
7549
  if (source === void 0) {
7177
7550
  blockers.push({ file: relative7, reason: "this file could not be read" });
7178
7551
  continue;
7179
7552
  }
7180
- const migrated = migrateTestFile(join29(cwd, relative7), source, defaultImported, asyncHelpers);
7553
+ const migrated = migrateTestFile(join31(cwd, relative7), source, defaultImported, asyncHelpers);
7181
7554
  for (const member of migrated.unhandled) {
7182
7555
  blockers.push({
7183
7556
  file: `${relative7}:${member.line}`,
@@ -7185,10 +7558,7 @@ function migrateModuleTests(cwd) {
7185
7558
  });
7186
7559
  }
7187
7560
  for (const refusal of migrated.refusals) {
7188
- blockers.push({
7189
- file: relative7,
7190
- reason: `\`${refusal.binding}\` cannot move into vi.hoisted: ${refusal.reason}`
7191
- });
7561
+ blockers.push({ file: relative7, reason: refusal.reason });
7192
7562
  }
7193
7563
  if (!migrated.changed) continue;
7194
7564
  files++;
@@ -7200,11 +7570,12 @@ function migrateModuleTests(cwd) {
7200
7570
  }
7201
7571
 
7202
7572
  // src/roles/test/nest/generate-config.ts
7203
- import { readdirSync as readdirSync6, readFileSync as readFileSync24, statSync as statSync6 } from "fs";
7204
- import { join as join30, relative as relative6 } from "path";
7573
+ import { readdirSync as readdirSync7, readFileSync as readFileSync26, statSync as statSync7 } from "fs";
7574
+ import { join as join32, relative as relative6 } from "path";
7205
7575
  var NEST_TEST_PRESET_SPECIFIER = "@hublo/sentinel/test/nest";
7206
7576
  var SENTINEL_TEST_SETUP = "@hublo/sentinel/test/setup/nest";
7207
7577
  var SENTINEL_MSW_SETUP = "@hublo/sentinel/test/setup/msw-lifecycle";
7578
+ var SENTINEL_WORKSPACE_SETUP = "@hublo/sentinel/test/setup/workspace";
7208
7579
  var NEST_TEST_CONFIG_FILE = "vitest.config.mts";
7209
7580
  function hopsToRoot(cwd, workspaceRoot) {
7210
7581
  const up = relative6(cwd, workspaceRoot).replaceAll("\\", "/");
@@ -7235,16 +7606,16 @@ function readModuleTraits(cwd) {
7235
7606
  const walk2 = (dir) => {
7236
7607
  let entries;
7237
7608
  try {
7238
- entries = readdirSync6(dir);
7609
+ entries = readdirSync7(dir);
7239
7610
  } catch {
7240
7611
  return;
7241
7612
  }
7242
7613
  for (const entry of entries) {
7243
7614
  if (SKIPPED3.has(entry)) continue;
7244
- const path = join30(dir, entry);
7615
+ const path = join32(dir, entry);
7245
7616
  let directory;
7246
7617
  try {
7247
- directory = statSync6(path).isDirectory();
7618
+ directory = statSync7(path).isDirectory();
7248
7619
  } catch {
7249
7620
  continue;
7250
7621
  }
@@ -7255,7 +7626,7 @@ function readModuleTraits(cwd) {
7255
7626
  if (!/\.tsx?$/.test(entry)) continue;
7256
7627
  let source;
7257
7628
  try {
7258
- source = readFileSync24(path, "utf8");
7629
+ source = readFileSync26(path, "utf8");
7259
7630
  } catch {
7260
7631
  continue;
7261
7632
  }
@@ -7273,8 +7644,14 @@ function readModuleTraits(cwd) {
7273
7644
  }
7274
7645
  var withoutRootDir = (value) => value.replaceAll("<rootDir>/", "./").replaceAll("<rootDir>", ".");
7275
7646
  function setupBlock(reading, msw) {
7276
- const own = [...reading.setupFiles ?? [], ...reading.setupFilesAfterEnv ?? []].filter((file) => !file.endsWith("jest.setup.after.env.js")).map((file) => `'${withoutRootDir(file)}'`);
7277
- const sentinel = [`'${SENTINEL_TEST_SETUP}'`, ...msw ? [`'${SENTINEL_MSW_SETUP}'`] : []];
7647
+ const declared = [...reading.setupFiles ?? [], ...reading.setupFilesAfterEnv ?? []];
7648
+ const namedTheRootSetup = declared.some((file) => file.endsWith("jest.setup.after.env.js"));
7649
+ const own = declared.filter((file) => !file.endsWith("jest.setup.after.env.js")).map((file) => `'${withoutRootDir(file)}'`);
7650
+ const sentinel = [
7651
+ `'${SENTINEL_TEST_SETUP}'`,
7652
+ ...namedTheRootSetup ? [`'${SENTINEL_WORKSPACE_SETUP}'`] : [],
7653
+ ...msw ? [`'${SENTINEL_MSW_SETUP}'`] : []
7654
+ ];
7278
7655
  return ` setupFiles: [${[...sentinel, ...own].join(", ")}],`;
7279
7656
  }
7280
7657
  function envBlock(reading) {
@@ -7282,6 +7659,19 @@ function envBlock(reading) {
7282
7659
  const entries = reading.env.map((entry) => ` ${entry.name}: ${entry.value},`);
7283
7660
  return [" env: {", ...entries, " },"].join("\n");
7284
7661
  }
7662
+ function concurrencyBlock(cwd, reading, target) {
7663
+ const declared = reading.maxWorkers;
7664
+ if (declared !== void 0) {
7665
+ return declared === "1" ? ` fileParallelism: false,` : ` maxWorkers: ${declared},`;
7666
+ }
7667
+ const ci = projectTargets(cwd)?.[target]?.configurations?.ci;
7668
+ if (ci === void 0) return "";
7669
+ if (ci.runInBand === true) return ` fileParallelism: false,`;
7670
+ if (typeof ci.maxWorkers === "number") {
7671
+ return ci.maxWorkers === 1 ? ` fileParallelism: false,` : ` maxWorkers: ${ci.maxWorkers},`;
7672
+ }
7673
+ return "";
7674
+ }
7285
7675
  function timeoutBlock(reading) {
7286
7676
  return reading.testTimeout === void 0 ? "" : ` testTimeout: ${reading.testTimeout},`;
7287
7677
  }
@@ -7300,14 +7690,19 @@ function excludeBlock(reading) {
7300
7690
  const globs = reading.excludeGlobs.map((glob) => `'${glob}'`).join(", ");
7301
7691
  return ` exclude: [...configDefaults.exclude, ${globs}],`;
7302
7692
  }
7693
+ function isPackage(replacement) {
7694
+ return !replacement.startsWith(".") && !replacement.startsWith("/") && !replacement.toLowerCase().includes("<rootdir>");
7695
+ }
7303
7696
  function aliasBlock(reading) {
7304
7697
  if (reading.aliases === void 0 || reading.aliases.length === 0) return "";
7305
- const entries = reading.aliases.map(
7306
- (alias) => ` { find: /${alias.find.replaceAll("/", "\\\\/")}/, replacement: path.resolve(import.meta.dirname, '${withoutRootDir(alias.replacement)}') },`
7307
- );
7698
+ const entries = reading.aliases.map((alias) => {
7699
+ const pattern = alias.find.replaceAll(String.raw`\\`, "\\").replaceAll("/", String.raw`\/`);
7700
+ const target = isPackage(alias.replacement) ? `'${alias.replacement}'` : `path.resolve(import.meta.dirname, '${withoutRootDir(alias.replacement)}')`;
7701
+ return ` { find: /${pattern}/, replacement: ${target} },`;
7702
+ });
7308
7703
  return [" resolve: {", " alias: [", ...entries, " ],", " },"].join("\n");
7309
7704
  }
7310
- function generateNestTestConfig(cwd, reading) {
7705
+ function generateNestTestConfig(cwd, reading, target = "test") {
7311
7706
  if (reading.refusals.length > 0) return { refusals: reading.refusals };
7312
7707
  const workspaceRoot = findWorkspaceRoot(cwd);
7313
7708
  if (workspaceRoot === void 0) {
@@ -7320,6 +7715,7 @@ function generateNestTestConfig(cwd, reading) {
7320
7715
  setupBlock(reading, traits.msw),
7321
7716
  envBlock(reading),
7322
7717
  timeoutBlock(reading),
7718
+ concurrencyBlock(cwd, reading, target),
7323
7719
  globalSetupBlock(reading),
7324
7720
  includeBlock(reading),
7325
7721
  excludeBlock(reading),
@@ -7355,14 +7751,15 @@ ${overrides.join("\n")}
7355
7751
  }
7356
7752
 
7357
7753
  // src/roles/test/nest/read-jest-config.ts
7358
- import { existsSync as existsSync25, readFileSync as readFileSync25 } from "fs";
7359
- import { join as join31 } from "path";
7754
+ import { existsSync as existsSync26, readFileSync as readFileSync27 } from "fs";
7755
+ import { join as join33 } from "path";
7360
7756
  var CARRIED = [
7361
7757
  "testEnvironment",
7362
7758
  "coverageDirectory",
7363
7759
  "setupFilesAfterEnv",
7364
7760
  "setupFiles",
7365
7761
  "testTimeout",
7762
+ "maxWorkers",
7366
7763
  "globalSetup",
7367
7764
  "testMatch",
7368
7765
  "collectCoverageFrom",
@@ -7376,9 +7773,10 @@ var DISCARDED = [
7376
7773
  "transform",
7377
7774
  "extensionsToTreatAsEsm"
7378
7775
  ];
7379
- var STRING = String.raw`['"]([^'"]*)['"]`;
7776
+ var STRING = String.raw`['"\`]([^'"\`]*)['"\`]`;
7380
7777
  function readString(body, key) {
7381
- return new RegExp(String.raw`\b${key}\s*:\s*${STRING}`).exec(body)?.[1];
7778
+ const value = new RegExp(String.raw`\b${key}\s*:\s*${STRING}`).exec(body)?.[1];
7779
+ return value?.includes("${") === true ? void 0 : value;
7382
7780
  }
7383
7781
  function readNumber(body, key) {
7384
7782
  return new RegExp(String.raw`\b${key}\s*:\s*(\d+)`).exec(body)?.[1];
@@ -7486,19 +7884,73 @@ function configBody(source) {
7486
7884
  function declaredKeys(body) {
7487
7885
  const keys = [];
7488
7886
  let depth = 0;
7489
- for (const line of body.split("\n")) {
7490
- if (depth === 0) {
7491
- const key = /^\s*([A-Za-z_$][\w$]*)\s*:/.exec(line);
7492
- if (key !== null) keys.push(key[1]);
7887
+ let quote;
7888
+ let token = "";
7889
+ for (let i = 0; i < body.length; i += 1) {
7890
+ const char = body[i];
7891
+ if (quote !== void 0) {
7892
+ if (char === quote && body[i - 1] !== "\\") quote = void 0;
7893
+ continue;
7493
7894
  }
7494
- for (const char of line) {
7495
- if (char === "{" || char === "[") depth += 1;
7496
- else if (char === "}" || char === "]") depth -= 1;
7895
+ if (char === "'" || char === '"' || char === "`") {
7896
+ quote = char;
7897
+ continue;
7497
7898
  }
7899
+ if (char === "/" && body[i + 1] === "/") {
7900
+ const end = body.indexOf("\n", i);
7901
+ i = end === -1 ? body.length : end;
7902
+ token = "";
7903
+ continue;
7904
+ }
7905
+ if (char === "/" && body[i + 1] === "*") {
7906
+ const end = body.indexOf("*/", i + 2);
7907
+ i = end === -1 ? body.length : end + 1;
7908
+ token = "";
7909
+ continue;
7910
+ }
7911
+ if (char === "{" || char === "[" || char === "(") {
7912
+ depth += 1;
7913
+ token = "";
7914
+ continue;
7915
+ }
7916
+ if (char === "}" || char === "]" || char === ")") {
7917
+ depth -= 1;
7918
+ token = "";
7919
+ continue;
7920
+ }
7921
+ if (depth === 0 && char === ":") {
7922
+ const key = /([A-Za-z_$][\w$]*)\s*$/.exec(token)?.[1];
7923
+ if (key !== void 0) keys.push(key);
7924
+ token = "";
7925
+ continue;
7926
+ }
7927
+ if (depth === 0 && char === ",") {
7928
+ token = "";
7929
+ continue;
7930
+ }
7931
+ token += char;
7498
7932
  }
7499
7933
  return keys;
7500
7934
  }
7501
- function sideEffects(source) {
7935
+ function extendedConfig(source, cwd) {
7936
+ if (cwd === void 0) return void 0;
7937
+ for (const line of source.split("\n")) {
7938
+ const match = /^\s*import\s+(?!type\b)(\w+)\s+from\s+['"](\.[^'"]*)['"]/.exec(line);
7939
+ if (match === null) continue;
7940
+ const [, binding, specifier] = match;
7941
+ const candidates = [specifier, `${specifier}.ts`, `${specifier}.js`].map(
7942
+ (name) => name.replace(/^\.\//, "")
7943
+ );
7944
+ const file = candidates.find(
7945
+ (name) => /^jest[\w.-]*\.(ts|js)$/.test(name) && existsSync26(join33(cwd, name))
7946
+ );
7947
+ if (file === void 0) continue;
7948
+ if (!new RegExp(String.raw`\{\s*\.\.\.${binding}\b`).test(source)) continue;
7949
+ return { binding, file };
7950
+ }
7951
+ return void 0;
7952
+ }
7953
+ function sideEffects(source, extended) {
7502
7954
  const found = [];
7503
7955
  for (const line of source.split("\n")) {
7504
7956
  const valueImport = /^\s*import\s+(?!type\b)(.*)$/.exec(line);
@@ -7507,6 +7959,7 @@ function sideEffects(source) {
7507
7959
  ...(/\{([^}]*)\}/.exec(valueImport[1])?.[1] ?? "").split(","),
7508
7960
  /^\s*(\w+)\s*(?:,|from)/.exec(valueImport[1])?.[1] ?? ""
7509
7961
  ].map((name) => name.split(" as ").pop()?.trim() ?? "").filter((name) => name !== "");
7962
+ if (extended !== void 0 && bindings.includes(extended.binding)) continue;
7510
7963
  const used = bindings.some((name) => {
7511
7964
  const uses = source.match(new RegExp(String.raw`\b${name}\b`, "g")) ?? [];
7512
7965
  return uses.length > 1;
@@ -7515,16 +7968,17 @@ function sideEffects(source) {
7515
7968
  }
7516
7969
  return found;
7517
7970
  }
7518
- function readJestConfig(cwd) {
7519
- const file = JEST_CONFIG_FILES.find((name) => existsSync25(join31(cwd, name)));
7520
- if (file === void 0) return void 0;
7971
+ function readJestConfig(cwd, named) {
7972
+ const file = named ?? JEST_CONFIG_FILES.find((name) => existsSync26(join33(cwd, name)));
7973
+ if (file === void 0 || !existsSync26(join33(cwd, file))) return void 0;
7521
7974
  let source;
7522
7975
  try {
7523
- source = readFileSync25(join31(cwd, file), "utf8");
7976
+ source = readFileSync27(join33(cwd, file), "utf8");
7524
7977
  } catch {
7525
7978
  return { file, refusals: [`${file} could not be read`], notes: [] };
7526
7979
  }
7527
- const refusals = sideEffects(source);
7980
+ const extended = extendedConfig(source, cwd);
7981
+ const refusals = sideEffects(source, extended);
7528
7982
  const body = configBody(source);
7529
7983
  if (body === void 0) {
7530
7984
  return { file, refusals: [...refusals, "its config object could not be located"], notes: [] };
@@ -7554,39 +8008,108 @@ function readJestConfig(cwd) {
7554
8008
  `\`moduleNameMapper\` entry \`${find}\` pointed at another build of the package it matches, which is jest saying it could not load the package as it ships. Vitest can, so the mapping is dropped rather than carried`
7555
8009
  );
7556
8010
  }
8011
+ for (const alias of mapper.aliases) {
8012
+ if (/<rootdir>/i.test(alias.replacement) && !alias.replacement.includes("<rootDir>")) {
8013
+ notes.push(
8014
+ `\`moduleNameMapper\` entry \`${alias.find}\` writes \`${/<[Rr]oot[Dd]ir>/.exec(alias.replacement)?.[0] ?? "<RootDir>"}\`, and jest only substitutes \`<rootDir>\`. That mapping resolved to a literal directory under jest too, so it has never done anything; it is carried unchanged rather than quietly repaired`
8015
+ );
8016
+ }
8017
+ }
7557
8018
  const coverageFrom = readCoverageFrom(body);
7558
8019
  const env = readEnvironment(source);
7559
8020
  const globalSetup = readString(body, "globalSetup");
7560
8021
  const testTimeout = readNumber(body, "testTimeout");
8022
+ const maxWorkers = readNumber(body, "maxWorkers");
7561
8023
  const testMatch = readStringArray(body, "testMatch");
8024
+ const base = extended === void 0 ? void 0 : readJestConfig(cwd, extended.file);
8025
+ const inherited = base === void 0 ? {} : { ...base, refusals: [], notes: [] };
8026
+ for (const [jestKey, field] of [
8027
+ ["testPathIgnorePatterns", "excludeGlobs"],
8028
+ ["setupFilesAfterEnv", "setupFilesAfterEnv"],
8029
+ ["setupFiles", "setupFiles"],
8030
+ ["testMatch", "testMatch"],
8031
+ ["collectCoverageFrom", "coverageFrom"],
8032
+ ["moduleNameMapper", "aliases"]
8033
+ ]) {
8034
+ if (declaredKeys(body).includes(jestKey)) delete inherited[field];
8035
+ }
7562
8036
  return {
8037
+ ...inherited,
7563
8038
  file,
7564
8039
  ...readString(body, "testEnvironment") !== void 0 ? { testEnvironment: readString(body, "testEnvironment") } : {},
7565
8040
  ...readString(body, "coverageDirectory") !== void 0 ? { coverageDirectory: readString(body, "coverageDirectory") } : {},
7566
8041
  ...afterEnv !== void 0 ? { setupFilesAfterEnv: afterEnv } : {},
7567
8042
  ...before !== void 0 ? { setupFiles: before } : {},
7568
8043
  ...testTimeout !== void 0 ? { testTimeout } : {},
8044
+ ...maxWorkers !== void 0 ? { maxWorkers } : {},
7569
8045
  ...globalSetup !== void 0 ? { globalSetup: [globalSetup] } : {},
7570
8046
  ...testMatch !== void 0 ? { testMatch } : {},
7571
8047
  ...coverageFrom !== void 0 ? { coverageFrom } : {},
7572
8048
  ...excludeGlobs.length > 0 ? { excludeGlobs } : {},
7573
8049
  ...mapper.aliases.length > 0 ? { aliases: mapper.aliases } : {},
7574
8050
  ...env.length > 0 ? { env } : {},
7575
- refusals,
7576
- notes
8051
+ refusals: [...base?.refusals ?? [], ...refusals],
8052
+ notes: [...base?.notes ?? [], ...notes]
7577
8053
  };
7578
8054
  }
7579
8055
 
8056
+ // src/roles/test/nest/run-modes.ts
8057
+ import { existsSync as existsSync27 } from "fs";
8058
+ import { basename as basename2, join as join34 } from "path";
8059
+ var JEST_EXECUTOR = "@nx/jest:jest";
8060
+ var PRIMARY_CONFIG_FILE = "vitest.config.mts";
8061
+ var PRIMARY_TARGET = "test";
8062
+ function configName(declared) {
8063
+ return basename2(declared.replaceAll("\\", "/"));
8064
+ }
8065
+ function configFileFor(target) {
8066
+ if (target === PRIMARY_TARGET) return PRIMARY_CONFIG_FILE;
8067
+ const suffix = target.startsWith("test-") ? target.slice("test-".length) : target;
8068
+ return `vitest.${suffix}.config.mts`;
8069
+ }
8070
+ function runModes(cwd, fallback) {
8071
+ const targets = projectTargets(cwd) ?? {};
8072
+ const declared = [];
8073
+ for (const [target, raw] of Object.entries(targets)) {
8074
+ const entry = raw;
8075
+ if (entry.executor !== JEST_EXECUTOR) continue;
8076
+ const jestConfig = entry.options?.jestConfig;
8077
+ if (jestConfig === void 0) continue;
8078
+ const name = configName(jestConfig);
8079
+ if (!existsSync27(join34(cwd, name))) continue;
8080
+ declared.push({
8081
+ target,
8082
+ jestConfig: name,
8083
+ vitestConfig: configFileFor(target),
8084
+ primary: target === PRIMARY_TARGET
8085
+ });
8086
+ }
8087
+ if (declared.length === 0) {
8088
+ const [only] = fallback;
8089
+ return only === void 0 ? [] : [
8090
+ {
8091
+ target: PRIMARY_TARGET,
8092
+ jestConfig: only,
8093
+ vitestConfig: PRIMARY_CONFIG_FILE,
8094
+ primary: true
8095
+ }
8096
+ ];
8097
+ }
8098
+ return declared.sort(
8099
+ (left, right) => left.primary === right.primary ? left.target.localeCompare(right.target) : Number(right.primary) - Number(left.primary)
8100
+ );
8101
+ }
8102
+
7580
8103
  // src/roles/test/jest-migration.ts
7581
8104
  var SPEC_TSCONFIG = "tsconfig.spec.json";
7582
8105
  var VITEST_GLOBAL_TYPES = "vitest/globals";
7583
8106
  function specTypesOperation(cwd) {
7584
- const path = join32(cwd, SPEC_TSCONFIG);
7585
- if (!existsSync26(path)) return void 0;
8107
+ const path = join35(cwd, SPEC_TSCONFIG);
8108
+ if (!existsSync28(path)) return void 0;
7586
8109
  let config;
7587
8110
  try {
7588
8111
  config = parseJsonc(
7589
- readFileSync26(path, "utf8"),
8112
+ readFileSync28(path, "utf8"),
7590
8113
  SPEC_TSCONFIG
7591
8114
  );
7592
8115
  } catch {
@@ -7617,23 +8140,32 @@ function blocked(reason) {
7617
8140
  return { operations: [], blocked: reason };
7618
8141
  }
7619
8142
  function planJestMigration(context, adoption) {
7620
- if (adoption.jestConfigs.length > 1) {
7621
- const [primary, ...rest] = adoption.jestConfigs;
7622
- return blocked(
7623
- `this module has a second run mode (${rest.join(", ")}) beside ${primary}, with its own target. One config file cannot stand for both, and how run modes are expressed under Vitest is an open question here, so this module is not migrated yet.`
7624
- );
7625
- }
7626
- const reading = readJestConfig(context.cwd);
7627
- if (reading === void 0) {
7628
- return blocked(`${adoption.jestConfigs.join(", ")} could not be read, so nothing was written.`);
7629
- }
7630
- const generated = generateNestTestConfig(context.cwd, reading);
7631
- if (generated.source === void 0) {
7632
- return blocked(
7633
- `this module's jest config does not transpose as it stands: ${generated.refusals.join("; ")}. Nothing was written; this needs a look before the migration runs.`
7634
- );
8143
+ const modes = runModes(context.cwd, adoption.jestConfigs);
8144
+ if (modes.length === 0) {
8145
+ return blocked(`no jest config could be found for this module, so nothing was written.`);
8146
+ }
8147
+ const prepared = [];
8148
+ for (const mode of modes) {
8149
+ const reading = readJestConfig(context.cwd, mode.jestConfig);
8150
+ if (reading === void 0) {
8151
+ return blocked(`${mode.jestConfig} could not be read, so nothing was written.`);
8152
+ }
8153
+ const generated = generateNestTestConfig(context.cwd, reading, mode.target);
8154
+ if (generated.source === void 0) {
8155
+ return blocked(
8156
+ `${mode.jestConfig} (the \`${mode.target}\` target) does not transpose as it stands: ${generated.refusals.join("; ")}. Nothing was written; this needs a look before the migration runs.`
8157
+ );
8158
+ }
8159
+ prepared.push({ mode, reading, source: generated.source });
7635
8160
  }
7636
- const migration = migrateModuleTests(context.cwd);
8161
+ const migration = migrateModuleTests(
8162
+ context.cwd,
8163
+ prepared.flatMap(({ reading }) => [
8164
+ ...reading.setupFiles ?? [],
8165
+ ...reading.setupFilesAfterEnv ?? [],
8166
+ ...reading.globalSetup ?? []
8167
+ ])
8168
+ );
7637
8169
  if (migration.blockers.length > 0) {
7638
8170
  const listed = migration.blockers.map((blocker) => `
7639
8171
  ${blocker.file}: ${blocker.reason}`).join("");
@@ -7641,32 +8173,41 @@ function planJestMigration(context, adoption) {
7641
8173
  `${migration.blockers.length} thing(s) in this module's test files need a human before it can move. Nothing was written, because a config without its files is a suite calling jest.* under Vitest.${listed}`
7642
8174
  );
7643
8175
  }
7644
- const existing = projectTargets(context.cwd)?.[TEST_SCRIPT_NAME];
8176
+ const targets = projectTargets(context.cwd) ?? {};
8177
+ const existingFor = (name) => targets[name];
7645
8178
  const siblings = siblingScriptRewrites(context.cwd);
7646
8179
  const specTypes = specTypesOperation(context.cwd);
8180
+ const scripts = Object.fromEntries(
8181
+ prepared.map(({ mode }) => [
8182
+ mode.target,
8183
+ mode.primary ? SENTINEL_TEST_COMMAND : `${SENTINEL_TEST_COMMAND} --config ${mode.vitestConfig}`
8184
+ ])
8185
+ );
7647
8186
  const operations = [
7648
- { kind: "write", path: NEST_TEST_CONFIG_FILE, contents: generated.source },
8187
+ ...prepared.map(({ mode, source }) => ({
8188
+ kind: "write",
8189
+ path: mode.vitestConfig,
8190
+ contents: source
8191
+ })),
7649
8192
  ...migration.operations,
7650
8193
  ...specTypes === void 0 ? [] : [specTypes],
7651
- manifestOperation(context.cwd, { [TEST_SCRIPT_NAME]: SENTINEL_TEST_COMMAND, ...siblings }),
7652
- ...nxTargetOperations({
7653
- cwd: context.cwd,
7654
- targets: testTarget({
7655
- configFiles: [NEST_TEST_CONFIG_FILE],
7656
- outputs: existing?.outputs ?? [],
7657
- dependsOn: existing?.dependsOn ?? [],
7658
- configurations: existing?.configurations ?? {}
8194
+ manifestOperation(context.cwd, { ...scripts, ...siblings }),
8195
+ ...prepared.flatMap(
8196
+ ({ mode }) => nxTargetOperations({
8197
+ cwd: context.cwd,
8198
+ targets: testTarget({
8199
+ name: mode.target,
8200
+ configFiles: [mode.vitestConfig],
8201
+ outputs: existingFor(mode.target)?.outputs ?? [],
8202
+ dependsOn: existingFor(mode.target)?.dependsOn ?? [],
8203
+ configurations: existingFor(mode.target)?.configurations ?? {}
8204
+ })
7659
8205
  })
7660
- }),
7661
- ...adoption.jestConfigs.map((name) => ({ kind: "delete", path: name }))
8206
+ ),
8207
+ ...[.../* @__PURE__ */ new Set([...adoption.jestConfigs, ...prepared.map(({ mode }) => mode.jestConfig)])].map(
8208
+ (name) => ({ kind: "delete", path: name })
8209
+ )
7662
8210
  ];
7663
- if (existing !== void 0) {
7664
- operations.push({
7665
- kind: "remove-json-keys",
7666
- path: "project.json",
7667
- keys: [["targets", TEST_SCRIPT_NAME]]
7668
- });
7669
- }
7670
8211
  const notes = [
7671
8212
  `${migration.files} test file(s) migrated from jest to Vitest, ${migration.renamed} site(s) rewritten${migration.hoisted > 0 ? `, ${migration.hoisted} of them moving a mock factory's captures into vi.hoisted` : ""}.`,
7672
8213
  `${NEST_TEST_CONFIG_FILE} written from what ${adoption.jestConfigs.join(", ")} declared, and that config deleted. Every specifier in the new one is a package name: nothing in it reaches outside this module.`
@@ -7679,6 +8220,9 @@ function planJestMigration(context, adoption) {
7679
8220
  notes.push(
7680
8221
  `run this module's own lint --fix (or its formatter) after installing: the Vitest type imports are written beside the existing ones, and ordering belongs to the tool that enforces it.`
7681
8222
  );
8223
+ notes.push(
8224
+ `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.`
8225
+ );
7682
8226
  if (Object.keys(siblings).length > 0) {
7683
8227
  notes.push(`rewrote ${Object.keys(siblings).join(", ")} to run through sentinel.`);
7684
8228
  }
@@ -7901,9 +8445,9 @@ function registerTest() {
7901
8445
 
7902
8446
  // src/roles/typescript/adapters/tsc/tsc.adapter.ts
7903
8447
  import { spawnSync as spawnSync6 } from "child_process";
7904
- import { existsSync as existsSync27, readFileSync as readFileSync28 } from "fs";
7905
- import { createRequire as createRequire4 } from "module";
7906
- import { join as join34 } from "path";
8448
+ import { existsSync as existsSync29, readFileSync as readFileSync30 } from "fs";
8449
+ import { createRequire as createRequire6 } from "module";
8450
+ import { join as join37 } from "path";
7907
8451
 
7908
8452
  // src/roles/typescript/presets/nest.json
7909
8453
  var nest_default2 = {
@@ -8084,8 +8628,8 @@ function readTsconfigAdoption(cwd) {
8084
8628
  }
8085
8629
 
8086
8630
  // src/roles/typescript/adapters/tsc/plan.ts
8087
- import { readFileSync as readFileSync27 } from "fs";
8088
- import { join as join33 } from "path";
8631
+ import { readFileSync as readFileSync29 } from "fs";
8632
+ import { join as join36 } from "path";
8089
8633
 
8090
8634
  // src/roles/typescript/typecheck-script.ts
8091
8635
  var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
@@ -8178,7 +8722,7 @@ function planAdoption(context) {
8178
8722
  };
8179
8723
  }
8180
8724
  const existing = parseJsonc(
8181
- readFileSync27(join33(context.cwd, target.path), "utf8"),
8725
+ readFileSync29(join36(context.cwd, target.path), "utf8"),
8182
8726
  target.path
8183
8727
  );
8184
8728
  const extendsChain = composeExtends(existing.extends, preset);
@@ -8316,7 +8860,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
8316
8860
  let chain;
8317
8861
  try {
8318
8862
  const parsed = parseJsonc(
8319
- readFileSync28(join34(cwd, target.path), "utf8"),
8863
+ readFileSync30(join37(cwd, target.path), "utf8"),
8320
8864
  target.path
8321
8865
  );
8322
8866
  chain = parsed.extends;
@@ -8329,7 +8873,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
8329
8873
  );
8330
8874
  if (preset === void 0) return void 0;
8331
8875
  try {
8332
- createRequire4(join34(cwd, "noop.js")).resolve(preset);
8876
+ createRequire6(join37(cwd, "noop.js")).resolve(preset);
8333
8877
  return void 0;
8334
8878
  } catch {
8335
8879
  return preset;
@@ -8425,7 +8969,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
8425
8969
  referencedProjects(cwd, config) {
8426
8970
  try {
8427
8971
  const parsed = parseJsonc(
8428
- readFileSync28(join34(cwd, config), "utf8"),
8972
+ readFileSync30(join37(cwd, config), "utf8"),
8429
8973
  config
8430
8974
  );
8431
8975
  const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
@@ -8441,7 +8985,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
8441
8985
  * check.
8442
8986
  */
8443
8987
  typecheckTarget(cwd) {
8444
- if (existsSync27(join34(cwd, "tsconfig.json"))) return "tsconfig.json";
8988
+ if (existsSync29(join37(cwd, "tsconfig.json"))) return "tsconfig.json";
8445
8989
  const target = resolveTsconfigTarget(cwd);
8446
8990
  return target.reason === "none" ? null : target.path;
8447
8991
  }
@@ -8590,7 +9134,7 @@ function detectFramework(packageJson) {
8590
9134
  }
8591
9135
 
8592
9136
  // src/core/dispatch.ts
8593
- import { basename as basename2 } from "path";
9137
+ import { basename as basename3 } from "path";
8594
9138
 
8595
9139
  // src/shared/text.ts
8596
9140
  function ensureLines(current, lines) {
@@ -8646,7 +9190,7 @@ function replaceLines(current, replacements) {
8646
9190
  }
8647
9191
 
8648
9192
  // src/core/apply-plan.ts
8649
- import { existsSync as existsSync28, readFileSync as readFileSync29, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
9193
+ import { existsSync as existsSync30, readFileSync as readFileSync31, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
8650
9194
  import { resolve as resolve5, sep } from "path";
8651
9195
  import { applyEdits as applyEdits3, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
8652
9196
 
@@ -8665,7 +9209,7 @@ function resolveWithinRoot(cwd, relativePath) {
8665
9209
  return absolutePath;
8666
9210
  }
8667
9211
  function readIfExists(absolutePath) {
8668
- return existsSync28(absolutePath) ? readFileSync29(absolutePath, "utf8") : void 0;
9212
+ return existsSync30(absolutePath) ? readFileSync31(absolutePath, "utf8") : void 0;
8669
9213
  }
8670
9214
  function* leaves(value, prefix = []) {
8671
9215
  for (const [key, keyValue] of Object.entries(value)) {
@@ -8783,8 +9327,8 @@ function applyPlan(cwd, plan4) {
8783
9327
  }
8784
9328
 
8785
9329
  // src/core/config/preset-evidence.ts
8786
- import { existsSync as existsSync29, readdirSync as readdirSync7, readFileSync as readFileSync30 } from "fs";
8787
- import { join as join35 } from "path";
9330
+ import { existsSync as existsSync31, readdirSync as readdirSync8, readFileSync as readFileSync32 } from "fs";
9331
+ import { join as join38 } from "path";
8788
9332
  var PATH_SIGNALS = [
8789
9333
  {
8790
9334
  preset: "nest",
@@ -8812,18 +9356,18 @@ function sourceImports(cwd, pattern) {
8812
9356
  const dir = stack.pop();
8813
9357
  let entries;
8814
9358
  try {
8815
- entries = readdirSync7(dir, { withFileTypes: true });
9359
+ entries = readdirSync8(dir, { withFileTypes: true });
8816
9360
  } catch {
8817
9361
  continue;
8818
9362
  }
8819
9363
  for (const entry of entries) {
8820
9364
  if (entry.isDirectory()) {
8821
- if (!NOT_SCANNED.has(entry.name)) stack.push(join35(dir, entry.name));
9365
+ if (!NOT_SCANNED.has(entry.name)) stack.push(join38(dir, entry.name));
8822
9366
  continue;
8823
9367
  }
8824
9368
  if (!SOURCE_FILE2.test(entry.name)) continue;
8825
9369
  try {
8826
- if (pattern.test(readFileSync30(join35(dir, entry.name), "utf8"))) return true;
9370
+ if (pattern.test(readFileSync32(join38(dir, entry.name), "utf8"))) return true;
8827
9371
  } catch {
8828
9372
  continue;
8829
9373
  }
@@ -8832,11 +9376,11 @@ function sourceImports(cwd, pattern) {
8832
9376
  return false;
8833
9377
  }
8834
9378
  function dependencyNames(cwd) {
8835
- const path = join35(cwd, "package.json");
8836
- if (!existsSync29(path)) return [];
9379
+ const path = join38(cwd, "package.json");
9380
+ if (!existsSync31(path)) return [];
8837
9381
  try {
8838
9382
  const manifest = parseJsonc(
8839
- readFileSync30(path, "utf8"),
9383
+ readFileSync32(path, "utf8"),
8840
9384
  path
8841
9385
  );
8842
9386
  return [
@@ -8850,7 +9394,7 @@ function dependencyNames(cwd) {
8850
9394
  function declaresJsx(cwd) {
8851
9395
  let entries;
8852
9396
  try {
8853
- entries = readdirSync7(cwd).filter(
9397
+ entries = readdirSync8(cwd).filter(
8854
9398
  (name) => name.startsWith("tsconfig") && name.endsWith(".json")
8855
9399
  );
8856
9400
  } catch {
@@ -8859,7 +9403,7 @@ function declaresJsx(cwd) {
8859
9403
  for (const name of entries) {
8860
9404
  try {
8861
9405
  const config = parseJsonc(
8862
- readFileSync30(join35(cwd, name), "utf8"),
9406
+ readFileSync32(join38(cwd, name), "utf8"),
8863
9407
  name
8864
9408
  );
8865
9409
  if (config.compilerOptions?.jsx !== void 0) return true;
@@ -9014,7 +9558,7 @@ async function dispatch(opts) {
9014
9558
  process.stderr.write(` fixing what ${opts.target} can fix automatically...
9015
9559
  `);
9016
9560
  await adapter.afterInit({
9017
- module: basename2(opts.cwd),
9561
+ module: basename3(opts.cwd),
9018
9562
  cwd: opts.cwd,
9019
9563
  preset,
9020
9564
  ci: false,
@@ -9047,4 +9591,4 @@ export {
9047
9591
  detectFramework,
9048
9592
  dispatch
9049
9593
  };
9050
- //# sourceMappingURL=chunk-O3XCQGTJ.js.map
9594
+ //# sourceMappingURL=chunk-4UIZJ3TR.js.map