@hublo/sentinel 1.4.0-alpha.2 → 1.4.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/sentinel.d.ts +1 -0
- package/dist/bin/sentinel.js +10 -11
- package/dist/chunk-2XLX6PFR.js.map +1 -0
- package/dist/{chunk-3TDUIKVQ.js → chunk-3ZKTXODW.js} +6 -6
- package/dist/chunk-3ZKTXODW.js.map +1 -0
- package/dist/{chunk-4UIZJ3TR.js → chunk-7LE72AFG.js} +982 -206
- package/dist/{chunk-WLFE5RUU.js → chunk-NW6UHNYX.js} +6 -6
- package/dist/chunk-NW6UHNYX.js.map +1 -0
- package/dist/chunk-PWV3BMDA.js.map +1 -0
- package/dist/{chunk-CPCUPK4J.js → chunk-TKDVAYYV.js} +4 -4
- package/dist/chunk-TKDVAYYV.js.map +1 -0
- package/dist/chunk-ZV5KMOS7.js +427 -0
- package/dist/chunk-ZV5KMOS7.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +2 -3
- package/dist/roles/build/nest/toolchain.js +7 -7
- package/dist/roles/test/nest/toolchain.d.ts +3 -23
- package/dist/roles/test/nest/toolchain.js +5 -269
- package/dist/roles/test/nest/toolchain.js.map +1 -1
- package/dist/roles/test/react/toolchain.d.ts +5 -1
- package/dist/roles/test/react/toolchain.js +11 -3
- package/dist/roles/test/react/toolchain.js.map +1 -1
- package/dist/roles/test/setup/{nest.js → jest-parity.js} +5 -5
- package/dist/roles/test/setup/jest-parity.js.map +1 -0
- package/dist/roles/test/setup/workspace-entry.js +2 -2
- package/dist/roles/test/setup/workspace-entry.js.map +1 -1
- package/dist/roles/test/shared-test-config.d.ts +32 -0
- package/dist/roles/test/shared-test-config.js +8 -0
- package/dist/roles/test/shared-test-config.js.map +1 -0
- package/dist/roles/test/tools/msw.d.ts +1 -0
- package/dist/roles/test/tools/msw.js +3 -0
- package/dist/roles/test/tools/msw.js.map +1 -0
- package/docs/test-adoption.md +54 -10
- package/docs/using-sentinel.md +25 -10
- package/package.json +14 -7
- package/types/jest-global.d.ts +41 -0
- package/types/mock-extended.d.ts +52 -0
- package/dist/chunk-NX4GHIHF.js +0 -25
- package/dist/roles/test/setup/nest.js.map +0 -1
- /package/dist/roles/test/setup/{nest.d.ts → jest-parity.d.ts} +0 -0
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
readNxProjectName,
|
|
6
6
|
readOwnPackage,
|
|
7
7
|
readProjectPackageJson
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-NW6UHNYX.js";
|
|
9
9
|
|
|
10
10
|
// src/core/registry.ts
|
|
11
11
|
var adapters = [];
|
|
@@ -109,7 +109,7 @@ var SWEEPABLE_TARGETS = TARGETS.filter(
|
|
|
109
109
|
var PRESET_NAMES = ["react", "nest", "svelte", "node", "tools"];
|
|
110
110
|
|
|
111
111
|
// src/shared/color.ts
|
|
112
|
-
import { styleText } from "util";
|
|
112
|
+
import { styleText } from "node:util";
|
|
113
113
|
function palette(stream) {
|
|
114
114
|
const paint = (format, text) => styleText(format, text, { stream });
|
|
115
115
|
return {
|
|
@@ -123,8 +123,8 @@ function palette(stream) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
// src/roles/build/nest/nx-target.ts
|
|
126
|
-
import { existsSync, readFileSync } from "fs";
|
|
127
|
-
import { join, relative } from "path";
|
|
126
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
127
|
+
import { join, relative } from "node:path";
|
|
128
128
|
var DEPRECATED_NX_BUILDER = "@nx/webpack:webpack";
|
|
129
129
|
var TRANSLATABLE_WEBPACK_CONFIG = "tools/webpack-configs/update.webpack.config.js";
|
|
130
130
|
var readProjectJson = (cwd) => {
|
|
@@ -195,8 +195,8 @@ function workspaceRootHops(cwd, target) {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
// src/roles/build/read-adoption.ts
|
|
198
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
199
|
-
import { join as join4 } from "path";
|
|
198
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
199
|
+
import { join as join4 } from "node:path";
|
|
200
200
|
|
|
201
201
|
// src/core/config/module-type.ts
|
|
202
202
|
function esmReadiness(fileName, packageType) {
|
|
@@ -210,8 +210,8 @@ function esmReadiness(fileName, packageType) {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
// src/core/config/manifest.ts
|
|
213
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
214
|
-
import { basename, join as join2 } from "path";
|
|
213
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
214
|
+
import { basename, join as join2 } from "node:path";
|
|
215
215
|
function moduleScripts(cwd) {
|
|
216
216
|
try {
|
|
217
217
|
const pkg = JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
|
|
@@ -220,6 +220,14 @@ function moduleScripts(cwd) {
|
|
|
220
220
|
return {};
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
|
+
function manifestTargets(cwd) {
|
|
224
|
+
try {
|
|
225
|
+
const pkg = JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
|
|
226
|
+
return pkg.nx?.targets ?? {};
|
|
227
|
+
} catch {
|
|
228
|
+
return {};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
223
231
|
function moduleName2(cwd) {
|
|
224
232
|
try {
|
|
225
233
|
const pkg = JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
|
|
@@ -256,8 +264,8 @@ function manifestOperation(cwd, scripts) {
|
|
|
256
264
|
}
|
|
257
265
|
|
|
258
266
|
// src/core/config/project-json.ts
|
|
259
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
260
|
-
import { join as join3 } from "path";
|
|
267
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
268
|
+
import { join as join3 } from "node:path";
|
|
261
269
|
function projectTargets(cwd) {
|
|
262
270
|
const path = join3(cwd, "project.json");
|
|
263
271
|
if (!existsSync3(path)) return void 0;
|
|
@@ -514,11 +522,11 @@ function readBuildAdoption(cwd) {
|
|
|
514
522
|
}
|
|
515
523
|
|
|
516
524
|
// src/roles/build/adapters/vite/vite-role.adapter.ts
|
|
517
|
-
import { spawnSync } from "child_process";
|
|
525
|
+
import { spawnSync } from "node:child_process";
|
|
518
526
|
|
|
519
527
|
// src/core/config/tool-args.ts
|
|
520
|
-
import { existsSync as existsSync5 } from "fs";
|
|
521
|
-
import { isAbsolute, resolve as resolve2 } from "path";
|
|
528
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
529
|
+
import { isAbsolute, resolve as resolve2 } from "node:path";
|
|
522
530
|
function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
523
531
|
const takesValue = new Set(valueFlags);
|
|
524
532
|
const options = [];
|
|
@@ -535,9 +543,9 @@ function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
|
535
543
|
}
|
|
536
544
|
|
|
537
545
|
// src/shared/resolve-bin.ts
|
|
538
|
-
import { existsSync as existsSync6 } from "fs";
|
|
539
|
-
import { createRequire } from "module";
|
|
540
|
-
import { delimiter, dirname, join as join5 } from "path";
|
|
546
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
547
|
+
import { createRequire } from "node:module";
|
|
548
|
+
import { delimiter, dirname, join as join5 } from "node:path";
|
|
541
549
|
var require2 = createRequire(import.meta.url);
|
|
542
550
|
function resolveBin(fromDir, name) {
|
|
543
551
|
let dir = fromDir;
|
|
@@ -553,9 +561,9 @@ function binFromOwnInstall(packageName, binName) {
|
|
|
553
561
|
try {
|
|
554
562
|
const manifest = require2.resolve(`${packageName}/package.json`);
|
|
555
563
|
const bin = require2(manifest).bin;
|
|
556
|
-
const
|
|
557
|
-
if (!
|
|
558
|
-
const executable = join5(dirname(manifest),
|
|
564
|
+
const relative8 = typeof bin === "string" ? bin : bin?.[binName];
|
|
565
|
+
if (!relative8) return void 0;
|
|
566
|
+
const executable = join5(dirname(manifest), relative8);
|
|
559
567
|
return existsSync6(executable) ? executable : void 0;
|
|
560
568
|
} catch {
|
|
561
569
|
return void 0;
|
|
@@ -568,12 +576,12 @@ function binSearchPath(cwd) {
|
|
|
568
576
|
}
|
|
569
577
|
|
|
570
578
|
// src/roles/build/plan.ts
|
|
571
|
-
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
572
|
-
import { join as join10 } from "path";
|
|
579
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
|
|
580
|
+
import { join as join10 } from "node:path";
|
|
573
581
|
|
|
574
582
|
// src/core/config/existing-command.ts
|
|
575
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
576
|
-
import { join as join6 } from "path";
|
|
583
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
584
|
+
import { join as join6 } from "node:path";
|
|
577
585
|
|
|
578
586
|
// src/shared/jsonc.ts
|
|
579
587
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
@@ -623,8 +631,8 @@ function existingCommand(cwd, target) {
|
|
|
623
631
|
}
|
|
624
632
|
|
|
625
633
|
// src/core/config/nx-target.ts
|
|
626
|
-
import { existsSync as existsSync7 } from "fs";
|
|
627
|
-
import { join as join7 } from "path";
|
|
634
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
635
|
+
import { join as join7 } from "node:path";
|
|
628
636
|
function nxTargetMetadataOperations(options) {
|
|
629
637
|
const { targets } = options;
|
|
630
638
|
if (Object.keys(targets).length === 0) return [];
|
|
@@ -689,8 +697,8 @@ function keepsOtherCommands(script, sentinelCommand) {
|
|
|
689
697
|
}
|
|
690
698
|
|
|
691
699
|
// src/roles/format/read-adoption.ts
|
|
692
|
-
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
693
|
-
import { join as join8 } from "path";
|
|
700
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
|
|
701
|
+
import { join as join8 } from "node:path";
|
|
694
702
|
|
|
695
703
|
// src/roles/format/config-policy.ts
|
|
696
704
|
var FORMAT_CONFIG_FILE = ".oxfmtrc.json";
|
|
@@ -901,8 +909,8 @@ function devTargetOperations(cwd, devScript) {
|
|
|
901
909
|
}
|
|
902
910
|
|
|
903
911
|
// src/roles/build/nest/adopt.ts
|
|
904
|
-
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
905
|
-
import { join as join9 } from "path";
|
|
912
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
|
|
913
|
+
import { join as join9 } from "node:path";
|
|
906
914
|
var NEST_CONFIG_FILE = "vite.config.mts";
|
|
907
915
|
function asSource(value, depth) {
|
|
908
916
|
const pad = " ".repeat(depth);
|
|
@@ -1445,8 +1453,8 @@ var ViteDevAdapter = class extends ViteRoleAdapter {
|
|
|
1445
1453
|
};
|
|
1446
1454
|
|
|
1447
1455
|
// src/roles/build/declared-outputs.ts
|
|
1448
|
-
import { existsSync as existsSync11, rmSync, statSync, writeFileSync } from "fs";
|
|
1449
|
-
import { join as join11 } from "path";
|
|
1456
|
+
import { existsSync as existsSync11, rmSync, statSync, writeFileSync } from "node:fs";
|
|
1457
|
+
import { join as join11 } from "node:path";
|
|
1450
1458
|
function declaredBuildTarget(cwd) {
|
|
1451
1459
|
return readProjectPackageJson(cwd).nx?.targets?.[BUILD_SCRIPT_NAME];
|
|
1452
1460
|
}
|
|
@@ -1492,7 +1500,7 @@ function outputsProblem(cwd, startedAt) {
|
|
|
1492
1500
|
}
|
|
1493
1501
|
|
|
1494
1502
|
// src/roles/build/prerequisite.ts
|
|
1495
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
1503
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1496
1504
|
var PREBUILD_SCRIPT_NAME = `pre${BUILD_SCRIPT_NAME}`;
|
|
1497
1505
|
function insideBuildScript(env = process.env) {
|
|
1498
1506
|
return env.npm_lifecycle_event === BUILD_SCRIPT_NAME;
|
|
@@ -1618,13 +1626,13 @@ function registerBuild() {
|
|
|
1618
1626
|
}
|
|
1619
1627
|
|
|
1620
1628
|
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
1621
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
1622
|
-
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
1623
|
-
import { join as join16 } from "path";
|
|
1629
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1630
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "node:fs";
|
|
1631
|
+
import { join as join16 } from "node:path";
|
|
1624
1632
|
|
|
1625
1633
|
// src/core/config/has-source.ts
|
|
1626
|
-
import { readdirSync } from "fs";
|
|
1627
|
-
import { extname, join as join12, relative as relative2 } from "path";
|
|
1634
|
+
import { readdirSync } from "node:fs";
|
|
1635
|
+
import { extname, join as join12, relative as relative2 } from "node:path";
|
|
1628
1636
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1629
1637
|
"node_modules",
|
|
1630
1638
|
"dist",
|
|
@@ -1742,8 +1750,8 @@ function writesWhenRewritten(name, command) {
|
|
|
1742
1750
|
var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
|
|
1743
1751
|
|
|
1744
1752
|
// src/roles/format/inherited-ignores.ts
|
|
1745
|
-
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
1746
|
-
import { join as join13 } from "path";
|
|
1753
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
|
|
1754
|
+
import { join as join13 } from "node:path";
|
|
1747
1755
|
var ROOT_IGNORE_FILE = ".prettierignore";
|
|
1748
1756
|
function isPattern(line) {
|
|
1749
1757
|
const trimmed = line.trim();
|
|
@@ -1808,13 +1816,13 @@ function needsSvelteCompiler(output) {
|
|
|
1808
1816
|
}
|
|
1809
1817
|
|
|
1810
1818
|
// src/roles/format/prettier-config.ts
|
|
1811
|
-
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
1812
|
-
import { join as join15 } from "path";
|
|
1819
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
|
|
1820
|
+
import { join as join15 } from "node:path";
|
|
1813
1821
|
|
|
1814
1822
|
// src/roles/format/resolve-oxfmt.ts
|
|
1815
|
-
import { readFileSync as readFileSync10 } from "fs";
|
|
1816
|
-
import { createRequire as createRequire2 } from "module";
|
|
1817
|
-
import { dirname as dirname2, join as join14 } from "path";
|
|
1823
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
1824
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
1825
|
+
import { dirname as dirname2, join as join14 } from "node:path";
|
|
1818
1826
|
function resolveOxfmt(cwd) {
|
|
1819
1827
|
return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
|
|
1820
1828
|
}
|
|
@@ -2314,9 +2322,9 @@ function registerFormat() {
|
|
|
2314
2322
|
}
|
|
2315
2323
|
|
|
2316
2324
|
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
2317
|
-
import { spawnSync as spawnSync4 } from "child_process";
|
|
2318
|
-
import { existsSync as existsSync22, readFileSync as readFileSync20, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2319
|
-
import { join as join26 } from "path";
|
|
2325
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
2326
|
+
import { existsSync as existsSync22, readFileSync as readFileSync20, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2327
|
+
import { join as join26 } from "node:path";
|
|
2320
2328
|
|
|
2321
2329
|
// src/core/config/deferred-rules.ts
|
|
2322
2330
|
function deferredRuleNames(rules) {
|
|
@@ -2324,8 +2332,8 @@ function deferredRuleNames(rules) {
|
|
|
2324
2332
|
}
|
|
2325
2333
|
|
|
2326
2334
|
// src/core/fix-report.ts
|
|
2327
|
-
import { statSync as statSync2 } from "fs";
|
|
2328
|
-
import { join as join17 } from "path";
|
|
2335
|
+
import { statSync as statSync2 } from "node:fs";
|
|
2336
|
+
import { join as join17 } from "node:path";
|
|
2329
2337
|
var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
|
|
2330
2338
|
function readFailure(output) {
|
|
2331
2339
|
const lines = output.split("\n").map((line2) => line2.trim());
|
|
@@ -2360,8 +2368,12 @@ function changedSince(cwd, before, extensions) {
|
|
|
2360
2368
|
return changed.sort();
|
|
2361
2369
|
}
|
|
2362
2370
|
var NAMED_IN_REPORT = 5;
|
|
2371
|
+
var NOT_INSTALLED_YET = /invalid config file[\s\S]*node_modules/i;
|
|
2363
2372
|
function describeFixOutcome(outcome, toolLabel) {
|
|
2364
2373
|
if (outcome.failure !== void 0) {
|
|
2374
|
+
if (NOT_INSTALLED_YET.test(outcome.failure)) {
|
|
2375
|
+
return `the ${toolLabel} preset is not installed in this module yet, which is expected: this command just added it and the install comes next. Nothing was fixed. Run \`pnpm install\`, then \`sentinel --run --${toolLabel} --fix\` to clear what a machine can clear.`;
|
|
2376
|
+
}
|
|
2365
2377
|
return `the ${toolLabel} autofix could not run, so nothing was fixed and this module has not been checked: ${outcome.failure}`;
|
|
2366
2378
|
}
|
|
2367
2379
|
if (outcome.changed.length === 0) return `the ${toolLabel} autofix changed nothing`;
|
|
@@ -2372,8 +2384,8 @@ function describeFixOutcome(outcome, toolLabel) {
|
|
|
2372
2384
|
}
|
|
2373
2385
|
|
|
2374
2386
|
// src/roles/lint/module-baseline.ts
|
|
2375
|
-
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
2376
|
-
import { join as join18 } from "path";
|
|
2387
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
|
|
2388
|
+
import { join as join18 } from "node:path";
|
|
2377
2389
|
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
2378
2390
|
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
2379
2391
|
var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
|
|
@@ -4247,8 +4259,8 @@ function downgradedRulesFor(preset) {
|
|
|
4247
4259
|
}
|
|
4248
4260
|
|
|
4249
4261
|
// src/roles/lint/extra-layers.ts
|
|
4250
|
-
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
4251
|
-
import { isAbsolute as isAbsolute2, join as join19, resolve as resolve3 } from "path";
|
|
4262
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
|
|
4263
|
+
import { isAbsolute as isAbsolute2, join as join19, resolve as resolve3 } from "node:path";
|
|
4252
4264
|
function ruleCount(cwd, specifier) {
|
|
4253
4265
|
const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
|
|
4254
4266
|
if (!existsSync16(path)) return void 0;
|
|
@@ -4333,8 +4345,8 @@ function errorCountsByConfigRule(stdout) {
|
|
|
4333
4345
|
}
|
|
4334
4346
|
|
|
4335
4347
|
// src/core/config/read-adoption.ts
|
|
4336
|
-
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
4337
|
-
import { join as join21 } from "path";
|
|
4348
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "node:fs";
|
|
4349
|
+
import { join as join21 } from "node:path";
|
|
4338
4350
|
|
|
4339
4351
|
// src/core/config/owned-keys.ts
|
|
4340
4352
|
function presetOwnedKeys(config, permitted, presetSets) {
|
|
@@ -4349,8 +4361,8 @@ function localOnlyKeys(config, permitted, presetSets) {
|
|
|
4349
4361
|
}
|
|
4350
4362
|
|
|
4351
4363
|
// src/core/config/resolve-config-target.ts
|
|
4352
|
-
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
4353
|
-
import { join as join20 } from "path";
|
|
4364
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "node:fs";
|
|
4365
|
+
import { join as join20 } from "node:path";
|
|
4354
4366
|
function readExtends(absolutePath) {
|
|
4355
4367
|
let parsed;
|
|
4356
4368
|
try {
|
|
@@ -4441,10 +4453,10 @@ function readLintAdoption(cwd) {
|
|
|
4441
4453
|
}
|
|
4442
4454
|
|
|
4443
4455
|
// src/roles/lint/resolve-oxlint.ts
|
|
4444
|
-
import { existsSync as existsSync19 } from "fs";
|
|
4445
|
-
import { createRequire as createRequire3 } from "module";
|
|
4446
|
-
import { delimiter as delimiter2, dirname as dirname3, join as join22 } from "path";
|
|
4447
|
-
import { fileURLToPath } from "url";
|
|
4456
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
4457
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
4458
|
+
import { delimiter as delimiter2, dirname as dirname3, join as join22 } from "node:path";
|
|
4459
|
+
import { fileURLToPath } from "node:url";
|
|
4448
4460
|
var PACKAGE_OF = {
|
|
4449
4461
|
oxlint: "oxlint",
|
|
4450
4462
|
tsgolint: "oxlint-tsgolint"
|
|
@@ -4486,12 +4498,12 @@ function oxlintSearchPath(cwd) {
|
|
|
4486
4498
|
}
|
|
4487
4499
|
|
|
4488
4500
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
4489
|
-
import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
|
|
4490
|
-
import { join as join25 } from "path";
|
|
4501
|
+
import { existsSync as existsSync21, readFileSync as readFileSync19 } from "node:fs";
|
|
4502
|
+
import { join as join25 } from "node:path";
|
|
4491
4503
|
|
|
4492
4504
|
// src/roles/lint/eslint-ignores.ts
|
|
4493
|
-
import { existsSync as existsSync20, readFileSync as readFileSync17 } from "fs";
|
|
4494
|
-
import { join as join23 } from "path";
|
|
4505
|
+
import { existsSync as existsSync20, readFileSync as readFileSync17 } from "node:fs";
|
|
4506
|
+
import { join as join23 } from "node:path";
|
|
4495
4507
|
import { parseSync as parseSync2 } from "oxc-parser";
|
|
4496
4508
|
|
|
4497
4509
|
// src/shared/ast.ts
|
|
@@ -4615,8 +4627,8 @@ function lintPresetFor(preset) {
|
|
|
4615
4627
|
}
|
|
4616
4628
|
|
|
4617
4629
|
// src/roles/lint/rename-suppressions.ts
|
|
4618
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync18, statSync as statSync3 } from "fs";
|
|
4619
|
-
import { join as join24, relative as relative3 } from "path";
|
|
4630
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync18, statSync as statSync3 } from "node:fs";
|
|
4631
|
+
import { join as join24, relative as relative3 } from "node:path";
|
|
4620
4632
|
var SOURCE_EXTENSIONS = [
|
|
4621
4633
|
".ts",
|
|
4622
4634
|
".tsx",
|
|
@@ -5393,7 +5405,10 @@ function registerLint() {
|
|
|
5393
5405
|
}
|
|
5394
5406
|
|
|
5395
5407
|
// src/roles/test/adapters/vitest/vitest.adapter.ts
|
|
5396
|
-
import { spawnSync as spawnSync5 } from "child_process";
|
|
5408
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
5409
|
+
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync31 } from "node:fs";
|
|
5410
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
5411
|
+
import { isAbsolute as isAbsolute3, join as join39, resolve as resolve6 } from "node:path";
|
|
5397
5412
|
|
|
5398
5413
|
// src/roles/test/presets/toolchain.json
|
|
5399
5414
|
var toolchain_default2 = {
|
|
@@ -5470,17 +5485,36 @@ function testTarget(options) {
|
|
|
5470
5485
|
}
|
|
5471
5486
|
|
|
5472
5487
|
// src/roles/test/jest-migration.ts
|
|
5473
|
-
import { existsSync as
|
|
5474
|
-
import {
|
|
5488
|
+
import { existsSync as existsSync30, readFileSync as readFileSync29, readdirSync as readdirSync8 } from "node:fs";
|
|
5489
|
+
import { createRequire as createRequire6 } from "node:module";
|
|
5490
|
+
import { dirname as dirname8, join as join37, relative as relative7 } from "node:path";
|
|
5475
5491
|
|
|
5476
5492
|
// src/roles/test/codemod/migrate-module.ts
|
|
5477
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
5478
|
-
import { join as
|
|
5493
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync26, statSync as statSync6 } from "node:fs";
|
|
5494
|
+
import { join as join32 } from "node:path";
|
|
5479
5495
|
import { parseSync as parseSync8 } from "oxc-parser";
|
|
5480
5496
|
|
|
5497
|
+
// src/roles/test/msw-lifecycle.ts
|
|
5498
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21 } from "node:fs";
|
|
5499
|
+
import { join as join27 } from "node:path";
|
|
5500
|
+
var ROOT_SETUP = "jest.setup.after.env.js";
|
|
5501
|
+
function ownsMswLifecycle(cwd, setupFiles) {
|
|
5502
|
+
for (const raw of setupFiles) {
|
|
5503
|
+
if (raw.endsWith(ROOT_SETUP)) continue;
|
|
5504
|
+
const file = join27(cwd, raw.replaceAll("<rootDir>/", "").replaceAll("<rootDir>", "."));
|
|
5505
|
+
if (!existsSync23(file)) continue;
|
|
5506
|
+
try {
|
|
5507
|
+
if (/\.listen\s*\(/.test(readFileSync21(file, "utf8"))) return true;
|
|
5508
|
+
} catch {
|
|
5509
|
+
continue;
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5512
|
+
return false;
|
|
5513
|
+
}
|
|
5514
|
+
|
|
5481
5515
|
// src/roles/test/read-adoption.ts
|
|
5482
|
-
import { existsSync as
|
|
5483
|
-
import { join as
|
|
5516
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, readdirSync as readdirSync3 } from "node:fs";
|
|
5517
|
+
import { join as join28, relative as relative4 } from "node:path";
|
|
5484
5518
|
var JEST_CONFIG_FILES = [
|
|
5485
5519
|
"jest.config.ts",
|
|
5486
5520
|
"jest.config.js",
|
|
@@ -5489,15 +5523,15 @@ var JEST_CONFIG_FILES = [
|
|
|
5489
5523
|
"jest.integration-config.ts"
|
|
5490
5524
|
];
|
|
5491
5525
|
function testConfigFile(cwd) {
|
|
5492
|
-
return TEST_CONFIG_FILES.find((name) =>
|
|
5526
|
+
return TEST_CONFIG_FILES.find((name) => existsSync24(join28(cwd, name)));
|
|
5493
5527
|
}
|
|
5494
5528
|
function jestConfigFiles(cwd) {
|
|
5495
|
-
return JEST_CONFIG_FILES.filter((name) =>
|
|
5529
|
+
return JEST_CONFIG_FILES.filter((name) => existsSync24(join28(cwd, name)));
|
|
5496
5530
|
}
|
|
5497
5531
|
function rootJestConfigFiles(cwd) {
|
|
5498
5532
|
const root = findWorkspaceRoot(cwd);
|
|
5499
5533
|
if (root === void 0 || root === cwd) return [];
|
|
5500
|
-
return JEST_CONFIG_FILES.filter((name) =>
|
|
5534
|
+
return JEST_CONFIG_FILES.filter((name) => existsSync24(join28(root, name)));
|
|
5501
5535
|
}
|
|
5502
5536
|
var TEST_FILE = /[.-](spec|test)\.[cm]?[jt]sx?$/;
|
|
5503
5537
|
var NOT_WALKED = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".git", ".nx", ".turbo"]);
|
|
@@ -5520,9 +5554,9 @@ function walkTestFiles(cwd, stopAtFirst) {
|
|
|
5520
5554
|
}
|
|
5521
5555
|
for (const entry of entries) {
|
|
5522
5556
|
if (entry.isDirectory()) {
|
|
5523
|
-
if (!NOT_WALKED.has(entry.name)) stack.push(
|
|
5557
|
+
if (!NOT_WALKED.has(entry.name)) stack.push(join28(dir, entry.name));
|
|
5524
5558
|
} else if (TEST_FILE.test(entry.name)) {
|
|
5525
|
-
found.push(relative4(cwd,
|
|
5559
|
+
found.push(relative4(cwd, join28(dir, entry.name)).replaceAll("\\", "/"));
|
|
5526
5560
|
if (stopAtFirst) return found;
|
|
5527
5561
|
}
|
|
5528
5562
|
}
|
|
@@ -5531,7 +5565,7 @@ function walkTestFiles(cwd, stopAtFirst) {
|
|
|
5531
5565
|
}
|
|
5532
5566
|
function readTestConfigSource(cwd, fileName) {
|
|
5533
5567
|
try {
|
|
5534
|
-
return
|
|
5568
|
+
return readFileSync22(join28(cwd, fileName), "utf8");
|
|
5535
5569
|
} catch {
|
|
5536
5570
|
return "";
|
|
5537
5571
|
}
|
|
@@ -5600,6 +5634,17 @@ function readTestAdoption(cwd) {
|
|
|
5600
5634
|
};
|
|
5601
5635
|
}
|
|
5602
5636
|
|
|
5637
|
+
// src/roles/test/support-file-name.ts
|
|
5638
|
+
var JEST_PREFIX = "jest.";
|
|
5639
|
+
var VITEST_PREFIX = "vitest.";
|
|
5640
|
+
function vitestSupportName(relative8) {
|
|
5641
|
+
const cut = relative8.lastIndexOf("/");
|
|
5642
|
+
const directory = cut === -1 ? "" : relative8.slice(0, cut + 1);
|
|
5643
|
+
const base = relative8.slice(cut + 1);
|
|
5644
|
+
if (!base.startsWith(JEST_PREFIX)) return relative8;
|
|
5645
|
+
return `${directory}${VITEST_PREFIX}${base.slice(JEST_PREFIX.length)}`;
|
|
5646
|
+
}
|
|
5647
|
+
|
|
5603
5648
|
// src/roles/test/codemod/migrate-file.ts
|
|
5604
5649
|
import { parseSync as parseSync7 } from "oxc-parser";
|
|
5605
5650
|
|
|
@@ -5715,7 +5760,7 @@ function rewriteAssertionReturnValue(source, program) {
|
|
|
5715
5760
|
for (const reading of readings) {
|
|
5716
5761
|
const from = reading.start;
|
|
5717
5762
|
let to = reading.end;
|
|
5718
|
-
while (to < source.length && /[\
|
|
5763
|
+
while (to < source.length && /[ \t;]/.test(source[to])) to += 1;
|
|
5719
5764
|
let head = from;
|
|
5720
5765
|
while (head > 0 && /[ \t]/.test(source[head - 1])) head -= 1;
|
|
5721
5766
|
if (head > 0 && source[head - 1] === "\n") head -= 1;
|
|
@@ -6450,9 +6495,9 @@ ${source}`;
|
|
|
6450
6495
|
}
|
|
6451
6496
|
|
|
6452
6497
|
// src/roles/test/codemod/named-rules.ts
|
|
6453
|
-
import { existsSync as
|
|
6454
|
-
import { createRequire as createRequire4 } from "module";
|
|
6455
|
-
import { dirname as dirname4, join as
|
|
6498
|
+
import { existsSync as existsSync25, readFileSync as readFileSync23 } from "node:fs";
|
|
6499
|
+
import { createRequire as createRequire4 } from "node:module";
|
|
6500
|
+
import { dirname as dirname4, join as join29 } from "node:path";
|
|
6456
6501
|
function isCommonJsPackage(fromFile, specifier) {
|
|
6457
6502
|
if (specifier.startsWith(".") || specifier.startsWith("/")) return false;
|
|
6458
6503
|
let resolved;
|
|
@@ -6466,10 +6511,10 @@ function isCommonJsPackage(fromFile, specifier) {
|
|
|
6466
6511
|
if (resolved.endsWith(".cjs")) return true;
|
|
6467
6512
|
let dir = dirname4(resolved);
|
|
6468
6513
|
for (let depth = 0; depth < 12; depth += 1) {
|
|
6469
|
-
const manifest =
|
|
6470
|
-
if (
|
|
6514
|
+
const manifest = join29(dir, "package.json");
|
|
6515
|
+
if (existsSync25(manifest)) {
|
|
6471
6516
|
try {
|
|
6472
|
-
return JSON.parse(
|
|
6517
|
+
return JSON.parse(readFileSync23(manifest, "utf8")).type !== "module";
|
|
6473
6518
|
} catch {
|
|
6474
6519
|
return false;
|
|
6475
6520
|
}
|
|
@@ -6581,12 +6626,47 @@ function applyNamedRules(source, program, fileName = "") {
|
|
|
6581
6626
|
const applied = /* @__PURE__ */ new Set();
|
|
6582
6627
|
const asyncify = /* @__PURE__ */ new Set();
|
|
6583
6628
|
const mocked = mockedSpecifiers(program);
|
|
6584
|
-
const wholeStatement = /* @__PURE__ */ new
|
|
6629
|
+
const wholeStatement = /* @__PURE__ */ new Map();
|
|
6585
6630
|
walk(program, (node) => {
|
|
6586
6631
|
if (node.type !== "ExpressionStatement") return;
|
|
6587
6632
|
const expression = node.expression;
|
|
6588
|
-
if (expression !== void 0) wholeStatement.
|
|
6633
|
+
if (expression !== void 0) wholeStatement.set(expression, node);
|
|
6634
|
+
});
|
|
6635
|
+
const standsAlone = /* @__PURE__ */ new Set();
|
|
6636
|
+
const alone = (node) => {
|
|
6637
|
+
if (node !== void 0) standsAlone.add(node);
|
|
6638
|
+
};
|
|
6639
|
+
walk(program, (node) => {
|
|
6640
|
+
switch (node.type) {
|
|
6641
|
+
case "VariableDeclarator":
|
|
6642
|
+
return alone(node.init);
|
|
6643
|
+
case "AssignmentExpression":
|
|
6644
|
+
return alone(node.right);
|
|
6645
|
+
case "ReturnStatement":
|
|
6646
|
+
case "ExportDefaultDeclaration":
|
|
6647
|
+
return alone(node.argument ?? node.declaration);
|
|
6648
|
+
case "ArrowFunctionExpression":
|
|
6649
|
+
return node.expression === true ? alone(node.body) : void 0;
|
|
6650
|
+
case "ConditionalExpression":
|
|
6651
|
+
alone(node.consequent);
|
|
6652
|
+
return alone(node.alternate);
|
|
6653
|
+
case "ArrayExpression":
|
|
6654
|
+
return (node.elements ?? []).forEach((e) => alone(e ?? void 0));
|
|
6655
|
+
case "Property":
|
|
6656
|
+
case "ObjectProperty":
|
|
6657
|
+
return node.shorthand === true ? void 0 : alone(node.value);
|
|
6658
|
+
case "CallExpression":
|
|
6659
|
+
case "NewExpression":
|
|
6660
|
+
return (node.arguments ?? []).forEach((a) => alone(a));
|
|
6661
|
+
default:
|
|
6662
|
+
return;
|
|
6663
|
+
}
|
|
6589
6664
|
});
|
|
6665
|
+
const placed = (node, expression, trailing = "") => {
|
|
6666
|
+
if (trailing !== "") return `(${expression})${trailing}`;
|
|
6667
|
+
if (wholeStatement.has(node)) return expression;
|
|
6668
|
+
return standsAlone.has(node) ? expression : `(${expression})`;
|
|
6669
|
+
};
|
|
6590
6670
|
const handedToTheRunnerAsAModule = /* @__PURE__ */ new Set();
|
|
6591
6671
|
walk(program, (node) => {
|
|
6592
6672
|
const returned = mockFactoryReturn(node);
|
|
@@ -6616,7 +6696,7 @@ function applyNamedRules(source, program, fileName = "") {
|
|
|
6616
6696
|
edits.push({
|
|
6617
6697
|
start: node.start,
|
|
6618
6698
|
end: node.end,
|
|
6619
|
-
text: `
|
|
6699
|
+
text: placed(node, `await import(${argumentText(source, node)})`)
|
|
6620
6700
|
});
|
|
6621
6701
|
applied.add("jest.requireMock");
|
|
6622
6702
|
if (enclosing !== void 0 && enclosing.async !== true) asyncify.add(enclosing);
|
|
@@ -6628,7 +6708,7 @@ function applyNamedRules(source, program, fileName = "") {
|
|
|
6628
6708
|
edits.push({
|
|
6629
6709
|
start: node.start,
|
|
6630
6710
|
end: node.end,
|
|
6631
|
-
text: `
|
|
6711
|
+
text: placed(node, `await vi.${asyncReplacement}(${argumentText(source, node)})`)
|
|
6632
6712
|
});
|
|
6633
6713
|
applied.add(`jest.${member}`);
|
|
6634
6714
|
if (enclosing !== void 0 && enclosing.async !== true) asyncify.add(enclosing);
|
|
@@ -6641,7 +6721,7 @@ function applyNamedRules(source, program, fileName = "") {
|
|
|
6641
6721
|
edits.push({
|
|
6642
6722
|
start: node.start,
|
|
6643
6723
|
end: node.end,
|
|
6644
|
-
text:
|
|
6724
|
+
text: placed(node, `await import(${argumentText(source, node)})`, interop)
|
|
6645
6725
|
});
|
|
6646
6726
|
applied.add("require");
|
|
6647
6727
|
if (enclosing !== void 0 && enclosing.async !== true) asyncify.add(enclosing);
|
|
@@ -6932,6 +7012,78 @@ function rewriteMockFactoryDefault(source, program, defaultImported) {
|
|
|
6932
7012
|
};
|
|
6933
7013
|
}
|
|
6934
7014
|
|
|
7015
|
+
// src/roles/test/codemod/mock-promise-resolve.ts
|
|
7016
|
+
var MOCK_FACTORY = /* @__PURE__ */ new Set(["fn", "spyOn"]);
|
|
7017
|
+
function isMockFactory(source, node) {
|
|
7018
|
+
if (node.type !== "CallExpression") return false;
|
|
7019
|
+
const callee = node.callee;
|
|
7020
|
+
if (callee?.type !== "MemberExpression") return false;
|
|
7021
|
+
const object = callee.object;
|
|
7022
|
+
const property = callee.property;
|
|
7023
|
+
if (object?.type !== "Identifier" || property?.type !== "Identifier") return false;
|
|
7024
|
+
const runner = source.slice(object.start, object.end);
|
|
7025
|
+
const method = source.slice(property.start, property.end);
|
|
7026
|
+
return (runner === "jest" || runner === "vi") && MOCK_FACTORY.has(method);
|
|
7027
|
+
}
|
|
7028
|
+
function resolvedValue(source, executor) {
|
|
7029
|
+
if (executor.type !== "ArrowFunctionExpression" && executor.type !== "FunctionExpression") {
|
|
7030
|
+
return void 0;
|
|
7031
|
+
}
|
|
7032
|
+
const params = executor.params;
|
|
7033
|
+
if (params?.length !== 1) return void 0;
|
|
7034
|
+
const parameter = params[0];
|
|
7035
|
+
if (parameter.type !== "Identifier") return void 0;
|
|
7036
|
+
const resolveName = source.slice(parameter.start, parameter.end);
|
|
7037
|
+
const body = executor.body;
|
|
7038
|
+
if (body === void 0) return void 0;
|
|
7039
|
+
let call;
|
|
7040
|
+
if (body.type === "BlockStatement") {
|
|
7041
|
+
const statements = body.body;
|
|
7042
|
+
if (statements?.length !== 1) return void 0;
|
|
7043
|
+
const only = statements[0];
|
|
7044
|
+
if (only.type !== "ExpressionStatement") return void 0;
|
|
7045
|
+
call = only.expression;
|
|
7046
|
+
} else {
|
|
7047
|
+
call = body;
|
|
7048
|
+
}
|
|
7049
|
+
if (call?.type !== "CallExpression") return void 0;
|
|
7050
|
+
const callee = call.callee;
|
|
7051
|
+
if (callee?.type !== "Identifier") return void 0;
|
|
7052
|
+
if (source.slice(callee.start, callee.end) !== resolveName) return void 0;
|
|
7053
|
+
const args = call.arguments;
|
|
7054
|
+
if (args?.length !== 1) return void 0;
|
|
7055
|
+
return args[0];
|
|
7056
|
+
}
|
|
7057
|
+
function rewriteMockPromiseResolve(source, program) {
|
|
7058
|
+
const edits = [];
|
|
7059
|
+
walk(program, (node) => {
|
|
7060
|
+
if (!isMockFactory(source, node)) return;
|
|
7061
|
+
for (const argument of node.arguments ?? []) {
|
|
7062
|
+
walk(argument, (inner) => {
|
|
7063
|
+
if (inner.type !== "NewExpression") return;
|
|
7064
|
+
const callee = inner.callee;
|
|
7065
|
+
if (callee?.type !== "Identifier") return;
|
|
7066
|
+
if (source.slice(callee.start, callee.end) !== "Promise") return;
|
|
7067
|
+
if ((inner.typeArguments ?? inner.typeParameters) !== void 0) return;
|
|
7068
|
+
const executors = inner.arguments ?? [];
|
|
7069
|
+
if (executors.length !== 1) return;
|
|
7070
|
+
const value = resolvedValue(source, executors[0]);
|
|
7071
|
+
if (value === void 0) return;
|
|
7072
|
+
edits.push({
|
|
7073
|
+
start: inner.start,
|
|
7074
|
+
end: inner.end,
|
|
7075
|
+
text: `Promise.resolve(${source.slice(value.start, value.end)})`
|
|
7076
|
+
});
|
|
7077
|
+
});
|
|
7078
|
+
}
|
|
7079
|
+
});
|
|
7080
|
+
let result = source;
|
|
7081
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
7082
|
+
result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
|
|
7083
|
+
}
|
|
7084
|
+
return { source: result, rewritten: edits.length };
|
|
7085
|
+
}
|
|
7086
|
+
|
|
6935
7087
|
// src/roles/test/codemod/propagate-await.ts
|
|
6936
7088
|
import { parseSync as parseSync5 } from "oxc-parser";
|
|
6937
7089
|
var FUNCTIONS3 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration"]);
|
|
@@ -7059,9 +7211,9 @@ function propagateAwait(fileName, source, original, elsewhere = /* @__PURE__ */
|
|
|
7059
7211
|
}
|
|
7060
7212
|
|
|
7061
7213
|
// src/roles/test/codemod/reexport-barrel-mock.ts
|
|
7062
|
-
import { readFileSync as
|
|
7063
|
-
import { createRequire as createRequire5 } from "module";
|
|
7064
|
-
import { dirname as dirname5, join as
|
|
7214
|
+
import { readFileSync as readFileSync24, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
|
|
7215
|
+
import { createRequire as createRequire5 } from "node:module";
|
|
7216
|
+
import { dirname as dirname5, join as join30 } from "node:path";
|
|
7065
7217
|
var MEMBER_TYPES13 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
7066
7218
|
var PACKAGE_SUBPATH = /^(@[^/]+\/[^/]+|[^@.][^/]*)\/(.+)$/;
|
|
7067
7219
|
var PURE_REEXPORT = /^module\.exports\s*=\s*require\(\s*(['"])([^'"]+)\1\s*\)\s*;?$/;
|
|
@@ -7114,11 +7266,11 @@ function barrelsFor(fromFile, name, target) {
|
|
|
7114
7266
|
const aliases = [];
|
|
7115
7267
|
for (const entry of entries) {
|
|
7116
7268
|
if (!entry.endsWith(".js")) continue;
|
|
7117
|
-
const path =
|
|
7269
|
+
const path = join30(root, entry);
|
|
7118
7270
|
let body;
|
|
7119
7271
|
try {
|
|
7120
7272
|
if (!statSync4(path).isFile()) continue;
|
|
7121
|
-
body =
|
|
7273
|
+
body = readFileSync24(path, "utf8");
|
|
7122
7274
|
} catch {
|
|
7123
7275
|
continue;
|
|
7124
7276
|
}
|
|
@@ -7160,12 +7312,9 @@ ${text}` });
|
|
|
7160
7312
|
// src/roles/test/codemod/repoint-msw.ts
|
|
7161
7313
|
import { parseSync as parseSync6 } from "oxc-parser";
|
|
7162
7314
|
var SENTINEL_MSW_SERVER = "@hublo/sentinel/test/msw";
|
|
7163
|
-
var
|
|
7315
|
+
var SENTINEL_MSW_API = "@hublo/sentinel/test/tools/msw";
|
|
7164
7316
|
var MSW_PACKAGE = /^msw$/;
|
|
7165
|
-
|
|
7166
|
-
const source = declaration.source?.value;
|
|
7167
|
-
return source !== void 0 && MSW_PACKAGE.test(source);
|
|
7168
|
-
}
|
|
7317
|
+
var MSW_SERVER_MODULE = /(^|\/)msw\/server$/;
|
|
7169
7318
|
function importsServer(declaration) {
|
|
7170
7319
|
return (declaration.specifiers ?? []).some((specifier) => {
|
|
7171
7320
|
const imported = specifier.imported;
|
|
@@ -7181,21 +7330,28 @@ function repointMswServer(fileName, source) {
|
|
|
7181
7330
|
if (node.type !== "ImportDeclaration") return;
|
|
7182
7331
|
const specifier = node.source;
|
|
7183
7332
|
if (specifier?.value === void 0) return;
|
|
7184
|
-
const
|
|
7185
|
-
|
|
7186
|
-
|
|
7333
|
+
const isServer = MSW_SERVER_MODULE.test(specifier.value) && importsServer(node);
|
|
7334
|
+
const isPackage2 = MSW_PACKAGE.test(specifier.value);
|
|
7335
|
+
if (!isServer && !isPackage2) return;
|
|
7336
|
+
edits.push({
|
|
7337
|
+
start: specifier.start,
|
|
7338
|
+
end: specifier.end,
|
|
7339
|
+
to: isServer ? SENTINEL_MSW_SERVER : SENTINEL_MSW_API
|
|
7340
|
+
});
|
|
7187
7341
|
});
|
|
7188
7342
|
if (edits.length === 0) return { source, repointed: false };
|
|
7189
7343
|
let rewritten = source;
|
|
7190
7344
|
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
7191
7345
|
const quote = source[edit.start] === '"' ? '"' : "'";
|
|
7192
|
-
rewritten = rewritten.slice(0, edit.start) + `${quote}${
|
|
7346
|
+
rewritten = rewritten.slice(0, edit.start) + `${quote}${edit.to}${quote}` + rewritten.slice(edit.end);
|
|
7193
7347
|
}
|
|
7194
7348
|
return { source: rewritten, repointed: true };
|
|
7195
7349
|
}
|
|
7196
7350
|
|
|
7197
7351
|
// src/roles/test/codemod/spy-instance-type.ts
|
|
7198
7352
|
var SPY_TYPES = /* @__PURE__ */ new Set(["SpyInstance"]);
|
|
7353
|
+
var QUALIFIED_ONLY = /* @__PURE__ */ new Set(["Mock"]);
|
|
7354
|
+
var VITEST_NAME = { SpyInstance: "MockInstance", Mock: "Mock" };
|
|
7199
7355
|
function typeNameOf(source, node) {
|
|
7200
7356
|
const name = node.typeName;
|
|
7201
7357
|
if (name === void 0) return void 0;
|
|
@@ -7207,17 +7363,20 @@ function rewriteSpyInstanceType(source, program) {
|
|
|
7207
7363
|
if (node.type !== "TSTypeReference") return;
|
|
7208
7364
|
const written = typeNameOf(source, node);
|
|
7209
7365
|
if (written === void 0) return;
|
|
7210
|
-
const
|
|
7211
|
-
|
|
7366
|
+
const qualified = written.startsWith("jest.");
|
|
7367
|
+
const bare = qualified ? written.slice("jest.".length) : written;
|
|
7368
|
+
if (!SPY_TYPES.has(bare) && !(qualified && QUALIFIED_ONLY.has(bare))) return;
|
|
7212
7369
|
const args = (node.typeArguments ?? node.typeParameters)?.params;
|
|
7213
7370
|
if (args === void 0 || args.length === 0) return;
|
|
7214
7371
|
const text = (argument) => source.slice(argument.start, argument.end);
|
|
7215
7372
|
const returns = text(args[0]);
|
|
7216
|
-
const parameters = args.length > 1 ? `...args: ${text(args[1])}` : "...args:
|
|
7373
|
+
const parameters = args.length > 1 ? `...args: ${text(args[1])}` : "...args: never[]";
|
|
7374
|
+
const target = VITEST_NAME[bare];
|
|
7217
7375
|
edits.push({
|
|
7218
7376
|
start: node.start,
|
|
7219
7377
|
end: node.end,
|
|
7220
|
-
text:
|
|
7378
|
+
text: `${target}<(${parameters}) => ${returns}>`,
|
|
7379
|
+
imports: target
|
|
7221
7380
|
});
|
|
7222
7381
|
});
|
|
7223
7382
|
let result = source;
|
|
@@ -7227,7 +7386,8 @@ function rewriteSpyInstanceType(source, program) {
|
|
|
7227
7386
|
return {
|
|
7228
7387
|
source: result,
|
|
7229
7388
|
rewritten: edits.length,
|
|
7230
|
-
|
|
7389
|
+
// Only the names actually written, so a file does not import a type it never uses.
|
|
7390
|
+
typeImports: [...new Set(edits.map((edit) => edit.imports))]
|
|
7231
7391
|
};
|
|
7232
7392
|
}
|
|
7233
7393
|
|
|
@@ -7252,7 +7412,7 @@ function parse2(fileName, source) {
|
|
|
7252
7412
|
const { program, errors } = parseSync7(fileName, source, { sourceType: "module" });
|
|
7253
7413
|
return errors.length > 0 ? void 0 : program;
|
|
7254
7414
|
}
|
|
7255
|
-
function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new Set(), asyncHelpers = /* @__PURE__ */ new Set()) {
|
|
7415
|
+
function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new Set(), asyncHelpers = /* @__PURE__ */ new Set(), modulesOwnMsw = false) {
|
|
7256
7416
|
const untouched = {
|
|
7257
7417
|
source,
|
|
7258
7418
|
changed: false,
|
|
@@ -7315,7 +7475,10 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
|
|
|
7315
7475
|
const afterEmpty = parse2(fileName, emptyImplementations.source);
|
|
7316
7476
|
if (afterEmpty === void 0) return unparsable("empty mock implementation");
|
|
7317
7477
|
const hooks = rewriteHookReturnValue(emptyImplementations.source, afterEmpty);
|
|
7318
|
-
const
|
|
7478
|
+
const afterHooks = parse2(fileName, hooks.source);
|
|
7479
|
+
if (afterHooks === void 0) return unparsable("hook return value");
|
|
7480
|
+
const promises = rewriteMockPromiseResolve(hooks.source, afterHooks);
|
|
7481
|
+
const awaited = propagateAwait(fileName, promises.source, original, asyncHelpers);
|
|
7319
7482
|
const program = parse2(fileName, awaited.source);
|
|
7320
7483
|
if (program === void 0) return unparsable("hook body");
|
|
7321
7484
|
const hoist = hoistCapturedBindings(
|
|
@@ -7330,7 +7493,7 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
|
|
|
7330
7493
|
...spyTypes.typeImports
|
|
7331
7494
|
]);
|
|
7332
7495
|
const deep = normaliseDeepImports(withImport);
|
|
7333
|
-
const repointed = repointMswServer(fileName, deep.source);
|
|
7496
|
+
const repointed = modulesOwnMsw ? { source: deep.source, repointed: false } : repointMswServer(fileName, deep.source);
|
|
7334
7497
|
const finalProgram = parse2(fileName, repointed.source);
|
|
7335
7498
|
if (finalProgram === void 0) return unparsable("msw server import");
|
|
7336
7499
|
const barrels = rewriteReexportBarrelMocks(fileName, repointed.source, finalProgram);
|
|
@@ -7366,8 +7529,8 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
|
|
|
7366
7529
|
}
|
|
7367
7530
|
|
|
7368
7531
|
// src/roles/test/codemod/test-support-files.ts
|
|
7369
|
-
import { existsSync as
|
|
7370
|
-
import { dirname as dirname6, join as
|
|
7532
|
+
import { existsSync as existsSync26, readFileSync as readFileSync25, readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
|
|
7533
|
+
import { dirname as dirname6, join as join31, relative as relative5, resolve as resolve4 } from "node:path";
|
|
7371
7534
|
var SKIPPED = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".git", ".vite"]);
|
|
7372
7535
|
var TEST_FILE2 = /\.(spec|test)\.[cm]?[jt]sx?$/;
|
|
7373
7536
|
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
@@ -7387,13 +7550,13 @@ function relativeSpecifiers(source) {
|
|
|
7387
7550
|
}
|
|
7388
7551
|
function resolveSpecifier(fromFile, specifier) {
|
|
7389
7552
|
const base = resolve4(dirname6(fromFile), specifier);
|
|
7390
|
-
if (
|
|
7553
|
+
if (existsSync26(base) && statSync5(base).isFile()) return base;
|
|
7391
7554
|
for (const extension of EXTENSIONS) {
|
|
7392
|
-
if (
|
|
7555
|
+
if (existsSync26(`${base}${extension}`)) return `${base}${extension}`;
|
|
7393
7556
|
}
|
|
7394
7557
|
for (const extension of EXTENSIONS) {
|
|
7395
|
-
const index =
|
|
7396
|
-
if (
|
|
7558
|
+
const index = join31(base, `index${extension}`);
|
|
7559
|
+
if (existsSync26(index)) return index;
|
|
7397
7560
|
}
|
|
7398
7561
|
return void 0;
|
|
7399
7562
|
}
|
|
@@ -7410,9 +7573,9 @@ function moduleSources(cwd) {
|
|
|
7410
7573
|
}
|
|
7411
7574
|
for (const entry of entries) {
|
|
7412
7575
|
if (entry.isDirectory()) {
|
|
7413
|
-
if (!SKIPPED.has(entry.name)) stack.push(
|
|
7576
|
+
if (!SKIPPED.has(entry.name)) stack.push(join31(dir, entry.name));
|
|
7414
7577
|
} else if (SOURCE_FILE.test(entry.name)) {
|
|
7415
|
-
found.push(
|
|
7578
|
+
found.push(join31(dir, entry.name));
|
|
7416
7579
|
}
|
|
7417
7580
|
}
|
|
7418
7581
|
}
|
|
@@ -7420,7 +7583,7 @@ function moduleSources(cwd) {
|
|
|
7420
7583
|
}
|
|
7421
7584
|
function read(path) {
|
|
7422
7585
|
try {
|
|
7423
|
-
return
|
|
7586
|
+
return readFileSync25(path, "utf8");
|
|
7424
7587
|
} catch {
|
|
7425
7588
|
return void 0;
|
|
7426
7589
|
}
|
|
@@ -7432,7 +7595,7 @@ function setupFilesInside(cwd, named) {
|
|
|
7432
7595
|
const full = resolve4(cwd, withoutToken);
|
|
7433
7596
|
const inside = relative5(cwd, full);
|
|
7434
7597
|
if (inside.startsWith("..") || inside === "") continue;
|
|
7435
|
-
if (!
|
|
7598
|
+
if (!existsSync26(full)) continue;
|
|
7436
7599
|
kept.push(inside.replaceAll("\\", "/"));
|
|
7437
7600
|
}
|
|
7438
7601
|
return kept;
|
|
@@ -7478,7 +7641,7 @@ function parse3(source) {
|
|
|
7478
7641
|
}
|
|
7479
7642
|
function read2(path) {
|
|
7480
7643
|
try {
|
|
7481
|
-
return
|
|
7644
|
+
return readFileSync26(path, "utf8");
|
|
7482
7645
|
} catch {
|
|
7483
7646
|
return void 0;
|
|
7484
7647
|
}
|
|
@@ -7496,7 +7659,7 @@ function defaultImportedSpecifiers(cwd) {
|
|
|
7496
7659
|
}
|
|
7497
7660
|
for (const entry of entries) {
|
|
7498
7661
|
if (SKIPPED2.has(entry)) continue;
|
|
7499
|
-
const path =
|
|
7662
|
+
const path = join32(dir, entry);
|
|
7500
7663
|
let directory;
|
|
7501
7664
|
try {
|
|
7502
7665
|
directory = statSync6(path).isDirectory();
|
|
@@ -7517,6 +7680,7 @@ function defaultImportedSpecifiers(cwd) {
|
|
|
7517
7680
|
return found;
|
|
7518
7681
|
}
|
|
7519
7682
|
function migrateModuleTests(cwd, setupFiles = []) {
|
|
7683
|
+
const modulesOwnMsw = ownsMswLifecycle(cwd, setupFiles);
|
|
7520
7684
|
const operations = [];
|
|
7521
7685
|
const blockers = [];
|
|
7522
7686
|
let files = 0;
|
|
@@ -7524,14 +7688,22 @@ function migrateModuleTests(cwd, setupFiles = []) {
|
|
|
7524
7688
|
let hoisted = 0;
|
|
7525
7689
|
const defaultImported = defaultImportedSpecifiers(cwd);
|
|
7526
7690
|
const asyncHelpers = /* @__PURE__ */ new Set();
|
|
7527
|
-
for (const
|
|
7528
|
-
const source = read2(
|
|
7691
|
+
for (const relative8 of listTestSupportFiles(cwd, setupFiles)) {
|
|
7692
|
+
const source = read2(join32(cwd, relative8));
|
|
7529
7693
|
if (source === void 0) {
|
|
7530
|
-
blockers.push({ file:
|
|
7694
|
+
blockers.push({ file: relative8, reason: "this file could not be read" });
|
|
7531
7695
|
continue;
|
|
7532
7696
|
}
|
|
7533
|
-
const migrated = migrateTestFile(
|
|
7534
|
-
|
|
7697
|
+
const migrated = migrateTestFile(
|
|
7698
|
+
join32(cwd, relative8),
|
|
7699
|
+
source,
|
|
7700
|
+
defaultImported,
|
|
7701
|
+
/* @__PURE__ */ new Set(),
|
|
7702
|
+
modulesOwnMsw
|
|
7703
|
+
);
|
|
7704
|
+
const renamedTo = vitestSupportName(relative8);
|
|
7705
|
+
if (!migrated.changed && renamedTo === relative8) continue;
|
|
7706
|
+
if (renamedTo !== relative8) operations.push({ kind: "delete", path: relative8 });
|
|
7535
7707
|
const before = parse3(source);
|
|
7536
7708
|
const after = parse3(migrated.source);
|
|
7537
7709
|
if (before !== void 0 && after !== void 0) {
|
|
@@ -7542,41 +7714,50 @@ function migrateModuleTests(cwd, setupFiles = []) {
|
|
|
7542
7714
|
}
|
|
7543
7715
|
files++;
|
|
7544
7716
|
renamed += migrated.renamed;
|
|
7545
|
-
operations.push({ kind: "write", path:
|
|
7717
|
+
operations.push({ kind: "write", path: renamedTo, contents: migrated.source });
|
|
7546
7718
|
}
|
|
7547
|
-
for (const
|
|
7548
|
-
const source = read2(
|
|
7719
|
+
for (const relative8 of listTestFiles(cwd)) {
|
|
7720
|
+
const source = read2(join32(cwd, relative8));
|
|
7549
7721
|
if (source === void 0) {
|
|
7550
|
-
blockers.push({ file:
|
|
7722
|
+
blockers.push({ file: relative8, reason: "this file could not be read" });
|
|
7551
7723
|
continue;
|
|
7552
7724
|
}
|
|
7553
|
-
const migrated = migrateTestFile(
|
|
7725
|
+
const migrated = migrateTestFile(
|
|
7726
|
+
join32(cwd, relative8),
|
|
7727
|
+
source,
|
|
7728
|
+
defaultImported,
|
|
7729
|
+
asyncHelpers,
|
|
7730
|
+
modulesOwnMsw
|
|
7731
|
+
);
|
|
7554
7732
|
for (const member of migrated.unhandled) {
|
|
7555
7733
|
blockers.push({
|
|
7556
|
-
file: `${
|
|
7734
|
+
file: `${relative8}:${member.line}`,
|
|
7557
7735
|
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}`
|
|
7558
7736
|
});
|
|
7559
7737
|
}
|
|
7560
7738
|
for (const refusal of migrated.refusals) {
|
|
7561
|
-
blockers.push({ file:
|
|
7739
|
+
blockers.push({ file: relative8, reason: refusal.reason });
|
|
7562
7740
|
}
|
|
7563
7741
|
if (!migrated.changed) continue;
|
|
7564
7742
|
files++;
|
|
7565
7743
|
renamed += migrated.renamed;
|
|
7566
7744
|
if (migrated.hoisted.length > 0) hoisted++;
|
|
7567
|
-
operations.push({ kind: "write", path:
|
|
7745
|
+
operations.push({ kind: "write", path: relative8, contents: migrated.source });
|
|
7568
7746
|
}
|
|
7569
7747
|
return blockers.length > 0 ? { operations: [], files, renamed, hoisted, blockers } : { operations, files, renamed, hoisted, blockers: [] };
|
|
7570
7748
|
}
|
|
7571
7749
|
|
|
7572
7750
|
// src/roles/test/nest/generate-config.ts
|
|
7573
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
7574
|
-
import { join as
|
|
7751
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync27, statSync as statSync7 } from "node:fs";
|
|
7752
|
+
import { join as join33, relative as relative6 } from "node:path";
|
|
7575
7753
|
var NEST_TEST_PRESET_SPECIFIER = "@hublo/sentinel/test/nest";
|
|
7576
|
-
var
|
|
7754
|
+
var REACT_TEST_PRESET_SPECIFIER = "@hublo/sentinel/test/react";
|
|
7755
|
+
function flavourOf(reading) {
|
|
7756
|
+
return reading.testEnvironment === "jsdom" ? "react" : "nest";
|
|
7757
|
+
}
|
|
7758
|
+
var SENTINEL_TEST_SETUP = "@hublo/sentinel/test/setup/jest-parity";
|
|
7577
7759
|
var SENTINEL_MSW_SETUP = "@hublo/sentinel/test/setup/msw-lifecycle";
|
|
7578
7760
|
var SENTINEL_WORKSPACE_SETUP = "@hublo/sentinel/test/setup/workspace";
|
|
7579
|
-
var NEST_TEST_CONFIG_FILE = "vitest.config.mts";
|
|
7580
7761
|
function hopsToRoot(cwd, workspaceRoot) {
|
|
7581
7762
|
const up = relative6(cwd, workspaceRoot).replaceAll("\\", "/");
|
|
7582
7763
|
return up === "" ? "." : up;
|
|
@@ -7612,7 +7793,7 @@ function readModuleTraits(cwd) {
|
|
|
7612
7793
|
}
|
|
7613
7794
|
for (const entry of entries) {
|
|
7614
7795
|
if (SKIPPED3.has(entry)) continue;
|
|
7615
|
-
const path =
|
|
7796
|
+
const path = join33(dir, entry);
|
|
7616
7797
|
let directory;
|
|
7617
7798
|
try {
|
|
7618
7799
|
directory = statSync7(path).isDirectory();
|
|
@@ -7626,7 +7807,7 @@ function readModuleTraits(cwd) {
|
|
|
7626
7807
|
if (!/\.tsx?$/.test(entry)) continue;
|
|
7627
7808
|
let source;
|
|
7628
7809
|
try {
|
|
7629
|
-
source =
|
|
7810
|
+
source = readFileSync27(path, "utf8");
|
|
7630
7811
|
} catch {
|
|
7631
7812
|
continue;
|
|
7632
7813
|
}
|
|
@@ -7643,14 +7824,17 @@ function readModuleTraits(cwd) {
|
|
|
7643
7824
|
return traits;
|
|
7644
7825
|
}
|
|
7645
7826
|
var withoutRootDir = (value) => value.replaceAll("<rootDir>/", "./").replaceAll("<rootDir>", ".");
|
|
7646
|
-
function
|
|
7827
|
+
function ownsMswLifecycle2(cwd, reading) {
|
|
7828
|
+
return ownsMswLifecycle(cwd, [...reading.setupFiles ?? [], ...reading.setupFilesAfterEnv ?? []]);
|
|
7829
|
+
}
|
|
7830
|
+
function setupBlock(cwd, reading, msw) {
|
|
7647
7831
|
const declared = [...reading.setupFiles ?? [], ...reading.setupFilesAfterEnv ?? []];
|
|
7648
7832
|
const namedTheRootSetup = declared.some((file) => file.endsWith("jest.setup.after.env.js"));
|
|
7649
|
-
const own = declared.filter((file) => !file.endsWith("jest.setup.after.env.js")).map((file) => `'${withoutRootDir(file)}'`);
|
|
7833
|
+
const own = declared.filter((file) => !file.endsWith("jest.setup.after.env.js")).map((file) => `'${vitestSupportName(withoutRootDir(file))}'`);
|
|
7650
7834
|
const sentinel = [
|
|
7651
7835
|
`'${SENTINEL_TEST_SETUP}'`,
|
|
7652
7836
|
...namedTheRootSetup ? [`'${SENTINEL_WORKSPACE_SETUP}'`] : [],
|
|
7653
|
-
...msw ? [`'${SENTINEL_MSW_SETUP}'`] : []
|
|
7837
|
+
...msw && namedTheRootSetup && !ownsMswLifecycle2(cwd, reading) ? [`'${SENTINEL_MSW_SETUP}'`] : []
|
|
7654
7838
|
];
|
|
7655
7839
|
return ` setupFiles: [${[...sentinel, ...own].join(", ")}],`;
|
|
7656
7840
|
}
|
|
@@ -7677,7 +7861,7 @@ function timeoutBlock(reading) {
|
|
|
7677
7861
|
}
|
|
7678
7862
|
function globalSetupBlock(reading) {
|
|
7679
7863
|
if (reading.globalSetup === void 0) return "";
|
|
7680
|
-
const files = reading.globalSetup.map((file) => `'${withoutRootDir(file)}'`).join(", ");
|
|
7864
|
+
const files = reading.globalSetup.map((file) => `'${vitestSupportName(withoutRootDir(file))}'`).join(", ");
|
|
7681
7865
|
return ` globalSetup: [${files}],`;
|
|
7682
7866
|
}
|
|
7683
7867
|
function includeBlock(reading) {
|
|
@@ -7697,7 +7881,8 @@ function aliasBlock(reading) {
|
|
|
7697
7881
|
if (reading.aliases === void 0 || reading.aliases.length === 0) return "";
|
|
7698
7882
|
const entries = reading.aliases.map((alias) => {
|
|
7699
7883
|
const pattern = alias.find.replaceAll(String.raw`\\`, "\\").replaceAll("/", String.raw`\/`);
|
|
7700
|
-
const
|
|
7884
|
+
const deadToken = /<rootdir>/i.test(alias.replacement) && !alias.replacement.includes("<rootDir>");
|
|
7885
|
+
const target = isPackage(alias.replacement) ? `'${alias.replacement}'` : deadToken ? `'${alias.replacement}'` : `path.resolve(import.meta.dirname, '${withoutRootDir(alias.replacement)}')`;
|
|
7701
7886
|
return ` { find: /${pattern}/, replacement: ${target} },`;
|
|
7702
7887
|
});
|
|
7703
7888
|
return [" resolve: {", " alias: [", ...entries, " ],", " },"].join("\n");
|
|
@@ -7712,7 +7897,7 @@ function generateNestTestConfig(cwd, reading, target = "test") {
|
|
|
7712
7897
|
const up = hopsToRoot(cwd, workspaceRoot);
|
|
7713
7898
|
const test = [
|
|
7714
7899
|
environmentBlock(reading),
|
|
7715
|
-
setupBlock(reading, traits.msw),
|
|
7900
|
+
setupBlock(cwd, reading, traits.msw),
|
|
7716
7901
|
envBlock(reading),
|
|
7717
7902
|
timeoutBlock(reading),
|
|
7718
7903
|
concurrencyBlock(cwd, reading, target),
|
|
@@ -7727,7 +7912,7 @@ function generateNestTestConfig(cwd, reading, target = "test") {
|
|
|
7727
7912
|
`import path from 'node:path'`,
|
|
7728
7913
|
"",
|
|
7729
7914
|
...needsDefaults ? [`import { configDefaults } from 'vitest/config'`, ""] : [],
|
|
7730
|
-
`import { defineConfig, nestTestConfig } from '${NEST_TEST_PRESET_SPECIFIER}'`
|
|
7915
|
+
flavourOf(reading) === "react" ? `import { defineConfig, reactTestConfig } from '${REACT_TEST_PRESET_SPECIFIER}'` : `import { defineConfig, nestTestConfig } from '${NEST_TEST_PRESET_SPECIFIER}'`
|
|
7731
7916
|
].join("\n");
|
|
7732
7917
|
const overrides = [aliasBlock(reading), " test: {", ...test, " },"].filter(
|
|
7733
7918
|
(line) => line !== ""
|
|
@@ -7738,7 +7923,7 @@ function generateNestTestConfig(cwd, reading, target = "test") {
|
|
|
7738
7923
|
source: `${imports}
|
|
7739
7924
|
|
|
7740
7925
|
export default defineConfig(
|
|
7741
|
-
nestTestConfig({
|
|
7926
|
+
${flavourOf(reading) === "react" ? "reactTestConfig" : "nestTestConfig"}({
|
|
7742
7927
|
root: import.meta.dirname,
|
|
7743
7928
|
workspaceRoot: path.resolve(import.meta.dirname, '${up}'),${decoratorLine}
|
|
7744
7929
|
overrides: {
|
|
@@ -7751,8 +7936,8 @@ ${overrides.join("\n")}
|
|
|
7751
7936
|
}
|
|
7752
7937
|
|
|
7753
7938
|
// src/roles/test/nest/read-jest-config.ts
|
|
7754
|
-
import { existsSync as
|
|
7755
|
-
import { join as
|
|
7939
|
+
import { existsSync as existsSync27, readFileSync as readFileSync28 } from "node:fs";
|
|
7940
|
+
import { join as join34 } from "node:path";
|
|
7756
7941
|
var CARRIED = [
|
|
7757
7942
|
"testEnvironment",
|
|
7758
7943
|
"coverageDirectory",
|
|
@@ -7942,7 +8127,7 @@ function extendedConfig(source, cwd) {
|
|
|
7942
8127
|
(name) => name.replace(/^\.\//, "")
|
|
7943
8128
|
);
|
|
7944
8129
|
const file = candidates.find(
|
|
7945
|
-
(name) => /^jest[\w.-]*\.(ts|js)$/.test(name) &&
|
|
8130
|
+
(name) => /^jest[\w.-]*\.(ts|js)$/.test(name) && existsSync27(join34(cwd, name))
|
|
7946
8131
|
);
|
|
7947
8132
|
if (file === void 0) continue;
|
|
7948
8133
|
if (!new RegExp(String.raw`\{\s*\.\.\.${binding}\b`).test(source)) continue;
|
|
@@ -7969,11 +8154,11 @@ function sideEffects(source, extended) {
|
|
|
7969
8154
|
return found;
|
|
7970
8155
|
}
|
|
7971
8156
|
function readJestConfig(cwd, named) {
|
|
7972
|
-
const file = named ?? JEST_CONFIG_FILES.find((name) =>
|
|
7973
|
-
if (file === void 0 || !
|
|
8157
|
+
const file = named ?? JEST_CONFIG_FILES.find((name) => existsSync27(join34(cwd, name)));
|
|
8158
|
+
if (file === void 0 || !existsSync27(join34(cwd, file))) return void 0;
|
|
7974
8159
|
let source;
|
|
7975
8160
|
try {
|
|
7976
|
-
source =
|
|
8161
|
+
source = readFileSync28(join34(cwd, file), "utf8");
|
|
7977
8162
|
} catch {
|
|
7978
8163
|
return { file, refusals: [`${file} could not be read`], notes: [] };
|
|
7979
8164
|
}
|
|
@@ -8054,8 +8239,8 @@ function readJestConfig(cwd, named) {
|
|
|
8054
8239
|
}
|
|
8055
8240
|
|
|
8056
8241
|
// src/roles/test/nest/run-modes.ts
|
|
8057
|
-
import { existsSync as
|
|
8058
|
-
import { basename as basename2, join as
|
|
8242
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
8243
|
+
import { basename as basename2, join as join35 } from "node:path";
|
|
8059
8244
|
var JEST_EXECUTOR = "@nx/jest:jest";
|
|
8060
8245
|
var PRIMARY_CONFIG_FILE = "vitest.config.mts";
|
|
8061
8246
|
var PRIMARY_TARGET = "test";
|
|
@@ -8076,7 +8261,7 @@ function runModes(cwd, fallback) {
|
|
|
8076
8261
|
const jestConfig = entry.options?.jestConfig;
|
|
8077
8262
|
if (jestConfig === void 0) continue;
|
|
8078
8263
|
const name = configName(jestConfig);
|
|
8079
|
-
if (!
|
|
8264
|
+
if (!existsSync28(join35(cwd, name))) continue;
|
|
8080
8265
|
declared.push({
|
|
8081
8266
|
target,
|
|
8082
8267
|
jestConfig: name,
|
|
@@ -8100,16 +8285,63 @@ function runModes(cwd, fallback) {
|
|
|
8100
8285
|
);
|
|
8101
8286
|
}
|
|
8102
8287
|
|
|
8288
|
+
// src/roles/test/written-file-formatter.ts
|
|
8289
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
8290
|
+
import { dirname as dirname7, join as join36 } from "node:path";
|
|
8291
|
+
var PRETTIER_CONFIGS = [
|
|
8292
|
+
".prettierrc",
|
|
8293
|
+
".prettierrc.json",
|
|
8294
|
+
".prettierrc.json5",
|
|
8295
|
+
".prettierrc.yaml",
|
|
8296
|
+
".prettierrc.yml",
|
|
8297
|
+
".prettierrc.js",
|
|
8298
|
+
".prettierrc.cjs",
|
|
8299
|
+
".prettierrc.mjs",
|
|
8300
|
+
".prettierrc.toml",
|
|
8301
|
+
"prettier.config.js",
|
|
8302
|
+
"prettier.config.cjs",
|
|
8303
|
+
"prettier.config.mjs"
|
|
8304
|
+
];
|
|
8305
|
+
function prettierConfigFrom(cwd) {
|
|
8306
|
+
let dir = cwd;
|
|
8307
|
+
for (; ; ) {
|
|
8308
|
+
for (const name of PRETTIER_CONFIGS) {
|
|
8309
|
+
if (existsSync29(join36(dir, name))) return join36(dir, name);
|
|
8310
|
+
}
|
|
8311
|
+
const parent = dirname7(dir);
|
|
8312
|
+
if (parent === dir) return void 0;
|
|
8313
|
+
dir = parent;
|
|
8314
|
+
}
|
|
8315
|
+
}
|
|
8316
|
+
function writtenFileFormatter(cwd) {
|
|
8317
|
+
if (readFormatAdoption(cwd).configFile !== null) {
|
|
8318
|
+
const oxfmt = resolveOxfmt(cwd);
|
|
8319
|
+
if (oxfmt !== void 0) {
|
|
8320
|
+
return { bin: oxfmt, args: ["--write"], origin: "this module's own oxfmt config" };
|
|
8321
|
+
}
|
|
8322
|
+
}
|
|
8323
|
+
const config = prettierConfigFrom(cwd);
|
|
8324
|
+
const prettier = resolveBin(cwd, "prettier");
|
|
8325
|
+
if (config !== void 0 && prettier !== void 0) {
|
|
8326
|
+
return {
|
|
8327
|
+
bin: prettier,
|
|
8328
|
+
args: ["--write", "--log-level", "warn"],
|
|
8329
|
+
origin: `the workspace's Prettier`
|
|
8330
|
+
};
|
|
8331
|
+
}
|
|
8332
|
+
return void 0;
|
|
8333
|
+
}
|
|
8334
|
+
|
|
8103
8335
|
// src/roles/test/jest-migration.ts
|
|
8104
8336
|
var SPEC_TSCONFIG = "tsconfig.spec.json";
|
|
8105
8337
|
var VITEST_GLOBAL_TYPES = "vitest/globals";
|
|
8106
8338
|
function specTypesOperation(cwd) {
|
|
8107
|
-
const path =
|
|
8108
|
-
if (!
|
|
8339
|
+
const path = join37(cwd, SPEC_TSCONFIG);
|
|
8340
|
+
if (!existsSync30(path)) return void 0;
|
|
8109
8341
|
let config;
|
|
8110
8342
|
try {
|
|
8111
8343
|
config = parseJsonc(
|
|
8112
|
-
|
|
8344
|
+
readFileSync29(path, "utf8"),
|
|
8113
8345
|
SPEC_TSCONFIG
|
|
8114
8346
|
);
|
|
8115
8347
|
} catch {
|
|
@@ -8127,6 +8359,70 @@ function specTypesOperation(cwd) {
|
|
|
8127
8359
|
}
|
|
8128
8360
|
};
|
|
8129
8361
|
}
|
|
8362
|
+
function shippedTypesPath(cwd, declaration) {
|
|
8363
|
+
try {
|
|
8364
|
+
const manifest = createRequire6(join37(cwd, "noop.cjs")).resolve("@hublo/sentinel/package.json");
|
|
8365
|
+
const file = join37(dirname8(manifest), declaration);
|
|
8366
|
+
if (existsSync30(file)) {
|
|
8367
|
+
const path = relative7(cwd, file);
|
|
8368
|
+
return path.startsWith(".") ? path : `./${path}`;
|
|
8369
|
+
}
|
|
8370
|
+
} catch {
|
|
8371
|
+
}
|
|
8372
|
+
return `./node_modules/@hublo/sentinel/${declaration}`;
|
|
8373
|
+
}
|
|
8374
|
+
function tsconfigFileListOperations(cwd) {
|
|
8375
|
+
const operations = [];
|
|
8376
|
+
let names;
|
|
8377
|
+
try {
|
|
8378
|
+
names = readdirSync8(cwd).filter((name) => TSCONFIG_FILE.test(name));
|
|
8379
|
+
} catch {
|
|
8380
|
+
return operations;
|
|
8381
|
+
}
|
|
8382
|
+
for (const name of names) {
|
|
8383
|
+
let config;
|
|
8384
|
+
try {
|
|
8385
|
+
config = parseJsonc(
|
|
8386
|
+
readFileSync29(join37(cwd, name), "utf8"),
|
|
8387
|
+
name
|
|
8388
|
+
);
|
|
8389
|
+
} catch {
|
|
8390
|
+
continue;
|
|
8391
|
+
}
|
|
8392
|
+
const include = Array.isArray(config?.include) ? rewriteFileList(config.include) : void 0;
|
|
8393
|
+
const exclude = Array.isArray(config?.exclude) ? rewriteFileList(config.exclude) : void 0;
|
|
8394
|
+
const files = Array.isArray(config?.files) ? rewriteFileList(config.files) : void 0;
|
|
8395
|
+
const withTypes = name === SPEC_TSCONFIG ? withShippedTypes(cwd, files ?? config?.files) : void 0;
|
|
8396
|
+
if (include === void 0 && exclude === void 0 && files === void 0 && withTypes === void 0)
|
|
8397
|
+
continue;
|
|
8398
|
+
operations.push({
|
|
8399
|
+
kind: "merge-json",
|
|
8400
|
+
path: name,
|
|
8401
|
+
value: {
|
|
8402
|
+
...include === void 0 ? {} : { include },
|
|
8403
|
+
...exclude === void 0 ? {} : { exclude },
|
|
8404
|
+
...withTypes === void 0 ? files === void 0 ? {} : { files } : { files: withTypes }
|
|
8405
|
+
}
|
|
8406
|
+
});
|
|
8407
|
+
}
|
|
8408
|
+
return operations;
|
|
8409
|
+
}
|
|
8410
|
+
function rewriteFileList(entries) {
|
|
8411
|
+
const rewritten = entries.filter((entry) => typeof entry !== "string" || !JEST_CONFIG_ENTRY.test(entry)).map((entry) => typeof entry === "string" ? vitestSupportName(entry) : entry);
|
|
8412
|
+
const unchanged = rewritten.length === entries.length && rewritten.every((entry, index) => entry === entries[index]);
|
|
8413
|
+
return unchanged ? void 0 : rewritten;
|
|
8414
|
+
}
|
|
8415
|
+
function withShippedTypes(cwd, files) {
|
|
8416
|
+
const entries = Array.isArray(files) ? files : [];
|
|
8417
|
+
const missing = SHIPPED_TYPES.filter(
|
|
8418
|
+
(declaration) => !entries.some((entry) => entry.endsWith(declaration))
|
|
8419
|
+
);
|
|
8420
|
+
if (missing.length === 0) return void 0;
|
|
8421
|
+
return [...entries, ...missing.map((declaration) => shippedTypesPath(cwd, declaration))];
|
|
8422
|
+
}
|
|
8423
|
+
var SHIPPED_TYPES = ["types/mock-extended.d.ts", "types/jest-global.d.ts"];
|
|
8424
|
+
var JEST_CONFIG_ENTRY = /(^|\/)jest\.[\w.-]*config\.[cm]?[jt]s$/;
|
|
8425
|
+
var TSCONFIG_FILE = /^tsconfig[\w.-]*\.json$/;
|
|
8130
8426
|
function siblingScriptRewrites(cwd) {
|
|
8131
8427
|
const rewrites = {};
|
|
8132
8428
|
for (const [name, command] of Object.entries(moduleScripts(cwd))) {
|
|
@@ -8173,8 +8469,9 @@ function planJestMigration(context, adoption) {
|
|
|
8173
8469
|
`${migration.blockers.length} thing(s) in this module's test files need a human before it can move. Nothing was written, because a config without its files is a suite calling jest.* under Vitest.${listed}`
|
|
8174
8470
|
);
|
|
8175
8471
|
}
|
|
8176
|
-
const
|
|
8177
|
-
const
|
|
8472
|
+
const fromProject = projectTargets(context.cwd) ?? {};
|
|
8473
|
+
const fromManifest = manifestTargets(context.cwd);
|
|
8474
|
+
const existingFor = (name) => fromProject[name] ?? fromManifest[name];
|
|
8178
8475
|
const siblings = siblingScriptRewrites(context.cwd);
|
|
8179
8476
|
const specTypes = specTypesOperation(context.cwd);
|
|
8180
8477
|
const scripts = Object.fromEntries(
|
|
@@ -8191,6 +8488,7 @@ function planJestMigration(context, adoption) {
|
|
|
8191
8488
|
})),
|
|
8192
8489
|
...migration.operations,
|
|
8193
8490
|
...specTypes === void 0 ? [] : [specTypes],
|
|
8491
|
+
...tsconfigFileListOperations(context.cwd),
|
|
8194
8492
|
manifestOperation(context.cwd, { ...scripts, ...siblings }),
|
|
8195
8493
|
...prepared.flatMap(
|
|
8196
8494
|
({ mode }) => nxTargetOperations({
|
|
@@ -8208,18 +8506,26 @@ function planJestMigration(context, adoption) {
|
|
|
8208
8506
|
(name) => ({ kind: "delete", path: name })
|
|
8209
8507
|
)
|
|
8210
8508
|
];
|
|
8509
|
+
const escaping = (prepared[0]?.reading.aliases ?? []).map((alias) => alias.replacement).filter((replacement) => replacement.includes(".."));
|
|
8211
8510
|
const notes = [
|
|
8212
8511
|
`${migration.files} test file(s) migrated from jest to Vitest, ${migration.renamed} site(s) rewritten${migration.hoisted > 0 ? `, ${migration.hoisted} of them moving a mock factory's captures into vi.hoisted` : ""}.`,
|
|
8213
|
-
`${
|
|
8512
|
+
`${prepared.map(({ mode }) => mode.vitestConfig).join(", ")} written from what ${prepared.map(({ mode }) => mode.jestConfig).join(", ")} declared, and that config deleted.` + (escaping.length === 0 ? ` Every specifier in the new one is a package name: nothing in it reaches outside this module.` : ` \u26A0\uFE0F ${escaping.length} alias(es) carried from that config reach OUTSIDE this module: ${escaping.slice(0, 3).join(", ")}. They came from its \`moduleNameMapper\` and are kept as they were, but a module that reaches out cannot be moved on its own.`)
|
|
8214
8513
|
];
|
|
8215
8514
|
if (specTypes !== void 0) {
|
|
8216
8515
|
notes.push(
|
|
8217
8516
|
`${SPEC_TSCONFIG} now declares \`${VITEST_GLOBAL_TYPES}\` instead of \`jest\`. Without it the module keeps depending on @types/jest and \`jest.Mock\` still resolves, so the migration would look finished while the dependency it cuts is still declared.`
|
|
8218
8517
|
);
|
|
8219
8518
|
}
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8519
|
+
const formatter = writtenFileFormatter(context.cwd);
|
|
8520
|
+
if (formatter !== void 0) {
|
|
8521
|
+
notes.push(
|
|
8522
|
+
`the files this rewrote were run through ${formatter.origin} afterwards. Two things needed it: the Vitest type imports land beside the existing ones, and a rewritten call is often SHORTER than the one it replaced, so the line breaks around it are no longer the ones your config would pick. Neither is a defect, and both would have failed your format check. What no formatter does is ORDER imports, so if your lint enforces \`import/order\`, run its fixer too.`
|
|
8523
|
+
);
|
|
8524
|
+
} else {
|
|
8525
|
+
notes.push(
|
|
8526
|
+
`no style is declared here, by this module or its workspace, so what was written keeps the shape it was written in, and the Vitest type imports sit beside the existing ones rather than where your \`import/order\` wants them. That rule is classified by a prefix list this package cannot read, so it is left to the tool that owns it: run your module's own lint fixer (\`nx run <module>:lint --fix\` here) and its formatter before committing.`
|
|
8527
|
+
);
|
|
8528
|
+
}
|
|
8223
8529
|
notes.push(
|
|
8224
8530
|
`then run this module's TYPECHECK, not only its tests. The migration rewrites types as well as calls, and a wrong type is invisible to a green suite: measured on a real module, \`--run --test\` passed while \`--run --typescript\` reported ten errors it had not had before.`
|
|
8225
8531
|
);
|
|
@@ -8308,7 +8614,7 @@ function plan3(context) {
|
|
|
8308
8614
|
`${configFile} now imports ${rewrite.moved.join(", ")} from sentinel. Only the import lines changed: every alias, setup file, timeout and coverage path is untouched, so this suite runs what it ran before.`
|
|
8309
8615
|
);
|
|
8310
8616
|
}
|
|
8311
|
-
const existing = projectTargets(context.cwd)?.[TEST_SCRIPT_NAME];
|
|
8617
|
+
const existing = projectTargets(context.cwd)?.[TEST_SCRIPT_NAME] ?? manifestTargets(context.cwd)[TEST_SCRIPT_NAME];
|
|
8312
8618
|
const outputs = existing?.outputs ?? [];
|
|
8313
8619
|
const dependsOn = existing?.dependsOn ?? [];
|
|
8314
8620
|
const configurations = existing?.configurations ?? {};
|
|
@@ -8363,6 +8669,329 @@ function plan3(context) {
|
|
|
8363
8669
|
return { operations, notes };
|
|
8364
8670
|
}
|
|
8365
8671
|
|
|
8672
|
+
// src/roles/test/reference.ts
|
|
8673
|
+
import { execFileSync } from "node:child_process";
|
|
8674
|
+
import { createHash } from "node:crypto";
|
|
8675
|
+
import { existsSync as existsSync31, mkdirSync, mkdtempSync, readFileSync as readFileSync30, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8676
|
+
import { tmpdir } from "node:os";
|
|
8677
|
+
import { join as join38, resolve as resolve5 } from "node:path";
|
|
8678
|
+
|
|
8679
|
+
// src/roles/test/baseline.ts
|
|
8680
|
+
function readSnapshot(report) {
|
|
8681
|
+
const files = report?.testResults ?? [];
|
|
8682
|
+
const passing = [];
|
|
8683
|
+
const failing = [];
|
|
8684
|
+
const unloadable = [];
|
|
8685
|
+
for (const file of files) {
|
|
8686
|
+
const assertions = file?.assertionResults ?? [];
|
|
8687
|
+
if (file?.status === "failed" && assertions.length === 0) {
|
|
8688
|
+
unloadable.push(typeof file.name === "string" ? file.name : "an unnamed file");
|
|
8689
|
+
}
|
|
8690
|
+
for (const assertion of assertions) {
|
|
8691
|
+
const name = assertion?.fullName;
|
|
8692
|
+
if (typeof name !== "string" || name === "") continue;
|
|
8693
|
+
if (assertion.status === "passed") passing.push(name);
|
|
8694
|
+
else if (assertion.status === "failed") failing.push(name);
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
const claimed = report?.numFailedTests;
|
|
8698
|
+
return {
|
|
8699
|
+
files: files.length,
|
|
8700
|
+
passing: passing.sort(),
|
|
8701
|
+
failing: failing.sort(),
|
|
8702
|
+
unloadable: unloadable.sort(),
|
|
8703
|
+
claimedFailing: typeof claimed === "number" ? claimed : failing.length
|
|
8704
|
+
};
|
|
8705
|
+
}
|
|
8706
|
+
function difference(left, right) {
|
|
8707
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
8708
|
+
for (const name of right) remaining.set(name, (remaining.get(name) ?? 0) + 1);
|
|
8709
|
+
const missing = [];
|
|
8710
|
+
for (const name of left) {
|
|
8711
|
+
const left_ = remaining.get(name) ?? 0;
|
|
8712
|
+
if (left_ === 0) {
|
|
8713
|
+
missing.push(name);
|
|
8714
|
+
continue;
|
|
8715
|
+
}
|
|
8716
|
+
remaining.set(name, left_ - 1);
|
|
8717
|
+
}
|
|
8718
|
+
return missing.sort();
|
|
8719
|
+
}
|
|
8720
|
+
var withoutRenderingQuotes = (name) => name.replaceAll(String.raw`\'`, "'").replaceAll(String.raw`\"`, '"').replaceAll(/['"`]\s*\+\s*['"`]/g, "").replaceAll(/(^|[\s:,[{(])(''|"")($|[\s,)\]}])/g, "$1<empty-string>$3").replaceAll(/['"`]/g, "");
|
|
8721
|
+
function withoutRenderingNoise(name) {
|
|
8722
|
+
const spelled = withSortedKeys(
|
|
8723
|
+
withoutUndefinedEntries(
|
|
8724
|
+
withoutErrorRendering(
|
|
8725
|
+
withoutNaN(
|
|
8726
|
+
withoutTimestamps(withoutRenderingQuotes(withoutClassBodies(withoutStackFrames(name))))
|
|
8727
|
+
)
|
|
8728
|
+
)
|
|
8729
|
+
)
|
|
8730
|
+
);
|
|
8731
|
+
return withoutArrayBrackets(
|
|
8732
|
+
spelled.replaceAll(/\[\s+/g, "[").replaceAll(/\s+\]/g, "]").replaceAll(/\{\s+/g, "{").replaceAll(/\s+\}/g, "}").replaceAll(/(?<=[[{,\s])\+0\b/g, "0").replaceAll(/:\s+/g, ":").replaceAll(/,\s+/g, ",")
|
|
8733
|
+
);
|
|
8734
|
+
}
|
|
8735
|
+
function withoutArrayBrackets(name) {
|
|
8736
|
+
return name.replaceAll(
|
|
8737
|
+
/\[([^[\]]*)\]/g,
|
|
8738
|
+
(match, inner) => (
|
|
8739
|
+
// `[Object]` and `[Array]` are the formatter saying it STOPPED, not an array literal.
|
|
8740
|
+
// Stripping their brackets would erase the marker `collapsedOf` pairs on.
|
|
8741
|
+
/^(?:Object|Array)$/.test(inner) ? match : inner.replaceAll(/,\s+/g, ",")
|
|
8742
|
+
)
|
|
8743
|
+
);
|
|
8744
|
+
}
|
|
8745
|
+
var withoutClassBodies = (name) => name.replaceAll(
|
|
8746
|
+
/class\s+([A-Za-z_$][\w$]*)\s*\{\s*\}/g,
|
|
8747
|
+
(_match, className) => `class ${className}`
|
|
8748
|
+
).replaceAll(
|
|
8749
|
+
/class\s+([A-Za-z_$][\w$]*)[\s\S]*?\n\}/g,
|
|
8750
|
+
(_match, className) => `class ${className}`
|
|
8751
|
+
);
|
|
8752
|
+
var withoutTimestamps = (name) => name.replaceAll(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z/g, "<instant>");
|
|
8753
|
+
var withoutNaN = (name) => name.replaceAll(/\bNaN\b/g, "null");
|
|
8754
|
+
function entriesOf(inner) {
|
|
8755
|
+
const parts = [];
|
|
8756
|
+
let depth = 0;
|
|
8757
|
+
let start = 0;
|
|
8758
|
+
for (let index = 0; index < inner.length; index += 1) {
|
|
8759
|
+
const character = inner[index];
|
|
8760
|
+
if (character === "[" || character === "(") depth += 1;
|
|
8761
|
+
else if (character === "]" || character === ")") depth -= 1;
|
|
8762
|
+
else if (character === "," && depth === 0) {
|
|
8763
|
+
parts.push(inner.slice(start, index));
|
|
8764
|
+
start = index + 1;
|
|
8765
|
+
}
|
|
8766
|
+
if (depth < 0) return void 0;
|
|
8767
|
+
}
|
|
8768
|
+
if (depth !== 0) return void 0;
|
|
8769
|
+
parts.push(inner.slice(start));
|
|
8770
|
+
return parts.map((part) => part.trim()).filter(Boolean);
|
|
8771
|
+
}
|
|
8772
|
+
var sortedGroup = (match, inner) => {
|
|
8773
|
+
if (!inner.includes(":")) return match;
|
|
8774
|
+
const parts = entriesOf(inner);
|
|
8775
|
+
if (parts === void 0) return match;
|
|
8776
|
+
if (parts.length < 2 || !parts.every((part) => part.includes(":"))) return match;
|
|
8777
|
+
return `{${[...parts].sort().join(",")}}`;
|
|
8778
|
+
};
|
|
8779
|
+
var HELD = "\u241F";
|
|
8780
|
+
var withSortedKeys = (name) => {
|
|
8781
|
+
const held = [];
|
|
8782
|
+
let current = name;
|
|
8783
|
+
for (let depth = 0; depth < 24; depth += 1) {
|
|
8784
|
+
const next = current.replaceAll(/\{([^{}]*)\}/g, (match, inner) => {
|
|
8785
|
+
held.push(sortedGroup(match, inner));
|
|
8786
|
+
return `${HELD}${held.length - 1}${HELD}`;
|
|
8787
|
+
});
|
|
8788
|
+
if (next === current) break;
|
|
8789
|
+
current = next;
|
|
8790
|
+
}
|
|
8791
|
+
for (let depth = 0; depth < 24 && current.includes(HELD); depth += 1) {
|
|
8792
|
+
current = current.replaceAll(
|
|
8793
|
+
new RegExp(`${HELD}(\\d+)${HELD}`, "g"),
|
|
8794
|
+
(_match, index) => held[Number(index)]
|
|
8795
|
+
);
|
|
8796
|
+
}
|
|
8797
|
+
return current;
|
|
8798
|
+
};
|
|
8799
|
+
var withoutStackFrames = (name) => {
|
|
8800
|
+
const frame = name.search(/\n\s+at\s/);
|
|
8801
|
+
return frame === -1 ? name : name.slice(0, frame);
|
|
8802
|
+
};
|
|
8803
|
+
var withoutErrorRendering = (name) => name.replaceAll(/\[[A-Za-z]*(?:Error|Exception):[^\][]*\]/g, "{}");
|
|
8804
|
+
var withoutUndefinedEntries = (name) => name.replaceAll(/([{,])\s*[A-Za-z_$][\w$]*\s*:\s*undefined\s*(?=[,}])/g, "$1").replaceAll(/\{\s*,/g, "{").replaceAll(/,\s*\}/g, "}").replaceAll(/\{\s*\}/g, "{}");
|
|
8805
|
+
function collapsedOf(candidate, name) {
|
|
8806
|
+
if (!/\[(?:Object|Array)\]/.test(candidate)) return false;
|
|
8807
|
+
const pattern = candidate.split(/\[(?:Object|Array)\]/).map((part) => part.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
8808
|
+
return new RegExp(`^${pattern}$`, "s").test(name);
|
|
8809
|
+
}
|
|
8810
|
+
function truncationOf(candidate, name) {
|
|
8811
|
+
const cut = candidate.indexOf("\u2026");
|
|
8812
|
+
if (cut === -1) return false;
|
|
8813
|
+
const head = candidate.slice(0, cut);
|
|
8814
|
+
const tail = candidate.slice(cut + 1);
|
|
8815
|
+
return name.startsWith(head) && name.endsWith(tail);
|
|
8816
|
+
}
|
|
8817
|
+
function pairRenamed(missing, added) {
|
|
8818
|
+
const remaining = [...added];
|
|
8819
|
+
const renamed = [];
|
|
8820
|
+
const unmatched = [];
|
|
8821
|
+
for (const name of missing) {
|
|
8822
|
+
const normalised = withoutRenderingNoise(name);
|
|
8823
|
+
const at = remaining.findIndex((candidate) => withoutRenderingNoise(candidate) === normalised);
|
|
8824
|
+
if (at === -1) {
|
|
8825
|
+
unmatched.push(name);
|
|
8826
|
+
continue;
|
|
8827
|
+
}
|
|
8828
|
+
renamed.push({ before: name, after: remaining[at] });
|
|
8829
|
+
remaining.splice(at, 1);
|
|
8830
|
+
}
|
|
8831
|
+
const lost = [];
|
|
8832
|
+
for (const name of unmatched) {
|
|
8833
|
+
const normalised = withoutRenderingNoise(name);
|
|
8834
|
+
const at = remaining.findIndex(
|
|
8835
|
+
(candidate) => truncationOf(withoutRenderingNoise(candidate), normalised) || collapsedOf(normalised, withoutRenderingNoise(candidate))
|
|
8836
|
+
);
|
|
8837
|
+
if (at === -1) {
|
|
8838
|
+
lost.push(name);
|
|
8839
|
+
continue;
|
|
8840
|
+
}
|
|
8841
|
+
renamed.push({ before: name, after: remaining[at] });
|
|
8842
|
+
remaining.splice(at, 1);
|
|
8843
|
+
}
|
|
8844
|
+
return { renamed, missing: lost, added: remaining };
|
|
8845
|
+
}
|
|
8846
|
+
function compareSnapshots(before, after) {
|
|
8847
|
+
const beforeAll = [...before.passing, ...before.failing];
|
|
8848
|
+
const afterAll = [...after.passing, ...after.failing];
|
|
8849
|
+
const paired = pairRenamed(difference(beforeAll, afterAll), difference(afterAll, beforeAll));
|
|
8850
|
+
const renamedBefore = new Set(paired.renamed.map((pair) => pair.before));
|
|
8851
|
+
const regressed = difference(before.passing, after.passing).filter(
|
|
8852
|
+
(name) => !paired.missing.includes(name) && !renamedBefore.has(name)
|
|
8853
|
+
);
|
|
8854
|
+
const failedBefore = new Set(before.failing);
|
|
8855
|
+
const recovered = after.passing.filter((name) => failedBefore.has(name));
|
|
8856
|
+
return {
|
|
8857
|
+
identical: paired.missing.length === 0 && paired.added.length === 0 && regressed.length === 0,
|
|
8858
|
+
missing: paired.missing,
|
|
8859
|
+
added: paired.added,
|
|
8860
|
+
regressed,
|
|
8861
|
+
renamed: paired.renamed,
|
|
8862
|
+
recovered,
|
|
8863
|
+
alreadyFailing: before.failing.length
|
|
8864
|
+
};
|
|
8865
|
+
}
|
|
8866
|
+
function unusableAsBaseline(snapshot) {
|
|
8867
|
+
if (snapshot.unloadable.length > 0) {
|
|
8868
|
+
return `${snapshot.unloadable.length} file(s) in the reference run did not load, so they reported no test at all and the run is short by however many they hold: ${snapshot.unloadable.slice(0, 3).join(", ")}. A reference that quietly lost tests cannot prove anything about the run after it`;
|
|
8869
|
+
}
|
|
8870
|
+
if (snapshot.claimedFailing > snapshot.failing.length) {
|
|
8871
|
+
return `the reference run says ${snapshot.claimedFailing} test(s) failed and names only ${snapshot.failing.length} of them, so the comparison has nothing to pair the rest against and would report them as invented by the migration`;
|
|
8872
|
+
}
|
|
8873
|
+
if (snapshot.passing.length === 0) {
|
|
8874
|
+
return `the reference run reports no passing test at all, so a green run after would prove nothing: there is no claim to preserve`;
|
|
8875
|
+
}
|
|
8876
|
+
return void 0;
|
|
8877
|
+
}
|
|
8878
|
+
function renamedExample(before, after) {
|
|
8879
|
+
let shared = 0;
|
|
8880
|
+
while (shared < before.length && shared < after.length && before[shared] === after[shared]) {
|
|
8881
|
+
shared += 1;
|
|
8882
|
+
}
|
|
8883
|
+
const boundary = before.lastIndexOf(" ", shared);
|
|
8884
|
+
const from = boundary > 0 ? boundary + 1 : 0;
|
|
8885
|
+
const ellipsis = from > 0 ? "\u2026" : "";
|
|
8886
|
+
return `"${ellipsis}${before.slice(from)}" -> "${ellipsis}${after.slice(from)}"`;
|
|
8887
|
+
}
|
|
8888
|
+
function describeComparison(comparison) {
|
|
8889
|
+
const firstRename = comparison.renamed[0];
|
|
8890
|
+
const renamedNote = firstRename === void 0 ? "" : ` ${comparison.renamed.length} test(s) kept their identity and changed name, because the runners render an each title's interpolated value differently (e.g. ${renamedExample(firstRename.before, firstRename.after)}).`;
|
|
8891
|
+
const alreadyRedNote = comparison.alreadyFailing > 0 ? ` ${comparison.alreadyFailing} test(s) were already failing before the migration and are outside this comparison, which covers only what passed.` : "";
|
|
8892
|
+
const recoveredNote = comparison.recovered.length > 0 ? ` ${comparison.recovered.length} test(s) were failing before and pass now.` : "";
|
|
8893
|
+
if (comparison.identical) {
|
|
8894
|
+
return `every test that ran before runs and passes now.${recoveredNote}${renamedNote}${alreadyRedNote}`;
|
|
8895
|
+
}
|
|
8896
|
+
const parts = [];
|
|
8897
|
+
if (comparison.recovered.length > 0) {
|
|
8898
|
+
parts.push(`${comparison.recovered.length} test(s) were failing before and pass now`);
|
|
8899
|
+
}
|
|
8900
|
+
if (comparison.missing.length > 0) {
|
|
8901
|
+
parts.push(
|
|
8902
|
+
`${comparison.missing.length} test(s) no longer exist: ${comparison.missing.slice(0, 3).join(", ")}`
|
|
8903
|
+
);
|
|
8904
|
+
}
|
|
8905
|
+
if (comparison.added.length > 0) {
|
|
8906
|
+
parts.push(
|
|
8907
|
+
`${comparison.added.length} test(s) appeared, which a migration should not do: ${comparison.added.slice(0, 3).join(", ")}`
|
|
8908
|
+
);
|
|
8909
|
+
}
|
|
8910
|
+
if (comparison.regressed.length > 0) {
|
|
8911
|
+
parts.push(
|
|
8912
|
+
`${comparison.regressed.length} test(s) passed before and do not now: ${comparison.regressed.slice(0, 3).join(", ")}`
|
|
8913
|
+
);
|
|
8914
|
+
}
|
|
8915
|
+
return `${parts.join("; ")}.${renamedNote}${alreadyRedNote}`;
|
|
8916
|
+
}
|
|
8917
|
+
|
|
8918
|
+
// src/roles/test/reference.ts
|
|
8919
|
+
function referencePath(cwd) {
|
|
8920
|
+
const key = createHash("sha256").update(resolve5(cwd)).digest("hex").slice(0, 16);
|
|
8921
|
+
return join38(tmpdir(), "sentinel-test-reference", `${key}.json`);
|
|
8922
|
+
}
|
|
8923
|
+
function runJest(cwd, config) {
|
|
8924
|
+
const bin = resolveBin(cwd, "jest");
|
|
8925
|
+
if (bin === void 0) return void 0;
|
|
8926
|
+
const root = findWorkspaceRoot(cwd) ?? cwd;
|
|
8927
|
+
const out = join38(mkdtempSync(join38(tmpdir(), "sentinel-reference-")), "jest.json");
|
|
8928
|
+
try {
|
|
8929
|
+
execFileSync(bin, ["-c", join38(cwd, config), "--json", `--outputFile=${out}`, "--silent"], {
|
|
8930
|
+
cwd: root,
|
|
8931
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
8932
|
+
maxBuffer: 1 << 28
|
|
8933
|
+
});
|
|
8934
|
+
} catch {
|
|
8935
|
+
}
|
|
8936
|
+
if (!existsSync31(out)) return void 0;
|
|
8937
|
+
try {
|
|
8938
|
+
return JSON.parse(readFileSync30(out, "utf8"));
|
|
8939
|
+
} catch {
|
|
8940
|
+
return void 0;
|
|
8941
|
+
}
|
|
8942
|
+
}
|
|
8943
|
+
function captureReference(cwd, config) {
|
|
8944
|
+
const report = runJest(cwd, config);
|
|
8945
|
+
if (report === void 0) {
|
|
8946
|
+
return {
|
|
8947
|
+
captured: false,
|
|
8948
|
+
message: `could not run jest to record a reference, so this migration cannot be PROVED. It is still applied: a module whose jest run is broken can move, it just cannot be compared.`
|
|
8949
|
+
};
|
|
8950
|
+
}
|
|
8951
|
+
const snapshot = readSnapshot(report);
|
|
8952
|
+
const unusable = unusableAsBaseline(snapshot);
|
|
8953
|
+
if (unusable !== void 0) {
|
|
8954
|
+
return {
|
|
8955
|
+
captured: false,
|
|
8956
|
+
message: `no reference recorded: ${unusable} The migration is applied anyway, unproved.`
|
|
8957
|
+
};
|
|
8958
|
+
}
|
|
8959
|
+
const stored = {
|
|
8960
|
+
runner: "jest",
|
|
8961
|
+
config,
|
|
8962
|
+
takenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8963
|
+
snapshot
|
|
8964
|
+
};
|
|
8965
|
+
const path = referencePath(cwd);
|
|
8966
|
+
mkdirSync(join38(path, ".."), { recursive: true });
|
|
8967
|
+
writeFileSync3(path, `${JSON.stringify(stored, void 0, 2)}
|
|
8968
|
+
`);
|
|
8969
|
+
return {
|
|
8970
|
+
captured: true,
|
|
8971
|
+
message: `recorded ${snapshot.passing.length} passing test name(s) from ${config} as the reference. The next \`sentinel --run --test\` compares against it.`
|
|
8972
|
+
};
|
|
8973
|
+
}
|
|
8974
|
+
function hasReference(cwd) {
|
|
8975
|
+
return existsSync31(referencePath(cwd));
|
|
8976
|
+
}
|
|
8977
|
+
function compareAgainstReference(cwd, report) {
|
|
8978
|
+
const path = referencePath(cwd);
|
|
8979
|
+
let stored;
|
|
8980
|
+
try {
|
|
8981
|
+
stored = JSON.parse(readFileSync30(path, "utf8"));
|
|
8982
|
+
} catch {
|
|
8983
|
+
rmSync3(path, { force: true });
|
|
8984
|
+
return { ok: true, message: `the recorded reference could not be read, so nothing was proved.` };
|
|
8985
|
+
}
|
|
8986
|
+
const comparison = compareSnapshots(stored.snapshot, readSnapshot(report));
|
|
8987
|
+
if (comparison.identical) rmSync3(path, { force: true });
|
|
8988
|
+
const kept = comparison.identical ? "" : ` The reference is KEPT until a run passes it, so this does not go green by running again. Fix the suite and re-run, or delete ${path} to abandon the proof on purpose.`;
|
|
8989
|
+
return {
|
|
8990
|
+
ok: comparison.identical,
|
|
8991
|
+
message: `${describeComparison(comparison)} (reference: ${stored.config}, ${stored.takenAt})${kept}`
|
|
8992
|
+
};
|
|
8993
|
+
}
|
|
8994
|
+
|
|
8366
8995
|
// src/roles/test/adapters/vitest/vitest.adapter.ts
|
|
8367
8996
|
function resolveVitest(cwd) {
|
|
8368
8997
|
const fromSentinel = binFromOwnInstall("vitest", "vitest");
|
|
@@ -8370,12 +8999,102 @@ function resolveVitest(cwd) {
|
|
|
8370
8999
|
const fromModule = resolveBin(cwd, "vitest");
|
|
8371
9000
|
return fromModule !== void 0 ? { bin: fromModule, origin: "module" } : { bin: void 0, origin: "module" };
|
|
8372
9001
|
}
|
|
9002
|
+
var FORMATTABLE = /\.(m|c)?(j|t)sx?$|\.json$/;
|
|
9003
|
+
var FORMAT_BATCH = 25;
|
|
8373
9004
|
var VitestAdapter = class extends BaseAdapter {
|
|
8374
9005
|
target = "test";
|
|
8375
9006
|
runner = "vitest";
|
|
8376
9007
|
appliesTo(_preset) {
|
|
8377
9008
|
return true;
|
|
8378
9009
|
}
|
|
9010
|
+
/**
|
|
9011
|
+
* Record what this module's tests do TODAY, at the last moment it is still true.
|
|
9012
|
+
*
|
|
9013
|
+
* The plan about to be applied deletes the jest config, and with it any way of asking again. A
|
|
9014
|
+
* migration that cannot be compared to what came before is a migration nobody can check, which
|
|
9015
|
+
* is the whole reason this role has a baseline gate at all.
|
|
9016
|
+
*/
|
|
9017
|
+
async beforeInit(ctx) {
|
|
9018
|
+
const adoption = readTestAdoption(ctx.cwd);
|
|
9019
|
+
if (adoption.state !== "jest") return Promise.resolve();
|
|
9020
|
+
const [config] = adoption.jestConfigs;
|
|
9021
|
+
if (config === void 0) return Promise.resolve();
|
|
9022
|
+
this.say(`recording the jest reference before migrating...`);
|
|
9023
|
+
this.say(captureReference(ctx.cwd, config).message);
|
|
9024
|
+
return Promise.resolve();
|
|
9025
|
+
}
|
|
9026
|
+
/**
|
|
9027
|
+
* Hand what was just written to the module's OWN formatter.
|
|
9028
|
+
*
|
|
9029
|
+
* ⚠️ Not a style sentinel imposes: the module adopted `.oxfmtrc.json`, and this runs that
|
|
9030
|
+
* config over the files this role created. Without it, `--init --test` leaves a module whose
|
|
9031
|
+
* own `--run --format` fails on a file the module never wrote.
|
|
9032
|
+
*
|
|
9033
|
+
* Measured on `apps/nest/microservices/agency`, in this repository's CI:
|
|
9034
|
+
*
|
|
9035
|
+
* agency:format 12 files, including vitest.config.mts and
|
|
9036
|
+
* vitest.integration.config.mts, both generated here
|
|
9037
|
+
*
|
|
9038
|
+
* The generator cannot avoid it by wrapping at a fixed width, because the width is the
|
|
9039
|
+
* MODULE's decision: `printWidth` lives in its own config, and 12 modules already set their
|
|
9040
|
+
* own. Guessing it is how a tool starts owning a setting it does not own.
|
|
9041
|
+
*
|
|
9042
|
+
* The same delegation the build role asks a human to do by hand, done.
|
|
9043
|
+
*
|
|
9044
|
+
* ## ⚠️ Two boundaries, both learned by crossing them
|
|
9045
|
+
*
|
|
9046
|
+
* ### Only a module that has a formatter CONFIG
|
|
9047
|
+
*
|
|
9048
|
+
* The question is not "can a formatter be found". It always can: sentinel depends on oxfmt and
|
|
9049
|
+
* ships the binary, so `resolveOxfmt` falls back to its own install and never returns undefined
|
|
9050
|
+
* once sentinel is installed. Asking it that way meant a module with no oxfmt config was
|
|
9051
|
+
* formatted with oxfmt's DEFAULTS, which is a style nobody here chose.
|
|
9052
|
+
*
|
|
9053
|
+
* So the CONFIG is read. A module that wrote its own `.oxfmtrc.json` without adopting the role
|
|
9054
|
+
* has still stated a style, and running it is delegation; running oxfmt with no config is us
|
|
9055
|
+
* choosing one.
|
|
9056
|
+
*
|
|
9057
|
+
* Measured on `apps/nest/microservices/institution`, which has not adopted format and whose lint
|
|
9058
|
+
* forbids semicolons: `institution:lint` went from green to failing, thousands of `semi`, on a
|
|
9059
|
+
* module whose 2958 tests pass. So the adoption is read, not the binary.
|
|
9060
|
+
*
|
|
9061
|
+
* ### Only the files this just WROTE
|
|
9062
|
+
*
|
|
9063
|
+
* `--write .` is a whole-directory pass, and that is not a tidy-up. Same measurement: 1813 files
|
|
9064
|
+
* changed, 47333 insertions, 55506 deletions, in a module whose migration touched 268. A
|
|
9065
|
+
* migration diff is read by a person.
|
|
9066
|
+
*
|
|
9067
|
+
* A module that has not adopted format keeps what was written exactly as written, and is told so.
|
|
9068
|
+
* The codemod rewrites tokens in place, so those files keep the shape they already had.
|
|
9069
|
+
*/
|
|
9070
|
+
async afterInit(ctx) {
|
|
9071
|
+
const written = (ctx.written ?? []).filter((file) => FORMATTABLE.test(file));
|
|
9072
|
+
if (written.length === 0) return Promise.resolve();
|
|
9073
|
+
const formatter = writtenFileFormatter(ctx.cwd);
|
|
9074
|
+
if (formatter === void 0) {
|
|
9075
|
+
this.say(
|
|
9076
|
+
`no style is declared here, by this module or its workspace, so the ${written.length} file(s) this wrote keep the shape they were written in. Run your own formatter over them before committing.`
|
|
9077
|
+
);
|
|
9078
|
+
return Promise.resolve();
|
|
9079
|
+
}
|
|
9080
|
+
let failed = 0;
|
|
9081
|
+
for (let at = 0; at < written.length; at += FORMAT_BATCH) {
|
|
9082
|
+
const batch = written.slice(at, at + FORMAT_BATCH);
|
|
9083
|
+
const pass = spawnSync5(formatter.bin, [...formatter.args, ...batch], {
|
|
9084
|
+
cwd: ctx.cwd,
|
|
9085
|
+
encoding: "utf8"
|
|
9086
|
+
});
|
|
9087
|
+
if (pass.status !== 0) failed += batch.length;
|
|
9088
|
+
}
|
|
9089
|
+
this.say(
|
|
9090
|
+
failed === 0 ? `formatted the ${written.length} file(s) this wrote with ${formatter.origin}, so a format check passes on files it did not write itself.` : (
|
|
9091
|
+
// Adoption SUCCEEDED; only the tidy-up did not. Failing here would revert a migration
|
|
9092
|
+
// over a formatting pass, which is the wrong trade.
|
|
9093
|
+
`${formatter.origin} could not run over ${failed} of the ${written.length} file(s) this wrote. Adoption itself is applied; run your formatter before committing.`
|
|
9094
|
+
)
|
|
9095
|
+
);
|
|
9096
|
+
return Promise.resolve();
|
|
9097
|
+
}
|
|
8379
9098
|
plan(context) {
|
|
8380
9099
|
return plan3(context);
|
|
8381
9100
|
}
|
|
@@ -8420,15 +9139,36 @@ var VitestAdapter = class extends BaseAdapter {
|
|
|
8420
9139
|
`using the MODULE's vitest, not sentinel's. An adopted config gets its plugins from sentinel, and two copies fail in ways that never mention a version. Remove vitest from this module's package.json.`
|
|
8421
9140
|
);
|
|
8422
9141
|
}
|
|
8423
|
-
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
9142
|
+
const { options: typedOptions, paths: typedPaths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
9143
|
+
valueFlags: ["--config", "-c"]
|
|
9144
|
+
});
|
|
9145
|
+
const { cwd: runFrom, options, paths } = fromWorkspaceRoot(ctx.cwd, typedOptions, typedPaths);
|
|
8424
9146
|
const watching = options.includes("--watch") || options.includes("-w");
|
|
8425
|
-
const
|
|
8426
|
-
const
|
|
9147
|
+
const proving = !watching && hasReference(ctx.cwd);
|
|
9148
|
+
const reportFile = proving ? join39(mkdtempSync2(join39(tmpdir2(), "sentinel-run-")), "vitest.json") : void 0;
|
|
9149
|
+
const reporters = reportFile === void 0 ? [] : ["--reporter=default", "--reporter=json", `--outputFile=${reportFile}`];
|
|
9150
|
+
const argv = watching ? [...options, ...paths] : ["run", ...reporters, ...options, ...paths];
|
|
9151
|
+
const result = spawnSync5(bin, argv, { cwd: runFrom, stdio: "inherit" });
|
|
8427
9152
|
if (result.error) {
|
|
8428
9153
|
this.say(`could not run vitest (${result.error.message})`);
|
|
8429
9154
|
return Promise.resolve({ ok: false, code: 1 });
|
|
8430
9155
|
}
|
|
8431
9156
|
const code = result.status ?? 1;
|
|
9157
|
+
if (reportFile !== void 0) {
|
|
9158
|
+
let report;
|
|
9159
|
+
try {
|
|
9160
|
+
report = JSON.parse(readFileSync31(reportFile, "utf8"));
|
|
9161
|
+
} catch {
|
|
9162
|
+
report = void 0;
|
|
9163
|
+
}
|
|
9164
|
+
if (report === void 0) {
|
|
9165
|
+
this.say(`vitest wrote no report, so this run proved nothing against the reference.`);
|
|
9166
|
+
} else {
|
|
9167
|
+
const outcome = compareAgainstReference(ctx.cwd, report);
|
|
9168
|
+
this.say(outcome.message);
|
|
9169
|
+
if (!outcome.ok) return Promise.resolve({ ok: false, code: code === 0 ? 1 : code });
|
|
9170
|
+
}
|
|
9171
|
+
}
|
|
8432
9172
|
return Promise.resolve({ ok: code === 0, code });
|
|
8433
9173
|
}
|
|
8434
9174
|
say(message) {
|
|
@@ -8436,6 +9176,31 @@ var VitestAdapter = class extends BaseAdapter {
|
|
|
8436
9176
|
`);
|
|
8437
9177
|
}
|
|
8438
9178
|
};
|
|
9179
|
+
function fromWorkspaceRoot(moduleDir, options, paths) {
|
|
9180
|
+
const root = findWorkspaceRoot(moduleDir);
|
|
9181
|
+
if (root === void 0 || root === moduleDir) {
|
|
9182
|
+
return { cwd: moduleDir, options: [...options], paths: [...paths] };
|
|
9183
|
+
}
|
|
9184
|
+
const absolute = (value) => isAbsolute3(value) ? value : resolve6(moduleDir, value);
|
|
9185
|
+
const moved = [];
|
|
9186
|
+
let previousWasConfig = false;
|
|
9187
|
+
for (const option of options) {
|
|
9188
|
+
if (previousWasConfig) {
|
|
9189
|
+
moved.push(absolute(option));
|
|
9190
|
+
previousWasConfig = false;
|
|
9191
|
+
continue;
|
|
9192
|
+
}
|
|
9193
|
+
if (option.startsWith("--config=")) moved.push(`--config=${absolute(option.slice(9))}`);
|
|
9194
|
+
else moved.push(option);
|
|
9195
|
+
previousWasConfig = option === "--config" || option === "-c";
|
|
9196
|
+
}
|
|
9197
|
+
return {
|
|
9198
|
+
cwd: root,
|
|
9199
|
+
// `--root` LAST, so a caller cannot be silently overridden by our own flag.
|
|
9200
|
+
options: [...moved, "--root", moduleDir],
|
|
9201
|
+
paths: paths.map((path) => absolute(path))
|
|
9202
|
+
};
|
|
9203
|
+
}
|
|
8439
9204
|
|
|
8440
9205
|
// src/roles/test/register.ts
|
|
8441
9206
|
function registerTest() {
|
|
@@ -8444,10 +9209,10 @@ function registerTest() {
|
|
|
8444
9209
|
}
|
|
8445
9210
|
|
|
8446
9211
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
8447
|
-
import { spawnSync as spawnSync6 } from "child_process";
|
|
8448
|
-
import { existsSync as
|
|
8449
|
-
import { createRequire as
|
|
8450
|
-
import { join as
|
|
9212
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
9213
|
+
import { existsSync as existsSync32, readFileSync as readFileSync33 } from "node:fs";
|
|
9214
|
+
import { createRequire as createRequire7 } from "node:module";
|
|
9215
|
+
import { join as join41 } from "node:path";
|
|
8451
9216
|
|
|
8452
9217
|
// src/roles/typescript/presets/nest.json
|
|
8453
9218
|
var nest_default2 = {
|
|
@@ -8628,8 +9393,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
8628
9393
|
}
|
|
8629
9394
|
|
|
8630
9395
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
8631
|
-
import { readFileSync as
|
|
8632
|
-
import { join as
|
|
9396
|
+
import { readFileSync as readFileSync32 } from "node:fs";
|
|
9397
|
+
import { join as join40 } from "node:path";
|
|
8633
9398
|
|
|
8634
9399
|
// src/roles/typescript/typecheck-script.ts
|
|
8635
9400
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -8722,7 +9487,7 @@ function planAdoption(context) {
|
|
|
8722
9487
|
};
|
|
8723
9488
|
}
|
|
8724
9489
|
const existing = parseJsonc(
|
|
8725
|
-
|
|
9490
|
+
readFileSync32(join40(context.cwd, target.path), "utf8"),
|
|
8726
9491
|
target.path
|
|
8727
9492
|
);
|
|
8728
9493
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -8860,7 +9625,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
8860
9625
|
let chain;
|
|
8861
9626
|
try {
|
|
8862
9627
|
const parsed = parseJsonc(
|
|
8863
|
-
|
|
9628
|
+
readFileSync33(join41(cwd, target.path), "utf8"),
|
|
8864
9629
|
target.path
|
|
8865
9630
|
);
|
|
8866
9631
|
chain = parsed.extends;
|
|
@@ -8873,7 +9638,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
8873
9638
|
);
|
|
8874
9639
|
if (preset === void 0) return void 0;
|
|
8875
9640
|
try {
|
|
8876
|
-
|
|
9641
|
+
createRequire7(join41(cwd, "noop.js")).resolve(preset);
|
|
8877
9642
|
return void 0;
|
|
8878
9643
|
} catch {
|
|
8879
9644
|
return preset;
|
|
@@ -8969,7 +9734,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
8969
9734
|
referencedProjects(cwd, config) {
|
|
8970
9735
|
try {
|
|
8971
9736
|
const parsed = parseJsonc(
|
|
8972
|
-
|
|
9737
|
+
readFileSync33(join41(cwd, config), "utf8"),
|
|
8973
9738
|
config
|
|
8974
9739
|
);
|
|
8975
9740
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
|
|
@@ -8985,7 +9750,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
8985
9750
|
* check.
|
|
8986
9751
|
*/
|
|
8987
9752
|
typecheckTarget(cwd) {
|
|
8988
|
-
if (
|
|
9753
|
+
if (existsSync32(join41(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
8989
9754
|
const target = resolveTsconfigTarget(cwd);
|
|
8990
9755
|
return target.reason === "none" ? null : target.path;
|
|
8991
9756
|
}
|
|
@@ -9134,7 +9899,7 @@ function detectFramework(packageJson) {
|
|
|
9134
9899
|
}
|
|
9135
9900
|
|
|
9136
9901
|
// src/core/dispatch.ts
|
|
9137
|
-
import { basename as basename3 } from "path";
|
|
9902
|
+
import { basename as basename3 } from "node:path";
|
|
9138
9903
|
|
|
9139
9904
|
// src/shared/text.ts
|
|
9140
9905
|
function ensureLines(current, lines) {
|
|
@@ -9190,8 +9955,8 @@ function replaceLines(current, replacements) {
|
|
|
9190
9955
|
}
|
|
9191
9956
|
|
|
9192
9957
|
// src/core/apply-plan.ts
|
|
9193
|
-
import { existsSync as
|
|
9194
|
-
import { resolve as
|
|
9958
|
+
import { existsSync as existsSync33, readFileSync as readFileSync34, renameSync, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9959
|
+
import { resolve as resolve7, sep } from "node:path";
|
|
9195
9960
|
import { applyEdits as applyEdits3, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
9196
9961
|
|
|
9197
9962
|
// src/shared/deep-merge.ts
|
|
@@ -9201,15 +9966,15 @@ function isPlainObject(value) {
|
|
|
9201
9966
|
|
|
9202
9967
|
// src/core/apply-plan.ts
|
|
9203
9968
|
function resolveWithinRoot(cwd, relativePath) {
|
|
9204
|
-
const root =
|
|
9205
|
-
const absolutePath =
|
|
9969
|
+
const root = resolve7(cwd);
|
|
9970
|
+
const absolutePath = resolve7(root, relativePath);
|
|
9206
9971
|
if (absolutePath !== root && !absolutePath.startsWith(root + sep)) {
|
|
9207
9972
|
throw new Error(`Refusing to write outside the module root: "${relativePath}".`);
|
|
9208
9973
|
}
|
|
9209
9974
|
return absolutePath;
|
|
9210
9975
|
}
|
|
9211
9976
|
function readIfExists(absolutePath) {
|
|
9212
|
-
return
|
|
9977
|
+
return existsSync33(absolutePath) ? readFileSync34(absolutePath, "utf8") : void 0;
|
|
9213
9978
|
}
|
|
9214
9979
|
function* leaves(value, prefix = []) {
|
|
9215
9980
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -9311,14 +10076,14 @@ function preparePlan(cwd, plan4) {
|
|
|
9311
10076
|
}
|
|
9312
10077
|
function writeFileAtomic(absolutePath, contents) {
|
|
9313
10078
|
const tempPath = `${absolutePath}.sentinel-${process.pid}.tmp`;
|
|
9314
|
-
|
|
10079
|
+
writeFileSync4(tempPath, contents);
|
|
9315
10080
|
renameSync(tempPath, absolutePath);
|
|
9316
10081
|
}
|
|
9317
10082
|
function applyPlan(cwd, plan4) {
|
|
9318
10083
|
const changed = preparePlan(cwd, plan4).filter((file) => file.before !== file.after);
|
|
9319
10084
|
for (const file of changed) {
|
|
9320
10085
|
if (file.deleted) {
|
|
9321
|
-
|
|
10086
|
+
rmSync4(file.absolutePath, { force: true });
|
|
9322
10087
|
continue;
|
|
9323
10088
|
}
|
|
9324
10089
|
writeFileAtomic(file.absolutePath, file.after);
|
|
@@ -9327,8 +10092,8 @@ function applyPlan(cwd, plan4) {
|
|
|
9327
10092
|
}
|
|
9328
10093
|
|
|
9329
10094
|
// src/core/config/preset-evidence.ts
|
|
9330
|
-
import { existsSync as
|
|
9331
|
-
import { join as
|
|
10095
|
+
import { existsSync as existsSync34, readdirSync as readdirSync9, readFileSync as readFileSync35 } from "node:fs";
|
|
10096
|
+
import { join as join42 } from "node:path";
|
|
9332
10097
|
var PATH_SIGNALS = [
|
|
9333
10098
|
{
|
|
9334
10099
|
preset: "nest",
|
|
@@ -9356,18 +10121,18 @@ function sourceImports(cwd, pattern) {
|
|
|
9356
10121
|
const dir = stack.pop();
|
|
9357
10122
|
let entries;
|
|
9358
10123
|
try {
|
|
9359
|
-
entries =
|
|
10124
|
+
entries = readdirSync9(dir, { withFileTypes: true });
|
|
9360
10125
|
} catch {
|
|
9361
10126
|
continue;
|
|
9362
10127
|
}
|
|
9363
10128
|
for (const entry of entries) {
|
|
9364
10129
|
if (entry.isDirectory()) {
|
|
9365
|
-
if (!NOT_SCANNED.has(entry.name)) stack.push(
|
|
10130
|
+
if (!NOT_SCANNED.has(entry.name)) stack.push(join42(dir, entry.name));
|
|
9366
10131
|
continue;
|
|
9367
10132
|
}
|
|
9368
10133
|
if (!SOURCE_FILE2.test(entry.name)) continue;
|
|
9369
10134
|
try {
|
|
9370
|
-
if (pattern.test(
|
|
10135
|
+
if (pattern.test(readFileSync35(join42(dir, entry.name), "utf8"))) return true;
|
|
9371
10136
|
} catch {
|
|
9372
10137
|
continue;
|
|
9373
10138
|
}
|
|
@@ -9376,11 +10141,11 @@ function sourceImports(cwd, pattern) {
|
|
|
9376
10141
|
return false;
|
|
9377
10142
|
}
|
|
9378
10143
|
function dependencyNames(cwd) {
|
|
9379
|
-
const path =
|
|
9380
|
-
if (!
|
|
10144
|
+
const path = join42(cwd, "package.json");
|
|
10145
|
+
if (!existsSync34(path)) return [];
|
|
9381
10146
|
try {
|
|
9382
10147
|
const manifest = parseJsonc(
|
|
9383
|
-
|
|
10148
|
+
readFileSync35(path, "utf8"),
|
|
9384
10149
|
path
|
|
9385
10150
|
);
|
|
9386
10151
|
return [
|
|
@@ -9394,7 +10159,7 @@ function dependencyNames(cwd) {
|
|
|
9394
10159
|
function declaresJsx(cwd) {
|
|
9395
10160
|
let entries;
|
|
9396
10161
|
try {
|
|
9397
|
-
entries =
|
|
10162
|
+
entries = readdirSync9(cwd).filter(
|
|
9398
10163
|
(name) => name.startsWith("tsconfig") && name.endsWith(".json")
|
|
9399
10164
|
);
|
|
9400
10165
|
} catch {
|
|
@@ -9403,7 +10168,7 @@ function declaresJsx(cwd) {
|
|
|
9403
10168
|
for (const name of entries) {
|
|
9404
10169
|
try {
|
|
9405
10170
|
const config = parseJsonc(
|
|
9406
|
-
|
|
10171
|
+
readFileSync35(join42(cwd, name), "utf8"),
|
|
9407
10172
|
name
|
|
9408
10173
|
);
|
|
9409
10174
|
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
@@ -9548,9 +10313,20 @@ async function dispatch(opts) {
|
|
|
9548
10313
|
if (opts.dryRun) {
|
|
9549
10314
|
return previewPlan(opts, plan4);
|
|
9550
10315
|
}
|
|
10316
|
+
if (adapter.beforeInit) {
|
|
10317
|
+
await adapter.beforeInit({
|
|
10318
|
+
module: basename3(opts.cwd),
|
|
10319
|
+
cwd: opts.cwd,
|
|
10320
|
+
preset,
|
|
10321
|
+
ci: false,
|
|
10322
|
+
fix: false
|
|
10323
|
+
});
|
|
10324
|
+
}
|
|
10325
|
+
const written = [];
|
|
9551
10326
|
for (const change of applyPlan(opts.cwd, plan4)) {
|
|
9552
10327
|
process.stderr.write(` ${change.deleted ? "removed" : "wrote"} ${change.path}
|
|
9553
10328
|
`);
|
|
10329
|
+
if (!change.deleted) written.push(change.path);
|
|
9554
10330
|
}
|
|
9555
10331
|
for (const note of plan4.notes ?? []) process.stderr.write(` ${note}
|
|
9556
10332
|
`);
|
|
@@ -9562,7 +10338,8 @@ async function dispatch(opts) {
|
|
|
9562
10338
|
cwd: opts.cwd,
|
|
9563
10339
|
preset,
|
|
9564
10340
|
ci: false,
|
|
9565
|
-
fix: true
|
|
10341
|
+
fix: true,
|
|
10342
|
+
written
|
|
9566
10343
|
});
|
|
9567
10344
|
}
|
|
9568
10345
|
return 0;
|
|
@@ -9591,4 +10368,3 @@ export {
|
|
|
9591
10368
|
detectFramework,
|
|
9592
10369
|
dispatch
|
|
9593
10370
|
};
|
|
9594
|
-
//# sourceMappingURL=chunk-4UIZJ3TR.js.map
|