@theford/runly 0.0.0-stage → 0.20.0
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 +202 -0
- package/README.md +157 -2
- package/dist/cli/client.d.ts +80 -0
- package/dist/cli/client.js +294 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/daemon-unit.d.ts +58 -0
- package/dist/cli/daemon-unit.js +289 -0
- package/dist/cli/daemon-unit.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1482 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.d.ts +9 -0
- package/dist/cli/install.js +53 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/logs.d.ts +43 -0
- package/dist/cli/logs.js +232 -0
- package/dist/cli/logs.js.map +1 -0
- package/dist/cli/tether.d.ts +3 -0
- package/dist/cli/tether.js +65 -0
- package/dist/cli/tether.js.map +1 -0
- package/dist/cli/watchdog.d.ts +1 -0
- package/dist/cli/watchdog.js +81 -0
- package/dist/cli/watchdog.js.map +1 -0
- package/dist/core/builds.d.ts +20 -0
- package/dist/core/builds.js +93 -0
- package/dist/core/builds.js.map +1 -0
- package/dist/core/caller-env.d.ts +17 -0
- package/dist/core/caller-env.js +77 -0
- package/dist/core/caller-env.js.map +1 -0
- package/dist/core/diagnostics.d.ts +68 -0
- package/dist/core/diagnostics.js +34 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/env-vars.d.ts +35 -0
- package/dist/core/env-vars.js +32 -0
- package/dist/core/env-vars.js.map +1 -0
- package/dist/core/events.d.ts +10 -0
- package/dist/core/events.js +42 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/exec.d.ts +22 -0
- package/dist/core/exec.js +84 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/journal.d.ts +227 -0
- package/dist/core/journal.js +395 -0
- package/dist/core/journal.js.map +1 -0
- package/dist/core/logs.d.ts +56 -0
- package/dist/core/logs.js +259 -0
- package/dist/core/logs.js.map +1 -0
- package/dist/core/manifest.d.ts +292 -0
- package/dist/core/manifest.js +179 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/namespaces.d.ts +7 -0
- package/dist/core/namespaces.js +59 -0
- package/dist/core/namespaces.js.map +1 -0
- package/dist/core/paths.d.ts +31 -0
- package/dist/core/paths.js +65 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/policy.d.ts +94 -0
- package/dist/core/policy.js +122 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/ports.d.ts +52 -0
- package/dist/core/ports.js +94 -0
- package/dist/core/ports.js.map +1 -0
- package/dist/core/presets.d.ts +25 -0
- package/dist/core/presets.js +86 -0
- package/dist/core/presets.js.map +1 -0
- package/dist/core/procscan.d.ts +111 -0
- package/dist/core/procscan.js +342 -0
- package/dist/core/procscan.js.map +1 -0
- package/dist/core/retention.d.ts +97 -0
- package/dist/core/retention.js +322 -0
- package/dist/core/retention.js.map +1 -0
- package/dist/core/sleep.d.ts +51 -0
- package/dist/core/sleep.js +99 -0
- package/dist/core/sleep.js.map +1 -0
- package/dist/core/tree-ledger.d.ts +39 -0
- package/dist/core/tree-ledger.js +92 -0
- package/dist/core/tree-ledger.js.map +1 -0
- package/dist/core/types.d.ts +21 -0
- package/dist/core/types.js +6 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/units.d.ts +20 -0
- package/dist/core/units.js +69 -0
- package/dist/core/units.js.map +1 -0
- package/dist/core/upkeep.d.ts +90 -0
- package/dist/core/upkeep.js +176 -0
- package/dist/core/upkeep.js.map +1 -0
- package/dist/core/util.d.ts +46 -0
- package/dist/core/util.js +168 -0
- package/dist/core/util.js.map +1 -0
- package/dist/core/version.d.ts +39 -0
- package/dist/core/version.js +144 -0
- package/dist/core/version.js.map +1 -0
- package/dist/core/worktree.d.ts +51 -0
- package/dist/core/worktree.js +309 -0
- package/dist/core/worktree.js.map +1 -0
- package/dist/daemon/budget.d.ts +155 -0
- package/dist/daemon/budget.js +288 -0
- package/dist/daemon/budget.js.map +1 -0
- package/dist/daemon/election.d.ts +9 -0
- package/dist/daemon/election.js +140 -0
- package/dist/daemon/election.js.map +1 -0
- package/dist/daemon/engine.d.ts +1703 -0
- package/dist/daemon/engine.js +5305 -0
- package/dist/daemon/engine.js.map +1 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +412 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/proxy.d.ts +108 -0
- package/dist/daemon/proxy.js +615 -0
- package/dist/daemon/proxy.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +101 -0
- package/dist/daemon/supervisor.js +493 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/drivers/appliances.d.ts +11 -0
- package/dist/drivers/appliances.js +174 -0
- package/dist/drivers/appliances.js.map +1 -0
- package/dist/drivers/datastores.d.ts +144 -0
- package/dist/drivers/datastores.js +775 -0
- package/dist/drivers/datastores.js.map +1 -0
- package/dist/drivers/preview.d.ts +69 -0
- package/dist/drivers/preview.js +605 -0
- package/dist/drivers/preview.js.map +1 -0
- package/dist/drivers/types.d.ts +70 -0
- package/dist/drivers/types.js +13 -0
- package/dist/drivers/types.js.map +1 -0
- package/package.json +63 -4
- package/schema/backlot.schema.json +549 -0
- package/schema/runly.schema.json +556 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core domain model (see docs/architecture.md §3).
|
|
3
|
+
* Persisted in the per-machine SQLite journal; the daemon's memory is a cache.
|
|
4
|
+
*/
|
|
5
|
+
export type EnvState = 'provisioning' | 'hot' | 'warm' | 'degraded' | 'recycling';
|
|
6
|
+
export type Hygiene = 'reuse' | 'reset-data' | 'pristine';
|
|
7
|
+
/** Every lease is a session lease (decision 0032); a `run` row from a pre-0.13 journal is read as one. */
|
|
8
|
+
export type LeaseKind = 'session';
|
|
9
|
+
/** The field an agent branches on mechanically (decision 0010). */
|
|
10
|
+
export type ErrorClass = 'work-error' | 'env-error' | 'infra-error';
|
|
11
|
+
/**
|
|
12
|
+
* A recorded service process. `startTime` (kernel clock ticks since boot,
|
|
13
|
+
* Linux only) pins the pid to one process *life*, so a later daemon can tell
|
|
14
|
+
* "still my service" from "the OS reused that pid" before signalling it.
|
|
15
|
+
*/
|
|
16
|
+
export interface ServicePid {
|
|
17
|
+
pid: number;
|
|
18
|
+
startTime?: number;
|
|
19
|
+
pgid?: number;
|
|
20
|
+
pgids?: number[];
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two units a manifest and the policy knobs speak: durations (`90s`,
|
|
3
|
+
* `10m`, `2h`) and sizes (`600M`, `2G`). Parsing lives in one place so the
|
|
4
|
+
* schema, the engine and `runly plan` can never disagree about what `2G` is.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* A duration in milliseconds. Strings take a unit (`ms`, `s`, `m`, `h`, `d`);
|
|
8
|
+
* a bare number is SECONDS (the manifest convention, like `timeout:`).
|
|
9
|
+
* `never`/`off` is Infinity. Returns undefined for anything unreadable.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseDuration(v: unknown): number | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* A size in bytes. Numbers are bytes; strings take K/M/G/T (binary multiples,
|
|
14
|
+
* with or without `i`/`B`): `600M`, `2G`, `1.5GiB`. Undefined when unreadable.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseSize(v: unknown): number | undefined;
|
|
17
|
+
/** `2.0G`, `600M`, `12K` — for humans, one decimal at most. */
|
|
18
|
+
export declare function formatSize(bytes: number): string;
|
|
19
|
+
/** `45s`, `10m`, `1.5h`. */
|
|
20
|
+
export declare function formatDuration(ms: number): string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two units a manifest and the policy knobs speak: durations (`90s`,
|
|
3
|
+
* `10m`, `2h`) and sizes (`600M`, `2G`). Parsing lives in one place so the
|
|
4
|
+
* schema, the engine and `runly plan` can never disagree about what `2G` is.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* A duration in milliseconds. Strings take a unit (`ms`, `s`, `m`, `h`, `d`);
|
|
8
|
+
* a bare number is SECONDS (the manifest convention, like `timeout:`).
|
|
9
|
+
* `never`/`off` is Infinity. Returns undefined for anything unreadable.
|
|
10
|
+
*/
|
|
11
|
+
export function parseDuration(v) {
|
|
12
|
+
if (typeof v === 'number')
|
|
13
|
+
return Number.isFinite(v) && v >= 0 ? v * 1000 : undefined;
|
|
14
|
+
if (typeof v !== 'string')
|
|
15
|
+
return undefined;
|
|
16
|
+
const s = v.trim().toLowerCase();
|
|
17
|
+
if (s === 'never' || s === 'off')
|
|
18
|
+
return Infinity;
|
|
19
|
+
const m = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/.exec(s);
|
|
20
|
+
if (!m)
|
|
21
|
+
return undefined;
|
|
22
|
+
const n = Number(m[1]);
|
|
23
|
+
const unit = m[2] ?? 's';
|
|
24
|
+
const mult = unit === 'ms' ? 1 : unit === 's' ? 1000 : unit === 'm' ? 60_000 : unit === 'h' ? 3_600_000 : 86_400_000;
|
|
25
|
+
return n * mult;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A size in bytes. Numbers are bytes; strings take K/M/G/T (binary multiples,
|
|
29
|
+
* with or without `i`/`B`): `600M`, `2G`, `1.5GiB`. Undefined when unreadable.
|
|
30
|
+
*/
|
|
31
|
+
export function parseSize(v) {
|
|
32
|
+
if (typeof v === 'number')
|
|
33
|
+
return Number.isFinite(v) && v >= 0 ? v : undefined;
|
|
34
|
+
if (typeof v !== 'string')
|
|
35
|
+
return undefined;
|
|
36
|
+
const m = /^(\d+(?:\.\d+)?)\s*([kmgt])?(i?b)?$/i.exec(v.trim());
|
|
37
|
+
if (!m)
|
|
38
|
+
return undefined;
|
|
39
|
+
const n = Number(m[1]);
|
|
40
|
+
const unit = (m[2] ?? '').toLowerCase();
|
|
41
|
+
const exp = unit === 'k' ? 1 : unit === 'm' ? 2 : unit === 'g' ? 3 : unit === 't' ? 4 : 0;
|
|
42
|
+
return Math.round(n * 1024 ** exp);
|
|
43
|
+
}
|
|
44
|
+
/** `2.0G`, `600M`, `12K` — for humans, one decimal at most. */
|
|
45
|
+
export function formatSize(bytes) {
|
|
46
|
+
if (!Number.isFinite(bytes))
|
|
47
|
+
return '∞';
|
|
48
|
+
const units = ['B', 'K', 'M', 'G', 'T'];
|
|
49
|
+
let v = Math.max(0, bytes);
|
|
50
|
+
let i = 0;
|
|
51
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
52
|
+
v /= 1024;
|
|
53
|
+
i++;
|
|
54
|
+
}
|
|
55
|
+
return `${v >= 10 || i === 0 ? Math.round(v) : v.toFixed(1)}${units[i]}`;
|
|
56
|
+
}
|
|
57
|
+
/** `45s`, `10m`, `1.5h`. */
|
|
58
|
+
export function formatDuration(ms) {
|
|
59
|
+
if (!Number.isFinite(ms))
|
|
60
|
+
return 'never';
|
|
61
|
+
if (ms < 1000)
|
|
62
|
+
return `${Math.round(ms)}ms`;
|
|
63
|
+
if (ms < 60_000)
|
|
64
|
+
return `${Math.round(ms / 1000)}s`;
|
|
65
|
+
if (ms < 3_600_000)
|
|
66
|
+
return `${Math.round(ms / 60_000)}m`;
|
|
67
|
+
return `${(ms / 3_600_000).toFixed(1)}h`;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=units.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"units.js","sourceRoot":"","sources":["../../src/core/units.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type Manifest } from './manifest.js';
|
|
2
|
+
export interface UpkeepStep {
|
|
3
|
+
/** 1-based position in the manifest's upkeep list — progress never names the command. */
|
|
4
|
+
index: number;
|
|
5
|
+
when: string;
|
|
6
|
+
/** `ran` executed now; `fresh` matched the ledger; `skipped` was not this caller's to run. */
|
|
7
|
+
status: 'ran' | 'fresh' | 'skipped';
|
|
8
|
+
durationMs: number;
|
|
9
|
+
reason?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface UpkeepOutcome {
|
|
12
|
+
ran: Array<{
|
|
13
|
+
when: string;
|
|
14
|
+
run: string;
|
|
15
|
+
}>;
|
|
16
|
+
fingerprints: Record<string, string>;
|
|
17
|
+
/** Names of datastores whose templates must be rebaked (@rebake-template). */
|
|
18
|
+
rebakeTemplates: string[];
|
|
19
|
+
steps: UpkeepStep[];
|
|
20
|
+
}
|
|
21
|
+
export interface UpkeepOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Evaluate `@` built-ins (default true). They act on an environment's data
|
|
24
|
+
* (`@rebake-template`), so `runly warm` — which has no environment — skips
|
|
25
|
+
* them and reports why.
|
|
26
|
+
*/
|
|
27
|
+
builtins?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Persist the ledger as it moves. Called with the rule's key REMOVED before
|
|
30
|
+
* its command runs, and with the new hash after it succeeds: a rule that fails
|
|
31
|
+
* half-way has left the worktree in neither state, and a ledger still naming
|
|
32
|
+
* the old trigger would let a later revert to that trigger skip the repair.
|
|
33
|
+
*/
|
|
34
|
+
commit?: (fingerprints: Record<string, string>) => void;
|
|
35
|
+
/** Receives each command rule's output as it runs (`upkeep.build.log`, decision 0038). */
|
|
36
|
+
onOutput?: {
|
|
37
|
+
begin: (label: string) => void;
|
|
38
|
+
data: (stream: 'out' | 'err', chunk: string) => void;
|
|
39
|
+
end: (stream: 'out' | 'err') => void;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export declare const ruleKey: (rule: {
|
|
43
|
+
when: string;
|
|
44
|
+
run: string;
|
|
45
|
+
}) => string;
|
|
46
|
+
/**
|
|
47
|
+
* The worktree files any upkeep rule's `when:` glob matches — the only files a
|
|
48
|
+
* bind reads to decide what upkeep is due. Empty, without asking git, when the
|
|
49
|
+
* manifest declares no rules.
|
|
50
|
+
*/
|
|
51
|
+
export declare function triggerFiles(root: string, manifest: Manifest): Promise<string[]>;
|
|
52
|
+
/**
|
|
53
|
+
* The trigger files with their content hashes, read once per bind.
|
|
54
|
+
*
|
|
55
|
+
* Trigger globs can match large files — a consumer's database backups are
|
|
56
|
+
* 160 MB, named by several @rebake-template rules each — so hashing them per
|
|
57
|
+
* rule on every bind would re-read the same bytes several times on the
|
|
58
|
+
* daemon's thread. Each file is hashed once per bind, and only when its
|
|
59
|
+
* (size, mtime) moved since the cache under `cacheDir` recorded it. The cache
|
|
60
|
+
* holds the trigger files and nothing else, and lives in the state root.
|
|
61
|
+
*
|
|
62
|
+
* Filesystem mtimes are coarse, so a same-size rewrite inside one timestamp
|
|
63
|
+
* tick of the recorded stat is indistinguishable from the file already hashed.
|
|
64
|
+
* git's "racily clean" rule applies: an entry whose mtime is not strictly older
|
|
65
|
+
* than the cache write (minus a window) is re-hashed.
|
|
66
|
+
*/
|
|
67
|
+
export interface TriggerSet {
|
|
68
|
+
files: string[];
|
|
69
|
+
hashOf: (rel: string) => string | null;
|
|
70
|
+
}
|
|
71
|
+
export declare function triggerSet(root: string, manifest: Manifest, cacheDir?: string): Promise<TriggerSet>;
|
|
72
|
+
/** The fingerprint of one `when:` glob: the content of exactly the files it matches. */
|
|
73
|
+
export declare function triggerHash(root: string, files: string[] | TriggerSet, when: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* Content-derived bake key per datastore (vetbill-1i49).
|
|
76
|
+
*
|
|
77
|
+
* The baked-template identity used to hash only the static `create:` command
|
|
78
|
+
* string — identical for every branch — so two environments of the same stack
|
|
79
|
+
* whose trees carry *different* migrations/seeds could silently share one
|
|
80
|
+
* template with the wrong schema. The @rebake-template rules already declare
|
|
81
|
+
* exactly which files define a datastore's baked content; hashing those
|
|
82
|
+
* files' current contents into the template name makes divergent content
|
|
83
|
+
* yield disjoint templates by construction (rebake becomes cleanup, not the
|
|
84
|
+
* only line of defense).
|
|
85
|
+
*
|
|
86
|
+
* Datastores with no @rebake-template rule get no key (undefined) and keep
|
|
87
|
+
* their historical template names — existing bakes stay valid.
|
|
88
|
+
*/
|
|
89
|
+
export declare function templateBakeKeys(manifest: Manifest, root: string, files: string[] | TriggerSet): Record<string, string>;
|
|
90
|
+
export declare function runUpkeep(root: string, files: string[] | TriggerSet, manifest: Manifest, previous: Record<string, string>, onProgress?: (phase: string) => void, opts?: UpkeepOptions): Promise<UpkeepOutcome>;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fingerprint ledger (decision 0008): a closed list of (trigger -> action)
|
|
3
|
+
* rules, evaluated at bind (and `runly warm`) time, direction-agnostic. Actions
|
|
4
|
+
* are repo commands, or engine built-ins prefixed with @.
|
|
5
|
+
*
|
|
6
|
+
* A rule's fingerprint is the content of the files its `when:` glob matches —
|
|
7
|
+
* those files and nothing else (decision 0032). runly keeps no identity of the
|
|
8
|
+
* rest of the worktree and no record of builds.
|
|
9
|
+
*/
|
|
10
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { cmdTimeoutS, runBounded } from './exec.js';
|
|
13
|
+
import { existsSync } from 'node:fs';
|
|
14
|
+
import { globToRegex, sha256, fileHash, BrokerError, commandFailure, safeJoin } from './util.js';
|
|
15
|
+
import { enumerateSource, enumerateSourceStats } from './worktree.js';
|
|
16
|
+
import { upkeepOutputs } from './manifest.js';
|
|
17
|
+
export const ruleKey = (rule) => `${rule.when} -> ${rule.run}`;
|
|
18
|
+
/**
|
|
19
|
+
* The worktree files any upkeep rule's `when:` glob matches — the only files a
|
|
20
|
+
* bind reads to decide what upkeep is due. Empty, without asking git, when the
|
|
21
|
+
* manifest declares no rules.
|
|
22
|
+
*/
|
|
23
|
+
export async function triggerFiles(root, manifest) {
|
|
24
|
+
const whens = (manifest.upkeep ?? []).map((rule) => rule.when);
|
|
25
|
+
return whens.length === 0 ? [] : enumerateSource(root, manifest, whens);
|
|
26
|
+
}
|
|
27
|
+
const RACY_WINDOW_MS = 2000;
|
|
28
|
+
export async function triggerSet(root, manifest, cacheDir) {
|
|
29
|
+
// The listing's own stat is the cache's check: one stat per trigger file.
|
|
30
|
+
const whens = (manifest.upkeep ?? []).map((rule) => rule.when);
|
|
31
|
+
const listed = whens.length === 0 ? [] : await enumerateSourceStats(root, manifest, whens);
|
|
32
|
+
const files = listed.map((f) => f.path);
|
|
33
|
+
const statOfListed = new Map(listed.map((f) => [f.path, f]));
|
|
34
|
+
let cache = { entries: {} };
|
|
35
|
+
if (cacheDir) {
|
|
36
|
+
try {
|
|
37
|
+
const raw = JSON.parse(readFileSync(join(cacheDir, 'triggers.json'), 'utf8'));
|
|
38
|
+
if (raw && typeof raw === 'object' && raw.entries && typeof raw.entries === 'object')
|
|
39
|
+
cache = raw;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* absent or torn: hash everything once */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const racyFrom = cache.writtenAt === undefined ? -Infinity : cache.writtenAt - RACY_WINDOW_MS;
|
|
46
|
+
const next = {};
|
|
47
|
+
const hashes = new Map();
|
|
48
|
+
for (const rel of files) {
|
|
49
|
+
const abs = join(root, rel);
|
|
50
|
+
const st = statOfListed.get(rel);
|
|
51
|
+
if (!st) {
|
|
52
|
+
hashes.set(rel, null);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const cached = cache.entries[rel];
|
|
56
|
+
const trust = cached !== undefined && cached.size === st.size && cached.mtime === st.mtimeMs && cached.mtime < racyFrom;
|
|
57
|
+
const hash = trust ? cached.hash : fileHash(abs);
|
|
58
|
+
hashes.set(rel, hash);
|
|
59
|
+
if (hash !== null)
|
|
60
|
+
next[rel] = { hash, size: st.size, mtime: st.mtimeMs };
|
|
61
|
+
}
|
|
62
|
+
if (cacheDir) {
|
|
63
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
64
|
+
const tmp = join(cacheDir, `triggers.json.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
65
|
+
writeFileSync(tmp, JSON.stringify({ root, writtenAt: Date.now(), entries: next }));
|
|
66
|
+
renameSync(tmp, join(cacheDir, 'triggers.json'));
|
|
67
|
+
}
|
|
68
|
+
return { files, hashOf: (rel) => (hashes.has(rel) ? (hashes.get(rel) ?? null) : fileHash(join(root, rel))) };
|
|
69
|
+
}
|
|
70
|
+
/** The fingerprint of one `when:` glob: the content of exactly the files it matches. */
|
|
71
|
+
export function triggerHash(root, files, when) {
|
|
72
|
+
const re = globToRegex(when);
|
|
73
|
+
const list = Array.isArray(files) ? files : files.files;
|
|
74
|
+
const hashOf = Array.isArray(files) ? (rel) => fileHash(join(root, rel)) : files.hashOf;
|
|
75
|
+
const matching = list.filter((f) => re.test(f)).sort();
|
|
76
|
+
return sha256(matching.map((f) => `${f}:${hashOf(f) ?? 'gone'}`).join('\n'));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Content-derived bake key per datastore (vetbill-1i49).
|
|
80
|
+
*
|
|
81
|
+
* The baked-template identity used to hash only the static `create:` command
|
|
82
|
+
* string — identical for every branch — so two environments of the same stack
|
|
83
|
+
* whose trees carry *different* migrations/seeds could silently share one
|
|
84
|
+
* template with the wrong schema. The @rebake-template rules already declare
|
|
85
|
+
* exactly which files define a datastore's baked content; hashing those
|
|
86
|
+
* files' current contents into the template name makes divergent content
|
|
87
|
+
* yield disjoint templates by construction (rebake becomes cleanup, not the
|
|
88
|
+
* only line of defense).
|
|
89
|
+
*
|
|
90
|
+
* Datastores with no @rebake-template rule get no key (undefined) and keep
|
|
91
|
+
* their historical template names — existing bakes stay valid.
|
|
92
|
+
*/
|
|
93
|
+
export function templateBakeKeys(manifest, root, files) {
|
|
94
|
+
const perDs = {};
|
|
95
|
+
for (const rule of manifest.upkeep ?? []) {
|
|
96
|
+
if (!rule.run.startsWith('@rebake-template'))
|
|
97
|
+
continue;
|
|
98
|
+
const target = rule.run.slice('@rebake-template'.length).trim() || 'main';
|
|
99
|
+
(perDs[target] ??= []).push(triggerHash(root, files, rule.when));
|
|
100
|
+
}
|
|
101
|
+
return Object.fromEntries(Object.entries(perDs).map(([ds, hashes]) => [ds, sha256(hashes.sort().join('\n')).slice(0, 12)]));
|
|
102
|
+
}
|
|
103
|
+
export async function runUpkeep(root, files, manifest, previous, onProgress, opts = {}) {
|
|
104
|
+
const outcome = { ran: [], fingerprints: { ...previous }, rebakeTemplates: [], steps: [] };
|
|
105
|
+
const builtins = opts.builtins ?? true;
|
|
106
|
+
for (const [index, rule] of (manifest.upkeep ?? []).entries()) {
|
|
107
|
+
const key = ruleKey(rule);
|
|
108
|
+
const position = index + 1;
|
|
109
|
+
if (rule.run.startsWith('@') && !builtins) {
|
|
110
|
+
outcome.steps.push({ index: position, when: rule.when, status: 'skipped', durationMs: 0, reason: 'acts on an environment\'s data; the next bind applies it' });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const hash = triggerHash(root, files, rule.when);
|
|
114
|
+
// Its trigger is unchanged — but a rule that declares what it leaves
|
|
115
|
+
// behind is due again when that is gone (a pool's `git clean -fdx`).
|
|
116
|
+
const missing = rule.run.startsWith('@') ? undefined : upkeepOutputs(rule).find((o) => !existsSync(safeJoin(root, o, 'upkeep outputs')));
|
|
117
|
+
if (previous[key] === hash && missing === undefined) {
|
|
118
|
+
outcome.steps.push({ index: position, when: rule.when, status: 'fresh', durationMs: 0 });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (previous[key] === hash)
|
|
122
|
+
onProgress?.(`upkeep rule ${position}: its output ${missing} is missing — running it again`);
|
|
123
|
+
const started = Date.now();
|
|
124
|
+
if (rule.run.startsWith('@')) {
|
|
125
|
+
const [builtin, ...args] = rule.run.slice(1).split(/\s+/);
|
|
126
|
+
if (builtin === 'rebake-template') {
|
|
127
|
+
outcome.rebakeTemplates.push(args[0] ?? 'main');
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
throw new BrokerError('work-error', `unknown upkeep built-in '@${builtin}'`, 'upkeep');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Bounded like every other repo-declared command: an install blocking on
|
|
135
|
+
// a half-up registry used to hold the env's busy bit until the daemon
|
|
136
|
+
// was killed.
|
|
137
|
+
const timeoutS = cmdTimeoutS(rule.timeout);
|
|
138
|
+
// Commands and their output may contain credentials. Progress names only
|
|
139
|
+
// the manifest rule's position, never its command or captured output.
|
|
140
|
+
const label = `upkeep rule ${position}`;
|
|
141
|
+
onProgress?.(`${label}: starting (timeout ${timeoutS}s)`);
|
|
142
|
+
delete outcome.fingerprints[key];
|
|
143
|
+
opts.commit?.(outcome.fingerprints);
|
|
144
|
+
const heartbeat = onProgress && setInterval(() => {
|
|
145
|
+
onProgress(`${label}: running (${Math.floor((Date.now() - started) / 1000)}s elapsed)`);
|
|
146
|
+
}, 5000);
|
|
147
|
+
heartbeat?.unref();
|
|
148
|
+
let r;
|
|
149
|
+
try {
|
|
150
|
+
opts.onOutput?.begin(`${label} (when: ${rule.when})`);
|
|
151
|
+
r = await runBounded(rule.run, root, timeoutS, undefined, opts.onOutput);
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
if (heartbeat)
|
|
155
|
+
clearInterval(heartbeat);
|
|
156
|
+
}
|
|
157
|
+
if (r.timedOut) {
|
|
158
|
+
onProgress?.(`${label}: timed out after ${timeoutS}s`);
|
|
159
|
+
throw new BrokerError('work-error', `upkeep rule timed out after ${timeoutS}s (process group killed): ${rule.run}`, rule.when, r.output.slice(-800));
|
|
160
|
+
}
|
|
161
|
+
if (r.code !== 0) {
|
|
162
|
+
onProgress?.(`${label}: failed (${Math.floor((Date.now() - started) / 1000)}s elapsed)`);
|
|
163
|
+
// Triggered by the binding's own change -> work-error by default (decision 0008).
|
|
164
|
+
throw commandFailure(`upkeep rule failed: ${rule.run}`, rule.when, r.output);
|
|
165
|
+
}
|
|
166
|
+
onProgress?.(`${label}: finished (${Math.floor((Date.now() - started) / 1000)}s elapsed)`);
|
|
167
|
+
}
|
|
168
|
+
outcome.ran.push({ when: rule.when, run: rule.run });
|
|
169
|
+
outcome.fingerprints[key] = hash;
|
|
170
|
+
if (!rule.run.startsWith('@'))
|
|
171
|
+
opts.commit?.(outcome.fingerprints);
|
|
172
|
+
outcome.steps.push({ index: position, when: rule.when, status: 'ran', durationMs: Date.now() - started });
|
|
173
|
+
}
|
|
174
|
+
return outcome;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=upkeep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upkeep.js","sourceRoot":"","sources":["../../src/core/upkeep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const sha256: (data: string | Buffer) => string;
|
|
2
|
+
/**
|
|
3
|
+
* Run a shell command, swallowing failures (best-effort cleanup paths).
|
|
4
|
+
* Bounded: these run inside awaited sweeps, and a hung `drop` against an
|
|
5
|
+
* unreachable appliance used to stall retention forever while each later
|
|
6
|
+
* sweep piled another wedged `sh` onto the same marker.
|
|
7
|
+
*/
|
|
8
|
+
export declare const runQuiet: (cmd: string, cwd: string) => Promise<void>;
|
|
9
|
+
export declare const fileHash: (path: string) => string | null;
|
|
10
|
+
/** One stat, no throw: what a listing of tens of thousands of files can afford per file. */
|
|
11
|
+
export declare const statOf: (p: string) => import("node:fs").Stats | undefined;
|
|
12
|
+
export declare const isFile: (p: string) => boolean;
|
|
13
|
+
export declare function globToRegex(pattern: string): RegExp;
|
|
14
|
+
export declare const matchesAny: (path: string, patterns: string[]) => boolean;
|
|
15
|
+
/** Resolve {{...}} placeholders against a nested context object. */
|
|
16
|
+
export declare function template(str: string, ctx: Record<string, unknown>): string;
|
|
17
|
+
export declare const templateEnv: (env: Record<string, string> | undefined, ctx: Record<string, unknown>) => Record<string, string>;
|
|
18
|
+
export declare class BrokerError extends Error {
|
|
19
|
+
readonly klass: 'work-error' | 'env-error' | 'infra-error';
|
|
20
|
+
readonly source?: string | undefined;
|
|
21
|
+
readonly logExcerpt?: string | undefined;
|
|
22
|
+
constructor(klass: 'work-error' | 'env-error' | 'infra-error', message: string, source?: string | undefined, logExcerpt?: string | undefined);
|
|
23
|
+
toJSON(): {
|
|
24
|
+
class: "work-error" | "env-error" | "infra-error";
|
|
25
|
+
message: string;
|
|
26
|
+
source: string | undefined;
|
|
27
|
+
logExcerpt: string | undefined;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A manifest-supplied relative path (sync.include, outputs, a datastore key)
|
|
32
|
+
* must stay INSIDE its base after resolution — never escape via `..` or an
|
|
33
|
+
* absolute path. Returns the safe joined absolute path, or throws work-error.
|
|
34
|
+
* This is the guard that keeps file ops from leaving runly's own dirs even
|
|
35
|
+
* on an honest `../shared/.env` typo, not only a malicious manifest.
|
|
36
|
+
*/
|
|
37
|
+
export declare function safeJoin(base: string, rel: string, what: string): string;
|
|
38
|
+
/** A hint naming the daemon's environment when `output` looks like one, else undefined. */
|
|
39
|
+
export declare function environmentHint(output: string): string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* A failed repo command as an error: env-error with the hint when it reads as
|
|
42
|
+
* a missing tool or runtime, else `klass` (work-error: the code's fault).
|
|
43
|
+
*/
|
|
44
|
+
export declare function commandFailure(message: string, source: string | undefined, output: string, klass?: 'work-error' | 'env-error'): BrokerError;
|
|
45
|
+
export declare const now: () => number;
|
|
46
|
+
export declare const shortId: () => string;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
3
|
+
import * as pathMod from 'node:path';
|
|
4
|
+
import { cmdTimeoutS, runBounded } from './exec.js';
|
|
5
|
+
export const sha256 = (data) => createHash('sha256').update(data).digest('hex');
|
|
6
|
+
/**
|
|
7
|
+
* Run a shell command, swallowing failures (best-effort cleanup paths).
|
|
8
|
+
* Bounded: these run inside awaited sweeps, and a hung `drop` against an
|
|
9
|
+
* unreachable appliance used to stall retention forever while each later
|
|
10
|
+
* sweep piled another wedged `sh` onto the same marker.
|
|
11
|
+
*/
|
|
12
|
+
export const runQuiet = (cmd, cwd) => runBounded(cmd, cwd, cmdTimeoutS()).then(() => undefined);
|
|
13
|
+
export const fileHash = (path) => {
|
|
14
|
+
try {
|
|
15
|
+
return sha256(readFileSync(path));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/** One stat, no throw: what a listing of tens of thousands of files can afford per file. */
|
|
22
|
+
export const statOf = (p) => statSync(p, { throwIfNoEntry: false });
|
|
23
|
+
export const isFile = (p) => statOf(p)?.isFile() ?? false;
|
|
24
|
+
/**
|
|
25
|
+
* Minimal glob matcher for manifest patterns (caches, sync.include, outputs,
|
|
26
|
+
* upkeep and build `when:`). Supports **, *, ?. A bare name with no glob chars
|
|
27
|
+
* and no slash matches that path segment anywhere (node_modules). All patterns
|
|
28
|
+
* also protect their subtree (an implicit trailing /**).
|
|
29
|
+
*
|
|
30
|
+
* `**` is anchored at path segments: a leading or inner `**\/` stands for zero
|
|
31
|
+
* or more WHOLE directories, so `**\/bin` matches `bin` and `src/bin` but never
|
|
32
|
+
* `src/Cabin` (it used to compile to `^.*bin`, which made a .NET
|
|
33
|
+
* `caches: [**\/bin]` swallow every source directory whose name ends in "bin").
|
|
34
|
+
* `*` and `?` stay inside one segment. Dotfiles are not special.
|
|
35
|
+
*
|
|
36
|
+
* Compiled patterns are cached: the same few manifest globs are matched
|
|
37
|
+
* against every file of a worktree listing, and compiling them per file cost
|
|
38
|
+
* seconds per `up` on a large tree.
|
|
39
|
+
*/
|
|
40
|
+
const globCache = new Map();
|
|
41
|
+
const GLOB_CACHE_MAX = 4096;
|
|
42
|
+
export function globToRegex(pattern) {
|
|
43
|
+
const hit = globCache.get(pattern);
|
|
44
|
+
if (hit)
|
|
45
|
+
return hit;
|
|
46
|
+
const re = compileGlob(pattern);
|
|
47
|
+
if (globCache.size >= GLOB_CACHE_MAX)
|
|
48
|
+
globCache.clear();
|
|
49
|
+
globCache.set(pattern, re);
|
|
50
|
+
return re;
|
|
51
|
+
}
|
|
52
|
+
function compileGlob(pattern) {
|
|
53
|
+
const p = pattern.replace(/^glob\((.*)\)$/, '$1').replace(/^\.\//, '');
|
|
54
|
+
if (!/[*?[]/.test(p) && !p.includes('/')) {
|
|
55
|
+
return new RegExp(`(^|/)${p.replace(/[.+^${}()|\\]/g, '\\$&')}(/|$)`);
|
|
56
|
+
}
|
|
57
|
+
let re = '';
|
|
58
|
+
for (let i = 0; i < p.length; i++) {
|
|
59
|
+
const c = p[i];
|
|
60
|
+
if (c === '*') {
|
|
61
|
+
if (p[i + 1] === '*') {
|
|
62
|
+
const segmentStart = i === 0 || p[i - 1] === '/';
|
|
63
|
+
if (segmentStart && p[i + 2] === '/') {
|
|
64
|
+
// `**/`: zero or more whole directories.
|
|
65
|
+
re += '(?:.*/)?';
|
|
66
|
+
i += 2;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// A trailing `**` (everything below), or `**` inside a segment.
|
|
70
|
+
re += '.*';
|
|
71
|
+
i++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else
|
|
75
|
+
re += '[^/]*';
|
|
76
|
+
}
|
|
77
|
+
else if (c === '?')
|
|
78
|
+
re += '[^/]';
|
|
79
|
+
else
|
|
80
|
+
re += c.replace(/[.+^${}()|\\[\]]/g, '\\$&');
|
|
81
|
+
}
|
|
82
|
+
return new RegExp(`^${re}(/.*)?$`);
|
|
83
|
+
}
|
|
84
|
+
export const matchesAny = (path, patterns) => {
|
|
85
|
+
for (const p of patterns)
|
|
86
|
+
if (globToRegex(p).test(path))
|
|
87
|
+
return true;
|
|
88
|
+
return false;
|
|
89
|
+
};
|
|
90
|
+
/** Resolve {{...}} placeholders against a nested context object. */
|
|
91
|
+
export function template(str, ctx) {
|
|
92
|
+
return str.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (_, expr) => {
|
|
93
|
+
const val = expr.split('.').reduce((acc, key) => {
|
|
94
|
+
if (acc && typeof acc === 'object' && key in acc) {
|
|
95
|
+
return acc[key];
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}, ctx);
|
|
99
|
+
if (val === undefined || val === null)
|
|
100
|
+
throw new Error(`unresolved template variable {{${expr}}}`);
|
|
101
|
+
return String(val);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export const templateEnv = (env, ctx) => {
|
|
105
|
+
const out = {};
|
|
106
|
+
for (const [k, v] of Object.entries(env ?? {}))
|
|
107
|
+
out[k] = template(v, ctx);
|
|
108
|
+
return out;
|
|
109
|
+
};
|
|
110
|
+
export class BrokerError extends Error {
|
|
111
|
+
klass;
|
|
112
|
+
source;
|
|
113
|
+
logExcerpt;
|
|
114
|
+
constructor(klass, message, source, logExcerpt) {
|
|
115
|
+
super(message);
|
|
116
|
+
this.klass = klass;
|
|
117
|
+
this.source = source;
|
|
118
|
+
this.logExcerpt = logExcerpt;
|
|
119
|
+
}
|
|
120
|
+
toJSON() {
|
|
121
|
+
return { class: this.klass, message: this.message, source: this.source, logExcerpt: this.logExcerpt };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* A manifest-supplied relative path (sync.include, outputs, a datastore key)
|
|
126
|
+
* must stay INSIDE its base after resolution — never escape via `..` or an
|
|
127
|
+
* absolute path. Returns the safe joined absolute path, or throws work-error.
|
|
128
|
+
* This is the guard that keeps file ops from leaving runly's own dirs even
|
|
129
|
+
* on an honest `../shared/.env` typo, not only a malicious manifest.
|
|
130
|
+
*/
|
|
131
|
+
export function safeJoin(base, rel, what) {
|
|
132
|
+
const { resolve, isAbsolute } = pathMod;
|
|
133
|
+
if (isAbsolute(rel))
|
|
134
|
+
throw new BrokerError('work-error', `${what} must be a relative path, got absolute '${rel}'`, 'manifest');
|
|
135
|
+
const abs = resolve(base, rel);
|
|
136
|
+
const baseResolved = resolve(base);
|
|
137
|
+
if (abs !== baseResolved && !abs.startsWith(baseResolved + pathMod.sep)) {
|
|
138
|
+
throw new BrokerError('work-error', `${what} '${rel}' escapes its directory — path traversal is not allowed`, 'manifest');
|
|
139
|
+
}
|
|
140
|
+
return abs;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The output of a failed command that reads as a missing tool or runtime — the
|
|
144
|
+
* DAEMON's environment, not the repo's code. Under a unit it is the
|
|
145
|
+
* environment `runly daemon install` captured; a missing DOTNET_ROOT once made
|
|
146
|
+
* every template bake fail there, reported as the code's fault.
|
|
147
|
+
*/
|
|
148
|
+
const ENVIRONMENTAL = /command not found|: not found\b|You must install or update \.NET|\.NET location: Not found|DOTNET_ROOT|spawn \S+ ENOENT|exec format error|cannot execute binary|env: '?[^\s']+'?: No such file or directory/i;
|
|
149
|
+
/** A hint naming the daemon's environment when `output` looks like one, else undefined. */
|
|
150
|
+
export function environmentHint(output) {
|
|
151
|
+
if (!ENVIRONMENTAL.test(output))
|
|
152
|
+
return undefined;
|
|
153
|
+
const sup = process.env.RUNLY_SUPERVISOR;
|
|
154
|
+
return sup === 'systemd' || sup === 'launchd'
|
|
155
|
+
? ` — this looks like the daemon's environment, not the code: a tool or runtime was not found. The daemon runs under its ${sup} unit with the environment 'runly daemon install' captured (it prints it); re-run the install from a shell that has the tool, or add a variable with --env NAME, then 'runly daemon stop' — the next verb starts it again`
|
|
156
|
+
: ` — this looks like the daemon's environment, not the code: a tool or runtime was not found. The daemon kept the environment of the shell that started it; 'runly daemon stop', and the next verb starts it from yours`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* A failed repo command as an error: env-error with the hint when it reads as
|
|
160
|
+
* a missing tool or runtime, else `klass` (work-error: the code's fault).
|
|
161
|
+
*/
|
|
162
|
+
export function commandFailure(message, source, output, klass = 'work-error') {
|
|
163
|
+
const hint = environmentHint(output);
|
|
164
|
+
return new BrokerError(hint ? 'env-error' : klass, `${message}${hint ?? ''}`, source, output.slice(-800));
|
|
165
|
+
}
|
|
166
|
+
export const now = () => Date.now();
|
|
167
|
+
export const shortId = () => Math.random().toString(36).slice(2, 8);
|
|
168
|
+
//# sourceMappingURL=util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"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"}
|