@hublo/sentinel 1.2.0 → 1.4.0-alpha.1
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/README.md +2 -0
- package/dist/bin/sentinel.d.ts +0 -1
- package/dist/bin/sentinel.js +28 -9
- package/dist/chunk-2XLX6PFR.js +132 -0
- package/dist/chunk-3TDUIKVQ.js +178 -0
- package/dist/{chunk-Z5I6SEKF.js → chunk-O3XCQGTJ.js} +2880 -520
- package/dist/chunk-PWV3BMDA.js +15 -0
- package/dist/chunk-WLFE5RUU.js +264 -0
- package/dist/index.js +2 -1
- package/dist/roles/build/nest/toolchain.d.ts +4 -36
- package/dist/roles/build/nest/toolchain.js +10 -178
- package/dist/roles/build/nest/toolchain.js.map +1 -0
- package/dist/roles/build/toolchain.js.map +1 -0
- package/dist/roles/test/nest/toolchain.d.ts +29 -0
- package/dist/roles/test/nest/toolchain.js +224 -0
- package/dist/roles/test/nest/toolchain.js.map +1 -0
- package/dist/roles/test/react/toolchain.d.ts +62 -0
- package/dist/roles/test/react/toolchain.js +22 -0
- package/dist/roles/test/react/toolchain.js.map +1 -0
- package/dist/roles/test/setup/mock-extended.d.ts +46 -0
- package/dist/roles/test/setup/mock-extended.js +65 -0
- package/dist/roles/test/setup/mock-extended.js.map +1 -0
- package/dist/roles/test/setup/msw-lifecycle.d.ts +16 -0
- package/dist/roles/test/setup/msw-lifecycle.js +12 -0
- package/dist/roles/test/setup/msw-lifecycle.js.map +1 -0
- package/dist/roles/test/setup/msw-server.d.ts +3 -0
- package/dist/roles/test/setup/msw-server.js +10 -0
- package/dist/roles/test/setup/msw-server.js.map +1 -0
- package/dist/roles/test/setup/nest.d.ts +2 -0
- package/dist/roles/test/setup/nest.js +211 -0
- package/dist/roles/test/setup/nest.js.map +1 -0
- package/dist/tsconfig-aliases-Ce6axdJ4.d.ts +36 -0
- package/package.json +24 -3
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_MAX_DIAGNOSTICS,
|
|
3
|
+
findWorkspaceRoot,
|
|
4
|
+
moduleName,
|
|
5
|
+
readNxProjectName,
|
|
6
|
+
readOwnPackage,
|
|
7
|
+
readProjectPackageJson
|
|
8
|
+
} from "./chunk-WLFE5RUU.js";
|
|
9
|
+
|
|
1
10
|
// src/core/registry.ts
|
|
2
11
|
var adapters = [];
|
|
3
12
|
var PresetUnsupportedError = class extends Error {
|
|
@@ -67,58 +76,6 @@ function resolve(target, preset, runner) {
|
|
|
67
76
|
return matching[0];
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
// src/shared/package-json.ts
|
|
71
|
-
import { existsSync, readFileSync } from "fs";
|
|
72
|
-
import { basename, dirname, join } from "path";
|
|
73
|
-
import { fileURLToPath } from "url";
|
|
74
|
-
function readOwnPackage() {
|
|
75
|
-
let dir = dirname(fileURLToPath(import.meta.url));
|
|
76
|
-
for (; ; ) {
|
|
77
|
-
const pkgPath = join(dir, "package.json");
|
|
78
|
-
if (existsSync(pkgPath)) {
|
|
79
|
-
try {
|
|
80
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
81
|
-
if (typeof pkg.version === "string") {
|
|
82
|
-
const bin = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin ?? {})[0];
|
|
83
|
-
return { name: pkg.name ?? "@hublo/sentinel", version: pkg.version, bin };
|
|
84
|
-
}
|
|
85
|
-
} catch {
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
const parent = dirname(dir);
|
|
89
|
-
if (parent === dir) return { name: "@hublo/sentinel", version: "0.0.0" };
|
|
90
|
-
dir = parent;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function readOwnVersion() {
|
|
94
|
-
return readOwnPackage().version;
|
|
95
|
-
}
|
|
96
|
-
function readProjectPackageJson(dir) {
|
|
97
|
-
const path = join(dir, "package.json");
|
|
98
|
-
if (!existsSync(path)) return {};
|
|
99
|
-
try {
|
|
100
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
101
|
-
} catch {
|
|
102
|
-
process.stderr.write(`sentinel: could not parse ${path}; ignoring for detection.
|
|
103
|
-
`);
|
|
104
|
-
return {};
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function readNxProjectName(dir) {
|
|
108
|
-
const path = join(dir, "project.json");
|
|
109
|
-
if (!existsSync(path)) return void 0;
|
|
110
|
-
try {
|
|
111
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
112
|
-
return typeof parsed.name === "string" ? parsed.name : void 0;
|
|
113
|
-
} catch {
|
|
114
|
-
return void 0;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
function moduleName(dir) {
|
|
118
|
-
const declared = readProjectPackageJson(dir).name;
|
|
119
|
-
return readNxProjectName(dir) ?? (declared === void 0 ? basename(dir) : declared);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
79
|
// src/core/base-adapter.ts
|
|
123
80
|
var BaseAdapter = class {
|
|
124
81
|
inspect(_ctx) {
|
|
@@ -166,15 +123,15 @@ function palette(stream) {
|
|
|
166
123
|
}
|
|
167
124
|
|
|
168
125
|
// src/roles/build/nest/nx-target.ts
|
|
169
|
-
import { existsSync
|
|
170
|
-
import { join
|
|
126
|
+
import { existsSync, readFileSync } from "fs";
|
|
127
|
+
import { join, relative } from "path";
|
|
171
128
|
var DEPRECATED_NX_BUILDER = "@nx/webpack:webpack";
|
|
172
129
|
var TRANSLATABLE_WEBPACK_CONFIG = "tools/webpack-configs/update.webpack.config.js";
|
|
173
130
|
var readProjectJson = (cwd) => {
|
|
174
|
-
const path =
|
|
175
|
-
if (!
|
|
131
|
+
const path = join(cwd, "project.json");
|
|
132
|
+
if (!existsSync(path)) return void 0;
|
|
176
133
|
try {
|
|
177
|
-
return JSON.parse(
|
|
134
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
178
135
|
} catch {
|
|
179
136
|
return void 0;
|
|
180
137
|
}
|
|
@@ -217,7 +174,7 @@ function liveFileReplacements(cwd, target) {
|
|
|
217
174
|
const live = [];
|
|
218
175
|
for (const configuration of Object.values(configurations)) {
|
|
219
176
|
for (const replacement of configuration.fileReplacements ?? []) {
|
|
220
|
-
const exists =
|
|
177
|
+
const exists = existsSync(join(workspaceRoot, replacement.replace)) || existsSync(join(workspaceRoot, replacement.with));
|
|
221
178
|
if (exists) live.push(replacement.replace);
|
|
222
179
|
}
|
|
223
180
|
}
|
|
@@ -238,8 +195,8 @@ function workspaceRootHops(cwd, target) {
|
|
|
238
195
|
}
|
|
239
196
|
|
|
240
197
|
// src/roles/build/read-adoption.ts
|
|
241
|
-
import { existsSync as
|
|
242
|
-
import { join as
|
|
198
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
199
|
+
import { join as join4 } from "path";
|
|
243
200
|
|
|
244
201
|
// src/core/config/module-type.ts
|
|
245
202
|
function esmReadiness(fileName, packageType) {
|
|
@@ -253,11 +210,11 @@ function esmReadiness(fileName, packageType) {
|
|
|
253
210
|
}
|
|
254
211
|
|
|
255
212
|
// src/core/config/manifest.ts
|
|
256
|
-
import { existsSync as
|
|
257
|
-
import { basename
|
|
213
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
214
|
+
import { basename, join as join2 } from "path";
|
|
258
215
|
function moduleScripts(cwd) {
|
|
259
216
|
try {
|
|
260
|
-
const pkg = JSON.parse(
|
|
217
|
+
const pkg = JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
|
|
261
218
|
return pkg.scripts ?? {};
|
|
262
219
|
} catch {
|
|
263
220
|
return {};
|
|
@@ -265,7 +222,7 @@ function moduleScripts(cwd) {
|
|
|
265
222
|
}
|
|
266
223
|
function moduleName2(cwd) {
|
|
267
224
|
try {
|
|
268
|
-
const pkg = JSON.parse(
|
|
225
|
+
const pkg = JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
|
|
269
226
|
return pkg.name;
|
|
270
227
|
} catch {
|
|
271
228
|
return void 0;
|
|
@@ -280,7 +237,7 @@ function selfCommand(cwd, flags) {
|
|
|
280
237
|
return `node ${own.bin.replace(/^\.\//, "")} ${flags}`;
|
|
281
238
|
}
|
|
282
239
|
function adoptedPackageName(cwd) {
|
|
283
|
-
return readProjectPackageJson(cwd).name ?? readNxProjectName(cwd) ??
|
|
240
|
+
return readProjectPackageJson(cwd).name ?? readNxProjectName(cwd) ?? basename(cwd);
|
|
284
241
|
}
|
|
285
242
|
function manifestOperation(cwd, scripts) {
|
|
286
243
|
const own = readOwnPackage();
|
|
@@ -288,7 +245,7 @@ function manifestOperation(cwd, scripts) {
|
|
|
288
245
|
scripts,
|
|
289
246
|
devDependencies: { [own.name]: isSelfAdoption(cwd) ? "link:." : own.version }
|
|
290
247
|
};
|
|
291
|
-
if (
|
|
248
|
+
if (existsSync2(join2(cwd, "package.json"))) {
|
|
292
249
|
return { kind: "merge-json", path: "package.json", value };
|
|
293
250
|
}
|
|
294
251
|
return {
|
|
@@ -298,6 +255,19 @@ function manifestOperation(cwd, scripts) {
|
|
|
298
255
|
};
|
|
299
256
|
}
|
|
300
257
|
|
|
258
|
+
// src/core/config/project-json.ts
|
|
259
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
260
|
+
import { join as join3 } from "path";
|
|
261
|
+
function projectTargets(cwd) {
|
|
262
|
+
const path = join3(cwd, "project.json");
|
|
263
|
+
if (!existsSync3(path)) return void 0;
|
|
264
|
+
try {
|
|
265
|
+
return JSON.parse(readFileSync3(path, "utf8")).targets;
|
|
266
|
+
} catch {
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
301
271
|
// src/roles/build/presets/requirements.json
|
|
302
272
|
var requirements_default = {
|
|
303
273
|
svelte: {
|
|
@@ -413,19 +383,6 @@ var SENTINEL_OWNED_BUILD_PACKAGES = OWNED_PACKAGES.map(
|
|
|
413
383
|
(entry) => entry.package
|
|
414
384
|
);
|
|
415
385
|
|
|
416
|
-
// src/roles/build/project-json.ts
|
|
417
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
418
|
-
import { join as join4 } from "path";
|
|
419
|
-
function projectTargets(cwd) {
|
|
420
|
-
const path = join4(cwd, "project.json");
|
|
421
|
-
if (!existsSync4(path)) return void 0;
|
|
422
|
-
try {
|
|
423
|
-
return JSON.parse(readFileSync4(path, "utf8")).targets;
|
|
424
|
-
} catch {
|
|
425
|
-
return void 0;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
386
|
// src/roles/build/adoption-drift.ts
|
|
430
387
|
function buildAdoptionDrift(cwd) {
|
|
431
388
|
const drift = [];
|
|
@@ -488,7 +445,7 @@ var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
|
488
445
|
unreadable
|
|
489
446
|
});
|
|
490
447
|
function buildConfigFile(cwd) {
|
|
491
|
-
return BUILD_CONFIG_FILES.find((name) =>
|
|
448
|
+
return BUILD_CONFIG_FILES.find((name) => existsSync4(join4(cwd, name)));
|
|
492
449
|
}
|
|
493
450
|
function importsSpecifier(source, specifier) {
|
|
494
451
|
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -514,7 +471,7 @@ function readBuildConfigSource(cwd) {
|
|
|
514
471
|
const configFile = buildConfigFile(cwd);
|
|
515
472
|
if (!configFile) return void 0;
|
|
516
473
|
try {
|
|
517
|
-
return
|
|
474
|
+
return readFileSync4(join4(cwd, configFile), "utf8");
|
|
518
475
|
} catch {
|
|
519
476
|
return void 0;
|
|
520
477
|
}
|
|
@@ -524,7 +481,7 @@ function readBuildAdoption(cwd) {
|
|
|
524
481
|
if (!configFile) return NOT_ADOPTED(null);
|
|
525
482
|
let source;
|
|
526
483
|
try {
|
|
527
|
-
source =
|
|
484
|
+
source = readFileSync4(join4(cwd, configFile), "utf8");
|
|
528
485
|
} catch (error) {
|
|
529
486
|
return NOT_ADOPTED(configFile, error instanceof Error ? error.message : String(error));
|
|
530
487
|
}
|
|
@@ -560,7 +517,7 @@ function readBuildAdoption(cwd) {
|
|
|
560
517
|
import { spawnSync } from "child_process";
|
|
561
518
|
|
|
562
519
|
// src/core/config/tool-args.ts
|
|
563
|
-
import { existsSync as
|
|
520
|
+
import { existsSync as existsSync5 } from "fs";
|
|
564
521
|
import { isAbsolute, resolve as resolve2 } from "path";
|
|
565
522
|
function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
566
523
|
const takesValue = new Set(valueFlags);
|
|
@@ -570,7 +527,7 @@ function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
|
570
527
|
for (const arg of toolArgs) {
|
|
571
528
|
const looksLikeOption = arg.startsWith("-");
|
|
572
529
|
const target = isAbsolute(arg) ? arg : resolve2(cwd, arg);
|
|
573
|
-
if (!previousTakesValue && !looksLikeOption &&
|
|
530
|
+
if (!previousTakesValue && !looksLikeOption && existsSync5(target)) paths.push(arg);
|
|
574
531
|
else options.push(arg);
|
|
575
532
|
previousTakesValue = !arg.includes("=") && takesValue.has(arg);
|
|
576
533
|
}
|
|
@@ -578,16 +535,16 @@ function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
|
578
535
|
}
|
|
579
536
|
|
|
580
537
|
// src/shared/resolve-bin.ts
|
|
581
|
-
import { existsSync as
|
|
538
|
+
import { existsSync as existsSync6 } from "fs";
|
|
582
539
|
import { createRequire } from "module";
|
|
583
|
-
import { delimiter, dirname
|
|
540
|
+
import { delimiter, dirname, join as join5 } from "path";
|
|
584
541
|
var require2 = createRequire(import.meta.url);
|
|
585
542
|
function resolveBin(fromDir, name) {
|
|
586
543
|
let dir = fromDir;
|
|
587
544
|
for (; ; ) {
|
|
588
|
-
const candidate =
|
|
589
|
-
if (
|
|
590
|
-
const parent =
|
|
545
|
+
const candidate = join5(dir, "node_modules", ".bin", name);
|
|
546
|
+
if (existsSync6(candidate)) return candidate;
|
|
547
|
+
const parent = dirname(dir);
|
|
591
548
|
if (parent === dir) return void 0;
|
|
592
549
|
dir = parent;
|
|
593
550
|
}
|
|
@@ -596,10 +553,10 @@ function binFromOwnInstall(packageName, binName) {
|
|
|
596
553
|
try {
|
|
597
554
|
const manifest = require2.resolve(`${packageName}/package.json`);
|
|
598
555
|
const bin = require2(manifest).bin;
|
|
599
|
-
const
|
|
600
|
-
if (!
|
|
601
|
-
const executable =
|
|
602
|
-
return
|
|
556
|
+
const relative7 = typeof bin === "string" ? bin : bin?.[binName];
|
|
557
|
+
if (!relative7) return void 0;
|
|
558
|
+
const executable = join5(dirname(manifest), relative7);
|
|
559
|
+
return existsSync6(executable) ? executable : void 0;
|
|
603
560
|
} catch {
|
|
604
561
|
return void 0;
|
|
605
562
|
}
|
|
@@ -611,12 +568,12 @@ function binSearchPath(cwd) {
|
|
|
611
568
|
}
|
|
612
569
|
|
|
613
570
|
// src/roles/build/plan.ts
|
|
614
|
-
import { existsSync as
|
|
615
|
-
import { join as
|
|
571
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
572
|
+
import { join as join10 } from "path";
|
|
616
573
|
|
|
617
574
|
// src/core/config/existing-command.ts
|
|
618
|
-
import { readFileSync as
|
|
619
|
-
import { join as
|
|
575
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
576
|
+
import { join as join6 } from "path";
|
|
620
577
|
|
|
621
578
|
// src/shared/jsonc.ts
|
|
622
579
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
@@ -635,7 +592,7 @@ function nxTargetCommand(cwd, target) {
|
|
|
635
592
|
let project;
|
|
636
593
|
try {
|
|
637
594
|
project = parseJsonc(
|
|
638
|
-
|
|
595
|
+
readFileSync5(join6(cwd, "project.json"), "utf8"),
|
|
639
596
|
"project.json"
|
|
640
597
|
);
|
|
641
598
|
} catch {
|
|
@@ -666,8 +623,8 @@ function existingCommand(cwd, target) {
|
|
|
666
623
|
}
|
|
667
624
|
|
|
668
625
|
// src/core/config/nx-target.ts
|
|
669
|
-
import { existsSync as
|
|
670
|
-
import { join as
|
|
626
|
+
import { existsSync as existsSync7 } from "fs";
|
|
627
|
+
import { join as join7 } from "path";
|
|
671
628
|
function nxTargetMetadataOperations(options) {
|
|
672
629
|
const { targets } = options;
|
|
673
630
|
if (Object.keys(targets).length === 0) return [];
|
|
@@ -678,7 +635,7 @@ function nxTargetOperations(options) {
|
|
|
678
635
|
const names = Object.keys(targets);
|
|
679
636
|
if (names.length === 0) return [];
|
|
680
637
|
const operations = [];
|
|
681
|
-
if (
|
|
638
|
+
if (existsSync7(join7(cwd, "project.json"))) {
|
|
682
639
|
operations.push({
|
|
683
640
|
kind: "remove-json-keys",
|
|
684
641
|
path: "project.json",
|
|
@@ -732,8 +689,8 @@ function keepsOtherCommands(script, sentinelCommand) {
|
|
|
732
689
|
}
|
|
733
690
|
|
|
734
691
|
// src/roles/format/read-adoption.ts
|
|
735
|
-
import { existsSync as
|
|
736
|
-
import { join as
|
|
692
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
693
|
+
import { join as join8 } from "path";
|
|
737
694
|
|
|
738
695
|
// src/roles/format/config-policy.ts
|
|
739
696
|
var FORMAT_CONFIG_FILE = ".oxfmtrc.json";
|
|
@@ -839,11 +796,11 @@ function sameValue(a, b) {
|
|
|
839
796
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
840
797
|
}
|
|
841
798
|
function readFormatAdoption(cwd) {
|
|
842
|
-
const path =
|
|
843
|
-
if (!
|
|
799
|
+
const path = join8(cwd, FORMAT_CONFIG_FILE);
|
|
800
|
+
if (!existsSync8(path)) return NOT_ADOPTED2(null);
|
|
844
801
|
let parsed;
|
|
845
802
|
try {
|
|
846
|
-
parsed = parseJsonc(
|
|
803
|
+
parsed = parseJsonc(readFileSync6(path, "utf8"), FORMAT_CONFIG_FILE);
|
|
847
804
|
} catch (error) {
|
|
848
805
|
const reason = error instanceof Error ? error.message : String(error);
|
|
849
806
|
return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
|
|
@@ -944,8 +901,8 @@ function devTargetOperations(cwd, devScript) {
|
|
|
944
901
|
}
|
|
945
902
|
|
|
946
903
|
// src/roles/build/nest/adopt.ts
|
|
947
|
-
import { existsSync as
|
|
948
|
-
import { join as
|
|
904
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
905
|
+
import { join as join9 } from "path";
|
|
949
906
|
var NEST_CONFIG_FILE = "vite.config.mts";
|
|
950
907
|
function asSource(value, depth) {
|
|
951
908
|
const pad = " ".repeat(depth);
|
|
@@ -994,9 +951,9 @@ function nestBuildOutputs(target) {
|
|
|
994
951
|
}
|
|
995
952
|
var OUT_DIR_IN_CONFIG = /^\s*outDir:\s*'([^']+)'/m;
|
|
996
953
|
function nestOutputsFromConfig(cwd) {
|
|
997
|
-
const configPath =
|
|
998
|
-
if (!
|
|
999
|
-
const outDir = OUT_DIR_IN_CONFIG.exec(
|
|
954
|
+
const configPath = join9(cwd, NEST_CONFIG_FILE);
|
|
955
|
+
if (!existsSync9(configPath)) return void 0;
|
|
956
|
+
const outDir = OUT_DIR_IN_CONFIG.exec(readFileSync7(configPath, "utf8"))?.[1];
|
|
1000
957
|
return outDir === void 0 ? void 0 : [`{workspaceRoot}/${outDir}`];
|
|
1001
958
|
}
|
|
1002
959
|
function nestAdoptionOperations(cwd, target) {
|
|
@@ -1005,9 +962,9 @@ function nestAdoptionOperations(cwd, target) {
|
|
|
1005
962
|
|
|
1006
963
|
// src/roles/build/requirements.ts
|
|
1007
964
|
function atLeast(declared, needed) {
|
|
1008
|
-
const
|
|
1009
|
-
const left =
|
|
1010
|
-
const right =
|
|
965
|
+
const parse4 = (value) => value.replace(/^[\^~>=<\s]+/, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
966
|
+
const left = parse4(declared);
|
|
967
|
+
const right = parse4(needed);
|
|
1011
968
|
for (let at = 0; at < Math.max(left.length, right.length); at++) {
|
|
1012
969
|
const a = left[at] ?? 0;
|
|
1013
970
|
const b = right[at] ?? 0;
|
|
@@ -1037,14 +994,15 @@ var movedSpecifiers = (toolchain) => new Set(toolchain.owned.map((entry) => entr
|
|
|
1037
994
|
var defaultExportNames = (toolchain) => Object.fromEntries(
|
|
1038
995
|
toolchain.owned.filter((entry) => entry.default !== void 0).map((entry) => [entry.specifier, entry.default])
|
|
1039
996
|
);
|
|
1040
|
-
function renderImport(bindings, presetSpecifier) {
|
|
997
|
+
function renderImport(bindings, presetSpecifier, semi) {
|
|
1041
998
|
const render = (list) => list.map((b) => b.exported === b.local ? b.exported : `${b.exported} as ${b.local}`).sort((left, right) => left.localeCompare(right)).join(", ");
|
|
999
|
+
const end = semi ? ";" : "";
|
|
1042
1000
|
const values = bindings.filter((b) => !b.typeOnly);
|
|
1043
1001
|
const types = bindings.filter((b) => b.typeOnly);
|
|
1044
1002
|
const lines = [];
|
|
1045
|
-
if (values.length > 0) lines.push(`import { ${render(values)} } from '${presetSpecifier}'`);
|
|
1003
|
+
if (values.length > 0) lines.push(`import { ${render(values)} } from '${presetSpecifier}'${end}`);
|
|
1046
1004
|
if (types.length > 0) {
|
|
1047
|
-
lines.push(`import type { ${render(types)} } from '${presetSpecifier}'`);
|
|
1005
|
+
lines.push(`import type { ${render(types)} } from '${presetSpecifier}'${end}`);
|
|
1048
1006
|
}
|
|
1049
1007
|
return lines.join("\n");
|
|
1050
1008
|
}
|
|
@@ -1113,7 +1071,8 @@ function rewriteToolchainImports(toolchain, source, fileName) {
|
|
|
1113
1071
|
const after = out.slice(span.end).match(/^\r?\n/)?.[0].length ?? 0;
|
|
1114
1072
|
out = out.slice(0, span.start) + out.slice(span.end + after);
|
|
1115
1073
|
}
|
|
1116
|
-
|
|
1074
|
+
const semi = source.slice(first.start, first.end).trimEnd().endsWith(";");
|
|
1075
|
+
out = out.slice(0, first.start) + renderImport(bindings, toolchain.presetSpecifier, semi) + out.slice(first.end);
|
|
1117
1076
|
return {
|
|
1118
1077
|
kind: "rewritten",
|
|
1119
1078
|
source: out,
|
|
@@ -1299,9 +1258,9 @@ function planNestService(context, webpackTarget) {
|
|
|
1299
1258
|
};
|
|
1300
1259
|
}
|
|
1301
1260
|
function readConfigSource(cwd, configFile) {
|
|
1302
|
-
if (!
|
|
1261
|
+
if (!existsSync10(join10(cwd, configFile))) return "";
|
|
1303
1262
|
try {
|
|
1304
|
-
return
|
|
1263
|
+
return readFileSync8(join10(cwd, configFile), "utf8");
|
|
1305
1264
|
} catch {
|
|
1306
1265
|
return "";
|
|
1307
1266
|
}
|
|
@@ -1486,180 +1445,23 @@ var ViteDevAdapter = class extends ViteRoleAdapter {
|
|
|
1486
1445
|
};
|
|
1487
1446
|
|
|
1488
1447
|
// src/roles/build/declared-outputs.ts
|
|
1489
|
-
import { existsSync as
|
|
1490
|
-
import { join as
|
|
1491
|
-
|
|
1492
|
-
// src/core/workspace-prep.ts
|
|
1493
|
-
import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync } from "fs";
|
|
1494
|
-
import { dirname as dirname3, join as join12, relative as relative2 } from "path";
|
|
1495
|
-
|
|
1496
|
-
// src/core/settings.ts
|
|
1497
|
-
var WORKSPACE_ROOT_MARKER = "nx.json";
|
|
1498
|
-
var DEFAULT_MAX_DIAGNOSTICS = 100;
|
|
1499
|
-
|
|
1500
|
-
// src/core/workspace-prep.ts
|
|
1501
|
-
var OVERRIDE_KEY = "i18next>typescript";
|
|
1502
|
-
var NATIVE_TS_ALIAS = "@typescript/native";
|
|
1503
|
-
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
1504
|
-
var RELEASE_AGE_KEY = "minimumReleaseAgeExclude";
|
|
1505
|
-
var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
|
|
1506
|
-
function findWorkspaceRoot(startDir) {
|
|
1507
|
-
let dir = startDir;
|
|
1508
|
-
for (; ; ) {
|
|
1509
|
-
if (existsSync12(join12(dir, WORKSPACE_ROOT_MARKER))) return dir;
|
|
1510
|
-
const parent = dirname3(dir);
|
|
1511
|
-
if (parent === dir) return void 0;
|
|
1512
|
-
dir = parent;
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
function declaredNativeTs(pkg) {
|
|
1516
|
-
const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
|
|
1517
|
-
if (!spec) return void 0;
|
|
1518
|
-
const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
|
|
1519
|
-
return version || void 0;
|
|
1520
|
-
}
|
|
1521
|
-
function ensureI18nextSingleton(root, dryRun) {
|
|
1522
|
-
const pkgPath = join12(root, "package.json");
|
|
1523
|
-
if (!existsSync12(pkgPath)) return void 0;
|
|
1524
|
-
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
1525
|
-
const want = declaredNativeTs(pkg);
|
|
1526
|
-
if (!want) return void 0;
|
|
1527
|
-
const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1528
|
-
if (have === want) return void 0;
|
|
1529
|
-
if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} (i18next singleton)`;
|
|
1530
|
-
pkg.pnpm ??= {};
|
|
1531
|
-
pkg.pnpm.overrides ??= {};
|
|
1532
|
-
pkg.pnpm.overrides[OVERRIDE_KEY] = want;
|
|
1533
|
-
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1534
|
-
return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
|
|
1535
|
-
}
|
|
1536
|
-
function ensureReleaseAgeAllowList(root, dryRun) {
|
|
1537
|
-
const yamlPath = join12(root, WORKSPACE_YAML);
|
|
1538
|
-
if (!existsSync12(yamlPath)) return void 0;
|
|
1539
|
-
const own = readOwnPackage().name;
|
|
1540
|
-
const lines = readFileSync10(yamlPath, "utf8").split("\n");
|
|
1541
|
-
const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
|
|
1542
|
-
if (keyIdx === -1) return void 0;
|
|
1543
|
-
let lastItemIdx = keyIdx;
|
|
1544
|
-
let indent = " ";
|
|
1545
|
-
for (let i = keyIdx + 1; i < lines.length; i++) {
|
|
1546
|
-
const match = lines[i]?.match(LIST_ITEM);
|
|
1547
|
-
if (!match) break;
|
|
1548
|
-
indent = match[1] ?? indent;
|
|
1549
|
-
lastItemIdx = i;
|
|
1550
|
-
if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
|
|
1551
|
-
}
|
|
1552
|
-
if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
|
|
1553
|
-
lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
|
|
1554
|
-
writeFileSync(yamlPath, lines.join("\n"));
|
|
1555
|
-
return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
|
|
1556
|
-
}
|
|
1557
|
-
var PRETTIER_IGNORE = ".prettierignore";
|
|
1558
|
-
var EXCLUSION_HEADER = "# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier";
|
|
1559
|
-
var ROOT_PRETTIER_CONFIGS = [
|
|
1560
|
-
".prettierrc",
|
|
1561
|
-
".prettierrc.json",
|
|
1562
|
-
".prettierrc.json5",
|
|
1563
|
-
".prettierrc.yml",
|
|
1564
|
-
".prettierrc.yaml",
|
|
1565
|
-
".prettierrc.js",
|
|
1566
|
-
".prettierrc.cjs",
|
|
1567
|
-
".prettierrc.mjs",
|
|
1568
|
-
"prettier.config.js",
|
|
1569
|
-
"prettier.config.cjs",
|
|
1570
|
-
"prettier.config.mjs"
|
|
1571
|
-
];
|
|
1572
|
-
function ensureFormatterExclusion(root, moduleDir, dryRun) {
|
|
1573
|
-
const rel = relative2(root, moduleDir).replaceAll("\\", "/");
|
|
1574
|
-
if (rel === "" || rel.startsWith("..")) return void 0;
|
|
1575
|
-
const ignorePath = join12(root, PRETTIER_IGNORE);
|
|
1576
|
-
const hasPrettier = existsSync12(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync12(join12(root, name)));
|
|
1577
|
-
if (!hasPrettier) return void 0;
|
|
1578
|
-
const pattern = `/${rel}/`;
|
|
1579
|
-
const existing = existsSync12(ignorePath) ? readFileSync10(ignorePath, "utf8") : "";
|
|
1580
|
-
const lines = existing.split("\n");
|
|
1581
|
-
if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
|
|
1582
|
-
return void 0;
|
|
1583
|
-
}
|
|
1584
|
-
if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`;
|
|
1585
|
-
const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER);
|
|
1586
|
-
if (headerIdx === -1) {
|
|
1587
|
-
const prefix = existing.trim() === "" ? "" : `${existing.replace(/\n*$/, "")}
|
|
1588
|
-
|
|
1589
|
-
`;
|
|
1590
|
-
writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}
|
|
1591
|
-
${pattern}
|
|
1592
|
-
`);
|
|
1593
|
-
} else {
|
|
1594
|
-
let end = headerIdx + 1;
|
|
1595
|
-
while (end < lines.length && lines[end]?.trim().startsWith("/")) end += 1;
|
|
1596
|
-
const managed = [...lines.slice(headerIdx + 1, end), pattern].sort();
|
|
1597
|
-
writeFileSync(
|
|
1598
|
-
ignorePath,
|
|
1599
|
-
[...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join("\n")
|
|
1600
|
-
);
|
|
1601
|
-
}
|
|
1602
|
-
return `excluded ${rel} from the root Prettier (${PRETTIER_IGNORE}): it formats itself now`;
|
|
1603
|
-
}
|
|
1604
|
-
function ensureWorkspacePrep(opts) {
|
|
1605
|
-
const dryRun = Boolean(opts.dryRun);
|
|
1606
|
-
return [
|
|
1607
|
-
ensureI18nextSingleton(opts.root, dryRun),
|
|
1608
|
-
ensureReleaseAgeAllowList(opts.root, dryRun),
|
|
1609
|
-
opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
|
|
1610
|
-
].filter((message) => message !== void 0);
|
|
1611
|
-
}
|
|
1612
|
-
function inspectWorkspacePrep(root) {
|
|
1613
|
-
const entries = [];
|
|
1614
|
-
let pkg = {};
|
|
1615
|
-
const pkgPath = join12(root, "package.json");
|
|
1616
|
-
if (existsSync12(pkgPath)) {
|
|
1617
|
-
try {
|
|
1618
|
-
pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
1619
|
-
} catch {
|
|
1620
|
-
pkg = {};
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
const pinned = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
1624
|
-
if (pinned !== void 0) {
|
|
1625
|
-
const stillForks = declaredNativeTs(pkg);
|
|
1626
|
-
entries.push({
|
|
1627
|
-
rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
|
|
1628
|
-
reason: stillForks === void 0 ? `pinned to ${pinned}, but the root no longer declares ${NATIVE_TS_ALIAS}, so nothing forks any more. This override is now dead config and can be removed.` : `pinned to ${pinned}, matching the second TypeScript the root declares (${NATIVE_TS_ALIAS}). It re-resolves i18next for EVERY module that depends on it, not only adopted ones. It retires with the second TypeScript.`
|
|
1629
|
-
});
|
|
1630
|
-
}
|
|
1631
|
-
const own = readOwnPackage().name;
|
|
1632
|
-
const yamlPath = join12(root, WORKSPACE_YAML);
|
|
1633
|
-
if (existsSync12(yamlPath)) {
|
|
1634
|
-
const yaml = readFileSync10(yamlPath, "utf8");
|
|
1635
|
-
const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
|
|
1636
|
-
if (listed) {
|
|
1637
|
-
const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
|
|
1638
|
-
entries.push({
|
|
1639
|
-
rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
|
|
1640
|
-
reason: gated ? `${own} is exempt from this workspace's release-age gate, so a freshly published version installs instead of being held. That is a standing exemption from a supply-chain control: the exposure is one bump wide, since the version is pinned and the lockfile carries its integrity, so a re-publish of the same version does not pass. Retire it once a bump can simply wait out the window.` : `${own} is allow-listed, but this workspace no longer sets minimumReleaseAge, so there is no gate to be exempt from. This entry is now dead config and can be removed.`
|
|
1641
|
-
});
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
return entries;
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
// src/roles/build/declared-outputs.ts
|
|
1448
|
+
import { existsSync as existsSync11, rmSync, statSync, writeFileSync } from "fs";
|
|
1449
|
+
import { join as join11 } from "path";
|
|
1648
1450
|
function declaredBuildTarget(cwd) {
|
|
1649
1451
|
return readProjectPackageJson(cwd).nx?.targets?.[BUILD_SCRIPT_NAME];
|
|
1650
1452
|
}
|
|
1651
1453
|
function resolveOutputToken(token, cwd) {
|
|
1652
|
-
if (token.startsWith("{projectRoot}/")) return
|
|
1454
|
+
if (token.startsWith("{projectRoot}/")) return join11(cwd, token.slice("{projectRoot}/".length));
|
|
1653
1455
|
if (token.startsWith("{workspaceRoot}/")) {
|
|
1654
1456
|
const root = findWorkspaceRoot(cwd);
|
|
1655
|
-
return root === void 0 ? void 0 :
|
|
1457
|
+
return root === void 0 ? void 0 : join11(root, token.slice("{workspaceRoot}/".length));
|
|
1656
1458
|
}
|
|
1657
|
-
return
|
|
1459
|
+
return join11(cwd, token);
|
|
1658
1460
|
}
|
|
1659
1461
|
function buildStamp(cwd) {
|
|
1660
|
-
const marker =
|
|
1462
|
+
const marker = join11(cwd, `.sentinel-build-stamp-${process.pid}`);
|
|
1661
1463
|
try {
|
|
1662
|
-
|
|
1464
|
+
writeFileSync(marker, "");
|
|
1663
1465
|
const stamp = statSync(marker).mtimeMs;
|
|
1664
1466
|
rmSync(marker, { force: true });
|
|
1665
1467
|
return stamp;
|
|
@@ -1668,7 +1470,7 @@ function buildStamp(cwd) {
|
|
|
1668
1470
|
}
|
|
1669
1471
|
}
|
|
1670
1472
|
function writtenSince(path, startedAt) {
|
|
1671
|
-
if (!
|
|
1473
|
+
if (!existsSync11(path)) return false;
|
|
1672
1474
|
try {
|
|
1673
1475
|
return statSync(path).mtimeMs >= startedAt;
|
|
1674
1476
|
} catch {
|
|
@@ -1817,12 +1619,12 @@ function registerBuild() {
|
|
|
1817
1619
|
|
|
1818
1620
|
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
1819
1621
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1820
|
-
import { existsSync as
|
|
1821
|
-
import { join as
|
|
1622
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
1623
|
+
import { join as join16 } from "path";
|
|
1822
1624
|
|
|
1823
1625
|
// src/core/config/has-source.ts
|
|
1824
1626
|
import { readdirSync } from "fs";
|
|
1825
|
-
import { extname, join as
|
|
1627
|
+
import { extname, join as join12, relative as relative2 } from "path";
|
|
1826
1628
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1827
1629
|
"node_modules",
|
|
1828
1630
|
"dist",
|
|
@@ -1873,7 +1675,7 @@ function hasSourceFiles(cwd, extensions) {
|
|
|
1873
1675
|
}
|
|
1874
1676
|
for (const entry of entries) {
|
|
1875
1677
|
if (entry.isDirectory()) {
|
|
1876
|
-
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(
|
|
1678
|
+
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join12(dir, entry.name));
|
|
1877
1679
|
continue;
|
|
1878
1680
|
}
|
|
1879
1681
|
if (wanted.has(extname(entry.name))) return true;
|
|
@@ -1894,12 +1696,12 @@ function listSourceFiles(cwd, extensions) {
|
|
|
1894
1696
|
continue;
|
|
1895
1697
|
}
|
|
1896
1698
|
for (const entry of entries) {
|
|
1897
|
-
const full =
|
|
1699
|
+
const full = join12(dir, entry.name);
|
|
1898
1700
|
if (entry.isDirectory()) {
|
|
1899
1701
|
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
|
|
1900
1702
|
continue;
|
|
1901
1703
|
}
|
|
1902
|
-
if (wanted.has(extname(entry.name))) found.push(
|
|
1704
|
+
if (wanted.has(extname(entry.name))) found.push(relative2(cwd, full));
|
|
1903
1705
|
}
|
|
1904
1706
|
}
|
|
1905
1707
|
return found;
|
|
@@ -1940,8 +1742,8 @@ function writesWhenRewritten(name, command) {
|
|
|
1940
1742
|
var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
|
|
1941
1743
|
|
|
1942
1744
|
// src/roles/format/inherited-ignores.ts
|
|
1943
|
-
import { existsSync as
|
|
1944
|
-
import { join as
|
|
1745
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
1746
|
+
import { join as join13 } from "path";
|
|
1945
1747
|
var ROOT_IGNORE_FILE = ".prettierignore";
|
|
1946
1748
|
function isPattern(line) {
|
|
1947
1749
|
const trimmed = line.trim();
|
|
@@ -1951,16 +1753,17 @@ function toModulePattern(pattern) {
|
|
|
1951
1753
|
const trimmed = pattern.trim();
|
|
1952
1754
|
if (trimmed.startsWith("!")) return void 0;
|
|
1953
1755
|
if (trimmed.startsWith("/**/")) return trimmed.slice(1);
|
|
1756
|
+
if (trimmed.startsWith("**/")) return trimmed;
|
|
1954
1757
|
if (trimmed.startsWith("/")) return void 0;
|
|
1955
1758
|
return trimmed.replace(/\/$/, "").includes("/") ? void 0 : trimmed;
|
|
1956
1759
|
}
|
|
1957
1760
|
function inheritedIgnorePatterns(workspaceRoot) {
|
|
1958
1761
|
if (!workspaceRoot) return [];
|
|
1959
|
-
const path =
|
|
1960
|
-
if (!
|
|
1762
|
+
const path = join13(workspaceRoot, ROOT_IGNORE_FILE);
|
|
1763
|
+
if (!existsSync12(path)) return [];
|
|
1961
1764
|
let contents;
|
|
1962
1765
|
try {
|
|
1963
|
-
contents =
|
|
1766
|
+
contents = readFileSync9(path, "utf8");
|
|
1964
1767
|
} catch {
|
|
1965
1768
|
return [];
|
|
1966
1769
|
}
|
|
@@ -2005,13 +1808,13 @@ function needsSvelteCompiler(output) {
|
|
|
2005
1808
|
}
|
|
2006
1809
|
|
|
2007
1810
|
// src/roles/format/prettier-config.ts
|
|
2008
|
-
import { existsSync as
|
|
2009
|
-
import { join as
|
|
1811
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
1812
|
+
import { join as join15 } from "path";
|
|
2010
1813
|
|
|
2011
1814
|
// src/roles/format/resolve-oxfmt.ts
|
|
2012
|
-
import { readFileSync as
|
|
1815
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
2013
1816
|
import { createRequire as createRequire2 } from "module";
|
|
2014
|
-
import { dirname as
|
|
1817
|
+
import { dirname as dirname2, join as join14 } from "path";
|
|
2015
1818
|
function resolveOxfmt(cwd) {
|
|
2016
1819
|
return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
|
|
2017
1820
|
}
|
|
@@ -2040,8 +1843,8 @@ function configSchema() {
|
|
|
2040
1843
|
function readConfigSchema() {
|
|
2041
1844
|
try {
|
|
2042
1845
|
const manifest = createRequire2(import.meta.url).resolve("oxfmt/package.json");
|
|
2043
|
-
const schemaPath =
|
|
2044
|
-
return JSON.parse(
|
|
1846
|
+
const schemaPath = join14(dirname2(manifest), "configuration_schema.json");
|
|
1847
|
+
return JSON.parse(readFileSync10(schemaPath, "utf8"));
|
|
2045
1848
|
} catch {
|
|
2046
1849
|
return void 0;
|
|
2047
1850
|
}
|
|
@@ -2072,14 +1875,14 @@ function toOxfmtOverrides(value) {
|
|
|
2072
1875
|
return { overrides, unresolved };
|
|
2073
1876
|
}
|
|
2074
1877
|
function readPrettierSettings(cwd) {
|
|
2075
|
-
const file = PRETTIER_CONFIG_FILES.find((name) =>
|
|
1878
|
+
const file = PRETTIER_CONFIG_FILES.find((name) => existsSync13(join15(cwd, name)));
|
|
2076
1879
|
if (!file) return { options: {}, unresolved: [] };
|
|
2077
1880
|
if (/\.(js|cjs|mjs)$/.test(file)) {
|
|
2078
1881
|
return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
|
|
2079
1882
|
}
|
|
2080
1883
|
let parsed;
|
|
2081
1884
|
try {
|
|
2082
|
-
parsed = parseJsonc(
|
|
1885
|
+
parsed = parseJsonc(readFileSync11(join15(cwd, file), "utf8"), file);
|
|
2083
1886
|
} catch {
|
|
2084
1887
|
return { options: {}, file, unresolved: [`${file} could not be parsed`] };
|
|
2085
1888
|
}
|
|
@@ -2190,7 +1993,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
2190
1993
|
manifestOperation(context.cwd, this.formatScripts(context.cwd))
|
|
2191
1994
|
];
|
|
2192
1995
|
const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
|
|
2193
|
-
(name) =>
|
|
1996
|
+
(name) => existsSync14(join16(context.cwd, name))
|
|
2194
1997
|
);
|
|
2195
1998
|
for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
|
|
2196
1999
|
const removableDeps = this.modulePrettierDependencies(context.cwd);
|
|
@@ -2430,7 +2233,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
2430
2233
|
modulePrettierDependencies(cwd) {
|
|
2431
2234
|
const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
|
|
2432
2235
|
try {
|
|
2433
|
-
const manifest = JSON.parse(
|
|
2236
|
+
const manifest = JSON.parse(readFileSync12(join16(cwd, "package.json"), "utf8"));
|
|
2434
2237
|
return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
|
|
2435
2238
|
} catch {
|
|
2436
2239
|
return [];
|
|
@@ -2512,8 +2315,8 @@ function registerFormat() {
|
|
|
2512
2315
|
|
|
2513
2316
|
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
2514
2317
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
2515
|
-
import { existsSync as
|
|
2516
|
-
import { join as
|
|
2318
|
+
import { existsSync as existsSync22, readFileSync as readFileSync20, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2319
|
+
import { join as join26 } from "path";
|
|
2517
2320
|
|
|
2518
2321
|
// src/core/config/deferred-rules.ts
|
|
2519
2322
|
function deferredRuleNames(rules) {
|
|
@@ -2522,7 +2325,7 @@ function deferredRuleNames(rules) {
|
|
|
2522
2325
|
|
|
2523
2326
|
// src/core/fix-report.ts
|
|
2524
2327
|
import { statSync as statSync2 } from "fs";
|
|
2525
|
-
import { join as
|
|
2328
|
+
import { join as join17 } from "path";
|
|
2526
2329
|
var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
|
|
2527
2330
|
function readFailure(output) {
|
|
2528
2331
|
const lines = output.split("\n").map((line2) => line2.trim());
|
|
@@ -2542,7 +2345,7 @@ function fingerprint(path) {
|
|
|
2542
2345
|
function snapshotFiles(cwd, extensions) {
|
|
2543
2346
|
const snapshot = /* @__PURE__ */ new Map();
|
|
2544
2347
|
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2545
|
-
const mark = fingerprint(
|
|
2348
|
+
const mark = fingerprint(join17(cwd, file));
|
|
2546
2349
|
if (mark !== void 0) snapshot.set(file, mark);
|
|
2547
2350
|
}
|
|
2548
2351
|
return snapshot;
|
|
@@ -2550,7 +2353,7 @@ function snapshotFiles(cwd, extensions) {
|
|
|
2550
2353
|
function changedSince(cwd, before, extensions) {
|
|
2551
2354
|
const changed = [];
|
|
2552
2355
|
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2553
|
-
const now = fingerprint(
|
|
2356
|
+
const now = fingerprint(join17(cwd, file));
|
|
2554
2357
|
if (now === void 0) continue;
|
|
2555
2358
|
if (before.get(file) !== now) changed.push(file);
|
|
2556
2359
|
}
|
|
@@ -2569,8 +2372,8 @@ function describeFixOutcome(outcome, toolLabel) {
|
|
|
2569
2372
|
}
|
|
2570
2373
|
|
|
2571
2374
|
// src/roles/lint/module-baseline.ts
|
|
2572
|
-
import { existsSync as
|
|
2573
|
-
import { join as
|
|
2375
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
2376
|
+
import { join as join18 } from "path";
|
|
2574
2377
|
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
2575
2378
|
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
2576
2379
|
var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
|
|
@@ -2614,11 +2417,11 @@ function describeHolds(holds) {
|
|
|
2614
2417
|
return `held ${holds.length} rule(s) at \`warn\` for this module, covering ${total} violation(s) that were already there: ${listed}${rest}. They are written to ${LINT_BASELINE_FILE}, they still report, and they return to \`error\` once fixed and re-initialized`;
|
|
2615
2418
|
}
|
|
2616
2419
|
function heldRules(cwd) {
|
|
2617
|
-
const path =
|
|
2618
|
-
if (!
|
|
2420
|
+
const path = join18(cwd, LINT_BASELINE_FILE);
|
|
2421
|
+
if (!existsSync15(path)) return [];
|
|
2619
2422
|
try {
|
|
2620
2423
|
const parsed = parseJsonc(
|
|
2621
|
-
|
|
2424
|
+
readFileSync13(path, "utf8"),
|
|
2622
2425
|
LINT_BASELINE_FILE
|
|
2623
2426
|
);
|
|
2624
2427
|
return Object.keys(parsed.rules ?? {});
|
|
@@ -4444,14 +4247,14 @@ function downgradedRulesFor(preset) {
|
|
|
4444
4247
|
}
|
|
4445
4248
|
|
|
4446
4249
|
// src/roles/lint/extra-layers.ts
|
|
4447
|
-
import { existsSync as
|
|
4448
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4250
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
4251
|
+
import { isAbsolute as isAbsolute2, join as join19, resolve as resolve3 } from "path";
|
|
4449
4252
|
function ruleCount(cwd, specifier) {
|
|
4450
4253
|
const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
|
|
4451
|
-
if (!
|
|
4254
|
+
if (!existsSync16(path)) return void 0;
|
|
4452
4255
|
try {
|
|
4453
4256
|
const parsed = parseJsonc(
|
|
4454
|
-
|
|
4257
|
+
readFileSync14(path, "utf8"),
|
|
4455
4258
|
specifier
|
|
4456
4259
|
);
|
|
4457
4260
|
return Object.keys(parsed.rules ?? {}).length;
|
|
@@ -4471,7 +4274,7 @@ function extraLayers(cwd, extendsList) {
|
|
|
4471
4274
|
function committedExtendsList(cwd, configFile) {
|
|
4472
4275
|
try {
|
|
4473
4276
|
const parsed = parseJsonc(
|
|
4474
|
-
|
|
4277
|
+
readFileSync14(join19(cwd, configFile), "utf8"),
|
|
4475
4278
|
configFile
|
|
4476
4279
|
);
|
|
4477
4280
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4530,8 +4333,8 @@ function errorCountsByConfigRule(stdout) {
|
|
|
4530
4333
|
}
|
|
4531
4334
|
|
|
4532
4335
|
// src/core/config/read-adoption.ts
|
|
4533
|
-
import { existsSync as
|
|
4534
|
-
import { join as
|
|
4336
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
4337
|
+
import { join as join21 } from "path";
|
|
4535
4338
|
|
|
4536
4339
|
// src/core/config/owned-keys.ts
|
|
4537
4340
|
function presetOwnedKeys(config, permitted, presetSets) {
|
|
@@ -4546,12 +4349,12 @@ function localOnlyKeys(config, permitted, presetSets) {
|
|
|
4546
4349
|
}
|
|
4547
4350
|
|
|
4548
4351
|
// src/core/config/resolve-config-target.ts
|
|
4549
|
-
import { existsSync as
|
|
4550
|
-
import { join as
|
|
4352
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
4353
|
+
import { join as join20 } from "path";
|
|
4551
4354
|
function readExtends(absolutePath) {
|
|
4552
4355
|
let parsed;
|
|
4553
4356
|
try {
|
|
4554
|
-
parsed = parseJsonc(
|
|
4357
|
+
parsed = parseJsonc(readFileSync15(absolutePath, "utf8"), absolutePath);
|
|
4555
4358
|
} catch {
|
|
4556
4359
|
return [];
|
|
4557
4360
|
}
|
|
@@ -4565,8 +4368,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
|
|
|
4565
4368
|
let existing;
|
|
4566
4369
|
let existingExtendsSomething = false;
|
|
4567
4370
|
for (const candidate of candidates) {
|
|
4568
|
-
const absolutePath =
|
|
4569
|
-
if (!
|
|
4371
|
+
const absolutePath = join20(moduleDir, candidate);
|
|
4372
|
+
if (!existsSync17(absolutePath)) continue;
|
|
4570
4373
|
const chain = readExtends(absolutePath);
|
|
4571
4374
|
if (existing === void 0) {
|
|
4572
4375
|
existing = candidate;
|
|
@@ -4599,12 +4402,12 @@ var NOT_ADOPTED3 = (configFile, unreadable = null) => ({
|
|
|
4599
4402
|
});
|
|
4600
4403
|
function readAdoption(cwd, options) {
|
|
4601
4404
|
const target = resolveConfigTarget(cwd, options);
|
|
4602
|
-
if (target.reason === "none" || !
|
|
4405
|
+
if (target.reason === "none" || !existsSync18(join21(cwd, target.path))) {
|
|
4603
4406
|
return NOT_ADOPTED3(target.reason === "none" ? null : target.path);
|
|
4604
4407
|
}
|
|
4605
4408
|
let parsed;
|
|
4606
4409
|
try {
|
|
4607
|
-
parsed = parseJsonc(
|
|
4410
|
+
parsed = parseJsonc(readFileSync16(join21(cwd, target.path), "utf8"), target.path);
|
|
4608
4411
|
} catch (error) {
|
|
4609
4412
|
const reason = error instanceof Error ? error.message : String(error);
|
|
4610
4413
|
return NOT_ADOPTED3(target.path, `${target.path} could not be parsed (${reason})`);
|
|
@@ -4638,10 +4441,10 @@ function readLintAdoption(cwd) {
|
|
|
4638
4441
|
}
|
|
4639
4442
|
|
|
4640
4443
|
// src/roles/lint/resolve-oxlint.ts
|
|
4641
|
-
import { existsSync as
|
|
4444
|
+
import { existsSync as existsSync19 } from "fs";
|
|
4642
4445
|
import { createRequire as createRequire3 } from "module";
|
|
4643
|
-
import { delimiter as delimiter2, dirname as
|
|
4644
|
-
import { fileURLToPath
|
|
4446
|
+
import { delimiter as delimiter2, dirname as dirname3, join as join22 } from "path";
|
|
4447
|
+
import { fileURLToPath } from "url";
|
|
4645
4448
|
var PACKAGE_OF = {
|
|
4646
4449
|
oxlint: "oxlint",
|
|
4647
4450
|
tsgolint: "oxlint-tsgolint"
|
|
@@ -4656,7 +4459,7 @@ function canRunTypeAware(cwd) {
|
|
|
4656
4459
|
function oxlintPath(cwd, env) {
|
|
4657
4460
|
const shim = tsgolintShim(cwd);
|
|
4658
4461
|
if (!shim) return env.PATH;
|
|
4659
|
-
return [
|
|
4462
|
+
return [dirname3(shim), env.PATH].filter(Boolean).join(delimiter2);
|
|
4660
4463
|
}
|
|
4661
4464
|
function tsgolintShim(cwd) {
|
|
4662
4465
|
const fromModule = resolveBin(cwd, "tsgolint");
|
|
@@ -4664,32 +4467,49 @@ function tsgolintShim(cwd) {
|
|
|
4664
4467
|
const candidates = [];
|
|
4665
4468
|
for (const owner of ["oxlint-tsgolint", "oxlint"]) {
|
|
4666
4469
|
try {
|
|
4667
|
-
const packageDir =
|
|
4668
|
-
candidates.push(
|
|
4669
|
-
candidates.push(
|
|
4470
|
+
const packageDir = dirname3(require3.resolve(`${owner}/package.json`));
|
|
4471
|
+
candidates.push(join22(packageDir, "node_modules", ".bin", "tsgolint"));
|
|
4472
|
+
candidates.push(join22(packageDir, "..", ".bin", "tsgolint"));
|
|
4670
4473
|
} catch {
|
|
4671
4474
|
}
|
|
4672
4475
|
}
|
|
4673
4476
|
try {
|
|
4674
|
-
const ownRoot =
|
|
4675
|
-
candidates.push(
|
|
4477
|
+
const ownRoot = dirname3(require3.resolve("@hublo/sentinel/package.json"));
|
|
4478
|
+
candidates.push(join22(ownRoot, "node_modules", ".bin", "tsgolint"));
|
|
4676
4479
|
} catch {
|
|
4677
|
-
candidates.push(resolveBin(
|
|
4480
|
+
candidates.push(resolveBin(dirname3(fileURLToPath(import.meta.url)), "tsgolint") ?? "");
|
|
4678
4481
|
}
|
|
4679
|
-
return candidates.find((candidate) => candidate !== "" &&
|
|
4482
|
+
return candidates.find((candidate) => candidate !== "" && existsSync19(candidate));
|
|
4680
4483
|
}
|
|
4681
4484
|
function oxlintSearchPath(cwd) {
|
|
4682
4485
|
return binSearchPath(cwd);
|
|
4683
4486
|
}
|
|
4684
4487
|
|
|
4685
4488
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
4686
|
-
import { existsSync as
|
|
4687
|
-
import { join as
|
|
4489
|
+
import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
|
|
4490
|
+
import { join as join25 } from "path";
|
|
4688
4491
|
|
|
4689
4492
|
// src/roles/lint/eslint-ignores.ts
|
|
4690
|
-
import { existsSync as
|
|
4691
|
-
import { join as
|
|
4493
|
+
import { existsSync as existsSync20, readFileSync as readFileSync17 } from "fs";
|
|
4494
|
+
import { join as join23 } from "path";
|
|
4692
4495
|
import { parseSync as parseSync2 } from "oxc-parser";
|
|
4496
|
+
|
|
4497
|
+
// src/shared/ast.ts
|
|
4498
|
+
var SKIP = /* @__PURE__ */ Symbol("skip-subtree");
|
|
4499
|
+
function walk(node, visit) {
|
|
4500
|
+
if (node === null || typeof node !== "object") return;
|
|
4501
|
+
if (Array.isArray(node)) {
|
|
4502
|
+
for (const entry of node) walk(entry, visit);
|
|
4503
|
+
return;
|
|
4504
|
+
}
|
|
4505
|
+
const current = node;
|
|
4506
|
+
if (typeof current.type === "string" && visit(current) === SKIP) return;
|
|
4507
|
+
for (const value of Object.values(current)) {
|
|
4508
|
+
if (value !== null && typeof value === "object") walk(value, visit);
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4512
|
+
// src/roles/lint/eslint-ignores.ts
|
|
4693
4513
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
4694
4514
|
function toOxlintPattern(pattern) {
|
|
4695
4515
|
return pattern.replace(/^\.\//, "");
|
|
@@ -4700,11 +4520,11 @@ function readRootEslintIgnores(root) {
|
|
|
4700
4520
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
4701
4521
|
}
|
|
4702
4522
|
function readEslintIgnores(cwd) {
|
|
4703
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
4523
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join23(cwd, name)).find((path) => existsSync20(path));
|
|
4704
4524
|
if (!config) return { patterns: [], unresolved: [] };
|
|
4705
4525
|
let source;
|
|
4706
4526
|
try {
|
|
4707
|
-
source =
|
|
4527
|
+
source = readFileSync17(config, "utf8");
|
|
4708
4528
|
} catch {
|
|
4709
4529
|
return { patterns: [], unresolved: [] };
|
|
4710
4530
|
}
|
|
@@ -4735,16 +4555,6 @@ function readEslintIgnores(cwd) {
|
|
|
4735
4555
|
});
|
|
4736
4556
|
return { patterns: [...patterns], unresolved: [...unresolved] };
|
|
4737
4557
|
}
|
|
4738
|
-
function walk(node, visit) {
|
|
4739
|
-
if (!node || typeof node !== "object") return;
|
|
4740
|
-
const current = node;
|
|
4741
|
-
if (typeof current.type === "string") visit(current);
|
|
4742
|
-
for (const key of Object.keys(current)) {
|
|
4743
|
-
const value = current[key];
|
|
4744
|
-
if (Array.isArray(value)) for (const entry of value) walk(entry, visit);
|
|
4745
|
-
else if (value && typeof value === "object") walk(value, visit);
|
|
4746
|
-
}
|
|
4747
|
-
}
|
|
4748
4558
|
function calleeName(node) {
|
|
4749
4559
|
const callee = node.callee;
|
|
4750
4560
|
return callee?.type === "Identifier" ? callee.name : void 0;
|
|
@@ -4805,8 +4615,8 @@ function lintPresetFor(preset) {
|
|
|
4805
4615
|
}
|
|
4806
4616
|
|
|
4807
4617
|
// src/roles/lint/rename-suppressions.ts
|
|
4808
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
4809
|
-
import { join as
|
|
4618
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync18, statSync as statSync3 } from "fs";
|
|
4619
|
+
import { join as join24, relative as relative3 } from "path";
|
|
4810
4620
|
var SOURCE_EXTENSIONS = [
|
|
4811
4621
|
".ts",
|
|
4812
4622
|
".tsx",
|
|
@@ -4853,7 +4663,7 @@ function* sourceFiles(dir) {
|
|
|
4853
4663
|
return;
|
|
4854
4664
|
}
|
|
4855
4665
|
for (const entry of entries) {
|
|
4856
|
-
const full =
|
|
4666
|
+
const full = join24(dir, entry);
|
|
4857
4667
|
let isDirectory;
|
|
4858
4668
|
try {
|
|
4859
4669
|
isDirectory = statSync3(full).isDirectory();
|
|
@@ -4873,7 +4683,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4873
4683
|
for (const file of sourceFiles(cwd)) {
|
|
4874
4684
|
let content;
|
|
4875
4685
|
try {
|
|
4876
|
-
content =
|
|
4686
|
+
content = readFileSync18(file, "utf8");
|
|
4877
4687
|
} catch {
|
|
4878
4688
|
continue;
|
|
4879
4689
|
}
|
|
@@ -4897,7 +4707,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4897
4707
|
}
|
|
4898
4708
|
if (hits.length === 0) return;
|
|
4899
4709
|
found.push({
|
|
4900
|
-
file:
|
|
4710
|
+
file: relative3(cwd, file),
|
|
4901
4711
|
line: index + 1,
|
|
4902
4712
|
from: line,
|
|
4903
4713
|
to: line.replace(rules, renamed.join(", ")),
|
|
@@ -5001,7 +4811,7 @@ function plan2(context) {
|
|
|
5001
4811
|
keys: removableDeps
|
|
5002
4812
|
});
|
|
5003
4813
|
}
|
|
5004
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) =>
|
|
4814
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync21(join25(context.cwd, name)));
|
|
5005
4815
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
5006
4816
|
operations.push(
|
|
5007
4817
|
...nxTargetOperations({
|
|
@@ -5099,7 +4909,7 @@ function lintScripts(cwd) {
|
|
|
5099
4909
|
function committedExtends(cwd) {
|
|
5100
4910
|
try {
|
|
5101
4911
|
const parsed = parseJsonc(
|
|
5102
|
-
|
|
4912
|
+
readFileSync19(join25(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5103
4913
|
LINT_CONFIG_FILE
|
|
5104
4914
|
);
|
|
5105
4915
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -5110,7 +4920,7 @@ function committedExtends(cwd) {
|
|
|
5110
4920
|
function committedIgnorePatterns(cwd) {
|
|
5111
4921
|
try {
|
|
5112
4922
|
const parsed = parseJsonc(
|
|
5113
|
-
|
|
4923
|
+
readFileSync19(join25(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5114
4924
|
LINT_CONFIG_FILE
|
|
5115
4925
|
);
|
|
5116
4926
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -5122,7 +4932,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
5122
4932
|
const isEslintPackage = (name) => name === "eslint" || name === "@types/eslint" || name === "typescript-eslint" || name.startsWith("@typescript-eslint/") || name.startsWith("eslint-plugin-") || name.startsWith("eslint-config-") || name.startsWith("@eslint/");
|
|
5123
4933
|
let manifest;
|
|
5124
4934
|
try {
|
|
5125
|
-
manifest = JSON.parse(
|
|
4935
|
+
manifest = JSON.parse(readFileSync19(join25(cwd, "package.json"), "utf8"));
|
|
5126
4936
|
} catch {
|
|
5127
4937
|
return [];
|
|
5128
4938
|
}
|
|
@@ -5256,23 +5066,23 @@ ${result.stderr ?? ""}`);
|
|
|
5256
5066
|
* build the developer can see, rather than a silent half-adoption they cannot.
|
|
5257
5067
|
*/
|
|
5258
5068
|
writeModuleBaseline(ctx, oxlint, env) {
|
|
5259
|
-
const configPath =
|
|
5260
|
-
const baselinePath =
|
|
5261
|
-
if (!
|
|
5069
|
+
const configPath = join26(ctx.cwd, LINT_CONFIG_FILE);
|
|
5070
|
+
const baselinePath = join26(ctx.cwd, LINT_BASELINE_FILE);
|
|
5071
|
+
if (!existsSync22(configPath)) return;
|
|
5262
5072
|
let config;
|
|
5263
5073
|
try {
|
|
5264
5074
|
config = parseJsonc(
|
|
5265
|
-
|
|
5075
|
+
readFileSync20(configPath, "utf8"),
|
|
5266
5076
|
LINT_CONFIG_FILE
|
|
5267
5077
|
);
|
|
5268
5078
|
} catch {
|
|
5269
5079
|
return;
|
|
5270
5080
|
}
|
|
5271
5081
|
const current = Array.isArray(config.extends) ? config.extends : [];
|
|
5272
|
-
const measurePath =
|
|
5082
|
+
const measurePath = join26(ctx.cwd, LINT_MEASURE_FILE);
|
|
5273
5083
|
let measured;
|
|
5274
5084
|
try {
|
|
5275
|
-
|
|
5085
|
+
writeFileSync2(
|
|
5276
5086
|
measurePath,
|
|
5277
5087
|
`${JSON.stringify({ ...config, extends: extendsWithBaseline(current, false) }, null, 2)}
|
|
5278
5088
|
`
|
|
@@ -5304,7 +5114,7 @@ ${result.stderr ?? ""}`);
|
|
|
5304
5114
|
const holds = holdsFrom(errorCountsByConfigRule(measured.stdout ?? ""));
|
|
5305
5115
|
const next = extendsWithBaseline(current, holds.length > 0);
|
|
5306
5116
|
if (holds.length > 0) {
|
|
5307
|
-
|
|
5117
|
+
writeFileSync2(baselinePath, renderBaseline(holds, readOwnPackage().version));
|
|
5308
5118
|
process.stderr.write(` ${palette(process.stderr).warn(describeHolds(holds))}
|
|
5309
5119
|
`);
|
|
5310
5120
|
} else {
|
|
@@ -5312,12 +5122,12 @@ ${result.stderr ?? ""}`);
|
|
|
5312
5122
|
}
|
|
5313
5123
|
if (next.length !== current.length || next.some((entry, at) => entry !== current[at])) {
|
|
5314
5124
|
config.extends = next;
|
|
5315
|
-
|
|
5125
|
+
writeFileSync2(configPath, `${JSON.stringify(config, null, 2)}
|
|
5316
5126
|
`);
|
|
5317
5127
|
}
|
|
5318
5128
|
}
|
|
5319
5129
|
async run(ctx) {
|
|
5320
|
-
if (!
|
|
5130
|
+
if (!existsSync22(join26(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
5321
5131
|
process.stderr.write(
|
|
5322
5132
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
5323
5133
|
`
|
|
@@ -5465,7 +5275,7 @@ ${result.stderr ?? ""}`);
|
|
|
5465
5275
|
let stub;
|
|
5466
5276
|
try {
|
|
5467
5277
|
stub = parseJsonc(
|
|
5468
|
-
|
|
5278
|
+
readFileSync20(join26(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5469
5279
|
LINT_CONFIG_FILE
|
|
5470
5280
|
);
|
|
5471
5281
|
} catch {
|
|
@@ -5475,7 +5285,7 @@ ${result.stderr ?? ""}`);
|
|
|
5475
5285
|
for (const entry of stub.extends ?? []) {
|
|
5476
5286
|
try {
|
|
5477
5287
|
const preset = parseJsonc(
|
|
5478
|
-
|
|
5288
|
+
readFileSync20(join26(cwd, entry), "utf8"),
|
|
5479
5289
|
entry
|
|
5480
5290
|
);
|
|
5481
5291
|
for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
|
|
@@ -5545,14 +5355,14 @@ ${result.stderr ?? ""}`);
|
|
|
5545
5355
|
let parsed;
|
|
5546
5356
|
try {
|
|
5547
5357
|
parsed = parseJsonc(
|
|
5548
|
-
|
|
5358
|
+
readFileSync20(join26(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5549
5359
|
LINT_CONFIG_FILE
|
|
5550
5360
|
);
|
|
5551
5361
|
} catch {
|
|
5552
5362
|
return void 0;
|
|
5553
5363
|
}
|
|
5554
5364
|
const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
|
|
5555
|
-
return targets.find((target) => !
|
|
5365
|
+
return targets.find((target) => !existsSync22(join26(cwd, target)));
|
|
5556
5366
|
}
|
|
5557
5367
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
5558
5368
|
announceDisabled(preset) {
|
|
@@ -5582,99 +5392,2606 @@ function registerLint() {
|
|
|
5582
5392
|
setDefaultRunner("lint", "oxlint");
|
|
5583
5393
|
}
|
|
5584
5394
|
|
|
5585
|
-
// src/roles/
|
|
5395
|
+
// src/roles/test/adapters/vitest/vitest.adapter.ts
|
|
5586
5396
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5587
|
-
import { existsSync as existsSync25, readFileSync as readFileSync24 } from "fs";
|
|
5588
|
-
import { createRequire as createRequire4 } from "module";
|
|
5589
|
-
import { join as join30 } from "path";
|
|
5590
5397
|
|
|
5591
|
-
// src/roles/
|
|
5592
|
-
var
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5398
|
+
// src/roles/test/presets/toolchain.json
|
|
5399
|
+
var toolchain_default2 = {
|
|
5400
|
+
$comment: "The test toolchain sentinel owns. Same shape and same rule as the build one: a single table drives what `--init` removes from a module's package.json, which import specifiers move, and the name each default import is re-exported under, so the three cannot disagree.",
|
|
5401
|
+
owned: [
|
|
5402
|
+
{
|
|
5403
|
+
package: "vitest",
|
|
5404
|
+
specifier: "vitest/config",
|
|
5405
|
+
named: ["defineConfig", "mergeConfig"],
|
|
5406
|
+
types: ["ConfigEnv", "TestUserConfig", "ViteUserConfig"],
|
|
5407
|
+
why: "the runner itself, declared at the workspace ROOT today, which is what stops a module being testable on its own. Note the package is `vitest` and the specifier is `vitest/config`: the same split as nitro on the build side, and the reason one table holds both."
|
|
5408
|
+
},
|
|
5409
|
+
{
|
|
5410
|
+
package: "vite",
|
|
5411
|
+
specifier: "vite",
|
|
5412
|
+
named: ["loadEnv"],
|
|
5413
|
+
why: "`loadEnv` has no equivalent in `vitest/config`, and two of the six configs import it from vite to build their test env. Re-exported here rather than claimed: the BUILD role owns the `vite` package, and two roles owning one package is how a version conflict gets written down twice."
|
|
5414
|
+
}
|
|
5415
|
+
]
|
|
5606
5416
|
};
|
|
5607
5417
|
|
|
5608
|
-
// src/roles/
|
|
5609
|
-
var
|
|
5610
|
-
compilerOptions: {
|
|
5611
|
-
module: "esnext",
|
|
5612
|
-
moduleResolution: "bundler",
|
|
5613
|
-
types: ["node"],
|
|
5614
|
-
declaration: true
|
|
5615
|
-
},
|
|
5616
|
-
notes: {
|
|
5617
|
-
module: "esnext/bundler like the react and nest presets, matching a monorepo whose libs are bundler-resolved in practice. NOT NodeNext: that would force its stricter ESM rules onto libs the repo resolves through a bundler"
|
|
5618
|
-
}
|
|
5619
|
-
};
|
|
5418
|
+
// src/roles/test/preset-data.ts
|
|
5419
|
+
var OWNED_TEST_PACKAGES = toolchain_default2.owned;
|
|
5620
5420
|
|
|
5621
|
-
// src/roles/
|
|
5622
|
-
var
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
lib: ["ES2023", "DOM", "DOM.Iterable"],
|
|
5626
|
-
module: "ESNext",
|
|
5627
|
-
moduleResolution: "bundler",
|
|
5628
|
-
jsx: "react-jsx",
|
|
5629
|
-
useDefineForClassFields: true,
|
|
5630
|
-
isolatedModules: true,
|
|
5631
|
-
moduleDetection: "force",
|
|
5632
|
-
noEmit: true,
|
|
5633
|
-
noUncheckedSideEffectImports: true
|
|
5634
|
-
},
|
|
5635
|
-
notes: {
|
|
5636
|
-
noEmit: "vite owns the build; tsc only typechecks here"
|
|
5637
|
-
}
|
|
5421
|
+
// src/roles/test/config-policy.ts
|
|
5422
|
+
var TEST_PRESET_SPECIFIERS = {
|
|
5423
|
+
react: "@hublo/sentinel/test/react",
|
|
5424
|
+
nest: "@hublo/sentinel/test/nest"
|
|
5638
5425
|
};
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5426
|
+
function testPresetSpecifier(preset) {
|
|
5427
|
+
return TEST_PRESET_SPECIFIERS[preset] ?? TEST_PRESET_SPECIFIERS.react;
|
|
5428
|
+
}
|
|
5429
|
+
var ALL_TEST_PRESET_SPECIFIERS = Object.values(TEST_PRESET_SPECIFIERS);
|
|
5430
|
+
var TEST_PRESET_SPECIFIER = TEST_PRESET_SPECIFIERS.react;
|
|
5431
|
+
var TEST_CONFIG_FILES = [
|
|
5432
|
+
"vitest.config.ts",
|
|
5433
|
+
"vitest.config.mts",
|
|
5434
|
+
"vitest.config.js",
|
|
5435
|
+
"vitest.config.mjs"
|
|
5436
|
+
];
|
|
5437
|
+
var TEST_OWNED_PACKAGES = OWNED_TEST_PACKAGES;
|
|
5438
|
+
var SENTINEL_OWNED_TEST_PACKAGES = TEST_OWNED_PACKAGES.filter(
|
|
5439
|
+
(entry) => entry.package !== "vite"
|
|
5440
|
+
).map((entry) => entry.package);
|
|
5441
|
+
function testToolchain(preset) {
|
|
5442
|
+
return { presetSpecifier: testPresetSpecifier(preset), owned: TEST_OWNED_PACKAGES };
|
|
5443
|
+
}
|
|
5444
|
+
var TEST_SCRIPT_NAME = "test";
|
|
5445
|
+
var SENTINEL_TEST_COMMAND = "sentinel --run --test --";
|
|
5446
|
+
var VITEST_INVOCATION = /(^|&&\s*|\|\|\s*|;\s*)(?:npx\s+|pnpm\s+(?:exec\s+|dlx\s+)?)?vitest\b([^&|;]*)/g;
|
|
5447
|
+
function rewriteVitestScript(command) {
|
|
5448
|
+
return command.replace(VITEST_INVOCATION, (_match, lead, tail) => {
|
|
5449
|
+
const args = tail.trim().split(/\s+/).filter(Boolean);
|
|
5450
|
+
const oneShot = args[0] === "run" || args.includes("--run");
|
|
5451
|
+
const rest = args.filter((arg, index) => !(index === 0 && arg === "run") && arg !== "--run");
|
|
5452
|
+
const passthrough = oneShot ? rest : ["--watch", ...rest];
|
|
5453
|
+
const suffix = passthrough.length > 0 ? ` -- ${passthrough.join(" ")}` : "";
|
|
5454
|
+
const trailing = /\s$/.test(tail) ? " " : "";
|
|
5455
|
+
return `${lead}sentinel --run --test${suffix}${trailing}`;
|
|
5456
|
+
});
|
|
5457
|
+
}
|
|
5458
|
+
function testTarget(options) {
|
|
5459
|
+
const { configFiles, outputs, dependsOn, configurations } = options;
|
|
5460
|
+
const cached = outputs !== void 0 && outputs.length > 0;
|
|
5461
|
+
return {
|
|
5462
|
+
[TEST_SCRIPT_NAME]: {
|
|
5463
|
+
cache: cached,
|
|
5464
|
+
inputs: ["default", "^default", ...configFiles.map((name) => `{projectRoot}/${name}`)],
|
|
5465
|
+
...cached ? { outputs: [...outputs] } : {},
|
|
5466
|
+
...dependsOn !== void 0 && dependsOn.length > 0 ? { dependsOn: [...dependsOn] } : {},
|
|
5467
|
+
...configurations !== void 0 && Object.keys(configurations).length > 0 ? { configurations: { ...configurations } } : {}
|
|
5662
5468
|
}
|
|
5663
|
-
}
|
|
5664
|
-
}
|
|
5469
|
+
};
|
|
5470
|
+
}
|
|
5665
5471
|
|
|
5666
|
-
// src/roles/
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
};
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5472
|
+
// src/roles/test/jest-migration.ts
|
|
5473
|
+
import { existsSync as existsSync26, readFileSync as readFileSync26 } from "fs";
|
|
5474
|
+
import { join as join32 } from "path";
|
|
5475
|
+
|
|
5476
|
+
// src/roles/test/codemod/migrate-module.ts
|
|
5477
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync23, statSync as statSync5 } from "fs";
|
|
5478
|
+
import { join as join29 } from "path";
|
|
5479
|
+
import { parseSync as parseSync8 } from "oxc-parser";
|
|
5480
|
+
|
|
5481
|
+
// src/roles/test/read-adoption.ts
|
|
5482
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21, readdirSync as readdirSync3 } from "fs";
|
|
5483
|
+
import { join as join27, relative as relative4 } from "path";
|
|
5484
|
+
var JEST_CONFIG_FILES = [
|
|
5485
|
+
"jest.config.ts",
|
|
5486
|
+
"jest.config.js",
|
|
5487
|
+
"jest.prisma.config.ts",
|
|
5488
|
+
"jest.functional.config.ts",
|
|
5489
|
+
"jest.integration-config.ts"
|
|
5490
|
+
];
|
|
5491
|
+
function testConfigFile(cwd) {
|
|
5492
|
+
return TEST_CONFIG_FILES.find((name) => existsSync23(join27(cwd, name)));
|
|
5493
|
+
}
|
|
5494
|
+
function jestConfigFiles(cwd) {
|
|
5495
|
+
return JEST_CONFIG_FILES.filter((name) => existsSync23(join27(cwd, name)));
|
|
5496
|
+
}
|
|
5497
|
+
function rootJestConfigFiles(cwd) {
|
|
5498
|
+
const root = findWorkspaceRoot(cwd);
|
|
5499
|
+
if (root === void 0 || root === cwd) return [];
|
|
5500
|
+
return JEST_CONFIG_FILES.filter((name) => existsSync23(join27(root, name)));
|
|
5501
|
+
}
|
|
5502
|
+
var TEST_FILE = /[.-](spec|test)\.[cm]?[jt]sx?$/;
|
|
5503
|
+
var NOT_WALKED = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".git", ".nx", ".turbo"]);
|
|
5504
|
+
function hasTestFiles(cwd) {
|
|
5505
|
+
return walkTestFiles(cwd, true).length > 0;
|
|
5506
|
+
}
|
|
5507
|
+
function listTestFiles(cwd) {
|
|
5508
|
+
return walkTestFiles(cwd, false).sort();
|
|
5509
|
+
}
|
|
5510
|
+
function walkTestFiles(cwd, stopAtFirst) {
|
|
5511
|
+
const found = [];
|
|
5512
|
+
const stack = [cwd];
|
|
5513
|
+
while (stack.length > 0) {
|
|
5514
|
+
const dir = stack.pop();
|
|
5515
|
+
let entries;
|
|
5516
|
+
try {
|
|
5517
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
5518
|
+
} catch {
|
|
5519
|
+
continue;
|
|
5520
|
+
}
|
|
5521
|
+
for (const entry of entries) {
|
|
5522
|
+
if (entry.isDirectory()) {
|
|
5523
|
+
if (!NOT_WALKED.has(entry.name)) stack.push(join27(dir, entry.name));
|
|
5524
|
+
} else if (TEST_FILE.test(entry.name)) {
|
|
5525
|
+
found.push(relative4(cwd, join27(dir, entry.name)).replaceAll("\\", "/"));
|
|
5526
|
+
if (stopAtFirst) return found;
|
|
5527
|
+
}
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
return found;
|
|
5531
|
+
}
|
|
5532
|
+
function readTestConfigSource(cwd, fileName) {
|
|
5533
|
+
try {
|
|
5534
|
+
return readFileSync21(join27(cwd, fileName), "utf8");
|
|
5535
|
+
} catch {
|
|
5536
|
+
return "";
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
function importsPreset2(source) {
|
|
5540
|
+
return ALL_TEST_PRESET_SPECIFIERS.some((specifier) => {
|
|
5541
|
+
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5542
|
+
return new RegExp(String.raw`(?:from|import)\s*\(?\s*['"]${escaped}['"]`).test(source);
|
|
5543
|
+
});
|
|
5544
|
+
}
|
|
5545
|
+
function readTestAdoption(cwd) {
|
|
5546
|
+
const manifest = readProjectPackageJson(cwd);
|
|
5547
|
+
const jestConfigs = jestConfigFiles(cwd);
|
|
5548
|
+
const configFile = testConfigFile(cwd);
|
|
5549
|
+
const declared = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
5550
|
+
const ownDeclarations = ["vitest", "jest", "@nx/jest", "jest-mock-extended", "@swc/jest"].filter((name) => declared[name] !== void 0).map((name) => ({ name, version: declared[name] }));
|
|
5551
|
+
if (configFile === void 0) {
|
|
5552
|
+
const hasTests = hasTestFiles(cwd);
|
|
5553
|
+
const inheritsRootConfig = hasTests && jestConfigs.length === 0 && rootJestConfigFiles(cwd).length > 0;
|
|
5554
|
+
return {
|
|
5555
|
+
configFile: null,
|
|
5556
|
+
preset: null,
|
|
5557
|
+
adopted: false,
|
|
5558
|
+
conformant: false,
|
|
5559
|
+
drift: [],
|
|
5560
|
+
unreadable: null,
|
|
5561
|
+
state: hasTests ? "jest" : "no-tests",
|
|
5562
|
+
runner: hasTests ? "jest" : "none",
|
|
5563
|
+
jestConfigs,
|
|
5564
|
+
inheritsRootConfig,
|
|
5565
|
+
ownDeclarations
|
|
5566
|
+
};
|
|
5567
|
+
}
|
|
5568
|
+
const source = readTestConfigSource(cwd, configFile);
|
|
5569
|
+
if (source === "") {
|
|
5570
|
+
return {
|
|
5571
|
+
configFile,
|
|
5572
|
+
preset: null,
|
|
5573
|
+
adopted: false,
|
|
5574
|
+
conformant: false,
|
|
5575
|
+
drift: [],
|
|
5576
|
+
unreadable: `${configFile} could not be read`,
|
|
5577
|
+
state: "vitest-own-toolchain",
|
|
5578
|
+
runner: "vitest",
|
|
5579
|
+
jestConfigs,
|
|
5580
|
+
inheritsRootConfig: false,
|
|
5581
|
+
// it has a config of its own, readable or not
|
|
5582
|
+
ownDeclarations
|
|
5583
|
+
};
|
|
5584
|
+
}
|
|
5585
|
+
const adopted = importsPreset2(source);
|
|
5586
|
+
const script = moduleScripts(cwd)["test"];
|
|
5587
|
+
return {
|
|
5588
|
+
configFile,
|
|
5589
|
+
preset: adopted ? "react" : null,
|
|
5590
|
+
adopted,
|
|
5591
|
+
conformant: adopted && script !== void 0 && jestConfigs.length === 0,
|
|
5592
|
+
drift: [],
|
|
5593
|
+
unreadable: null,
|
|
5594
|
+
state: adopted ? "adopted" : "vitest-own-toolchain",
|
|
5595
|
+
runner: "vitest",
|
|
5596
|
+
jestConfigs,
|
|
5597
|
+
inheritsRootConfig: false,
|
|
5598
|
+
// local always wins: this module is out of the root's reach
|
|
5599
|
+
ownDeclarations
|
|
5600
|
+
};
|
|
5601
|
+
}
|
|
5602
|
+
|
|
5603
|
+
// src/roles/test/codemod/migrate-file.ts
|
|
5604
|
+
import { parseSync as parseSync7 } from "oxc-parser";
|
|
5605
|
+
|
|
5606
|
+
// src/roles/test/codemod/assertion-return-value.ts
|
|
5607
|
+
var MEMBER_TYPES = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
5608
|
+
function rootedAtExpect(node) {
|
|
5609
|
+
let current = node;
|
|
5610
|
+
for (let depth = 0; current !== void 0 && depth < 12; depth += 1) {
|
|
5611
|
+
if (current.type === "CallExpression") {
|
|
5612
|
+
const callee = current.callee;
|
|
5613
|
+
if (callee?.type === "Identifier") return callee.name === "expect";
|
|
5614
|
+
current = callee;
|
|
5615
|
+
continue;
|
|
5616
|
+
}
|
|
5617
|
+
if (MEMBER_TYPES.has(current.type)) {
|
|
5618
|
+
current = current.object;
|
|
5619
|
+
continue;
|
|
5620
|
+
}
|
|
5621
|
+
return false;
|
|
5622
|
+
}
|
|
5623
|
+
return false;
|
|
5624
|
+
}
|
|
5625
|
+
function capture(node) {
|
|
5626
|
+
if (node.type !== "VariableDeclaration") return void 0;
|
|
5627
|
+
const declarations = node.declarations ?? [];
|
|
5628
|
+
if (declarations.length !== 1) return void 0;
|
|
5629
|
+
const only = declarations[0];
|
|
5630
|
+
const id = only.id;
|
|
5631
|
+
const init = only.init;
|
|
5632
|
+
if (id?.type !== "Identifier" || init?.type !== "AwaitExpression") return void 0;
|
|
5633
|
+
if (!rootedAtExpect(init.argument)) return void 0;
|
|
5634
|
+
return { name: id.name, init };
|
|
5635
|
+
}
|
|
5636
|
+
function undefinedAssertionOf(node, name) {
|
|
5637
|
+
if (node.type !== "CallExpression") return false;
|
|
5638
|
+
const callee = node.callee;
|
|
5639
|
+
if (callee === void 0 || !MEMBER_TYPES.has(callee.type)) return false;
|
|
5640
|
+
const property = callee.property;
|
|
5641
|
+
if (property?.type !== "Identifier" || property.name !== "toBeUndefined") return false;
|
|
5642
|
+
const object = callee.object;
|
|
5643
|
+
if (object?.type !== "CallExpression") return false;
|
|
5644
|
+
if (object.callee?.type !== "Identifier") return false;
|
|
5645
|
+
if (object.callee.name !== "expect") return false;
|
|
5646
|
+
const [argument] = object.arguments ?? [];
|
|
5647
|
+
return argument?.type === "Identifier" && argument.name === name;
|
|
5648
|
+
}
|
|
5649
|
+
function rewriteAssertionReturnValue(source, program) {
|
|
5650
|
+
const captures = [];
|
|
5651
|
+
walk(program, (node) => {
|
|
5652
|
+
const found = capture(node);
|
|
5653
|
+
if (found === void 0) return;
|
|
5654
|
+
captures.push({
|
|
5655
|
+
name: found.name,
|
|
5656
|
+
declarationStart: node.start,
|
|
5657
|
+
initStart: found.init.start
|
|
5658
|
+
});
|
|
5659
|
+
});
|
|
5660
|
+
const notReferences = [];
|
|
5661
|
+
walk(program, (node) => {
|
|
5662
|
+
if (node.type === "Property" || node.type === "ObjectProperty" || node.type === "PropertyDefinition") {
|
|
5663
|
+
if (node.computed === true) return;
|
|
5664
|
+
const key = node.key;
|
|
5665
|
+
if (key !== void 0)
|
|
5666
|
+
notReferences.push({ start: key.start, end: key.end });
|
|
5667
|
+
return;
|
|
5668
|
+
}
|
|
5669
|
+
if (MEMBER_TYPES.has(node.type) && node.computed !== true) {
|
|
5670
|
+
const property = node.property;
|
|
5671
|
+
if (property !== void 0) {
|
|
5672
|
+
notReferences.push({ start: property.start, end: property.end });
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
});
|
|
5676
|
+
const functions = [];
|
|
5677
|
+
walk(program, (node) => {
|
|
5678
|
+
if (/Function|ArrowFunctionExpression/.test(node.type)) functions.push(node);
|
|
5679
|
+
});
|
|
5680
|
+
const scopeOf = (position) => {
|
|
5681
|
+
const containing = functions.filter((fn) => position >= fn.start && position <= fn.end).sort(
|
|
5682
|
+
(a, b) => a.end - a.start - (b.end - b.start)
|
|
5683
|
+
);
|
|
5684
|
+
const nearest = containing[0];
|
|
5685
|
+
return nearest === void 0 ? { start: 0, end: source.length } : { start: nearest.start, end: nearest.end };
|
|
5686
|
+
};
|
|
5687
|
+
const edits = [];
|
|
5688
|
+
const refusals = [];
|
|
5689
|
+
for (const found of captures) {
|
|
5690
|
+
const readings = [];
|
|
5691
|
+
let readElsewhere = false;
|
|
5692
|
+
const scope = scopeOf(found.declarationStart);
|
|
5693
|
+
walk(program, (node) => {
|
|
5694
|
+
const start = node.start;
|
|
5695
|
+
if (start < scope.start || node.end > scope.end) return;
|
|
5696
|
+
if (undefinedAssertionOf(node, found.name)) {
|
|
5697
|
+
readings.push(node);
|
|
5698
|
+
return;
|
|
5699
|
+
}
|
|
5700
|
+
if (node.type !== "Identifier" || node.name !== found.name) return;
|
|
5701
|
+
if (notReferences.some((span) => start >= span.start && node.end <= span.end)) {
|
|
5702
|
+
return;
|
|
5703
|
+
}
|
|
5704
|
+
const inside = readings.some(
|
|
5705
|
+
(reading) => start >= reading.start && node.end <= reading.end
|
|
5706
|
+
);
|
|
5707
|
+
const isBinding = start < found.initStart;
|
|
5708
|
+
if (!inside && !isBinding) readElsewhere = true;
|
|
5709
|
+
});
|
|
5710
|
+
if (readings.length === 0 || readElsewhere) {
|
|
5711
|
+
if (readings.length > 0) refusals.push(found.name);
|
|
5712
|
+
continue;
|
|
5713
|
+
}
|
|
5714
|
+
edits.push({ start: found.declarationStart, end: found.initStart, text: "" });
|
|
5715
|
+
for (const reading of readings) {
|
|
5716
|
+
const from = reading.start;
|
|
5717
|
+
let to = reading.end;
|
|
5718
|
+
while (to < source.length && /[\s;]/.test(source[to])) to += 1;
|
|
5719
|
+
let head = from;
|
|
5720
|
+
while (head > 0 && /[ \t]/.test(source[head - 1])) head -= 1;
|
|
5721
|
+
if (head > 0 && source[head - 1] === "\n") head -= 1;
|
|
5722
|
+
edits.push({ start: head, end: to, text: "" });
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
let result = source;
|
|
5726
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
5727
|
+
result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
|
|
5728
|
+
}
|
|
5729
|
+
return { source: result, rewritten: captures.length - refusals.length, refusals };
|
|
5730
|
+
}
|
|
5731
|
+
|
|
5732
|
+
// src/roles/test/codemod/constructible-mocks.ts
|
|
5733
|
+
var MEMBER_TYPES2 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
5734
|
+
function mockImplementationArrow(node) {
|
|
5735
|
+
if (node.type !== "CallExpression") return void 0;
|
|
5736
|
+
const callee = node.callee;
|
|
5737
|
+
if (callee === void 0 || !MEMBER_TYPES2.has(callee.type)) return void 0;
|
|
5738
|
+
const property = callee.property;
|
|
5739
|
+
if (property?.type !== "Identifier" || property.name !== "mockImplementation") return void 0;
|
|
5740
|
+
const [argument] = node.arguments ?? [];
|
|
5741
|
+
return argument?.type === "ArrowFunctionExpression" ? argument : void 0;
|
|
5742
|
+
}
|
|
5743
|
+
function assertedMockedClass(source, node) {
|
|
5744
|
+
if (node.type !== "CallExpression") return false;
|
|
5745
|
+
const callee = node.callee;
|
|
5746
|
+
if (callee === void 0 || !MEMBER_TYPES2.has(callee.type)) return false;
|
|
5747
|
+
const object = callee.object;
|
|
5748
|
+
if (object === void 0) return false;
|
|
5749
|
+
return /\bMockedClass\b/.test(source.slice(object.start, object.end));
|
|
5750
|
+
}
|
|
5751
|
+
function readsThis(node) {
|
|
5752
|
+
let found = false;
|
|
5753
|
+
walk(node, (inner) => {
|
|
5754
|
+
if (inner.type === "ThisExpression") found = true;
|
|
5755
|
+
});
|
|
5756
|
+
return found;
|
|
5757
|
+
}
|
|
5758
|
+
function rewriteConstructibleMocks(source, program, factories) {
|
|
5759
|
+
const edits = [];
|
|
5760
|
+
const refusals = [];
|
|
5761
|
+
let rewritten = 0;
|
|
5762
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5763
|
+
const subtrees = [...factories];
|
|
5764
|
+
walk(program, (node) => {
|
|
5765
|
+
if (assertedMockedClass(source, node)) subtrees.push(node);
|
|
5766
|
+
});
|
|
5767
|
+
for (const subtree of subtrees) {
|
|
5768
|
+
walk(subtree, (node) => {
|
|
5769
|
+
const arrow = mockImplementationArrow(node);
|
|
5770
|
+
if (arrow === void 0 || seen.has(arrow)) return;
|
|
5771
|
+
seen.add(arrow);
|
|
5772
|
+
if (readsThis(arrow)) {
|
|
5773
|
+
refusals.push({
|
|
5774
|
+
binding: "mockImplementation",
|
|
5775
|
+
reason: `a mock implementation reads \`this\`, so it cannot become a function expression, and an arrow cannot be constructed by Vitest. This one needs a human.`
|
|
5776
|
+
});
|
|
5777
|
+
return;
|
|
5778
|
+
}
|
|
5779
|
+
const params = arrow.params ?? [];
|
|
5780
|
+
const paramsText = params.length === 0 ? "" : source.slice(params[0]?.start, params[params.length - 1]?.end);
|
|
5781
|
+
const body = arrow.body;
|
|
5782
|
+
const bodyText = source.slice(body.start, body.end);
|
|
5783
|
+
const text = body.type === "BlockStatement" ? `function (${paramsText}) ${bodyText}` : `function (${paramsText}) {
|
|
5784
|
+
return ${bodyText}
|
|
5785
|
+
}`;
|
|
5786
|
+
edits.push({ start: arrow.start, end: arrow.end, text });
|
|
5787
|
+
rewritten += 1;
|
|
5788
|
+
});
|
|
5789
|
+
}
|
|
5790
|
+
let result = source;
|
|
5791
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
5792
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
5793
|
+
}
|
|
5794
|
+
return { source: result, rewritten, refusals };
|
|
5795
|
+
}
|
|
5796
|
+
|
|
5797
|
+
// src/roles/test/codemod/deep-imports.ts
|
|
5798
|
+
var DEEP_ENTRIES = {
|
|
5799
|
+
"jest-mock-extended/lib/Mock": { root: "jest-mock-extended", defaultExport: "mock" }
|
|
5800
|
+
};
|
|
5801
|
+
function normaliseDeepImports(source) {
|
|
5802
|
+
let result = source;
|
|
5803
|
+
let rewritten = 0;
|
|
5804
|
+
for (const [deep, { root, defaultExport }] of Object.entries(DEEP_ENTRIES)) {
|
|
5805
|
+
const quoted = deep.replaceAll("/", "\\/");
|
|
5806
|
+
const defaultImport = new RegExp(`import\\s+(\\w+)\\s+from\\s+(['"])${quoted}\\2`, "g");
|
|
5807
|
+
result = result.replace(defaultImport, (_whole, binding, quote) => {
|
|
5808
|
+
rewritten += 1;
|
|
5809
|
+
const named = binding === defaultExport ? defaultExport : `${defaultExport} as ${binding}`;
|
|
5810
|
+
return `import { ${named} } from ${quote}${root}${quote}`;
|
|
5811
|
+
});
|
|
5812
|
+
const anyImport = new RegExp(`(from\\s+)(['"])${quoted}\\2`, "g");
|
|
5813
|
+
result = result.replace(anyImport, (_whole, from, quote) => {
|
|
5814
|
+
rewritten += 1;
|
|
5815
|
+
return `${from}${quote}${root}${quote}`;
|
|
5816
|
+
});
|
|
5817
|
+
}
|
|
5818
|
+
return { source: result, rewritten };
|
|
5819
|
+
}
|
|
5820
|
+
|
|
5821
|
+
// src/roles/test/codemod/each-table-rows.ts
|
|
5822
|
+
var MEMBER_TYPES3 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
5823
|
+
var RUNNERS = /* @__PURE__ */ new Set(["it", "test", "describe"]);
|
|
5824
|
+
function eachTable(node) {
|
|
5825
|
+
if (node.type !== "CallExpression") return void 0;
|
|
5826
|
+
const callee = node.callee;
|
|
5827
|
+
if (callee === void 0 || !MEMBER_TYPES3.has(callee.type)) return void 0;
|
|
5828
|
+
const property = callee.property;
|
|
5829
|
+
if (property?.type !== "Identifier" || property.name !== "each") return void 0;
|
|
5830
|
+
let object = callee.object;
|
|
5831
|
+
while (object !== void 0 && MEMBER_TYPES3.has(object.type)) {
|
|
5832
|
+
object = object.object;
|
|
5833
|
+
}
|
|
5834
|
+
if (object?.type !== "Identifier" || !RUNNERS.has(object.name)) return void 0;
|
|
5835
|
+
const [table] = node.arguments ?? [];
|
|
5836
|
+
return table?.type === "ArrayExpression" ? table : void 0;
|
|
5837
|
+
}
|
|
5838
|
+
function rewriteEachTableRows(source, program) {
|
|
5839
|
+
const edits = [];
|
|
5840
|
+
walk(program, (node) => {
|
|
5841
|
+
const table = eachTable(node);
|
|
5842
|
+
if (table === void 0) return;
|
|
5843
|
+
const rows = table.elements ?? [];
|
|
5844
|
+
if (rows.length === 0 || rows.some((row) => row === null)) return;
|
|
5845
|
+
const arrays = rows.filter((row) => row.type === "ArrayExpression").length;
|
|
5846
|
+
if (arrays === 0 || arrays === rows.length) return;
|
|
5847
|
+
const wrapped = rows.map((row) => source.slice(row.start, row.end)).map((text) => `[${text}]`).join(", ");
|
|
5848
|
+
edits.push({ start: table.start, end: table.end, text: `[${wrapped}]` });
|
|
5849
|
+
});
|
|
5850
|
+
let result = source;
|
|
5851
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
5852
|
+
result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
|
|
5853
|
+
}
|
|
5854
|
+
return { source: result, rewritten: edits.length };
|
|
5855
|
+
}
|
|
5856
|
+
|
|
5857
|
+
// src/roles/test/codemod/each-title.ts
|
|
5858
|
+
var MEMBER_TYPES4 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
5859
|
+
function eachTitle(node) {
|
|
5860
|
+
if (node.type !== "CallExpression") return void 0;
|
|
5861
|
+
const inner = node.callee;
|
|
5862
|
+
if (inner?.type !== "CallExpression") return void 0;
|
|
5863
|
+
const member = inner.callee;
|
|
5864
|
+
if (member === void 0 || !MEMBER_TYPES4.has(member.type)) return void 0;
|
|
5865
|
+
const property = member.property;
|
|
5866
|
+
if (property?.type !== "Identifier" || property.name !== "each") return void 0;
|
|
5867
|
+
const [title] = node.arguments ?? [];
|
|
5868
|
+
if (title === void 0) return void 0;
|
|
5869
|
+
return title.type === "StringLiteral" || title.type === "Literal" || title.type === "TemplateLiteral" ? title : void 0;
|
|
5870
|
+
}
|
|
5871
|
+
function rewriteSpecifiers(text) {
|
|
5872
|
+
let result = "";
|
|
5873
|
+
let rewritten = 0;
|
|
5874
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
5875
|
+
if (text[index] !== "%") {
|
|
5876
|
+
result += text[index];
|
|
5877
|
+
continue;
|
|
5878
|
+
}
|
|
5879
|
+
const next = text[index + 1];
|
|
5880
|
+
if (next === "%") {
|
|
5881
|
+
result += "%%";
|
|
5882
|
+
index += 1;
|
|
5883
|
+
continue;
|
|
5884
|
+
}
|
|
5885
|
+
if (next === "p") {
|
|
5886
|
+
result += "%j";
|
|
5887
|
+
rewritten += 1;
|
|
5888
|
+
index += 1;
|
|
5889
|
+
continue;
|
|
5890
|
+
}
|
|
5891
|
+
result += "%";
|
|
5892
|
+
}
|
|
5893
|
+
return { text: result, rewritten };
|
|
5894
|
+
}
|
|
5895
|
+
function rewriteEachTitles(source, program) {
|
|
5896
|
+
const edits = [];
|
|
5897
|
+
let rewritten = 0;
|
|
5898
|
+
walk(program, (node) => {
|
|
5899
|
+
const title = eachTitle(node);
|
|
5900
|
+
if (title === void 0) return;
|
|
5901
|
+
const start = title.start;
|
|
5902
|
+
const end = title.end;
|
|
5903
|
+
const replacement = rewriteSpecifiers(source.slice(start, end));
|
|
5904
|
+
if (replacement.rewritten === 0) return;
|
|
5905
|
+
edits.push({ start, end, text: replacement.text });
|
|
5906
|
+
rewritten += replacement.rewritten;
|
|
5907
|
+
});
|
|
5908
|
+
let result = source;
|
|
5909
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
5910
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
5911
|
+
}
|
|
5912
|
+
return { source: result, rewritten };
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5915
|
+
// src/roles/test/codemod/empty-mock-implementation.ts
|
|
5916
|
+
var METHODS = /* @__PURE__ */ new Set(["mockImplementation", "mockImplementationOnce"]);
|
|
5917
|
+
var MEMBER_TYPES5 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
5918
|
+
function rewriteEmptyMockImplementation(source, program) {
|
|
5919
|
+
const insertAt = [];
|
|
5920
|
+
walk(program, (node) => {
|
|
5921
|
+
if (node.type !== "CallExpression") return;
|
|
5922
|
+
if ((node.arguments ?? []).length > 0) return;
|
|
5923
|
+
const callee = node.callee;
|
|
5924
|
+
if (callee === void 0 || !MEMBER_TYPES5.has(callee.type)) return;
|
|
5925
|
+
const property = callee.property;
|
|
5926
|
+
if (property?.type !== "Identifier" || !METHODS.has(property.name)) return;
|
|
5927
|
+
insertAt.push(node.end - 1);
|
|
5928
|
+
});
|
|
5929
|
+
let result = source;
|
|
5930
|
+
for (const position of [...insertAt].sort((left, right) => right - left)) {
|
|
5931
|
+
result = `${result.slice(0, position)}() => undefined${result.slice(position)}`;
|
|
5932
|
+
}
|
|
5933
|
+
return { source: result, rewritten: insertAt.length };
|
|
5934
|
+
}
|
|
5935
|
+
|
|
5936
|
+
// src/roles/test/codemod/scope.ts
|
|
5937
|
+
function readIdentifiers(node, runsLater) {
|
|
5938
|
+
const names = [];
|
|
5939
|
+
walk(node, (current) => {
|
|
5940
|
+
if (current !== node && runsLater?.(current) === true) return SKIP;
|
|
5941
|
+
if (current.type === "Property" || current.type === "ObjectProperty") {
|
|
5942
|
+
if (current.shorthand === true) {
|
|
5943
|
+
names.push(...readIdentifiers(current.value, runsLater));
|
|
5944
|
+
return SKIP;
|
|
5945
|
+
}
|
|
5946
|
+
if (current.computed === true) return;
|
|
5947
|
+
const key = current.key;
|
|
5948
|
+
if (key?.type !== "Identifier") return;
|
|
5949
|
+
names.push(...readIdentifiers(current.value, runsLater));
|
|
5950
|
+
return SKIP;
|
|
5951
|
+
}
|
|
5952
|
+
if (current.type === "MemberExpression" || current.type === "StaticMemberExpression") {
|
|
5953
|
+
if (current.computed === true) return;
|
|
5954
|
+
names.push(...readIdentifiers(current.object, runsLater));
|
|
5955
|
+
return SKIP;
|
|
5956
|
+
}
|
|
5957
|
+
if (current.type === "Identifier") {
|
|
5958
|
+
names.push(current.name);
|
|
5959
|
+
return SKIP;
|
|
5960
|
+
}
|
|
5961
|
+
return void 0;
|
|
5962
|
+
});
|
|
5963
|
+
return names;
|
|
5964
|
+
}
|
|
5965
|
+
function patternNames(pattern) {
|
|
5966
|
+
const names = [];
|
|
5967
|
+
const visit = (node) => {
|
|
5968
|
+
if (node === null || typeof node !== "object") return;
|
|
5969
|
+
const current = node;
|
|
5970
|
+
switch (current.type) {
|
|
5971
|
+
case "Identifier":
|
|
5972
|
+
names.push(current.name);
|
|
5973
|
+
return;
|
|
5974
|
+
case "ObjectPattern":
|
|
5975
|
+
for (const property of current.properties ?? []) {
|
|
5976
|
+
visit(property.type === "RestElement" ? property.argument : property.value);
|
|
5977
|
+
}
|
|
5978
|
+
return;
|
|
5979
|
+
case "ArrayPattern":
|
|
5980
|
+
for (const element of current.elements ?? []) visit(element);
|
|
5981
|
+
return;
|
|
5982
|
+
case "AssignmentPattern":
|
|
5983
|
+
visit(current.left);
|
|
5984
|
+
return;
|
|
5985
|
+
case "RestElement":
|
|
5986
|
+
visit(current.argument);
|
|
5987
|
+
return;
|
|
5988
|
+
default:
|
|
5989
|
+
return;
|
|
5990
|
+
}
|
|
5991
|
+
};
|
|
5992
|
+
visit(pattern);
|
|
5993
|
+
return names;
|
|
5994
|
+
}
|
|
5995
|
+
function moduleBindings(program) {
|
|
5996
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
5997
|
+
for (const statement of program.body ?? []) {
|
|
5998
|
+
if (statement.type !== "VariableDeclaration") continue;
|
|
5999
|
+
for (const declarator of statement.declarations ?? []) {
|
|
6000
|
+
const init = declarator.init ?? void 0;
|
|
6001
|
+
for (const name of patternNames(declarator.id)) bindings.set(name, init);
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
return bindings;
|
|
6005
|
+
}
|
|
6006
|
+
function importedNames(program) {
|
|
6007
|
+
const names = /* @__PURE__ */ new Set();
|
|
6008
|
+
for (const statement of program.body ?? []) {
|
|
6009
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
6010
|
+
for (const specifier of statement.specifiers ?? []) {
|
|
6011
|
+
const local = specifier.local;
|
|
6012
|
+
if (local?.type === "Identifier") names.add(local.name);
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
6015
|
+
return names;
|
|
6016
|
+
}
|
|
6017
|
+
function declaredInside(factory) {
|
|
6018
|
+
const names = /* @__PURE__ */ new Set();
|
|
6019
|
+
walk(factory, (node) => {
|
|
6020
|
+
if (node.type === "VariableDeclarator" || node.type === "FunctionDeclaration") {
|
|
6021
|
+
for (const name of patternNames(node.id)) names.add(name);
|
|
6022
|
+
}
|
|
6023
|
+
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") {
|
|
6024
|
+
for (const parameter of node.params ?? []) {
|
|
6025
|
+
for (const name of patternNames(parameter)) names.add(name);
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
});
|
|
6029
|
+
return names;
|
|
6030
|
+
}
|
|
6031
|
+
var FUNCTION_TYPES = /* @__PURE__ */ new Set([
|
|
6032
|
+
"ArrowFunctionExpression",
|
|
6033
|
+
"FunctionExpression",
|
|
6034
|
+
"FunctionDeclaration"
|
|
6035
|
+
]);
|
|
6036
|
+
function capturedBindings(factory, bindings) {
|
|
6037
|
+
const own = declaredInside(factory);
|
|
6038
|
+
const invoked = /* @__PURE__ */ new Set();
|
|
6039
|
+
walk(factory, (node) => {
|
|
6040
|
+
if (node.type !== "CallExpression") return;
|
|
6041
|
+
let callee = node.callee;
|
|
6042
|
+
while (callee?.type === "ParenthesizedExpression") callee = callee.expression;
|
|
6043
|
+
if (callee !== void 0 && FUNCTION_TYPES.has(callee.type)) invoked.add(callee);
|
|
6044
|
+
});
|
|
6045
|
+
const runsLater = (candidate) => FUNCTION_TYPES.has(candidate.type) && !invoked.has(candidate);
|
|
6046
|
+
const captured = /* @__PURE__ */ new Set();
|
|
6047
|
+
for (const name of readIdentifiers(factory, runsLater)) {
|
|
6048
|
+
if (bindings.has(name) && !own.has(name)) captured.add(name);
|
|
6049
|
+
}
|
|
6050
|
+
return [...captured];
|
|
6051
|
+
}
|
|
6052
|
+
function hoistable(name, bindings, imported, seen = /* @__PURE__ */ new Set()) {
|
|
6053
|
+
if (seen.has(name)) return true;
|
|
6054
|
+
seen.add(name);
|
|
6055
|
+
const init = bindings.get(name);
|
|
6056
|
+
if (init === void 0) return false;
|
|
6057
|
+
for (const read3 of readIdentifiers(init)) {
|
|
6058
|
+
if (imported.has(read3)) return false;
|
|
6059
|
+
if (bindings.has(read3) && !hoistable(read3, bindings, imported, seen)) return false;
|
|
6060
|
+
}
|
|
6061
|
+
return true;
|
|
6062
|
+
}
|
|
6063
|
+
|
|
6064
|
+
// src/roles/test/codemod/hoist.ts
|
|
6065
|
+
function simpleDeclarations(program) {
|
|
6066
|
+
const found = /* @__PURE__ */ new Map();
|
|
6067
|
+
for (const statement of program.body ?? []) {
|
|
6068
|
+
if (statement.type !== "VariableDeclaration") continue;
|
|
6069
|
+
const declarations = statement.declarations ?? [];
|
|
6070
|
+
if (declarations.length !== 1) continue;
|
|
6071
|
+
const declarator = declarations[0];
|
|
6072
|
+
const id = declarator.id;
|
|
6073
|
+
const init = declarator.init;
|
|
6074
|
+
if (id?.type !== "Identifier" || init === void 0 || init === null) continue;
|
|
6075
|
+
found.set(id.name, { statement, init, kind: statement.kind ?? "const" });
|
|
6076
|
+
}
|
|
6077
|
+
return found;
|
|
6078
|
+
}
|
|
6079
|
+
function interdependent(names, bindings) {
|
|
6080
|
+
for (const name of names) {
|
|
6081
|
+
for (const read3 of readIdentifiers(bindings.get(name))) {
|
|
6082
|
+
if (read3 !== name && names.includes(read3)) return `${name} reads ${read3}`;
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6085
|
+
return void 0;
|
|
6086
|
+
}
|
|
6087
|
+
function statementSpan(source, statement) {
|
|
6088
|
+
let end = statement.end;
|
|
6089
|
+
if (source[end] === "\r") end++;
|
|
6090
|
+
if (source[end] === "\n") end++;
|
|
6091
|
+
return { start: statement.start, end };
|
|
6092
|
+
}
|
|
6093
|
+
function hoistCapturedBindings(source, program, factories, bindings, imported) {
|
|
6094
|
+
const captured = [...new Set(factories.flatMap((factory) => capturedBindings(factory, bindings)))];
|
|
6095
|
+
if (captured.length === 0) return { source, hoisted: [], refusals: [] };
|
|
6096
|
+
const refusals = [];
|
|
6097
|
+
for (const name of captured) {
|
|
6098
|
+
if (hoistable(name, bindings, imported)) continue;
|
|
6099
|
+
refusals.push({
|
|
6100
|
+
binding: name,
|
|
6101
|
+
reason: bindings.get(name) === void 0 ? `it is declared without an initializer, so there is nothing for vi.hoisted to carry` : `its initializer reads something imported, so it cannot live above the import graph`
|
|
6102
|
+
});
|
|
6103
|
+
}
|
|
6104
|
+
const declarations = simpleDeclarations(program);
|
|
6105
|
+
for (const name of captured) {
|
|
6106
|
+
if (!declarations.has(name) && !refusals.some((refusal) => refusal.binding === name)) {
|
|
6107
|
+
refusals.push({
|
|
6108
|
+
binding: name,
|
|
6109
|
+
reason: `it is not declared as a plain \`${name} = ...\` at module scope`
|
|
6110
|
+
});
|
|
6111
|
+
}
|
|
6112
|
+
}
|
|
6113
|
+
const cycle = interdependent(captured, bindings);
|
|
6114
|
+
if (cycle !== void 0) {
|
|
6115
|
+
refusals.push({
|
|
6116
|
+
binding: captured.join(", "),
|
|
6117
|
+
reason: `${cycle}, and an object literal cannot refer to its own keys`
|
|
6118
|
+
});
|
|
6119
|
+
}
|
|
6120
|
+
if (refusals.length > 0) return { source, hoisted: [], refusals };
|
|
6121
|
+
const ordered = captured.map((name) => ({
|
|
6122
|
+
name,
|
|
6123
|
+
...declarations.get(name)
|
|
6124
|
+
})).sort((left, right) => left.statement.start - right.statement.start);
|
|
6125
|
+
const first = ordered[0];
|
|
6126
|
+
const semi = source.slice(first.statement.start, first.statement.end).trimEnd().endsWith(";");
|
|
6127
|
+
const end = semi ? ";" : "";
|
|
6128
|
+
const initOf = ({ init }) => source.slice(init.start, init.end);
|
|
6129
|
+
const constants = ordered.filter(({ kind }) => kind === "const");
|
|
6130
|
+
const objectLine = constants.length === 0 ? void 0 : `const { ${constants.map(({ name }) => name).join(", ")} } = vi.hoisted(() => ({ ${constants.map((entry) => `${entry.name}: ${initOf(entry)}`).join(", ")} }))${end}
|
|
6131
|
+
`;
|
|
6132
|
+
const lines = [];
|
|
6133
|
+
let objectEmitted = false;
|
|
6134
|
+
for (const entry of ordered) {
|
|
6135
|
+
if (entry.kind === "const") {
|
|
6136
|
+
if (!objectEmitted && objectLine !== void 0) {
|
|
6137
|
+
lines.push(objectLine);
|
|
6138
|
+
objectEmitted = true;
|
|
6139
|
+
}
|
|
6140
|
+
continue;
|
|
6141
|
+
}
|
|
6142
|
+
lines.push(`${entry.kind} ${entry.name} = vi.hoisted(() => ${initOf(entry)})${end}
|
|
6143
|
+
`);
|
|
6144
|
+
}
|
|
6145
|
+
const hoistedLine = lines.join("");
|
|
6146
|
+
const edits = ordered.map(({ statement }, index) => {
|
|
6147
|
+
const span = statementSpan(source, statement);
|
|
6148
|
+
return { ...span, text: index === 0 ? hoistedLine : "" };
|
|
6149
|
+
});
|
|
6150
|
+
let rewritten = source;
|
|
6151
|
+
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
6152
|
+
rewritten = rewritten.slice(0, edit.start) + edit.text + rewritten.slice(edit.end);
|
|
6153
|
+
}
|
|
6154
|
+
return { source: rewritten, hoisted: ordered.map(({ name }) => name), refusals: [] };
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6157
|
+
// src/roles/test/codemod/hook-return-value.ts
|
|
6158
|
+
var HOOKS = /* @__PURE__ */ new Set(["beforeEach", "afterEach", "beforeAll", "afterAll"]);
|
|
6159
|
+
var MEMBER_TYPES6 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6160
|
+
function hookCallback(node) {
|
|
6161
|
+
if (node.type !== "CallExpression") return void 0;
|
|
6162
|
+
const callee = node.callee;
|
|
6163
|
+
if (callee?.type !== "Identifier" || !HOOKS.has(callee.name)) return void 0;
|
|
6164
|
+
const [callback] = node.arguments ?? [];
|
|
6165
|
+
return callback?.type === "ArrowFunctionExpression" ? callback : void 0;
|
|
6166
|
+
}
|
|
6167
|
+
function configuresAMock(body) {
|
|
6168
|
+
if (body.type !== "CallExpression") return false;
|
|
6169
|
+
const callee = body.callee;
|
|
6170
|
+
if (callee === void 0 || !MEMBER_TYPES6.has(callee.type)) return false;
|
|
6171
|
+
const property = callee.property;
|
|
6172
|
+
return property?.type === "Identifier" && /^mock[A-Z]/.test(property.name);
|
|
6173
|
+
}
|
|
6174
|
+
function rewriteHookReturnValue(source, program) {
|
|
6175
|
+
const edits = [];
|
|
6176
|
+
walk(program, (node) => {
|
|
6177
|
+
const callback = hookCallback(node);
|
|
6178
|
+
if (callback === void 0 || callback.async === true) return;
|
|
6179
|
+
const body = callback.body;
|
|
6180
|
+
if (body === void 0 || body.type === "BlockStatement") return;
|
|
6181
|
+
if (!configuresAMock(body)) return;
|
|
6182
|
+
const start = body.start;
|
|
6183
|
+
const end = body.end;
|
|
6184
|
+
edits.push({ start, end, text: `{
|
|
6185
|
+
${source.slice(start, end)}
|
|
6186
|
+
}` });
|
|
6187
|
+
});
|
|
6188
|
+
let result = source;
|
|
6189
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
6190
|
+
result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
|
|
6191
|
+
}
|
|
6192
|
+
return { source: result, rewritten: edits.length };
|
|
6193
|
+
}
|
|
6194
|
+
|
|
6195
|
+
// src/roles/test/codemod/inject-type-import.ts
|
|
6196
|
+
import { parseSync as parseSync4 } from "oxc-parser";
|
|
6197
|
+
|
|
6198
|
+
// src/roles/test/codemod/rename.ts
|
|
6199
|
+
import { parseSync as parseSync3 } from "oxc-parser";
|
|
6200
|
+
var VITEST_TYPE_SOURCE = "vitest";
|
|
6201
|
+
var RUNTIME_RENAMES = /* @__PURE__ */ new Set([
|
|
6202
|
+
// Mocks
|
|
6203
|
+
"clearAllMocks",
|
|
6204
|
+
"doMock",
|
|
6205
|
+
"fn",
|
|
6206
|
+
"mock",
|
|
6207
|
+
"mocked",
|
|
6208
|
+
"resetAllMocks",
|
|
6209
|
+
"resetModules",
|
|
6210
|
+
"restoreAllMocks",
|
|
6211
|
+
"spyOn",
|
|
6212
|
+
"unmock",
|
|
6213
|
+
/*
|
|
6214
|
+
* Timers, as a family rather than as the two the corpus happens to use today. Running this over
|
|
6215
|
+
* the repo reported `jest.runAllTimers` as unknown, which was a hole in the table rather than a
|
|
6216
|
+
* finding about the code. Each name here was then checked against a running Vitest, so adding the
|
|
6217
|
+
* siblings is not speculation: a file gaining one tomorrow gets the same answer as one using it
|
|
6218
|
+
* today, instead of a report about a member that was always going to be a rename.
|
|
6219
|
+
*/
|
|
6220
|
+
"advanceTimersByTime",
|
|
6221
|
+
"advanceTimersByTimeAsync",
|
|
6222
|
+
"advanceTimersToNextTimer",
|
|
6223
|
+
"clearAllTimers",
|
|
6224
|
+
"getRealSystemTime",
|
|
6225
|
+
"getTimerCount",
|
|
6226
|
+
"runAllTimers",
|
|
6227
|
+
"runAllTimersAsync",
|
|
6228
|
+
"runOnlyPendingTimers",
|
|
6229
|
+
"setSystemTime",
|
|
6230
|
+
"useFakeTimers",
|
|
6231
|
+
"useRealTimers"
|
|
6232
|
+
]);
|
|
6233
|
+
var TYPE_RENAMES = {
|
|
6234
|
+
Mock: "Mock",
|
|
6235
|
+
MockedClass: "MockedClass",
|
|
6236
|
+
MockedFunction: "MockedFunction",
|
|
6237
|
+
MockedObject: "MockedObject",
|
|
6238
|
+
Mocked: "Mocked",
|
|
6239
|
+
SpiedFunction: "MockInstance",
|
|
6240
|
+
SpyInstance: "MockInstance"
|
|
6241
|
+
};
|
|
6242
|
+
var NAMED_RULE = {
|
|
6243
|
+
requireActual: `await vi.importActual(...), which makes the surrounding factory async`,
|
|
6244
|
+
requireMock: `await vi.importMock(...), which makes the surrounding factory async`,
|
|
6245
|
+
setTimeout: `vi.setConfig({ testTimeout: ... }), which is configuration rather than a call`
|
|
6246
|
+
};
|
|
6247
|
+
var OWNED_BY_A_LATER_PASS = /* @__PURE__ */ new Set(["isolateModules", "isolateModulesAsync"]);
|
|
6248
|
+
function lineOf(source, offset) {
|
|
6249
|
+
let line = 1;
|
|
6250
|
+
for (let index = 0; index < offset && index < source.length; index++) {
|
|
6251
|
+
if (source[index] === "\n") line++;
|
|
6252
|
+
}
|
|
6253
|
+
return line;
|
|
6254
|
+
}
|
|
6255
|
+
function valueEdit(node, source, outcome) {
|
|
6256
|
+
const object = node.object;
|
|
6257
|
+
const property = node.property;
|
|
6258
|
+
if (object?.type !== "Identifier" || object.name !== "jest") return void 0;
|
|
6259
|
+
if (property?.type !== "Identifier") return void 0;
|
|
6260
|
+
const member = property.name;
|
|
6261
|
+
if (RUNTIME_RENAMES.has(member)) {
|
|
6262
|
+
outcome.rewritten++;
|
|
6263
|
+
return { start: object.start, end: object.end, text: "vi" };
|
|
6264
|
+
}
|
|
6265
|
+
if (OWNED_BY_A_LATER_PASS.has(member)) return void 0;
|
|
6266
|
+
outcome.unhandled.push({
|
|
6267
|
+
member: `jest.${member}`,
|
|
6268
|
+
line: lineOf(source, object.start),
|
|
6269
|
+
...NAMED_RULE[member] === void 0 ? {} : { rule: NAMED_RULE[member] }
|
|
6270
|
+
});
|
|
6271
|
+
return void 0;
|
|
6272
|
+
}
|
|
6273
|
+
function typeEdit(node, source, outcome, needed) {
|
|
6274
|
+
const left = node.left;
|
|
6275
|
+
const right = node.right;
|
|
6276
|
+
if (left?.type !== "Identifier" || left.name !== "jest") return void 0;
|
|
6277
|
+
if (right?.type !== "Identifier") return void 0;
|
|
6278
|
+
const member = right.name;
|
|
6279
|
+
const replacement = TYPE_RENAMES[member];
|
|
6280
|
+
if (replacement === void 0) {
|
|
6281
|
+
outcome.unhandled.push({ member: `jest.${member}`, line: lineOf(source, node.start) });
|
|
6282
|
+
return void 0;
|
|
6283
|
+
}
|
|
6284
|
+
needed.add(replacement);
|
|
6285
|
+
outcome.rewritten++;
|
|
6286
|
+
return { start: node.start, end: node.end, text: replacement };
|
|
6287
|
+
}
|
|
6288
|
+
function renameJestToVitest(fileName, source) {
|
|
6289
|
+
const outcome = { source, typeImports: [], rewritten: 0, unhandled: [] };
|
|
6290
|
+
const { program, errors } = parseSync3(fileName, source, { sourceType: "module" });
|
|
6291
|
+
if (errors.length > 0) return outcome;
|
|
6292
|
+
const edits = [];
|
|
6293
|
+
const needed = /* @__PURE__ */ new Set();
|
|
6294
|
+
walk(program, (node) => {
|
|
6295
|
+
if (node.type === "MemberExpression" || node.type === "StaticMemberExpression") {
|
|
6296
|
+
const edit = valueEdit(node, source, outcome);
|
|
6297
|
+
if (edit !== void 0) edits.push(edit);
|
|
6298
|
+
return;
|
|
6299
|
+
}
|
|
6300
|
+
if (node.type === "TSQualifiedName") {
|
|
6301
|
+
const edit = typeEdit(node, source, outcome, needed);
|
|
6302
|
+
if (edit !== void 0) edits.push(edit);
|
|
6303
|
+
}
|
|
6304
|
+
});
|
|
6305
|
+
outcome.typeImports = [...needed].sort();
|
|
6306
|
+
let rewritten = source;
|
|
6307
|
+
for (const edit of edits.sort((a, b) => b.start - a.start)) {
|
|
6308
|
+
rewritten = rewritten.slice(0, edit.start) + edit.text + rewritten.slice(edit.end);
|
|
6309
|
+
}
|
|
6310
|
+
outcome.source = rewritten;
|
|
6311
|
+
return outcome;
|
|
6312
|
+
}
|
|
6313
|
+
|
|
6314
|
+
// src/roles/test/codemod/inject-type-import.ts
|
|
6315
|
+
function renderTypeImport(names, semi) {
|
|
6316
|
+
return `import type { ${names.join(", ")} } from '${VITEST_TYPE_SOURCE}'${semi ? ";" : ""}`;
|
|
6317
|
+
}
|
|
6318
|
+
function importDeclarations(program) {
|
|
6319
|
+
return (program.body ?? []).filter((node) => node.type === "ImportDeclaration");
|
|
6320
|
+
}
|
|
6321
|
+
function usesSemicolons(source, imports) {
|
|
6322
|
+
const first = imports[0];
|
|
6323
|
+
if (first === void 0) return false;
|
|
6324
|
+
return source.slice(first.start, first.end).trimEnd().endsWith(";");
|
|
6325
|
+
}
|
|
6326
|
+
function existingVitestImport(imports) {
|
|
6327
|
+
return imports.find(
|
|
6328
|
+
(node) => node.source?.value === VITEST_TYPE_SOURCE
|
|
6329
|
+
);
|
|
6330
|
+
}
|
|
6331
|
+
function alreadyImported(declaration) {
|
|
6332
|
+
return (declaration.specifiers ?? []).filter((specifier) => specifier.type === "ImportSpecifier").map((specifier) => specifier.imported?.name).filter((name) => name !== void 0);
|
|
6333
|
+
}
|
|
6334
|
+
function injectTypeImport(fileName, source, typeNames) {
|
|
6335
|
+
if (typeNames.length === 0) return source;
|
|
6336
|
+
const { program, errors } = parseSync4(fileName, source, { sourceType: "module" });
|
|
6337
|
+
if (errors.length > 0) return source;
|
|
6338
|
+
const imports = importDeclarations(program);
|
|
6339
|
+
const semi = usesSemicolons(source, imports);
|
|
6340
|
+
const existing = existingVitestImport(imports);
|
|
6341
|
+
if (existing !== void 0) {
|
|
6342
|
+
const merged = [.../* @__PURE__ */ new Set([...alreadyImported(existing), ...typeNames])].sort();
|
|
6343
|
+
if (merged.length === alreadyImported(existing).length) return source;
|
|
6344
|
+
return source.slice(0, existing.start) + renderTypeImport(merged, semi) + source.slice(existing.end);
|
|
6345
|
+
}
|
|
6346
|
+
const line = renderTypeImport([...typeNames].sort(), semi);
|
|
6347
|
+
const first = imports[0];
|
|
6348
|
+
if (first === void 0) return `${line}
|
|
6349
|
+
|
|
6350
|
+
${source}`;
|
|
6351
|
+
return source.slice(0, first.start) + line + "\n" + source.slice(first.start);
|
|
6352
|
+
}
|
|
6353
|
+
|
|
6354
|
+
// src/roles/test/codemod/named-rules.ts
|
|
6355
|
+
var MEMBER_TYPES7 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6356
|
+
var ASYNC_REPLACEMENTS = {
|
|
6357
|
+
requireActual: "importActual",
|
|
6358
|
+
requireMock: "importMock"
|
|
6359
|
+
};
|
|
6360
|
+
function mockedSpecifiers(program) {
|
|
6361
|
+
const found = /* @__PURE__ */ new Set();
|
|
6362
|
+
walk(program, (node) => {
|
|
6363
|
+
if (node.type !== "CallExpression") return;
|
|
6364
|
+
const callee = node.callee;
|
|
6365
|
+
if (callee === void 0 || !MEMBER_TYPES7.has(callee.type)) return;
|
|
6366
|
+
const object = callee.object;
|
|
6367
|
+
const property = callee.property;
|
|
6368
|
+
if (object?.type !== "Identifier") return;
|
|
6369
|
+
if (object.name !== "jest" && object.name !== "vi") return;
|
|
6370
|
+
if (property?.type !== "Identifier" || property.name !== "mock") return;
|
|
6371
|
+
const [specifier] = node.arguments ?? [];
|
|
6372
|
+
if (specifier?.type === "StringLiteral" || specifier?.type === "Literal") {
|
|
6373
|
+
found.add(String(specifier.value));
|
|
6374
|
+
}
|
|
6375
|
+
});
|
|
6376
|
+
return found;
|
|
6377
|
+
}
|
|
6378
|
+
var FUNCTIONS = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration"]);
|
|
6379
|
+
function walkWithEnclosingFunction(node, enclosing, visit) {
|
|
6380
|
+
if (node === null || typeof node !== "object") return;
|
|
6381
|
+
if (Array.isArray(node)) {
|
|
6382
|
+
for (const entry of node) walkWithEnclosingFunction(entry, enclosing, visit);
|
|
6383
|
+
return;
|
|
6384
|
+
}
|
|
6385
|
+
const current = node;
|
|
6386
|
+
const next = FUNCTIONS.has(current.type) ? current : enclosing;
|
|
6387
|
+
if (typeof current.type === "string") visit(current, enclosing);
|
|
6388
|
+
for (const value of Object.values(current)) {
|
|
6389
|
+
if (value !== null && typeof value === "object") walkWithEnclosingFunction(value, next, visit);
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
6392
|
+
function bareRequire(node) {
|
|
6393
|
+
if (node.type !== "CallExpression") return false;
|
|
6394
|
+
const callee = node.callee;
|
|
6395
|
+
if (callee?.type !== "Identifier" || callee.name !== "require") return false;
|
|
6396
|
+
const args = node.arguments ?? [];
|
|
6397
|
+
return args.length === 1 && args[0] !== void 0;
|
|
6398
|
+
}
|
|
6399
|
+
function ownedCall(node) {
|
|
6400
|
+
if (node.type !== "CallExpression") return void 0;
|
|
6401
|
+
const callee = node.callee;
|
|
6402
|
+
if (callee?.type !== "MemberExpression" && callee?.type !== "StaticMemberExpression") {
|
|
6403
|
+
return void 0;
|
|
6404
|
+
}
|
|
6405
|
+
const object = callee.object;
|
|
6406
|
+
const property = callee.property;
|
|
6407
|
+
if (object?.type !== "Identifier" || object.name !== "jest") return void 0;
|
|
6408
|
+
return property?.type === "Identifier" ? property.name : void 0;
|
|
6409
|
+
}
|
|
6410
|
+
function argumentText(source, call) {
|
|
6411
|
+
const args = call.arguments ?? [];
|
|
6412
|
+
if (args.length === 0) return "";
|
|
6413
|
+
const first = args[0];
|
|
6414
|
+
const last = args[args.length - 1];
|
|
6415
|
+
return source.slice(first.start, last.end);
|
|
6416
|
+
}
|
|
6417
|
+
function applyEdits(source, edits) {
|
|
6418
|
+
let result = source;
|
|
6419
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
6420
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
6421
|
+
}
|
|
6422
|
+
return result;
|
|
6423
|
+
}
|
|
6424
|
+
function applyNamedRules(source, program) {
|
|
6425
|
+
const edits = [];
|
|
6426
|
+
const applied = /* @__PURE__ */ new Set();
|
|
6427
|
+
const asyncify = /* @__PURE__ */ new Set();
|
|
6428
|
+
const mocked = mockedSpecifiers(program);
|
|
6429
|
+
walkWithEnclosingFunction(program, void 0, (node, enclosing) => {
|
|
6430
|
+
const member = ownedCall(node);
|
|
6431
|
+
if (member === void 0 && !bareRequire(node)) return;
|
|
6432
|
+
if (member === "requireMock") {
|
|
6433
|
+
const [specifier] = node.arguments ?? [];
|
|
6434
|
+
const literal = specifier?.type === "StringLiteral" || specifier?.type === "Literal" ? String(specifier.value) : void 0;
|
|
6435
|
+
if (literal !== void 0 && mocked.has(literal)) {
|
|
6436
|
+
edits.push({
|
|
6437
|
+
start: node.start,
|
|
6438
|
+
end: node.end,
|
|
6439
|
+
text: `(await import(${argumentText(source, node)}))`
|
|
6440
|
+
});
|
|
6441
|
+
applied.add("jest.requireMock");
|
|
6442
|
+
if (enclosing !== void 0 && enclosing.async !== true) asyncify.add(enclosing);
|
|
6443
|
+
return;
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
const asyncReplacement = member === void 0 ? void 0 : ASYNC_REPLACEMENTS[member];
|
|
6447
|
+
if (asyncReplacement !== void 0) {
|
|
6448
|
+
edits.push({
|
|
6449
|
+
start: node.start,
|
|
6450
|
+
end: node.end,
|
|
6451
|
+
text: `(await vi.${asyncReplacement}(${argumentText(source, node)}))`
|
|
6452
|
+
});
|
|
6453
|
+
applied.add(`jest.${member}`);
|
|
6454
|
+
if (enclosing !== void 0 && enclosing.async !== true) asyncify.add(enclosing);
|
|
6455
|
+
return;
|
|
6456
|
+
}
|
|
6457
|
+
if (bareRequire(node)) {
|
|
6458
|
+
edits.push({
|
|
6459
|
+
start: node.start,
|
|
6460
|
+
end: node.end,
|
|
6461
|
+
text: `(await import(${argumentText(source, node)}))`
|
|
6462
|
+
});
|
|
6463
|
+
applied.add("require");
|
|
6464
|
+
if (enclosing !== void 0 && enclosing.async !== true) asyncify.add(enclosing);
|
|
6465
|
+
return;
|
|
6466
|
+
}
|
|
6467
|
+
if (member === "mock" && enclosing !== void 0) {
|
|
6468
|
+
const callee = node.callee;
|
|
6469
|
+
edits.push({
|
|
6470
|
+
start: callee.start,
|
|
6471
|
+
end: callee.end,
|
|
6472
|
+
text: "vi.doMock"
|
|
6473
|
+
});
|
|
6474
|
+
applied.add("jest.mock inside a function");
|
|
6475
|
+
return;
|
|
6476
|
+
}
|
|
6477
|
+
if (member === "setTimeout") {
|
|
6478
|
+
edits.push({
|
|
6479
|
+
start: node.start,
|
|
6480
|
+
end: node.end,
|
|
6481
|
+
text: `vi.setConfig({ testTimeout: ${argumentText(source, node)} })`
|
|
6482
|
+
});
|
|
6483
|
+
applied.add("jest.setTimeout");
|
|
6484
|
+
}
|
|
6485
|
+
});
|
|
6486
|
+
for (const fn of asyncify) {
|
|
6487
|
+
edits.push({ start: fn.start, end: fn.start, text: "async " });
|
|
6488
|
+
}
|
|
6489
|
+
return { source: applyEdits(source, edits), applied: [...applied].sort() };
|
|
6490
|
+
}
|
|
6491
|
+
|
|
6492
|
+
// src/roles/test/codemod/isolate-modules.ts
|
|
6493
|
+
var MEMBER_TYPES8 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6494
|
+
var ISOLATORS = /* @__PURE__ */ new Set(["isolateModules", "isolateModulesAsync"]);
|
|
6495
|
+
var FUNCTIONS2 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression"]);
|
|
6496
|
+
function isolatingCall(node) {
|
|
6497
|
+
if (node.type !== "CallExpression") return void 0;
|
|
6498
|
+
const callee = node.callee;
|
|
6499
|
+
if (callee === void 0 || !MEMBER_TYPES8.has(callee.type)) return void 0;
|
|
6500
|
+
const object = callee.object;
|
|
6501
|
+
const property = callee.property;
|
|
6502
|
+
if (object?.type !== "Identifier" || object.name !== "jest") return void 0;
|
|
6503
|
+
if (property?.type !== "Identifier" || !ISOLATORS.has(property.name)) return void 0;
|
|
6504
|
+
const [callback] = node.arguments ?? [];
|
|
6505
|
+
return callback !== void 0 && FUNCTIONS2.has(callback.type) ? { call: node, callback } : void 0;
|
|
6506
|
+
}
|
|
6507
|
+
function unusableIsolatingCall(node) {
|
|
6508
|
+
if (node.type !== "CallExpression") return void 0;
|
|
6509
|
+
const callee = node.callee;
|
|
6510
|
+
if (callee === void 0 || !MEMBER_TYPES8.has(callee.type)) return void 0;
|
|
6511
|
+
const object = callee.object;
|
|
6512
|
+
const property = callee.property;
|
|
6513
|
+
if (object?.type !== "Identifier" || object.name !== "jest") return void 0;
|
|
6514
|
+
if (property?.type !== "Identifier" || !ISOLATORS.has(property.name)) return void 0;
|
|
6515
|
+
const [callback] = node.arguments ?? [];
|
|
6516
|
+
return callback === void 0 || FUNCTIONS2.has(callback.type) ? void 0 : `jest.${property.name}`;
|
|
6517
|
+
}
|
|
6518
|
+
function indentationAt(source, offset) {
|
|
6519
|
+
const lineStart = source.lastIndexOf("\n", offset - 1) + 1;
|
|
6520
|
+
return /^[ \t]*/.exec(source.slice(lineStart, offset))?.[0] ?? "";
|
|
6521
|
+
}
|
|
6522
|
+
function rewriteIsolateModules(source, program) {
|
|
6523
|
+
const edits = [];
|
|
6524
|
+
const refusals = [];
|
|
6525
|
+
const asyncify = /* @__PURE__ */ new Set();
|
|
6526
|
+
let rewritten = 0;
|
|
6527
|
+
walkWithEnclosingFunction(program, void 0, (node, enclosing) => {
|
|
6528
|
+
const unusable = unusableIsolatingCall(node);
|
|
6529
|
+
if (unusable !== void 0) {
|
|
6530
|
+
refusals.push({
|
|
6531
|
+
binding: unusable,
|
|
6532
|
+
reason: `${unusable} is called with something other than a function, so the callback cannot be invoked against a reset registry. This one needs a human.`
|
|
6533
|
+
});
|
|
6534
|
+
return;
|
|
6535
|
+
}
|
|
6536
|
+
const found = isolatingCall(node);
|
|
6537
|
+
if (found === void 0) return;
|
|
6538
|
+
const callbackText = source.slice(found.callback.start, found.callback.end);
|
|
6539
|
+
const callback = found.callback.async === true ? callbackText : `async ${callbackText}`;
|
|
6540
|
+
const indent = indentationAt(source, found.call.start);
|
|
6541
|
+
edits.push({
|
|
6542
|
+
start: found.call.start,
|
|
6543
|
+
end: found.call.end,
|
|
6544
|
+
text: `vi.resetModules()
|
|
6545
|
+
${indent}await (${callback})()`
|
|
6546
|
+
});
|
|
6547
|
+
rewritten += 1;
|
|
6548
|
+
if (enclosing !== void 0 && enclosing.async !== true) asyncify.add(enclosing);
|
|
6549
|
+
});
|
|
6550
|
+
for (const fn of asyncify) {
|
|
6551
|
+
edits.push({ start: fn.start, end: fn.start, text: "async " });
|
|
6552
|
+
}
|
|
6553
|
+
let result = source;
|
|
6554
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
6555
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
6556
|
+
}
|
|
6557
|
+
return { source: result, rewritten, refusals };
|
|
6558
|
+
}
|
|
6559
|
+
|
|
6560
|
+
// src/roles/test/codemod/mock-as-value.ts
|
|
6561
|
+
var MEMBER_TYPES9 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6562
|
+
function isViMockCall(node) {
|
|
6563
|
+
if (node?.type !== "CallExpression") return false;
|
|
6564
|
+
const callee = node.callee;
|
|
6565
|
+
if (callee === void 0 || !MEMBER_TYPES9.has(callee.type)) return false;
|
|
6566
|
+
const object = callee.object;
|
|
6567
|
+
const property = callee.property;
|
|
6568
|
+
return object?.type === "Identifier" && object.name === "vi" && property?.type === "Identifier" && (property.name === "mock" || property.name === "doMock");
|
|
6569
|
+
}
|
|
6570
|
+
function mockBinding(node) {
|
|
6571
|
+
if (node.type !== "VariableDeclaration") return void 0;
|
|
6572
|
+
const declarations = node.declarations ?? [];
|
|
6573
|
+
if (declarations.length !== 1) return void 0;
|
|
6574
|
+
const [declarator] = declarations;
|
|
6575
|
+
const id = declarator?.id;
|
|
6576
|
+
const init = declarator?.init;
|
|
6577
|
+
if (id?.type !== "Identifier" || !isViMockCall(init)) return void 0;
|
|
6578
|
+
return { name: id.name, call: init };
|
|
6579
|
+
}
|
|
6580
|
+
function rewriteMockAsValue(source, program) {
|
|
6581
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
6582
|
+
walk(program, (node) => {
|
|
6583
|
+
const found = mockBinding(node);
|
|
6584
|
+
if (found !== void 0) bindings.set(found.name, { declaration: node, call: found.call });
|
|
6585
|
+
});
|
|
6586
|
+
if (bindings.size === 0) return { source, rewritten: 0, refusals: [] };
|
|
6587
|
+
const memberUses = /* @__PURE__ */ new Map();
|
|
6588
|
+
const otherUses = /* @__PURE__ */ new Map();
|
|
6589
|
+
walk(program, (node) => {
|
|
6590
|
+
if (MEMBER_TYPES9.has(node.type)) {
|
|
6591
|
+
const object = node.object;
|
|
6592
|
+
if (object?.type === "Identifier" && bindings.has(object.name)) {
|
|
6593
|
+
const list = memberUses.get(object.name) ?? [];
|
|
6594
|
+
list.push(object);
|
|
6595
|
+
memberUses.set(object.name, list);
|
|
6596
|
+
return;
|
|
6597
|
+
}
|
|
6598
|
+
}
|
|
6599
|
+
if (node.type !== "Identifier" || !bindings.has(node.name)) return;
|
|
6600
|
+
const declared = bindings.get(node.name);
|
|
6601
|
+
const insideDeclaration = node.start >= declared?.declaration.start && node.end <= declared?.declaration.end;
|
|
6602
|
+
if (insideDeclaration) return;
|
|
6603
|
+
const seen = memberUses.get(node.name) ?? [];
|
|
6604
|
+
if (seen.some((use) => use.start === node.start)) return;
|
|
6605
|
+
otherUses.set(node.name, (otherUses.get(node.name) ?? 0) + 1);
|
|
6606
|
+
});
|
|
6607
|
+
const edits = [];
|
|
6608
|
+
const refusals = [];
|
|
6609
|
+
let rewritten = 0;
|
|
6610
|
+
for (const [name, { declaration, call }] of bindings) {
|
|
6611
|
+
if ((otherUses.get(name) ?? 0) > 0) {
|
|
6612
|
+
refusals.push({
|
|
6613
|
+
binding: name,
|
|
6614
|
+
reason: `\`${name}\` is \`jest.mock()\`'s return value used as a value elsewhere, not only as \`${name}.member(...)\`. vi.mock() returns nothing, so this needs a human.`
|
|
6615
|
+
});
|
|
6616
|
+
continue;
|
|
6617
|
+
}
|
|
6618
|
+
edits.push({
|
|
6619
|
+
start: declaration.start,
|
|
6620
|
+
end: declaration.end,
|
|
6621
|
+
text: source.slice(call.start, call.end)
|
|
6622
|
+
});
|
|
6623
|
+
for (const use of memberUses.get(name) ?? []) {
|
|
6624
|
+
edits.push({ start: use.start, end: use.end, text: "vi" });
|
|
6625
|
+
}
|
|
6626
|
+
rewritten += 1;
|
|
6627
|
+
}
|
|
6628
|
+
let result = source;
|
|
6629
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
6630
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
6631
|
+
}
|
|
6632
|
+
return { source: result, rewritten, refusals };
|
|
6633
|
+
}
|
|
6634
|
+
|
|
6635
|
+
// src/roles/test/codemod/mock-factory-default.ts
|
|
6636
|
+
var MEMBER_TYPES10 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6637
|
+
var BINDING = "sentinelMockedModule";
|
|
6638
|
+
function mockCall(node) {
|
|
6639
|
+
if (node.type !== "CallExpression") return void 0;
|
|
6640
|
+
const callee = node.callee;
|
|
6641
|
+
if (callee === void 0 || !MEMBER_TYPES10.has(callee.type)) return void 0;
|
|
6642
|
+
const property = callee.property;
|
|
6643
|
+
if (property?.type !== "Identifier" || property.name !== "mock") return void 0;
|
|
6644
|
+
const object = callee.object;
|
|
6645
|
+
if (object?.type !== "Identifier" || !["vi", "vitest"].includes(object.name)) {
|
|
6646
|
+
return void 0;
|
|
6647
|
+
}
|
|
6648
|
+
const [specifier, factory] = node.arguments ?? [];
|
|
6649
|
+
if (specifier?.type !== "Literal" || typeof specifier.value !== "string") return void 0;
|
|
6650
|
+
if (factory === void 0) return void 0;
|
|
6651
|
+
return { specifier: specifier.value, factory };
|
|
6652
|
+
}
|
|
6653
|
+
function returnedExpression(factory) {
|
|
6654
|
+
const object = returnedObject(factory);
|
|
6655
|
+
if (object !== void 0) return { node: object, isObject: true };
|
|
6656
|
+
if (factory.type !== "ArrowFunctionExpression") return void 0;
|
|
6657
|
+
let body = factory.body;
|
|
6658
|
+
while (body?.type === "ParenthesizedExpression") body = body.expression;
|
|
6659
|
+
if (body === void 0) return void 0;
|
|
6660
|
+
if (body.type !== "BlockStatement") return { node: body, isObject: false };
|
|
6661
|
+
const statements = body.body ?? [];
|
|
6662
|
+
if (statements.length !== 1) return void 0;
|
|
6663
|
+
const only = statements[0];
|
|
6664
|
+
if (only?.type !== "ReturnStatement") return void 0;
|
|
6665
|
+
const argument = only.argument;
|
|
6666
|
+
return argument === void 0 ? void 0 : { node: argument, isObject: false };
|
|
6667
|
+
}
|
|
6668
|
+
function returnedObject(factory) {
|
|
6669
|
+
if (factory.type !== "ArrowFunctionExpression") return void 0;
|
|
6670
|
+
let body = factory.body;
|
|
6671
|
+
while (body?.type === "ParenthesizedExpression") body = body.expression;
|
|
6672
|
+
if (body?.type === "ObjectExpression") return body;
|
|
6673
|
+
const statements = body?.body ?? [];
|
|
6674
|
+
if (statements.length !== 1) return void 0;
|
|
6675
|
+
const only = statements[0];
|
|
6676
|
+
if (only?.type === "ExpressionStatement" && only.expression?.type === "ObjectExpression") {
|
|
6677
|
+
return only.expression;
|
|
6678
|
+
}
|
|
6679
|
+
if (only?.type === "ReturnStatement" && only.argument?.type === "ObjectExpression") {
|
|
6680
|
+
return only.argument;
|
|
6681
|
+
}
|
|
6682
|
+
return void 0;
|
|
6683
|
+
}
|
|
6684
|
+
function namesDefault(object) {
|
|
6685
|
+
return (object.properties ?? []).some((property) => {
|
|
6686
|
+
const key = property.key;
|
|
6687
|
+
if (key?.type === "Identifier") return key.name === "default";
|
|
6688
|
+
if (key?.type === "Literal") return key.value === "default";
|
|
6689
|
+
return false;
|
|
6690
|
+
});
|
|
6691
|
+
}
|
|
6692
|
+
function rewriteMockFactoryDefault(source, program, defaultImported) {
|
|
6693
|
+
const edits = [];
|
|
6694
|
+
walk(program, (node) => {
|
|
6695
|
+
const call = mockCall(node);
|
|
6696
|
+
if (call === void 0) return;
|
|
6697
|
+
const returned = returnedExpression(call.factory);
|
|
6698
|
+
if (returned === void 0) return;
|
|
6699
|
+
if (returned.isObject && namesDefault(returned.node)) return;
|
|
6700
|
+
if (returned.isObject && !defaultImported.has(call.specifier)) return;
|
|
6701
|
+
if (!returned.isObject) {
|
|
6702
|
+
const expression = source.slice(returned.node.start, returned.node.end);
|
|
6703
|
+
edits.push({
|
|
6704
|
+
start: returned.node.start,
|
|
6705
|
+
end: returned.node.end,
|
|
6706
|
+
literal: `{ default: ${expression} }`,
|
|
6707
|
+
specifier: call.specifier,
|
|
6708
|
+
wrap: false
|
|
6709
|
+
});
|
|
6710
|
+
return;
|
|
6711
|
+
}
|
|
6712
|
+
const object = returned.node;
|
|
6713
|
+
const literal = source.slice(object.start, object.end);
|
|
6714
|
+
if (literal.includes(BINDING)) return;
|
|
6715
|
+
const body = call.factory.body;
|
|
6716
|
+
edits.push({
|
|
6717
|
+
start: body.start,
|
|
6718
|
+
end: body.end,
|
|
6719
|
+
literal,
|
|
6720
|
+
specifier: call.specifier,
|
|
6721
|
+
wrap: true
|
|
6722
|
+
});
|
|
6723
|
+
});
|
|
6724
|
+
let result = source;
|
|
6725
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
6726
|
+
const text = edit.wrap ? `{ const ${BINDING} = ${edit.literal}; return { ...${BINDING}, default: ${BINDING} } }` : edit.literal;
|
|
6727
|
+
result = `${result.slice(0, edit.start)}${text}${result.slice(edit.end)}`;
|
|
6728
|
+
}
|
|
6729
|
+
return {
|
|
6730
|
+
source: result,
|
|
6731
|
+
rewritten: edits.length,
|
|
6732
|
+
specifiers: [...new Set(edits.map((edit) => edit.specifier))]
|
|
6733
|
+
};
|
|
6734
|
+
}
|
|
6735
|
+
|
|
6736
|
+
// src/roles/test/codemod/propagate-await.ts
|
|
6737
|
+
import { parseSync as parseSync5 } from "oxc-parser";
|
|
6738
|
+
var FUNCTIONS3 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration"]);
|
|
6739
|
+
var MEMBER_TYPES11 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6740
|
+
var ALREADY_A_PROMISE = /* @__PURE__ */ new Set(["then", "catch", "finally"]);
|
|
6741
|
+
function asyncFunctionNames(program) {
|
|
6742
|
+
const names = /* @__PURE__ */ new Set();
|
|
6743
|
+
walk(program, (node) => {
|
|
6744
|
+
if (node.type === "FunctionDeclaration" && node.async === true) {
|
|
6745
|
+
const id2 = node.id;
|
|
6746
|
+
if (id2?.type === "Identifier") names.add(id2.name);
|
|
6747
|
+
return;
|
|
6748
|
+
}
|
|
6749
|
+
if (node.type !== "VariableDeclarator") return;
|
|
6750
|
+
const init = node.init ?? void 0;
|
|
6751
|
+
const id = node.id;
|
|
6752
|
+
if (init === void 0 || !FUNCTIONS3.has(init.type) || init.async !== true) return;
|
|
6753
|
+
if (id?.type === "Identifier") names.add(id.name);
|
|
6754
|
+
});
|
|
6755
|
+
return names;
|
|
6756
|
+
}
|
|
6757
|
+
function returningFunctionNames(program, changed) {
|
|
6758
|
+
const names = /* @__PURE__ */ new Set();
|
|
6759
|
+
const returnsChanged = (fn) => {
|
|
6760
|
+
const body = fn.body;
|
|
6761
|
+
if (body === void 0) return false;
|
|
6762
|
+
if (callTo(body, changed) !== void 0) return true;
|
|
6763
|
+
let found = false;
|
|
6764
|
+
walk(body, (node) => {
|
|
6765
|
+
if (node.type !== "ReturnStatement") return;
|
|
6766
|
+
const argument = node.argument;
|
|
6767
|
+
if (argument !== void 0 && callTo(argument, changed) !== void 0) found = true;
|
|
6768
|
+
});
|
|
6769
|
+
return found;
|
|
6770
|
+
};
|
|
6771
|
+
walk(program, (node) => {
|
|
6772
|
+
if (node.type === "FunctionDeclaration" && returnsChanged(node)) {
|
|
6773
|
+
const id2 = node.id;
|
|
6774
|
+
if (id2?.type === "Identifier") names.add(id2.name);
|
|
6775
|
+
return;
|
|
6776
|
+
}
|
|
6777
|
+
if (node.type !== "VariableDeclarator") return;
|
|
6778
|
+
const init = node.init ?? void 0;
|
|
6779
|
+
const id = node.id;
|
|
6780
|
+
if (init === void 0 || !FUNCTIONS3.has(init.type)) return;
|
|
6781
|
+
if (id?.type === "Identifier" && returnsChanged(init)) names.add(id.name);
|
|
6782
|
+
});
|
|
6783
|
+
return names;
|
|
6784
|
+
}
|
|
6785
|
+
function callTo(node, names) {
|
|
6786
|
+
if (node.type !== "CallExpression") return void 0;
|
|
6787
|
+
const callee = node.callee;
|
|
6788
|
+
if (callee?.type !== "Identifier") return void 0;
|
|
6789
|
+
const name = callee.name;
|
|
6790
|
+
return names.has(name) ? name : void 0;
|
|
6791
|
+
}
|
|
6792
|
+
function walkWithContext(node, enclosing, parent, visit) {
|
|
6793
|
+
if (node === null || typeof node !== "object") return;
|
|
6794
|
+
if (Array.isArray(node)) {
|
|
6795
|
+
for (const item of node) walkWithContext(item, enclosing, parent, visit);
|
|
6796
|
+
return;
|
|
6797
|
+
}
|
|
6798
|
+
const current = node;
|
|
6799
|
+
const isFunction = typeof current.type === "string" && FUNCTIONS3.has(current.type);
|
|
6800
|
+
if (typeof current.type === "string") visit(current, enclosing, parent);
|
|
6801
|
+
const nextEnclosing = isFunction ? current : enclosing;
|
|
6802
|
+
const nextParent = typeof current.type === "string" ? current : parent;
|
|
6803
|
+
for (const value of Object.values(current)) {
|
|
6804
|
+
if (value !== null && typeof value === "object") {
|
|
6805
|
+
walkWithContext(value, nextEnclosing, nextParent, visit);
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
function alreadyHandled(call, parent) {
|
|
6810
|
+
if (parent === void 0) return false;
|
|
6811
|
+
if (parent.type === "AwaitExpression") return true;
|
|
6812
|
+
if (parent.type === "ReturnStatement" || parent.type === "ArrowFunctionExpression") return true;
|
|
6813
|
+
if (MEMBER_TYPES11.has(parent.type) && parent.object === call) {
|
|
6814
|
+
const property = parent.property;
|
|
6815
|
+
return property?.type === "Identifier" && ALREADY_A_PROMISE.has(property.name);
|
|
6816
|
+
}
|
|
6817
|
+
return false;
|
|
6818
|
+
}
|
|
6819
|
+
function applyEdits2(source, edits) {
|
|
6820
|
+
let result = source;
|
|
6821
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
6822
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
6823
|
+
}
|
|
6824
|
+
return result;
|
|
6825
|
+
}
|
|
6826
|
+
function propagateAwait(fileName, source, original, elsewhere = /* @__PURE__ */ new Set()) {
|
|
6827
|
+
const wasAsync = asyncFunctionNames(original);
|
|
6828
|
+
let current = source;
|
|
6829
|
+
let rewritten = 0;
|
|
6830
|
+
for (let round = 0; round < 12; round += 1) {
|
|
6831
|
+
let parsed;
|
|
6832
|
+
try {
|
|
6833
|
+
parsed = parseSync5(fileName, current);
|
|
6834
|
+
} catch {
|
|
6835
|
+
return { source: current, rewritten };
|
|
6836
|
+
}
|
|
6837
|
+
const program = parsed.program;
|
|
6838
|
+
const changed = new Set(elsewhere);
|
|
6839
|
+
for (const name of asyncFunctionNames(program)) {
|
|
6840
|
+
if (!wasAsync.has(name)) changed.add(name);
|
|
6841
|
+
}
|
|
6842
|
+
if (changed.size === 0) return { source: current, rewritten };
|
|
6843
|
+
for (const name of returningFunctionNames(program, changed)) changed.add(name);
|
|
6844
|
+
const edits = [];
|
|
6845
|
+
const asyncify = /* @__PURE__ */ new Set();
|
|
6846
|
+
walkWithContext(program, void 0, void 0, (node, enclosing, parent) => {
|
|
6847
|
+
if (callTo(node, changed) === void 0) return;
|
|
6848
|
+
if (alreadyHandled(node, parent)) return;
|
|
6849
|
+
edits.push({ start: node.start, end: node.start, text: "await " });
|
|
6850
|
+
if (enclosing !== void 0 && enclosing.async !== true) asyncify.add(enclosing);
|
|
6851
|
+
});
|
|
6852
|
+
if (edits.length === 0) return { source: current, rewritten };
|
|
6853
|
+
for (const fn of asyncify) {
|
|
6854
|
+
edits.push({ start: fn.start, end: fn.start, text: "async " });
|
|
6855
|
+
}
|
|
6856
|
+
rewritten += edits.length - asyncify.size;
|
|
6857
|
+
current = applyEdits2(current, edits);
|
|
6858
|
+
}
|
|
6859
|
+
return { source: current, rewritten };
|
|
6860
|
+
}
|
|
6861
|
+
|
|
6862
|
+
// src/roles/test/codemod/repoint-msw.ts
|
|
6863
|
+
import { parseSync as parseSync6 } from "oxc-parser";
|
|
6864
|
+
var SENTINEL_MSW_SERVER = "@hublo/sentinel/test/msw";
|
|
6865
|
+
var MSW_SERVER_MODULE = /(^|\/)msw\/server$/;
|
|
6866
|
+
var MSW_PACKAGE = /^msw$/;
|
|
6867
|
+
function importsMswPackage(declaration) {
|
|
6868
|
+
const source = declaration.source?.value;
|
|
6869
|
+
return source !== void 0 && MSW_PACKAGE.test(source);
|
|
6870
|
+
}
|
|
6871
|
+
function importsServer(declaration) {
|
|
6872
|
+
return (declaration.specifiers ?? []).some((specifier) => {
|
|
6873
|
+
const imported = specifier.imported;
|
|
6874
|
+
const local = specifier.local;
|
|
6875
|
+
return imported?.name === "server" || imported === void 0 && local?.name === "server";
|
|
6876
|
+
});
|
|
6877
|
+
}
|
|
6878
|
+
function repointMswServer(fileName, source) {
|
|
6879
|
+
const { program, errors } = parseSync6(fileName, source, { sourceType: "module" });
|
|
6880
|
+
if (errors.length > 0) return { source, repointed: false };
|
|
6881
|
+
const edits = [];
|
|
6882
|
+
walk(program, (node) => {
|
|
6883
|
+
if (node.type !== "ImportDeclaration") return;
|
|
6884
|
+
const specifier = node.source;
|
|
6885
|
+
if (specifier?.value === void 0) return;
|
|
6886
|
+
const isServerModule = MSW_SERVER_MODULE.test(specifier.value) && importsServer(node);
|
|
6887
|
+
if (!isServerModule && !importsMswPackage(node)) return;
|
|
6888
|
+
edits.push({ start: specifier.start, end: specifier.end });
|
|
6889
|
+
});
|
|
6890
|
+
if (edits.length === 0) return { source, repointed: false };
|
|
6891
|
+
let rewritten = source;
|
|
6892
|
+
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
6893
|
+
const quote = source[edit.start] === '"' ? '"' : "'";
|
|
6894
|
+
rewritten = rewritten.slice(0, edit.start) + `${quote}${SENTINEL_MSW_SERVER}${quote}` + rewritten.slice(edit.end);
|
|
6895
|
+
}
|
|
6896
|
+
return { source: rewritten, repointed: true };
|
|
6897
|
+
}
|
|
6898
|
+
|
|
6899
|
+
// src/roles/test/codemod/migrate-file.ts
|
|
6900
|
+
function mockFactories(program) {
|
|
6901
|
+
const factories = [];
|
|
6902
|
+
walk(program, (node) => {
|
|
6903
|
+
if (node.type !== "CallExpression") return;
|
|
6904
|
+
const callee = node.callee;
|
|
6905
|
+
if (callee?.type !== "MemberExpression" && callee?.type !== "StaticMemberExpression") return;
|
|
6906
|
+
const object = callee.object;
|
|
6907
|
+
const property = callee.property;
|
|
6908
|
+
if (object?.type !== "Identifier") return;
|
|
6909
|
+
if (object.name !== "jest" && object.name !== "vi") return;
|
|
6910
|
+
if (property?.name !== "mock") return;
|
|
6911
|
+
const args = node.arguments ?? [];
|
|
6912
|
+
if (args.length > 1) factories.push(args[1]);
|
|
6913
|
+
});
|
|
6914
|
+
return factories;
|
|
6915
|
+
}
|
|
6916
|
+
function parse2(fileName, source) {
|
|
6917
|
+
const { program, errors } = parseSync7(fileName, source, { sourceType: "module" });
|
|
6918
|
+
return errors.length > 0 ? void 0 : program;
|
|
6919
|
+
}
|
|
6920
|
+
function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new Set(), asyncHelpers = /* @__PURE__ */ new Set()) {
|
|
6921
|
+
const untouched = {
|
|
6922
|
+
source,
|
|
6923
|
+
changed: false,
|
|
6924
|
+
renamed: 0,
|
|
6925
|
+
hoisted: [],
|
|
6926
|
+
unhandled: [],
|
|
6927
|
+
refusals: [],
|
|
6928
|
+
rulesApplied: [],
|
|
6929
|
+
mswRepointed: false
|
|
6930
|
+
};
|
|
6931
|
+
const original = parse2(fileName, source);
|
|
6932
|
+
if (original === void 0) return untouched;
|
|
6933
|
+
const titles = rewriteEachTitles(source, original);
|
|
6934
|
+
const tables = rewriteEachTableRows(titles.source, original);
|
|
6935
|
+
const afterTables = parse2(fileName, tables.source);
|
|
6936
|
+
if (afterTables === void 0) return untouched;
|
|
6937
|
+
const ruled = applyNamedRules(tables.source, afterTables);
|
|
6938
|
+
const renamed = renameJestToVitest(fileName, ruled.source);
|
|
6939
|
+
const afterRename = parse2(fileName, renamed.source);
|
|
6940
|
+
if (afterRename === void 0) return untouched;
|
|
6941
|
+
const mocks = rewriteMockAsValue(renamed.source, afterRename);
|
|
6942
|
+
const afterMocks = parse2(fileName, mocks.source);
|
|
6943
|
+
if (afterMocks === void 0) return untouched;
|
|
6944
|
+
const constructible = rewriteConstructibleMocks(
|
|
6945
|
+
mocks.source,
|
|
6946
|
+
afterMocks,
|
|
6947
|
+
mockFactories(afterMocks)
|
|
6948
|
+
);
|
|
6949
|
+
const afterConstructible = parse2(fileName, constructible.source);
|
|
6950
|
+
if (afterConstructible === void 0) return untouched;
|
|
6951
|
+
const factoryDefault = rewriteMockFactoryDefault(
|
|
6952
|
+
constructible.source,
|
|
6953
|
+
afterConstructible,
|
|
6954
|
+
defaultImported
|
|
6955
|
+
);
|
|
6956
|
+
const beforeReturnValue = parse2(fileName, factoryDefault.source);
|
|
6957
|
+
if (beforeReturnValue === void 0) return untouched;
|
|
6958
|
+
const returnValue = rewriteAssertionReturnValue(factoryDefault.source, beforeReturnValue);
|
|
6959
|
+
const afterReturnValue = parse2(fileName, returnValue.source);
|
|
6960
|
+
if (afterReturnValue === void 0) return untouched;
|
|
6961
|
+
const isolated = rewriteIsolateModules(returnValue.source, afterReturnValue);
|
|
6962
|
+
const afterIsolate = parse2(fileName, isolated.source);
|
|
6963
|
+
if (afterIsolate === void 0) return untouched;
|
|
6964
|
+
const emptyImplementations = rewriteEmptyMockImplementation(isolated.source, afterIsolate);
|
|
6965
|
+
const afterEmpty = parse2(fileName, emptyImplementations.source);
|
|
6966
|
+
if (afterEmpty === void 0) return untouched;
|
|
6967
|
+
const hooks = rewriteHookReturnValue(emptyImplementations.source, afterEmpty);
|
|
6968
|
+
const awaited = propagateAwait(fileName, hooks.source, original, asyncHelpers);
|
|
6969
|
+
const program = parse2(fileName, awaited.source);
|
|
6970
|
+
if (program === void 0) return untouched;
|
|
6971
|
+
const hoist = hoistCapturedBindings(
|
|
6972
|
+
awaited.source,
|
|
6973
|
+
program,
|
|
6974
|
+
mockFactories(program),
|
|
6975
|
+
moduleBindings(program),
|
|
6976
|
+
importedNames(program)
|
|
6977
|
+
);
|
|
6978
|
+
const withImport = injectTypeImport(fileName, hoist.source, renamed.typeImports);
|
|
6979
|
+
const deep = normaliseDeepImports(withImport);
|
|
6980
|
+
const repointed = repointMswServer(fileName, deep.source);
|
|
6981
|
+
return {
|
|
6982
|
+
source: repointed.source,
|
|
6983
|
+
changed: repointed.source !== source,
|
|
6984
|
+
renamed: renamed.rewritten,
|
|
6985
|
+
hoisted: hoist.hoisted,
|
|
6986
|
+
unhandled: renamed.unhandled,
|
|
6987
|
+
rulesApplied: [
|
|
6988
|
+
...ruled.applied,
|
|
6989
|
+
...titles.rewritten > 0 ? ["each title %p"] : [],
|
|
6990
|
+
...mocks.rewritten > 0 ? ["vi.mock as a value"] : [],
|
|
6991
|
+
...deep.rewritten > 0 ? ["deep import normalised"] : [],
|
|
6992
|
+
...constructible.rewritten > 0 ? ["constructible mock implementation"] : [],
|
|
6993
|
+
...isolated.rewritten > 0 ? ["jest.isolateModules"] : [],
|
|
6994
|
+
...emptyImplementations.rewritten > 0 ? ["empty mockImplementation"] : [],
|
|
6995
|
+
...hooks.rewritten > 0 ? ["hook body, so it does not return its mock"] : [],
|
|
6996
|
+
...awaited.rewritten > 0 ? ["await propagated to a helper that became async"] : []
|
|
6997
|
+
].sort(),
|
|
6998
|
+
refusals: [
|
|
6999
|
+
...hoist.refusals,
|
|
7000
|
+
...mocks.refusals.map((refusal) => ({ binding: refusal.binding, reason: refusal.reason })),
|
|
7001
|
+
...constructible.refusals,
|
|
7002
|
+
...isolated.refusals
|
|
7003
|
+
],
|
|
7004
|
+
mswRepointed: repointed.repointed
|
|
7005
|
+
};
|
|
7006
|
+
}
|
|
7007
|
+
|
|
7008
|
+
// src/roles/test/codemod/test-support-files.ts
|
|
7009
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
|
|
7010
|
+
import { dirname as dirname4, join as join28, relative as relative5, resolve as resolve4 } from "path";
|
|
7011
|
+
var SKIPPED = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".git", ".vite"]);
|
|
7012
|
+
var TEST_FILE2 = /\.(spec|test)\.[cm]?[jt]sx?$/;
|
|
7013
|
+
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
7014
|
+
var EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"];
|
|
7015
|
+
function relativeSpecifiers(source) {
|
|
7016
|
+
const found = [];
|
|
7017
|
+
const patterns = [
|
|
7018
|
+
/\bfrom\s+['"](\.[^'"]*)['"]/g,
|
|
7019
|
+
/\bimport\s+['"](\.[^'"]*)['"]/g,
|
|
7020
|
+
/\bimport\s*\(\s*['"](\.[^'"]*)['"]\s*\)/g,
|
|
7021
|
+
/\brequire\s*\(\s*['"](\.[^'"]*)['"]\s*\)/g
|
|
7022
|
+
];
|
|
7023
|
+
for (const pattern of patterns) {
|
|
7024
|
+
for (const match of source.matchAll(pattern)) found.push(match[1]);
|
|
7025
|
+
}
|
|
7026
|
+
return found;
|
|
7027
|
+
}
|
|
7028
|
+
function resolveSpecifier(fromFile, specifier) {
|
|
7029
|
+
const base = resolve4(dirname4(fromFile), specifier);
|
|
7030
|
+
if (existsSync24(base) && statSync4(base).isFile()) return base;
|
|
7031
|
+
for (const extension of EXTENSIONS) {
|
|
7032
|
+
if (existsSync24(`${base}${extension}`)) return `${base}${extension}`;
|
|
7033
|
+
}
|
|
7034
|
+
for (const extension of EXTENSIONS) {
|
|
7035
|
+
const index = join28(base, `index${extension}`);
|
|
7036
|
+
if (existsSync24(index)) return index;
|
|
7037
|
+
}
|
|
7038
|
+
return void 0;
|
|
7039
|
+
}
|
|
7040
|
+
function moduleSources(cwd) {
|
|
7041
|
+
const found = [];
|
|
7042
|
+
const stack = [cwd];
|
|
7043
|
+
while (stack.length > 0) {
|
|
7044
|
+
const dir = stack.pop();
|
|
7045
|
+
let entries;
|
|
7046
|
+
try {
|
|
7047
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
7048
|
+
} catch {
|
|
7049
|
+
continue;
|
|
7050
|
+
}
|
|
7051
|
+
for (const entry of entries) {
|
|
7052
|
+
if (entry.isDirectory()) {
|
|
7053
|
+
if (!SKIPPED.has(entry.name)) stack.push(join28(dir, entry.name));
|
|
7054
|
+
} else if (SOURCE_FILE.test(entry.name)) {
|
|
7055
|
+
found.push(join28(dir, entry.name));
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
}
|
|
7059
|
+
return found;
|
|
7060
|
+
}
|
|
7061
|
+
function read(path) {
|
|
7062
|
+
try {
|
|
7063
|
+
return readFileSync22(path, "utf8");
|
|
7064
|
+
} catch {
|
|
7065
|
+
return void 0;
|
|
7066
|
+
}
|
|
7067
|
+
}
|
|
7068
|
+
function listTestSupportFiles(cwd) {
|
|
7069
|
+
const sources = moduleSources(cwd);
|
|
7070
|
+
const contents = /* @__PURE__ */ new Map();
|
|
7071
|
+
for (const file of sources) {
|
|
7072
|
+
const source = read(file);
|
|
7073
|
+
if (source !== void 0) contents.set(file, source);
|
|
7074
|
+
}
|
|
7075
|
+
const importedBy = /* @__PURE__ */ new Map();
|
|
7076
|
+
for (const [file, source] of contents) {
|
|
7077
|
+
for (const specifier of relativeSpecifiers(source)) {
|
|
7078
|
+
const target = resolveSpecifier(file, specifier);
|
|
7079
|
+
if (target === void 0 || !contents.has(target)) continue;
|
|
7080
|
+
importedBy.set(target, [...importedBy.get(target) ?? [], file]);
|
|
7081
|
+
}
|
|
7082
|
+
}
|
|
7083
|
+
const testOnly = /* @__PURE__ */ new Set();
|
|
7084
|
+
const queue = sources.filter((file) => TEST_FILE2.test(file));
|
|
7085
|
+
const isTestOnly = (file) => (importedBy.get(file) ?? []).every(
|
|
7086
|
+
(importer) => TEST_FILE2.test(importer) || testOnly.has(importer)
|
|
7087
|
+
);
|
|
7088
|
+
while (queue.length > 0) {
|
|
7089
|
+
const file = queue.shift();
|
|
7090
|
+
for (const specifier of relativeSpecifiers(contents.get(file) ?? "")) {
|
|
7091
|
+
const target = resolveSpecifier(file, specifier);
|
|
7092
|
+
if (target === void 0 || TEST_FILE2.test(target) || testOnly.has(target)) continue;
|
|
7093
|
+
if (!isTestOnly(target)) continue;
|
|
7094
|
+
testOnly.add(target);
|
|
7095
|
+
queue.push(target);
|
|
7096
|
+
}
|
|
7097
|
+
}
|
|
7098
|
+
return [...testOnly].filter((file) => /\brequire\s*\(/.test(contents.get(file) ?? "")).map((file) => relative5(cwd, file).replaceAll("\\", "/")).sort();
|
|
7099
|
+
}
|
|
7100
|
+
|
|
7101
|
+
// src/roles/test/codemod/migrate-module.ts
|
|
7102
|
+
function parse3(source) {
|
|
7103
|
+
const { program, errors } = parseSync8("file.ts", source, { sourceType: "module" });
|
|
7104
|
+
return errors.length > 0 ? void 0 : program;
|
|
7105
|
+
}
|
|
7106
|
+
function read2(path) {
|
|
7107
|
+
try {
|
|
7108
|
+
return readFileSync23(path, "utf8");
|
|
7109
|
+
} catch {
|
|
7110
|
+
return void 0;
|
|
7111
|
+
}
|
|
7112
|
+
}
|
|
7113
|
+
var SKIPPED2 = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".git", ".vite"]);
|
|
7114
|
+
function defaultImportedSpecifiers(cwd) {
|
|
7115
|
+
const found = /* @__PURE__ */ new Set();
|
|
7116
|
+
const pattern = /^\s*import\s+[A-Za-z_$][\w$]*\s*(?:,\s*\{[^}]*\}\s*)?from\s+['"]([^'"]+)['"]/gm;
|
|
7117
|
+
const walk2 = (dir) => {
|
|
7118
|
+
let entries;
|
|
7119
|
+
try {
|
|
7120
|
+
entries = readdirSync5(dir);
|
|
7121
|
+
} catch {
|
|
7122
|
+
return;
|
|
7123
|
+
}
|
|
7124
|
+
for (const entry of entries) {
|
|
7125
|
+
if (SKIPPED2.has(entry)) continue;
|
|
7126
|
+
const path = join29(dir, entry);
|
|
7127
|
+
let directory;
|
|
7128
|
+
try {
|
|
7129
|
+
directory = statSync5(path).isDirectory();
|
|
7130
|
+
} catch {
|
|
7131
|
+
continue;
|
|
7132
|
+
}
|
|
7133
|
+
if (directory) {
|
|
7134
|
+
walk2(path);
|
|
7135
|
+
continue;
|
|
7136
|
+
}
|
|
7137
|
+
if (!/\.tsx?$/.test(entry)) continue;
|
|
7138
|
+
const source = read2(path);
|
|
7139
|
+
if (source === void 0) continue;
|
|
7140
|
+
for (const match of source.matchAll(pattern)) found.add(match[1]);
|
|
7141
|
+
}
|
|
7142
|
+
};
|
|
7143
|
+
walk2(cwd);
|
|
7144
|
+
return found;
|
|
7145
|
+
}
|
|
7146
|
+
function migrateModuleTests(cwd) {
|
|
7147
|
+
const operations = [];
|
|
7148
|
+
const blockers = [];
|
|
7149
|
+
let files = 0;
|
|
7150
|
+
let renamed = 0;
|
|
7151
|
+
let hoisted = 0;
|
|
7152
|
+
const defaultImported = defaultImportedSpecifiers(cwd);
|
|
7153
|
+
const asyncHelpers = /* @__PURE__ */ new Set();
|
|
7154
|
+
for (const relative7 of listTestSupportFiles(cwd)) {
|
|
7155
|
+
const source = read2(join29(cwd, relative7));
|
|
7156
|
+
if (source === void 0) {
|
|
7157
|
+
blockers.push({ file: relative7, reason: "this file could not be read" });
|
|
7158
|
+
continue;
|
|
7159
|
+
}
|
|
7160
|
+
const migrated = migrateTestFile(join29(cwd, relative7), source, defaultImported);
|
|
7161
|
+
if (!migrated.changed) continue;
|
|
7162
|
+
const before = parse3(source);
|
|
7163
|
+
const after = parse3(migrated.source);
|
|
7164
|
+
if (before !== void 0 && after !== void 0) {
|
|
7165
|
+
const wasAsync = asyncFunctionNames(before);
|
|
7166
|
+
for (const name of asyncFunctionNames(after)) {
|
|
7167
|
+
if (!wasAsync.has(name)) asyncHelpers.add(name);
|
|
7168
|
+
}
|
|
7169
|
+
}
|
|
7170
|
+
files++;
|
|
7171
|
+
renamed += migrated.renamed;
|
|
7172
|
+
operations.push({ kind: "write", path: relative7, contents: migrated.source });
|
|
7173
|
+
}
|
|
7174
|
+
for (const relative7 of listTestFiles(cwd)) {
|
|
7175
|
+
const source = read2(join29(cwd, relative7));
|
|
7176
|
+
if (source === void 0) {
|
|
7177
|
+
blockers.push({ file: relative7, reason: "this file could not be read" });
|
|
7178
|
+
continue;
|
|
7179
|
+
}
|
|
7180
|
+
const migrated = migrateTestFile(join29(cwd, relative7), source, defaultImported, asyncHelpers);
|
|
7181
|
+
for (const member of migrated.unhandled) {
|
|
7182
|
+
blockers.push({
|
|
7183
|
+
file: `${relative7}:${member.line}`,
|
|
7184
|
+
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}`
|
|
7185
|
+
});
|
|
7186
|
+
}
|
|
7187
|
+
for (const refusal of migrated.refusals) {
|
|
7188
|
+
blockers.push({
|
|
7189
|
+
file: relative7,
|
|
7190
|
+
reason: `\`${refusal.binding}\` cannot move into vi.hoisted: ${refusal.reason}`
|
|
7191
|
+
});
|
|
7192
|
+
}
|
|
7193
|
+
if (!migrated.changed) continue;
|
|
7194
|
+
files++;
|
|
7195
|
+
renamed += migrated.renamed;
|
|
7196
|
+
if (migrated.hoisted.length > 0) hoisted++;
|
|
7197
|
+
operations.push({ kind: "write", path: relative7, contents: migrated.source });
|
|
7198
|
+
}
|
|
7199
|
+
return blockers.length > 0 ? { operations: [], files, renamed, hoisted, blockers } : { operations, files, renamed, hoisted, blockers: [] };
|
|
7200
|
+
}
|
|
7201
|
+
|
|
7202
|
+
// src/roles/test/nest/generate-config.ts
|
|
7203
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync24, statSync as statSync6 } from "fs";
|
|
7204
|
+
import { join as join30, relative as relative6 } from "path";
|
|
7205
|
+
var NEST_TEST_PRESET_SPECIFIER = "@hublo/sentinel/test/nest";
|
|
7206
|
+
var SENTINEL_TEST_SETUP = "@hublo/sentinel/test/setup/nest";
|
|
7207
|
+
var SENTINEL_MSW_SETUP = "@hublo/sentinel/test/setup/msw-lifecycle";
|
|
7208
|
+
var NEST_TEST_CONFIG_FILE = "vitest.config.mts";
|
|
7209
|
+
function hopsToRoot(cwd, workspaceRoot) {
|
|
7210
|
+
const up = relative6(cwd, workspaceRoot).replaceAll("\\", "/");
|
|
7211
|
+
return up === "" ? "." : up;
|
|
7212
|
+
}
|
|
7213
|
+
function inlineBlock(packages) {
|
|
7214
|
+
if (packages.length === 0) return "";
|
|
7215
|
+
const list = packages.map((name) => `'${name}'`).join(", ");
|
|
7216
|
+
return ` server: { deps: { inline: [${list}] } },`;
|
|
7217
|
+
}
|
|
7218
|
+
function coverageBlock(reading) {
|
|
7219
|
+
return reading.coverageDirectory === void 0 ? "" : ` coverage: { reportsDirectory: '${reading.coverageDirectory}' },`;
|
|
7220
|
+
}
|
|
7221
|
+
function environmentBlock(reading) {
|
|
7222
|
+
return reading.testEnvironment === void 0 ? "" : ` environment: '${reading.testEnvironment}',`;
|
|
7223
|
+
}
|
|
7224
|
+
var SKIPPED3 = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".git", ".vite"]);
|
|
7225
|
+
var BARE_AUTOMOCK = /(?:vi|vitest|jest)\.mock\(\s*['"]([^'".][^'"]*)['"]\s*\)/g;
|
|
7226
|
+
var MENTIONS_MSW = /from\s+['"](msw(\/[^'"]*)?|@hublo\/test\/msw\/server|@hublo\/sentinel\/test\/msw)['"]/;
|
|
7227
|
+
var DECORATED_ABSTRACT_MEMBER = /@[A-Za-z_$][\w$]*\s*(?:\([^)]*\))?\s*(?:\r?\n\s*(?:@[A-Za-z_$][\w$]*\s*(?:\([^)]*\))?\s*)*)*\r?\n\s*(?:public |protected |private |readonly )*abstract\s+[A-Za-z_$][\w$]*\s*[?!]?\s*[:(]/;
|
|
7228
|
+
function readModuleTraits(cwd) {
|
|
7229
|
+
const traits = {
|
|
7230
|
+
msw: false,
|
|
7231
|
+
decoratedAbstractMember: false,
|
|
7232
|
+
automockedPackages: []
|
|
7233
|
+
};
|
|
7234
|
+
const automocked = /* @__PURE__ */ new Set();
|
|
7235
|
+
const walk2 = (dir) => {
|
|
7236
|
+
let entries;
|
|
7237
|
+
try {
|
|
7238
|
+
entries = readdirSync6(dir);
|
|
7239
|
+
} catch {
|
|
7240
|
+
return;
|
|
7241
|
+
}
|
|
7242
|
+
for (const entry of entries) {
|
|
7243
|
+
if (SKIPPED3.has(entry)) continue;
|
|
7244
|
+
const path = join30(dir, entry);
|
|
7245
|
+
let directory;
|
|
7246
|
+
try {
|
|
7247
|
+
directory = statSync6(path).isDirectory();
|
|
7248
|
+
} catch {
|
|
7249
|
+
continue;
|
|
7250
|
+
}
|
|
7251
|
+
if (directory) {
|
|
7252
|
+
walk2(path);
|
|
7253
|
+
continue;
|
|
7254
|
+
}
|
|
7255
|
+
if (!/\.tsx?$/.test(entry)) continue;
|
|
7256
|
+
let source;
|
|
7257
|
+
try {
|
|
7258
|
+
source = readFileSync24(path, "utf8");
|
|
7259
|
+
} catch {
|
|
7260
|
+
continue;
|
|
7261
|
+
}
|
|
7262
|
+
if (!traits.msw && MENTIONS_MSW.test(source)) traits.msw = true;
|
|
7263
|
+
if (!traits.decoratedAbstractMember && DECORATED_ABSTRACT_MEMBER.test(source)) {
|
|
7264
|
+
traits.decoratedAbstractMember = true;
|
|
7265
|
+
}
|
|
7266
|
+
BARE_AUTOMOCK.lastIndex = 0;
|
|
7267
|
+
for (const match of source.matchAll(BARE_AUTOMOCK)) automocked.add(match[1]);
|
|
7268
|
+
}
|
|
7269
|
+
};
|
|
7270
|
+
walk2(cwd);
|
|
7271
|
+
traits.automockedPackages = [...automocked].sort();
|
|
7272
|
+
return traits;
|
|
7273
|
+
}
|
|
7274
|
+
var withoutRootDir = (value) => value.replaceAll("<rootDir>/", "./").replaceAll("<rootDir>", ".");
|
|
7275
|
+
function setupBlock(reading, msw) {
|
|
7276
|
+
const own = [...reading.setupFiles ?? [], ...reading.setupFilesAfterEnv ?? []].filter((file) => !file.endsWith("jest.setup.after.env.js")).map((file) => `'${withoutRootDir(file)}'`);
|
|
7277
|
+
const sentinel = [`'${SENTINEL_TEST_SETUP}'`, ...msw ? [`'${SENTINEL_MSW_SETUP}'`] : []];
|
|
7278
|
+
return ` setupFiles: [${[...sentinel, ...own].join(", ")}],`;
|
|
7279
|
+
}
|
|
7280
|
+
function envBlock(reading) {
|
|
7281
|
+
if (reading.env === void 0 || reading.env.length === 0) return "";
|
|
7282
|
+
const entries = reading.env.map((entry) => ` ${entry.name}: ${entry.value},`);
|
|
7283
|
+
return [" env: {", ...entries, " },"].join("\n");
|
|
7284
|
+
}
|
|
7285
|
+
function timeoutBlock(reading) {
|
|
7286
|
+
return reading.testTimeout === void 0 ? "" : ` testTimeout: ${reading.testTimeout},`;
|
|
7287
|
+
}
|
|
7288
|
+
function globalSetupBlock(reading) {
|
|
7289
|
+
if (reading.globalSetup === void 0) return "";
|
|
7290
|
+
const files = reading.globalSetup.map((file) => `'${withoutRootDir(file)}'`).join(", ");
|
|
7291
|
+
return ` globalSetup: [${files}],`;
|
|
7292
|
+
}
|
|
7293
|
+
function includeBlock(reading) {
|
|
7294
|
+
if (reading.testMatch === void 0) return "";
|
|
7295
|
+
const globs = reading.testMatch.map((glob) => `'${withoutRootDir(glob)}'`).join(", ");
|
|
7296
|
+
return ` include: [${globs}],`;
|
|
7297
|
+
}
|
|
7298
|
+
function excludeBlock(reading) {
|
|
7299
|
+
if (reading.excludeGlobs === void 0 || reading.excludeGlobs.length === 0) return "";
|
|
7300
|
+
const globs = reading.excludeGlobs.map((glob) => `'${glob}'`).join(", ");
|
|
7301
|
+
return ` exclude: [...configDefaults.exclude, ${globs}],`;
|
|
7302
|
+
}
|
|
7303
|
+
function aliasBlock(reading) {
|
|
7304
|
+
if (reading.aliases === void 0 || reading.aliases.length === 0) return "";
|
|
7305
|
+
const entries = reading.aliases.map(
|
|
7306
|
+
(alias) => ` { find: /${alias.find.replaceAll("/", "\\\\/")}/, replacement: path.resolve(import.meta.dirname, '${withoutRootDir(alias.replacement)}') },`
|
|
7307
|
+
);
|
|
7308
|
+
return [" resolve: {", " alias: [", ...entries, " ],", " },"].join("\n");
|
|
7309
|
+
}
|
|
7310
|
+
function generateNestTestConfig(cwd, reading) {
|
|
7311
|
+
if (reading.refusals.length > 0) return { refusals: reading.refusals };
|
|
7312
|
+
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
7313
|
+
if (workspaceRoot === void 0) {
|
|
7314
|
+
return { refusals: ["the workspace root could not be found from this module"] };
|
|
7315
|
+
}
|
|
7316
|
+
const traits = readModuleTraits(cwd);
|
|
7317
|
+
const up = hopsToRoot(cwd, workspaceRoot);
|
|
7318
|
+
const test = [
|
|
7319
|
+
environmentBlock(reading),
|
|
7320
|
+
setupBlock(reading, traits.msw),
|
|
7321
|
+
envBlock(reading),
|
|
7322
|
+
timeoutBlock(reading),
|
|
7323
|
+
globalSetupBlock(reading),
|
|
7324
|
+
includeBlock(reading),
|
|
7325
|
+
excludeBlock(reading),
|
|
7326
|
+
inlineBlock(traits.automockedPackages),
|
|
7327
|
+
coverageBlock(reading)
|
|
7328
|
+
].filter((line) => line !== "");
|
|
7329
|
+
const needsDefaults = test.some((line) => line.includes("configDefaults"));
|
|
7330
|
+
const imports = [
|
|
7331
|
+
`import path from 'node:path'`,
|
|
7332
|
+
"",
|
|
7333
|
+
...needsDefaults ? [`import { configDefaults } from 'vitest/config'`, ""] : [],
|
|
7334
|
+
`import { defineConfig, nestTestConfig } from '${NEST_TEST_PRESET_SPECIFIER}'`
|
|
7335
|
+
].join("\n");
|
|
7336
|
+
const overrides = [aliasBlock(reading), " test: {", ...test, " },"].filter(
|
|
7337
|
+
(line) => line !== ""
|
|
7338
|
+
);
|
|
7339
|
+
const decoratorLine = traits.decoratedAbstractMember ? "\n lowerDecoratorsWithTypeScript: true," : "";
|
|
7340
|
+
return {
|
|
7341
|
+
refusals: [],
|
|
7342
|
+
source: `${imports}
|
|
7343
|
+
|
|
7344
|
+
export default defineConfig(
|
|
7345
|
+
nestTestConfig({
|
|
7346
|
+
root: import.meta.dirname,
|
|
7347
|
+
workspaceRoot: path.resolve(import.meta.dirname, '${up}'),${decoratorLine}
|
|
7348
|
+
overrides: {
|
|
7349
|
+
${overrides.join("\n")}
|
|
7350
|
+
},
|
|
7351
|
+
}),
|
|
7352
|
+
)
|
|
7353
|
+
`
|
|
7354
|
+
};
|
|
7355
|
+
}
|
|
7356
|
+
|
|
7357
|
+
// src/roles/test/nest/read-jest-config.ts
|
|
7358
|
+
import { existsSync as existsSync25, readFileSync as readFileSync25 } from "fs";
|
|
7359
|
+
import { join as join31 } from "path";
|
|
7360
|
+
var CARRIED = [
|
|
7361
|
+
"testEnvironment",
|
|
7362
|
+
"coverageDirectory",
|
|
7363
|
+
"setupFilesAfterEnv",
|
|
7364
|
+
"setupFiles",
|
|
7365
|
+
"testTimeout",
|
|
7366
|
+
"globalSetup",
|
|
7367
|
+
"testMatch",
|
|
7368
|
+
"collectCoverageFrom",
|
|
7369
|
+
"testPathIgnorePatterns",
|
|
7370
|
+
"moduleNameMapper"
|
|
7371
|
+
];
|
|
7372
|
+
var DISCARDED = [
|
|
7373
|
+
"preset",
|
|
7374
|
+
"displayName",
|
|
7375
|
+
"moduleFileExtensions",
|
|
7376
|
+
"transform",
|
|
7377
|
+
"extensionsToTreatAsEsm"
|
|
7378
|
+
];
|
|
7379
|
+
var STRING = String.raw`['"]([^'"]*)['"]`;
|
|
7380
|
+
function readString(body, key) {
|
|
7381
|
+
return new RegExp(String.raw`\b${key}\s*:\s*${STRING}`).exec(body)?.[1];
|
|
7382
|
+
}
|
|
7383
|
+
function readNumber(body, key) {
|
|
7384
|
+
return new RegExp(String.raw`\b${key}\s*:\s*(\d+)`).exec(body)?.[1];
|
|
7385
|
+
}
|
|
7386
|
+
function objectBody(source, key) {
|
|
7387
|
+
const at = new RegExp(String.raw`\b${key}\s*:\s*\{`).exec(source);
|
|
7388
|
+
if (at === null) return void 0;
|
|
7389
|
+
const from = at.index + at[0].length - 1;
|
|
7390
|
+
let depth = 0;
|
|
7391
|
+
for (let i = from; i < source.length; i += 1) {
|
|
7392
|
+
if (source[i] === "{") depth += 1;
|
|
7393
|
+
else if (source[i] === "}") {
|
|
7394
|
+
depth -= 1;
|
|
7395
|
+
if (depth === 0) return source.slice(from + 1, i);
|
|
7396
|
+
}
|
|
7397
|
+
}
|
|
7398
|
+
return void 0;
|
|
7399
|
+
}
|
|
7400
|
+
function objectBodyAfter(source, offset) {
|
|
7401
|
+
const open = source.indexOf("{", offset);
|
|
7402
|
+
if (open === -1) return void 0;
|
|
7403
|
+
let depth = 0;
|
|
7404
|
+
for (let i = open; i < source.length; i += 1) {
|
|
7405
|
+
if (source[i] === "{") depth += 1;
|
|
7406
|
+
else if (source[i] === "}") {
|
|
7407
|
+
depth -= 1;
|
|
7408
|
+
if (depth === 0) return source.slice(open + 1, i);
|
|
7409
|
+
}
|
|
7410
|
+
}
|
|
7411
|
+
return void 0;
|
|
7412
|
+
}
|
|
7413
|
+
function readEnvironment(source) {
|
|
7414
|
+
const entries = [];
|
|
7415
|
+
const at = /process\.env\s*=\s*Object\.assign\(\s*process\.env\s*,/.exec(source);
|
|
7416
|
+
if (at !== null) {
|
|
7417
|
+
const block = objectBody(source.slice(at.index), "assign") ?? objectBodyAfter(source, at.index);
|
|
7418
|
+
for (const raw of (block ?? "").split(/,(?![^[\]{}]*[\]}])/)) {
|
|
7419
|
+
const entry = /^\s*(?:\/\/[^\n]*\n\s*)?([A-Za-z_][\w]*)\s*:\s*([\s\S]+?)\s*$/.exec(raw);
|
|
7420
|
+
if (entry !== null) entries.push({ name: entry[1], value: entry[2] });
|
|
7421
|
+
}
|
|
7422
|
+
}
|
|
7423
|
+
for (const match of source.matchAll(/process\.env\.(\w+)\s*\?\?=\s*(.+?),?\s*$/gm)) {
|
|
7424
|
+
entries.push({ name: match[1], value: match[2] });
|
|
7425
|
+
}
|
|
7426
|
+
return entries;
|
|
7427
|
+
}
|
|
7428
|
+
function readCoverageFrom(body) {
|
|
7429
|
+
const globs = readStringArray(body, "collectCoverageFrom");
|
|
7430
|
+
if (globs === void 0) return void 0;
|
|
7431
|
+
return {
|
|
7432
|
+
include: globs.filter((glob) => !glob.startsWith("!")),
|
|
7433
|
+
exclude: globs.filter((glob) => glob.startsWith("!")).map((glob) => glob.slice(1))
|
|
7434
|
+
};
|
|
7435
|
+
}
|
|
7436
|
+
function translateIgnorePattern(raw) {
|
|
7437
|
+
const pattern = raw.replaceAll("\\\\", "\\");
|
|
7438
|
+
if (pattern === "/node_modules/" || pattern === "node_modules") return {};
|
|
7439
|
+
const suffix = /^\\\.([\w.\\-]+)\$$/.exec(pattern);
|
|
7440
|
+
if (suffix !== null) return { glob: `**/*.${suffix[1].replaceAll("\\.", ".")}` };
|
|
7441
|
+
const directory = /^\/([\w\-/]+)\/$/.exec(pattern);
|
|
7442
|
+
if (directory !== null) return { glob: `**/${directory[1]}/**` };
|
|
7443
|
+
return {
|
|
7444
|
+
refusal: `its \`testPathIgnorePatterns\` entry \`${raw}\` is a regex shape this reader does not translate. Vitest excludes by GLOB, and a regex copied across matches nothing, so the files it names would all run`
|
|
7445
|
+
};
|
|
7446
|
+
}
|
|
7447
|
+
function readModuleNameMapper(body) {
|
|
7448
|
+
const block = objectBody(body, "moduleNameMapper");
|
|
7449
|
+
if (block === void 0) return { aliases: [], dropped: [] };
|
|
7450
|
+
const aliases = [];
|
|
7451
|
+
const dropped = [];
|
|
7452
|
+
for (const entry of block.matchAll(
|
|
7453
|
+
new RegExp(String.raw`${STRING}\s*:\s*\n?\s*${STRING}`, "g")
|
|
7454
|
+
)) {
|
|
7455
|
+
const find = entry[1];
|
|
7456
|
+
const replacement = entry[2];
|
|
7457
|
+
const named = find.replace(/^\^/, "").replace(/\$$/, "").replaceAll("\\", "");
|
|
7458
|
+
if (named !== "" && replacement.includes(`node_modules/${named}/`)) {
|
|
7459
|
+
dropped.push(find);
|
|
7460
|
+
continue;
|
|
7461
|
+
}
|
|
7462
|
+
aliases.push({ find, replacement: replacement.replace(/^<rootDir>\//, "") });
|
|
7463
|
+
}
|
|
7464
|
+
return { aliases, dropped };
|
|
7465
|
+
}
|
|
7466
|
+
function readStringArray(body, key) {
|
|
7467
|
+
const raw = new RegExp(String.raw`\b${key}\s*:\s*\[([^\]]*)\]`).exec(body)?.[1];
|
|
7468
|
+
if (raw === void 0) return void 0;
|
|
7469
|
+
return [...raw.matchAll(new RegExp(STRING, "g"))].map((match) => match[1]);
|
|
7470
|
+
}
|
|
7471
|
+
function configBody(source) {
|
|
7472
|
+
const start = /(?:const\s+\w+(?::\s*\w+)?\s*=|export\s+default)\s*\{/.exec(source);
|
|
7473
|
+
if (start === null) return void 0;
|
|
7474
|
+
let depth = 0;
|
|
7475
|
+
const from = start.index + start[0].length - 1;
|
|
7476
|
+
for (let i = from; i < source.length; i += 1) {
|
|
7477
|
+
const char = source[i];
|
|
7478
|
+
if (char === "{") depth += 1;
|
|
7479
|
+
else if (char === "}") {
|
|
7480
|
+
depth -= 1;
|
|
7481
|
+
if (depth === 0) return source.slice(from + 1, i);
|
|
7482
|
+
}
|
|
7483
|
+
}
|
|
7484
|
+
return void 0;
|
|
7485
|
+
}
|
|
7486
|
+
function declaredKeys(body) {
|
|
7487
|
+
const keys = [];
|
|
7488
|
+
let depth = 0;
|
|
7489
|
+
for (const line of body.split("\n")) {
|
|
7490
|
+
if (depth === 0) {
|
|
7491
|
+
const key = /^\s*([A-Za-z_$][\w$]*)\s*:/.exec(line);
|
|
7492
|
+
if (key !== null) keys.push(key[1]);
|
|
7493
|
+
}
|
|
7494
|
+
for (const char of line) {
|
|
7495
|
+
if (char === "{" || char === "[") depth += 1;
|
|
7496
|
+
else if (char === "}" || char === "]") depth -= 1;
|
|
7497
|
+
}
|
|
7498
|
+
}
|
|
7499
|
+
return keys;
|
|
7500
|
+
}
|
|
7501
|
+
function sideEffects(source) {
|
|
7502
|
+
const found = [];
|
|
7503
|
+
for (const line of source.split("\n")) {
|
|
7504
|
+
const valueImport = /^\s*import\s+(?!type\b)(.*)$/.exec(line);
|
|
7505
|
+
if (valueImport === null || /from\s+['"]jest['"]/.test(line)) continue;
|
|
7506
|
+
const bindings = [
|
|
7507
|
+
...(/\{([^}]*)\}/.exec(valueImport[1])?.[1] ?? "").split(","),
|
|
7508
|
+
/^\s*(\w+)\s*(?:,|from)/.exec(valueImport[1])?.[1] ?? ""
|
|
7509
|
+
].map((name) => name.split(" as ").pop()?.trim() ?? "").filter((name) => name !== "");
|
|
7510
|
+
const used = bindings.some((name) => {
|
|
7511
|
+
const uses = source.match(new RegExp(String.raw`\b${name}\b`, "g")) ?? [];
|
|
7512
|
+
return uses.length > 1;
|
|
7513
|
+
});
|
|
7514
|
+
if (used) found.push(`it imports a value it uses (\`${line.trim()}\`)`);
|
|
7515
|
+
}
|
|
7516
|
+
return found;
|
|
7517
|
+
}
|
|
7518
|
+
function readJestConfig(cwd) {
|
|
7519
|
+
const file = JEST_CONFIG_FILES.find((name) => existsSync25(join31(cwd, name)));
|
|
7520
|
+
if (file === void 0) return void 0;
|
|
7521
|
+
let source;
|
|
7522
|
+
try {
|
|
7523
|
+
source = readFileSync25(join31(cwd, file), "utf8");
|
|
7524
|
+
} catch {
|
|
7525
|
+
return { file, refusals: [`${file} could not be read`], notes: [] };
|
|
7526
|
+
}
|
|
7527
|
+
const refusals = sideEffects(source);
|
|
7528
|
+
const body = configBody(source);
|
|
7529
|
+
if (body === void 0) {
|
|
7530
|
+
return { file, refusals: [...refusals, "its config object could not be located"], notes: [] };
|
|
7531
|
+
}
|
|
7532
|
+
const known = /* @__PURE__ */ new Set([...CARRIED, ...DISCARDED]);
|
|
7533
|
+
for (const key of declaredKeys(body)) {
|
|
7534
|
+
if (!known.has(key)) refusals.push(`it declares \`${key}\`, which is not transposed`);
|
|
7535
|
+
}
|
|
7536
|
+
const notes = [];
|
|
7537
|
+
const afterEnv = readStringArray(body, "setupFilesAfterEnv");
|
|
7538
|
+
const before = readStringArray(body, "setupFiles");
|
|
7539
|
+
if (afterEnv !== void 0 && before !== void 0) {
|
|
7540
|
+
notes.push(
|
|
7541
|
+
"jest runs `setupFiles` before its test framework is installed and `setupFilesAfterEnv` after it; Vitest has one `setupFiles`, so both land there in that order. A setup file that relied on the framework being ABSENT will now find it present"
|
|
7542
|
+
);
|
|
7543
|
+
}
|
|
7544
|
+
const ignore = readStringArray(body, "testPathIgnorePatterns") ?? [];
|
|
7545
|
+
const excludeGlobs = [];
|
|
7546
|
+
for (const pattern of ignore) {
|
|
7547
|
+
const { glob, refusal } = translateIgnorePattern(pattern);
|
|
7548
|
+
if (refusal !== void 0) refusals.push(refusal);
|
|
7549
|
+
else if (glob !== void 0) excludeGlobs.push(glob);
|
|
7550
|
+
}
|
|
7551
|
+
const mapper = readModuleNameMapper(body);
|
|
7552
|
+
for (const find of mapper.dropped) {
|
|
7553
|
+
notes.push(
|
|
7554
|
+
`\`moduleNameMapper\` entry \`${find}\` pointed at another build of the package it matches, which is jest saying it could not load the package as it ships. Vitest can, so the mapping is dropped rather than carried`
|
|
7555
|
+
);
|
|
7556
|
+
}
|
|
7557
|
+
const coverageFrom = readCoverageFrom(body);
|
|
7558
|
+
const env = readEnvironment(source);
|
|
7559
|
+
const globalSetup = readString(body, "globalSetup");
|
|
7560
|
+
const testTimeout = readNumber(body, "testTimeout");
|
|
7561
|
+
const testMatch = readStringArray(body, "testMatch");
|
|
7562
|
+
return {
|
|
7563
|
+
file,
|
|
7564
|
+
...readString(body, "testEnvironment") !== void 0 ? { testEnvironment: readString(body, "testEnvironment") } : {},
|
|
7565
|
+
...readString(body, "coverageDirectory") !== void 0 ? { coverageDirectory: readString(body, "coverageDirectory") } : {},
|
|
7566
|
+
...afterEnv !== void 0 ? { setupFilesAfterEnv: afterEnv } : {},
|
|
7567
|
+
...before !== void 0 ? { setupFiles: before } : {},
|
|
7568
|
+
...testTimeout !== void 0 ? { testTimeout } : {},
|
|
7569
|
+
...globalSetup !== void 0 ? { globalSetup: [globalSetup] } : {},
|
|
7570
|
+
...testMatch !== void 0 ? { testMatch } : {},
|
|
7571
|
+
...coverageFrom !== void 0 ? { coverageFrom } : {},
|
|
7572
|
+
...excludeGlobs.length > 0 ? { excludeGlobs } : {},
|
|
7573
|
+
...mapper.aliases.length > 0 ? { aliases: mapper.aliases } : {},
|
|
7574
|
+
...env.length > 0 ? { env } : {},
|
|
7575
|
+
refusals,
|
|
7576
|
+
notes
|
|
7577
|
+
};
|
|
7578
|
+
}
|
|
7579
|
+
|
|
7580
|
+
// src/roles/test/jest-migration.ts
|
|
7581
|
+
var SPEC_TSCONFIG = "tsconfig.spec.json";
|
|
7582
|
+
var VITEST_GLOBAL_TYPES = "vitest/globals";
|
|
7583
|
+
function specTypesOperation(cwd) {
|
|
7584
|
+
const path = join32(cwd, SPEC_TSCONFIG);
|
|
7585
|
+
if (!existsSync26(path)) return void 0;
|
|
7586
|
+
let config;
|
|
7587
|
+
try {
|
|
7588
|
+
config = parseJsonc(
|
|
7589
|
+
readFileSync26(path, "utf8"),
|
|
7590
|
+
SPEC_TSCONFIG
|
|
7591
|
+
);
|
|
7592
|
+
} catch {
|
|
7593
|
+
return void 0;
|
|
7594
|
+
}
|
|
7595
|
+
const types = config?.compilerOptions?.types;
|
|
7596
|
+
if (!Array.isArray(types) || !types.includes("jest")) return void 0;
|
|
7597
|
+
return {
|
|
7598
|
+
kind: "merge-json",
|
|
7599
|
+
path: SPEC_TSCONFIG,
|
|
7600
|
+
value: {
|
|
7601
|
+
compilerOptions: {
|
|
7602
|
+
types: types.map((entry) => entry === "jest" ? VITEST_GLOBAL_TYPES : entry)
|
|
7603
|
+
}
|
|
7604
|
+
}
|
|
7605
|
+
};
|
|
7606
|
+
}
|
|
7607
|
+
function siblingScriptRewrites(cwd) {
|
|
7608
|
+
const rewrites = {};
|
|
7609
|
+
for (const [name, command] of Object.entries(moduleScripts(cwd))) {
|
|
7610
|
+
if (name === TEST_SCRIPT_NAME || typeof command !== "string") continue;
|
|
7611
|
+
const rewritten = rewriteVitestScript(command);
|
|
7612
|
+
if (rewritten !== command) rewrites[name] = rewritten;
|
|
7613
|
+
}
|
|
7614
|
+
return rewrites;
|
|
7615
|
+
}
|
|
7616
|
+
function blocked(reason) {
|
|
7617
|
+
return { operations: [], blocked: reason };
|
|
7618
|
+
}
|
|
7619
|
+
function planJestMigration(context, adoption) {
|
|
7620
|
+
if (adoption.jestConfigs.length > 1) {
|
|
7621
|
+
const [primary, ...rest] = adoption.jestConfigs;
|
|
7622
|
+
return blocked(
|
|
7623
|
+
`this module has a second run mode (${rest.join(", ")}) beside ${primary}, with its own target. One config file cannot stand for both, and how run modes are expressed under Vitest is an open question here, so this module is not migrated yet.`
|
|
7624
|
+
);
|
|
7625
|
+
}
|
|
7626
|
+
const reading = readJestConfig(context.cwd);
|
|
7627
|
+
if (reading === void 0) {
|
|
7628
|
+
return blocked(`${adoption.jestConfigs.join(", ")} could not be read, so nothing was written.`);
|
|
7629
|
+
}
|
|
7630
|
+
const generated = generateNestTestConfig(context.cwd, reading);
|
|
7631
|
+
if (generated.source === void 0) {
|
|
7632
|
+
return blocked(
|
|
7633
|
+
`this module's jest config does not transpose as it stands: ${generated.refusals.join("; ")}. Nothing was written; this needs a look before the migration runs.`
|
|
7634
|
+
);
|
|
7635
|
+
}
|
|
7636
|
+
const migration = migrateModuleTests(context.cwd);
|
|
7637
|
+
if (migration.blockers.length > 0) {
|
|
7638
|
+
const listed = migration.blockers.map((blocker) => `
|
|
7639
|
+
${blocker.file}: ${blocker.reason}`).join("");
|
|
7640
|
+
return blocked(
|
|
7641
|
+
`${migration.blockers.length} thing(s) in this module's test files need a human before it can move. Nothing was written, because a config without its files is a suite calling jest.* under Vitest.${listed}`
|
|
7642
|
+
);
|
|
7643
|
+
}
|
|
7644
|
+
const existing = projectTargets(context.cwd)?.[TEST_SCRIPT_NAME];
|
|
7645
|
+
const siblings = siblingScriptRewrites(context.cwd);
|
|
7646
|
+
const specTypes = specTypesOperation(context.cwd);
|
|
7647
|
+
const operations = [
|
|
7648
|
+
{ kind: "write", path: NEST_TEST_CONFIG_FILE, contents: generated.source },
|
|
7649
|
+
...migration.operations,
|
|
7650
|
+
...specTypes === void 0 ? [] : [specTypes],
|
|
7651
|
+
manifestOperation(context.cwd, { [TEST_SCRIPT_NAME]: SENTINEL_TEST_COMMAND, ...siblings }),
|
|
7652
|
+
...nxTargetOperations({
|
|
7653
|
+
cwd: context.cwd,
|
|
7654
|
+
targets: testTarget({
|
|
7655
|
+
configFiles: [NEST_TEST_CONFIG_FILE],
|
|
7656
|
+
outputs: existing?.outputs ?? [],
|
|
7657
|
+
dependsOn: existing?.dependsOn ?? [],
|
|
7658
|
+
configurations: existing?.configurations ?? {}
|
|
7659
|
+
})
|
|
7660
|
+
}),
|
|
7661
|
+
...adoption.jestConfigs.map((name) => ({ kind: "delete", path: name }))
|
|
7662
|
+
];
|
|
7663
|
+
if (existing !== void 0) {
|
|
7664
|
+
operations.push({
|
|
7665
|
+
kind: "remove-json-keys",
|
|
7666
|
+
path: "project.json",
|
|
7667
|
+
keys: [["targets", TEST_SCRIPT_NAME]]
|
|
7668
|
+
});
|
|
7669
|
+
}
|
|
7670
|
+
const notes = [
|
|
7671
|
+
`${migration.files} test file(s) migrated from jest to Vitest, ${migration.renamed} site(s) rewritten${migration.hoisted > 0 ? `, ${migration.hoisted} of them moving a mock factory's captures into vi.hoisted` : ""}.`,
|
|
7672
|
+
`${NEST_TEST_CONFIG_FILE} written from what ${adoption.jestConfigs.join(", ")} declared, and that config deleted. Every specifier in the new one is a package name: nothing in it reaches outside this module.`
|
|
7673
|
+
];
|
|
7674
|
+
if (specTypes !== void 0) {
|
|
7675
|
+
notes.push(
|
|
7676
|
+
`${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.`
|
|
7677
|
+
);
|
|
7678
|
+
}
|
|
7679
|
+
notes.push(
|
|
7680
|
+
`run this module's own lint --fix (or its formatter) after installing: the Vitest type imports are written beside the existing ones, and ordering belongs to the tool that enforces it.`
|
|
7681
|
+
);
|
|
7682
|
+
if (Object.keys(siblings).length > 0) {
|
|
7683
|
+
notes.push(`rewrote ${Object.keys(siblings).join(", ")} to run through sentinel.`);
|
|
7684
|
+
}
|
|
7685
|
+
return { operations, notes };
|
|
7686
|
+
}
|
|
7687
|
+
|
|
7688
|
+
// src/roles/test/plan.ts
|
|
7689
|
+
function ownedTestDependencies(cwd, declared) {
|
|
7690
|
+
return declared.filter((entry) => SENTINEL_OWNED_TEST_PACKAGES.includes(entry.name)).flatMap((entry) => [
|
|
7691
|
+
["dependencies", entry.name],
|
|
7692
|
+
["devDependencies", entry.name]
|
|
7693
|
+
]).filter(([section]) => section !== void 0);
|
|
7694
|
+
}
|
|
7695
|
+
var DEFERRED = {
|
|
7696
|
+
svelte: `the svelte modules here carry no svelte test at all, so adopting them would prove nothing about svelte, and their owners have a framework migration under way that moves these versions anyway`
|
|
7697
|
+
};
|
|
7698
|
+
function plan3(context) {
|
|
7699
|
+
const deferred = DEFERRED[context.preset];
|
|
7700
|
+
if (deferred !== void 0) {
|
|
7701
|
+
return {
|
|
7702
|
+
operations: [],
|
|
7703
|
+
skipped: `the test role defers ${context.preset} for now. It defers because ${deferred}.`
|
|
7704
|
+
};
|
|
7705
|
+
}
|
|
7706
|
+
const configFile = testConfigFile(context.cwd);
|
|
7707
|
+
const adoption = readTestAdoption(context.cwd);
|
|
7708
|
+
if (adoption.state === "no-tests") {
|
|
7709
|
+
const operations2 = adoption.jestConfigs.map((name) => ({
|
|
7710
|
+
kind: "delete",
|
|
7711
|
+
path: name
|
|
7712
|
+
}));
|
|
7713
|
+
const owned2 = ownedTestDependencies(context.cwd, adoption.ownDeclarations);
|
|
7714
|
+
if (owned2.length > 0) {
|
|
7715
|
+
operations2.push({ kind: "remove-json-keys", path: "package.json", keys: owned2 });
|
|
7716
|
+
}
|
|
7717
|
+
const hasTarget = projectTargets(context.cwd)?.[TEST_SCRIPT_NAME] !== void 0;
|
|
7718
|
+
if (hasTarget) {
|
|
7719
|
+
operations2.push({
|
|
7720
|
+
kind: "remove-json-keys",
|
|
7721
|
+
path: "project.json",
|
|
7722
|
+
keys: [["targets", TEST_SCRIPT_NAME]]
|
|
7723
|
+
});
|
|
7724
|
+
}
|
|
7725
|
+
if (operations2.length === 0) {
|
|
7726
|
+
return {
|
|
7727
|
+
operations: [],
|
|
7728
|
+
skipped: `this module has no test and no jest leftovers, so there is nothing to do here.`
|
|
7729
|
+
};
|
|
7730
|
+
}
|
|
7731
|
+
return {
|
|
7732
|
+
operations: operations2,
|
|
7733
|
+
notes: [
|
|
7734
|
+
// Names what is actually removed, never a shape this module does not have: half of these
|
|
7735
|
+
// modules carry a config with no target behind it, and claiming otherwise would describe
|
|
7736
|
+
// an operation the plan never performs.
|
|
7737
|
+
`this module has no test file, so this is a cleanup rather than an adoption: ${[...adoption.jestConfigs, ...hasTarget ? ["the jest target it feeds"] : []].join(" and ")} removed, so nothing here breaks the day jest leaves the workspace. No test config is written in exchange, because there is nothing to configure.`
|
|
7738
|
+
]
|
|
7739
|
+
};
|
|
7740
|
+
}
|
|
7741
|
+
if (configFile === void 0) {
|
|
7742
|
+
if (adoption.state === "jest") return planJestMigration(context, adoption);
|
|
7743
|
+
return {
|
|
7744
|
+
operations: [],
|
|
7745
|
+
skipped: `this module has no test config and no test files, so there is nothing to point at sentinel. What it needs is its jest leftovers removed, which is a cleanup rather than an adoption.`
|
|
7746
|
+
};
|
|
7747
|
+
}
|
|
7748
|
+
const operations = [];
|
|
7749
|
+
const notes = [];
|
|
7750
|
+
const rewrite = rewriteToolchainImports(
|
|
7751
|
+
testToolchain(context.preset),
|
|
7752
|
+
readTestConfigSource(context.cwd, configFile),
|
|
7753
|
+
configFile
|
|
7754
|
+
);
|
|
7755
|
+
if (rewrite.kind === "blocked") {
|
|
7756
|
+
return {
|
|
7757
|
+
operations: [],
|
|
7758
|
+
skipped: `${configFile} could not be pointed at sentinel, so nothing was written: ${rewrite.why}. Nothing here is broken; this needs a look before the role applies.`
|
|
7759
|
+
};
|
|
7760
|
+
}
|
|
7761
|
+
if (rewrite.kind === "rewritten") {
|
|
7762
|
+
operations.push({ kind: "write", path: configFile, contents: rewrite.source });
|
|
7763
|
+
notes.push(
|
|
7764
|
+
`${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.`
|
|
7765
|
+
);
|
|
7766
|
+
}
|
|
7767
|
+
const existing = projectTargets(context.cwd)?.[TEST_SCRIPT_NAME];
|
|
7768
|
+
const outputs = existing?.outputs ?? [];
|
|
7769
|
+
const dependsOn = existing?.dependsOn ?? [];
|
|
7770
|
+
const configurations = existing?.configurations ?? {};
|
|
7771
|
+
if (dependsOn.length > 0) {
|
|
7772
|
+
notes.push(`carried \`dependsOn\` from the replaced target: ${dependsOn.join(", ")}.`);
|
|
7773
|
+
}
|
|
7774
|
+
if (Object.keys(configurations).length > 0) {
|
|
7775
|
+
notes.push(
|
|
7776
|
+
`carried the \`${Object.keys(configurations).join("`, `")}\` configuration(s): CI invokes \`--configuration=ci\`, and a target that no longer offers it fails the caller, not the module.`
|
|
7777
|
+
);
|
|
7778
|
+
}
|
|
7779
|
+
if (outputs.length === 0) {
|
|
7780
|
+
notes.push(
|
|
7781
|
+
`this target declares no \`outputs\`, so it is left UNCACHED. Caching a run that restores nothing is the defect where nx reports a hit and the coverage directory is empty; if this module writes coverage, declare where and the target can cache.`
|
|
7782
|
+
);
|
|
7783
|
+
}
|
|
7784
|
+
const siblingScripts = {};
|
|
7785
|
+
for (const [name, command] of Object.entries(moduleScripts(context.cwd))) {
|
|
7786
|
+
if (name === TEST_SCRIPT_NAME || typeof command !== "string") continue;
|
|
7787
|
+
const rewritten = rewriteVitestScript(command);
|
|
7788
|
+
if (rewritten !== command) siblingScripts[name] = rewritten;
|
|
7789
|
+
}
|
|
7790
|
+
if (Object.keys(siblingScripts).length > 0) {
|
|
7791
|
+
notes.push(
|
|
7792
|
+
`rewrote ${Object.keys(siblingScripts).join(", ")} to run through sentinel: they called vitest directly, and the dependency they relied on is removed below. A watch script keeps watching, through \`-- --watch\`.`
|
|
7793
|
+
);
|
|
7794
|
+
}
|
|
7795
|
+
operations.push(
|
|
7796
|
+
manifestOperation(context.cwd, {
|
|
7797
|
+
[TEST_SCRIPT_NAME]: SENTINEL_TEST_COMMAND,
|
|
7798
|
+
...siblingScripts
|
|
7799
|
+
})
|
|
7800
|
+
);
|
|
7801
|
+
operations.push(
|
|
7802
|
+
...nxTargetOperations({
|
|
7803
|
+
cwd: context.cwd,
|
|
7804
|
+
targets: testTarget({ configFiles: [configFile], outputs, dependsOn, configurations })
|
|
7805
|
+
})
|
|
7806
|
+
);
|
|
7807
|
+
const owned = ownedTestDependencies(context.cwd, adoption.ownDeclarations);
|
|
7808
|
+
if (owned.length > 0) {
|
|
7809
|
+
operations.push({ kind: "remove-json-keys", path: "package.json", keys: owned });
|
|
7810
|
+
notes.push(
|
|
7811
|
+
`removed ${[...new Set(owned.map(([, name]) => name))].join(", ")} from this module: sentinel owns the runner now, and two copies of Vitest in one run give the plugins a different Vite than the one running them.`
|
|
7812
|
+
);
|
|
7813
|
+
}
|
|
7814
|
+
if (adoption.jestConfigs.length > 0) {
|
|
7815
|
+
notes.push(
|
|
7816
|
+
`${adoption.jestConfigs.join(", ")} is still here beside the Vitest config. It is not this step's to delete, since something may still run it, but the workspace cannot drop jest while it exists.`
|
|
7817
|
+
);
|
|
7818
|
+
}
|
|
7819
|
+
return { operations, notes };
|
|
7820
|
+
}
|
|
7821
|
+
|
|
7822
|
+
// src/roles/test/adapters/vitest/vitest.adapter.ts
|
|
7823
|
+
function resolveVitest(cwd) {
|
|
7824
|
+
const fromSentinel = binFromOwnInstall("vitest", "vitest");
|
|
7825
|
+
if (fromSentinel !== void 0) return { bin: fromSentinel, origin: "sentinel" };
|
|
7826
|
+
const fromModule = resolveBin(cwd, "vitest");
|
|
7827
|
+
return fromModule !== void 0 ? { bin: fromModule, origin: "module" } : { bin: void 0, origin: "module" };
|
|
7828
|
+
}
|
|
7829
|
+
var VitestAdapter = class extends BaseAdapter {
|
|
7830
|
+
target = "test";
|
|
7831
|
+
runner = "vitest";
|
|
7832
|
+
appliesTo(_preset) {
|
|
7833
|
+
return true;
|
|
7834
|
+
}
|
|
7835
|
+
plan(context) {
|
|
7836
|
+
return plan3(context);
|
|
7837
|
+
}
|
|
7838
|
+
async status(ctx) {
|
|
7839
|
+
return Promise.resolve(readTestAdoption(ctx.cwd));
|
|
7840
|
+
}
|
|
7841
|
+
async inspect(ctx) {
|
|
7842
|
+
const adoption = readTestAdoption(ctx.cwd);
|
|
7843
|
+
return Promise.resolve({
|
|
7844
|
+
state: adoption.state,
|
|
7845
|
+
runner: adoption.runner,
|
|
7846
|
+
configFile: adoption.configFile,
|
|
7847
|
+
jestConfigs: adoption.jestConfigs,
|
|
7848
|
+
adopted: adoption.adopted,
|
|
7849
|
+
conformant: adoption.conformant,
|
|
7850
|
+
drift: adoption.drift,
|
|
7851
|
+
unreadable: adoption.unreadable
|
|
7852
|
+
});
|
|
7853
|
+
}
|
|
7854
|
+
/**
|
|
7855
|
+
* Run the module's tests.
|
|
7856
|
+
*
|
|
7857
|
+
* A module that has not adopted is reported and PASSES, the stance every role takes: `--run`
|
|
7858
|
+
* with no named target sweeps the workspace, so failing here would exit non-zero on the 107
|
|
7859
|
+
* modules still on jest, which says nothing anyone can act on.
|
|
7860
|
+
*/
|
|
7861
|
+
async run(ctx) {
|
|
7862
|
+
const adoption = readTestAdoption(ctx.cwd);
|
|
7863
|
+
if (!adoption.adopted) {
|
|
7864
|
+
this.say(
|
|
7865
|
+
adoption.state === "jest" ? `this module tests with jest (${adoption.jestConfigs.join(", ")}); run \`sentinel --init --test\` to migrate it.` : adoption.state === "no-tests" ? `this module has no tests.` : `no sentinel preset in this module's Vitest config; run \`sentinel --init --test\` to adopt.`
|
|
7866
|
+
);
|
|
7867
|
+
return Promise.resolve({ ok: true, code: 0 });
|
|
7868
|
+
}
|
|
7869
|
+
const { bin, origin } = resolveVitest(ctx.cwd);
|
|
7870
|
+
if (bin === void 0) {
|
|
7871
|
+
this.say(`could not find the vitest binary. Run \`pnpm install\` in the module.`);
|
|
7872
|
+
return Promise.resolve({ ok: false, code: 1 });
|
|
7873
|
+
}
|
|
7874
|
+
if (origin === "module") {
|
|
7875
|
+
this.say(
|
|
7876
|
+
`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.`
|
|
7877
|
+
);
|
|
7878
|
+
}
|
|
7879
|
+
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, { valueFlags: [] });
|
|
7880
|
+
const watching = options.includes("--watch") || options.includes("-w");
|
|
7881
|
+
const argv = watching ? [...options, ...paths] : ["run", ...options, ...paths];
|
|
7882
|
+
const result = spawnSync5(bin, argv, { cwd: ctx.cwd, stdio: "inherit" });
|
|
7883
|
+
if (result.error) {
|
|
7884
|
+
this.say(`could not run vitest (${result.error.message})`);
|
|
7885
|
+
return Promise.resolve({ ok: false, code: 1 });
|
|
7886
|
+
}
|
|
7887
|
+
const code = result.status ?? 1;
|
|
7888
|
+
return Promise.resolve({ ok: code === 0, code });
|
|
7889
|
+
}
|
|
7890
|
+
say(message) {
|
|
7891
|
+
process.stderr.write(`sentinel test(vitest): ${message}
|
|
7892
|
+
`);
|
|
7893
|
+
}
|
|
7894
|
+
};
|
|
7895
|
+
|
|
7896
|
+
// src/roles/test/register.ts
|
|
7897
|
+
function registerTest() {
|
|
7898
|
+
register(new VitestAdapter());
|
|
7899
|
+
setDefaultRunner("test", "vitest");
|
|
7900
|
+
}
|
|
7901
|
+
|
|
7902
|
+
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
7903
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
7904
|
+
import { existsSync as existsSync27, readFileSync as readFileSync28 } from "fs";
|
|
7905
|
+
import { createRequire as createRequire4 } from "module";
|
|
7906
|
+
import { join as join34 } from "path";
|
|
7907
|
+
|
|
7908
|
+
// src/roles/typescript/presets/nest.json
|
|
7909
|
+
var nest_default2 = {
|
|
7910
|
+
compilerOptions: {
|
|
7911
|
+
module: "esnext",
|
|
7912
|
+
moduleResolution: "bundler",
|
|
7913
|
+
types: ["node"],
|
|
7914
|
+
experimentalDecorators: true,
|
|
7915
|
+
emitDecoratorMetadata: true,
|
|
7916
|
+
declaration: true,
|
|
7917
|
+
sourceMap: true
|
|
7918
|
+
},
|
|
7919
|
+
notes: {
|
|
7920
|
+
module: "the backends are webpack-bundled and tsc only typechecks, so the module format is the bundler's concern; `bundler` resolution is what the repo already uses, and the only Node-friendly option TS7 still accepts since it removed classic `node`",
|
|
7921
|
+
target: "deliberately NOT set, here or in lib: those are environment the repo base owns. Overriding target flips useDefineForClassFields (TS2612) and narrowing lib drops DOM (GLfloat); both REPLACE the base rather than merge"
|
|
7922
|
+
}
|
|
7923
|
+
};
|
|
7924
|
+
|
|
7925
|
+
// src/roles/typescript/presets/node.json
|
|
7926
|
+
var node_default2 = {
|
|
7927
|
+
compilerOptions: {
|
|
7928
|
+
module: "esnext",
|
|
7929
|
+
moduleResolution: "bundler",
|
|
7930
|
+
types: ["node"],
|
|
7931
|
+
declaration: true
|
|
7932
|
+
},
|
|
7933
|
+
notes: {
|
|
7934
|
+
module: "esnext/bundler like the react and nest presets, matching a monorepo whose libs are bundler-resolved in practice. NOT NodeNext: that would force its stricter ESM rules onto libs the repo resolves through a bundler"
|
|
7935
|
+
}
|
|
7936
|
+
};
|
|
7937
|
+
|
|
7938
|
+
// src/roles/typescript/presets/react.json
|
|
7939
|
+
var react_default2 = {
|
|
7940
|
+
compilerOptions: {
|
|
7941
|
+
target: "ES2023",
|
|
7942
|
+
lib: ["ES2023", "DOM", "DOM.Iterable"],
|
|
7943
|
+
module: "ESNext",
|
|
7944
|
+
moduleResolution: "bundler",
|
|
7945
|
+
jsx: "react-jsx",
|
|
7946
|
+
useDefineForClassFields: true,
|
|
7947
|
+
isolatedModules: true,
|
|
7948
|
+
moduleDetection: "force",
|
|
7949
|
+
noEmit: true,
|
|
7950
|
+
noUncheckedSideEffectImports: true
|
|
7951
|
+
},
|
|
7952
|
+
notes: {
|
|
7953
|
+
noEmit: "vite owns the build; tsc only typechecks here"
|
|
7954
|
+
}
|
|
7955
|
+
};
|
|
7956
|
+
|
|
7957
|
+
// src/roles/typescript/presets/shared.json
|
|
7958
|
+
var shared_default2 = {
|
|
7959
|
+
$schema: "https://json.schemastore.org/tsconfig",
|
|
7960
|
+
compilerOptions: {
|
|
7961
|
+
strict: true,
|
|
7962
|
+
noFallthroughCasesInSwitch: true,
|
|
7963
|
+
forceConsistentCasingInFileNames: true,
|
|
7964
|
+
esModuleInterop: true,
|
|
7965
|
+
skipLibCheck: true
|
|
7966
|
+
},
|
|
7967
|
+
deferred: {
|
|
7968
|
+
noImplicitAny: {
|
|
7969
|
+
phase: 2,
|
|
7970
|
+
reason: "the implicit-any migration (TS70xx)"
|
|
7971
|
+
},
|
|
7972
|
+
noUnusedLocals: {
|
|
7973
|
+
phase: 2,
|
|
7974
|
+
reason: "unused-local cleanup (TS6133)"
|
|
7975
|
+
},
|
|
7976
|
+
noUnusedParameters: {
|
|
7977
|
+
phase: 2,
|
|
7978
|
+
reason: "unused-parameter cleanup (TS6133)"
|
|
7979
|
+
}
|
|
7980
|
+
}
|
|
7981
|
+
};
|
|
7982
|
+
|
|
7983
|
+
// src/roles/typescript/preset-data.ts
|
|
7984
|
+
var SHARED = shared_default2;
|
|
7985
|
+
var LAYERS = {
|
|
7986
|
+
nest: nest_default2,
|
|
7987
|
+
node: node_default2,
|
|
7988
|
+
react: react_default2
|
|
7989
|
+
};
|
|
7990
|
+
var SHIPPED_PRESETS = Object.keys(LAYERS).sort();
|
|
7991
|
+
var DEFERRED_RULES = Object.entries(SHARED.deferred ?? {}).map(([rule, entry]) => ({ rule, phase: entry.phase, reason: entry.reason })).sort((left, right) => left.rule.localeCompare(right.rule));
|
|
7992
|
+
function validate2() {
|
|
7993
|
+
for (const { rule, phase, reason } of DEFERRED_RULES) {
|
|
7994
|
+
if (!Number.isInteger(phase) || phase < 2) {
|
|
5678
7995
|
throw new Error(
|
|
5679
7996
|
`deferred rule "${rule}" has phase ${phase}; a deferred rule is scheduled for phase 2 or later`
|
|
5680
7997
|
);
|
|
@@ -5767,8 +8084,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
5767
8084
|
}
|
|
5768
8085
|
|
|
5769
8086
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
5770
|
-
import { readFileSync as
|
|
5771
|
-
import { join as
|
|
8087
|
+
import { readFileSync as readFileSync27 } from "fs";
|
|
8088
|
+
import { join as join33 } from "path";
|
|
5772
8089
|
|
|
5773
8090
|
// src/roles/typescript/typecheck-script.ts
|
|
5774
8091
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -5861,7 +8178,7 @@ function planAdoption(context) {
|
|
|
5861
8178
|
};
|
|
5862
8179
|
}
|
|
5863
8180
|
const existing = parseJsonc(
|
|
5864
|
-
|
|
8181
|
+
readFileSync27(join33(context.cwd, target.path), "utf8"),
|
|
5865
8182
|
target.path
|
|
5866
8183
|
);
|
|
5867
8184
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -5999,7 +8316,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5999
8316
|
let chain;
|
|
6000
8317
|
try {
|
|
6001
8318
|
const parsed = parseJsonc(
|
|
6002
|
-
|
|
8319
|
+
readFileSync28(join34(cwd, target.path), "utf8"),
|
|
6003
8320
|
target.path
|
|
6004
8321
|
);
|
|
6005
8322
|
chain = parsed.extends;
|
|
@@ -6012,7 +8329,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
6012
8329
|
);
|
|
6013
8330
|
if (preset === void 0) return void 0;
|
|
6014
8331
|
try {
|
|
6015
|
-
createRequire4(
|
|
8332
|
+
createRequire4(join34(cwd, "noop.js")).resolve(preset);
|
|
6016
8333
|
return void 0;
|
|
6017
8334
|
} catch {
|
|
6018
8335
|
return preset;
|
|
@@ -6053,7 +8370,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
6053
8370
|
return { ok: false, code: 1 };
|
|
6054
8371
|
}
|
|
6055
8372
|
if (options.length > 0) return this.runWithOptions(ctx, tsc, config);
|
|
6056
|
-
const result =
|
|
8373
|
+
const result = spawnSync6(tsc, ["-b", config], { cwd: ctx.cwd, stdio: "inherit" });
|
|
6057
8374
|
if (result.error) {
|
|
6058
8375
|
process.stderr.write(
|
|
6059
8376
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -6088,7 +8405,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
6088
8405
|
let worst = 0;
|
|
6089
8406
|
for (const project of this.referencedProjects(ctx.cwd, config)) {
|
|
6090
8407
|
const args = ["-p", project, "--noEmit", "--composite", "false", ...ctx.toolArgs ?? []];
|
|
6091
|
-
const result =
|
|
8408
|
+
const result = spawnSync6(tsc, args, { cwd: ctx.cwd, stdio: "inherit" });
|
|
6092
8409
|
if (result.error) {
|
|
6093
8410
|
process.stderr.write(
|
|
6094
8411
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -6108,7 +8425,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
6108
8425
|
referencedProjects(cwd, config) {
|
|
6109
8426
|
try {
|
|
6110
8427
|
const parsed = parseJsonc(
|
|
6111
|
-
|
|
8428
|
+
readFileSync28(join34(cwd, config), "utf8"),
|
|
6112
8429
|
config
|
|
6113
8430
|
);
|
|
6114
8431
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
|
|
@@ -6124,7 +8441,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
6124
8441
|
* check.
|
|
6125
8442
|
*/
|
|
6126
8443
|
typecheckTarget(cwd) {
|
|
6127
|
-
if (
|
|
8444
|
+
if (existsSync27(join34(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
6128
8445
|
const target = resolveTsconfigTarget(cwd);
|
|
6129
8446
|
return target.reason === "none" ? null : target.path;
|
|
6130
8447
|
}
|
|
@@ -6177,7 +8494,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
6177
8494
|
return { ok: true, code: 0, metrics: _TscAdapter.NOTHING_TO_REPORT };
|
|
6178
8495
|
}
|
|
6179
8496
|
const tsc = resolveBin(ctx.cwd, "tsc") ?? "tsc";
|
|
6180
|
-
const result =
|
|
8497
|
+
const result = spawnSync6(tsc, ["-b", config], { cwd: ctx.cwd, encoding: "utf8" });
|
|
6181
8498
|
if (result.error) {
|
|
6182
8499
|
process.stderr.write(
|
|
6183
8500
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -6220,7 +8537,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
6220
8537
|
* errors in the output mean the rule must be on.
|
|
6221
8538
|
*/
|
|
6222
8539
|
noImplicitAnyEnabled(cwd, tsc, config, mainOutput) {
|
|
6223
|
-
const shown =
|
|
8540
|
+
const shown = spawnSync6(tsc, ["-p", config, "--showConfig"], { cwd, encoding: "utf8" });
|
|
6224
8541
|
if (shown.status === 0 && shown.stdout) {
|
|
6225
8542
|
try {
|
|
6226
8543
|
const co = parseJsonc(
|
|
@@ -6247,6 +8564,7 @@ function registerAdapters() {
|
|
|
6247
8564
|
registerLint();
|
|
6248
8565
|
registerFormat();
|
|
6249
8566
|
registerBuild();
|
|
8567
|
+
registerTest();
|
|
6250
8568
|
}
|
|
6251
8569
|
|
|
6252
8570
|
// src/core/detect-framework.ts
|
|
@@ -6272,7 +8590,7 @@ function detectFramework(packageJson) {
|
|
|
6272
8590
|
}
|
|
6273
8591
|
|
|
6274
8592
|
// src/core/dispatch.ts
|
|
6275
|
-
import { basename as
|
|
8593
|
+
import { basename as basename2 } from "path";
|
|
6276
8594
|
|
|
6277
8595
|
// src/shared/text.ts
|
|
6278
8596
|
function ensureLines(current, lines) {
|
|
@@ -6328,9 +8646,9 @@ function replaceLines(current, replacements) {
|
|
|
6328
8646
|
}
|
|
6329
8647
|
|
|
6330
8648
|
// src/core/apply-plan.ts
|
|
6331
|
-
import { existsSync as
|
|
6332
|
-
import { resolve as
|
|
6333
|
-
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
8649
|
+
import { existsSync as existsSync28, readFileSync as readFileSync29, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
8650
|
+
import { resolve as resolve5, sep } from "path";
|
|
8651
|
+
import { applyEdits as applyEdits3, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
6334
8652
|
|
|
6335
8653
|
// src/shared/deep-merge.ts
|
|
6336
8654
|
function isPlainObject(value) {
|
|
@@ -6339,15 +8657,15 @@ function isPlainObject(value) {
|
|
|
6339
8657
|
|
|
6340
8658
|
// src/core/apply-plan.ts
|
|
6341
8659
|
function resolveWithinRoot(cwd, relativePath) {
|
|
6342
|
-
const root =
|
|
6343
|
-
const absolutePath =
|
|
8660
|
+
const root = resolve5(cwd);
|
|
8661
|
+
const absolutePath = resolve5(root, relativePath);
|
|
6344
8662
|
if (absolutePath !== root && !absolutePath.startsWith(root + sep)) {
|
|
6345
8663
|
throw new Error(`Refusing to write outside the module root: "${relativePath}".`);
|
|
6346
8664
|
}
|
|
6347
8665
|
return absolutePath;
|
|
6348
8666
|
}
|
|
6349
8667
|
function readIfExists(absolutePath) {
|
|
6350
|
-
return
|
|
8668
|
+
return existsSync28(absolutePath) ? readFileSync29(absolutePath, "utf8") : void 0;
|
|
6351
8669
|
}
|
|
6352
8670
|
function* leaves(value, prefix = []) {
|
|
6353
8671
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -6362,7 +8680,7 @@ function mergeJsonc(current, value) {
|
|
|
6362
8680
|
const edits = modify(text, path, leaf, {
|
|
6363
8681
|
formattingOptions: { insertSpaces: true, tabSize: 2 }
|
|
6364
8682
|
});
|
|
6365
|
-
text =
|
|
8683
|
+
text = applyEdits3(text, edits);
|
|
6366
8684
|
}
|
|
6367
8685
|
return text.endsWith("\n") ? text : text + "\n";
|
|
6368
8686
|
}
|
|
@@ -6374,7 +8692,7 @@ function removeJsoncKeys(current, keys) {
|
|
|
6374
8692
|
const edits = modify(text, path, void 0, {
|
|
6375
8693
|
formattingOptions: { insertSpaces: true, tabSize: 2 }
|
|
6376
8694
|
});
|
|
6377
|
-
text =
|
|
8695
|
+
text = applyEdits3(text, edits);
|
|
6378
8696
|
}
|
|
6379
8697
|
return text.endsWith("\n") ? text : text + "\n";
|
|
6380
8698
|
}
|
|
@@ -6428,9 +8746,9 @@ function applyOperationTo(current, operation) {
|
|
|
6428
8746
|
}
|
|
6429
8747
|
}
|
|
6430
8748
|
}
|
|
6431
|
-
function preparePlan(cwd,
|
|
8749
|
+
function preparePlan(cwd, plan4) {
|
|
6432
8750
|
const prepared = /* @__PURE__ */ new Map();
|
|
6433
|
-
for (const operation of
|
|
8751
|
+
for (const operation of plan4.operations) {
|
|
6434
8752
|
const absolutePath = resolveWithinRoot(cwd, operation.path);
|
|
6435
8753
|
const existing = prepared.get(operation.path);
|
|
6436
8754
|
const before = existing?.before ?? readIfExists(absolutePath) ?? "";
|
|
@@ -6449,11 +8767,11 @@ function preparePlan(cwd, plan3) {
|
|
|
6449
8767
|
}
|
|
6450
8768
|
function writeFileAtomic(absolutePath, contents) {
|
|
6451
8769
|
const tempPath = `${absolutePath}.sentinel-${process.pid}.tmp`;
|
|
6452
|
-
|
|
8770
|
+
writeFileSync3(tempPath, contents);
|
|
6453
8771
|
renameSync(tempPath, absolutePath);
|
|
6454
8772
|
}
|
|
6455
|
-
function applyPlan(cwd,
|
|
6456
|
-
const changed = preparePlan(cwd,
|
|
8773
|
+
function applyPlan(cwd, plan4) {
|
|
8774
|
+
const changed = preparePlan(cwd, plan4).filter((file) => file.before !== file.after);
|
|
6457
8775
|
for (const file of changed) {
|
|
6458
8776
|
if (file.deleted) {
|
|
6459
8777
|
rmSync3(file.absolutePath, { force: true });
|
|
@@ -6465,8 +8783,8 @@ function applyPlan(cwd, plan3) {
|
|
|
6465
8783
|
}
|
|
6466
8784
|
|
|
6467
8785
|
// src/core/config/preset-evidence.ts
|
|
6468
|
-
import { existsSync as
|
|
6469
|
-
import { join as
|
|
8786
|
+
import { existsSync as existsSync29, readdirSync as readdirSync7, readFileSync as readFileSync30 } from "fs";
|
|
8787
|
+
import { join as join35 } from "path";
|
|
6470
8788
|
var PATH_SIGNALS = [
|
|
6471
8789
|
{
|
|
6472
8790
|
preset: "nest",
|
|
@@ -6475,16 +8793,50 @@ var PATH_SIGNALS = [
|
|
|
6475
8793
|
}
|
|
6476
8794
|
];
|
|
6477
8795
|
var DEPENDENCY_SIGNALS = [
|
|
6478
|
-
|
|
8796
|
+
// Runtime dependencies only. `eslint-plugin-react*` used to count here, but a lint plugin is
|
|
8797
|
+
// TOOLING: this very repo depends on `eslint-plugin-react-refresh` to ship React rules to
|
|
8798
|
+
// other projects, and was therefore read as a React app itself. No module in the monorepo
|
|
8799
|
+
// carries an `eslint-plugin-react*` without also carrying react, so nothing loses detection.
|
|
8800
|
+
{ preset: "react", pattern: /^(react|react-dom|@types\/react)$/ },
|
|
6479
8801
|
{ preset: "svelte", pattern: /^svelte$|^@sveltejs\// },
|
|
6480
8802
|
{ preset: "nest", pattern: /^@nestjs\// }
|
|
6481
8803
|
];
|
|
8804
|
+
var IMPORT_SIGNALS = [
|
|
8805
|
+
{ preset: "nest", pattern: /@nestjs\//, evidence: 'its code imports "@nestjs/"' }
|
|
8806
|
+
];
|
|
8807
|
+
var SOURCE_FILE2 = /\.[cm]?[jt]sx?$/;
|
|
8808
|
+
var NOT_SCANNED = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".git", ".nx", ".turbo", "build"]);
|
|
8809
|
+
function sourceImports(cwd, pattern) {
|
|
8810
|
+
const stack = [cwd];
|
|
8811
|
+
while (stack.length > 0) {
|
|
8812
|
+
const dir = stack.pop();
|
|
8813
|
+
let entries;
|
|
8814
|
+
try {
|
|
8815
|
+
entries = readdirSync7(dir, { withFileTypes: true });
|
|
8816
|
+
} catch {
|
|
8817
|
+
continue;
|
|
8818
|
+
}
|
|
8819
|
+
for (const entry of entries) {
|
|
8820
|
+
if (entry.isDirectory()) {
|
|
8821
|
+
if (!NOT_SCANNED.has(entry.name)) stack.push(join35(dir, entry.name));
|
|
8822
|
+
continue;
|
|
8823
|
+
}
|
|
8824
|
+
if (!SOURCE_FILE2.test(entry.name)) continue;
|
|
8825
|
+
try {
|
|
8826
|
+
if (pattern.test(readFileSync30(join35(dir, entry.name), "utf8"))) return true;
|
|
8827
|
+
} catch {
|
|
8828
|
+
continue;
|
|
8829
|
+
}
|
|
8830
|
+
}
|
|
8831
|
+
}
|
|
8832
|
+
return false;
|
|
8833
|
+
}
|
|
6482
8834
|
function dependencyNames(cwd) {
|
|
6483
|
-
const path =
|
|
6484
|
-
if (!
|
|
8835
|
+
const path = join35(cwd, "package.json");
|
|
8836
|
+
if (!existsSync29(path)) return [];
|
|
6485
8837
|
try {
|
|
6486
8838
|
const manifest = parseJsonc(
|
|
6487
|
-
|
|
8839
|
+
readFileSync30(path, "utf8"),
|
|
6488
8840
|
path
|
|
6489
8841
|
);
|
|
6490
8842
|
return [
|
|
@@ -6498,7 +8850,7 @@ function dependencyNames(cwd) {
|
|
|
6498
8850
|
function declaresJsx(cwd) {
|
|
6499
8851
|
let entries;
|
|
6500
8852
|
try {
|
|
6501
|
-
entries =
|
|
8853
|
+
entries = readdirSync7(cwd).filter(
|
|
6502
8854
|
(name) => name.startsWith("tsconfig") && name.endsWith(".json")
|
|
6503
8855
|
);
|
|
6504
8856
|
} catch {
|
|
@@ -6507,7 +8859,7 @@ function declaresJsx(cwd) {
|
|
|
6507
8859
|
for (const name of entries) {
|
|
6508
8860
|
try {
|
|
6509
8861
|
const config = parseJsonc(
|
|
6510
|
-
|
|
8862
|
+
readFileSync30(join35(cwd, name), "utf8"),
|
|
6511
8863
|
name
|
|
6512
8864
|
);
|
|
6513
8865
|
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
@@ -6532,8 +8884,16 @@ function presetSignals(cwd) {
|
|
|
6532
8884
|
signals.push({ preset, evidence });
|
|
6533
8885
|
}
|
|
6534
8886
|
}
|
|
8887
|
+
for (const { preset, pattern, evidence } of IMPORT_SIGNALS) {
|
|
8888
|
+
if (signals.some((signal) => signal.preset === preset)) continue;
|
|
8889
|
+
if (sourceImports(cwd, pattern)) signals.push({ preset, evidence });
|
|
8890
|
+
}
|
|
6535
8891
|
return signals;
|
|
6536
8892
|
}
|
|
8893
|
+
function correctingSignal(detected, cwd) {
|
|
8894
|
+
const [signal] = presetSignals(cwd);
|
|
8895
|
+
return signal !== void 0 && signal.preset !== detected ? signal : void 0;
|
|
8896
|
+
}
|
|
6537
8897
|
function presetContradiction(declared, cwd) {
|
|
6538
8898
|
const signals = presetSignals(cwd);
|
|
6539
8899
|
if (signals.length === 0) return void 0;
|
|
@@ -6558,14 +8918,14 @@ function resolveFlavour(opts) {
|
|
|
6558
8918
|
}
|
|
6559
8919
|
return detection.preset;
|
|
6560
8920
|
}
|
|
6561
|
-
function previewPlan(opts,
|
|
6562
|
-
const changed = preparePlan(opts.cwd,
|
|
8921
|
+
function previewPlan(opts, plan4) {
|
|
8922
|
+
const changed = preparePlan(opts.cwd, plan4).filter((file) => file.before !== file.after);
|
|
6563
8923
|
if (opts.json) {
|
|
6564
8924
|
process.stdout.write(
|
|
6565
8925
|
JSON.stringify(
|
|
6566
8926
|
{
|
|
6567
8927
|
dryRun: true,
|
|
6568
|
-
notes:
|
|
8928
|
+
notes: plan4.notes ?? [],
|
|
6569
8929
|
files: changed.map(({ path, before, after, deleted }) => ({
|
|
6570
8930
|
path,
|
|
6571
8931
|
action: deleted ? "delete" : before.length === 0 ? "create" : "update",
|
|
@@ -6580,7 +8940,7 @@ function previewPlan(opts, plan3) {
|
|
|
6580
8940
|
return 0;
|
|
6581
8941
|
}
|
|
6582
8942
|
process.stderr.write(" dry run: no files written\n");
|
|
6583
|
-
for (const note of
|
|
8943
|
+
for (const note of plan4.notes ?? []) process.stderr.write(` ${note}
|
|
6584
8944
|
`);
|
|
6585
8945
|
if (changed.length === 0) {
|
|
6586
8946
|
process.stderr.write(" nothing to change\n");
|
|
@@ -6602,6 +8962,12 @@ async function dispatch(opts) {
|
|
|
6602
8962
|
}
|
|
6603
8963
|
const detected = resolveFlavour(opts);
|
|
6604
8964
|
const resolutionPreset = opts.preset ?? declaredPresetFor(opts.target, opts.cwd) ?? detected;
|
|
8965
|
+
if (resolutionPreset === "tools") {
|
|
8966
|
+
const message = `tools modules are not adopted one at a time: they sometimes hold configuration shared with the workspace root, so they move WITH the root, as one reviewed change owned by the tooling team rather than by the module's team.`;
|
|
8967
|
+
process.stderr.write(` ${palette(process.stderr).dim(`${opts.target}: ${message}`)}
|
|
8968
|
+
`);
|
|
8969
|
+
return 0;
|
|
8970
|
+
}
|
|
6605
8971
|
const adapter = resolve(opts.target, resolutionPreset, opts.runner);
|
|
6606
8972
|
const preset = opts.preset ?? adapter.declaredPreset?.(opts.cwd) ?? detected;
|
|
6607
8973
|
if (opts.preset !== void 0) {
|
|
@@ -6614,8 +8980,8 @@ async function dispatch(opts) {
|
|
|
6614
8980
|
}
|
|
6615
8981
|
let effective = preset;
|
|
6616
8982
|
if (opts.preset === void 0 && adapter.declaredPreset?.(opts.cwd) === void 0) {
|
|
6617
|
-
const
|
|
6618
|
-
if (signal !== void 0
|
|
8983
|
+
const signal = correctingSignal(preset, opts.cwd);
|
|
8984
|
+
if (signal !== void 0) {
|
|
6619
8985
|
effective = signal.preset;
|
|
6620
8986
|
process.stderr.write(
|
|
6621
8987
|
` detected "${preset}" from dependencies, but ${signal.evidence}, so adopting as "${signal.preset}". Pass --preset to override.
|
|
@@ -6624,31 +8990,31 @@ async function dispatch(opts) {
|
|
|
6624
8990
|
}
|
|
6625
8991
|
}
|
|
6626
8992
|
const context = { cwd: opts.cwd, preset: effective };
|
|
6627
|
-
const
|
|
6628
|
-
if (
|
|
6629
|
-
process.stderr.write(`sentinel (${opts.target}): ${
|
|
8993
|
+
const plan4 = await adapter.plan(context);
|
|
8994
|
+
if (plan4.blocked) {
|
|
8995
|
+
process.stderr.write(`sentinel (${opts.target}): ${plan4.blocked}
|
|
6630
8996
|
`);
|
|
6631
8997
|
return 1;
|
|
6632
8998
|
}
|
|
6633
|
-
if (
|
|
6634
|
-
process.stderr.write(` ${palette(process.stderr).dim(`${opts.target}: ${
|
|
8999
|
+
if (plan4.skipped) {
|
|
9000
|
+
process.stderr.write(` ${palette(process.stderr).dim(`${opts.target}: ${plan4.skipped}`)}
|
|
6635
9001
|
`);
|
|
6636
9002
|
return 0;
|
|
6637
9003
|
}
|
|
6638
9004
|
if (opts.dryRun) {
|
|
6639
|
-
return previewPlan(opts,
|
|
9005
|
+
return previewPlan(opts, plan4);
|
|
6640
9006
|
}
|
|
6641
|
-
for (const change of applyPlan(opts.cwd,
|
|
9007
|
+
for (const change of applyPlan(opts.cwd, plan4)) {
|
|
6642
9008
|
process.stderr.write(` ${change.deleted ? "removed" : "wrote"} ${change.path}
|
|
6643
9009
|
`);
|
|
6644
9010
|
}
|
|
6645
|
-
for (const note of
|
|
9011
|
+
for (const note of plan4.notes ?? []) process.stderr.write(` ${note}
|
|
6646
9012
|
`);
|
|
6647
9013
|
if (adapter.afterInit) {
|
|
6648
9014
|
process.stderr.write(` fixing what ${opts.target} can fix automatically...
|
|
6649
9015
|
`);
|
|
6650
9016
|
await adapter.afterInit({
|
|
6651
|
-
module:
|
|
9017
|
+
module: basename2(opts.cwd),
|
|
6652
9018
|
cwd: opts.cwd,
|
|
6653
9019
|
preset,
|
|
6654
9020
|
ci: false,
|
|
@@ -6666,16 +9032,9 @@ export {
|
|
|
6666
9032
|
declaredPresetFor,
|
|
6667
9033
|
availableTargets,
|
|
6668
9034
|
resolve,
|
|
6669
|
-
readOwnVersion,
|
|
6670
|
-
readProjectPackageJson,
|
|
6671
|
-
moduleName,
|
|
6672
9035
|
buildConfigFile,
|
|
6673
9036
|
BaseAdapter,
|
|
6674
9037
|
resolveBin,
|
|
6675
|
-
WORKSPACE_ROOT_MARKER,
|
|
6676
|
-
findWorkspaceRoot,
|
|
6677
|
-
ensureWorkspacePrep,
|
|
6678
|
-
inspectWorkspacePrep,
|
|
6679
9038
|
VERBS,
|
|
6680
9039
|
TARGETS,
|
|
6681
9040
|
LONG_RUNNING_TARGETS,
|
|
@@ -6683,8 +9042,9 @@ export {
|
|
|
6683
9042
|
PRESET_NAMES,
|
|
6684
9043
|
palette,
|
|
6685
9044
|
registerAdapters,
|
|
9045
|
+
correctingSignal,
|
|
6686
9046
|
describeFramework,
|
|
6687
9047
|
detectFramework,
|
|
6688
9048
|
dispatch
|
|
6689
9049
|
};
|
|
6690
|
-
//# sourceMappingURL=chunk-
|
|
9050
|
+
//# sourceMappingURL=chunk-O3XCQGTJ.js.map
|