@hublo/sentinel 1.2.0-alpha.16 → 1.2.0-alpha.18

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.
@@ -22,7 +22,7 @@ import {
22
22
  registerAdapters,
23
23
  resolve,
24
24
  resolveBin
25
- } from "../chunk-Q3ZLHHW2.js";
25
+ } from "../chunk-MJ3S7LQT.js";
26
26
 
27
27
  // bin/sentinel.ts
28
28
  import { program } from "commander";
@@ -333,11 +333,19 @@ var BUILD_SCRIPT_NAME = "build";
333
333
  var SENTINEL_BUILD_COMMAND = "sentinel --run --build --";
334
334
  var DEV_SCRIPT_NAME = "serve";
335
335
  var SENTINEL_DEV_COMMAND = "sentinel --run --dev";
336
- function buildTarget(configFiles = BUILD_CONFIG_FILES) {
336
+ var BUILD_OUTPUT_CANDIDATES = ["dist", ".output", "build"];
337
+ function buildTarget(options) {
338
+ const { moduleName: moduleName3, configFiles = BUILD_CONFIG_FILES, outputs } = options;
337
339
  return {
338
340
  [BUILD_SCRIPT_NAME]: {
341
+ executor: "nx:run-commands",
342
+ options: {
343
+ command: `pnpm --filter ${moduleName3} run ${BUILD_SCRIPT_NAME}`,
344
+ forwardAllArgs: false
345
+ },
339
346
  cache: true,
340
- inputs: ["default", "^default", ...configFiles.map((name) => `{projectRoot}/${name}`)]
347
+ inputs: ["default", "^default", ...configFiles.map((name) => `{projectRoot}/${name}`)],
348
+ outputs: [...outputs]
341
349
  }
342
350
  };
343
351
  }
@@ -457,9 +465,9 @@ function binFromOwnInstall(packageName, binName) {
457
465
  try {
458
466
  const manifest = require2.resolve(`${packageName}/package.json`);
459
467
  const bin = require2(manifest).bin;
460
- const relative6 = typeof bin === "string" ? bin : bin?.[binName];
461
- if (!relative6) return void 0;
462
- const executable = join4(dirname2(manifest), relative6);
468
+ const relative5 = typeof bin === "string" ? bin : bin?.[binName];
469
+ if (!relative5) return void 0;
470
+ const executable = join4(dirname2(manifest), relative5);
463
471
  return existsSync5(executable) ? executable : void 0;
464
472
  } catch {
465
473
  return void 0;
@@ -472,8 +480,8 @@ function binSearchPath(cwd) {
472
480
  }
473
481
 
474
482
  // src/roles/build/plan.ts
475
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
476
- import { join as join8 } from "path";
483
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
484
+ import { join as join9 } from "path";
477
485
 
478
486
  // src/core/config/existing-command.ts
479
487
  import { readFileSync as readFileSync5 } from "fs";
@@ -518,6 +526,9 @@ function selfCommand(cwd, flags) {
518
526
  if (!isSelfAdoption(cwd) || !own.bin) return void 0;
519
527
  return `node ${own.bin.replace(/^\.\//, "")} ${flags}`;
520
528
  }
529
+ function adoptedPackageName(cwd) {
530
+ return readProjectPackageJson(cwd).name ?? readNxProjectName(cwd) ?? basename2(cwd);
531
+ }
521
532
  function manifestOperation(cwd, scripts) {
522
533
  const own = readOwnPackage();
523
534
  const value = {
@@ -530,7 +541,7 @@ function manifestOperation(cwd, scripts) {
530
541
  return {
531
542
  kind: "merge-json",
532
543
  path: "package.json",
533
- value: { name: readNxProjectName(cwd) ?? basename2(cwd), private: true, ...value }
544
+ value: { name: adoptedPackageName(cwd), private: true, ...value }
534
545
  };
535
546
  }
536
547
 
@@ -656,7 +667,8 @@ function composeDevScript(existing, command) {
656
667
  }
657
668
 
658
669
  // src/roles/build/nest/adopt.ts
659
- import { relative as relative2 } from "path";
670
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
671
+ import { join as join8 } from "path";
660
672
  var NEST_CONFIG_FILE = "vite.config.mts";
661
673
  function nestConfigSource(cwd, target) {
662
674
  const hops = workspaceRootHops(cwd, target);
@@ -673,34 +685,18 @@ export default nestService({
673
685
  })
674
686
  `;
675
687
  }
676
- function nestBuildTarget(cwd, target) {
677
- const workspaceRoot = workspaceRootFor(cwd, target);
678
- if (workspaceRoot === void 0) {
679
- throw new Error(
680
- `sentinel build(nest): cannot place ${cwd} inside a workspace from ${target.main}`
681
- );
682
- }
683
- const moduleRoot = relative2(workspaceRoot, cwd);
684
- return {
685
- executor: "nx:run-commands",
686
- outputs: [`{workspaceRoot}/${target.outputPath}`],
687
- options: {
688
- cwd: moduleRoot,
689
- command: "pnpm exec sentinel --run --build",
690
- forwardAllArgs: false
691
- }
692
- };
688
+ function nestBuildOutputs(target) {
689
+ return [`{workspaceRoot}/${target.outputPath}`];
690
+ }
691
+ var OUT_DIR_IN_CONFIG = /^\s*outDir:\s*'([^']+)'/m;
692
+ function nestOutputsFromConfig(cwd) {
693
+ const configPath = join8(cwd, NEST_CONFIG_FILE);
694
+ if (!existsSync8(configPath)) return void 0;
695
+ const outDir = OUT_DIR_IN_CONFIG.exec(readFileSync6(configPath, "utf8"))?.[1];
696
+ return outDir === void 0 ? void 0 : [`{workspaceRoot}/${outDir}`];
693
697
  }
694
698
  function nestAdoptionOperations(cwd, target) {
695
- return [
696
- { kind: "write", path: NEST_CONFIG_FILE, contents: nestConfigSource(cwd, target) },
697
- { kind: "remove-json-keys", path: "project.json", keys: [["targets", "build"]] },
698
- {
699
- kind: "merge-json",
700
- path: "project.json",
701
- value: { targets: { build: nestBuildTarget(cwd, target) } }
702
- }
703
- ];
699
+ return [{ kind: "write", path: NEST_CONFIG_FILE, contents: nestConfigSource(cwd, target) }];
704
700
  }
705
701
 
706
702
  // src/roles/build/requirements.ts
@@ -836,6 +832,9 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
836
832
  }
837
833
 
838
834
  // src/roles/build/plan.ts
835
+ function candidateOutputs() {
836
+ return BUILD_OUTPUT_CANDIDATES.map((name) => `{projectRoot}/${name}`);
837
+ }
839
838
  function ownedBuildDependencies(cwd) {
840
839
  const manifest = readProjectPackageJson(cwd);
841
840
  const keys = [];
@@ -937,7 +936,15 @@ function planReactApp(context, configFile) {
937
936
  );
938
937
  }
939
938
  operations.push(manifestOperation(context.cwd, buildScripts(context.cwd)));
940
- operations.push(...nxTargetOperations({ cwd: context.cwd, targets: buildTarget() }));
939
+ operations.push(
940
+ ...nxTargetOperations({
941
+ cwd: context.cwd,
942
+ targets: buildTarget({
943
+ moduleName: adoptedPackageName(context.cwd),
944
+ outputs: candidateOutputs()
945
+ })
946
+ })
947
+ );
941
948
  const owned = ownedBuildDependencies(context.cwd);
942
949
  if (owned.length > 0) {
943
950
  operations.push({ kind: "remove-json-keys", path: "package.json", keys: owned });
@@ -948,8 +955,22 @@ function planReactApp(context, configFile) {
948
955
  return { operations, notes };
949
956
  }
950
957
  function planNestService(context, webpackTarget) {
958
+ const outputs = webpackTarget === void 0 ? nestOutputsFromConfig(context.cwd) : nestBuildOutputs(webpackTarget);
959
+ if (outputs === void 0) {
960
+ return {
961
+ operations: [],
962
+ blocked: `${NEST_CONFIG_FILE} does not declare an \`outDir\`, so sentinel cannot say where this service's build lands. Declaring an nx target without it would cache the build and restore nothing from that cache. Nothing was written.`
963
+ };
964
+ }
951
965
  const metadata = [
952
- ...nxTargetMetadataOperations({ cwd: context.cwd, targets: buildTarget([NEST_CONFIG_FILE]) }),
966
+ ...nxTargetOperations({
967
+ cwd: context.cwd,
968
+ targets: buildTarget({
969
+ moduleName: adoptedPackageName(context.cwd),
970
+ configFiles: [NEST_CONFIG_FILE],
971
+ outputs
972
+ })
973
+ }),
953
974
  manifestOperation(context.cwd, { [BUILD_SCRIPT_NAME]: SENTINEL_BUILD_COMMAND })
954
975
  ];
955
976
  if (webpackTarget === void 0) return { operations: metadata };
@@ -968,9 +989,9 @@ function planNestService(context, webpackTarget) {
968
989
  };
969
990
  }
970
991
  function readConfigSource(cwd, configFile) {
971
- if (!existsSync8(join8(cwd, configFile))) return "";
992
+ if (!existsSync9(join9(cwd, configFile))) return "";
972
993
  try {
973
- return readFileSync6(join8(cwd, configFile), "utf8");
994
+ return readFileSync7(join9(cwd, configFile), "utf8");
974
995
  } catch {
975
996
  return "";
976
997
  }
@@ -1154,6 +1175,199 @@ var ViteDevAdapter = class extends ViteRoleAdapter {
1154
1175
  }
1155
1176
  };
1156
1177
 
1178
+ // src/roles/build/declared-outputs.ts
1179
+ import { existsSync as existsSync11, statSync } from "fs";
1180
+ import { join as join11 } from "path";
1181
+
1182
+ // src/core/workspace-prep.ts
1183
+ import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync } from "fs";
1184
+ import { dirname as dirname3, join as join10, relative as relative2 } from "path";
1185
+
1186
+ // src/core/settings.ts
1187
+ var WORKSPACE_ROOT_MARKER = "nx.json";
1188
+ var DEFAULT_MAX_DIAGNOSTICS = 100;
1189
+
1190
+ // src/core/workspace-prep.ts
1191
+ var OVERRIDE_KEY = "i18next>typescript";
1192
+ var NATIVE_TS_ALIAS = "@typescript/native";
1193
+ var WORKSPACE_YAML = "pnpm-workspace.yaml";
1194
+ var RELEASE_AGE_KEY = "minimumReleaseAgeExclude";
1195
+ var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
1196
+ function findWorkspaceRoot(startDir) {
1197
+ let dir = startDir;
1198
+ for (; ; ) {
1199
+ if (existsSync10(join10(dir, WORKSPACE_ROOT_MARKER))) return dir;
1200
+ const parent = dirname3(dir);
1201
+ if (parent === dir) return void 0;
1202
+ dir = parent;
1203
+ }
1204
+ }
1205
+ function declaredNativeTs(pkg) {
1206
+ const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
1207
+ if (!spec) return void 0;
1208
+ const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
1209
+ return version || void 0;
1210
+ }
1211
+ function ensureI18nextSingleton(root, dryRun) {
1212
+ const pkgPath = join10(root, "package.json");
1213
+ if (!existsSync10(pkgPath)) return void 0;
1214
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
1215
+ const want = declaredNativeTs(pkg);
1216
+ if (!want) return void 0;
1217
+ const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
1218
+ if (have === want) return void 0;
1219
+ if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} (i18next singleton)`;
1220
+ pkg.pnpm ??= {};
1221
+ pkg.pnpm.overrides ??= {};
1222
+ pkg.pnpm.overrides[OVERRIDE_KEY] = want;
1223
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1224
+ return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
1225
+ }
1226
+ function ensureReleaseAgeAllowList(root, dryRun) {
1227
+ const yamlPath = join10(root, WORKSPACE_YAML);
1228
+ if (!existsSync10(yamlPath)) return void 0;
1229
+ const own = readOwnPackage().name;
1230
+ const lines = readFileSync8(yamlPath, "utf8").split("\n");
1231
+ const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
1232
+ if (keyIdx === -1) return void 0;
1233
+ let lastItemIdx = keyIdx;
1234
+ let indent = " ";
1235
+ for (let i = keyIdx + 1; i < lines.length; i++) {
1236
+ const match = lines[i]?.match(LIST_ITEM);
1237
+ if (!match) break;
1238
+ indent = match[1] ?? indent;
1239
+ lastItemIdx = i;
1240
+ if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
1241
+ }
1242
+ if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
1243
+ lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
1244
+ writeFileSync(yamlPath, lines.join("\n"));
1245
+ return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
1246
+ }
1247
+ var PRETTIER_IGNORE = ".prettierignore";
1248
+ var EXCLUSION_HEADER = "# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier";
1249
+ var ROOT_PRETTIER_CONFIGS = [
1250
+ ".prettierrc",
1251
+ ".prettierrc.json",
1252
+ ".prettierrc.json5",
1253
+ ".prettierrc.yml",
1254
+ ".prettierrc.yaml",
1255
+ ".prettierrc.js",
1256
+ ".prettierrc.cjs",
1257
+ ".prettierrc.mjs",
1258
+ "prettier.config.js",
1259
+ "prettier.config.cjs",
1260
+ "prettier.config.mjs"
1261
+ ];
1262
+ function ensureFormatterExclusion(root, moduleDir, dryRun) {
1263
+ const rel = relative2(root, moduleDir).replaceAll("\\", "/");
1264
+ if (rel === "" || rel.startsWith("..")) return void 0;
1265
+ const ignorePath = join10(root, PRETTIER_IGNORE);
1266
+ const hasPrettier = existsSync10(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync10(join10(root, name)));
1267
+ if (!hasPrettier) return void 0;
1268
+ const pattern = `/${rel}/`;
1269
+ const existing = existsSync10(ignorePath) ? readFileSync8(ignorePath, "utf8") : "";
1270
+ const lines = existing.split("\n");
1271
+ if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
1272
+ return void 0;
1273
+ }
1274
+ if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`;
1275
+ const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER);
1276
+ if (headerIdx === -1) {
1277
+ const prefix = existing.trim() === "" ? "" : `${existing.replace(/\n*$/, "")}
1278
+
1279
+ `;
1280
+ writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}
1281
+ ${pattern}
1282
+ `);
1283
+ } else {
1284
+ let end = headerIdx + 1;
1285
+ while (end < lines.length && lines[end]?.trim().startsWith("/")) end += 1;
1286
+ const managed = [...lines.slice(headerIdx + 1, end), pattern].sort();
1287
+ writeFileSync(
1288
+ ignorePath,
1289
+ [...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join("\n")
1290
+ );
1291
+ }
1292
+ return `excluded ${rel} from the root Prettier (${PRETTIER_IGNORE}): it formats itself now`;
1293
+ }
1294
+ function ensureWorkspacePrep(opts) {
1295
+ const dryRun = Boolean(opts.dryRun);
1296
+ return [
1297
+ ensureI18nextSingleton(opts.root, dryRun),
1298
+ ensureReleaseAgeAllowList(opts.root, dryRun),
1299
+ opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
1300
+ ].filter((message) => message !== void 0);
1301
+ }
1302
+ function inspectWorkspacePrep(root) {
1303
+ const entries = [];
1304
+ let pkg = {};
1305
+ const pkgPath = join10(root, "package.json");
1306
+ if (existsSync10(pkgPath)) {
1307
+ try {
1308
+ pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
1309
+ } catch {
1310
+ pkg = {};
1311
+ }
1312
+ }
1313
+ const pinned = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
1314
+ if (pinned !== void 0) {
1315
+ const stillForks = declaredNativeTs(pkg);
1316
+ entries.push({
1317
+ rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
1318
+ reason: stillForks === void 0 ? `pinned to ${pinned}, but the root no longer declares ${NATIVE_TS_ALIAS}, so nothing forks any more. This override is now dead config and can be removed.` : `pinned to ${pinned}, matching the second TypeScript the root declares (${NATIVE_TS_ALIAS}). It re-resolves i18next for EVERY module that depends on it, not only adopted ones. It retires with the second TypeScript.`
1319
+ });
1320
+ }
1321
+ const own = readOwnPackage().name;
1322
+ const yamlPath = join10(root, WORKSPACE_YAML);
1323
+ if (existsSync10(yamlPath)) {
1324
+ const yaml = readFileSync8(yamlPath, "utf8");
1325
+ const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
1326
+ if (listed) {
1327
+ const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
1328
+ entries.push({
1329
+ rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
1330
+ reason: gated ? `${own} is exempt from this workspace's release-age gate, so a freshly published version installs instead of being held. That is a standing exemption from a supply-chain control: the exposure is one bump wide, since the version is pinned and the lockfile carries its integrity, so a re-publish of the same version does not pass. Retire it once a bump can simply wait out the window.` : `${own} is allow-listed, but this workspace no longer sets minimumReleaseAge, so there is no gate to be exempt from. This entry is now dead config and can be removed.`
1331
+ });
1332
+ }
1333
+ }
1334
+ return entries;
1335
+ }
1336
+
1337
+ // src/roles/build/declared-outputs.ts
1338
+ function declaredBuildTarget(cwd) {
1339
+ return readProjectPackageJson(cwd).nx?.targets?.[BUILD_SCRIPT_NAME];
1340
+ }
1341
+ function resolveOutputToken(token, cwd) {
1342
+ if (token.startsWith("{projectRoot}/")) return join11(cwd, token.slice("{projectRoot}/".length));
1343
+ if (token.startsWith("{workspaceRoot}/")) {
1344
+ const root = findWorkspaceRoot(cwd);
1345
+ return root === void 0 ? void 0 : join11(root, token.slice("{workspaceRoot}/".length));
1346
+ }
1347
+ return join11(cwd, token);
1348
+ }
1349
+ function writtenSince(path, startedAt) {
1350
+ if (!existsSync11(path)) return false;
1351
+ try {
1352
+ return statSync(path).mtimeMs >= startedAt;
1353
+ } catch {
1354
+ return true;
1355
+ }
1356
+ }
1357
+ function outputsProblem(cwd, startedAt) {
1358
+ const target = declaredBuildTarget(cwd);
1359
+ if (target === void 0) return void 0;
1360
+ const declared = target.outputs ?? [];
1361
+ if (declared.length === 0) {
1362
+ if (target.cache !== true) return void 0;
1363
+ return `this module's nx build target sets \`cache: true\` and declares no \`outputs\`, so nx will cache this build and restore nothing from it: the next cache hit reports success and leaves no build on disk. Add the directory this build writes to under \`nx.targets.${BUILD_SCRIPT_NAME}.outputs\` in package.json, or re-run \`sentinel --init --build\`, which declares it.`;
1364
+ }
1365
+ const resolved = declared.map((token) => ({ token, path: resolveOutputToken(token, cwd) })).filter((entry) => entry.path !== void 0);
1366
+ if (resolved.length === 0) return void 0;
1367
+ if (resolved.some((entry) => writtenSince(entry.path, startedAt))) return void 0;
1368
+ return `the build wrote nothing to any of the directories this module declares as its nx \`outputs\` (${declared.join(", ")}), so nx would cache this build and restore an empty result from it. Whatever directory the build DID write to belongs in \`nx.targets.${BUILD_SCRIPT_NAME}.outputs\` in package.json. The build itself succeeded; only its cache declaration is wrong.`;
1369
+ }
1370
+
1157
1371
  // src/roles/build/prerequisite.ts
