@hublo/sentinel 1.2.0-alpha.25 → 1.2.0-alpha.27

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-C4BWV6SU.js";
25
+ } from "../chunk-LNSAUFIM.js";
26
26
 
27
27
  // bin/sentinel.ts
28
28
  import { program } from "commander";
@@ -241,9 +241,16 @@ function workspaceRootHops(cwd, target) {
241
241
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
242
242
  import { join as join5 } from "path";
243
243
 
244
- // src/roles/build/adoption-drift.ts
245
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
246
- import { join as join4 } from "path";
244
+ // src/core/config/module-type.ts
245
+ function esmReadiness(fileName, packageType) {
246
+ if (/\.(mts|mjs)$/.test(fileName)) return { kind: "ready" };
247
+ if (packageType === "module") return { kind: "ready" };
248
+ const rename = fileName.replace(/\.(ts|js)$/, ".mts");
249
+ return {
250
+ kind: "blocked",
251
+ why: `sentinel is ESM only, and this module ${packageType === void 0 ? 'declares no `"type"`' : `declares \`"type": "${packageType}"\``}, so Node loads ${fileName} as CommonJS and the import fails. Two remedies, and adoption takes neither by itself because both reach beyond this build: add \`"type": "module"\` to this module's package.json, which changes how EVERY \`.js\` here loads, or rename the config to \`${rename}\`, which settles it in the filename where nothing can undo it.`
252
+ };
253
+ }
247
254
 
248
255
  // src/core/config/manifest.ts
249
256
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
@@ -406,7 +413,9 @@ var SENTINEL_OWNED_BUILD_PACKAGES = OWNED_PACKAGES.map(
406
413
  (entry) => entry.package
407
414
  );
408
415
 
409
- // src/roles/build/adoption-drift.ts
416
+ // src/roles/build/project-json.ts
417
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
418
+ import { join as join4 } from "path";
410
419
  function projectTargets(cwd) {
411
420
  const path = join4(cwd, "project.json");
412
421
  if (!existsSync4(path)) return void 0;
@@ -416,6 +425,8 @@ function projectTargets(cwd) {
416
425
  return void 0;
417
426
  }
418
427
  }
428
+
429
+ // src/roles/build/adoption-drift.ts
419
430
  function buildAdoptionDrift(cwd) {
420
431
  const drift = [];
421
432
  const manifest = readProjectPackageJson(cwd);
@@ -457,6 +468,12 @@ function buildAdoptionDrift(cwd) {
457
468
  `still declares a \`${BUILD_SCRIPT_NAME}\` target in project.json beside the one in package.json, so the module has two and which runs depends on the caller`
458
469
  );
459
470
  }
471
+ const devScript = moduleScripts(cwd)[DEV_SCRIPT_NAME];
472
+ if (devScript?.includes(SENTINEL_DEV_COMMAND) === true && projectTargets(cwd)?.[DEV_SCRIPT_NAME] !== void 0) {
473
+ drift.push(
474
+ `runs its \`${DEV_SCRIPT_NAME}\` script through sentinel but still declares a \`${DEV_SCRIPT_NAME}\` target in project.json, so one dev server has two declarations and editing the target stops the script from being what runs`
475
+ );
476
+ }
460
477
  return drift;
461
478
  }
462
479
 
@@ -511,7 +528,10 @@ function readBuildAdoption(cwd) {
511
528
  } catch (error) {
512
529
  return NOT_ADOPTED(configFile, error instanceof Error ? error.message : String(error));
513
530
  }
514
- if (!importsPreset(source)) return NOT_ADOPTED(configFile);
531
+ if (!importsPreset(source)) {
532
+ const esm = esmReadiness(configFile, readProjectPackageJson(cwd).type);
533
+ return NOT_ADOPTED(configFile, esm.kind === "blocked" ? esm.why : null);
534
+ }
515
535
  const manifest = readProjectPackageJson(cwd);
516
536
  const declared = { ...manifest.dependencies, ...manifest.devDependencies };
