@inixiative/config 0.5.45 → 0.5.46

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
@@ -71,11 +71,11 @@ bunx @inixiative/config ports [project]
71
71
 
72
72
  `ports` prints each project's local ports from `ports.json` (also exported as `@inixiative/config/ports.json`). A project's port for a service is the service's usual default plus 100 times the project's block, so no two projects collide and none sits on a default: Archive (block 7) serves HTTP on 4700 with Postgres on 6132; Kingdom (block 2) has web 3200, API 8200, Postgres 5632, Redis 6579. Reserved ports (macOS AirPlay on 5000 and 7000) are never assigned. Give a new project the next free block, and a new kind of service its usual default; the tests fail on any collision.
73
73
 
74
- `check` is read-only and exits non-zero on drift — run it in CI after a frozen-lockfile install. It verifies toolchain pins — declared once, at the root, pinned exactly: a workspace member declaring one is an error, and `bun.lock` must resolve each only to its pin (no second copy) — toolchain peer ranges (they must admit the pin and no older major; `sync` writes `>=<pin major>.<pin minor>.0`), `"latest"` ranges, `packageManager`/`.bun-version`, legacy `bun.lockb`, a committed (tracked, un-ignored) `bun.lock`, required scripts (`check`/`typecheck`/`lint`/`test`), lefthook (dep + `lefthook.yml` extending the shared hooks + `prepare` script; git repos only), stub `extends`, presence of this package, and for every ecosystem dependency — in the root or any workspace member, of either lane — that the declared range admits the blessed version and the lockfile actually resolves to it (the stale-lockfile class). An npm alias (`"@inixiative/session-archive": "npm:@inixiative/archive@^0.2.1"`) is held to its target's blessed version and rewritten as an alias; `workspace:`, `file:` and `link:` ranges are a repo linking itself and are left alone.
74
+ `check` is read-only and exits non-zero on drift — run it in CI after a frozen-lockfile install. It verifies toolchain pins — declared once, at the root, pinned exactly: a workspace member declaring one is an error, and `bun.lock` must resolve each only to its pin (no second copy) — toolchain peer ranges (they must admit the pin and no older major; `sync` writes `>=<pin major>.<pin minor>.0`), `"latest"` ranges, `packageManager`/`.bun-version`, legacy `bun.lockb`, a committed (tracked, un-ignored) `bun.lock`, required scripts (`check`/`typecheck`/`lint`/`test`), lefthook (dep + `lefthook.yml` extending the shared hooks + `prepare` script; git repos only), stub `extends`, presence of this package, and for every ecosystem dependency — in the root or any workspace member, of either lane — that the declared range admits the blessed version and the lockfile actually resolves to it (the stale-lockfile class). A range that does not admit the blessed version is rewritten to `^<blessed>`, except an exact pin, which stays exact and moves to the blessed version. An npm alias (`"@inixiative/session-archive": "npm:@inixiative/archive@^0.2.1"`) is held to its target's blessed version and rewritten as an alias; `workspace:`, `file:` and `link:` ranges are a repo linking itself and are left alone.
75
75
 
76
76
  `sync` applies every fix (deleting member toolchain entries and their stale member links), re-locks via `bun install` (`--force` when a second toolchain copy survives), and runs `bun update` on stale ecosystem entries in the directory that declares them. It refuses to run on a dirty working tree without `--force`. The react preset is inferred from a `react` dependency; override with `--preset`. Missing scripts are filled with defaults; existing script bodies are never touched.
77
77
 
78
- `scan` runs `check` across every ecosystem checkout under a root directory, for every lane or just the one `--lane` names. Targets are derived from the BOM: lane packages match by package name — the root package or any workspace member, so a monorepo such as foundry is found by the packages it publishes — and consumers match by their `origin` GitHub repo. Directory names and local layout never need declaring: scan looks at each child of the root and one level into a child without a `package.json` (a workspace folder such as `kingdom-workspace/`). Linked git worktrees are other sessions' branches and never targets; when several clones claim one package or repo, only those with an `origin` remote count, and if that still leaves more than one the target is reported ambiguous rather than guessed. Origin is the truth, not the checkout: scan fetches per repo and fails on checkouts behind origin (their findings describe a stale tree), fails when a BOM entry has no checkout at all, warns when a checkout sits on a non-default branch (its findings describe the branch, not what ships), and surfaces unmerged `claude/*` session branches.
78
+ `scan` runs `check` across every ecosystem checkout under a root directory, for every lane or just the one `--lane` names. Targets are derived from the BOM: lane packages match by package name — the root package or any workspace member, so a monorepo such as foundry is found by the packages it publishes — and consumers match by their `origin` GitHub repo. Directory names and local layout never need declaring: scan looks at each child of the root and one level into a child without a `package.json` (a workspace folder such as `kingdom-workspace/`). Linked git worktrees are other sessions' branches and never targets; when several clones claim one package or repo, only those with an `origin` remote count, a clone directly under the root wins over one nested in a workspace folder, and if that still leaves more than one the target is reported ambiguous rather than guessed. Origin is the truth, not the checkout: scan fetches per repo and fails on checkouts behind origin (their findings describe a stale tree), fails when a BOM entry has no checkout at all, warns when a checkout sits on a non-default branch (its findings describe the branch, not what ships), and surfaces unmerged `claude/*` session branches.
79
79
 
