@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
package/dist/semver.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// A tiny, dependency-light semver-ish comparison.
|
|
2
|
+
//
|
|
3
|
+
// We only need "is the installed version behind the latest release?" — not a
|
|
4
|
+
// full spec-compliant semver implementation. This tolerates the prerelease
|
|
5
|
+
// shapes the monorepo actually ships (`1.0.0`, `1.0.0-rc.2`, `v1.2.3`) and
|
|
6
|
+
// degrades gracefully on anything it can't parse (returns 0 → "equal", so an
|
|
7
|
+
// unparseable version is never reported as an update).
|
|
8
|
+
//
|
|
9
|
+
// Precedence rules (subset of semver §11):
|
|
10
|
+
// - numeric release fields compare numerically (1.2.0 < 1.10.0);
|
|
11
|
+
// - a version WITH a prerelease has LOWER precedence than the same version
|
|
12
|
+
// WITHOUT one (1.0.0-rc.1 < 1.0.0);
|
|
13
|
+
// - prerelease identifiers compare left-to-right; numeric identifiers
|
|
14
|
+
// compare numerically, others lexically, numeric < non-numeric.
|
|
15
|
+
/** Strip a leading `v` and any build-metadata (`+…`) we don't compare on. */
|
|
16
|
+
function parse(version) {
|
|
17
|
+
const trimmed = version.trim().replace(/^v/i, "");
|
|
18
|
+
if (!trimmed)
|
|
19
|
+
return null;
|
|
20
|
+
const [core, ...preParts] = trimmed.split("-");
|
|
21
|
+
// Drop build metadata (`+sha`) from the prerelease tail and the core.
|
|
22
|
+
const prerelease = preParts.join("-").split("+")[0] ?? "";
|
|
23
|
+
const coreNoBuild = (core ?? "").split("+")[0] ?? "";
|
|
24
|
+
const release = coreNoBuild.split(".").map((n) => Number.parseInt(n, 10));
|
|
25
|
+
if (release.length === 0 || release.some((n) => Number.isNaN(n)))
|
|
26
|
+
return null;
|
|
27
|
+
return {
|
|
28
|
+
release,
|
|
29
|
+
prerelease: prerelease.length > 0 ? prerelease.split(".") : [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Compare two version strings. Returns <0 if `a` precedes `b`, >0 if `a`
|
|
34
|
+
* follows `b`, 0 if equal or either is unparseable (so an unknown version
|
|
35
|
+
* never reads as "behind").
|
|
36
|
+
*/
|
|
37
|
+
export function compareVersions(a, b) {
|
|
38
|
+
const pa = parse(a);
|
|
39
|
+
const pb = parse(b);
|
|
40
|
+
if (!pa || !pb)
|
|
41
|
+
return 0;
|
|
42
|
+
const len = Math.max(pa.release.length, pb.release.length);
|
|
43
|
+
for (let i = 0; i < len; i += 1) {
|
|
44
|
+
const da = pa.release[i] ?? 0;
|
|
45
|
+
const db = pb.release[i] ?? 0;
|
|
46
|
+
if (da !== db)
|
|
47
|
+
return da < db ? -1 : 1;
|
|
48
|
+
}
|
|
49
|
+
// Equal release fields → a version with a prerelease is LOWER than one
|
|
50
|
+
// without (1.0.0-rc.1 < 1.0.0).
|
|
51
|
+
const aPre = pa.prerelease.length > 0;
|
|
52
|
+
const bPre = pb.prerelease.length > 0;
|
|
53
|
+
if (aPre && !bPre)
|
|
54
|
+
return -1;
|
|
55
|
+
if (!aPre && bPre)
|
|
56
|
+
return 1;
|
|
57
|
+
if (!aPre && !bPre)
|
|
58
|
+
return 0;
|
|
59
|
+
return comparePrerelease(pa.prerelease, pb.prerelease);
|
|
60
|
+
}
|
|
61
|
+
function comparePrerelease(a, b) {
|
|
62
|
+
const len = Math.max(a.length, b.length);
|
|
63
|
+
for (let i = 0; i < len; i += 1) {
|
|
64
|
+
// A longer prerelease list (with all-else-equal prefixes) has higher
|
|
65
|
+
// precedence (rc.1 < rc.1.1).
|
|
66
|
+
if (i >= a.length)
|
|
67
|
+
return -1;
|
|
68
|
+
if (i >= b.length)
|
|
69
|
+
return 1;
|
|
70
|
+
const ai = a[i] ?? "";
|
|
71
|
+
const bi = b[i] ?? "";
|
|
72
|
+
const an = /^\d+$/.test(ai);
|
|
73
|
+
const bn = /^\d+$/.test(bi);
|
|
74
|
+
if (an && bn) {
|
|
75
|
+
const na = Number.parseInt(ai, 10);
|
|
76
|
+
const nb = Number.parseInt(bi, 10);
|
|
77
|
+
if (na !== nb)
|
|
78
|
+
return na < nb ? -1 : 1;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (an !== bn)
|
|
82
|
+
return an ? -1 : 1; // numeric identifiers rank below alphanumeric
|
|
83
|
+
if (ai !== bi)
|
|
84
|
+
return ai < bi ? -1 : 1;
|
|
85
|
+
}
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* True iff `installed` is strictly behind `latest`. Either side being unknown
|
|
90
|
+
* (empty / unparseable) yields `false` — the caller renders `?`, never a
|
|
91
|
+
* false "update available".
|
|
92
|
+
*/
|
|
93
|
+
export function isBehind(installed, latest) {
|
|
94
|
+
if (!installed || !latest)
|
|
95
|
+
return false;
|
|
96
|
+
return compareVersions(installed, latest) < 0;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=semver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semver.js","sourceRoot":"","sources":["../src/semver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** The curated admin verbs exposed under `server admin` (spec §7). */
|
|
2
|
+
export declare const ADMIN_VERBS: readonly ["backup", "restore", "auth", "rebuild"];
|
|
3
|
+
export type AdminVerb = (typeof ADMIN_VERBS)[number];
|
|
4
|
+
/** A teaching error from `admin`; the runtime renders `.message` as one stderr line. */
|
|
5
|
+
export declare class AdminError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
export interface AdminOptions {
|
|
9
|
+
/** Platform for preflight's daemon hint. Default `process.platform`. */
|
|
10
|
+
platform?: NodeJS.Platform | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Whether the run is interactive (a TTY is attached). When true we add
|
|
13
|
+
* `docker exec -it` so the in-container CLI can prompt (e.g. `restore`'s
|
|
14
|
+
* `--secret-key` prompt, `auth reset-password`). A non-interactive run omits
|
|
15
|
+
* `-it` so it works in scripts/CI without a TTY.
|
|
16
|
+
*/
|
|
17
|
+
interactive?: boolean | undefined;
|
|
18
|
+
}
|
|
19
|
+
export interface AdminResult {
|
|
20
|
+
/** Human-readable report for stdout (the in-container CLI's own output). */
|
|
21
|
+
output: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run `server admin <verb> [args…]`. Validates the verb is curated, preflights
|
|
25
|
+
* docker, confirms the container is running, then runs
|
|
26
|
+
* `docker exec [-it] the-librarian the-librarian <verb> [args…]` — passing the
|
|
27
|
+
* args through VERBATIM. A rejected verb / down container is a teaching
|
|
28
|
+
* `AdminError`; no `docker exec` runs in either case.
|
|
29
|
+
*/
|
|
30
|
+
export declare function runAdmin(argv: readonly string[], options?: AdminOptions): Promise<AdminResult>;
|
|
31
|
+
//# sourceMappingURL=admin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/server/admin.ts"],"names":[],"mappings":"AA+BA,sEAAsE;AACtE,eAAO,MAAM,WAAW,mDAAoD,CAAC;AAE7E,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,wFAAwF;AACxF,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;IACvC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;CAChB;AAyCD;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,WAAW,CAAC,CAqCtB"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// `librarian server admin <verb> [args…]` — run a curated subset of the
|
|
2
|
+
// folded-in admin CLI (`@librarian/cli`, the `the-librarian` binary) INSIDE the
|
|
3
|
+
// running all-in-one container. Spec §7 ("Folded-in admin").
|
|
4
|
+
//
|
|
5
|
+
// One uniform mechanism: `docker exec the-librarian the-librarian <verb> [args…]`.
|
|
6
|
+
// Running it in the container (not on the host) gives the admin CLI direct
|
|
7
|
+
// access to the data dir + the store/settings the server already uses — which is
|
|
8
|
+
// exactly what lets `auth` recovery bypass a locked dashboard and lets
|
|
9
|
+
// `backup`/`restore` reuse the store rather than re-implement it. (The CLI is
|
|
10
|
+
// bundled into the runtime image in the same slice — see all-in-one.Dockerfile.)
|
|
11
|
+
//
|
|
12
|
+
// CURATED set (spec §7): only `backup | restore | auth | rebuild` are exposed.
|
|
13
|
+
// `seed` / `migrate-data-dir` / `export` / `handoffs` exist in @librarian/cli but
|
|
14
|
+
// are deliberately NOT folded in here (seed is dev-only; migrate-data-dir runs
|
|
15
|
+
// automatically on `update`; export + handoffs are power-user/dashboard surfaces).
|
|
16
|
+
// They stay reachable via a raw `docker exec` if ever truly needed.
|
|
17
|
+
//
|
|
18
|
+
// Args after the verb are passed through VERBATIM, in order — this module never
|
|
19
|
+
// parses, reorders, or rewrites them (so e.g. `auth reset-password --user x` and
|
|
20
|
+
// `restore --secret-key …` reach the CLI exactly as typed). A bearer/secret may
|
|
21
|
+
// ride through as an arg the CLI itself reads; this module never logs or persists
|
|
22
|
+
// it (AGENTS.md: privacy beats convenience).
|
|
23
|
+
//
|
|
24
|
+
// Everything goes through the injectable `docker.ts` seam, so tests assert the
|
|
25
|
+
// exact argv without a real daemon or container.
|
|
26
|
+
import { run } from "./docker.js";
|
|
27
|
+
import { preflight } from "./preflight.js";
|
|
28
|
+
import { redactSecrets } from "./redact.js";
|
|
29
|
+
import { CONTAINER_NAME } from "./up.js";
|
|
30
|
+
/** The curated admin verbs exposed under `server admin` (spec §7). */
|
|
31
|
+
export const ADMIN_VERBS = ["backup", "restore", "auth", "rebuild"];
|
|
32
|
+
/** A teaching error from `admin`; the runtime renders `.message` as one stderr line. */
|
|
33
|
+
export class AdminError extends Error {
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "AdminError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** True iff `verb` is one of the curated admin verbs. */
|
|
40
|
+
function isAdminVerb(verb) {
|
|
41
|
+
return ADMIN_VERBS.includes(verb);
|
|
42
|
+
}
|
|
43
|
+
/** The teaching message for an unknown/dropped verb — names what IS available. */
|
|
44
|
+
function unknownVerbMessage(verb) {
|
|
45
|
+
const allowed = ADMIN_VERBS.join(" | ");
|
|
46
|
+
const lead = verb === undefined
|
|
47
|
+
? "`librarian server admin` needs a subcommand."
|
|
48
|
+
: `\`${verb}\` is not an \`admin\` subcommand.`;
|
|
49
|
+
return [
|
|
50
|
+
lead,
|
|
51
|
+
"",
|
|
52
|
+
`Allowed: ${allowed}.`,
|
|
53
|
+
"",
|
|
54
|
+
"Not exposed here (by design): `seed` (dev-only), `migrate-data-dir` (runs",
|
|
55
|
+
"automatically on `librarian server update`), `export`, and `handoffs`",
|
|
56
|
+
"(power-user / dashboard surfaces).",
|
|
57
|
+
].join("\n");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Confirm the container is up before exec'ing into it. `docker inspect` reports
|
|
61
|
+
* `running` when it is; any other status (or a failed inspect → absent) is a
|
|
62
|
+
* teaching error pointing at `server up`, with NO `docker exec`.
|
|
63
|
+
*/
|
|
64
|
+
async function assertContainerRunning() {
|
|
65
|
+
const result = await run("docker", ["inspect", "--format", "{{.State.Status}}", CONTAINER_NAME]);
|
|
66
|
+
const status = result.code === 0 ? result.stdout.trim() : null;
|
|
67
|
+
if (status !== "running") {
|
|
68
|
+
throw new AdminError("The server isn't running — run `librarian server up` first, then re-run " +
|
|
69
|
+
"this admin command.");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Run `server admin <verb> [args…]`. Validates the verb is curated, preflights
|
|
74
|
+
* docker, confirms the container is running, then runs
|
|
75
|
+
* `docker exec [-it] the-librarian the-librarian <verb> [args…]` — passing the
|
|
76
|
+
* args through VERBATIM. A rejected verb / down container is a teaching
|
|
77
|
+
* `AdminError`; no `docker exec` runs in either case.
|
|
78
|
+
*/
|
|
79
|
+
export async function runAdmin(argv, options = {}) {
|
|
80
|
+
const [verb, ...rest] = argv;
|
|
81
|
+
// Validate FIRST — a rejected verb must never reach a `docker exec` (and we
|
|
82
|
+
// don't even need a daemon to tell the user the verb is wrong).
|
|
83
|
+
if (verb === undefined || !isAdminVerb(verb)) {
|
|
84
|
+
throw new AdminError(unknownVerbMessage(verb));
|
|
85
|
+
}
|
|
86
|
+
await preflight(options.platform ? { platform: options.platform } : {});
|
|
87
|
+
await assertContainerRunning();
|
|
88
|
+
// `docker exec [-it] the-librarian the-librarian <verb> [args…]` — args verbatim.
|
|
89
|
+
const execArgs = [
|
|
90
|
+
"exec",
|
|
91
|
+
...(options.interactive ? ["-it"] : []),
|
|
92
|
+
CONTAINER_NAME,
|
|
93
|
+
"the-librarian",
|
|
94
|
+
verb,
|
|
95
|
+
...rest,
|
|
96
|
+
];
|
|
97
|
+
const result = await run("docker", execArgs);
|
|
98
|
+
if (result.code !== 0) {
|
|
99
|
+
// `admin` forwards secret-bearing args (`restore --secret-key …`, `auth …`),
|
|
100
|
+
// so a failed in-container step can echo them back. Redact before surfacing,
|
|
101
|
+
// identically to `update`/`up` (I-3) — the shared helper is the choke point.
|
|
102
|
+
const detail = redactSecrets(result.stderr.trim() || result.stdout.trim());
|
|
103
|
+
throw new AdminError(`\`the-librarian ${verb}\` failed in the container (exit ${result.code ?? "signal"})` +
|
|
104
|
+
(detail ? `:\n${detail}` : ".") +
|
|
105
|
+
`\n\nResolve the error above, then re-run \`librarian server admin ${verb}\`.`);
|
|
106
|
+
}
|
|
107
|
+
// The in-container CLI's own output is the report (stdout, then any stderr).
|
|
108
|
+
return { output: result.stdout + result.stderr };
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../../src/server/admin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** The systemd unit file name (single instance per host). */
|
|
2
|
+
export declare const UNIT_NAME = "the-librarian.service";
|
|
3
|
+
/** The absolute path the unit is installed at (the system unit, via sudo). */
|
|
4
|
+
export declare function unitPath(): string;
|
|
5
|
+
export interface BootOptions {
|
|
6
|
+
/** Platform — gates the macOS deferral. Default `process.platform`. */
|
|
7
|
+
platform?: NodeJS.Platform | undefined;
|
|
8
|
+
}
|
|
9
|
+
/** A teaching error from boot ops; the runtime renders `.message` as one stderr line. */
|
|
10
|
+
export declare class BootError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
export interface BootResult {
|
|
14
|
+
/** Human-readable report for stdout. */
|
|
15
|
+
output: string;
|
|
16
|
+
}
|
|
17
|
+
/** Inputs to the pure unit generator (only a docker path — NEVER a secret). */
|
|
18
|
+
export interface UnitInput {
|
|
19
|
+
/** The absolute path to the `docker` binary used in ExecStart/ExecStop. */
|
|
20
|
+
dockerPath: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Generate the systemd unit text. PURE + unit-tested. The headline invariant:
|
|
24
|
+
* it contains NO secret — no agent token, no `LIBRARIAN_AGENT_TOKEN`, no
|
|
25
|
+
* `docker run` / `-e` env injection — only a `docker start --attach` /
|
|
26
|
+
* `docker stop` of the EXISTING named container. The container already carries
|
|
27
|
+
* its env (incl. the token) in Docker's own state, so referencing it by name
|
|
28
|
+
* needs no secret in this file.
|
|
29
|
+
*/
|
|
30
|
+
export declare function generateUnit(input: UnitInput): string;
|
|
31
|
+
/** The one-line macOS-deferred notice (shared by enable/disable + `up`). */
|
|
32
|
+
export declare const MACOS_NOTICE: string;
|
|
33
|
+
/**
|
|
34
|
+
* Enable boot persistence. On Linux: write the unit to `/etc/systemd/system`
|
|
35
|
+
* (root, so temp-file + `sudo cp` + `sudo chmod 644`), `sudo systemctl
|
|
36
|
+
* daemon-reload`, then `sudo systemctl enable --now the-librarian.service`.
|
|
37
|
+
* Idempotent — re-running rewrites the SAME unit path and re-enables, never
|
|
38
|
+
* duplicating. On macOS: print the deferred notice and skip cleanly (NOT an
|
|
39
|
+
* error).
|
|
40
|
+
*/
|
|
41
|
+
export declare function enableBoot(options?: BootOptions): Promise<BootResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Disable boot persistence. On Linux: `sudo systemctl disable --now
|
|
44
|
+
* the-librarian.service`, remove the unit file (`sudo rm -f`), then `sudo
|
|
45
|
+
* systemctl daemon-reload`. Safe if already disabled/absent — a non-zero
|
|
46
|
+
* `disable` (unit not loaded) becomes a friendly teaching message, never a
|
|
47
|
+
* crash; the `rm -f` + reload still run so a stale unit file is cleaned up. On
|
|
48
|
+
* macOS: print the deferred notice and skip cleanly.
|
|
49
|
+
*/
|
|
50
|
+
export declare function disableBoot(options?: BootOptions): Promise<BootResult>;
|
|
51
|
+
//# sourceMappingURL=boot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boot.d.ts","sourceRoot":"","sources":["../../src/server/boot.ts"],"names":[],"mappings":"AAoCA,6DAA6D;AAC7D,eAAO,MAAM,SAAS,0BAA0B,CAAC;AAKjD,8EAA8E;AAC9E,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAKD,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;CACxC;AAED,yFAAyF;AACzF,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,UAAU;IACzB,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CA+BrD;AAED,4EAA4E;AAC5E,eAAO,MAAM,YAAY,QAGsC,CAAC;AAkChE;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA2B/E;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA+BhF"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// `librarian server enable-boot` / `disable-boot` — Linux systemd boot
|
|
2
|
+
// persistence for the all-in-one container. (macOS launchd is deferred — §9.)
|
|
3
|
+
//
|
|
4
|
+
// SECURITY — the whole point of this slice. The systemd unit MUST NOT contain
|
|
5
|
+
// any secret. We do NOT bake a `docker run -e LIBRARIAN_AGENT_TOKEN=…` into
|
|
6
|
+
// `ExecStart`: that would write the agent token into a world-readable
|
|
7
|
+
// `/etc/systemd/system/*.service` file — a leak. Instead the unit references the
|
|
8
|
+
// EXISTING named container (created by `up`/`update`, whose env — including the
|
|
9
|
+
// token — lives in Docker's own container state, not a file we manage):
|
|
10
|
+
//
|
|
11
|
+
// ExecStart=/usr/bin/docker start --attach the-librarian
|
|
12
|
+
// ExecStop=/usr/bin/docker stop the-librarian
|
|
13
|
+
//
|
|
14
|
+
// `docker start --attach` re-uses the container exactly as `up` created it
|
|
15
|
+
// (same name, same env, same volume) and stays in the foreground so systemd's
|
|
16
|
+
// `Type=simple` supervision + `Restart=always` work. This survives a `server
|
|
17
|
+
// update` (which recreates the SAME container name) and reboots, with NO secret
|
|
18
|
+
// anywhere on disk that we write.
|
|
19
|
+
//
|
|
20
|
+
// We default to a SYSTEM unit (`/etc/systemd/system/…`, via `sudo`) because it
|
|
21
|
+
// boots reliably without a login session. (A `--user` unit under
|
|
22
|
+
// `~/.config/systemd/user/` would need `loginctl enable-linger` to survive
|
|
23
|
+
// logout — out of scope for v1; deliberately not built.)
|
|
24
|
+
//
|
|
25
|
+
// Writing to `/etc/systemd/system` needs root, so the unit is written to a
|
|
26
|
+
// temp file first (NON-SECRET content) and then `sudo cp`'d into place — every
|
|
27
|
+
// privileged step (`sudo cp`/`chmod`/`rm`, `sudo systemctl …`) routes through
|
|
28
|
+
// the injectable `docker.ts` runner so tests assert the exact argv and never
|
|
29
|
+
// touch real systemd, sudo, or docker.
|
|
30
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
31
|
+
import { tmpdir } from "node:os";
|
|
32
|
+
import path from "node:path";
|
|
33
|
+
import { run, which } from "./docker.js";
|
|
34
|
+
import { CONTAINER_NAME } from "./up.js";
|
|
35
|
+
/** The systemd unit file name (single instance per host). */
|
|
36
|
+
export const UNIT_NAME = "the-librarian.service";
|
|
37
|
+
/** The system-unit directory (a system unit boots without a login session). */
|
|
38
|
+
const SYSTEMD_SYSTEM_DIR = "/etc/systemd/system";
|
|
39
|
+
/** The absolute path the unit is installed at (the system unit, via sudo). */
|
|
40
|
+
export function unitPath() {
|
|
41
|
+
return path.join(SYSTEMD_SYSTEM_DIR, UNIT_NAME);
|
|
42
|
+
}
|
|
43
|
+
/** Fallback docker path if `which docker` can't be resolved (well-known location). */
|
|
44
|
+
const DEFAULT_DOCKER_PATH = "/usr/bin/docker";
|
|
45
|
+
/** A teaching error from boot ops; the runtime renders `.message` as one stderr line. */
|
|
46
|
+
export class BootError extends Error {
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "BootError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate the systemd unit text. PURE + unit-tested. The headline invariant:
|
|
54
|
+
* it contains NO secret — no agent token, no `LIBRARIAN_AGENT_TOKEN`, no
|
|
55
|
+
* `docker run` / `-e` env injection — only a `docker start --attach` /
|
|
56
|
+
* `docker stop` of the EXISTING named container. The container already carries
|
|
57
|
+
* its env (incl. the token) in Docker's own state, so referencing it by name
|
|
58
|
+
* needs no secret in this file.
|
|
59
|
+
*/
|
|
60
|
+
export function generateUnit(input) {
|
|
61
|
+
const { dockerPath } = input;
|
|
62
|
+
return [
|
|
63
|
+
"[Unit]",
|
|
64
|
+
"Description=The Librarian (all-in-one)",
|
|
65
|
+
"After=docker.service",
|
|
66
|
+
"Requires=docker.service",
|
|
67
|
+
"",
|
|
68
|
+
"[Service]",
|
|
69
|
+
"Type=simple",
|
|
70
|
+
// SC5 (ADR 0008) — env-file POINTER, deliberately a comment, not a directive.
|
|
71
|
+
// `docker start --attach` re-uses the container's env exactly as `up`/`update`
|
|
72
|
+
// baked it from <deployDir>/deploy.env (0600); it does NOT read systemd's
|
|
73
|
+
// `Environment*`. A live `EnvironmentFile=` here would be silently ignored —
|
|
74
|
+
// misleading config — so this stays a comment that just tells a reader where
|
|
75
|
+
// the credentials live, with no value of any kind written into this file.
|
|
76
|
+
`# Container env (incl. credentials) is baked into the '${CONTAINER_NAME}' container`,
|
|
77
|
+
"# at `librarian server up`/`update`, sourced from the 0600 deploy.env in the",
|
|
78
|
+
"# deploy dir (default ~/.librarian/server). `docker start` re-uses that frozen",
|
|
79
|
+
"# env, so nothing sensitive is (or should be) written into this unit file.",
|
|
80
|
+
// Reference the EXISTING named container — NO `docker run`, NO `-e <secret>`.
|
|
81
|
+
// `--attach` keeps it in the foreground so Type=simple supervision works.
|
|
82
|
+
`ExecStart=${dockerPath} start --attach ${CONTAINER_NAME}`,
|
|
83
|
+
`ExecStop=${dockerPath} stop ${CONTAINER_NAME}`,
|
|
84
|
+
"Restart=always",
|
|
85
|
+
"RestartSec=5",
|
|
86
|
+
"",
|
|
87
|
+
"[Install]",
|
|
88
|
+
"WantedBy=multi-user.target",
|
|
89
|
+
"",
|
|
90
|
+
].join("\n");
|
|
91
|
+
}
|
|
92
|
+
/** The one-line macOS-deferred notice (shared by enable/disable + `up`). */
|
|
93
|
+
export const MACOS_NOTICE = "Boot persistence is Linux-only for now (launchd support is deferred). " +
|
|
94
|
+
"Skipping — on macOS, start the server manually with `librarian server up`, " +
|
|
95
|
+
"or use Docker Desktop's 'start on login' for the container.";
|
|
96
|
+
/** True iff boot persistence is unsupported on this platform (macOS for now). */
|
|
97
|
+
function isUnsupportedPlatform(platform) {
|
|
98
|
+
return platform === "darwin";
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Boot-specific preflight: `docker` must be on PATH (the unit runs `docker
|
|
102
|
+
* start` at boot). Unlike the container-op preflight this does NOT require git
|
|
103
|
+
* or a reachable daemon — enabling/disabling a unit file needs neither. A
|
|
104
|
+
* missing docker is a teaching `BootError`.
|
|
105
|
+
*/
|
|
106
|
+
async function requireDocker() {
|
|
107
|
+
if ((await which("docker")) === null) {
|
|
108
|
+
throw new BootError("Docker is required but was not found on your PATH. " +
|
|
109
|
+
"The boot unit runs `docker start` — install Docker Engine (Linux) " +
|
|
110
|
+
"— see https://docs.docker.com/get-docker/ — then re-run this command.");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Resolve the absolute `docker` path for the unit's ExecStart. Goes through the
|
|
115
|
+
* injectable runner (`which docker`); falls back to the well-known
|
|
116
|
+
* `/usr/bin/docker` so a non-standard `which` output never breaks unit
|
|
117
|
+
* generation. The path is non-secret, so a fallback is safe.
|
|
118
|
+
*/
|
|
119
|
+
async function resolveDockerPath() {
|
|
120
|
+
const resolved = await which("docker");
|
|
121
|
+
return resolved && resolved.trim().length > 0 ? resolved.trim() : DEFAULT_DOCKER_PATH;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Enable boot persistence. On Linux: write the unit to `/etc/systemd/system`
|
|
125
|
+
* (root, so temp-file + `sudo cp` + `sudo chmod 644`), `sudo systemctl
|
|
126
|
+
* daemon-reload`, then `sudo systemctl enable --now the-librarian.service`.
|
|
127
|
+
* Idempotent — re-running rewrites the SAME unit path and re-enables, never
|
|
128
|
+
* duplicating. On macOS: print the deferred notice and skip cleanly (NOT an
|
|
129
|
+
* error).
|
|
130
|
+
*/
|
|
131
|
+
export async function enableBoot(options = {}) {
|
|
132
|
+
const platform = options.platform ?? process.platform;
|
|
133
|
+
if (isUnsupportedPlatform(platform)) {
|
|
134
|
+
return { output: MACOS_NOTICE };
|
|
135
|
+
}
|
|
136
|
+
// Boot persistence runs `docker start` at boot, so it needs `docker` on PATH
|
|
137
|
+
// — but NOT git (no clone here) and NOT a reachable daemon right now (the unit
|
|
138
|
+
// fires at boot, not at enable time). A lighter, boot-specific preflight.
|
|
139
|
+
await requireDocker();
|
|
140
|
+
const dockerPath = await resolveDockerPath();
|
|
141
|
+
const unit = generateUnit({ dockerPath });
|
|
142
|
+
await installUnit(unit);
|
|
143
|
+
// daemon-reload BEFORE enable --now so systemd sees the (possibly rewritten)
|
|
144
|
+
// unit. enable --now both enables-on-boot and starts the container now.
|
|
145
|
+
await sudoSystemctl(["daemon-reload"]);
|
|
146
|
+
await sudoSystemctl(["enable", "--now", UNIT_NAME]);
|
|
147
|
+
return {
|
|
148
|
+
output: [
|
|
149
|
+
`Boot persistence enabled — ${UNIT_NAME} will start the container on boot.`,
|
|
150
|
+
` Unit: ${unitPath()} (references the existing '${CONTAINER_NAME}' container; no secret in the file)`,
|
|
151
|
+
"Disable it again with `librarian server disable-boot`.",
|
|
152
|
+
].join("\n"),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Disable boot persistence. On Linux: `sudo systemctl disable --now
|
|
157
|
+
* the-librarian.service`, remove the unit file (`sudo rm -f`), then `sudo
|
|
158
|
+
* systemctl daemon-reload`. Safe if already disabled/absent — a non-zero
|
|
159
|
+
* `disable` (unit not loaded) becomes a friendly teaching message, never a
|
|
160
|
+
* crash; the `rm -f` + reload still run so a stale unit file is cleaned up. On
|
|
161
|
+
* macOS: print the deferred notice and skip cleanly.
|
|
162
|
+
*/
|
|
163
|
+
export async function disableBoot(options = {}) {
|
|
164
|
+
const platform = options.platform ?? process.platform;
|
|
165
|
+
if (isUnsupportedPlatform(platform)) {
|
|
166
|
+
return { output: MACOS_NOTICE };
|
|
167
|
+
}
|
|
168
|
+
await requireDocker();
|
|
169
|
+
// disable --now: tolerate "unit does not exist / not loaded" — already off.
|
|
170
|
+
const disable = await run("sudo", ["systemctl", "disable", "--now", UNIT_NAME]);
|
|
171
|
+
const alreadyAbsent = disable.code !== 0 && isUnitAbsent(disable.stderr);
|
|
172
|
+
if (disable.code !== 0 && !alreadyAbsent) {
|
|
173
|
+
failIfNonZero("sudo", ["systemctl", "disable", "--now", UNIT_NAME], disable);
|
|
174
|
+
}
|
|
175
|
+
// Remove the unit file (idempotent with `-f`), then daemon-reload so systemd
|
|
176
|
+
// forgets it. `rm -f` never errors on an absent file.
|
|
177
|
+
await sudoRun(["rm", "-f", unitPath()]);
|
|
178
|
+
await sudoSystemctl(["daemon-reload"]);
|
|
179
|
+
return {
|
|
180
|
+
output: alreadyAbsent
|
|
181
|
+
? [
|
|
182
|
+
`Boot persistence was not enabled (${UNIT_NAME} was not loaded) — nothing to disable.`,
|
|
183
|
+
`Removed any stale unit file at ${unitPath()} and reloaded systemd.`,
|
|
184
|
+
].join("\n")
|
|
185
|
+
: [
|
|
186
|
+
`Boot persistence disabled — ${UNIT_NAME} removed and systemd reloaded.`,
|
|
187
|
+
"The container itself is untouched; stop it with `librarian server down`.",
|
|
188
|
+
].join("\n"),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// --- privileged steps (every one through the injectable runner) ----------
|
|
192
|
+
/**
|
|
193
|
+
* Write the unit to the system path. `/etc/systemd/system` needs root, so the
|
|
194
|
+
* NON-SECRET unit text is written to a temp file first, then `sudo cp`'d into
|
|
195
|
+
* place and `sudo chmod 644`'d (world-readable is fine — there is no secret in
|
|
196
|
+
* it, by construction). The temp file is removed afterward. The `cp`/`chmod`
|
|
197
|
+
* argv is asserted by tests; the temp source carries the unit content for the
|
|
198
|
+
* test's no-secret check (the fake runner doesn't really move it).
|
|
199
|
+
*/
|
|
200
|
+
async function installUnit(unit) {
|
|
201
|
+
const dir = mkdtempSync(path.join(tmpdir(), "librarian-unit-"));
|
|
202
|
+
const tmp = path.join(dir, UNIT_NAME);
|
|
203
|
+
writeFileSync(tmp, unit, "utf8");
|
|
204
|
+
try {
|
|
205
|
+
await sudoRun(["cp", tmp, unitPath()]);
|
|
206
|
+
await sudoRun(["chmod", "644", unitPath()]);
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
rmSync(dir, { recursive: true, force: true });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/** Run `sudo systemctl <args…>`; a non-zero exit is a teaching error. */
|
|
213
|
+
async function sudoSystemctl(args) {
|
|
214
|
+
await sudoRun(["systemctl", ...args]);
|
|
215
|
+
}
|
|
216
|
+
/** Run `sudo <args…>`; a non-zero exit is a teaching error. */
|
|
217
|
+
async function sudoRun(args) {
|
|
218
|
+
const result = await run("sudo", args);
|
|
219
|
+
failIfNonZero("sudo", args, result);
|
|
220
|
+
}
|
|
221
|
+
/** True when a `systemctl disable` failure means the unit simply isn't loaded. */
|
|
222
|
+
function isUnitAbsent(stderr) {
|
|
223
|
+
return /does not exist|not loaded|no such file|not found/i.test(stderr);
|
|
224
|
+
}
|
|
225
|
+
function failIfNonZero(cmd, args, result) {
|
|
226
|
+
if (result.code === 0)
|
|
227
|
+
return;
|
|
228
|
+
const detail = result.stderr.trim() || result.stdout.trim();
|
|
229
|
+
throw new BootError(`\`${cmd} ${args[0]}\` failed (exit ${result.code ?? "signal"})` +
|
|
230
|
+
(detail ? `:\n${detail}` : ".") +
|
|
231
|
+
"\n\nResolve the error above (you may need sudo privileges), then re-run the command.");
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=boot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boot.js","sourceRoot":"","sources":["../../src/server/boot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deploy-state recorded after a successful `up` (and rewritten by `update`).
|
|
3
|
+
* Every field is NON-SECRET — see the module header. Do NOT add a token/key.
|
|
4
|
+
*/
|
|
5
|
+
export interface DeployState {
|
|
6
|
+
/** The container name every `server` command operates on. */
|
|
7
|
+
containerName: string;
|
|
8
|
+
/** The resolved bind host the container publishes on (`127.0.0.1`, a tailnet IP, `0.0.0.0`). */
|
|
9
|
+
host: string;
|
|
10
|
+
/** The named data volume mounted at `/data` (sacred across `down`/`update`). */
|
|
11
|
+
dataVolume: string;
|
|
12
|
+
/** The deployed ref — a `vX.Y.Z` tag or `main` (what was checked out + built). */
|
|
13
|
+
ref: string;
|
|
14
|
+
/** The built image tag (`the-librarian:<ref>`). */
|
|
15
|
+
imageTag: string;
|
|
16
|
+
}
|
|
17
|
+
/** `<dir>/deploy-state.json` — the deploy-state file path within a deploy dir. */
|
|
18
|
+
export declare function deployStatePath(dir: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Write the deploy-state to `<dir>/deploy-state.json`, creating `dir` if absent.
|
|
21
|
+
*
|
|
22
|
+
* Only the five declared fields are persisted — a `pick`, not a spread — so no
|
|
23
|
+
* extra key (e.g. a smuggled token) can ride along into the file. The file is
|
|
24
|
+
* non-secret, so it gets ordinary (not 0600) permissions.
|
|
25
|
+
*/
|
|
26
|
+
export declare function writeDeployState(dir: string, state: DeployState): void;
|
|
27
|
+
/**
|
|
28
|
+
* Read the deploy-state back, or `null` if the file is absent, unparseable, or
|
|
29
|
+
* missing any required field. Never throws — a missing/corrupt state file means
|
|
30
|
+
* the caller falls back (e.g. `status` uses `git describe`), never crashes.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readDeployState(dir: string): DeployState | null;
|
|
33
|
+
//# sourceMappingURL=deploy-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-state.d.ts","sourceRoot":"","sources":["../../src/server/deploy-state.ts"],"names":[],"mappings":"AAmBA;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAYtE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAyB/D"}
|