517
537
  const ownDeclarations = SENTINEL_OWNED_BUILD_PACKAGES.filter((name) => name in declared).map(
@@ -591,8 +611,8 @@ function binSearchPath(cwd) {
591
611
  }
592
612
 
593
613
  // src/roles/build/plan.ts
594
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
595
- import { join as join10 } from "path";
614
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
615
+ import { join as join11 } from "path";
596
616
 
597
617
  // src/core/config/existing-command.ts
598
618
  import { readFileSync as readFileSync6 } from "fs";
@@ -711,6 +731,161 @@ function keepsOtherCommands(script, sentinelCommand) {
711
731
  return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
712
732
  }
713
733
 
734
+ // src/roles/format/read-adoption.ts
735
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
736
+ import { join as join9 } from "path";
737
+
738
+ // src/roles/format/config-policy.ts
739
+ var FORMAT_CONFIG_FILE = ".oxfmtrc.json";
740
+ var FORMAT_SCRIPT_NAME = "format";
741
+ var PROVENANCE_KEY = "$sentinel";
742
+ var PERMITTED_LOCAL_KEYS = [
743
+ "useTabs",
744
+ "tabWidth",
745
+ "printWidth",
746
+ "semi",
747
+ "singleQuote",
748
+ "jsxSingleQuote",
749
+ "trailingComma",
750
+ "quoteProps",
751
+ "bracketSpacing",
752
+ "bracketSameLine",
753
+ "arrowParens",
754
+ "endOfLine",
755
+ "objectWrap",
756
+ "proseWrap",
757
+ "experimentalOperatorPosition"
758
+ ];
759
+ var ALWAYS_LOCAL_KEYS = ["ignorePatterns", "overrides"];
760
+ var FORMAT_DEFAULT_IGNORES = ["**/*.toml"];
761
+ var PRETTIER_CONFIG_FILES = [
762
+ ".prettierrc",
763
+ ".prettierrc.json",
764
+ ".prettierrc.json5",
765
+ ".prettierrc.yml",
766
+ ".prettierrc.yaml",
767
+ ".prettierrc.js",
768
+ ".prettierrc.cjs",
769
+ ".prettierrc.mjs",
770
+ "prettier.config.js",
771
+ "prettier.config.cjs",
772
+ "prettier.config.mjs"
773
+ ];
774
+ function formatTargets() {
775
+ return {
776
+ [FORMAT_SCRIPT_NAME]: {
777
+ cache: true,
778
+ inputs: ["default", `{projectRoot}/${FORMAT_CONFIG_FILE}`]
779
+ },
780
+ [`${FORMAT_SCRIPT_NAME}:fix`]: { cache: false }
781
+ };
782
+ }
783
+
784
+ // src/roles/format/presets/base.json
785
+ var base_default = {
786
+ printWidth: 80,
787
+ semi: false,
788
+ singleQuote: true,
789
+ trailingComma: "all",
790
+ sortPackageJson: false,
791
+ sortImports: false
792
+ };
793
+
794
+ // src/roles/format/presets/nest-imports.json
795
+ var nest_imports_default = {
796
+ customGroups: [
797
+ {
798
+ groupName: "workspace",
799
+ elementNamePattern: ["@hublo/**", "@shared/**", "@front/**", "@network/**"]
800
+ }
801
+ ],
802
+ groups: ["builtin", "external", "workspace", ["parent", "index"], "sibling"],
803
+ newlinesBetween: true,
804
+ order: "asc",
805
+ ignoreCase: false
806
+ };
807
+
808
+ // src/roles/format/preset-data.ts
809
+ var FORMAT_PRESETS = ["base", "svelte", "nest"];
810
+ function hasFormatPreset(name) {
811
+ return FORMAT_PRESETS.includes(name);
812
+ }
813
+ function formatPresetName(declared) {
814
+ if (declared === "svelte") return "svelte";
815
+ if (declared === "nest") return "nest";
816
+ return "base";
817
+ }
818
+ function formatPresetFor(preset) {
819
+ const base = base_default;
820
+ const name = formatPresetName(preset);
821
+ if (name === "svelte") return { ...base, svelte: true };
822
+ if (name === "nest") return { ...base, sortImports: nest_imports_default };
823
+ return base;
824
+ }
825
+
826
+ // src/roles/format/read-adoption.ts
827
+ var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
828
+ configFile,
829
+ preset: null,
830
+ adopted: false,
831
+ conformant: false,
832
+ drift: [],
833
+ local: {},
834
+ presetVersion: null,
835
+ effective: {},
836
+ unreadable
837
+ });
838
+ function sameValue(a, b) {
839
+ return JSON.stringify(a) === JSON.stringify(b);
840
+ }
841
+ function readFormatAdoption(cwd) {
842
+ const path = join9(cwd, FORMAT_CONFIG_FILE);
843
+ if (!existsSync9(path)) return NOT_ADOPTED2(null);
844
+ let parsed;
845
+ try {
846
+ parsed = parseJsonc(readFileSync7(path, "utf8"), FORMAT_CONFIG_FILE);
847
+ } catch (error) {
848
+ const reason = error instanceof Error ? error.message : String(error);
849
+ return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
850
+ }
851
+ const provenance = parsed[PROVENANCE_KEY];
852
+ if (!provenance || typeof provenance.preset !== "string") return NOT_ADOPTED2(FORMAT_CONFIG_FILE);
853
+ if (!hasFormatPreset(provenance.preset)) {
854
+ return NOT_ADOPTED2(
855
+ FORMAT_CONFIG_FILE,
856
+ `${FORMAT_CONFIG_FILE} declares the format preset "${provenance.preset}", which sentinel does not ship (shipped: ${FORMAT_PRESETS.join(", ")}). Re-run \`sentinel --init --format\`.`
857
+ );
858
+ }
859
+ const declaredLocal = Array.isArray(provenance.local) ? provenance.local : [];
860
+ const preset = formatPresetFor(provenance.preset);
861
+ const drift = [];
862
+ const local = {};
863
+ for (const [key, expected] of Object.entries(preset)) {
864
+ if (declaredLocal.includes(key)) {
865
+ local[key] = parsed[key];
866
+ continue;
867
+ }
868
+ if (!(key in parsed) || !sameValue(parsed[key], expected)) drift.push(key);
869
+ }
870
+ for (const key of declaredLocal) {
871
+ if (!(key in preset)) local[key] = parsed[key];
872
+ }
873
+ for (const key of ALWAYS_LOCAL_KEYS) {
874
+ if (key in parsed) local[key] = parsed[key];
875
+ }
876
+ return {
877
+ configFile: FORMAT_CONFIG_FILE,
878
+ preset: provenance.preset,
879
+ adopted: true,
880
+ conformant: drift.length === 0,
881
+ drift,
882
+ local,
883
+ presetVersion: typeof provenance.version === "string" ? provenance.version : null,
884
+ effective: parsed,
885
+ unreadable: null
886
+ };
887
+ }
888
+
714
889
  // src/roles/build/dev-script.ts