80
80
  `train` is the release cascade, run from this repo's checkout. It walks every lane in order (primitives, then agentic), or only the lane `--lane` names. Origin is the truth: it refuses to run from a config checkout behind origin, and it fast-forwards clean checkouts to origin before working (dirty or diverged repos still skip). It only ships the default branch: a checkout on a feature branch is skipped whole — never re-locked, committed to, or published from.
81
81
 
@@ -83,7 +83,7 @@ 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. 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.
87
87
 
88
88
  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
89
 
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { spawnSync as spawnSync2 } from "child_process";
5
5
  import { existsSync as existsSync2, mkdtempSync, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
6
6
  import { tmpdir } from "os";
7
- import { dirname as dirname2, join as join2, resolve } from "path";
7
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
8
8
 
9
9
  // src/lib.ts
10
10
  import { spawnSync } from "child_process";
@@ -17,7 +17,7 @@ import {
17
17
  statSync,
18
18
  writeFileSync
19
19
  } from "fs";
20
- import { dirname, join, sep } from "path";
20
+ import { dirname, join, resolve, sep } from "path";
21
21
  import { fileURLToPath } from "url";
22
22
  var packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
23
23
  var LANES = ["primitives", "agentic"];
@@ -250,7 +250,9 @@ var discover = (root, manifest2, lanes2 = LANES) => {
250
250
  const chosen = /* @__PURE__ */ new Map();
251
251
  const ambiguous = [];
252
252
  for (const [key, candidates] of claims) {
253
- const pool = candidates.length > 1 ? candidates.filter((candidate) => originRepo(candidate.dir)) : candidates;
253
+ let pool = candidates.length > 1 ? candidates.filter((candidate) => originRepo(candidate.dir)) : candidates;
254
+ const direct = pool.filter((candidate) => dirname(candidate.dir) === resolve(root));
255
+ if (pool.length > 1 && direct.length === 1) pool = direct;
254
256
  if (pool.length === 1) chosen.set(pool[0].dir, pool[0]);
255
257
  else ambiguous.push({ key, dirs: candidates.map((candidate) => candidate.dir).sort() });
256
258
  }
@@ -673,13 +675,14 @@ function inspect(dir2, manifest2, presetOverride) {
673
675
  const rewrite = (next) => name === key ? next : `npm:${name}@${next}`;
674
676
  const ok = admits(range, blessed);
675
677
  if (ok === false) {
678
+ const next = /^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(range) ? blessed : `^${blessed}`;
676
679
  findings.push({
677
680
  level: "error",
678
681
  kind: "ecosystem-range",
679
682
  name,
680
- message: `${target.label}${key} ${field} range ${spec} does not admit blessed ${blessed} \u2192 ${rewrite(`^${blessed}`)}`,
683
+ message: `${target.label}${key} ${field} range ${spec} does not admit blessed ${blessed} \u2192 ${rewrite(next)}`,
681
684
  fix: () => {
682
- deps[key] = rewrite(`^${blessed}`);
685
+ deps[key] = rewrite(next);
683
686
  target.mark();
684
687
  }
685
688
  });
@@ -838,7 +841,7 @@ var flags = new Set(args.filter((arg) => arg.startsWith("--")).map((arg) => arg.
838
841
  var presetFlag = args.find((arg) => arg.startsWith("--preset="))?.split("=")[1];
839
842
  var laneFlag = args.find((arg) => arg.startsWith("--lane="))?.split("=")[1];
840
843
  var positional = args.slice(1).filter((arg) => !arg.startsWith("--"));
841
- var dir = resolve(positional[0] ?? ".");
844
+ var dir = resolve2(positional[0] ?? ".");
842
845
  var usage = () => {
843
846
  console.log(
844
847
  "Usage: inixiative-config <check|sync|scan|train> [dir] [--preset=base|node|react] [--lane=primitives|agentic] [--force] [--no-install] [--push]\n inixiative-config ports [project]"
@@ -1025,7 +1028,7 @@ if (command === "train") {
1025
1028
  staleLock: inspection.findings.some((finding) => finding.kind === "stale-lock")
1026
1029
  };
1027
1030
  };
1028
- const relockAndCheck = (checkout) => {
1031
+ const relockAndCheck = (checkout, check = true) => {
1029
1032
  if (spawnSync2("bun", ["install"], { cwd: checkout.dir, stdio: "inherit" }).status !== 0) {
1030
1033
  throw new Abort(`bun install failed in ${checkout.name}`);
1031
1034
  }
@@ -1035,10 +1038,11 @@ if (command === "train") {
1035
1038
  )) {
1036
1039
  spawnSync2("bun", ["update", ...names], { cwd, stdio: "inherit" });
1037
1040
  }
1038
- if (spawnSync2("bun", ["run", "check"], { cwd: checkout.dir, stdio: "inherit" }).status !== 0) {
1041
+ if (check && spawnSync2("bun", ["run", "check"], { cwd: checkout.dir, stdio: "inherit" }).status !== 0) {
1039
1042
  throw new Abort(`check failed in ${checkout.name}`);
1040
1043
  }
1041
1044
  };
1045
+ const followers = [];
1042
1046
  const commitOwned = (checkout, packagePaths) => {
1043
1047
  const tracked = [...packagePaths, "bun.lock"];
1044
1048
  const mutated = git(checkout.dir, "status", "--porcelain", "--", ...tracked);
@@ -1120,6 +1124,7 @@ if (command === "train") {
1120
1124
  if (bumped || staleLock || releases.some((release) => release.ahead))
1121
1125
  relockAndCheck(checkout);
1122
1126
  if (commitOwned(checkout, inspection.packagePaths)) committed.add(checkout.dir);
1127
+ followers.push(checkout);
1123
1128
  for (const { entry, local, remote, ahead } of releases) {
1124
1129
  if (ahead) {
1125
1130
  console.log(`publishing ${entry.name}@${local} (npm has ${remote ?? "none"})`);
@@ -1196,6 +1201,7 @@ if (command === "train") {
1196
1201
  if (git(checkout.dir, "switch", "-c", branch).status !== 0) {
1197
1202
  throw new Abort(`could not create ${branch} in ${checkout.name}`);
1198
1203
  }
1204
+ followers.push(checkout);
1199
1205
  if (commitOwned(checkout, inspection.packagePaths)) {
1200
1206
  reviewBranches.push({ dir: checkout.dir, branch, base });
1201
1207
  console.log(`committed on ${branch} for review; the checkout stays on it until merged`);
@@ -1253,6 +1259,20 @@ blessed: ${blessed.length === 0 ? "nothing" : blessed.join(", ")}`);
1253
1259
  }
1254
1260
  committed.add(packageRoot);
1255
1261
  console.log(`published: @inixiative/config@${version} \u2014 the BOM names the new state`);
1262
+ for (const checkout of followers) {
1263
+ try {
1264
+ const { inspection, bumped, staleLock } = bumpRanges(checkout);
1265
+ if (!bumped && !staleLock) continue;
1266
+ relockAndCheck(checkout, false);
1267
+ if (commitOwned(checkout, inspection.packagePaths) && !checkout.consumer)
1268
+ committed.add(checkout.dir);
1269
+ console.log(`\u2713 ${checkout.name} locked to @inixiative/config@${version}`);
1270
+ } catch (error) {
1271
+ if (!(error instanceof Abort)) throw error;
1272
+ failed.push(checkout.name);
1273
+ console.error(`\u2717 ${error.message} \u2014 re-lock onto @inixiative/config@${version} by hand`);
1274
+ }
1275
+ }
1256
1276
  }
1257
1277
  if (flags.has("--push")) {
1258
1278
  for (const repoDir of committed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inixiative/config",
3
- "version": "0.5.45",
3
+ "version": "0.5.46",
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,7 +13,7 @@
13
13
  "@types/bun"
14
14
  ],
15
15
  "ecosystem": {
16
- "@inixiative/config": "0.5.45",
16
+ "@inixiative/config": "0.5.46",
17
17
  "@inixiative/json-rules": "2.26.0",
18
18
  "@inixiative/permissions": "0.3.3",
19
19
  "@inixiative/transitions": "0.1.0",
@@ -25,8 +25,8 @@
25
25
  "agentic": {
26
26
  "@inixiative/archive": "0.7.0",
27
27
  "@inixiative/agent-session": "0.3.1",
28
- "@inixiative/foundry-core": "0.2.0",
29
- "@inixiative/foundry": "0.2.0"
28
+ "@inixiative/foundry-core": "0.3.0",
29
+ "@inixiative/foundry": "0.3.0"
30
30
  },
31
31
  "consumers": {
32
32
  "inixiative/template": {