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

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-CWJX5DVF.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";
@@ -656,7 +664,8 @@ function composeDevScript(existing, command) {
656
664
  }
657
665
 
658
666
  // src/roles/build/nest/adopt.ts
659
- import { relative as relative2 } from "path";
667
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
668
+ import { join as join8 } from "path";
660
669
  var NEST_CONFIG_FILE = "vite.config.mts";
661
670
  function nestConfigSource(cwd, target) {
662
671
  const hops = workspaceRootHops(cwd, target);
@@ -673,34 +682,18 @@ export default nestService({
673
682
  })
674
683
  `;
675
684
  }
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
- };
685
+ function nestBuildOutputs(target) {
686
+ return [`{workspaceRoot}/${target.outputPath}`];
687
+ }
688
+ var OUT_DIR_IN_CONFIG = /^\s*outDir:\s*'([^']+)'/m;
689
+ function nestOutputsFromConfig(cwd) {
690
+ const configPath = join8(cwd, NEST_CONFIG_FILE);
691
+ if (!existsSync8(configPath)) return void 0;
692
+ const outDir = OUT_DIR_IN_CONFIG.exec(readFileSync6(configPath, "utf8"))?.[1];
693
+ return outDir === void 0 ? void 0 : [`{workspaceRoot}/${outDir}`];
693
694
  }
694
695
  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
- ];
696
+ return [{ kind: "write", path: NEST_CONFIG_FILE, contents: nestConfigSource(cwd, target) }];
704
697
  }
705
698
 
706
699
  // src/roles/build/requirements.ts
@@ -836,6 +829,13 @@ function rewriteBuildImports(source, fileName = "vite.config.ts") {
836
829
  }
837
830
 
838
831
  // src/roles/build/plan.ts
832
+ function packageNameFor(cwd) {
833
+ return readProjectPackageJson(cwd).name;
834
+ }
835
+ function candidateOutputs() {
836
+ return BUILD_OUTPUT_CANDIDATES.map((name) => `{projectRoot}/${name}`);
837
+ }
838
+ var NO_PACKAGE_NAME = `this module's package.json declares no \`name\`, so the nx build target would have no way to reach it: the command is \`pnpm --filter <name> run build\`, and an unnamed package matches nothing. Name the package and re-run; nothing was written.`;
839
839
  function ownedBuildDependencies(cwd) {
840
840
  const manifest = readProjectPackageJson(cwd);
841
841
  const keys = [];
@@ -908,6 +908,8 @@ function planReactApp(context, configFile) {
908
908
  const operations = [];
909
909
  const notEsm = esmBlocker(configFile, readProjectPackageJson(context.cwd).type);
910
910
  if (notEsm !== void 0) return { operations: [], skipped: notEsm };
911
+ const packageName = packageNameFor(context.cwd);
912
+ if (packageName === void 0) return { operations: [], skipped: NO_PACKAGE_NAME };
911
913
  const rewrite = rewriteBuildImports(readConfigSource(context.cwd, configFile), configFile);
912
914
  if (rewrite.kind === "blocked") {
913
915
  return {
@@ -937,7 +939,12 @@ function planReactApp(context, configFile) {
937
939
  );
938
940
  }
939
941
  operations.push(manifestOperation(context.cwd, buildScripts(context.cwd)));
940
- operations.push(...nxTargetOperations({ cwd: context.cwd, targets: buildTarget() }));
942
+ operations.push(
943
+ ...nxTargetOperations({
944
+ cwd: context.cwd,
945
+ targets: buildTarget({ moduleName: packageName, outputs: candidateOutputs() })
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,24 @@ function planReactApp(context, configFile) {
948
955
  return { operations, notes };
949
956
  }
950
957
  function planNestService(context, webpackTarget) {
958
+ const packageName = packageNameFor(context.cwd);
959
+ if (packageName === void 0) return { operations: [], skipped: NO_PACKAGE_NAME };
960
+ const outputs = webpackTarget === void 0 ? nestOutputsFromConfig(context.cwd) : nestBuildOutputs(webpackTarget);
961
+ if (outputs === void 0) {
962
+ return {
963
+ operations: [],
964
+ 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.`
965
+ };
966
+ }
951
967
  const metadata = [
952
- ...nxTargetMetadataOperations({ cwd: context.cwd, targets: buildTarget([NEST_CONFIG_FILE]) }),
968
+ ...nxTargetOperations({
969
+ cwd: context.cwd,
970
+ targets: buildTarget({
971
+ moduleName: packageName,
972
+ configFiles: [NEST_CONFIG_FILE],
973
+ outputs
974
+ })
975
+ }),
953
976
  manifestOperation(context.cwd, { [BUILD_SCRIPT_NAME]: SENTINEL_BUILD_COMMAND })
954
977
  ];
955
978
  if (webpackTarget === void 0) return { operations: metadata };
@@ -968,9 +991,9 @@ function planNestService(context, webpackTarget) {
968
991
  };
969
992
  }
970
993
  function readConfigSource(cwd, configFile) {
971
- if (!existsSync8(join8(cwd, configFile))) return "";
994
+ if (!existsSync9(join9(cwd, configFile))) return "";
972
995
  try {
973
- return readFileSync6(join8(cwd, configFile), "utf8");
996
+ return readFileSync7(join9(cwd, configFile), "utf8");
974
997
  } catch {
975
998
  return "";
976
999
  }
@@ -1154,6 +1177,199 @@ var ViteDevAdapter = class extends ViteRoleAdapter {
1154
1177
  }
1155
1178
  };
1156
1179
 
1180
+ // src/roles/build/declared-outputs.ts
1181
+ import { existsSync as existsSync11, statSync } from "fs";
1182
+ import { join as join11 } from "path";
1183
+
1184
+ // src/core/workspace-prep.ts
1185
+ import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync } from "fs";
1186
+ import { dirname as dirname3, join as join10, relative as relative2 } from "path";
1187
+
1188
+ // src/core/settings.ts
1189
+ var WORKSPACE_ROOT_MARKER = "nx.json";
1190
+ var DEFAULT_MAX_DIAGNOSTICS = 100;
1191
+
1192
+ // src/core/workspace-prep.ts
1193
+ var OVERRIDE_KEY = "i18next>typescript";
1194
+ var NATIVE_TS_ALIAS = "@typescript/native";
1195
+ var WORKSPACE_YAML = "pnpm-workspace.yaml";
1196
+ var RELEASE_AGE_KEY = "minimumReleaseAgeExclude";
1197
+ var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
1198
+ function findWorkspaceRoot(startDir) {
1199
+ let dir = startDir;
1200
+ for (; ; ) {
1201
+ if (existsSync10(join10(dir, WORKSPACE_ROOT_MARKER))) return dir;
1202
+ const parent = dirname3(dir);
1203
+ if (parent === dir) return void 0;
1204
+ dir = parent;
1205
+ }
1206
+ }
1207
+ function declaredNativeTs(pkg) {
1208
+ const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
1209
+ if (!spec) return void 0;
1210
+ const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
1211
+ return version || void 0;
1212
+ }
1213
+ function ensureI18nextSingleton(root, dryRun) {
1214
+ const pkgPath = join10(root, "package.json");
1215
+ if (!existsSync10(pkgPath)) return void 0;
1216
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
1217
+ const want = declaredNativeTs(pkg);
1218
+ if (!want) return void 0;
1219
+ const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
1220
+ if (have === want) return void 0;
1221
+ if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} (i18next singleton)`;
1222
+ pkg.pnpm ??= {};
1223
+ pkg.pnpm.overrides ??= {};
1224
+ pkg.pnpm.overrides[OVERRIDE_KEY] = want;
1225
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1226
+ return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
1227
+ }
1228
+ function ensureReleaseAgeAllowList(root, dryRun) {
1229
+ const yamlPath = join10(root, WORKSPACE_YAML);
1230
+ if (!existsSync10(yamlPath)) return void 0;
1231
+ const own = readOwnPackage().name;
1232
+ const lines = readFileSync8(yamlPath, "utf8").split("\n");
1233
+ const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
1234
+ if (keyIdx === -1) return void 0;
1235
+ let lastItemIdx = keyIdx;
1236
+ let indent = " ";
1237
+ for (let i = keyIdx + 1; i < lines.length; i++) {
1238
+ const match = lines[i]?.match(LIST_ITEM);
1239
+ if (!match) break;
1240
+ indent = match[1] ?? indent;
1241
+ lastItemIdx = i;
1242
+ if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
1243
+ }
1244
+ if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
1245
+ lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
1246
+ writeFileSync(yamlPath, lines.join("\n"));
1247
+ return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
1248
+ }
1249
+ var PRETTIER_IGNORE = ".prettierignore";
1250
+ var EXCLUSION_HEADER = "# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier";
1251
+ var ROOT_PRETTIER_CONFIGS = [
1252
+ ".prettierrc",
1253
+ ".prettierrc.json",
1254
+ ".prettierrc.json5",
1255
+ ".prettierrc.yml",
1256
+ ".prettierrc.yaml",
1257
+ ".prettierrc.js",
1258
+ ".prettierrc.cjs",
1259
+ ".prettierrc.mjs",
1260
+ "prettier.config.js",
1261
+ "prettier.config.cjs",
1262
+ "prettier.config.mjs"
1263
+ ];
1264
+ function ensureFormatterExclusion(root, moduleDir, dryRun) {
1265
+ const rel = relative2(root, moduleDir).replaceAll("\\", "/");
1266
+ if (rel === "" || rel.startsWith("..")) return void 0;
1267
+ const ignorePath = join10(root, PRETTIER_IGNORE);
1268
+ const hasPrettier = existsSync10(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync10(join10(root, name)));
1269
+ if (!hasPrettier) return void 0;
1270
+ const pattern = `/${rel}/`;
1271
+ const existing = existsSync10(ignorePath) ? readFileSync8(ignorePath, "utf8") : "";
1272
+ const lines = existing.split("\n");
1273
+ if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
1274
+ return void 0;
1275
+ }
1276
+ if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`;
1277
+ const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER);
1278
+ if (headerIdx === -1) {
1279
+ const prefix = existing.trim() === "" ? "" : `${existing.replace(/\n*$/, "")}
1280
+
1281
+ `;
1282
+ writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}
1283
+ ${pattern}
1284
+ `);
1285
+ } else {
1286
+ let end = headerIdx + 1;
1287
+ while (end < lines.length && lines[end]?.trim().startsWith("/")) end += 1;
1288
+ const managed = [...lines.slice(headerIdx + 1, end), pattern].sort();
1289
+ writeFileSync(
1290
+ ignorePath,
1291
+ [...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join("\n")
1292
+ );
1293
+ }
1294
+ return `excluded ${rel} from the root Prettier (${PRETTIER_IGNORE}): it formats itself now`;
1295
+ }
1296
+ function ensureWorkspacePrep(opts) {
1297
+ const dryRun = Boolean(opts.dryRun);
1298
+ return [
1299
+ ensureI18nextSingleton(opts.root, dryRun),
1300
+ ensureReleaseAgeAllowList(opts.root, dryRun),
1301
+ opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
1302
+ ].filter((message) => message !== void 0);
1303
+ }
1304
+ function inspectWorkspacePrep(root) {
1305
+ const entries = [];
1306
+ let pkg = {};
1307
+ const pkgPath = join10(root, "package.json");
1308
+ if (existsSync10(pkgPath)) {
1309
+ try {
1310
+ pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
1311
+ } catch {
1312
+ pkg = {};
1313
+ }
1314
+ }
1315
+ const pinned = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
1316
+ if (pinned !== void 0) {
1317
+ const stillForks = declaredNativeTs(pkg);
1318
+ entries.push({
1319
+ rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
1320
+ 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.`
1321
+ });
1322
+ }
1323
+ const own = readOwnPackage().name;
1324
+ const yamlPath = join10(root, WORKSPACE_YAML);
1325
+ if (existsSync10(yamlPath)) {
1326
+ const yaml = readFileSync8(yamlPath, "utf8");
1327
+ const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
1328
+ if (listed) {
1329
+ const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
1330
+ entries.push({
1331
+ rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
1332
+ 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.`
1333
+ });
1334
+ }
1335
+ }
1336
+ return entries;
1337
+ }
1338
+
1339
+ // src/roles/build/declared-outputs.ts
1340
+ function declaredBuildTarget(cwd) {
1341
+ return readProjectPackageJson(cwd).nx?.targets?.[BUILD_SCRIPT_NAME];
1342
+ }
1343
+ function resolveOutputToken(token, cwd) {
1344
+ if (token.startsWith("{projectRoot}/")) return join11(cwd, token.slice("{projectRoot}/".length));
1345
+ if (token.startsWith("{workspaceRoot}/")) {
1346
+ const root = findWorkspaceRoot(cwd);
1347
+ return root === void 0 ? void 0 : join11(root, token.slice("{workspaceRoot}/".length));
1348
+ }
1349
+ return join11(cwd, token);
1350
+ }
1351
+ function writtenSince(path, startedAt) {
1352
+ if (!existsSync11(path)) return false;
1353
+ try {
1354
+ return statSync(path).mtimeMs >= startedAt;
1355
+ } catch {
1356
+ return true;
1357
+ }
1358
+ }
1359
+ function outputsProblem(cwd, startedAt) {
1360
+ const target = declaredBuildTarget(cwd);
1361
+ if (target === void 0) return void 0;
1362
+ const declared = target.outputs ?? [];
1363
+ if (declared.length === 0) {
1364
+ if (target.cache !== true) return void 0;
1365
+ 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.`;
1366
+ }
1367
+ const resolved = declared.map((token) => ({ token, path: resolveOutputToken(token, cwd) })).filter((entry) => entry.path !== void 0);
1368
+ if (resolved.length === 0) return void 0;
1369
+ if (resolved.some((entry) => writtenSince(entry.path, startedAt))) return void 0;
1370
+ 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.`;
1371
+ }
1372
+
1157
1373
  // src/roles/build/prerequisite.ts
1158
1374
  import { spawnSync as spawnSync2 } from "child_process";
1159
1375
  var PREBUILD_SCRIPT_NAME = `pre${BUILD_SCRIPT_NAME}`;
@@ -1202,7 +1418,15 @@ var ViteAdapter = class extends ViteRoleAdapter {
1202
1418
  );
1203
1419
  return { ok: false, code: prerequisite.code };
1204
1420
  }
1205
- return this.spawnVite(ctx, ready.vite, ["build"], 1);
1421
+ const startedAt = Date.now();
1422
+ const result = await this.spawnVite(ctx, ready.vite, ["build"], 1);
1423
+ if (!result.ok) return result;
1424
+ const problem = outputsProblem(ctx.cwd, startedAt);
1425
+ if (problem !== void 0) {
1426
+ this.say(problem);
1427
+ return { ok: false, code: 1 };
1428
+ }
1429
+ return result;
1206
1430
  }
1207
1431
  /**
1208
1432
  * What this module builds with, WITHOUT loading its config.
@@ -1274,12 +1498,12 @@ function registerBuild() {
1274
1498
 
1275
1499
  // src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
1276
1500
  import { spawnSync as spawnSync3 } from "child_process";
1277
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
1278
- import { join as join15 } from "path";
1501
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
1502
+ import { join as join17 } from "path";
1279
1503
 
1280
1504
  // src/core/config/has-source.ts
1281
1505
  import { readdirSync } from "fs";
1282
- import { extname, join as join9, relative as relative3 } from "path";
1506
+ import { extname, join as join12, relative as relative3 } from "path";
1283
1507
  var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
1284
1508
  "node_modules",
1285
1509
  "dist",
@@ -1330,7 +1554,7 @@ function hasSourceFiles(cwd, extensions) {
1330
1554
  }
1331
1555
  for (const entry of entries) {
1332
1556
  if (entry.isDirectory()) {
1333
- if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join9(dir, entry.name));
1557
+ if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join12(dir, entry.name));
1334
1558
  continue;
1335
1559
  }
1336
1560
  if (wanted.has(extname(entry.name))) return true;
@@ -1351,7 +1575,7 @@ function listSourceFiles(cwd, extensions) {
1351
1575
  continue;
1352
1576
  }
1353
1577
  for (const entry of entries) {
1354
- const full = join9(dir, entry.name);
1578
+ const full = join12(dir, entry.name);
1355
1579
  if (entry.isDirectory()) {
1356
1580
  if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
1357
1581
  continue;
@@ -1369,159 +1593,6 @@ function scopeFlags(paths, options = []) {
1369
1593
  return [TOLERATE_EMPTY_FLAG];
1370
1594
  }
1371
1595
 
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
1596
  // src/roles/format/config-policy.ts
1526
1597
  var FORMAT_CONFIG_FILE = ".oxfmtrc.json";
1527
1598
  var FORMAT_SCRIPT_NAME = "format";
@@ -1596,8 +1667,8 @@ function writesWhenRewritten(name, command) {
1596
1667
  var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
1597
1668
 
1598
1669
  // src/roles/format/inherited-ignores.ts
1599
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
1600
- import { join as join11 } from "path";
1670
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
1671
+ import { join as join13 } from "path";
1601
1672
  var ROOT_IGNORE_FILE = ".prettierignore";
1602
1673
  function isPattern(line) {
1603
1674
  const trimmed = line.trim();
@@ -1612,11 +1683,11 @@ function toModulePattern(pattern) {
1612
1683
  }
1613
1684
  function inheritedIgnorePatterns(workspaceRoot) {
1614
1685
  if (!workspaceRoot) return [];
1615
- const path = join11(workspaceRoot, ROOT_IGNORE_FILE);
1616
- if (!existsSync10(path)) return [];
1686
+ const path = join13(workspaceRoot, ROOT_IGNORE_FILE);
1687
+ if (!existsSync12(path)) return [];
1617
1688
  let contents;
1618
1689
  try {
1619
- contents = readFileSync8(path, "utf8");
1690
+ contents = readFileSync9(path, "utf8");
1620
1691
  } catch {
1621
1692
  return [];
1622
1693
  }
@@ -1703,13 +1774,13 @@ function formatPresetFor(preset) {
1703
1774
  }
1704
1775
 
1705
1776
  // src/roles/format/prettier-config.ts
1706
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
1707
- import { join as join13 } from "path";
1777
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
1778
+ import { join as join15 } from "path";
1708
1779
 
1709
1780
  // src/roles/format/resolve-oxfmt.ts
1710
- import { readFileSync as readFileSync9 } from "fs";
1781
+ import { readFileSync as readFileSync10 } from "fs";
1711
1782
  import { createRequire as createRequire2 } from "module";
1712
- import { dirname as dirname4, join as join12 } from "path";
1783
+ import { dirname as dirname4, join as join14 } from "path";
1713
1784
  function resolveOxfmt(cwd) {
1714
1785
  return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
1715
1786
  }
@@ -1738,8 +1809,8 @@ function configSchema() {
1738
1809
  function readConfigSchema() {
1739
1810
  try {
1740
1811
  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"));
1812
+ const schemaPath = join14(dirname4(manifest), "configuration_schema.json");
1813
+ return JSON.parse(readFileSync10(schemaPath, "utf8"));
1743
1814
  } catch {
1744
1815
  return void 0;
1745
1816
  }
@@ -1770,14 +1841,14 @@ function toOxfmtOverrides(value) {
1770
1841
  return { overrides, unresolved };
1771
1842
  }
1772
1843
  function readPrettierSettings(cwd) {
1773
- const file = PRETTIER_CONFIG_FILES.find((name) => existsSync11(join13(cwd, name)));
1844
+ const file = PRETTIER_CONFIG_FILES.find((name) => existsSync13(join15(cwd, name)));
1774
1845
  if (!file) return { options: {}, unresolved: [] };
1775
1846
  if (/\.(js|cjs|mjs)$/.test(file)) {
1776
1847
  return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
1777
1848
  }
1778
1849
  let parsed;
1779
1850
  try {
1780
- parsed = parseJsonc(readFileSync10(join13(cwd, file), "utf8"), file);
1851
+ parsed = parseJsonc(readFileSync11(join15(cwd, file), "utf8"), file);
1781
1852
  } catch {
1782
1853
  return { options: {}, file, unresolved: [`${file} could not be parsed`] };
1783
1854
  }
@@ -1807,8 +1878,8 @@ function readPrettierSettings(cwd) {
1807
1878
  }
1808
1879
 
1809
1880
  // src/roles/format/read-adoption.ts
1810
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
1811
- import { join as join14 } from "path";
1881
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
1882
+ import { join as join16 } from "path";
1812
1883
  var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
1813
1884
  configFile,
1814
1885
  preset: null,
@@ -1824,11 +1895,11 @@ function sameValue(a, b) {
1824
1895
  return JSON.stringify(a) === JSON.stringify(b);
1825
1896
  }
1826
1897
  function readFormatAdoption(cwd) {
1827
- const path = join14(cwd, FORMAT_CONFIG_FILE);
1828
- if (!existsSync12(path)) return NOT_ADOPTED2(null);
1898
+ const path = join16(cwd, FORMAT_CONFIG_FILE);
1899
+ if (!existsSync14(path)) return NOT_ADOPTED2(null);
1829
1900
  let parsed;
1830
1901
  try {
1831
- parsed = parseJsonc(readFileSync11(path, "utf8"), FORMAT_CONFIG_FILE);
1902
+ parsed = parseJsonc(readFileSync12(path, "utf8"), FORMAT_CONFIG_FILE);
1832
1903
  } catch (error) {
1833
1904
  const reason = error instanceof Error ? error.message : String(error);
1834
1905
  return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
@@ -1953,7 +2024,7 @@ var OxfmtAdapter = class extends BaseAdapter {
1953
2024
  manifestOperation(context.cwd, this.formatScripts(context.cwd))
1954
2025
  ];
1955
2026
  const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
1956
- (name) => existsSync13(join15(context.cwd, name))
2027
+ (name) => existsSync15(join17(context.cwd, name))
1957
2028
  );
1958
2029
  for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
1959
2030
  const removableDeps = this.modulePrettierDependencies(context.cwd);
@@ -2193,7 +2264,7 @@ var OxfmtAdapter = class extends BaseAdapter {
2193
2264
  modulePrettierDependencies(cwd) {
2194
2265
  const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
2195
2266
  try {
2196
- const manifest = JSON.parse(readFileSync12(join15(cwd, "package.json"), "utf8"));
2267
+ const manifest = JSON.parse(readFileSync13(join17(cwd, "package.json"), "utf8"));
2197
2268
  return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
2198
2269
  } catch {
2199
2270
  return [];
@@ -2275,8 +2346,8 @@ function registerFormat() {
2275
2346
 
2276
2347
  // src/roles/lint/adapters/oxlint/oxlint.adapter.ts
2277
2348
  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";
2349
+ import { existsSync as existsSync23, readFileSync as readFileSync21, rmSync, writeFileSync as writeFileSync2 } from "fs";
2350
+ import { join as join27 } from "path";
2280
2351
 
2281
2352
  // src/core/config/deferred-rules.ts
2282
2353
  function deferredRuleNames(rules) {
@@ -2284,8 +2355,8 @@ function deferredRuleNames(rules) {
2284
2355
  }
2285
2356
 
2286
2357
  // src/core/fix-report.ts
2287
- import { statSync } from "fs";
2288
- import { join as join16 } from "path";
2358
+ import { statSync as statSync2 } from "fs";
2359
+ import { join as join18 } from "path";
2289
2360
  var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
2290
2361
  function readFailure(output) {
2291
2362
  const lines = output.split("\n").map((line2) => line2.trim());
@@ -2296,7 +2367,7 @@ function readFailure(output) {
2296
2367
  }
2297
2368
  function fingerprint(path) {
2298
2369
  try {
2299
- const stat = statSync(path);
2370
+ const stat = statSync2(path);
2300
2371
  return `${stat.size}:${stat.mtimeMs}`;
2301
2372
  } catch {
2302
2373
  return void 0;
@@ -2305,7 +2376,7 @@ function fingerprint(path) {
2305
2376
  function snapshotFiles(cwd, extensions) {
2306
2377
  const snapshot = /* @__PURE__ */ new Map();
2307
2378
  for (const file of listSourceFiles(cwd, extensions)) {
2308
- const mark = fingerprint(join16(cwd, file));
2379
+ const mark = fingerprint(join18(cwd, file));
2309
2380
  if (mark !== void 0) snapshot.set(file, mark);
2310
2381
  }
2311
2382
  return snapshot;
@@ -2313,7 +2384,7 @@ function snapshotFiles(cwd, extensions) {
2313
2384
  function changedSince(cwd, before, extensions) {
2314
2385
  const changed = [];
2315
2386
  for (const file of listSourceFiles(cwd, extensions)) {
2316
- const now = fingerprint(join16(cwd, file));
2387
+ const now = fingerprint(join18(cwd, file));
2317
2388
  if (now === void 0) continue;
2318
2389
  if (before.get(file) !== now) changed.push(file);
2319
2390
  }
@@ -2332,8 +2403,8 @@ function describeFixOutcome(outcome, toolLabel) {
2332
2403
  }
2333
2404
 
2334
2405
  // src/roles/lint/module-baseline.ts
2335
- import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
2336
- import { join as join17 } from "path";
2406
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
2407
+ import { join as join19 } from "path";
2337
2408
  var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
2338
2409
  var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
2339
2410
  var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
@@ -2377,11 +2448,11 @@ function describeHolds(holds) {
2377
2448
  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
2449
  }
2379
2450
  function heldRules(cwd) {
2380
- const path = join17(cwd, LINT_BASELINE_FILE);
2381
- if (!existsSync14(path)) return [];
2451
+ const path = join19(cwd, LINT_BASELINE_FILE);
2452
+ if (!existsSync16(path)) return [];
2382
2453
  try {
2383
2454
  const parsed = parseJsonc(
2384
- readFileSync13(path, "utf8"),
2455
+ readFileSync14(path, "utf8"),
2385
2456
  LINT_BASELINE_FILE
2386
2457
  );
2387
2458
  return Object.keys(parsed.rules ?? {});
@@ -4204,14 +4275,14 @@ function downgradedRulesFor(preset) {
4204
4275
  }
4205
4276
 
4206
4277
  // 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";
4278
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
4279
+ import { isAbsolute as isAbsolute2, join as join20, resolve as resolve3 } from "path";
4209
4280
  function ruleCount(cwd, specifier) {
4210
4281
  const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
4211
- if (!existsSync15(path)) return void 0;
4282
+ if (!existsSync17(path)) return void 0;
4212
4283
  try {
4213
4284
  const parsed = parseJsonc(
4214
- readFileSync14(path, "utf8"),
4285
+ readFileSync15(path, "utf8"),
4215
4286
  specifier
4216
4287
  );
4217
4288
  return Object.keys(parsed.rules ?? {}).length;
@@ -4231,7 +4302,7 @@ function extraLayers(cwd, extendsList) {
4231
4302
  function committedExtendsList(cwd, configFile) {
4232
4303
  try {
4233
4304
  const parsed = parseJsonc(
4234
- readFileSync14(join18(cwd, configFile), "utf8"),
4305
+ readFileSync15(join20(cwd, configFile), "utf8"),
4235
4306
  configFile
4236
4307
  );
4237
4308
  return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
@@ -4290,8 +4361,8 @@ function errorCountsByConfigRule(stdout) {
4290
4361
  }
4291
4362
 
4292
4363
  // src/core/config/read-adoption.ts
4293
- import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
4294
- import { join as join20 } from "path";
4364
+ import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
4365
+ import { join as join22 } from "path";
4295
4366
 
4296
4367
  // src/core/config/owned-keys.ts
4297
4368
  function presetOwnedKeys(config, permitted, presetSets) {
@@ -4306,12 +4377,12 @@ function localOnlyKeys(config, permitted, presetSets) {
4306
4377
  }
4307
4378
 
4308
4379
  // src/core/config/resolve-config-target.ts
4309
- import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
4310
- import { join as join19 } from "path";
4380
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
4381
+ import { join as join21 } from "path";
4311
4382
  function readExtends(absolutePath) {
4312
4383
  let parsed;
4313
4384
  try {
4314
- parsed = parseJsonc(readFileSync15(absolutePath, "utf8"), absolutePath);
4385
+ parsed = parseJsonc(readFileSync16(absolutePath, "utf8"), absolutePath);
4315
4386
  } catch {
4316
4387
  return [];
4317
4388
  }
@@ -4325,8 +4396,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
4325
4396
  let existing;
4326
4397
  let existingExtendsSomething = false;
4327
4398
  for (const candidate of candidates) {
4328
- const absolutePath = join19(moduleDir, candidate);
4329
- if (!existsSync16(absolutePath)) continue;
4399
+ const absolutePath = join21(moduleDir, candidate);
4400
+ if (!existsSync18(absolutePath)) continue;
4330
4401
  const chain = readExtends(absolutePath);
4331
4402
  if (existing === void 0) {
4332
4403
  existing = candidate;
@@ -4359,12 +4430,12 @@ var NOT_ADOPTED3 = (configFile, unreadable = null) => ({
4359
4430
  });
4360
4431
  function readAdoption(cwd, options) {
4361
4432
  const target = resolveConfigTarget(cwd, options);
4362
- if (target.reason === "none" || !existsSync17(join20(cwd, target.path))) {
4433
+ if (target.reason === "none" || !existsSync19(join22(cwd, target.path))) {
4363
4434
  return NOT_ADOPTED3(target.reason === "none" ? null : target.path);
4364
4435
  }
4365
4436
  let parsed;
4366
4437
  try {
4367
- parsed = parseJsonc(readFileSync16(join20(cwd, target.path), "utf8"), target.path);
4438
+ parsed = parseJsonc(readFileSync17(join22(cwd, target.path), "utf8"), target.path);
4368
4439
  } catch (error) {
4369
4440
  const reason = error instanceof Error ? error.message : String(error);
4370
4441
  return NOT_ADOPTED3(target.path, `${target.path} could not be parsed (${reason})`);
@@ -4398,9 +4469,9 @@ function readLintAdoption(cwd) {
4398
4469
  }
4399
4470
 
4400
4471
  // src/roles/lint/resolve-oxlint.ts
4401
- import { existsSync as existsSync18 } from "fs";
4472
+ import { existsSync as existsSync20 } from "fs";
4402
4473
  import { createRequire as createRequire3 } from "module";
4403
- import { delimiter as delimiter2, dirname as dirname5, join as join21 } from "path";
4474
+ import { delimiter as delimiter2, dirname as dirname5, join as join23 } from "path";
4404
4475
  import { fileURLToPath as fileURLToPath2 } from "url";
4405
4476
  var PACKAGE_OF = {
4406
4477
  oxlint: "oxlint",
@@ -4425,30 +4496,30 @@ function tsgolintShim(cwd) {
4425
4496
  for (const owner of ["oxlint-tsgolint", "oxlint"]) {
4426
4497
  try {
4427
4498
  const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
4428
- candidates.push(join21(packageDir, "node_modules", ".bin", "tsgolint"));
4429
- candidates.push(join21(packageDir, "..", ".bin", "tsgolint"));
4499
+ candidates.push(join23(packageDir, "node_modules", ".bin", "tsgolint"));
4500
+ candidates.push(join23(packageDir, "..", ".bin", "tsgolint"));
4430
4501
  } catch {
4431
4502
  }
4432
4503
  }
4433
4504
  try {
4434
4505
  const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
4435
- candidates.push(join21(ownRoot, "node_modules", ".bin", "tsgolint"));
4506
+ candidates.push(join23(ownRoot, "node_modules", ".bin", "tsgolint"));
4436
4507
  } catch {
4437
4508
  candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
4438
4509
  }
4439
- return candidates.find((candidate) => candidate !== "" && existsSync18(candidate));
4510
+ return candidates.find((candidate) => candidate !== "" && existsSync20(candidate));
4440
4511
  }
4441
4512
  function oxlintSearchPath(cwd) {
4442
4513
  return binSearchPath(cwd);
4443
4514
  }
4444
4515
 
4445
4516
  // src/roles/lint/adapters/oxlint/plan.ts
4446
- import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
4447
- import { join as join24 } from "path";
4517
+ import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
4518
+ import { join as join26 } from "path";
4448
4519
 
4449
4520
  // src/roles/lint/eslint-ignores.ts
4450
- import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
4451
- import { join as join22 } from "path";
4521
+ import { existsSync as existsSync21, readFileSync as readFileSync18 } from "fs";
4522
+ import { join as join24 } from "path";
4452
4523
  import { parseSync as parseSync2 } from "oxc-parser";
4453
4524
  var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
4454
4525
  function toOxlintPattern(pattern) {
@@ -4460,11 +4531,11 @@ function readRootEslintIgnores(root) {
4460
4531
  return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
4461
4532
  }
4462
4533
  function readEslintIgnores(cwd) {
4463
- const config = ESLINT_CONFIG_FILES.map((name) => join22(cwd, name)).find((path) => existsSync19(path));
4534
+ const config = ESLINT_CONFIG_FILES.map((name) => join24(cwd, name)).find((path) => existsSync21(path));
4464
4535
  if (!config) return { patterns: [], unresolved: [] };
4465
4536
  let source;
4466
4537
  try {
4467
- source = readFileSync17(config, "utf8");
4538
+ source = readFileSync18(config, "utf8");
4468
4539
  } catch {
4469
4540
  return { patterns: [], unresolved: [] };
4470
4541
  }
@@ -4565,8 +4636,8 @@ function lintPresetFor(preset) {
4565
4636
  }
4566
4637
 
4567
4638
  // 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";
4639
+ import { readdirSync as readdirSync2, readFileSync as readFileSync19, statSync as statSync3 } from "fs";
4640
+ import { join as join25, relative as relative4 } from "path";
4570
4641
  var SOURCE_EXTENSIONS = [
4571
4642
  ".ts",
4572
4643
  ".tsx",
@@ -4613,10 +4684,10 @@ function* sourceFiles(dir) {
4613
4684
  return;
4614
4685
  }
4615
4686
  for (const entry of entries) {
4616
- const full = join23(dir, entry);
4687
+ const full = join25(dir, entry);
4617
4688
  let isDirectory;
4618
4689
  try {
4619
- isDirectory = statSync2(full).isDirectory();
4690
+ isDirectory = statSync3(full).isDirectory();
4620
4691
  } catch {
4621
4692
  continue;
4622
4693
  }
@@ -4633,7 +4704,7 @@ function findSuppressionRenames(cwd, renames) {
4633
4704
  for (const file of sourceFiles(cwd)) {
4634
4705
  let content;
4635
4706
  try {
4636
- content = readFileSync18(file, "utf8");
4707
+ content = readFileSync19(file, "utf8");
4637
4708
  } catch {
4638
4709
  continue;
4639
4710
  }
@@ -4657,7 +4728,7 @@ function findSuppressionRenames(cwd, renames) {
4657
4728
  }
4658
4729
  if (hits.length === 0) return;
4659
4730
  found.push({
4660
- file: relative5(cwd, file),
4731
+ file: relative4(cwd, file),
4661
4732
  line: index + 1,
4662
4733
  from: line,
4663
4734
  to: line.replace(rules, renamed.join(", ")),
@@ -4761,7 +4832,7 @@ function plan2(context) {
4761
4832
  keys: removableDeps
4762
4833
  });
4763
4834
  }
4764
- const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync20(join24(context.cwd, name)));
4835
+ const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync22(join26(context.cwd, name)));
4765
4836
  for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
4766
4837
  operations.push(
4767
4838
  ...nxTargetOperations({
@@ -4859,7 +4930,7 @@ function lintScripts(cwd) {
4859
4930
  function committedExtends(cwd) {
4860
4931
  try {
4861
4932
  const parsed = parseJsonc(
4862
- readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
4933
+ readFileSync20(join26(cwd, LINT_CONFIG_FILE), "utf8"),
4863
4934
  LINT_CONFIG_FILE
4864
4935
  );
4865
4936
  return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
@@ -4870,7 +4941,7 @@ function committedExtends(cwd) {
4870
4941
  function committedIgnorePatterns(cwd) {
4871
4942
  try {
4872
4943
  const parsed = parseJsonc(
4873
- readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
4944
+ readFileSync20(join26(cwd, LINT_CONFIG_FILE), "utf8"),
4874
4945
  LINT_CONFIG_FILE
4875
4946
  );
4876
4947
  return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
@@ -4882,7 +4953,7 @@ function moduleEslintDependencies(cwd) {
4882
4953
  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
4954
  let manifest;
4884
4955
  try {
4885
- manifest = JSON.parse(readFileSync19(join24(cwd, "package.json"), "utf8"));
4956
+ manifest = JSON.parse(readFileSync20(join26(cwd, "package.json"), "utf8"));
4886
4957
  } catch {
4887
4958
  return [];
4888
4959
  }
@@ -5016,20 +5087,20 @@ ${result.stderr ?? ""}`);
5016
5087
  * build the developer can see, rather than a silent half-adoption they cannot.
5017
5088
  */
5018
5089
  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;
5090
+ const configPath = join27(ctx.cwd, LINT_CONFIG_FILE);
5091
+ const baselinePath = join27(ctx.cwd, LINT_BASELINE_FILE);
5092
+ if (!existsSync23(configPath)) return;
5022
5093
  let config;
5023
5094
  try {
5024
5095
  config = parseJsonc(
5025
- readFileSync20(configPath, "utf8"),
5096
+ readFileSync21(configPath, "utf8"),
5026
5097
  LINT_CONFIG_FILE
5027
5098
  );
5028
5099
  } catch {
5029
5100
  return;
5030
5101
  }
5031
5102
  const current = Array.isArray(config.extends) ? config.extends : [];
5032
- const measurePath = join25(ctx.cwd, LINT_MEASURE_FILE);
5103
+ const measurePath = join27(ctx.cwd, LINT_MEASURE_FILE);
5033
5104
  let measured;
5034
5105
  try {
5035
5106
  writeFileSync2(
@@ -5077,7 +5148,7 @@ ${result.stderr ?? ""}`);
5077
5148
  }
5078
5149
  }
5079
5150
  async run(ctx) {
5080
- if (!existsSync21(join25(ctx.cwd, LINT_CONFIG_FILE))) {
5151
+ if (!existsSync23(join27(ctx.cwd, LINT_CONFIG_FILE))) {
5081
5152
  process.stderr.write(
5082
5153
  `sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
5083
5154
  `
@@ -5225,7 +5296,7 @@ ${result.stderr ?? ""}`);
5225
5296
  let stub;
5226
5297
  try {
5227
5298
  stub = parseJsonc(
5228
- readFileSync20(join25(cwd, LINT_CONFIG_FILE), "utf8"),
5299
+ readFileSync21(join27(cwd, LINT_CONFIG_FILE), "utf8"),
5229
5300
  LINT_CONFIG_FILE
5230
5301
  );
5231
5302
  } catch {
@@ -5235,7 +5306,7 @@ ${result.stderr ?? ""}`);
5235
5306
  for (const entry of stub.extends ?? []) {
5236
5307
  try {
5237
5308
  const preset = parseJsonc(
5238
- readFileSync20(join25(cwd, entry), "utf8"),
5309
+ readFileSync21(join27(cwd, entry), "utf8"),
5239
5310
  entry
5240
5311
  );
5241
5312
  for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
@@ -5305,14 +5376,14 @@ ${result.stderr ?? ""}`);
5305
5376
  let parsed;
5306
5377
  try {
5307
5378
  parsed = parseJsonc(
5308
- readFileSync20(join25(cwd, LINT_CONFIG_FILE), "utf8"),
5379
+ readFileSync21(join27(cwd, LINT_CONFIG_FILE), "utf8"),
5309
5380
  LINT_CONFIG_FILE
5310
5381
  );
5311
5382
  } catch {
5312
5383
  return void 0;
5313
5384
  }
5314
5385
  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)));
5386
+ return targets.find((target) => !existsSync23(join27(cwd, target)));
5316
5387
  }
5317
5388
  /** Announce what is not enforced, so reduced coverage is never silent. */
5318
5389
  announceDisabled(preset) {
@@ -5344,9 +5415,9 @@ function registerLint() {
5344
5415
 
5345
5416
  // src/roles/typescript/adapters/tsc/tsc.adapter.ts
5346
5417
  import { spawnSync as spawnSync5 } from "child_process";
5347
- import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
5418
+ import { existsSync as existsSync24, readFileSync as readFileSync23 } from "fs";
5348
5419
  import { createRequire as createRequire4 } from "module";
5349
- import { join as join27 } from "path";
5420
+ import { join as join29 } from "path";
5350
5421
 
5351
5422
  // src/roles/typescript/presets/nest.json
5352
5423
  var nest_default2 = {
@@ -5527,8 +5598,8 @@ function readTsconfigAdoption(cwd) {
5527
5598
  }
5528
5599
 
5529
5600
  // src/roles/typescript/adapters/tsc/plan.ts
5530
- import { readFileSync as readFileSync21 } from "fs";
5531
- import { join as join26 } from "path";
5601
+ import { readFileSync as readFileSync22 } from "fs";
5602
+ import { join as join28 } from "path";
5532
5603
 
5533
5604
  // src/roles/typescript/typecheck-script.ts
5534
5605
  var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
@@ -5621,7 +5692,7 @@ function planAdoption(context) {
5621
5692
  };
5622
5693
  }
5623
5694
  const existing = parseJsonc(
5624
- readFileSync21(join26(context.cwd, target.path), "utf8"),
5695
+ readFileSync22(join28(context.cwd, target.path), "utf8"),
5625
5696
  target.path
5626
5697
  );
5627
5698
  const extendsChain = composeExtends(existing.extends, preset);
@@ -5759,7 +5830,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5759
5830
  let chain;
5760
5831
  try {
5761
5832
  const parsed = parseJsonc(
5762
- readFileSync22(join27(cwd, target.path), "utf8"),
5833
+ readFileSync23(join29(cwd, target.path), "utf8"),
5763
5834
  target.path
5764
5835
  );
5765
5836
  chain = parsed.extends;
@@ -5772,7 +5843,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5772
5843
  );
5773
5844
  if (preset === void 0) return void 0;
5774
5845
  try {
5775
- createRequire4(join27(cwd, "noop.js")).resolve(preset);
5846
+ createRequire4(join29(cwd, "noop.js")).resolve(preset);
5776
5847
  return void 0;
5777
5848
  } catch {
5778
5849
  return preset;
@@ -5868,7 +5939,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5868
5939
  referencedProjects(cwd, config) {
5869
5940
  try {
5870
5941
  const parsed = parseJsonc(
5871
- readFileSync22(join27(cwd, config), "utf8"),
5942
+ readFileSync23(join29(cwd, config), "utf8"),
5872
5943
  config
5873
5944
  );
5874
5945
  const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
@@ -5884,7 +5955,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
5884
5955
  * check.
5885
5956
  */
5886
5957
  typecheckTarget(cwd) {
5887
- if (existsSync22(join27(cwd, "tsconfig.json"))) return "tsconfig.json";
5958
+ if (existsSync24(join29(cwd, "tsconfig.json"))) return "tsconfig.json";
5888
5959
  const target = resolveTsconfigTarget(cwd);
5889
5960
  return target.reason === "none" ? null : target.path;
5890
5961
  }
@@ -6088,7 +6159,7 @@ function replaceLines(current, replacements) {
6088
6159
  }
6089
6160
 
6090
6161
  // src/core/apply-plan.ts
6091
- import { existsSync as existsSync23, readFileSync as readFileSync23, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
6162
+ import { existsSync as existsSync25, readFileSync as readFileSync24, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
6092
6163
  import { resolve as resolve4, sep } from "path";
6093
6164
  import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
6094
6165
 
@@ -6107,7 +6178,7 @@ function resolveWithinRoot(cwd, relativePath) {
6107
6178
  return absolutePath;
6108
6179
  }
6109
6180
  function readIfExists(absolutePath) {
6110
- return existsSync23(absolutePath) ? readFileSync23(absolutePath, "utf8") : void 0;
6181
+ return existsSync25(absolutePath) ? readFileSync24(absolutePath, "utf8") : void 0;
6111
6182
  }
6112
6183
  function* leaves(value, prefix = []) {
6113
6184
  for (const [key, keyValue] of Object.entries(value)) {
@@ -6225,8 +6296,8 @@ function applyPlan(cwd, plan3) {
6225
6296
  }
6226
6297
 
6227
6298
  // 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";
6299
+ import { existsSync as existsSync26, readdirSync as readdirSync3, readFileSync as readFileSync25 } from "fs";
6300
+ import { join as join30 } from "path";
6230
6301
  var PATH_SIGNALS = [
6231
6302
  {
6232
6303
  preset: "nest",
@@ -6240,11 +6311,11 @@ var DEPENDENCY_SIGNALS = [
6240
6311
  { preset: "nest", pattern: /^@nestjs\// }
6241
6312
  ];
6242
6313
  function dependencyNames(cwd) {
6243
- const path = join28(cwd, "package.json");
6244
- if (!existsSync24(path)) return [];
6314
+ const path = join30(cwd, "package.json");
6315
+ if (!existsSync26(path)) return [];
6245
6316
  try {
6246
6317
  const manifest = parseJsonc(
6247
- readFileSync24(path, "utf8"),
6318
+ readFileSync25(path, "utf8"),
6248
6319
  path
6249
6320
  );
6250
6321
  return [
@@ -6267,7 +6338,7 @@ function declaresJsx(cwd) {
6267
6338
  for (const name of entries) {
6268
6339
  try {
6269
6340
  const config = parseJsonc(
6270
- readFileSync24(join28(cwd, name), "utf8"),
6341
+ readFileSync25(join30(cwd, name), "utf8"),
6271
6342
  name
6272
6343
  );
6273
6344
  if (config.compilerOptions?.jsx !== void 0) return true;
@@ -6432,19 +6503,19 @@ export {
6432
6503
  buildConfigFile,
6433
6504
  BaseAdapter,
6434
6505
  resolveBin,
6506
+ WORKSPACE_ROOT_MARKER,
6507
+ findWorkspaceRoot,
6508
+ ensureWorkspacePrep,
6509
+ inspectWorkspacePrep,
6435
6510
  VERBS,
6436
6511
  TARGETS,
6437
6512
  LONG_RUNNING_TARGETS,
6438
6513
  SWEEPABLE_TARGETS,
6439
6514
  PRESET_NAMES,
6440
- WORKSPACE_ROOT_MARKER,
6441
- findWorkspaceRoot,
6442
- ensureWorkspacePrep,
6443
- inspectWorkspacePrep,
6444
6515
  palette,
6445
6516
  registerAdapters,
6446
6517
  describeFramework,
6447
6518
  detectFramework,
6448
6519
  dispatch
6449
6520
  };
6450
- //# sourceMappingURL=chunk-Q3ZLHHW2.js.map
6521
+ //# sourceMappingURL=chunk-CWJX5DVF.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-CWJX5DVF.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.17",
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",