@volter/world-runtime 3.0.163 → 3.0.165
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/dist/src/ancestry.js +2 -2
- package/dist/src/app-url.d.ts +5 -1
- package/dist/src/app-url.js +14 -1
- package/dist/src/branch.js +28 -8
- package/dist/src/browser-files.d.ts +5 -0
- package/dist/src/browser-files.js +14 -0
- package/dist/src/browsers.d.ts +2 -3
- package/dist/src/browsers.js +4 -4
- package/dist/src/catalog.d.ts +2 -1
- package/dist/src/catalog.js +9 -3
- package/dist/src/changeset.js +1 -1
- package/dist/src/cli-commands.js +71 -18
- package/dist/src/cli-load-clock.d.ts +1 -0
- package/dist/src/cli-load-clock.js +2 -0
- package/dist/src/cli.js +5 -1
- package/dist/src/clickhouse-backing.d.ts +2 -0
- package/dist/src/clickhouse-backing.js +145 -0
- package/dist/src/command-lifetime.d.mts +1 -1
- package/dist/src/command-lifetime.mjs +5 -2
- package/dist/src/configs.js +1 -1
- package/dist/src/consumers.js +1 -1
- package/dist/src/covers.d.ts +4 -2
- package/dist/src/covers.js +8 -4
- package/dist/src/go-mysql-binding.d.ts +19 -0
- package/dist/src/go-mysql-binding.js +51 -0
- package/dist/src/guest-processes.d.mts +1 -1
- package/dist/src/guest-processes.mjs +22 -14
- package/dist/src/host-cli.js +8 -1
- package/dist/src/host-ready.d.ts +16 -2
- package/dist/src/host-ready.js +66 -39
- package/dist/src/host-worker.js +7 -1
- package/dist/src/host.d.ts +26 -0
- package/dist/src/host.js +93 -3
- package/dist/src/infra-definition.d.ts +35 -0
- package/dist/src/infra-definition.js +146 -0
- package/dist/src/infra.d.ts +3 -0
- package/dist/src/infra.js +375 -105
- package/dist/src/init.d.ts +46 -0
- package/dist/src/init.js +446 -19
- package/dist/src/lifecycle-record.d.ts +5 -1
- package/dist/src/lifecycle-record.js +27 -4
- package/dist/src/local-branches.js +1 -1
- package/dist/src/mariadb-bootstrap.d.ts +32 -0
- package/dist/src/mariadb-bootstrap.js +90 -0
- package/dist/src/mariadb-supplier.d.ts +23 -0
- package/dist/src/mariadb-supplier.js +215 -0
- package/dist/src/mongodb-backing.d.ts +4 -0
- package/dist/src/mongodb-backing.js +71 -1
- package/dist/src/native-mariadb-backing.d.ts +13 -0
- package/dist/src/native-mariadb-backing.js +512 -0
- package/dist/src/native-postgres-backing.d.ts +4 -10
- package/dist/src/native-postgres-backing.js +428 -43
- package/dist/src/native-server-inputs.d.ts +16 -0
- package/dist/src/native-server-inputs.js +204 -0
- package/dist/src/origin.js +3 -3
- package/dist/src/original-inputs.d.ts +14 -0
- package/dist/src/original-inputs.js +84 -0
- package/dist/src/pack-facts.d.ts +9 -1
- package/dist/src/pglite-backing.d.ts +41 -17
- package/dist/src/pglite-backing.js +213 -79
- package/dist/src/pglite-host.mjs +482 -10
- package/dist/src/process-groups.d.mts +6 -1
- package/dist/src/process-groups.d.ts +2 -1
- package/dist/src/process-groups.js +1 -1
- package/dist/src/process-groups.mjs +82 -15
- package/dist/src/process-signal.d.mts +2 -2
- package/dist/src/process-signal.mjs +29 -9
- package/dist/src/project-inspect.d.ts +12 -0
- package/dist/src/project-inspect.js +41 -4
- package/dist/src/proxy-env.d.ts +34 -0
- package/dist/src/proxy-env.js +114 -0
- package/dist/src/redirect-proxy.d.ts +1 -31
- package/dist/src/redirect-proxy.js +3 -112
- package/dist/src/redis-backing.d.ts +6 -4
- package/dist/src/redis-backing.js +20 -168
- package/dist/src/runtime.d.ts +1 -0
- package/dist/src/runtime.js +503 -218
- package/dist/src/schema.d.ts +1 -0
- package/dist/src/schema.js +5 -3
- package/dist/src/served-world.js +3 -3
- package/dist/src/twin-backing.d.ts +47 -0
- package/dist/src/twin-backing.js +343 -0
- package/dist/src/up-cli.js +56 -3
- package/package.json +3 -3
- package/src/ancestry.ts +2 -2
- package/src/app-url.ts +9 -2
- package/src/branch.ts +26 -8
- package/src/browser-files.ts +15 -0
- package/src/browsers.ts +4 -4
- package/src/catalog.ts +9 -4
- package/src/changeset.ts +1 -1
- package/src/cli-commands.ts +85 -18
- package/src/cli-load-clock.ts +2 -0
- package/src/cli.ts +5 -1
- package/src/clickhouse-backing.ts +70 -0
- package/src/command-lifetime.d.mts +1 -1
- package/src/command-lifetime.mjs +5 -2
- package/src/configs.ts +1 -1
- package/src/consumers.ts +1 -1
- package/src/covers.ts +9 -4
- package/src/go-mysql-binding.ts +49 -0
- package/src/guest-processes.d.mts +1 -1
- package/src/guest-processes.mjs +22 -14
- package/src/host-cli.ts +7 -1
- package/src/host-ready.ts +62 -31
- package/src/host-worker.ts +6 -2
- package/src/host.ts +88 -6
- package/src/infra-definition.ts +135 -0
- package/src/infra.ts +250 -45
- package/src/init.ts +417 -18
- package/src/lifecycle-record.ts +18 -4
- package/src/local-branches.ts +1 -1
- package/src/mariadb-bootstrap.ts +107 -0
- package/src/mariadb-supplier.ts +199 -0
- package/src/mongodb-backing.ts +59 -1
- package/src/native-mariadb-backing.ts +418 -0
- package/src/native-postgres-backing.ts +353 -41
- package/src/native-server-inputs.ts +169 -0
- package/src/origin.ts +3 -3
- package/src/original-inputs.ts +72 -0
- package/src/pack-facts.ts +1 -1
- package/src/pglite-backing.ts +192 -80
- package/src/pglite-host.mjs +482 -10
- package/src/process-groups.d.mts +6 -1
- package/src/process-groups.mjs +82 -15
- package/src/process-groups.ts +2 -1
- package/src/process-signal.d.mts +2 -2
- package/src/process-signal.mjs +29 -9
- package/src/project-inspect.ts +37 -4
- package/src/proxy-env.ts +128 -0
- package/src/redirect-proxy.ts +3 -125
- package/src/redis-backing.ts +19 -136
- package/src/runtime.ts +342 -107
- package/src/schema.ts +5 -4
- package/src/served-world.ts +3 -3
- package/src/twin-backing.ts +300 -0
- package/src/up-cli.ts +52 -3
package/src/branch.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// base running at once share the database port its definition names; run one at a time (the SDK does). `checkout <name>`
|
|
8
8
|
// brings a stopped world back up from its own instance record, state intact.
|
|
9
9
|
import { runInfrastructure } from './infra.ts';
|
|
10
|
-
import { cpSync, existsSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
10
|
+
import { cpSync, existsSync, lstatSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
11
11
|
import { dirname, join, resolve } from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
import { captureHistory, historyAtInstant, forkTwin, stateGeneration, withHistoryLocks, worldPaths, worldStateRoot } from '@volter/world-core/runtime';
|
|
@@ -49,7 +49,7 @@ export async function branchWorld(base: string, name: string, options: BranchOpt
|
|
|
49
49
|
const position = options.at?.positions?.[service] ?? options.at?.positions?.[state] ?? timed?.position;
|
|
50
50
|
return { state, view, position, generation: stateGeneration(worldPaths(state, fromRoot).dir) };
|
|
51
51
|
}) };
|
|
52
|
-
}));
|
|
52
|
+
}), { operation: 'capture' });
|
|
53
53
|
// the base's managed databases (checked before the branch boots: its database would share the base's port)
|
|
54
54
|
const databases = managedDatabases(from);
|
|
55
55
|
if (databases.length && (options.at?.instant || options.at?.positions || options.at?.views)) {
|
|
@@ -83,23 +83,28 @@ export async function branchWorld(base: string, name: string, options: BranchOpt
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/** The base's containerless managed databases: each PGlite `pglite-<kind>-data`, MongoDB's
|
|
86
|
-
* `mongod-mongodb-data` (pglite-backing.ts), the
|
|
86
|
+
* `mongod-mongodb-data` (pglite-backing.ts), the declared Redis/ClickHouse twin trees, and native PostgreSQL. A stopped World's
|
|
87
87
|
* hosts leave no pid file; a docker-backed World keeps its data under the container's volume, which a branch does not
|
|
88
88
|
* carry. */
|
|
89
89
|
function managedDatabases(from: WorldInstance): string[] {
|
|
90
90
|
const data = from.dirs.data;
|
|
91
91
|
if (!existsSync(data)) return [];
|
|
92
|
-
const known = (name: string) => /^pglite-[a-z0-9_-]+-data$/.test(name) || name === 'mongod-mongodb-data' ||
|
|
92
|
+
const known = (name: string) => /^pglite-[a-z0-9_-]+-data$/.test(name) || name === 'mongod-mongodb-data' || ['native-postgres-data', 'native-mariadb-data'].includes(name);
|
|
93
93
|
if (readdirSync(data).some(name => name.endsWith('-data') && !known(name) && !from.services[name])) {
|
|
94
94
|
throw new Error('volter-world branch: unrecognised storage; retain the original World');
|
|
95
95
|
}
|
|
96
|
-
return readdirSync(data).filter((n) => /^pglite-[a-z0-9_-]+-data$/.test(n) || n === 'mongod-mongodb-data' ||
|
|
96
|
+
return readdirSync(data).filter((n) => /^pglite-[a-z0-9_-]+-data$/.test(n) || n === 'mongod-mongodb-data' || ['redis-twin', 'clickhouse-twin', 'native-postgres-data', 'native-mariadb-data'].includes(n));
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/** The pid files of the hosts serving one managed database directory. */
|
|
100
100
|
function hostPidFiles(data: string, db: string): string[] {
|
|
101
|
+
if (db === 'native-mariadb-data') return readdirSync(data).filter(name => /^native-mariadb\.(?:owner|starting)\.json(?:\.spawn\.json)?$/u.test(name)
|
|
102
|
+
|| /^native-mariadb-tool-.*\.json(?:\.spawn\.json)?$/u.test(name)).map(name => join(data, name));
|
|
101
103
|
if (db === 'native-postgres-data') return [join(data, 'native-postgres.owner.json')];
|
|
102
104
|
if (db === 'redis-twin') return readdirSync(data).filter((n) => /^redis-twin-\d+\.pid$/.test(n)).map((n) => join(data, n));
|
|
105
|
+
if (db === 'clickhouse-twin') return [...new Set(readdirSync(data)
|
|
106
|
+
.filter(n => new RegExp(`^${db}-\\d+\\.pid(?:\\.spawn\\.json)?$`).test(n))
|
|
107
|
+
.map(n => join(data, n.replace(/\.spawn\.json$/, ''))))];
|
|
103
108
|
return [join(data, `${db.slice(0, -'-data'.length)}.pid`)];
|
|
104
109
|
}
|
|
105
110
|
|
|
@@ -107,7 +112,19 @@ function hostPidFiles(data: string, db: string): string[] {
|
|
|
107
112
|
* port: the base must be stopped first (`volter world branch` stops it, as a branch replaces its base). */
|
|
108
113
|
function refuseLiveDatabases(from: WorldInstance, dbs: string[]): void {
|
|
109
114
|
for (const db of dbs) {
|
|
115
|
+
if (db === 'native-mariadb-data' && (existsSync(join(from.dirs.data, db, 'server.pid')) || existsSync(join(from.dirs.data, db, 'server.sock'))))
|
|
116
|
+
throw new Error(`volter-world branch: ${from.name}'s managed MariaDB retains PID/socket files; verified down is required`);
|
|
117
|
+
if (db === 'native-mariadb-data') {
|
|
118
|
+
const stamp = join(from.dirs.data, db, '.world-policy.json');
|
|
119
|
+
if (!existsSync(stamp) || !lstatSync(stamp).isFile() || existsSync(join(from.dirs.data, db, '.world-initializing.json')))
|
|
120
|
+
throw new Error(`volter-world branch: ${from.name}'s MariaDB has incomplete initialization; original data retained`);
|
|
121
|
+
}
|
|
110
122
|
for (const pidFile of hostPidFiles(from.dirs.data, db)) {
|
|
123
|
+
// Successful managed-twin down removes both receipts. A partial publication or an
|
|
124
|
+
// uncertain retirement must finish there before branch copies this store.
|
|
125
|
+
if (db === 'clickhouse-twin' || db === 'native-mariadb-data') {
|
|
126
|
+
throw new Error(`volter-world branch: ${from.name}'s managed ${db} retains ownership receipts; stop ${from.name} before copying its store`);
|
|
127
|
+
}
|
|
111
128
|
const recorded = existsSync(pidFile) ? readFileSync(pidFile, 'utf8').trim() : '';
|
|
112
129
|
const pid = db === 'native-postgres-data' && recorded ? JSON.parse(recorded).pid : Number(recorded);
|
|
113
130
|
let live = false;
|
|
@@ -121,6 +138,7 @@ function refuseLiveDatabases(from: WorldInstance, dbs: string[]): void {
|
|
|
121
138
|
function requireMatchingBackings(source: string[], child: string[]): void {
|
|
122
139
|
const kinds = {
|
|
123
140
|
postgres: ['native-postgres-data', 'pglite-postgres-data'],
|
|
141
|
+
mysql: ['native-mariadb-data'],
|
|
124
142
|
mongodb: ['mongod-mongodb-data'],
|
|
125
143
|
};
|
|
126
144
|
for (const [kind, formats] of Object.entries(kinds)) {
|
|
@@ -158,15 +176,15 @@ async function branchManagedDatabases(from: WorldInstance, instance: WorldInstan
|
|
|
158
176
|
const infrastructure = config.services.find((service) => service.type === 'external' && (service.external?.up[0] === 'volter-world-infra' || service.id === 'infrastructure'));
|
|
159
177
|
if (!infrastructure) throw new Error(`volter-world branch: ${instance.name}'s managed databases have no declared infrastructure service`);
|
|
160
178
|
const launch = { env: externalServiceEnv(infrastructure, instance.dirs, instance.env, config.stripEnv) };
|
|
161
|
-
if (dbs.
|
|
179
|
+
if (dbs.some(directory => ['native-postgres-data', 'native-mariadb-data', 'clickhouse-twin'].includes(directory))) {
|
|
162
180
|
const io = { out: (text: string) => process.stdout.write(text), err: (text: string) => process.stderr.write(text) };
|
|
163
|
-
if (await runInfrastructure('down', launch.env, io) !== 0) throw new Error('managed
|
|
181
|
+
if (await runInfrastructure('down', launch.env, io) !== 0) throw new Error('managed database branch could not stop its new backing');
|
|
164
182
|
for (const db of dbs) {
|
|
165
183
|
const to = join(instance.dirs.data, db);
|
|
166
184
|
rmSync(to, { recursive: true, force: true });
|
|
167
185
|
cpSync(join(from.dirs.data, db), to, { recursive: true });
|
|
168
186
|
}
|
|
169
|
-
if (await runInfrastructure('up', launch.env, io) !== 0) throw new Error('managed
|
|
187
|
+
if (await runInfrastructure('up', launch.env, io) !== 0) throw new Error('managed database branch could not resume its copied backing');
|
|
170
188
|
return;
|
|
171
189
|
}
|
|
172
190
|
const redisCli = dbs.includes('redis-twin') && redis.length ? redisTwinCli([dirname(fileURLToPath(import.meta.url)), dirname(instance.configPath)]) : undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getActiveWorldStore, stateDirName } from '@volter/world-core/runtime-state';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/** Instance bookkeeping, alongside the clock; retained at down, discarded at reset. */
|
|
5
|
+
export const browsersDir = (data: string): string => join(dirname(data), 'browser-records');
|
|
6
|
+
|
|
7
|
+
/** Empty Worlds need no browser validators or cookie/load machinery. A prior
|
|
8
|
+
* projection still loads the ordinary materializer to remove stale files. */
|
|
9
|
+
export async function materializeWorldBrowserFiles(data: string, root: string, name: string, main = name): Promise<void> {
|
|
10
|
+
const store = getActiveWorldStore();
|
|
11
|
+
if (!store.list(browsersDir(data)).some(file => file.endsWith('.json'))
|
|
12
|
+
&& store.read(join(root, stateDirName(), 'browsers', '.source.json')) === null) return;
|
|
13
|
+
const { materializeBrowserFiles } = await import('./browsers.ts');
|
|
14
|
+
materializeBrowserFiles(data, root, name, main);
|
|
15
|
+
}
|
package/src/browsers.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { assertBrowserName, validateBrowserState, type BrowserState } from '@volter/world-core';
|
|
2
|
-
import { getActiveWorldStore, stateDirName, type WorldStore } from '@volter/world-core/runtime';
|
|
1
|
+
import { assertBrowserName, validateBrowserState, type BrowserState } from '@volter/world-core/browser-state';
|
|
2
|
+
import { getActiveWorldStore, stateDirName, type WorldStore } from '@volter/world-core/runtime-state';
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
|
|
6
6
|
export type WorldBrowser = { name: string; state: BrowserState; version: string };
|
|
7
|
-
|
|
8
|
-
export
|
|
7
|
+
import { browsersDir } from './browser-files.ts';
|
|
8
|
+
export { browsersDir } from './browser-files.ts';
|
|
9
9
|
const path = (data: string, name: string): string => { assertBrowserName(name); return join(browsersDir(data), `${name}.json`); };
|
|
10
10
|
const records = new WeakMap<WorldStore, Map<string, { version: string; text: string; record: WorldBrowser }>>();
|
|
11
11
|
export function worldBrowser(data: string, name: string): WorldBrowser | null {
|
package/src/catalog.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
10
|
import { join, resolve } from 'node:path';
|
|
11
11
|
import { overlayPackFacts, type PackFacts } from './pack-facts.ts';
|
|
12
|
-
import { installedPackageDir, installedVendorPacks, isDerivedPack, packRepository } from '@volter/world-core/runtime';
|
|
12
|
+
import { installedPackageDir, installedVendorPacks, isDerivedPack, packRepository } from '@volter/world-core/runtime-state';
|
|
13
13
|
|
|
14
14
|
export const TWIN_PACKAGE_PREFIX = '@volter/twin-';
|
|
15
15
|
|
|
@@ -99,7 +99,10 @@ export function checkoutCatalog(root: string): Catalog {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export function installedCatalog(root: string, options: { sources?: Readonly<Record<string, string>>; vendors?: readonly string[] } = {}): Catalog {
|
|
102
|
-
|
|
102
|
+
// An explicit empty selection needs no package discovery or publisher choice.
|
|
103
|
+
const twins = options.vendors?.length === 0
|
|
104
|
+
? new Map<string, string>()
|
|
105
|
+
: new Map([...installedVendorPacks(root, options)].filter(([, dir]) => packCli(dir) !== undefined));
|
|
103
106
|
return {
|
|
104
107
|
kind: 'installed',
|
|
105
108
|
root: resolve(root),
|
|
@@ -156,16 +159,18 @@ export function packFactsFile(dir: string): string | undefined {
|
|
|
156
159
|
|
|
157
160
|
/** The catalog for a World root: its pack repository (or the one beside its platform checkout), merged with installed
|
|
158
161
|
* packages. Only the selected packs' own facts are overlaid on the kernel's. */
|
|
159
|
-
export function resolveCatalog(root: string, options: { sources?: Readonly<Record<string, string>>; vendors?: readonly string[] } = {}): Catalog {
|
|
162
|
+
export function resolveCatalog(root: string, options: { sources?: Readonly<Record<string, string>>; vendors?: readonly string[]; mayBeEmpty?: boolean } = {}): Catalog {
|
|
160
163
|
const resolved = resolve(root);
|
|
161
164
|
const installed = installedCatalog(resolved, options);
|
|
165
|
+
if (options.vendors?.length === 0) return installed;
|
|
162
166
|
const repo = packRepository(resolved);
|
|
163
167
|
const catalog = repo === undefined ? installed : mergedCatalog([checkoutCatalog(repo), installed]);
|
|
164
168
|
for (const vendor of catalog.vendors()) {
|
|
165
169
|
const file = packFactsFile(catalog.packDir(vendor)!);
|
|
166
170
|
if (file) overlayPackFactsFile(file);
|
|
167
171
|
}
|
|
168
|
-
|
|
172
|
+
// A World whose application runs beside infrastructure alone has no twin to find (`init --needs`, architecture).
|
|
173
|
+
if (catalog.vendors().length === 0 && !options.mayBeEmpty) {
|
|
169
174
|
throw new Error(
|
|
170
175
|
`No twins found for ${resolved}: no pack repository and no \`@volter/twin-<vendor>\` package installed above it. `
|
|
171
176
|
+ `Install the twins your app needs (e.g. \`bun add -d @volter/twin-stripe\`) and run init again.`,
|
package/src/changeset.ts
CHANGED
|
@@ -641,7 +641,7 @@ export function rebaseWorldChangeset(name: string, options: WorldRootOptions & {
|
|
|
641
641
|
const moved = [...new Set(located.changeset.actions.map((e) => e.service))].map((service) => ({ st: located.changeset.actions.find((e) => e.service === service)?.stateService ?? service, controlRoot: join(inst.dirs.data, service) }));
|
|
642
642
|
withHistoryLocks(moved.map(({ st, controlRoot }) => worldPaths(st, controlRoot).dir), () => {
|
|
643
643
|
for (const { st, controlRoot } of moved) rebaseBranch(st, controlRoot, { origin: Boolean(readBranchMeta(st, controlRoot)?.origin) });
|
|
644
|
-
});
|
|
644
|
+
}, { operation: 'branch-pointer adjustment' });
|
|
645
645
|
if (!changesetHashMatches(located.changeset)) throw new Error(`Refusing to rebase changeset "${name}": its stored contentHash does not match its body — the object drifted after authoring`);
|
|
646
646
|
const instance = requireWorld(located.world, root);
|
|
647
647
|
const targets: RebaseTarget[] = [...new Set(located.changeset.actions.map((entry) => entry.service))].map((service) => ({ service, stateService: located.changeset.actions.find((entry) => entry.service === service)!.stateService, controlRoot: join(instance.dirs.data, service) }));
|
package/src/cli-commands.ts
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
+
import type { statusWorld } from './runtime.ts';
|
|
1
2
|
// Every volter-world command but `up` (./up-cli.ts), loaded by ./cli.ts only when one of them is asked for.
|
|
2
|
-
|
|
3
|
+
|
|
3
4
|
import { optionValue, ownerOption } from './cli-args.ts';
|
|
4
|
-
|
|
5
|
+
|
|
5
6
|
import { spawn, spawnSync } from 'node:child_process';
|
|
6
7
|
import { createRequire } from 'node:module';
|
|
7
8
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
8
9
|
import { tmpdir } from 'node:os';
|
|
9
10
|
import { dirname, join, resolve, resolve as resolvePath } from 'node:path';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
13
14
|
import type { WorldManifest } from './attach.ts';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
18
19
|
import type { PrerequisiteId } from './index.ts';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
|
|
@@ -54,7 +55,7 @@ function acknowledgeOptions(args: string[], command: string): Record<string, str
|
|
|
54
55
|
|
|
55
56
|
function printHelp(): void {
|
|
56
57
|
process.stdout.write(`Usage:
|
|
57
|
-
volter-world init <name> --repo <path> [--out <dir>] [--force] [--allow-unknown] [--acknowledge vendor=reason]... [--json] [--root <repo>]
|
|
58
|
+
volter-world init <name> --repo <path> [--out <dir>] [--needs <needs.json>] [--force] [--allow-unknown] [--acknowledge vendor=reason]... [--json] [--root <repo>]
|
|
58
59
|
# the FRONT DOOR: detect the repo's vendors, EMIT a world config +
|
|
59
60
|
# env file OUTSIDE the repo (default <repo>/../<name>-pilot/, the
|
|
60
61
|
# repo is never written to), then run the coverage proof over the
|
|
@@ -62,6 +63,7 @@ function printHelp(): void {
|
|
|
62
63
|
# Deterministic: same repo + same pack catalog => identical bytes.
|
|
63
64
|
# init DIAGNOSES; it never boots — it prints the exact next commands
|
|
64
65
|
volter-world up <config> --env-out=<path> [--owner <label>] [--name <name>] [--mode local|share|sealed] [--isolation process|colocated|worker] [--keep-state] [--app-url <url>] [--in-process] [--json] [--root <repo>]
|
|
66
|
+
volter-world up <config> --env-out=<path> --in-process --attach-after-line [--name <name>] [--keep-state] [--app-url <url>] [--root <repo>] -- <command...> # after its last boot line, waits for stdin "attach" before running the command
|
|
65
67
|
volter-world outdated <world> [--json] — each mounted twin: pinned / mounted / catalog-current version and protocol standing (R17)
|
|
66
68
|
volter-world audit <world> [--json] — each mounted twin's grade, as the catalog records it (R17)
|
|
67
69
|
volter-world down <name> [--grace-ms <ms>] [--purge] [--if-instance <token>] [--root <repo>] # SIGTERM, then SIGKILL survivors after the grace (default 5000ms)
|
|
@@ -184,6 +186,7 @@ export async function main(): Promise<void> {
|
|
|
184
186
|
}
|
|
185
187
|
|
|
186
188
|
if (cmd === 'consumers') {
|
|
189
|
+
const { retireWorldConsumers } = await import('./runtime.ts');
|
|
187
190
|
// `volter-world consumers retire <world> [--consumer <id>] [--root <repo>] [--json]`: the owner's
|
|
188
191
|
// explicit resolution of attachments whose runner and command are provably gone.
|
|
189
192
|
const args = rest.filter((arg): arg is string => arg !== undefined);
|
|
@@ -205,6 +208,9 @@ export async function main(): Promise<void> {
|
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
if (cmd === 'resources' || cmd === 'prune') {
|
|
211
|
+
const { pruneWorlds, statusWorld } = await import('./runtime.ts');
|
|
212
|
+
const { worldResourceReport } = await import('./resources.ts');
|
|
213
|
+
const { worldConsumers, worldUsage } = await import('./consumers.ts');
|
|
208
214
|
const args = [subject, ...rest].filter((arg): arg is string => arg !== undefined);
|
|
209
215
|
const reportRoot = optionValue(args, '--root', process.cwd());
|
|
210
216
|
// Destructive commands must reject typos, not silently broaden a target to all Worlds.
|
|
@@ -263,6 +269,7 @@ export async function main(): Promise<void> {
|
|
|
263
269
|
|
|
264
270
|
|
|
265
271
|
if (cmd === 'down') {
|
|
272
|
+
const { downWorld } = await import('./runtime.ts');
|
|
266
273
|
if (!subject) throw new Error('volter-world down: missing world name');
|
|
267
274
|
const graceArg = optionValue(rest, '--grace-ms');
|
|
268
275
|
if (graceArg && !(Number(graceArg) >= 0)) throw new Error(`volter-world down: invalid --grace-ms "${graceArg}" (want a non-negative number)`);
|
|
@@ -298,6 +305,8 @@ export async function main(): Promise<void> {
|
|
|
298
305
|
}
|
|
299
306
|
|
|
300
307
|
if (cmd === 'events') {
|
|
308
|
+
const { statusWorld, worldEventsPath } = await import('./runtime.ts');
|
|
309
|
+
const { readWorldEvents, describeWorldEvent } = await import('./service-exit.ts');
|
|
301
310
|
if (!subject) throw new Error('volter-world events: missing world name');
|
|
302
311
|
const status = statusWorld(subject, root, { readOnly: true, light: true });
|
|
303
312
|
const events = readWorldEvents(worldEventsPath(status.dirs.logs));
|
|
@@ -308,6 +317,9 @@ export async function main(): Promise<void> {
|
|
|
308
317
|
}
|
|
309
318
|
|
|
310
319
|
if (cmd === 'status') {
|
|
320
|
+
const { statusWorld, worldEventsPath } = await import('./runtime.ts');
|
|
321
|
+
const { worldUsage } = await import('./consumers.ts');
|
|
322
|
+
const { readServiceEnd, describeServiceEnd, readWorldEvents, describeWorldEvent } = await import('./service-exit.ts');
|
|
311
323
|
if (!subject) throw new Error('volter-world status: missing world name');
|
|
312
324
|
const status = statusWorld(subject, root);
|
|
313
325
|
const usage = worldUsage(root, subject);
|
|
@@ -364,6 +376,7 @@ export async function main(): Promise<void> {
|
|
|
364
376
|
// `plan` and `review` retired 2026-09-05: the changeset verbs are the one review path (verify, approve, status, push).
|
|
365
377
|
|
|
366
378
|
if (cmd === 'doctor') {
|
|
379
|
+
const { doctorWorld } = await import('./runtime.ts');
|
|
367
380
|
if (!subject) throw new Error('volter-world doctor: missing world name');
|
|
368
381
|
const report = await doctorWorld(subject, { root, verifyPublic: rest.includes('--verify-public') });
|
|
369
382
|
if (rest.includes('--json')) {
|
|
@@ -377,6 +390,7 @@ export async function main(): Promise<void> {
|
|
|
377
390
|
}
|
|
378
391
|
|
|
379
392
|
if (cmd === 'doctor-prereqs') {
|
|
393
|
+
const { checkPrerequisites, formatPrerequisiteChecks } = await import('./prerequisites.ts');
|
|
380
394
|
const required = rest.flatMap((arg, index) => arg === '--require' ? [rest[index + 1]] : [])
|
|
381
395
|
.filter(Boolean) as PrerequisiteId[];
|
|
382
396
|
const checks = checkPrerequisites(required.length ? required : ['local-execution']);
|
|
@@ -392,6 +406,7 @@ export async function main(): Promise<void> {
|
|
|
392
406
|
}
|
|
393
407
|
|
|
394
408
|
if (cmd === 'inspect-project') {
|
|
409
|
+
const { inspectProject, formatProjectInspection } = await import('./project-inspect.ts');
|
|
395
410
|
const args = [...(subject ? [subject] : []), ...rest];
|
|
396
411
|
const path = args.find((arg) => !arg.startsWith('--')) || process.cwd();
|
|
397
412
|
const report = inspectProject(path);
|
|
@@ -400,6 +415,7 @@ export async function main(): Promise<void> {
|
|
|
400
415
|
}
|
|
401
416
|
|
|
402
417
|
if (cmd === 'migrate-config') {
|
|
418
|
+
const { migrateWorldConfig } = await import('./configs.ts');
|
|
403
419
|
if (!subject) throw new Error('volter-world migrate-config: missing config path or name');
|
|
404
420
|
const migration = migrateWorldConfig(subject, root);
|
|
405
421
|
if (rest.includes('--json')) process.stdout.write(`${JSON.stringify(migration, null, 2)}\n`);
|
|
@@ -430,20 +446,26 @@ export async function main(): Promise<void> {
|
|
|
430
446
|
}
|
|
431
447
|
|
|
432
448
|
if (cmd === 'init') {
|
|
449
|
+
const { initWorld, formatInitReport } = await import('./init.ts');
|
|
433
450
|
if (!subject) throw new Error('volter-world init: missing world name (usage: volter-world init <name> --repo <path>)');
|
|
434
451
|
const repo = optionValue(rest, '--repo');
|
|
435
452
|
if (!repo) throw new Error('volter-world init: --repo <path> is required (the application repo to build the world for)');
|
|
436
453
|
const out = optionValue(rest, '--out');
|
|
454
|
+
const needs = optionValue(rest, '--needs');
|
|
437
455
|
const result = initWorld(subject, repo, {
|
|
438
456
|
root,
|
|
439
457
|
...(out ? { out } : {}),
|
|
458
|
+
...(needs ? { needs } : {}),
|
|
440
459
|
force: rest.includes('--force'),
|
|
441
460
|
allowUnknown: rest.includes('--allow-unknown'),
|
|
442
461
|
acknowledge: acknowledgeOptions(rest, 'init'),
|
|
443
462
|
});
|
|
444
463
|
process.stdout.write(rest.includes('--json') ? `${JSON.stringify(result, null, 2)}\n` : formatInitReport(result));
|
|
445
464
|
// Vendor coverage IS the exit code. App boot risks remain a separate, prominent report field.
|
|
446
|
-
|
|
465
|
+
// A World made from a stack's needs is the exception, and says so: it is made for the services the stack
|
|
466
|
+
// names, a vendor with no twin is its report and not its failure, and its maker goes by this exit alone.
|
|
467
|
+
if (!result.ok && needs) process.stdout.write('\nVENDOR COVERAGE: incomplete (the vendors above with no pack). With --needs that is reported and is not a failure: the World is written, exit 0.\n');
|
|
468
|
+
if (!result.ok && !needs) process.exit(1);
|
|
447
469
|
return;
|
|
448
470
|
}
|
|
449
471
|
|
|
@@ -451,9 +473,10 @@ export async function main(): Promise<void> {
|
|
|
451
473
|
// twins this world mounted; `audit` is each one's grade as the catalog records it. Both read
|
|
452
474
|
// instance.json and the committed artifacts — nothing is fetched.
|
|
453
475
|
if (cmd === 'outdated' || cmd === 'audit') {
|
|
476
|
+
const { statusWorld } = await import('./runtime.ts');
|
|
454
477
|
if (!subject) throw new Error(`volter-world ${cmd}: missing world name (usage: volter-world ${cmd} <world> [--json])`);
|
|
455
478
|
const status = statusWorld(subject, root);
|
|
456
|
-
const rows = worldUpkeep(status, cmd, root);
|
|
479
|
+
const rows = await worldUpkeep(status, cmd, root);
|
|
457
480
|
if (rest.includes('--json')) process.stdout.write(`${JSON.stringify(rows, null, 2)}\n`);
|
|
458
481
|
else {
|
|
459
482
|
if (rows.length === 0) process.stdout.write(`World ${status.name}: no twin mounted with a resolved version — boot it once (up) so instance.json records what was mounted\n`);
|
|
@@ -463,6 +486,7 @@ export async function main(): Promise<void> {
|
|
|
463
486
|
return;
|
|
464
487
|
}
|
|
465
488
|
if (cmd === 'covers') {
|
|
489
|
+
const { coverWorld, formatCoverageReport, recordCoverage } = await import('./covers.ts');
|
|
466
490
|
if (!subject) throw new Error('volter-world covers: missing world name (usage: volter-world covers <world> --repo <path>)');
|
|
467
491
|
const repo = optionValue(rest, '--repo');
|
|
468
492
|
if (!repo) throw new Error('volter-world covers: --repo <path> is required (the application repo to prove coverage for)');
|
|
@@ -481,6 +505,8 @@ export async function main(): Promise<void> {
|
|
|
481
505
|
}
|
|
482
506
|
|
|
483
507
|
if (cmd === 'clock') {
|
|
508
|
+
const { clockFile } = await import('./runtime.ts');
|
|
509
|
+
const { writeWorldClock } = await import('./clock.ts');
|
|
484
510
|
// THE operator door for world time (physics): show / set <iso> / advance <duration> / shift <duration> / clear.
|
|
485
511
|
// The clock every twin reads per request (kernel worldNow()), and every application process through the injector
|
|
486
512
|
// (inject.cjs, WORLD TIME); a change takes effect live, no restarts. Its two forms (world-clock.cjs): `set` freezes
|
|
@@ -539,6 +565,7 @@ export async function main(): Promise<void> {
|
|
|
539
565
|
}
|
|
540
566
|
|
|
541
567
|
if (cmd === 'urls') {
|
|
568
|
+
const { urlsWorld } = await import('./runtime.ts');
|
|
542
569
|
if (!subject) throw new Error('volter-world urls: missing world name');
|
|
543
570
|
const urls = urlsWorld(subject, root);
|
|
544
571
|
if (rest.includes('--json')) {
|
|
@@ -553,6 +580,7 @@ export async function main(): Promise<void> {
|
|
|
553
580
|
}
|
|
554
581
|
|
|
555
582
|
if (cmd === 'url') {
|
|
583
|
+
const { urlWorld } = await import('./runtime.ts');
|
|
556
584
|
if (!subject) throw new Error('volter-world url: missing world name (usage: volter-world url <world> [service])');
|
|
557
585
|
const [service] = positionalArgs(rest);
|
|
558
586
|
const urls = urlWorld(subject, { root, ...(service === undefined ? {} : { service }) });
|
|
@@ -562,6 +590,7 @@ export async function main(): Promise<void> {
|
|
|
562
590
|
}
|
|
563
591
|
|
|
564
592
|
if (cmd === 'app-url') {
|
|
593
|
+
const { setAppUrl, detectAppUrl, addAppHosts, readAppUrl, appUrlUnsetMessage } = await import('./app-url.ts');
|
|
565
594
|
if (!subject) throw new Error('volter-world app-url: missing world name (usage: volter-world app-url <world> [--set <url>|--detect <pid|port>])');
|
|
566
595
|
const set = optionValue(rest, '--set');
|
|
567
596
|
const detect = optionValue(rest, '--detect');
|
|
@@ -592,6 +621,7 @@ export async function main(): Promise<void> {
|
|
|
592
621
|
}
|
|
593
622
|
|
|
594
623
|
if (cmd === 'tail' || cmd === 'log') {
|
|
624
|
+
const { tailWorldActions } = await import('./tail.ts');
|
|
595
625
|
if (!subject) throw new Error(`volter-world ${cmd}: missing world name (usage: volter-world log <world> [service...])`);
|
|
596
626
|
const services = positionalArgs(rest);
|
|
597
627
|
const controller = new AbortController();
|
|
@@ -610,6 +640,7 @@ export async function main(): Promise<void> {
|
|
|
610
640
|
}
|
|
611
641
|
|
|
612
642
|
if (cmd === 'seed' || cmd === 'reset') {
|
|
643
|
+
const { seedWorld, resetWorld } = await import('./placeholder.ts');
|
|
613
644
|
if (!subject) throw new Error(`volter-world ${cmd}: missing world name (usage: volter-world ${cmd} <world> [--entry <file>] [--cwd <dir>])`);
|
|
614
645
|
if (rest.some(arg => arg === '--env-file' || arg.startsWith('--env-file='))) throw new Error('--env-file is not an input for this command. Reset accepts --env-out=<new-output-path>.');
|
|
615
646
|
const envFile = optionValue(rest, '--env-out') || undefined;
|
|
@@ -624,6 +655,8 @@ export async function main(): Promise<void> {
|
|
|
624
655
|
}
|
|
625
656
|
|
|
626
657
|
if (cmd === 'branch') {
|
|
658
|
+
const { branchWorld } = await import('./branch.ts');
|
|
659
|
+
const { printReady } = await import('./up-cli.ts');
|
|
627
660
|
if (rest.some(arg => arg === '--env-file' || arg.startsWith('--env-file='))) throw new Error('--env-file is not an input for this command. Use --env-out=<new-output-path>; never pass credentials or custody as an output.');
|
|
628
661
|
const [name] = positionalArgs(rest);
|
|
629
662
|
if (!subject || !name) throw new Error('volter-world branch: usage: volter-world branch <world> <name> [--env-out=<path>]');
|
|
@@ -634,12 +667,15 @@ export async function main(): Promise<void> {
|
|
|
634
667
|
return;
|
|
635
668
|
}
|
|
636
669
|
if (cmd === 'checkout') {
|
|
670
|
+
const { checkoutWorld } = await import('./branch.ts');
|
|
671
|
+
const { printReady } = await import('./up-cli.ts');
|
|
637
672
|
if (!subject) throw new Error('volter-world checkout: missing world name');
|
|
638
673
|
const instance = await checkoutWorld(subject, { root });
|
|
639
674
|
if (rest.includes('--json')) process.stdout.write(`${JSON.stringify({ name: instance.name, instance: instance.instance, createdAt: instance.createdAt }, null, 2)}\n`); else printReady(instance);
|
|
640
675
|
return;
|
|
641
676
|
}
|
|
642
677
|
if (cmd === 'fetch') {
|
|
678
|
+
const { fetchFromOrigin } = await import('./origin.ts');
|
|
643
679
|
if (!subject) throw new Error('volter-world fetch: missing world name (usage: volter-world fetch <world> --key <key> [--origin <url> --namespace <org>/<world>] [--full])');
|
|
644
680
|
const key = optionValue(rest, '--key') || process.env.VOLTER_TWINS_READ_KEY || process.env.VOLTER_TWINS_KEY || '';
|
|
645
681
|
if (!key) throw new Error('volter-world fetch: --key <twins key> is required (the read key for a fetch, the admin key for --full)');
|
|
@@ -652,6 +688,7 @@ export async function main(): Promise<void> {
|
|
|
652
688
|
return;
|
|
653
689
|
}
|
|
654
690
|
if (cmd === 'origin') {
|
|
691
|
+
const { worldOrigin } = await import('./origin.ts');
|
|
655
692
|
if (!subject) throw new Error('volter-world origin: missing world name');
|
|
656
693
|
const origin = worldOrigin(subject, root);
|
|
657
694
|
if (rest.includes('--json')) process.stdout.write(`${JSON.stringify(origin, null, 2)}\n`);
|
|
@@ -660,6 +697,7 @@ export async function main(): Promise<void> {
|
|
|
660
697
|
}
|
|
661
698
|
|
|
662
699
|
if (cmd === 'mark') {
|
|
700
|
+
const { markWorld } = await import('./changeset.ts');
|
|
663
701
|
if (!subject) throw new Error('volter-world mark: missing world name (usage: volter-world mark <world> [--id <marker>] [--note <text>])');
|
|
664
702
|
const id = optionValue(rest, '--id');
|
|
665
703
|
const note = optionValue(rest, '--note');
|
|
@@ -670,6 +708,8 @@ export async function main(): Promise<void> {
|
|
|
670
708
|
}
|
|
671
709
|
|
|
672
710
|
if (cmd === 'diff') {
|
|
711
|
+
const { diffWorld } = await import('./changeset.ts');
|
|
712
|
+
const { formatLedgerDelta } = await import('@volter/world-core/runtime');
|
|
673
713
|
if (!subject) throw new Error('volter-world diff: missing world name (usage: volter-world diff <world> [--base <marker>])');
|
|
674
714
|
const base = optionValue(rest, '--base');
|
|
675
715
|
const delta = diffWorld(subject, { root, ...(base ? { base } : {}) });
|
|
@@ -678,6 +718,8 @@ export async function main(): Promise<void> {
|
|
|
678
718
|
}
|
|
679
719
|
|
|
680
720
|
if (cmd === 'changeset') {
|
|
721
|
+
const { parseVerifierExpression, formatChangeset, formatReplayReport, formatVerification, formatChangesetStatus, formatRebaseReport, formatApplication } = await import('@volter/world-core/runtime');
|
|
722
|
+
const { createWorldChangeset, findWorldChangeset, listWorldChangesets, replayWorldChangeset, verifyWorldChangeset, approveWorldChangeset, statusWorldChangeset, rebaseWorldChangeset, pushWorldChangeset } = await import('./changeset.ts');
|
|
681
723
|
const CHANGESET_VALUE_FLAGS = ['--root', '--base', '--into', '--world', '--verifier', '--as', '--note'];
|
|
682
724
|
const args = positionalArgs(rest, CHANGESET_VALUE_FLAGS);
|
|
683
725
|
const world = optionValue(rest, '--world');
|
|
@@ -773,6 +815,8 @@ export async function main(): Promise<void> {
|
|
|
773
815
|
}
|
|
774
816
|
|
|
775
817
|
if (cmd === 'list') {
|
|
818
|
+
const { listWorlds } = await import('./runtime.ts');
|
|
819
|
+
const { worldUsage } = await import('./consumers.ts');
|
|
776
820
|
const listArgs = [subject, ...rest].filter((arg): arg is string => arg !== undefined);
|
|
777
821
|
const listRoot = optionValue(listArgs, '--root', process.cwd());
|
|
778
822
|
const worlds = listWorlds(listRoot).map(world => ({ ...world, usage: worldUsage(listRoot, world.name) }));
|
|
@@ -785,6 +829,7 @@ export async function main(): Promise<void> {
|
|
|
785
829
|
}
|
|
786
830
|
|
|
787
831
|
if (cmd === 'env') {
|
|
832
|
+
const { attachWorld } = await import('./runtime.ts');
|
|
788
833
|
if (!subject) throw new Error('volter-world env: missing world name');
|
|
789
834
|
const split = rest.indexOf('--');
|
|
790
835
|
if (split < 0) throw new Error('volter-world env: pass command after --');
|
|
@@ -793,6 +838,10 @@ export async function main(): Promise<void> {
|
|
|
793
838
|
}
|
|
794
839
|
|
|
795
840
|
if (cmd === 'attach') {
|
|
841
|
+
// The local env attach, every app's CMD path, needs the World's ref and its runtime alone: the two load together,
|
|
842
|
+
// and the remote door (serve.ts) and the reflect front (reflect.ts, ca-trust.ts) load only on their own paths.
|
|
843
|
+
// All five were imported one after another first (S2 R16: Dub's attach, 225 bodies in 341 ms before its `next`).
|
|
844
|
+
const [{ resolveWorldRef, isRemoteWorldRef, worldManifest }, { withoutVaultSignIn, instanceDir, attachWorld }] = await Promise.all([import('./attach.ts'), import('./runtime.ts')]);
|
|
796
845
|
// `subject` may be the ref, a flag, or the `--` separator — reassemble and split.
|
|
797
846
|
const args = [subject, ...rest].filter((arg): arg is string => arg !== undefined);
|
|
798
847
|
const split = args.indexOf('--');
|
|
@@ -807,6 +856,7 @@ export async function main(): Promise<void> {
|
|
|
807
856
|
throw new Error('--owner is supported for local attach --via env commands');
|
|
808
857
|
}
|
|
809
858
|
if (isRemoteWorldRef(resolved.ref)) {
|
|
859
|
+
const { fetchRemoteManifest, remoteAttachEnv } = await import('./serve.ts');
|
|
810
860
|
// remote world: fetch the manifest, synthesize the attach env, exec
|
|
811
861
|
const attachToken = optionValue(head, '--token') || process.env.VOLTER_WORLD_TOKEN || undefined;
|
|
812
862
|
const manifest = await fetchRemoteManifest(resolved.ref, attachToken === undefined ? {} : { token: attachToken });
|
|
@@ -849,6 +899,7 @@ export async function main(): Promise<void> {
|
|
|
849
899
|
// gets the override — and everything else runs with the trust env and VOLTER_WORLD set, its DNS
|
|
850
900
|
// being the caller's to point (a container's --dns, a resolver on the host).
|
|
851
901
|
if (command.length === 0) throw new Error('volter-world attach: pass the command after --');
|
|
902
|
+
const [{ readReflectManifest, splitDockerComposeArgs, composeOverrideForReflect, dockerComposeWithOverride, ATTACHED_CA_PATH }, { sessionTrustEnv }] = await Promise.all([import('./reflect.ts'), import('./ca-trust.ts')]);
|
|
852
903
|
const manifest = readReflectManifest(attachRoot, resolved.ref);
|
|
853
904
|
if (!manifest) throw new Error(`volter-world attach --via reflect: no reflect front is running for world ${resolved.ref} — start one: volter-world reflect ${resolved.ref} --target-ip <ip> --port 443 --resolver-port 53`);
|
|
854
905
|
const env: Record<string, string> = { ...withoutVaultSignIn(process.env) as Record<string, string>, VOLTER_WORLD: resolved.ref, VOLTER_REFLECT_DNS: manifest.targetIp, VOLTER_WORLD_CA: manifest.caCertPath };
|
|
@@ -879,12 +930,15 @@ export async function main(): Promise<void> {
|
|
|
879
930
|
}
|
|
880
931
|
|
|
881
932
|
if (cmd === 'manifest') {
|
|
933
|
+
const { worldManifest } = await import('./attach.ts');
|
|
882
934
|
if (!subject) throw new Error('volter-world manifest: missing world name');
|
|
883
935
|
process.stdout.write(`${JSON.stringify(worldManifest(subject, root), null, 2)}\n`);
|
|
884
936
|
return;
|
|
885
937
|
}
|
|
886
938
|
|
|
887
939
|
if (cmd === 'reflect') {
|
|
940
|
+
const { reflectRoutesPath, startReflectFront, startReflectResolver, readReflectRoutes, writeReflectManifest, clearReflectManifest } = await import('./reflect.ts');
|
|
941
|
+
const { statusWorld, instanceDir } = await import('./runtime.ts');
|
|
888
942
|
if (!subject) throw new Error('volter-world reflect: missing world name');
|
|
889
943
|
const targetIp = optionValue(rest, '--target-ip');
|
|
890
944
|
if (!targetIp) throw new Error('volter-world reflect: --target-ip <ip> is required (the address attachers reach the front at)');
|
|
@@ -918,6 +972,9 @@ export async function main(): Promise<void> {
|
|
|
918
972
|
}
|
|
919
973
|
|
|
920
974
|
if (cmd === 'serve') {
|
|
975
|
+
const { startReflectFront } = await import('./reflect.ts');
|
|
976
|
+
const { statusWorld, instanceDir } = await import('./runtime.ts');
|
|
977
|
+
const { startManifestServer, advertiseWorldManifest, MANIFEST_PATH } = await import('./serve.ts');
|
|
921
978
|
if (!subject) throw new Error('volter-world serve: missing world name');
|
|
922
979
|
const advertise = optionValue(rest, '--advertise');
|
|
923
980
|
if (!advertise) throw new Error('volter-world serve: --advertise <https-origin> is required (how attachers reach the door, e.g. https://worlds.corp:8443)');
|
|
@@ -956,6 +1013,7 @@ export async function main(): Promise<void> {
|
|
|
956
1013
|
}
|
|
957
1014
|
|
|
958
1015
|
if (cmd === 'route') {
|
|
1016
|
+
const { reflectRoutesPath, readReflectRoutes, writeReflectRoutes } = await import('./reflect.ts');
|
|
959
1017
|
if (!subject) throw new Error('volter-world route: missing world name');
|
|
960
1018
|
const [verb, host] = rest.filter((arg) => !arg.startsWith('--'));
|
|
961
1019
|
const routesPath = reflectRoutesPath(root, subject);
|
|
@@ -974,17 +1032,21 @@ export async function main(): Promise<void> {
|
|
|
974
1032
|
}
|
|
975
1033
|
|
|
976
1034
|
if (cmd === 'activate') {
|
|
1035
|
+
const { activateScript } = await import('./runtime.ts');
|
|
977
1036
|
if (!subject) throw new Error('volter-world activate: missing world name');
|
|
978
1037
|
process.stdout.write(activateScript(subject, root));
|
|
979
1038
|
return;
|
|
980
1039
|
}
|
|
981
1040
|
|
|
982
1041
|
if (cmd === 'shell') {
|
|
1042
|
+
const { shellWorld } = await import('./runtime.ts');
|
|
983
1043
|
if (!subject) throw new Error('volter-world shell: missing world name');
|
|
984
1044
|
process.exit(await shellWorld(subject, root));
|
|
985
1045
|
}
|
|
986
1046
|
|
|
987
1047
|
if (cmd === 'run') {
|
|
1048
|
+
const { runWorld } = await import('./runtime.ts');
|
|
1049
|
+
const { readServiceEnd, describeServiceEnd } = await import('./service-exit.ts');
|
|
988
1050
|
if (!subject) throw new Error('volter-world run: missing config');
|
|
989
1051
|
const split = rest.indexOf('--');
|
|
990
1052
|
if (split < 0) throw new Error('volter-world run: pass command after --');
|
|
@@ -1013,6 +1075,7 @@ export async function main(): Promise<void> {
|
|
|
1013
1075
|
}
|
|
1014
1076
|
|
|
1015
1077
|
if (cmd === 'share') {
|
|
1078
|
+
const { shareWorldServices } = await import('./runtime.ts');
|
|
1016
1079
|
if (!subject) throw new Error('volter-world share: missing world name');
|
|
1017
1080
|
const service = optionValue(rest, '--service') || undefined;
|
|
1018
1081
|
const verifyPath = rest.includes('--no-verify')
|
|
@@ -1044,6 +1107,7 @@ export async function main(): Promise<void> {
|
|
|
1044
1107
|
}
|
|
1045
1108
|
|
|
1046
1109
|
if (cmd === 'unshare') {
|
|
1110
|
+
const { unshareWorld } = await import('./runtime.ts');
|
|
1047
1111
|
if (!subject) throw new Error('volter-world unshare: missing world name');
|
|
1048
1112
|
const service = optionValue(rest, '--service') || undefined;
|
|
1049
1113
|
const result = await unshareWorld(subject, { root, service });
|
|
@@ -1059,7 +1123,10 @@ export async function main(): Promise<void> {
|
|
|
1059
1123
|
/** The rows `outdated` / `audit` print. `outdated`: pinned vs mounted vs catalog-current version and the
|
|
1060
1124
|
* protocol standing; `audit`: each mounted twin's grade, from the catalog's record. `attention` marks a row the
|
|
1061
1125
|
* operator should act on (exit 1). */
|
|
1062
|
-
function worldUpkeep(status: ReturnType<typeof statusWorld>, kind: 'outdated' | 'audit', root: string): Array<{ id: string; line: string; attention: boolean }
|
|
1126
|
+
async function worldUpkeep(status: ReturnType<typeof statusWorld>, kind: 'outdated' | 'audit', root: string): Promise<Array<{ id: string; line: string; attention: boolean }>> {
|
|
1127
|
+
const { resolveCatalog } = await import('./catalog.ts');
|
|
1128
|
+
const { loadWorldConfig } = await import('./configs.ts');
|
|
1129
|
+
const { vendorOfPackModule } = await import('@volter/world-core/runtime');
|
|
1063
1130
|
const catalog = resolveCatalog(root);
|
|
1064
1131
|
const config = loadWorldConfig(status.name, root).config;
|
|
1065
1132
|
const out: Array<{ id: string; line: string; attention: boolean }> = [];
|
package/src/cli.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// volter-world: each command loads only what it needs. `up` (./up-cli.ts) starts the World's owner process
|
|
3
3
|
// before any other command's modules load; every other command is ./cli-commands.ts.
|
|
4
|
+
import { cliLoadStarted } from './cli-load-clock.ts';
|
|
4
5
|
import { optionValue } from './cli-args.ts';
|
|
5
6
|
import { errorMessage } from './error-message.ts';
|
|
6
7
|
|
|
7
8
|
const [cmd, subject, ...rest] = process.argv.slice(2);
|
|
8
9
|
const run = cmd === 'up' && subject !== '--help' && subject !== '-h'
|
|
9
|
-
? import('./up-cli.ts').then(({ upCommand }) =>
|
|
10
|
+
? import('./up-cli.ts').then(({ upCommand }) => {
|
|
11
|
+
process.stderr.write(`volter-world up: entry modules loaded in ${(performance.now() - cliLoadStarted).toFixed(1)} ms\n`);
|
|
12
|
+
return upCommand(subject, rest, optionValue(rest.includes('--attach-after-line') && rest.includes('--') ? rest.slice(0, rest.indexOf('--')) : rest, '--root', process.cwd()));
|
|
13
|
+
})
|
|
10
14
|
: import('./cli-commands.ts').then(({ main }) => main());
|
|
11
15
|
run.catch((error) => {
|
|
12
16
|
process.stderr.write(`${errorMessage(error)}\n`);
|