@volter/world 3.0.185 → 3.0.186
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/examples/customer-onboarding/.env.world +3 -0
- package/dist/examples/customer-onboarding/README.md +14 -13
- package/dist/examples/customer-onboarding/compose.yml +20 -0
- package/dist/examples/customer-onboarding/demo.mjs +1 -1
- package/dist/examples/customer-onboarding/gitignore.template +4 -0
- package/dist/examples/customer-onboarding/onboarding.mjs +1 -0
- package/dist/examples/customer-onboarding/package.json +5 -5
- package/dist/examples/customer-onboarding/public-files.json +2 -0
- package/dist/examples/index.json +2 -0
- package/dist/examples/integration-branches/README.md +4 -4
- package/dist/examples/integration-branches/package.json +5 -5
- package/dist/examples/workplace-agent/README.md +1 -1
- package/dist/examples/workplace-agent/package.json +5 -5
- package/dist/skills/volter-world/AGENTS.md +21 -12
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +33 -13
- package/dist/src/locate.d.ts +4 -3
- package/dist/src/locate.js +12 -8
- package/dist/src/world.d.ts +13 -4
- package/dist/src/world.js +17 -27
- package/examples/customer-onboarding/.env.world +3 -0
- package/examples/customer-onboarding/README.md +14 -13
- package/examples/customer-onboarding/compose.yml +20 -0
- package/examples/customer-onboarding/demo.mjs +1 -1
- package/examples/customer-onboarding/gitignore.template +4 -0
- package/examples/customer-onboarding/onboarding.mjs +1 -0
- package/examples/customer-onboarding/package.json +5 -5
- package/examples/customer-onboarding/public-files.json +2 -0
- package/examples/index.json +2 -0
- package/examples/integration-branches/README.md +4 -4
- package/examples/integration-branches/package.json +5 -5
- package/examples/workplace-agent/README.md +1 -1
- package/examples/workplace-agent/package.json +5 -5
- package/package.json +5 -5
- package/skills/volter-world/AGENTS.md +21 -12
- package/src/cli.ts +33 -16
- package/src/locate.ts +12 -8
- package/src/world.ts +19 -27
package/src/locate.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Where the world is (docs/concepts/worlds.md#the-config-and-the-running-world): the nearest
|
|
2
|
-
// above the cwd names the
|
|
1
|
+
// Where the world is (docs/concepts/worlds.md#the-config-and-the-running-world): the nearest standard
|
|
2
|
+
// World folder or `.volter/world.json` above the cwd names the root; `.volter/current` names the branch that
|
|
3
3
|
// is checked out (absent: the main branch, the config's `id`). Every `volter` verb and
|
|
4
4
|
// `World.open()` start here, the way `git` starts from the nearest `.git`.
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
@@ -11,15 +11,19 @@ export const WORLD_FILE = 'world.json';
|
|
|
11
11
|
|
|
12
12
|
/** `<root>/.volter` */
|
|
13
13
|
export function stateDir(root: string): string { return join(root, stateDirName()); }
|
|
14
|
-
/**
|
|
14
|
+
/** The in-repository config path used by initialization. */
|
|
15
15
|
export function worldConfigRelative(): string { return join(stateDirName(), WORLD_FILE); }
|
|
16
|
-
|
|
16
|
+
/** A standalone World keeps its config at the folder root; an in-repository World uses `.volter`. */
|
|
17
|
+
export function worldConfigPath(root: string): string {
|
|
18
|
+
const standard = join(root, WORLD_FILE);
|
|
19
|
+
return existsSync(standard) ? standard : join(stateDir(root), WORLD_FILE);
|
|
20
|
+
}
|
|
17
21
|
/** `<root>/.volter/world.env` — where `up` writes the live env (gitignored by init). */
|
|
18
22
|
export function worldEnvPath(root: string): string { return join(stateDir(root), 'world.env'); }
|
|
19
|
-
/**
|
|
20
|
-
export function worldSeedPath(root: string): string { return join(
|
|
23
|
+
/** The standard seed lives beside the selected config. */
|
|
24
|
+
export function worldSeedPath(root: string): string { return join(dirname(worldConfigPath(root)), 'seed.ts'); }
|
|
21
25
|
|
|
22
|
-
/** The
|
|
26
|
+
/** The nearest directory holding a standard World config. */
|
|
23
27
|
export function findWorldRoot(from: string = process.cwd()): string | null {
|
|
24
28
|
let dir = resolve(from);
|
|
25
29
|
for (;;) {
|
|
@@ -33,7 +37,7 @@ export function findWorldRoot(from: string = process.cwd()): string | null {
|
|
|
33
37
|
export function requireWorldRoot(from: string = process.cwd()): string {
|
|
34
38
|
const root = findWorldRoot(from);
|
|
35
39
|
if (root === null) {
|
|
36
|
-
throw new Error(`Not in a world: no ${worldConfigRelative()} here or above ${resolve(from)}. Run \`volter world init\` in your app, or pass --world <
|
|
40
|
+
throw new Error(`Not in a world: no world.json or ${worldConfigRelative()} here or above ${resolve(from)}. Run \`volter world init\` in your app, or pass --world <folder>.`);
|
|
37
41
|
}
|
|
38
42
|
return root;
|
|
39
43
|
}
|
package/src/world.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { renderDeploymentTemplates, type DeploymentTemplate } from '@volter/world-runtime/deployment-env';
|
|
1
2
|
// WORLD — the twins an app needs, running together (docs/concepts/the-model.md,
|
|
2
3
|
// docs/concepts/worlds.md#the-config-and-the-running-world): a branch with compute attached. `World.open()` finds the world of the cwd the
|
|
3
4
|
// way git finds its repository; every method is a name for a world-runtime verb, in the words a
|
|
4
5
|
// user reads. The `volter` command is one client of this class and adds nothing.
|
|
5
6
|
import { platformPerson } from './credentials.ts';
|
|
6
|
-
import { rebaseChangeset, worldBootMarker, type Changeset, type
|
|
7
|
+
import { rebaseChangeset, worldBootMarker, type Changeset, type LedgerDelta, type TwinAction, type WorldMarker, type ApplyReceipt, listActions, inWorldHistory, pushablePendingActions, pendingActions, applyTwinWrite, twinResources, type TwinResource } from '@volter/world-core/runtime';
|
|
7
8
|
import { spawnSync } from 'node:child_process';
|
|
8
9
|
import clockForm from '@volter/world-core/world-clock';
|
|
9
10
|
import {
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
16
17
|
import { dirname, join, resolve } from 'node:path';
|
|
17
18
|
import { requireToken, storeToken } from './credentials.ts';
|
|
18
|
-
import { currentBranch, findWorldRoot, mainBranch, requireWorldRoot, setCurrentBranch,
|
|
19
|
+
import { currentBranch, findWorldRoot, mainBranch, requireWorldRoot, setCurrentBranch, worldConfigPath, worldEnvPath, worldSeedPath } from './locate.ts';
|
|
19
20
|
import { destinationOf, parentEntries, rebaseBranch, type Receipt, type RootConfig } from '@volter/world-core/runtime';
|
|
20
21
|
|
|
21
22
|
export type WorldRef = { name?: string; root?: string };
|
|
@@ -65,8 +66,8 @@ export function parseOriginUrl(url: string, worldRoot = process.cwd()): { url: s
|
|
|
65
66
|
// a PATH is a world on this machine (git's file transport): its directory, and its served name
|
|
66
67
|
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(url)) {
|
|
67
68
|
const root = resolve(worldRoot, url);
|
|
68
|
-
const configPath =
|
|
69
|
-
if (!existsSync(configPath)) throw new Error(`Not a remote URL or world directory: ${url} (want https://<host>/<org>/<world>, or a
|
|
69
|
+
const configPath = worldConfigPath(root);
|
|
70
|
+
if (!existsSync(configPath)) throw new Error(`Not a remote URL or world directory: ${url} (want https://<host>/<org>/<world>, or a standard World folder)`);
|
|
70
71
|
const config = loadWorldConfig(configPath, root).config;
|
|
71
72
|
return { url: root, namespace: config.bare?.name ?? `${config.id}/${config.id}` };
|
|
72
73
|
}
|
|
@@ -80,14 +81,14 @@ export function parseOriginUrl(url: string, worldRoot = process.cwd()): { url: s
|
|
|
80
81
|
export class World {
|
|
81
82
|
/** The branch this handle is on — the instance name in the kernel. */
|
|
82
83
|
readonly name: string;
|
|
83
|
-
/** The
|
|
84
|
+
/** The World folder, or the repository holding its `.volter` config. */
|
|
84
85
|
readonly root: string;
|
|
85
86
|
private constructor(name: string, root: string) { this.name = name; this.root = root; }
|
|
86
87
|
|
|
87
88
|
// ── finding and making worlds ───────────────────────────────────────────────────────────
|
|
88
89
|
/** The world of the cwd (or of `ref.root`), on its checked-out branch (or `ref.name`). */
|
|
89
90
|
/** The config `loadWorldConfig` resolves for this world: the in-repo world.json, else the world's name. */
|
|
90
|
-
private configRef(): string { const
|
|
91
|
+
private configRef(): string { const config = worldConfigPath(this.root); return existsSync(config) ? config : this.name; }
|
|
91
92
|
|
|
92
93
|
static open(ref: WorldRef = {}): World {
|
|
93
94
|
const root = ref.root === undefined ? requireWorldRoot() : requireWorldRoot(ref.root);
|
|
@@ -133,35 +134,28 @@ export class World {
|
|
|
133
134
|
* stops compute, up resumes it — `reset` is the way back to the default data); a fresh branch
|
|
134
135
|
* boots clean and loads the default data unless `seed: false`.
|
|
135
136
|
*/
|
|
136
|
-
async up(opts: { mode?: WorldMode; seed?: boolean
|
|
137
|
+
async up(opts: { mode?: WorldMode; seed?: boolean } = {}): Promise<WorldInstance> {
|
|
137
138
|
const existing = this.instance();
|
|
138
139
|
if (existing?.running) return existing; // already up: nothing to do
|
|
139
|
-
const instance = await upWorld(
|
|
140
|
+
const instance = await upWorld(this.configRef(), {
|
|
140
141
|
name: this.name, root: this.root, envFile: existing?.envFile ?? worldEnvPath(this.root),
|
|
141
142
|
...(opts.mode ? { mode: opts.mode } : existing ? { mode: existing.mode } : {}),
|
|
142
|
-
...(existing ? { keepState: true } : {}),
|
|
143
|
+
...(existing ? { keepState: true } : {}), seed: opts.seed,
|
|
143
144
|
});
|
|
144
|
-
await this.finishStartup(instance, existing === null, opts);
|
|
145
145
|
return instance;
|
|
146
146
|
}
|
|
147
|
-
/** Product startup completes its seed sequence; the runtime owns only the boot. */
|
|
148
|
-
private async finishStartup(instance: WorldInstance, fresh: boolean, opts: { seed?: boolean; cwd?: string } = {}): Promise<void> {
|
|
149
|
-
try {
|
|
150
|
-
if (fresh && opts.seed !== false && existsSync(worldSeedPath(this.root))) {
|
|
151
|
-
// a story that fails is a loud failure: the world is up, but not the world the app expects
|
|
152
|
-
const seeded = await seedWorld(this.name, { root: this.root, cwd: opts.cwd ?? this.root });
|
|
153
|
-
if (seeded.exitCode !== 0) throw new Error(`the story failed (${seeded.entry} exited ${seeded.exitCode}); state may be partially seeded — fix the seed, start this World and run \`volter world seed\``);
|
|
154
|
-
}
|
|
155
|
-
} finally {
|
|
156
|
-
// a root set while the world was stopped reaches the twins' state after the default data (which a
|
|
157
|
-
// fresh boot runs simulated), where the deploy reads it — also when the seed failed
|
|
158
|
-
materializeRoots(loadWorldConfig(this.configRef(), this.root).config, instance.dirs.data, this.root);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
147
|
/** Stop the twins; the branch's state stays (`up` resumes). `purge` forgets it. */
|
|
162
148
|
down(opts: { purge?: boolean } = {}): ReturnType<typeof downWorld> { return downWorld(this.name, this.root, opts); }
|
|
163
149
|
/** Run a command inside the world: the app or its tests, with the twins' URLs and fake credentials in its env. */
|
|
164
|
-
run(command: string[], opts: { cwd?: string; verbose?: boolean } = {}): Promise<number> {
|
|
150
|
+
async run(command: string[], opts: { cwd?: string; verbose?: boolean; templates?: DeploymentTemplate[]; app?: string; appUrl?: string } = {}): Promise<number> {
|
|
151
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
152
|
+
const rendered = await renderDeploymentTemplates({ world: { name: this.name, root: this.root }, sourceRoot: cwd, mode: 'process', templates: opts.templates, app: opts.app, appUrl: opts.appUrl });
|
|
153
|
+
return runWithWorldEnv(this.name, command, this.root, { cwd, verbose: opts.verbose, env: rendered.env });
|
|
154
|
+
}
|
|
155
|
+
/** Render deployment templates into their selected dotenv files. */
|
|
156
|
+
env(opts: { cwd?: string; outputRoot?: string; templates?: DeploymentTemplate[]; app?: string; appUrl?: string } = {}): ReturnType<typeof renderDeploymentTemplates> {
|
|
157
|
+
return renderDeploymentTemplates({ world: { name: this.name, root: this.root }, sourceRoot: opts.cwd ?? process.cwd(), outputRoot: opts.outputRoot, mode: 'files', templates: opts.templates, app: opts.app, appUrl: opts.appUrl });
|
|
158
|
+
}
|
|
165
159
|
/** The shell script that activates the world in the current shell: `eval "$(volter world activate)"` — vendor CLIs and curl reach the twins. */
|
|
166
160
|
activateScript(): string { return activateScript(this.name, this.root); }
|
|
167
161
|
/** A subshell with the world active; resolves to its exit code. */
|
|
@@ -364,7 +358,6 @@ export class World {
|
|
|
364
358
|
serve(opts: { port?: number; host?: string; consolePort?: number; announce?: (info: { name: string; base: string; token: string; readToken: string; console: string | null }) => void } = {}): Promise<ServedWorld> { return serveWorld(this.name, { root: this.root, ...opts }); }
|
|
365
359
|
/** `volter world view`: this World, its twins' screens, its branches and the console on one origin (docs/contributing/architecture.md, "Viewing a World"). */
|
|
366
360
|
async view(opts: { port?: number; host?: string; console?: ViewConsole; announce?: (line: string) => void; origins?: boolean; origin?: string } = {}): Promise<WorldView> {
|
|
367
|
-
const fresh = this.instance() === null;
|
|
368
361
|
const person = platformPerson();
|
|
369
362
|
const announced: string[] = [];
|
|
370
363
|
let ready = false;
|
|
@@ -372,7 +365,6 @@ export class World {
|
|
|
372
365
|
try {
|
|
373
366
|
const instance = this.instance();
|
|
374
367
|
if (!instance) throw new Error('the viewer started without a recorded World instance');
|
|
375
|
-
await this.finishStartup(instance, fresh);
|
|
376
368
|
for (const line of announced) opts.announce?.(line);
|
|
377
369
|
ready = true;
|
|
378
370
|
return view;
|