@hublo/sentinel 1.5.1-alpha.2 → 1.6.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -70,6 +70,7 @@ import {
70
70
  readProjectPackageJson
71
71
  } from "./chunk-JRZI5IIN.js";
72
72
  import {
73
+ DEFAULT_ENV_LEVEL,
73
74
  PRESET_NAMES,
74
75
  REPORT_DIR,
75
76
  SCRIPTS,
@@ -80,7 +81,10 @@ import {
80
81
  extensionsOf,
81
82
  isPlainObject,
82
83
  overridableSettings
83
- } from "./chunk-WBDBRGLV.js";
84
+ } from "./chunk-7E6YTTAK.js";
85
+ import {
86
+ defineEnv
87
+ } from "./chunk-4EIY7TTR.js";
84
88
  import {
85
89
  globToRegExp
86
90
  } from "./chunk-O7REVMOC.js";
@@ -171,6 +175,75 @@ function palette(stream) {
171
175
  };
172
176
  }
173
177
 
178
+ // src/roles/env/presets/shared.json
179
+ var shared_default = {
180
+ readers: {
181
+ required: ["getOrFail", "requireEnv", "requireEnvVariable"],
182
+ optional: ["getOrDefault", "getEnvVariableOrDefault"]
183
+ },
184
+ scan: {
185
+ roots: ["."],
186
+ ext: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
187
+ excludeSuffixes: [
188
+ ".spec.ts",
189
+ ".test.ts",
190
+ ".spec.tsx",
191
+ ".test.tsx",
192
+ ".spec.js",
193
+ ".test.js",
194
+ ".spec.jsx",
195
+ ".test.jsx",
196
+ ".spec.mts",
197
+ ".test.mts",
198
+ ".spec.cts",
199
+ ".test.cts",
200
+ ".spec.mjs",
201
+ ".test.mjs",
202
+ ".spec.cjs",
203
+ ".test.cjs"
204
+ ],
205
+ excludeDirs: ["node_modules", "dist"]
206
+ },
207
+ references: {
208
+ roots: ["."],
209
+ ext: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".svelte", ".vue"],
210
+ excludeSuffixes: [],
211
+ excludeDirs: ["node_modules", "dist"]
212
+ },
213
+ referencePatterns: [
214
+ "process\\.env\\s*\\.\\s*([A-Za-z0-9_]+)",
215
+ "process\\.env\\[\\s*['\"`]([^'\"`]+)['\"`]\\s*\\]",
216
+ "import\\.meta\\.env\\s*\\.\\s*([A-Za-z0-9_]+)",
217
+ "import\\.meta\\.env\\[\\s*['\"`]([^'\"`]+)['\"`]\\s*\\]",
218
+ `import\\s*\\{([^}]*)\\}\\s*from\\s*['"]\\$env/static/(?:public|private)['"]`,
219
+ "\\benv\\.([A-Z][A-Z0-9_]*)",
220
+ "\\{([^{}]*)\\}\\s*=\\s*env\\b"
221
+ ],
222
+ secretNamePatterns: ["SECRET", "TOKEN", "PASSWORD", "PRIVATE_?KEY", "ACCESS_?KEY", "API_?KEY"],
223
+ publicPrefixes: ["VITE_", "PUBLIC_", "NEXT_PUBLIC_", "REACT_APP_"],
224
+ envFiles: ["**/.env", "**/.env.*", "**/*.env"],
225
+ contracts: ["**/*.env*.example"],
226
+ nodeVersionFile: ".node-version"
227
+ };
228
+
229
+ // src/roles/env/preset-data.ts
230
+ var data = shared_default;
231
+ var envDefaults = data;
232
+ function referencePatterns() {
233
+ return data.referencePatterns.map((source) => new RegExp(source, "g"));
234
+ }
235
+ function secretNamePatterns() {
236
+ return data.secretNamePatterns.map((source) => new RegExp(source, "i"));
237
+ }
238
+ var TARGET2 = "env";
239
+ var ENV_CONFIG_FILE = "env.config.mjs";
240
+ var REPORT_DIR2 = "env-report";
241
+ var ENV_SHAPES = ["console", "json", "markdown"];
242
+ var SHAPE_FILE = {
243
+ json: "env.json",
244
+ markdown: "env.md"
245
+ };
246
+
174
247
  // src/roles/build/nest/nx-target.ts
175
248
  import { existsSync, readFileSync } from "node:fs";
176
249
  import { join, relative } from "node:path";
@@ -1289,15 +1362,1068 @@ function registerBuild() {
1289
1362
  setDefaultRunner("dev", "vite");
1290
1363
  }
1291
1364
 
