@hublo/sentinel 1.4.0-alpha.32 → 1.4.0-alpha.34

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.
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  matchesTsconfigPattern
3
3
  } from "./chunk-O7REVMOC.js";
4
+ import {
5
+ parseJsonc
6
+ } from "./chunk-QXFCZON7.js";
4
7
  import {
5
8
  WORKSPACE_ROOT_MARKER,
6
9
  findWorkspaceRoot,
@@ -24,8 +27,8 @@ var BaseAdapter = class {
24
27
  };
25
28
 
26
29
  // src/roles/test/read-adoption.ts
27
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "node:fs";
28
- import { join as join2, relative } from "node:path";
30
+ import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync } from "node:fs";
31
+ import { join as join3, relative } from "node:path";
29
32
 
30
33
  // src/core/config/manifest.ts
31
34
  import { existsSync, readFileSync } from "node:fs";
@@ -81,6 +84,10 @@ function manifestOperation(cwd, scripts) {
81
84
  };
82
85
  }
83
86
 
87
+ // src/roles/test/config-policy.ts
88
+ import { readFileSync as readFileSync2 } from "node:fs";
89
+ import { join as join2 } from "node:path";
90
+
84
91
  // src/roles/test/presets/toolchain.json
85
92
  var toolchain_default = {
86
93
  $comment: "The test toolchain sentinel owns. Same shape and same rule as the build one: a single table drives what `--init` removes from a module's package.json, which import specifiers move, and the name each default import is re-exported under, so the three cannot disagree.",
@@ -141,13 +148,30 @@ function rewriteVitestScript(command) {
141
148
  return `${lead}sentinel --run --test${suffix}${trailing}`;
142
149
  });
143
150
  }
