@inixiative/config 0.5.61 → 0.5.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,7 +65,7 @@ TypeScript 6.0 notes, discovered by this repo's fixture suite:
65
65
  bunx @inixiative/config check [dir] [--preset=base|node|react]
66
66
  bunx @inixiative/config sync [dir] [--preset=...] [--force] [--no-install]
67
67
  bunx @inixiative/config scan [root] [--lane=primitives|agentic]
68
- bunx @inixiative/config train [root] [--lane=primitives|agentic] [--push]
68
+ bunx @inixiative/config train [root] [--lane=primitives|agentic] [--push] [--dry-run]
69
69
  bunx @inixiative/config ports [project]
70
70
  ```
71
71
 
@@ -83,7 +83,9 @@ Lanes stand alone. A lane with a package missing a checkout, or claimed by ambig
83
83
 
84
84
  Consumers run once, after every selected lane, upstreams first, against the full blessed set: a consumer of both lanes ends coherent with both whichever lane ran. They get the same bump → re-lock → `check`, never a publish, and their changes go through review: the train commits them on a `train/ecosystem-sync-<date>` branch and leaves the checkout there until it is merged.
85
85
 
86
- When anything was blessed it then ships this package last, so the BOM names the new state: bumps its own patch version if npm already has the current one, blesses that version in the BOM, brings the `test/fixtures` consumers onto the blessed set, runs `check`, commits, publishes. Then every checkout the train re-locked (lane packages on their default branch, consumers on their review branch) is re-locked onto that new `@inixiative/config` and committed, so none is left pinning the previous release. It never pushes unless asked: pass `--push` to push every repo it committed to and open a PR for each consumer branch; otherwise it prints the commands.
86
+ When anything was blessed it then ships this package last, so the BOM names the new state: bumps its own patch version if npm already has the current one, blesses that version in the BOM, brings the `test/fixtures` consumers onto the blessed set, runs `check`, commits, publishes. Then every checkout the train re-locked (lane packages on their default branch, consumers on their review branch) is re-locked onto that new `@inixiative/config` and committed, so none is left pinning the previous release. It never pushes unless asked: pass `--push` to push every repo it committed to and open a PR for each consumer branch; otherwise it prints the commands. `--dry-run` walks the same plan and prints each step it would take (range rewrites, re-locks and checks, commits, review branches, publishes, BOM blessings, pushes) without running any of them; it only fetches and reads git state and `npm view`.
87
+
88
+ Each command accepts only its own flags. An unknown or misplaced flag (`--dryrun`, `--push` on `sync`) is a usage error and exits 2 before anything runs.
87
89
 
88
90
  Division of labor: this CLI owns the toolchain set, ecosystem coherence, stubs, and lockfile format. Renovate owns everything else plus bumping `@inixiative/config` itself, and must be fenced off the toolchain packages.
89
91
 
package/dist/cli.js CHANGED
@@ -6,6 +6,9 @@ import { existsSync as existsSync2, mkdtempSync, readdirSync as readdirSync2, re
6
6
  import { tmpdir } from "os";
7
7
  import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
8
8
 
9
+ // src/args.ts
10
+ import { parseArgs } from "util";
11
+
9
12
  // src/lib.ts
10
13
  import { spawnSync } from "child_process";
11
14
  import {
@@ -22,13 +25,13 @@ import { fileURLToPath } from "url";
22
25
  var packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
23
26
  var LANES = ["primitives", "agentic"];
24
27
  var isLane = (value) => LANES.includes(value);
25
- var lanePackages = (manifest2, lane) => {
26
- const section = lane === "primitives" ? manifest2.ecosystem : manifest2.agentic ?? {};
28
+ var lanePackages = (manifest2, lane2) => {
29
+ const section = lane2 === "primitives" ? manifest2.ecosystem : manifest2.agentic ?? {};
27
30
  const { "@inixiative/config": _config, ...packages } = section;
28
31
  return packages;
29
32
  };
30
- var laneSection = (manifest2, lane) => {
31
- if (lane === "primitives") return manifest2.ecosystem;
33
+ var laneSection = (manifest2, lane2) => {
34
+ if (lane2 === "primitives") return manifest2.ecosystem;
32
35
  manifest2.agentic ??= {};
33
36
  return manifest2.agentic;
34
37
  };
@@ -38,7 +41,7 @@ var blessedVersions = (manifest2) => ({
38
41
  });
39
42
  var consumersFor = (manifest2, lanes2) => {
40
43
  const consumers = manifest2.consumers ?? {};
41
- const selected = Object.keys(consumers).filter((repo) => consumers[repo].lanes.some((lane) => lanes2.includes(lane))).sort();
44
+ const selected = Object.keys(consumers).filter((repo) => consumers[repo].lanes.some((lane2) => lanes2.includes(lane2))).sort();
42
45
  const order = [];
43
46
  const visit = (repo, trail) => {
44
47
  if (order.includes(repo)) return;
@@ -211,8 +214,8 @@ var candidateDirs = (root) => {
211
214
  };
212
215
  var discover = (root, manifest2, lanes2 = LANES) => {
213
216
  const wanted = /* @__PURE__ */ new Map();
214
- for (const lane of lanes2) {
215
- for (const name of Object.keys(lanePackages(manifest2, lane))) wanted.set(name, lane);
217
+ for (const lane2 of lanes2) {
218
+ for (const name of Object.keys(lanePackages(manifest2, lane2))) wanted.set(name, lane2);
216
219
  }
217
220
  const consumerRepos = new Set(consumersFor(manifest2, lanes2));
218
221
  const claims = /* @__PURE__ */ new Map();
@@ -328,11 +331,11 @@ var trainPlan = (root, manifest2, lanes2 = LANES) => {
328
331
  )
329
332
  );
330
333
  return {
331
- lanes: LANES.filter((lane) => lanes2.includes(lane)).map((lane) => {
332
- const names = new Set(Object.keys(lanePackages(manifest2, lane)));
333
- const own = checkouts.filter((checkout) => checkout.lane === lane);
334
+ lanes: LANES.filter((lane2) => lanes2.includes(lane2)).map((lane2) => {
335
+ const names = new Set(Object.keys(lanePackages(manifest2, lane2)));
336
+ const own = checkouts.filter((checkout) => checkout.lane === lane2);
334
337
  return {
335
- lane,
338
+ lane: lane2,
336
339
  checkouts: topoOrder(own),
337
340
  missing: [...names].filter(
338
341
  (name) => !own.some((checkout) => checkout.packages.some((entry) => entry.name === name)) && !ambiguous.some((entry) => entry.key === name)
@@ -729,8 +732,8 @@ function inspect(dir2, manifest2, presetOverride) {
729
732
  }
730
733
  }
731
734
  }
732
- const preset = detectPreset(pkg, presetOverride);
733
- const tsconfigTarget = `@inixiative/config/tsconfig/${preset}.json`;
735
+ const preset2 = detectPreset(pkg, presetOverride);
736
+ const tsconfigTarget = `@inixiative/config/tsconfig/${preset2}.json`;
734
737
  const tsconfigPath = join(dir2, "tsconfig.json");
735
738
  if (!existsSync(tsconfigPath)) {
736
739
  findings.push({
@@ -758,7 +761,7 @@ function inspect(dir2, manifest2, presetOverride) {
758
761
  });
759
762
  }
760
763
  }
761
- const biomeTargets = preset === "react" ? ["@inixiative/config/biome/base.json", "@inixiative/config/biome/react.json"] : ["@inixiative/config/biome/base.json"];
764
+ const biomeTargets = preset2 === "react" ? ["@inixiative/config/biome/base.json", "@inixiative/config/biome/react.json"] : ["@inixiative/config/biome/base.json"];
762
765
  const biomePath = join(dir2, "biome.json");
763
766
  if (!existsSync(biomePath)) {
764
767
  findings.push({
@@ -804,6 +807,58 @@ function inspect(dir2, manifest2, presetOverride) {
804
807
  };
805
808
  }
806
809
 
810
+ // src/args.ts
811
+ var COMMANDS = ["check", "sync", "scan", "train", "ports"];
812
+ var PRESETS = ["base", "node", "react"];
813
+ var preset = { type: "string" };
814
+ var lane = { type: "string" };
815
+ var flag = { type: "boolean", default: false };
816
+ var OPTIONS = {
817
+ check: { preset },
818
+ sync: { preset, force: flag, "no-install": flag },
819
+ scan: { lane },
820
+ train: { lane, push: flag, "dry-run": flag },
821
+ ports: {}
822
+ };
823
+ var UsageError = class extends Error {
824
+ };
825
+ var isCommand = (value) => COMMANDS.includes(value);
826
+ var parseCommandLine = (argv) => {
827
+ const command2 = argv[0];
828
+ if (!isCommand(command2)) throw new UsageError(`unknown command: ${command2 ?? "(none)"}`);
829
+ let values;
830
+ let positionals2;
831
+ try {
832
+ const options = OPTIONS[command2];
833
+ ({ values, positionals: positionals2 } = parseArgs({
834
+ args: argv.slice(1),
835
+ options,
836
+ allowPositionals: true,
837
+ strict: true
838
+ }));
839
+ } catch (error) {
840
+ throw new UsageError(`${command2}: ${error instanceof Error ? error.message : String(error)}`);
841
+ }
842
+ const presetValue = values.preset;
843
+ if (typeof presetValue === "string" && !PRESETS.includes(presetValue)) {
844
+ throw new UsageError(`unknown preset: ${presetValue}`);
845
+ }
846
+ const laneValue = values.lane;
847
+ if (typeof laneValue === "string" && !isLane(laneValue)) {
848
+ throw new UsageError(`unknown lane: ${laneValue}`);
849
+ }
850
+ return {
851
+ command: command2,
852
+ positionals: positionals2,
853
+ preset: typeof presetValue === "string" ? presetValue : void 0,
854
+ lane: typeof laneValue === "string" && isLane(laneValue) ? laneValue : void 0,
855
+ force: values.force === true,
856
+ noInstall: values["no-install"] === true,
857
+ push: values.push === true,
858
+ dryRun: values["dry-run"] === true
859
+ };
860
+ };
861
+
807
862
  // ports.json
808
863
  var ports_default = {
809
864
  $comment: "Local port registry. A project's port for a service is the service's default plus 100 times the project's block, so projects never collide and none uses a default. Add a project at the next free block; add a service with its usual default.",
@@ -847,32 +902,32 @@ function portsFor(project) {
847
902
  }
848
903
 
849
904
  // src/cli.ts
850
- var args = process.argv.slice(2);
851
- var command = args[0];
852
- var flags = new Set(args.filter((arg) => arg.startsWith("--")).map((arg) => arg.split("=")[0]));
853
- var presetFlag = args.find((arg) => arg.startsWith("--preset="))?.split("=")[1];
854
- var laneFlag = args.find((arg) => arg.startsWith("--lane="))?.split("=")[1];
855
- var positional = args.slice(1).filter((arg) => !arg.startsWith("--"));
856
- var dir = resolve2(positional[0] ?? ".");
857
- var usage = () => {
858
- console.log(
859
- "Usage: inixiative-config <check|sync|scan|train> [dir] [--preset=base|node|react] [--lane=primitives|agentic] [--force] [--no-install] [--push]\n inixiative-config ports [project]"
905
+ var commandLine;
906
+ try {
907
+ commandLine = parseCommandLine(process.argv.slice(2));
908
+ } catch (error) {
909
+ if (!(error instanceof UsageError)) throw error;
910
+ console.error(`\u2717 ${error.message}`);
911
+ console.error(
912
+ "Usage: inixiative-config check [dir] [--preset=base|node|react]\n inixiative-config sync [dir] [--preset=base|node|react] [--force] [--no-install]\n inixiative-config scan [root] [--lane=primitives|agentic]\n inixiative-config train [root] [--lane=primitives|agentic] [--push] [--dry-run]\n inixiative-config ports [project]"
860
913
  );
861
914
  process.exit(2);
862
- };
915
+ }
916
+ var { command, positionals, preset: presetFlag, dryRun } = commandLine;
917
+ var dir = resolve2(positionals[0] ?? ".");
863
918
  if (command === "ports") {
864
- const project = positional[0];
865
- if (project !== void 0 && !projects.includes(project)) usage();
919
+ const project = positionals[0];
920
+ if (project !== void 0 && !projects.includes(project)) {
921
+ console.error(`\u2717 unknown project: ${project} (${projects.join(", ")})`);
922
+ process.exit(2);
923
+ }
866
924
  for (const name of project ? [project] : projects)
867
925
  console.log(
868
926
  `${name}: ${Object.entries(portsFor(name)).map(([service, port]) => `${service}=${port}`).join(" ")}`
869
927
  );
870
928
  process.exit(0);
871
929
  }
872
- if (!["check", "sync", "scan", "train"].includes(command)) usage();
873
- if (presetFlag && !["base", "node", "react"].includes(presetFlag)) usage();
874
- if (laneFlag !== void 0 && !isLane(laneFlag)) usage();
875
- var lanes = laneFlag && isLane(laneFlag) ? [laneFlag] : [...LANES];
930
+ var lanes = commandLine.lane ? [commandLine.lane] : [...LANES];
876
931
  var manifest = loadManifest();
877
932
  var report = (findings) => {
878
933
  for (const finding of findings) {
@@ -931,13 +986,13 @@ if (command === "check") {
931
986
  var laneLabel = (checkout) => checkout.consumer ? `consumer of ${checkout.consumer.lanes.join(" + ")}` : `${checkout.lane} lane`;
932
987
  if (command === "scan") {
933
988
  const plan = trainPlan(dir, manifest, lanes);
934
- const checkouts = [...plan.lanes.flatMap((lane) => lane.checkouts), ...plan.consumers];
989
+ const checkouts = [...plan.lanes.flatMap((lane2) => lane2.checkouts), ...plan.consumers];
935
990
  if (checkouts.length === 0) {
936
991
  console.error(`\u2717 no ecosystem repos found under ${dir}`);
937
992
  process.exit(1);
938
993
  }
939
- const missing = [...plan.lanes.flatMap((lane) => lane.missing), ...plan.missingConsumers];
940
- const ambiguous = [...plan.lanes.flatMap((lane) => lane.ambiguous), ...plan.ambiguousConsumers];
994
+ const missing = [...plan.lanes.flatMap((lane2) => lane2.missing), ...plan.missingConsumers];
995
+ const ambiguous = [...plan.lanes.flatMap((lane2) => lane2.ambiguous), ...plan.ambiguousConsumers];
941
996
  const drifted = [];
942
997
  for (const checkout of checkouts) {
943
998
  console.log(`