1365
+ // src/roles/env/adapters/conformance/conformance.adapter.ts
1366
+ import { existsSync as existsSync7, mkdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
1367
+ import { join as join10 } from "node:path";
1368
+
1369
+ // src/roles/env/collect.ts
1370
+ import { dirname, join as join8 } from "node:path/posix";
1371
+
1372
+ // src/roles/env/checks.ts
1373
+ var READER_VAR_NAME = String.raw`[A-Z0-9_]+`;
1374
+ var REFERENCED_NAME = /[A-Za-z_][A-Za-z0-9_]*/g;
1375
+ function lineAt(content, index) {
1376
+ let line = 1;
1377
+ for (let position = content.indexOf("\n"); position !== -1 && position < index; ) {
1378
+ line += 1;
1379
+ position = content.indexOf("\n", position + 1);
1380
+ }
1381
+ return line;
1382
+ }
1383
+ function readerRegex(readerName) {
1384
+ return new RegExp(String.raw`(?<!\w)${readerName}\(\s*[\`'"](${READER_VAR_NAME})[\`'"]`, "g");
1385
+ }
1386
+ function recordReads(reads, matchers, file) {
1387
+ for (const matcher of matchers) {
1388
+ for (const match of file.content.matchAll(matcher)) {
1389
+ const name = match[1];
1390
+ if (name === void 0) continue;
1391
+ const sites = reads.get(name);
1392
+ if (sites?.some((site2) => site2.path === file.path)) continue;
1393
+ const site = { path: file.path, line: lineAt(file.content, match.index) };
1394
+ if (sites) sites.push(site);
1395
+ else reads.set(name, [site]);
1396
+ }
1397
+ }
1398
+ }
1399
+ function deriveVars(files, readers) {
1400
+ const requiredMatchers = (readers.required ?? []).map(readerRegex);
1401
+ const optionalMatchers = (readers.optional ?? []).map(readerRegex);
1402
+ const required = /* @__PURE__ */ new Map();
1403
+ const optional = /* @__PURE__ */ new Map();
1404
+ for (const file of files) {
1405
+ recordReads(required, requiredMatchers, file);
1406
+ recordReads(optional, optionalMatchers, file);
1407
+ }
1408
+ return { required, optional };
1409
+ }
1410
+ var ENV_LINE = /^(?:export\s+)?([\w.-]+)\s*=(.*)$/;
1411
+ var QUOTES = /* @__PURE__ */ new Set(['"', "'", "`"]);
1412
+ function closingQuote(text, quote, from) {
1413
+ for (let index = from; index < text.length; index += 1) {
1414
+ if (text[index] === "\\" && text[index + 1] === quote) index += 1;
1415
+ else if (text[index] === quote) return index;
1416
+ }
1417
+ return -1;
1418
+ }
1419
+ function readValue(raw) {
1420
+ const value = raw.trimStart();
1421
+ const quote = value[0];
1422
+ if (quote !== void 0 && QUOTES.has(quote)) {
1423
+ const end = closingQuote(value, quote, 1);
1424
+ if (end === -1) return { blank: false, opens: quote };
1425
+ const rest = value.slice(end + 1).trim();
1426
+ return { blank: end === 1 && (rest === "" || rest.startsWith("#")) };
1427
+ }
1428
+ const comment = value.indexOf("#");
1429
+ return { blank: (comment === -1 ? value : value.slice(0, comment)).trim() === "" };
1430
+ }
1431
+ function parseEnvFile(content) {
1432
+ const entries = [];
1433
+ const lines = content.split("\n");
1434
+ let closing;
1435
+ for (const [index, rawLine] of lines.entries()) {
1436
+ const line = rawLine.trim();
1437
+ if (closing !== void 0) {
1438
+ if (closingQuote(line, closing, 0) !== -1) closing = void 0;
1439
+ continue;
1440
+ }
1441
+ if (line === "" || line.startsWith("#")) continue;
1442
+ const match = ENV_LINE.exec(line);
1443
+ if (!match) continue;
1444
+ const { blank, opens } = readValue(match[2] ?? "");
1445
+ entries.push({ key: match[1] ?? "", line: index + 1, blank });
1446
+ closing = opens;
1447
+ }
1448
+ return {
1449
+ entries,
1450
+ keys: entries.map((entry) => entry.key),
1451
+ blanked: entries.filter((entry) => entry.blank).map((entry) => entry.key),
1452
+ filled: entries.filter((entry) => !entry.blank).map((entry) => entry.key)
1453
+ };
1454
+ }
1455
+ function diffContract(derived, declared, externallyProvided = []) {
1456
+ const declaredKeys = new Set(declared);
1457
+ const external = new Set(externallyProvided);
1458
+ const unaccounted = [...derived.required.keys()].filter((name) => !declaredKeys.has(name) && !external.has(name)).sort();
1459
+ const optionalUndeclared = [...derived.optional.keys()].filter((name) => !declaredKeys.has(name)).sort();
1460
+ return { unaccounted, optionalUndeclared };
1461
+ }
1462
+ function findOrphanEnvFiles(envFiles, allow = []) {
1463
+ const allowed = new Set(allow);
1464
+ return envFiles.filter((path2) => !path2.endsWith(".example") && !allowed.has(path2)).sort();
1465
+ }
1466
+ function* referencedNames(content, patterns) {
1467
+ for (const pattern of patterns) {
1468
+ for (const match of content.matchAll(pattern)) {
1469
+ for (const [name] of (match[1] ?? "").matchAll(REFERENCED_NAME)) yield name;
1470
+ }
1471
+ }
1472
+ }
1473
+ function findReferencedVars(files, patterns) {
1474
+ const referenced = /* @__PURE__ */ new Map();
1475
+ for (const { path: path2, content } of files) {
1476
+ for (const name of referencedNames(content, patterns)) {
1477
+ const readers = referenced.get(name) ?? /* @__PURE__ */ new Set();
1478
+ readers.add(path2);
1479
+ referenced.set(name, readers);
1480
+ }
1481
+ }
1482
+ return referenced;
1483
+ }
1484
+ function findDeadVars(declaredKeys, referenced, allow = [], allowPatterns = []) {
1485
+ const alive = new Set(referenced);
1486
+ const allowed = new Set(allow);
1487
+ const isExternal = (key) => allowed.has(key) || allowPatterns.some((pattern) => pattern.test(key));
1488
+ return [...new Set(declaredKeys)].filter((key) => !alive.has(key) && !isExternal(key)).sort();
1489
+ }
1490
+ function auditEnvFileSecrets(parsed, options) {
1491
+ const secretFilled = parsed.entries.filter(
1492
+ (entry) => !entry.blank && options.secretPatterns.some((pattern) => pattern.test(entry.key))
1493
+ );
1494
+ const isPublic = (entry) => options.publicPrefixes.some((prefix) => entry.key.startsWith(prefix));
1495
+ return {
1496
+ mustBlank: secretFilled.filter((entry) => !isPublic(entry)),
1497
+ publicSecrets: secretFilled.filter(isPublic)
1498
+ };
1499
+ }
1500
+ function splitAllowlisted(locations, allowlist) {
1501
+ const allow = new Set(allowlist);
1502
+ const allowed = [];
1503
+ const flagged = [];
1504
+ for (const location of locations) (allow.has(location) ? allowed : flagged).push(location);
1505
+ return { allowed, flagged };
1506
+ }
1507
+ function isDockerfilePath(path2) {
1508
+ const name = path2.slice(path2.lastIndexOf("/") + 1);
1509
+ if (name.endsWith(".dockerignore") || name.endsWith(".md")) return false;
1510
+ return name === "dockerfile" || /^Dockerfile([.-][^/]+)?$/.test(name) || /\.[Dd]ockerfile$/.test(name);
1511
+ }
1512
+ function copySources(line) {
1513
+ const words = line.split(/\s+/).slice(1);
1514
+ const flags = words.findIndex((word) => !word.startsWith("--"));
1515
+ const rest = flags === -1 ? "" : words.slice(flags).join(" ");
1516
+ if (rest.startsWith("<<")) return [];
1517
+ if (rest.startsWith("[")) {
1518
+ try {
1519
+ const parsed = JSON.parse(rest);
1520
+ if (Array.isArray(parsed) && parsed.every((entry) => typeof entry === "string")) {
1521
+ return parsed.slice(0, -1);
1522
+ }
1523
+ } catch {
1524
+ }
1525
+ }
1526
+ return words.slice(flags === -1 ? words.length : flags).slice(0, -1).map((word) => /^(['"])(.*)\1$/.exec(word)?.[2] ?? word);
1527
+ }
1528
+ function parseDockerfile(content) {
1529
+ const froms = [];
1530
+ const contextCopySources = [];
1531
+ for (const [index, rawLine] of content.split("\n").entries()) {
1532
+ const line = rawLine.trim();
1533
+ const from = /^FROM\s+(\S+)/i.exec(line);
1534
+ if (from) {
1535
+ froms.push({ image: from[1] ?? "", line: index + 1 });
1536
+ continue;
1537
+ }
1538
+ if (/^COPY\s/i.test(line) && !/--from=/i.test(line)) {
1539
+ for (const source of copySources(line)) {
1540
+ if (!/[*?$]/.test(source)) contextCopySources.push({ source, line: index + 1 });
1541
+ }
1542
+ }
1543
+ }
1544
+ return { froms, contextCopySources };
1545
+ }
1546
+ function dockerNodeVersion(fromImage) {
1547
+ const match = /(?:^|\/)node:([0-9]+(?:\.[0-9]+){0,2})/.exec(fromImage);
1548
+ return match?.[1] ?? null;
1549
+ }
1550
+ function normalizeNodeVersion(raw) {
1551
+ const version = raw.trim().replace(/^v/i, "");
1552
+ return version === "" ? void 0 : version;
1553
+ }
1554
+ function nodeVersionAligned(dockerVersion, expected) {
1555
+ return expected === dockerVersion || expected.startsWith(`${dockerVersion}.`);
1556
+ }
1557
+ function dockerHardening(content) {
1558
+ const lines = content.split("\n").map((line) => line.trim());
1559
+ let lastFrom = -1;
1560
+ lines.forEach((line, index) => {
1561
+ if (/^FROM\s/i.test(line)) lastFrom = index;
1562
+ });
1563
+ const start = lastFrom === -1 ? 0 : lastFrom;
1564
+ const finalStage = lines.slice(start).map((text, offset) => ({ text, line: start + offset + 1 }));
1565
+ let user = null;
1566
+ let userLine = start + 1;
1567
+ for (const { text, line } of finalStage) {
1568
+ const match = /^USER\s+(\S+)/i.exec(text);
1569
+ if (match) {
1570
+ user = match[1] ?? null;
1571
+ userLine = line;
1572
+ }
1573
+ }
1574
+ const runsAsRoot = !user || /^(root|0)(:|$)/i.test(user);
1575
+ const ownsTree = finalStage.some(({ text }) => /chown\s+-R/i.test(text));
1576
+ const copiesWithoutChown = ownsTree ? [] : finalStage.filter(({ text }) => /^COPY\s/i.test(text) && !/--chown=/i.test(text)).map(({ text, line }) => ({ instruction: text.replace(/\s+/g, " "), line }));
1577
+ return { user, userLine, runsAsRoot, copiesWithoutChown };
1578
+ }
1579
+ function parseNxTargetRefs(content) {
1580
+ return [...content.matchAll(/\bnx run\s+([^\s:$]+):([^\s$]+)/g)].map((match) => ({
1581
+ project: match[1] ?? "",
1582
+ targetSpec: match[2] ?? "",
1583
+ line: lineAt(content, match.index)
1584
+ }));
1585
+ }
1586
+ function nxTargetCandidates(targetSpec) {
1587
+ const parts = targetSpec.split(":");
1588
+ const candidates = [targetSpec];
1589
+ if (parts.length > 1) candidates.push(parts.slice(0, -1).join(":"));
1590
+ return candidates;
1591
+ }
1592
+ function resolveApp(path2, projects) {
1593
+ let best = null;
1594
+ for (const project of projects) {
1595
+ if (path2 === project.root || path2.startsWith(`${project.root}/`)) {
1596
+ if (!best || project.root.length > best.root.length) best = project;
1597
+ }
1598
+ }
1599
+ return best ? best.name : null;
1600
+ }
1601
+
1602
+ // src/roles/env/repository.ts
1603
+ import { execFileSync } from "node:child_process";
1604
+ import { readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
1605
+ import { join as join7 } from "node:path";
1606
+ var MAX_BUFFER = 256 * 1024 * 1024;
1607
+ function trackedPaths(root) {
1608
+ const output = execFileSync("git", ["ls-files", "-z"], {
1609
+ cwd: root,
1610
+ encoding: "utf8",
1611
+ maxBuffer: MAX_BUFFER
1612
+ });
1613
+ return output.split("\0").filter(Boolean);
1614
+ }
1615
+ function loadNxTargets(root) {
1616
+ const nx = resolveBin(root, "nx");
1617
+ if (nx === void 0) return void 0;
1618
+ const runNx = (args) => execFileSync(nx, args, {
1619
+ cwd: root,
1620
+ encoding: "utf8",
1621
+ stdio: ["ignore", "pipe", "ignore"],
1622
+ maxBuffer: MAX_BUFFER,
1623
+ env: { ...process.env, NX_DAEMON: "false" }
1624
+ });
1625
+ try {
1626
+ const projects = new Set(JSON.parse(runNx(["show", "projects", "--json"])));
1627
+ const cache = /* @__PURE__ */ new Map();
1628
+ return (project) => {
1629
+ if (!projects.has(project)) return null;
1630
+ const known = cache.get(project);
1631
+ if (known) return known;
1632
+ const data2 = JSON.parse(runNx(["show", "project", project, "--json"]));
1633
+ const targets2 = new Set(Object.keys(data2.targets ?? {}));
1634
+ cache.set(project, targets2);
1635
+ return targets2;
1636
+ };
1637
+ } catch {
1638
+ return void 0;
1639
+ }
1640
+ }
1641
+ function openRepository(root) {
1642
+ let tracked;
1643
+ try {
1644
+ tracked = trackedPaths(root);
1645
+ } catch (error) {
1646
+ throw new Error(
1647
+ `git could not list the tracked files of ${root}: ${error instanceof Error ? error.message : String(error)}`,
1648
+ { cause: error }
1649
+ );
1650
+ }
1651
+ let nx = null;
1652
+ const hasNx = tracked.includes("nx.json");
1653
+ return {
1654
+ root,
1655
+ tracked,
1656
+ read(path2) {
1657
+ try {
1658
+ return readFileSync6(join7(root, path2), "utf8");
1659
+ } catch {
1660
+ return void 0;
1661
+ }
1662
+ },
1663
+ exists(path2) {
1664
+ try {
1665
+ statSync2(join7(root, path2));
1666
+ return true;
1667
+ } catch {
1668
+ return false;
1669
+ }
1670
+ },
1671
+ hasNx,
1672
+ nxTargets() {
1673
+ if (nx === null) nx = hasNx ? loadNxTargets(root) : void 0;
1674
+ return nx;
1675
+ }
1676
+ };
1677
+ }
1678
+ function loadProjects(view) {
1679
+ const byRoot = /* @__PURE__ */ new Map();
1680
+ for (const file of view.tracked) {
1681
+ const manifest = /(?:^|\/)(project|package)\.json$/.exec(file);
1682
+ if (!manifest) continue;
1683
+ const slash = file.lastIndexOf("/");
1684
+ if (slash === -1) continue;
1685
+ const root = file.slice(0, slash);
1686
+ const fromProjectJson = manifest[1] === "project";
1687
+ const existing = byRoot.get(root);
1688
+ if (existing && !(fromProjectJson && !existing.fromProjectJson)) continue;
1689
+ try {
1690
+ const name = JSON.parse(view.read(file) ?? "").name;
1691
+ if (typeof name === "string" && name !== "") byRoot.set(root, { name, fromProjectJson });
1692
+ } catch {
1693
+ }
1694
+ }
1695
+ return [...byRoot].map(([root, { name }]) => ({ root, name }));
1696
+ }
1697
+
1698
+ // src/roles/env/collect.ts
1699
+ var RULES = {
1700
+ "required-var-undeclared": {
1701
+ number: 1,
1702
+ severity: "blocking",
1703
+ help: "read by a required reader but declared in no contract: add it to the right .env*.example, or to externallyProvided if CI or a secret store provides it"
1704
+ },
1705
+ "optional-var-undeclared": {
1706
+ number: 1,
1707
+ severity: "info",
1708
+ help: "read by an optional reader and declared in no contract; a default applies"
1709
+ },
1710
+ "committed-env-file": {
1711
+ number: 2,
1712
+ severity: "blocking",
1713
+ help: "a real env file is committed: remove it from git (only .example contracts belong there; values come from a secret store)"
1714
+ },
1715
+ "var-not-in-app-contract": {
1716
+ number: 3,
1717
+ severity: "advisory",
1718
+ help: "read in this app but declared only in another app's contract: declare it in this app's own contract, or in a shared root one"
1719
+ },
1720
+ "dead-var": {
1721
+ number: 4,
1722
+ severity: "advisory",
1723
+ help: "declared but read nowhere in source: a candidate to remove, or add it to knownExternalVars if a tool or CI consumes it"
1724
+ },
1725
+ "public-secret-to-review": {
1726
+ number: 5,
1727
+ severity: "advisory",
1728
+ help: "a public-prefixed, secret-named variable carries a committed value: it ships to the browser by design, confirm it is genuinely public"
1729
+ },
1730
+ "secret-not-blanked": {
1731
+ number: 6,
1732
+ severity: "blocking",
1733
+ help: "a secret-named variable carries a committed value: blank it (leave the value empty) and move the value to a secret store"
1734
+ },
1735
+ "secret-allowlisted": {
1736
+ number: 6,
1737
+ severity: "advisory",
1738
+ help: "a secret-named variable carries a committed value, accepted by allowNonBlankedSecrets: confirm it stays non-secret"
1739
+ },
1740
+ "dead-copy-path": {
1741
+ number: 7,
1742
+ severity: "blocking",
1743
+ help: "this COPY source does not exist: remove the line or fix the path"
1744
+ },
1745
+ "nx-project-missing": {
1746
+ number: 8,
1747
+ severity: "blocking",
1748
+ help: "this Dockerfile runs an nx project that does not exist: fix or remove the reference"
1749
+ },
1750
+ "nx-target-missing": {
1751
+ number: 8,
1752
+ severity: "blocking",
1753
+ help: "this Dockerfile runs an nx target the project does not have: fix or remove the reference"
1754
+ },
1755
+ "node-version-mismatch": {
1756
+ number: 9,
1757
+ severity: "blocking",
1758
+ help: "the FROM node version differs from .node-version: align the tag"
1759
+ },
1760
+ "dockerfile-runs-as-root": {
1761
+ number: 10,
1762
+ severity: "blocking",
1763
+ help: "the final stage runs as root: set a non-root USER (USER node, or USER 1000:1000) so a compromise cannot tamper with the running image"
1764
+ },
1765
+ "copy-not-owned": {
1766
+ number: 10,
1767
+ severity: "blocking",
1768
+ help: "this COPY into the runtime stage has no --chown, so it lands root-owned under a non-root USER: add --chown (node:node, or 1000:1000 with --link) or a RUN chown -R"
1769
+ }
1770
+ };
1771
+ var RULE_ORDER = Object.keys(RULES);
1772
+ function inScope(path2, scope) {
1773
+ const underRoot = scope.roots.some((root) => {
1774
+ const cleaned = root.replace(/^\.\//, "").replace(/\/+$/, "");
1775
+ return cleaned === "." || cleaned === "" || path2.startsWith(`${cleaned}/`);
1776
+ });
1777
+ if (!underRoot) return false;
1778
+ const segments = path2.split("/");
1779
+ const name = segments.pop() ?? "";
1780
+ if (segments.some((segment) => scope.excludeDirs.includes(segment))) return false;
1781
+ return scope.ext.some((suffix) => name.endsWith(suffix)) && !scope.excludeSuffixes.some((suffix) => name.endsWith(suffix));
1782
+ }
1783
+ function* sourcesOf(view, scope) {
1784
+ for (const path2 of view.tracked) {
1785
+ if (!inScope(path2, scope)) continue;
1786
+ const content = view.read(path2);
1787
+ if (content !== void 0) yield { path: path2, content };
1788
+ }
1789
+ }
1790
+ function missingRoots(view, settings) {
1791
+ const roots = [...settings.scan.roots, ...settings.references.roots];
1792
+ return [
1793
+ ...new Set(
1794
+ roots.filter((root) => {
1795
+ const cleaned = root.replace(/^\.\//, "").replace(/\/+$/, "");
1796
+ if (cleaned === "." || cleaned === "") return false;
1797
+ return !view.tracked.some((path2) => path2.startsWith(`${cleaned}/`));
1798
+ })
1799
+ )
1800
+ ];
1801
+ }
1802
+ function finding(rule, fields) {
1803
+ const { name, file, line, detail, readIn, app } = fields;
1804
+ return {
1805
+ rule,
1806
+ number: RULES[rule].number,
1807
+ severity: RULES[rule].severity,
1808
+ name,
1809
+ ...file === void 0 ? {} : { file },
1810
+ ...line === void 0 ? {} : { line },
1811
+ ...detail === void 0 ? {} : { detail },
1812
+ ...readIn === void 0 ? {} : { readIn },
1813
+ ...app === void 0 ? {} : { app }
1814
+ };
1815
+ }
1816
+ function byReportOrder(left, right) {
1817
+ return RULE_ORDER.indexOf(left.rule) - RULE_ORDER.indexOf(right.rule) || (left.file ?? "").localeCompare(right.file ?? "") || (left.line ?? 0) - (right.line ?? 0) || left.name.localeCompare(right.name);
1818
+ }
1819
+ function readEnvFiles(view, settings) {
1820
+ const envGlobs = settings.envFiles.map(globToRegExp);
1821
+ const contractGlobs = settings.contracts.map(globToRegExp);
1822
+ const envFiles = view.tracked.filter((path2) => envGlobs.some((glob) => glob.test(path2)));
1823
+ const contracts = view.tracked.filter((path2) => contractGlobs.some((glob) => glob.test(path2)));
1824
+ const parsed = /* @__PURE__ */ new Map();
1825
+ for (const path2 of /* @__PURE__ */ new Set([...envFiles, ...contracts])) {
1826
+ const content = view.read(path2);
1827
+ if (content !== void 0) parsed.set(path2, parseEnvFile(content));
1828
+ }
1829
+ return { envFiles, contracts, parsed };
1830
+ }
1831
+ function secretFindings(parsed, settings) {
1832
+ const options = {
1833
+ secretPatterns: settings.secretNamePatterns,
1834
+ publicPrefixes: settings.publicPrefixes
1835
+ };
1836
+ const findings = [];
1837
+ const mustBlank = /* @__PURE__ */ new Map();
1838
+ for (const [file, content] of parsed) {
1839
+ const audit = auditEnvFileSecrets(content, options);
1840
+ for (const entry of audit.publicSecrets) {
1841
+ findings.push(finding("public-secret-to-review", { name: entry.key, file, line: entry.line }));
1842
+ }
1843
+ for (const entry of audit.mustBlank) {
1844
+ mustBlank.set(`${file}:${entry.key}`, { file, line: entry.line, name: entry.key });
1845
+ }
1846
+ }
1847
+ const { allowed, flagged } = splitAllowlisted(
1848
+ [...mustBlank.keys()],
1849
+ settings.allowNonBlankedSecrets
1850
+ );
1851
+ for (const location of flagged) {
1852
+ const entry = mustBlank.get(location);
1853
+ if (entry) findings.push(finding("secret-not-blanked", entry));
1854
+ }
1855
+ for (const location of allowed) {
1856
+ const entry = mustBlank.get(location);
1857
+ if (entry) findings.push(finding("secret-allowlisted", entry));
1858
+ }
1859
+ return findings;
1860
+ }
1861
+ function allReadNames(derived, referenced) {
1862
+ return /* @__PURE__ */ new Set([...referenced.keys(), ...derived.required.keys(), ...derived.optional.keys()]);
1863
+ }
1864
+ function misplacedFindings(view, settings, derived, referenced, contracts, declared) {
1865
+ const projects = loadProjects(view);
1866
+ const readsByApp = /* @__PURE__ */ new Map();
1867
+ const addRead = (name, path2) => {
1868
+ const app = resolveApp(path2, projects);
1869
+ if (!app) return;
1870
+ const reads = readsByApp.get(app) ?? /* @__PURE__ */ new Map();
1871
+ if (!reads.has(name)) reads.set(name, path2);
1872
+ readsByApp.set(app, reads);
1873
+ };
1874
+ for (const [name, sites] of derived.required) for (const site of sites) addRead(name, site.path);
1875
+ for (const [name, paths] of referenced) for (const path2 of paths) addRead(name, path2);
1876
+ const shared = /* @__PURE__ */ new Set();
1877
+ const keysByApp = /* @__PURE__ */ new Map();
1878
+ for (const { path: path2, parsed } of contracts) {
1879
+ const app = resolveApp(path2, projects);
1880
+ const keys = app === null ? shared : keysByApp.get(app) ?? /* @__PURE__ */ new Set();
1881
+ for (const key of parsed.keys) keys.add(key);
1882
+ if (app !== null) keysByApp.set(app, keys);
1883
+ }
1884
+ const external = new Set(settings.externallyProvided);
1885
+ const findings = [];
1886
+ for (const [app, reads] of readsByApp) {
1887
+ const own = keysByApp.get(app);
1888
+ if (!own) continue;
1889
+ for (const [name, path2] of reads) {
1890
+ if (declared.has(name) && !own.has(name) && !shared.has(name) && !external.has(name)) {
1891
+ findings.push(
1892
+ finding("var-not-in-app-contract", {
1893
+ name,
1894
+ file: path2,
1895
+ app,
1896
+ detail: `read in ${app}`
1897
+ })
1898
+ );
1899
+ }
1900
+ }
1901
+ }
1902
+ return findings;
1903
+ }
1904
+ function codeFindings(view, settings, contracts) {
1905
+ const derived = deriveVars(sourcesOf(view, settings.scan), settings.readers);
1906
+ const inventory = {
1907
+ required: [...derived.required].map(([name, sites]) => ({ name, files: sites.length })).sort((left, right) => left.name.localeCompare(right.name)),
1908
+ optional: [...derived.optional.keys()].sort(),
1909
+ contracts: contracts.map(({ path: path2, parsed }) => ({ path: path2, keys: parsed.keys.length }))
1910
+ };
1911
+ const counts = { required: derived.required.size, optional: derived.optional.size };
1912
+ if (contracts.length === 0) return { findings: [], inventory, counts, compared: false };
1913
+ const declaredAt = /* @__PURE__ */ new Map();
1914
+ for (const { path: path2, parsed } of contracts) {
1915
+ for (const entry of parsed.entries) {
1916
+ if (!declaredAt.has(entry.key)) declaredAt.set(entry.key, { file: path2, line: entry.line });
1917
+ }
1918
+ }
1919
+ const declared = new Set(declaredAt.keys());
1920
+ const { unaccounted, optionalUndeclared } = diffContract(
1921
+ derived,
1922
+ declared,
1923
+ settings.externallyProvided
1924
+ );
1925
+ const referenced = findReferencedVars(
1926
+ sourcesOf(view, settings.references),
1927
+ settings.referencePatterns
1928
+ );
1929
+ const dead = findDeadVars(
1930
+ declared,
1931
+ allReadNames(derived, referenced),
1932
+ settings.knownExternalVars,
1933
+ settings.knownExternalPatterns
1934
+ );
1935
+ const findings = [
1936
+ ...unaccounted.map((name) => {
1937
+ const sites = derived.required.get(name) ?? [];
1938
+ return finding("required-var-undeclared", {
1939
+ name,
1940
+ file: sites[0]?.path,
1941
+ line: sites[0]?.line,
1942
+ readIn: sites.map((site) => site.path)
1943
+ });
1944
+ }),
1945
+ ...misplacedFindings(view, settings, derived, referenced, contracts, declared),
1946
+ ...dead.map((name) => finding("dead-var", { name, ...declaredAt.get(name) })),
1947
+ ...optionalUndeclared.map((name) => {
1948
+ const site = derived.optional.get(name)?.[0];
1949
+ return finding("optional-var-undeclared", { name, file: site?.path, line: site?.line });
1950
+ })
1951
+ ];
1952
+ return { findings, inventory, counts, compared: true };
1953
+ }
1954
+ function dockerFindings(view, settings) {
1955
+ const excluded = (path2) => settings.docker.excludePaths.some((prefix) => path2.startsWith(prefix));
1956
+ const dockerfiles = view.tracked.filter((path2) => isDockerfilePath(path2) && !excluded(path2));
1957
+ const rawNodeVersion = view.read(settings.nodeVersionFile);
1958
+ const expected = rawNodeVersion === void 0 ? void 0 : normalizeNodeVersion(rawNodeVersion);
1959
+ const findings = [];
1960
+ const notes = [];
1961
+ let uncheckedNxRefs = 0;
1962
+ let nxUnavailable = false;
1963
+ for (const file of dockerfiles) {
1964
+ const content = view.read(file);
1965
+ if (content === void 0) continue;
1966
+ const { froms, contextCopySources } = parseDockerfile(content);
1967
+ for (const { image, line } of froms) {
1968
+ const version = dockerNodeVersion(image);
1969
+ if (version && expected && !nodeVersionAligned(version, expected)) {
1970
+ findings.push(
1971
+ finding("node-version-mismatch", {
1972
+ name: `node:${version}`,
1973
+ file,
1974
+ line,
1975
+ detail: `expected ${expected} (${settings.nodeVersionFile})`
1976
+ })
1977
+ );
1978
+ }
1979
+ }
1980
+ for (const { source, line } of contextCopySources) {
1981
+ if (!view.exists(join8(".", source)) && !view.exists(join8(dirname(file), source))) {
1982
+ findings.push(finding("dead-copy-path", { name: source, file, line }));
1983
+ }
1984
+ }
1985
+ const { runsAsRoot, user, userLine, copiesWithoutChown } = dockerHardening(content);
1986
+ if (runsAsRoot && !settings.docker.allowRoot.includes(file)) {
1987
+ findings.push(
1988
+ finding("dockerfile-runs-as-root", {
1989
+ name: user ? `USER ${user}` : "no USER set",
1990
+ file,
1991
+ line: userLine
1992
+ })
1993
+ );
1994
+ }
1995
+ for (const { instruction, line } of copiesWithoutChown) {
1996
+ findings.push(finding("copy-not-owned", { name: instruction, file, line }));
1997
+ }
1998
+ const refs = parseNxTargetRefs(content);
1999
+ if (refs.length === 0) continue;
2000
+ const lookup = view.hasNx ? view.nxTargets() : void 0;
2001
+ if (lookup === void 0) {
2002
+ uncheckedNxRefs += refs.length;
2003
+ nxUnavailable ||= view.hasNx;
2004
+ continue;
2005
+ }
2006
+ for (const { project, targetSpec, line } of refs) {
2007
+ const targets2 = lookup(project);
2008
+ if (targets2 === null) {
2009
+ findings.push(finding("nx-project-missing", { name: project, file, line }));
2010
+ } else if (!nxTargetCandidates(targetSpec).some((target) => targets2.has(target))) {
2011
+ findings.push(
2012
+ finding("nx-target-missing", { name: `${project}:${targetSpec}`, file, line })
2013
+ );
2014
+ }
2015
+ }
2016
+ }
2017
+ if (dockerfiles.length === 0)
2018
+ notes.push("no Dockerfile is tracked: rules 7 to 10 had nothing to check");
2019
+ if (dockerfiles.length > 0 && expected === void 0) {
2020
+ notes.push(
2021
+ `no ${settings.nodeVersionFile} at the repository root: rule 9 had no version to compare the FROM tags with`
2022
+ );
2023
+ }
2024
+ if (!view.hasNx) {
2025
+ notes.push(
2026
+ uncheckedNxRefs > 0 ? `not an nx workspace (no nx.json): rule 8 skipped, ${uncheckedNxRefs} \`nx run\` reference(s) not checked` : "not an nx workspace (no nx.json): rule 8 skipped"
2027
+ );
2028
+ } else if (nxUnavailable) {
2029
+ notes.push(
2030
+ `the repository's own nx could not run (not installed?): rule 8 skipped, ${uncheckedNxRefs} \`nx run\` reference(s) not checked`
2031
+ );
2032
+ }
2033
+ return { findings, notes, dockerfiles: dockerfiles.length, nodeVersion: expected ?? null };
2034
+ }
2035
+ function collectEnvReport(view, settings, level, config) {
2036
+ const missing = missingRoots(view, settings);
2037
+ if (missing.length > 0) {
2038
+ throw new Error(
2039
+ `${config ?? "the env settings"}: scan root(s) holding no tracked file: ${missing.join(", ")}`
2040
+ );
2041
+ }
2042
+ const { envFiles, contracts, parsed } = readEnvFiles(view, settings);
2043
+ const contractFiles = contracts.flatMap((path2) => {
2044
+ const content = parsed.get(path2);
2045
+ return content ? [{ path: path2, parsed: content }] : [];
2046
+ });
2047
+ const code = codeFindings(view, settings, contractFiles);
2048
+ const docker = dockerFindings(view, settings);
2049
+ const orphans = findOrphanEnvFiles(envFiles, settings.allowedCommittedEnvFiles);
2050
+ const notes = [];
2051
+ if (!code.compared) {
2052
+ notes.push(
2053
+ `no contract is committed (${settings.contracts.join(", ")}): rules 1, 3 and 4 had nothing to compare the code with`
2054
+ );
2055
+ }
2056
+ if (code.counts.required === 0) {
2057
+ notes.push(
2058
+ `no required reader call found (${settings.readers.required.join(", ")}): rule 1 had nothing to check; name the repository's own helper in readers.required`
2059
+ );
2060
+ }
2061
+ notes.push(...docker.notes);
2062
+ return {
2063
+ level,
2064
+ config,
2065
+ scanned: settings.scan.roots,
2066
+ counts: {
2067
+ required: code.counts.required,
2068
+ optional: code.counts.optional,
2069
+ contracts: contractFiles.length,
2070
+ envFiles: envFiles.length,
2071
+ dockerfiles: docker.dockerfiles
2072
+ },
2073
+ nodeVersion: docker.nodeVersion,
2074
+ findings: [
2075
+ ...code.findings,
2076
+ ...orphans.map((file) => finding("committed-env-file", { name: file, file })),
2077
+ ...secretFindings(parsed, settings),
2078
+ ...docker.findings
2079
+ ].sort(byReportOrder),
2080
+ notes,
2081
+ inventory: code.inventory
2082
+ };
2083
+ }
2084
+ function blockingFindings(report) {
2085
+ return report.findings.filter((entry) => entry.severity === "blocking");
2086
+ }
2087
+
2088
+ // src/roles/env/configs/resolve.ts
2089
+ import { existsSync as existsSync6 } from "node:fs";
2090
+ import { join as join9 } from "node:path";
2091
+ import { pathToFileURL } from "node:url";
2092
+ function explainUnreadableEnvConfig(error) {
2093
+ const message = error instanceof Error ? error.message : String(error);
2094
+ const code = error.code;
2095
+ const missingPackage = (code === "ERR_PACKAGE_PATH_NOT_EXPORTED" || code === "ERR_MODULE_NOT_FOUND") && message.includes("@hublo/sentinel");
2096
+ if (!missingPackage) return message;
2097
+ return `${message}
2098
+ This repository cannot import @hublo/sentinel/env: it does not install sentinel, or installs a release without the env role. Export the same object without defineEnv (export default { ... }), which sentinel validates the same way.`;
2099
+ }
2100
+ function unique(values) {
2101
+ return [...new Set(values)];
2102
+ }
2103
+ function scopeOf(base, input) {
2104
+ return {
2105
+ roots: input?.roots ?? base.roots,
2106
+ ext: input?.ext ?? base.ext,
2107
+ excludeSuffixes: input?.excludeSuffixes ?? base.excludeSuffixes,
2108
+ excludeDirs: input?.excludeDirs ?? base.excludeDirs
2109
+ };
2110
+ }
2111
+ function asPattern(entry) {
2112
+ return entry instanceof RegExp ? entry : new RegExp(entry);
2113
+ }
2114
+ function resolveEnvSettings(input) {
2115
+ return {
2116
+ readers: {
2117
+ required: unique([...envDefaults.readers.required, ...input.readers?.required ?? []]),
2118
+ optional: unique([...envDefaults.readers.optional, ...input.readers?.optional ?? []])
2119
+ },
2120
+ scan: scopeOf(envDefaults.scan, input.scan),
2121
+ references: scopeOf(envDefaults.references, input.references),
2122
+ referencePatterns: referencePatterns(),
2123
+ secretNamePatterns: secretNamePatterns(),
2124
+ publicPrefixes: [...envDefaults.publicPrefixes],
2125
+ envFiles: unique([...envDefaults.envFiles, ...input.envFiles ?? []]),
2126
+ contracts: unique([...envDefaults.contracts, ...input.contracts ?? []]),
2127
+ nodeVersionFile: envDefaults.nodeVersionFile,
2128
+ externallyProvided: input.externallyProvided ?? [],
2129
+ knownExternalVars: input.knownExternalVars ?? [],
2130
+ knownExternalPatterns: (input.knownExternalPatterns ?? []).map(asPattern),
2131
+ allowNonBlankedSecrets: input.allowNonBlankedSecrets ?? [],
2132
+ allowedCommittedEnvFiles: input.allowedCommittedEnvFiles ?? [],
2133
+ docker: {
2134
+ allowRoot: input.docker?.allowRoot ?? [],
2135
+ excludePaths: input.docker?.excludePaths ?? []
2136
+ }
2137
+ };
2138
+ }
2139
+ async function loadEnvConfig(root) {
2140
+ const path2 = join9(root, ENV_CONFIG_FILE);
2141
+ if (!existsSync6(path2)) {
2142
+ return { settings: resolveEnvSettings({}), from: null, unreadable: null };
2143
+ }
2144
+ try {
2145
+ const loaded = await import(pathToFileURL(path2).href);
2146
+ const declared = defineEnv(loaded.default ?? {});
2147
+ return { settings: resolveEnvSettings(declared), from: ENV_CONFIG_FILE, unreadable: null };
2148
+ } catch (error) {
2149
+ return {
2150
+ settings: resolveEnvSettings({}),
2151
+ from: null,
2152
+ unreadable: `${ENV_CONFIG_FILE}: ${explainUnreadableEnvConfig(error)}`
2153
+ };
2154
+ }
2155
+ }
2156
+
2157
+ // src/roles/env/report.ts
2158
+ var LEVEL_MEANING = {
2159
+ info: "report only",
2160
+ warning: "report, and annotate in GitHub Actions; never fails",
2161
+ error: "fails on a blocking finding (rules 1, 2, 6, 7 to 10)"
2162
+ };
2163
+ function locationOf(entry) {
2164
+ if (entry.file === void 0) return "";
2165
+ return entry.line === void 0 ? entry.file : `${entry.file}:${entry.line}`;
2166
+ }
2167
+ function groupByRule(findings) {
2168
+ const groups = /* @__PURE__ */ new Map();
2169
+ for (const entry of findings) {
2170
+ const group = groups.get(entry.rule) ?? [];
2171
+ group.push(entry);
2172
+ groups.set(entry.rule, group);
2173
+ }
2174
+ return groups;
2175
+ }
2176
+ function describe(entry) {
2177
+ const where = locationOf(entry);
2178
+ const subject = entry.rule === "committed-env-file" ? "" : entry.name;
2179
+ const more = entry.readIn !== void 0 && entry.readIn.length > 1 ? ` (read in ${entry.readIn.length} files)` : entry.detail ? ` (${entry.detail})` : "";
2180
+ return [where, subject].filter(Boolean).join(" ") + more;
2181
+ }
2182
+ function verdictOf(report) {
2183
+ const blocking = blockingFindings(report).length;
2184
+ if (blocking === 0) return "no blocking finding";
2185
+ const counted = `${blocking} blocking finding${blocking === 1 ? "" : "s"}`;
2186
+ return report.level === "error" ? `${counted}: failing` : `${counted}: reported, not failing`;
2187
+ }
2188
+ function headline(report) {
2189
+ const { counts } = report;
2190
+ return `${counts.envFiles} env file(s), ${counts.contracts} contract(s), ${counts.required} required and ${counts.optional} optional variable(s) read, ${counts.dockerfiles} Dockerfile(s)` + (report.nodeVersion ? `, node ${report.nodeVersion}` : "");
2191
+ }
2192
+ function renderConsole(report, palette2) {
2193
+ const lines = [];
2194
+ lines.push(
2195
+ `${palette2.strong("env")} ${palette2.dim(`(level ${report.level}: ${LEVEL_MEANING[report.level]})`)}`
2196
+ );
2197
+ lines.push(` ${headline(report)}`);
2198
+ lines.push(
2199
+ ` ${palette2.dim(`settings: ${report.config ?? "sentinel defaults (no env.config.mjs)"}`)}`
2200
+ );
2201
+ const listed = report.findings.filter((entry) => entry.severity !== "info");
2202
+ const informational = report.findings.length - listed.length;
2203
+ for (const [rule, entries] of groupByRule(listed)) {
2204
+ const meta = RULES[rule];
2205
+ const mark = meta.severity === "blocking" ? palette2.fail("\u2717") : palette2.warn("!");
2206
+ lines.push("");
2207
+ lines.push(
2208
+ ` ${mark} ${palette2.strong(`[${rule}]`)} ${palette2.dim(`rule ${meta.number}, ${meta.severity}, ${entries.length}`)}`
2209
+ );
2210
+ lines.push(` ${palette2.dim(meta.help)}`);
2211
+ for (const entry of entries) lines.push(` ${describe(entry)}`);
2212
+ }
2213
+ if (informational > 0) {
2214
+ lines.push("");
2215
+ lines.push(
2216
+ ` ${palette2.dim(`info: ${informational} optional variable(s) in no contract (a default applies); listed in --json`)}`
2217
+ );
2218
+ }
2219
+ for (const note of report.notes) lines.push(` ${palette2.dim(`note: ${note}`)}`);
2220
+ lines.push("");
2221
+ const blocking = blockingFindings(report).length;
2222
+ const verdict = verdictOf(report);
2223
+ lines.push(
2224
+ ` ${blocking === 0 ? palette2.ok(verdict) : report.level === "error" ? palette2.fail(verdict) : palette2.warn(verdict)}`
2225
+ );
2226
+ return `${lines.join("\n")}
2227
+ `;
2228
+ }
2229
+ function cell(text) {
2230
+ return text.replace(/\|/g, "\\|");
2231
+ }
2232
+ function renderMarkdown(report) {
2233
+ const lines = ["<!-- sentinel-env-report -->", "## Env conformance", ""];
2234
+ lines.push(`**Level:** \`${report.level}\` (${LEVEL_MEANING[report.level]}) `);
2235
+ lines.push(`**Scanned:** ${headline(report)} `);
2236
+ lines.push(`**Settings:** ${report.config ? `\`${report.config}\`` : "sentinel defaults"}`);
2237
+ lines.push("");
2238
+ const listed = report.findings.filter((entry) => entry.severity !== "info");
2239
+ const groups = groupByRule(listed);
2240
+ if (groups.size === 0) {
2241
+ lines.push("No finding.");
2242
+ } else {
2243
+ lines.push("| rule | finding | severity | count |", "| --- | --- | --- | --- |");
2244
+ for (const [rule, entries] of groups) {
2245
+ const meta = RULES[rule];
2246
+ lines.push(`| ${meta.number} | \`${rule}\` | ${meta.severity} | ${entries.length} |`);
2247
+ }
2248
+ for (const [rule, entries] of groups) {
2249
+ lines.push("", `<details><summary><code>${rule}</code>: ${entries.length}</summary>`, "");
2250
+ lines.push(cell(RULES[rule].help), "");
2251
+ for (const entry of entries) lines.push(`- \`${cell(describe(entry))}\``);
2252
+ lines.push("", "</details>");
2253
+ }
2254
+ }
2255
+ if (report.notes.length > 0) {
2256
+ lines.push("");
2257
+ for (const note of report.notes) lines.push(`> ${note}`);
2258
+ }
2259
+ lines.push("", `**Verdict:** ${verdictOf(report)}`);
2260
+ return `${lines.join("\n")}
2261
+ `;
2262
+ }
2263
+ function reportDocument(report) {
2264
+ return {
2265
+ level: report.level,
2266
+ verdict: verdictOf(report),
2267
+ blocking: blockingFindings(report).length,
2268
+ config: report.config,
2269
+ scanned: report.scanned,
2270
+ counts: report.counts,
2271
+ nodeVersion: report.nodeVersion,
2272
+ notes: report.notes,
2273
+ findings: report.findings,
2274
+ inventory: report.inventory
2275
+ };
2276
+ }
2277
+ function renderJson(report) {
2278
+ return `${JSON.stringify(reportDocument(report), null, 2)}
2279
+ `;
2280
+ }
2281
+ function escapeData(text) {
2282
+ return text.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
2283
+ }
2284
+ function escapeProperty(text) {
2285
+ return escapeData(text).replace(/:/g, "%3A").replace(/,/g, "%2C");
2286
+ }
2287
+ var GITHUB_ANNOTATION_CAP = 10;
2288
+ function summaryAnnotation(command, count, level) {
2289
+ const what = command === "error" ? "blocking findings" : level === "error" ? "advisory findings" : "findings";
2290
+ const message = `${count} ${what} (GitHub shows at most ${GITHUB_ANNOTATION_CAP} ${command} annotations per step): the full list is in the env report, in this step's log, and in env-report/env.md with --output markdown`;
2291
+ return `::${command} title=${escapeProperty(`env: ${count} ${what}`)}::${escapeData(message)}`;
2292
+ }
2293
+ function renderAnnotations(report) {
2294
+ if (report.level === "info") return [];
2295
+ const annotations = report.findings.filter((entry) => entry.severity !== "info").map((entry) => {
2296
+ const command = report.level === "error" && entry.severity === "blocking" ? "error" : "warning";
2297
+ const properties = [
2298
+ entry.file === void 0 ? void 0 : `file=${escapeProperty(entry.file)}`,
2299
+ entry.line === void 0 ? void 0 : `line=${entry.line}`,
2300
+ `title=${escapeProperty(`env rule ${entry.number}: ${entry.rule}`)}`
2301
+ ].filter(Boolean);
2302
+ const message = `${entry.name}${entry.detail ? ` (${entry.detail})` : ""}: ${RULES[entry.rule].help}`;
2303
+ return { command, line: `::${command} ${properties.join(",")}::${escapeData(message)}` };
2304
+ });
2305
+ const summaries = ["error", "warning"].flatMap((command) => {
2306
+ const count = annotations.filter((entry) => entry.command === command).length;
2307
+ return count > GITHUB_ANNOTATION_CAP ? [summaryAnnotation(command, count, report.level)] : [];
2308
+ });
2309
+ return [...summaries, ...annotations.map((entry) => entry.line)];
2310
+ }
2311
+
2312
+ // src/roles/env/adapters/conformance/conformance.adapter.ts
2313
+ function shapesOf(output) {
2314
+ const asked = [
2315
+ ...new Set(
2316
+ (output ?? "console").split(",").map((one) => one.trim()).filter((one) => one !== "")
2317
+ )
2318
+ ];
2319
+ return {
2320
+ known: asked.filter((one) => ENV_SHAPES.includes(one)),
2321
+ unknown: asked.filter((one) => !ENV_SHAPES.includes(one))
2322
+ };
2323
+ }
2324
+ function writeShapes(root, report, shapes) {
2325
+ const files = shapes.filter((shape) => shape !== "console");
2326
+ if (files.length === 0) return [];
2327
+ const directory = join10(root, REPORT_DIR2);
2328
+ mkdirSync(directory, { recursive: true });
2329
+ for (const [shape, name] of Object.entries(SHAPE_FILE)) {
2330
+ const path2 = join10(directory, name);
2331
+ if (!files.includes(shape) && existsSync7(path2)) unlinkSync(path2);
2332
+ }
2333
+ return files.map((shape) => {
2334
+ const name = SHAPE_FILE[shape];
2335
+ writeFileSync2(
2336
+ join10(directory, name),
2337
+ shape === "json" ? renderJson(report) : renderMarkdown(report)
2338
+ );
2339
+ return `${REPORT_DIR2}/${name}`;
2340
+ });
2341
+ }
2342
+ function oneLine(message) {
2343
+ const [head = "", ...rest] = message.split("\n").map((line) => line.trim().replace(/^- /, "")).filter((line) => line !== "");
2344
+ return rest.length === 0 ? head : `${head} ${rest.join("; ")}`;
2345
+ }
2346
+ var ConformanceAdapter = class extends BaseAdapter {
2347
+ target = TARGET2;
2348
+ runner = "conformance";
2349
+ /** A repository has no stack, so every preset applies: the role never reads one. */
2350
+ appliesTo(_preset) {
2351
+ return true;
2352
+ }
2353
+ /** Nothing to adopt: the role reads the repository and writes no config of its own. */
2354
+ plan() {
2355
+ return {
2356
+ operations: [],
2357
+ blocked: "env has no --init: it writes no config. An optional env.config.mjs at the repository root declares exceptions; run it with `sentinel --run --env`."
2358
+ };
2359
+ }
2360
+ /** `--run`: check, print the report, write the asked files, annotate. */
2361
+ async run(ctx) {
2362
+ return this.check(ctx, true);
2363
+ }
2364
+ /** `--run --json`: the same check, for a machine. Prints nothing on stdout. */
2365
+ async report(ctx) {
2366
+ return this.check(ctx, false);
2367
+ }
2368
+ say(message) {
2369
+ process.stderr.write(`sentinel env: ${message}
2370
+ `);
2371
+ }
2372
+ async check(ctx, printing) {
2373
+ const level = ctx.envLevel ?? DEFAULT_ENV_LEVEL;
2374
+ const { known, unknown } = shapesOf(ctx.output);
2375
+ if (unknown.length > 0) {
2376
+ this.say(
2377
+ `writes ${ENV_SHAPES.join(", ")}; not ${unknown.join(", ")}, which another role may write.`
2378
+ );
2379
+ }
2380
+ const refuse = (message) => {
2381
+ this.say(message);
2382
+ return { ok: false, code: 1, metrics: { level, error: oneLine(message) } };
2383
+ };
2384
+ const { settings, from, unreadable } = await loadEnvConfig(ctx.cwd);
2385
+ if (unreadable) return refuse(unreadable);
2386
+ let report;
2387
+ try {
2388
+ report = collectEnvReport(openRepository(ctx.cwd), settings, level, from);
2389
+ } catch (error) {
2390
+ return refuse(error instanceof Error ? error.message : String(error));
2391
+ }
2392
+ if (printing) {
2393
+ const written = writeShapes(ctx.cwd, report, known);
2394
+ process.stdout.write(renderConsole(report, palette(process.stdout)));
2395
+ if (written.length > 0) process.stdout.write(`
2396
+ written: ${written.join(", ")}
2397
+ `);
2398
+ }
2399
+ if (ctx.githubActions) {
2400
+ const annotations = renderAnnotations(report);
2401
+ if (annotations.length > 0) {
2402
+ const stream = printing ? process.stdout : process.stderr;
2403
+ stream.write(`${annotations.join("\n")}
2404
+ `);
2405
+ }
2406
+ }
2407
+ const failing = level === "error" && blockingFindings(report).length > 0;
2408
+ return { ok: !failing, code: failing ? 1 : 0, metrics: reportDocument(report) };
2409
+ }
2410
+ };
2411
+
2412
+ // src/roles/env/register.ts
2413
+ function registerEnv() {
2414
+ register(new ConformanceAdapter());
2415
+ setDefaultRunner("env", "conformance");
2416
+ }
2417
+
1292
2418
  // src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
1293
2419
  import { spawnSync as spawnSync3 } from "node:child_process";
1294
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
1295
- import { join as join11 } from "node:path";
2420
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
2421
+ import { join as join15 } from "node:path";
1296
2422
  import { isDeepStrictEqual } from "node:util";
1297
2423
 
1298
2424
  // src/core/config/has-source.ts
1299
2425
  import { readdirSync } from "node:fs";
1300
- import { extname, join as join7, relative as relative2 } from "node:path";
2426
+ import { extname, join as join11, relative as relative2 } from "node:path";
1301
2427
  var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
1302
2428
  "node_modules",
1303
2429
  "dist",
@@ -1348,7 +2474,7 @@ function hasSourceFiles(cwd, extensions) {
1348
2474
  }
1349
2475
  for (const entry of entries) {
1350
2476
  if (entry.isDirectory()) {
1351
- if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join7(dir, entry.name));
2477
+ if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join11(dir, entry.name));
1352
2478
  continue;
1353
2479
  }
1354
2480
  if (wanted.has(extname(entry.name))) return true;
@@ -1369,7 +2495,7 @@ function listSourceFiles(cwd, extensions) {
1369
2495
  continue;
1370
2496
  }
1371
2497
  for (const entry of entries) {
1372
- const full = join7(dir, entry.name);
2498
+ const full = join11(dir, entry.name);
1373
2499
  if (entry.isDirectory()) {
1374
2500
  if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
1375
2501
  continue;
@@ -1388,8 +2514,8 @@ function scopeFlags(paths, options = []) {
1388
2514
  }
1389
2515
 
1390
2516
  // 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";
2517
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
2518
+ import { join as join12 } from "node:path";
1393
2519
  import { parseSync } from "oxc-parser";
1394
2520
  import { format } from "oxfmt";
1395
2521
 
@@ -1526,8 +2652,8 @@ async function pinDeliberateImportOrder(cwd, options) {
1526
2652
  if (!options.sortImports) return [];
1527
2653
  const pinned = [];
1528
2654
  for (const file of listSourceFiles(cwd, CODE_EXTENSIONS).sort()) {
1529
- const path2 = join8(cwd, file);
1530
- const text = readFileSync6(path2, "utf8");
2655
+ const path2 = join12(cwd, file);
2656
+ const text = readFileSync7(path2, "utf8");
1531
2657
  if (!text.includes("eslint-disable") && !MODULE_NAMES.some((name) => text.includes(name)))
1532
2658
  continue;
1533
2659
  const before = readImports(file, text);
@@ -1535,7 +2661,7 @@ async function pinDeliberateImportOrder(cwd, options) {
1535
2661
  const sorted = await format(file, text, options);
1536
2662
  if (sorted.errors.length > 0) continue;
1537
2663
  if (!wouldMove(before, readImports(file, sorted.code))) continue;
1538
- writeFileSync2(path2, pin(text, before));
2664
+ writeFileSync3(path2, pin(text, before));
1539
2665
  pinned.push(file);
1540
2666
  }
1541
2667
  return pinned;
@@ -1569,8 +2695,8 @@ function writesWhenRewritten(name, command) {
1569
2695
  var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
1570
2696
 
1571
2697
  // src/roles/format/inherited-ignores.ts
1572
- import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
1573
- import { join as join9 } from "node:path";
2698
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
2699
+ import { join as join13 } from "node:path";
1574
2700
  var ROOT_IGNORE_FILE = ".prettierignore";
1575
2701
  function isPattern(line) {
1576
2702
  const trimmed = line.trim();
@@ -1586,11 +2712,11 @@ function toModulePattern(pattern) {
1586
2712
  }
1587
2713
  function inheritedIgnorePatterns(workspaceRoot) {
1588
2714
  if (!workspaceRoot) return [];
1589
- const path2 = join9(workspaceRoot, ROOT_IGNORE_FILE);
1590
- if (!existsSync6(path2)) return [];
2715
+ const path2 = join13(workspaceRoot, ROOT_IGNORE_FILE);
2716
+ if (!existsSync8(path2)) return [];
1591
2717
  let contents;
1592
2718
  try {
1593
- contents = readFileSync7(path2, "utf8");
2719
+ contents = readFileSync8(path2, "utf8");
1594
2720
  } catch {
1595
2721
  return [];
1596
2722
  }
@@ -1635,8 +2761,8 @@ function needsSvelteCompiler(output) {
1635
2761
  }
1636
2762
 
1637
2763
  // src/roles/format/prettier-config.ts
1638
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
1639
- import { join as join10 } from "node:path";
2764
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
2765
+ import { join as join14 } from "node:path";
1640
2766
  var UNSUPPORTED = /* @__PURE__ */ new Set(["plugins", "parser", "filepath", "rangeStart", "rangeEnd"]);
1641
2767
  function toOxfmtOverrides(value) {
1642
2768
  if (!Array.isArray(value)) return { overrides: [], unresolved: [] };
@@ -1661,14 +2787,14 @@ function toOxfmtOverrides(value) {
1661
2787
  return { overrides, unresolved };
1662
2788
  }
1663
2789
  function readPrettierSettings(cwd) {
1664
- const file = PRETTIER_CONFIG_FILES.find((name) => existsSync7(join10(cwd, name)));
2790
+ const file = PRETTIER_CONFIG_FILES.find((name) => existsSync9(join14(cwd, name)));
1665
2791
  if (!file) return { options: {}, unresolved: [] };
1666
2792
  if (/\.(js|cjs|mjs)$/.test(file)) {
1667
2793
  return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
1668
2794
  }
1669
2795
  let parsed;
1670
2796
  try {
1671
- parsed = parseJsonc(readFileSync8(join10(cwd, file), "utf8"), file);
2797
+ parsed = parseJsonc(readFileSync9(join14(cwd, file), "utf8"), file);
1672
2798
  } catch {
1673
2799
  return { options: {}, file, unresolved: [`${file} could not be parsed`] };
1674
2800
  }
@@ -1786,7 +2912,7 @@ var OxfmtAdapter = class extends BaseAdapter {
1786
2912
  manifestOperation(context2.cwd, this.formatScripts(context2.cwd))
1787
2913
  ];
1788
2914
  const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
1789
- (name) => existsSync8(join11(context2.cwd, name))
2915
+ (name) => existsSync10(join15(context2.cwd, name))
1790
2916
  );
1791
2917
  for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
1792
2918
  const removableDeps = this.modulePrettierDependencies(context2.cwd);
@@ -1979,7 +3105,7 @@ var OxfmtAdapter = class extends BaseAdapter {
1979
3105
  const adoption = readFormatAdoption(ctx.cwd);
1980
3106
  const preset = formatPresetFor(adoption.preset ?? DEFAULT_PRESET);
1981
3107
  const options = { ...preset, ...adoption.local };
1982
- const describe = (value) => typeof value === "string" ? value : JSON.stringify(value);
3108
+ const describe2 = (value) => typeof value === "string" ? value : JSON.stringify(value);
1983
3109
  return {
1984
3110
  runner: "oxfmt",
1985
3111
  configFile: adoption.configFile,
@@ -1988,7 +3114,7 @@ var OxfmtAdapter = class extends BaseAdapter {
1988
3114
  options,
1989
3115
  overrides: Object.entries(adoption.local).filter(([key]) => !ALWAYS_LOCAL_KEYS.includes(key)).map(([key, value]) => ({
1990
3116
  rule: key,
1991
- reason: key in preset ? `${describe(value)}, where the ${adoption.preset ?? DEFAULT_PRESET} preset says ${describe(preset[key])}` : `${describe(value)}, which the preset does not set`
3117
+ reason: key in preset ? `${describe2(value)}, where the ${adoption.preset ?? DEFAULT_PRESET} preset says ${describe2(preset[key])}` : `${describe2(value)}, which the preset does not set`
1992
3118
  })),
1993
3119
  // Which files this module does not format at all. A long list here is worth a look:
1994
3120
  // it is the quiet way a module stops being covered.
@@ -2063,7 +3189,7 @@ var OxfmtAdapter = class extends BaseAdapter {
2063
3189
  modulePrettierDependencies(cwd) {
2064
3190
  const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
2065
3191
  try {
2066
- const manifest = JSON.parse(readFileSync9(join11(cwd, "package.json"), "utf8"));
3192
+ const manifest = JSON.parse(readFileSync10(join15(cwd, "package.json"), "utf8"));
2067
3193
  return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
2068
3194
  } catch {
2069
3195
  return [];
@@ -2145,8 +3271,8 @@ function registerFormat() {
2145
3271
 
2146
3272
  // src/roles/lint/adapters/oxlint/oxlint.adapter.ts
2147
3273
  import { spawnSync as spawnSync4 } from "node:child_process";
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";
3274
+ import { existsSync as existsSync14, readFileSync as readFileSync15, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
3275
+ import { join as join21 } from "node:path";
2150
3276
 
2151
3277
  // src/core/config/deferred-rules.ts
2152
3278
  function deferredRuleNames(rules) {
@@ -2154,8 +3280,8 @@ function deferredRuleNames(rules) {
2154
3280
  }
2155
3281
 
2156
3282
  // src/core/fix-report.ts
2157
- import { statSync as statSync2 } from "node:fs";
2158
- import { join as join12 } from "node:path";
3283
+ import { statSync as statSync3 } from "node:fs";
3284
+ import { join as join16 } from "node:path";
2159
3285
  var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
2160
3286
  function readFailure(output) {
2161
3287
  const lines = output.split("\n").map((line2) => line2.trim());
@@ -2166,7 +3292,7 @@ function readFailure(output) {
2166
3292
  }
2167
3293
  function fingerprint(path2) {
2168
3294
  try {
2169
- const stat = statSync2(path2);
3295
+ const stat = statSync3(path2);
2170
3296
  return `${stat.size}:${stat.mtimeMs}`;
2171
3297
  } catch {
2172
3298
  return void 0;
@@ -2175,7 +3301,7 @@ function fingerprint(path2) {
2175
3301
  function snapshotFiles(cwd, extensions) {
2176
3302
  const snapshot = /* @__PURE__ */ new Map();
2177
3303
  for (const file of listSourceFiles(cwd, extensions)) {
2178
- const mark = fingerprint(join12(cwd, file));
3304
+ const mark = fingerprint(join16(cwd, file));
2179
3305
  if (mark !== void 0) snapshot.set(file, mark);
2180
3306
  }
2181
3307
  return snapshot;
@@ -2183,7 +3309,7 @@ function snapshotFiles(cwd, extensions) {
2183
3309
  function changedSince(cwd, before, extensions) {
2184
3310
  const changed = [];
2185
3311
  for (const file of listSourceFiles(cwd, extensions)) {
2186
- const now = fingerprint(join12(cwd, file));
3312
+ const now = fingerprint(join16(cwd, file));
2187
3313
  if (now === void 0) continue;
2188
3314
  if (before.get(file) !== now) changed.push(file);
2189
3315
  }
@@ -4153,7 +5279,7 @@ var react_default = {
4153
5279
  };
4154
5280
 
4155
5281
  // src/roles/lint/presets/shared.json
4156
- var shared_default = {
5282
+ var shared_default2 = {
4157
5283
  rules: {
4158
5284
  "no-var": "error",
4159
5285
  "prefer-const": [
@@ -4530,7 +5656,7 @@ var tools_default = {
4530
5656
  };
4531
5657
 
4532
5658
  // src/roles/lint/preset-data.ts
4533
- var SHARED_RULES = shared_default.rules;
5659
+ var SHARED_RULES = shared_default2.rules;
4534
5660
  var PRESET_FILES = {
4535
5661
  nest: nest_default,
4536
5662
  node: node_default,
@@ -4643,14 +5769,14 @@ function downgradedRulesFor(preset) {
4643
5769
  }
4644
5770
 
4645
5771
  // src/roles/lint/extra-layers.ts
4646
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
4647
- import { isAbsolute, join as join13, resolve as resolve2 } from "node:path";
5772
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
5773
+ import { isAbsolute, join as join17, resolve as resolve2 } from "node:path";
4648
5774
  function ruleCount(cwd, specifier) {
4649
5775
  const path2 = isAbsolute(specifier) ? specifier : resolve2(cwd, specifier);
4650
- if (!existsSync9(path2)) return void 0;
5776
+ if (!existsSync11(path2)) return void 0;
4651
5777
  try {
4652
5778
  const parsed = parseJsonc(
4653
- readFileSync10(path2, "utf8"),
5779
+ readFileSync11(path2, "utf8"),
4654
5780
  specifier
4655
5781
  );
4656
5782
  return Object.keys(parsed.rules ?? {}).length;
@@ -4670,7 +5796,7 @@ function extraLayers(cwd, extendsList) {
4670
5796
  function committedExtendsList(cwd, configFile) {
4671
5797
  try {
4672
5798
  const parsed = parseJsonc(
4673
- readFileSync10(join13(cwd, configFile), "utf8"),
5799
+ readFileSync11(join17(cwd, configFile), "utf8"),
4674
5800
  configFile
4675
5801
  );
4676
5802
  return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
@@ -4853,12 +5979,12 @@ function lintPresetFor(preset) {
4853
5979
  }
4854
5980
 
4855
5981
  // src/roles/lint/adapters/oxlint/plan.ts
4856
- import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
4857
- import { join as join16 } from "node:path";
5982
+ import { existsSync as existsSync13, readFileSync as readFileSync14 } from "node:fs";
5983
+ import { join as join20 } from "node:path";
4858
5984
 
4859
5985
  // src/roles/lint/eslint-ignores.ts
4860
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
4861
- import { join as join14 } from "node:path";
5986
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "node:fs";
5987
+ import { join as join18 } from "node:path";
4862
5988
  import { parseSync as parseSync2 } from "oxc-parser";
4863
5989
  var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
4864
5990
  function toOxlintPattern(pattern) {
@@ -4870,11 +5996,11 @@ function readRootEslintIgnores(root) {
4870
5996
  return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
4871
5997
  }
4872
5998
  function readEslintIgnores(cwd) {
4873
- const config = ESLINT_CONFIG_FILES.map((name) => join14(cwd, name)).find((path2) => existsSync10(path2));
5999
+ const config = ESLINT_CONFIG_FILES.map((name) => join18(cwd, name)).find((path2) => existsSync12(path2));
4874
6000
  if (!config) return { patterns: [], unresolved: [] };
4875
6001
  let source;
4876
6002
  try {
4877
- source = readFileSync11(config, "utf8");
6003
+ source = readFileSync12(config, "utf8");
4878
6004
  } catch {
4879
6005
  return { patterns: [], unresolved: [] };
4880
6006
  }
@@ -4945,8 +6071,8 @@ function keepsOtherCommands3(script) {
4945
6071
  }
4946
6072
 
4947
6073
  // src/roles/lint/rename-suppressions.ts
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";
6074
+ import { readdirSync as readdirSync2, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
6075
+ import { join as join19, relative as relative3 } from "node:path";
4950
6076
  var SOURCE_EXTENSIONS = [
4951
6077
  ".ts",
4952
6078
  ".tsx",
@@ -4993,10 +6119,10 @@ function* sourceFiles(dir) {
4993
6119
  return;
4994
6120
  }
4995
6121
  for (const entry of entries) {
4996
- const full = join15(dir, entry);
6122
+ const full = join19(dir, entry);
4997
6123
  let isDirectory;
4998
6124
  try {
4999
- isDirectory = statSync3(full).isDirectory();
6125
+ isDirectory = statSync4(full).isDirectory();
5000
6126
  } catch {
5001
6127
  continue;
5002
6128
  }
@@ -5013,7 +6139,7 @@ function findSuppressionRenames(cwd, renames) {
5013
6139
  for (const file of sourceFiles(cwd)) {
5014
6140
  let content;
5015
6141
  try {
5016
- content = readFileSync12(file, "utf8");
6142
+ content = readFileSync13(file, "utf8");
5017
6143
  } catch {
5018
6144
  continue;
5019
6145
  }
@@ -5159,7 +6285,7 @@ function plan2(context2) {
5159
6285
  keys: removableDeps
5160
6286
  });
5161
6287
  }
5162
- const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync11(join16(context2.cwd, name)));
6288
+ const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync13(join20(context2.cwd, name)));
5163
6289
  for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
5164
6290
  operations.push(
5165
6291
  ...nxTargetOperations({
@@ -5273,7 +6399,7 @@ function lintScripts(cwd) {
5273
6399
  function committedExtends(cwd) {
5274
6400
  try {
5275
6401
  const parsed = parseJsonc(
5276
- readFileSync13(join16(cwd, LINT_CONFIG_FILE), "utf8"),
6402
+ readFileSync14(join20(cwd, LINT_CONFIG_FILE), "utf8"),
5277
6403
  LINT_CONFIG_FILE
5278
6404
  );
5279
6405
  return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
@@ -5285,7 +6411,7 @@ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array
5285
6411
  function committedConfig(cwd) {
5286
6412
  try {
5287
6413
  const parsed = parseJsonc(
5288
- readFileSync13(join16(cwd, LINT_CONFIG_FILE), "utf8"),
6414
+ readFileSync14(join20(cwd, LINT_CONFIG_FILE), "utf8"),
5289
6415
  LINT_CONFIG_FILE
5290
6416
  );
5291
6417
  return isRecord2(parsed) ? parsed : {};
@@ -5309,7 +6435,7 @@ function describeGoverned(rule, variant) {
5309
6435
  function committedIgnorePatterns(cwd) {
5310
6436
  try {
5311
6437
  const parsed = parseJsonc(
5312
- readFileSync13(join16(cwd, LINT_CONFIG_FILE), "utf8"),
6438
+ readFileSync14(join20(cwd, LINT_CONFIG_FILE), "utf8"),
5313
6439
  LINT_CONFIG_FILE
5314
6440
  );
5315
6441
  return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
@@ -5321,7 +6447,7 @@ function moduleEslintDependencies(cwd) {
5321
6447
  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/");
5322
6448
  let manifest;
5323
6449
  try {
5324
- manifest = JSON.parse(readFileSync13(join16(cwd, "package.json"), "utf8"));
6450
+ manifest = JSON.parse(readFileSync14(join20(cwd, "package.json"), "utf8"));
5325
6451
  } catch {
5326
6452
  return [];
5327
6453
  }
@@ -5502,23 +6628,23 @@ ${result.stderr ?? ""}`);
5502
6628
  * build the developer can see, rather than a silent half-adoption they cannot.
5503
6629
  */
5504
6630
  writeModuleBaseline(ctx, oxlint, env) {
5505
- const configPath = join17(ctx.cwd, LINT_CONFIG_FILE);
5506
- const baselinePath = join17(ctx.cwd, LINT_BASELINE_FILE);
5507
- if (!existsSync12(configPath)) return;
6631
+ const configPath = join21(ctx.cwd, LINT_CONFIG_FILE);
6632
+ const baselinePath = join21(ctx.cwd, LINT_BASELINE_FILE);
6633
+ if (!existsSync14(configPath)) return;
5508
6634
  let config;
5509
6635
  try {
5510
6636
  config = parseJsonc(
5511
- readFileSync14(configPath, "utf8"),
6637
+ readFileSync15(configPath, "utf8"),
5512
6638
  LINT_CONFIG_FILE
5513
6639
  );
5514
6640
  } catch {
5515
6641
  return;
5516
6642
  }
5517
6643
  const current = Array.isArray(config.extends) ? config.extends : [];
5518
- const measurePath = join17(ctx.cwd, LINT_MEASURE_FILE);
6644
+ const measurePath = join21(ctx.cwd, LINT_MEASURE_FILE);
5519
6645
  let measured;
5520
6646
  try {
5521
- writeFileSync3(
6647
+ writeFileSync4(
5522
6648
  measurePath,
5523
6649
  `${JSON.stringify({ ...config, extends: extendsWithBaseline(current, false) }, null, 2)}
5524
6650
  `
@@ -5553,7 +6679,7 @@ ${result.stderr ?? ""}`);
5553
6679
  );
5554
6680
  const next = extendsWithBaseline(current, holds.length > 0);
5555
6681
  if (holds.length > 0) {
5556
- writeFileSync3(baselinePath, renderBaseline(holds, readOwnPackage().version));
6682
+ writeFileSync4(baselinePath, renderBaseline(holds, readOwnPackage().version));
5557
6683
  process.stderr.write(` ${palette(process.stderr).warn(describeHolds(holds))}
5558
6684
  `);
5559
6685
  } else {
@@ -5561,12 +6687,12 @@ ${result.stderr ?? ""}`);
5561
6687
  }
5562
6688
  if (next.length !== current.length || next.some((entry, at) => entry !== current[at])) {
5563
6689
  config.extends = next;
5564
- writeFileSync3(configPath, `${JSON.stringify(config, null, 2)}
6690
+ writeFileSync4(configPath, `${JSON.stringify(config, null, 2)}
5565
6691
  `);
5566
6692
  }
5567
6693
  }
5568
6694
  async run(ctx) {
5569
- if (!existsSync12(join17(ctx.cwd, LINT_CONFIG_FILE))) {
6695
+ if (!existsSync14(join21(ctx.cwd, LINT_CONFIG_FILE))) {
5570
6696
  process.stderr.write(
5571
6697
  `sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
5572
6698
  `
@@ -5715,7 +6841,7 @@ ${result.stderr ?? ""}`);
5715
6841
  let stub;
5716
6842
  try {
5717
6843
  stub = parseJsonc(
5718
- readFileSync14(join17(cwd, LINT_CONFIG_FILE), "utf8"),
6844
+ readFileSync15(join21(cwd, LINT_CONFIG_FILE), "utf8"),
5719
6845
  LINT_CONFIG_FILE
5720
6846
  );
5721
6847
  } catch {
@@ -5725,7 +6851,7 @@ ${result.stderr ?? ""}`);
5725
6851
  for (const entry of stub.extends ?? []) {
5726
6852
  try {
5727
6853
  const preset = parseJsonc(
5728
- readFileSync14(join17(cwd, entry), "utf8"),
6854
+ readFileSync15(join21(cwd, entry), "utf8"),
5729
6855
  entry
5730
6856
  );
5731
6857
  for (const rule of Object.keys(preset.rules ?? {})) names.add(canonicalRuleName(rule));
@@ -5795,14 +6921,14 @@ ${result.stderr ?? ""}`);
5795
6921
  let parsed;
5796
6922
  try {
5797
6923
  parsed = parseJsonc(
5798
- readFileSync14(join17(cwd, LINT_CONFIG_FILE), "utf8"),
6924
+ readFileSync15(join21(cwd, LINT_CONFIG_FILE), "utf8"),
5799
6925
  LINT_CONFIG_FILE
5800
6926
  );
5801
6927
  } catch {
5802
6928
  return void 0;
5803
6929
  }
5804
6930
  const targets2 = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
5805
- return targets2.find((target) => !existsSync12(join17(cwd, target)));
6931
+ return targets2.find((target) => !existsSync14(join21(cwd, target)));
5806
6932
  }
5807
6933
  /** Announce what is not enforced, so reduced coverage is never silent. */
5808
6934
  announceDisabled(preset) {
@@ -5834,8 +6960,8 @@ function registerLint() {
5834
6960
 
5835
6961
  // src/roles/static-analysis/adapters/jscpd/jscpd.adapter.ts
5836
6962
  import { spawnSync as spawnSync6 } from "node:child_process";
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";
6963
+ import { existsSync as existsSync20, mkdirSync as mkdirSync3, readFileSync as readFileSync21, unlinkSync as unlinkSync2, writeFileSync as writeFileSync8 } from "node:fs";
6964
+ import { join as join31 } from "node:path";
5839
6965
  import { globSync as globSync5 } from "tinyglobby";
5840
6966
 
5841
6967
  // src/roles/static-analysis/bundle/budgets.ts
@@ -5864,32 +6990,32 @@ function bandFor(valueKb, referenceKb) {
5864
6990
  }
5865
6991
 
5866
6992
  // src/roles/static-analysis/bundle/scan.ts
5867
- import { existsSync as existsSync14, readFileSync as readFileSync16 } from "node:fs";
5868
- import { basename, join as join19 } from "node:path";
5869
- import { pathToFileURL } from "node:url";
6993
+ import { existsSync as existsSync16, readFileSync as readFileSync17 } from "node:fs";
6994
+ import { basename, join as join23 } from "node:path";
6995
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
5870
6996
  import { gzipSync } from "node:zlib";
5871
6997
  import { globSync as globSync2 } from "tinyglobby";
5872
6998
 
5873
6999
  // src/roles/static-analysis/bundle/find-build.ts
5874
- import { existsSync as existsSync13, readFileSync as readFileSync15, statSync as statSync4 } from "node:fs";
5875
- import { join as join18 } from "node:path";
7000
+ import { existsSync as existsSync15, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
7001
+ import { join as join22 } from "node:path";
5876
7002
  import { globSync } from "tinyglobby";
5877
7003
  var MANIFEST_GLOB = "server/_tanstack-start-manifest*.mjs";
5878
7004
  function candidateDirs(cwd) {
5879
7005
  const declared = readProjectPackageJson(cwd).nx?.targets?.build?.["outputs"];
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")];
7006
+ const fromDeclaration = Array.isArray(declared) ? declared.filter((token) => typeof token === "string").map((token) => join22(cwd, token.replace(/^\{projectRoot\}\//, ""))) : [];
7007
+ return [...fromDeclaration, join22(cwd, ".output"), join22(cwd, "dist"), join22(cwd, "build")];
5882
7008
  }
5883
7009
  function findBuildOutput(cwd) {
5884
7010
  const lookedIn = [];
5885
7011
  for (const dir of candidateDirs(cwd)) {
5886
7012
  lookedIn.push(dir);
5887
- if (!existsSync13(dir)) continue;
7013
+ if (!existsSync15(dir)) continue;
5888
7014
  const found = globSync(MANIFEST_GLOB, { cwd: dir, absolute: true, dot: false });
5889
7015
  if (found.length === 0) continue;
5890
7016
  const manifestPath = found[0];
5891
- const publicDir = join18(dir, "public");
5892
- if (!existsSync13(publicDir)) continue;
7017
+ const publicDir = join22(dir, "public");
7018
+ if (!existsSync15(publicDir)) continue;
5893
7019
  return { manifestPath, publicDir, ...dateOf(dir, manifestPath) };
5894
7020
  }
5895
7021
  const pkg = readProjectPackageJson(cwd);
@@ -5911,10 +7037,10 @@ function findBuildOutput(cwd) {
5911
7037
  };
5912
7038
  }
5913
7039
  function dateOf(dir, manifestPath) {
5914
- const record = join18(dir, "nitro.json");
5915
- if (existsSync13(record)) {
7040
+ const record = join22(dir, "nitro.json");
7041
+ if (existsSync15(record)) {
5916
7042
  try {
5917
- const parsed = JSON.parse(readFileSync15(record, "utf8"));
7043
+ const parsed = JSON.parse(readFileSync16(record, "utf8"));
5918
7044
  if (typeof parsed.date === "string") {
5919
7045
  const date = new Date(parsed.date);
5920
7046
  if (!Number.isNaN(date.getTime())) return { builtAt: date, builtAtFrom: "build record" };
@@ -5922,7 +7048,7 @@ function dateOf(dir, manifestPath) {
5922
7048
  } catch {
5923
7049
  }
5924
7050
  }
5925
- return { builtAt: statSync4(manifestPath).mtime, builtAtFrom: "file timestamp" };
7051
+ return { builtAt: statSync5(manifestPath).mtime, builtAtFrom: "file timestamp" };
5926
7052
  }
5927
7053
  function isBuildOutput(found) {
5928
7054
  return "manifestPath" in found;
@@ -5940,10 +7066,10 @@ function kindOf(path2) {
5940
7066
  return "other";
5941
7067
  }
5942
7068
  function resolveAnnounced(publicDir, announced) {
5943
- const direct = join19(publicDir, announced);
5944
- if (existsSync14(direct)) return direct;
5945
- const stripped = join19(publicDir, announced.replace(/^\/[^/]+\//, "/"));
5946
- return existsSync14(stripped) ? stripped : void 0;
7069
+ const direct = join23(publicDir, announced);
7070
+ if (existsSync16(direct)) return direct;
7071
+ const stripped = join23(publicDir, announced.replace(/^\/[^/]+\//, "/"));
7072
+ return existsSync16(stripped) ? stripped : void 0;
5947
7073
  }
5948
7074
  function emptyWeight() {
5949
7075
  return { transfer: 0, parsed: 0, files: 0 };
@@ -5979,7 +7105,7 @@ async function scanBundle(cwd) {
5979
7105
  unresolved.push(announced);
5980
7106
  measured.set(announced, void 0);
5981
7107
  } else {
5982
- const buffer = readFileSync16(path2);
7108
+ const buffer = readFileSync17(path2);
5983
7109
  measured.set(announced, {
5984
7110
  parsed: buffer.length,
5985
7111
  transfer: gzipSync(buffer, { level: 9 }).length
@@ -6030,7 +7156,7 @@ async function scanBundle(cwd) {
6030
7156
  }
6031
7157
  async function loadManifest(path2) {
6032
7158
  try {
6033
- const loaded = await import(pathToFileURL(path2).href);
7159
+ const loaded = await import(pathToFileURL2(path2).href);
6034
7160
  if (typeof loaded.tsrStartManifest !== "function") return void 0;
6035
7161
  const produced = loaded.tsrStartManifest();
6036
7162
  return produced !== null && typeof produced === "object" ? produced : void 0;
@@ -6066,7 +7192,7 @@ function scanDynamicLayer(publicDir, references) {
6066
7192
  })) {
6067
7193
  const name = basename(path2);
6068
7194
  if (referenced.has(name)) continue;
6069
- const buffer = readFileSync16(path2);
7195
+ const buffer = readFileSync17(path2);
6070
7196
  const size = { parsed: buffer.length, transfer: gzipSync(buffer, { level: 9 }).length };
6071
7197
  add(all2, size);
6072
7198
  const group = groupOf(name);
@@ -6094,28 +7220,28 @@ function scanDynamicLayer(publicDir, references) {
6094
7220
  }
6095
7221
 
6096
7222
  // src/roles/static-analysis/sources.ts
6097
- import { readFileSync as readFileSync17 } from "node:fs";
6098
- import { join as join22 } from "node:path";
7223
+ import { readFileSync as readFileSync18 } from "node:fs";
7224
+ import { join as join26 } from "node:path";
6099
7225
  import { parseSync as parseSync3 } from "oxc-parser";
6100
7226
  import { globSync as globSync3 } from "tinyglobby";
6101
7227
 
6102
7228
  // src/roles/static-analysis/adapters/jscpd/tool-config.ts
6103
- import { existsSync as existsSync16, mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
7229
+ import { existsSync as existsSync18, mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
6104
7230
  import { tmpdir } from "node:os";
6105
- import { join as join21 } from "node:path";
7231
+ import { join as join25 } from "node:path";
6106
7232
 
6107
7233
  // src/roles/static-analysis/configs/resolve.ts
6108
- import { existsSync as existsSync15 } from "node:fs";
6109
- import { join as join20 } from "node:path";
6110
- import { pathToFileURL as pathToFileURL2 } from "node:url";
7234
+ import { existsSync as existsSync17 } from "node:fs";
7235
+ import { join as join24 } from "node:path";
7236
+ import { pathToFileURL as pathToFileURL3 } from "node:url";
6111
7237
  var STATIC_CONFIG_FILE = "static-analysis.config.mjs";
6112
7238
  async function loadStaticConfig(cwd) {
6113
- const path2 = join20(cwd, STATIC_CONFIG_FILE);
6114
- if (!existsSync15(path2)) {
7239
+ const path2 = join24(cwd, STATIC_CONFIG_FILE);
7240
+ if (!existsSync17(path2)) {
6115
7241
  return { config: defineStaticAnalysis(), from: null, unreadable: null };
6116
7242
  }
6117
7243
  try {
6118
- const loaded = await import(pathToFileURL2(path2).href);
7244
+ const loaded = await import(pathToFileURL3(path2).href);
6119
7245
  return { config: adopt(loaded.default), from: STATIC_CONFIG_FILE, unreadable: null };
6120
7246
  } catch (error) {
6121
7247
  return {
@@ -6157,7 +7283,7 @@ function exclusions(config, scope = "tests") {
6157
7283
  return [.../* @__PURE__ */ new Set([...config.ignore, ...OWN_ARTEFACTS, ...tests])];
6158
7284
  }
6159
7285
  function measuredRoots(cwd) {
6160
- return existsSync16(join21(cwd, "src")) ? ["src"] : ["."];
7286
+ return existsSync18(join25(cwd, "src")) ? ["src"] : ["."];
6161
7287
  }
6162
7288
  function fileSettings(config, scope, extraEntries = []) {
6163
7289
  return {
@@ -6187,9 +7313,9 @@ function fileSettings(config, scope, extraEntries = []) {
6187
7313
  };
6188
7314
  }
6189
7315
  function duplicationRun(config, paths, scope) {
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)}
7316
+ const dir = mkdtempSync(join25(tmpdir(), "sentinel-static-"));
7317
+ const configPath = join25(dir, "jscpd.json");
7318
+ writeFileSync5(configPath, `${JSON.stringify(fileSettings(config, scope), null, 2)}
6193
7319
  `, "utf8");
6194
7320
  const args = [
6195
7321
  "--config",
@@ -6214,9 +7340,9 @@ function duplicationRun(config, paths, scope) {
6214
7340
  return { args, rawReportDir: dir, dispose: () => rmSync3(dir, { force: true, recursive: true }) };
6215
7341
  }
6216
7342
  function deadCodeRun(config, paths, extraEntries = []) {
6217
- const dir = mkdtempSync(join21(tmpdir(), "sentinel-static-"));
6218
- const configPath = join21(dir, "jscpd.json");
6219
- writeFileSync4(
7343
+ const dir = mkdtempSync(join25(tmpdir(), "sentinel-static-"));
7344
+ const configPath = join25(dir, "jscpd.json");
7345
+ writeFileSync5(
6220
7346
  configPath,
6221
7347
  `${JSON.stringify(fileSettings(config, "dead-code", extraEntries), null, 2)}
6222
7348
  `,
@@ -6267,7 +7393,7 @@ function readParsed(cwd, files, passes) {
6267
7393
  function parsedSource(cwd, path2) {
6268
7394
  let text;
6269
7395
  try {
6270
- text = readFileSync17(join22(cwd, path2), "utf8");
7396
+ text = readFileSync18(join26(cwd, path2), "utf8");
6271
7397
  } catch {
6272
7398
  return void 0;
6273
7399
  }
@@ -6361,7 +7487,7 @@ function nameOf(fn, parent) {
6361
7487
  if (key) return key;
6362
7488
  return "(anonymous)";
6363
7489
  }
6364
- function lineAt(source, offset) {
7490
+ function lineAt2(source, offset) {
6365
7491
  if (offset === void 0) return 1;
6366
7492
  let line = 1;
6367
7493
  for (let index = 0; index < offset && index < source.length; index += 1) {
@@ -6383,7 +7509,7 @@ function complexityOfProgram(program, source) {
6383
7509
  if (!FUNCTION_NODES.has(node.type ?? "")) return;
6384
7510
  found.push({
6385
7511
  name: nameOf(node, parents.get(node)),
6386
- line: lineAt(source, node.start),
7512
+ line: lineAt2(source, node.start),
6387
7513
  complexity: complexityOf(node)
6388
7514
  });
6389
7515
  },
@@ -6454,13 +7580,13 @@ function unreadableNote(coverage) {
6454
7580
  }
6455
7581
 
6456
7582
  // src/roles/static-analysis/excerpt.ts
6457
- import { readFileSync as readFileSync18 } from "node:fs";
6458
- import { join as join24 } from "node:path";
7583
+ import { readFileSync as readFileSync19 } from "node:fs";
7584
+ import { join as join28 } from "node:path";
6459
7585
 
6460
7586
  // src/roles/static-analysis/maintainability/size-rules.ts
6461
- import { mkdtempSync as mkdtempSync2, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
7587
+ import { mkdtempSync as mkdtempSync2, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
6462
7588
  import { tmpdir as tmpdir2 } from "node:os";
6463
- import { join as join23 } from "node:path";
7589
+ import { join as join27 } from "node:path";
6464
7590
  var GAUGE_THRESHOLDS = {
6465
7591
  // McCabe 1976 says 10, our p90 says 11. The only indicator where two independent candidates agree
6466
7592
  // within one unit, and 94.8% of our functions already sit in the 1-10 band SEI calls low risk.
@@ -6489,7 +7615,7 @@ var GAUGE_THRESHOLDS = {
6489
7615
  "cognitive-complexity": 7
6490
7616
  };
6491
7617
  var COGNITIVE = "cognitive-complexity";
6492
- var cognitivePlugin = () => join23(ownPackageDir(), "plugins", "sonar-cognitive.js");
7618
+ var cognitivePlugin = () => join27(ownPackageDir(), "plugins", "sonar-cognitive.js");
6493
7619
  var CATEGORIES = [
6494
7620
  "correctness",
6495
7621
  "suspicious",
@@ -6521,9 +7647,9 @@ function sizeRulesConfig() {
6521
7647
  };
6522
7648
  }
6523
7649
  function sizeRulesRun(paths, ignore) {
6524
- const dir = mkdtempSync2(join23(tmpdir2(), "sentinel-size-"));
6525
- const configPath = join23(dir, "oxlintrc.json");
6526
- writeFileSync5(configPath, `${JSON.stringify(sizeRulesConfig(), null, 2)}
7650
+ const dir = mkdtempSync2(join27(tmpdir2(), "sentinel-size-"));
7651
+ const configPath = join27(dir, "oxlintrc.json");
7652
+ writeFileSync6(configPath, `${JSON.stringify(sizeRulesConfig(), null, 2)}
6527
7653
  `, "utf8");
6528
7654
  return {
6529
7655
  // `--format json` because the console shape is rendered by this role from the figures: a
@@ -7047,23 +8173,23 @@ function helpDetails(help, withoutReference) {
7047
8173
  <p class="source">Source: ${prose(help.source)}</p>
7048
8174
  </details>`;
7049
8175
  }
7050
- function secondSide(finding) {
7051
- if (!finding.other) return "";
8176
+ function secondSide(finding2) {
8177
+ if (!finding2.other) return "";
7052
8178
  const file = (at) => at.split(":")[0] ?? at;
7053
- const sameFile = file(finding.other) === file(finding.path);
8179
+ const sameFile = file(finding2.other) === file(finding2.path);
7054
8180
  const label = sameFile ? "and line" : "and";
7055
- const shownSide = sameFile ? finding.other.split(":")[1] ?? finding.other : finding.other;
8181
+ const shownSide = sameFile ? finding2.other.split(":")[1] ?? finding2.other : finding2.other;
7056
8182
  return ` <span class="unit">${label}</span> <code>${escape(shownSide)}</code>`;
7057
8183
  }
7058
8184
  var HTML_GROUP_LIMIT = 50;
7059
8185
  function listedByMetric(findings) {
7060
8186
  const groups = /* @__PURE__ */ new Map();
7061
- for (const finding of findings) {
7062
- const bucket = groups.get(finding.metric) ?? [];
7063
- bucket.push(finding);
7064
- groups.set(finding.metric, bucket);
8187
+ for (const finding2 of findings) {
8188
+ const bucket = groups.get(finding2.metric) ?? [];
8189
+ bucket.push(finding2);
8190
+ groups.set(finding2.metric, bucket);
7065
8191
  }
7066
- const weight = (finding) => finding.value ?? finding.lines ?? 0;
8192
+ const weight = (finding2) => finding2.value ?? finding2.lines ?? 0;
7067
8193
  return new Map(
7068
8194
  [...groups.entries()].map(([metric, all2]) => {
7069
8195
  const sorted = [...all2].sort((left, right) => weight(right) - weight(left));
@@ -7093,7 +8219,7 @@ function caveatsOf(report) {
7093
8219
  }
7094
8220
  return found;
7095
8221
  }
7096
- function renderConsole(report) {
8222
+ function renderConsole2(report) {
7097
8223
  const lines = [];
7098
8224
  const width = (pick) => Math.max(...report.rows.map((row) => pick(row).length));
7099
8225
  const label = (row) => helpFor(row.metric)?.label ?? row.metric;
@@ -7148,7 +8274,7 @@ function renderConsole(report) {
7148
8274
  `;
7149
8275
  }
7150
8276
  var JSON_SCHEMA_VERSION = 1;
7151
- function renderJson(report) {
8277
+ function renderJson2(report) {
7152
8278
  const rows = report.rows.map((row) => {
7153
8279
  const help = helpFor(row.metric);
7154
8280
  return { ...row, help: help ? { label: help.label, caveat: help.caveat } : null };
@@ -7165,11 +8291,11 @@ function renderJson(report) {
7165
8291
  return `${JSON.stringify(envelope, null, 2)}
7166
8292
  `;
7167
8293
  }
7168
- function renderMarkdown(report) {
8294
+ function renderMarkdown2(report) {
7169
8295
  const version = report.settings.find((setting) => setting.key === "sentinel")?.value;
7170
8296
  const places = /* @__PURE__ */ new Map();
7171
- for (const finding of report.findings) {
7172
- places.set(finding.metric, (places.get(finding.metric) ?? 0) + 1);
8297
+ for (const finding2 of report.findings) {
8298
+ places.set(finding2.metric, (places.get(finding2.metric) ?? 0) + 1);
7173
8299
  }
7174
8300
  const figureLine = (row) => {
7175
8301
  const help = helpFor(row.metric);
@@ -7223,64 +8349,64 @@ function ruleChip(metric) {
7223
8349
  const threshold = GAUGE_THRESHOLDS[metric];
7224
8350
  return threshold === void 0 ? "" : ` <span class="chip"><code>${escape(metric)}</code> &gt; ${threshold}</span>`;
7225
8351
  }
7226
- function measureOf(finding) {
7227
- const metric = finding.metric;
8352
+ function measureOf(finding2) {
8353
+ const metric = finding2.metric;
7228
8354
  const number = (value, unit = "") => `<span class="pmeas">${grouped(value)}${unit === "" ? "" : `<span class="unit">${escape(unit)}</span>`}</span>`;
7229
- if (finding.value !== void 0) {
7230
- if (GAUGE_THRESHOLDS[metric] !== void 0) return number(finding.value);
7231
- if (metric.startsWith("complexity")) return number(finding.value, "complexity");
7232
- if (finding.dependsOn !== void 0) return number(finding.value, "imported by");
7233
- if (metric === "cycles") return number(finding.value, "files in the cycle");
7234
- return number(finding.value);
8355
+ if (finding2.value !== void 0) {
8356
+ if (GAUGE_THRESHOLDS[metric] !== void 0) return number(finding2.value);
8357
+ if (metric.startsWith("complexity")) return number(finding2.value, "complexity");
8358
+ if (finding2.dependsOn !== void 0) return number(finding2.value, "imported by");
8359
+ if (metric === "cycles") return number(finding2.value, "files in the cycle");
8360
+ return number(finding2.value);
7235
8361
  }
7236
- return finding.lines === void 0 ? "" : number(finding.lines, "lines");
8362
+ return finding2.lines === void 0 ? "" : number(finding2.lines, "lines");
7237
8363
  }
7238
- function metaOf(finding) {
8364
+ function metaOf(finding2) {
7239
8365
  const pairs = [];
7240
8366
  const add2 = (label, value) => {
7241
8367
  pairs.push(`<span class="pair"><span class="k">${escape(label)}</span>${value}</span>`);
7242
8368
  };
7243
- if (finding.name) {
8369
+ if (finding2.name) {
7244
8370
  add2(
7245
- finding.metric.startsWith("complexity") ? "function" : "name",
7246
- `<code>${escape(finding.name)}</code>`
8371
+ finding2.metric.startsWith("complexity") ? "function" : "name",
8372
+ `<code>${escape(finding2.name)}</code>`
7247
8373
  );
7248
8374
  }
7249
- if (finding.dependsOn !== void 0) add2("depends on", String(finding.dependsOn));
7250
- if (finding.instability !== void 0) add2("instability", finding.instability.toFixed(2));
7251
- if (finding.centrality !== void 0) add2("centrality", finding.centrality.toFixed(4));
7252
- if (finding.lines !== void 0 && finding.value !== void 0)
7253
- add2("length", `${finding.lines} lines`);
7254
- if (finding.confidence !== void 0) add2("confidence", `${finding.confidence}%`);
7255
- if (finding.reason) add2("unsure", `<span class="reason">${escape(finding.reason)}</span>`);
8375
+ if (finding2.dependsOn !== void 0) add2("depends on", String(finding2.dependsOn));
8376
+ if (finding2.instability !== void 0) add2("instability", finding2.instability.toFixed(2));
8377
+ if (finding2.centrality !== void 0) add2("centrality", finding2.centrality.toFixed(4));
8378
+ if (finding2.lines !== void 0 && finding2.value !== void 0)
8379
+ add2("length", `${finding2.lines} lines`);
8380
+ if (finding2.confidence !== void 0) add2("confidence", `${finding2.confidence}%`);
8381
+ if (finding2.reason) add2("unsure", `<span class="reason">${escape(finding2.reason)}</span>`);
7256
8382
  return pairs.length === 0 ? "" : ` <div class="pmeta">${pairs.join("")}</div>
7257
8383
  `;
7258
8384
  }
7259
- function codeSummary(finding) {
7260
- const count = finding.excerpt === void 0 ? 0 : finding.excerpt.split("\n").length;
7261
- if (finding.metric === "clones" || finding.metric === "clonesTests") {
7262
- return `${finding.lines ?? count} duplicated lines`;
8385
+ function codeSummary(finding2) {
8386
+ const count = finding2.excerpt === void 0 ? 0 : finding2.excerpt.split("\n").length;
8387
+ if (finding2.metric === "clones" || finding2.metric === "clonesTests") {
8388
+ return `${finding2.lines ?? count} duplicated lines`;
7263
8389
  }
7264
- if (finding.excerptFirstLine === 1) return `the first ${count} lines`;
7265
- return `${count} lines from ${finding.excerptFirstLine ?? "?"}`;
8390
+ if (finding2.excerptFirstLine === 1) return `the first ${count} lines`;
8391
+ return `${count} lines from ${finding2.excerptFirstLine ?? "?"}`;
7266
8392
  }
7267
- function codeBlock(finding) {
7268
- if (finding.excerpt === void 0) return "";
7269
- const first = finding.excerptFirstLine;
7270
- const body = first === void 0 ? escape(finding.excerpt) : finding.excerpt.split("\n").map((line, offset) => `<span class="n">${first + offset}</span>${escape(line)}`).join("\n");
8393
+ function codeBlock(finding2) {
8394
+ if (finding2.excerpt === void 0) return "";
8395
+ const first = finding2.excerptFirstLine;
8396
+ const body = first === void 0 ? escape(finding2.excerpt) : finding2.excerpt.split("\n").map((line, offset) => `<span class="n">${first + offset}</span>${escape(line)}`).join("\n");
7271
8397
  return ` <details class="pcode">
7272
- <summary>${escape(codeSummary(finding))}</summary>
8398
+ <summary>${escape(codeSummary(finding2))}</summary>
7273
8399
  <pre class="code">${body}</pre>
7274
8400
  </details>
7275
8401
  `;
7276
8402
  }
7277
- function placeCard(finding) {
8403
+ function placeCard(finding2) {
7278
8404
  return ` <div class="pcard">
7279
8405
  <div class="phead">
7280
- <code class="path">${escape(finding.path)}</code>${secondSide(finding)}
7281
- <span class="pright">${measureOf(finding)}${ruleChip(finding.metric)}</span>
8406
+ <code class="path">${escape(finding2.path)}</code>${secondSide(finding2)}
8407
+ <span class="pright">${measureOf(finding2)}${ruleChip(finding2.metric)}</span>
7282
8408
  </div>
7283
- ${metaOf(finding)}${codeBlock(finding)} </div>`;
8409
+ ${metaOf(finding2)}${codeBlock(finding2)} </div>`;
7284
8410
  }
7285
8411
  function placesFor(group) {
7286
8412
  const total = group.listed.length + group.rest;
@@ -7288,7 +8414,7 @@ function placesFor(group) {
7288
8414
  return ` <details class="place">
7289
8415
  <summary>where to look <span class="count">${grouped(total)} ${total === 1 ? "place" : "places"}</span>${capped}</summary>
7290
8416
  <div class="cards">
7291
- ${group.listed.map((finding) => placeCard(finding)).join("\n")}
8417
+ ${group.listed.map((finding2) => placeCard(finding2)).join("\n")}
7292
8418
  </div>
7293
8419
  ${group.rest > 0 ? ` <p class="sub">The other ${group.rest} are not on this page. <code>${SCRIPTS.json}</code> writes all of them, from the same run as this one.</p>` : ""}
7294
8420
  </details>`;
@@ -7345,17 +8471,17 @@ var KIND_UNITS = {
7345
8471
  cycles: "files",
7346
8472
  mostImported: "imported by"
7347
8473
  };
7348
- function findingRow(finding) {
7349
- const path2 = finding.path;
8474
+ function findingRow(finding2) {
8475
+ const path2 = finding2.path;
7350
8476
  const cut = path2.lastIndexOf("/");
7351
8477
  const dir = cut === -1 ? "" : path2.slice(0, cut + 1);
7352
8478
  const file = cut === -1 ? path2 : path2.slice(cut + 1);
7353
- const measure = finding.value ?? finding.lines;
7354
- const unit = KIND_UNITS[finding.metric];
7355
- const haystack = [path2, finding.name, finding.other].filter(Boolean).join(" ").toLowerCase();
7356
- return ` <tr data-kind="${escape(kindOf2(finding.metric))}" data-find="${escape(haystack)}">
7357
- <td><span class="kind">${escape(kindOf2(finding.metric))}</span></td>
7358
- <td class="where"><span class="dir">${escape(dir)}</span><b>${escape(file)}</b>${finding.name === void 0 ? "" : `<span class="sym">${escape(finding.name)}</span>`}${finding.other === void 0 ? "" : `<span class="sym">&#8596; ${escape(finding.other)}</span>`}</td>
8479
+ const measure = finding2.value ?? finding2.lines;
8480
+ const unit = KIND_UNITS[finding2.metric];
8481
+ const haystack = [path2, finding2.name, finding2.other].filter(Boolean).join(" ").toLowerCase();
8482
+ return ` <tr data-kind="${escape(kindOf2(finding2.metric))}" data-find="${escape(haystack)}">
8483
+ <td><span class="kind">${escape(kindOf2(finding2.metric))}</span></td>
8484
+ <td class="where"><span class="dir">${escape(dir)}</span><b>${escape(file)}</b>${finding2.name === void 0 ? "" : `<span class="sym">${escape(finding2.name)}</span>`}${finding2.other === void 0 ? "" : `<span class="sym">&#8596; ${escape(finding2.other)}</span>`}</td>
7359
8485
  <td class="meas">${measure === void 0 ? "" : `<b>${escape(typeof measure === "number" ? grouped(measure) : String(measure))}</b>${unit === void 0 ? "" : `<span>${escape(unit)}</span>`}`}</td>
7360
8486
  </tr>`;
7361
8487
  }
@@ -7365,7 +8491,7 @@ function findingsPanel(report) {
7365
8491
  return ` <p class="read">This run produced no finding to list.</p>`;
7366
8492
  }
7367
8493
  const counts = /* @__PURE__ */ new Map();
7368
- for (const finding of findings) counts.set(finding.metric, (counts.get(finding.metric) ?? 0) + 1);
8494
+ for (const finding2 of findings) counts.set(finding2.metric, (counts.get(finding2.metric) ?? 0) + 1);
7369
8495
  const chips = [...counts.entries()].map(
7370
8496
  ([metric, count]) => `<button type="button" class="fchip" data-kind="${escape(kindOf2(metric))}">${escape(kindOf2(metric))} <span>${grouped(count)}</span></button>`
7371
8497
  ).join("\n ");
@@ -7388,7 +8514,7 @@ function findingsPanel(report) {
7388
8514
  <tr><th scope="col">Kind</th><th scope="col">Where</th><th scope="col" class="meas">Measure</th></tr>
7389
8515
  </thead>
7390
8516
  <tbody>
7391
- ${findings.map((finding) => findingRow(finding)).join("\n")}
8517
+ ${findings.map((finding2) => findingRow(finding2)).join("\n")}
7392
8518
  </tbody>
7393
8519
  </table>`;
7394
8520
  }
@@ -7401,7 +8527,7 @@ function summaryParts(report, bold) {
7401
8527
  const withReference = report.rows.filter((row) => row.status !== void 0);
7402
8528
  const above = withReference.filter((row) => row.status === "exceed").length;
7403
8529
  const findings = report.findings ?? [];
7404
- const inTests = findings.filter((finding) => finding.metric === "clonesTests").length;
8530
+ const inTests = findings.filter((finding2) => finding2.metric === "clonesTests").length;
7405
8531
  const inProduct = findings.length - inTests;
7406
8532
  const thresholds = withReference.length === 0 ? [] : above === 0 ? [`${bold("No threshold exceeded")}: the measured levels are within the recommended norm`] : [`${bold(`${above} threshold${above === 1 ? "" : "s"} exceeded`)}`];
7407
8533
  return [
@@ -7980,7 +9106,7 @@ function linesOf(cache, cwd, relative5) {
7980
9106
  if (cache.has(relative5)) return cache.get(relative5);
7981
9107
  let lines;
7982
9108
  try {
7983
- lines = readFileSync18(join24(cwd, relative5), "utf8").split("\n");
9109
+ lines = readFileSync19(join28(cwd, relative5), "utf8").split("\n");
7984
9110
  } catch {
7985
9111
  lines = void 0;
7986
9112
  }
@@ -8003,16 +9129,16 @@ function declarationOf(lines, name) {
8003
9129
  }
8004
9130
  return hits.length === 1 ? hits[0] : void 0;
8005
9131
  }
8006
- function windowFor(finding, lines) {
8007
- const { line } = pointOf(finding.path);
9132
+ function windowFor(finding2, lines) {
9133
+ const { line } = pointOf(finding2.path);
8008
9134
  const head = () => ({
8009
9135
  firstLine: 1,
8010
9136
  text: lines.slice(0, WINDOW.lines).join("\n")
8011
9137
  });
8012
- if (WHOLE_FILE.has(finding.metric) || SHOWS_THE_HEAD.has(finding.metric)) return head();
8013
- const at = line ?? (finding.name ? declarationOf(lines, finding.name) : void 0);
9138
+ if (WHOLE_FILE.has(finding2.metric) || SHOWS_THE_HEAD.has(finding2.metric)) return head();
9139
+ const at = line ?? (finding2.name ? declarationOf(lines, finding2.name) : void 0);
8014
9140
  if (at === void 0) return void 0;
8015
- const nests = NESTS_UPWARD.has(finding.metric);
9141
+ const nests = NESTS_UPWARD.has(finding2.metric);
8016
9142
  const from = nests ? Math.max(1, at - WINDOW.nesting.before) : at;
8017
9143
  const to = Math.min(lines.length, nests ? at + WINDOW.nesting.after : at + WINDOW.lines - 1);
8018
9144
  if (from > lines.length) return void 0;
@@ -8021,18 +9147,18 @@ function windowFor(finding, lines) {
8021
9147
  function withExcerpts(report, cwd) {
8022
9148
  const wanted = /* @__PURE__ */ new Set();
8023
9149
  for (const group of listedByMetric(report.findings).values()) {
8024
- for (const finding of group.listed) {
8025
- if (finding.excerpt === void 0) wanted.add(finding);
9150
+ for (const finding2 of group.listed) {
9151
+ if (finding2.excerpt === void 0) wanted.add(finding2);
8026
9152
  }
8027
9153
  }
8028
9154
  if (wanted.size === 0) return report;
8029
9155
  const cache = /* @__PURE__ */ new Map();
8030
- const findings = report.findings.map((finding) => {
8031
- if (!wanted.has(finding)) return finding;
8032
- const lines = linesOf(cache, cwd, pointOf(finding.path).file);
8033
- if (lines === void 0) return finding;
8034
- const found = windowFor(finding, lines);
8035
- return found === void 0 ? finding : { ...finding, excerpt: found.text, excerptFirstLine: found.firstLine };
9156
+ const findings = report.findings.map((finding2) => {
9157
+ if (!wanted.has(finding2)) return finding2;
9158
+ const lines = linesOf(cache, cwd, pointOf(finding2.path).file);
9159
+ if (lines === void 0) return finding2;
9160
+ const found = windowFor(finding2, lines);
9161
+ return found === void 0 ? finding2 : { ...finding2, excerpt: found.text, excerptFirstLine: found.firstLine };
8036
9162
  });
8037
9163
  return { ...report, findings };
8038
9164
  }
@@ -8212,7 +9338,7 @@ var SCRIPT = `(function(){
8212
9338
  })();`;
8213
9339
 
8214
9340
  // src/roles/static-analysis/graph/imports.ts
8215
- import { dirname, join as join25 } from "node:path";
9341
+ import { dirname as dirname2, join as join29 } from "node:path";
8216
9342
  var TS_BEHIND_JS = {
8217
9343
  ".js": [".ts", ".tsx"],
8218
9344
  ".mjs": [".mts"],
@@ -8265,7 +9391,7 @@ function specifiersOf(program) {
8265
9391
  }
8266
9392
  function resolveSpecifier(from, specifier, known) {
8267
9393
  if (!specifier.startsWith(".")) return void 0;
8268
- const base = join25(dirname(from), specifier);
9394
+ const base = join29(dirname2(from), specifier);
8269
9395
  if (known.has(base)) return base;
8270
9396
  for (const extension of RESOLVABLE_EXTENSIONS) {
8271
9397
  if (known.has(base + extension)) return base + extension;
@@ -8276,7 +9402,7 @@ function resolveSpecifier(from, specifier, known) {
8276
9402
  if (known.has(swapped)) return swapped;
8277
9403
  }
8278
9404
  for (const extension of RESOLVABLE_EXTENSIONS) {
8279
- const index = join25(base, `index${extension}`);
9405
+ const index = join29(base, `index${extension}`);
8280
9406
  if (known.has(index)) return index;
8281
9407
  }
8282
9408
  return void 0;
@@ -8389,26 +9515,26 @@ function scanSyntax(cwd, config) {
8389
9515
 
8390
9516
  // src/roles/static-analysis/workspace/consumers.ts
8391
9517
  import { spawnSync as spawnSync5 } from "node:child_process";
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");
9518
+ import { existsSync as existsSync19, mkdirSync as mkdirSync2, readFileSync as readFileSync20, statSync as statSync6, writeFileSync as writeFileSync7 } from "node:fs";
9519
+ import { dirname as dirname3, join as join30, relative as relative4, resolve as resolve3, sep } from "node:path";
9520
+ var CACHE_DIR = join30("node_modules", ".cache", "sentinel");
8395
9521
  var CACHE_FILE = "consumer-index.json";
8396
9522
  var SHAPE = 4;
8397
9523
  function workspaceRootFrom(cwd) {
8398
9524
  let dir = resolve3(cwd);
8399
9525
  for (; ; ) {
8400
- if (existsSync17(join26(dir, WORKSPACE_ROOT_MARKER))) return dir;
8401
- const parent = dirname2(dir);
9526
+ if (existsSync19(join30(dir, WORKSPACE_ROOT_MARKER))) return dir;
9527
+ const parent = dirname3(dir);
8402
9528
  if (parent === dir) return void 0;
8403
9529
  dir = parent;
8404
9530
  }
8405
9531
  }
8406
9532
  function aliasTable(workspaceRoot) {
8407
- const file = join26(workspaceRoot, "tsconfig.base.json");
8408
- if (!existsSync17(file)) return /* @__PURE__ */ new Map();
9533
+ const file = join30(workspaceRoot, "tsconfig.base.json");
9534
+ if (!existsSync19(file)) return /* @__PURE__ */ new Map();
8409
9535
  let paths;
8410
9536
  try {
8411
- const parsed = JSON.parse(readFileSync19(file, "utf8"));
9537
+ const parsed = JSON.parse(readFileSync20(file, "utf8"));
8412
9538
  paths = parsed.compilerOptions?.paths ?? {};
8413
9539
  } catch {
8414
9540
  return /* @__PURE__ */ new Map();
@@ -8426,7 +9552,7 @@ function aliasTable(workspaceRoot) {
8426
9552
  function resolveSpecifier2(specifier, table, workspaceRoot) {
8427
9553
  const isFile = (candidate) => {
8428
9554
  try {
8429
- return statSync5(join26(workspaceRoot, candidate)).isFile();
9555
+ return statSync6(join30(workspaceRoot, candidate)).isFile();
8430
9556
  } catch {
8431
9557
  return false;
8432
9558
  }
@@ -8463,11 +9589,11 @@ function resolveSpecifier2(specifier, table, workspaceRoot) {
8463
9589
  return void 0;
8464
9590
  }
8465
9591
  function resolveRelative(importer, specifier, workspaceRoot) {
8466
- const base = join26(dirname2(importer), specifier).split(sep).join("/");
9592
+ const base = join30(dirname3(importer), specifier).split(sep).join("/");
8467
9593
  if (base.startsWith("..")) return void 0;
8468
9594
  const isFile = (candidate) => {
8469
9595
  try {
8470
- return statSync5(join26(workspaceRoot, candidate)).isFile();
9596
+ return statSync6(join30(workspaceRoot, candidate)).isFile();
8471
9597
  } catch {
8472
9598
  return false;
8473
9599
  }
@@ -8565,10 +9691,10 @@ function hash(text) {
8565
9691
  }
8566
9692
  function consumerIndex(workspaceRoot, moduleRoots) {
8567
9693
  const key = cacheKey(workspaceRoot);
8568
- const file = join26(workspaceRoot, CACHE_DIR, CACHE_FILE);
8569
- if (key && existsSync17(file)) {
9694
+ const file = join30(workspaceRoot, CACHE_DIR, CACHE_FILE);
9695
+ if (key && existsSync19(file)) {
8570
9696
  try {
8571
- const cached = JSON.parse(readFileSync19(file, "utf8"));
9697
+ const cached = JSON.parse(readFileSync20(file, "utf8"));
8572
9698
  if (cached.key === key) {
8573
9699
  return {
8574
9700
  imported: new Set(cached.imported),
@@ -8582,14 +9708,14 @@ function consumerIndex(workspaceRoot, moduleRoots) {
8582
9708
  const index = buildConsumerIndex(workspaceRoot, moduleRoots());
8583
9709
  if (key) {
8584
9710
  try {
8585
- mkdirSync(join26(workspaceRoot, CACHE_DIR), { recursive: true });
9711
+ mkdirSync2(join30(workspaceRoot, CACHE_DIR), { recursive: true });
8586
9712
  const payload = {
8587
9713
  key,
8588
9714
  imported: [...index.imported],
8589
9715
  testOnly: [...index.testOnly],
8590
9716
  builtFrom: index.builtFrom
8591
9717
  };
8592
- writeFileSync6(file, JSON.stringify(payload), "utf8");
9718
+ writeFileSync7(file, JSON.stringify(payload), "utf8");
8593
9719
  } catch {
8594
9720
  }
8595
9721
  }
@@ -8686,7 +9812,7 @@ var JscpdAdapter = class extends BaseAdapter {
8686
9812
  operations: [
8687
9813
  // `write` and not `merge-json`: this file is code, and the engine's in-place merge is for
8688
9814
  // JSON. A re-init therefore RESTORES the stub, so an existing one is left alone below.
8689
- ...existsSync18(join27(context2.cwd, STATIC_CONFIG_FILE)) ? [] : [{ kind: "write", path: STATIC_CONFIG_FILE, contents: configStub() }],
9815
+ ...existsSync20(join31(context2.cwd, STATIC_CONFIG_FILE)) ? [] : [{ kind: "write", path: STATIC_CONFIG_FILE, contents: configStub() }],
8690
9816
  manifestOperation(context2.cwd, scripts()),
8691
9817
  ...nxTargetOperations({ cwd: context2.cwd, targets: targets() })
8692
9818
  // The report directory is ignored ONCE at the workspace root, by `--init`'s workspace prep,
@@ -8803,7 +9929,7 @@ var JscpdAdapter = class extends BaseAdapter {
8803
9929
  const written = [];
8804
9930
  for (const shape of shapes)
8805
9931
  written.push(...await emit(report, shape, ctx.cwd, graph, coverage));
8806
- process.stdout.write(renderConsole(report));
9932
+ process.stdout.write(renderConsole2(report));
8807
9933
  if (written.length > 0) process.stdout.write(`
8808
9934
  written: ${written.join(", ")}
8809
9935
  `);
@@ -8936,7 +10062,7 @@ var JscpdAdapter = class extends BaseAdapter {
8936
10062
  const declared = moduleScripts(ctx.cwd);
8937
10063
  const missing = Object.keys(scripts()).filter((name) => !(name in declared));
8938
10064
  const adopted = missing.length < Object.keys(scripts()).length;
8939
- const hasConfig = existsSync18(join27(ctx.cwd, STATIC_CONFIG_FILE));
10065
+ const hasConfig = existsSync20(join31(ctx.cwd, STATIC_CONFIG_FILE));
8940
10066
  return {
8941
10067
  adopted,
8942
10068
  preset: adopted ? ctx.preset : null,
@@ -9028,15 +10154,15 @@ function runDeadCode(jscpd, config, paths, cwd, extraEntries = []) {
9028
10154
  try {
9029
10155
  const ran = runTool(jscpd, args, cwd, "dead-code");
9030
10156
  if (ran.kind !== "ran") return ran;
9031
- const path2 = join27(rawReportDir, RAW_REPORT["dead-code"]);
9032
- if (!existsSync18(path2)) {
10157
+ const path2 = join31(rawReportDir, RAW_REPORT["dead-code"]);
10158
+ if (!existsSync20(path2)) {
9033
10159
  process.stderr.write(
9034
10160
  `sentinel static-analysis(jscpd): the dead-code pass wrote no ${RAW_REPORT["dead-code"]}
9035
10161
  `
9036
10162
  );
9037
10163
  return { kind: "failed", code: 1 };
9038
10164
  }
9039
- const parsed = safeParseDeadCode(readFileSync20(path2, "utf8"));
10165
+ const parsed = safeParseDeadCode(readFileSync21(path2, "utf8"));
9040
10166
  const statistics = parsed?.statistics;
9041
10167
  if (!statistics) {
9042
10168
  process.stderr.write(
@@ -9049,8 +10175,8 @@ function runDeadCode(jscpd, config, paths, cwd, extraEntries = []) {
9049
10175
  return { kind: "no-entry-point", files: statistics.files ?? 0 };
9050
10176
  }
9051
10177
  const byCategory = {};
9052
- for (const finding of parsed?.findings ?? []) {
9053
- const category = finding.category ?? "unknown";
10178
+ for (const finding2 of parsed?.findings ?? []) {
10179
+ const category = finding2.category ?? "unknown";
9054
10180
  byCategory[category] = (byCategory[category] ?? 0) + 1;
9055
10181
  }
9056
10182
  return { kind: "figures", statistics, byCategory, findings: parsed?.findings ?? [] };
@@ -9059,11 +10185,11 @@ function runDeadCode(jscpd, config, paths, cwd, extraEntries = []) {
9059
10185
  }
9060
10186
  }
9061
10187
  function moduleRelative(name, roots, cwd) {
9062
- if (existsSync18(join27(cwd, name))) return name;
10188
+ if (existsSync20(join31(cwd, name))) return name;
9063
10189
  for (const root of roots) {
9064
10190
  if (root === ".") continue;
9065
10191
  const candidate = `${root}/${name}`;
9066
- if (existsSync18(join27(cwd, candidate))) return candidate;
10192
+ if (existsSync20(join31(cwd, candidate))) return candidate;
9067
10193
  }
9068
10194
  return name;
9069
10195
  }
@@ -9072,8 +10198,8 @@ function runDuplication(jscpd, config, paths, scope, cwd) {
9072
10198
  try {
9073
10199
  const ran = runTool(jscpd, args, cwd, scope);
9074
10200
  if (ran.kind !== "ran") return ran;
9075
- const path2 = join27(rawReportDir, RAW_REPORT.duplication);
9076
- const parsed = existsSync18(path2) ? safeParse(readFileSync20(path2, "utf8")) : void 0;
10201
+ const path2 = join31(rawReportDir, RAW_REPORT.duplication);
10202
+ const parsed = existsSync20(path2) ? safeParse(readFileSync21(path2, "utf8")) : void 0;
9077
10203
  const total = parsed?.statistics?.total;
9078
10204
  if (!total) {
9079
10205
  process.stderr.write(
@@ -9137,7 +10263,7 @@ function placesToLook(product, tests, dead, complexity, graph, size, outsideTest
9137
10263
  const toolTestOnlyPaths = new Set(
9138
10264
  dead.kind === "figures" ? dead.findings.filter((f) => f.category === "unused-file" && f.reasons?.includes("used-only-by-tests")).map((f) => f.path ?? "").filter(Boolean) : []
9139
10265
  );
9140
- const metricOf = (finding) => finding.category === "unused-file" && finding.reasons?.includes("used-only-by-tests") === true ? "usedOnlyByTests" : deadMetric[finding.category ?? ""] ?? "deadLinesPercent";
10266
+ const metricOf = (finding2) => finding2.category === "unused-file" && finding2.reasons?.includes("used-only-by-tests") === true ? "usedOnlyByTests" : deadMetric[finding2.category ?? ""] ?? "deadLinesPercent";
9141
10267
  const weight = (f) => f.value ?? f.lines ?? 0;
9142
10268
  const inOrder = (all2) => [...all2].sort(
9143
10269
  (left, right) => left.metric.localeCompare(right.metric) || weight(right) - weight(left) || left.path.localeCompare(right.path) || (left.other ?? "").localeCompare(right.other ?? "")
@@ -9152,14 +10278,14 @@ function placesToLook(product, tests, dead, complexity, graph, size, outsideTest
9152
10278
  name: fn.name,
9153
10279
  value: fn.complexity
9154
10280
  })),
9155
- ...dead.kind === "figures" && !unreadable ? dead.findings.map((finding) => ({
9156
- metric: metricOf(finding),
9157
- path: finding.path ?? "(unknown)",
9158
- ...finding.name ? { name: finding.name } : {},
9159
- ...finding.lines === void 0 ? {} : { lines: finding.lines },
9160
- ...finding.confidence === void 0 ? {} : { confidence: finding.confidence },
9161
- ...finding.reasons?.length ? { reason: finding.reasons.join(", ") } : {},
9162
- ...finding.message ? { message: finding.message } : {}
10281
+ ...dead.kind === "figures" && !unreadable ? dead.findings.map((finding2) => ({
10282
+ metric: metricOf(finding2),
10283
+ path: finding2.path ?? "(unknown)",
10284
+ ...finding2.name ? { name: finding2.name } : {},
10285
+ ...finding2.lines === void 0 ? {} : { lines: finding2.lines },
10286
+ ...finding2.confidence === void 0 ? {} : { confidence: finding2.confidence },
10287
+ ...finding2.reasons?.length ? { reason: finding2.reasons.join(", ") } : {},
10288
+ ...finding2.message ? { message: finding2.message } : {}
9163
10289
  })) : [],
9164
10290
  /*
9165
10291
  * The files the workspace index found, which the tool cannot see.
@@ -9242,8 +10368,8 @@ function outsideOf(cwd) {
9242
10368
  function moduleRootsOf(workspaceRoot) {
9243
10369
  const roots = [];
9244
10370
  for (const area of ["apps", "libs"]) {
9245
- const base = join27(workspaceRoot, area);
9246
- if (!existsSync18(base)) continue;
10371
+ const base = join31(workspaceRoot, area);
10372
+ if (!existsSync20(base)) continue;
9247
10373
  for (const entry of globSync5([`${area}/**/package.json`, `${area}/**/project.json`], {
9248
10374
  cwd: workspaceRoot,
9249
10375
  ignore: ["**/node_modules/**"],
@@ -9400,8 +10526,8 @@ function assembleReport({
9400
10526
  }
9401
10527
  const toolTestOnly = new Set(
9402
10528
  dead.kind === "figures" ? dead.findings.filter(
9403
- (finding) => finding.category === "unused-file" && finding.reasons?.includes("used-only-by-tests") === true
9404
- ).map((finding) => finding.path ?? "").filter(Boolean) : []
10529
+ (finding2) => finding2.category === "unused-file" && finding2.reasons?.includes("used-only-by-tests") === true
10530
+ ).map((finding2) => finding2.path ?? "").filter(Boolean) : []
9405
10531
  );
9406
10532
  if (dead.kind === "figures" && !unreadable) {
9407
10533
  const stats = dead.statistics;
@@ -9576,23 +10702,23 @@ function assembleReport({
9576
10702
  byLanguage
9577
10703
  };
9578
10704
  }
9579
- var SHAPE_FILE = {
10705
+ var SHAPE_FILE2 = {
9580
10706
  json: `${TARGET}.json`,
9581
10707
  html: `${TARGET}.html`,
9582
10708
  markdown: `${TARGET}.md`
9583
10709
  };
9584
10710
  var PICTURE_FILE = `${TARGET}-graph.svg`;
9585
10711
  function clearStaleOutputs(cwd, keeping) {
9586
- for (const name of [...Object.values(SHAPE_FILE), PICTURE_FILE]) {
10712
+ for (const name of [...Object.values(SHAPE_FILE2), PICTURE_FILE]) {
9587
10713
  if (keeping.has(name)) continue;
9588
- const path2 = join27(cwd, REPORT_DIR, name);
9589
- if (existsSync18(path2)) unlinkSync(path2);
10714
+ const path2 = join31(cwd, REPORT_DIR, name);
10715
+ if (existsSync20(path2)) unlinkSync2(path2);
9590
10716
  }
9591
10717
  }
9592
10718
  function outputsOf(shapes, graph, coverage) {
9593
10719
  const keeping = /* @__PURE__ */ new Set();
9594
10720
  for (const shape of shapes) {
9595
- const name = SHAPE_FILE[shape];
10721
+ const name = SHAPE_FILE2[shape];
9596
10722
  if (name !== void 0) keeping.add(name);
9597
10723
  }
9598
10724
  if (shapes.includes("html") && drawsPicture(graph, coverage)) keeping.add(PICTURE_FILE);
@@ -9604,19 +10730,19 @@ function drawsPicture(graph, coverage) {
9604
10730
  async function emit(report, shape, cwd, graph, coverage) {
9605
10731
  if (shape === "console") return [];
9606
10732
  const detailed = shape === "markdown" ? report : withExcerpts(report, cwd);
9607
- mkdirSync2(join27(cwd, REPORT_DIR), { recursive: true });
10733
+ mkdirSync3(join31(cwd, REPORT_DIR), { recursive: true });
9608
10734
  const written = [];
9609
10735
  if (shape === "html" && drawsPicture(graph, coverage)) {
9610
10736
  const drawn = layOut(graph.graph, graph.communityOf);
9611
- const path2 = join27(cwd, REPORT_DIR, PICTURE_FILE);
10737
+ const path2 = join31(cwd, REPORT_DIR, PICTURE_FILE);
9612
10738
  await writeGraphPicture(drawn, path2);
9613
10739
  written.push(`${REPORT_DIR}/${PICTURE_FILE}`);
9614
10740
  detailed.picture = buildExplorer(drawn, graph.communityOf).markup;
9615
10741
  }
9616
- const RENDER = { json: renderJson, html: renderHtml, markdown: renderMarkdown };
9617
- const name = SHAPE_FILE[shape] ?? `${TARGET}.${shape}`;
10742
+ const RENDER = { json: renderJson2, html: renderHtml, markdown: renderMarkdown2 };
10743
+ const name = SHAPE_FILE2[shape] ?? `${TARGET}.${shape}`;
9618
10744
  const body = RENDER[shape](detailed);
9619
- writeFileSync7(join27(cwd, REPORT_DIR, name), body, "utf8");
10745
+ writeFileSync8(join31(cwd, REPORT_DIR, name), body, "utf8");
9620
10746
  written.unshift(`${REPORT_DIR}/${name}`);
9621
10747
  return written;
9622
10748
  }
@@ -9650,9 +10776,9 @@ function registerTest() {
9650
10776
 
9651
10777
  // src/roles/typescript/adapters/tsc/tsc.adapter.ts
9652
10778
  import { spawnSync as spawnSync7 } from "node:child_process";
9653
- import { existsSync as existsSync19, readFileSync as readFileSync22 } from "node:fs";
10779
+ import { existsSync as existsSync21, readFileSync as readFileSync23 } from "node:fs";
9654
10780
  import { createRequire } from "node:module";
9655
- import { join as join29 } from "node:path";
10781
+ import { join as join33 } from "node:path";
9656
10782
 
9657
10783
  // src/roles/typescript/presets/nest.json
9658
10784
  var nest_default2 = {
@@ -9704,7 +10830,7 @@ var react_default2 = {
9704
10830
  };
9705
10831
 
9706
10832
  // src/roles/typescript/presets/shared.json
9707
- var shared_default2 = {
10833
+ var shared_default3 = {
9708
10834
  $schema: "https://json.schemastore.org/tsconfig",
9709
10835
  compilerOptions: {
9710
10836
  strict: true,
@@ -9730,7 +10856,7 @@ var shared_default2 = {
9730
10856
  };
9731
10857
 
9732
10858
  // src/roles/typescript/preset-data.ts
9733
- var SHARED = shared_default2;
10859
+ var SHARED = shared_default3;
9734
10860
  var LAYERS = {
9735
10861
  nest: nest_default2,
9736
10862
  node: node_default2,
@@ -9837,8 +10963,8 @@ function readTsconfigAdoption(cwd) {
9837
10963
  }
9838
10964
 
9839
10965
  // src/roles/typescript/adapters/tsc/plan.ts
9840
- import { readFileSync as readFileSync21 } from "node:fs";
9841
- import { join as join28 } from "node:path";
10966
+ import { readFileSync as readFileSync22 } from "node:fs";
10967
+ import { join as join32 } from "node:path";
9842
10968
 
9843
10969
  // src/roles/typescript/typecheck-script.ts
9844
10970
  var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
@@ -9931,7 +11057,7 @@ function planAdoption(context2) {
9931
11057
  };
9932
11058
  }
9933
11059
  const existing = parseJsonc(
9934
- readFileSync21(join28(context2.cwd, target.path), "utf8"),
11060
+ readFileSync22(join32(context2.cwd, target.path), "utf8"),
9935
11061
  target.path
9936
11062
  );
9937
11063
  const extendsChain = composeExtends(existing.extends, preset);
@@ -10069,7 +11195,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
10069
11195
  let chain;
10070
11196
  try {
10071
11197
  const parsed = parseJsonc(
10072
- readFileSync22(join29(cwd, target.path), "utf8"),
11198
+ readFileSync23(join33(cwd, target.path), "utf8"),
10073
11199
  target.path
10074
11200
  );
10075
11201
  chain = parsed.extends;
@@ -10082,7 +11208,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
10082
11208
  );
10083
11209
  if (preset === void 0) return void 0;
10084
11210
  try {
10085
- createRequire(join29(cwd, "noop.js")).resolve(preset);
11211
+ createRequire(join33(cwd, "noop.js")).resolve(preset);
10086
11212
  return void 0;
10087
11213
  } catch {
10088
11214
  return preset;
@@ -10178,7 +11304,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
10178
11304
  referencedProjects(cwd, config) {
10179
11305
  try {
10180
11306
  const parsed = parseJsonc(
10181
- readFileSync22(join29(cwd, config), "utf8"),
11307
+ readFileSync23(join33(cwd, config), "utf8"),
10182
11308
  config
10183
11309
  );
10184
11310
  const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path2) => typeof path2 === "string" && path2.length > 0);
@@ -10194,7 +11320,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
10194
11320
  * check.
10195
11321
  */
10196
11322
  typecheckTarget(cwd) {
10197
- if (existsSync19(join29(cwd, "tsconfig.json"))) return "tsconfig.json";
11323
+ if (existsSync21(join33(cwd, "tsconfig.json"))) return "tsconfig.json";
10198
11324
  const target = resolveTsconfigTarget(cwd);
10199
11325
  return target.reason === "none" ? null : target.path;
10200
11326
  }
@@ -10319,6 +11445,7 @@ function registerAdapters() {
10319
11445
  registerBuild();
10320
11446
  registerTest();
10321
11447
  registerStaticAnalysis();
11448
+ registerEnv();
10322
11449
  }
10323
11450
 
10324
11451
  // src/core/detect-framework.ts
@@ -10365,12 +11492,12 @@ function diffLines(before, after) {
10365
11492
  { length: from.length + 1 },
10366
11493
  () => new Array(to.length + 1).fill(0)
10367
11494
  );
10368
- const cell = (i2, j2) => lcs[i2]?.[j2] ?? 0;
11495
+ const cell2 = (i2, j2) => lcs[i2]?.[j2] ?? 0;
10369
11496
  for (let i2 = from.length - 1; i2 >= 0; i2--) {
10370
11497
  const row = lcs[i2];
10371
11498
  if (!row) continue;
10372
11499
  for (let j2 = to.length - 1; j2 >= 0; j2--) {
10373
- row[j2] = from[i2] === to[j2] ? cell(i2 + 1, j2 + 1) + 1 : Math.max(cell(i2 + 1, j2), cell(i2, j2 + 1));
11500
+ row[j2] = from[i2] === to[j2] ? cell2(i2 + 1, j2 + 1) + 1 : Math.max(cell2(i2 + 1, j2), cell2(i2, j2 + 1));
10374
11501
  }
10375
11502
  }
10376
11503
  const out = [];
@@ -10381,7 +11508,7 @@ function diffLines(before, after) {
10381
11508
  out.push(` ${from[i] ?? ""}`);
10382
11509
  i++;
10383
11510
  j++;
10384
- } else if (cell(i + 1, j) >= cell(i, j + 1)) {
11511
+ } else if (cell2(i + 1, j) >= cell2(i, j + 1)) {
10385
11512
  out.push(`- ${from[i] ?? ""}`);
10386
11513
  i++;
10387
11514
  } else {
@@ -10400,7 +11527,7 @@ function replaceLines(current, replacements) {
10400
11527
  }
10401
11528
 
10402
11529
  // src/core/apply-plan.ts
10403
- import { existsSync as existsSync20, readFileSync as readFileSync23, renameSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "node:fs";
11530
+ import { existsSync as existsSync22, readFileSync as readFileSync24, renameSync, rmSync as rmSync5, writeFileSync as writeFileSync9 } from "node:fs";
10404
11531
  import { resolve as resolve4, sep as sep2 } from "node:path";
10405
11532
  import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
10406
11533
  function resolveWithinRoot(cwd, relativePath) {
@@ -10412,7 +11539,7 @@ function resolveWithinRoot(cwd, relativePath) {
10412
11539
  return absolutePath;
10413
11540
  }
10414
11541
  function readIfExists(absolutePath) {
10415
- return existsSync20(absolutePath) ? readFileSync23(absolutePath, "utf8") : void 0;
11542
+ return existsSync22(absolutePath) ? readFileSync24(absolutePath, "utf8") : void 0;
10416
11543
  }
10417
11544
  function* leaves(value, prefix = []) {
10418
11545
  for (const [key, keyValue] of Object.entries(value)) {
@@ -10514,7 +11641,7 @@ function preparePlan(cwd, plan3) {
10514
11641
  }
10515
11642
  function writeFileAtomic(absolutePath, contents) {
10516
11643
  const tempPath = `${absolutePath}.sentinel-${process.pid}.tmp`;
10517
- writeFileSync8(tempPath, contents);
11644
+ writeFileSync9(tempPath, contents);
10518
11645
  renameSync(tempPath, absolutePath);
10519
11646
  }
10520
11647
  function applyPlan(cwd, plan3) {
@@ -10530,8 +11657,8 @@ function applyPlan(cwd, plan3) {
10530
11657
  }
10531
11658
 
10532
11659
  // src/core/config/preset-evidence.ts
10533
- import { existsSync as existsSync21, readdirSync as readdirSync3, readFileSync as readFileSync24 } from "node:fs";
10534
- import { join as join30 } from "node:path";
11660
+ import { existsSync as existsSync23, readdirSync as readdirSync3, readFileSync as readFileSync25 } from "node:fs";
11661
+ import { join as join34 } from "node:path";
10535
11662
  var PATH_SIGNALS = [
10536
11663
  {
10537
11664
  preset: "nest",
@@ -10565,12 +11692,12 @@ function sourceImports(cwd, pattern) {
10565
11692
  }
10566
11693
  for (const entry of entries) {
10567
11694
  if (entry.isDirectory()) {
10568
- if (!NOT_SCANNED.has(entry.name)) stack.push(join30(dir, entry.name));
11695
+ if (!NOT_SCANNED.has(entry.name)) stack.push(join34(dir, entry.name));
10569
11696
  continue;
10570
11697
  }
10571
11698
  if (!SOURCE_FILE.test(entry.name)) continue;
10572
11699
  try {
10573
- if (pattern.test(readFileSync24(join30(dir, entry.name), "utf8"))) return true;
11700
+ if (pattern.test(readFileSync25(join34(dir, entry.name), "utf8"))) return true;
10574
11701
  } catch {
10575
11702
  continue;
10576
11703
  }
@@ -10579,11 +11706,11 @@ function sourceImports(cwd, pattern) {
10579
11706
  return false;
10580
11707
  }
10581
11708
  function dependencyNames(cwd) {
10582
- const path2 = join30(cwd, "package.json");
10583
- if (!existsSync21(path2)) return [];
11709
+ const path2 = join34(cwd, "package.json");
11710
+ if (!existsSync23(path2)) return [];
10584
11711
  try {
10585
11712
  const manifest = parseJsonc(
10586
- readFileSync24(path2, "utf8"),
11713
+ readFileSync25(path2, "utf8"),
10587
11714
  path2
10588
11715
  );
10589
11716
  return [
@@ -10606,7 +11733,7 @@ function reactTsconfigEvidence(cwd) {
10606
11733
  }
10607
11734
  for (const name of entries) {
10608
11735
  try {
10609
- const config = parseJsonc(readFileSync24(join30(cwd, name), "utf8"), name);
11736
+ const config = parseJsonc(readFileSync25(join34(cwd, name), "utf8"), name);
10610
11737
  if (config.compilerOptions?.jsx !== void 0) return 'its tsconfig sets "jsx"';
10611
11738
  const extended = Array.isArray(config.extends) ? config.extends : config.extends === void 0 ? [] : [config.extends];
10612
11739
  if (extended.some((entry) => entry === REACT_TYPESCRIPT_PRESET)) {
@@ -10813,6 +11940,7 @@ export {
10813
11940
  resolve,
10814
11941
  buildConfigFile,
10815
11942
  palette,
11943
+ ENV_SHAPES,
10816
11944
  presetNotInstalledYet,
10817
11945
  registerAdapters,
10818
11946
  correctingSignal,