1158
1372
  import { spawnSync as spawnSync2 } from "child_process";
1159
1373
  var PREBUILD_SCRIPT_NAME = `pre${BUILD_SCRIPT_NAME}`;
@@ -1202,7 +1416,15 @@ var ViteAdapter = class extends ViteRoleAdapter {
1202
1416
  );
1203
1417
  return { ok: false, code: prerequisite.code };
1204
1418
  }
1205
- return this.spawnVite(ctx, ready.vite, ["build"], 1);
1419
+ const startedAt = Date.now();
1420
+ const result = await this.spawnVite(ctx, ready.vite, ["build"], 1);
1421
+ if (!result.ok) return result;
1422
+ const problem = outputsProblem(ctx.cwd, startedAt);
1423
+ if (problem !== void 0) {
1424
+ this.say(problem);
1425
+ return { ok: false, code: 1 };
1426
+ }
1427
+ return result;
1206
1428
  }
1207
1429
  /**
1208
1430
  * What this module builds with, WITHOUT loading its config.
@@ -1274,12 +1496,12 @@ function registerBuild() {
1274
1496
 
1275
1497
  // src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
1276
1498
  import { spawnSync as spawnSync3 } from "child_process";
1277
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
1278
- import { join as join15 } from "path";
1499
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
1500
+ import { join as join17 } from "path";
1279
1501
 
1280
1502
  // src/core/config/has-source.ts
1281
1503
  import { readdirSync } from "fs";
1282
- import { extname, join as join9, relative as relative3 } from "path";
1504
+ import { extname, join as join12, relative as relative3 } from "path";
1283
1505
  var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
1284
1506
  "node_modules",
1285
1507
  "dist",
@@ -1330,7 +1552,7 @@ function hasSourceFiles(cwd, extensions) {
1330
1552
  }
1331
1553
  for (const entry of entries) {
1332
1554
  if (entry.isDirectory()) {
1333
- if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join9(dir, entry.name));
1555
+ if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join12(dir, entry.name));
1334
1556
  continue;
1335
1557
  }
1336
1558
  if (wanted.has(extname(entry.name))) return true;
@@ -1351,7 +1573,7 @@ function listSourceFiles(cwd, extensions) {
1351
1573
  continue;
1352
1574
  }
1353
1575
  for (const entry of entries) {
1354
- const full = join9(dir, entry.name);
1576
+ const full = join12(dir, entry.name);
1355
1577
  if (entry.isDirectory()) {
1356
1578
  if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
1357
1579
  continue;
@@ -1369,159 +1591,6 @@ function scopeFlags(paths, options = []) {
1369
1591
  return [TOLERATE_EMPTY_FLAG];
1370
1592
  }
1371
1593
 
1372
- // src/core/settings.ts
1373
- var WORKSPACE_ROOT_MARKER = "nx.json";
1374
- var DEFAULT_MAX_DIAGNOSTICS = 100;
1375
-
1376
- // src/core/workspace-prep.ts
1377
- import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync } from "fs";
1378
- import { dirname as dirname3, join as join10, relative as relative4 } from "path";
1379
- var OVERRIDE_KEY = "i18next>typescript";
1380
- var NATIVE_TS_ALIAS = "@typescript/native";
1381
- var WORKSPACE_YAML = "pnpm-workspace.yaml";
1382
- var RELEASE_AGE_KEY = "minimumReleaseAgeExclude";
1383
- var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
1384
- function findWorkspaceRoot(startDir) {
1385
- let dir = startDir;
1386
- for (; ; ) {
1387
- if (existsSync9(join10(dir, WORKSPACE_ROOT_MARKER))) return dir;
1388
- const parent = dirname3(dir);
1389
- if (parent === dir) return void 0;
1390
- dir = parent;
1391
- }
1392
- }
1393
- function declaredNativeTs(pkg) {
1394
- const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
1395
- if (!spec) return void 0;
1396
- const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
1397
- return version || void 0;
1398
- }
1399
- function ensureI18nextSingleton(root, dryRun) {
1400
- const pkgPath = join10(root, "package.json");
1401
- if (!existsSync9(pkgPath)) return void 0;
1402
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
1403
- const want = declaredNativeTs(pkg);
1404
- if (!want) return void 0;
1405
- const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
1406
- if (have === want) return void 0;
1407
- if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} (i18next singleton)`;
1408
- pkg.pnpm ??= {};
1409
- pkg.pnpm.overrides ??= {};
1410
- pkg.pnpm.overrides[OVERRIDE_KEY] = want;
1411
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1412
- return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
1413
- }
1414
- function ensureReleaseAgeAllowList(root, dryRun) {
1415
- const yamlPath = join10(root, WORKSPACE_YAML);
1416
- if (!existsSync9(yamlPath)) return void 0;
1417
- const own = readOwnPackage().name;
1418
- const lines = readFileSync7(yamlPath, "utf8").split("\n");
1419
- const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
1420
- if (keyIdx === -1) return void 0;
1421
- let lastItemIdx = keyIdx;
1422
- let indent = " ";
1423
- for (let i = keyIdx + 1; i < lines.length; i++) {
1424
- const match = lines[i]?.match(LIST_ITEM);
1425
- if (!match) break;
1426
- indent = match[1] ?? indent;
1427
- lastItemIdx = i;
1428
- if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
1429
- }
1430
- if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
1431
- lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
1432
- writeFileSync(yamlPath, lines.join("\n"));
1433
- return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
1434
- }
1435
- var PRETTIER_IGNORE = ".prettierignore";
1436
- var EXCLUSION_HEADER = "# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier";
1437
- var ROOT_PRETTIER_CONFIGS = [
1438
- ".prettierrc",
1439
- ".prettierrc.json",
1440
- ".prettierrc.json5",
1441
- ".prettierrc.yml",
1442
- ".prettierrc.yaml",
1443
- ".prettierrc.js",
1444
- ".prettierrc.cjs",
1445
- ".prettierrc.mjs",
1446
- "prettier.config.js",
1447
- "prettier.config.cjs",
1448
- "prettier.config.mjs"
1449
- ];
1450
- function ensureFormatterExclusion(root, moduleDir, dryRun) {
1451
- const rel = relative4(root, moduleDir).replaceAll("\\", "/");
1452
- if (rel === "" || rel.startsWith("..")) return void 0;
1453
- const ignorePath = join10(root, PRETTIER_IGNORE);
1454
- const hasPrettier = existsSync9(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync9(join10(root, name)));
1455
- if (!hasPrettier) return void 0;
1456
- const pattern = `/${rel}/`;
1457
- const existing = existsSync9(ignorePath) ? readFileSync7(ignorePath, "utf8") : "";
1458
- const lines = existing.split("\n");
1459
- if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
1460
- return void 0;
1461
- }
1462
- if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`;
1463
- const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER);
1464
- if (headerIdx === -1) {
1465
- const prefix = existing.trim() === "" ? "" : `${existing.replace(/\n*$/, "")}
1466
-
1467
- `;
1468
- writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}
1469
- ${pattern}
1470
- `);
1471
- } else {
1472
- let end = headerIdx + 1;
1473
- while (end < lines.length && lines[end]?.trim().startsWith("/")) end += 1;
1474
- const managed = [...lines.slice(headerIdx + 1, end), pattern].sort();
1475
- writeFileSync(
1476
- ignorePath,
1477
- [...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join("\n")
1478
- );
1479
- }
1480
- return `excluded ${rel} from the root Prettier (${PRETTIER_IGNORE}): it formats itself now`;
1481
- }
1482
- function ensureWorkspacePrep(opts) {
1483
- const dryRun = Boolean(opts.dryRun);
1484
- return [
1485
- ensureI18nextSingleton(opts.root, dryRun),
1486
- ensureReleaseAgeAllowList(opts.root, dryRun),
1487
- opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
1488
- ].filter((message) => message !== void 0);
1489
- }
1490
- function inspectWorkspacePrep(root) {
1491
- const entries = [];
1492
- let pkg = {};
1493
- const pkgPath = join10(root, "package.json");
1494
- if (existsSync9(pkgPath)) {
1495
- try {
1496
- pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
1497
- } catch {
1498
- pkg = {};
1499
- }
1500
- }
1501
- const pinned = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
1502
- if (pinned !== void 0) {
1503
- const stillForks = declaredNativeTs(pkg);
1504
- entries.push({
1505
- rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
1506
- reason: stillForks === void 0 ? `pinned to ${pinned}, but the root no longer declares ${NATIVE_TS_ALIAS}, so nothing forks any more. This override is now dead config and can be removed.` : `pinned to ${pinned}, matching the second TypeScript the root declares (${NATIVE_TS_ALIAS}). It re-resolves i18next for EVERY module that depends on it, not only adopted ones. It retires with the second TypeScript.`
1507
- });
1508
- }
1509
- const own = readOwnPackage().name;
1510
- const yamlPath = join10(root, WORKSPACE_YAML);
1511
- if (existsSync9(yamlPath)) {
1512
- const yaml = readFileSync7(yamlPath, "utf8");
1513
- const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
1514
- if (listed) {
1515
- const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
1516
- entries.push({
1517
- rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
1518
- reason: gated ? `${own} is exempt from this workspace's release-age gate, so a freshly published version installs instead of being held. That is a standing exemption from a supply-chain control: the exposure is one bump wide, since the version is pinned and the lockfile carries its integrity, so a re-publish of the same version does not pass. Retire it once a bump can simply wait out the window.` : `${own} is allow-listed, but this workspace no longer sets minimumReleaseAge, so there is no gate to be exempt from. This entry is now dead config and can be removed.`
1519
- });
1520
- }
1521
- }
1522
- return entries;
1523
- }
1524
-
1525
1594
  // src/roles/format/config-policy.ts
1526
1595
  var FORMAT_CONFIG_FILE = ".oxfmtrc.json";
1527
1596
  var FORMAT_SCRIPT_NAME = "format";
@@ -1596,8 +1665,8 @@ function writesWhenRewritten(name, command) {
1596
1665
  var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
1597
1666
 
1598
1667
  // src/roles/format/inherited-ignores.ts
1599
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
1600
- import { join as join11 } from "path";
1668
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
1669
+ import { join as join13 } from "path";
1601
1670
  var ROOT_IGNORE_FILE = ".prettierignore";
1602
1671
  function isPattern(line) {
1603
1672
  const trimmed = line.trim();
@@ -1612,11 +1681,11 @@ function toModulePattern(pattern) {
1612
1681
  }
1613
1682
  function inheritedIgnorePatterns(workspaceRoot) {
1614
1683
  if (!workspaceRoot) return [];
1615
- const path = join11(workspaceRoot, ROOT_IGNORE_FILE);
1616
- if (!existsSync10(path)) return [];
1684
+ const path = join13(workspaceRoot, ROOT_IGNORE_FILE);
1685
+ if (!existsSync12(path)) return [];
1617
1686
  let contents;
1618
1687
  try {
1619
- contents = readFileSync8(path, "utf8");
1688
+ contents = readFileSync9(path, "utf8");
1620
1689
  } catch {
1621
1690
  return [];
1622
1691
  }
@@ -1703,13 +1772,13 @@ function formatPresetFor(preset) {
1703
1772
  }
1704
1773
 
1705
1774
  // src/roles/format/prettier-config.ts
1706
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
1707
- import { join as join13 } from "path";
1775
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
1776
+ import { join as join15 } from "path";
1708
1777
 
1709
1778
  // src/roles/format/resolve-oxfmt.ts
1710
- import { readFileSync as readFileSync9 } from "fs";
1779
+ import { readFileSync as readFileSync10 } from "fs";
1711
1780
  import { createRequire as createRequire2 } from "module";
1712
- import { dirname as dirname4, join as join12 } from "path";
1781
+ import { dirname as dirname4, join as join14 } from "path";
1713
1782
  function resolveOxfmt(cwd) {
1714
1783
  return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
1715
1784
  }
@@ -1738,8 +1807,8 @@ function configSchema() {
1738
1807
  function readConfigSchema() {
1739
1808
  try {
1740
1809
  const manifest = createRequire2(import.meta.url).resolve("oxfmt/package.json");
1741
- const schemaPath = join12(dirname4(manifest), "configuration_schema.json");
1742
- return JSON.parse(readFileSync9(schemaPath, "utf8"));
1810
+ const schemaPath = join14(dirname4(manifest), "configuration_schema.json");
1811
+ return JSON.parse(readFileSync10(schemaPath, "utf8"));
1743
1812
  } catch {
1744
1813
  return void 0;
1745
1814
  }
@@ -1770,14 +1839,14 @@ function toOxfmtOverrides(value) {
1770
1839
  return { overrides, unresolved };
1771
1840
  }
1772
1841
  function readPrettierSettings(cwd) {
1773
- const file = PRETTIER_CONFIG_FILES.find((name) => existsSync11(join13(cwd, name)));
1842
+ const file = PRETTIER_CONFIG_FILES.find((name) => existsSync13(join15(cwd, name)));
1774
1843
  if (!file) return { options: {}, unresolved: [] };
1775
1844
  if (/\.(js|cjs|mjs)$/.test(file)) {
1776
1845
  return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
1777
1846
  }
1778
1847
  let parsed;
1779
1848
  try {
1780
- parsed = parseJsonc(readFileSync10(join13(cwd, file), "utf8"), file);
1849
+ parsed = parseJsonc(readFileSync11(join15(cwd, file), "utf8"), file);
1781
1850
  } catch {
1782
1851
  return { options: {}, file, unresolved: [`${file} could not be parsed`] };
1783
1852
  }
@@ -1807,8 +1876,8 @@ function readPrettierSettings(cwd) {
1807
1876
  }
1808
1877
 
1809
1878
  // src/roles/format/read-adoption.ts
1810
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
1811
- import { join as join14 } from "path";
1879
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
1880
+ import { join as join16 } from "path";
1812
1881
  var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
1813
1882
  configFile,
1814
1883
  preset: null,
@@ -1824,11 +1893,11 @@ function sameValue(a, b) {
1824
1893
  return JSON.stringify(a) === JSON.stringify(b);
1825
1894
  }
1826
1895
  function readFormatAdoption(cwd) {
1827
- const path = join14(cwd, FORMAT_CONFIG_FILE);
1828
- if (!existsSync12(path)) return NOT_ADOPTED2(null);
1896
+ const path = join16(cwd, FORMAT_CONFIG_FILE);
1897
+ if (!existsSync14(path)) return NOT_ADOPTED2(null);
1829
1898
  let parsed;
1830
1899
  try {
1831
- parsed = parseJsonc(readFileSync11(path, "utf8"), FORMAT_CONFIG_FILE);
1900
+ parsed = parseJsonc(readFileSync12(path, "utf8"), FORMAT_CONFIG_FILE);
1832
1901
  } catch (error) {
1833
1902
  const reason = error instanceof Error ? error.message : String(error);
1834
1903
  return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
@@ -1953,7 +2022,7 @@ var OxfmtAdapter = class extends BaseAdapter {
1953
2022
  manifestOperation(context.cwd, this.formatScripts(context.cwd))
1954
2023
  ];
1955
2024
  const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
1956
- (name) => existsSync13(join15(context.cwd, name))
2025
+ (name) => existsSync15(join17(context.cwd, name))
1957
2026
  );
1958
2027
  for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
1959
2028
  const removableDeps = this.modulePrettierDependencies(context.cwd);
@@ -2193,7 +2262,7 @@ var OxfmtAdapter = class extends BaseAdapter {
2193
2262
  modulePrettierDependencies(cwd) {
2194
2263
  const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
2195
2264
  try {
2196
- const manifest = JSON.parse(readFileSync12(join15(cwd, "package.json"), "utf8"));
2265
+ const manifest = JSON.parse(readFileSync13(join17(cwd, "package.json"), "utf8"));
2197
2266
  return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
2198
2267
  } catch {
2199
2268
  return [];
@@ -2275,8 +2344,8 @@ function registerFormat() {
2275
2344
 
2276
2345
  // src/roles/lint/adapters/oxlint/oxlint.adapter.ts
2277
2346
  import { spawnSync as spawnSync4 } from "child_process";
2278
- import { existsSync as existsSync21, readFileSync as readFileSync20, rmSync, writeFileSync as writeFileSync2 } from "fs";
2279
- import { join as join25 } from "path";
2347
+ import { existsSync as existsSync23, readFileSync as readFileSync21, rmSync, writeFileSync as writeFileSync2 } from "fs";
2348
+ import { join as join27 } from "path";
2280
2349
 
2281
2350
  // src/core/config/deferred-rules.ts
2282
2351
  function deferredRuleNames(rules) {
@@ -2284,8 +2353,8 @@ function deferredRuleNames(rules) {
2284
2353
  }
2285
2354
 
2286
2355
  // src/core/fix-report.ts
2287
- import { statSync } from "fs";
2288
- import { join as join16 } from "path";
2356
+ import { statSync as statSync2 } from "fs";
2357
+ import { join as join18 } from "path";
2289
2358
  var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
2290
2359
  function readFailure(output) {
2291
2360
  const lines = output.split("\n").map((line2) => line2.trim());
@@ -2296,7 +2365,7 @@ function readFailure(output) {
2296
2365
  }
2297
2366
  function fingerprint(path) {
2298
2367
  try {
2299
- const stat = statSync(path);
2368
+ const stat = statSync2(path);
2300
2369
  return `${stat.size}:${stat.mtimeMs}`;
2301
2370
  } catch {
2302
2371
  return void 0;
@@ -2305,7 +2374,7 @@ function fingerprint(path) {
2305
2374
  function snapshotFiles(cwd, extensions) {
2306
2375
  const snapshot = /* @__PURE__ */ new Map();