715
890
  var CHAIN = " -- ";
716
891
  var VITE_TOKEN = /(^|\s|\/)vite(\s|$)/;
@@ -731,14 +906,76 @@ function composeDevScript(existing, command) {
731
906
  return chunks.join(CHAIN);
732
907
  }
733
908
 
909
+ // src/roles/build/dev-target.ts
910
+ var DELEGATION_KEYS = /* @__PURE__ */ new Set(["executor", "options"]);
911
+ var DELEGATION_OPTION_KEYS = /* @__PURE__ */ new Set(["command", "cwd"]);
912
+ function delegatesToScript(target, scriptName) {
913
+ if (typeof target !== "object" || target === null || Array.isArray(target)) return false;
914
+ const entries = target;
915
+ if (!Object.keys(entries).every((key) => DELEGATION_KEYS.has(key))) return false;
916
+ if (entries["executor"] !== "nx:run-commands") return false;
917
+ const options = entries["options"];
918
+ if (typeof options !== "object" || options === null || Array.isArray(options)) return false;
919
+ const declared = options;
920
+ if (!Object.keys(declared).every((key) => DELEGATION_OPTION_KEYS.has(key))) return false;
921
+ const command = declared["command"];
922
+ if (typeof command !== "string") return false;
923
+ return new RegExp(String.raw`(^|\s)run\s+${scriptName}$`).test(command.trim());
924
+ }
925
+ function devScriptIsAdopted(devScript) {
926
+ return devScript !== void 0 && devScript.includes(SENTINEL_DEV_COMMAND);
927
+ }
928
+ function devTargetOperations(cwd, devScript) {
929
+ if (!devScriptIsAdopted(devScript)) return { operations: [] };
930
+ const target = projectTargets(cwd)?.[DEV_SCRIPT_NAME];
931
+ if (target === void 0) return { operations: [] };
932
+ if (!delegatesToScript(target, DEV_SCRIPT_NAME)) {
933
+ return {
934
+ operations: [],
935
+ note: `left the \`${DEV_SCRIPT_NAME}\` target in project.json alone: it does more than call \`pnpm run ${DEV_SCRIPT_NAME}\`, so removing it would drop behaviour this role never declared. Move what it does into the script, or keep it and accept the second home.`
936
+ };
937
+ }
938
+ return {
939
+ operations: [
940
+ { kind: "remove-json-keys", path: "project.json", keys: [["targets", DEV_SCRIPT_NAME]] }
941
+ ],
942
+ note: `removed the \`${DEV_SCRIPT_NAME}\` target from project.json: it only called \`pnpm run ${DEV_SCRIPT_NAME}\`, which nx infers from the script itself. host-admin has run without that target all along.`
943
+ };
944
+ }
945
+
734
946
  // src/roles/build/nest/adopt.ts
735
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
736
- import { join as join9 } from "path";
947
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
948
+ import { join as join10 } from "path";
737
949
  var NEST_CONFIG_FILE = "vite.config.mts";
950
+ function asSource(value, depth) {
951
+ const pad = " ".repeat(depth);
952
+ const inner = " ".repeat(depth + 1);
953
+ if (Array.isArray(value)) {
954
+ if (value.length === 0) return "[]";
955
+ return `[
956
+ ${value.map((item) => `${inner}${asSource(item, depth + 1)}`).join(",\n")},
957
+ ${pad}]`;
958
+ }
959
+ if (value !== null && typeof value === "object") {
960
+ const entries = Object.entries(value);
961
+ if (entries.length === 0) return "{}";
962
+ return `{
963
+ ${entries.map(([key, item]) => `${inner}${asKey(key)}: ${asSource(item, depth + 1)}`).join(",\n")},
964
+ ${pad}}`;
965
+ }
966
+ if (typeof value === "string") return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
967
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
968
+ throw new Error(
969
+ `sentinel build(nest): cannot write ${typeof value} into the generated config. The build target carries a shape this translation was not written for, and it should refuse rather than emit something nobody has read.`
970
+ );
971
+ }
972
+ function asKey(key) {
973
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : `'${key}'`;
974
+ }
738
975
  function nestConfigSource(cwd, target) {
739
976
  const hops = workspaceRootHops(cwd, target);
740
977
  const up = Array.from({ length: hops }, () => "..").join("/");
741
- const carried = (name, value) => value === void 0 || value.length === 0 ? "" : ` ${name}: ${JSON.stringify(value)},
978
+ const carried = (name, value) => value === void 0 || value.length === 0 ? "" : ` ${name}: ${asSource(value, 1)},
742
979
  `;
743
980
  return `import path from 'node:path'
744
981
 
@@ -757,9 +994,9 @@ function nestBuildOutputs(target) {
757
994
  }
758
995
  var OUT_DIR_IN_CONFIG = /^\s*outDir:\s*'([^']+)'/m;
