@inixiative/config 0.5.184 → 0.5.186
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 +44 -22
- package/dist/cli.js +310 -63
- package/package.json +2 -2
- package/versions.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @inixiative/config
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> One of the [inixiative](https://www.inixiative.com) building blocks. Stop rediscovering what you need to build. See [all the projects](https://github.com/inixiative).
|
|
4
|
+
|
|
5
|
+
The shared toolchain and theme for the inixiative packages: tsconfig, Biome and tsup presets, the brand theme CSS, a version manifest (BOM), and a CLI. One `@inixiative/config` version names one set of `@inixiative/*` versions released and checked together. `check` fails CI when a repo drifts from that set (toolchain pins, ranges, a lockfile that resolves to another version), `sync` fixes the drift, and `train` publishes the packages in dependency order.
|
|
4
6
|
|
|
5
7
|
## Presets
|
|
6
8
|
|
|
@@ -37,16 +39,16 @@ Apps load the theme from this package at runtime, so it is a runtime `dependency
|
|
|
37
39
|
|
|
38
40
|
## versions.json (the BOM)
|
|
39
41
|
|
|
40
|
-
One version of `@inixiative/config` names one coherent
|
|
42
|
+
One version of `@inixiative/config` names one coherent state of the inixiative packages:
|
|
41
43
|
|
|
42
|
-
- `bun
|
|
43
|
-
- `toolchain
|
|
44
|
-
- `required
|
|
45
|
-
- `ecosystem
|
|
46
|
-
- `agentic
|
|
47
|
-
- `consumers
|
|
48
|
-
- `candidates
|
|
49
|
-
- `holds
|
|
44
|
+
- `bun`: blessed runtime, written to `.bun-version` and `packageManager`
|
|
45
|
+
- `toolchain`: exact pins for `@biomejs/biome`, `typescript`, `tsup`, `@types/bun`
|
|
46
|
+
- `required`: toolchain packages every repo must carry
|
|
47
|
+
- `ecosystem`: the primitives lane's blessed, mutually-verified `@inixiative/*` set, plus this package
|
|
48
|
+
- `agentic`: the agentic lane's blessed set
|
|
49
|
+
- `consumers`: private repos that follow the blessed sets but never publish, keyed by GitHub `owner/repo`: `lanes` names the lanes each consumes, `upstream` the repo it syncs from
|
|
50
|
+
- `candidates`: published lane versions newer than their blessed one, waiting for every dependent to adopt them (see `train`); absent when there are none
|
|
51
|
+
- `holds`: lane packages kept below a version, keyed by package (see Holds below); absent when there are none
|
|
50
52
|
|
|
51
53
|
```jsonc
|
|
52
54
|
"consumers": {
|
|
@@ -71,29 +73,49 @@ bunx @inixiative/config sync [dir] [--preset=...] [--force] [--no-install]
|
|
|
71
73
|
bunx @inixiative/config scan [root] [--lane=primitives|agentic]
|
|
72
74
|
bunx @inixiative/config train [root] [--lane=primitives|agentic] [--push] [--dry-run]
|
|
73
75
|
bunx @inixiative/config ports [project]
|
|
76
|
+
bunx @inixiative/config terms [dir] [--write-baseline]
|
|
74
77
|
```
|
|
75
78
|
|
|
76
79
|
`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. Zealot alone sits at block 0, the defaults (Aron, 2026-10-20: it already runs there); every other project is off the defaults: 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 (never 0), and a new kind of service its usual default; the tests fail on any collision.
|
|
77
80
|
|
|
78
|
-
`check` is read-only and exits non-zero on drift
|
|
81
|
+
`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, with 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`, a gitignored `tmp/` (scratch space; `sync` adds `/tmp/*`, which keeps re-included files tracked), 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 `@inixiative/*` dependency (in the root or any workspace member, of either lane) that the declared range admits the blessed version and the lockfile resolves to it (the stale-lockfile class). A package with a candidate (one no hold covers) also accepts the candidate and anything between: a range admitting either passes, and so does a lock from the blessed version up to the candidate. 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.
|
|
82
|
+
|
|
83
|
+
`check` also runs the **retired-terms** rule (alone: `terms`), which keeps public copy from rotting back to names the project retired. It reads text people read, never code identifiers: every Markdown, MDX, HTML and Astro file a repo tracks, and each `package.json` `description`. Fixtures, changelogs (history records the rename), `node_modules`, generated Warden regions (`.foundry/regions/`; fix their source docs) and root `dist`, `build`, `coverage` and `tmp` are skipped. The rule runs in `check` and `terms` only; `scan`, `sync` and the train are about package drift and don't run it. The list lives in `src/retiredTerms.ts`, each term with the word to use instead, and comes from the workspace CLAUDE.md "Dead concepts" and the copy brief's "Retired words":
|
|
84
|
+
|
|
85
|
+
| Term | Checked in | Use instead |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| Kastle, Castle (capitalised) | every doc | Kingdom, owner, integration or Signet |
|
|
88
|
+
| `RuntimeInstallation`, `kingdom_runtime_` | every doc | a Signet enrollment |
|
|
89
|
+
| `signetDemo`, `medicalQuestionStep` | every doc | nothing; demos live in template-workbench |
|
|
90
|
+
| `kingdom-workspace`, `foundry-lab`, `native-candidate` | every doc | the canonical repos |
|
|
91
|
+
| foundry-oracle | every doc | Foundry and Oracle, two products |
|
|
92
|
+
| "your own LLM/API keys" | every doc | your own subscriptions |
|
|
93
|
+
| Cartographer, Librarian, Artificer, Steward (capitalised) | READMEs, package descriptions, site pages only | say what it does; Foundry's code still has a Librarian and a Cartographer |
|
|
94
|
+
|
|
95
|
+
Left out on purpose, because a pattern would flag mostly legitimate text: "medical" (the doctor's-visit story is allowed in marketing), "Connection" and `connectionId` (WebSocket and database connections; Foundry's viewer has a live per-socket `connectionId`), "principal" (the permissions README's glossary word for any holder, job titles, the principal–agent problem), and "Cursor as a runtime" or "token savings" (a phrase can't tell a claim from a comparison).
|
|
96
|
+
|
|
97
|
+
Per repo, in `scripts/ci/retired-terms/` (the same shape as template's doc-freshness rule):
|
|
98
|
+
|
|
99
|
+
- `allow.txt` for legitimate uses: `<term>` allows it everywhere, `<path>\t<term>` in one file, or under a directory when the path ends in `/` (a retired-names table, a history doc). Stale copy never goes here.
|
|
100
|
+
- `baseline.txt` for uses that predate the rule, `<path>\t<term>\t<count>`: reported as one warning, not failed. A use beyond the count fails, and so does a count that dropped (lower or delete the line), so the baseline only shrinks. `terms --write-baseline` writes it when there is none, and later only to shrink it: it refuses to add a use.
|
|
79
101
|
|
|
80
|
-
`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
|
|
102
|
+
`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 `@inixiative/*` 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.
|
|
81
103
|
|
|
82
|
-
`scan` runs `check` across every
|
|
104
|
+
`scan` runs `check` across every inixiative 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 `team-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 lists unmerged `claude/*` session branches.
|
|
83
105
|
|
|
84
|
-
`train` is the release cascade, run from a git checkout of this repo; it refuses to train from a copy that isn't one (a bunx or npm cache). One train at a time per machine: it takes one machine-wide lock, `~/.inixiative/train.lock`, shared by every clone, worktree and lane, and refuses to start while another train holds it, naming the holder and the lock path. The lock is created atomically and records the holder's pid, hostname and process start time. A lock is stale, and taken over, when its process is gone, its pid now belongs to a different process, or the file can't be parsed. A lock from another host is never judged stale; if its train is
|
|
106
|
+
`train` is the release cascade, run from a git checkout of this repo; it refuses to train from a copy that isn't one (a bunx or npm cache). One train at a time per machine: it takes one machine-wide lock, `~/.inixiative/train.lock`, shared by every clone, worktree and lane, and refuses to start while another train holds it, naming the holder and the lock path. The lock is created atomically and records the holder's pid, hostname and process start time. A lock is stale, and taken over, when its process is gone, its pid now belongs to a different process, or the file can't be parsed. A lock from another host is never judged stale; if its train is gone, delete the file. The lock is released on exit and on SIGINT, SIGTERM or SIGHUP. `--dry-run` takes no lock. 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.
|
|
85
107
|
|
|
86
|
-
Lanes stand alone. A lane with a package missing a checkout, or claimed by ambiguous checkouts, does not run; a lane whose own check (not one against candidates, below), commit or publish fails stops there: what it already published stays a candidate, and none of its candidates is blessed that run. Neither holds back the other lane. Within a lane it walks checkouts in dependency order; per checkout it: bumps
|
|
108
|
+
Lanes stand alone. A lane with a package missing a checkout, or claimed by ambiguous checkouts, does not run; a lane whose own check (not one against candidates, below), commit or publish fails stops there: what it already published stays a candidate, and none of its candidates is blessed that run. Neither holds back the other lane. Within a lane it walks checkouts in dependency order; per checkout it: bumps `@inixiative/*` ranges to the blessed sets with their candidates in place → re-locks → runs the repo's `check` → commits only its own changes (`package.json` files + `bun.lock`) → publishes each lane package whose local version is ahead of npm, in dependency order, and waits until the registry serves it (up to 15 minutes, since npm holds new versions for processing) → records it as a candidate in the BOM.
|
|
87
109
|
|
|
88
110
|
Blessing is two-phase, so a dependent that needs work never deadlocks a release:
|
|
89
111
|
|
|
90
112
|
1. **Candidate.** A published version is recorded under `candidates`, and the config release that ends the train carries it, so `check` in any repo's CI accepts it from then on.
|
|
91
113
|
2. **Adopt.** A repo (lane checkout or consumer) whose check fails against the candidates it was moved onto doesn't stop the train. Its checkout is put back as it was, and the train opens `train/adopt-<pkg>-<version>` in a worktree (the repo's own `worktree:create <base> <branch>` when it declares one, so a template-based repo gets its slot's databases, released with its `worktree:destroy`; otherwise a temporary linked worktree; either way with the repo's ignored `.env*` files copied in where missing): the same bump and re-lock, then the repo's `train:adopt` script if its package.json declares one (Foundry's re-records cassettes with `bun run test:live`), all committed, and re-locked onto the new config release so the PR's CI judges it against the candidates. A lane checkout that adopts publishes nothing this run. A consumer that passes against candidates gets the same branch name: its default branch is still on the previous versions until the branch merges. If the adoption's commit is refused (its hooks typecheck), the branch is deleted and the summary says to adopt by hand.
|
|
92
|
-
3. **Promote.** At the end of each run a candidate is blessed (moved into its lane's section) unless something holds it: its lane stopped; a repo has an adoption branch for it that is not yet on its default branch (opened this run, unmerged, or committed but never pushed); or a repo that declares it was not walked to an outcome this run
|
|
114
|
+
3. **Promote.** At the end of each run a candidate is blessed (moved into its lane's section) unless something holds it: its lane stopped; a repo has an adoption branch for it that is not yet on its default branch (opened this run, unmerged, or committed but never pushed); or a repo that declares it was not walked to an outcome this run (another lane's checkout, a skipped or failed one, a consumer without a checkout). A later train finds a waiting adoption branch and opens nothing new; once it merges, the repo is on the candidate and the train promotes it. A candidate every dependent already accepts is blessed in the run that published it. An adoption PR closed without merging holds its candidates until its branch is deleted.
|
|
93
115
|
|
|
94
116
|
The summary lists what was blessed, every candidate with what holds it, every hold with its reason, and each pending adoption branch with its PR when `gh` can find one.
|
|
95
117
|
|
|
96
|
-
**Holds.** A hold keeps a package's breaking line out of the
|
|
118
|
+
**Holds.** A hold keeps a package's breaking line out of the blessed set while its dependents can't take it yet (signet 0.12 waiting on Kingdom's grants cutover), without stopping the package's own releases or anyone else's:
|
|
97
119
|
|
|
98
120
|
```jsonc
|
|
99
121
|
"holds": {
|
|
@@ -115,7 +137,7 @@ Every version at or above `below` is held; versions below it ride the train as u
|
|
|
115
137
|
|
|
116
138
|
`scan` and the train's summary print each hold with its reason, and name a held candidate as not applied. Lifting a hold is deleting its entry: the next train records the held version found on npm at the local version (or applies the held candidate) and adopts it like any other.
|
|
117
139
|
|
|
118
|
-
A root package publishes with `npm publish`; a workspace member is packed with `bun pm pack`, which rewrites `workspace:` ranges to the versions being released, and the tarball is published with `npm publish`, so OTP prompts pass through either way. A package already on npm at its local version but neither blessed nor a candidate
|
|
140
|
+
A root package publishes with `npm publish`; a workspace member is packed with `bun pm pack`, which rewrites `workspace:` ranges to the versions being released, and the tarball is published with `npm publish`, so OTP prompts pass through either way. A package already on npm at its local version but neither blessed nor a candidate (published by hand, or by a train whose lane later failed) becomes a candidate on the next run, unless a hold covers that version. Likewise, an `@inixiative/config` version committed ahead of npm (a release whose publish failed) is published by the next run, even when it blesses nothing new.
|
|
119
141
|
|
|
120
142
|
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, in a worktree, and puts the checkout back on its default branch, clean.
|
|
121
143
|
|
|
@@ -125,7 +147,7 @@ When anything was recorded or blessed it then ships this package last, so the BO
|
|
|
125
147
|
|
|
126
148
|
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.
|
|
127
149
|
|
|
128
|
-
Division of labor: this CLI owns the toolchain set,
|
|
150
|
+
Division of labor: this CLI owns the toolchain set, `@inixiative/*` version coherence, stubs, and lockfile format. Renovate owns everything else plus bumping `@inixiative/config` itself, and must be fenced off the toolchain packages.
|
|
129
151
|
|
|
130
152
|
## CI
|
|
131
153
|
|
|
@@ -142,13 +164,13 @@ jobs:
|
|
|
142
164
|
uses: inixiative/config/.github/workflows/lib-ci.yml@main
|
|
143
165
|
```
|
|
144
166
|
|
|
145
|
-
`lib-ci.yml` pins bun from `.bun-version`, installs with `--frozen-lockfile` (kills the stale-lockfile class), runs `inixiative-config check
|
|
167
|
+
`lib-ci.yml` pins bun from `.bun-version`, installs with `--frozen-lockfile` (kills the stale-lockfile class), runs `inixiative-config check .` (retired terms included), then the repo's own `bun run check`. A repo that doesn't use `lib-ci.yml` (template, Kingdom, Oracle, agent-session, gloss, Tribe) adopts the retired-terms rule by running `bunx inixiative-config terms .` from its own CI rules (template: a `scripts/ci/rules/retired-terms.sh` beside `doc-freshness.sh`).
|
|
146
168
|
|
|
147
169
|
## Release flow
|
|
148
170
|
|
|
149
171
|
Two lanes, each publishing in dependency order, with config last:
|
|
150
172
|
|
|
151
|
-
- **primitives
|
|
152
|
-
- **agentic
|
|
173
|
+
- **primitives:** for example json-rules → permissions → transitions → rules-builder, plus prisma-map, atlas and gloss. Consumers: template, kingdom.
|
|
174
|
+
- **agentic:** signet, kcp (the Kingdom CLI proxy, on signet), archive and agent-session, then foundry-core → foundry → foundry-acp (foundry depends on archive), built on the primitives' blessed set. foundry-core, foundry and foundry-acp publish from the foundry monorepo's `packages/core`, `packages/foundry` and `packages/acp`. Consumers: oracle (a private leaf), kingdom.
|
|
153
175
|
|
|
154
176
|
A release is `train` (or `train --lane=agentic` to leave the primitives alone): bump the package's version on its default branch, then run the train. `train` automates the whole walk, this package and the consumer PRs included. Downstream repos pick up toolchain changes via `sync`.
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { spawnSync as
|
|
4
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
5
5
|
import {
|
|
6
|
-
existsSync as
|
|
7
|
-
mkdirSync as
|
|
6
|
+
existsSync as existsSync3,
|
|
7
|
+
mkdirSync as mkdirSync3,
|
|
8
8
|
mkdtempSync,
|
|
9
|
-
readdirSync as
|
|
10
|
-
readFileSync as
|
|
11
|
-
rmSync as
|
|
12
|
-
writeFileSync as
|
|
9
|
+
readdirSync as readdirSync3,
|
|
10
|
+
readFileSync as readFileSync3,
|
|
11
|
+
rmSync as rmSync3,
|
|
12
|
+
writeFileSync as writeFileSync3
|
|
13
13
|
} from "fs";
|
|
14
14
|
import { hostname as hostname2, tmpdir } from "os";
|
|
15
|
-
import { basename, dirname as
|
|
15
|
+
import { basename, dirname as dirname3, join as join3, resolve as resolve2 } from "path";
|
|
16
16
|
|
|
17
17
|
// src/args.ts
|
|
18
18
|
import { parseArgs } from "util";
|
|
@@ -999,7 +999,7 @@ var isConfigCheckout = (dir2) => {
|
|
|
999
999
|
};
|
|
1000
1000
|
|
|
1001
1001
|
// src/args.ts
|
|
1002
|
-
var COMMANDS = ["check", "sync", "scan", "train", "ports"];
|
|
1002
|
+
var COMMANDS = ["check", "sync", "scan", "train", "ports", "terms"];
|
|
1003
1003
|
var PRESETS = ["base", "node", "react"];
|
|
1004
1004
|
var preset = { type: "string" };
|
|
1005
1005
|
var lane = { type: "string" };
|
|
@@ -1009,7 +1009,8 @@ var OPTIONS = {
|
|
|
1009
1009
|
sync: { preset, force: flag, "no-install": flag },
|
|
1010
1010
|
scan: { lane },
|
|
1011
1011
|
train: { lane, push: flag, "dry-run": flag },
|
|
1012
|
-
ports: {}
|
|
1012
|
+
ports: {},
|
|
1013
|
+
terms: { "write-baseline": flag }
|
|
1013
1014
|
};
|
|
1014
1015
|
var UsageError = class extends Error {
|
|
1015
1016
|
};
|
|
@@ -1057,7 +1058,8 @@ var parseCommandLine = (argv) => {
|
|
|
1057
1058
|
force: values.force === true,
|
|
1058
1059
|
noInstall: values["no-install"] === true,
|
|
1059
1060
|
push: values.push === true,
|
|
1060
|
-
dryRun: values["dry-run"] === true
|
|
1061
|
+
dryRun: values["dry-run"] === true,
|
|
1062
|
+
writeBaseline: values["write-baseline"] === true
|
|
1061
1063
|
};
|
|
1062
1064
|
};
|
|
1063
1065
|
|
|
@@ -1103,6 +1105,238 @@ function portsFor(project) {
|
|
|
1103
1105
|
);
|
|
1104
1106
|
}
|
|
1105
1107
|
|
|
1108
|
+
// src/retiredTerms.ts
|
|
1109
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1110
|
+
import {
|
|
1111
|
+
existsSync as existsSync2,
|
|
1112
|
+
lstatSync,
|
|
1113
|
+
mkdirSync as mkdirSync2,
|
|
1114
|
+
readdirSync as readdirSync2,
|
|
1115
|
+
readFileSync as readFileSync2,
|
|
1116
|
+
rmSync as rmSync2,
|
|
1117
|
+
writeFileSync as writeFileSync2
|
|
1118
|
+
} from "fs";
|
|
1119
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1120
|
+
var RETIRED_TERMS = [
|
|
1121
|
+
{
|
|
1122
|
+
id: "kastle",
|
|
1123
|
+
pattern: /\bkastles?\b/gi,
|
|
1124
|
+
scope: "docs",
|
|
1125
|
+
instead: "Kingdom, owner, integration or Signet"
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
id: "castle",
|
|
1129
|
+
pattern: /\bCastles?\b/g,
|
|
1130
|
+
scope: "docs",
|
|
1131
|
+
instead: "Kingdom, owner, integration or Signet"
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
id: "runtime-keys",
|
|
1135
|
+
pattern: /\bRuntimeInstallations?\b|\bkingdom_runtime_/g,
|
|
1136
|
+
scope: "docs",
|
|
1137
|
+
instead: "a Signet enrollment on the Foundry integration"
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
id: "medical-demo",
|
|
1141
|
+
pattern: /\bsignetDemo\b|\bmedicalQuestionStep\b/g,
|
|
1142
|
+
scope: "docs",
|
|
1143
|
+
instead: "nothing: the demo is gone (demos live in template-workbench)"
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
id: "retired-workspaces",
|
|
1147
|
+
pattern: /\bkingdom-workspace\b|\bfoundry-lab\b|\bnative-candidate\b/g,
|
|
1148
|
+
scope: "docs",
|
|
1149
|
+
instead: "the canonical repos (kingdom, foundry)"
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
id: "foundry-oracle",
|
|
1153
|
+
pattern: /\bfoundry-oracle\b/gi,
|
|
1154
|
+
scope: "docs",
|
|
1155
|
+
instead: "Foundry and Oracle, two products"
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
id: "own-llm-keys",
|
|
1159
|
+
pattern: /\byour own (?:LLM|model|API) keys?\b/gi,
|
|
1160
|
+
scope: "docs",
|
|
1161
|
+
instead: "your own subscriptions (Foundry runs on subscription logins)"
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
id: "foundry-roles",
|
|
1165
|
+
pattern: /\b(?:Cartographer|Librarian|Artificer|Steward)s?\b/g,
|
|
1166
|
+
scope: "public",
|
|
1167
|
+
instead: "say what it does: Wardens point at what exists; your agent decides"
|
|
1168
|
+
}
|
|
1169
|
+
];
|
|
1170
|
+
var RETIRED_TERMS_DIR = "scripts/ci/retired-terms";
|
|
1171
|
+
var ALLOW_FILE = `${RETIRED_TERMS_DIR}/allow.txt`;
|
|
1172
|
+
var BASELINE_FILE = `${RETIRED_TERMS_DIR}/baseline.txt`;
|
|
1173
|
+
var TEXT_FILE = /\.(md|mdx|html|astro)$/i;
|
|
1174
|
+
var PUBLIC_FILE = /(^|\/)README[^/]*\.md$|\.(html|astro|mdx)$/i;
|
|
1175
|
+
var SKIPPED = /(^|\/)(node_modules|\.git|\.worktrees|fixtures|rule-violations)\/|^(dist|build|coverage|tmp)\/|^\.foundry\/regions\/|(^|\/)CHANGELOG[^/]*\.md$/i;
|
|
1176
|
+
var walk = (root, dir2 = "", out = []) => {
|
|
1177
|
+
for (const entry of readdirSync2(join2(root, dir2), { withFileTypes: true })) {
|
|
1178
|
+
const rel = dir2 ? `${dir2}/${entry.name}` : entry.name;
|
|
1179
|
+
if (entry.isDirectory() && !SKIPPED.test(`${rel}/`)) walk(root, rel, out);
|
|
1180
|
+
else if (entry.isFile()) out.push(rel);
|
|
1181
|
+
}
|
|
1182
|
+
return out;
|
|
1183
|
+
};
|
|
1184
|
+
var readText = (abs) => {
|
|
1185
|
+
try {
|
|
1186
|
+
return lstatSync(abs).isFile() ? readFileSync2(abs, "utf8") : null;
|
|
1187
|
+
} catch {
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
var listFiles = (root) => {
|
|
1192
|
+
if (existsSync2(join2(root, ".git"))) {
|
|
1193
|
+
const listed = spawnSync2(
|
|
1194
|
+
"git",
|
|
1195
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard"],
|
|
1196
|
+
{
|
|
1197
|
+
cwd: root,
|
|
1198
|
+
encoding: "utf8"
|
|
1199
|
+
}
|
|
1200
|
+
);
|
|
1201
|
+
if (listed.status === 0) return listed.stdout.split("\0").filter(Boolean);
|
|
1202
|
+
}
|
|
1203
|
+
return walk(root);
|
|
1204
|
+
};
|
|
1205
|
+
var lineOf = (text, index) => text.slice(0, index).split("\n").length;
|
|
1206
|
+
var scanRetiredTerms = (root) => {
|
|
1207
|
+
const hits = [];
|
|
1208
|
+
for (const path of listFiles(root).sort()) {
|
|
1209
|
+
if (SKIPPED.test(path) || path.startsWith(`${RETIRED_TERMS_DIR}/`)) continue;
|
|
1210
|
+
const isManifest = /(^|\/)package\.json$/.test(path);
|
|
1211
|
+
if (!isManifest && !TEXT_FILE.test(path)) continue;
|
|
1212
|
+
const raw = readText(join2(root, path));
|
|
1213
|
+
if (raw === null) continue;
|
|
1214
|
+
let text = raw;
|
|
1215
|
+
if (isManifest) {
|
|
1216
|
+
try {
|
|
1217
|
+
const description = JSON.parse(raw).description;
|
|
1218
|
+
if (typeof description !== "string") continue;
|
|
1219
|
+
text = description;
|
|
1220
|
+
} catch {
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
const isPublic = isManifest || PUBLIC_FILE.test(path);
|
|
1225
|
+
for (const term of RETIRED_TERMS) {
|
|
1226
|
+
if (term.scope === "public" && !isPublic) continue;
|
|
1227
|
+
for (const match of text.matchAll(term.pattern)) {
|
|
1228
|
+
hits.push({
|
|
1229
|
+
path,
|
|
1230
|
+
line: isManifest ? lineOf(raw, raw.indexOf('"description"')) : lineOf(text, match.index),
|
|
1231
|
+
term,
|
|
1232
|
+
text: match[0]
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return hits;
|
|
1238
|
+
};
|
|
1239
|
+
var readList = (root, file) => {
|
|
1240
|
+
const abs = join2(root, file);
|
|
1241
|
+
if (!existsSync2(abs)) return [];
|
|
1242
|
+
return readFileSync2(abs, "utf8").split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() !== "" && !line.trimStart().startsWith("#"));
|
|
1243
|
+
};
|
|
1244
|
+
var allowedBy = (lines) => {
|
|
1245
|
+
const rules = lines.map((line) => {
|
|
1246
|
+
const [first2, second] = line.split(" ");
|
|
1247
|
+
return second === void 0 ? { path: "", term: first2.trim() } : { path: first2, term: second.trim() };
|
|
1248
|
+
});
|
|
1249
|
+
return (hit) => rules.some(
|
|
1250
|
+
(rule) => rule.term === hit.term.id && (rule.path === "" || rule.path === hit.path || rule.path.endsWith("/") && hit.path.startsWith(rule.path))
|
|
1251
|
+
);
|
|
1252
|
+
};
|
|
1253
|
+
var countByKey = (hits) => {
|
|
1254
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1255
|
+
for (const hit of hits) {
|
|
1256
|
+
const key = `${hit.path} ${hit.term.id}`;
|
|
1257
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
1258
|
+
}
|
|
1259
|
+
return counts;
|
|
1260
|
+
};
|
|
1261
|
+
var BASELINE_LINE = /^([^\t]+)\t([^\t]+)\t(\d+)$/;
|
|
1262
|
+
var readBaseline = (root) => {
|
|
1263
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1264
|
+
const malformed = [];
|
|
1265
|
+
for (const line of readList(root, BASELINE_FILE)) {
|
|
1266
|
+
const match = BASELINE_LINE.exec(line);
|
|
1267
|
+
if (match) counts.set(`${match[1]} ${match[2]}`, Number(match[3]));
|
|
1268
|
+
else malformed.push(line);
|
|
1269
|
+
}
|
|
1270
|
+
return { counts, malformed };
|
|
1271
|
+
};
|
|
1272
|
+
var checkRetiredTerms = (root) => {
|
|
1273
|
+
const allowed = allowedBy(readList(root, ALLOW_FILE));
|
|
1274
|
+
const hits = scanRetiredTerms(root).filter((hit) => !allowed(hit));
|
|
1275
|
+
const counts = countByKey(hits);
|
|
1276
|
+
const { counts: baseline, malformed } = readBaseline(root);
|
|
1277
|
+
const findings = malformed.map((line) => ({
|
|
1278
|
+
level: "error",
|
|
1279
|
+
message: `${BASELINE_FILE}: "${line}" is not <path>\\t<term>\\t<count> \u2192 rewrite it with terms --write-baseline`
|
|
1280
|
+
}));
|
|
1281
|
+
for (const [key, count] of counts) {
|
|
1282
|
+
const known = baseline.get(key) ?? 0;
|
|
1283
|
+
if (count <= known) continue;
|
|
1284
|
+
const fresh = hits.filter((hit) => `${hit.path} ${hit.term.id}` === key).slice(known);
|
|
1285
|
+
for (const hit of fresh) {
|
|
1286
|
+
findings.push({
|
|
1287
|
+
level: "error",
|
|
1288
|
+
message: `retired term in ${hit.path}:${hit.line}: "${hit.text}" (${hit.term.id}) \u2192 ${hit.term.instead}`
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
for (const [key, known] of baseline) {
|
|
1293
|
+
const count = counts.get(key) ?? 0;
|
|
1294
|
+
if (count >= known) continue;
|
|
1295
|
+
const [path, term] = key.split(" ");
|
|
1296
|
+
findings.push({
|
|
1297
|
+
level: "error",
|
|
1298
|
+
message: count === 0 ? `${BASELINE_FILE}: ${path} no longer uses ${term} \u2192 delete its line` : `${BASELINE_FILE}: ${path} uses ${term} ${count}\xD7 now, not ${known} \u2192 lower its count`
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
const baselined = [...counts].reduce(
|
|
1302
|
+
(sum, [key, count]) => sum + Math.min(count, baseline.get(key) ?? 0),
|
|
1303
|
+
0
|
|
1304
|
+
);
|
|
1305
|
+
if (baselined > 0) {
|
|
1306
|
+
findings.push({
|
|
1307
|
+
level: "warn",
|
|
1308
|
+
message: `${baselined} retired-term use${baselined === 1 ? "" : "s"} in ${BASELINE_FILE} (not blocking; fix the copy and shrink the baseline)`
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
return { findings, hits };
|
|
1312
|
+
};
|
|
1313
|
+
var writeRetiredTermsBaseline = (root) => {
|
|
1314
|
+
const allowed = allowedBy(readList(root, ALLOW_FILE));
|
|
1315
|
+
const counts = countByKey(scanRetiredTerms(root).filter((hit) => !allowed(hit)));
|
|
1316
|
+
const path = join2(root, BASELINE_FILE);
|
|
1317
|
+
if (existsSync2(path)) {
|
|
1318
|
+
const { counts: baseline } = readBaseline(root);
|
|
1319
|
+
const refused = [...counts].filter(([key, count]) => count > (baseline.get(key) ?? 0)).map(
|
|
1320
|
+
([key, count]) => `${key.replace(" ", " ")} ${count}\xD7 (baseline ${baseline.get(key) ?? 0})`
|
|
1321
|
+
);
|
|
1322
|
+
if (refused.length > 0) return { written: 0, refused };
|
|
1323
|
+
}
|
|
1324
|
+
const header = [
|
|
1325
|
+
"# Retired-term uses that predate the rule: reported, not failed (@inixiative/config check).",
|
|
1326
|
+
"# One per line: <path>\\t<term>\\t<count>. Fix the copy and lower or delete its line.",
|
|
1327
|
+
"# Rewrite after fixing: inixiative-config terms --write-baseline (it refuses to grow)."
|
|
1328
|
+
];
|
|
1329
|
+
const lines = [...counts].sort(([a], [b]) => a.localeCompare(b)).map(([key, n]) => `${key} ${n}`);
|
|
1330
|
+
if (lines.length === 0) {
|
|
1331
|
+
rmSync2(path, { force: true });
|
|
1332
|
+
return { written: 0, refused: [] };
|
|
1333
|
+
}
|
|
1334
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
1335
|
+
writeFileSync2(path, `${[...header, ...lines].join("\n")}
|
|
1336
|
+
`);
|
|
1337
|
+
return { written: lines.length, refused: [] };
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1106
1340
|
// src/cli.ts
|
|
1107
1341
|
var commandLine;
|
|
1108
1342
|
try {
|
|
@@ -1111,7 +1345,7 @@ try {
|
|
|
1111
1345
|
if (!(error instanceof UsageError)) throw error;
|
|
1112
1346
|
console.error(`\u2717 ${error.message}`);
|
|
1113
1347
|
console.error(
|
|
1114
|
-
"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]"
|
|
1348
|
+
"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]\n inixiative-config terms [dir] [--write-baseline]"
|
|
1115
1349
|
);
|
|
1116
1350
|
process.exit(2);
|
|
1117
1351
|
}
|
|
@@ -1145,7 +1379,7 @@ var report = (findings) => {
|
|
|
1145
1379
|
if (process.versions.bun && process.versions.bun !== manifest.bun) {
|
|
1146
1380
|
console.log(`\u26A0 running bun ${process.versions.bun}, blessed is ${manifest.bun} (bun upgrade)`);
|
|
1147
1381
|
}
|
|
1148
|
-
var git = (cwd, ...gitArgs) =>
|
|
1382
|
+
var git = (cwd, ...gitArgs) => spawnSync3("git", gitArgs, { cwd, encoding: "utf8" });
|
|
1149
1383
|
var behindOrigin = (cwd) => {
|
|
1150
1384
|
const behind = git(cwd, "rev-list", "--count", "HEAD..@{upstream}");
|
|
1151
1385
|
return behind.status === 0 ? Number(behind.stdout.trim()) : 0;
|
|
@@ -1183,7 +1417,20 @@ var offDefaultBranch = (cwd) => {
|
|
|
1183
1417
|
};
|
|
1184
1418
|
if (command === "check") {
|
|
1185
1419
|
const { findings } = inspect(dir, manifest, presetFlag);
|
|
1186
|
-
process.exit(report(findings) > 0 ? 1 : 0);
|
|
1420
|
+
process.exit(report([...findings, ...checkRetiredTerms(dir).findings]) > 0 ? 1 : 0);
|
|
1421
|
+
}
|
|
1422
|
+
if (command === "terms") {
|
|
1423
|
+
if (commandLine.writeBaseline) {
|
|
1424
|
+
const { written, refused } = writeRetiredTermsBaseline(dir);
|
|
1425
|
+
if (refused.length > 0) {
|
|
1426
|
+
console.error("\u2717 the baseline only shrinks; fix these uses (or allow a legitimate one):");
|
|
1427
|
+
for (const entry of refused) console.error(` ${entry}`);
|
|
1428
|
+
process.exit(1);
|
|
1429
|
+
}
|
|
1430
|
+
console.log(`wrote ${written} baseline entr${written === 1 ? "y" : "ies"}`);
|
|
1431
|
+
process.exit(0);
|
|
1432
|
+
}
|
|
1433
|
+
process.exit(report(checkRetiredTerms(dir).findings) > 0 ? 1 : 0);
|
|
1187
1434
|
}
|
|
1188
1435
|
var laneLabel = (checkout) => checkout.consumer ? `consumer of ${checkout.consumer.lanes.join(" + ")}` : `${checkout.lane} lane`;
|
|
1189
1436
|
if (command === "scan") {
|
|
@@ -1306,7 +1553,7 @@ if (command === "train") {
|
|
|
1306
1553
|
const worktrees = [];
|
|
1307
1554
|
const removeWorktree = ({ repo, path, tooling }) => {
|
|
1308
1555
|
if (tooling) {
|
|
1309
|
-
|
|
1556
|
+
spawnSync3("bun", ["run", "worktree:destroy", basename(path), "--force"], {
|
|
1310
1557
|
cwd: repo,
|
|
1311
1558
|
stdio: "inherit"
|
|
1312
1559
|
});
|
|
@@ -1355,7 +1602,7 @@ if (command === "train") {
|
|
|
1355
1602
|
}
|
|
1356
1603
|
if (!dryRun) {
|
|
1357
1604
|
console.log(`\u2193 fast-forwarded to origin (+${behind})`);
|
|
1358
|
-
checkout.pkg = JSON.parse(
|
|
1605
|
+
checkout.pkg = JSON.parse(readFileSync3(join3(checkout.dir, "package.json"), "utf8"));
|
|
1359
1606
|
}
|
|
1360
1607
|
}
|
|
1361
1608
|
return true;
|
|
@@ -1381,7 +1628,7 @@ if (command === "train") {
|
|
|
1381
1628
|
};
|
|
1382
1629
|
};
|
|
1383
1630
|
const relockAndCheck = (checkout, check = true) => effect(`re-lock ${checkout.name}${check ? " and run its check" : ""}`, void 0, () => {
|
|
1384
|
-
const install = (...flags) =>
|
|
1631
|
+
const install = (...flags) => spawnSync3("bun", ["install", ...flags], { cwd: checkout.dir, stdio: "inherit" }).status === 0;
|
|
1385
1632
|
if (!install() && !install("--no-cache")) {
|
|
1386
1633
|
throw new Abort(`bun install failed in ${checkout.name}`);
|
|
1387
1634
|
}
|
|
@@ -1389,9 +1636,9 @@ if (command === "train") {
|
|
|
1389
1636
|
checkout.dir,
|
|
1390
1637
|
inspect(checkout.dir, target()).findings
|
|
1391
1638
|
)) {
|
|
1392
|
-
|
|
1639
|
+
spawnSync3("bun", ["update", ...names], { cwd, stdio: "inherit" });
|
|
1393
1640
|
}
|
|
1394
|
-
if (check &&
|
|
1641
|
+
if (check && spawnSync3("bun", ["run", "check"], { cwd: checkout.dir, stdio: "inherit" }).status !== 0) {
|
|
1395
1642
|
throw new CheckFailed(`check failed in ${checkout.name}`);
|
|
1396
1643
|
}
|
|
1397
1644
|
});
|
|
@@ -1400,7 +1647,7 @@ if (command === "train") {
|
|
|
1400
1647
|
if (git(checkout.dir, "status", "--porcelain").stdout.trim() === "") return;
|
|
1401
1648
|
git(checkout.dir, "reset", "--quiet", "--hard", "HEAD");
|
|
1402
1649
|
git(checkout.dir, "clean", "--quiet", "-fd");
|
|
1403
|
-
|
|
1650
|
+
spawnSync3("bun", ["install", "--frozen-lockfile"], { cwd: checkout.dir, stdio: "inherit" });
|
|
1404
1651
|
});
|
|
1405
1652
|
const movesOf = (checkout) => {
|
|
1406
1653
|
const candidates2 = activeCandidates(manifest);
|
|
@@ -1430,11 +1677,11 @@ if (command === "train") {
|
|
|
1430
1677
|
};
|
|
1431
1678
|
const worktree = (checkout, branch) => {
|
|
1432
1679
|
const tooling = !!checkout.pkg.scripts?.["worktree:create"];
|
|
1433
|
-
const path = tooling ?
|
|
1434
|
-
const created = tooling ?
|
|
1680
|
+
const path = tooling ? join3(checkout.dir, ".worktrees", branch.replaceAll("/", "-")) : join3(mkdtempSync(join3(tmpdir(), "inixiative-train-")), basename(checkout.dir));
|
|
1681
|
+
const created = tooling ? spawnSync3("bun", ["run", "worktree:create", defaultBranch(checkout.dir), branch], {
|
|
1435
1682
|
cwd: checkout.dir,
|
|
1436
1683
|
stdio: "inherit"
|
|
1437
|
-
}).status === 0 &&
|
|
1684
|
+
}).status === 0 && existsSync3(path) : git(checkout.dir, "worktree", "add", "--quiet", "-b", branch, path, "HEAD").status === 0;
|
|
1438
1685
|
if (!created) throw new Abort(`could not create ${branch} in ${checkout.name}`);
|
|
1439
1686
|
worktrees.push({ repo: checkout.dir, path, tooling });
|
|
1440
1687
|
const envFiles = git(
|
|
@@ -1449,9 +1696,9 @@ if (command === "train") {
|
|
|
1449
1696
|
(file) => /(^|\/)\.env[^/]*$/.test(file) && !/(^|\/)(node_modules|\.worktrees)\//.test(file)
|
|
1450
1697
|
);
|
|
1451
1698
|
for (const file of envFiles) {
|
|
1452
|
-
if (
|
|
1453
|
-
|
|
1454
|
-
|
|
1699
|
+
if (existsSync3(join3(path, file))) continue;
|
|
1700
|
+
mkdirSync3(dirname3(join3(path, file)), { recursive: true });
|
|
1701
|
+
writeFileSync3(join3(path, file), readFileSync3(join3(checkout.dir, file)));
|
|
1455
1702
|
}
|
|
1456
1703
|
return { ...checkout, dir: path };
|
|
1457
1704
|
};
|
|
@@ -1475,8 +1722,8 @@ if (command === "train") {
|
|
|
1475
1722
|
() => {
|
|
1476
1723
|
const branchCheckout = worktree(checkout, branch);
|
|
1477
1724
|
for (const path of [...packagePaths, "bun.lock"]) {
|
|
1478
|
-
if (
|
|
1479
|
-
|
|
1725
|
+
if (existsSync3(join3(checkout.dir, path))) {
|
|
1726
|
+
writeFileSync3(join3(branchCheckout.dir, path), readFileSync3(join3(checkout.dir, path)));
|
|
1480
1727
|
}
|
|
1481
1728
|
}
|
|
1482
1729
|
restore(checkout);
|
|
@@ -1517,7 +1764,7 @@ if (command === "train") {
|
|
|
1517
1764
|
try {
|
|
1518
1765
|
bumpRanges(branchCheckout);
|
|
1519
1766
|
relockAndCheck(branchCheckout, false);
|
|
1520
|
-
if (script &&
|
|
1767
|
+
if (script && spawnSync3("bun", ["run", "train:adopt"], {
|
|
1521
1768
|
cwd: branchCheckout.dir,
|
|
1522
1769
|
stdio: "inherit"
|
|
1523
1770
|
}).status !== 0) {
|
|
@@ -1526,7 +1773,7 @@ if (command === "train") {
|
|
|
1526
1773
|
);
|
|
1527
1774
|
}
|
|
1528
1775
|
git(branchCheckout.dir, "add", "-A");
|
|
1529
|
-
const commit =
|
|
1776
|
+
const commit = spawnSync3("git", ["commit", "-m", `chore: adopt ${label}`], {
|
|
1530
1777
|
cwd: branchCheckout.dir,
|
|
1531
1778
|
stdio: "inherit"
|
|
1532
1779
|
});
|
|
@@ -1555,14 +1802,14 @@ if (command === "train") {
|
|
|
1555
1802
|
if (dryRun && !changes) return false;
|
|
1556
1803
|
return effect(`commit its package.json files + bun.lock in ${checkout.name}`, true, () => {
|
|
1557
1804
|
const tracked = [...packagePaths, "bun.lock"].filter(
|
|
1558
|
-
(path) =>
|
|
1805
|
+
(path) => existsSync3(join3(checkout.dir, path))
|
|
1559
1806
|
);
|
|
1560
1807
|
const mutated = git(checkout.dir, "status", "--porcelain", "--", ...tracked);
|
|
1561
1808
|
if (mutated.status !== 0 || mutated.stdout.trim().length === 0) return false;
|
|
1562
1809
|
if (git(checkout.dir, "add", "--", ...tracked).status !== 0) {
|
|
1563
1810
|
throw new Abort(`git add failed in ${checkout.name} \u2014 is bun.lock ignored?`);
|
|
1564
1811
|
}
|
|
1565
|
-
const commit =
|
|
1812
|
+
const commit = spawnSync3("git", ["commit", "-m", message], {
|
|
1566
1813
|
cwd: checkout.dir,
|
|
1567
1814
|
stdio: "inherit"
|
|
1568
1815
|
});
|
|
@@ -1580,24 +1827,24 @@ if (command === "train") {
|
|
|
1580
1827
|
});
|
|
1581
1828
|
const publishToNpm = (checkout, entry) => {
|
|
1582
1829
|
if (entry.path === "package.json") {
|
|
1583
|
-
return
|
|
1830
|
+
return spawnSync3("npm", ["publish"], { cwd: checkout.dir, stdio: "inherit" }).status === 0;
|
|
1584
1831
|
}
|
|
1585
|
-
const memberDir =
|
|
1586
|
-
const out = mkdtempSync(
|
|
1832
|
+
const memberDir = join3(checkout.dir, dirname3(entry.path));
|
|
1833
|
+
const out = mkdtempSync(join3(tmpdir(), "inixiative-train-"));
|
|
1587
1834
|
try {
|
|
1588
|
-
const pack =
|
|
1835
|
+
const pack = spawnSync3("bun", ["pm", "pack", "--destination", out], {
|
|
1589
1836
|
cwd: memberDir,
|
|
1590
1837
|
stdio: "inherit"
|
|
1591
1838
|
});
|
|
1592
|
-
const tarball =
|
|
1839
|
+
const tarball = readdirSync3(out).find((name) => name.endsWith(".tgz"));
|
|
1593
1840
|
if (pack.status !== 0 || !tarball) return false;
|
|
1594
|
-
return
|
|
1841
|
+
return spawnSync3("npm", ["publish", join3(out, tarball)], { cwd: memberDir, stdio: "inherit" }).status === 0;
|
|
1595
1842
|
} finally {
|
|
1596
|
-
|
|
1843
|
+
rmSync3(out, { recursive: true, force: true });
|
|
1597
1844
|
}
|
|
1598
1845
|
};
|
|
1599
1846
|
const npmLatest = (name) => {
|
|
1600
|
-
const remote =
|
|
1847
|
+
const remote = spawnSync3("npm", ["view", `${name}@latest`, "version"], { encoding: "utf8" });
|
|
1601
1848
|
return remote.status === 0 ? remote.stdout.trim() : null;
|
|
1602
1849
|
};
|
|
1603
1850
|
for (const { lane: lane2, checkouts, missing, ambiguous } of plan.lanes) {
|
|
@@ -1626,7 +1873,7 @@ if (command === "train") {
|
|
|
1626
1873
|
continue;
|
|
1627
1874
|
}
|
|
1628
1875
|
const releases = checkout.packages.map((entry) => {
|
|
1629
|
-
const local = JSON.parse(
|
|
1876
|
+
const local = JSON.parse(readFileSync3(join3(checkout.dir, entry.path), "utf8")).version ?? "0.0.0";
|
|
1630
1877
|
const remote = npmLatest(entry.name);
|
|
1631
1878
|
return { entry, local, remote, ahead: remote === null || compare(local, remote) > 0 };
|
|
1632
1879
|
});
|
|
@@ -1816,10 +2063,10 @@ blessed: ${promoted.length === 0 ? "nothing" : promoted.join(", ")}`);
|
|
|
1816
2063
|
version = bumpPatch(remoteVersion);
|
|
1817
2064
|
const bumped = version;
|
|
1818
2065
|
effect(`bump @inixiative/config to ${bumped} in package.json`, void 0, () => {
|
|
1819
|
-
const pkgPath =
|
|
1820
|
-
const pkg = JSON.parse(
|
|
2066
|
+
const pkgPath = join3(packageRoot, "package.json");
|
|
2067
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
1821
2068
|
pkg.version = bumped;
|
|
1822
|
-
|
|
2069
|
+
writeFileSync3(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
1823
2070
|
`);
|
|
1824
2071
|
});
|
|
1825
2072
|
}
|
|
@@ -1838,7 +2085,7 @@ blessed: ${promoted.length === 0 ? "nothing" : promoted.join(", ")}`);
|
|
|
1838
2085
|
}
|
|
1839
2086
|
);
|
|
1840
2087
|
effect("run bun run check in @inixiative/config", void 0, () => {
|
|
1841
|
-
const check =
|
|
2088
|
+
const check = spawnSync3("bun", ["run", "check"], { cwd: packageRoot, stdio: "inherit" });
|
|
1842
2089
|
if (check.status !== 0) {
|
|
1843
2090
|
console.error("\u2717 check failed in @inixiative/config \u2014 aborting before publish");
|
|
1844
2091
|
process.exit(1);
|
|
@@ -1849,12 +2096,12 @@ blessed: ${promoted.length === 0 ? "nothing" : promoted.join(", ")}`);
|
|
|
1849
2096
|
recorded.length > 0 ? `candidate ${recorded.join(", ")}` : ""
|
|
1850
2097
|
].filter(Boolean).join("; ") || "release"} \u2014 ${version}`;
|
|
1851
2098
|
effect(`commit "${message}" in @inixiative/config`, void 0, () => {
|
|
1852
|
-
|
|
2099
|
+
spawnSync3("git", ["add", "--", "package.json", "versions.json", "test/fixtures"], {
|
|
1853
2100
|
cwd: packageRoot
|
|
1854
2101
|
});
|
|
1855
|
-
if (
|
|
2102
|
+
if (spawnSync3("git", ["diff", "--cached", "--quiet"], { cwd: packageRoot }).status === 0)
|
|
1856
2103
|
return;
|
|
1857
|
-
const commit =
|
|
2104
|
+
const commit = spawnSync3("git", ["commit", "-m", message], {
|
|
1858
2105
|
cwd: packageRoot,
|
|
1859
2106
|
stdio: "inherit"
|
|
1860
2107
|
});
|
|
@@ -1864,7 +2111,7 @@ blessed: ${promoted.length === 0 ? "nothing" : promoted.join(", ")}`);
|
|
|
1864
2111
|
}
|
|
1865
2112
|
});
|
|
1866
2113
|
effect(`publish @inixiative/config@${version} to npm`, void 0, () => {
|
|
1867
|
-
const publish2 =
|
|
2114
|
+
const publish2 = spawnSync3("npm", ["publish"], { cwd: packageRoot, stdio: "inherit" });
|
|
1868
2115
|
if (publish2.status !== 0 || !servedByRegistry("@inixiative/config", version)) {
|
|
1869
2116
|
console.error("\u2717 publish failed for @inixiative/config");
|
|
1870
2117
|
process.exit(1);
|
|
@@ -1898,17 +2145,17 @@ dry run complete \u2014 a real train would commit in ${committed.size} repo(s) a
|
|
|
1898
2145
|
for (const { dir: repoDir, branch } of reviewBranches) console.log(` ${repoDir} (${branch})`);
|
|
1899
2146
|
} else if (commandLine.push) {
|
|
1900
2147
|
for (const repoDir of committed) {
|
|
1901
|
-
if (
|
|
2148
|
+
if (spawnSync3("git", ["push"], { cwd: repoDir, stdio: "inherit" }).status !== 0) {
|
|
1902
2149
|
console.error(`\u2717 push failed in ${repoDir}`);
|
|
1903
2150
|
process.exit(1);
|
|
1904
2151
|
}
|
|
1905
2152
|
}
|
|
1906
2153
|
for (const { dir: repoDir, branch, base } of reviewBranches) {
|
|
1907
|
-
const push =
|
|
2154
|
+
const push = spawnSync3("git", ["push", "-u", "origin", branch], {
|
|
1908
2155
|
cwd: repoDir,
|
|
1909
2156
|
stdio: "inherit"
|
|
1910
2157
|
});
|
|
1911
|
-
const pr = push.status === 0 &&
|
|
2158
|
+
const pr = push.status === 0 && spawnSync3("gh", ["pr", "create", "--fill", "--base", base, "--head", branch], {
|
|
1912
2159
|
cwd: repoDir,
|
|
1913
2160
|
stdio: "inherit"
|
|
1914
2161
|
}).status === 0;
|
|
@@ -1927,7 +2174,7 @@ dry run complete \u2014 a real train would commit in ${committed.size} repo(s) a
|
|
|
1927
2174
|
process.exit(failed.length > 0 ? 1 : 0);
|
|
1928
2175
|
}
|
|
1929
2176
|
function prUrl(cwd, branch) {
|
|
1930
|
-
const view =
|
|
2177
|
+
const view = spawnSync3("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
|
|
1931
2178
|
cwd,
|
|
1932
2179
|
encoding: "utf8"
|
|
1933
2180
|
});
|
|
@@ -1945,7 +2192,7 @@ function freeBranch(cwd, name) {
|
|
|
1945
2192
|
function servedByRegistry(name, version) {
|
|
1946
2193
|
const deadline = Date.now() + 15 * 6e4;
|
|
1947
2194
|
while (true) {
|
|
1948
|
-
const view =
|
|
2195
|
+
const view = spawnSync3("npm", ["view", `${name}@${version}`, "version", "--prefer-online"], {
|
|
1949
2196
|
encoding: "utf8"
|
|
1950
2197
|
});
|
|
1951
2198
|
if (view.status === 0 && view.stdout.trim() === version) return true;
|
|
@@ -1958,12 +2205,12 @@ function bumpPatch(version) {
|
|
|
1958
2205
|
return `${major}.${minor}.${(patch ?? 0) + 1}`;
|
|
1959
2206
|
}
|
|
1960
2207
|
function fixtureDirs() {
|
|
1961
|
-
const root =
|
|
1962
|
-
if (!
|
|
1963
|
-
return
|
|
2208
|
+
const root = join3(packageRoot, "test", "fixtures");
|
|
2209
|
+
if (!existsSync3(root)) return [];
|
|
2210
|
+
return readdirSync3(root).map((name) => join3(root, name)).filter((dir2) => existsSync3(join3(dir2, "package.json")));
|
|
1964
2211
|
}
|
|
1965
|
-
if (
|
|
1966
|
-
const status =
|
|
2212
|
+
if (existsSync3(join3(dir, ".git")) && !commandLine.force) {
|
|
2213
|
+
const status = spawnSync3("git", ["status", "--porcelain"], { cwd: dir, encoding: "utf8" });
|
|
1967
2214
|
if (status.status === 0 && status.stdout.trim().length > 0) {
|
|
1968
2215
|
console.error("\u2717 working tree dirty \u2014 commit first or pass --force");
|
|
1969
2216
|
process.exit(1);
|
|
@@ -1977,14 +2224,14 @@ if (fixed > 0) console.log(`applied ${fixed} fix${fixed === 1 ? "" : "es"}`);
|
|
|
1977
2224
|
if (!commandLine.noInstall) {
|
|
1978
2225
|
const relinked = new Set(
|
|
1979
2226
|
first.findings.flatMap(
|
|
1980
|
-
(finding) => finding.kind === "member-toolchain" && finding.path ? [
|
|
2227
|
+
(finding) => finding.kind === "member-toolchain" && finding.path ? [dirname3(finding.path)] : []
|
|
1981
2228
|
)
|
|
1982
2229
|
);
|
|
1983
2230
|
for (const member of relinked) {
|
|
1984
|
-
|
|
2231
|
+
rmSync3(join3(dir, member, "node_modules"), { recursive: true, force: true });
|
|
1985
2232
|
}
|
|
1986
2233
|
const install = (...extra) => {
|
|
1987
|
-
if (
|
|
2234
|
+
if (spawnSync3("bun", ["install", ...extra], { cwd: dir, stdio: "inherit" }).status !== 0) {
|
|
1988
2235
|
console.error("\u2717 bun install failed");
|
|
1989
2236
|
process.exit(1);
|
|
1990
2237
|
}
|
|
@@ -1996,7 +2243,7 @@ if (!commandLine.noInstall) {
|
|
|
1996
2243
|
installed = inspect(dir, manifest, presetFlag);
|
|
1997
2244
|
}
|
|
1998
2245
|
for (const { cwd, names } of staleUpdates(dir, installed.findings)) {
|
|
1999
|
-
if (
|
|
2246
|
+
if (spawnSync3("bun", ["update", ...names], { cwd, stdio: "inherit" }).status !== 0) {
|
|
2000
2247
|
console.error(`\u2717 bun update failed in ${cwd}`);
|
|
2001
2248
|
process.exit(1);
|
|
2002
2249
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inixiative/config",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Shared toolchain for the inixiative
|
|
3
|
+
"version": "0.5.186",
|
|
4
|
+
"description": "Shared toolchain and theme for the inixiative packages: tsconfig/biome/tsup presets, theme CSS, a version manifest (BOM), and a sync/check/train CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Aron Greenspan",
|
package/versions.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"@types/bun"
|
|
14
14
|
],
|
|
15
15
|
"ecosystem": {
|
|
16
|
-
"@inixiative/config": "0.5.
|
|
16
|
+
"@inixiative/config": "0.5.186",
|
|
17
17
|
"@inixiative/json-rules": "3.16.0",
|
|
18
18
|
"@inixiative/permissions": "0.18.0",
|
|
19
19
|
"@inixiative/transitions": "0.6.0",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@inixiative/signet": "0.12.2",
|
|
27
27
|
"@inixiative/kcp": "0.1.0",
|
|
28
28
|
"@inixiative/archive": "0.33.0",
|
|
29
|
-
"@inixiative/agent-session": "0.
|
|
29
|
+
"@inixiative/agent-session": "0.10.0",
|
|
30
30
|
"@inixiative/foundry-core": "0.5.10",
|
|
31
31
|
"@inixiative/foundry": "0.5.7",
|
|
32
32
|
"@inixiative/foundry-acp": "0.1.2"
|
|
@@ -52,6 +52,6 @@
|
|
|
52
52
|
},
|
|
53
53
|
"candidates": {
|
|
54
54
|
"@inixiative/atlas": "0.5.0",
|
|
55
|
-
"@inixiative/agent-session": "0.
|
|
55
|
+
"@inixiative/agent-session": "0.11.0"
|
|
56
56
|
}
|
|
57
57
|
}
|