@volter/world-runtime 2.0.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/dist/known-external-services.json +1108 -0
- package/dist/src/ancestry.d.ts +2 -0
- package/dist/src/ancestry.js +42 -0
- package/dist/src/app-url.d.ts +47 -0
- package/dist/src/app-url.js +239 -0
- package/dist/src/attach.d.ts +48 -0
- package/dist/src/attach.js +87 -0
- package/dist/src/branch.d.ts +20 -0
- package/dist/src/branch.js +65 -0
- package/dist/src/browser-proxy-cli.d.ts +2 -0
- package/dist/src/browser-proxy-cli.js +41 -0
- package/dist/src/ca-trust.d.ts +5 -0
- package/dist/src/ca-trust.js +64 -0
- package/dist/src/catalog.d.ts +31 -0
- package/dist/src/catalog.js +148 -0
- package/dist/src/changeset.d.ts +142 -0
- package/dist/src/changeset.js +570 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +1262 -0
- package/dist/src/command-lifetime.d.ts +15 -0
- package/dist/src/command-lifetime.js +98 -0
- package/dist/src/configs.d.ts +18 -0
- package/dist/src/configs.js +119 -0
- package/dist/src/console-apart.d.ts +38 -0
- package/dist/src/console-apart.js +107 -0
- package/dist/src/consumers.d.ts +46 -0
- package/dist/src/consumers.js +200 -0
- package/dist/src/covers.d.ts +183 -0
- package/dist/src/covers.js +800 -0
- package/dist/src/fixture-env.d.ts +42 -0
- package/dist/src/fixture-env.js +221 -0
- package/dist/src/host-cli.d.ts +2 -0
- package/dist/src/host-cli.js +92 -0
- package/dist/src/host-fault-fixture.d.ts +32 -0
- package/dist/src/host-fault-fixture.js +100 -0
- package/dist/src/host-worker.d.ts +1 -0
- package/dist/src/host-worker.js +23 -0
- package/dist/src/host.d.ts +38 -0
- package/dist/src/host.js +135 -0
- package/dist/src/index.d.ts +48 -0
- package/dist/src/index.js +35 -0
- package/dist/src/infra-cli.d.ts +2 -0
- package/dist/src/infra-cli.js +136 -0
- package/dist/src/init.d.ts +227 -0
- package/dist/src/init.js +1117 -0
- package/dist/src/inject-map.d.ts +34 -0
- package/dist/src/inject-map.js +56 -0
- package/dist/src/lifecycle-record.d.ts +47 -0
- package/dist/src/lifecycle-record.js +196 -0
- package/dist/src/origin.d.ts +31 -0
- package/dist/src/origin.js +139 -0
- package/dist/src/pack-facts.d.ts +75 -0
- package/dist/src/pack-facts.js +98 -0
- package/dist/src/pglite-backing.d.ts +21 -0
- package/dist/src/pglite-backing.js +158 -0
- package/dist/src/pglite-host.mjs +147 -0
- package/dist/src/placeholder.d.ts +20 -0
- package/dist/src/placeholder.js +100 -0
- package/dist/src/prerequisites.d.ts +21 -0
- package/dist/src/prerequisites.js +49 -0
- package/dist/src/process-groups.d.ts +4 -0
- package/dist/src/process-groups.js +49 -0
- package/dist/src/project-inspect.d.ts +109 -0
- package/dist/src/project-inspect.js +827 -0
- package/dist/src/proxy-daemon.d.ts +2 -0
- package/dist/src/proxy-daemon.js +18 -0
- package/dist/src/redirect-proxy.d.ts +105 -0
- package/dist/src/redirect-proxy.js +665 -0
- package/dist/src/reflect.d.ts +74 -0
- package/dist/src/reflect.js +392 -0
- package/dist/src/resources.d.ts +26 -0
- package/dist/src/resources.js +22 -0
- package/dist/src/root.d.ts +114 -0
- package/dist/src/root.js +312 -0
- package/dist/src/run-task-worker.d.ts +1 -0
- package/dist/src/run-task-worker.js +38 -0
- package/dist/src/run-task.d.ts +18 -0
- package/dist/src/run-task.js +48 -0
- package/dist/src/runtime-test-support.d.ts +59 -0
- package/dist/src/runtime-test-support.js +205 -0
- package/dist/src/runtime.d.ts +256 -0
- package/dist/src/runtime.js +3502 -0
- package/dist/src/schema.d.ts +449 -0
- package/dist/src/schema.js +605 -0
- package/dist/src/serve.d.ts +30 -0
- package/dist/src/serve.js +82 -0
- package/dist/src/served-world.d.ts +194 -0
- package/dist/src/served-world.js +986 -0
- package/dist/src/service-exit.d.ts +46 -0
- package/dist/src/service-exit.js +195 -0
- package/dist/src/service-recorder.d.ts +1 -0
- package/dist/src/service-recorder.js +121 -0
- package/dist/src/sibling.d.ts +1 -0
- package/dist/src/sibling.js +9 -0
- package/dist/src/signals.d.ts +1 -0
- package/dist/src/signals.js +11 -0
- package/dist/src/storage-capacity.d.ts +8 -0
- package/dist/src/storage-capacity.js +61 -0
- package/dist/src/tail.d.ts +30 -0
- package/dist/src/tail.js +160 -0
- package/dist/src/tcp-port.d.ts +2 -0
- package/dist/src/tcp-port.js +36 -0
- package/dist/src/up-task-worker.d.ts +1 -0
- package/dist/src/up-task-worker.js +61 -0
- package/dist/src/up-task.d.ts +17 -0
- package/dist/src/up-task.js +49 -0
- package/dist/src/websocket-relay.d.ts +3 -0
- package/dist/src/websocket-relay.js +40 -0
- package/known-external-services.json +1108 -0
- package/package.json +83 -0
- package/src/ancestry.ts +36 -0
- package/src/app-url.ts +253 -0
- package/src/attach.ts +117 -0
- package/src/branch.ts +63 -0
- package/src/browser-proxy-cli.ts +44 -0
- package/src/ca-trust.ts +57 -0
- package/src/catalog.ts +156 -0
- package/src/changeset.ts +627 -0
- package/src/cli.ts +1111 -0
- package/src/command-lifetime.ts +79 -0
- package/src/configs.ts +110 -0
- package/src/console-apart.ts +90 -0
- package/src/consumers.ts +185 -0
- package/src/covers.ts +934 -0
- package/src/fixture-env.ts +230 -0
- package/src/host-cli.ts +90 -0
- package/src/host-worker.ts +23 -0
- package/src/host.ts +169 -0
- package/src/index.ts +171 -0
- package/src/infra-cli.ts +133 -0
- package/src/init.ts +1316 -0
- package/src/inject-map.ts +72 -0
- package/src/lifecycle-record.ts +168 -0
- package/src/origin.ts +134 -0
- package/src/pack-facts.ts +128 -0
- package/src/pglite-backing.ts +141 -0
- package/src/pglite-host.mjs +147 -0
- package/src/placeholder.ts +89 -0
- package/src/prerequisites.ts +66 -0
- package/src/process-groups.ts +33 -0
- package/src/project-inspect.ts +770 -0
- package/src/proxy-daemon.ts +21 -0
- package/src/redirect-proxy.ts +684 -0
- package/src/reflect.ts +440 -0
- package/src/resources.ts +22 -0
- package/src/root.ts +290 -0
- package/src/run-task-worker.ts +27 -0
- package/src/run-task.ts +44 -0
- package/src/runtime-test-support.ts +208 -0
- package/src/runtime.ts +3357 -0
- package/src/schema.ts +922 -0
- package/src/serve.ts +102 -0
- package/src/served-world.ts +812 -0
- package/src/service-exit.ts +175 -0
- package/src/service-recorder.ts +89 -0
- package/src/sibling.ts +10 -0
- package/src/signals.ts +10 -0
- package/src/storage-capacity.ts +60 -0
- package/src/tail.ts +205 -0
- package/src/tcp-port.ts +35 -0
- package/src/up-task-worker.ts +40 -0
- package/src/up-task.ts +45 -0
- package/src/websocket-relay.ts +32 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Shared setup for the runtime test files. Extracted so runtime.test.ts could be split by
|
|
2
|
+
// concern: 25 sequential tests in one process floored the gate at ~24s, and the gate shards
|
|
3
|
+
// per FILE. Every helper lives here — including the five that used to sit BETWEEN tests,
|
|
4
|
+
// whose loss broke the first attempt at this split.
|
|
5
|
+
import { afterEach } from 'bun:test';
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import net from 'node:net';
|
|
9
|
+
import { downWorld, statusWorld } from "./runtime.js";
|
|
10
|
+
import { stateDirName } from '@volter/world-core';
|
|
11
|
+
/** Test-only seam (see runtime.ts `proxyDaemonDeadlineMs`): force the daemon-poll deadline to `ms`
|
|
12
|
+
* for the duration of `fn`, then restore whatever was there before — deterministic, no sleeps. */
|
|
13
|
+
export async function withProxyDeadline(ms, fn) {
|
|
14
|
+
const prev = process.env.VOLTER_PROXY_DEADLINE_MS;
|
|
15
|
+
process.env.VOLTER_PROXY_DEADLINE_MS = String(ms);
|
|
16
|
+
try {
|
|
17
|
+
return await fn();
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
if (prev === undefined)
|
|
21
|
+
delete process.env.VOLTER_PROXY_DEADLINE_MS;
|
|
22
|
+
else
|
|
23
|
+
process.env.VOLTER_PROXY_DEADLINE_MS = prev;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Best-effort cleanup for the forced-timeout tests below: with the deadline forced to 0, the
|
|
27
|
+
* detached proxy-daemon child is still spawned and may go on to write `proxy.json` a little later
|
|
28
|
+
* (asynchronously, after our synchronous poll already gave up) — kill it so it doesn't linger as an
|
|
29
|
+
* orphan process once the test's tmp root is discarded. Never gates test pass/fail. */
|
|
30
|
+
export async function reapLeakedProxyDaemon(root, name) {
|
|
31
|
+
const proxyJsonPath = join(root, stateDirName(), 'worlds', name, 'proxy.json');
|
|
32
|
+
const deadline = Date.now() + 3000;
|
|
33
|
+
while (Date.now() < deadline) {
|
|
34
|
+
if (existsSync(proxyJsonPath)) {
|
|
35
|
+
try {
|
|
36
|
+
const state = JSON.parse(readFileSync(proxyJsonPath, 'utf8'));
|
|
37
|
+
if (state.pid) {
|
|
38
|
+
try {
|
|
39
|
+
process.kill(state.pid, 'SIGTERM');
|
|
40
|
+
}
|
|
41
|
+
catch { /* already gone */ }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch { /* ignore parse races */ }
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** A hostname under the IANA-reserved `.invalid` TLD (RFC 2606) — guaranteed to NEVER resolve, on
|
|
51
|
+
* any network, forever. Used as the "untwinned public host" below: it is unambiguously non-local
|
|
52
|
+
* (so `isLocalOrPrivateHost` never exempts it) and unambiguously not a vendor (so it is never
|
|
53
|
+
* redirected to a twin), and a real (non-strict-egress) attempt to reach it fails FAST on DNS
|
|
54
|
+
* resolution — offline and deterministic, no dependency on real internet access. */
|
|
55
|
+
export const UNTWINNED_HOST = 'definitely-untwinned.invalid';
|
|
56
|
+
/** Drive a raw CONNECT through the ambient redirect proxy and collect whatever bytes come back
|
|
57
|
+
* before the socket closes (or `timeoutMs` elapses, as a safety net — a blind-tunnel attempt to an
|
|
58
|
+
* unresolvable host closes with NO data at all, so this never actually waits the full timeout). */
|
|
59
|
+
export function connectThroughProxy(port, host, targetHost, targetPort = 443, timeoutMs = 3000) {
|
|
60
|
+
return new Promise((resolvePromise, reject) => {
|
|
61
|
+
const sock = net.connect(port, host, () => {
|
|
62
|
+
sock.write(`CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\nHost: ${targetHost}:${targetPort}\r\n\r\n`);
|
|
63
|
+
});
|
|
64
|
+
let data = '';
|
|
65
|
+
const timer = setTimeout(() => { sock.destroy(); resolvePromise(data); }, timeoutMs);
|
|
66
|
+
sock.on('data', (chunk) => { data += chunk.toString('utf8'); });
|
|
67
|
+
sock.on('close', () => { clearTimeout(timer); resolvePromise(data); });
|
|
68
|
+
sock.on('error', (err) => { clearTimeout(timer); reject(err); });
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** Force `opensslAvailable()` to false via its TEST-ONLY seam (see `redirect-proxy.ts`). A
|
|
72
|
+
* PATH-shim (fake `openssl` binary prepended to PATH) was tried first and does NOT reliably work
|
|
73
|
+
* here: `opensslAvailable()`'s `spawnSync('openssl', …)` call passes no explicit `env`, and this
|
|
74
|
+
* runtime resolves the executable against a PATH snapshotted at process start rather than a
|
|
75
|
+
* same-process `process.env.PATH` mutation made mid-test (verified: a fresh child process DOES
|
|
76
|
+
* see a shimmed PATH, but mutating PATH in the already-running test process does not change what
|
|
77
|
+
* THIS process's own `spawnSync` resolves) — so the deterministic, seam-based override is used. */
|
|
78
|
+
export function withNoOpenssl() {
|
|
79
|
+
const previous = process.env.VOLTER_TEST_NO_OPENSSL;
|
|
80
|
+
process.env.VOLTER_TEST_NO_OPENSSL = '1';
|
|
81
|
+
return {
|
|
82
|
+
restore: () => {
|
|
83
|
+
if (previous === undefined)
|
|
84
|
+
delete process.env.VOLTER_TEST_NO_OPENSSL;
|
|
85
|
+
else
|
|
86
|
+
process.env.VOLTER_TEST_NO_OPENSSL = previous;
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export const worlds = [];
|
|
91
|
+
/**
|
|
92
|
+
* Register the teardown hook for the CALLING test file. Every file that pushes to `worlds` must
|
|
93
|
+
* call this at top level.
|
|
94
|
+
*
|
|
95
|
+
* It has to be a function call rather than a module-scope `afterEach`, and that distinction was an
|
|
96
|
+
* eight-file process leak. A hook registered while this module's body runs belongs to whichever
|
|
97
|
+
* file happened to import it FIRST; module caching means the body never runs again, so every other
|
|
98
|
+
* file got no hook at all. Each file individually looked clean — it was the first importer — and
|
|
99
|
+
* only a whole-directory run leaked, which is exactly the shape that reads as flakiness. The
|
|
100
|
+
* stray proxy daemons and tunnel stubs then held ports until unrelated HTTP tests started failing.
|
|
101
|
+
*
|
|
102
|
+
* Teardown also settles ALL worlds before reporting: the list is spliced empty up front, so a bare
|
|
103
|
+
* `for … await` loop that rejected on the first world abandoned the rest with nothing left holding
|
|
104
|
+
* a reference to them. Nothing about a world that failed to stop makes the next one less important
|
|
105
|
+
* to stop.
|
|
106
|
+
*/
|
|
107
|
+
export function useWorldCleanup() {
|
|
108
|
+
afterEach(async () => {
|
|
109
|
+
const pending = worlds.splice(0);
|
|
110
|
+
const failures = await Promise.allSettled(pending.map((w) => downWorld(w.name, w.root)));
|
|
111
|
+
const errors = failures.filter((f) => f.status === 'rejected');
|
|
112
|
+
// Reported, never swallowed: a world that cannot be torn down is a real finding about the
|
|
113
|
+
// runtime, and silently ignoring it is how this stayed invisible for so long.
|
|
114
|
+
if (errors.length) {
|
|
115
|
+
throw new Error(`world teardown failed for ${errors.length}/${pending.length} world(s): ${errors.map((e) => String(e.reason)).join('; ')}`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
export function writeRaceConfig(root, id) {
|
|
120
|
+
mkdirSync(join(root, 'worlds', 'configs'), { recursive: true });
|
|
121
|
+
writeFileSync(join(root, 'worlds', 'configs', `${id}.json`), JSON.stringify({
|
|
122
|
+
id,
|
|
123
|
+
services: [
|
|
124
|
+
{
|
|
125
|
+
id: 'app',
|
|
126
|
+
type: 'process',
|
|
127
|
+
command: 'node',
|
|
128
|
+
args: ['-e', "require('node:http').createServer((_req,res)=>res.end('ok')).listen(Number(process.env.PORT),'127.0.0.1')"],
|
|
129
|
+
env: { NODE_OPTIONS: '' },
|
|
130
|
+
portArg: false,
|
|
131
|
+
rootArg: false,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
/** Bounded wait (no fixed sleeps) for SIGTERM'd services to actually exit after downWorld. */
|
|
137
|
+
export async function waitForWorldDown(name, root) {
|
|
138
|
+
const deadline = Date.now() + 5_000;
|
|
139
|
+
while (Date.now() < deadline) {
|
|
140
|
+
if (!statusWorld(name, root).running)
|
|
141
|
+
return true;
|
|
142
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
143
|
+
}
|
|
144
|
+
return !statusWorld(name, root).running;
|
|
145
|
+
}
|
|
146
|
+
export function pidAlive(pid) {
|
|
147
|
+
try {
|
|
148
|
+
process.kill(pid, 0);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// --- TWIN-60: `--mode sealed` must actually ENFORCE strict egress (VOLTER_TWIN_STRICT_EGRESS),
|
|
156
|
+
// not just record intent (VOLTER_WORLD_SEALED) that nothing consumes. One vendor twin (s3) is
|
|
157
|
+
// enough to exercise both enforcement points: the Node injector (control-plane/inject.cjs, HTTP
|
|
158
|
+
// path) and the ambient TLS redirect proxy (redirect-proxy.ts, CONNECT path).
|
|
159
|
+
export function writeSingleTwinConfig(root, id) {
|
|
160
|
+
mkdirSync(join(root, 'worlds', 'configs'), { recursive: true });
|
|
161
|
+
writeFileSync(join(root, 'worlds', 'configs', `${id}.json`), JSON.stringify({
|
|
162
|
+
id,
|
|
163
|
+
services: [
|
|
164
|
+
{
|
|
165
|
+
id: 's3',
|
|
166
|
+
command: 'node',
|
|
167
|
+
args: ['-e', "require('node:http').createServer((_req,res)=>res.end('s3')).listen(Number(process.env.PORT),'127.0.0.1')"],
|
|
168
|
+
env: { NODE_OPTIONS: '' },
|
|
169
|
+
injectEnv: 'S3_TWIN_URL',
|
|
170
|
+
portArg: false,
|
|
171
|
+
rootArg: false,
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
/** Twin + a second (non-twin) service — the proxy-attempt loop tries the ambient proxy again
|
|
177
|
+
* before starting each subsequent service, so this reaches the ambient proxy regardless of
|
|
178
|
+
* whether the single/last-service re-evaluation (TWIN-64) has landed yet — kept independent of
|
|
179
|
+
* that fix so this CONNECT-level test exercises TWIN-60's wiring in isolation. */
|
|
180
|
+
export function writeTwinPlusAppConfig(root, id) {
|
|
181
|
+
mkdirSync(join(root, 'worlds', 'configs'), { recursive: true });
|
|
182
|
+
writeFileSync(join(root, 'worlds', 'configs', `${id}.json`), JSON.stringify({
|
|
183
|
+
id,
|
|
184
|
+
services: [
|
|
185
|
+
{
|
|
186
|
+
id: 's3',
|
|
187
|
+
command: 'node',
|
|
188
|
+
args: ['-e', "require('node:http').createServer((_req,res)=>res.end('s3')).listen(Number(process.env.PORT),'127.0.0.1')"],
|
|
189
|
+
env: { NODE_OPTIONS: '' },
|
|
190
|
+
injectEnv: 'S3_TWIN_URL',
|
|
191
|
+
portArg: false,
|
|
192
|
+
rootArg: false,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: 'app',
|
|
196
|
+
type: 'process',
|
|
197
|
+
command: 'node',
|
|
198
|
+
args: ['-e', "require('node:http').createServer((_req,res)=>res.end('ok')).listen(Number(process.env.PORT),'127.0.0.1')"],
|
|
199
|
+
env: { NODE_OPTIONS: '' },
|
|
200
|
+
portArg: false,
|
|
201
|
+
rootArg: false,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { type CaTrustInputs } from './ca-trust.js';
|
|
2
|
+
import type { WorldInstance, WorldIsolation, WorldMode, WorldRunOutcome } from './schema.js';
|
|
3
|
+
/** The World's events file: every service's start and end, and the World's own stops, one JSON line each. */
|
|
4
|
+
export declare function worldEventsPath(logs: string): string;
|
|
5
|
+
export type UpWorldOptions = {
|
|
6
|
+
owner?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
root?: string;
|
|
9
|
+
mode?: WorldMode;
|
|
10
|
+
/** Overrides the config's `isolation` for this boot (e.g. force 'process' for a share world). */
|
|
11
|
+
isolation?: WorldIsolation;
|
|
12
|
+
envFile?: string;
|
|
13
|
+
share?: Omit<ShareWorldOptions, 'root' | 'service' | 'verifyPath'> & {
|
|
14
|
+
verifyPath?: string | false;
|
|
15
|
+
};
|
|
16
|
+
/** `checkout`: bring a stopped world back with its state — the data dir, marks, changesets and
|
|
17
|
+
* episodes survive the boot; logs and the pid file start clean. Never the default: `up` starts
|
|
18
|
+
* clean, and the world dir is the reproducible story. */
|
|
19
|
+
keepState?: boolean;
|
|
20
|
+
};
|
|
21
|
+
export type ShareWorldOptions = {
|
|
22
|
+
root?: string;
|
|
23
|
+
service?: string;
|
|
24
|
+
provider?: 'cloudflare-quick' | 'command';
|
|
25
|
+
command?: string;
|
|
26
|
+
args?: string[];
|
|
27
|
+
ephemeral?: boolean;
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
verifyPath?: string | false;
|
|
30
|
+
};
|
|
31
|
+
export type ShareWorldServicesOptions = Omit<ShareWorldOptions, 'service' | 'verifyPath'> & {
|
|
32
|
+
service?: string;
|
|
33
|
+
verifyPath?: string | false;
|
|
34
|
+
};
|
|
35
|
+
export type WorldDoctorCheck = {
|
|
36
|
+
id: string;
|
|
37
|
+
ok: boolean;
|
|
38
|
+
message: string;
|
|
39
|
+
};
|
|
40
|
+
export type WorldDoctorReport = {
|
|
41
|
+
name: string;
|
|
42
|
+
ok: boolean;
|
|
43
|
+
checks: WorldDoctorCheck[];
|
|
44
|
+
};
|
|
45
|
+
export type RunWorldOptions = UpWorldOptions & {
|
|
46
|
+
keep?: boolean;
|
|
47
|
+
verbose?: boolean;
|
|
48
|
+
};
|
|
49
|
+
export type WorldUrlInfo = {
|
|
50
|
+
name: string;
|
|
51
|
+
mode: WorldMode;
|
|
52
|
+
services: Record<string, {
|
|
53
|
+
localUrl: string;
|
|
54
|
+
publicUrl?: string;
|
|
55
|
+
publicReady?: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
58
|
+
/** Exported for the reflect attachment (reflect.ts), whose routes file lives in
|
|
59
|
+
* the instance dir — one layout owner, no duplicated path knowledge. */
|
|
60
|
+
/** THE WORLD CLOCK file (TWIN-PROGRAMMING-MODEL P3): a single frozen ISO instant every twin
|
|
61
|
+
* stamps from (kernel worldNow()); absent → real wall-clock. Written only by the
|
|
62
|
+
* `volter-world clock` operator door — time is physics, advanced explicitly, never drifting. */
|
|
63
|
+
export declare function clockFile(root: string, name: string): string;
|
|
64
|
+
export declare function instanceDir(root: string, name: string): string;
|
|
65
|
+
export type SharingClaim = {
|
|
66
|
+
token: string;
|
|
67
|
+
pid: number;
|
|
68
|
+
hostname: string;
|
|
69
|
+
serviceId?: string;
|
|
70
|
+
claimedAt: string;
|
|
71
|
+
heartbeatAt: string;
|
|
72
|
+
};
|
|
73
|
+
/** Exported for direct drive: the liveness rule, pure over a claim. */
|
|
74
|
+
export declare function sharingClaimAlive(claim: SharingClaim): boolean;
|
|
75
|
+
/** The process's environment as the world admits it: what `stripEnv` names stays outside. */
|
|
76
|
+
export declare function admittedEnv(strip: string[] | undefined, env?: NodeJS.ProcessEnv): Record<string, string>;
|
|
77
|
+
export declare function saveWorldInstance(instance: WorldInstance): void;
|
|
78
|
+
export declare function upWorld(configId: string, options?: UpWorldOptions, abort?: AbortSignal): Promise<WorldInstance>;
|
|
79
|
+
export declare function downWorld(name: string, root?: string, options?: {
|
|
80
|
+
graceMs?: number;
|
|
81
|
+
purge?: boolean;
|
|
82
|
+
expectedCreatedAt?: string;
|
|
83
|
+
}): Promise<{
|
|
84
|
+
name: string;
|
|
85
|
+
stopped: number[];
|
|
86
|
+
escalated: number[];
|
|
87
|
+
externalStopped: string[];
|
|
88
|
+
externalErrors: string[];
|
|
89
|
+
notSignalled?: string[];
|
|
90
|
+
purged?: string;
|
|
91
|
+
}>;
|
|
92
|
+
/** A service's state as a process, not only its leader: `lingering` is a service whose World parent ended while its
|
|
93
|
+
* process group still holds live processes (a server's workers, a runtime's child), which `down` still stops. */
|
|
94
|
+
export type ServiceState = 'running' | 'stopped' | 'lingering' | 'gave-up' | 'external';
|
|
95
|
+
export declare function statusWorld(name: string, root?: string, options?: {
|
|
96
|
+
readOnly?: boolean;
|
|
97
|
+
light?: boolean;
|
|
98
|
+
}): WorldInstance & {
|
|
99
|
+
running: boolean;
|
|
100
|
+
livePids: number[];
|
|
101
|
+
serviceStates: Record<string, ServiceState>;
|
|
102
|
+
degraded: boolean;
|
|
103
|
+
};
|
|
104
|
+
export type WorldCleanupEntry = {
|
|
105
|
+
name: string;
|
|
106
|
+
path: string;
|
|
107
|
+
eligible: boolean;
|
|
108
|
+
reason: string;
|
|
109
|
+
stoppedAt?: string;
|
|
110
|
+
logicalBytes?: number;
|
|
111
|
+
removed?: boolean;
|
|
112
|
+
};
|
|
113
|
+
/** Preview by default. Apply holds up's lock across fresh eligibility checking and deletion.
|
|
114
|
+
* Never call down here: that would turn a stale preview into permission to kill a new World. */
|
|
115
|
+
export declare function pruneWorlds(options?: {
|
|
116
|
+
root?: string;
|
|
117
|
+
name?: string;
|
|
118
|
+
apply?: boolean;
|
|
119
|
+
}): {
|
|
120
|
+
root: string;
|
|
121
|
+
apply: boolean;
|
|
122
|
+
entries: WorldCleanupEntry[];
|
|
123
|
+
};
|
|
124
|
+
export declare function parseCloudflareQuickPublicUrl(candidate: string): string | undefined;
|
|
125
|
+
export declare function shareWorld(name: string, options?: ShareWorldOptions): Promise<WorldInstance>;
|
|
126
|
+
export declare function shareWorldServices(name: string, options?: ShareWorldServicesOptions): Promise<WorldInstance>;
|
|
127
|
+
/** TWIN-62: `unshare` must give tunnels the same contract plain `downWorld` already gives owned
|
|
128
|
+
* services — SIGTERM, confirm dead (escalating to SIGKILL past the grace), and only THEN erase the
|
|
129
|
+
* tunnel facts/pids. A best-effort SIGTERM with no confirmation can leave a `cloudflared` that
|
|
130
|
+
* ignores SIGTERM serving the public URL forever while instance.json (and the pids file) say
|
|
131
|
+
* nothing is shared — the exact "half-shared" state docs/concepts/worlds.md promises cannot exist. Stays
|
|
132
|
+
* synchronous (via `killSurvivorsAfterGraceSync`) to match the existing signature/call sites
|
|
133
|
+
* (the CLI does not await it). Returns the instance plus the tunnel pids that needed escalating,
|
|
134
|
+
* for callers that want to report it. */
|
|
135
|
+
export declare function unshareWorld(name: string, options?: {
|
|
136
|
+
root?: string;
|
|
137
|
+
service?: string;
|
|
138
|
+
graceMs?: number;
|
|
139
|
+
}): WorldInstance & {
|
|
140
|
+
escalated: number[];
|
|
141
|
+
};
|
|
142
|
+
export declare function doctorWorld(name: string, options?: {
|
|
143
|
+
root?: string;
|
|
144
|
+
verifyPublic?: boolean;
|
|
145
|
+
timeoutMs?: number;
|
|
146
|
+
}): Promise<WorldDoctorReport>;
|
|
147
|
+
export declare function urlsWorld(name: string, root?: string): WorldUrlInfo;
|
|
148
|
+
/**
|
|
149
|
+
* The clean (unquoted) base URL(s) of a RUNNING world's services — the `volter-world url`
|
|
150
|
+
* surface. Resolved from the generated instance, so callers never shell-parse `world.env`
|
|
151
|
+
* (its values are single-quoted). One `{ id, url }` when `service` is named; every
|
|
152
|
+
* URL-bearing service otherwise ('external' services have no assigned URL — their endpoints
|
|
153
|
+
* are discovered env vars, so they are omitted from the list and refused by name).
|
|
154
|
+
*/
|
|
155
|
+
export declare function urlWorld(name: string, options?: {
|
|
156
|
+
service?: string;
|
|
157
|
+
root?: string;
|
|
158
|
+
}): Array<{
|
|
159
|
+
id: string;
|
|
160
|
+
url: string;
|
|
161
|
+
}>;
|
|
162
|
+
export declare function runWorld(configId: string, command: string[], options?: RunWorldOptions): Promise<{
|
|
163
|
+
instance: WorldInstance;
|
|
164
|
+
exitCode: number;
|
|
165
|
+
outcome: WorldRunOutcome;
|
|
166
|
+
}>;
|
|
167
|
+
/** Internal task entry. The lifetime owner exists before admission/startup. Cancellation during
|
|
168
|
+
* boot is latched and handled after startup settles, so no abandoned startup can create a late
|
|
169
|
+
* service after teardown. Persistent --keep runs use the caller directly. */
|
|
170
|
+
export declare function runWorldOwned(configId: string, command: string[], options: RunWorldOptions, abort?: AbortSignal): Promise<{
|
|
171
|
+
instance: WorldInstance;
|
|
172
|
+
exitCode: number;
|
|
173
|
+
outcome: WorldRunOutcome;
|
|
174
|
+
}>;
|
|
175
|
+
export declare function listWorlds(root?: string): Array<{
|
|
176
|
+
name: string;
|
|
177
|
+
running: boolean;
|
|
178
|
+
degraded?: boolean;
|
|
179
|
+
config?: string;
|
|
180
|
+
}>;
|
|
181
|
+
/** Runs the command in the world's env in the foreground and resolves to its exit code. The child is
|
|
182
|
+
* awaited, not blocked on: a runtime that runs no synchronous child (the browser engine) still runs
|
|
183
|
+
* this one, and the caller sees no difference — stdio is inherited either way. */
|
|
184
|
+
export declare function runWithWorldEnv(name: string, command: string[], root?: string, options?: {
|
|
185
|
+
cwd?: string;
|
|
186
|
+
verbose?: boolean;
|
|
187
|
+
}): Promise<number>;
|
|
188
|
+
/** Local CLI attachment with one instance-bound consumer record per concurrent command.
|
|
189
|
+
* The synchronous SDK method remains compatible; its consumers are explicitly untracked. */
|
|
190
|
+
export type ConsumerRetirement = {
|
|
191
|
+
id: string;
|
|
192
|
+
owner?: string;
|
|
193
|
+
retired: boolean;
|
|
194
|
+
reason: string;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* The owner's explicit resolution of uncertain attachments (never automatic, never implied by age).
|
|
198
|
+
* A record is retired only when it belongs to this host and the running instance, its runner and
|
|
199
|
+
* its command are both gone (ESRCH), and no live process remains in the command's process group
|
|
200
|
+
* (`survivingOwnedGroups`, the lifecycle's own retirement evidence); anything else is kept with the
|
|
201
|
+
* reason, surviving members named by their group. Active consumers are never touched.
|
|
202
|
+
*/
|
|
203
|
+
export declare function retireWorldConsumers(name: string, root?: string, options?: {
|
|
204
|
+
consumer?: string;
|
|
205
|
+
}): ConsumerRetirement[];
|
|
206
|
+
export declare function attachWorld(name: string, command: string[], root?: string, options?: {
|
|
207
|
+
cwd?: string;
|
|
208
|
+
owner?: string;
|
|
209
|
+
verbose?: boolean;
|
|
210
|
+
}): Promise<number>;
|
|
211
|
+
/** The shell + env + cwd a `volter-world shell` subshell launches with (extracted for testability):
|
|
212
|
+
* the world env (twin `*_URL`s + fake keys + the Node injector + cliRedirect endpoint vars) on top
|
|
213
|
+
* of the caller's env, plus `VOLTER_WORLD` so a prompt can show which world is active. The ambient
|
|
214
|
+
* TLS-proxy env (HTTPS_PROXY + the CA-trust vars) is layered in by `shellWorld` once the proxy is up,
|
|
215
|
+
* since starting a server is async; this pure helper stays synchronous + side-effect-free for tests. */
|
|
216
|
+
export declare function worldShellEnv(name: string, root?: string): {
|
|
217
|
+
shell: string;
|
|
218
|
+
cwd: string;
|
|
219
|
+
env: Record<string, string>;
|
|
220
|
+
};
|
|
221
|
+
/** Drop into an interactive subshell with the world active — the contained sibling of `activate`
|
|
222
|
+
* (which wires your CURRENT shell + sets the `(world:<name>)` prompt marker). The subshell gets the
|
|
223
|
+
* world env AND, when openssl is available, an in-process ambient TLS redirect proxy: unmodified
|
|
224
|
+
* `gh`/`stripe`/`curl`/app in the subshell transparently hit the twins. Vendor calls in this subshell
|
|
225
|
+
* land in the twins; `exit` restores normal (the proxy + its session CA are torn down). The CA is
|
|
226
|
+
* trusted ONLY via this subshell's env — never installed system-wide. See docs/guides/route-a-cli-through-the-world.md. */
|
|
227
|
+
export declare function shellWorld(name: string, root?: string): Promise<number>;
|
|
228
|
+
/** Ensure a DETACHED ambient-redirect proxy daemon is running for this world (used by `activate`,
|
|
229
|
+
* which only emits a script and can't host an in-process server for the life of the shell). Idempotent:
|
|
230
|
+
* reuses a live daemon. Returns its proxy env, or null if openssl is missing (env-only fallback). The
|
|
231
|
+
* daemon is recorded in `proxy.json` and stopped by `down`. */
|
|
232
|
+
export declare function ensureWorldProxy(name: string, root?: string, caller?: CaTrustInputs): {
|
|
233
|
+
url: string;
|
|
234
|
+
caCertPath: string;
|
|
235
|
+
env: Record<string, string>;
|
|
236
|
+
} | null;
|
|
237
|
+
/** The detached proxy daemon's body (invoked as its own process by `ensureWorldProxy`). Starts the
|
|
238
|
+
* redirect proxy bound to an ephemeral port, records its pid/url/CA in `proxy.json`, and stays up
|
|
239
|
+
* until killed by `down`. Exported so the daemon entry file can delegate to it. */
|
|
240
|
+
export declare function runProxyDaemon(name: string, root?: string, envFile?: string): Promise<void>;
|
|
241
|
+
/**
|
|
242
|
+
* Emit a POSIX (bash/zsh) script to `eval` that ACTIVATES the world in the current shell — a
|
|
243
|
+
* virtualenv for vendor APIs. It exports the world env (twin `*_URL`s + fake keys + the Node
|
|
244
|
+
* injector + any `cliRedirect` endpoint vars), prepends `(world:<name>)` to the prompt so you can
|
|
245
|
+
* always see you're pointed at twins (a safety signal, not cosmetic), and defines a `deactivate`
|
|
246
|
+
* function that restores the prompt and unsets the vars. Usage: `eval "$(volter-world activate dev)"`.
|
|
247
|
+
*
|
|
248
|
+
* It ALSO starts (idempotently) the ambient TLS redirect proxy daemon for this world and exports
|
|
249
|
+
* `HTTPS_PROXY`/`HTTP_PROXY` + the CA-trust vars (`NODE_EXTRA_CA_CERTS`, `CURL_CA_BUNDLE`, …) so an
|
|
250
|
+
* UNMODIFIED `gh`/`stripe`/`curl`/app in the shell transparently hits the twins with zero per-tool
|
|
251
|
+
* config (WORLD_ACTIVATE.md tier 1). The session CA is trusted ONLY via these per-shell exports,
|
|
252
|
+
* never system-wide; `down` stops the daemon and drops the CA. If openssl is unavailable the script
|
|
253
|
+
* still emits the env-only redirect (Phase-1 behavior) — it just skips the proxy exports.
|
|
254
|
+
* See docs/guides/route-a-cli-through-the-world.md.
|
|
255
|
+
*/
|
|
256
|
+
export declare function activateScript(name: string, root?: string): string;
|