759
996
  function nestOutputsFromConfig(cwd) {
760
- const configPath = join9(cwd, NEST_CONFIG_FILE);
761
- if (!existsSync9(configPath)) return void 0;
762
- const outDir = OUT_DIR_IN_CONFIG.exec(readFileSync7(configPath, "utf8"))?.[1];
997
+ const configPath = join10(cwd, NEST_CONFIG_FILE);
998
+ if (!existsSync10(configPath)) return void 0;
999
+ const outDir = OUT_DIR_IN_CONFIG.exec(readFileSync8(configPath, "utf8"))?.[1];
763
1000
  return outDir === void 0 ? void 0 : [`{workspaceRoot}/${outDir}`];
764
1001
  }
765
1002
  function nestAdoptionOperations(cwd, target) {
@@ -797,11 +1034,6 @@ function describeUnmet(preset, unmet) {
797
1034
  // src/core/config/rewrite-imports.ts
798
1035
  import { parseSync } from "oxc-parser";
799
1036
  var movedSpecifiers = (toolchain) => new Set(toolchain.owned.map((entry) => entry.specifier));
800
- function esmBlocker(fileName, packageType) {
801
- if (/\.(mts|mjs)$/.test(fileName)) return void 0;
802
- if (packageType === "module") return void 0;
803
- return `sentinel is ESM only, and this module does not declare \`"type": "module"\`, so Vite would load ${fileName} as CommonJS and fail to require it. Add \`"type": "module"\` to this module's package.json, or rename the config to \`${fileName.replace(/\.(ts|js)$/, ".m$1")}\`, then run this again.`;
804
- }
805
1037
  var defaultExportNames = (toolchain) => Object.fromEntries(
806
1038
  toolchain.owned.filter((entry) => entry.default !== void 0).map((entry) => [entry.specifier, entry.default])
807
1039
  );
@@ -972,8 +1204,8 @@ function plan(context) {
972
1204
  function planReactApp(context, configFile) {
973
1205
  const notes = [];
974
1206
  const operations = [];
975
- const notEsm = esmBlocker(configFile, readProjectPackageJson(context.cwd).type);
976
- if (notEsm !== void 0) return { operations: [], skipped: notEsm };
1207
+ const esm = esmReadiness(configFile, readProjectPackageJson(context.cwd).type);
1208
+ if (esm.kind === "blocked") return { operations: [], skipped: esm.why };
977
1209
  const rewrite = rewriteBuildImports(readConfigSource(context.cwd, configFile), configFile);
978
1210
  if (rewrite.kind === "blocked") {
979
1211
  return {
@@ -1002,7 +1234,8 @@ function planReactApp(context, configFile) {
1002
1234
  `${configFile} now imports ${rewrite.moved.join(", ")} from \`${BUILD_PRESET_SPECIFIER}\`. Only the import lines changed: every attribute, alias and path is untouched, so this build produces what it produced before.`
1003
1235
  );
1004
1236
  }
1005
- operations.push(manifestOperation(context.cwd, buildScripts(context.cwd)));
1237
+ const scripts = buildScripts(context.cwd);
1238
+ operations.push(manifestOperation(context.cwd, scripts));
1006
1239
  operations.push(
1007
1240
  ...nxTargetOperations({
1008
1241
  cwd: context.cwd,
@@ -1012,6 +1245,9 @@ function planReactApp(context, configFile) {
1012
1245
  })
1013
1246
  })
1014
1247
  );
1248
+ const dev = devTargetOperations(context.cwd, scripts[DEV_SCRIPT_NAME]);
1249
+ operations.push(...dev.operations);
1250
+ if (dev.note !== void 0) notes.push(dev.note);
1015
1251
  const owned = ownedBuildDependencies(context.cwd);
1016
1252
  if (owned.length > 0) {
1017
1253
  operations.push({ kind: "remove-json-keys", path: "package.json", keys: owned });
@@ -1019,6 +1255,10 @@ function planReactApp(context, configFile) {
1019
1255
  `removed ${owned.map(([, name]) => name).join(", ")} from this module: sentinel owns them now, and two copies of Vite in one build give the plugins a different Vite than the one running them.`
1020
1256
  );
1021
1257
  }
1258
+ const formatted = readFormatAdoption(context.cwd).adopted;
1259
+ notes.push(
1260
+ formatted ? `run \`sentinel --run --format --fix\` on this module: adoption rewrote ${configFile}, and a rewritten import can be longer than your formatter wraps at.` : `this module is formatted by the workspace, not by sentinel, so format ${configFile} with whatever owns it before committing: a rewritten import can be longer than that formatter wraps at, and the check would fail on a file nobody typed.`
1261
+ );
1022
1262
  notes.push(
1023
1263
  `this config is code, and it runs before the build does: any environment variable it reads has to be present wherever you build, including a fresh clone, another CI job or an image. Adoption does not change what it reads, but a failure there arrives early and names this role rather than the variable.`
1024
1264
  );
@@ -1059,9 +1299,9 @@ function planNestService(context, webpackTarget) {
1059
1299
  };
1060
1300
  }
1061
1301
  function readConfigSource(cwd, configFile) {
1062
- if (!existsSync10(join10(cwd, configFile))) return "";
1302
+ if (!existsSync11(join11(cwd, configFile))) return "";
1063
1303
  try {
1064
- return readFileSync8(join10(cwd, configFile), "utf8");
1304
+ return readFileSync9(join11(cwd, configFile), "utf8");
1065
1305
  } catch {
1066
1306
  return "";
1067
1307
  }
@@ -1246,12 +1486,12 @@ var ViteDevAdapter = class extends ViteRoleAdapter {
1246
1486
  };
1247
1487
 
1248
1488
  // src/roles/build/declared-outputs.ts
1249
- import { existsSync as existsSync12, rmSync, statSync, writeFileSync as writeFileSync2 } from "fs";
1250
- import { join as join12 } from "path";
1489
+ import { existsSync as existsSync13, rmSync, statSync, writeFileSync as writeFileSync2 } from "fs";
1490
+ import { join as join13 } from "path";
1251
1491
 
1252
1492
  // src/core/workspace-prep.ts
1253
- import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync } from "fs";
1254
- import { dirname as dirname3, join as join11, relative as relative2 } from "path";
1493
+ import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync } from "fs";
1494
+ import { dirname as dirname3, join as join12, relative as relative2 } from "path";
1255
1495
 
1256
1496
  // src/core/settings.ts
1257
1497
  var WORKSPACE_ROOT_MARKER = "nx.json";
@@ -1266,7 +1506,7 @@ var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
1266
1506
  function findWorkspaceRoot(startDir) {
1267
1507
  let dir = startDir;
1268
1508
  for (; ; ) {
1269
- if (existsSync11(join11(dir, WORKSPACE_ROOT_MARKER))) return dir;
1509
+ if (existsSync12(join12(dir, WORKSPACE_ROOT_MARKER))) return dir;
1270
1510
  const parent = dirname3(dir);
1271
1511
  if (parent === dir) return void 0;
1272
1512
  dir = parent;
@@ -1279,9 +1519,9 @@ function declaredNativeTs(pkg) {
1279
1519
  return version || void 0;
1280
1520
  }
1281
1521
  function ensureI18nextSingleton(root, dryRun) {
1282
- const pkgPath = join11(root, "package.json");
1283
- if (!existsSync11(pkgPath)) return void 0;
1284
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
1522
+ const pkgPath = join12(root, "package.json");
1523
+ if (!existsSync12(pkgPath)) return void 0;
1524
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
1285
1525
  const want = declaredNativeTs(pkg);
1286
1526
  if (!want) return void 0;
1287
1527
  const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
@@ -1294,10 +1534,10 @@ function ensureI18nextSingleton(root, dryRun) {
1294
1534
  return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
1295
1535
  }
1296
1536
  function ensureReleaseAgeAllowList(root, dryRun) {
1297
- const yamlPath = join11(root, WORKSPACE_YAML);
1298
- if (!existsSync11(yamlPath)) return void 0;
1537
+ const yamlPath = join12(root, WORKSPACE_YAML);
1538
+ if (!existsSync12(yamlPath)) return void 0;
1299
1539
  const own = readOwnPackage().name;
1300
- const lines = readFileSync9(yamlPath, "utf8").split("\n");
1540
+ const lines = readFileSync10(yamlPath, "utf8").split("\n");
1301
1541
  const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
1302
1542
  if (keyIdx === -1) return void 0;
1303
1543
  let lastItemIdx = keyIdx;
@@ -1332,11 +1572,11 @@ var ROOT_PRETTIER_CONFIGS = [
1332
1572
  function ensureFormatterExclusion(root, moduleDir, dryRun) {
1333
1573
  const rel = relative2(root, moduleDir).replaceAll("\\", "/");
1334
1574
  if (rel === "" || rel.startsWith("..")) return void 0;
1335
- const ignorePath = join11(root, PRETTIER_IGNORE);
1336
- const hasPrettier = existsSync11(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync11(join11(root, name)));
1575
+ const ignorePath = join12(root, PRETTIER_IGNORE);
1576
+ const hasPrettier = existsSync12(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync12(join12(root, name)));
1337
1577
  if (!hasPrettier) return void 0;
1338
1578
  const pattern = `/${rel}/`;
1339
- const existing = existsSync11(ignorePath) ? readFileSync9(ignorePath, "utf8") : "";
1579
+ const existing = existsSync12(ignorePath) ? readFileSync10(ignorePath, "utf8") : "";
1340
1580
  const lines = existing.split("\n");
1341
1581
  if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
1342
1582
  return void 0;
@@ -1372,10 +1612,10 @@ function ensureWorkspacePrep(opts) {
1372
1612
  function inspectWorkspacePrep(root) {
1373
1613
  const entries = [];
1374
1614
  let pkg = {};
1375
- const pkgPath = join11(root, "package.json");
1376
- if (existsSync11(pkgPath)) {
1615
+ const pkgPath = join12(root, "package.json");
1616
+ if (existsSync12(pkgPath)) {
1377
1617
  try {
1378
- pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
1618
+ pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
1379
1619
  } catch {
1380
1620
  pkg = {};
1381
1621
  }
@@ -1389,9 +1629,9 @@ function inspectWorkspacePrep(root) {
1389
1629
  });
1390
1630
  }
1391
1631
  const own = readOwnPackage().name;
1392
- const yamlPath = join11(root, WORKSPACE_YAML);
1393
- if (existsSync11(yamlPath)) {
1394
- const yaml = readFileSync9(yamlPath, "utf8");
1632
+ const yamlPath = join12(root, WORKSPACE_YAML);
1633
+ if (existsSync12(yamlPath)) {
1634
+ const yaml = readFileSync10(yamlPath, "utf8");
1395
1635
  const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
1396
1636
  if (listed) {
1397
1637
  const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
@@ -1409,15 +1649,15 @@ function declaredBuildTarget(cwd) {
1409
1649
  return readProjectPackageJson(cwd).nx?.targets?.[BUILD_SCRIPT_NAME];
1410
1650
  }
1411
1651
  function resolveOutputToken(token, cwd) {
1412
- if (token.startsWith("{projectRoot}/")) return join12(cwd, token.slice("{projectRoot}/".length));
1652
+ if (token.startsWith("{projectRoot}/")) return join13(cwd, token.slice("{projectRoot}/".length));
1413
1653
  if (token.startsWith("{workspaceRoot}/")) {
1414
1654
  const root = findWorkspaceRoot(cwd);
1415
- return root === void 0 ? void 0 : join12(root, token.slice("{workspaceRoot}/".length));
1655
+ return root === void 0 ? void 0 : join13(root, token.slice("{workspaceRoot}/".length));
1416
1656
  }
1417
- return join12(cwd, token);
1657
+ return join13(cwd, token);
1418
1658
  }
1419
1659
  function buildStamp(cwd) {
1420
- const marker = join12(cwd, `.sentinel-build-stamp-${process.pid}`);
1660
+ const marker = join13(cwd, `.sentinel-build-stamp-${process.pid}`);
1421
1661
  try {
1422
1662
  writeFileSync2(marker, "");
1423
1663
  const stamp = statSync(marker).mtimeMs;
@@ -1428,7 +1668,7 @@ function buildStamp(cwd) {
1428
1668
  }
1429
1669
  }
1430
1670
  function writtenSince(path, startedAt) {
1431
- if (!existsSync12(path)) return false;
1671
+ if (!existsSync13(path)) return false;
1432
1672
  try {
1433
1673
  return statSync(path).mtimeMs >= startedAt;
1434
1674
  } catch {
@@ -1582,7 +1822,7 @@ import { join as join18 } from "path";
1582
1822
 
1583
1823
  // src/core/config/has-source.ts
1584
1824
  import { readdirSync } from "fs";
1585
- import { extname, join as join13, relative as relative3 } from "path";
1825
+ import { extname, join as join14, relative as relative3 } from "path";
1586
1826
  var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
1587
1827
  "node_modules",
1588
1828
  "dist",
@@ -1633,7 +1873,7 @@ function hasSourceFiles(cwd, extensions) {
1633
1873
  }
1634
1874
  for (const entry of entries) {
1635
1875
  if (entry.isDirectory()) {
1636
- if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join13(dir, entry.name));
1876
+ if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join14(dir, entry.name));
1637
1877
  continue;
1638
1878
  }
1639
1879
  if (wanted.has(extname(entry.name))) return true;
@@ -1654,7 +1894,7 @@ function listSourceFiles(cwd, extensions) {
1654
1894
  continue;
1655
1895
  }
1656
1896
  for (const entry of entries) {
1657
- const full = join13(dir, entry.name);
1897
+ const full = join14(dir, entry.name);
1658
1898
  if (entry.isDirectory()) {
1659
1899
  if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
1660
1900
  continue;
@@ -1672,52 +1912,6 @@ function scopeFlags(paths, options = []) {
1672
1912
  return [TOLERATE_EMPTY_FLAG];
1673
1913
  }
1674
1914
 
1675
- // src/roles/format/config-policy.ts
1676
- var FORMAT_CONFIG_FILE = ".oxfmtrc.json";
1677
- var FORMAT_SCRIPT_NAME = "format";
1678
- var PROVENANCE_KEY = "$sentinel";
1679
- var PERMITTED_LOCAL_KEYS = [
1680
- "useTabs",
1681
- "tabWidth",
1682
- "printWidth",
1683
- "semi",
1684
- "singleQuote",
1685
- "jsxSingleQuote",
1686
- "trailingComma",
1687
- "quoteProps",
1688
- "bracketSpacing",
1689
- "bracketSameLine",
1690
- "arrowParens",
1691
- "endOfLine",
1692
- "objectWrap",
1693
- "proseWrap",
1694
- "experimentalOperatorPosition"
1695
- ];
1696
- var ALWAYS_LOCAL_KEYS = ["ignorePatterns", "overrides"];
1697
- var FORMAT_DEFAULT_IGNORES = ["**/*.toml"];
1698
- var PRETTIER_CONFIG_FILES = [
1699
- ".prettierrc",
1700
- ".prettierrc.json",
1701
- ".prettierrc.json5",
1702
- ".prettierrc.yml",
1703
- ".prettierrc.yaml",
1704
- ".prettierrc.js",
1705
- ".prettierrc.cjs",
1706
- ".prettierrc.mjs",
1707
- "prettier.config.js",
1708
- "prettier.config.cjs",
1709
- "prettier.config.mjs"
1710
- ];
1711
- function formatTargets() {
1712
- return {
1713
- [FORMAT_SCRIPT_NAME]: {
1714
- cache: true,
1715
- inputs: ["default", `{projectRoot}/${FORMAT_CONFIG_FILE}`]
1716
- },
1717
- [`${FORMAT_SCRIPT_NAME}:fix`]: { cache: false }
1718
- };
1719
- }
1720
-
1721
1915
  // src/roles/format/format-script.ts
1722
1916
  var SENTINEL_FORMAT_COMMAND = "sentinel --run --format";
1723
1917
  function isPrettierSegment(segment) {
@@ -1746,8 +1940,8 @@ function writesWhenRewritten(name, command) {
1746
1940
  var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
1747
1941
 
1748
1942
  // src/roles/format/inherited-ignores.ts
1749
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
1750
- import { join as join14 } from "path";
1943
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
1944
+ import { join as join15 } from "path";
1751
1945
  var ROOT_IGNORE_FILE = ".prettierignore";
1752
1946
  function isPattern(line) {
1753
1947
  const trimmed = line.trim();
@@ -1762,11 +1956,11 @@ function toModulePattern(pattern) {
1762
1956
  }
1763
1957
  function inheritedIgnorePatterns(workspaceRoot) {
1764
1958
  if (!workspaceRoot) return [];
1765
- const path = join14(workspaceRoot, ROOT_IGNORE_FILE);
1766
- if (!existsSync13(path)) return [];
1959
+ const path = join15(workspaceRoot, ROOT_IGNORE_FILE);
1960
+ if (!existsSync14(path)) return [];
1767
1961
  let contents;
1768
1962
  try {
1769
- contents = readFileSync10(path, "utf8");
1963
+ contents = readFileSync11(path, "utf8");
1770
1964
  } catch {
1771
1965
  return [];
1772
1966
  }
@@ -1810,56 +2004,14 @@ function needsSvelteCompiler(output) {
1810
2004
  return /svelte\/compiler/.test(output);
1811
2005
  }
1812
2006
 
1813
- // src/roles/format/presets/base.json
1814
- var base_default = {
1815
- printWidth: 80,
1816
- semi: false,
1817
- singleQuote: true,
1818
- trailingComma: "all",
1819
- sortPackageJson: false,
1820
- sortImports: false
1821
- };
1822
-
1823
- // src/roles/format/presets/nest-imports.json
1824
- var nest_imports_default = {
1825
- customGroups: [
1826
- {
1827
- groupName: "workspace",
1828
- elementNamePattern: ["@hublo/**", "@shared/**", "@front/**", "@network/**"]
1829
- }
1830
- ],
1831
- groups: ["builtin", "external", "workspace", ["parent", "index"], "sibling"],
1832
- newlinesBetween: true,
1833
- order: "asc",
1834
- ignoreCase: false
1835
- };
1836
-
1837
- // src/roles/format/preset-data.ts
1838
- var FORMAT_PRESETS = ["base", "svelte", "nest"];
1839
- function hasFormatPreset(name) {
1840
- return FORMAT_PRESETS.includes(name);
1841
- }
1842
- function formatPresetName(declared) {
1843
- if (declared === "svelte") return "svelte";
1844
- if (declared === "nest") return "nest";
1845
- return "base";
1846
- }
1847
- function formatPresetFor(preset) {
1848
- const base = base_default;
1849
- const name = formatPresetName(preset);
1850
- if (name === "svelte") return { ...base, svelte: true };
1851
- if (name === "nest") return { ...base, sortImports: nest_imports_default };
1852
- return base;
1853
- }
1854
-
1855
2007
  // src/roles/format/prettier-config.ts
1856
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
1857
- import { join as join16 } from "path";
2008
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
2009
+ import { join as join17 } from "path";
1858
2010
 
1859
2011
  // src/roles/format/resolve-oxfmt.ts
1860
- import { readFileSync as readFileSync11 } from "fs";
2012
+ import { readFileSync as readFileSync12 } from "fs";
1861
2013
  import { createRequire as createRequire2 } from "module";
1862
- import { dirname as dirname4, join as join15 } from "path";
2014
+ import { dirname as dirname4, join as join16 } from "path";
1863
2015
  function resolveOxfmt(cwd) {
1864
2016
  return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
1865
2017
  }
@@ -1888,8 +2040,8 @@ function configSchema() {
1888
2040
  function readConfigSchema() {
1889
2041
  try {
1890
2042
  const manifest = createRequire2(import.meta.url).resolve("oxfmt/package.json");
1891
- const schemaPath = join15(dirname4(manifest), "configuration_schema.json");
1892
- return JSON.parse(readFileSync11(schemaPath, "utf8"));
2043
+ const schemaPath = join16(dirname4(manifest), "configuration_schema.json");
2044
+ return JSON.parse(readFileSync12(schemaPath, "utf8"));
1893
2045
  } catch {
1894
2046
  return void 0;
1895
2047
  }
@@ -1920,14 +2072,14 @@ function toOxfmtOverrides(value) {
1920
2072
  return { overrides, unresolved };
1921
2073
  }
1922
2074
  function readPrettierSettings(cwd) {
1923
- const file = PRETTIER_CONFIG_FILES.find((name) => existsSync14(join16(cwd, name)));
2075
+ const file = PRETTIER_CONFIG_FILES.find((name) => existsSync15(join17(cwd, name)));
1924
2076
  if (!file) return { options: {}, unresolved: [] };
1925
2077
  if (/\.(js|cjs|mjs)$/.test(file)) {
1926
2078
  return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
1927
2079
  }
1928
2080
  let parsed;
1929
2081
  try {
1930
- parsed = parseJsonc(readFileSync12(join16(cwd, file), "utf8"), file);
2082
+ parsed = parseJsonc(readFileSync13(join17(cwd, file), "utf8"), file);
1931
2083
  } catch {
1932
2084
  return { options: {}, file, unresolved: [`${file} could not be parsed`] };
1933
2085
  }
@@ -1956,71 +2108,6 @@ function readPrettierSettings(cwd) {
1956
2108
  return { options, file, unresolved };
1957
2109
  }
1958
2110
 
1959
- // src/roles/format/read-adoption.ts
1960
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
1961
- import { join as join17 } from "path";
1962
- var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
1963
- configFile,
1964
- preset: null,
1965
- adopted: false,
1966
- conformant: false,
1967
- drift: [],
1968
- local: {},
1969
- presetVersion: null,
1970
- effective: {},
1971
- unreadable
1972
- });
1973
- function sameValue(a, b) {
1974
- return JSON.stringify(a) === JSON.stringify(b);
1975
- }
1976
- function readFormatAdoption(cwd) {
1977
- const path = join17(cwd, FORMAT_CONFIG_FILE);
1978
- if (!existsSync15(path)) return NOT_ADOPTED2(null);
1979
- let parsed;
1980
- try {
1981
- parsed = parseJsonc(readFileSync13(path, "utf8"), FORMAT_CONFIG_FILE);
1982
- } catch (error) {
1983
- const reason = error instanceof Error ? error.message : String(error);
1984
- return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
1985
- }
1986
- const provenance = parsed[PROVENANCE_KEY];
1987
- if (!provenance || typeof provenance.preset !== "string") return NOT_ADOPTED2(FORMAT_CONFIG_FILE);
1988
- if (!hasFormatPreset(provenance.preset)) {
1989
- return NOT_ADOPTED2(
1990
- FORMAT_CONFIG_FILE,
1991
- `${FORMAT_CONFIG_FILE} declares the format preset "${provenance.preset}", which sentinel does not ship (shipped: ${FORMAT_PRESETS.join(", ")}). Re-run \`sentinel --init --format\`.`
1992
- );
1993
- }
1994
- const declaredLocal = Array.isArray(provenance.local) ? provenance.local : [];
1995
- const preset = formatPresetFor(provenance.preset);
1996
- const drift = [];
1997
- const local = {};
1998
- for (const [key, expected] of Object.entries(preset)) {
1999
- if (declaredLocal.includes(key)) {
2000
- local[key] = parsed[key];
2001
- continue;
2002
- }
2003
- if (!(key in parsed) || !sameValue(parsed[key], expected)) drift.push(key);
2004
- }
2005
- for (const key of declaredLocal) {
2006
- if (!(key in preset)) local[key] = parsed[key];
2007
- }
2008
- for (const key of ALWAYS_LOCAL_KEYS) {
2009
- if (key in parsed) local[key] = parsed[key];
2010
- }
2011
- return {
2012
- configFile: FORMAT_CONFIG_FILE,
2013
- preset: provenance.preset,
2014
- adopted: true,
2015
- conformant: drift.length === 0,
2016
- drift,
2017
- local,
2018
- presetVersion: typeof provenance.version === "string" ? provenance.version : null,
2019
- effective: parsed,
2020
- unreadable: null
2021
- };
2022
- }
2023
-
2024
2111
  // src/roles/format/unenforced.ts
2025
2112
  function unenforcedFormatOptions(options) {
2026
2113
  const gaps = [];
@@ -6600,4 +6687,4 @@ export {
6600
6687
  detectFramework,
6601
6688
  dispatch
6602
6689
  };
6603
- //# sourceMappingURL=chunk-C4BWV6SU.js.map
6690
+ //# sourceMappingURL=chunk-LNSAUFIM.js.map
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  registerAdapters,
8
8
  resolve,
9
9
  setDefaultRunner
10
- } from "./chunk-C4BWV6SU.js";
10
+ } from "./chunk-LNSAUFIM.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.25",
3
+ "version": "1.2.0-alpha.27",
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",