@volter/world-runtime 3.0.163 → 3.0.164

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.
Files changed (137) hide show
  1. package/dist/src/ancestry.js +2 -2
  2. package/dist/src/app-url.d.ts +5 -1
  3. package/dist/src/app-url.js +14 -1
  4. package/dist/src/branch.js +28 -8
  5. package/dist/src/browser-files.d.ts +5 -0
  6. package/dist/src/browser-files.js +14 -0
  7. package/dist/src/browsers.d.ts +2 -3
  8. package/dist/src/browsers.js +4 -4
  9. package/dist/src/catalog.d.ts +2 -1
  10. package/dist/src/catalog.js +3 -2
  11. package/dist/src/changeset.js +1 -1
  12. package/dist/src/cli-commands.js +71 -18
  13. package/dist/src/cli-load-clock.d.ts +1 -0
  14. package/dist/src/cli-load-clock.js +2 -0
  15. package/dist/src/cli.js +5 -1
  16. package/dist/src/clickhouse-backing.d.ts +2 -0
  17. package/dist/src/clickhouse-backing.js +145 -0
  18. package/dist/src/command-lifetime.d.mts +1 -1
  19. package/dist/src/command-lifetime.mjs +5 -2
  20. package/dist/src/configs.js +1 -1
  21. package/dist/src/consumers.js +1 -1
  22. package/dist/src/covers.d.ts +4 -2
  23. package/dist/src/covers.js +8 -4
  24. package/dist/src/go-mysql-binding.d.ts +19 -0
  25. package/dist/src/go-mysql-binding.js +51 -0
  26. package/dist/src/guest-processes.d.mts +1 -1
  27. package/dist/src/guest-processes.mjs +22 -14
  28. package/dist/src/host-cli.js +8 -1
  29. package/dist/src/host-ready.d.ts +16 -2
  30. package/dist/src/host-ready.js +66 -39
  31. package/dist/src/host-worker.js +7 -1
  32. package/dist/src/host.d.ts +26 -0
  33. package/dist/src/host.js +93 -3
  34. package/dist/src/infra-definition.d.ts +35 -0
  35. package/dist/src/infra-definition.js +146 -0
  36. package/dist/src/infra.d.ts +3 -0
  37. package/dist/src/infra.js +375 -105
  38. package/dist/src/init.d.ts +46 -0
  39. package/dist/src/init.js +445 -18
  40. package/dist/src/lifecycle-record.d.ts +5 -1
  41. package/dist/src/lifecycle-record.js +27 -4
  42. package/dist/src/local-branches.js +1 -1
  43. package/dist/src/mariadb-bootstrap.d.ts +32 -0
  44. package/dist/src/mariadb-bootstrap.js +90 -0
  45. package/dist/src/mariadb-supplier.d.ts +23 -0
  46. package/dist/src/mariadb-supplier.js +215 -0
  47. package/dist/src/mongodb-backing.d.ts +4 -0
  48. package/dist/src/mongodb-backing.js +71 -1
  49. package/dist/src/native-mariadb-backing.d.ts +13 -0
  50. package/dist/src/native-mariadb-backing.js +512 -0
  51. package/dist/src/native-postgres-backing.d.ts +4 -10
  52. package/dist/src/native-postgres-backing.js +428 -43
  53. package/dist/src/native-server-inputs.d.ts +16 -0
  54. package/dist/src/native-server-inputs.js +204 -0
  55. package/dist/src/origin.js +3 -3
  56. package/dist/src/original-inputs.d.ts +14 -0
  57. package/dist/src/original-inputs.js +84 -0
  58. package/dist/src/pack-facts.d.ts +9 -1
  59. package/dist/src/pglite-backing.d.ts +41 -17
  60. package/dist/src/pglite-backing.js +213 -79
  61. package/dist/src/pglite-host.mjs +482 -10
  62. package/dist/src/process-groups.d.mts +6 -1
  63. package/dist/src/process-groups.d.ts +2 -1
  64. package/dist/src/process-groups.js +1 -1
  65. package/dist/src/process-groups.mjs +82 -15
  66. package/dist/src/process-signal.d.mts +2 -2
  67. package/dist/src/process-signal.mjs +29 -9
  68. package/dist/src/project-inspect.d.ts +12 -0
  69. package/dist/src/project-inspect.js +41 -4
  70. package/dist/src/proxy-env.d.ts +34 -0
  71. package/dist/src/proxy-env.js +114 -0
  72. package/dist/src/redirect-proxy.d.ts +1 -31
  73. package/dist/src/redirect-proxy.js +3 -112
  74. package/dist/src/redis-backing.d.ts +6 -4
  75. package/dist/src/redis-backing.js +20 -168
  76. package/dist/src/runtime.d.ts +1 -0
  77. package/dist/src/runtime.js +503 -218
  78. package/dist/src/schema.d.ts +1 -0
  79. package/dist/src/schema.js +5 -3
  80. package/dist/src/served-world.js +3 -3
  81. package/dist/src/twin-backing.d.ts +47 -0
  82. package/dist/src/twin-backing.js +343 -0
  83. package/dist/src/up-cli.js +56 -3
  84. package/package.json +3 -3
  85. package/src/ancestry.ts +2 -2
  86. package/src/app-url.ts +9 -2
  87. package/src/branch.ts +26 -8
  88. package/src/browser-files.ts +15 -0
  89. package/src/browsers.ts +4 -4
  90. package/src/catalog.ts +4 -3
  91. package/src/changeset.ts +1 -1
  92. package/src/cli-commands.ts +85 -18
  93. package/src/cli-load-clock.ts +2 -0
  94. package/src/cli.ts +5 -1
  95. package/src/clickhouse-backing.ts +70 -0
  96. package/src/command-lifetime.d.mts +1 -1
  97. package/src/command-lifetime.mjs +5 -2
  98. package/src/configs.ts +1 -1
  99. package/src/consumers.ts +1 -1
  100. package/src/covers.ts +9 -4
  101. package/src/go-mysql-binding.ts +49 -0
  102. package/src/guest-processes.d.mts +1 -1
  103. package/src/guest-processes.mjs +22 -14
  104. package/src/host-cli.ts +7 -1
  105. package/src/host-ready.ts +62 -31
  106. package/src/host-worker.ts +6 -2
  107. package/src/host.ts +88 -6
  108. package/src/infra-definition.ts +135 -0
  109. package/src/infra.ts +250 -45
  110. package/src/init.ts +416 -17
  111. package/src/lifecycle-record.ts +18 -4
  112. package/src/local-branches.ts +1 -1
  113. package/src/mariadb-bootstrap.ts +107 -0
  114. package/src/mariadb-supplier.ts +199 -0
  115. package/src/mongodb-backing.ts +59 -1
  116. package/src/native-mariadb-backing.ts +418 -0
  117. package/src/native-postgres-backing.ts +353 -41
  118. package/src/native-server-inputs.ts +169 -0
  119. package/src/origin.ts +3 -3
  120. package/src/original-inputs.ts +72 -0
  121. package/src/pack-facts.ts +1 -1
  122. package/src/pglite-backing.ts +192 -80
  123. package/src/pglite-host.mjs +482 -10
  124. package/src/process-groups.d.mts +6 -1
  125. package/src/process-groups.mjs +82 -15
  126. package/src/process-groups.ts +2 -1
  127. package/src/process-signal.d.mts +2 -2
  128. package/src/process-signal.mjs +29 -9
  129. package/src/project-inspect.ts +37 -4
  130. package/src/proxy-env.ts +128 -0
  131. package/src/redirect-proxy.ts +3 -125
  132. package/src/redis-backing.ts +19 -136
  133. package/src/runtime.ts +342 -107
  134. package/src/schema.ts +5 -4
  135. package/src/served-world.ts +3 -3
  136. package/src/twin-backing.ts +300 -0
  137. 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 redis twin's `redis-twin` tree (redis-backing.ts), and native PostgreSQL. A stopped World's
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' || name === 'native-postgres-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' || n === 'redis-twin' || n === 'native-postgres-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.includes('native-postgres-data')) {
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 native database branch could not stop its new backing');
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 native database branch could not resume its copied backing');
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
- /** Instance bookkeeping, alongside the clock; retained at down, discarded at reset. */
8
- export const browsersDir = (data: string): string => join(dirname(data), 'browser-records');
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
 
@@ -156,7 +156,7 @@ export function packFactsFile(dir: string): string | undefined {
156
156
 
157
157
  /** The catalog for a World root: its pack repository (or the one beside its platform checkout), merged with installed
158
158
  * 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 {
159
+ export function resolveCatalog(root: string, options: { sources?: Readonly<Record<string, string>>; vendors?: readonly string[]; mayBeEmpty?: boolean } = {}): Catalog {
160
160
  const resolved = resolve(root);
161
161
  const installed = installedCatalog(resolved, options);
162
162
  const repo = packRepository(resolved);
@@ -165,7 +165,8 @@ export function resolveCatalog(root: string, options: { sources?: Readonly<Recor
165
165
  const file = packFactsFile(catalog.packDir(vendor)!);
166
166
  if (file) overlayPackFactsFile(file);
167
167
  }
168
- if (catalog.vendors().length === 0) {
168
+ // A World whose application runs beside infrastructure alone has no twin to find (`init --needs`, architecture).
169
+ if (catalog.vendors().length === 0 && !options.mayBeEmpty) {
169
170
  throw new Error(
170
171
  `No twins found for ${resolved}: no pack repository and no \`@volter/twin-<vendor>\` package installed above it. `
171
172
  + `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) }));
@@ -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
- import { sessionTrustEnv } from './ca-trust.ts';
3
+
3
4
  import { optionValue, ownerOption } from './cli-args.ts';
4
- import { printReady } from './up-cli.ts';
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
- import { activateScript, branchWorld, checkoutWorld, fetchFromOrigin, pushWorldChangeset, rebaseWorldChangeset, resetWorld, seedWorld, worldOrigin, approveWorldChangeset, writeWorldClock, checkPrerequisites, coverWorld, createWorldChangeset, diffWorld, doctorWorld, downWorld, findWorldChangeset, formatCoverageReport, recordCoverage, formatInitReport, formatPrerequisiteChecks, formatProjectInspection, initWorld, inspectProject, isRemoteWorldRef, listWorldChangesets, listWorlds, markWorld, migrateWorldConfig, readReflectRoutes, reflectRoutesPath, replayWorldChangeset, resolveWorldRef, attachWorld, retireWorldConsumers, runWorld, shareWorldServices, shellWorld, startReflectFront, startReflectResolver, statusWorld, statusWorldChangeset, tailWorldActions, unshareWorld, upWorld, urlsWorld, urlWorld, verifyWorldChangeset, worldManifest, writeReflectRoutes, readReflectManifest, writeReflectManifest, clearReflectManifest, composeOverrideForReflect, splitDockerComposeArgs, dockerComposeWithOverride, ATTACHED_CA_PATH } from './index.ts';
11
- import { formatApplication, formatChangeset, formatRebaseReport, formatChangesetStatus, formatLedgerDelta, formatReplayReport, formatVerification, parseVerifierExpression } from '@volter/world-core/runtime';
12
- import { advertiseWorldManifest, fetchRemoteManifest, MANIFEST_PATH, remoteAttachEnv, startManifestServer } from './serve.ts';
11
+
12
+
13
+
13
14
  import type { WorldManifest } from './attach.ts';
14
- import { clockFile, instanceDir, pruneWorlds, withoutVaultSignIn } from './runtime.ts';
15
- import { worldResourceReport } from './resources.ts';
16
- import { worldConsumers, worldUsage } from './consumers.ts';
17
- import { addAppHosts, appUrlUnsetMessage, detectAppUrl, readAppUrl, setAppUrl } from './app-url.ts';
15
+
16
+
17
+
18
+
18
19
  import type { PrerequisiteId } from './index.ts';
19
- import { describeServiceEnd, describeWorldEvent, readServiceEnd, readWorldEvents } from './service-exit.ts';
20
- import { worldEventsPath } from './runtime.ts';
21
- import { resolveCatalog } from './catalog.ts';
22
- import { loadWorldConfig } from './configs.ts';
23
- import { vendorOfPackModule } from '@volter/world-core/runtime';
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
- if (!result.ok) process.exit(1);
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 }> = [];
@@ -0,0 +1,2 @@
1
+ // Imported first by the CLI: measures only its modules, after the engine starts this first body.
2
+ export const cliLoadStarted = performance.now();
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 }) => upCommand(subject, rest, optionValue(rest, '--root', process.cwd())))
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`);