@hublo/sentinel 1.5.1-alpha.0 → 1.5.1-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dist/bin/sentinel.js +194 -161
- package/dist/{chunk-CL7HOHTU.js → chunk-JHLNNIZC.js} +567 -187
- package/dist/{chunk-I2WDZNPR.js → chunk-P7NYSMLS.js} +689 -2
- package/dist/index.js +2 -2
- package/dist/{validate-S6PIOVAH.js → validate-5SD5CTTO.js} +1 -1
- package/docs/format-adoption.md +27 -1
- package/docs/lint-adoption.md +68 -2
- package/docs/using-sentinel.md +6 -2
- package/docs/validating-a-change.md +171 -0
- package/package.json +30 -23
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
LINT_CONFIG_FILE,
|
|
11
11
|
LINT_MEASURE_FILE,
|
|
12
12
|
LINT_SCRIPT_NAME,
|
|
13
|
+
OWNED_WHEN_SET_KEYS,
|
|
13
14
|
PERMITTED_LOCAL_KEYS,
|
|
14
15
|
PRETTIER_CONFIG_FILES,
|
|
15
16
|
PROVENANCE_KEY,
|
|
@@ -19,7 +20,9 @@ import {
|
|
|
19
20
|
binFromOwnInstall,
|
|
20
21
|
binSearchPath,
|
|
21
22
|
canRunTypeAware,
|
|
23
|
+
canonicalRuleName,
|
|
22
24
|
describeHolds,
|
|
25
|
+
describeRuleKey,
|
|
23
26
|
extendsWithBaseline,
|
|
24
27
|
extendsWithPreset,
|
|
25
28
|
formatPresetFor,
|
|
@@ -51,11 +54,12 @@ import {
|
|
|
51
54
|
resolveOxfmt,
|
|
52
55
|
resolveOxlint,
|
|
53
56
|
rewriteToolchainImports,
|
|
57
|
+
scriptPath,
|
|
54
58
|
selfCommand,
|
|
55
59
|
splitToolArgs,
|
|
56
60
|
walk,
|
|
57
61
|
writtenFileFormatter
|
|
58
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-P7NYSMLS.js";
|
|
59
63
|
import {
|
|
60
64
|
DEFAULT_MAX_DIAGNOSTICS,
|
|
61
65
|
WORKSPACE_ROOT_MARKER,
|
|
@@ -77,6 +81,9 @@ import {
|
|
|
77
81
|
isPlainObject,
|
|
78
82
|
overridableSettings
|
|
79
83
|
} from "./chunk-WBDBRGLV.js";
|
|
84
|
+
import {
|
|
85
|
+
globToRegExp
|
|
86
|
+
} from "./chunk-O7REVMOC.js";
|
|
80
87
|
import {
|
|
81
88
|
parseJsonc
|
|
82
89
|
} from "./chunk-QXFCZON7.js";
|
|
@@ -153,7 +160,7 @@ function resolve(target, preset, runner) {
|
|
|
153
160
|
// src/shared/color.ts
|
|
154
161
|
import { styleText } from "node:util";
|
|
155
162
|
function palette(stream) {
|
|
156
|
-
const paint = (
|
|
163
|
+
const paint = (format2, text) => styleText(format2, text, { stream });
|
|
157
164
|
return {
|
|
158
165
|
ok: (text) => paint(["green", "bold"], text),
|
|
159
166
|
fail: (text) => paint(["red", "bold"], text),
|
|
@@ -581,6 +588,19 @@ function composeToolScript(existing, options) {
|
|
|
581
588
|
function keepsOtherCommands(script, sentinelCommand) {
|
|
582
589
|
return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
|
|
583
590
|
}
|
|
591
|
+
function leadingCommands(script) {
|
|
592
|
+
if (script === void 0) return void 0;
|
|
593
|
+
const parts = script.split(SEGMENT_SEPARATOR);
|
|
594
|
+
const leading = [];
|
|
595
|
+
for (let index = 0; index < parts.length; index += 2) {
|
|
596
|
+
const segment = (parts[index] ?? "").trim();
|
|
597
|
+
if (invokes(segment, "sentinel") || segment.includes("bin/sentinel.js")) break;
|
|
598
|
+
if (index + 1 >= parts.length) return void 0;
|
|
599
|
+
if ((parts[index + 1] ?? "").trim() !== "&&") return void 0;
|
|
600
|
+
leading.push(segment);
|
|
601
|
+
}
|
|
602
|
+
return leading.length > 0 ? leading.join(" && ") : void 0;
|
|
603
|
+
}
|
|
584
604
|
|
|
585
605
|
// src/roles/build/dev-script.ts
|
|
586
606
|
var CHAIN = " -- ";
|
|
@@ -819,8 +839,10 @@ function planReactApp(context2, configFile) {
|
|
|
819
839
|
skipped: `${configFile} could not be pointed at sentinel, so nothing was written: ${rewrite.why}. Nothing here is broken; this needs a look before the role applies.`
|
|
820
840
|
};
|
|
821
841
|
}
|
|
842
|
+
const rewritten = [];
|
|
822
843
|
if (rewrite.kind === "rewritten") {
|
|
823
844
|
operations.push({ kind: "write", path: configFile, contents: rewrite.source });
|
|
845
|
+
rewritten.push(configFile);
|
|
824
846
|
}
|
|
825
847
|
for (const name of COMPANION_CONFIG_FILES) {
|
|
826
848
|
const source = readConfigSource(context2.cwd, name);
|
|
@@ -828,6 +850,7 @@ function planReactApp(context2, configFile) {
|
|
|
828
850
|
const companion = rewriteBuildImports(source, name);
|
|
829
851
|
if (companion.kind === "rewritten") {
|
|
830
852
|
operations.push({ kind: "write", path: name, contents: companion.source });
|
|
853
|
+
rewritten.push(name);
|
|
831
854
|
notes.push(`${name} now imports ${companion.moved.join(", ")} from sentinel too.`);
|
|
832
855
|
} else if (companion.kind === "blocked") {
|
|
833
856
|
notes.push(
|
|
@@ -861,10 +884,12 @@ function planReactApp(context2, configFile) {
|
|
|
861
884
|
`removed ${owned.map(([, name]) => name).join(", ")} from this module: sentinel owns them now, and two copies of Vite in one build give the plugins a different Vite than the one running them.`
|
|
862
885
|
);
|
|
863
886
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
887
|
+
if (rewritten.length > 0) {
|
|
888
|
+
const files = rewritten.join(", ");
|
|
889
|
+
notes.push(
|
|
890
|
+
readFormatAdoption(context2.cwd).adopted ? `run \`sentinel --run --format --fix\` on this module: adoption rewrote ${files}, and a rewritten import can be longer than your formatter wraps at.` : `this module is formatted by the workspace, not by sentinel, so format ${files} with whatever owns it before committing: a rewritten import can be longer than that formatter wraps at, and the check would fail on a file nobody typed.`
|
|
891
|
+
);
|
|
892
|
+
}
|
|
868
893
|
notes.push(
|
|
869
894
|
`this config is code, and it runs before the build does: any environment variable it reads has to be present wherever you build, including a fresh clone, another CI job or an image. Adoption does not change what it reads, but a failure there arrives early and names this role rather than the variable.`
|
|
870
895
|
);
|
|
@@ -1266,8 +1291,9 @@ function registerBuild() {
|
|
|
1266
1291
|
|
|
1267
1292
|
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
1268
1293
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1269
|
-
import { existsSync as existsSync8, readFileSync as
|
|
1270
|
-
import { join as
|
|
1294
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
|
|
1295
|
+
import { join as join11 } from "node:path";
|
|
1296
|
+
import { isDeepStrictEqual } from "node:util";
|
|
1271
1297
|
|
|
1272
1298
|
// src/core/config/has-source.ts
|
|
1273
1299
|
import { readdirSync } from "node:fs";
|
|
@@ -1361,6 +1387,160 @@ function scopeFlags(paths, options = []) {
|
|
|
1361
1387
|
return [TOLERATE_EMPTY_FLAG];
|
|
1362
1388
|
}
|
|
1363
1389
|
|
|
1390
|
+
// src/roles/format/deliberate-import-order.ts
|
|
1391
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1392
|
+
import { join as join8 } from "node:path";
|
|
1393
|
+
import { parseSync } from "oxc-parser";
|
|
1394
|
+
import { format } from "oxfmt";
|
|
1395
|
+
|
|
1396
|
+
// src/roles/format/presets/deliberate-import-order.json
|
|
1397
|
+
var deliberate_import_order_default = {
|
|
1398
|
+
rules: [
|
|
1399
|
+
"import/order",
|
|
1400
|
+
"import/first",
|
|
1401
|
+
"import-x/order",
|
|
1402
|
+
"import-x/first",
|
|
1403
|
+
"simple-import-sort/imports",
|
|
1404
|
+
"simple-import-sort/exports"
|
|
1405
|
+
],
|
|
1406
|
+
modules: [
|
|
1407
|
+
"dd-trace",
|
|
1408
|
+
"dd-trace/**",
|
|
1409
|
+
"reflect-metadata",
|
|
1410
|
+
"source-map-support/register",
|
|
1411
|
+
"@opentelemetry/sdk-**",
|
|
1412
|
+
"@opentelemetry/auto-instrumentations-**",
|
|
1413
|
+
"core-js",
|
|
1414
|
+
"core-js/**",
|
|
1415
|
+
"regenerator-runtime",
|
|
1416
|
+
"regenerator-runtime/**"
|
|
1417
|
+
]
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
// src/roles/format/deliberate-import-order.ts
|
|
1421
|
+
var PIN_COMMENT = "// oxfmt-ignore";
|
|
1422
|
+
var CODE_EXTENSIONS = [".js", ".mjs", ".cjs", ".jsx", ".ts", ".mts", ".cts", ".tsx"];
|
|
1423
|
+
var ORDER_RULES = deliberate_import_order_default.rules;
|
|
1424
|
+
var LOAD_ORDER_MODULES = deliberate_import_order_default.modules.map((glob) => globToRegExp(glob));
|
|
1425
|
+
var MODULE_NAMES = [
|
|
1426
|
+
...new Set(deliberate_import_order_default.modules.map((glob) => glob.split("*")[0] ?? glob))
|
|
1427
|
+
];
|
|
1428
|
+
function disabledRules(comment, directive) {
|
|
1429
|
+
const match = new RegExp(String.raw`^\s*${directive}(?:\s+(.*?))?\s*(?:--.*)?$`).exec(comment);
|
|
1430
|
+
if (match === null) return void 0;
|
|
1431
|
+
return (match[1] ?? "").split(/[\s,]+/).filter(Boolean);
|
|
1432
|
+
}
|
|
1433
|
+
function namesOrderRule(rules) {
|
|
1434
|
+
return rules !== void 0 && rules.some((rule) => ORDER_RULES.includes(rule));
|
|
1435
|
+
}
|
|
1436
|
+
function lineOf(starts, offset) {
|
|
1437
|
+
let low = 0;
|
|
1438
|
+
let high = starts.length - 1;
|
|
1439
|
+
while (low < high) {
|
|
1440
|
+
const middle = Math.ceil((low + high) / 2);
|
|
1441
|
+
if ((starts[middle] ?? 0) <= offset) low = middle;
|
|
1442
|
+
else high = middle - 1;
|
|
1443
|
+
}
|
|
1444
|
+
return low;
|
|
1445
|
+
}
|
|
1446
|
+
function readImports(fileName, text) {
|
|
1447
|
+
const { program, comments, errors } = parseSync(fileName, text, { sourceType: "module" });
|
|
1448
|
+
if (errors.length > 0) return [];
|
|
1449
|
+
const starts = [0];
|
|
1450
|
+
for (let index = 0; index < text.length; index++) if (text[index] === "\n") starts.push(index + 1);
|
|
1451
|
+
const nextLine = /* @__PURE__ */ new Set();
|
|
1452
|
+
const sameLine = /* @__PURE__ */ new Set();
|
|
1453
|
+
const regions = [];
|
|
1454
|
+
let regionStart;
|
|
1455
|
+
for (const comment of comments) {
|
|
1456
|
+
const line = lineOf(starts, comment.start);
|
|
1457
|
+
if (comment.type === "Line") {
|
|
1458
|
+
if (namesOrderRule(disabledRules(comment.value, "eslint-disable-next-line"))) {
|
|
1459
|
+
nextLine.add(lineOf(starts, comment.end) + 1);
|
|
1460
|
+
}
|
|
1461
|
+
if (namesOrderRule(disabledRules(comment.value, "eslint-disable-line"))) sameLine.add(line);
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
if (regionStart === void 0 && namesOrderRule(disabledRules(comment.value, "eslint-disable"))) {
|
|
1465
|
+
regionStart = comment.end;
|
|
1466
|
+
continue;
|
|
1467
|
+
}
|
|
1468
|
+
const enabled = disabledRules(comment.value, "eslint-enable");
|
|
1469
|
+
if (regionStart !== void 0 && enabled !== void 0) {
|
|
1470
|
+
if (enabled.length === 0 || namesOrderRule(enabled)) {
|
|
1471
|
+
regions.push([regionStart, comment.start]);
|
|
1472
|
+
regionStart = void 0;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if (regionStart !== void 0) regions.push([regionStart, text.length]);
|
|
1477
|
+
const imports = [];
|
|
1478
|
+
for (const node of program.body) {
|
|
1479
|
+
if (node.type !== "ImportDeclaration") continue;
|
|
1480
|
+
const line = lineOf(starts, node.start);
|
|
1481
|
+
const source = node.source.value;
|
|
1482
|
+
const typeOnly = node.importKind === "type";
|
|
1483
|
+
const signalled = !typeOnly && (LOAD_ORDER_MODULES.some((pattern) => pattern.test(source)) || nextLine.has(line) || sameLine.has(line) || regions.some(([from, to]) => node.start >= from && node.start < to));
|
|
1484
|
+
imports.push({ line, source, signalled });
|
|
1485
|
+
}
|
|
1486
|
+
return imports;
|
|
1487
|
+
}
|
|
1488
|
+
function wouldMove(before, after) {
|
|
1489
|
+
const order = (list) => list.map((entry) => entry.source);
|
|
1490
|
+
const was = order(before);
|
|
1491
|
+
const now = order(after);
|
|
1492
|
+
return before.some((entry, at) => {
|
|
1493
|
+
if (!entry.signalled) return false;
|
|
1494
|
+
const earlier = new Set(was.slice(0, at));
|
|
1495
|
+
const index = now.indexOf(entry.source);
|
|
1496
|
+
const nowEarlier = new Set(now.slice(0, index));
|
|
1497
|
+
return earlier.size !== nowEarlier.size || [...earlier].some((name) => !nowEarlier.has(name));
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
function pin(text, imports) {
|
|
1501
|
+
const lines = text.split("\n");
|
|
1502
|
+
const insertAt = [];
|
|
1503
|
+
for (const { line, signalled } of imports) {
|
|
1504
|
+
if (!signalled) continue;
|
|
1505
|
+
let top = line;
|
|
1506
|
+
while (top > 0 && /^\s*\/\//.test(lines[top - 1] ?? "")) top -= 1;
|
|
1507
|
+
if (lines.slice(top, line).some((above) => above.trim() === PIN_COMMENT)) continue;
|
|
1508
|
+
insertAt.push(top);
|
|
1509
|
+
}
|
|
1510
|
+
for (const at of [...new Set(insertAt)].sort((a, b) => b - a)) {
|
|
1511
|
+
const indent = /^\s*/.exec(lines[at] ?? "")?.[0] ?? "";
|
|
1512
|
+
lines.splice(at, 0, `${indent}${PIN_COMMENT}`);
|
|
1513
|
+
}
|
|
1514
|
+
return lines.join("\n");
|
|
1515
|
+
}
|
|
1516
|
+
function formatOptions(config) {
|
|
1517
|
+
const {
|
|
1518
|
+
$sentinel: _provenance,
|
|
1519
|
+
ignorePatterns: _ignored,
|
|
1520
|
+
overrides: _overrides,
|
|
1521
|
+
...options
|
|
1522
|
+
} = config;
|
|
1523
|
+
return options;
|
|
1524
|
+
}
|
|
1525
|
+
async function pinDeliberateImportOrder(cwd, options) {
|
|
1526
|
+
if (!options.sortImports) return [];
|
|
1527
|
+
const pinned = [];
|
|
1528
|
+
for (const file of listSourceFiles(cwd, CODE_EXTENSIONS).sort()) {
|
|
1529
|
+
const path2 = join8(cwd, file);
|
|
1530
|
+
const text = readFileSync6(path2, "utf8");
|
|
1531
|
+
if (!text.includes("eslint-disable") && !MODULE_NAMES.some((name) => text.includes(name)))
|
|
1532
|
+
continue;
|
|
1533
|
+
const before = readImports(file, text);
|
|
1534
|
+
if (!before.some((entry) => entry.signalled)) continue;
|
|
1535
|
+
const sorted = await format(file, text, options);
|
|
1536
|
+
if (sorted.errors.length > 0) continue;
|
|
1537
|
+
if (!wouldMove(before, readImports(file, sorted.code))) continue;
|
|
1538
|
+
writeFileSync2(path2, pin(text, before));
|
|
1539
|
+
pinned.push(file);
|
|
1540
|
+
}
|
|
1541
|
+
return pinned;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1364
1544
|
// src/roles/format/format-script.ts
|
|
1365
1545
|
var SENTINEL_FORMAT_COMMAND = "sentinel --run --format";
|
|
1366
1546
|
function isPrettierSegment(segment) {
|
|
@@ -1389,8 +1569,8 @@ function writesWhenRewritten(name, command) {
|
|
|
1389
1569
|
var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
|
|
1390
1570
|
|
|
1391
1571
|
// src/roles/format/inherited-ignores.ts
|
|
1392
|
-
import { existsSync as existsSync6, readFileSync as
|
|
1393
|
-
import { join as
|
|
1572
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
1573
|
+
import { join as join9 } from "node:path";
|
|
1394
1574
|
var ROOT_IGNORE_FILE = ".prettierignore";
|
|
1395
1575
|
function isPattern(line) {
|
|
1396
1576
|
const trimmed = line.trim();
|
|
@@ -1406,11 +1586,11 @@ function toModulePattern(pattern) {
|
|
|
1406
1586
|
}
|
|
1407
1587
|
function inheritedIgnorePatterns(workspaceRoot) {
|
|
1408
1588
|
if (!workspaceRoot) return [];
|
|
1409
|
-
const path2 =
|
|
1589
|
+
const path2 = join9(workspaceRoot, ROOT_IGNORE_FILE);
|
|
1410
1590
|
if (!existsSync6(path2)) return [];
|
|
1411
1591
|
let contents;
|
|
1412
1592
|
try {
|
|
1413
|
-
contents =
|
|
1593
|
+
contents = readFileSync7(path2, "utf8");
|
|
1414
1594
|
} catch {
|
|
1415
1595
|
return [];
|
|
1416
1596
|
}
|
|
@@ -1455,8 +1635,8 @@ function needsSvelteCompiler(output) {
|
|
|
1455
1635
|
}
|
|
1456
1636
|
|
|
1457
1637
|
// src/roles/format/prettier-config.ts
|
|
1458
|
-
import { existsSync as existsSync7, readFileSync as
|
|
1459
|
-
import { join as
|
|
1638
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
|
|
1639
|
+
import { join as join10 } from "node:path";
|
|
1460
1640
|
var UNSUPPORTED = /* @__PURE__ */ new Set(["plugins", "parser", "filepath", "rangeStart", "rangeEnd"]);
|
|
1461
1641
|
function toOxfmtOverrides(value) {
|
|
1462
1642
|
if (!Array.isArray(value)) return { overrides: [], unresolved: [] };
|
|
@@ -1481,14 +1661,14 @@ function toOxfmtOverrides(value) {
|
|
|
1481
1661
|
return { overrides, unresolved };
|
|
1482
1662
|
}
|
|
1483
1663
|
function readPrettierSettings(cwd) {
|
|
1484
|
-
const file = PRETTIER_CONFIG_FILES.find((name) => existsSync7(
|
|
1664
|
+
const file = PRETTIER_CONFIG_FILES.find((name) => existsSync7(join10(cwd, name)));
|
|
1485
1665
|
if (!file) return { options: {}, unresolved: [] };
|
|
1486
1666
|
if (/\.(js|cjs|mjs)$/.test(file)) {
|
|
1487
1667
|
return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
|
|
1488
1668
|
}
|
|
1489
1669
|
let parsed;
|
|
1490
1670
|
try {
|
|
1491
|
-
parsed = parseJsonc(
|
|
1671
|
+
parsed = parseJsonc(readFileSync8(join10(cwd, file), "utf8"), file);
|
|
1492
1672
|
} catch {
|
|
1493
1673
|
return { options: {}, file, unresolved: [`${file} could not be parsed`] };
|
|
1494
1674
|
}
|
|
@@ -1570,6 +1750,11 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1570
1750
|
const existing = readFormatAdoption(context2.cwd);
|
|
1571
1751
|
const prettier = readPrettierSettings(context2.cwd);
|
|
1572
1752
|
const local = existing.adopted ? { ...existing.local } : { ...prettier.options };
|
|
1753
|
+
const recorded = existing.preset === null ? void 0 : formatPresetFor(existing.preset);
|
|
1754
|
+
const ownedWhenSet = existing.adopted ? OWNED_WHEN_SET_KEYS.filter(
|
|
1755
|
+
(key) => !(key in local) && key in existing.effective && !isDeepStrictEqual(existing.effective[key], preset[key]) && !isDeepStrictEqual(existing.effective[key], recorded?.[key])
|
|
1756
|
+
) : [];
|
|
1757
|
+
for (const key of ownedWhenSet) local[key] = existing.effective[key];
|
|
1573
1758
|
for (const [key, value] of Object.entries(local)) {
|
|
1574
1759
|
if (ALWAYS_LOCAL_KEYS.includes(key)) continue;
|
|
1575
1760
|
if (key in preset && JSON.stringify(preset[key]) === JSON.stringify(value)) delete local[key];
|
|
@@ -1581,17 +1766,19 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1581
1766
|
.../* @__PURE__ */ new Set([...declaredIgnores, ...inherited, ...FORMAT_DEFAULT_IGNORES])
|
|
1582
1767
|
].sort();
|
|
1583
1768
|
if (ignorePatterns.length > 0) local.ignorePatterns = ignorePatterns;
|
|
1584
|
-
const
|
|
1585
|
-
|
|
1586
|
-
const config = {
|
|
1587
|
-
[PROVENANCE_KEY]: { preset: presetName, version, local: localKeys },
|
|
1769
|
+
const configStamped = (version2) => ({
|
|
1770
|
+
[PROVENANCE_KEY]: { preset: presetName, version: version2, local: localKeys },
|
|
1588
1771
|
...preset,
|
|
1589
1772
|
...Object.fromEntries(
|
|
1590
1773
|
[...localKeys, ...ALWAYS_LOCAL_KEYS].flatMap(
|
|
1591
1774
|
(key) => key in local ? [[key, local[key]]] : []
|
|
1592
1775
|
)
|
|
1593
1776
|
)
|
|
1594
|
-
};
|
|
1777
|
+
});
|
|
1778
|
+
const previous = existing.adopted ? existing.presetVersion : null;
|
|
1779
|
+
const unchanged = previous !== null && isDeepStrictEqual(configStamped(previous), existing.effective);
|
|
1780
|
+
const version = unchanged ? previous : own.version;
|
|
1781
|
+
const config = configStamped(version);
|
|
1595
1782
|
const operations = [
|
|
1596
1783
|
// Written pre-formatted (2-space, trailing newline): oxfmt formats JSON too, and until
|
|
1597
1784
|
// the format pass runs this file is the one config nothing has formatted yet.
|
|
@@ -1599,7 +1786,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1599
1786
|
manifestOperation(context2.cwd, this.formatScripts(context2.cwd))
|
|
1600
1787
|
];
|
|
1601
1788
|
const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
|
|
1602
|
-
(name) => existsSync8(
|
|
1789
|
+
(name) => existsSync8(join11(context2.cwd, name))
|
|
1603
1790
|
);
|
|
1604
1791
|
for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
|
|
1605
1792
|
const removableDeps = this.modulePrettierDependencies(context2.cwd);
|
|
@@ -1608,8 +1795,13 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1608
1795
|
}
|
|
1609
1796
|
operations.push(...nxTargetOperations({ cwd: context2.cwd, targets: formatTargets() }));
|
|
1610
1797
|
const notes = [
|
|
1611
|
-
`materialized the ${presetName} format preset into ${FORMAT_CONFIG_FILE} (${own.name}@${own.version}). oxfmt has no \`extends\`, so the values live here; \`sentinel --status --format\` reports modules left behind by a preset change`
|
|
1798
|
+
`materialized the ${presetName} format preset into ${FORMAT_CONFIG_FILE} (${own.name}@${version}${unchanged && version !== own.version ? ", unchanged since" : ""}). oxfmt has no \`extends\`, so the values live here; \`sentinel --status --format\` reports modules left behind by a preset change`
|
|
1612
1799
|
];
|
|
1800
|
+
if (ownedWhenSet.length > 0) {
|
|
1801
|
+
notes.push(
|
|
1802
|
+
`kept this module's own ${ownedWhenSet.join(", ")} and declared it in \`${PROVENANCE_KEY}.local\`: the module set it, so a re-init does not take it back`
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1613
1805
|
if (localKeys.length > 0) {
|
|
1614
1806
|
notes.push(
|
|
1615
1807
|
`kept this module's own formatting (${localKeys.join(", ")})${existing.adopted ? "" : ` from ${prettier.file}`}: the preset is the repo's, these are yours`
|
|
@@ -1667,13 +1859,29 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1667
1859
|
async rehearseAfterInit(ctx, origin) {
|
|
1668
1860
|
const oxfmt = resolveOxfmt(origin);
|
|
1669
1861
|
if (!oxfmt) return "oxfmt is not installed in this module yet";
|
|
1862
|
+
await this.keepDeliberateImportOrder(ctx.cwd);
|
|
1670
1863
|
const pass = spawnSync3(oxfmt, ["--write", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
1671
1864
|
if (pass.error) return pass.error.message;
|
|
1672
1865
|
return pass.status === 0 ? void 0 : oxfmtSaid(`${pass.stdout ?? ""}${pass.stderr ?? ""}`);
|
|
1673
1866
|
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Pin, before the formatter sorts, the imports whose order is deliberate. See
|
|
1869
|
+
* `deliberate-import-order.ts`. Returns the line to print, or undefined when nothing was pinned.
|
|
1870
|
+
*/
|
|
1871
|
+
async keepDeliberateImportOrder(cwd) {
|
|
1872
|
+
const pinned = await pinDeliberateImportOrder(
|
|
1873
|
+
cwd,
|
|
1874
|
+
formatOptions(readFormatAdoption(cwd).effective)
|
|
1875
|
+
);
|
|
1876
|
+
if (pinned.length === 0) return void 0;
|
|
1877
|
+
return `kept the import order of ${pinned.length} file(s) where it is deliberate (an eslint-disable naming an import-order rule, or an import that has to load first, such as dd-trace): ${pinned.join(", ")}. Each such import now carries \`${PIN_COMMENT}\`, so the import sort leaves it where it is`;
|
|
1878
|
+
}
|
|
1674
1879
|
async afterInit(ctx) {
|
|
1675
1880
|
const oxfmt = resolveOxfmt(ctx.cwd);
|
|
1676
1881
|
if (!oxfmt) return;
|
|
1882
|
+
const kept = await this.keepDeliberateImportOrder(ctx.cwd);
|
|
1883
|
+
if (kept !== void 0) process.stderr.write(` ${palette(process.stderr).warn(kept)}
|
|
1884
|
+
`);
|
|
1677
1885
|
const pass = spawnSync3(oxfmt, ["--write", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
1678
1886
|
process.stderr.write(` ${palette(process.stderr).dim(FORMATTER_DIFFERENCES)}
|
|
1679
1887
|
`);
|
|
@@ -1855,7 +2063,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1855
2063
|
modulePrettierDependencies(cwd) {
|
|
1856
2064
|
const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
|
|
1857
2065
|
try {
|
|
1858
|
-
const manifest = JSON.parse(
|
|
2066
|
+
const manifest = JSON.parse(readFileSync9(join11(cwd, "package.json"), "utf8"));
|
|
1859
2067
|
return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
|
|
1860
2068
|
} catch {
|
|
1861
2069
|
return [];
|
|
@@ -1937,8 +2145,8 @@ function registerFormat() {
|
|
|
1937
2145
|
|
|
1938
2146
|
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
1939
2147
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
1940
|
-
import { existsSync as existsSync12, readFileSync as
|
|
1941
|
-
import { join as
|
|
2148
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2149
|
+
import { join as join17 } from "node:path";
|
|
1942
2150
|
|
|
1943
2151
|
// src/core/config/deferred-rules.ts
|
|
1944
2152
|
function deferredRuleNames(rules) {
|
|
@@ -1947,7 +2155,7 @@ function deferredRuleNames(rules) {
|
|
|
1947
2155
|
|
|
1948
2156
|
// src/core/fix-report.ts
|
|
1949
2157
|
import { statSync as statSync2 } from "node:fs";
|
|
1950
|
-
import { join as
|
|
2158
|
+
import { join as join12 } from "node:path";
|
|
1951
2159
|
var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
|
|
1952
2160
|
function readFailure(output) {
|
|
1953
2161
|
const lines = output.split("\n").map((line2) => line2.trim());
|
|
@@ -1967,7 +2175,7 @@ function fingerprint(path2) {
|
|
|
1967
2175
|
function snapshotFiles(cwd, extensions) {
|
|
1968
2176
|
const snapshot = /* @__PURE__ */ new Map();
|
|
1969
2177
|
for (const file of listSourceFiles(cwd, extensions)) {
|
|
1970
|
-
const mark = fingerprint(
|
|
2178
|
+
const mark = fingerprint(join12(cwd, file));
|
|
1971
2179
|
if (mark !== void 0) snapshot.set(file, mark);
|
|
1972
2180
|
}
|
|
1973
2181
|
return snapshot;
|
|
@@ -1975,7 +2183,7 @@ function snapshotFiles(cwd, extensions) {
|
|
|
1975
2183
|
function changedSince(cwd, before, extensions) {
|
|
1976
2184
|
const changed = [];
|
|
1977
2185
|
for (const file of listSourceFiles(cwd, extensions)) {
|
|
1978
|
-
const now = fingerprint(
|
|
2186
|
+
const now = fingerprint(join12(cwd, file));
|
|
1979
2187
|
if (now === void 0) continue;
|
|
1980
2188
|
if (before.get(file) !== now) changed.push(file);
|
|
1981
2189
|
}
|
|
@@ -4381,6 +4589,19 @@ function validate(preset, rules) {
|
|
|
4381
4589
|
}
|
|
4382
4590
|
}
|
|
4383
4591
|
}
|
|
4592
|
+
function rejectTwoSpellings(label, rules) {
|
|
4593
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4594
|
+
for (const rule of Object.keys(rules)) {
|
|
4595
|
+
const canonical = canonicalRuleName(rule);
|
|
4596
|
+
const other = seen.get(canonical);
|
|
4597
|
+
if (other !== void 0) {
|
|
4598
|
+
throw new Error(
|
|
4599
|
+
`${label}: "${other}" and "${rule}" are the same rule to oxlint (${canonical}). Configure it once, under one name: oxlint keeps both and picks one by hash order.`
|
|
4600
|
+
);
|
|
4601
|
+
}
|
|
4602
|
+
seen.set(canonical, rule);
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4384
4605
|
function layered(preset) {
|
|
4385
4606
|
const base = sharedRules();
|
|
4386
4607
|
const layer = presetRules(preset);
|
|
@@ -4390,6 +4611,7 @@ function layered(preset) {
|
|
|
4390
4611
|
}
|
|
4391
4612
|
function toRuleMap(label, rules) {
|
|
4392
4613
|
validate(label, rules);
|
|
4614
|
+
rejectTwoSpellings(label, rules);
|
|
4393
4615
|
const entries = Object.entries(rules).map(([rule, value]) => {
|
|
4394
4616
|
if (!isDecision(value)) return [rule, value];
|
|
4395
4617
|
const options = value.options ?? [];
|
|
@@ -4421,14 +4643,14 @@ function downgradedRulesFor(preset) {
|
|
|
4421
4643
|
}
|
|
4422
4644
|
|
|
4423
4645
|
// src/roles/lint/extra-layers.ts
|
|
4424
|
-
import { existsSync as existsSync9, readFileSync as
|
|
4425
|
-
import { isAbsolute, join as
|
|
4646
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
|
|
4647
|
+
import { isAbsolute, join as join13, resolve as resolve2 } from "node:path";
|
|
4426
4648
|
function ruleCount(cwd, specifier) {
|
|
4427
4649
|
const path2 = isAbsolute(specifier) ? specifier : resolve2(cwd, specifier);
|
|
4428
4650
|
if (!existsSync9(path2)) return void 0;
|
|
4429
4651
|
try {
|
|
4430
4652
|
const parsed = parseJsonc(
|
|
4431
|
-
|
|
4653
|
+
readFileSync10(path2, "utf8"),
|
|
4432
4654
|
specifier
|
|
4433
4655
|
);
|
|
4434
4656
|
return Object.keys(parsed.rules ?? {}).length;
|
|
@@ -4448,7 +4670,7 @@ function extraLayers(cwd, extendsList) {
|
|
|
4448
4670
|
function committedExtendsList(cwd, configFile) {
|
|
4449
4671
|
try {
|
|
4450
4672
|
const parsed = parseJsonc(
|
|
4451
|
-
|
|
4673
|
+
readFileSync10(join13(cwd, configFile), "utf8"),
|
|
4452
4674
|
configFile
|
|
4453
4675
|
);
|
|
4454
4676
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4457,6 +4679,110 @@ function committedExtendsList(cwd, configFile) {
|
|
|
4457
4679
|
}
|
|
4458
4680
|
}
|
|
4459
4681
|
|
|
4682
|
+
// src/roles/lint/module-own-config.ts
|
|
4683
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4684
|
+
function pluginKey(entry) {
|
|
4685
|
+
if (typeof entry === "string") return entry;
|
|
4686
|
+
if (isRecord(entry) && typeof entry.name === "string") return entry.name;
|
|
4687
|
+
if (isRecord(entry) && typeof entry.specifier === "string") return entry.specifier;
|
|
4688
|
+
return void 0;
|
|
4689
|
+
}
|
|
4690
|
+
function governedRuleNames(preset) {
|
|
4691
|
+
return /* @__PURE__ */ new Set([
|
|
4692
|
+
...Object.keys(preset.rules),
|
|
4693
|
+
...(preset.overrides ?? []).flatMap((override) => Object.keys(override.rules))
|
|
4694
|
+
]);
|
|
4695
|
+
}
|
|
4696
|
+
function governedRules(preset) {
|
|
4697
|
+
const governed = /* @__PURE__ */ new Map();
|
|
4698
|
+
for (const name of governedRuleNames(preset)) {
|
|
4699
|
+
const canonical = canonicalRuleName(name);
|
|
4700
|
+
if (!governed.has(canonical)) governed.set(canonical, name);
|
|
4701
|
+
}
|
|
4702
|
+
return governed;
|
|
4703
|
+
}
|
|
4704
|
+
function moduleOwnBlocks(committed, preset) {
|
|
4705
|
+
const blocks = {};
|
|
4706
|
+
const kept = [];
|
|
4707
|
+
const dropped = [];
|
|
4708
|
+
if (Array.isArray(committed.plugins)) {
|
|
4709
|
+
const own = committed.plugins.filter(
|
|
4710
|
+
(name) => typeof name === "string" && !preset.plugins.includes(name)
|
|
4711
|
+
);
|
|
4712
|
+
if (own.length > 0) {
|
|
4713
|
+
blocks.plugins = own;
|
|
4714
|
+
kept.push(
|
|
4715
|
+
`kept this module's own plugins (${own.join(", ")}): the preset does not enable them`
|
|
4716
|
+
);
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
if (Array.isArray(committed.jsPlugins)) {
|
|
4720
|
+
const presetKeys = new Set(preset.jsPlugins.map((entry) => pluginKey(entry)));
|
|
4721
|
+
const own = [];
|
|
4722
|
+
for (const entry of committed.jsPlugins) {
|
|
4723
|
+
const key = pluginKey(entry);
|
|
4724
|
+
if (key !== void 0 && presetKeys.has(key)) dropped.push(`jsPlugins ${key}`);
|
|
4725
|
+
else own.push(entry);
|
|
4726
|
+
}
|
|
4727
|
+
if (own.length > 0) {
|
|
4728
|
+
blocks.jsPlugins = own;
|
|
4729
|
+
kept.push(
|
|
4730
|
+
`kept ${own.length} jsPlugin(s) this module added (${own.map((entry) => pluginKey(entry) ?? "?").join(", ")})`
|
|
4731
|
+
);
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
if (isRecord(committed.settings)) {
|
|
4735
|
+
const presetSettings = preset.settings ?? {};
|
|
4736
|
+
const own = {};
|
|
4737
|
+
for (const [key, value] of Object.entries(committed.settings)) {
|
|
4738
|
+
if (key in presetSettings) dropped.push(`settings.${key}`);
|
|
4739
|
+
else own[key] = value;
|
|
4740
|
+
}
|
|
4741
|
+
if (Object.keys(own).length > 0) {
|
|
4742
|
+
blocks.settings = own;
|
|
4743
|
+
kept.push(`kept this module's own settings (${Object.keys(own).join(", ")})`);
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
if (Array.isArray(committed.overrides)) {
|
|
4747
|
+
const governed = governedRules(preset);
|
|
4748
|
+
const byFiles = /* @__PURE__ */ new Map();
|
|
4749
|
+
for (const block of committed.overrides) {
|
|
4750
|
+
if (!isRecord(block) || !Array.isArray(block.files)) continue;
|
|
4751
|
+
const files = JSON.stringify([...block.files].map(String).sort());
|
|
4752
|
+
const rules = {};
|
|
4753
|
+
for (const [rule, value] of Object.entries(isRecord(block.rules) ? block.rules : {})) {
|
|
4754
|
+
const presetSpelling = governed.get(canonicalRuleName(rule));
|
|
4755
|
+
if (presetSpelling === void 0) rules[rule] = value;
|
|
4756
|
+
else
|
|
4757
|
+
dropped.push(
|
|
4758
|
+
`overrides[${block.files.join(", ")}] ${describeRuleKey(rule, presetSpelling)}`
|
|
4759
|
+
);
|
|
4760
|
+
}
|
|
4761
|
+
const { rules: _rules, ...rest } = block;
|
|
4762
|
+
const merged = byFiles.get(files);
|
|
4763
|
+
if (merged === void 0) {
|
|
4764
|
+
byFiles.set(files, { ...rest, ...Object.keys(rules).length > 0 ? { rules } : {} });
|
|
4765
|
+
continue;
|
|
4766
|
+
}
|
|
4767
|
+
const { files: _sameFiles, ...others } = rest;
|
|
4768
|
+
const mergedRules = { ...isRecord(merged.rules) ? merged.rules : {}, ...rules };
|
|
4769
|
+
Object.assign(
|
|
4770
|
+
merged,
|
|
4771
|
+
others,
|
|
4772
|
+
Object.keys(mergedRules).length > 0 ? { rules: mergedRules } : {}
|
|
4773
|
+
);
|
|
4774
|
+
}
|
|
4775
|
+
const own = [...byFiles.values()].filter((block) => Object.keys(block).length > 1);
|
|
4776
|
+
if (own.length > 0) {
|
|
4777
|
+
blocks.overrides = own;
|
|
4778
|
+
kept.push(
|
|
4779
|
+
`kept ${own.length} override block(s) this module set (${own.map((block) => block.files.join(", ")).join("; ")})`
|
|
4780
|
+
);
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
return { blocks, kept, dropped };
|
|
4784
|
+
}
|
|
4785
|
+
|
|
4460
4786
|
// src/roles/lint/parse-diagnostics.ts
|
|
4461
4787
|
function ruleId(code) {
|
|
4462
4788
|
const inner = /\(([^)]+)\)/.exec(code);
|
|
@@ -4506,14 +4832,34 @@ function errorCountsByConfigRule(stdout) {
|
|
|
4506
4832
|
return counts;
|
|
4507
4833
|
}
|
|
4508
4834
|
|
|
4835
|
+
// src/roles/lint/presets.ts
|
|
4836
|
+
var hasRuleData = (name) => GENERATED_LINT_PRESETS.includes(name);
|
|
4837
|
+
function lintPresetFor(preset) {
|
|
4838
|
+
const file = presetFile(preset);
|
|
4839
|
+
const resolved = hasRuleData(preset) ? preset : "node";
|
|
4840
|
+
return {
|
|
4841
|
+
plugins: file.plugins,
|
|
4842
|
+
jsPlugins: file.jsPlugins,
|
|
4843
|
+
...file.settings === null ? {} : { settings: file.settings },
|
|
4844
|
+
...file.renames ? { renames: file.renames } : {},
|
|
4845
|
+
...file.overrides ? {
|
|
4846
|
+
overrides: file.overrides.map((override, index) => ({
|
|
4847
|
+
files: override.files,
|
|
4848
|
+
rules: toRuleMap(`${preset} override ${index}`, override.rules)
|
|
4849
|
+
}))
|
|
4850
|
+
} : {},
|
|
4851
|
+
rules: rulesFor(resolved)
|
|
4852
|
+
};
|
|
4853
|
+
}
|
|
4854
|
+
|
|
4509
4855
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
4510
|
-
import { existsSync as existsSync11, readFileSync as
|
|
4511
|
-
import { join as
|
|
4856
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
|
|
4857
|
+
import { join as join16 } from "node:path";
|
|
4512
4858
|
|
|
4513
4859
|
// src/roles/lint/eslint-ignores.ts
|
|
4514
|
-
import { existsSync as existsSync10, readFileSync as
|
|
4515
|
-
import { join as
|
|
4516
|
-
import { parseSync } from "oxc-parser";
|
|
4860
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
|
|
4861
|
+
import { join as join14 } from "node:path";
|
|
4862
|
+
import { parseSync as parseSync2 } from "oxc-parser";
|
|
4517
4863
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
4518
4864
|
function toOxlintPattern(pattern) {
|
|
4519
4865
|
return pattern.replace(/^\.\//, "");
|
|
@@ -4524,17 +4870,17 @@ function readRootEslintIgnores(root) {
|
|
|
4524
4870
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
4525
4871
|
}
|
|
4526
4872
|
function readEslintIgnores(cwd) {
|
|
4527
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
4873
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join14(cwd, name)).find((path2) => existsSync10(path2));
|
|
4528
4874
|
if (!config) return { patterns: [], unresolved: [] };
|
|
4529
4875
|
let source;
|
|
4530
4876
|
try {
|
|
4531
|
-
source =
|
|
4877
|
+
source = readFileSync11(config, "utf8");
|
|
4532
4878
|
} catch {
|
|
4533
4879
|
return { patterns: [], unresolved: [] };
|
|
4534
4880
|
}
|
|
4535
4881
|
const patterns = /* @__PURE__ */ new Set();
|
|
4536
4882
|
const unresolved = new Set([...source.matchAll(OPAQUE_SOURCE)].map((match) => match[0]));
|
|
4537
|
-
const { program, errors } =
|
|
4883
|
+
const { program, errors } = parseSync2(config, source, { sourceType: "module" });
|
|
4538
4884
|
if (errors.length > 0) {
|
|
4539
4885
|
return { patterns: [], unresolved: [`${config} could not be parsed`] };
|
|
4540
4886
|
}
|
|
@@ -4598,29 +4944,9 @@ function keepsOtherCommands3(script) {
|
|
|
4598
4944
|
return keepsOtherCommands(script, SENTINEL_LINT_COMMAND);
|
|
4599
4945
|
}
|
|
4600
4946
|
|
|
4601
|
-
// src/roles/lint/presets.ts
|
|
4602
|
-
var hasRuleData = (name) => GENERATED_LINT_PRESETS.includes(name);
|
|
4603
|
-
function lintPresetFor(preset) {
|
|
4604
|
-
const file = presetFile(preset);
|
|
4605
|
-
const resolved = hasRuleData(preset) ? preset : "node";
|
|
4606
|
-
return {
|
|
4607
|
-
plugins: file.plugins,
|
|
4608
|
-
jsPlugins: file.jsPlugins,
|
|
4609
|
-
...file.settings === null ? {} : { settings: file.settings },
|
|
4610
|
-
...file.renames ? { renames: file.renames } : {},
|
|
4611
|
-
...file.overrides ? {
|
|
4612
|
-
overrides: file.overrides.map((override, index) => ({
|
|
4613
|
-
files: override.files,
|
|
4614
|
-
rules: toRuleMap(`${preset} override ${index}`, override.rules)
|
|
4615
|
-
}))
|
|
4616
|
-
} : {},
|
|
4617
|
-
rules: rulesFor(resolved)
|
|
4618
|
-
};
|
|
4619
|
-
}
|
|
4620
|
-
|
|
4621
4947
|
// src/roles/lint/rename-suppressions.ts
|
|
4622
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
4623
|
-
import { join as
|
|
4948
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync3 } from "node:fs";
|
|
4949
|
+
import { join as join15, relative as relative3 } from "node:path";
|
|
4624
4950
|
var SOURCE_EXTENSIONS = [
|
|
4625
4951
|
".ts",
|
|
4626
4952
|
".tsx",
|
|
@@ -4667,7 +4993,7 @@ function* sourceFiles(dir) {
|
|
|
4667
4993
|
return;
|
|
4668
4994
|
}
|
|
4669
4995
|
for (const entry of entries) {
|
|
4670
|
-
const full =
|
|
4996
|
+
const full = join15(dir, entry);
|
|
4671
4997
|
let isDirectory;
|
|
4672
4998
|
try {
|
|
4673
4999
|
isDirectory = statSync3(full).isDirectory();
|
|
@@ -4687,7 +5013,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4687
5013
|
for (const file of sourceFiles(cwd)) {
|
|
4688
5014
|
let content;
|
|
4689
5015
|
try {
|
|
4690
|
-
content =
|
|
5016
|
+
content = readFileSync12(file, "utf8");
|
|
4691
5017
|
} catch {
|
|
4692
5018
|
continue;
|
|
4693
5019
|
}
|
|
@@ -4807,13 +5133,18 @@ function plan2(context2) {
|
|
|
4807
5133
|
...rootIgnores.patterns
|
|
4808
5134
|
].filter((pattern) => !defaults2.has(bare(pattern)));
|
|
4809
5135
|
const carried = [.../* @__PURE__ */ new Set([...DEFAULT_IGNORE_PATTERNS, ...moduleOwn])];
|
|
5136
|
+
const committed = committedConfig(context2.cwd);
|
|
4810
5137
|
const { kept: teamRules, governed: droppedRules } = moduleOwnRules(
|
|
4811
|
-
|
|
5138
|
+
isRecord2(committed.rules) ? committed.rules : {},
|
|
4812
5139
|
lintPresetFor(variant)
|
|
4813
5140
|
);
|
|
5141
|
+
const teamBlocks = moduleOwnBlocks(committed, lintPresetFor(variant));
|
|
5142
|
+
const { overrides: teamOverrides, ...teamTopLevel } = teamBlocks.blocks;
|
|
4814
5143
|
const stub = {
|
|
4815
5144
|
extends: extendsWithPreset(committedExtends(context2.cwd), variant),
|
|
5145
|
+
...teamTopLevel,
|
|
4816
5146
|
...Object.keys(teamRules).length > 0 ? { rules: teamRules } : {},
|
|
5147
|
+
...teamOverrides !== void 0 ? { overrides: teamOverrides } : {},
|
|
4817
5148
|
...carried.length > 0 ? { ignorePatterns: carried } : {}
|
|
4818
5149
|
};
|
|
4819
5150
|
const operations = [
|
|
@@ -4828,7 +5159,7 @@ function plan2(context2) {
|
|
|
4828
5159
|
keys: removableDeps
|
|
4829
5160
|
});
|
|
4830
5161
|
}
|
|
4831
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync11(
|
|
5162
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync11(join16(context2.cwd, name)));
|
|
4832
5163
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
4833
5164
|
operations.push(
|
|
4834
5165
|
...nxTargetOperations({
|
|
@@ -4869,7 +5200,13 @@ function plan2(context2) {
|
|
|
4869
5200
|
}
|
|
4870
5201
|
if (droppedRules.length > 0) {
|
|
4871
5202
|
notes.push(
|
|
4872
|
-
`dropped ${droppedRules.length} rule(s) this module set over the preset (${droppedRules.join(", ")}): the preset governs them`
|
|
5203
|
+
`dropped ${droppedRules.length} rule(s) this module set over the preset (${droppedRules.map((rule) => describeGoverned(rule, variant)).join(", ")}): the preset governs them`
|
|
5204
|
+
);
|
|
5205
|
+
}
|
|
5206
|
+
notes.push(...teamBlocks.kept);
|
|
5207
|
+
if (teamBlocks.dropped.length > 0) {
|
|
5208
|
+
notes.push(
|
|
5209
|
+
`dropped ${teamBlocks.dropped.length} entr(ies) this module set over the preset (${teamBlocks.dropped.join(", ")}): the preset governs them`
|
|
4873
5210
|
);
|
|
4874
5211
|
}
|
|
4875
5212
|
if (rootIgnores.patterns.length > 0) {
|
|
@@ -4936,7 +5273,7 @@ function lintScripts(cwd) {
|
|
|
4936
5273
|
function committedExtends(cwd) {
|
|
4937
5274
|
try {
|
|
4938
5275
|
const parsed = parseJsonc(
|
|
4939
|
-
|
|
5276
|
+
readFileSync13(join16(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4940
5277
|
LINT_CONFIG_FILE
|
|
4941
5278
|
);
|
|
4942
5279
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4944,35 +5281,35 @@ function committedExtends(cwd) {
|
|
|
4944
5281
|
return [];
|
|
4945
5282
|
}
|
|
4946
5283
|
}
|
|
4947
|
-
|
|
5284
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5285
|
+
function committedConfig(cwd) {
|
|
4948
5286
|
try {
|
|
4949
5287
|
const parsed = parseJsonc(
|
|
4950
|
-
|
|
5288
|
+
readFileSync13(join16(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4951
5289
|
LINT_CONFIG_FILE
|
|
4952
5290
|
);
|
|
4953
|
-
|
|
4954
|
-
return typeof rules === "object" && rules !== null && !Array.isArray(rules) ? rules : {};
|
|
5291
|
+
return isRecord2(parsed) ? parsed : {};
|
|
4955
5292
|
} catch {
|
|
4956
5293
|
return {};
|
|
4957
5294
|
}
|
|
4958
5295
|
}
|
|
4959
5296
|
function moduleOwnRules(committed, preset) {
|
|
4960
|
-
const governedNames =
|
|
4961
|
-
...Object.keys(preset.rules),
|
|
4962
|
-
...(preset.overrides ?? []).flatMap((override) => Object.keys(override.rules))
|
|
4963
|
-
]);
|
|
5297
|
+
const governedNames = governedRules(preset);
|
|
4964
5298
|
const kept = {};
|
|
4965
5299
|
const governed = [];
|
|
4966
5300
|
for (const [rule, value] of Object.entries(committed)) {
|
|
4967
|
-
if (governedNames.has(rule)) governed.push(rule);
|
|
5301
|
+
if (governedNames.has(canonicalRuleName(rule))) governed.push(rule);
|
|
4968
5302
|
else kept[rule] = value;
|
|
4969
5303
|
}
|
|
4970
5304
|
return { kept, governed };
|
|
4971
5305
|
}
|
|
5306
|
+
function describeGoverned(rule, variant) {
|
|
5307
|
+
return describeRuleKey(rule, governedRules(lintPresetFor(variant)).get(canonicalRuleName(rule)));
|
|
5308
|
+
}
|
|
4972
5309
|
function committedIgnorePatterns(cwd) {
|
|
4973
5310
|
try {
|
|
4974
5311
|
const parsed = parseJsonc(
|
|
4975
|
-
|
|
5312
|
+
readFileSync13(join16(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4976
5313
|
LINT_CONFIG_FILE
|
|
4977
5314
|
);
|
|
4978
5315
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4984,7 +5321,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
4984
5321
|
const isEslintPackage = (name) => name === "eslint" || name === "@types/eslint" || name === "typescript-eslint" || name.startsWith("@typescript-eslint/") || name.startsWith("eslint-plugin-") || name.startsWith("eslint-config-") || name.startsWith("@eslint/");
|
|
4985
5322
|
let manifest;
|
|
4986
5323
|
try {
|
|
4987
|
-
manifest = JSON.parse(
|
|
5324
|
+
manifest = JSON.parse(readFileSync13(join16(cwd, "package.json"), "utf8"));
|
|
4988
5325
|
} catch {
|
|
4989
5326
|
return [];
|
|
4990
5327
|
}
|
|
@@ -5098,7 +5435,44 @@ ${result.stderr ?? ""}`);
|
|
|
5098
5435
|
const env = { ...process.env, PATH: oxlintPath(ctx.cwd, process.env) };
|
|
5099
5436
|
process.stderr.write(` ${describeFixOutcome(this.fixPass(ctx, oxlint, env), "lint")}
|
|
5100
5437
|
`);
|
|
5101
|
-
this.writeModuleBaseline(ctx, oxlint, env);
|
|
5438
|
+
if (this.runLeadingCommands(ctx, env)) this.writeModuleBaseline(ctx, oxlint, env);
|
|
5439
|
+
}
|
|
5440
|
+
/**
|
|
5441
|
+
* Run what the module's `lint` script runs before sentinel, so the baseline measures the module
|
|
5442
|
+
* `pnpm run lint` lints. See `leadingCommands`: on hr-management that is `svelte-kit sync`, and
|
|
5443
|
+
* without it the measurement met a tsconfig extending a file that did not exist yet and held
|
|
5444
|
+
* `typescript/tsconfig-error`, a hold a fresh clone would commit.
|
|
5445
|
+
*
|
|
5446
|
+
* Shelled the way a package manager runs a script, with every `node_modules/.bin` up the tree
|
|
5447
|
+
* on PATH. False when the command failed: a baseline measured without it is the wrong module,
|
|
5448
|
+
* and recording that is worse than recording nothing, so the existing baseline is left alone
|
|
5449
|
+
* and the developer is told what to run.
|
|
5450
|
+
*/
|
|
5451
|
+
runLeadingCommands(ctx, env) {
|
|
5452
|
+
const leading = leadingCommands(moduleScripts(ctx.cwd)[LINT_SCRIPT_NAME]);
|
|
5453
|
+
if (leading === void 0) return true;
|
|
5454
|
+
const result = spawnSync4(leading, {
|
|
5455
|
+
cwd: ctx.cwd,
|
|
5456
|
+
shell: true,
|
|
5457
|
+
encoding: "utf8",
|
|
5458
|
+
env: { ...env, PATH: scriptPath(ctx.cwd, env.PATH) },
|
|
5459
|
+
maxBuffer: CAPTURE_MAX_BUFFER
|
|
5460
|
+
});
|
|
5461
|
+
if (!result.error && result.status === 0) {
|
|
5462
|
+
process.stderr.write(
|
|
5463
|
+
` ran \`${leading}\` before measuring, as the lint script runs it before sentinel
|
|
5464
|
+
`
|
|
5465
|
+
);
|
|
5466
|
+
return true;
|
|
5467
|
+
}
|
|
5468
|
+
const said = (result.error?.message ?? `${result.stderr ?? ""}${result.stdout ?? ""}`).trim().split("\n").slice(-1)[0];
|
|
5469
|
+
process.stderr.write(
|
|
5470
|
+
` ${palette(process.stderr).warn(
|
|
5471
|
+
`the lint script runs \`${leading}\` before sentinel, and it ${result.error ? "could not start" : `exited ${result.status ?? 1}`} here${said ? ` (${said})` : ""}, so this module's existing violations were not measured and ${LINT_BASELINE_FILE} was left as it was. Fix that, then run \`sentinel --init --lint\` again.`
|
|
5472
|
+
)}
|
|
5473
|
+
`
|
|
5474
|
+
);
|
|
5475
|
+
return false;
|
|
5102
5476
|
}
|
|
5103
5477
|
/**
|
|
5104
5478
|
* The fix pass alone, in the copy `--init --dry-run` hands it, without the baseline measurement
|
|
@@ -5128,23 +5502,23 @@ ${result.stderr ?? ""}`);
|
|
|
5128
5502
|
* build the developer can see, rather than a silent half-adoption they cannot.
|
|
5129
5503
|
*/
|
|
5130
5504
|
writeModuleBaseline(ctx, oxlint, env) {
|
|
5131
|
-
const configPath =
|
|
5132
|
-
const baselinePath =
|
|
5505
|
+
const configPath = join17(ctx.cwd, LINT_CONFIG_FILE);
|
|
5506
|
+
const baselinePath = join17(ctx.cwd, LINT_BASELINE_FILE);
|
|
5133
5507
|
if (!existsSync12(configPath)) return;
|
|
5134
5508
|
let config;
|
|
5135
5509
|
try {
|
|
5136
5510
|
config = parseJsonc(
|
|
5137
|
-
|
|
5511
|
+
readFileSync14(configPath, "utf8"),
|
|
5138
5512
|
LINT_CONFIG_FILE
|
|
5139
5513
|
);
|
|
5140
5514
|
} catch {
|
|
5141
5515
|
return;
|
|
5142
5516
|
}
|
|
5143
5517
|
const current = Array.isArray(config.extends) ? config.extends : [];
|
|
5144
|
-
const measurePath =
|
|
5518
|
+
const measurePath = join17(ctx.cwd, LINT_MEASURE_FILE);
|
|
5145
5519
|
let measured;
|
|
5146
5520
|
try {
|
|
5147
|
-
|
|
5521
|
+
writeFileSync3(
|
|
5148
5522
|
measurePath,
|
|
5149
5523
|
`${JSON.stringify({ ...config, extends: extendsWithBaseline(current, false) }, null, 2)}
|
|
5150
5524
|
`
|
|
@@ -5173,10 +5547,13 @@ ${result.stderr ?? ""}`);
|
|
|
5173
5547
|
);
|
|
5174
5548
|
return;
|
|
5175
5549
|
}
|
|
5176
|
-
const holds = holdsFrom(
|
|
5550
|
+
const holds = holdsFrom(
|
|
5551
|
+
errorCountsByConfigRule(measured.stdout ?? ""),
|
|
5552
|
+
governedRules(lintPresetFor(presetVariant(ctx.preset, ctx.cwd)))
|
|
5553
|
+
);
|
|
5177
5554
|
const next = extendsWithBaseline(current, holds.length > 0);
|
|
5178
5555
|
if (holds.length > 0) {
|
|
5179
|
-
|
|
5556
|
+
writeFileSync3(baselinePath, renderBaseline(holds, readOwnPackage().version));
|
|
5180
5557
|
process.stderr.write(` ${palette(process.stderr).warn(describeHolds(holds))}
|
|
5181
5558
|
`);
|
|
5182
5559
|
} else {
|
|
@@ -5184,12 +5561,12 @@ ${result.stderr ?? ""}`);
|
|
|
5184
5561
|
}
|
|
5185
5562
|
if (next.length !== current.length || next.some((entry, at) => entry !== current[at])) {
|
|
5186
5563
|
config.extends = next;
|
|
5187
|
-
|
|
5564
|
+
writeFileSync3(configPath, `${JSON.stringify(config, null, 2)}
|
|
5188
5565
|
`);
|
|
5189
5566
|
}
|
|
5190
5567
|
}
|
|
5191
5568
|
async run(ctx) {
|
|
5192
|
-
if (!existsSync12(
|
|
5569
|
+
if (!existsSync12(join17(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
5193
5570
|
process.stderr.write(
|
|
5194
5571
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
5195
5572
|
`
|
|
@@ -5329,7 +5706,8 @@ ${result.stderr ?? ""}`);
|
|
|
5329
5706
|
* exactly when something is broken.
|
|
5330
5707
|
*
|
|
5331
5708
|
* Counted as a SET of rule names, so the baseline re-stating a preset rule at `warn` is
|
|
5332
|
-
* the same rule, not a second one.
|
|
5709
|
+
* the same rule, not a second one. The rule oxlint RUNS (`canonicalRuleName`), so a layer
|
|
5710
|
+
* spelling it `no-unused-vars` beside the preset's `typescript/no-unused-vars` is one rule too.
|
|
5333
5711
|
*/
|
|
5334
5712
|
presetRuleCount(cwd) {
|
|
5335
5713
|
const target = this.unresolvedPreset(cwd);
|
|
@@ -5337,7 +5715,7 @@ ${result.stderr ?? ""}`);
|
|
|
5337
5715
|
let stub;
|
|
5338
5716
|
try {
|
|
5339
5717
|
stub = parseJsonc(
|
|
5340
|
-
|
|
5718
|
+
readFileSync14(join17(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5341
5719
|
LINT_CONFIG_FILE
|
|
5342
5720
|
);
|
|
5343
5721
|
} catch {
|
|
@@ -5347,10 +5725,10 @@ ${result.stderr ?? ""}`);
|
|
|
5347
5725
|
for (const entry of stub.extends ?? []) {
|
|
5348
5726
|
try {
|
|
5349
5727
|
const preset = parseJsonc(
|
|
5350
|
-
|
|
5728
|
+
readFileSync14(join17(cwd, entry), "utf8"),
|
|
5351
5729
|
entry
|
|
5352
5730
|
);
|
|
5353
|
-
for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
|
|
5731
|
+
for (const rule of Object.keys(preset.rules ?? {})) names.add(canonicalRuleName(rule));
|
|
5354
5732
|
} catch {
|
|
5355
5733
|
continue;
|
|
5356
5734
|
}
|
|
@@ -5417,14 +5795,14 @@ ${result.stderr ?? ""}`);
|
|
|
5417
5795
|
let parsed;
|
|
5418
5796
|
try {
|
|
5419
5797
|
parsed = parseJsonc(
|
|
5420
|
-
|
|
5798
|
+
readFileSync14(join17(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
5421
5799
|
LINT_CONFIG_FILE
|
|
5422
5800
|
);
|
|
5423
5801
|
} catch {
|
|
5424
5802
|
return void 0;
|
|
5425
5803
|
}
|
|
5426
5804
|
const targets2 = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
|
|
5427
|
-
return targets2.find((target) => !existsSync12(
|
|
5805
|
+
return targets2.find((target) => !existsSync12(join17(cwd, target)));
|
|
5428
5806
|
}
|
|
5429
5807
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
5430
5808
|
announceDisabled(preset) {
|
|
@@ -5456,8 +5834,8 @@ function registerLint() {
|
|
|
5456
5834
|
|
|
5457
5835
|
// src/roles/static-analysis/adapters/jscpd/jscpd.adapter.ts
|
|
5458
5836
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
5459
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync2, readFileSync as
|
|
5460
|
-
import { join as
|
|
5837
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync2, readFileSync as readFileSync20, unlinkSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
5838
|
+
import { join as join27 } from "node:path";
|
|
5461
5839
|
import { globSync as globSync5 } from "tinyglobby";
|
|
5462
5840
|
|
|
5463
5841
|
// src/roles/static-analysis/bundle/budgets.ts
|
|
@@ -5486,21 +5864,21 @@ function bandFor(valueKb, referenceKb) {
|
|
|
5486
5864
|
}
|
|
5487
5865
|
|
|
5488
5866
|
// src/roles/static-analysis/bundle/scan.ts
|
|
5489
|
-
import { existsSync as existsSync14, readFileSync as
|
|
5490
|
-
import { basename, join as
|
|
5867
|
+
import { existsSync as existsSync14, readFileSync as readFileSync16 } from "node:fs";
|
|
5868
|
+
import { basename, join as join19 } from "node:path";
|
|
5491
5869
|
import { pathToFileURL } from "node:url";
|
|
5492
5870
|
import { gzipSync } from "node:zlib";
|
|
5493
5871
|
import { globSync as globSync2 } from "tinyglobby";
|
|
5494
5872
|
|
|
5495
5873
|
// src/roles/static-analysis/bundle/find-build.ts
|
|
5496
|
-
import { existsSync as existsSync13, readFileSync as
|
|
5497
|
-
import { join as
|
|
5874
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15, statSync as statSync4 } from "node:fs";
|
|
5875
|
+
import { join as join18 } from "node:path";
|
|
5498
5876
|
import { globSync } from "tinyglobby";
|
|
5499
5877
|
var MANIFEST_GLOB = "server/_tanstack-start-manifest*.mjs";
|
|
5500
5878
|
function candidateDirs(cwd) {
|
|
5501
5879
|
const declared = readProjectPackageJson(cwd).nx?.targets?.build?.["outputs"];
|
|
5502
|
-
const fromDeclaration = Array.isArray(declared) ? declared.filter((token) => typeof token === "string").map((token) =>
|
|
5503
|
-
return [...fromDeclaration,
|
|
5880
|
+
const fromDeclaration = Array.isArray(declared) ? declared.filter((token) => typeof token === "string").map((token) => join18(cwd, token.replace(/^\{projectRoot\}\//, ""))) : [];
|
|
5881
|
+
return [...fromDeclaration, join18(cwd, ".output"), join18(cwd, "dist"), join18(cwd, "build")];
|
|
5504
5882
|
}
|
|
5505
5883
|
function findBuildOutput(cwd) {
|
|
5506
5884
|
const lookedIn = [];
|
|
@@ -5510,7 +5888,7 @@ function findBuildOutput(cwd) {
|
|
|
5510
5888
|
const found = globSync(MANIFEST_GLOB, { cwd: dir, absolute: true, dot: false });
|
|
5511
5889
|
if (found.length === 0) continue;
|
|
5512
5890
|
const manifestPath = found[0];
|
|
5513
|
-
const publicDir =
|
|
5891
|
+
const publicDir = join18(dir, "public");
|
|
5514
5892
|
if (!existsSync13(publicDir)) continue;
|
|
5515
5893
|
return { manifestPath, publicDir, ...dateOf(dir, manifestPath) };
|
|
5516
5894
|
}
|
|
@@ -5533,10 +5911,10 @@ function findBuildOutput(cwd) {
|
|
|
5533
5911
|
};
|
|
5534
5912
|
}
|
|
5535
5913
|
function dateOf(dir, manifestPath) {
|
|
5536
|
-
const record =
|
|
5914
|
+
const record = join18(dir, "nitro.json");
|
|
5537
5915
|
if (existsSync13(record)) {
|
|
5538
5916
|
try {
|
|
5539
|
-
const parsed = JSON.parse(
|
|
5917
|
+
const parsed = JSON.parse(readFileSync15(record, "utf8"));
|
|
5540
5918
|
if (typeof parsed.date === "string") {
|
|
5541
5919
|
const date = new Date(parsed.date);
|
|
5542
5920
|
if (!Number.isNaN(date.getTime())) return { builtAt: date, builtAtFrom: "build record" };
|
|
@@ -5562,9 +5940,9 @@ function kindOf(path2) {
|
|
|
5562
5940
|
return "other";
|
|
5563
5941
|
}
|
|
5564
5942
|
function resolveAnnounced(publicDir, announced) {
|
|
5565
|
-
const direct =
|
|
5943
|
+
const direct = join19(publicDir, announced);
|
|
5566
5944
|
if (existsSync14(direct)) return direct;
|
|
5567
|
-
const stripped =
|
|
5945
|
+
const stripped = join19(publicDir, announced.replace(/^\/[^/]+\//, "/"));
|
|
5568
5946
|
return existsSync14(stripped) ? stripped : void 0;
|
|
5569
5947
|
}
|
|
5570
5948
|
function emptyWeight() {
|
|
@@ -5601,7 +5979,7 @@ async function scanBundle(cwd) {
|
|
|
5601
5979
|
unresolved.push(announced);
|
|
5602
5980
|
measured.set(announced, void 0);
|
|
5603
5981
|
} else {
|
|
5604
|
-
const buffer =
|
|
5982
|
+
const buffer = readFileSync16(path2);
|
|
5605
5983
|
measured.set(announced, {
|
|
5606
5984
|
parsed: buffer.length,
|
|
5607
5985
|
transfer: gzipSync(buffer, { level: 9 }).length
|
|
@@ -5688,7 +6066,7 @@ function scanDynamicLayer(publicDir, references) {
|
|
|
5688
6066
|
})) {
|
|
5689
6067
|
const name = basename(path2);
|
|
5690
6068
|
if (referenced.has(name)) continue;
|
|
5691
|
-
const buffer =
|
|
6069
|
+
const buffer = readFileSync16(path2);
|
|
5692
6070
|
const size = { parsed: buffer.length, transfer: gzipSync(buffer, { level: 9 }).length };
|
|
5693
6071
|
add(all2, size);
|
|
5694
6072
|
const group = groupOf(name);
|
|
@@ -5716,23 +6094,23 @@ function scanDynamicLayer(publicDir, references) {
|
|
|
5716
6094
|
}
|
|
5717
6095
|
|
|
5718
6096
|
// src/roles/static-analysis/sources.ts
|
|
5719
|
-
import { readFileSync as
|
|
5720
|
-
import { join as
|
|
5721
|
-
import { parseSync as
|
|
6097
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
6098
|
+
import { join as join22 } from "node:path";
|
|
6099
|
+
import { parseSync as parseSync3 } from "oxc-parser";
|
|
5722
6100
|
import { globSync as globSync3 } from "tinyglobby";
|
|
5723
6101
|
|
|
5724
6102
|
// src/roles/static-analysis/adapters/jscpd/tool-config.ts
|
|
5725
|
-
import { existsSync as existsSync16, mkdtempSync, rmSync as rmSync3, writeFileSync as
|
|
6103
|
+
import { existsSync as existsSync16, mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5726
6104
|
import { tmpdir } from "node:os";
|
|
5727
|
-
import { join as
|
|
6105
|
+
import { join as join21 } from "node:path";
|
|
5728
6106
|
|
|
5729
6107
|
// src/roles/static-analysis/configs/resolve.ts
|
|
5730
6108
|
import { existsSync as existsSync15 } from "node:fs";
|
|
5731
|
-
import { join as
|
|
6109
|
+
import { join as join20 } from "node:path";
|
|
5732
6110
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
5733
6111
|
var STATIC_CONFIG_FILE = "static-analysis.config.mjs";
|
|
5734
6112
|
async function loadStaticConfig(cwd) {
|
|
5735
|
-
const path2 =
|
|
6113
|
+
const path2 = join20(cwd, STATIC_CONFIG_FILE);
|
|
5736
6114
|
if (!existsSync15(path2)) {
|
|
5737
6115
|
return { config: defineStaticAnalysis(), from: null, unreadable: null };
|
|
5738
6116
|
}
|
|
@@ -5779,7 +6157,7 @@ function exclusions(config, scope = "tests") {
|
|
|
5779
6157
|
return [.../* @__PURE__ */ new Set([...config.ignore, ...OWN_ARTEFACTS, ...tests])];
|
|
5780
6158
|
}
|
|
5781
6159
|
function measuredRoots(cwd) {
|
|
5782
|
-
return existsSync16(
|
|
6160
|
+
return existsSync16(join21(cwd, "src")) ? ["src"] : ["."];
|
|
5783
6161
|
}
|
|
5784
6162
|
function fileSettings(config, scope, extraEntries = []) {
|
|
5785
6163
|
return {
|
|
@@ -5809,9 +6187,9 @@ function fileSettings(config, scope, extraEntries = []) {
|
|
|
5809
6187
|
};
|
|
5810
6188
|
}
|
|
5811
6189
|
function duplicationRun(config, paths, scope) {
|
|
5812
|
-
const dir = mkdtempSync(
|
|
5813
|
-
const configPath =
|
|
5814
|
-
|
|
6190
|
+
const dir = mkdtempSync(join21(tmpdir(), "sentinel-static-"));
|
|
6191
|
+
const configPath = join21(dir, "jscpd.json");
|
|
6192
|
+
writeFileSync4(configPath, `${JSON.stringify(fileSettings(config, scope), null, 2)}
|
|
5815
6193
|
`, "utf8");
|
|
5816
6194
|
const args = [
|
|
5817
6195
|
"--config",
|
|
@@ -5836,9 +6214,9 @@ function duplicationRun(config, paths, scope) {
|
|
|
5836
6214
|
return { args, rawReportDir: dir, dispose: () => rmSync3(dir, { force: true, recursive: true }) };
|
|
5837
6215
|
}
|
|
5838
6216
|
function deadCodeRun(config, paths, extraEntries = []) {
|
|
5839
|
-
const dir = mkdtempSync(
|
|
5840
|
-
const configPath =
|
|
5841
|
-
|
|
6217
|
+
const dir = mkdtempSync(join21(tmpdir(), "sentinel-static-"));
|
|
6218
|
+
const configPath = join21(dir, "jscpd.json");
|
|
6219
|
+
writeFileSync4(
|
|
5842
6220
|
configPath,
|
|
5843
6221
|
`${JSON.stringify(fileSettings(config, "dead-code", extraEntries), null, 2)}
|
|
5844
6222
|
`,
|
|
@@ -5889,12 +6267,12 @@ function readParsed(cwd, files, passes) {
|
|
|
5889
6267
|
function parsedSource(cwd, path2) {
|
|
5890
6268
|
let text;
|
|
5891
6269
|
try {
|
|
5892
|
-
text =
|
|
6270
|
+
text = readFileSync17(join22(cwd, path2), "utf8");
|
|
5893
6271
|
} catch {
|
|
5894
6272
|
return void 0;
|
|
5895
6273
|
}
|
|
5896
6274
|
try {
|
|
5897
|
-
const parsed =
|
|
6275
|
+
const parsed = parseSync3(path2, text);
|
|
5898
6276
|
if (parsed.errors.length > 0) return void 0;
|
|
5899
6277
|
return { path: path2, text, program: parsed.program };
|
|
5900
6278
|
} catch {
|
|
@@ -5903,7 +6281,7 @@ function parsedSource(cwd, path2) {
|
|
|
5903
6281
|
}
|
|
5904
6282
|
|
|
5905
6283
|
// src/roles/static-analysis/complexity/cyclomatic.ts
|
|
5906
|
-
import { parseSync as
|
|
6284
|
+
import { parseSync as parseSync4 } from "oxc-parser";
|
|
5907
6285
|
var FUNCTION_NODES = /* @__PURE__ */ new Set([
|
|
5908
6286
|
"FunctionDeclaration",
|
|
5909
6287
|
"FunctionExpression",
|
|
@@ -6076,13 +6454,13 @@ function unreadableNote(coverage) {
|
|
|
6076
6454
|
}
|
|
6077
6455
|
|
|
6078
6456
|
// src/roles/static-analysis/excerpt.ts
|
|
6079
|
-
import { readFileSync as
|
|
6080
|
-
import { join as
|
|
6457
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
6458
|
+
import { join as join24 } from "node:path";
|
|
6081
6459
|
|
|
6082
6460
|
// src/roles/static-analysis/maintainability/size-rules.ts
|
|
6083
|
-
import { mkdtempSync as mkdtempSync2, rmSync as rmSync4, writeFileSync as
|
|
6461
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
6084
6462
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
6085
|
-
import { join as
|
|
6463
|
+
import { join as join23 } from "node:path";
|
|
6086
6464
|
var GAUGE_THRESHOLDS = {
|
|
6087
6465
|
// McCabe 1976 says 10, our p90 says 11. The only indicator where two independent candidates agree
|
|
6088
6466
|
// within one unit, and 94.8% of our functions already sit in the 1-10 band SEI calls low risk.
|
|
@@ -6111,7 +6489,7 @@ var GAUGE_THRESHOLDS = {
|
|
|
6111
6489
|
"cognitive-complexity": 7
|
|
6112
6490
|
};
|
|
6113
6491
|
var COGNITIVE = "cognitive-complexity";
|
|
6114
|
-
var cognitivePlugin = () =>
|
|
6492
|
+
var cognitivePlugin = () => join23(ownPackageDir(), "plugins", "sonar-cognitive.js");
|
|
6115
6493
|
var CATEGORIES = [
|
|
6116
6494
|
"correctness",
|
|
6117
6495
|
"suspicious",
|
|
@@ -6143,9 +6521,9 @@ function sizeRulesConfig() {
|
|
|
6143
6521
|
};
|
|
6144
6522
|
}
|
|
6145
6523
|
function sizeRulesRun(paths, ignore) {
|
|
6146
|
-
const dir = mkdtempSync2(
|
|
6147
|
-
const configPath =
|
|
6148
|
-
|
|
6524
|
+
const dir = mkdtempSync2(join23(tmpdir2(), "sentinel-size-"));
|
|
6525
|
+
const configPath = join23(dir, "oxlintrc.json");
|
|
6526
|
+
writeFileSync5(configPath, `${JSON.stringify(sizeRulesConfig(), null, 2)}
|
|
6149
6527
|
`, "utf8");
|
|
6150
6528
|
return {
|
|
6151
6529
|
// `--format json` because the console shape is rendered by this role from the figures: a
|
|
@@ -7602,7 +7980,7 @@ function linesOf(cache, cwd, relative5) {
|
|
|
7602
7980
|
if (cache.has(relative5)) return cache.get(relative5);
|
|
7603
7981
|
let lines;
|
|
7604
7982
|
try {
|
|
7605
|
-
lines =
|
|
7983
|
+
lines = readFileSync18(join24(cwd, relative5), "utf8").split("\n");
|
|
7606
7984
|
} catch {
|
|
7607
7985
|
lines = void 0;
|
|
7608
7986
|
}
|
|
@@ -7834,7 +8212,7 @@ var SCRIPT = `(function(){
|
|
|
7834
8212
|
})();`;
|
|
7835
8213
|
|
|
7836
8214
|
// src/roles/static-analysis/graph/imports.ts
|
|
7837
|
-
import { dirname, join as
|
|
8215
|
+
import { dirname, join as join25 } from "node:path";
|
|
7838
8216
|
var TS_BEHIND_JS = {
|
|
7839
8217
|
".js": [".ts", ".tsx"],
|
|
7840
8218
|
".mjs": [".mts"],
|
|
@@ -7887,7 +8265,7 @@ function specifiersOf(program) {
|
|
|
7887
8265
|
}
|
|
7888
8266
|
function resolveSpecifier(from, specifier, known) {
|
|
7889
8267
|
if (!specifier.startsWith(".")) return void 0;
|
|
7890
|
-
const base =
|
|
8268
|
+
const base = join25(dirname(from), specifier);
|
|
7891
8269
|
if (known.has(base)) return base;
|
|
7892
8270
|
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
7893
8271
|
if (known.has(base + extension)) return base + extension;
|
|
@@ -7898,7 +8276,7 @@ function resolveSpecifier(from, specifier, known) {
|
|
|
7898
8276
|
if (known.has(swapped)) return swapped;
|
|
7899
8277
|
}
|
|
7900
8278
|
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
7901
|
-
const index =
|
|
8279
|
+
const index = join25(base, `index${extension}`);
|
|
7902
8280
|
if (known.has(index)) return index;
|
|
7903
8281
|
}
|
|
7904
8282
|
return void 0;
|
|
@@ -8011,26 +8389,26 @@ function scanSyntax(cwd, config) {
|
|
|
8011
8389
|
|
|
8012
8390
|
// src/roles/static-analysis/workspace/consumers.ts
|
|
8013
8391
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
8014
|
-
import { existsSync as existsSync17, mkdirSync, readFileSync as
|
|
8015
|
-
import { dirname as dirname2, join as
|
|
8016
|
-
var CACHE_DIR =
|
|
8392
|
+
import { existsSync as existsSync17, mkdirSync, readFileSync as readFileSync19, statSync as statSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8393
|
+
import { dirname as dirname2, join as join26, relative as relative4, resolve as resolve3, sep } from "node:path";
|
|
8394
|
+
var CACHE_DIR = join26("node_modules", ".cache", "sentinel");
|
|
8017
8395
|
var CACHE_FILE = "consumer-index.json";
|
|
8018
8396
|
var SHAPE = 4;
|
|
8019
8397
|
function workspaceRootFrom(cwd) {
|
|
8020
8398
|
let dir = resolve3(cwd);
|
|
8021
8399
|
for (; ; ) {
|
|
8022
|
-
if (existsSync17(
|
|
8400
|
+
if (existsSync17(join26(dir, WORKSPACE_ROOT_MARKER))) return dir;
|
|
8023
8401
|
const parent = dirname2(dir);
|
|
8024
8402
|
if (parent === dir) return void 0;
|
|
8025
8403
|
dir = parent;
|
|
8026
8404
|
}
|
|
8027
8405
|
}
|
|
8028
8406
|
function aliasTable(workspaceRoot) {
|
|
8029
|
-
const file =
|
|
8407
|
+
const file = join26(workspaceRoot, "tsconfig.base.json");
|
|
8030
8408
|
if (!existsSync17(file)) return /* @__PURE__ */ new Map();
|
|
8031
8409
|
let paths;
|
|
8032
8410
|
try {
|
|
8033
|
-
const parsed = JSON.parse(
|
|
8411
|
+
const parsed = JSON.parse(readFileSync19(file, "utf8"));
|
|
8034
8412
|
paths = parsed.compilerOptions?.paths ?? {};
|
|
8035
8413
|
} catch {
|
|
8036
8414
|
return /* @__PURE__ */ new Map();
|
|
@@ -8048,7 +8426,7 @@ function aliasTable(workspaceRoot) {
|
|
|
8048
8426
|
function resolveSpecifier2(specifier, table, workspaceRoot) {
|
|
8049
8427
|
const isFile = (candidate) => {
|
|
8050
8428
|
try {
|
|
8051
|
-
return statSync5(
|
|
8429
|
+
return statSync5(join26(workspaceRoot, candidate)).isFile();
|
|
8052
8430
|
} catch {
|
|
8053
8431
|
return false;
|
|
8054
8432
|
}
|
|
@@ -8085,11 +8463,11 @@ function resolveSpecifier2(specifier, table, workspaceRoot) {
|
|
|
8085
8463
|
return void 0;
|
|
8086
8464
|
}
|
|
8087
8465
|
function resolveRelative(importer, specifier, workspaceRoot) {
|
|
8088
|
-
const base =
|
|
8466
|
+
const base = join26(dirname2(importer), specifier).split(sep).join("/");
|
|
8089
8467
|
if (base.startsWith("..")) return void 0;
|
|
8090
8468
|
const isFile = (candidate) => {
|
|
8091
8469
|
try {
|
|
8092
|
-
return statSync5(
|
|
8470
|
+
return statSync5(join26(workspaceRoot, candidate)).isFile();
|
|
8093
8471
|
} catch {
|
|
8094
8472
|
return false;
|
|
8095
8473
|
}
|
|
@@ -8187,10 +8565,10 @@ function hash(text) {
|
|
|
8187
8565
|
}
|
|
8188
8566
|
function consumerIndex(workspaceRoot, moduleRoots) {
|
|
8189
8567
|
const key = cacheKey(workspaceRoot);
|
|
8190
|
-
const file =
|
|
8568
|
+
const file = join26(workspaceRoot, CACHE_DIR, CACHE_FILE);
|
|
8191
8569
|
if (key && existsSync17(file)) {
|
|
8192
8570
|
try {
|
|
8193
|
-
const cached = JSON.parse(
|
|
8571
|
+
const cached = JSON.parse(readFileSync19(file, "utf8"));
|
|
8194
8572
|
if (cached.key === key) {
|
|
8195
8573
|
return {
|
|
8196
8574
|
imported: new Set(cached.imported),
|
|
@@ -8204,14 +8582,14 @@ function consumerIndex(workspaceRoot, moduleRoots) {
|
|
|
8204
8582
|
const index = buildConsumerIndex(workspaceRoot, moduleRoots());
|
|
8205
8583
|
if (key) {
|
|
8206
8584
|
try {
|
|
8207
|
-
mkdirSync(
|
|
8585
|
+
mkdirSync(join26(workspaceRoot, CACHE_DIR), { recursive: true });
|
|
8208
8586
|
const payload = {
|
|
8209
8587
|
key,
|
|
8210
8588
|
imported: [...index.imported],
|
|
8211
8589
|
testOnly: [...index.testOnly],
|
|
8212
8590
|
builtFrom: index.builtFrom
|
|
8213
8591
|
};
|
|
8214
|
-
|
|
8592
|
+
writeFileSync6(file, JSON.stringify(payload), "utf8");
|
|
8215
8593
|
} catch {
|
|
8216
8594
|
}
|
|
8217
8595
|
}
|
|
@@ -8308,7 +8686,7 @@ var JscpdAdapter = class extends BaseAdapter {
|
|
|
8308
8686
|
operations: [
|
|
8309
8687
|
// `write` and not `merge-json`: this file is code, and the engine's in-place merge is for
|
|
8310
8688
|
// JSON. A re-init therefore RESTORES the stub, so an existing one is left alone below.
|
|
8311
|
-
...existsSync18(
|
|
8689
|
+
...existsSync18(join27(context2.cwd, STATIC_CONFIG_FILE)) ? [] : [{ kind: "write", path: STATIC_CONFIG_FILE, contents: configStub() }],
|
|
8312
8690
|
manifestOperation(context2.cwd, scripts()),
|
|
8313
8691
|
...nxTargetOperations({ cwd: context2.cwd, targets: targets() })
|
|
8314
8692
|
// The report directory is ignored ONCE at the workspace root, by `--init`'s workspace prep,
|
|
@@ -8558,7 +8936,7 @@ var JscpdAdapter = class extends BaseAdapter {
|
|
|
8558
8936
|
const declared = moduleScripts(ctx.cwd);
|
|
8559
8937
|
const missing = Object.keys(scripts()).filter((name) => !(name in declared));
|
|
8560
8938
|
const adopted = missing.length < Object.keys(scripts()).length;
|
|
8561
|
-
const hasConfig = existsSync18(
|
|
8939
|
+
const hasConfig = existsSync18(join27(ctx.cwd, STATIC_CONFIG_FILE));
|
|
8562
8940
|
return {
|
|
8563
8941
|
adopted,
|
|
8564
8942
|
preset: adopted ? ctx.preset : null,
|
|
@@ -8650,7 +9028,7 @@ function runDeadCode(jscpd, config, paths, cwd, extraEntries = []) {
|
|
|
8650
9028
|
try {
|
|
8651
9029
|
const ran = runTool(jscpd, args, cwd, "dead-code");
|
|
8652
9030
|
if (ran.kind !== "ran") return ran;
|
|
8653
|
-
const path2 =
|
|
9031
|
+
const path2 = join27(rawReportDir, RAW_REPORT["dead-code"]);
|
|
8654
9032
|
if (!existsSync18(path2)) {
|
|
8655
9033
|
process.stderr.write(
|
|
8656
9034
|
`sentinel static-analysis(jscpd): the dead-code pass wrote no ${RAW_REPORT["dead-code"]}
|
|
@@ -8658,7 +9036,7 @@ function runDeadCode(jscpd, config, paths, cwd, extraEntries = []) {
|
|
|
8658
9036
|
);
|
|
8659
9037
|
return { kind: "failed", code: 1 };
|
|
8660
9038
|
}
|
|
8661
|
-
const parsed = safeParseDeadCode(
|
|
9039
|
+
const parsed = safeParseDeadCode(readFileSync20(path2, "utf8"));
|
|
8662
9040
|
const statistics = parsed?.statistics;
|
|
8663
9041
|
if (!statistics) {
|
|
8664
9042
|
process.stderr.write(
|
|
@@ -8681,11 +9059,11 @@ function runDeadCode(jscpd, config, paths, cwd, extraEntries = []) {
|
|
|
8681
9059
|
}
|
|
8682
9060
|
}
|
|
8683
9061
|
function moduleRelative(name, roots, cwd) {
|
|
8684
|
-
if (existsSync18(
|
|
9062
|
+
if (existsSync18(join27(cwd, name))) return name;
|
|
8685
9063
|
for (const root of roots) {
|
|
8686
9064
|
if (root === ".") continue;
|
|
8687
9065
|
const candidate = `${root}/${name}`;
|
|
8688
|
-
if (existsSync18(
|
|
9066
|
+
if (existsSync18(join27(cwd, candidate))) return candidate;
|
|
8689
9067
|
}
|
|
8690
9068
|
return name;
|
|
8691
9069
|
}
|
|
@@ -8694,8 +9072,8 @@ function runDuplication(jscpd, config, paths, scope, cwd) {
|
|
|
8694
9072
|
try {
|
|
8695
9073
|
const ran = runTool(jscpd, args, cwd, scope);
|
|
8696
9074
|
if (ran.kind !== "ran") return ran;
|
|
8697
|
-
const path2 =
|
|
8698
|
-
const parsed = existsSync18(path2) ? safeParse(
|
|
9075
|
+
const path2 = join27(rawReportDir, RAW_REPORT.duplication);
|
|
9076
|
+
const parsed = existsSync18(path2) ? safeParse(readFileSync20(path2, "utf8")) : void 0;
|
|
8699
9077
|
const total = parsed?.statistics?.total;
|
|
8700
9078
|
if (!total) {
|
|
8701
9079
|
process.stderr.write(
|
|
@@ -8864,7 +9242,7 @@ function outsideOf(cwd) {
|
|
|
8864
9242
|
function moduleRootsOf(workspaceRoot) {
|
|
8865
9243
|
const roots = [];
|
|
8866
9244
|
for (const area of ["apps", "libs"]) {
|
|
8867
|
-
const base =
|
|
9245
|
+
const base = join27(workspaceRoot, area);
|
|
8868
9246
|
if (!existsSync18(base)) continue;
|
|
8869
9247
|
for (const entry of globSync5([`${area}/**/package.json`, `${area}/**/project.json`], {
|
|
8870
9248
|
cwd: workspaceRoot,
|
|
@@ -9207,7 +9585,7 @@ var PICTURE_FILE = `${TARGET}-graph.svg`;
|
|
|
9207
9585
|
function clearStaleOutputs(cwd, keeping) {
|
|
9208
9586
|
for (const name of [...Object.values(SHAPE_FILE), PICTURE_FILE]) {
|
|
9209
9587
|
if (keeping.has(name)) continue;
|
|
9210
|
-
const path2 =
|
|
9588
|
+
const path2 = join27(cwd, REPORT_DIR, name);
|
|
9211
9589
|
if (existsSync18(path2)) unlinkSync(path2);
|
|
9212
9590
|
}
|
|
9213
9591
|
}
|
|
@@ -9226,11 +9604,11 @@ function drawsPicture(graph, coverage) {
|
|
|
9226
9604
|
async function emit(report, shape, cwd, graph, coverage) {
|
|
9227
9605
|
if (shape === "console") return [];
|
|
9228
9606
|
const detailed = shape === "markdown" ? report : withExcerpts(report, cwd);
|
|
9229
|
-
mkdirSync2(
|
|
9607
|
+
mkdirSync2(join27(cwd, REPORT_DIR), { recursive: true });
|
|
9230
9608
|
const written = [];
|
|
9231
9609
|
if (shape === "html" && drawsPicture(graph, coverage)) {
|
|
9232
9610
|
const drawn = layOut(graph.graph, graph.communityOf);
|
|
9233
|
-
const path2 =
|
|
9611
|
+
const path2 = join27(cwd, REPORT_DIR, PICTURE_FILE);
|
|
9234
9612
|
await writeGraphPicture(drawn, path2);
|
|
9235
9613
|
written.push(`${REPORT_DIR}/${PICTURE_FILE}`);
|
|
9236
9614
|
detailed.picture = buildExplorer(drawn, graph.communityOf).markup;
|
|
@@ -9238,7 +9616,7 @@ async function emit(report, shape, cwd, graph, coverage) {
|
|
|
9238
9616
|
const RENDER = { json: renderJson, html: renderHtml, markdown: renderMarkdown };
|
|
9239
9617
|
const name = SHAPE_FILE[shape] ?? `${TARGET}.${shape}`;
|
|
9240
9618
|
const body = RENDER[shape](detailed);
|
|
9241
|
-
|
|
9619
|
+
writeFileSync7(join27(cwd, REPORT_DIR, name), body, "utf8");
|
|
9242
9620
|
written.unshift(`${REPORT_DIR}/${name}`);
|
|
9243
9621
|
return written;
|
|
9244
9622
|
}
|
|
@@ -9272,9 +9650,9 @@ function registerTest() {
|
|
|
9272
9650
|
|
|
9273
9651
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
9274
9652
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
9275
|
-
import { existsSync as existsSync19, readFileSync as
|
|
9653
|
+
import { existsSync as existsSync19, readFileSync as readFileSync22 } from "node:fs";
|
|
9276
9654
|
import { createRequire } from "node:module";
|
|
9277
|
-
import { join as
|
|
9655
|
+
import { join as join29 } from "node:path";
|
|
9278
9656
|
|
|
9279
9657
|
// src/roles/typescript/presets/nest.json
|
|
9280
9658
|
var nest_default2 = {
|
|
@@ -9459,8 +9837,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
9459
9837
|
}
|
|
9460
9838
|
|
|
9461
9839
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
9462
|
-
import { readFileSync as
|
|
9463
|
-
import { join as
|
|
9840
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
9841
|
+
import { join as join28 } from "node:path";
|
|
9464
9842
|
|
|
9465
9843
|
// src/roles/typescript/typecheck-script.ts
|
|
9466
9844
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -9553,7 +9931,7 @@ function planAdoption(context2) {
|
|
|
9553
9931
|
};
|
|
9554
9932
|
}
|
|
9555
9933
|
const existing = parseJsonc(
|
|
9556
|
-
|
|
9934
|
+
readFileSync21(join28(context2.cwd, target.path), "utf8"),
|
|
9557
9935
|
target.path
|
|
9558
9936
|
);
|
|
9559
9937
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -9691,7 +10069,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
9691
10069
|
let chain;
|
|
9692
10070
|
try {
|
|
9693
10071
|
const parsed = parseJsonc(
|
|
9694
|
-
|
|
10072
|
+
readFileSync22(join29(cwd, target.path), "utf8"),
|
|
9695
10073
|
target.path
|
|
9696
10074
|
);
|
|
9697
10075
|
chain = parsed.extends;
|
|
@@ -9704,7 +10082,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
9704
10082
|
);
|
|
9705
10083
|
if (preset === void 0) return void 0;
|
|
9706
10084
|
try {
|
|
9707
|
-
createRequire(
|
|
10085
|
+
createRequire(join29(cwd, "noop.js")).resolve(preset);
|
|
9708
10086
|
return void 0;
|
|
9709
10087
|
} catch {
|
|
9710
10088
|
return preset;
|
|
@@ -9800,7 +10178,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
9800
10178
|
referencedProjects(cwd, config) {
|
|
9801
10179
|
try {
|
|
9802
10180
|
const parsed = parseJsonc(
|
|
9803
|
-
|
|
10181
|
+
readFileSync22(join29(cwd, config), "utf8"),
|
|
9804
10182
|
config
|
|
9805
10183
|
);
|
|
9806
10184
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path2) => typeof path2 === "string" && path2.length > 0);
|
|
@@ -9816,7 +10194,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
9816
10194
|
* check.
|
|
9817
10195
|
*/
|
|
9818
10196
|
typecheckTarget(cwd) {
|
|
9819
|
-
if (existsSync19(
|
|
10197
|
+
if (existsSync19(join29(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
9820
10198
|
const target = resolveTsconfigTarget(cwd);
|
|
9821
10199
|
return target.reason === "none" ? null : target.path;
|
|
9822
10200
|
}
|
|
@@ -10022,7 +10400,7 @@ function replaceLines(current, replacements) {
|
|
|
10022
10400
|
}
|
|
10023
10401
|
|
|
10024
10402
|
// src/core/apply-plan.ts
|
|
10025
|
-
import { existsSync as existsSync20, readFileSync as
|
|
10403
|
+
import { existsSync as existsSync20, readFileSync as readFileSync23, renameSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
10026
10404
|
import { resolve as resolve4, sep as sep2 } from "node:path";
|
|
10027
10405
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
10028
10406
|
function resolveWithinRoot(cwd, relativePath) {
|
|
@@ -10034,7 +10412,7 @@ function resolveWithinRoot(cwd, relativePath) {
|
|
|
10034
10412
|
return absolutePath;
|
|
10035
10413
|
}
|
|
10036
10414
|
function readIfExists(absolutePath) {
|
|
10037
|
-
return existsSync20(absolutePath) ?
|
|
10415
|
+
return existsSync20(absolutePath) ? readFileSync23(absolutePath, "utf8") : void 0;
|
|
10038
10416
|
}
|
|
10039
10417
|
function* leaves(value, prefix = []) {
|
|
10040
10418
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -10136,7 +10514,7 @@ function preparePlan(cwd, plan3) {
|
|
|
10136
10514
|
}
|
|
10137
10515
|
function writeFileAtomic(absolutePath, contents) {
|
|
10138
10516
|
const tempPath = `${absolutePath}.sentinel-${process.pid}.tmp`;
|
|
10139
|
-
|
|
10517
|
+
writeFileSync8(tempPath, contents);
|
|
10140
10518
|
renameSync(tempPath, absolutePath);
|
|
10141
10519
|
}
|
|
10142
10520
|
function applyPlan(cwd, plan3) {
|
|
@@ -10152,8 +10530,8 @@ function applyPlan(cwd, plan3) {
|
|
|
10152
10530
|
}
|
|
10153
10531
|
|
|
10154
10532
|
// src/core/config/preset-evidence.ts
|
|
10155
|
-
import { existsSync as existsSync21, readdirSync as readdirSync3, readFileSync as
|
|
10156
|
-
import { join as
|
|
10533
|
+
import { existsSync as existsSync21, readdirSync as readdirSync3, readFileSync as readFileSync24 } from "node:fs";
|
|
10534
|
+
import { join as join30 } from "node:path";
|
|
10157
10535
|
var PATH_SIGNALS = [
|
|
10158
10536
|
{
|
|
10159
10537
|
preset: "nest",
|
|
@@ -10187,12 +10565,12 @@ function sourceImports(cwd, pattern) {
|
|
|
10187
10565
|
}
|
|
10188
10566
|
for (const entry of entries) {
|
|
10189
10567
|
if (entry.isDirectory()) {
|
|
10190
|
-
if (!NOT_SCANNED.has(entry.name)) stack.push(
|
|
10568
|
+
if (!NOT_SCANNED.has(entry.name)) stack.push(join30(dir, entry.name));
|
|
10191
10569
|
continue;
|
|
10192
10570
|
}
|
|
10193
10571
|
if (!SOURCE_FILE.test(entry.name)) continue;
|
|
10194
10572
|
try {
|
|
10195
|
-
if (pattern.test(
|
|
10573
|
+
if (pattern.test(readFileSync24(join30(dir, entry.name), "utf8"))) return true;
|
|
10196
10574
|
} catch {
|
|
10197
10575
|
continue;
|
|
10198
10576
|
}
|
|
@@ -10201,11 +10579,11 @@ function sourceImports(cwd, pattern) {
|
|
|
10201
10579
|
return false;
|
|
10202
10580
|
}
|
|
10203
10581
|
function dependencyNames(cwd) {
|
|
10204
|
-
const path2 =
|
|
10582
|
+
const path2 = join30(cwd, "package.json");
|
|
10205
10583
|
if (!existsSync21(path2)) return [];
|
|
10206
10584
|
try {
|
|
10207
10585
|
const manifest = parseJsonc(
|
|
10208
|
-
|
|
10586
|
+
readFileSync24(path2, "utf8"),
|
|
10209
10587
|
path2
|
|
10210
10588
|
);
|
|
10211
10589
|
return [
|
|
@@ -10228,7 +10606,7 @@ function reactTsconfigEvidence(cwd) {
|
|
|
10228
10606
|
}
|
|
10229
10607
|
for (const name of entries) {
|
|
10230
10608
|
try {
|
|
10231
|
-
const config = parseJsonc(
|
|
10609
|
+
const config = parseJsonc(readFileSync24(join30(cwd, name), "utf8"), name);
|
|
10232
10610
|
if (config.compilerOptions?.jsx !== void 0) return 'its tsconfig sets "jsx"';
|
|
10233
10611
|
const extended = Array.isArray(config.extends) ? config.extends : config.extends === void 0 ? [] : [config.extends];
|
|
10234
10612
|
if (extended.some((entry) => entry === REACT_TYPESCRIPT_PRESET)) {
|
|
@@ -10329,8 +10707,10 @@ async function dispatch(opts) {
|
|
|
10329
10707
|
}
|
|
10330
10708
|
const detected = resolveFlavour(opts);
|
|
10331
10709
|
const resolutionPreset = opts.preset ?? declaredPresetFor(opts.target, opts.cwd) ?? detected;
|
|
10332
|
-
|
|
10333
|
-
|
|
10710
|
+
const declaredTools = opts.preset === void 0 ? availableTargets().find((target) => declaredPresetFor(target, opts.cwd) === "tools") : void 0;
|
|
10711
|
+
if (resolutionPreset === "tools" || declaredTools !== void 0) {
|
|
10712
|
+
const declaredBy = declaredTools === void 0 || declaredTools === opts.target ? "" : `this module's ${declaredTools} config declares the tools preset, and `;
|
|
10713
|
+
const message = `${declaredBy}tools modules are not adopted one at a time: they sometimes hold configuration shared with the workspace root, so they move WITH the root, as one reviewed change owned by the tooling team rather than by the module's team.`;
|
|
10334
10714
|
process.stderr.write(` ${palette(process.stderr).dim(`${opts.target}: ${message}`)}
|
|
10335
10715
|
`);
|
|
10336
10716
|
opts.record?.({ role: opts.target, status: "deferred", reason: message });
|