@the-librarian/cli 1.0.0-rc.21
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/LICENSE +201 -0
- package/README.md +94 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +15 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/install.d.ts +28 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +204 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/shared.d.ts +16 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +30 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/uninstall.d.ts +18 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +67 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.d.ts +21 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +75 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +65 -0
- package/dist/config.js.map +1 -0
- package/dist/doctor.d.ts +23 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +81 -0
- package/dist/doctor.js.map +1 -0
- package/dist/env.d.ts +39 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +208 -0
- package/dist/env.js.map +1 -0
- package/dist/exec.d.ts +37 -0
- package/dist/exec.d.ts.map +1 -0
- package/dist/exec.js +81 -0
- package/dist/exec.js.map +1 -0
- package/dist/harnesses/claude.d.ts +3 -0
- package/dist/harnesses/claude.d.ts.map +1 -0
- package/dist/harnesses/claude.js +88 -0
- package/dist/harnesses/claude.js.map +1 -0
- package/dist/harnesses/cli.d.ts +4 -0
- package/dist/harnesses/cli.d.ts.map +1 -0
- package/dist/harnesses/cli.js +16 -0
- package/dist/harnesses/cli.js.map +1 -0
- package/dist/harnesses/codex.d.ts +3 -0
- package/dist/harnesses/codex.d.ts.map +1 -0
- package/dist/harnesses/codex.js +183 -0
- package/dist/harnesses/codex.js.map +1 -0
- package/dist/harnesses/hermes.d.ts +16 -0
- package/dist/harnesses/hermes.d.ts.map +1 -0
- package/dist/harnesses/hermes.js +158 -0
- package/dist/harnesses/hermes.js.map +1 -0
- package/dist/harnesses/index.d.ts +12 -0
- package/dist/harnesses/index.d.ts.map +1 -0
- package/dist/harnesses/index.js +29 -0
- package/dist/harnesses/index.js.map +1 -0
- package/dist/harnesses/opencode.d.ts +3 -0
- package/dist/harnesses/opencode.d.ts.map +1 -0
- package/dist/harnesses/opencode.js +141 -0
- package/dist/harnesses/opencode.js.map +1 -0
- package/dist/harnesses/pi.d.ts +3 -0
- package/dist/harnesses/pi.d.ts.map +1 -0
- package/dist/harnesses/pi.js +72 -0
- package/dist/harnesses/pi.js.map +1 -0
- package/dist/harnesses/types.d.ts +48 -0
- package/dist/harnesses/types.d.ts.map +1 -0
- package/dist/harnesses/types.js +22 -0
- package/dist/harnesses/types.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/machine.d.ts +10 -0
- package/dist/machine.d.ts.map +1 -0
- package/dist/machine.js +39 -0
- package/dist/machine.js.map +1 -0
- package/dist/parse-args.d.ts +12 -0
- package/dist/parse-args.d.ts.map +1 -0
- package/dist/parse-args.js +54 -0
- package/dist/parse-args.js.map +1 -0
- package/dist/paths.d.ts +32 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +75 -0
- package/dist/paths.js.map +1 -0
- package/dist/prompt.d.ts +53 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +165 -0
- package/dist/prompt.js.map +1 -0
- package/dist/runtime.d.ts +45 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +370 -0
- package/dist/runtime.js.map +1 -0
- package/dist/semver.d.ts +13 -0
- package/dist/semver.d.ts.map +1 -0
- package/dist/semver.js +98 -0
- package/dist/semver.js.map +1 -0
- package/dist/server/admin.d.ts +31 -0
- package/dist/server/admin.d.ts.map +1 -0
- package/dist/server/admin.js +110 -0
- package/dist/server/admin.js.map +1 -0
- package/dist/server/boot.d.ts +51 -0
- package/dist/server/boot.d.ts.map +1 -0
- package/dist/server/boot.js +233 -0
- package/dist/server/boot.js.map +1 -0
- package/dist/server/deploy-state.d.ts +33 -0
- package/dist/server/deploy-state.d.ts.map +1 -0
- package/dist/server/deploy-state.js +79 -0
- package/dist/server/deploy-state.js.map +1 -0
- package/dist/server/docker.d.ts +37 -0
- package/dist/server/docker.d.ts.map +1 -0
- package/dist/server/docker.js +138 -0
- package/dist/server/docker.js.map +1 -0
- package/dist/server/down.d.ts +19 -0
- package/dist/server/down.d.ts.map +1 -0
- package/dist/server/down.js +62 -0
- package/dist/server/down.js.map +1 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +48 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/logs.d.ts +50 -0
- package/dist/server/logs.d.ts.map +1 -0
- package/dist/server/logs.js +162 -0
- package/dist/server/logs.js.map +1 -0
- package/dist/server/preflight.d.ts +24 -0
- package/dist/server/preflight.d.ts.map +1 -0
- package/dist/server/preflight.js +58 -0
- package/dist/server/preflight.js.map +1 -0
- package/dist/server/redact.d.ts +2 -0
- package/dist/server/redact.d.ts.map +1 -0
- package/dist/server/redact.js +26 -0
- package/dist/server/redact.js.map +1 -0
- package/dist/server/status.d.ts +20 -0
- package/dist/server/status.d.ts.map +1 -0
- package/dist/server/status.js +103 -0
- package/dist/server/status.js.map +1 -0
- package/dist/server/up.d.ts +211 -0
- package/dist/server/up.d.ts.map +1 -0
- package/dist/server/up.js +625 -0
- package/dist/server/up.js.map +1 -0
- package/dist/server/update.d.ts +35 -0
- package/dist/server/update.d.ts.map +1 -0
- package/dist/server/update.js +308 -0
- package/dist/server/update.js.map +1 -0
- package/dist/status.d.ts +18 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +113 -0
- package/dist/status.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +20 -0
- package/dist/version.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface UpdateOptions {
|
|
2
|
+
/** Pinned ref (`vX.Y.Z` tag or `main`). Default: the latest release tag. */
|
|
3
|
+
ref?: string | undefined;
|
|
4
|
+
/** Auto-accept prompts (none today; wired for surface parity with `up`). */
|
|
5
|
+
yes?: boolean | undefined;
|
|
6
|
+
/** Deploy dir override. Default: `~/.librarian/server`. */
|
|
7
|
+
dir?: string | undefined;
|
|
8
|
+
/** Override home (tests). */
|
|
9
|
+
home?: string | undefined;
|
|
10
|
+
/** Platform for preflight's daemon hint. Default `process.platform`. */
|
|
11
|
+
platform?: NodeJS.Platform | undefined;
|
|
12
|
+
/** Health-wait bound: how many polls before declaring failure (small in tests). */
|
|
13
|
+
healthAttempts?: number | undefined;
|
|
14
|
+
/** Milliseconds between health polls (0 in tests). */
|
|
15
|
+
healthIntervalMs?: number | undefined;
|
|
16
|
+
/** Lines of `docker logs` to surface on a failed health-wait. */
|
|
17
|
+
logTailLines?: number | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** A teaching error from `update`; the runtime renders `.message` as one stderr line. */
|
|
20
|
+
export declare class UpdateError extends Error {
|
|
21
|
+
constructor(message: string);
|
|
22
|
+
}
|
|
23
|
+
export interface UpdateResult {
|
|
24
|
+
/** Human-readable report for stdout (carries a FRESH agent token at most once). */
|
|
25
|
+
output: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Run `server update`. Throws `UpdateError` (teaching message) on a failure
|
|
29
|
+
* before/around the recreate; a failed health-wait throws the (already
|
|
30
|
+
* secret-redacted) `UpError` from `waitForHealthy` after rolling the new
|
|
31
|
+
* container back. deploy-state is advanced ONLY after a confirmed-healthy
|
|
32
|
+
* recreate, so a failed update never records the new ref.
|
|
33
|
+
*/
|
|
34
|
+
export declare function runUpdate(options?: UpdateOptions): Promise<UpdateResult>;
|
|
35
|
+
//# sourceMappingURL=update.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/server/update.ts"],"names":[],"mappings":"AA6EA,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;IACvC,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,yFAAyF;AACzF,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CA8FlF"}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// `librarian server update` — re-pin forward, rebuild, recreate, migrate.
|
|
2
|
+
//
|
|
3
|
+
// The tag-pinned, deploy-dir-owning successor to `pull-and-restart.sh` (the
|
|
4
|
+
// stash/branch dance is gone — the CLI owns its deploy dir, so it just fetches
|
|
5
|
+
// tags and checks out the resolved ref). The flow (spec §8, success criterion 5):
|
|
6
|
+
//
|
|
7
|
+
// 1. Resolve the target ref: `--ref <tag|main>` pins it; default = the latest
|
|
8
|
+
// release tag (`fetchLatestVersion`).
|
|
9
|
+
// 2. IDEMPOTENCY FIRST: read deploy-state (current ref) + the container's
|
|
10
|
+
// health. Already at the resolved ref AND healthy → a CLEAN no-op (no
|
|
11
|
+
// build, no run, no recreate). When `--ref` is given, compare against it.
|
|
12
|
+
// 3. Otherwise update, IN THE DEPLOY DIR:
|
|
13
|
+
// git fetch --tags origin → git checkout <ref>
|
|
14
|
+
// → docker build -f docker/all-in-one.Dockerfile -t the-librarian:<ref> .
|
|
15
|
+
// → read back the EXISTING agent token + master key from the running
|
|
16
|
+
// container's env (so clients keep working AND secrets stay
|
|
17
|
+
// decryptable) BEFORE removing it
|
|
18
|
+
// → docker stop → docker rm <container> (NEVER `-v`, NEVER `volume rm`)
|
|
19
|
+
// → write the 0600 deploy env-file (preserved/fresh token + preserved-or-
|
|
20
|
+
// omitted master key + loopback ALLOW_NO_AUTH) then docker run …
|
|
21
|
+
// (buildRunArgs with the SAME host + dataVolume from deploy-state, via
|
|
22
|
+
// `--env-file`)
|
|
23
|
+
// → waitForHealthy (rolls back with `docker rm -f` on failure — reusing
|
|
24
|
+
// up's pattern, so a failed recreate leaves no half-up container and
|
|
25
|
+
// does NOT advance deploy-state)
|
|
26
|
+
// → docker exec the-librarian the-librarian migrate-data-dir
|
|
27
|
+
// (server boot only WARNS about pending data-dir migrations; the admin
|
|
28
|
+
// CLI APPLIES them — see packages/cli/src/commands/migrate-data-dir.ts.
|
|
29
|
+
// The `the-librarian` runtime binary is bundled into the image in S7;
|
|
30
|
+
// this slice asserts the argv, the runtime target arrives with S7.)
|
|
31
|
+
// 4. writeDeployState with the new ref/imageTag (host/dataVolume unchanged).
|
|
32
|
+
//
|
|
33
|
+
// SECRETS ON UPDATE (decision): recreating the container needs the agent token
|
|
34
|
+
// AND — ADR 0008 P4 — the master key (`LIBRARIAN_SECRET_KEY`) again. Both are
|
|
35
|
+
// correctly NOT persisted host-side except in the 0600 deploy env-file. We read
|
|
36
|
+
// BOTH back from the running container's env via `docker inspect` BEFORE we
|
|
37
|
+
// remove it (P4 put the master key there via `--env-file`, so it's in
|
|
38
|
+
// `.Config.Env`), then recreate with them — clients keep working AND every
|
|
39
|
+
// settings.json secret stays decryptable.
|
|
40
|
+
//
|
|
41
|
+
// - AGENT TOKEN: prefer the read-back token; only if the old container is
|
|
42
|
+
// gone/unreadable do we mint a FRESH token and surface it ONCE with a
|
|
43
|
+
// "clients must update their token" note.
|
|
44
|
+
// - MASTER KEY: prefer the read-back key. If it can't be read back we DO NOT
|
|
45
|
+
// mint a fresh one — re-minting would orphan every secret encrypted under
|
|
46
|
+
// the old key. Instead we OMIT `LIBRARIAN_SECRET_KEY` from the new env-file
|
|
47
|
+
// so the server resolves it from `/data/secret.key` (env → file → generate),
|
|
48
|
+
// which preserves a pre-P4 on-disk key (and only generates when the data dir
|
|
49
|
+
// is genuinely fresh — nothing to orphan).
|
|
50
|
+
//
|
|
51
|
+
// Both secrets ride ONLY in the 0600 deploy env-file fed to `docker run
|
|
52
|
+
// --env-file` — never inline on argv, never in any other host file or log (the
|
|
53
|
+
// spec's no-leak boundary; tests scan for them).
|
|
54
|
+
//
|
|
55
|
+
// The DATA VOLUME is sacred: recreate removes the CONTAINER only (`docker rm`),
|
|
56
|
+
// never the named volume (the `-v` flag / `docker volume rm` never appear), so
|
|
57
|
+
// `up`/`update`/`down` all recall the same memories from the untouched volume.
|
|
58
|
+
//
|
|
59
|
+
// Everything goes through the injectable `docker.ts` runner + the injectable
|
|
60
|
+
// latest-release fetcher, so tests assert the exact argv (and its order) WITHOUT
|
|
61
|
+
// a real daemon, network, or git.
|
|
62
|
+
import path from "node:path";
|
|
63
|
+
import { librarianDir } from "../paths.js";
|
|
64
|
+
import { fetchLatestVersion } from "../status.js";
|
|
65
|
+
import { readDeployState, writeDeployState } from "./deploy-state.js";
|
|
66
|
+
import { run } from "./docker.js";
|
|
67
|
+
import { preflight } from "./preflight.js";
|
|
68
|
+
import { redactSecrets } from "./redact.js";
|
|
69
|
+
import { buildRunArgs, CONTAINER_NAME, mintAgentToken, waitForHealthy, writeDeployEnvFile, } from "./up.js";
|
|
70
|
+
/** A teaching error from `update`; the runtime renders `.message` as one stderr line. */
|
|
71
|
+
export class UpdateError extends Error {
|
|
72
|
+
constructor(message) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = "UpdateError";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Run `server update`. Throws `UpdateError` (teaching message) on a failure
|
|
79
|
+
* before/around the recreate; a failed health-wait throws the (already
|
|
80
|
+
* secret-redacted) `UpError` from `waitForHealthy` after rolling the new
|
|
81
|
+
* container back. deploy-state is advanced ONLY after a confirmed-healthy
|
|
82
|
+
* recreate, so a failed update never records the new ref.
|
|
83
|
+
*/
|
|
84
|
+
export async function runUpdate(options = {}) {
|
|
85
|
+
// 1) Preflight: docker (daemon reachable) + git, or a teaching error.
|
|
86
|
+
await preflight(options.platform ? { platform: options.platform } : {});
|
|
87
|
+
const deployDir = options.dir ?? path.join(librarianDir(options.home), "server");
|
|
88
|
+
// The deploy-state is authoritative for the bind host + data volume to reuse.
|
|
89
|
+
// Without it we cannot recreate the container with the same config — teach.
|
|
90
|
+
const state = readDeployState(deployDir);
|
|
91
|
+
if (!state) {
|
|
92
|
+
throw new UpdateError(`No deploy-state found at ${deployDir} — this host has not run \`librarian server up\` ` +
|
|
93
|
+
"(or the deploy dir is wrong). Run `librarian server up` first, " +
|
|
94
|
+
"or pass `--dir <path>` to point at an existing deploy dir.");
|
|
95
|
+
}
|
|
96
|
+
// 2) Resolve the target ref (explicit `--ref` wins; else the latest tag).
|
|
97
|
+
const targetRef = await resolveRef(options.ref);
|
|
98
|
+
// 3) IDEMPOTENCY FIRST: already at the resolved ref AND healthy → clean no-op.
|
|
99
|
+
if (state.ref === targetRef && (await isHealthy())) {
|
|
100
|
+
return {
|
|
101
|
+
output: [
|
|
102
|
+
`Already up to date (${targetRef}) — the container is healthy.`,
|
|
103
|
+
"Nothing to do. Pin a different ref with `--ref <tag|main>` to change it.",
|
|
104
|
+
].join("\n"),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// 4) Update, in the deploy dir: fetch tags → checkout the resolved ref.
|
|
108
|
+
await git(["-C", deployDir, "fetch", "--tags", "origin"]);
|
|
109
|
+
await checkoutRef(deployDir, targetRef);
|
|
110
|
+
// 5) Build the new image from the deploy dir.
|
|
111
|
+
await dockerInDir(["build", "-f", "docker/all-in-one.Dockerfile", "-t", `${CONTAINER_NAME}:${targetRef}`, "."], deployDir);
|
|
112
|
+
// 6) Read the EXISTING secrets back from the running container's env BEFORE
|
|
113
|
+
// removal (one `docker inspect`; ADR 0008 P4 put the master key there via
|
|
114
|
+
// `--env-file`). PREFER reusing both:
|
|
115
|
+
// - agent token: reuse, else mint fresh (clients re-paste).
|
|
116
|
+
// - master key: reuse, else OMIT (server resolves /data/secret.key) —
|
|
117
|
+
// NEVER mint a fresh key, which would orphan encrypted secrets.
|
|
118
|
+
const existingEnv = await readExistingContainerEnv();
|
|
119
|
+
const existingToken = existingEnv.get("LIBRARIAN_AGENT_TOKEN") ?? null;
|
|
120
|
+
const existingKey = existingEnv.get("LIBRARIAN_SECRET_KEY") ?? null;
|
|
121
|
+
const agentToken = existingToken ?? mintAgentToken();
|
|
122
|
+
const tokenIsFresh = existingToken === null;
|
|
123
|
+
// 7) Recreate — CONTAINER ONLY. `docker stop` then `docker rm <name>`:
|
|
124
|
+
// NEVER `-v`, NEVER `docker volume rm`. The named data volume persists.
|
|
125
|
+
await dockerStop();
|
|
126
|
+
await dockerRm();
|
|
127
|
+
// 8) Write the 0600 deploy env-file, then run the new container with the SAME
|
|
128
|
+
// host + data volume from deploy-state, secrets via `--env-file` (ADR 0008
|
|
129
|
+
// P4 — never inline on argv). The master key is the preserved one, or
|
|
130
|
+
// omitted (then the server resolves it from /data/secret.key).
|
|
131
|
+
const envFile = writeDeployEnvFile(deployDir, {
|
|
132
|
+
agentToken,
|
|
133
|
+
secretKey: existingKey ?? undefined,
|
|
134
|
+
host: state.host,
|
|
135
|
+
});
|
|
136
|
+
await dockerInDir(buildRunArgs({
|
|
137
|
+
host: state.host,
|
|
138
|
+
dataVolume: state.dataVolume,
|
|
139
|
+
tag: targetRef,
|
|
140
|
+
envFile,
|
|
141
|
+
}), deployDir);
|
|
142
|
+
// 9) Wait for health (rolls back with `docker rm -f` on failure — up's
|
|
143
|
+
// pattern). If this throws, deploy-state is NOT advanced below.
|
|
144
|
+
await waitForHealthy(options);
|
|
145
|
+
// 10) Apply pending data-dir migrations via the bundled admin CLI (S7 bundles
|
|
146
|
+
// the `the-librarian` binary into the image; this slice asserts the argv).
|
|
147
|
+
await dockerExecMigrate();
|
|
148
|
+
// 11) Persist the new ref/imageTag — host/dataVolume/containerName unchanged.
|
|
149
|
+
writeDeployState(deployDir, {
|
|
150
|
+
containerName: state.containerName,
|
|
151
|
+
host: state.host,
|
|
152
|
+
dataVolume: state.dataVolume,
|
|
153
|
+
ref: targetRef,
|
|
154
|
+
imageTag: `${CONTAINER_NAME}:${targetRef}`,
|
|
155
|
+
});
|
|
156
|
+
return { output: renderSuccess(targetRef, tokenIsFresh ? agentToken : null) };
|
|
157
|
+
}
|
|
158
|
+
// --- ref + health probes -------------------------------------------------
|
|
159
|
+
/** Resolve the target ref: an explicit `--ref` wins; else the latest tag. */
|
|
160
|
+
async function resolveRef(ref) {
|
|
161
|
+
if (ref && ref.trim().length > 0)
|
|
162
|
+
return ref.trim();
|
|
163
|
+
const latest = await fetchLatestVersion();
|
|
164
|
+
if (!latest) {
|
|
165
|
+
throw new UpdateError("Could not resolve the latest release tag from GitHub. " +
|
|
166
|
+
"Check your network, or pin a ref with `--ref <tag|main>`.");
|
|
167
|
+
}
|
|
168
|
+
// `fetchLatestVersion` strips the leading `v`; the tag we check out keeps it.
|
|
169
|
+
return `v${latest}`;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* True iff the container exists AND reports `healthy`. Used by the idempotency
|
|
173
|
+
* check — already at the ref but unhealthy means we still recreate. A failing
|
|
174
|
+
* `docker inspect` (no such container) → not healthy.
|
|
175
|
+
*/
|
|
176
|
+
async function isHealthy() {
|
|
177
|
+
const status = await run("docker", ["inspect", "--format", "{{.State.Status}}", CONTAINER_NAME]);
|
|
178
|
+
if (status.code !== 0 || status.stdout.trim() !== "running")
|
|
179
|
+
return false;
|
|
180
|
+
const health = await run("docker", [
|
|
181
|
+
"inspect",
|
|
182
|
+
"--format",
|
|
183
|
+
"{{.State.Health.Status}}",
|
|
184
|
+
CONTAINER_NAME,
|
|
185
|
+
]);
|
|
186
|
+
return health.code === 0 && health.stdout.trim() === "healthy";
|
|
187
|
+
}
|
|
188
|
+
// --- secret read-back (clients keep working; secrets stay decryptable) ----
|
|
189
|
+
/**
|
|
190
|
+
* Read the running container's env (`docker inspect .Config.Env`) into a map,
|
|
191
|
+
* BEFORE the container is removed, so an `update` reuses the existing secrets
|
|
192
|
+
* rather than silently breaking clients or orphaning encrypted settings. Returns
|
|
193
|
+
* an EMPTY map when the container is gone/unreadable (a non-zero inspect) — the
|
|
194
|
+
* caller then mints a fresh agent token and omits the master key. Empty values
|
|
195
|
+
* are dropped (treated as "not present"). The values are returned for reuse on
|
|
196
|
+
* recreate — never logged.
|
|
197
|
+
*/
|
|
198
|
+
async function readExistingContainerEnv() {
|
|
199
|
+
const env = new Map();
|
|
200
|
+
const result = await run("docker", [
|
|
201
|
+
"inspect",
|
|
202
|
+
"--format",
|
|
203
|
+
"{{range .Config.Env}}{{println .}}{{end}}",
|
|
204
|
+
CONTAINER_NAME,
|
|
205
|
+
]);
|
|
206
|
+
if (result.code !== 0)
|
|
207
|
+
return env;
|
|
208
|
+
for (const line of result.stdout.split("\n")) {
|
|
209
|
+
const trimmed = line.trim();
|
|
210
|
+
const eq = trimmed.indexOf("=");
|
|
211
|
+
if (eq <= 0)
|
|
212
|
+
continue; // no `=`, or a leading `=` (no name) → skip
|
|
213
|
+
const name = trimmed.slice(0, eq);
|
|
214
|
+
const value = trimmed.slice(eq + 1);
|
|
215
|
+
if (value.length > 0)
|
|
216
|
+
env.set(name, value);
|
|
217
|
+
}
|
|
218
|
+
return env;
|
|
219
|
+
}
|
|
220
|
+
// --- thin runner wrappers (teaching errors on a non-zero exit) ----------
|
|
221
|
+
/** Run a `git …` command; a non-zero exit is a teaching error. */
|
|
222
|
+
async function git(args) {
|
|
223
|
+
const result = await run("git", args);
|
|
224
|
+
failIfNonZero("git", args, result);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Check out `ref` in `dir` without letting a `--…`-shaped ref inject a git
|
|
228
|
+
* option (S-1). `git checkout` does NOT honor `--end-of-options` — it reads the
|
|
229
|
+
* marker itself as a pathspec (verified on git 2.43) — so we resolve the ref to
|
|
230
|
+
* a commit SHA with `git rev-parse` (which DOES honor it, the injection guard
|
|
231
|
+
* that works), then check out that SHA — a hex object id can't be an option.
|
|
232
|
+
*/
|
|
233
|
+
async function checkoutRef(dir, ref) {
|
|
234
|
+
const resolved = await run("git", [
|
|
235
|
+
"-C",
|
|
236
|
+
dir,
|
|
237
|
+
"rev-parse",
|
|
238
|
+
"--verify",
|
|
239
|
+
"--end-of-options",
|
|
240
|
+
`${ref}^{commit}`,
|
|
241
|
+
]);
|
|
242
|
+
failIfNonZero("git", ["-C", dir, "rev-parse", ref], resolved);
|
|
243
|
+
await git(["-C", dir, "checkout", resolved.stdout.trim()]);
|
|
244
|
+
}
|
|
245
|
+
/** Run a `docker …` command from the deploy dir; non-zero exit → teaching error. */
|
|
246
|
+
async function dockerInDir(args, cwd) {
|
|
247
|
+
const result = await run("docker", args, { cwd });
|
|
248
|
+
failIfNonZero("docker", args, result);
|
|
249
|
+
}
|
|
250
|
+
/** Stop the old container. A not-running/not-found container is fine (we recreate). */
|
|
251
|
+
async function dockerStop() {
|
|
252
|
+
const result = await run("docker", ["stop", CONTAINER_NAME]);
|
|
253
|
+
if (result.code === 0)
|
|
254
|
+
return;
|
|
255
|
+
if (isNotFound(result.stderr))
|
|
256
|
+
return; // nothing to stop — proceed to rm/run
|
|
257
|
+
failIfNonZero("docker", ["stop", CONTAINER_NAME], result);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Remove the old CONTAINER (NOT the volume). `docker rm <name>` — never `-v`,
|
|
261
|
+
* never `docker volume rm`. A not-found container is fine (we recreate).
|
|
262
|
+
*/
|
|
263
|
+
async function dockerRm() {
|
|
264
|
+
const result = await run("docker", ["rm", CONTAINER_NAME]);
|
|
265
|
+
if (result.code === 0)
|
|
266
|
+
return;
|
|
267
|
+
if (isNotFound(result.stderr))
|
|
268
|
+
return; // already gone — proceed to run
|
|
269
|
+
failIfNonZero("docker", ["rm", CONTAINER_NAME], result);
|
|
270
|
+
}
|
|
271
|
+
/** Apply pending data-dir migrations inside the (now-healthy) container. */
|
|
272
|
+
async function dockerExecMigrate() {
|
|
273
|
+
const args = ["exec", CONTAINER_NAME, CONTAINER_NAME, "migrate-data-dir"];
|
|
274
|
+
const result = await run("docker", args);
|
|
275
|
+
failIfNonZero("docker", args, result);
|
|
276
|
+
}
|
|
277
|
+
/** True when a docker error means the container simply isn't there. */
|
|
278
|
+
function isNotFound(stderr) {
|
|
279
|
+
return /no such container|no such object|is not running/i.test(stderr);
|
|
280
|
+
}
|
|
281
|
+
function failIfNonZero(cmd, args, result) {
|
|
282
|
+
if (result.code === 0)
|
|
283
|
+
return;
|
|
284
|
+
// Redact in case a non-zero docker step echoed a secret-shaped line.
|
|
285
|
+
const detail = redactSecrets(result.stderr.trim() || result.stdout.trim());
|
|
286
|
+
throw new UpdateError(`\`${cmd} ${args[0]}\` failed (exit ${result.code ?? "signal"})` +
|
|
287
|
+
(detail ? `:\n${detail}` : ".") +
|
|
288
|
+
"\n\nResolve the error above, then re-run `librarian server update`.");
|
|
289
|
+
}
|
|
290
|
+
// --- success report ------------------------------------------------------
|
|
291
|
+
/**
|
|
292
|
+
* The success report. `freshToken` is non-null ONLY when we had to mint a new
|
|
293
|
+
* agent token (the old container's token was unreadable) — then we surface it
|
|
294
|
+
* ONCE with a clients-must-update note. When the existing token was reused,
|
|
295
|
+
* nothing about the token is printed (clients keep working untouched).
|
|
296
|
+
*/
|
|
297
|
+
function renderSuccess(ref, freshToken) {
|
|
298
|
+
const lines = [
|
|
299
|
+
`Updated The Librarian server to ${ref} — the container is healthy.`,
|
|
300
|
+
"The data volume was preserved; pending data-dir migrations were applied.",
|
|
301
|
+
];
|
|
302
|
+
if (freshToken) {
|
|
303
|
+
lines.push("", "NOTE: the previous container's agent token could not be read back, so a " +
|
|
304
|
+
"FRESH agent token was minted. Existing clients must update their token:", ` Agent token: ${freshToken}`, "Paste it into `librarian install` / `librarian config --token <token>` on your clients.");
|
|
305
|
+
}
|
|
306
|
+
return lines.join("\n");
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/server/update.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,kFAAkF;AAClF,EAAE;AACF,gFAAgF;AAChF,2CAA2C;AAC3C,4EAA4E;AAC5E,2EAA2E;AAC3E,+EAA+E;AAC/E,4CAA4C;AAC5C,sDAAsD;AACtD,iFAAiF;AACjF,4EAA4E;AAC5E,qEAAqE;AACrE,2CAA2C;AAC3C,iFAAiF;AACjF,iFAAiF;AACjF,0EAA0E;AAC1E,gFAAgF;AAChF,yBAAyB;AACzB,+EAA+E;AAC/E,8EAA8E;AAC9E,0CAA0C;AAC1C,oEAAoE;AACpE,gFAAgF;AAChF,kFAAkF;AAClF,gFAAgF;AAChF,8EAA8E;AAC9E,+EAA+E;AAC/E,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,gFAAgF;AAChF,4EAA4E;AAC5E,sEAAsE;AACtE,2EAA2E;AAC3E,0CAA0C;AAC1C,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,8CAA8C;AAC9C,+EAA+E;AAC/E,8EAA8E;AAC9E,gFAAgF;AAChF,iFAAiF;AACjF,iFAAiF;AACjF,+CAA+C;AAC/C,EAAE;AACF,wEAAwE;AACxE,+EAA+E;AAC/E,iDAAiD;AACjD,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,+EAA+E;AAC/E,EAAE;AACF,6EAA6E;AAC7E,iFAAiF;AACjF,kCAAkC;AAElC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,GAAG,EAAkB,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,YAAY,EACZ,cAAc,EACd,cAAc,EACd,cAAc,EACd,kBAAkB,GACnB,MAAM,SAAS,CAAC;AAqBjB,yFAAyF;AACzF,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAOD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,UAAyB,EAAE;IACzD,sEAAsE;IACtE,MAAM,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAExE,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;IAEjF,8EAA8E;IAC9E,4EAA4E;IAC5E,MAAM,KAAK,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,WAAW,CACnB,4BAA4B,SAAS,mDAAmD;YACtF,iEAAiE;YACjE,4DAA4D,CAC/D,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAEhD,+EAA+E;IAC/E,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC,MAAM,SAAS,EAAE,CAAC,EAAE,CAAC;QACnD,OAAO;YACL,MAAM,EAAE;gBACN,uBAAuB,SAAS,+BAA+B;gBAC/D,0EAA0E;aAC3E,CAAC,IAAI,CAAC,IAAI,CAAC;SACb,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC1D,MAAM,WAAW,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAExC,8CAA8C;IAC9C,MAAM,WAAW,CACf,CAAC,OAAO,EAAE,IAAI,EAAE,8BAA8B,EAAE,IAAI,EAAE,GAAG,cAAc,IAAI,SAAS,EAAE,EAAE,GAAG,CAAC,EAC5F,SAAS,CACV,CAAC;IAEF,4EAA4E;IAC5E,6EAA6E;IAC7E,yCAAyC;IACzC,iEAAiE;IACjE,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,WAAW,GAAG,MAAM,wBAAwB,EAAE,CAAC;IACrD,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,IAAI,CAAC;IACvE,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,IAAI,CAAC;IACpE,MAAM,UAAU,GAAG,aAAa,IAAI,cAAc,EAAE,CAAC;IACrD,MAAM,YAAY,GAAG,aAAa,KAAK,IAAI,CAAC;IAE5C,uEAAuE;IACvE,2EAA2E;IAC3E,MAAM,UAAU,EAAE,CAAC;IACnB,MAAM,QAAQ,EAAE,CAAC;IAEjB,8EAA8E;IAC9E,8EAA8E;IAC9E,yEAAyE;IACzE,kEAAkE;IAClE,MAAM,OAAO,GAAG,kBAAkB,CAAC,SAAS,EAAE;QAC5C,UAAU;QACV,SAAS,EAAE,WAAW,IAAI,SAAS;QACnC,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC,CAAC;IACH,MAAM,WAAW,CACf,YAAY,CAAC;QACX,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,GAAG,EAAE,SAAS;QACd,OAAO;KACR,CAAC,EACF,SAAS,CACV,CAAC;IAEF,uEAAuE;IACvE,mEAAmE;IACnE,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;IAE9B,8EAA8E;IAC9E,+EAA+E;IAC/E,MAAM,iBAAiB,EAAE,CAAC;IAE1B,8EAA8E;IAC9E,gBAAgB,CAAC,SAAS,EAAE;QAC1B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,GAAG,EAAE,SAAS;QACd,QAAQ,EAAE,GAAG,cAAc,IAAI,SAAS,EAAE;KAC3C,CAAC,CAAC;IAEH,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;AAChF,CAAC;AAED,4EAA4E;AAE5E,6EAA6E;AAC7E,KAAK,UAAU,UAAU,CAAC,GAAuB;IAC/C,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,WAAW,CACnB,wDAAwD;YACtD,2DAA2D,CAC9D,CAAC;IACJ,CAAC;IACD,8EAA8E;IAC9E,OAAO,IAAI,MAAM,EAAE,CAAC;AACtB,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,SAAS;IACtB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,mBAAmB,EAAE,cAAc,CAAC,CAAC,CAAC;IACjG,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC1E,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE;QACjC,SAAS;QACT,UAAU;QACV,0BAA0B;QAC1B,cAAc;KACf,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,SAAS,CAAC;AACjE,CAAC;AAED,6EAA6E;AAE7E;;;;;;;;GAQG;AACH,KAAK,UAAU,wBAAwB;IACrC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE;QACjC,SAAS;QACT,UAAU;QACV,2CAA2C;QAC3C,cAAc;KACf,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,EAAE,IAAI,CAAC;YAAE,SAAS,CAAC,4CAA4C;QACnE,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACpC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,2EAA2E;AAE3E,kEAAkE;AAClE,KAAK,UAAU,GAAG,CAAC,IAAc;IAC/B,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACtC,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,WAAW,CAAC,GAAW,EAAE,GAAW;IACjD,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE;QAChC,IAAI;QACJ,GAAG;QACH,WAAW;QACX,UAAU;QACV,kBAAkB;QAClB,GAAG,GAAG,WAAW;KAClB,CAAC,CAAC;IACH,aAAa,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9D,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,oFAAoF;AACpF,KAAK,UAAU,WAAW,CAAC,IAAc,EAAE,GAAW;IACpD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAClD,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,uFAAuF;AACvF,KAAK,UAAU,UAAU;IACvB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IAC7D,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO;IAC9B,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC;QAAE,OAAO,CAAC,sCAAsC;IAC7E,aAAa,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,QAAQ;IACrB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC;IAC3D,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO;IAC9B,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC;QAAE,OAAO,CAAC,gCAAgC;IACvE,aAAa,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;AAC1D,CAAC;AAED,4EAA4E;AAC5E,KAAK,UAAU,iBAAiB;IAC9B,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACzC,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,uEAAuE;AACvE,SAAS,UAAU,CAAC,MAAc;IAChC,OAAO,kDAAkD,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,aAAa,CAAC,GAAW,EAAE,IAAc,EAAE,MAAiB;IACnE,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO;IAC9B,qEAAqE;IACrE,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3E,MAAM,IAAI,WAAW,CACnB,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,mBAAmB,MAAM,CAAC,IAAI,IAAI,QAAQ,GAAG;QAC9D,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QAC/B,qEAAqE,CACxE,CAAC;AACJ,CAAC;AAED,4EAA4E;AAE5E;;;;;GAKG;AACH,SAAS,aAAa,CAAC,GAAW,EAAE,UAAyB;IAC3D,MAAM,KAAK,GAAG;QACZ,mCAAmC,GAAG,8BAA8B;QACpE,0EAA0E;KAC3E,CAAC;IACF,IAAI,UAAU,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CACR,EAAE,EACF,0EAA0E;YACxE,yEAAyE,EAC3E,kBAAkB,UAAU,EAAE,EAC9B,yFAAyF,CAC1F,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
package/dist/status.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the monorepo's latest release version (the tag, `v`-stripped), or
|
|
3
|
+
* `null` on ANY failure (offline, timeout, non-2xx, unparseable). Injectable
|
|
4
|
+
* so tests never hit the network.
|
|
5
|
+
*/
|
|
6
|
+
export type LatestFetcher = () => Promise<string | null>;
|
|
7
|
+
/** Override the latest-version fetcher (tests inject a canned/failing one). */
|
|
8
|
+
export declare function setLatestFetcher(next: LatestFetcher): void;
|
|
9
|
+
/** Restore the default (network) latest-version fetcher (tests). */
|
|
10
|
+
export declare function resetLatestFetcher(): void;
|
|
11
|
+
/** The default fetcher, exported for direct use / testing of the GET itself. */
|
|
12
|
+
export declare const fetchLatestVersion: LatestFetcher;
|
|
13
|
+
/**
|
|
14
|
+
* Probe every harness in parallel and the latest version once, returning the
|
|
15
|
+
* rendered table. `home` is injectable for tests.
|
|
16
|
+
*/
|
|
17
|
+
export declare function status(home?: string): Promise<string>;
|
|
18
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAyBA;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AA0BzD,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAE1D;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,EAAE,aAAqC,CAAC;AAWvE;;;GAGG;AACH,wBAAsB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAY3D"}
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// `librarian status` — live-probe every harness and render a plain table.
|
|
2
|
+
//
|
|
3
|
+
// Columns: harness · installed · version · latest · update? · url.
|
|
4
|
+
// - installed/version come from each harness's live `detect()` (never a
|
|
5
|
+
// cached file — spec §9);
|
|
6
|
+
// - `latest` is the monorepo's latest GitHub release tag, fetched once and
|
|
7
|
+
// shared across rows;
|
|
8
|
+
// - `update?` is `yes`/`no`/`?` — `?` whenever the installed version or the
|
|
9
|
+
// latest version is unknown, so an offline run never lies;
|
|
10
|
+
// - `url` is the configured MCP URL (from `readConfig`), shown only when a
|
|
11
|
+
// harness is installed.
|
|
12
|
+
//
|
|
13
|
+
// The "latest" fetch is INJECTABLE (`setLatestFetcher`) and OFFLINE-TOLERANT:
|
|
14
|
+
// any failure resolves to `null`, latest renders `unknown`, and `update?`
|
|
15
|
+
// renders `?`. The CLI never crashes because GitHub was unreachable.
|
|
16
|
+
import { readConfig } from "./config.js";
|
|
17
|
+
import { allHarnesses } from "./harnesses/index.js";
|
|
18
|
+
import { isBehind } from "./semver.js";
|
|
19
|
+
/** GitHub releases API for the monorepo (latest published release). */
|
|
20
|
+
const LATEST_RELEASE_URL = "https://api.github.com/repos/JimJafar/the-librarian/releases/latest";
|
|
21
|
+
/** A short timeout so an unreachable network never hangs `status`. */
|
|
22
|
+
const FETCH_TIMEOUT_MS = 3000;
|
|
23
|
+
const defaultLatestFetcher = async () => {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(LATEST_RELEASE_URL, {
|
|
28
|
+
redirect: "error",
|
|
29
|
+
signal: controller.signal,
|
|
30
|
+
headers: { Accept: "application/vnd.github+json" },
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok)
|
|
33
|
+
return null;
|
|
34
|
+
const body = (await res.json());
|
|
35
|
+
const tag = typeof body.tag_name === "string" ? body.tag_name.trim() : "";
|
|
36
|
+
if (!tag)
|
|
37
|
+
return null;
|
|
38
|
+
return tag.replace(/^v/i, "");
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Offline, DNS failure, timeout, redirect, bad JSON — all → unknown.
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
let latestFetcher = defaultLatestFetcher;
|
|
49
|
+
/** Override the latest-version fetcher (tests inject a canned/failing one). */
|
|
50
|
+
export function setLatestFetcher(next) {
|
|
51
|
+
latestFetcher = next;
|
|
52
|
+
}
|
|
53
|
+
/** Restore the default (network) latest-version fetcher (tests). */
|
|
54
|
+
export function resetLatestFetcher() {
|
|
55
|
+
latestFetcher = defaultLatestFetcher;
|
|
56
|
+
}
|
|
57
|
+
/** The default fetcher, exported for direct use / testing of the GET itself. */
|
|
58
|
+
export const fetchLatestVersion = () => latestFetcher();
|
|
59
|
+
/**
|
|
60
|
+
* Probe every harness in parallel and the latest version once, returning the
|
|
61
|
+
* rendered table. `home` is injectable for tests.
|
|
62
|
+
*/
|
|
63
|
+
export async function status(home) {
|
|
64
|
+
const [detections, latest] = await Promise.all([
|
|
65
|
+
Promise.all(allHarnesses.map((h) => h.detect())),
|
|
66
|
+
fetchLatestVersion(),
|
|
67
|
+
]);
|
|
68
|
+
const cfg = readConfig(home);
|
|
69
|
+
const mcpUrl = cfg?.mcpUrl ?? "";
|
|
70
|
+
const rows = allHarnesses.map((h, i) => toRow(h, detections[i] ?? { installed: false }, latest, mcpUrl));
|
|
71
|
+
return renderTable(rows, latest);
|
|
72
|
+
}
|
|
73
|
+
function toRow(harness, detect, latest, mcpUrl) {
|
|
74
|
+
const version = detect.version ?? "";
|
|
75
|
+
const update = !detect.installed
|
|
76
|
+
? "no"
|
|
77
|
+
: !version || !latest
|
|
78
|
+
? "?"
|
|
79
|
+
: isBehind(version, latest)
|
|
80
|
+
? "yes"
|
|
81
|
+
: "no";
|
|
82
|
+
return {
|
|
83
|
+
harness: harness.displayName,
|
|
84
|
+
installed: detect.installed,
|
|
85
|
+
version: detect.installed ? version || "(unknown)" : "—",
|
|
86
|
+
latest: latest ?? "unknown",
|
|
87
|
+
update,
|
|
88
|
+
url: detect.installed ? mcpUrl || "—" : "—",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// --- plain-text table rendering (aligned, no table lib) ------------------
|
|
92
|
+
const HEADERS = ["HARNESS", "INSTALLED", "VERSION", "LATEST", "UPDATE?", "URL"];
|
|
93
|
+
function renderTable(rows, latest) {
|
|
94
|
+
const cells = rows.map((r) => [
|
|
95
|
+
r.harness,
|
|
96
|
+
r.installed ? "yes" : "no",
|
|
97
|
+
r.version,
|
|
98
|
+
r.latest,
|
|
99
|
+
r.update,
|
|
100
|
+
r.url,
|
|
101
|
+
]);
|
|
102
|
+
const widths = HEADERS.map((h, col) => Math.max(h.length, ...cells.map((row) => (row[col] ?? "").length)));
|
|
103
|
+
const line = (parts) => parts
|
|
104
|
+
.map((part, col) => part.padEnd(widths[col] ?? part.length))
|
|
105
|
+
.join(" ")
|
|
106
|
+
.trimEnd();
|
|
107
|
+
const out = [line(HEADERS), ...cells.map(line)];
|
|
108
|
+
if (latest === null) {
|
|
109
|
+
out.push("", "latest version unknown (could not reach GitHub) — update column shows ?");
|
|
110
|
+
}
|
|
111
|
+
return out.join("\n");
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAUA,wBAAgB,UAAU,IAAI,MAAM,CASnC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Resolve the CLI's own version from its package.json.
|
|
2
|
+
//
|
|
3
|
+
// At runtime the built bin lives at `dist/bin.js`, so package.json is one
|
|
4
|
+
// directory up. Reading it (rather than baking the version in) keeps the
|
|
5
|
+
// reported version in lockstep with the published package — the phase gate
|
|
6
|
+
// owns the actual version bump.
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
export function cliVersion() {
|
|
10
|
+
try {
|
|
11
|
+
const pkgUrl = new URL("../package.json", import.meta.url);
|
|
12
|
+
const raw = fs.readFileSync(fileURLToPath(pkgUrl), "utf8");
|
|
13
|
+
const parsed = JSON.parse(raw);
|
|
14
|
+
return typeof parsed.version === "string" ? parsed.version : "0.0.0";
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return "0.0.0";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,EAAE;AACF,0EAA0E;AAC1E,yEAAyE;AACzE,2EAA2E;AAC3E,gCAAgC;AAEhC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3D,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;QAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA0B,CAAC;QACxD,OAAO,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@the-librarian/cli",
|
|
3
|
+
"version": "1.0.0-rc.21",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"description": "Cross-harness installer CLI for The Librarian. Bin: librarian.",
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"bin": {
|
|
13
|
+
"librarian": "dist/bin.js"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.10.5",
|
|
26
|
+
"typescript": "^5.7.2",
|
|
27
|
+
"vitest": "^2.1.8"
|
|
28
|
+
},
|
|
29
|
+
"license": "Apache-2.0",
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.json",
|
|
32
|
+
"test:vitest": "vitest run",
|
|
33
|
+
"test": "pnpm run build && pnpm run test:vitest",
|
|
34
|
+
"typecheck": "tsc --noEmit"
|
|
35
|
+
}
|
|
36
|
+
}
|