2307
2376
  for (const file of listSourceFiles(cwd, extensions)) {
2308
- const mark = fingerprint(join16(cwd, file));
2377
+ const mark = fingerprint(join18(cwd, file));
2309
2378
  if (mark !== void 0) snapshot.set(file, mark);
2310
2379
  }
2311
2380
  return snapshot;
@@ -2313,7 +2382,7 @@ function snapshotFiles(cwd, extensions) {
2313
2382
  function changedSince(cwd, before, extensions) {
2314
2383
  const changed = [];
2315
2384
  for (const file of listSourceFiles(cwd, extensions)) {
2316
- const now = fingerprint(join16(cwd, file));
2385
+ const now = fingerprint(join18(cwd, file));
2317
2386
  if (now === void 0) continue;
2318
2387
  if (before.get(file) !== now) changed.push(file);
2319
2388
  }
@@ -2332,8 +2401,8 @@ function describeFixOutcome(outcome, toolLabel) {
2332
2401
  }
2333
2402
 
2334
2403
  // src/roles/lint/module-baseline.ts
2335
- import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
2336
- import { join as join17 } from "path";
2404
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
2405
+ import { join as join19 } from "path";
2337
2406
  var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
2338
2407
  var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
2339
2408
  var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
@@ -2377,11 +2446,11 @@ function describeHolds(holds) {
2377
2446
  return `held ${holds.length} rule(s) at \`warn\` for this module, covering ${total} violation(s) that were already there: ${listed}${rest}. They are written to ${LINT_BASELINE_FILE}, they still report, and they return to \`error\` once fixed and re-initialized`;
2378
2447
  }
2379
2448
  function heldRules(cwd) {
2380
- const path = join17(cwd, LINT_BASELINE_FILE);
2381
- if (!existsSync14(path)) return [];
2449
+ const path = join19(cwd, LINT_BASELINE_FILE);
2450
+ if (!existsSync16(path)) return [];
2382
2451
  try {
2383
2452
  const parsed = parseJsonc(
2384
- readFileSync13(path, "utf8"),
2453
+ readFileSync14(path, "utf8"),
2385
2454
  LINT_BASELINE_FILE
2386
2455
  );
2387
2456
  return Object.keys(parsed.rules ?? {});
@@ -4204,14 +4273,14 @@ function downgradedRulesFor(preset) {
4204
4273
  }
4205
4274
 
4206
4275
  // src/roles/lint/extra-layers.ts
4207
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
4208
- import { isAbsolute as isAbsolute2, join as join18, resolve as resolve3 } from "path";
4276
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
4277
+ import { isAbsolute as isAbsolute2, join as join20, resolve as resolve3 } from "path";
4209
4278
  function ruleCount(cwd, specifier) {
4210
4279
  const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
4211
- if (!existsSync15(path)) return void 0;
4280
+ if (!existsSync17(path)) return void 0;
4212
4281
  try {
4213
4282
  const parsed = parseJsonc(
4214
- readFileSync14(path, "utf8"),
4283
+ readFileSync15(path, "utf8"),
4215
4284
  specifier
4216
4285
  );
4217
4286
  return Object.keys(parsed.rules ?? {}).length;
@@ -4231,7 +4300,7 @@ function extraLayers(cwd, extendsList) {
4231
4300
  function committedExtendsList(cwd, configFile) {
4232
4301
  try {
4233
4302
  const parsed = parseJsonc(
4234
- readFileSync14(join18(cwd, configFile), "utf8"),
4303
+ readFileSync15(join20(cwd, configFile), "utf8"),
4235
4304
  configFile
4236
4305
  );
4237
4306
  return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
@@ -4290,8 +4359,8 @@ function errorCountsByConfigRule(stdout) {
4290
4359
  }
4291
4360
 
4292
4361
  // src/core/config/read-adoption.ts
4293
- import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
4294
- import { join as join20 } from "path";
4362
+ import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
4363
+ import { join as join22 } from "path";
4295
4364
 
4296
4365
  // src/core/config/owned-keys.ts
4297
4366
  function presetOwnedKeys(config, permitted, presetSets) {
@@ -4306,12 +4375,12 @@ function localOnlyKeys(config, permitted, presetSets) {
4306
4375
  }
4307
4376
 
4308
4377
  // src/core/config/resolve-config-target.ts
4309
- import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
4310
- import { join as join19 } from "path";
4378
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
4379
+ import { join as join21 } from "path";
4311
4380
  function readExtends(absolutePath) {
4312
4381
  let parsed;
4313
4382
  try {
4314
- parsed = parseJsonc(readFileSync15(absolutePath, "utf8"), absolutePath);
4383
+ parsed = parseJsonc(readFileSync16(absolutePath, "utf8"), absolutePath);
4315
4384
  } catch {
4316
4385
  return [];
4317
4386
  }
@@ -4325,8 +4394,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
4325
4394
  let existing;
4326
4395
  let existingExtendsSomething = false;
4327
4396
  for (const candidate of candidates) {
4328
- const absolutePath = join19(moduleDir, candidate);
4329
- if (!existsSync16(absolutePath)) continue;
4397
+ const absolutePath = join21(moduleDir, candidate);
4398
+ if (!existsSync18(absolutePath)) continue;
4330
4399
  const chain = readExtends(absolutePath);
4331
4400
  if (existing === void 0) {
4332
4401
  existing = candidate;
@@ -4359,12 +4428,12 @@ var NOT_ADOPTED3 = (configFile, unreadable = null) => ({
4359
4428
  });
4360
4429
  function readAdoption(cwd, options) {
4361
4430
  const target = resolveConfigTarget(cwd, options);
4362
- if (target.reason === "none" || !existsSync17(join20(cwd, target.path))) {
4431
+ if (target.reason === "none" || !existsSync19(join22(cwd, target.path))) {
4363
4432
  return NOT_ADOPTED3(target.reason === "none" ? null : target.path);
4364
4433
  }
4365
4434
  let parsed;
4366
4435
  try {
4367
- parsed = parseJsonc(readFileSync16(join20(cwd, target.path), "utf8"), target.path);
4436
+ parsed = parseJsonc(readFileSync17(join22(cwd, target.path), "utf8"), target.path);
4368
4437
  } catch (error) {
4369
4438
  const reason = error instanceof Error ? error.message : String(error);
4370
4439
  return NOT_ADOPTED3(target.path, `${target.path} could not be parsed (${reason})`);
@@ -4398,9 +4467,9 @@ function readLintAdoption(cwd) {
4398
4467
  }
4399
4468
 
4400
4469
  // src/roles/lint/resolve-oxlint.ts
4401
- import { existsSync as existsSync18 } from "fs";
4470
+ import { existsSync as existsSync20 } from "fs";
4402
4471
  import { createRequire as createRequire3 } from "module";
4403
- import { delimiter as delimiter2, dirname as dirname5, join as join21 } from "path";
4472
+ import { delimiter as delimiter2, dirname as dirname5, join as join23 } from "path";
4404
4473
  import { fileURLToPath as fileURLToPath2 } from "url";
4405
4474
  var PACKAGE_OF = {
4406
4475
  oxlint: "oxlint",
@@ -4425,30 +4494,30 @@ function tsgolintShim(cwd) {
4425
4494
  for (const owner of ["oxlint-tsgolint", "oxlint"]) {
4426
4495
  try {
4427
4496
  const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
4428
- candidates.push(join21(packageDir, "node_modules", ".bin", "tsgolint"));
4429
- candidates.push(join21(packageDir, "..", ".bin", "tsgolint"));
4497
+ candidates.push(join23(packageDir, "node_modules", ".bin", "tsgolint"));
4498
+ candidates.push(join23(packageDir, "..", ".bin", "tsgolint"));
4430
4499
  } catch {
4431
4500
  }
4432
4501
  }
4433
4502
  try {
4434
4503
  const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
4435
- candidates.push(join21(ownRoot, "node_modules", ".bin", "tsgolint"));
4504
+ candidates.push(join23(ownRoot, "node_modules", ".bin", "tsgolint"));
4436
4505
  } catch {
4437
4506
  candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
4438
4507
  }
4439
- return candidates.find((candidate) => candidate !== "" && existsSync18(candidate));
4508
+ return candidates.find((candidate) => candidate !== "" && existsSync20(candidate));
4440
4509
  }
4441
4510
  function oxlintSearchPath(cwd) {
4442
4511
  return binSearchPath(cwd);
4443
4512
  }
4444
4513
 
4445
4514
  // src/roles/lint/adapters/oxlint/plan.ts
4446
- import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
4447
- import { join as join24 } from "path";
4515
+ import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
4516
+ import { join as join26 } from "path";
4448
4517
 
4449
4518
  // src/roles/lint/eslint-ignores.ts
4450
- import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
4451
- import { join as join22 } from "path";
4519
+ import { existsSync as existsSync21, readFileSync as readFileSync18 } from "fs";
4520
+ import { join as join24 } from "path";
4452
4521
  import { parseSync as parseSync2 } from "oxc-parser";
4453
4522
  var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
4454
4523
  function toOxlintPattern(pattern) {
@@ -4460,11 +4529,11 @@ function readRootEslintIgnores(root) {
4460
4529
  return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
4461
4530
  }
4462
4531
  function readEslintIgnores(cwd) {
4463
- const config = ESLINT_CONFIG_FILES.map((name) => join22(cwd, name)).find((path) => existsSync19(path));
4532
+ const config = ESLINT_CONFIG_FILES.map((name) => join24(cwd, name)).find((path) => existsSync21(path));
4464
4533
  if (!config) return { patterns: [], unresolved: [] };
4465
4534
  let source;
4466
4535
  try {
4467
- source = readFileSync17(config, "utf8");
4536
+ source = readFileSync18(config, "utf8");
4468
4537
  } catch {
4469
4538
  return { patterns: [], unresolved: [] };
4470
4539
  }
@@ -4565,8 +4634,8 @@ function lintPresetFor(preset) {
4565
4634
  }
4566
4635
 
4567
4636
  // src/roles/lint/rename-suppressions.ts
4568
- import { readdirSync as readdirSync2, readFileSync as readFileSync18, statSync as statSync2 } from "fs";
4569
- import { join as join23, relative as relative5 } from "path";
4637
+ import { readdirSync as readdirSync2, readFileSync as readFileSync19, statSync as statSync3 } from "fs";
4638
+ import { join as join25, relative as relative4 } from "path";
4570
4639
  var SOURCE_EXTENSIONS = [
4571
4640
  ".ts",
4572
4641
  ".tsx",
@@ -4613,10 +4682,10 @@ function* sourceFiles(dir) {
4613
4682
  return;
4614
4683
  }
4615
4684
  for (const entry of entries) {
4616
- const full = join23(dir, entry);
4685
+ const full = join25(dir, entry);
4617
4686
  let isDirectory;
4618
4687
  try {
4619
- isDirectory = statSync2(full).isDirectory();
4688
+ isDirectory = statSync3(full).isDirectory();
4620
4689
  } catch {
4621
4690
  continue;
4622
4691
  }
@@ -4633,7 +4702,7 @@ function findSuppressionRenames(cwd, renames) {
4633
4702
  for (const file of sourceFiles(cwd)) {
4634
4703
  let content;
4635
4704
  try {
4636
- content = readFileSync18(file, "utf8");
4705
+ content = readFileSync19(file, "utf8");
4637
4706
  } catch {
4638
4707
  continue;
4639
4708
  }
@@ -4657,7 +4726,7 @@ function findSuppressionRenames(cwd, renames) {
4657
4726
  }
4658
4727
  if (hits.length === 0) return;
4659
4728
  found.push({
4660
- file: relative5(cwd, file),
4729
+ file: relative4(cwd, file),
4661
4730
  line: index + 1,
4662
4731
  from: line,
4663
4732
  to: line.replace(rules, renamed.join(", ")),
@@ -4761,7 +4830,7 @@ function plan2(context) {
4761
4830
  keys: removableDeps
4762
4831
  });
4763
4832
  }
4764
- const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync20(join24(context.cwd, name)));
4833
+ const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync22(join26(context.cwd, name)));
4765
4834
  for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
4766
4835
  operations.push(
4767
4836
  ...nxTargetOperations({
@@ -4859,7 +4928,7 @@ function lintScripts(cwd) {
4859
4928
  function committedExtends(cwd) {
4860
4929
  try {
4861
4930
  const parsed = parseJsonc(
4862
- readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
4931
+ readFileSync20(join26(cwd, LINT_CONFIG_FILE), "utf8"),
4863
4932
  LINT_CONFIG_FILE
4864
4933
  );
4865
4934
  return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
@@ -4870,7 +4939,7 @@ function committedExtends(cwd) {
4870
4939
  function committedIgnorePatterns(cwd) {
4871
4940
  try {
4872
4941
  const parsed = parseJsonc(
4873
- readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
4942
+ readFileSync20(join26(cwd, LINT_CONFIG_FILE), "utf8"),
4874
4943
  LINT_CONFIG_FILE
4875
4944
  );
4876
4945
  return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
@@ -4882,7 +4951,7 @@ function moduleEslintDependencies(cwd) {
4882
4951
  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/");
4883
4952
  let manifest;
4884
4953
  try {
4885
- manifest = JSON.parse(readFileSync19(join24(cwd, "package.json"), "utf8"));
4954
+ manifest = JSON.parse(readFileSync20(join26(cwd, "package.json"), "utf8"));
4886
4955
  } catch {
4887
4956
  return [];
4888
4957
  }
@@ -5016,20 +5085,20 @@ ${result.stderr ?? ""}`);
5016
5085
  * build the developer can see, rather than a silent half-adoption they cannot.
5017
5086
  */
5018
5087
  writeModuleBaseline(ctx, oxlint, env) {
5019
- const configPath = join25(ctx.cwd, LINT_CONFIG_FILE);
5020
- const baselinePath = join25(ctx.cwd, LINT_BASELINE_FILE);
5021
- if (!existsSync21(configPath)) return;
5088
+ const configPath = join27(ctx.cwd, LINT_CONFIG_FILE);
5089
+ const baselinePath = join27(ctx.cwd, LINT_BASELINE_FILE);
5090
+ if (!existsSync23(configPath)) return;
5022
5091
  let config;
5023
5092
  try {
5024
5093
  config = parseJsonc(
5025
- readFileSync20(configPath, "utf8"),
5094
+ readFileSync21(configPath, "utf8"),
5026
5095
  LINT_CONFIG_FILE
5027
5096
  );
5028
5097
  } catch {
5029
5098
  return;
5030
5099
  }
5031
5100
  const current = Array.isArray(config.extends) ? config.extends : [];
5032
- const measurePath = join25(ctx.cwd, LINT_MEASURE_FILE);
5101
+ const measurePath = join27(ctx.cwd, LINT_MEASURE_FILE);
5033
5102
  let measured;
5034
5103
  try {
5035
5104
  writeFileSync2(
@@ -5077,7 +5146,7 @@ ${result.stderr ?? ""}`);
5077
5146
  }
5078
5147
  }
5079
5148
  async run(ctx) {
5080
- if (!existsSync21(join25(ctx.cwd, LINT_CONFIG_FILE))) {
5149
+ if (!existsSync23(join27(ctx.cwd, LINT_CONFIG_FILE))) {
5081
5150
  process.stderr.write(
5082
5151
  `sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
5083
5152
  `
@@ -5225,7 +5294,7 @@ ${result.stderr ?? ""}`);
5225
5294
  let stub;
5226
5295
  try {
5227
5296
  stub = parseJsonc(
5228
- readFileSync20(join25(cwd, LINT_CONFIG_FILE), "utf8"),
5297
+ readFileSync21(join27(cwd, LINT_CONFIG_FILE), "utf8"),
5229
5298
  LINT_CONFIG_FILE
5230
5299
  );
5231
5300
  } catch {
@@ -5235,7 +5304,7 @@ ${result.stderr ?? ""}`);
5235
5304
  for (const entry of stub.extends ?? []) {
5236
5305
  try {
5237
5306
  const preset = parseJsonc(
5238
- readFileSync20(join25(cwd, entry), "utf8"),
5307
+ readFileSync21(join27(cwd, entry), "utf8"),
5239
5308
  entry
5240
5309
  );
5241
5310
  for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
@@ -5305,14 +5374,14 @@ ${result.stderr ?? ""}`);
5305
5374
  let parsed;
5306
5375
  try {
5307
5376
  parsed = parseJsonc(
5308
- readFileSync20(join25(cwd, LINT_CONFIG_FILE), "utf8"),
5377
+ readFileSync21(join27(cwd, LINT_CONFIG_FILE), "utf8"),
5309
5378
  LINT_CONFIG_FILE
5310
5379
  );
5311
5380
  } catch {
5312
5381
  return void 0;
5313
5382
  }
5314
5383
  const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
5315
- return targets.find((target) => !existsSync21(join25(cwd, target)));
5384
+ return targets.find((target) => !existsSync23(join27(cwd, target)));
5316
5385
  }
5317
5386
  /** Announce what is not enforced, so reduced coverage is never silent. */
5318
5387
  announceDisabled(preset) {
@@ -5344,9 +5413,9 @@ function registerLint() {
5344
5413
 
5345
5414
  // src/roles/typescript/adapters/tsc/tsc.adapter.ts
5346
5415
  import { spawnSync as spawnSync5 } from "child_process";
5347
- import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
5416
+ import { existsSync as existsSync24, readFileSync as readFileSync23 } from "fs";
5348
5417
  import { createRequire as createRequire4 } from "module";
5349
- import { join as join27 } from "path";
5418
+ import { join as join29 } from "path";
5350
5419
 
5351
5420
  // src/roles/typescript/presets/nest.json
5352
5421
  var nest_default2 = {
@@ -5527,8 +5596,8 @@ function readTsconfigAdoption(cwd) {
5527
5596
  }
5528
5597
 
5529
5598
  // src/roles/typescript/adapters/tsc/plan.ts
5530
- import { readFileSync as readFileSync21 } from "fs";
5531
- import { join as join26 } from "path";
5599
+ import { readFileSync as readFileSync22 } from "fs";
5600
+ import { join as join28 } from "path";
5532
5601
 
5533
5602
  // src/roles/typescript/typecheck-script.ts
5534
5603
  var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
@@ -5621,7 +5690,7 @@ function planAdoption(context) {
5621
5690
  };
5622
5691
  }
5623
5692
  const existing = parseJsonc(
5624
- readFileSync21(join26(context.cwd, target.path), "utf8"),
5693
+ readFileSync22(join28(context.cwd, target.path), "utf8"),
5625
5694
  target.path
5626
5695
  );
5627
5696
  const extendsChain = composeExtends(existing.extends, preset);
@@ -5759,7 +5828,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5759
5828
  let chain;
5760
5829
  try {
5761
5830
  const parsed = parseJsonc(
5762
- readFileSync22(join27(cwd, target.path), "utf8"),
5831
+ readFileSync23(join29(cwd, target.path), "utf8"),
5763
5832
  target.path
5764
5833
  );
5765
5834
  chain = parsed.extends;
@@ -5772,7 +5841,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5772
5841
  );
5773
5842
  if (preset === void 0) return void 0;
5774
5843
  try {
5775
- createRequire4(join27(cwd, "noop.js")).resolve(preset);
5844
+ createRequire4(join29(cwd, "noop.js")).resolve(preset);
5776
5845
  return void 0;
5777
5846
  } catch {
5778
5847
  return preset;
@@ -5868,7 +5937,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5868
5937
  referencedProjects(cwd, config) {
5869
5938
  try {
5870
5939
  const parsed = parseJsonc(
5871
- readFileSync22(join27(cwd, config), "utf8"),
5940
+ readFileSync23(join29(cwd, config), "utf8"),
5872
5941
  config
5873
5942
  );
5874
5943
  const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
@@ -5884,7 +5953,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5884
5953
  * check.
5885
5954
  */
5886
5955
  typecheckTarget(cwd) {
5887
- if (existsSync22(join27(cwd, "tsconfig.json"))) return "tsconfig.json";
5956
+ if (existsSync24(join29(cwd, "tsconfig.json"))) return "tsconfig.json";
5888
5957
  const target = resolveTsconfigTarget(cwd);
5889
5958
  return target.reason === "none" ? null : target.path;
5890
5959
  }
@@ -6088,7 +6157,7 @@ function replaceLines(current, replacements) {
6088
6157
  }
6089
6158
 
6090
6159
  // src/core/apply-plan.ts
6091
- import { existsSync as existsSync23, readFileSync as readFileSync23, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
6160
+ import { existsSync as existsSync25, readFileSync as readFileSync24, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
6092
6161
  import { resolve as resolve4, sep } from "path";
6093
6162
  import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
6094
6163
 
@@ -6107,7 +6176,7 @@ function resolveWithinRoot(cwd, relativePath) {
6107
6176
  return absolutePath;
6108
6177
  }
6109
6178
  function readIfExists(absolutePath) {
6110
- return existsSync23(absolutePath) ? readFileSync23(absolutePath, "utf8") : void 0;
6179
+ return existsSync25(absolutePath) ? readFileSync24(absolutePath, "utf8") : void 0;
6111
6180
  }
6112
6181
  function* leaves(value, prefix = []) {
6113
6182
  for (const [key, keyValue] of Object.entries(value)) {
@@ -6225,8 +6294,8 @@ function applyPlan(cwd, plan3) {
6225
6294
  }
6226
6295
 
6227
6296
  // src/core/config/preset-evidence.ts
6228
- import { existsSync as existsSync24, readdirSync as readdirSync3, readFileSync as readFileSync24 } from "fs";
6229
- import { join as join28 } from "path";
6297
+ import { existsSync as existsSync26, readdirSync as readdirSync3, readFileSync as readFileSync25 } from "fs";
6298
+ import { join as join30 } from "path";
6230
6299
  var PATH_SIGNALS = [
6231
6300
  {
6232
6301
  preset: "nest",
@@ -6240,11 +6309,11 @@ var DEPENDENCY_SIGNALS = [
6240
6309
  { preset: "nest", pattern: /^@nestjs\// }
6241
6310
  ];
6242
6311
  function dependencyNames(cwd) {
6243
- const path = join28(cwd, "package.json");
6244
- if (!existsSync24(path)) return [];
6312
+ const path = join30(cwd, "package.json");
6313
+ if (!existsSync26(path)) return [];
6245
6314
  try {
6246
6315
  const manifest = parseJsonc(
6247
- readFileSync24(path, "utf8"),
6316
+ readFileSync25(path, "utf8"),
6248
6317
  path
6249
6318
  );
6250
6319
  return [
@@ -6267,7 +6336,7 @@ function declaresJsx(cwd) {
6267
6336
  for (const name of entries) {
6268
6337
  try {
6269
6338
  const config = parseJsonc(
6270
- readFileSync24(join28(cwd, name), "utf8"),
6339
+ readFileSync25(join30(cwd, name), "utf8"),
6271
6340
  name
6272
6341
  );
6273
6342
  if (config.compilerOptions?.jsx !== void 0) return true;
@@ -6432,19 +6501,19 @@ export {
6432
6501
  buildConfigFile,
6433
6502
  BaseAdapter,
6434
6503
  resolveBin,
6504
+ WORKSPACE_ROOT_MARKER,
6505
+ findWorkspaceRoot,
6506
+ ensureWorkspacePrep,
6507
+ inspectWorkspacePrep,
6435
6508
  VERBS,
6436
6509
  TARGETS,
6437
6510
  LONG_RUNNING_TARGETS,
6438
6511
  SWEEPABLE_TARGETS,
6439
6512
  PRESET_NAMES,
6440
- WORKSPACE_ROOT_MARKER,
6441
- findWorkspaceRoot,
6442
- ensureWorkspacePrep,
6443
- inspectWorkspacePrep,
6444
6513
  palette,
6445
6514
  registerAdapters,
6446
6515
  describeFramework,
6447
6516
  detectFramework,
6448
6517
  dispatch
6449
6518
  };
6450
- //# sourceMappingURL=chunk-Q3ZLHHW2.js.map
6519
+ //# sourceMappingURL=chunk-MJ3S7LQT.js.map
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  registerAdapters,
8
8
  resolve,
9
9
  setDefaultRunner
10
- } from "./chunk-Q3ZLHHW2.js";
10
+ } from "./chunk-MJ3S7LQT.js";
11
11
  export {
12
12
  BaseAdapter,
13
13
  all,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hublo/sentinel",
3
- "version": "1.2.0-alpha.16",
3
+ "version": "1.2.0-alpha.18",
4
4
  "description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
5
5
  "type": "module",
6
6
  "license": "MIT",