@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,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Appliances: singular, machine-shared backing services (a Postgres server, a
|
|
3
|
+
* Redis, a MinIO) that environments point at but never own.
|
|
4
|
+
*
|
|
5
|
+
* The contract is ENSURE, NOT OWN (decision 0018). An appliance's identity is
|
|
6
|
+
* its probe address: whoever answers `host:port` IS the appliance, no matter
|
|
7
|
+
* who started it — a compose file yesterday, a teammate's shell, or runly a
|
|
8
|
+
* minute ago. runly never supervises the process (the `start:` command is
|
|
9
|
+
* expected to daemonize, e.g. `docker run -d`), never stops one automatically,
|
|
10
|
+
* and blames nobody's code when one is missing: every failure here is an
|
|
11
|
+
* infra-error by construction.
|
|
12
|
+
*/
|
|
13
|
+
import { runBounded, DEFAULT_CMD_TIMEOUT_S } from '../core/exec.js';
|
|
14
|
+
import { connect } from 'node:net';
|
|
15
|
+
import { mkdirSync, rmdirSync, statSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { stateRoot } from '../core/paths.js';
|
|
18
|
+
import { BrokerError, now, sha256 } from '../core/util.js';
|
|
19
|
+
const PROBE_TIMEOUT_MS = 3000;
|
|
20
|
+
const START_POLL_MS = 500;
|
|
21
|
+
/** A start lock older than this is a crashed starter, not a live one. */
|
|
22
|
+
/**
|
|
23
|
+
* A start lock is stale once nobody could still be legitimately holding it.
|
|
24
|
+
* A fixed 5 minutes was WRONG for an appliance declaring a longer timeout: a
|
|
25
|
+
* second caller stole the lock mid-start and raced the first one's container.
|
|
26
|
+
*/
|
|
27
|
+
const LOCK_STALE_MS = 5 * 60 * 1000;
|
|
28
|
+
const lockStaleMs = (timeoutS) => Math.max(LOCK_STALE_MS, (timeoutS ?? 0) * 1000 + 60_000);
|
|
29
|
+
export function probeTcp(target, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
30
|
+
const [host, portStr] = target.split(':');
|
|
31
|
+
const port = Number(portStr);
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
const sock = connect({ host: host || 'localhost', port, timeout: timeoutMs });
|
|
34
|
+
sock.once('connect', () => {
|
|
35
|
+
sock.end();
|
|
36
|
+
resolve(true);
|
|
37
|
+
});
|
|
38
|
+
const fail = () => {
|
|
39
|
+
sock.destroy();
|
|
40
|
+
resolve(false);
|
|
41
|
+
};
|
|
42
|
+
sock.once('error', fail);
|
|
43
|
+
sock.once('timeout', fail);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Appliance commands are the likeliest to hang: `start: docker run ...` without
|
|
48
|
+
* `-d` never returns, and an unbounded wait there wedges the bind AND leaves
|
|
49
|
+
* the environment permanently busy (so its lease never expires and teardown
|
|
50
|
+
* refuses it). Bounded, in its own process group.
|
|
51
|
+
*/
|
|
52
|
+
const sh = async (cmd, cwd) => {
|
|
53
|
+
const r = await runBounded(cmd, cwd);
|
|
54
|
+
return {
|
|
55
|
+
code: r.code,
|
|
56
|
+
output: r.timedOut ? `${r.output}\n[killed: exceeded ${DEFAULT_CMD_TIMEOUT_S}s]` : r.output,
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* One start attempt per probe target machine-wide, whatever stack asked.
|
|
61
|
+
* Two stacks declaring "the postgres on 5433" race to the same lock, and the
|
|
62
|
+
* loser adopts what the winner started.
|
|
63
|
+
*/
|
|
64
|
+
function lockDir(target) {
|
|
65
|
+
const dir = join(stateRoot(), 'appliances');
|
|
66
|
+
mkdirSync(dir, { recursive: true });
|
|
67
|
+
return join(dir, `${target.replace(/[^A-Za-z0-9._-]/g, '_')}-${sha256(target).slice(0, 8)}.lock`);
|
|
68
|
+
}
|
|
69
|
+
function acquireLock(target, timeoutS) {
|
|
70
|
+
const lock = lockDir(target);
|
|
71
|
+
try {
|
|
72
|
+
mkdirSync(lock);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
try {
|
|
77
|
+
if (now() - statSync(lock).mtimeMs > lockStaleMs(timeoutS)) {
|
|
78
|
+
rmdirSync(lock);
|
|
79
|
+
mkdirSync(lock);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
/* raced — treat as not acquired */
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function releaseLock(target) {
|
|
90
|
+
try {
|
|
91
|
+
rmdirSync(lockDir(target));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
/* already gone */
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function awaitUp(name, spec, root, deadline) {
|
|
98
|
+
while (now() < deadline) {
|
|
99
|
+
if (await probeTcp(spec.probe)) {
|
|
100
|
+
if (!spec.ready)
|
|
101
|
+
return true;
|
|
102
|
+
const r = await sh(spec.ready, root);
|
|
103
|
+
if (r.code === 0)
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
await new Promise((r) => setTimeout(r, START_POLL_MS));
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Probe; if down and a `start:` is declared, start it (once, machine-wide) and
|
|
112
|
+
* wait for the probe (and optional `ready:` gate) to accept. Throws infra-error
|
|
113
|
+
* when the appliance stays unreachable — with the start output when we tried.
|
|
114
|
+
*/
|
|
115
|
+
export async function ensureAppliance(name, spec, root, say) {
|
|
116
|
+
const timeoutMsEarly = (spec.timeout ?? 60) * 1000;
|
|
117
|
+
if (await probeTcp(spec.probe)) {
|
|
118
|
+
// An open TCP port is NOT readiness. Postgres accepts connections while
|
|
119
|
+
// still recovering; a container forwards the port before the server is
|
|
120
|
+
// listening. Returning 'up' here skipped the declared ready: gate entirely,
|
|
121
|
+
// so the bind proceeded against a half-started appliance and the failure
|
|
122
|
+
// surfaced later as an unrelated work-error.
|
|
123
|
+
if (!spec.ready)
|
|
124
|
+
return 'up';
|
|
125
|
+
const first = await sh(spec.ready, root);
|
|
126
|
+
if (first.code === 0)
|
|
127
|
+
return 'up';
|
|
128
|
+
say(`appliance '${name}' answers at ${spec.probe} but is not ready yet — waiting`);
|
|
129
|
+
if (await awaitUp(name, spec, root, now() + timeoutMsEarly))
|
|
130
|
+
return 'adopted';
|
|
131
|
+
throw new BrokerError('infra-error', `appliance '${name}' answers at ${spec.probe} but its ready: gate never passed within ${spec.timeout ?? 60}s`, 'appliance', first.output.slice(0, 800));
|
|
132
|
+
}
|
|
133
|
+
if (!spec.start) {
|
|
134
|
+
throw new BrokerError('infra-error', `appliance '${name}' unreachable at ${spec.probe} and declares no start: — bring it up yourself`, 'appliance');
|
|
135
|
+
}
|
|
136
|
+
const timeoutMs = (spec.timeout ?? 60) * 1000;
|
|
137
|
+
const deadline = now() + timeoutMs;
|
|
138
|
+
if (!acquireLock(spec.probe, spec.timeout)) {
|
|
139
|
+
// Someone else is starting it right now; wait for their result.
|
|
140
|
+
say(`appliance '${name}' is being started by another consumer — waiting`);
|
|
141
|
+
if (await awaitUp(name, spec, root, deadline))
|
|
142
|
+
return 'adopted';
|
|
143
|
+
throw new BrokerError('infra-error', `appliance '${name}' still unreachable at ${spec.probe} after waiting ${spec.timeout ?? 60}s for a concurrent start`, 'appliance');
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
// Locked. Re-probe: it may have come up between our probe and the lock.
|
|
147
|
+
// Same rule as above — the ready: gate decides, not the open port.
|
|
148
|
+
if (await probeTcp(spec.probe) && (await awaitUp(name, spec, root, now() + 1000)))
|
|
149
|
+
return 'up';
|
|
150
|
+
say(`starting appliance '${name}'`);
|
|
151
|
+
const r = await sh(spec.start, root);
|
|
152
|
+
if (r.code !== 0) {
|
|
153
|
+
throw new BrokerError('infra-error', `appliance '${name}' start command failed`, 'appliance', r.output.slice(0, 800));
|
|
154
|
+
}
|
|
155
|
+
if (!(await awaitUp(name, spec, root, deadline))) {
|
|
156
|
+
throw new BrokerError('infra-error', `appliance '${name}' started but never answered at ${spec.probe} within ${spec.timeout ?? 60}s`, 'appliance', r.output.slice(0, 800));
|
|
157
|
+
}
|
|
158
|
+
return 'started';
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
releaseLock(spec.probe);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** Explicit stop verb only — runly never stops an appliance on its own. */
|
|
165
|
+
export async function stopAppliance(name, spec, root) {
|
|
166
|
+
if (!spec.stop) {
|
|
167
|
+
throw new BrokerError('work-error', `appliance '${name}' declares no stop: command`, 'appliance');
|
|
168
|
+
}
|
|
169
|
+
const r = await sh(spec.stop, root);
|
|
170
|
+
if (r.code !== 0) {
|
|
171
|
+
throw new BrokerError('infra-error', `appliance '${name}' stop command failed`, 'appliance', r.output.slice(0, 800));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=appliances.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appliances.js","sourceRoot":"","sources":["../../src/drivers/appliances.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { type DatastoreSpec } from '../core/manifest.js';
|
|
2
|
+
export interface DsHandle {
|
|
3
|
+
envId: string;
|
|
4
|
+
/**
|
|
5
|
+
* Where the datastore's repo commands (create/drop/template_restore) run:
|
|
6
|
+
* the caller's worktree, which is where the environment runs (decision
|
|
7
|
+
* 0032). Teardown falls back to the environment's own directory when the
|
|
8
|
+
* worktree is already gone.
|
|
9
|
+
*/
|
|
10
|
+
cwd: string;
|
|
11
|
+
dataDir: string;
|
|
12
|
+
}
|
|
13
|
+
export interface DsDriver {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
/** The ns for an environment: sqlite = file path; server = SQL-safe db name. */
|
|
16
|
+
ns(h: DsHandle): string;
|
|
17
|
+
/** The connection string services/checks receive. */
|
|
18
|
+
url(h: DsHandle): string;
|
|
19
|
+
/** infra-error (never code blame) when the external server is unreachable. */
|
|
20
|
+
probe(): Promise<void>;
|
|
21
|
+
/** Create/restore the namespace at the preset. force = recreate even if present. */
|
|
22
|
+
ensure(h: DsHandle, preset: string, force: boolean, exists: boolean): Promise<void>;
|
|
23
|
+
/** Best-effort removal (recycle). */
|
|
24
|
+
drop(h: DsHandle): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* The drop as data, for a row that must be reapable without the manifest
|
|
27
|
+
* (a `runly db` copy, decision 0034): the already-templated command for a
|
|
28
|
+
* server datastore, or null when the namespace is a file under the state
|
|
29
|
+
* root (sqlite) — removing that is the engine's to do.
|
|
30
|
+
*/
|
|
31
|
+
dropCommand(h: DsHandle): string | null;
|
|
32
|
+
/** True when the namespace is a local file (sqlite), false for a server database. */
|
|
33
|
+
readonly fileBased: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* How to drop this namespace later without the manifest (decision 0037):
|
|
36
|
+
* the templated command and where to run it, or the file to delete.
|
|
37
|
+
*/
|
|
38
|
+
dropRecipe(h: DsHandle): {
|
|
39
|
+
cmd?: string;
|
|
40
|
+
cwd?: string;
|
|
41
|
+
path?: string;
|
|
42
|
+
ns?: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The template a restore at `preset` uses, as `<stack>/<marker file>` under
|
|
46
|
+
* the templates root, or null when this datastore bakes none. Retention
|
|
47
|
+
* keeps a template while a row references it (decision 0037).
|
|
48
|
+
*/
|
|
49
|
+
templateRef(preset: string): string | null;
|
|
50
|
+
/**
|
|
51
|
+
* Drop this datastore's CURRENT templates (every preset, the current
|
|
52
|
+
* template key) and their server-side databases, so the next restore bakes
|
|
53
|
+
* afresh. Only `--pristine` asks for this (decision 0039): a template whose
|
|
54
|
+
* key matches was baked from the same create command and trigger content,
|
|
55
|
+
* so an `@rebake-template` rule that fires only says which template is
|
|
56
|
+
* current — it never rebakes one that already exists.
|
|
57
|
+
*/
|
|
58
|
+
rebake(cwd?: string): void | Promise<void>;
|
|
59
|
+
/** Is the template a restore at `preset` would use already baked? Always false without templates. */
|
|
60
|
+
templateBaked(preset: string): boolean;
|
|
61
|
+
}
|
|
62
|
+
/** Exclusive: bake, rebake, prune or drop the stack's templates. */
|
|
63
|
+
export declare function withBakeLock<T>(key: string, fn: () => Promise<T>): Promise<T>;
|
|
64
|
+
/** Shared: restore from one of the stack's templates; no writer runs meanwhile. */
|
|
65
|
+
export declare function withTemplateRead<T>(key: string, fn: () => Promise<T>): Promise<T>;
|
|
66
|
+
/**
|
|
67
|
+
* One datastore's templates: the stack lock shared, then this datastore's lock
|
|
68
|
+
* — exclusive (`write`) to bake or drop one, shared to restore from one. The
|
|
69
|
+
* order is always stack, then datastore, and nothing waits for the stack lock
|
|
70
|
+
* while holding a datastore lock, so the two levels cannot deadlock.
|
|
71
|
+
*/
|
|
72
|
+
export declare function withDatastoreLock<T>(stackId: string, ds: string, write: boolean, fn: () => Promise<T>): Promise<T>;
|
|
73
|
+
/**
|
|
74
|
+
* Baked-template markers are self-describing (vetbill-1i49): they carry the
|
|
75
|
+
* server-side template ns AND the already-templated drop command, so
|
|
76
|
+
* retention/rebake can DROP the actual database when the marker is pruned —
|
|
77
|
+
* previously only the marker file was deleted and `backlot_tpl_*` databases
|
|
78
|
+
* leaked on the appliance forever. Legacy markers (bare ns string) still
|
|
79
|
+
* parse; they just can't be dropped server-side.
|
|
80
|
+
*/
|
|
81
|
+
export interface BakedMarker {
|
|
82
|
+
v: 1;
|
|
83
|
+
ns: string;
|
|
84
|
+
drop: string | null;
|
|
85
|
+
/** The datastore it was baked for (0.18+), so a rebake drops only that datastore's. */
|
|
86
|
+
ds?: string;
|
|
87
|
+
/**
|
|
88
|
+
* When, and by which bake (0.19): two bakes of one key write different
|
|
89
|
+
* markers, so a restore that failed can tell a sibling's rebake from the
|
|
90
|
+
* template it failed on — the content alone is deterministic.
|
|
91
|
+
*/
|
|
92
|
+
bakedAt?: number;
|
|
93
|
+
nonce?: string;
|
|
94
|
+
/** In `<name>@shared/` (0.20, decision 0044). */
|
|
95
|
+
shared?: boolean;
|
|
96
|
+
/** A `--pristine` bake private to one worktree of a shared datastore (0.20). */
|
|
97
|
+
private?: boolean;
|
|
98
|
+
/** The stack dir a shared marker was adopted from, naming its database (0.20). */
|
|
99
|
+
adoptedFrom?: string;
|
|
100
|
+
}
|
|
101
|
+
export declare function parseBakedMarker(content: string): BakedMarker;
|
|
102
|
+
export declare function hasOtherTemplateOwner(full: string, ns: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Shared templates (decision 0044). A template whose key folds in the content
|
|
105
|
+
* of its `@rebake-template` trigger files is a function of (manifest name,
|
|
106
|
+
* datastore, preset, create command, trigger content) — never of the worktree
|
|
107
|
+
* path — so every worktree of one manifest name on this daemon can restore
|
|
108
|
+
* from the same one. They live in `<name>@shared/` next to the per-stack dirs
|
|
109
|
+
* (`<name>-<8 chars>`); a manifest name cannot contain `@`, so the two never
|
|
110
|
+
* collide.
|
|
111
|
+
*/
|
|
112
|
+
export declare const SHARED_TEMPLATE_SUFFIX = "@shared";
|
|
113
|
+
export declare const sharedTemplateDir: (project: string) => string;
|
|
114
|
+
export declare const isSharedTemplateDir: (dir: string) => boolean;
|
|
115
|
+
/** The manifest name a templates dir belongs to: `<name>@shared`, or a stack id `<name>-<8-char hash>`. */
|
|
116
|
+
export declare function templateProject(dir: string): string;
|
|
117
|
+
/**
|
|
118
|
+
* A template baked by `--pristine` for one worktree of a shared datastore
|
|
119
|
+
* (decision 0044): `<ds>-<preset>@<key>.own.<ext>` in the stack's own dir. It
|
|
120
|
+
* outranks the shared one for that worktree while it exists, and retention
|
|
121
|
+
* keeps it only while a row references it.
|
|
122
|
+
*/
|
|
123
|
+
export declare const PRIVATE_TEMPLATE_TAG = ".own";
|
|
124
|
+
export declare const isPrivateTemplateFile: (file: string) => boolean;
|
|
125
|
+
/** How a datastore's templates are scoped. */
|
|
126
|
+
export interface TemplateScope {
|
|
127
|
+
/** Content bake key: the `@rebake-template` trigger content (vetbill-1i49). */
|
|
128
|
+
bakeKey?: string;
|
|
129
|
+
/**
|
|
130
|
+
* The manifest name. With a bake key and without `share_templates: false`,
|
|
131
|
+
* the templates are shared by every worktree of this name (decision 0044).
|
|
132
|
+
* Omitted: per stack, as before 0.20.
|
|
133
|
+
*/
|
|
134
|
+
project?: string;
|
|
135
|
+
/** `--pristine` on a shared datastore: bake a template private to this worktree instead of dropping the shared one. */
|
|
136
|
+
privateBake?: boolean;
|
|
137
|
+
}
|
|
138
|
+
/** Whether a datastore's templates are shared across worktrees (decision 0044). */
|
|
139
|
+
export declare function sharesTemplates(spec: DatastoreSpec, scope: TemplateScope): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* `scope` is the template scope; a bare string is the bake key alone (per
|
|
142
|
+
* stack, as before 0.20 — unit tests and callers that only read the url).
|
|
143
|
+
*/
|
|
144
|
+
export declare function makeDatastore(name: string, spec: DatastoreSpec, stackId: string, scope?: string | TemplateScope): DsDriver;
|