@@ -973,13 +1028,24 @@ scanned ${checkouts.length} repos (${lanes.join(" + ")}): ${drifted.length === 0
973
1028
  var Abort = class extends Error {
974
1029
  };
975
1030
  if (command === "train") {
1031
+ const effect = (description, planned, run) => {
1032
+ if (!dryRun) return run();
1033
+ console.log(` [dry-run] would ${description}`);
1034
+ return planned;
1035
+ };
1036
+ if (dryRun) console.log("dry run: nothing is installed, written, committed, published or pushed");
976
1037
  git(packageRoot, "fetch", "--quiet");
977
1038
  if (behindOrigin(packageRoot) > 0) {
978
- console.error("\u2717 this config checkout is behind origin \u2014 its blessed set is stale; pull first");
979
- process.exit(1);
1039
+ if (!dryRun) {
1040
+ console.error(
1041
+ "\u2717 this config checkout is behind origin \u2014 its blessed set is stale; pull first"
1042
+ );
1043
+ process.exit(1);
1044
+ }
1045
+ console.log("\u26A0 this config checkout is behind origin \u2014 the plan uses a stale blessed set");
980
1046
  }
981
1047
  const plan = trainPlan(dir, manifest, lanes);
982
- if (plan.lanes.every((lane) => lane.checkouts.length === 0) && plan.consumers.length === 0) {
1048
+ if (plan.lanes.every((lane2) => lane2.checkouts.length === 0) && plan.consumers.length === 0) {
983
1049
  console.error(`\u2717 no ecosystem repos found under ${dir}`);
984
1050
  process.exit(1);
985
1051
  }
@@ -1017,30 +1083,39 @@ if (command === "train") {
1017
1083
  console.log("\u26A0 diverged from origin \u2014 reconcile first, skipping");
1018
1084
  return false;
1019
1085
  }
1020
- if (git(checkout.dir, "merge", "--ff-only", "@{upstream}").status !== 0) {
1086
+ const merged = effect(
1087
+ `fast-forward to origin (+${behind}); the plan below reflects the local tree`,
1088
+ true,
1089
+ () => git(checkout.dir, "merge", "--ff-only", "@{upstream}").status === 0
1090
+ );
1091
+ if (!merged) {
1021
1092
  console.log("\u26A0 fast-forward to origin failed \u2014 reconcile first, skipping");
1022
1093
  return false;
1023
1094
  }
1024
- console.log(`\u2193 fast-forwarded to origin (+${behind})`);
1025
- checkout.pkg = JSON.parse(readFileSync2(join2(checkout.dir, "package.json"), "utf8"));
1095
+ if (!dryRun) {
1096
+ console.log(`\u2193 fast-forwarded to origin (+${behind})`);
1097
+ checkout.pkg = JSON.parse(readFileSync2(join2(checkout.dir, "package.json"), "utf8"));
1098
+ }
1026
1099
  }
1027
1100
  return true;
1028
1101
  };
1029
1102
  const bumpRanges = (checkout) => {
1030
1103
  const inspection = inspect(checkout.dir, manifest);
1031
1104
  const bumps = inspection.findings.filter((finding) => finding.kind === "ecosystem-range");
1032
- for (const bump of bumps) {
1033
- console.log(` ${bump.message}`);
1034
- bump.fix?.();
1105
+ for (const bump of bumps) console.log(` ${bump.message}`);
1106
+ if (bumps.length > 0) {
1107
+ effect(`rewrite ${bumps.length} ecosystem range(s) to the blessed set`, void 0, () => {
1108
+ for (const bump of bumps) bump.fix?.();
1109
+ inspection.flush();
1110
+ });
1035
1111
  }
1036
- inspection.flush();
1037
1112
  return {
1038
1113
  inspection,
1039
1114
  bumped: bumps.length > 0,
1040
1115
  staleLock: inspection.findings.some((finding) => finding.kind === "stale-lock")
1041
1116
  };
1042
1117
  };
1043
- const relockAndCheck = (checkout, check = true) => {
1118
+ const relockAndCheck = (checkout, check = true) => effect(`re-lock ${checkout.name}${check ? " and run its check" : ""}`, void 0, () => {
1044
1119
  if (spawnSync2("bun", ["install"], { cwd: checkout.dir, stdio: "inherit" }).status !== 0) {
1045
1120
  throw new Abort(`bun install failed in ${checkout.name}`);
1046
1121
  }
@@ -1053,23 +1128,38 @@ if (command === "train") {
1053
1128
  if (check && spawnSync2("bun", ["run", "check"], { cwd: checkout.dir, stdio: "inherit" }).status !== 0) {
1054
1129
  throw new Abort(`check failed in ${checkout.name}`);
1055
1130
  }
1056
- };
1131
+ });
1057
1132
  const followers = [];
1058
- const commitOwned = (checkout, packagePaths) => {
1059
- const tracked = [...packagePaths, "bun.lock"];
1060
- const mutated = git(checkout.dir, "status", "--porcelain", "--", ...tracked);
1061
- if (mutated.status !== 0 || mutated.stdout.trim().length === 0) return false;
1062
- if (git(checkout.dir, "add", "--", ...tracked).status !== 0) {
1063
- throw new Abort(`git add failed in ${checkout.name} \u2014 is bun.lock ignored?`);
1064
- }
1065
- const commit = spawnSync2("git", ["commit", "-m", "chore: sync ecosystem deps to blessed set"], {
1066
- cwd: checkout.dir,
1067
- stdio: "inherit"
1133
+ const commitOwned = (checkout, packagePaths, changes) => {
1134
+ if (dryRun && !changes) return false;
1135
+ return effect(`commit its package.json files + bun.lock in ${checkout.name}`, true, () => {
1136
+ const tracked = [...packagePaths, "bun.lock"];
1137
+ const mutated = git(checkout.dir, "status", "--porcelain", "--", ...tracked);
1138
+ if (mutated.status !== 0 || mutated.stdout.trim().length === 0) return false;
1139
+ if (git(checkout.dir, "add", "--", ...tracked).status !== 0) {
1140
+ throw new Abort(`git add failed in ${checkout.name} \u2014 is bun.lock ignored?`);
1141
+ }
1142
+ const commit = spawnSync2(
1143
+ "git",
1144
+ ["commit", "-m", "chore: sync ecosystem deps to blessed set"],
1145
+ {
1146
+ cwd: checkout.dir,
1147
+ stdio: "inherit"
1148
+ }
1149
+ );
1150
+ if (commit.status !== 0) throw new Abort(`commit failed in ${checkout.name}`);
1151
+ return true;
1068
1152
  });
1069
- if (commit.status !== 0) throw new Abort(`commit failed in ${checkout.name}`);
1070
- return true;
1071
1153
  };
1072
- const publish = (checkout, entry) => {
1154
+ const publish = (checkout, entry, version, remote) => effect(`publish ${entry.name}@${version} to npm (npm has ${remote ?? "none"})`, true, () => {
1155
+ console.log(`publishing ${entry.name}@${version} (npm has ${remote ?? "none"})`);
1156
+ if (!publishToNpm(checkout, entry)) throw new Abort(`publish failed for ${entry.name}`);
1157
+ if (!servedByRegistry(entry.name, version)) {
1158
+ throw new Abort(`npm accepted ${entry.name}@${version} but the registry does not serve it`);
1159
+ }
1160
+ return true;
1161
+ });
1162
+ const publishToNpm = (checkout, entry) => {
1073
1163
  if (entry.path === "package.json") {
1074
1164
  return spawnSync2("npm", ["publish"], { cwd: checkout.dir, stdio: "inherit" }).status === 0;
1075
1165
  }
@@ -1091,12 +1181,12 @@ if (command === "train") {
1091
1181
  const remote = spawnSync2("npm", ["view", `${name}@latest`, "version"], { encoding: "utf8" });
1092
1182
  return remote.status === 0 ? remote.stdout.trim() : null;
1093
1183
  };
1094
- for (const { lane, checkouts, missing, ambiguous } of plan.lanes) {
1184
+ for (const { lane: lane2, checkouts, missing, ambiguous } of plan.lanes) {
1095
1185
  console.log(
1096
1186
  `
1097
- \u2550 ${lane} lane: ${checkouts.map((checkout) => checkout.name).join(" \u2192 ") || "(none)"}`
1187
+ \u2550 ${lane2} lane: ${checkouts.map((checkout) => checkout.name).join(" \u2192 ") || "(none)"}`
1098
1188
  );
1099
- const section = laneSection(manifest, lane);
1189
+ const section = laneSection(manifest, lane2);
1100
1190
  const before = { ...section };
1101
1191
  const laneBlessed = [];
1102
1192
  try {
@@ -1133,19 +1223,13 @@ if (command === "train") {
1133
1223
  skipped.push(checkout.name);
1134
1224
  continue;
1135
1225
  }
1136
- if (bumped || staleLock || releases.some((release) => release.ahead))
1137
- relockAndCheck(checkout);
1138
- if (commitOwned(checkout, inspection.packagePaths)) committed.add(checkout.dir);
1226
+ const relocked = bumped || staleLock || releases.some((release) => release.ahead);
1227
+ if (relocked) relockAndCheck(checkout);
1228
+ if (commitOwned(checkout, inspection.packagePaths, relocked)) committed.add(checkout.dir);
1139
1229
  followers.push(checkout);
1140
1230
  for (const { entry, local, remote, ahead } of releases) {
1141
1231
  if (ahead) {
1142
- console.log(`publishing ${entry.name}@${local} (npm has ${remote ?? "none"})`);
1143
- if (!publish(checkout, entry)) throw new Abort(`publish failed for ${entry.name}`);
1144
- if (!servedByRegistry(entry.name, local)) {
1145
- throw new Abort(
1146
- `npm accepted ${entry.name}@${local} but the registry does not serve it`
1147
- );
1148
- }
1232
+ publish(checkout, entry, local, remote);
1149
1233
  } else if (section[entry.name] === local) {
1150
1234
  console.log(`\u2713 ${entry.name}@${local} already on npm and blessed`);
1151
1235
  continue;
@@ -1157,19 +1241,23 @@ if (command === "train") {
1157
1241
  }
1158
1242
  }
1159
1243
  if (laneBlessed.length > 0) {
1160
- writeManifest(manifest);
1244
+ effect(
1245
+ `bless ${laneBlessed.join(", ")} in versions.json`,
1246
+ void 0,
1247
+ () => writeManifest(manifest)
1248
+ );
1161
1249
  bomDirty = true;
1162
1250
  blessed.push(...laneBlessed);
1163
1251
  }
1164
1252
  console.log(
1165
- `${lane} lane blessed: ${laneBlessed.length === 0 ? "nothing new" : laneBlessed.join(", ")}`
1253
+ `${lane2} lane blessed: ${laneBlessed.length === 0 ? "nothing new" : laneBlessed.join(", ")}`
1166
1254
  );
1167
1255
  } catch (error) {
1168
1256
  if (!(error instanceof Abort)) throw error;
1169
1257
  for (const name of Object.keys(section)) delete section[name];
1170
1258
  Object.assign(section, before);
1171
- failed.push(`${lane} lane`);
1172
- console.error(`\u2717 ${error.message} \u2014 ${lane} lane stopped; its BOM entries are unchanged`);
1259
+ failed.push(`${lane2} lane`);
1260
+ console.error(`\u2717 ${error.message} \u2014 ${lane2} lane stopped; its BOM entries are unchanged`);
1173
1261
  if (laneBlessed.length > 0) {
1174
1262
  console.error(
1175
1263
  ` published but not blessed: ${laneBlessed.join(", ")} \u2014 the next train blesses them`
@@ -1210,13 +1298,18 @@ if (command === "train") {
1210
1298
  checkout.dir,
1211
1299
  `train/ecosystem-sync-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`
1212
1300
  );
1213
- if (git(checkout.dir, "switch", "-c", branch).status !== 0) {
1214
- throw new Abort(`could not create ${branch} in ${checkout.name}`);
1215
- }
1301
+ const switched = effect(
1302
+ `create review branch ${branch} in ${checkout.name}`,
1303
+ true,
1304
+ () => git(checkout.dir, "switch", "-c", branch).status === 0
1305
+ );
1306
+ if (!switched) throw new Abort(`could not create ${branch} in ${checkout.name}`);
1216
1307
  followers.push(checkout);
1217
- if (commitOwned(checkout, inspection.packagePaths)) {
1308
+ if (commitOwned(checkout, inspection.packagePaths, true)) {
1218
1309
  reviewBranches.push({ dir: checkout.dir, branch, base });
1219
- console.log(`committed on ${branch} for review; the checkout stays on it until merged`);
1310
+ if (!dryRun) {
1311
+ console.log(`committed on ${branch} for review; the checkout stays on it until merged`);
1312
+ }
1220
1313
  }
1221
1314
  } catch (error) {
1222
1315
  if (!(error instanceof Abort)) throw error;
@@ -1233,52 +1326,73 @@ blessed: ${blessed.length === 0 ? "nothing" : blessed.join(", ")}`);
1233
1326
  let version = ownVersion();
1234
1327
  if (remoteVersion !== null && compare(version, remoteVersion) <= 0) {
1235
1328
  version = bumpPatch(remoteVersion);
1236
- const pkgPath = join2(packageRoot, "package.json");
1237
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
1238
- pkg.version = version;
1239
- writeFileSync2(pkgPath, `${JSON.stringify(pkg, null, 2)}
1329
+ const bumped = version;
1330
+ effect(`bump @inixiative/config to ${bumped} in package.json`, void 0, () => {
1331
+ const pkgPath = join2(packageRoot, "package.json");
1332
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
1333
+ pkg.version = bumped;
1334
+ writeFileSync2(pkgPath, `${JSON.stringify(pkg, null, 2)}
1240
1335
  `);
1336
+ });
1241
1337
  }
1242
1338
  manifest.ecosystem["@inixiative/config"] = version;
1243
- writeManifest(manifest);
1244
- for (const fixture of fixtureDirs()) {
1245
- const inspection = inspect(fixture, manifest);
1246
- for (const finding of inspection.findings)
1247
- if (finding.kind === "ecosystem-range") finding.fix?.();
1248
- inspection.flush();
1249
- }
1250
- const check = spawnSync2("bun", ["run", "check"], { cwd: packageRoot, stdio: "inherit" });
1251
- if (check.status !== 0) {
1252
- console.error("\u2717 check failed in @inixiative/config \u2014 aborting before publish");
1253
- process.exit(1);
1254
- }
1255
- spawnSync2("git", ["add", "--", "package.json", "versions.json", "test/fixtures"], {
1256
- cwd: packageRoot
1339
+ effect(
1340
+ `bless @inixiative/config@${version} in versions.json and move test/fixtures onto the blessed set`,
1341
+ void 0,
1342
+ () => {
1343
+ writeManifest(manifest);
1344
+ for (const fixture of fixtureDirs()) {
1345
+ const inspection = inspect(fixture, manifest);
1346
+ for (const finding of inspection.findings)
1347
+ if (finding.kind === "ecosystem-range") finding.fix?.();
1348
+ inspection.flush();
1349
+ }
1350
+ }
1351
+ );
1352
+ effect("run bun run check in @inixiative/config", void 0, () => {
1353
+ const check = spawnSync2("bun", ["run", "check"], { cwd: packageRoot, stdio: "inherit" });
1354
+ if (check.status !== 0) {
1355
+ console.error("\u2717 check failed in @inixiative/config \u2014 aborting before publish");
1356
+ process.exit(1);
1357
+ }
1257
1358
  });
1258
- const commit = spawnSync2(
1259
- "git",
1260
- ["commit", "-m", `chore: bless ${blessed.join(", ")} \u2014 ${version}`],
1261
- { cwd: packageRoot, stdio: "inherit" }
1359
+ effect(
1360
+ `commit "chore: bless ${blessed.join(", ")} \u2014 ${version}" in @inixiative/config`,
1361
+ void 0,
1362
+ () => {
1363
+ spawnSync2("git", ["add", "--", "package.json", "versions.json", "test/fixtures"], {
1364
+ cwd: packageRoot
1365
+ });
1366
+ const commit = spawnSync2(
1367
+ "git",
1368
+ ["commit", "-m", `chore: bless ${blessed.join(", ")} \u2014 ${version}`],
1369
+ { cwd: packageRoot, stdio: "inherit" }
1370
+ );
1371
+ if (commit.status !== 0) {
1372
+ console.error("\u2717 commit failed in @inixiative/config \u2014 aborting before publish");
1373
+ process.exit(1);
1374
+ }
1375
+ }
1262
1376
  );
1263
- if (commit.status !== 0) {
1264
- console.error("\u2717 commit failed in @inixiative/config \u2014 aborting before publish");
1265
- process.exit(1);
1266
- }
1267
- const publish2 = spawnSync2("npm", ["publish"], { cwd: packageRoot, stdio: "inherit" });
1268
- if (publish2.status !== 0 || !servedByRegistry("@inixiative/config", version)) {
1269
- console.error("\u2717 publish failed for @inixiative/config");
1270
- process.exit(1);
1271
- }
1377
+ effect(`publish @inixiative/config@${version} to npm`, void 0, () => {
1378
+ const publish2 = spawnSync2("npm", ["publish"], { cwd: packageRoot, stdio: "inherit" });
1379
+ if (publish2.status !== 0 || !servedByRegistry("@inixiative/config", version)) {
1380
+ console.error("\u2717 publish failed for @inixiative/config");
1381
+ process.exit(1);
1382
+ }
1383
+ console.log(`published: @inixiative/config@${version} \u2014 the BOM names the new state`);
1384
+ });
1272
1385
  committed.add(packageRoot);
1273
- console.log(`published: @inixiative/config@${version} \u2014 the BOM names the new state`);
1386
+ if (followers.length > 0) console.log(`
1387
+ \u2550 re-lock onto @inixiative/config@${version}`);
1274
1388
  for (const checkout of followers) {
1275
1389
  try {
1276
1390
  const { inspection, bumped, staleLock } = bumpRanges(checkout);
1277
1391
  if (!bumped && !staleLock) continue;
1278
1392
  relockAndCheck(checkout, false);
1279
- if (commitOwned(checkout, inspection.packagePaths) && !checkout.consumer)
1393
+ if (commitOwned(checkout, inspection.packagePaths, true) && !checkout.consumer)
1280
1394
  committed.add(checkout.dir);
1281
- console.log(`\u2713 ${checkout.name} locked to @inixiative/config@${version}`);
1395
+ if (!dryRun) console.log(`\u2713 ${checkout.name} locked to @inixiative/config@${version}`);
1282
1396
  } catch (error) {
1283
1397
  if (!(error instanceof Abort)) throw error;
1284
1398
  failed.push(checkout.name);
@@ -1286,7 +1400,14 @@ blessed: ${blessed.length === 0 ? "nothing" : blessed.join(", ")}`);
1286
1400
  }
1287
1401
  }
1288
1402
  }
1289
- if (flags.has("--push")) {
1403
+ if (dryRun) {
1404
+ console.log(
1405
+ `
1406
+ dry run complete \u2014 a real train would commit in ${committed.size} repo(s) and open ${reviewBranches.length} review branch(es)${commandLine.push ? ", then push them" : ""}:`
1407
+ );
1408
+ for (const repoDir of committed) console.log(` ${repoDir}`);
1409
+ for (const { dir: repoDir, branch } of reviewBranches) console.log(` ${repoDir} (${branch})`);
1410
+ } else if (commandLine.push) {
1290
1411
  for (const repoDir of committed) {
1291
1412
  if (spawnSync2("git", ["push"], { cwd: repoDir, stdio: "inherit" }).status !== 0) {
1292
1413
  console.error(`\u2717 push failed in ${repoDir}`);
@@ -1345,7 +1466,7 @@ function fixtureDirs() {
1345
1466
  if (!existsSync2(root)) return [];
1346
1467
  return readdirSync2(root).map((name) => join2(root, name)).filter((dir2) => existsSync2(join2(dir2, "package.json")));
1347
1468
  }
1348
- if (existsSync2(join2(dir, ".git")) && !flags.has("--force")) {
1469
+ if (existsSync2(join2(dir, ".git")) && !commandLine.force) {
1349
1470
  const status = spawnSync2("git", ["status", "--porcelain"], { cwd: dir, encoding: "utf8" });
1350
1471
  if (status.status === 0 && status.stdout.trim().length > 0) {
1351
1472
  console.error("\u2717 working tree dirty \u2014 commit first or pass --force");
@@ -1357,7 +1478,7 @@ for (const finding of first.findings) finding.fix?.();
1357
1478
  first.flush();
1358
1479
  var fixed = first.findings.filter((finding) => finding.fix).length;
1359
1480
  if (fixed > 0) console.log(`applied ${fixed} fix${fixed === 1 ? "" : "es"}`);
1360
- if (!flags.has("--no-install")) {
1481
+ if (!commandLine.noInstall) {
1361
1482
  const relinked = new Set(
1362
1483
  first.findings.flatMap(
1363
1484
  (finding) => finding.kind === "member-toolchain" && finding.path ? [dirname2(finding.path)] : []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inixiative/config",
3
- "version": "0.5.61",
3
+ "version": "0.5.63",
4
4
  "description": "Shared toolchain for the inixiative ecosystem: tsconfig/biome/tsup presets, a version manifest (BOM), and a sync/check CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/versions.json CHANGED
@@ -13,10 +13,10 @@
13
13
  "@types/bun"
14
14
  ],
15
15
  "ecosystem": {
16
- "@inixiative/config": "0.5.61",
17
- "@inixiative/json-rules": "3.4.0",
16
+ "@inixiative/config": "0.5.63",
17
+ "@inixiative/json-rules": "3.4.1",
18
18
  "@inixiative/permissions": "0.4.0",
19
- "@inixiative/transitions": "0.3.0",
19
+ "@inixiative/transitions": "0.4.0",
20
20
  "@inixiative/rules-builder": "0.30.0",
21
21
  "@inixiative/prisma-map": "0.2.0",
22
22
  "@inixiative/atlas": "0.2.0",
@@ -24,10 +24,10 @@
24
24
  },
25
25
  "agentic": {
26
26
  "@inixiative/signet": "0.3.0",
27
- "@inixiative/archive": "0.9.0",
28
- "@inixiative/agent-session": "0.3.1",
29
- "@inixiative/foundry-core": "0.3.0",
30
- "@inixiative/foundry": "0.3.0"
27
+ "@inixiative/archive": "0.10.0",
28
+ "@inixiative/agent-session": "0.4.0",
29
+ "@inixiative/foundry-core": "0.4.0",
30
+ "@inixiative/foundry": "0.4.0"
31
31
  },
32
32
  "consumers": {
33
33
  "inixiative/template": {