@hublo/sentinel 1.4.0-alpha.1 → 1.4.0-alpha.3
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 +6 -4
- package/dist/bin/sentinel.js +1 -1
- package/dist/chunk-CPCUPK4J.js +70 -0
- package/dist/{chunk-O3XCQGTJ.js → chunk-MUQWAZQN.js} +1084 -170
- package/dist/chunk-SX5CBE3Z.js +310 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1 -1
- package/dist/roles/test/nest/toolchain.d.ts +3 -23
- package/dist/roles/test/nest/toolchain.js +4 -203
- package/dist/roles/test/nest/toolchain.js.map +1 -1
- package/dist/roles/test/react/toolchain.d.ts +5 -1
- package/dist/roles/test/react/toolchain.js +11 -20
- package/dist/roles/test/react/toolchain.js.map +1 -1
- package/dist/roles/test/setup/nest.js +12 -64
- package/dist/roles/test/setup/nest.js.map +1 -1
- package/dist/roles/test/setup/workspace-entry.d.ts +2 -0
- package/dist/roles/test/setup/workspace-entry.js +8 -0
- package/dist/roles/test/setup/workspace-entry.js.map +1 -0
- package/dist/shared-test-config-CGxvMlmk.d.ts +28 -0
- package/docs/.gitkeep +0 -0
- package/docs/build-adoption.md +521 -0
- package/docs/format-adoption.md +321 -0
- package/docs/lint-adoption.md +290 -0
- package/docs/performance.md +49 -0
- package/docs/test-adoption.md +175 -0
- package/docs/typescript-adoption.md +184 -0
- package/docs/typescript-traces.md +798 -0
- package/docs/using-sentinel.md +180 -0
- package/docs/validating-a-change.md +101 -0
- package/package.json +9 -3
|
@@ -5394,6 +5394,9 @@ function registerLint() {
|
|
|
5394
5394
|
|
|
5395
5395
|
// src/roles/test/adapters/vitest/vitest.adapter.ts
|
|
5396
5396
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5397
|
+
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync30 } from "fs";
|
|
5398
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
5399
|
+
import { join as join37 } from "path";
|
|
5397
5400
|
|
|
5398
5401
|
// src/roles/test/presets/toolchain.json
|
|
5399
5402
|
var toolchain_default2 = {
|
|
@@ -5456,12 +5459,12 @@ function rewriteVitestScript(command) {
|
|
|
5456
5459
|
});
|
|
5457
5460
|
}
|
|
5458
5461
|
function testTarget(options) {
|
|
5459
|
-
const { configFiles, outputs, dependsOn, configurations } = options;
|
|
5462
|
+
const { name, configFiles, outputs, dependsOn, configurations } = options;
|
|
5460
5463
|
const cached = outputs !== void 0 && outputs.length > 0;
|
|
5461
5464
|
return {
|
|
5462
|
-
[TEST_SCRIPT_NAME]: {
|
|
5465
|
+
[name ?? TEST_SCRIPT_NAME]: {
|
|
5463
5466
|
cache: cached,
|
|
5464
|
-
inputs: ["default", "^default", ...configFiles.map((
|
|
5467
|
+
inputs: ["default", "^default", ...configFiles.map((name2) => `{projectRoot}/${name2}`)],
|
|
5465
5468
|
...cached ? { outputs: [...outputs] } : {},
|
|
5466
5469
|
...dependsOn !== void 0 && dependsOn.length > 0 ? { dependsOn: [...dependsOn] } : {},
|
|
5467
5470
|
...configurations !== void 0 && Object.keys(configurations).length > 0 ? { configurations: { ...configurations } } : {}
|
|
@@ -5470,12 +5473,12 @@ function testTarget(options) {
|
|
|
5470
5473
|
}
|
|
5471
5474
|
|
|
5472
5475
|
// src/roles/test/jest-migration.ts
|
|
5473
|
-
import { existsSync as
|
|
5474
|
-
import { join as
|
|
5476
|
+
import { existsSync as existsSync29, readFileSync as readFileSync28 } from "fs";
|
|
5477
|
+
import { join as join35 } from "path";
|
|
5475
5478
|
|
|
5476
5479
|
// src/roles/test/codemod/migrate-module.ts
|
|
5477
|
-
import { readdirSync as
|
|
5478
|
-
import { join as
|
|
5480
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync25, statSync as statSync6 } from "fs";
|
|
5481
|
+
import { join as join31 } from "path";
|
|
5479
5482
|
import { parseSync as parseSync8 } from "oxc-parser";
|
|
5480
5483
|
|
|
5481
5484
|
// src/roles/test/read-adoption.ts
|
|
@@ -5933,6 +5936,104 @@ function rewriteEmptyMockImplementation(source, program) {
|
|
|
5933
5936
|
return { source: result, rewritten: insertAt.length };
|
|
5934
5937
|
}
|
|
5935
5938
|
|
|
5939
|
+
// src/roles/test/codemod/fake-timer-options.ts
|
|
5940
|
+
var MEMBER_TYPES6 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
5941
|
+
var TIMER_CALLS = /* @__PURE__ */ new Set(["useFakeTimers"]);
|
|
5942
|
+
function timerOptions(node) {
|
|
5943
|
+
if (node.type !== "CallExpression") return void 0;
|
|
5944
|
+
const callee = node.callee;
|
|
5945
|
+
if (callee === void 0 || !MEMBER_TYPES6.has(callee.type)) return void 0;
|
|
5946
|
+
const object = callee.object;
|
|
5947
|
+
const property = callee.property;
|
|
5948
|
+
if (object?.type !== "Identifier") return void 0;
|
|
5949
|
+
if (object.name !== "jest" && object.name !== "vi") return void 0;
|
|
5950
|
+
if (property?.type !== "Identifier" || !TIMER_CALLS.has(property.name)) return void 0;
|
|
5951
|
+
const [options] = node.arguments ?? [];
|
|
5952
|
+
return options?.type === "ObjectExpression" ? options : void 0;
|
|
5953
|
+
}
|
|
5954
|
+
var FAKEABLE = [
|
|
5955
|
+
"setTimeout",
|
|
5956
|
+
"clearTimeout",
|
|
5957
|
+
"setImmediate",
|
|
5958
|
+
"clearImmediate",
|
|
5959
|
+
"setInterval",
|
|
5960
|
+
"clearInterval",
|
|
5961
|
+
"Date",
|
|
5962
|
+
"hrtime",
|
|
5963
|
+
"requestAnimationFrame",
|
|
5964
|
+
"cancelAnimationFrame",
|
|
5965
|
+
"requestIdleCallback",
|
|
5966
|
+
"cancelIdleCallback",
|
|
5967
|
+
"performance"
|
|
5968
|
+
];
|
|
5969
|
+
function stringLiterals2(node) {
|
|
5970
|
+
if (node.type !== "ArrayExpression") return void 0;
|
|
5971
|
+
const values = [];
|
|
5972
|
+
for (const element of node.elements ?? []) {
|
|
5973
|
+
if (element?.type !== "Literal" || typeof element.value !== "string") return void 0;
|
|
5974
|
+
values.push(element.value);
|
|
5975
|
+
}
|
|
5976
|
+
return values;
|
|
5977
|
+
}
|
|
5978
|
+
function rewriteFakeTimerOptions(source, program) {
|
|
5979
|
+
const edits = [];
|
|
5980
|
+
const translated = [];
|
|
5981
|
+
const refusals = [];
|
|
5982
|
+
walk(program, (node) => {
|
|
5983
|
+
const options = timerOptions(node);
|
|
5984
|
+
if (options === void 0) return;
|
|
5985
|
+
for (const property of options.properties ?? []) {
|
|
5986
|
+
const key = property.key;
|
|
5987
|
+
if (key?.type !== "Identifier") continue;
|
|
5988
|
+
const value = property.value;
|
|
5989
|
+
if (value === void 0) continue;
|
|
5990
|
+
const span = { start: property.start, end: property.end };
|
|
5991
|
+
if (key.name === "advanceTimers") {
|
|
5992
|
+
if (value.type === "Literal" && value.value === true) {
|
|
5993
|
+
edits.push({ ...span, text: "shouldAdvanceTime: true" });
|
|
5994
|
+
translated.push("advanceTimers -> shouldAdvanceTime");
|
|
5995
|
+
continue;
|
|
5996
|
+
}
|
|
5997
|
+
if (value.type === "Literal" && typeof value.value === "number") {
|
|
5998
|
+
edits.push({
|
|
5999
|
+
...span,
|
|
6000
|
+
text: `shouldAdvanceTime: true, advanceTimeDelta: ${value.value}`
|
|
6001
|
+
});
|
|
6002
|
+
translated.push("advanceTimers -> shouldAdvanceTime + advanceTimeDelta");
|
|
6003
|
+
continue;
|
|
6004
|
+
}
|
|
6005
|
+
refusals.push({
|
|
6006
|
+
binding: "advanceTimers",
|
|
6007
|
+
reason: "`useFakeTimers({ advanceTimers })` translates to sinon's `shouldAdvanceTime`, but only when the value can be read here: this one is neither `true` nor a number, and inventing a delta would be inventing behaviour."
|
|
6008
|
+
});
|
|
6009
|
+
continue;
|
|
6010
|
+
}
|
|
6011
|
+
if (key.name === "doNotFake") {
|
|
6012
|
+
const excluded = stringLiterals2(value);
|
|
6013
|
+
if (excluded === void 0) {
|
|
6014
|
+
refusals.push({
|
|
6015
|
+
binding: "doNotFake",
|
|
6016
|
+
reason: "`useFakeTimers({ doNotFake })` becomes sinon's `toFake`, which is its COMPLEMENT, so the list has to be readable here. This one is not a list of string literals."
|
|
6017
|
+
});
|
|
6018
|
+
continue;
|
|
6019
|
+
}
|
|
6020
|
+
const kept = FAKEABLE.filter((name) => !excluded.includes(name));
|
|
6021
|
+
edits.push({
|
|
6022
|
+
...span,
|
|
6023
|
+
text: `toFake: [${kept.map((name) => `'${name}'`).join(", ")}]`
|
|
6024
|
+
});
|
|
6025
|
+
translated.push("doNotFake -> toFake");
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
});
|
|
6029
|
+
if (edits.length === 0) return { source, translated, refusals };
|
|
6030
|
+
let result = source;
|
|
6031
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
6032
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
6033
|
+
}
|
|
6034
|
+
return { source: result, translated, refusals };
|
|
6035
|
+
}
|
|
6036
|
+
|
|
5936
6037
|
// src/roles/test/codemod/scope.ts
|
|
5937
6038
|
function readIdentifiers(node, runsLater) {
|
|
5938
6039
|
const names = [];
|
|
@@ -6098,7 +6199,7 @@ function hoistCapturedBindings(source, program, factories, bindings, imported) {
|
|
|
6098
6199
|
if (hoistable(name, bindings, imported)) continue;
|
|
6099
6200
|
refusals.push({
|
|
6100
6201
|
binding: name,
|
|
6101
|
-
reason: bindings.get(name) === void 0 ?
|
|
6202
|
+
reason: bindings.get(name) === void 0 ? `\`${name}\` cannot move into vi.hoisted: it is declared without an initializer, so there is nothing for vi.hoisted to carry` : `\`${name}\` cannot move into vi.hoisted: its initializer reads something imported, so it cannot live above the import graph`
|
|
6102
6203
|
});
|
|
6103
6204
|
}
|
|
6104
6205
|
const declarations = simpleDeclarations(program);
|
|
@@ -6156,7 +6257,7 @@ function hoistCapturedBindings(source, program, factories, bindings, imported) {
|
|
|
6156
6257
|
|
|
6157
6258
|
// src/roles/test/codemod/hook-return-value.ts
|
|
6158
6259
|
var HOOKS = /* @__PURE__ */ new Set(["beforeEach", "afterEach", "beforeAll", "afterAll"]);
|
|
6159
|
-
var
|
|
6260
|
+
var MEMBER_TYPES7 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6160
6261
|
function hookCallback(node) {
|
|
6161
6262
|
if (node.type !== "CallExpression") return void 0;
|
|
6162
6263
|
const callee = node.callee;
|
|
@@ -6167,7 +6268,7 @@ function hookCallback(node) {
|
|
|
6167
6268
|
function configuresAMock(body) {
|
|
6168
6269
|
if (body.type !== "CallExpression") return false;
|
|
6169
6270
|
const callee = body.callee;
|
|
6170
|
-
if (callee === void 0 || !
|
|
6271
|
+
if (callee === void 0 || !MEMBER_TYPES7.has(callee.type)) return false;
|
|
6171
6272
|
const property = callee.property;
|
|
6172
6273
|
return property?.type === "Identifier" && /^mock[A-Z]/.test(property.name);
|
|
6173
6274
|
}
|
|
@@ -6352,7 +6453,37 @@ ${source}`;
|
|
|
6352
6453
|
}
|
|
6353
6454
|
|
|
6354
6455
|
// src/roles/test/codemod/named-rules.ts
|
|
6355
|
-
|
|
6456
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22 } from "fs";
|
|
6457
|
+
import { createRequire as createRequire4 } from "module";
|
|
6458
|
+
import { dirname as dirname4, join as join28 } from "path";
|
|
6459
|
+
function isCommonJsPackage(fromFile, specifier) {
|
|
6460
|
+
if (specifier.startsWith(".") || specifier.startsWith("/")) return false;
|
|
6461
|
+
let resolved;
|
|
6462
|
+
try {
|
|
6463
|
+
resolved = createRequire4(fromFile).resolve(specifier);
|
|
6464
|
+
} catch {
|
|
6465
|
+
return false;
|
|
6466
|
+
}
|
|
6467
|
+
if (!resolved.includes(`${"/"}node_modules${"/"}`)) return false;
|
|
6468
|
+
if (resolved.endsWith(".mjs")) return false;
|
|
6469
|
+
if (resolved.endsWith(".cjs")) return true;
|
|
6470
|
+
let dir = dirname4(resolved);
|
|
6471
|
+
for (let depth = 0; depth < 12; depth += 1) {
|
|
6472
|
+
const manifest = join28(dir, "package.json");
|
|
6473
|
+
if (existsSync24(manifest)) {
|
|
6474
|
+
try {
|
|
6475
|
+
return JSON.parse(readFileSync22(manifest, "utf8")).type !== "module";
|
|
6476
|
+
} catch {
|
|
6477
|
+
return false;
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
const parent = dirname4(dir);
|
|
6481
|
+
if (parent === dir) break;
|
|
6482
|
+
dir = parent;
|
|
6483
|
+
}
|
|
6484
|
+
return false;
|
|
6485
|
+
}
|
|
6486
|
+
var MEMBER_TYPES8 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6356
6487
|
var ASYNC_REPLACEMENTS = {
|
|
6357
6488
|
requireActual: "importActual",
|
|
6358
6489
|
requireMock: "importMock"
|
|
@@ -6362,7 +6493,7 @@ function mockedSpecifiers(program) {
|
|
|
6362
6493
|
walk(program, (node) => {
|
|
6363
6494
|
if (node.type !== "CallExpression") return;
|
|
6364
6495
|
const callee = node.callee;
|
|
6365
|
-
if (callee === void 0 || !
|
|
6496
|
+
if (callee === void 0 || !MEMBER_TYPES8.has(callee.type)) return;
|
|
6366
6497
|
const object = callee.object;
|
|
6367
6498
|
const property = callee.property;
|
|
6368
6499
|
if (object?.type !== "Identifier") return;
|
|
@@ -6396,6 +6527,33 @@ function bareRequire(node) {
|
|
|
6396
6527
|
const args = node.arguments ?? [];
|
|
6397
6528
|
return args.length === 1 && args[0] !== void 0;
|
|
6398
6529
|
}
|
|
6530
|
+
function mockFactoryReturn(node) {
|
|
6531
|
+
if (node.type !== "CallExpression") return void 0;
|
|
6532
|
+
const callee = node.callee;
|
|
6533
|
+
if (callee === void 0 || !MEMBER_TYPES8.has(callee.type)) return void 0;
|
|
6534
|
+
const object = callee.object;
|
|
6535
|
+
const property = callee.property;
|
|
6536
|
+
if (object?.type !== "Identifier") return void 0;
|
|
6537
|
+
if (!["jest", "vi", "vitest"].includes(object.name)) return void 0;
|
|
6538
|
+
if (property?.name !== "mock") return void 0;
|
|
6539
|
+
const factory = (node.arguments ?? [])[1];
|
|
6540
|
+
if (factory?.type !== "ArrowFunctionExpression" && factory?.type !== "FunctionExpression") {
|
|
6541
|
+
return void 0;
|
|
6542
|
+
}
|
|
6543
|
+
let body = factory.body;
|
|
6544
|
+
while (body?.type === "ParenthesizedExpression") body = body.expression;
|
|
6545
|
+
if (body === void 0) return void 0;
|
|
6546
|
+
if (body.type !== "BlockStatement") return body;
|
|
6547
|
+
const statements = body.body ?? [];
|
|
6548
|
+
if (statements.length !== 1) return void 0;
|
|
6549
|
+
const only = statements[0];
|
|
6550
|
+
if (only?.type !== "ReturnStatement") return void 0;
|
|
6551
|
+
let argument = only.argument;
|
|
6552
|
+
while (argument?.type === "ParenthesizedExpression") {
|
|
6553
|
+
argument = argument.expression;
|
|
6554
|
+
}
|
|
6555
|
+
return argument;
|
|
6556
|
+
}
|
|
6399
6557
|
function ownedCall(node) {
|
|
6400
6558
|
if (node.type !== "CallExpression") return void 0;
|
|
6401
6559
|
const callee = node.callee;
|
|
@@ -6421,11 +6579,36 @@ function applyEdits(source, edits) {
|
|
|
6421
6579
|
}
|
|
6422
6580
|
return result;
|
|
6423
6581
|
}
|
|
6424
|
-
function applyNamedRules(source, program) {
|
|
6582
|
+
function applyNamedRules(source, program, fileName = "") {
|
|
6425
6583
|
const edits = [];
|
|
6426
6584
|
const applied = /* @__PURE__ */ new Set();
|
|
6427
6585
|
const asyncify = /* @__PURE__ */ new Set();
|
|
6428
6586
|
const mocked = mockedSpecifiers(program);
|
|
6587
|
+
const wholeStatement = /* @__PURE__ */ new Set();
|
|
6588
|
+
walk(program, (node) => {
|
|
6589
|
+
if (node.type !== "ExpressionStatement") return;
|
|
6590
|
+
const expression = node.expression;
|
|
6591
|
+
if (expression !== void 0) wholeStatement.add(expression);
|
|
6592
|
+
});
|
|
6593
|
+
const handedToTheRunnerAsAModule = /* @__PURE__ */ new Set();
|
|
6594
|
+
walk(program, (node) => {
|
|
6595
|
+
const returned = mockFactoryReturn(node);
|
|
6596
|
+
if (returned !== void 0) handedToTheRunnerAsAModule.add(returned);
|
|
6597
|
+
});
|
|
6598
|
+
const readsANameOffIt = /* @__PURE__ */ new Set();
|
|
6599
|
+
walk(program, (node) => {
|
|
6600
|
+
if (node.type === "VariableDeclarator") {
|
|
6601
|
+
const id = node.id;
|
|
6602
|
+
const init = node.init ?? void 0;
|
|
6603
|
+
if (id !== void 0 && id.type !== "Identifier" && init !== void 0) {
|
|
6604
|
+
readsANameOffIt.add(init);
|
|
6605
|
+
}
|
|
6606
|
+
return;
|
|
6607
|
+
}
|
|
6608
|
+
if (!MEMBER_TYPES8.has(node.type)) return;
|
|
6609
|
+
const object = node.object;
|
|
6610
|
+
if (object !== void 0) readsANameOffIt.add(object);
|
|
6611
|
+
});
|
|
6429
6612
|
walkWithEnclosingFunction(program, void 0, (node, enclosing) => {
|
|
6430
6613
|
const member = ownedCall(node);
|
|
6431
6614
|
if (member === void 0 && !bareRequire(node)) return;
|
|
@@ -6455,10 +6638,13 @@ function applyNamedRules(source, program) {
|
|
|
6455
6638
|
return;
|
|
6456
6639
|
}
|
|
6457
6640
|
if (bareRequire(node)) {
|
|
6641
|
+
const [specifier] = node.arguments ?? [];
|
|
6642
|
+
const literal = specifier?.type === "StringLiteral" || specifier?.type === "Literal" ? String(specifier.value) : void 0;
|
|
6643
|
+
const interop = literal !== void 0 && !readsANameOffIt.has(node) && !handedToTheRunnerAsAModule.has(node) && !wholeStatement.has(node) && isCommonJsPackage(fileName, literal) ? ".default" : "";
|
|
6458
6644
|
edits.push({
|
|
6459
6645
|
start: node.start,
|
|
6460
6646
|
end: node.end,
|
|
6461
|
-
text: `(await import(${argumentText(source, node)}))`
|
|
6647
|
+
text: wholeStatement.has(node) ? `await import(${argumentText(source, node)});` : `(await import(${argumentText(source, node)}))${interop}`
|
|
6462
6648
|
});
|
|
6463
6649
|
applied.add("require");
|
|
6464
6650
|
if (enclosing !== void 0 && enclosing.async !== true) asyncify.add(enclosing);
|
|
@@ -6490,13 +6676,13 @@ function applyNamedRules(source, program) {
|
|
|
6490
6676
|
}
|
|
6491
6677
|
|
|
6492
6678
|
// src/roles/test/codemod/isolate-modules.ts
|
|
6493
|
-
var
|
|
6679
|
+
var MEMBER_TYPES9 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6494
6680
|
var ISOLATORS = /* @__PURE__ */ new Set(["isolateModules", "isolateModulesAsync"]);
|
|
6495
6681
|
var FUNCTIONS2 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression"]);
|
|
6496
6682
|
function isolatingCall(node) {
|
|
6497
6683
|
if (node.type !== "CallExpression") return void 0;
|
|
6498
6684
|
const callee = node.callee;
|
|
6499
|
-
if (callee === void 0 || !
|
|
6685
|
+
if (callee === void 0 || !MEMBER_TYPES9.has(callee.type)) return void 0;
|
|
6500
6686
|
const object = callee.object;
|
|
6501
6687
|
const property = callee.property;
|
|
6502
6688
|
if (object?.type !== "Identifier" || object.name !== "jest") return void 0;
|
|
@@ -6507,7 +6693,7 @@ function isolatingCall(node) {
|
|
|
6507
6693
|
function unusableIsolatingCall(node) {
|
|
6508
6694
|
if (node.type !== "CallExpression") return void 0;
|
|
6509
6695
|
const callee = node.callee;
|
|
6510
|
-
if (callee === void 0 || !
|
|
6696
|
+
if (callee === void 0 || !MEMBER_TYPES9.has(callee.type)) return void 0;
|
|
6511
6697
|
const object = callee.object;
|
|
6512
6698
|
const property = callee.property;
|
|
6513
6699
|
if (object?.type !== "Identifier" || object.name !== "jest") return void 0;
|
|
@@ -6558,11 +6744,11 @@ ${indent}await (${callback})()`
|
|
|
6558
6744
|
}
|
|
6559
6745
|
|
|
6560
6746
|
// src/roles/test/codemod/mock-as-value.ts
|
|
6561
|
-
var
|
|
6747
|
+
var MEMBER_TYPES10 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6562
6748
|
function isViMockCall(node) {
|
|
6563
6749
|
if (node?.type !== "CallExpression") return false;
|
|
6564
6750
|
const callee = node.callee;
|
|
6565
|
-
if (callee === void 0 || !
|
|
6751
|
+
if (callee === void 0 || !MEMBER_TYPES10.has(callee.type)) return false;
|
|
6566
6752
|
const object = callee.object;
|
|
6567
6753
|
const property = callee.property;
|
|
6568
6754
|
return object?.type === "Identifier" && object.name === "vi" && property?.type === "Identifier" && (property.name === "mock" || property.name === "doMock");
|
|
@@ -6587,7 +6773,7 @@ function rewriteMockAsValue(source, program) {
|
|
|
6587
6773
|
const memberUses = /* @__PURE__ */ new Map();
|
|
6588
6774
|
const otherUses = /* @__PURE__ */ new Map();
|
|
6589
6775
|
walk(program, (node) => {
|
|
6590
|
-
if (
|
|
6776
|
+
if (MEMBER_TYPES10.has(node.type)) {
|
|
6591
6777
|
const object = node.object;
|
|
6592
6778
|
if (object?.type === "Identifier" && bindings.has(object.name)) {
|
|
6593
6779
|
const list = memberUses.get(object.name) ?? [];
|
|
@@ -6633,12 +6819,12 @@ function rewriteMockAsValue(source, program) {
|
|
|
6633
6819
|
}
|
|
6634
6820
|
|
|
6635
6821
|
// src/roles/test/codemod/mock-factory-default.ts
|
|
6636
|
-
var
|
|
6822
|
+
var MEMBER_TYPES11 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6637
6823
|
var BINDING = "sentinelMockedModule";
|
|
6638
6824
|
function mockCall(node) {
|
|
6639
6825
|
if (node.type !== "CallExpression") return void 0;
|
|
6640
6826
|
const callee = node.callee;
|
|
6641
|
-
if (callee === void 0 || !
|
|
6827
|
+
if (callee === void 0 || !MEMBER_TYPES11.has(callee.type)) return void 0;
|
|
6642
6828
|
const property = callee.property;
|
|
6643
6829
|
if (property?.type !== "Identifier" || property.name !== "mock") return void 0;
|
|
6644
6830
|
const object = callee.object;
|
|
@@ -6657,7 +6843,7 @@ function returnedExpression(factory) {
|
|
|
6657
6843
|
let body = factory.body;
|
|
6658
6844
|
while (body?.type === "ParenthesizedExpression") body = body.expression;
|
|
6659
6845
|
if (body === void 0) return void 0;
|
|
6660
|
-
if (body.type !== "BlockStatement") return { node: body, isObject: false };
|
|
6846
|
+
if (body.type !== "BlockStatement") return { node: body, isObject: false, concise: true };
|
|
6661
6847
|
const statements = body.body ?? [];
|
|
6662
6848
|
if (statements.length !== 1) return void 0;
|
|
6663
6849
|
const only = statements[0];
|
|
@@ -6681,6 +6867,20 @@ function returnedObject(factory) {
|
|
|
6681
6867
|
}
|
|
6682
6868
|
return void 0;
|
|
6683
6869
|
}
|
|
6870
|
+
function isModuleNamespace(node) {
|
|
6871
|
+
let current = node;
|
|
6872
|
+
while (current?.type === "ParenthesizedExpression") {
|
|
6873
|
+
current = current.expression;
|
|
6874
|
+
}
|
|
6875
|
+
if (current?.type === "AwaitExpression") {
|
|
6876
|
+
let argument = current.argument;
|
|
6877
|
+
while (argument?.type === "ParenthesizedExpression") {
|
|
6878
|
+
argument = argument.expression;
|
|
6879
|
+
}
|
|
6880
|
+
current = argument;
|
|
6881
|
+
}
|
|
6882
|
+
return current?.type === "ImportExpression";
|
|
6883
|
+
}
|
|
6684
6884
|
function namesDefault(object) {
|
|
6685
6885
|
return (object.properties ?? []).some((property) => {
|
|
6686
6886
|
const key = property.key;
|
|
@@ -6698,12 +6898,14 @@ function rewriteMockFactoryDefault(source, program, defaultImported) {
|
|
|
6698
6898
|
if (returned === void 0) return;
|
|
6699
6899
|
if (returned.isObject && namesDefault(returned.node)) return;
|
|
6700
6900
|
if (returned.isObject && !defaultImported.has(call.specifier)) return;
|
|
6901
|
+
if (!returned.isObject && isModuleNamespace(returned.node)) return;
|
|
6701
6902
|
if (!returned.isObject) {
|
|
6702
6903
|
const expression = source.slice(returned.node.start, returned.node.end);
|
|
6703
6904
|
edits.push({
|
|
6704
6905
|
start: returned.node.start,
|
|
6705
6906
|
end: returned.node.end,
|
|
6706
|
-
|
|
6907
|
+
// Parenthesised when it is the arrow's own body, or it is read as a block, not an object.
|
|
6908
|
+
literal: returned.concise === true ? `({ default: ${expression} })` : `{ default: ${expression} }`,
|
|
6707
6909
|
specifier: call.specifier,
|
|
6708
6910
|
wrap: false
|
|
6709
6911
|
});
|
|
@@ -6736,7 +6938,7 @@ function rewriteMockFactoryDefault(source, program, defaultImported) {
|
|
|
6736
6938
|
// src/roles/test/codemod/propagate-await.ts
|
|
6737
6939
|
import { parseSync as parseSync5 } from "oxc-parser";
|
|
6738
6940
|
var FUNCTIONS3 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration"]);
|
|
6739
|
-
var
|
|
6941
|
+
var MEMBER_TYPES12 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
6740
6942
|
var ALREADY_A_PROMISE = /* @__PURE__ */ new Set(["then", "catch", "finally"]);
|
|
6741
6943
|
function asyncFunctionNames(program) {
|
|
6742
6944
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -6810,7 +7012,7 @@ function alreadyHandled(call, parent) {
|
|
|
6810
7012
|
if (parent === void 0) return false;
|
|
6811
7013
|
if (parent.type === "AwaitExpression") return true;
|
|
6812
7014
|
if (parent.type === "ReturnStatement" || parent.type === "ArrowFunctionExpression") return true;
|
|
6813
|
-
if (
|
|
7015
|
+
if (MEMBER_TYPES12.has(parent.type) && parent.object === call) {
|
|
6814
7016
|
const property = parent.property;
|
|
6815
7017
|
return property?.type === "Identifier" && ALREADY_A_PROMISE.has(property.name);
|
|
6816
7018
|
}
|
|
@@ -6859,6 +7061,105 @@ function propagateAwait(fileName, source, original, elsewhere = /* @__PURE__ */
|
|
|
6859
7061
|
return { source: current, rewritten };
|
|
6860
7062
|
}
|
|
6861
7063
|
|
|
7064
|
+
// src/roles/test/codemod/reexport-barrel-mock.ts
|
|
7065
|
+
import { readFileSync as readFileSync23, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
|
|
7066
|
+
import { createRequire as createRequire5 } from "module";
|
|
7067
|
+
import { dirname as dirname5, join as join29 } from "path";
|
|
7068
|
+
var MEMBER_TYPES13 = /* @__PURE__ */ new Set(["MemberExpression", "StaticMemberExpression"]);
|
|
7069
|
+
var PACKAGE_SUBPATH = /^(@[^/]+\/[^/]+|[^@.][^/]*)\/(.+)$/;
|
|
7070
|
+
var PURE_REEXPORT = /^module\.exports\s*=\s*require\(\s*(['"])([^'"]+)\1\s*\)\s*;?$/;
|
|
7071
|
+
function mockCalls(program) {
|
|
7072
|
+
const found = [];
|
|
7073
|
+
walk(program, (node) => {
|
|
7074
|
+
if (node.type !== "CallExpression") return;
|
|
7075
|
+
const callee = node.callee;
|
|
7076
|
+
if (callee === void 0 || !MEMBER_TYPES13.has(callee.type)) return;
|
|
7077
|
+
const object = callee.object;
|
|
7078
|
+
const property = callee.property;
|
|
7079
|
+
if (object?.type !== "Identifier") return;
|
|
7080
|
+
if (object.name !== "vi" && object.name !== "jest") return;
|
|
7081
|
+
if (property?.name !== "mock") return;
|
|
7082
|
+
const first = (node.arguments ?? [])[0];
|
|
7083
|
+
if (first?.type !== "Literal" || typeof first.value !== "string") return;
|
|
7084
|
+
found.push({
|
|
7085
|
+
specifier: first.value,
|
|
7086
|
+
start: node.start,
|
|
7087
|
+
end: node.end,
|
|
7088
|
+
literalStart: first.start,
|
|
7089
|
+
literalEnd: first.end
|
|
7090
|
+
});
|
|
7091
|
+
});
|
|
7092
|
+
return found;
|
|
7093
|
+
}
|
|
7094
|
+
function packageRoot(fromFile, name) {
|
|
7095
|
+
try {
|
|
7096
|
+
return dirname5(createRequire5(fromFile).resolve(`${name}/package.json`));
|
|
7097
|
+
} catch {
|
|
7098
|
+
return void 0;
|
|
7099
|
+
}
|
|
7100
|
+
}
|
|
7101
|
+
function resolveFrom(fromFile, specifier) {
|
|
7102
|
+
try {
|
|
7103
|
+
return createRequire5(fromFile).resolve(specifier);
|
|
7104
|
+
} catch {
|
|
7105
|
+
return void 0;
|
|
7106
|
+
}
|
|
7107
|
+
}
|
|
7108
|
+
function barrelsFor(fromFile, name, target) {
|
|
7109
|
+
const root = packageRoot(fromFile, name);
|
|
7110
|
+
if (root === void 0) return [];
|
|
7111
|
+
let entries;
|
|
7112
|
+
try {
|
|
7113
|
+
entries = readdirSync4(root);
|
|
7114
|
+
} catch {
|
|
7115
|
+
return [];
|
|
7116
|
+
}
|
|
7117
|
+
const aliases = [];
|
|
7118
|
+
for (const entry of entries) {
|
|
7119
|
+
if (!entry.endsWith(".js")) continue;
|
|
7120
|
+
const path = join29(root, entry);
|
|
7121
|
+
let body;
|
|
7122
|
+
try {
|
|
7123
|
+
if (!statSync4(path).isFile()) continue;
|
|
7124
|
+
body = readFileSync23(path, "utf8");
|
|
7125
|
+
} catch {
|
|
7126
|
+
continue;
|
|
7127
|
+
}
|
|
7128
|
+
const match = PURE_REEXPORT.exec(body.trim());
|
|
7129
|
+
if (match === null) continue;
|
|
7130
|
+
if (resolveFrom(path, match[2]) !== target) continue;
|
|
7131
|
+
aliases.push(`${name}/${entry.slice(0, -".js".length)}`);
|
|
7132
|
+
}
|
|
7133
|
+
return aliases.sort();
|
|
7134
|
+
}
|
|
7135
|
+
function rewriteReexportBarrelMocks(fromFile, source, program) {
|
|
7136
|
+
const calls = mockCalls(program);
|
|
7137
|
+
if (calls.length === 0) return { source, added: [] };
|
|
7138
|
+
const alreadyMocked = new Set(calls.map((call) => call.specifier));
|
|
7139
|
+
const added = [];
|
|
7140
|
+
const insertions = [];
|
|
7141
|
+
for (const call of calls) {
|
|
7142
|
+
const parts = PACKAGE_SUBPATH.exec(call.specifier);
|
|
7143
|
+
if (parts === null) continue;
|
|
7144
|
+
const target = resolveFrom(fromFile, call.specifier);
|
|
7145
|
+
if (target === void 0) continue;
|
|
7146
|
+
for (const alias of barrelsFor(fromFile, parts[1], target)) {
|
|
7147
|
+
if (alreadyMocked.has(alias)) continue;
|
|
7148
|
+
alreadyMocked.add(alias);
|
|
7149
|
+
added.push(alias);
|
|
7150
|
+
const text = source.slice(call.start, call.literalStart) + `'${alias}'` + source.slice(call.literalEnd, call.end);
|
|
7151
|
+
insertions.push({ at: call.end, text: `
|
|
7152
|
+
${text}` });
|
|
7153
|
+
}
|
|
7154
|
+
}
|
|
7155
|
+
if (insertions.length === 0) return { source, added: [] };
|
|
7156
|
+
let rewritten = source;
|
|
7157
|
+
for (const insertion of insertions.sort((a, b) => b.at - a.at)) {
|
|
7158
|
+
rewritten = rewritten.slice(0, insertion.at) + insertion.text + rewritten.slice(insertion.at);
|
|
7159
|
+
}
|
|
7160
|
+
return { source: rewritten, added };
|
|
7161
|
+
}
|
|
7162
|
+
|
|
6862
7163
|
// src/roles/test/codemod/repoint-msw.ts
|
|
6863
7164
|
import { parseSync as parseSync6 } from "oxc-parser";
|
|
6864
7165
|
var SENTINEL_MSW_SERVER = "@hublo/sentinel/test/msw";
|
|
@@ -6896,6 +7197,43 @@ function repointMswServer(fileName, source) {
|
|
|
6896
7197
|
return { source: rewritten, repointed: true };
|
|
6897
7198
|
}
|
|
6898
7199
|
|
|
7200
|
+
// src/roles/test/codemod/spy-instance-type.ts
|
|
7201
|
+
var SPY_TYPES = /* @__PURE__ */ new Set(["SpyInstance"]);
|
|
7202
|
+
function typeNameOf(source, node) {
|
|
7203
|
+
const name = node.typeName;
|
|
7204
|
+
if (name === void 0) return void 0;
|
|
7205
|
+
return source.slice(name.start, name.end);
|
|
7206
|
+
}
|
|
7207
|
+
function rewriteSpyInstanceType(source, program) {
|
|
7208
|
+
const edits = [];
|
|
7209
|
+
walk(program, (node) => {
|
|
7210
|
+
if (node.type !== "TSTypeReference") return;
|
|
7211
|
+
const written = typeNameOf(source, node);
|
|
7212
|
+
if (written === void 0) return;
|
|
7213
|
+
const bare = written.startsWith("jest.") ? written.slice("jest.".length) : written;
|
|
7214
|
+
if (!SPY_TYPES.has(bare)) return;
|
|
7215
|
+
const args = (node.typeArguments ?? node.typeParameters)?.params;
|
|
7216
|
+
if (args === void 0 || args.length === 0) return;
|
|
7217
|
+
const text = (argument) => source.slice(argument.start, argument.end);
|
|
7218
|
+
const returns = text(args[0]);
|
|
7219
|
+
const parameters = args.length > 1 ? `...args: ${text(args[1])}` : "...args: any[]";
|
|
7220
|
+
edits.push({
|
|
7221
|
+
start: node.start,
|
|
7222
|
+
end: node.end,
|
|
7223
|
+
text: `MockInstance<(${parameters}) => ${returns}>`
|
|
7224
|
+
});
|
|
7225
|
+
});
|
|
7226
|
+
let result = source;
|
|
7227
|
+
for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
|
|
7228
|
+
result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
|
|
7229
|
+
}
|
|
7230
|
+
return {
|
|
7231
|
+
source: result,
|
|
7232
|
+
rewritten: edits.length,
|
|
7233
|
+
typeImports: edits.length > 0 ? ["MockInstance"] : []
|
|
7234
|
+
};
|
|
7235
|
+
}
|
|
7236
|
+
|
|
6899
7237
|
// src/roles/test/codemod/migrate-file.ts
|
|
6900
7238
|
function mockFactories(program) {
|
|
6901
7239
|
const factories = [];
|
|
@@ -6928,46 +7266,61 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
|
|
|
6928
7266
|
rulesApplied: [],
|
|
6929
7267
|
mswRepointed: false
|
|
6930
7268
|
};
|
|
7269
|
+
const unparsable = (step) => ({
|
|
7270
|
+
...untouched,
|
|
7271
|
+
refusals: [
|
|
7272
|
+
{
|
|
7273
|
+
binding: step,
|
|
7274
|
+
reason: `this file no longer parses after the \`${step}\` step, so nothing was written for it. That is a defect in sentinel, not in the file: please report it with the file name.`
|
|
7275
|
+
}
|
|
7276
|
+
]
|
|
7277
|
+
});
|
|
6931
7278
|
const original = parse2(fileName, source);
|
|
6932
|
-
if (original === void 0) return
|
|
7279
|
+
if (original === void 0) return unparsable("reading the file");
|
|
6933
7280
|
const titles = rewriteEachTitles(source, original);
|
|
6934
7281
|
const tables = rewriteEachTableRows(titles.source, original);
|
|
6935
7282
|
const afterTables = parse2(fileName, tables.source);
|
|
6936
|
-
if (afterTables === void 0) return
|
|
6937
|
-
const
|
|
7283
|
+
if (afterTables === void 0) return unparsable("each table rows");
|
|
7284
|
+
const spyTypes = rewriteSpyInstanceType(tables.source, afterTables);
|
|
7285
|
+
const afterSpyTypes = parse2(fileName, spyTypes.source);
|
|
7286
|
+
if (afterSpyTypes === void 0) return unparsable("SpyInstance types");
|
|
7287
|
+
const ruled = applyNamedRules(spyTypes.source, afterSpyTypes, fileName);
|
|
6938
7288
|
const renamed = renameJestToVitest(fileName, ruled.source);
|
|
6939
7289
|
const afterRename = parse2(fileName, renamed.source);
|
|
6940
|
-
if (afterRename === void 0) return
|
|
6941
|
-
const
|
|
7290
|
+
if (afterRename === void 0) return unparsable("jest to vi rename");
|
|
7291
|
+
const fakeTimers = rewriteFakeTimerOptions(renamed.source, afterRename);
|
|
7292
|
+
const afterFakeTimers = parse2(fileName, fakeTimers.source);
|
|
7293
|
+
if (afterFakeTimers === void 0) return unparsable("fake timer options");
|
|
7294
|
+
const mocks = rewriteMockAsValue(fakeTimers.source, afterFakeTimers);
|
|
6942
7295
|
const afterMocks = parse2(fileName, mocks.source);
|
|
6943
|
-
if (afterMocks === void 0) return
|
|
7296
|
+
if (afterMocks === void 0) return unparsable("mock as a value");
|
|
6944
7297
|
const constructible = rewriteConstructibleMocks(
|
|
6945
7298
|
mocks.source,
|
|
6946
7299
|
afterMocks,
|
|
6947
7300
|
mockFactories(afterMocks)
|
|
6948
7301
|
);
|
|
6949
7302
|
const afterConstructible = parse2(fileName, constructible.source);
|
|
6950
|
-
if (afterConstructible === void 0) return
|
|
7303
|
+
if (afterConstructible === void 0) return unparsable("constructible mocks");
|
|
6951
7304
|
const factoryDefault = rewriteMockFactoryDefault(
|
|
6952
7305
|
constructible.source,
|
|
6953
7306
|
afterConstructible,
|
|
6954
7307
|
defaultImported
|
|
6955
7308
|
);
|
|
6956
7309
|
const beforeReturnValue = parse2(fileName, factoryDefault.source);
|
|
6957
|
-
if (beforeReturnValue === void 0) return
|
|
7310
|
+
if (beforeReturnValue === void 0) return unparsable("mock factory default");
|
|
6958
7311
|
const returnValue = rewriteAssertionReturnValue(factoryDefault.source, beforeReturnValue);
|
|
6959
7312
|
const afterReturnValue = parse2(fileName, returnValue.source);
|
|
6960
|
-
if (afterReturnValue === void 0) return
|
|
7313
|
+
if (afterReturnValue === void 0) return unparsable("assertion return value");
|
|
6961
7314
|
const isolated = rewriteIsolateModules(returnValue.source, afterReturnValue);
|
|
6962
7315
|
const afterIsolate = parse2(fileName, isolated.source);
|
|
6963
|
-
if (afterIsolate === void 0) return
|
|
7316
|
+
if (afterIsolate === void 0) return unparsable("isolate modules");
|
|
6964
7317
|
const emptyImplementations = rewriteEmptyMockImplementation(isolated.source, afterIsolate);
|
|
6965
7318
|
const afterEmpty = parse2(fileName, emptyImplementations.source);
|
|
6966
|
-
if (afterEmpty === void 0) return
|
|
7319
|
+
if (afterEmpty === void 0) return unparsable("empty mock implementation");
|
|
6967
7320
|
const hooks = rewriteHookReturnValue(emptyImplementations.source, afterEmpty);
|
|
6968
7321
|
const awaited = propagateAwait(fileName, hooks.source, original, asyncHelpers);
|
|
6969
7322
|
const program = parse2(fileName, awaited.source);
|
|
6970
|
-
if (program === void 0) return
|
|
7323
|
+
if (program === void 0) return unparsable("hook body");
|
|
6971
7324
|
const hoist = hoistCapturedBindings(
|
|
6972
7325
|
awaited.source,
|
|
6973
7326
|
program,
|
|
@@ -6975,12 +7328,18 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
|
|
|
6975
7328
|
moduleBindings(program),
|
|
6976
7329
|
importedNames(program)
|
|
6977
7330
|
);
|
|
6978
|
-
const withImport = injectTypeImport(fileName, hoist.source,
|
|
7331
|
+
const withImport = injectTypeImport(fileName, hoist.source, [
|
|
7332
|
+
...renamed.typeImports,
|
|
7333
|
+
...spyTypes.typeImports
|
|
7334
|
+
]);
|
|
6979
7335
|
const deep = normaliseDeepImports(withImport);
|
|
6980
7336
|
const repointed = repointMswServer(fileName, deep.source);
|
|
7337
|
+
const finalProgram = parse2(fileName, repointed.source);
|
|
7338
|
+
if (finalProgram === void 0) return unparsable("msw server import");
|
|
7339
|
+
const barrels = rewriteReexportBarrelMocks(fileName, repointed.source, finalProgram);
|
|
6981
7340
|
return {
|
|
6982
|
-
source:
|
|
6983
|
-
changed:
|
|
7341
|
+
source: barrels.source,
|
|
7342
|
+
changed: barrels.source !== source,
|
|
6984
7343
|
renamed: renamed.rewritten,
|
|
6985
7344
|
hoisted: hoist.hoisted,
|
|
6986
7345
|
unhandled: renamed.unhandled,
|
|
@@ -6993,9 +7352,13 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
|
|
|
6993
7352
|
...isolated.rewritten > 0 ? ["jest.isolateModules"] : [],
|
|
6994
7353
|
...emptyImplementations.rewritten > 0 ? ["empty mockImplementation"] : [],
|
|
6995
7354
|
...hooks.rewritten > 0 ? ["hook body, so it does not return its mock"] : [],
|
|
6996
|
-
...
|
|
7355
|
+
...spyTypes.rewritten > 0 ? ["SpyInstance<T> as a signature, not a return type"] : [],
|
|
7356
|
+
...awaited.rewritten > 0 ? ["await propagated to a helper that became async"] : [],
|
|
7357
|
+
...barrels.added.map((alias) => `mock carried to ${alias}, which re-exports the mocked file`),
|
|
7358
|
+
...fakeTimers.translated
|
|
6997
7359
|
].sort(),
|
|
6998
7360
|
refusals: [
|
|
7361
|
+
...fakeTimers.refusals,
|
|
6999
7362
|
...hoist.refusals,
|
|
7000
7363
|
...mocks.refusals.map((refusal) => ({ binding: refusal.binding, reason: refusal.reason })),
|
|
7001
7364
|
...constructible.refusals,
|
|
@@ -7006,8 +7369,8 @@ function migrateTestFile(fileName, source, defaultImported = /* @__PURE__ */ new
|
|
|
7006
7369
|
}
|
|
7007
7370
|
|
|
7008
7371
|
// src/roles/test/codemod/test-support-files.ts
|
|
7009
|
-
import { existsSync as
|
|
7010
|
-
import { dirname as
|
|
7372
|
+
import { existsSync as existsSync25, readFileSync as readFileSync24, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
|
|
7373
|
+
import { dirname as dirname6, join as join30, relative as relative5, resolve as resolve4 } from "path";
|
|
7011
7374
|
var SKIPPED = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", ".git", ".vite"]);
|
|
7012
7375
|
var TEST_FILE2 = /\.(spec|test)\.[cm]?[jt]sx?$/;
|
|
7013
7376
|
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
@@ -7026,14 +7389,14 @@ function relativeSpecifiers(source) {
|
|
|
7026
7389
|
return found;
|
|
7027
7390
|
}
|
|
7028
7391
|
function resolveSpecifier(fromFile, specifier) {
|
|
7029
|
-
const base = resolve4(
|
|
7030
|
-
if (
|
|
7392
|
+
const base = resolve4(dirname6(fromFile), specifier);
|
|
7393
|
+
if (existsSync25(base) && statSync5(base).isFile()) return base;
|
|
7031
7394
|
for (const extension of EXTENSIONS) {
|
|
7032
|
-
if (
|
|
7395
|
+
if (existsSync25(`${base}${extension}`)) return `${base}${extension}`;
|
|
7033
7396
|
}
|
|
7034
7397
|
for (const extension of EXTENSIONS) {
|
|
7035
|
-
const index =
|
|
7036
|
-
if (
|
|
7398
|
+
const index = join30(base, `index${extension}`);
|
|
7399
|
+
if (existsSync25(index)) return index;
|
|
7037
7400
|
}
|
|
7038
7401
|
return void 0;
|
|
7039
7402
|
}
|
|
@@ -7044,15 +7407,15 @@ function moduleSources(cwd) {
|
|
|
7044
7407
|
const dir = stack.pop();
|
|
7045
7408
|
let entries;
|
|
7046
7409
|
try {
|
|
7047
|
-
entries =
|
|
7410
|
+
entries = readdirSync5(dir, { withFileTypes: true });
|
|
7048
7411
|
} catch {
|
|
7049
7412
|
continue;
|
|
7050
7413
|
}
|
|
7051
7414
|
for (const entry of entries) {
|
|
7052
7415
|
if (entry.isDirectory()) {
|
|
7053
|
-
if (!SKIPPED.has(entry.name)) stack.push(
|
|
7416
|
+
if (!SKIPPED.has(entry.name)) stack.push(join30(dir, entry.name));
|
|
7054
7417
|
} else if (SOURCE_FILE.test(entry.name)) {
|
|
7055
|
-
found.push(
|
|
7418
|
+
found.push(join30(dir, entry.name));
|
|
7056
7419
|
}
|
|
7057
7420
|
}
|
|
7058
7421
|
}
|
|
@@ -7060,12 +7423,24 @@ function moduleSources(cwd) {
|
|
|
7060
7423
|
}
|
|
7061
7424
|
function read(path) {
|
|
7062
7425
|
try {
|
|
7063
|
-
return
|
|
7426
|
+
return readFileSync24(path, "utf8");
|
|
7064
7427
|
} catch {
|
|
7065
7428
|
return void 0;
|
|
7066
7429
|
}
|
|
7067
7430
|
}
|
|
7068
|
-
function
|
|
7431
|
+
function setupFilesInside(cwd, named) {
|
|
7432
|
+
const kept = [];
|
|
7433
|
+
for (const raw of named) {
|
|
7434
|
+
const withoutToken = raw.replaceAll("<rootDir>/", "").replaceAll("<rootDir>", ".");
|
|
7435
|
+
const full = resolve4(cwd, withoutToken);
|
|
7436
|
+
const inside = relative5(cwd, full);
|
|
7437
|
+
if (inside.startsWith("..") || inside === "") continue;
|
|
7438
|
+
if (!existsSync25(full)) continue;
|
|
7439
|
+
kept.push(inside.replaceAll("\\", "/"));
|
|
7440
|
+
}
|
|
7441
|
+
return kept;
|
|
7442
|
+
}
|
|
7443
|
+
function listTestSupportFiles(cwd, setupFiles = []) {
|
|
7069
7444
|
const sources = moduleSources(cwd);
|
|
7070
7445
|
const contents = /* @__PURE__ */ new Map();
|
|
7071
7446
|
for (const file of sources) {
|
|
@@ -7095,7 +7470,8 @@ function listTestSupportFiles(cwd) {
|
|
|
7095
7470
|
queue.push(target);
|
|
7096
7471
|
}
|
|
7097
7472
|
}
|
|
7098
|
-
|
|
7473
|
+
const reachable = [...testOnly].filter((file) => /\brequire\s*\(/.test(contents.get(file) ?? "")).map((file) => relative5(cwd, file).replaceAll("\\", "/"));
|
|
7474
|
+
return [.../* @__PURE__ */ new Set([...reachable, ...setupFilesInside(cwd, setupFiles)])].sort();
|
|
7099
7475
|
}
|
|
7100
7476
|
|
|
7101
7477
|
// src/roles/test/codemod/migrate-module.ts
|
|
@@ -7105,7 +7481,7 @@ function parse3(source) {
|
|
|
7105
7481
|
}
|
|
7106
7482
|
function read2(path) {
|
|
7107
7483
|
try {
|
|
7108
|
-
return
|
|
7484
|
+
return readFileSync25(path, "utf8");
|
|
7109
7485
|
} catch {
|
|
7110
7486
|
return void 0;
|
|
7111
7487
|
}
|
|
@@ -7117,16 +7493,16 @@ function defaultImportedSpecifiers(cwd) {
|
|
|
7117
7493
|
const walk2 = (dir) => {
|
|
7118
7494
|
let entries;
|
|
7119
7495
|
try {
|
|
7120
|
-
entries =
|
|
7496
|
+
entries = readdirSync6(dir);
|
|
7121
7497
|
} catch {
|
|
7122
7498
|
return;
|
|
7123
7499
|
}
|
|
7124
7500
|
for (const entry of entries) {
|
|
7125
7501
|
if (SKIPPED2.has(entry)) continue;
|
|
7126
|
-
const path =
|
|
7502
|
+
const path = join31(dir, entry);
|
|
7127
7503
|
let directory;
|
|
7128
7504
|
try {
|
|
7129
|
-
directory =
|
|
7505
|
+
directory = statSync6(path).isDirectory();
|
|
7130
7506
|
} catch {
|
|
7131
7507
|
continue;
|
|
7132
7508
|
}
|
|
@@ -7143,7 +7519,7 @@ function defaultImportedSpecifiers(cwd) {
|
|
|
7143
7519
|
walk2(cwd);
|
|
7144
7520
|
return found;
|
|
7145
7521
|
}
|
|
7146
|
-
function migrateModuleTests(cwd) {
|
|
7522
|
+
function migrateModuleTests(cwd, setupFiles = []) {
|
|
7147
7523
|
const operations = [];
|
|
7148
7524
|
const blockers = [];
|
|
7149
7525
|
let files = 0;
|
|
@@ -7151,13 +7527,13 @@ function migrateModuleTests(cwd) {
|
|
|
7151
7527
|
let hoisted = 0;
|
|
7152
7528
|
const defaultImported = defaultImportedSpecifiers(cwd);
|
|
7153
7529
|
const asyncHelpers = /* @__PURE__ */ new Set();
|
|
7154
|
-
for (const relative7 of listTestSupportFiles(cwd)) {
|
|
7155
|
-
const source = read2(
|
|
7530
|
+
for (const relative7 of listTestSupportFiles(cwd, setupFiles)) {
|
|
7531
|
+
const source = read2(join31(cwd, relative7));
|
|
7156
7532
|
if (source === void 0) {
|
|
7157
7533
|
blockers.push({ file: relative7, reason: "this file could not be read" });
|
|
7158
7534
|
continue;
|
|
7159
7535
|
}
|
|
7160
|
-
const migrated = migrateTestFile(
|
|
7536
|
+
const migrated = migrateTestFile(join31(cwd, relative7), source, defaultImported);
|
|
7161
7537
|
if (!migrated.changed) continue;
|
|
7162
7538
|
const before = parse3(source);
|
|
7163
7539
|
const after = parse3(migrated.source);
|
|
@@ -7172,12 +7548,12 @@ function migrateModuleTests(cwd) {
|
|
|
7172
7548
|
operations.push({ kind: "write", path: relative7, contents: migrated.source });
|
|
7173
7549
|
}
|
|
7174
7550
|
for (const relative7 of listTestFiles(cwd)) {
|
|
7175
|
-
const source = read2(
|
|
7551
|
+
const source = read2(join31(cwd, relative7));
|
|
7176
7552
|
if (source === void 0) {
|
|
7177
7553
|
blockers.push({ file: relative7, reason: "this file could not be read" });
|
|
7178
7554
|
continue;
|
|
7179
7555
|
}
|
|
7180
|
-
const migrated = migrateTestFile(
|
|
7556
|
+
const migrated = migrateTestFile(join31(cwd, relative7), source, defaultImported, asyncHelpers);
|
|
7181
7557
|
for (const member of migrated.unhandled) {
|
|
7182
7558
|
blockers.push({
|
|
7183
7559
|
file: `${relative7}:${member.line}`,
|
|
@@ -7185,10 +7561,7 @@ function migrateModuleTests(cwd) {
|
|
|
7185
7561
|
});
|
|
7186
7562
|
}
|
|
7187
7563
|
for (const refusal of migrated.refusals) {
|
|
7188
|
-
blockers.push({
|
|
7189
|
-
file: relative7,
|
|
7190
|
-
reason: `\`${refusal.binding}\` cannot move into vi.hoisted: ${refusal.reason}`
|
|
7191
|
-
});
|
|
7564
|
+
blockers.push({ file: relative7, reason: refusal.reason });
|
|
7192
7565
|
}
|
|
7193
7566
|
if (!migrated.changed) continue;
|
|
7194
7567
|
files++;
|
|
@@ -7200,11 +7573,16 @@ function migrateModuleTests(cwd) {
|
|
|
7200
7573
|
}
|
|
7201
7574
|
|
|
7202
7575
|
// src/roles/test/nest/generate-config.ts
|
|
7203
|
-
import { readdirSync as
|
|
7204
|
-
import { join as
|
|
7576
|
+
import { existsSync as existsSync26, readdirSync as readdirSync7, readFileSync as readFileSync26, statSync as statSync7 } from "fs";
|
|
7577
|
+
import { join as join32, relative as relative6 } from "path";
|
|
7205
7578
|
var NEST_TEST_PRESET_SPECIFIER = "@hublo/sentinel/test/nest";
|
|
7579
|
+
var REACT_TEST_PRESET_SPECIFIER = "@hublo/sentinel/test/react";
|
|
7580
|
+
function flavourOf(reading) {
|
|
7581
|
+
return reading.testEnvironment === "jsdom" ? "react" : "nest";
|
|
7582
|
+
}
|
|
7206
7583
|
var SENTINEL_TEST_SETUP = "@hublo/sentinel/test/setup/nest";
|
|
7207
7584
|
var SENTINEL_MSW_SETUP = "@hublo/sentinel/test/setup/msw-lifecycle";
|
|
7585
|
+
var SENTINEL_WORKSPACE_SETUP = "@hublo/sentinel/test/setup/workspace";
|
|
7208
7586
|
var NEST_TEST_CONFIG_FILE = "vitest.config.mts";
|
|
7209
7587
|
function hopsToRoot(cwd, workspaceRoot) {
|
|
7210
7588
|
const up = relative6(cwd, workspaceRoot).replaceAll("\\", "/");
|
|
@@ -7235,16 +7613,16 @@ function readModuleTraits(cwd) {
|
|
|
7235
7613
|
const walk2 = (dir) => {
|
|
7236
7614
|
let entries;
|
|
7237
7615
|
try {
|
|
7238
|
-
entries =
|
|
7616
|
+
entries = readdirSync7(dir);
|
|
7239
7617
|
} catch {
|
|
7240
7618
|
return;
|
|
7241
7619
|
}
|
|
7242
7620
|
for (const entry of entries) {
|
|
7243
7621
|
if (SKIPPED3.has(entry)) continue;
|
|
7244
|
-
const path =
|
|
7622
|
+
const path = join32(dir, entry);
|
|
7245
7623
|
let directory;
|
|
7246
7624
|
try {
|
|
7247
|
-
directory =
|
|
7625
|
+
directory = statSync7(path).isDirectory();
|
|
7248
7626
|
} catch {
|
|
7249
7627
|
continue;
|
|
7250
7628
|
}
|
|
@@ -7255,7 +7633,7 @@ function readModuleTraits(cwd) {
|
|
|
7255
7633
|
if (!/\.tsx?$/.test(entry)) continue;
|
|
7256
7634
|
let source;
|
|
7257
7635
|
try {
|
|
7258
|
-
source =
|
|
7636
|
+
source = readFileSync26(path, "utf8");
|
|
7259
7637
|
} catch {
|
|
7260
7638
|
continue;
|
|
7261
7639
|
}
|
|
@@ -7272,9 +7650,30 @@ function readModuleTraits(cwd) {
|
|
|
7272
7650
|
return traits;
|
|
7273
7651
|
}
|
|
7274
7652
|
var withoutRootDir = (value) => value.replaceAll("<rootDir>/", "./").replaceAll("<rootDir>", ".");
|
|
7275
|
-
function
|
|
7276
|
-
const
|
|
7277
|
-
|
|
7653
|
+
function ownsMswLifecycle(cwd, reading) {
|
|
7654
|
+
const declared = [...reading.setupFiles ?? [], ...reading.setupFilesAfterEnv ?? []].filter(
|
|
7655
|
+
(file) => !file.endsWith("jest.setup.after.env.js")
|
|
7656
|
+
);
|
|
7657
|
+
for (const raw of declared) {
|
|
7658
|
+
const file = join32(cwd, raw.replaceAll("<rootDir>/", "").replaceAll("<rootDir>", "."));
|
|
7659
|
+
if (!existsSync26(file)) continue;
|
|
7660
|
+
try {
|
|
7661
|
+
if (/\.listen\s*\(/.test(readFileSync26(file, "utf8"))) return true;
|
|
7662
|
+
} catch {
|
|
7663
|
+
continue;
|
|
7664
|
+
}
|
|
7665
|
+
}
|
|
7666
|
+
return false;
|
|
7667
|
+
}
|
|
7668
|
+
function setupBlock(cwd, reading, msw) {
|
|
7669
|
+
const declared = [...reading.setupFiles ?? [], ...reading.setupFilesAfterEnv ?? []];
|
|
7670
|
+
const namedTheRootSetup = declared.some((file) => file.endsWith("jest.setup.after.env.js"));
|
|
7671
|
+
const own = declared.filter((file) => !file.endsWith("jest.setup.after.env.js")).map((file) => `'${withoutRootDir(file)}'`);
|
|
7672
|
+
const sentinel = [
|
|
7673
|
+
`'${SENTINEL_TEST_SETUP}'`,
|
|
7674
|
+
...namedTheRootSetup ? [`'${SENTINEL_WORKSPACE_SETUP}'`] : [],
|
|
7675
|
+
...msw && !ownsMswLifecycle(cwd, reading) ? [`'${SENTINEL_MSW_SETUP}'`] : []
|
|
7676
|
+
];
|
|
7278
7677
|
return ` setupFiles: [${[...sentinel, ...own].join(", ")}],`;
|
|
7279
7678
|
}
|
|
7280
7679
|
function envBlock(reading) {
|
|
@@ -7282,6 +7681,19 @@ function envBlock(reading) {
|
|
|
7282
7681
|
const entries = reading.env.map((entry) => ` ${entry.name}: ${entry.value},`);
|
|
7283
7682
|
return [" env: {", ...entries, " },"].join("\n");
|
|
7284
7683
|
}
|
|
7684
|
+
function concurrencyBlock(cwd, reading, target) {
|
|
7685
|
+
const declared = reading.maxWorkers;
|
|
7686
|
+
if (declared !== void 0) {
|
|
7687
|
+
return declared === "1" ? ` fileParallelism: false,` : ` maxWorkers: ${declared},`;
|
|
7688
|
+
}
|
|
7689
|
+
const ci = projectTargets(cwd)?.[target]?.configurations?.ci;
|
|
7690
|
+
if (ci === void 0) return "";
|
|
7691
|
+
if (ci.runInBand === true) return ` fileParallelism: false,`;
|
|
7692
|
+
if (typeof ci.maxWorkers === "number") {
|
|
7693
|
+
return ci.maxWorkers === 1 ? ` fileParallelism: false,` : ` maxWorkers: ${ci.maxWorkers},`;
|
|
7694
|
+
}
|
|
7695
|
+
return "";
|
|
7696
|
+
}
|
|
7285
7697
|
function timeoutBlock(reading) {
|
|
7286
7698
|
return reading.testTimeout === void 0 ? "" : ` testTimeout: ${reading.testTimeout},`;
|
|
7287
7699
|
}
|
|
@@ -7300,14 +7712,19 @@ function excludeBlock(reading) {
|
|
|
7300
7712
|
const globs = reading.excludeGlobs.map((glob) => `'${glob}'`).join(", ");
|
|
7301
7713
|
return ` exclude: [...configDefaults.exclude, ${globs}],`;
|
|
7302
7714
|
}
|
|
7715
|
+
function isPackage(replacement) {
|
|
7716
|
+
return !replacement.startsWith(".") && !replacement.startsWith("/") && !replacement.toLowerCase().includes("<rootdir>");
|
|
7717
|
+
}
|
|
7303
7718
|
function aliasBlock(reading) {
|
|
7304
7719
|
if (reading.aliases === void 0 || reading.aliases.length === 0) return "";
|
|
7305
|
-
const entries = reading.aliases.map(
|
|
7306
|
-
|
|
7307
|
-
|
|
7720
|
+
const entries = reading.aliases.map((alias) => {
|
|
7721
|
+
const pattern = alias.find.replaceAll(String.raw`\\`, "\\").replaceAll("/", String.raw`\/`);
|
|
7722
|
+
const target = isPackage(alias.replacement) ? `'${alias.replacement}'` : `path.resolve(import.meta.dirname, '${withoutRootDir(alias.replacement)}')`;
|
|
7723
|
+
return ` { find: /${pattern}/, replacement: ${target} },`;
|
|
7724
|
+
});
|
|
7308
7725
|
return [" resolve: {", " alias: [", ...entries, " ],", " },"].join("\n");
|
|
7309
7726
|
}
|
|
7310
|
-
function generateNestTestConfig(cwd, reading) {
|
|
7727
|
+
function generateNestTestConfig(cwd, reading, target = "test") {
|
|
7311
7728
|
if (reading.refusals.length > 0) return { refusals: reading.refusals };
|
|
7312
7729
|
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
7313
7730
|
if (workspaceRoot === void 0) {
|
|
@@ -7317,9 +7734,10 @@ function generateNestTestConfig(cwd, reading) {
|
|
|
7317
7734
|
const up = hopsToRoot(cwd, workspaceRoot);
|
|
7318
7735
|
const test = [
|
|
7319
7736
|
environmentBlock(reading),
|
|
7320
|
-
setupBlock(reading, traits.msw),
|
|
7737
|
+
setupBlock(cwd, reading, traits.msw),
|
|
7321
7738
|
envBlock(reading),
|
|
7322
7739
|
timeoutBlock(reading),
|
|
7740
|
+
concurrencyBlock(cwd, reading, target),
|
|
7323
7741
|
globalSetupBlock(reading),
|
|
7324
7742
|
includeBlock(reading),
|
|
7325
7743
|
excludeBlock(reading),
|
|
@@ -7331,7 +7749,7 @@ function generateNestTestConfig(cwd, reading) {
|
|
|
7331
7749
|
`import path from 'node:path'`,
|
|
7332
7750
|
"",
|
|
7333
7751
|
...needsDefaults ? [`import { configDefaults } from 'vitest/config'`, ""] : [],
|
|
7334
|
-
`import { defineConfig, nestTestConfig } from '${NEST_TEST_PRESET_SPECIFIER}'`
|
|
7752
|
+
flavourOf(reading) === "react" ? `import { defineConfig, reactTestConfig } from '${REACT_TEST_PRESET_SPECIFIER}'` : `import { defineConfig, nestTestConfig } from '${NEST_TEST_PRESET_SPECIFIER}'`
|
|
7335
7753
|
].join("\n");
|
|
7336
7754
|
const overrides = [aliasBlock(reading), " test: {", ...test, " },"].filter(
|
|
7337
7755
|
(line) => line !== ""
|
|
@@ -7342,7 +7760,7 @@ function generateNestTestConfig(cwd, reading) {
|
|
|
7342
7760
|
source: `${imports}
|
|
7343
7761
|
|
|
7344
7762
|
export default defineConfig(
|
|
7345
|
-
nestTestConfig({
|
|
7763
|
+
${flavourOf(reading) === "react" ? "reactTestConfig" : "nestTestConfig"}({
|
|
7346
7764
|
root: import.meta.dirname,
|
|
7347
7765
|
workspaceRoot: path.resolve(import.meta.dirname, '${up}'),${decoratorLine}
|
|
7348
7766
|
overrides: {
|
|
@@ -7355,14 +7773,15 @@ ${overrides.join("\n")}
|
|
|
7355
7773
|
}
|
|
7356
7774
|
|
|
7357
7775
|
// src/roles/test/nest/read-jest-config.ts
|
|
7358
|
-
import { existsSync as
|
|
7359
|
-
import { join as
|
|
7776
|
+
import { existsSync as existsSync27, readFileSync as readFileSync27 } from "fs";
|
|
7777
|
+
import { join as join33 } from "path";
|
|
7360
7778
|
var CARRIED = [
|
|
7361
7779
|
"testEnvironment",
|
|
7362
7780
|
"coverageDirectory",
|
|
7363
7781
|
"setupFilesAfterEnv",
|
|
7364
7782
|
"setupFiles",
|
|
7365
7783
|
"testTimeout",
|
|
7784
|
+
"maxWorkers",
|
|
7366
7785
|
"globalSetup",
|
|
7367
7786
|
"testMatch",
|
|
7368
7787
|
"collectCoverageFrom",
|
|
@@ -7376,9 +7795,10 @@ var DISCARDED = [
|
|
|
7376
7795
|
"transform",
|
|
7377
7796
|
"extensionsToTreatAsEsm"
|
|
7378
7797
|
];
|
|
7379
|
-
var STRING = String.raw`['"]([^'"]*)['"]`;
|
|
7798
|
+
var STRING = String.raw`['"\`]([^'"\`]*)['"\`]`;
|
|
7380
7799
|
function readString(body, key) {
|
|
7381
|
-
|
|
7800
|
+
const value = new RegExp(String.raw`\b${key}\s*:\s*${STRING}`).exec(body)?.[1];
|
|
7801
|
+
return value?.includes("${") === true ? void 0 : value;
|
|
7382
7802
|
}
|
|
7383
7803
|
function readNumber(body, key) {
|
|
7384
7804
|
return new RegExp(String.raw`\b${key}\s*:\s*(\d+)`).exec(body)?.[1];
|
|
@@ -7486,19 +7906,73 @@ function configBody(source) {
|
|
|
7486
7906
|
function declaredKeys(body) {
|
|
7487
7907
|
const keys = [];
|
|
7488
7908
|
let depth = 0;
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7909
|
+
let quote;
|
|
7910
|
+
let token = "";
|
|
7911
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
7912
|
+
const char = body[i];
|
|
7913
|
+
if (quote !== void 0) {
|
|
7914
|
+
if (char === quote && body[i - 1] !== "\\") quote = void 0;
|
|
7915
|
+
continue;
|
|
7916
|
+
}
|
|
7917
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
7918
|
+
quote = char;
|
|
7919
|
+
continue;
|
|
7920
|
+
}
|
|
7921
|
+
if (char === "/" && body[i + 1] === "/") {
|
|
7922
|
+
const end = body.indexOf("\n", i);
|
|
7923
|
+
i = end === -1 ? body.length : end;
|
|
7924
|
+
token = "";
|
|
7925
|
+
continue;
|
|
7926
|
+
}
|
|
7927
|
+
if (char === "/" && body[i + 1] === "*") {
|
|
7928
|
+
const end = body.indexOf("*/", i + 2);
|
|
7929
|
+
i = end === -1 ? body.length : end + 1;
|
|
7930
|
+
token = "";
|
|
7931
|
+
continue;
|
|
7932
|
+
}
|
|
7933
|
+
if (char === "{" || char === "[" || char === "(") {
|
|
7934
|
+
depth += 1;
|
|
7935
|
+
token = "";
|
|
7936
|
+
continue;
|
|
7937
|
+
}
|
|
7938
|
+
if (char === "}" || char === "]" || char === ")") {
|
|
7939
|
+
depth -= 1;
|
|
7940
|
+
token = "";
|
|
7941
|
+
continue;
|
|
7493
7942
|
}
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7943
|
+
if (depth === 0 && char === ":") {
|
|
7944
|
+
const key = /([A-Za-z_$][\w$]*)\s*$/.exec(token)?.[1];
|
|
7945
|
+
if (key !== void 0) keys.push(key);
|
|
7946
|
+
token = "";
|
|
7947
|
+
continue;
|
|
7948
|
+
}
|
|
7949
|
+
if (depth === 0 && char === ",") {
|
|
7950
|
+
token = "";
|
|
7951
|
+
continue;
|
|
7497
7952
|
}
|
|
7953
|
+
token += char;
|
|
7498
7954
|
}
|
|
7499
7955
|
return keys;
|
|
7500
7956
|
}
|
|
7501
|
-
function
|
|
7957
|
+
function extendedConfig(source, cwd) {
|
|
7958
|
+
if (cwd === void 0) return void 0;
|
|
7959
|
+
for (const line of source.split("\n")) {
|
|
7960
|
+
const match = /^\s*import\s+(?!type\b)(\w+)\s+from\s+['"](\.[^'"]*)['"]/.exec(line);
|
|
7961
|
+
if (match === null) continue;
|
|
7962
|
+
const [, binding, specifier] = match;
|
|
7963
|
+
const candidates = [specifier, `${specifier}.ts`, `${specifier}.js`].map(
|
|
7964
|
+
(name) => name.replace(/^\.\//, "")
|
|
7965
|
+
);
|
|
7966
|
+
const file = candidates.find(
|
|
7967
|
+
(name) => /^jest[\w.-]*\.(ts|js)$/.test(name) && existsSync27(join33(cwd, name))
|
|
7968
|
+
);
|
|
7969
|
+
if (file === void 0) continue;
|
|
7970
|
+
if (!new RegExp(String.raw`\{\s*\.\.\.${binding}\b`).test(source)) continue;
|
|
7971
|
+
return { binding, file };
|
|
7972
|
+
}
|
|
7973
|
+
return void 0;
|
|
7974
|
+
}
|
|
7975
|
+
function sideEffects(source, extended) {
|
|
7502
7976
|
const found = [];
|
|
7503
7977
|
for (const line of source.split("\n")) {
|
|
7504
7978
|
const valueImport = /^\s*import\s+(?!type\b)(.*)$/.exec(line);
|
|
@@ -7507,6 +7981,7 @@ function sideEffects(source) {
|
|
|
7507
7981
|
...(/\{([^}]*)\}/.exec(valueImport[1])?.[1] ?? "").split(","),
|
|
7508
7982
|
/^\s*(\w+)\s*(?:,|from)/.exec(valueImport[1])?.[1] ?? ""
|
|
7509
7983
|
].map((name) => name.split(" as ").pop()?.trim() ?? "").filter((name) => name !== "");
|
|
7984
|
+
if (extended !== void 0 && bindings.includes(extended.binding)) continue;
|
|
7510
7985
|
const used = bindings.some((name) => {
|
|
7511
7986
|
const uses = source.match(new RegExp(String.raw`\b${name}\b`, "g")) ?? [];
|
|
7512
7987
|
return uses.length > 1;
|
|
@@ -7515,16 +7990,17 @@ function sideEffects(source) {
|
|
|
7515
7990
|
}
|
|
7516
7991
|
return found;
|
|
7517
7992
|
}
|
|
7518
|
-
function readJestConfig(cwd) {
|
|
7519
|
-
const file = JEST_CONFIG_FILES.find((name) =>
|
|
7520
|
-
if (file === void 0) return void 0;
|
|
7993
|
+
function readJestConfig(cwd, named) {
|
|
7994
|
+
const file = named ?? JEST_CONFIG_FILES.find((name) => existsSync27(join33(cwd, name)));
|
|
7995
|
+
if (file === void 0 || !existsSync27(join33(cwd, file))) return void 0;
|
|
7521
7996
|
let source;
|
|
7522
7997
|
try {
|
|
7523
|
-
source =
|
|
7998
|
+
source = readFileSync27(join33(cwd, file), "utf8");
|
|
7524
7999
|
} catch {
|
|
7525
8000
|
return { file, refusals: [`${file} could not be read`], notes: [] };
|
|
7526
8001
|
}
|
|
7527
|
-
const
|
|
8002
|
+
const extended = extendedConfig(source, cwd);
|
|
8003
|
+
const refusals = sideEffects(source, extended);
|
|
7528
8004
|
const body = configBody(source);
|
|
7529
8005
|
if (body === void 0) {
|
|
7530
8006
|
return { file, refusals: [...refusals, "its config object could not be located"], notes: [] };
|
|
@@ -7554,39 +8030,108 @@ function readJestConfig(cwd) {
|
|
|
7554
8030
|
`\`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
8031
|
);
|
|
7556
8032
|
}
|
|
8033
|
+
for (const alias of mapper.aliases) {
|
|
8034
|
+
if (/<rootdir>/i.test(alias.replacement) && !alias.replacement.includes("<rootDir>")) {
|
|
8035
|
+
notes.push(
|
|
8036
|
+
`\`moduleNameMapper\` entry \`${alias.find}\` writes \`${/<[Rr]oot[Dd]ir>/.exec(alias.replacement)?.[0] ?? "<RootDir>"}\`, and jest only substitutes \`<rootDir>\`. That mapping resolved to a literal directory under jest too, so it has never done anything; it is carried unchanged rather than quietly repaired`
|
|
8037
|
+
);
|
|
8038
|
+
}
|
|
8039
|
+
}
|
|
7557
8040
|
const coverageFrom = readCoverageFrom(body);
|
|
7558
8041
|
const env = readEnvironment(source);
|
|
7559
8042
|
const globalSetup = readString(body, "globalSetup");
|
|
7560
8043
|
const testTimeout = readNumber(body, "testTimeout");
|
|
8044
|
+
const maxWorkers = readNumber(body, "maxWorkers");
|
|
7561
8045
|
const testMatch = readStringArray(body, "testMatch");
|
|
8046
|
+
const base = extended === void 0 ? void 0 : readJestConfig(cwd, extended.file);
|
|
8047
|
+
const inherited = base === void 0 ? {} : { ...base, refusals: [], notes: [] };
|
|
8048
|
+
for (const [jestKey, field] of [
|
|
8049
|
+
["testPathIgnorePatterns", "excludeGlobs"],
|
|
8050
|
+
["setupFilesAfterEnv", "setupFilesAfterEnv"],
|
|
8051
|
+
["setupFiles", "setupFiles"],
|
|
8052
|
+
["testMatch", "testMatch"],
|
|
8053
|
+
["collectCoverageFrom", "coverageFrom"],
|
|
8054
|
+
["moduleNameMapper", "aliases"]
|
|
8055
|
+
]) {
|
|
8056
|
+
if (declaredKeys(body).includes(jestKey)) delete inherited[field];
|
|
8057
|
+
}
|
|
7562
8058
|
return {
|
|
8059
|
+
...inherited,
|
|
7563
8060
|
file,
|
|
7564
8061
|
...readString(body, "testEnvironment") !== void 0 ? { testEnvironment: readString(body, "testEnvironment") } : {},
|
|
7565
8062
|
...readString(body, "coverageDirectory") !== void 0 ? { coverageDirectory: readString(body, "coverageDirectory") } : {},
|
|
7566
8063
|
...afterEnv !== void 0 ? { setupFilesAfterEnv: afterEnv } : {},
|
|
7567
8064
|
...before !== void 0 ? { setupFiles: before } : {},
|
|
7568
8065
|
...testTimeout !== void 0 ? { testTimeout } : {},
|
|
8066
|
+
...maxWorkers !== void 0 ? { maxWorkers } : {},
|
|
7569
8067
|
...globalSetup !== void 0 ? { globalSetup: [globalSetup] } : {},
|
|
7570
8068
|
...testMatch !== void 0 ? { testMatch } : {},
|
|
7571
8069
|
...coverageFrom !== void 0 ? { coverageFrom } : {},
|
|
7572
8070
|
...excludeGlobs.length > 0 ? { excludeGlobs } : {},
|
|
7573
8071
|
...mapper.aliases.length > 0 ? { aliases: mapper.aliases } : {},
|
|
7574
8072
|
...env.length > 0 ? { env } : {},
|
|
7575
|
-
refusals,
|
|
7576
|
-
notes
|
|
8073
|
+
refusals: [...base?.refusals ?? [], ...refusals],
|
|
8074
|
+
notes: [...base?.notes ?? [], ...notes]
|
|
7577
8075
|
};
|
|
7578
8076
|
}
|
|
7579
8077
|
|
|
8078
|
+
// src/roles/test/nest/run-modes.ts
|
|
8079
|
+
import { existsSync as existsSync28 } from "fs";
|
|
8080
|
+
import { basename as basename2, join as join34 } from "path";
|
|
8081
|
+
var JEST_EXECUTOR = "@nx/jest:jest";
|
|
8082
|
+
var PRIMARY_CONFIG_FILE = "vitest.config.mts";
|
|
8083
|
+
var PRIMARY_TARGET = "test";
|
|
8084
|
+
function configName(declared) {
|
|
8085
|
+
return basename2(declared.replaceAll("\\", "/"));
|
|
8086
|
+
}
|
|
8087
|
+
function configFileFor(target) {
|
|
8088
|
+
if (target === PRIMARY_TARGET) return PRIMARY_CONFIG_FILE;
|
|
8089
|
+
const suffix = target.startsWith("test-") ? target.slice("test-".length) : target;
|
|
8090
|
+
return `vitest.${suffix}.config.mts`;
|
|
8091
|
+
}
|
|
8092
|
+
function runModes(cwd, fallback) {
|
|
8093
|
+
const targets = projectTargets(cwd) ?? {};
|
|
8094
|
+
const declared = [];
|
|
8095
|
+
for (const [target, raw] of Object.entries(targets)) {
|
|
8096
|
+
const entry = raw;
|
|
8097
|
+
if (entry.executor !== JEST_EXECUTOR) continue;
|
|
8098
|
+
const jestConfig = entry.options?.jestConfig;
|
|
8099
|
+
if (jestConfig === void 0) continue;
|
|
8100
|
+
const name = configName(jestConfig);
|
|
8101
|
+
if (!existsSync28(join34(cwd, name))) continue;
|
|
8102
|
+
declared.push({
|
|
8103
|
+
target,
|
|
8104
|
+
jestConfig: name,
|
|
8105
|
+
vitestConfig: configFileFor(target),
|
|
8106
|
+
primary: target === PRIMARY_TARGET
|
|
8107
|
+
});
|
|
8108
|
+
}
|
|
8109
|
+
if (declared.length === 0) {
|
|
8110
|
+
const [only] = fallback;
|
|
8111
|
+
return only === void 0 ? [] : [
|
|
8112
|
+
{
|
|
8113
|
+
target: PRIMARY_TARGET,
|
|
8114
|
+
jestConfig: only,
|
|
8115
|
+
vitestConfig: PRIMARY_CONFIG_FILE,
|
|
8116
|
+
primary: true
|
|
8117
|
+
}
|
|
8118
|
+
];
|
|
8119
|
+
}
|
|
8120
|
+
return declared.sort(
|
|
8121
|
+
(left, right) => left.primary === right.primary ? left.target.localeCompare(right.target) : Number(right.primary) - Number(left.primary)
|
|
8122
|
+
);
|
|
8123
|
+
}
|
|
8124
|
+
|
|
7580
8125
|
// src/roles/test/jest-migration.ts
|
|
7581
8126
|
var SPEC_TSCONFIG = "tsconfig.spec.json";
|
|
7582
8127
|
var VITEST_GLOBAL_TYPES = "vitest/globals";
|
|
7583
8128
|
function specTypesOperation(cwd) {
|
|
7584
|
-
const path =
|
|
7585
|
-
if (!
|
|
8129
|
+
const path = join35(cwd, SPEC_TSCONFIG);
|
|
8130
|
+
if (!existsSync29(path)) return void 0;
|
|
7586
8131
|
let config;
|
|
7587
8132
|
try {
|
|
7588
8133
|
config = parseJsonc(
|
|
7589
|
-
|
|
8134
|
+
readFileSync28(path, "utf8"),
|
|
7590
8135
|
SPEC_TSCONFIG
|
|
7591
8136
|
);
|
|
7592
8137
|
} catch {
|
|
@@ -7617,23 +8162,32 @@ function blocked(reason) {
|
|
|
7617
8162
|
return { operations: [], blocked: reason };
|
|
7618
8163
|
}
|
|
7619
8164
|
function planJestMigration(context, adoption) {
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
return blocked(
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
8165
|
+
const modes = runModes(context.cwd, adoption.jestConfigs);
|
|
8166
|
+
if (modes.length === 0) {
|
|
8167
|
+
return blocked(`no jest config could be found for this module, so nothing was written.`);
|
|
8168
|
+
}
|
|
8169
|
+
const prepared = [];
|
|
8170
|
+
for (const mode of modes) {
|
|
8171
|
+
const reading = readJestConfig(context.cwd, mode.jestConfig);
|
|
8172
|
+
if (reading === void 0) {
|
|
8173
|
+
return blocked(`${mode.jestConfig} could not be read, so nothing was written.`);
|
|
8174
|
+
}
|
|
8175
|
+
const generated = generateNestTestConfig(context.cwd, reading, mode.target);
|
|
8176
|
+
if (generated.source === void 0) {
|
|
8177
|
+
return blocked(
|
|
8178
|
+
`${mode.jestConfig} (the \`${mode.target}\` target) does not transpose as it stands: ${generated.refusals.join("; ")}. Nothing was written; this needs a look before the migration runs.`
|
|
8179
|
+
);
|
|
8180
|
+
}
|
|
8181
|
+
prepared.push({ mode, reading, source: generated.source });
|
|
7635
8182
|
}
|
|
7636
|
-
const migration = migrateModuleTests(
|
|
8183
|
+
const migration = migrateModuleTests(
|
|
8184
|
+
context.cwd,
|
|
8185
|
+
prepared.flatMap(({ reading }) => [
|
|
8186
|
+
...reading.setupFiles ?? [],
|
|
8187
|
+
...reading.setupFilesAfterEnv ?? [],
|
|
8188
|
+
...reading.globalSetup ?? []
|
|
8189
|
+
])
|
|
8190
|
+
);
|
|
7637
8191
|
if (migration.blockers.length > 0) {
|
|
7638
8192
|
const listed = migration.blockers.map((blocker) => `
|
|
7639
8193
|
${blocker.file}: ${blocker.reason}`).join("");
|
|
@@ -7641,32 +8195,41 @@ function planJestMigration(context, adoption) {
|
|
|
7641
8195
|
`${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
8196
|
);
|
|
7643
8197
|
}
|
|
7644
|
-
const
|
|
8198
|
+
const targets = projectTargets(context.cwd) ?? {};
|
|
8199
|
+
const existingFor = (name) => targets[name];
|
|
7645
8200
|
const siblings = siblingScriptRewrites(context.cwd);
|
|
7646
8201
|
const specTypes = specTypesOperation(context.cwd);
|
|
8202
|
+
const scripts = Object.fromEntries(
|
|
8203
|
+
prepared.map(({ mode }) => [
|
|
8204
|
+
mode.target,
|
|
8205
|
+
mode.primary ? SENTINEL_TEST_COMMAND : `${SENTINEL_TEST_COMMAND} --config ${mode.vitestConfig}`
|
|
8206
|
+
])
|
|
8207
|
+
);
|
|
7647
8208
|
const operations = [
|
|
7648
|
-
{
|
|
8209
|
+
...prepared.map(({ mode, source }) => ({
|
|
8210
|
+
kind: "write",
|
|
8211
|
+
path: mode.vitestConfig,
|
|
8212
|
+
contents: source
|
|
8213
|
+
})),
|
|
7649
8214
|
...migration.operations,
|
|
7650
8215
|
...specTypes === void 0 ? [] : [specTypes],
|
|
7651
|
-
manifestOperation(context.cwd, {
|
|
7652
|
-
...
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
8216
|
+
manifestOperation(context.cwd, { ...scripts, ...siblings }),
|
|
8217
|
+
...prepared.flatMap(
|
|
8218
|
+
({ mode }) => nxTargetOperations({
|
|
8219
|
+
cwd: context.cwd,
|
|
8220
|
+
targets: testTarget({
|
|
8221
|
+
name: mode.target,
|
|
8222
|
+
configFiles: [mode.vitestConfig],
|
|
8223
|
+
outputs: existingFor(mode.target)?.outputs ?? [],
|
|
8224
|
+
dependsOn: existingFor(mode.target)?.dependsOn ?? [],
|
|
8225
|
+
configurations: existingFor(mode.target)?.configurations ?? {}
|
|
8226
|
+
})
|
|
7659
8227
|
})
|
|
7660
|
-
|
|
7661
|
-
...adoption.jestConfigs.map((
|
|
8228
|
+
),
|
|
8229
|
+
...[.../* @__PURE__ */ new Set([...adoption.jestConfigs, ...prepared.map(({ mode }) => mode.jestConfig)])].map(
|
|
8230
|
+
(name) => ({ kind: "delete", path: name })
|
|
8231
|
+
)
|
|
7662
8232
|
];
|
|
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
8233
|
const notes = [
|
|
7671
8234
|
`${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
8235
|
`${NEST_TEST_CONFIG_FILE} written from what ${adoption.jestConfigs.join(", ")} declared, and that config deleted. Every specifier in the new one is a package name: nothing in it reaches outside this module.`
|
|
@@ -7679,6 +8242,9 @@ function planJestMigration(context, adoption) {
|
|
|
7679
8242
|
notes.push(
|
|
7680
8243
|
`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
8244
|
);
|
|
8245
|
+
notes.push(
|
|
8246
|
+
`then run this module's TYPECHECK, not only its tests. The migration rewrites types as well as calls, and a wrong type is invisible to a green suite: measured on a real module, \`--run --test\` passed while \`--run --typescript\` reported ten errors it had not had before.`
|
|
8247
|
+
);
|
|
7682
8248
|
if (Object.keys(siblings).length > 0) {
|
|
7683
8249
|
notes.push(`rewrote ${Object.keys(siblings).join(", ")} to run through sentinel.`);
|
|
7684
8250
|
}
|
|
@@ -7819,6 +8385,311 @@ function plan3(context) {
|
|
|
7819
8385
|
return { operations, notes };
|
|
7820
8386
|
}
|
|
7821
8387
|
|
|
8388
|
+
// src/roles/test/reference.ts
|
|
8389
|
+
import { execFileSync } from "child_process";
|
|
8390
|
+
import { existsSync as existsSync30, mkdtempSync, readFileSync as readFileSync29, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
8391
|
+
import { tmpdir } from "os";
|
|
8392
|
+
import { join as join36 } from "path";
|
|
8393
|
+
|
|
8394
|
+
// src/roles/test/baseline.ts
|
|
8395
|
+
function readSnapshot(report) {
|
|
8396
|
+
const files = report?.testResults ?? [];
|
|
8397
|
+
const passing = [];
|
|
8398
|
+
const failing = [];
|
|
8399
|
+
const unloadable = [];
|
|
8400
|
+
for (const file of files) {
|
|
8401
|
+
const assertions = file?.assertionResults ?? [];
|
|
8402
|
+
if (file?.status === "failed" && assertions.length === 0) {
|
|
8403
|
+
unloadable.push(typeof file.name === "string" ? file.name : "an unnamed file");
|
|
8404
|
+
}
|
|
8405
|
+
for (const assertion of assertions) {
|
|
8406
|
+
const name = assertion?.fullName;
|
|
8407
|
+
if (typeof name !== "string" || name === "") continue;
|
|
8408
|
+
if (assertion.status === "passed") passing.push(name);
|
|
8409
|
+
else if (assertion.status === "failed") failing.push(name);
|
|
8410
|
+
}
|
|
8411
|
+
}
|
|
8412
|
+
const claimed = report?.numFailedTests;
|
|
8413
|
+
return {
|
|
8414
|
+
files: files.length,
|
|
8415
|
+
passing: passing.sort(),
|
|
8416
|
+
failing: failing.sort(),
|
|
8417
|
+
unloadable: unloadable.sort(),
|
|
8418
|
+
claimedFailing: typeof claimed === "number" ? claimed : failing.length
|
|
8419
|
+
};
|
|
8420
|
+
}
|
|
8421
|
+
function difference(left, right) {
|
|
8422
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
8423
|
+
for (const name of right) remaining.set(name, (remaining.get(name) ?? 0) + 1);
|
|
8424
|
+
const missing = [];
|
|
8425
|
+
for (const name of left) {
|
|
8426
|
+
const left_ = remaining.get(name) ?? 0;
|
|
8427
|
+
if (left_ === 0) {
|
|
8428
|
+
missing.push(name);
|
|
8429
|
+
continue;
|
|
8430
|
+
}
|
|
8431
|
+
remaining.set(name, left_ - 1);
|
|
8432
|
+
}
|
|
8433
|
+
return missing.sort();
|
|
8434
|
+
}
|
|
8435
|
+
var withoutRenderingQuotes = (name) => name.replaceAll(String.raw`\'`, "'").replaceAll(String.raw`\"`, '"').replaceAll(/['"`]\s*\+\s*['"`]/g, "").replaceAll(/(^|[\s:,[{(])(''|"")($|[\s,)\]}])/g, "$1<empty-string>$3").replaceAll(/['"`]/g, "");
|
|
8436
|
+
function withoutRenderingNoise(name) {
|
|
8437
|
+
const spelled = withSortedKeys(
|
|
8438
|
+
withoutUndefinedEntries(
|
|
8439
|
+
withoutErrorRendering(
|
|
8440
|
+
withoutNaN(
|
|
8441
|
+
withoutTimestamps(withoutRenderingQuotes(withoutClassBodies(withoutStackFrames(name))))
|
|
8442
|
+
)
|
|
8443
|
+
)
|
|
8444
|
+
)
|
|
8445
|
+
);
|
|
8446
|
+
return withoutArrayBrackets(
|
|
8447
|
+
spelled.replaceAll(/\[\s+/g, "[").replaceAll(/\s+\]/g, "]").replaceAll(/\{\s+/g, "{").replaceAll(/\s+\}/g, "}").replaceAll(/(?<=[[{,\s])\+0\b/g, "0").replaceAll(/:\s+/g, ":").replaceAll(/,\s+/g, ",")
|
|
8448
|
+
);
|
|
8449
|
+
}
|
|
8450
|
+
function withoutArrayBrackets(name) {
|
|
8451
|
+
return name.replaceAll(
|
|
8452
|
+
/\[([^[\]]*)\]/g,
|
|
8453
|
+
(match, inner) => (
|
|
8454
|
+
// `[Object]` and `[Array]` are the formatter saying it STOPPED, not an array literal.
|
|
8455
|
+
// Stripping their brackets would erase the marker `collapsedOf` pairs on.
|
|
8456
|
+
/^(?:Object|Array)$/.test(inner) ? match : inner.replaceAll(/,\s+/g, ",")
|
|
8457
|
+
)
|
|
8458
|
+
);
|
|
8459
|
+
}
|
|
8460
|
+
var withoutClassBodies = (name) => name.replaceAll(
|
|
8461
|
+
/class\s+([A-Za-z_$][\w$]*)\s*\{\s*\}/g,
|
|
8462
|
+
(_match, className) => `class ${className}`
|
|
8463
|
+
).replaceAll(
|
|
8464
|
+
/class\s+([A-Za-z_$][\w$]*)[\s\S]*?\n\}/g,
|
|
8465
|
+
(_match, className) => `class ${className}`
|
|
8466
|
+
);
|
|
8467
|
+
var withoutTimestamps = (name) => name.replaceAll(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z/g, "<instant>");
|
|
8468
|
+
var withoutNaN = (name) => name.replaceAll(/\bNaN\b/g, "null");
|
|
8469
|
+
function entriesOf(inner) {
|
|
8470
|
+
const parts = [];
|
|
8471
|
+
let depth = 0;
|
|
8472
|
+
let start = 0;
|
|
8473
|
+
for (let index = 0; index < inner.length; index += 1) {
|
|
8474
|
+
const character = inner[index];
|
|
8475
|
+
if (character === "[" || character === "(") depth += 1;
|
|
8476
|
+
else if (character === "]" || character === ")") depth -= 1;
|
|
8477
|
+
else if (character === "," && depth === 0) {
|
|
8478
|
+
parts.push(inner.slice(start, index));
|
|
8479
|
+
start = index + 1;
|
|
8480
|
+
}
|
|
8481
|
+
if (depth < 0) return void 0;
|
|
8482
|
+
}
|
|
8483
|
+
if (depth !== 0) return void 0;
|
|
8484
|
+
parts.push(inner.slice(start));
|
|
8485
|
+
return parts.map((part) => part.trim()).filter(Boolean);
|
|
8486
|
+
}
|
|
8487
|
+
var sortedGroup = (match, inner) => {
|
|
8488
|
+
if (!inner.includes(":")) return match;
|
|
8489
|
+
const parts = entriesOf(inner);
|
|
8490
|
+
if (parts === void 0) return match;
|
|
8491
|
+
if (parts.length < 2 || !parts.every((part) => part.includes(":"))) return match;
|
|
8492
|
+
return `{${[...parts].sort().join(",")}}`;
|
|
8493
|
+
};
|
|
8494
|
+
var HELD = "\u241F";
|
|
8495
|
+
var withSortedKeys = (name) => {
|
|
8496
|
+
const held = [];
|
|
8497
|
+
let current = name;
|
|
8498
|
+
for (let depth = 0; depth < 24; depth += 1) {
|
|
8499
|
+
const next = current.replaceAll(/\{([^{}]*)\}/g, (match, inner) => {
|
|
8500
|
+
held.push(sortedGroup(match, inner));
|
|
8501
|
+
return `${HELD}${held.length - 1}${HELD}`;
|
|
8502
|
+
});
|
|
8503
|
+
if (next === current) break;
|
|
8504
|
+
current = next;
|
|
8505
|
+
}
|
|
8506
|
+
for (let depth = 0; depth < 24 && current.includes(HELD); depth += 1) {
|
|
8507
|
+
current = current.replaceAll(
|
|
8508
|
+
new RegExp(`${HELD}(\\d+)${HELD}`, "g"),
|
|
8509
|
+
(_match, index) => held[Number(index)]
|
|
8510
|
+
);
|
|
8511
|
+
}
|
|
8512
|
+
return current;
|
|
8513
|
+
};
|
|
8514
|
+
var withoutStackFrames = (name) => {
|
|
8515
|
+
const frame = name.search(/\n\s+at\s/);
|
|
8516
|
+
return frame === -1 ? name : name.slice(0, frame);
|
|
8517
|
+
};
|
|
8518
|
+
var withoutErrorRendering = (name) => name.replaceAll(/\[[A-Za-z]*(?:Error|Exception):[^\][]*\]/g, "{}");
|
|
8519
|
+
var withoutUndefinedEntries = (name) => name.replaceAll(/([{,])\s*[A-Za-z_$][\w$]*\s*:\s*undefined\s*(?=[,}])/g, "$1").replaceAll(/\{\s*,/g, "{").replaceAll(/,\s*\}/g, "}").replaceAll(/\{\s*\}/g, "{}");
|
|
8520
|
+
function collapsedOf(candidate, name) {
|
|
8521
|
+
if (!/\[(?:Object|Array)\]/.test(candidate)) return false;
|
|
8522
|
+
const pattern = candidate.split(/\[(?:Object|Array)\]/).map((part) => part.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
8523
|
+
return new RegExp(`^${pattern}$`, "s").test(name);
|
|
8524
|
+
}
|
|
8525
|
+
function truncationOf(candidate, name) {
|
|
8526
|
+
const cut = candidate.indexOf("\u2026");
|
|
8527
|
+
if (cut === -1) return false;
|
|
8528
|
+
const head = candidate.slice(0, cut);
|
|
8529
|
+
const tail = candidate.slice(cut + 1);
|
|
8530
|
+
return name.startsWith(head) && name.endsWith(tail);
|
|
8531
|
+
}
|
|
8532
|
+
function pairRenamed(missing, added) {
|
|
8533
|
+
const remaining = [...added];
|
|
8534
|
+
const renamed = [];
|
|
8535
|
+
const unmatched = [];
|
|
8536
|
+
for (const name of missing) {
|
|
8537
|
+
const normalised = withoutRenderingNoise(name);
|
|
8538
|
+
const at = remaining.findIndex((candidate) => withoutRenderingNoise(candidate) === normalised);
|
|
8539
|
+
if (at === -1) {
|
|
8540
|
+
unmatched.push(name);
|
|
8541
|
+
continue;
|
|
8542
|
+
}
|
|
8543
|
+
renamed.push({ before: name, after: remaining[at] });
|
|
8544
|
+
remaining.splice(at, 1);
|
|
8545
|
+
}
|
|
8546
|
+
const lost = [];
|
|
8547
|
+
for (const name of unmatched) {
|
|
8548
|
+
const normalised = withoutRenderingNoise(name);
|
|
8549
|
+
const at = remaining.findIndex(
|
|
8550
|
+
(candidate) => truncationOf(withoutRenderingNoise(candidate), normalised) || collapsedOf(normalised, withoutRenderingNoise(candidate))
|
|
8551
|
+
);
|
|
8552
|
+
if (at === -1) {
|
|
8553
|
+
lost.push(name);
|
|
8554
|
+
continue;
|
|
8555
|
+
}
|
|
8556
|
+
renamed.push({ before: name, after: remaining[at] });
|
|
8557
|
+
remaining.splice(at, 1);
|
|
8558
|
+
}
|
|
8559
|
+
return { renamed, missing: lost, added: remaining };
|
|
8560
|
+
}
|
|
8561
|
+
function compareSnapshots(before, after) {
|
|
8562
|
+
const beforeAll = [...before.passing, ...before.failing];
|
|
8563
|
+
const afterAll = [...after.passing, ...after.failing];
|
|
8564
|
+
const paired = pairRenamed(difference(beforeAll, afterAll), difference(afterAll, beforeAll));
|
|
8565
|
+
const renamedBefore = new Set(paired.renamed.map((pair) => pair.before));
|
|
8566
|
+
const regressed = difference(before.passing, after.passing).filter(
|
|
8567
|
+
(name) => !paired.missing.includes(name) && !renamedBefore.has(name)
|
|
8568
|
+
);
|
|
8569
|
+
const failedBefore = new Set(before.failing);
|
|
8570
|
+
const recovered = after.passing.filter((name) => failedBefore.has(name));
|
|
8571
|
+
return {
|
|
8572
|
+
identical: paired.missing.length === 0 && paired.added.length === 0 && regressed.length === 0,
|
|
8573
|
+
missing: paired.missing,
|
|
8574
|
+
added: paired.added,
|
|
8575
|
+
regressed,
|
|
8576
|
+
renamed: paired.renamed,
|
|
8577
|
+
recovered,
|
|
8578
|
+
alreadyFailing: before.failing.length
|
|
8579
|
+
};
|
|
8580
|
+
}
|
|
8581
|
+
function unusableAsBaseline(snapshot) {
|
|
8582
|
+
if (snapshot.unloadable.length > 0) {
|
|
8583
|
+
return `${snapshot.unloadable.length} file(s) in the reference run did not load, so they reported no test at all and the run is short by however many they hold: ${snapshot.unloadable.slice(0, 3).join(", ")}. A reference that quietly lost tests cannot prove anything about the run after it`;
|
|
8584
|
+
}
|
|
8585
|
+
if (snapshot.claimedFailing > snapshot.failing.length) {
|
|
8586
|
+
return `the reference run says ${snapshot.claimedFailing} test(s) failed and names only ${snapshot.failing.length} of them, so the comparison has nothing to pair the rest against and would report them as invented by the migration`;
|
|
8587
|
+
}
|
|
8588
|
+
if (snapshot.passing.length === 0) {
|
|
8589
|
+
return `the reference run reports no passing test at all, so a green run after would prove nothing: there is no claim to preserve`;
|
|
8590
|
+
}
|
|
8591
|
+
return void 0;
|
|
8592
|
+
}
|
|
8593
|
+
function describeComparison(comparison) {
|
|
8594
|
+
const renamedNote = comparison.renamed.length > 0 ? ` ${comparison.renamed.length} test(s) kept their identity and changed name, because the runners render an each title's interpolated value differently (e.g. "${comparison.renamed[0]?.before.slice(-40)}" -> "${comparison.renamed[0]?.after.slice(-40)}").` : "";
|
|
8595
|
+
const alreadyRedNote = comparison.alreadyFailing > 0 ? ` ${comparison.alreadyFailing} test(s) were already failing before the migration and are outside this comparison, which covers only what passed.` : "";
|
|
8596
|
+
const recoveredNote = comparison.recovered.length > 0 ? ` ${comparison.recovered.length} test(s) were failing before and pass now.` : "";
|
|
8597
|
+
if (comparison.identical) {
|
|
8598
|
+
return `every test that ran before runs and passes now.${recoveredNote}${renamedNote}${alreadyRedNote}`;
|
|
8599
|
+
}
|
|
8600
|
+
const parts = [];
|
|
8601
|
+
if (comparison.recovered.length > 0) {
|
|
8602
|
+
parts.push(`${comparison.recovered.length} test(s) were failing before and pass now`);
|
|
8603
|
+
}
|
|
8604
|
+
if (comparison.missing.length > 0) {
|
|
8605
|
+
parts.push(
|
|
8606
|
+
`${comparison.missing.length} test(s) no longer exist: ${comparison.missing.slice(0, 3).join(", ")}`
|
|
8607
|
+
);
|
|
8608
|
+
}
|
|
8609
|
+
if (comparison.added.length > 0) {
|
|
8610
|
+
parts.push(
|
|
8611
|
+
`${comparison.added.length} test(s) appeared, which a migration should not do: ${comparison.added.slice(0, 3).join(", ")}`
|
|
8612
|
+
);
|
|
8613
|
+
}
|
|
8614
|
+
if (comparison.regressed.length > 0) {
|
|
8615
|
+
parts.push(
|
|
8616
|
+
`${comparison.regressed.length} test(s) passed before and do not now: ${comparison.regressed.slice(0, 3).join(", ")}`
|
|
8617
|
+
);
|
|
8618
|
+
}
|
|
8619
|
+
return `${parts.join("; ")}${renamedNote}${alreadyRedNote}`;
|
|
8620
|
+
}
|
|
8621
|
+
|
|
8622
|
+
// src/roles/test/reference.ts
|
|
8623
|
+
var REFERENCE_FILE = ".sentinel-test-reference.json";
|
|
8624
|
+
function runJest(cwd, config) {
|
|
8625
|
+
const bin = resolveBin(cwd, "jest");
|
|
8626
|
+
if (bin === void 0) return void 0;
|
|
8627
|
+
const root = findWorkspaceRoot(cwd) ?? cwd;
|
|
8628
|
+
const out = join36(mkdtempSync(join36(tmpdir(), "sentinel-reference-")), "jest.json");
|
|
8629
|
+
try {
|
|
8630
|
+
execFileSync(bin, ["-c", join36(cwd, config), "--json", `--outputFile=${out}`, "--silent"], {
|
|
8631
|
+
cwd: root,
|
|
8632
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
8633
|
+
maxBuffer: 1 << 28
|
|
8634
|
+
});
|
|
8635
|
+
} catch {
|
|
8636
|
+
}
|
|
8637
|
+
if (!existsSync30(out)) return void 0;
|
|
8638
|
+
try {
|
|
8639
|
+
return JSON.parse(readFileSync29(out, "utf8"));
|
|
8640
|
+
} catch {
|
|
8641
|
+
return void 0;
|
|
8642
|
+
}
|
|
8643
|
+
}
|
|
8644
|
+
function captureReference(cwd, config) {
|
|
8645
|
+
const report = runJest(cwd, config);
|
|
8646
|
+
if (report === void 0) {
|
|
8647
|
+
return {
|
|
8648
|
+
captured: false,
|
|
8649
|
+
message: `could not run jest to record a reference, so this migration cannot be PROVED. It is still applied: a module whose jest run is broken can move, it just cannot be compared.`
|
|
8650
|
+
};
|
|
8651
|
+
}
|
|
8652
|
+
const snapshot = readSnapshot(report);
|
|
8653
|
+
const unusable = unusableAsBaseline(snapshot);
|
|
8654
|
+
if (unusable !== void 0) {
|
|
8655
|
+
return {
|
|
8656
|
+
captured: false,
|
|
8657
|
+
message: `no reference recorded: ${unusable} The migration is applied anyway, unproved.`
|
|
8658
|
+
};
|
|
8659
|
+
}
|
|
8660
|
+
const stored = {
|
|
8661
|
+
runner: "jest",
|
|
8662
|
+
config,
|
|
8663
|
+
takenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8664
|
+
snapshot
|
|
8665
|
+
};
|
|
8666
|
+
writeFileSync3(join36(cwd, REFERENCE_FILE), `${JSON.stringify(stored, void 0, 2)}
|
|
8667
|
+
`);
|
|
8668
|
+
return {
|
|
8669
|
+
captured: true,
|
|
8670
|
+
message: `recorded ${snapshot.passing.length} passing test name(s) from ${config} as the reference. The next \`sentinel --run --test\` compares against it.`
|
|
8671
|
+
};
|
|
8672
|
+
}
|
|
8673
|
+
function hasReference(cwd) {
|
|
8674
|
+
return existsSync30(join36(cwd, REFERENCE_FILE));
|
|
8675
|
+
}
|
|
8676
|
+
function compareAgainstReference(cwd, report) {
|
|
8677
|
+
const path = join36(cwd, REFERENCE_FILE);
|
|
8678
|
+
let stored;
|
|
8679
|
+
try {
|
|
8680
|
+
stored = JSON.parse(readFileSync29(path, "utf8"));
|
|
8681
|
+
} catch {
|
|
8682
|
+
rmSync3(path, { force: true });
|
|
8683
|
+
return { ok: true, message: `the recorded reference could not be read, so nothing was proved.` };
|
|
8684
|
+
}
|
|
8685
|
+
const comparison = compareSnapshots(stored.snapshot, readSnapshot(report));
|
|
8686
|
+
rmSync3(path, { force: true });
|
|
8687
|
+
return {
|
|
8688
|
+
ok: comparison.identical,
|
|
8689
|
+
message: `${describeComparison(comparison)} (reference: ${stored.config}, ${stored.takenAt})`
|
|
8690
|
+
};
|
|
8691
|
+
}
|
|
8692
|
+
|
|
7822
8693
|
// src/roles/test/adapters/vitest/vitest.adapter.ts
|
|
7823
8694
|
function resolveVitest(cwd) {
|
|
7824
8695
|
const fromSentinel = binFromOwnInstall("vitest", "vitest");
|
|
@@ -7832,6 +8703,22 @@ var VitestAdapter = class extends BaseAdapter {
|
|
|
7832
8703
|
appliesTo(_preset) {
|
|
7833
8704
|
return true;
|
|
7834
8705
|
}
|
|
8706
|
+
/**
|
|
8707
|
+
* Record what this module's tests do TODAY, at the last moment it is still true.
|
|
8708
|
+
*
|
|
8709
|
+
* The plan about to be applied deletes the jest config, and with it any way of asking again. A
|
|
8710
|
+
* migration that cannot be compared to what came before is a migration nobody can check, which
|
|
8711
|
+
* is the whole reason this role has a baseline gate at all.
|
|
8712
|
+
*/
|
|
8713
|
+
async beforeInit(ctx) {
|
|
8714
|
+
const adoption = readTestAdoption(ctx.cwd);
|
|
8715
|
+
if (adoption.state !== "jest") return Promise.resolve();
|
|
8716
|
+
const [config] = adoption.jestConfigs;
|
|
8717
|
+
if (config === void 0) return Promise.resolve();
|
|
8718
|
+
this.say(`recording the jest reference before migrating...`);
|
|
8719
|
+
this.say(captureReference(ctx.cwd, config).message);
|
|
8720
|
+
return Promise.resolve();
|
|
8721
|
+
}
|
|
7835
8722
|
plan(context) {
|
|
7836
8723
|
return plan3(context);
|
|
7837
8724
|
}
|
|
@@ -7878,13 +8765,31 @@ var VitestAdapter = class extends BaseAdapter {
|
|
|
7878
8765
|
}
|
|
7879
8766
|
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, { valueFlags: [] });
|
|
7880
8767
|
const watching = options.includes("--watch") || options.includes("-w");
|
|
7881
|
-
const
|
|
8768
|
+
const proving = !watching && hasReference(ctx.cwd);
|
|
8769
|
+
const reportFile = proving ? join37(mkdtempSync2(join37(tmpdir2(), "sentinel-run-")), "vitest.json") : void 0;
|
|
8770
|
+
const reporters = reportFile === void 0 ? [] : ["--reporter=default", "--reporter=json", `--outputFile=${reportFile}`];
|
|
8771
|
+
const argv = watching ? [...options, ...paths] : ["run", ...reporters, ...options, ...paths];
|
|
7882
8772
|
const result = spawnSync5(bin, argv, { cwd: ctx.cwd, stdio: "inherit" });
|
|
7883
8773
|
if (result.error) {
|
|
7884
8774
|
this.say(`could not run vitest (${result.error.message})`);
|
|
7885
8775
|
return Promise.resolve({ ok: false, code: 1 });
|
|
7886
8776
|
}
|
|
7887
8777
|
const code = result.status ?? 1;
|
|
8778
|
+
if (reportFile !== void 0) {
|
|
8779
|
+
let report;
|
|
8780
|
+
try {
|
|
8781
|
+
report = JSON.parse(readFileSync30(reportFile, "utf8"));
|
|
8782
|
+
} catch {
|
|
8783
|
+
report = void 0;
|
|
8784
|
+
}
|
|
8785
|
+
if (report === void 0) {
|
|
8786
|
+
this.say(`vitest wrote no report, so this run proved nothing against the reference.`);
|
|
8787
|
+
} else {
|
|
8788
|
+
const outcome = compareAgainstReference(ctx.cwd, report);
|
|
8789
|
+
this.say(outcome.message);
|
|
8790
|
+
if (!outcome.ok) return Promise.resolve({ ok: false, code: code === 0 ? 1 : code });
|
|
8791
|
+
}
|
|
8792
|
+
}
|
|
7888
8793
|
return Promise.resolve({ ok: code === 0, code });
|
|
7889
8794
|
}
|
|
7890
8795
|
say(message) {
|
|
@@ -7901,9 +8806,9 @@ function registerTest() {
|
|
|
7901
8806
|
|
|
7902
8807
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
7903
8808
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
7904
|
-
import { existsSync as
|
|
7905
|
-
import { createRequire as
|
|
7906
|
-
import { join as
|
|
8809
|
+
import { existsSync as existsSync31, readFileSync as readFileSync32 } from "fs";
|
|
8810
|
+
import { createRequire as createRequire6 } from "module";
|
|
8811
|
+
import { join as join39 } from "path";
|
|
7907
8812
|
|
|
7908
8813
|
// src/roles/typescript/presets/nest.json
|
|
7909
8814
|
var nest_default2 = {
|
|
@@ -8084,8 +8989,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
8084
8989
|
}
|
|
8085
8990
|
|
|
8086
8991
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
8087
|
-
import { readFileSync as
|
|
8088
|
-
import { join as
|
|
8992
|
+
import { readFileSync as readFileSync31 } from "fs";
|
|
8993
|
+
import { join as join38 } from "path";
|
|
8089
8994
|
|
|
8090
8995
|
// src/roles/typescript/typecheck-script.ts
|
|
8091
8996
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -8178,7 +9083,7 @@ function planAdoption(context) {
|
|
|
8178
9083
|
};
|
|
8179
9084
|
}
|
|
8180
9085
|
const existing = parseJsonc(
|
|
8181
|
-
|
|
9086
|
+
readFileSync31(join38(context.cwd, target.path), "utf8"),
|
|
8182
9087
|
target.path
|
|
8183
9088
|
);
|
|
8184
9089
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -8316,7 +9221,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
8316
9221
|
let chain;
|
|
8317
9222
|
try {
|
|
8318
9223
|
const parsed = parseJsonc(
|
|
8319
|
-
|
|
9224
|
+
readFileSync32(join39(cwd, target.path), "utf8"),
|
|
8320
9225
|
target.path
|
|
8321
9226
|
);
|
|
8322
9227
|
chain = parsed.extends;
|
|
@@ -8329,7 +9234,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
8329
9234
|
);
|
|
8330
9235
|
if (preset === void 0) return void 0;
|
|
8331
9236
|
try {
|
|
8332
|
-
|
|
9237
|
+
createRequire6(join39(cwd, "noop.js")).resolve(preset);
|
|
8333
9238
|
return void 0;
|
|
8334
9239
|
} catch {
|
|
8335
9240
|
return preset;
|
|
@@ -8425,7 +9330,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
8425
9330
|
referencedProjects(cwd, config) {
|
|
8426
9331
|
try {
|
|
8427
9332
|
const parsed = parseJsonc(
|
|
8428
|
-
|
|
9333
|
+
readFileSync32(join39(cwd, config), "utf8"),
|
|
8429
9334
|
config
|
|
8430
9335
|
);
|
|
8431
9336
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
|
|
@@ -8441,7 +9346,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
8441
9346
|
* check.
|
|
8442
9347
|
*/
|
|
8443
9348
|
typecheckTarget(cwd) {
|
|
8444
|
-
if (
|
|
9349
|
+
if (existsSync31(join39(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
8445
9350
|
const target = resolveTsconfigTarget(cwd);
|
|
8446
9351
|
return target.reason === "none" ? null : target.path;
|
|
8447
9352
|
}
|
|
@@ -8590,7 +9495,7 @@ function detectFramework(packageJson) {
|
|
|
8590
9495
|
}
|
|
8591
9496
|
|
|
8592
9497
|
// src/core/dispatch.ts
|
|
8593
|
-
import { basename as
|
|
9498
|
+
import { basename as basename3 } from "path";
|
|
8594
9499
|
|
|
8595
9500
|
// src/shared/text.ts
|
|
8596
9501
|
function ensureLines(current, lines) {
|
|
@@ -8646,7 +9551,7 @@ function replaceLines(current, replacements) {
|
|
|
8646
9551
|
}
|
|
8647
9552
|
|
|
8648
9553
|
// src/core/apply-plan.ts
|
|
8649
|
-
import { existsSync as
|
|
9554
|
+
import { existsSync as existsSync32, readFileSync as readFileSync33, renameSync, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
8650
9555
|
import { resolve as resolve5, sep } from "path";
|
|
8651
9556
|
import { applyEdits as applyEdits3, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
8652
9557
|
|
|
@@ -8665,7 +9570,7 @@ function resolveWithinRoot(cwd, relativePath) {
|
|
|
8665
9570
|
return absolutePath;
|
|
8666
9571
|
}
|
|
8667
9572
|
function readIfExists(absolutePath) {
|
|
8668
|
-
return
|
|
9573
|
+
return existsSync32(absolutePath) ? readFileSync33(absolutePath, "utf8") : void 0;
|
|
8669
9574
|
}
|
|
8670
9575
|
function* leaves(value, prefix = []) {
|
|
8671
9576
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -8767,14 +9672,14 @@ function preparePlan(cwd, plan4) {
|
|
|
8767
9672
|
}
|
|
8768
9673
|
function writeFileAtomic(absolutePath, contents) {
|
|
8769
9674
|
const tempPath = `${absolutePath}.sentinel-${process.pid}.tmp`;
|
|
8770
|
-
|
|
9675
|
+
writeFileSync4(tempPath, contents);
|
|
8771
9676
|
renameSync(tempPath, absolutePath);
|
|
8772
9677
|
}
|
|
8773
9678
|
function applyPlan(cwd, plan4) {
|
|
8774
9679
|
const changed = preparePlan(cwd, plan4).filter((file) => file.before !== file.after);
|
|
8775
9680
|
for (const file of changed) {
|
|
8776
9681
|
if (file.deleted) {
|
|
8777
|
-
|
|
9682
|
+
rmSync4(file.absolutePath, { force: true });
|
|
8778
9683
|
continue;
|
|
8779
9684
|
}
|
|
8780
9685
|
writeFileAtomic(file.absolutePath, file.after);
|
|
@@ -8783,8 +9688,8 @@ function applyPlan(cwd, plan4) {
|
|
|
8783
9688
|
}
|
|
8784
9689
|
|
|
8785
9690
|
// src/core/config/preset-evidence.ts
|
|
8786
|
-
import { existsSync as
|
|
8787
|
-
import { join as
|
|
9691
|
+
import { existsSync as existsSync33, readdirSync as readdirSync8, readFileSync as readFileSync34 } from "fs";
|
|
9692
|
+
import { join as join40 } from "path";
|
|
8788
9693
|
var PATH_SIGNALS = [
|
|
8789
9694
|
{
|
|
8790
9695
|
preset: "nest",
|
|
@@ -8812,18 +9717,18 @@ function sourceImports(cwd, pattern) {
|
|
|
8812
9717
|
const dir = stack.pop();
|
|
8813
9718
|
let entries;
|
|
8814
9719
|
try {
|
|
8815
|
-
entries =
|
|
9720
|
+
entries = readdirSync8(dir, { withFileTypes: true });
|
|
8816
9721
|
} catch {
|
|
8817
9722
|
continue;
|
|
8818
9723
|
}
|
|
8819
9724
|
for (const entry of entries) {
|
|
8820
9725
|
if (entry.isDirectory()) {
|
|
8821
|
-
if (!NOT_SCANNED.has(entry.name)) stack.push(
|
|
9726
|
+
if (!NOT_SCANNED.has(entry.name)) stack.push(join40(dir, entry.name));
|
|
8822
9727
|
continue;
|
|
8823
9728
|
}
|
|
8824
9729
|
if (!SOURCE_FILE2.test(entry.name)) continue;
|
|
8825
9730
|
try {
|
|
8826
|
-
if (pattern.test(
|
|
9731
|
+
if (pattern.test(readFileSync34(join40(dir, entry.name), "utf8"))) return true;
|
|
8827
9732
|
} catch {
|
|
8828
9733
|
continue;
|
|
8829
9734
|
}
|
|
@@ -8832,11 +9737,11 @@ function sourceImports(cwd, pattern) {
|
|
|
8832
9737
|
return false;
|
|
8833
9738
|
}
|
|
8834
9739
|
function dependencyNames(cwd) {
|
|
8835
|
-
const path =
|
|
8836
|
-
if (!
|
|
9740
|
+
const path = join40(cwd, "package.json");
|
|
9741
|
+
if (!existsSync33(path)) return [];
|
|
8837
9742
|
try {
|
|
8838
9743
|
const manifest = parseJsonc(
|
|
8839
|
-
|
|
9744
|
+
readFileSync34(path, "utf8"),
|
|
8840
9745
|
path
|
|
8841
9746
|
);
|
|
8842
9747
|
return [
|
|
@@ -8850,7 +9755,7 @@ function dependencyNames(cwd) {
|
|
|
8850
9755
|
function declaresJsx(cwd) {
|
|
8851
9756
|
let entries;
|
|
8852
9757
|
try {
|
|
8853
|
-
entries =
|
|
9758
|
+
entries = readdirSync8(cwd).filter(
|
|
8854
9759
|
(name) => name.startsWith("tsconfig") && name.endsWith(".json")
|
|
8855
9760
|
);
|
|
8856
9761
|
} catch {
|
|
@@ -8859,7 +9764,7 @@ function declaresJsx(cwd) {
|
|
|
8859
9764
|
for (const name of entries) {
|
|
8860
9765
|
try {
|
|
8861
9766
|
const config = parseJsonc(
|
|
8862
|
-
|
|
9767
|
+
readFileSync34(join40(cwd, name), "utf8"),
|
|
8863
9768
|
name
|
|
8864
9769
|
);
|
|
8865
9770
|
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
@@ -9004,6 +9909,15 @@ async function dispatch(opts) {
|
|
|
9004
9909
|
if (opts.dryRun) {
|
|
9005
9910
|
return previewPlan(opts, plan4);
|
|
9006
9911
|
}
|
|
9912
|
+
if (adapter.beforeInit) {
|
|
9913
|
+
await adapter.beforeInit({
|
|
9914
|
+
module: basename3(opts.cwd),
|
|
9915
|
+
cwd: opts.cwd,
|
|
9916
|
+
preset,
|
|
9917
|
+
ci: false,
|
|
9918
|
+
fix: false
|
|
9919
|
+
});
|
|
9920
|
+
}
|
|
9007
9921
|
for (const change of applyPlan(opts.cwd, plan4)) {
|
|
9008
9922
|
process.stderr.write(` ${change.deleted ? "removed" : "wrote"} ${change.path}
|
|
9009
9923
|
`);
|
|
@@ -9014,7 +9928,7 @@ async function dispatch(opts) {
|
|
|
9014
9928
|
process.stderr.write(` fixing what ${opts.target} can fix automatically...
|
|
9015
9929
|
`);
|
|
9016
9930
|
await adapter.afterInit({
|
|
9017
|
-
module:
|
|
9931
|
+
module: basename3(opts.cwd),
|
|
9018
9932
|
cwd: opts.cwd,
|
|
9019
9933
|
preset,
|
|
9020
9934
|
ci: false,
|
|
@@ -9047,4 +9961,4 @@ export {
|
|
|
9047
9961
|
detectFramework,
|
|
9048
9962
|
dispatch
|
|
9049
9963
|
};
|
|
9050
|
-
//# sourceMappingURL=chunk-
|
|
9964
|
+
//# sourceMappingURL=chunk-MUQWAZQN.js.map
|