151
+ function dependencyInput(cwd) {
152
+ const root = findWorkspaceRoot(cwd);
153
+ if (root === void 0) return "^default";
154
+ try {
155
+ const config = parseJsonc(
156
+ readFileSync2(join2(root, "nx.json"), "utf8"),
157
+ "nx.json"
158
+ );
159
+ return config?.namedInputs?.production === void 0 ? "^default" : "^production";
160
+ } catch {
161
+ return "^default";
162
+ }
163
+ }
144
164
  function testTarget(options) {
145
- const { name, configFiles, outputs, dependsOn, configurations } = options;
165
+ const { cwd, name, configFiles, outputs, dependsOn, configurations } = options;
146
166
  const cached = outputs !== void 0 && outputs.length > 0;
147
167
  return {
148
168
  [name ?? TEST_SCRIPT_NAME]: {
149
169
  cache: cached,
150
- inputs: ["default", "^default", ...configFiles.map((name2) => `{projectRoot}/${name2}`)],
170
+ inputs: [
171
+ "default",
172
+ dependencyInput(cwd),
173
+ ...configFiles.map((name2) => `{projectRoot}/${name2}`)
174
+ ],
151
175
  ...cached ? { outputs: [...outputs] } : {},
152
176
  ...dependsOn !== void 0 && dependsOn.length > 0 ? { dependsOn: [...dependsOn] } : {},
153
177
  ...configurations !== void 0 && Object.keys(configurations).length > 0 ? { configurations: { ...configurations } } : {}
@@ -164,15 +188,15 @@ var JEST_CONFIG_FILES = [
164
188
  "jest.integration-config.ts"
165
189
  ];
166
190
  function testConfigFile(cwd) {
167
- return TEST_CONFIG_FILES.find((name) => existsSync2(join2(cwd, name)));
191
+ return TEST_CONFIG_FILES.find((name) => existsSync2(join3(cwd, name)));
168
192
  }
169
193
  function jestConfigFiles(cwd) {
170
- return JEST_CONFIG_FILES.filter((name) => existsSync2(join2(cwd, name)));
194
+ return JEST_CONFIG_FILES.filter((name) => existsSync2(join3(cwd, name)));
171
195
  }
172
196
  function rootJestConfigFiles(cwd) {
173
197
  const root = findWorkspaceRoot(cwd);
174
198
  if (root === void 0 || root === cwd) return [];
175
- return JEST_CONFIG_FILES.filter((name) => existsSync2(join2(root, name)));
199
+ return JEST_CONFIG_FILES.filter((name) => existsSync2(join3(root, name)));
176
200
  }
177
201
  var TEST_FILE = /[.-](spec|test)\.[cm]?[jt]sx?$/;
178
202
  var NOT_WALKED = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".git", ".nx", ".turbo"]);
@@ -195,9 +219,9 @@ function walkTestFiles(cwd, stopAtFirst) {
195
219
  }
196
220
  for (const entry of entries) {
197
221
  if (entry.isDirectory()) {
198
- if (!NOT_WALKED.has(entry.name)) stack.push(join2(dir, entry.name));
222
+ if (!NOT_WALKED.has(entry.name)) stack.push(join3(dir, entry.name));
199
223
  } else if (TEST_FILE.test(entry.name)) {
200
- found.push(relative(cwd, join2(dir, entry.name)).replaceAll("\\", "/"));
224
+ found.push(relative(cwd, join3(dir, entry.name)).replaceAll("\\", "/"));
201
225
  if (stopAtFirst) return found;
202
226
  }
203
227
  }
@@ -206,7 +230,7 @@ function walkTestFiles(cwd, stopAtFirst) {
206
230
  }
207
231
  function readTestConfigSource(cwd, fileName) {
208
232
  try {
209
- return readFileSync2(join2(cwd, fileName), "utf8");
233
+ return readFileSync3(join3(cwd, fileName), "utf8");
210
234
  } catch {
211
235
  return "";
212
236
  }
@@ -277,15 +301,15 @@ function readTestAdoption(cwd) {
277
301
  }
278
302
 
279
303
  // src/roles/test/module-env.ts
280
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
281
- import { join as join3 } from "node:path";
304
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
305
+ import { join as join4 } from "node:path";
282
306
  var CASCADE = [".env", ".env.test"];
283
307
  var MODULE_CASCADE = [".env", ".env.local", ".env.test"];
284
308
  function parse(path) {
285
309
  const found = {};
286
310
  let contents;
287
311
  try {
288
- contents = readFileSync3(path, "utf8");
312
+ contents = readFileSync4(path, "utf8");
289
313
  } catch {
290
314
  return found;
291
315
  }
@@ -305,8 +329,8 @@ function moduleEnvironment(cwd) {
305
329
  const loaded = [];
306
330
  const added = {};
307
331
  const files = [
308
- ...root === void 0 ? [] : CASCADE.map((name) => [join3(root, name), name]),
309
- ...MODULE_CASCADE.map((name) => [join3(cwd, name), `<module>/${name}`])
332
+ ...root === void 0 ? [] : CASCADE.map((name) => [join4(root, name), name]),
333
+ ...MODULE_CASCADE.map((name) => [join4(cwd, name), `<module>/${name}`])
310
334
  ];
311
335
  for (const [path, label] of files) {
312
336
  if (!existsSync3(path)) continue;
@@ -320,19 +344,19 @@ function moduleEnvironment(cwd) {
320
344
  // src/roles/test/reference.ts
321
345
  import { execFileSync } from "node:child_process";
322
346
  import { createHash } from "node:crypto";
323
- import { existsSync as existsSync5, mkdirSync, mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync } from "node:fs";
347
+ import { existsSync as existsSync5, mkdirSync, mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync } from "node:fs";
324
348
  import { tmpdir } from "node:os";
325
- import { join as join5, resolve } from "node:path";
349
+ import { join as join6, resolve } from "node:path";
326
350
 
327
351
  // src/shared/resolve-bin.ts
328
352
  import { existsSync as existsSync4 } from "node:fs";
329
353
  import { createRequire } from "node:module";
330
- import { delimiter, dirname, join as join4 } from "node:path";
354
+ import { delimiter, dirname, join as join5 } from "node:path";
331
355
  var require2 = createRequire(import.meta.url);
332
356
  function resolveBin(fromDir, name) {
333
357
  let dir = fromDir;
334
358
  for (; ; ) {
335
- const candidate = join4(dir, "node_modules", ".bin", name);
359
+ const candidate = join5(dir, "node_modules", ".bin", name);
336
360
  if (existsSync4(candidate)) return candidate;
337
361
  const parent = dirname(dir);
338
362
  if (parent === dir) return void 0;
@@ -345,7 +369,7 @@ function binFromOwnInstall(packageName, binName) {
345
369
  const bin = require2(manifest).bin;
346
370
  const relative6 = typeof bin === "string" ? bin : bin?.[binName];
347
371
  if (!relative6) return void 0;
348
- const executable = join4(dirname(manifest), relative6);
372
+ const executable = join5(dirname(manifest), relative6);
349
373
  return existsSync4(executable) ? executable : void 0;
350
374
  } catch {
351
375
  return void 0;
@@ -623,7 +647,7 @@ function describeComparison(comparison) {
623
647
  // src/roles/test/reference.ts
624
648
  function referencePath(cwd, suite) {
625
649
  const key = createHash("sha256").update(`${resolve(cwd)}\0${suite}`).digest("hex").slice(0, 16);
626
- return join5(tmpdir(), "sentinel-test-reference", `${key}.json`);
650
+ return join6(tmpdir(), "sentinel-test-reference", `${key}.json`);
627
651
  }
628
652
  function suiteOf(config) {
629
653
  const base = config.replace(/^(jest|vitest)\./, "").replace(/\.[cm]?[jt]s$/, "");
@@ -634,9 +658,9 @@ function runJest(cwd, config) {
634
658
  const bin = resolveBin(cwd, "jest");
635
659
  if (bin === void 0) return void 0;
636
660
  const root = findWorkspaceRoot(cwd) ?? cwd;
637
- const out = join5(mkdtempSync(join5(tmpdir(), "sentinel-reference-")), "jest.json");
661
+ const out = join6(mkdtempSync(join6(tmpdir(), "sentinel-reference-")), "jest.json");
638
662
  try {
639
- execFileSync(bin, ["-c", join5(cwd, config), "--json", `--outputFile=${out}`, "--silent"], {
663
+ execFileSync(bin, ["-c", join6(cwd, config), "--json", `--outputFile=${out}`, "--silent"], {
640
664
  cwd: root,
641
665
  /*
642
666
  * ⚠️ The module's environment, not the shell's, and this is what made the reference lie.
@@ -653,7 +677,7 @@ function runJest(cwd, config) {
653
677
  }
654
678
  if (!existsSync5(out)) return void 0;
655
679
  try {
656
- return JSON.parse(readFileSync4(out, "utf8"));
680
+ return JSON.parse(readFileSync5(out, "utf8"));
657
681
  } catch {
658
682
  return void 0;
659
683
  }
@@ -684,7 +708,7 @@ function captureReference(cwd, config) {
684
708
  snapshot
685
709
  };
686
710
  const path = referencePath(cwd, suiteOf(config));
687
- mkdirSync(join5(path, ".."), { recursive: true });
711
+ mkdirSync(join6(path, ".."), { recursive: true });
688
712
  writeFileSync(path, `${JSON.stringify(stored, void 0, 2)}
689
713
  `);
690
714
  return {
@@ -699,7 +723,7 @@ function compareAgainstReference(cwd, suite, report) {
699
723
  const path = referencePath(cwd, suite);
700
724
  let stored;
701
725
  try {
702
- stored = JSON.parse(readFileSync4(path, "utf8"));
726
+ stored = JSON.parse(readFileSync5(path, "utf8"));
703
727
  } catch {
704
728
  rmSync(path, { force: true });
705
729
  return { ok: true, message: `the recorded reference could not be read, so nothing was proved.` };
@@ -723,7 +747,7 @@ function resolveVitest(cwd) {
723
747
 
724
748
  // src/roles/test/adapters/vitest/vitest.adapter.ts
725
749
  import { spawnSync } from "node:child_process";
726
- import { isAbsolute as isAbsolute2, resolve as resolve7 } from "node:path";
750
+ import { isAbsolute as isAbsolute2, resolve as resolve8 } from "node:path";
727
751
 
728
752
  // src/core/config/tool-args.ts
729
753
  import { existsSync as existsSync6 } from "node:fs";
@@ -745,7 +769,7 @@ function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
745
769
 
746
770
  // src/core/config/nx-target.ts
747
771
  import { existsSync as existsSync7 } from "node:fs";
748
- import { join as join6 } from "node:path";
772
+ import { join as join7 } from "node:path";
749
773
  function nxTargetMetadataOperations(options) {
750
774
  const { targets } = options;
751
775
  if (Object.keys(targets).length === 0) return [];
@@ -756,7 +780,7 @@ function nxTargetOperations(options) {
756
780
  const names = Object.keys(targets);
757
781
  if (names.length === 0) return [];
758
782
  const operations = [];
759
- if (existsSync7(join6(cwd, "project.json"))) {
783
+ if (existsSync7(join7(cwd, "project.json"))) {
760
784
  operations.push({
761
785
  kind: "remove-json-keys",
762
786
  path: "project.json",
@@ -768,13 +792,13 @@ function nxTargetOperations(options) {
768
792
  }
769
793
 
770
794
  // src/core/config/project-json.ts
771
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "node:fs";
772
- import { join as join7 } from "node:path";
795
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
796
+ import { join as join8 } from "node:path";
773
797
  function projectTargets(cwd) {
774
- const path = join7(cwd, "project.json");
798
+ const path = join8(cwd, "project.json");
775
799
  if (!existsSync8(path)) return void 0;
776
800
  try {
777
- return JSON.parse(readFileSync5(path, "utf8")).targets;
801
+ return JSON.parse(readFileSync6(path, "utf8")).targets;
778
802
  } catch {
779
803
  return void 0;
780
804
  }
@@ -873,38 +897,26 @@ function rewriteToolchainImports(toolchain, source, fileName) {
873
897
  }
874
898
 
875
899
  // src/roles/test/jest-migration.ts
876
- import { existsSync as existsSync18, readFileSync as readFileSync17, readdirSync as readdirSync6 } from "node:fs";
900
+ import { existsSync as existsSync18, readFileSync as readFileSync19, readdirSync as readdirSync6 } from "node:fs";
877
901
  import { createRequire as createRequire5 } from "node:module";
878
- import { dirname as dirname10, join as join22, relative as relative5 } from "node:path";
879
-
880
- // src/shared/jsonc.ts
881
- import { parse as parse2, printParseErrorCode } from "jsonc-parser";
882
- function parseJsonc(text, source = "config") {
883
- const errors = [];
884
- const value = parse2(text, errors, { allowTrailingComma: true });
885
- if (errors.length > 0) {
886
- const details = errors.map((error) => printParseErrorCode(error.error)).join(", ");
887
- throw new Error(`${source}: malformed JSONC (${details}).`);
888
- }
889
- return value;
890
- }
902
+ import { dirname as dirname11, join as join24, relative as relative5 } from "node:path";
891
903
 
892
904
  // src/roles/test/codemod/migrate-module.ts
893
- import { readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync3 } from "node:fs";
894
- import { join as join15 } from "node:path";
895
- import { parseSync as parseSync7 } from "oxc-parser";
905
+ import { readdirSync as readdirSync4, readFileSync as readFileSync14, statSync as statSync4 } from "node:fs";
906
+ import { join as join17 } from "node:path";
907
+ import { parseSync as parseSync8 } from "oxc-parser";
896
908
 
897
909
  // src/roles/test/msw-lifecycle.ts
898
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
899
- import { join as join8 } from "node:path";
910
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
911
+ import { join as join9 } from "node:path";
900
912
  var ROOT_SETUP = "jest.setup.after.env.js";
901
913
  function ownsMswLifecycle(cwd, setupFiles) {
902
914
  for (const raw of setupFiles) {
903
915
  if (raw.endsWith(ROOT_SETUP)) continue;
904
- const file = join8(cwd, raw.replaceAll("<rootDir>/", "").replaceAll("<rootDir>", "."));
916
+ const file = join9(cwd, raw.replaceAll("<rootDir>/", "").replaceAll("<rootDir>", "."));
905
917
  if (!existsSync9(file)) continue;
906
918
  try {
907
- if (/\.listen\s*\(/.test(readFileSync6(file, "utf8"))) return true;
919
+ if (/\.listen\s*\(/.test(readFileSync7(file, "utf8"))) return true;
908
920
  } catch {
909
921
  continue;
910
922
  }
@@ -943,6 +955,7 @@ function walk(node, visit) {
943
955
  if (value !== null && typeof value === "object") walk(value, visit);
944
956
  }
945
957
  }
958
+ var MEMBER_TYPES = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
946
959
 
947
960
  // src/roles/test/codemod/rename.ts
948
961
  var VITEST_TYPE_SOURCE = "vitest";
@@ -1121,11 +1134,19 @@ ${source}`;
1121
1134
  }
1122
1135
 
1123
1136
  // src/roles/test/codemod/migrate-file.ts
1124
- import { dirname as dirname6 } from "node:path";
1125
- import { parseSync as parseSync6 } from "oxc-parser";
1137
+ import { dirname as dirname7 } from "node:path";
1138
+ import { parseSync as parseSync7 } from "oxc-parser";
1139
+
1140
+ // src/roles/test/codemod/edits.ts
1141
+ function applyEdits(source, edits) {
1142
+ let result = source;
1143
+ for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
1144
+ result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
1145
+ }
1146
+ return result;
1147
+ }
1126
1148
 
1127
1149
  // src/roles/test/codemod/assertion-return-value.ts
1128
- var MEMBER_TYPES = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
1129
1150
  function rootedAtExpect(node) {
1130
1151
  let current = node;
1131
1152
  for (let depth = 0; current !== void 0 && depth < 12; depth += 1) {
@@ -1243,17 +1264,13 @@ function rewriteAssertionReturnValue(source, program) {
1243
1264
  edits.push({ start: head, end: to, text: "" });
1244
1265
  }
1245
1266
  }
1246
- let result = source;
1247
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
1248
- result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
1249
- }
1267
+ const result = applyEdits(source, edits);
1250
1268
  return { source: result, rewritten: captures.length - refusals.length, refusals };
1251
1269
  }
1252
1270
 
1253
1271
  // src/roles/test/codemod/bare-mock-factory.ts
1254
- var MEMBER_TYPES2 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
1255
1272
  function isMockFactory(node) {
1256
- if (node === void 0 || !MEMBER_TYPES2.has(node.type)) return false;
1273
+ if (node === void 0 || !MEMBER_TYPES.has(node.type)) return false;
1257
1274
  const object = node.object;
1258
1275
  const property = node.property;
1259
1276
  if (object?.type !== "Identifier") return false;
@@ -1277,11 +1294,10 @@ function invokeBareMockFactories(source, program) {
1277
1294
  }
1278
1295
 
1279
1296
  // src/roles/test/codemod/constructible-mocks.ts
1280
- var MEMBER_TYPES3 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
1281
1297
  function mockImplementationArrow(node) {
1282
1298
  if (node.type !== "CallExpression") return void 0;
1283
1299
  const callee = node.callee;
1284
- if (callee === void 0 || !MEMBER_TYPES3.has(callee.type)) return void 0;
1300
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return void 0;
1285
1301
  const property = callee.property;
1286
1302
  if (property?.type !== "Identifier" || property.name !== "mockImplementation") return void 0;
1287
1303
  const [argument] = node.arguments ?? [];
@@ -1290,7 +1306,7 @@ function mockImplementationArrow(node) {
1290
1306
  function assertedMockedClass(source, node) {
1291
1307
  if (node.type !== "CallExpression") return false;
1292
1308
  const callee = node.callee;
1293
- if (callee === void 0 || !MEMBER_TYPES3.has(callee.type)) return false;
1309
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return false;
1294
1310
  const object = callee.object;
1295
1311
  if (object === void 0) return false;
1296
1312
  return /\bMockedClass\b/.test(source.slice(object.start, object.end));
@@ -1348,10 +1364,7 @@ function rewriteConstructibleMocks(source, program, factories) {
1348
1364
  rewritten += 1;
1349
1365
  });
1350
1366
  }
1351
- let result = source;
1352
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
1353
- result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
1354
- }
1367
+ const result = applyEdits(source, edits);
1355
1368
  return { source: result, rewritten, refusals };
1356
1369
  }
1357
1370
 
@@ -1380,16 +1393,15 @@ function normaliseDeepImports(source) {
1380
1393
  }
1381
1394
 
1382
1395
  // src/roles/test/codemod/each-table-rows.ts
1383
- var MEMBER_TYPES4 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
1384
1396
  var RUNNERS = /* @__PURE__ */ new Set(["it", "test", "describe"]);
1385
1397
  function eachTable(node) {
1386
1398
  if (node.type !== "CallExpression") return void 0;
1387
1399
  const callee = node.callee;
1388
- if (callee === void 0 || !MEMBER_TYPES4.has(callee.type)) return void 0;
1400
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return void 0;
1389
1401
  const property = callee.property;
1390
1402
  if (property?.type !== "Identifier" || property.name !== "each") return void 0;
1391
1403
  let object = callee.object;
1392
- while (object !== void 0 && MEMBER_TYPES4.has(object.type)) {
1404
+ while (object !== void 0 && MEMBER_TYPES.has(object.type)) {
1393
1405
  object = object.object;
1394
1406
  }
1395
1407
  if (object?.type !== "Identifier" || !RUNNERS.has(object.name)) return void 0;
@@ -1408,21 +1420,17 @@ function rewriteEachTableRows(source, program) {
1408
1420
  const wrapped = rows.map((row) => source.slice(row.start, row.end)).map((text) => `[${text}]`).join(", ");
1409
1421
  edits.push({ start: table.start, end: table.end, text: `[${wrapped}]` });
1410
1422
  });
1411
- let result = source;
1412
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
1413
- result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
1414
- }
1423
+ const result = applyEdits(source, edits);
1415
1424
  return { source: result, rewritten: edits.length };
1416
1425
  }
1417
1426
 
1418
1427
  // src/roles/test/codemod/each-title.ts
1419
- var MEMBER_TYPES5 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
1420
1428
  function eachTitle(node) {
1421
1429
  if (node.type !== "CallExpression") return void 0;
1422
1430
  const inner = node.callee;
1423
1431
  if (inner?.type !== "CallExpression") return void 0;
1424
1432
  const member = inner.callee;
1425
- if (member === void 0 || !MEMBER_TYPES5.has(member.type)) return void 0;
1433
+ if (member === void 0 || !MEMBER_TYPES.has(member.type)) return void 0;
1426
1434
  const property = member.property;
1427
1435
  if (property?.type !== "Identifier" || property.name !== "each") return void 0;
1428
1436
  const [title] = node.arguments ?? [];
@@ -1466,23 +1474,19 @@ function rewriteEachTitles(source, program) {
1466
1474
  edits.push({ start, end, text: replacement.text });
1467
1475
  rewritten += replacement.rewritten;
1468
1476
  });
1469
- let result = source;
1470
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
1471
- result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
1472
- }
1477
+ const result = applyEdits(source, edits);
1473
1478
  return { source: result, rewritten };
1474
1479
  }
1475
1480
 
1476
1481
  // src/roles/test/codemod/empty-mock-implementation.ts
1477
1482
  var METHODS = /* @__PURE__ */ new Set(["mockImplementation", "mockImplementationOnce"]);
1478
- var MEMBER_TYPES6 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
1479
1483
  function rewriteEmptyMockImplementation(source, program) {
1480
1484
  const insertAt = [];
1481
1485
  walk(program, (node) => {
1482
1486
  if (node.type !== "CallExpression") return;
1483
1487
  if ((node.arguments ?? []).length > 0) return;
1484
1488
  const callee = node.callee;
1485
- if (callee === void 0 || !MEMBER_TYPES6.has(callee.type)) return;
1489
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return;
1486
1490
  const property = callee.property;
1487
1491
  if (property?.type !== "Identifier" || !METHODS.has(property.name)) return;
1488
1492
  insertAt.push(node.end - 1);
@@ -1495,12 +1499,11 @@ function rewriteEmptyMockImplementation(source, program) {
1495
1499
  }
1496
1500
 
1497
1501
  // src/roles/test/codemod/fake-timer-options.ts
1498
- var MEMBER_TYPES7 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
1499
1502
  var TIMER_CALLS = /* @__PURE__ */ new Set(["useFakeTimers"]);
1500
1503
  function timerOptions(node) {
1501
1504
  if (node.type !== "CallExpression") return void 0;
1502
1505
  const callee = node.callee;
1503
- if (callee === void 0 || !MEMBER_TYPES7.has(callee.type)) return void 0;
1506
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return void 0;
1504
1507
  const object = callee.object;
1505
1508
  const property = callee.property;
1506
1509
  if (object?.type !== "Identifier") return void 0;
@@ -1614,10 +1617,7 @@ function rewriteFakeTimerOptions(source, program) {
1614
1617
  }
1615
1618
  });
1616
1619
  if (edits.length === 0) return { source, translated, refusals, warnings };
1617
- let result = source;
1618
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
1619
- result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
1620
- }
1620
+ const result = applyEdits(source, edits);
1621
1621
  return { source: result, translated, refusals, warnings };
1622
1622
  }
1623
1623
 
@@ -1835,16 +1835,12 @@ function hoistCapturedBindings(source, program, factories, bindings, imported) {
1835
1835
  const span = statementSpan(source, statement2);
1836
1836
  return { ...span, text: index === 0 ? hoistedLine : "" };
1837
1837
  });
1838
- let rewritten = source;
1839
- for (const edit of edits.sort((left, right) => right.start - left.start)) {
1840
- rewritten = rewritten.slice(0, edit.start) + edit.text + rewritten.slice(edit.end);
1841
- }
1838
+ const rewritten = applyEdits(source, edits);
1842
1839
  return { source: rewritten, hoisted: ordered.map(({ name }) => name), refusals: [] };
1843
1840
  }
1844
1841
 
1845
1842
  // src/roles/test/codemod/hook-return-value.ts
1846
1843
  var HOOKS = /* @__PURE__ */ new Set(["beforeEach", "afterEach", "beforeAll", "afterAll"]);
1847
- var MEMBER_TYPES8 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
1848
1844
  function hookCallback(node) {
1849
1845
  if (node.type !== "CallExpression") return void 0;
1850
1846
  const callee = node.callee;
@@ -1855,7 +1851,7 @@ function hookCallback(node) {
1855
1851
  function configuresAMock(body) {
1856
1852
  if (body.type !== "CallExpression") return false;
1857
1853
  const callee = body.callee;
1858
- if (callee === void 0 || !MEMBER_TYPES8.has(callee.type)) return false;
1854
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return false;
1859
1855
  const property = callee.property;
1860
1856
  return property?.type === "Identifier" && /^mock[A-Z]/.test(property.name);
1861
1857
  }
@@ -1873,17 +1869,170 @@ function rewriteHookReturnValue(source, program) {
1873
1869
  ${source.slice(start, end)}
1874
1870
  }` });
1875
1871
  });
1876
- let result = source;
1877
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
1878
- result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
1879
- }
1872
+ const result = applyEdits(source, edits);
1880
1873
  return { source: result, rewritten: edits.length };
1881
1874
  }
1882
1875
 
1883
1876
  // src/roles/test/codemod/named-rules.ts
1884
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
1877
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
1885
1878
  import { createRequire as createRequire2 } from "node:module";
1886
- import { dirname as dirname2, join as join9 } from "node:path";
1879
+ import { dirname as dirname3, join as join11 } from "node:path";
1880
+
1881
+ // src/roles/test/codemod/static-import-for-require.ts
1882
+ import { readFileSync as readFileSync8, statSync } from "node:fs";
1883
+ import { dirname as dirname2, join as join10, resolve as resolve3 } from "node:path";
1884
+ import { parseSync as parseSync4 } from "oxc-parser";
1885
+ var EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
1886
+ function resolveRelative(fromFile, specifier) {
1887
+ const base = resolve3(dirname2(fromFile), specifier);
1888
+ const candidates = [
1889
+ base,
1890
+ ...EXTENSIONS.map((extension) => `${base}${extension}`),
1891
+ ...EXTENSIONS.map((extension) => join10(base, `index${extension}`))
1892
+ ];
1893
+ for (const candidate of candidates) {
1894
+ try {
1895
+ if (statSync(candidate).isFile()) return candidate;
1896
+ } catch {
1897
+ }
1898
+ }
1899
+ return void 0;
1900
+ }
1901
+ function relativeSpecifiers(source) {
1902
+ const found = [];
1903
+ const patterns = [
1904
+ /\bfrom\s+['"](\.[^'"]*)['"]/g,
1905
+ /\bimport\s+['"](\.[^'"]*)['"]/g,
1906
+ /\bimport\s*\(\s*['"](\.[^'"]*)['"]\s*\)/g,
1907
+ /\brequire\s*\(\s*['"](\.[^'"]*)['"]\s*\)/g
1908
+ ];
1909
+ for (const pattern of patterns) {
1910
+ for (const match of source.matchAll(pattern)) found.push(match[1]);
1911
+ }
1912
+ return found;
1913
+ }
1914
+ function reachesBack(fromFile, specifier) {
1915
+ const entry = resolveRelative(fromFile, specifier);
1916
+ if (entry === void 0) return true;
1917
+ const target = resolve3(fromFile);
1918
+ const seen = /* @__PURE__ */ new Set();
1919
+ const stack = [entry];
1920
+ while (stack.length > 0 && seen.size < 200) {
1921
+ const file = stack.pop();
1922
+ if (seen.has(file)) continue;
1923
+ seen.add(file);
1924
+ if (resolve3(file) === target) return true;
1925
+ let source;
1926
+ try {
1927
+ source = readFileSync8(file, "utf8");
1928
+ } catch {
1929
+ continue;
1930
+ }
1931
+ for (const relative6 of relativeSpecifiers(source)) {
1932
+ const next = resolveRelative(file, relative6);
1933
+ if (next !== void 0) stack.push(next);
1934
+ }
1935
+ }
1936
+ return seen.size >= 200;
1937
+ }
1938
+ function namedRequireDeclarations(program) {
1939
+ const found = /* @__PURE__ */ new Map();
1940
+ walk(program, (node) => {
1941
+ if (node.type !== "VariableDeclaration") return;
1942
+ const declarators = node.declarations ?? [];
1943
+ if (declarators.length !== 1) return;
1944
+ const declarator = declarators[0];
1945
+ const init = declarator.init;
1946
+ if (init?.type !== "CallExpression") return;
1947
+ const callee = init.callee;
1948
+ if (callee?.type !== "Identifier" || callee.name !== "require") return;
1949
+ const [argument] = init.arguments ?? [];
1950
+ if (argument?.type !== "StringLiteral" && argument?.type !== "Literal") return;
1951
+ const specifier = String(argument.value);
1952
+ if (!specifier.startsWith(".")) return;
1953
+ const id = declarator.id;
1954
+ if (id?.type !== "ObjectPattern") return;
1955
+ const names = [];
1956
+ for (const property of id.properties ?? []) {
1957
+ if (property.type !== "Property" && property.type !== "ObjectProperty") return;
1958
+ if (property.computed === true || property.shorthand !== true) return;
1959
+ const key = property.key;
1960
+ const value = property.value;
1961
+ if (key?.type !== "Identifier" || value?.type !== "Identifier") return;
1962
+ if (key.name !== value.name) return;
1963
+ names.push(key.name);
1964
+ }
1965
+ if (names.length === 0) return;
1966
+ found.set(init, { names, specifier, start: node.start, end: node.end });
1967
+ });
1968
+ return found;
1969
+ }
1970
+ function topLevelNames(program) {
1971
+ const names = /* @__PURE__ */ new Set();
1972
+ for (const node of program.body ?? []) {
1973
+ if (node.type === "ImportDeclaration") {
1974
+ for (const specifier of node.specifiers ?? []) {
1975
+ const local = specifier.local;
1976
+ if (local?.type === "Identifier") names.add(local.name);
1977
+ }
1978
+ continue;
1979
+ }
1980
+ if (node.type === "VariableDeclaration") {
1981
+ for (const declarator of node.declarations ?? []) {
1982
+ const id = declarator.id;
1983
+ if (id?.type === "Identifier") names.add(id.name);
1984
+ }
1985
+ continue;
1986
+ }
1987
+ if ((node.type === "FunctionDeclaration" || node.type === "ClassDeclaration") && node.id?.type === "Identifier") {
1988
+ names.add(node.id.name);
1989
+ }
1990
+ }
1991
+ return names;
1992
+ }
1993
+ function withLeadingComments(source, comments, start) {
1994
+ let from = start;
1995
+ for (; ; ) {
1996
+ const before = source.slice(0, from);
1997
+ const blank = before.length - before.trimEnd().length;
1998
+ const candidate = comments.find((comment) => comment.end === from - blank);
1999
+ if (candidate === void 0) break;
2000
+ const lineStart = source.lastIndexOf("\n", candidate.start) + 1;
2001
+ if (source.slice(lineStart, candidate.start).trim() !== "") break;
2002
+ from = lineStart;
2003
+ }
2004
+ return from;
2005
+ }
2006
+ function planStaticImport(fileName, source, program, requireCall2, semi) {
2007
+ const declaration = namedRequireDeclarations(program).get(requireCall2);
2008
+ if (declaration === void 0) return void 0;
2009
+ const bound = topLevelNames(program);
2010
+ if (declaration.names.some((name) => bound.has(name))) return void 0;
2011
+ if (reachesBack(fileName, declaration.specifier)) return void 0;
2012
+ const { comments } = parseSync4(fileName, source, { sourceType: "module" });
2013
+ const lineStart = source.lastIndexOf("\n", declaration.start - 1) + 1;
2014
+ const ownsItsLine = source.slice(lineStart, declaration.start).trim() === "";
2015
+ const start = withLeadingComments(
2016
+ source,
2017
+ comments,
2018
+ ownsItsLine ? lineStart : declaration.start
2019
+ );
2020
+ return {
2021
+ line: `import { ${declaration.names.join(", ")} } from '${declaration.specifier}'${semi ? ";" : ""}`,
2022
+ // To the end of the line, so the statement does not leave an empty one behind.
2023
+ remove: { start, end: endOfLine(source, declaration.end) }
2024
+ };
2025
+ }
2026
+ function endOfLine(source, end) {
2027
+ let at = end;
2028
+ if (source[at] === ";") at += 1;
2029
+ while (source[at] === " " || source[at] === " ") at += 1;
2030
+ if (source[at] === "\r") at += 1;
2031
+ if (source[at] === "\n") at += 1;
2032
+ return at;
2033
+ }
2034
+
2035
+ // src/roles/test/codemod/named-rules.ts
1887
2036
  function isCommonJsPackage(fromFile, specifier) {
1888
2037
  if (specifier.startsWith(".") || specifier.startsWith("/")) return false;
1889
2038
  let resolved;
@@ -1895,23 +2044,26 @@ function isCommonJsPackage(fromFile, specifier) {
1895
2044
  if (!resolved.includes(`${"/"}node_modules${"/"}`)) return false;
1896
2045
  if (resolved.endsWith(".mjs")) return false;
1897
2046
  if (resolved.endsWith(".cjs")) return true;
1898
- let dir = dirname2(resolved);
2047
+ let dir = dirname3(resolved);
1899
2048
  for (let depth = 0; depth < 12; depth += 1) {
1900
- const manifest = join9(dir, "package.json");
2049
+ const manifest = join11(dir, "package.json");
1901
2050
  if (existsSync10(manifest)) {
1902
2051
  try {
1903
- return JSON.parse(readFileSync7(manifest, "utf8")).type !== "module";
2052
+ return JSON.parse(readFileSync9(manifest, "utf8")).type !== "module";
1904
2053
  } catch {
1905
2054
  return false;
1906
2055
  }
1907
2056
  }
1908
- const parent = dirname2(dir);
2057
+ const parent = dirname3(dir);
1909
2058
  if (parent === dir) break;
1910
2059
  dir = parent;
1911
2060
  }
1912
2061
  return false;
1913
2062
  }
1914
- var MEMBER_TYPES9 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
2063
+ function fileUsesSemicolons(source) {
2064
+ const first = /^import\s[^\n]*$/m.exec(source);
2065
+ return first !== null && first[0].trimEnd().endsWith(";");
2066
+ }
1915
2067
  var ASYNC_REPLACEMENTS = {
1916
2068
  requireActual: "importActual",
1917
2069
  requireMock: "importMock"
@@ -1921,7 +2073,7 @@ function mockedSpecifiers(program) {
1921
2073
  walk(program, (node) => {
1922
2074
  if (node.type !== "CallExpression") return;
1923
2075
  const callee = node.callee;
1924
- if (callee === void 0 || !MEMBER_TYPES9.has(callee.type)) return;
2076
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return;
1925
2077
  const object = callee.object;
1926
2078
  const property = callee.property;
1927
2079
  if (object?.type !== "Identifier") return;
@@ -1934,6 +2086,16 @@ function mockedSpecifiers(program) {
1934
2086
  });
1935
2087
  return found;
1936
2088
  }
2089
+ function methodValues(program) {
2090
+ const found = /* @__PURE__ */ new Set();
2091
+ walk(program, (node) => {
2092
+ const isMethod = node.type === "MethodDefinition" || node.type === "ClassMethod" || node.type === "ObjectMethod" || (node.type === "Property" || node.type === "ObjectProperty") && (node.kind === "get" || node.kind === "set" || node.method === true);
2093
+ if (!isMethod) return;
2094
+ const value = node.value ?? node.body;
2095
+ if (value !== void 0) found.add(value);
2096
+ });
2097
+ return found;
2098
+ }
1937
2099
  var FUNCTIONS = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration"]);
1938
2100
  function walkWithEnclosingFunction(node, enclosing, visit) {
1939
2101
  if (node === null || typeof node !== "object") return;
@@ -1958,7 +2120,7 @@ function bareRequire(node) {
1958
2120
  function mockFactoryReturn(node) {
1959
2121
  if (node.type !== "CallExpression") return void 0;
1960
2122
  const callee = node.callee;
1961
- if (callee === void 0 || !MEMBER_TYPES9.has(callee.type)) return void 0;
2123
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return void 0;
1962
2124
  const object = callee.object;
1963
2125
  const property = callee.property;
1964
2126
  if (object?.type !== "Identifier") return void 0;
@@ -2005,18 +2167,29 @@ function typeArgumentText(source, call) {
2005
2167
  if (node?.start === void 0 || node.end === void 0) return "";
2006
2168
  return source.slice(node.start, node.end);
2007
2169
  }
2008
- function applyEdits(source, edits) {
2009
- let result = source;
2010
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
2011
- result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
2012
- }
2013
- return result;
2014
- }
2015
2170
  function applyNamedRules(source, program, fileName = "") {
2016
2171
  const edits = [];
2017
2172
  const applied = /* @__PURE__ */ new Set();
2018
2173
  const asyncify = /* @__PURE__ */ new Set();
2174
+ const refusals = [];
2175
+ const staticImports = [];
2019
2176
  const mocked = mockedSpecifiers(program);
2177
+ const methods = methodValues(program);
2178
+ const cannotAwaitHere = (enclosing, what, call) => {
2179
+ if (enclosing === void 0 || !methods.has(enclosing)) return false;
2180
+ const plan2 = planStaticImport(fileName, source, program, call, fileUsesSemicolons(source));
2181
+ if (plan2 !== void 0) {
2182
+ edits.push({ start: plan2.remove.start, end: plan2.remove.end, text: "" });
2183
+ staticImports.push(plan2.line);
2184
+ applied.add(`${what} as a static import`);
2185
+ return true;
2186
+ }
2187
+ refusals.push({
2188
+ binding: what,
2189
+ reason: `\`${what}\` sits inside a class or object METHOD, which cannot become async here: \`async get\` is not valid, and making a method that returns void return a promise changes what every caller gets. Left as it was. Under Vitest \`require\` is Node's own resolver, which knows nothing of \`.ts\`, so this one needs a person: a static import, or the dynamic one kept on purpose.`
2190
+ });
2191
+ return true;
2192
+ };
2020
2193
  const wholeStatement = /* @__PURE__ */ new Map();
2021
2194
  walk(program, (node) => {
2022
2195
  if (node.type !== "ExpressionStatement") return;
@@ -2073,7 +2246,7 @@ function applyNamedRules(source, program, fileName = "") {
2073
2246
  }
2074
2247
  return;
2075
2248
  }
2076
- if (!MEMBER_TYPES9.has(node.type)) return;
2249
+ if (!MEMBER_TYPES.has(node.type)) return;
2077
2250
  const object = node.object;
2078
2251
  if (object !== void 0) readsANameOffIt.add(object);
2079
2252
  });
@@ -2084,6 +2257,7 @@ function applyNamedRules(source, program, fileName = "") {
2084
2257
  const [specifier] = node.arguments ?? [];
2085
2258
  const literal = specifier?.type === "StringLiteral" || specifier?.type === "Literal" ? String(specifier.value) : void 0;
2086
2259
  if (literal !== void 0 && mocked.has(literal)) {
2260
+ if (cannotAwaitHere(enclosing, "jest.requireMock", node)) return;
2087
2261
  edits.push({
2088
2262
  start: node.start,
2089
2263
  end: node.end,
@@ -2096,6 +2270,7 @@ function applyNamedRules(source, program, fileName = "") {
2096
2270
  }
2097
2271
  const asyncReplacement = member === void 0 ? void 0 : ASYNC_REPLACEMENTS[member];
2098
2272
  if (asyncReplacement !== void 0) {
2273
+ if (cannotAwaitHere(enclosing, `jest.${member}`, node)) return;
2099
2274
  edits.push({
2100
2275
  start: node.start,
2101
2276
  end: node.end,
@@ -2109,6 +2284,7 @@ function applyNamedRules(source, program, fileName = "") {
2109
2284
  return;
2110
2285
  }
2111
2286
  if (bareRequire(node)) {
2287
+ if (cannotAwaitHere(enclosing, "require", node)) return;
2112
2288
  const [specifier] = node.arguments ?? [];
2113
2289
  const literal = specifier?.type === "StringLiteral" || specifier?.type === "Literal" ? String(specifier.value) : void 0;
2114
2290
  const interop = literal !== void 0 && !readsANameOffIt.has(node) && !handedToTheRunnerAsAModule.has(node) && !wholeStatement.has(node) && isCommonJsPackage(fileName, literal) ? ".default" : "";
@@ -2143,17 +2319,36 @@ function applyNamedRules(source, program, fileName = "") {
2143
2319
  for (const fn of asyncify) {
2144
2320
  edits.push({ start: fn.start, end: fn.start, text: "async " });
2145
2321
  }
2146
- return { source: applyEdits(source, edits), applied: [...applied].sort() };
2322
+ if (staticImports.length > 0) {
2323
+ const first = (program.body ?? []).find(
2324
+ (node) => node.type === "ImportDeclaration"
2325
+ );
2326
+ const byModule = /* @__PURE__ */ new Map();
2327
+ for (const line of staticImports) {
2328
+ const parsed = /^import \{ (.+) \} from '(.+)'(;?)$/.exec(line);
2329
+ if (parsed === null) continue;
2330
+ const names = byModule.get(parsed[2]) ?? /* @__PURE__ */ new Set();
2331
+ for (const name of parsed[1].split(", ")) names.add(name);
2332
+ byModule.set(parsed[2], names);
2333
+ }
2334
+ const semi = staticImports[0]?.endsWith(";") === true ? ";" : "";
2335
+ const block = `${[...byModule].map(([specifier, names]) => `import { ${[...names].join(", ")} } from '${specifier}'${semi}`).join("\n")}
2336
+ `;
2337
+ edits.push(
2338
+ first === void 0 ? { start: 0, end: 0, text: `${block}
2339
+ ` } : { start: first.start, end: first.start, text: block }
2340
+ );
2341
+ }
2342
+ return { source: applyEdits(source, edits), applied: [...applied].sort(), refusals };
2147
2343
  }
2148
2344
 
2149
2345
  // src/roles/test/codemod/isolate-modules.ts
2150
- var MEMBER_TYPES10 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
2151
2346
  var ISOLATORS = /* @__PURE__ */ new Set(["isolateModules", "isolateModulesAsync"]);
2152
2347
  var FUNCTIONS2 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression"]);
2153
2348
  function isolatingCall(node) {
2154
2349
  if (node.type !== "CallExpression") return void 0;
2155
2350
  const callee = node.callee;
2156
- if (callee === void 0 || !MEMBER_TYPES10.has(callee.type)) return void 0;
2351
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return void 0;
2157
2352
  const object = callee.object;
2158
2353
  const property = callee.property;
2159
2354
  if (object?.type !== "Identifier" || object.name !== "jest") return void 0;
@@ -2164,7 +2359,7 @@ function isolatingCall(node) {
2164
2359
  function unusableIsolatingCall(node) {
2165
2360
  if (node.type !== "CallExpression") return void 0;
2166
2361
  const callee = node.callee;
2167
- if (callee === void 0 || !MEMBER_TYPES10.has(callee.type)) return void 0;
2362
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return void 0;
2168
2363
  const object = callee.object;
2169
2364
  const property = callee.property;
2170
2365
  if (object?.type !== "Identifier" || object.name !== "jest") return void 0;
@@ -2207,19 +2402,15 @@ ${indent}await (${callback})()`
2207
2402
  for (const fn of asyncify) {
2208
2403
  edits.push({ start: fn.start, end: fn.start, text: "async " });
2209
2404
  }
2210
- let result = source;
2211
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
2212
- result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
2213
- }
2405
+ const result = applyEdits(source, edits);
2214
2406
  return { source: result, rewritten, refusals };
2215
2407
  }
2216
2408
 
2217
2409
  // src/roles/test/codemod/mock-as-value.ts
2218
- var MEMBER_TYPES11 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
2219
2410
  function isViMockCall(node) {
2220
2411
  if (node?.type !== "CallExpression") return false;
2221
2412
  const callee = node.callee;
2222
- if (callee === void 0 || !MEMBER_TYPES11.has(callee.type)) return false;
2413
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return false;
2223
2414
  const object = callee.object;
2224
2415
  const property = callee.property;
2225
2416
  return object?.type === "Identifier" && object.name === "vi" && property?.type === "Identifier" && (property.name === "mock" || property.name === "doMock");
@@ -2244,7 +2435,7 @@ function rewriteMockAsValue(source, program) {
2244
2435
  const memberUses = /* @__PURE__ */ new Map();
2245
2436
  const otherUses = /* @__PURE__ */ new Map();
2246
2437
  walk(program, (node) => {
2247
- if (MEMBER_TYPES11.has(node.type)) {
2438
+ if (MEMBER_TYPES.has(node.type)) {
2248
2439
  const object = node.object;
2249
2440
  if (object?.type === "Identifier" && bindings.has(object.name)) {
2250
2441
  const list = memberUses.get(object.name) ?? [];
@@ -2282,20 +2473,16 @@ function rewriteMockAsValue(source, program) {
2282
2473
  }
2283
2474
  rewritten += 1;
2284
2475
  }
2285
- let result = source;
2286
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
2287
- result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
2288
- }
2476
+ const result = applyEdits(source, edits);
2289
2477
  return { source: result, rewritten, refusals };
2290
2478
  }
2291
2479
 
2292
2480
  // src/roles/test/codemod/mock-factory-default.ts
2293
- var MEMBER_TYPES12 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
2294
2481
  var BINDING = "sentinelMockedModule";
2295
2482
  function mockCall(node) {
2296
2483
  if (node.type !== "CallExpression") return void 0;
2297
2484
  const callee = node.callee;
2298
- if (callee === void 0 || !MEMBER_TYPES12.has(callee.type)) return void 0;
2485
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return void 0;
2299
2486
  const property = callee.property;
2300
2487
  if (property?.type !== "Identifier" || property.name !== "mock") return void 0;
2301
2488
  const object = callee.object;
@@ -2394,11 +2581,14 @@ function rewriteMockFactoryDefault(source, program, defaultImported) {
2394
2581
  wrap: true
2395
2582
  });
2396
2583
  });
2397
- let result = source;
2398
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
2399
- const text = edit.wrap ? `{ const ${BINDING} = ${edit.literal}; return { ...${BINDING}, default: ${BINDING} } }` : edit.literal;
2400
- result = `${result.slice(0, edit.start)}${text}${result.slice(edit.end)}`;
2401
- }
2584
+ const result = applyEdits(
2585
+ source,
2586
+ edits.map((edit) => ({
2587
+ start: edit.start,
2588
+ end: edit.end,
2589
+ text: edit.wrap ? `{ const ${BINDING} = ${edit.literal}; return { ...${BINDING}, default: ${BINDING} } }` : edit.literal
2590
+ }))
2591
+ );
2402
2592
  return {
2403
2593
  source: result,
2404
2594
  rewritten: edits.length,
@@ -2471,19 +2661,15 @@ function rewriteMockPromiseResolve(source, program) {
2471
2661
  });
2472
2662
  }
2473
2663
  });
2474
- let result = source;
2475
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
2476
- result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
2477
- }
2664
+ const result = applyEdits(source, edits);
2478
2665
  return { source: result, rewritten: edits.length };
2479
2666
  }
2480
2667
 
2481
2668
  // src/roles/test/codemod/mocked-module-binding.ts
2482
- var MEMBER_TYPES13 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
2483
2669
  function mockedSpecifier(source, node) {
2484
2670
  if (node.type !== "CallExpression") return void 0;
2485
2671
  const callee = node.callee;
2486
- if (callee === void 0 || !MEMBER_TYPES13.has(callee.type)) return void 0;
2672
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return void 0;
2487
2673
  const object = callee.object;
2488
2674
  const property = callee.property;
2489
2675
  if (object?.type !== "Identifier") return void 0;
@@ -2531,17 +2717,13 @@ function annotateMockedBindings(source, program) {
2531
2717
  });
2532
2718
  }
2533
2719
  if (edits.length === 0) return { source, rewritten: 0, typeImports: [] };
2534
- let result = source;
2535
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
2536
- result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
2537
- }
2720
+ const result = applyEdits(source, edits);
2538
2721
  return { source: result, rewritten: edits.length, typeImports: ["Mock"] };
2539
2722
  }
2540
2723
 
2541
2724
  // src/roles/test/codemod/propagate-await.ts
2542
- import { parseSync as parseSync4 } from "oxc-parser";
2725
+ import { parseSync as parseSync5 } from "oxc-parser";
2543
2726
  var FUNCTIONS3 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration"]);
2544
- var MEMBER_TYPES14 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
2545
2727
  var ALREADY_A_PROMISE = /* @__PURE__ */ new Set(["then", "catch", "finally"]);
2546
2728
  function asyncFunctionNames(program) {
2547
2729
  const names = /* @__PURE__ */ new Set();
@@ -2615,19 +2797,12 @@ function alreadyHandled(call, parent) {
2615
2797
  if (parent === void 0) return false;
2616
2798
  if (parent.type === "AwaitExpression") return true;
2617
2799
  if (parent.type === "ReturnStatement" || parent.type === "ArrowFunctionExpression") return true;
2618
- if (MEMBER_TYPES14.has(parent.type) && parent.object === call) {
2800
+ if (MEMBER_TYPES.has(parent.type) && parent.object === call) {
2619
2801
  const property = parent.property;
2620
2802
  return property?.type === "Identifier" && ALREADY_A_PROMISE.has(property.name);
2621
2803
  }
2622
2804
  return false;
2623
2805
  }
2624
- function applyEdits2(source, edits) {
2625
- let result = source;
2626
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
2627
- result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
2628
- }
2629
- return result;
2630
- }
2631
2806
  function propagateAwait(fileName, source, original, elsewhere = /* @__PURE__ */ new Set()) {
2632
2807
  const wasAsync = asyncFunctionNames(original);
2633
2808
  let current = source;
@@ -2635,7 +2810,7 @@ function propagateAwait(fileName, source, original, elsewhere = /* @__PURE__ */
2635
2810
  for (let round = 0; round < 12; round += 1) {
2636
2811
  let parsed;
2637
2812
  try {
2638
- parsed = parseSync4(fileName, current);
2813
+ parsed = parseSync5(fileName, current);
2639
2814
  } catch {
2640
2815
  return { source: current, rewritten };
2641
2816
  }
@@ -2659,16 +2834,15 @@ function propagateAwait(fileName, source, original, elsewhere = /* @__PURE__ */
2659
2834
  edits.push({ start: fn.start, end: fn.start, text: "async " });
2660
2835
  }
2661
2836
  rewritten += edits.length - asyncify.size;
2662
- current = applyEdits2(current, edits);
2837
+ current = applyEdits(current, edits);
2663
2838
  }
2664
2839
  return { source: current, rewritten };
2665
2840
  }
2666
2841
 
2667
2842
  // src/roles/test/codemod/reexport-barrel-mock.ts
2668
- import { readFileSync as readFileSync8, readdirSync as readdirSync2, statSync } from "node:fs";
2843
+ import { readFileSync as readFileSync10, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
2669
2844
  import { createRequire as createRequire3 } from "node:module";
2670
- import { dirname as dirname3, join as join10 } from "node:path";
2671
- var MEMBER_TYPES15 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
2845
+ import { dirname as dirname4, join as join12 } from "node:path";
2672
2846
  var PACKAGE_SUBPATH = /^(@[^/]+\/[^/]+|[^@.][^/]*)\/(.+)$/;
2673
2847
  var PURE_REEXPORT = /^module\.exports\s*=\s*require\(\s*(['"])([^'"]+)\1\s*\)\s*;?$/;
2674
2848
  function mockCalls(program) {
@@ -2676,7 +2850,7 @@ function mockCalls(program) {
2676
2850
  walk(program, (node) => {
2677
2851
  if (node.type !== "CallExpression") return;
2678
2852
  const callee = node.callee;
2679
- if (callee === void 0 || !MEMBER_TYPES15.has(callee.type)) return;
2853
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return;
2680
2854
  const object = callee.object;
2681
2855
  const property = callee.property;
2682
2856
  if (object?.type !== "Identifier") return;
@@ -2696,7 +2870,7 @@ function mockCalls(program) {
2696
2870
  }
2697
2871
  function packageRoot(fromFile, name) {
2698
2872
  try {
2699
- return dirname3(createRequire3(fromFile).resolve(`${name}/package.json`));
2873
+ return dirname4(createRequire3(fromFile).resolve(`${name}/package.json`));
2700
2874
  } catch {
2701
2875
  return void 0;
2702
2876
  }
@@ -2720,11 +2894,11 @@ function barrelsFor(fromFile, name, target) {
2720
2894
  const aliases = [];
2721
2895
  for (const entry of entries) {
2722
2896
  if (!entry.endsWith(".js")) continue;
2723
- const path = join10(root, entry);
2897
+ const path = join12(root, entry);
2724
2898
  let body;
2725
2899
  try {
2726
- if (!statSync(path).isFile()) continue;
2727
- body = readFileSync8(path, "utf8");
2900
+ if (!statSync2(path).isFile()) continue;
2901
+ body = readFileSync10(path, "utf8");
2728
2902
  } catch {
2729
2903
  continue;
2730
2904
  }
@@ -2764,7 +2938,7 @@ ${text}` });
2764
2938
  }
2765
2939
 
2766
2940
  // src/roles/test/codemod/repoint-msw.ts
2767
- import { parseSync as parseSync5 } from "oxc-parser";
2941
+ import { parseSync as parseSync6 } from "oxc-parser";
2768
2942
  var SENTINEL_MSW_SERVER = "@hublo/sentinel/test/msw";
2769
2943
  var SENTINEL_MSW_API = "@hublo/sentinel/test/tools/msw";
2770
2944
  var MSW_PACKAGE = /^msw$/;
@@ -2777,7 +2951,7 @@ function importsServer(declaration) {
2777
2951
  });
2778
2952
  }
2779
2953
  function repointMswServer(fileName, source) {
2780
- const { program, errors } = parseSync5(fileName, source, { sourceType: "module" });
2954
+ const { program, errors } = parseSync6(fileName, source, { sourceType: "module" });
2781
2955
  if (errors.length > 0) return { source, repointed: false };
2782
2956
  const edits = [];
2783
2957
  walk(program, (node) => {
@@ -2794,26 +2968,28 @@ function repointMswServer(fileName, source) {
2794
2968
  });
2795
2969
  });
2796
2970
  if (edits.length === 0) return { source, repointed: false };
2797
- let rewritten = source;
2798
- for (const edit of edits.sort((left, right) => right.start - left.start)) {
2799
- const quote = source[edit.start] === '"' ? '"' : "'";
2800
- rewritten = rewritten.slice(0, edit.start) + `${quote}${edit.to}${quote}` + rewritten.slice(edit.end);
2801
- }
2971
+ const rewritten = applyEdits(
2972
+ source,
2973
+ edits.map((edit) => {
2974
+ const quote = source[edit.start] === '"' ? '"' : "'";
2975
+ return { start: edit.start, end: edit.end, text: `${quote}${edit.to}${quote}` };
2976
+ })
2977
+ );
2802
2978
  return { source: rewritten, repointed: true };
2803
2979
  }
2804
2980
 
2805
2981
  // src/roles/test/codemod/setup-static-imports.ts
2806
- import { dirname as dirname5, resolve as resolve5 } from "node:path";
2982
+ import { dirname as dirname6, resolve as resolve6 } from "node:path";
2807
2983
 
2808
2984
  // src/core/context.ts
2809
2985
  import { existsSync as existsSync11 } from "node:fs";
2810
- import { dirname as dirname4, join as join12, resolve as resolve3 } from "node:path";
2986
+ import { dirname as dirname5, join as join14, resolve as resolve4 } from "node:path";
2811
2987
 
2812
2988
  // src/core/discover-modules.ts
2813
2989
  import { execFileSync as execFileSync2 } from "node:child_process";
2814
- import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync9, rmSync as rmSync2 } from "node:fs";
2990
+ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync11, rmSync as rmSync2 } from "node:fs";
2815
2991
  import { tmpdir as tmpdir2 } from "node:os";
2816
- import { join as join11 } from "node:path";
2992
+ import { join as join13 } from "node:path";
2817
2993
  function runNx(cwd, args) {
2818
2994
  const nx = resolveBin(cwd, "nx") ?? "nx";
2819
2995
  try {
@@ -2831,11 +3007,11 @@ function runNx(cwd, args) {
2831
3007
  }
2832
3008
  }
2833
3009
  function readGraph(cwd) {
2834
- const dir = mkdtempSync2(join11(tmpdir2(), "sentinel-nx-"));
2835
- const file = join11(dir, "graph.json");
3010
+ const dir = mkdtempSync2(join13(tmpdir2(), "sentinel-nx-"));
3011
+ const file = join13(dir, "graph.json");
2836
3012
  try {
2837
3013
  runNx(cwd, ["graph", "--file", file]);
2838
- const parsed = JSON.parse(readFileSync9(file, "utf8"));
3014
+ const parsed = JSON.parse(readFileSync11(file, "utf8"));
2839
3015
  const nodes = parsed.graph?.nodes;
2840
3016
  if (!nodes || typeof nodes !== "object") {
2841
3017
  throw new Error(
@@ -2844,38 +3020,61 @@ function readGraph(cwd) {
2844
3020
  }
2845
3021
  return Object.entries(nodes).map(([name, node]) => ({
2846
3022
  name,
2847
- root: join11(cwd, node.data.root)
3023
+ root: join13(cwd, node.data.root)
2848
3024
  }));
2849
3025
  } finally {
2850
3026
  rmSync2(dir, { recursive: true, force: true });
2851
3027
  }
2852
3028
  }
3029
+ function parseProjectNames(raw) {
3030
+ const asNames = (text) => {
3031
+ try {
3032
+ const parsed = JSON.parse(text);
3033
+ return Array.isArray(parsed) && parsed.every((one) => typeof one === "string") ? parsed : void 0;
3034
+ } catch {
3035
+ return void 0;
3036
+ }
3037
+ };
3038
+ const end = raw.lastIndexOf("]");
3039
+ if (end === -1) return void 0;
3040
+ for (let start = raw.indexOf("["); start !== -1 && start < end; start = raw.indexOf("[", start + 1)) {
3041
+ const names = asNames(raw.slice(start, end + 1));
3042
+ if (names !== void 0) return names;
3043
+ }
3044
+ return void 0;
3045
+ }
2853
3046
  function discoverModules(cwd, options = {}) {
2854
3047
  const modules = readGraph(cwd);
2855
3048
  if (!options.affected) return modules;
2856
- const affected = new Set(
2857
- JSON.parse(runNx(cwd, ["show", "projects", "--affected", "--json"]))
2858
- );
3049
+ const raw = runNx(cwd, ["show", "projects", "--affected", "--json"]);
3050
+ const names = parseProjectNames(raw);
3051
+ if (names === void 0) {
3052
+ throw new Error(
3053
+ `sentinel: could not read the affected projects from nx. It answered:
3054
+ ${raw.slice(0, 300)}`
3055
+ );
3056
+ }
3057
+ const affected = new Set(names);
2859
3058
  return modules.filter((module) => affected.has(module.name));
2860
3059
  }
2861
3060
 
2862
3061
  // src/core/context.ts
2863
3062
  var MODULE_MARKERS = ["package.json", "project.json"];
2864
3063
  function isModuleDir(cwd) {
2865
- return MODULE_MARKERS.some((marker) => existsSync11(join12(cwd, marker)));
3064
+ return MODULE_MARKERS.some((marker) => existsSync11(join14(cwd, marker)));
2866
3065
  }
2867
3066
  function findModuleRoot(startDir, workspaceRoot) {
2868
- let dir = resolve3(startDir);
3067
+ let dir = resolve4(startDir);
2869
3068
  for (; ; ) {
2870
- if (workspaceRoot !== void 0 && dir === resolve3(workspaceRoot)) return void 0;
3069
+ if (workspaceRoot !== void 0 && dir === resolve4(workspaceRoot)) return void 0;
2871
3070
  if (isModuleDir(dir)) return dir;
2872
- const parent = dirname4(dir);
3071
+ const parent = dirname5(dir);
2873
3072
  if (parent === dir) return void 0;
2874
3073
  dir = parent;
2875
3074
  }
2876
3075
  }
2877
3076
  function resolveContext(cwd, opts) {
2878
- const atRoot = existsSync11(join12(cwd, WORKSPACE_ROOT_MARKER));
3077
+ const atRoot = existsSync11(join14(cwd, WORKSPACE_ROOT_MARKER));
2879
3078
  if (!atRoot && isModuleDir(cwd)) {
2880
3079
  if (opts.module) {
2881
3080
  throw new Error(
@@ -2903,16 +3102,16 @@ function resolveContext(cwd, opts) {
2903
3102
  }
2904
3103
 
2905
3104
  // src/roles/test/codemod/workspace-specifier.ts
2906
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
2907
- import { join as join13, relative as relative2, resolve as resolve4 } from "node:path";
2908
- var EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", "/index.ts", "/index.js"];
3105
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "node:fs";
3106
+ import { join as join15, relative as relative2, resolve as resolve5 } from "node:path";
3107
+ var EXTENSIONS2 = ["", ".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", "/index.ts", "/index.js"];
2909
3108
  function pathEntries(workspaceRoot) {
2910
- const file = join13(workspaceRoot, "tsconfig.base.json");
3109
+ const file = join15(workspaceRoot, "tsconfig.base.json");
2911
3110
  if (!existsSync12(file)) return [];
2912
3111
  let paths;
2913
3112
  try {
2914
3113
  paths = parseJsonc(
2915
- readFileSync10(file, "utf8"),
3114
+ readFileSync12(file, "utf8"),
2916
3115
  "tsconfig.base.json"
2917
3116
  )?.compilerOptions?.paths;
2918
3117
  } catch {
@@ -2925,17 +3124,17 @@ function pathEntries(workspaceRoot) {
2925
3124
  const wildcard = key.endsWith("/*") && target.endsWith("/*");
2926
3125
  entries.push({
2927
3126
  key: wildcard ? key.slice(0, -2) : key,
2928
- target: resolve4(workspaceRoot, wildcard ? target.slice(0, -2) : target),
3127
+ target: resolve5(workspaceRoot, wildcard ? target.slice(0, -2) : target),
2929
3128
  wildcard
2930
3129
  });
2931
3130
  }
2932
3131
  return entries.sort((left, right) => right.target.length - left.target.length);
2933
3132
  }
2934
3133
  function workspaceSpecifier(workspaceRoot, file) {
2935
- const absolute = resolve4(file);
3134
+ const absolute = resolve5(file);
2936
3135
  for (const entry of pathEntries(workspaceRoot)) {
2937
3136
  if (!entry.wildcard) {
2938
- if (EXTENSIONS.some((extension) => `${entry.target}${extension}` === absolute)) {
3137
+ if (EXTENSIONS2.some((extension) => `${entry.target}${extension}` === absolute)) {
2939
3138
  return entry.key;
2940
3139
  }
2941
3140
  continue;
@@ -2961,9 +3160,9 @@ function requireCall(source, node) {
2961
3160
  }
2962
3161
  function importable(written, fileName, workspaceRoot) {
2963
3162
  if (!written.startsWith(".")) return written;
2964
- const fileDir = dirname5(fileName);
3163
+ const fileDir = dirname6(fileName);
2965
3164
  const moduleRoot = findModuleRoot(fileDir, workspaceRoot) ?? fileDir;
2966
- const target = resolve5(fileDir, written);
3165
+ const target = resolve6(fileDir, written);
2967
3166
  if (target.startsWith(`${moduleRoot}/`)) return written;
2968
3167
  if (workspaceRoot === void 0) return void 0;
2969
3168
  return workspaceSpecifier(workspaceRoot, target);
@@ -3009,10 +3208,7 @@ function rewriteSetupStaticImports(fileName, source, program, workspaceRoot) {
3009
3208
  if (text === void 0) break;
3010
3209
  edits.push({ start: node.start, end: node.end, text });
3011
3210
  }
3012
- let result = source;
3013
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
3014
- result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
3015
- }
3211
+ const result = applyEdits(source, edits);
3016
3212
  return { source: result, rewritten: edits.length, refusals };
3017
3213
  }
3018
3214
 
@@ -3020,7 +3216,6 @@ function rewriteSetupStaticImports(fileName, source, program, workspaceRoot) {
3020
3216
  var SPY_TYPES = /* @__PURE__ */ new Set(["SpyInstance"]);
3021
3217
  var QUALIFIED_ONLY = /* @__PURE__ */ new Set(["Mock"]);
3022
3218
  var VITEST_NAME = { SpyInstance: "MockInstance", Mock: "Mock" };
3023
- var MEMBER_TYPES16 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
3024
3219
  function typeNameOf(source, node) {
3025
3220
  const name = node.typeName;
3026
3221
  if (name === void 0) return void 0;
@@ -3048,10 +3243,7 @@ function rewriteSpyInstanceType(source, program) {
3048
3243
  imports: target
3049
3244
  });
3050
3245
  });
3051
- let result = source;
3052
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
3053
- result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
3054
- }
3246
+ const result = applyEdits(source, edits);
3055
3247
  return {
3056
3248
  source: result,
3057
3249
  rewritten: edits.length,
@@ -3064,7 +3256,7 @@ function rewriteMockFactoryGenerics(source, program) {
3064
3256
  walk(program, (node) => {
3065
3257
  if (node.type !== "CallExpression") return;
3066
3258
  const callee = node.callee;
3067
- if (callee === void 0 || !MEMBER_TYPES16.has(callee.type)) return;
3259
+ if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return;
3068
3260
  const object = callee.object;
3069
3261
  const property = callee.property;
3070
3262
  if (object?.type !== "Identifier" || object.name !== "jest") return;
@@ -3079,10 +3271,7 @@ function rewriteMockFactoryGenerics(source, program) {
3079
3271
  text: `<(...args: ${text(args[1])}) => ${text(args[0])}>`
3080
3272
  });
3081
3273
  });
3082
- let result = source;
3083
- for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
3084
- result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
3085
- }
3274
+ const result = applyEdits(source, edits);
3086
3275
  return { source: result, rewritten: edits.length, typeImports: [] };
3087
3276
  }
3088
3277
 
@@ -3103,8 +3292,8 @@ function mockFactories(program) {
3103
3292
  });
3104
3293
  return factories;
3105
3294
  }
3106
- function parse3(fileName, source) {
3107
- const { program, errors } = parseSync6(fileName, source, { sourceType: "module" });
3295
+ function parse2(fileName, source) {
3296
+ const { program, errors } = parseSync7(fileName, source, { sourceType: "module" });
3108
3297
  return errors.length > 0 ? void 0 : program;
3109
3298
  }
3110
3299
  function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new Set(), asyncHelpers = /* @__PURE__ */ new Set(), modulesOwnMsw = false) {
@@ -3128,66 +3317,66 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
3128
3317
  }
3129
3318
  ]
3130
3319
  });
3131
- const beforeStatic = parse3(fileName, source);
3320
+ const beforeStatic = parse2(fileName, source);
3132
3321
  if (beforeStatic === void 0) return unparsable("reading the file");
3133
3322
  const statics = rewriteSetupStaticImports(
3134
3323
  fileName,
3135
3324
  source,
3136
3325
  beforeStatic,
3137
- findWorkspaceRoot(dirname6(fileName))
3326
+ findWorkspaceRoot(dirname7(fileName))
3138
3327
  );
3139
3328
  source = statics.source;
3140
- const original = statics.rewritten === 0 ? beforeStatic : parse3(fileName, source);
3329
+ const original = statics.rewritten === 0 ? beforeStatic : parse2(fileName, source);
3141
3330
  if (original === void 0) return unparsable("hoisting the setup requires");
3142
3331
  const titles = rewriteEachTitles(source, original);
3143
3332
  const tables = rewriteEachTableRows(titles.source, original);
3144
- const afterTables = parse3(fileName, tables.source);
3333
+ const afterTables = parse2(fileName, tables.source);
3145
3334
  if (afterTables === void 0) return unparsable("each table rows");
3146
3335
  const spyTypes = rewriteSpyInstanceType(tables.source, afterTables);
3147
- const afterSpyTypes = parse3(fileName, spyTypes.source);
3336
+ const afterSpyTypes = parse2(fileName, spyTypes.source);
3148
3337
  if (afterSpyTypes === void 0) return unparsable("SpyInstance types");
3149
3338
  const factoryGenerics = rewriteMockFactoryGenerics(spyTypes.source, afterSpyTypes);
3150
- const afterFactoryGenerics = parse3(fileName, factoryGenerics.source);
3339
+ const afterFactoryGenerics = parse2(fileName, factoryGenerics.source);
3151
3340
  if (afterFactoryGenerics === void 0) return unparsable("mock factory generics");
3152
3341
  const ruled = applyNamedRules(factoryGenerics.source, afterFactoryGenerics, fileName);
3153
3342
  const renamed = renameJestToVitest(fileName, ruled.source);
3154
- const afterRename = parse3(fileName, renamed.source);
3343
+ const afterRename = parse2(fileName, renamed.source);
3155
3344
  if (afterRename === void 0) return unparsable("jest to vi rename");
3156
3345
  const fakeTimers = rewriteFakeTimerOptions(renamed.source, afterRename);
3157
- const afterFakeTimers = parse3(fileName, fakeTimers.source);
3346
+ const afterFakeTimers = parse2(fileName, fakeTimers.source);
3158
3347
  if (afterFakeTimers === void 0) return unparsable("fake timer options");
3159
3348
  const mocks = rewriteMockAsValue(fakeTimers.source, afterFakeTimers);
3160
- const afterMocks = parse3(fileName, mocks.source);
3349
+ const afterMocks = parse2(fileName, mocks.source);
3161
3350
  if (afterMocks === void 0) return unparsable("mock as a value");
3162
3351
  const constructible = rewriteConstructibleMocks(
3163
3352
  mocks.source,
3164
3353
  afterMocks,
3165
3354
  mockFactories(afterMocks)
3166
3355
  );
3167
- const afterConstructible = parse3(fileName, constructible.source);
3356
+ const afterConstructible = parse2(fileName, constructible.source);
3168
3357
  if (afterConstructible === void 0) return unparsable("constructible mocks");
3169
3358
  const factoryDefault = rewriteMockFactoryDefault(
3170
3359
  constructible.source,
3171
3360
  afterConstructible,
3172
3361
  defaultImported
3173
3362
  );
3174
- const beforeReturnValue = parse3(fileName, factoryDefault.source);
3363
+ const beforeReturnValue = parse2(fileName, factoryDefault.source);
3175
3364
  if (beforeReturnValue === void 0) return unparsable("mock factory default");
3176
3365
  const returnValue = rewriteAssertionReturnValue(factoryDefault.source, beforeReturnValue);
3177
- const afterReturnValue = parse3(fileName, returnValue.source);
3366
+ const afterReturnValue = parse2(fileName, returnValue.source);
3178
3367
  if (afterReturnValue === void 0) return unparsable("assertion return value");
3179
3368
  const isolated = rewriteIsolateModules(returnValue.source, afterReturnValue);
3180
- const afterIsolate = parse3(fileName, isolated.source);
3369
+ const afterIsolate = parse2(fileName, isolated.source);
3181
3370
  if (afterIsolate === void 0) return unparsable("isolate modules");
3182
3371
  const emptyImplementations = rewriteEmptyMockImplementation(isolated.source, afterIsolate);
3183
- const afterEmpty = parse3(fileName, emptyImplementations.source);
3372
+ const afterEmpty = parse2(fileName, emptyImplementations.source);
3184
3373
  if (afterEmpty === void 0) return unparsable("empty mock implementation");
3185
3374
  const hooks = rewriteHookReturnValue(emptyImplementations.source, afterEmpty);
3186
- const afterHooks = parse3(fileName, hooks.source);
3375
+ const afterHooks = parse2(fileName, hooks.source);
3187
3376
  if (afterHooks === void 0) return unparsable("hook return value");
3188
3377
  const promises = rewriteMockPromiseResolve(hooks.source, afterHooks);
3189
3378
  const awaited = propagateAwait(fileName, promises.source, original, asyncHelpers);
3190
- const program = parse3(fileName, awaited.source);
3379
+ const program = parse2(fileName, awaited.source);
3191
3380
  if (program === void 0) return unparsable("hook body");
3192
3381
  const hoist = hoistCapturedBindings(
3193
3382
  awaited.source,
@@ -3196,10 +3385,10 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
3196
3385
  moduleBindings(program),
3197
3386
  importedNames(program)
3198
3387
  );
3199
- const afterHoist = parse3(fileName, hoist.source);
3388
+ const afterHoist = parse2(fileName, hoist.source);
3200
3389
  if (afterHoist === void 0) return unparsable("hoisting captured bindings");
3201
3390
  const mockedBindings = annotateMockedBindings(hoist.source, afterHoist);
3202
- const afterBindings = parse3(fileName, mockedBindings.source);
3391
+ const afterBindings = parse2(fileName, mockedBindings.source);
3203
3392
  if (afterBindings === void 0) return unparsable("mocked module bindings");
3204
3393
  const bareFactories = invokeBareMockFactories(mockedBindings.source, afterBindings);
3205
3394
  const withImport = injectTypeImport(fileName, bareFactories.source, [
@@ -3209,7 +3398,7 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
3209
3398
  ]);
3210
3399
  const deep = normaliseDeepImports(withImport);
3211
3400
  const repointed = modulesOwnMsw ? { source: deep.source, repointed: false } : repointMswServer(fileName, deep.source);
3212
- const finalProgram = parse3(fileName, repointed.source);
3401
+ const finalProgram = parse2(fileName, repointed.source);
3213
3402
  if (finalProgram === void 0) return unparsable("msw server import");
3214
3403
  const barrels = rewriteReexportBarrelMocks(fileName, repointed.source, finalProgram);
3215
3404
  return {
@@ -3249,7 +3438,8 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
3249
3438
  ...hoist.refusals,
3250
3439
  ...mocks.refusals.map((refusal) => ({ binding: refusal.binding, reason: refusal.reason })),
3251
3440
  ...constructible.refusals,
3252
- ...isolated.refusals
3441
+ ...isolated.refusals,
3442
+ ...ruled.refusals
3253
3443
  ],
3254
3444
  warnings: fakeTimers.warnings,
3255
3445
  mswRepointed: repointed.repointed
@@ -3257,13 +3447,13 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
3257
3447
  }
3258
3448
 
3259
3449
  // src/roles/test/codemod/test-support-files.ts
3260
- import { existsSync as existsSync13, readFileSync as readFileSync11, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
3261
- import { dirname as dirname7, join as join14, relative as relative3, resolve as resolve6 } from "node:path";
3450
+ import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
3451
+ import { dirname as dirname8, join as join16, relative as relative3, resolve as resolve7 } from "node:path";
3262
3452
  var SKIPPED = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".git", ".vite"]);
3263
3453
  var TEST_FILE2 = /\.(spec|test)\.[cm]?[jt]sx?$/;
3264
3454
  var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
3265
- var EXTENSIONS2 = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"];
3266
- function relativeSpecifiers(source) {
3455
+ var EXTENSIONS3 = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"];
3456
+ function relativeSpecifiers2(source) {
3267
3457
  const found = [];
3268
3458
  const patterns = [
3269
3459
  /\bfrom\s+['"](\.[^'"]*)['"]/g,
@@ -3277,13 +3467,13 @@ function relativeSpecifiers(source) {
3277
3467
  return found;
3278
3468
  }
3279
3469
  function resolveSpecifier(fromFile, specifier) {
3280
- const base = resolve6(dirname7(fromFile), specifier);
3281
- if (existsSync13(base) && statSync2(base).isFile()) return base;
3282
- for (const extension of EXTENSIONS2) {
3470
+ const base = resolve7(dirname8(fromFile), specifier);
3471
+ if (existsSync13(base) && statSync3(base).isFile()) return base;
3472
+ for (const extension of EXTENSIONS3) {
3283
3473
  if (existsSync13(`${base}${extension}`)) return `${base}${extension}`;
3284
3474
  }
3285
- for (const extension of EXTENSIONS2) {
3286
- const index = join14(base, `index${extension}`);
3475
+ for (const extension of EXTENSIONS3) {
3476
+ const index = join16(base, `index${extension}`);
3287
3477
  if (existsSync13(index)) return index;
3288
3478
  }
3289
3479
  return void 0;
@@ -3301,9 +3491,9 @@ function moduleSources(cwd) {
3301
3491
  }
3302
3492
  for (const entry of entries) {
3303
3493
  if (entry.isDirectory()) {
3304
- if (!SKIPPED.has(entry.name)) stack.push(join14(dir, entry.name));
3494
+ if (!SKIPPED.has(entry.name)) stack.push(join16(dir, entry.name));
3305
3495
  } else if (SOURCE_FILE.test(entry.name)) {
3306
- found.push(join14(dir, entry.name));
3496
+ found.push(join16(dir, entry.name));
3307
3497
  }
3308
3498
  }
3309
3499
  }
@@ -3311,7 +3501,7 @@ function moduleSources(cwd) {
3311
3501
  }
3312
3502
  function read(path) {
3313
3503
  try {
3314
- return readFileSync11(path, "utf8");
3504
+ return readFileSync13(path, "utf8");
3315
3505
  } catch {
3316
3506
  return void 0;
3317
3507
  }
@@ -3320,7 +3510,7 @@ function setupFilesInside(cwd, named) {
3320
3510
  const kept = [];
3321
3511
  for (const raw of named) {
3322
3512
  const withoutToken = raw.replaceAll("<rootDir>/", "").replaceAll("<rootDir>", ".");
3323
- const full = resolve6(cwd, withoutToken);
3513
+ const full = resolve7(cwd, withoutToken);
3324
3514
  const inside = relative3(cwd, full);
3325
3515
  if (inside.startsWith("..") || inside === "") continue;
3326
3516
  if (!existsSync13(full)) continue;
@@ -3337,7 +3527,7 @@ function listTestSupportFiles(cwd, setupFiles = []) {
3337
3527
  }
3338
3528
  const importedBy = /* @__PURE__ */ new Map();
3339
3529
  for (const [file, source] of contents) {
3340
- for (const specifier of relativeSpecifiers(source)) {
3530
+ for (const specifier of relativeSpecifiers2(source)) {
3341
3531
  const target = resolveSpecifier(file, specifier);
3342
3532
  if (target === void 0 || !contents.has(target)) continue;
3343
3533
  importedBy.set(target, [...importedBy.get(target) ?? [], file]);
@@ -3350,7 +3540,7 @@ function listTestSupportFiles(cwd, setupFiles = []) {
3350
3540
  );
3351
3541
  while (queue.length > 0) {
3352
3542
  const file = queue.shift();
3353
- for (const specifier of relativeSpecifiers(contents.get(file) ?? "")) {
3543
+ for (const specifier of relativeSpecifiers2(contents.get(file) ?? "")) {
3354
3544
  const target = resolveSpecifier(file, specifier);
3355
3545
  if (target === void 0 || TEST_FILE2.test(target) || testOnly.has(target)) continue;
3356
3546
  if (!isTestOnly(target)) continue;
@@ -3363,13 +3553,13 @@ function listTestSupportFiles(cwd, setupFiles = []) {
3363
3553
  }
3364
3554
 
3365
3555
  // src/roles/test/codemod/migrate-module.ts
3366
- function parse4(source) {
3367
- const { program, errors } = parseSync7("file.ts", source, { sourceType: "module" });
3556
+ function parse3(source) {
3557
+ const { program, errors } = parseSync8("file.ts", source, { sourceType: "module" });
3368
3558
  return errors.length > 0 ? void 0 : program;
3369
3559
  }
3370
3560
  function read2(path) {
3371
3561
  try {
3372
- return readFileSync12(path, "utf8");
3562
+ return readFileSync14(path, "utf8");
3373
3563
  } catch {
3374
3564
  return void 0;
3375
3565
  }
@@ -3387,10 +3577,10 @@ function defaultImportedSpecifiers(cwd) {
3387
3577
  }
3388
3578
  for (const entry of entries) {
3389
3579
  if (SKIPPED2.has(entry)) continue;
3390
- const path = join15(dir, entry);
3580
+ const path = join17(dir, entry);
3391
3581
  let directory;
3392
3582
  try {
3393
- directory = statSync3(path).isDirectory();
3583
+ directory = statSync4(path).isDirectory();
3394
3584
  } catch {
3395
3585
  continue;
3396
3586
  }
@@ -3418,23 +3608,33 @@ function migrateModuleTests(cwd, setupFiles = []) {
3418
3608
  const defaultImported = defaultImportedSpecifiers(cwd);
3419
3609
  const asyncHelpers = /* @__PURE__ */ new Set();
3420
3610
  for (const relative6 of listTestSupportFiles(cwd, setupFiles)) {
3421
- const source = read2(join15(cwd, relative6));
3611
+ const source = read2(join17(cwd, relative6));
3422
3612
  if (source === void 0) {
3423
3613
  blockers.push({ file: relative6, reason: "this file could not be read" });
3424
3614
  continue;
3425
3615
  }
3426
3616
  const migrated = migrateTestFile(
3427
- join15(cwd, relative6),
3617
+ join17(cwd, relative6),
3428
3618
  source,
3429
3619
  defaultImported,
3430
3620
  /* @__PURE__ */ new Set(),
3431
3621
  modulesOwnMsw
3432
3622
  );
3623
+ for (const member of migrated.unhandled) {
3624
+ blockers.push({
3625
+ file: `${relative6}:${member.line}`,
3626
+ reason: member.rule === void 0 ? `${member.member} has no rule, and guessing one is how a suite goes green for the wrong reason` : `${member.member} needs ${member.rule}`
3627
+ });
3628
+ }
3629
+ for (const refusal of migrated.refusals) {
3630
+ blockers.push({ file: relative6, reason: refusal.reason });
3631
+ }
3632
+ collected.push(...migrated.warnings);
3433
3633
  const renamedTo = vitestSupportName(relative6);
3434
3634
  if (!migrated.changed && renamedTo === relative6) continue;
3435
3635
  if (renamedTo !== relative6) operations.push({ kind: "delete", path: relative6 });
3436
- const before = parse4(source);
3437
- const after = parse4(migrated.source);
3636
+ const before = parse3(source);
3637
+ const after = parse3(migrated.source);
3438
3638
  if (before !== void 0 && after !== void 0) {
3439
3639
  const wasAsync = asyncFunctionNames(before);
3440
3640
  for (const name of asyncFunctionNames(after)) {
@@ -3443,17 +3643,17 @@ function migrateModuleTests(cwd, setupFiles = []) {
3443
3643
  }
3444
3644
  files++;
3445
3645
  renamed += migrated.renamed;
3446
- const contents = /\bvi\./.test(migrated.source) ? injectViImport(join15(cwd, renamedTo), migrated.source) : migrated.source;
3646
+ const contents = /\bvi\./.test(migrated.source) ? injectViImport(join17(cwd, renamedTo), migrated.source) : migrated.source;
3447
3647
  operations.push({ kind: "write", path: renamedTo, contents });
3448
3648
  }
3449
3649
  for (const relative6 of listTestFiles(cwd)) {
3450
- const source = read2(join15(cwd, relative6));
3650
+ const source = read2(join17(cwd, relative6));
3451
3651
  if (source === void 0) {
3452
3652
  blockers.push({ file: relative6, reason: "this file could not be read" });
3453
3653
  continue;
3454
3654
  }
3455
3655
  const migrated = migrateTestFile(
3456
- join15(cwd, relative6),
3656
+ join17(cwd, relative6),
3457
3657
  source,
3458
3658
  defaultImported,
3459
3659
  asyncHelpers,
@@ -3480,8 +3680,8 @@ function migrateModuleTests(cwd, setupFiles = []) {
3480
3680
  }
3481
3681
 
3482
3682
  // src/roles/test/nest/generate-config.ts
3483
- import { readdirSync as readdirSync5, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
3484
- import { join as join16, relative as relative4 } from "node:path";
3683
+ import { readdirSync as readdirSync5, readFileSync as readFileSync15, statSync as statSync5 } from "node:fs";
3684
+ import { join as join18, relative as relative4 } from "node:path";
3485
3685
  var NEST_TEST_PRESET_SPECIFIER = "@hublo/sentinel/test/nest";
3486
3686
  var REACT_TEST_PRESET_SPECIFIER = "@hublo/sentinel/test/react";
3487
3687
  function flavourOf(reading) {
@@ -3527,10 +3727,10 @@ function readModuleTraits(cwd) {
3527
3727
  }
3528
3728
  for (const entry of entries) {
3529
3729
  if (SKIPPED3.has(entry)) continue;
3530
- const path = join16(dir, entry);
3730
+ const path = join18(dir, entry);
3531
3731
  let directory;
3532
3732
  try {
3533
- directory = statSync4(path).isDirectory();
3733
+ directory = statSync5(path).isDirectory();
3534
3734
  } catch {
3535
3735
  continue;
3536
3736
  }
@@ -3541,7 +3741,7 @@ function readModuleTraits(cwd) {
3541
3741
  if (!/\.tsx?$/.test(entry)) continue;
3542
3742
  let source;
3543
3743
  try {
3544
- source = readFileSync13(path, "utf8");
3744
+ source = readFileSync15(path, "utf8");
3545
3745
  } catch {
3546
3746
  continue;
3547
3747
  }
@@ -3674,8 +3874,8 @@ ${overrides.join("\n")}
3674
3874
  }
3675
3875
 
3676
3876
  // src/roles/test/nest/read-jest-config.ts
3677
- import { existsSync as existsSync14, readFileSync as readFileSync14 } from "node:fs";
3678
- import { join as join17 } from "node:path";
3877
+ import { existsSync as existsSync14, readFileSync as readFileSync16 } from "node:fs";
3878
+ import { join as join19 } from "node:path";
3679
3879
  var CARRIED = [
3680
3880
  "testEnvironment",
3681
3881
  "coverageDirectory",
@@ -3865,7 +4065,7 @@ function extendedConfig(source, cwd) {
3865
4065
  (name) => name.replace(/^\.\//, "")
3866
4066
  );
3867
4067
  const file = candidates.find(
3868
- (name) => /^jest[\w.-]*\.(ts|js)$/.test(name) && existsSync14(join17(cwd, name))
4068
+ (name) => /^jest[\w.-]*\.(ts|js)$/.test(name) && existsSync14(join19(cwd, name))
3869
4069
  );
3870
4070
  if (file === void 0) continue;
3871
4071
  if (!new RegExp(String.raw`\{\s*\.\.\.${binding}\b`).test(source)) continue;
@@ -3892,11 +4092,11 @@ function sideEffects(source, extended) {
3892
4092
  return found;
3893
4093
  }
3894
4094
  function readJestConfig(cwd, named) {
3895
- const file = named ?? JEST_CONFIG_FILES.find((name) => existsSync14(join17(cwd, name)));
3896
- if (file === void 0 || !existsSync14(join17(cwd, file))) return void 0;
4095
+ const file = named ?? JEST_CONFIG_FILES.find((name) => existsSync14(join19(cwd, name)));
4096
+ if (file === void 0 || !existsSync14(join19(cwd, file))) return void 0;
3897
4097
  let source;
3898
4098
  try {
3899
- source = readFileSync14(join17(cwd, file), "utf8");
4099
+ source = readFileSync16(join19(cwd, file), "utf8");
3900
4100
  } catch {
3901
4101
  return { file, refusals: [`${file} could not be read`], notes: [] };
3902
4102
  }
@@ -3978,7 +4178,7 @@ function readJestConfig(cwd, named) {
3978
4178
 
3979
4179
  // src/roles/test/nest/run-modes.ts
3980
4180
  import { existsSync as existsSync15 } from "node:fs";
3981
- import { basename as basename2, join as join18 } from "node:path";
4181
+ import { basename as basename2, join as join20 } from "node:path";
3982
4182
  var JEST_EXECUTOR = "@nx/jest:jest";
3983
4183
  var PRIMARY_CONFIG_FILE = "vitest.config.mts";
3984
4184
  var PRIMARY_TARGET = "test";
@@ -3999,7 +4199,7 @@ function runModes(cwd, fallback) {
3999
4199
  const jestConfig = entry.options?.jestConfig;
4000
4200
  if (jestConfig === void 0) continue;
4001
4201
  const name = configName(jestConfig);
4002
- if (!existsSync15(join18(cwd, name))) continue;
4202
+ if (!existsSync15(join20(cwd, name))) continue;
4003
4203
  declared.push({
4004
4204
  target,
4005
4205
  jestConfig: name,
@@ -4025,11 +4225,11 @@ function runModes(cwd, fallback) {
4025
4225
 
4026
4226
  // src/roles/test/written-file-formatter.ts
4027
4227
  import { existsSync as existsSync17 } from "node:fs";
4028
- import { dirname as dirname9, join as join21 } from "node:path";
4228
+ import { dirname as dirname10, join as join23 } from "node:path";
4029
4229
 
4030
4230
  // src/roles/format/read-adoption.ts
4031
- import { existsSync as existsSync16, readFileSync as readFileSync15 } from "node:fs";
4032
- import { join as join19 } from "node:path";
4231
+ import { existsSync as existsSync16, readFileSync as readFileSync17 } from "node:fs";
4232
+ import { join as join21 } from "node:path";
4033
4233
 
4034
4234
  // src/roles/format/config-policy.ts
4035
4235
  var FORMAT_CONFIG_FILE = ".oxfmtrc.json";
@@ -4135,11 +4335,11 @@ function sameValue(a, b) {
4135
4335
  return JSON.stringify(a) === JSON.stringify(b);
4136
4336
  }
4137
4337
  function readFormatAdoption(cwd) {
4138
- const path = join19(cwd, FORMAT_CONFIG_FILE);
4338
+ const path = join21(cwd, FORMAT_CONFIG_FILE);
4139
4339
  if (!existsSync16(path)) return NOT_ADOPTED(null);
4140
4340
  let parsed;
4141
4341
  try {
4142
- parsed = parseJsonc(readFileSync15(path, "utf8"), FORMAT_CONFIG_FILE);
4342
+ parsed = parseJsonc(readFileSync17(path, "utf8"), FORMAT_CONFIG_FILE);
4143
4343
  } catch (error) {
4144
4344
  const reason = error instanceof Error ? error.message : String(error);
4145
4345
  return NOT_ADOPTED(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
@@ -4183,9 +4383,9 @@ function readFormatAdoption(cwd) {
4183
4383
  }
4184
4384
 
4185
4385
  // src/roles/format/resolve-oxfmt.ts
4186
- import { readFileSync as readFileSync16 } from "node:fs";
4386
+ import { readFileSync as readFileSync18 } from "node:fs";
4187
4387
  import { createRequire as createRequire4 } from "node:module";
4188
- import { dirname as dirname8, join as join20 } from "node:path";
4388
+ import { dirname as dirname9, join as join22 } from "node:path";
4189
4389
  function resolveOxfmt(cwd) {
4190
4390
  return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
4191
4391
  }
@@ -4214,8 +4414,8 @@ function configSchema() {
4214
4414
  function readConfigSchema() {
4215
4415
  try {
4216
4416
  const manifest = createRequire4(import.meta.url).resolve("oxfmt/package.json");
4217
- const schemaPath = join20(dirname8(manifest), "configuration_schema.json");
4218
- return JSON.parse(readFileSync16(schemaPath, "utf8"));
4417
+ const schemaPath = join22(dirname9(manifest), "configuration_schema.json");
4418
+ return JSON.parse(readFileSync18(schemaPath, "utf8"));
4219
4419
  } catch {
4220
4420
  return void 0;
4221
4421
  }
@@ -4240,9 +4440,9 @@ function prettierConfigFrom(cwd) {
4240
4440
  let dir = cwd;
4241
4441
  for (; ; ) {
4242
4442
  for (const name of PRETTIER_CONFIGS) {
4243
- if (existsSync17(join21(dir, name))) return join21(dir, name);
4443
+ if (existsSync17(join23(dir, name))) return join23(dir, name);
4244
4444
  }
4245
- const parent = dirname9(dir);
4445
+ const parent = dirname10(dir);
4246
4446
  if (parent === dir) return void 0;
4247
4447
  dir = parent;
4248
4448
  }
@@ -4270,12 +4470,12 @@ function writtenFileFormatter(cwd) {
4270
4470
  var SPEC_TSCONFIG = "tsconfig.spec.json";
4271
4471
  var VITEST_GLOBAL_TYPES = "vitest/globals";
4272
4472
  function specTypesOperation(cwd) {
4273
- const path = join22(cwd, SPEC_TSCONFIG);
4473
+ const path = join24(cwd, SPEC_TSCONFIG);
4274
4474
  if (!existsSync18(path)) return void 0;
4275
4475
  let config;
4276
4476
  try {
4277
4477
  config = parseJsonc(
4278
- readFileSync17(path, "utf8"),
4478
+ readFileSync19(path, "utf8"),
4279
4479
  SPEC_TSCONFIG
4280
4480
  );
4281
4481
  } catch {
@@ -4295,8 +4495,8 @@ function specTypesOperation(cwd) {
4295
4495
  }
4296
4496
  function shippedTypesPath(cwd, declaration) {
4297
4497
  try {
4298
- const manifest = createRequire5(join22(cwd, "noop.cjs")).resolve("@hublo/sentinel/package.json");
4299
- const file = join22(dirname10(manifest), declaration);
4498
+ const manifest = createRequire5(join24(cwd, "noop.cjs")).resolve("@hublo/sentinel/package.json");
4499
+ const file = join24(dirname11(manifest), declaration);
4300
4500
  if (existsSync18(file)) {
4301
4501
  const path = relative5(cwd, file);
4302
4502
  return path.startsWith(".") ? path : `./${path}`;
@@ -4316,7 +4516,7 @@ function helpersStillCallingJest(cwd) {
4316
4516
  return;
4317
4517
  }
4318
4518
  for (const entry of entries) {
4319
- const path = join22(dir, entry.name);
4519
+ const path = join24(dir, entry.name);
4320
4520
  if (entry.isDirectory()) {
4321
4521
  if (!skip.has(entry.name)) walk2(path);
4322
4522
  continue;
@@ -4326,7 +4526,7 @@ function helpersStillCallingJest(cwd) {
4326
4526
  if (/^(jest|vitest)\./.test(entry.name)) continue;
4327
4527
  let source;
4328
4528
  try {
4329
- source = readFileSync17(path, "utf8");
4529
+ source = readFileSync19(path, "utf8");
4330
4530
  } catch {
4331
4531
  continue;
4332
4532
  }
@@ -4338,8 +4538,7 @@ function helpersStillCallingJest(cwd) {
4338
4538
  walk2(cwd);
4339
4539
  return found;
4340
4540
  }
4341
- function compilesATestHelper(cwd, config) {
4342
- const helpers = helpersStillCallingJest(cwd);
4541
+ function compilesATestHelper(helpers, config) {
4343
4542
  if (helpers.length === 0) return false;
4344
4543
  const include = Array.isArray(config?.include) ? config.include : void 0;
4345
4544
  const exclude = Array.isArray(config?.exclude) ? config.exclude : [];
@@ -4358,11 +4557,13 @@ function tsconfigFileListOperations(cwd) {
4358
4557
  } catch {
4359
4558
  return operations;
4360
4559
  }
4560
+ let walked;
4561
+ const helpers = () => walked ??= helpersStillCallingJest(cwd);
4361
4562
  for (const name of names) {
4362
4563
  let config;
4363
4564
  try {
4364
4565
  config = parseJsonc(
4365
- readFileSync17(join22(cwd, name), "utf8"),
4566
+ readFileSync19(join24(cwd, name), "utf8"),
4366
4567
  name
4367
4568
  );
4368
4569
  } catch {
@@ -4371,7 +4572,7 @@ function tsconfigFileListOperations(cwd) {
4371
4572
  const include = Array.isArray(config?.include) ? rewriteFileList(config.include) : void 0;
4372
4573
  const exclude = Array.isArray(config?.exclude) ? rewriteFileList(config.exclude) : void 0;
4373
4574
  const files = Array.isArray(config?.files) ? rewriteFileList(config.files) : void 0;
4374
- const wantsTypes = name === SPEC_TSCONFIG || compilesATestHelper(cwd, config);
4575
+ const wantsTypes = name === SPEC_TSCONFIG || compilesATestHelper(helpers(), config);
4375
4576
  const withTypes = wantsTypes ? withShippedTypes(cwd, files ?? config?.files) : void 0;
4376
4577
  if (include === void 0 && exclude === void 0 && files === void 0 && withTypes === void 0)
4377
4578
  continue;
@@ -4474,6 +4675,7 @@ function planJestMigration(context, adoption) {
4474
4675
  ({ mode }) => nxTargetOperations({
4475
4676
  cwd: context.cwd,
4476
4677
  targets: testTarget({
4678
+ cwd: context.cwd,
4477
4679
  name: mode.target,
4478
4680
  configFiles: [mode.vitestConfig],
4479
4681
  outputs: existingFor(mode.target)?.outputs ?? [],
@@ -4638,7 +4840,13 @@ function plan(context) {
4638
4840
  operations.push(
4639
4841
  ...nxTargetOperations({
4640
4842
  cwd: context.cwd,
4641
- targets: testTarget({ configFiles: [configFile], outputs, dependsOn, configurations })
4843
+ targets: testTarget({
4844
+ cwd: context.cwd,
4845
+ configFiles: [configFile],
4846
+ outputs,
4847
+ dependsOn,
4848
+ configurations
4849
+ })
4642
4850
  })
4643
4851
  );
4644
4852
  const owned = ownedTestDependencies(context.cwd, adoption.ownDeclarations);
@@ -4830,7 +5038,7 @@ function fromWorkspaceRoot(moduleDir, options, paths) {
4830
5038
  if (root === void 0 || root === moduleDir) {
4831
5039
  return { cwd: moduleDir, options: [...options], paths: [...paths] };
4832
5040
  }
4833
- const absolute = (value) => isAbsolute2(value) ? value : resolve7(moduleDir, value);
5041
+ const absolute = (value) => isAbsolute2(value) ? value : resolve8(moduleDir, value);
4834
5042
  const moved = [];
4835
5043
  let previousWasConfig = false;
4836
5044
  for (const option of options) {
@@ -4862,7 +5070,6 @@ export {
4862
5070
  resolveBin,
4863
5071
  binFromOwnInstall,
4864
5072
  binSearchPath,
4865
- parseJsonc,
4866
5073
  nxTargetOperations,
4867
5074
  FORMAT_CONFIG_FILE,
4868
5075
  FORMAT_SCRIPT_NAME,