@volter/world 2.0.52 → 2.0.54
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/cli.js +3 -5
- package/dist/src/credentials.d.ts +4 -1
- package/dist/src/credentials.js +15 -2
- package/dist/src/world.js +14 -4
- package/package.json +5 -5
- package/src/cli.ts +3 -4
- package/src/credentials.ts +8 -2
- package/src/world.ts +12 -4
package/dist/src/cli.js
CHANGED
|
@@ -424,14 +424,12 @@ async function worldCommand(verb, args) {
|
|
|
424
424
|
}
|
|
425
425
|
case 'changeset': {
|
|
426
426
|
const message = value(args, '-m') ?? value(args, '--message');
|
|
427
|
-
|
|
428
|
-
throw new Error('volter world changeset: say what and why — volter world changeset -m "<message>"');
|
|
429
|
-
const cs = world.changeset({ ...(pos[0] ? { name: pos[0] } : {}), message, ...(value(args, '--base') ? { base: value(args, '--base') } : {}), overwrite: flag(args, '--force') });
|
|
427
|
+
const cs = world.changeset({ ...(pos[0] ? { name: pos[0] } : {}), ...(message !== undefined ? { message } : {}), ...(value(args, '--base') ? { base: value(args, '--base') } : {}), overwrite: flag(args, '--force') });
|
|
430
428
|
if (asJson) {
|
|
431
429
|
json(cs);
|
|
432
430
|
return;
|
|
433
431
|
}
|
|
434
|
-
out(`changeset ${cs.name} ${cs.actions.length} change${cs.actions.length === 1 ? '' : 's'}
|
|
432
|
+
out(`changeset ${cs.name} ${cs.actions.length} change${cs.actions.length === 1 ? '' : 's'}${cs.message ? `\n ${cs.message}` : ''}${cs.narration ? `\n${cs.narration.split('\n').map((l) => ` ${l}`).join('\n')}` : ''}`);
|
|
435
433
|
return;
|
|
436
434
|
}
|
|
437
435
|
case 'view':
|
|
@@ -684,7 +682,7 @@ async function loginCommand(args) {
|
|
|
684
682
|
if (!res.ok)
|
|
685
683
|
throw new Error(`${origin} answered ${res.status}: ${body.error ?? 'not signed in'}`);
|
|
686
684
|
storeToken(origin, token);
|
|
687
|
-
storePlatform(origin);
|
|
685
|
+
storePlatform(origin, undefined, body.person?.email ?? body.person?.id);
|
|
688
686
|
out(`signed in ${origin} as ${body.person?.email ?? body.person?.id ?? 'you'}\norgs ${(body.orgs ?? []).map((o) => o.slug ?? o.name).join(', ') || '(none yet)'}\nnext in your app's folder: volter world init, then volter remote add origin ${(body.orgs ?? [])[0]?.slug ?? '<org>'}/<world> --create`);
|
|
689
687
|
}
|
|
690
688
|
/** `volter whoami`: which platform the command is signed into, as whom, and the orgs it reaches. */
|
|
@@ -8,7 +8,10 @@ export declare function storeToken(origin: string, token: string, path?: string)
|
|
|
8
8
|
export declare function forgetToken(origin: string, path?: string): boolean;
|
|
9
9
|
/** `volter login`: the hosted platform a person signed the CLI into — its origin, beside the credentials. */
|
|
10
10
|
export declare function platformPath(): string;
|
|
11
|
-
export declare function storePlatform(origin: string, path?: string): void;
|
|
11
|
+
export declare function storePlatform(origin: string, path?: string, person?: string): void;
|
|
12
|
+
/** Whom `volter login` signed the CLI in as (the platform's email or id for them): this machine's person, whom a World
|
|
13
|
+
* served here records as acting from its own page (docs/contributing/architecture.md, "Who acted is recorded"). */
|
|
14
|
+
export declare function platformPerson(path?: string): string | undefined;
|
|
12
15
|
/** Forget which platform the CLI is signed into. */
|
|
13
16
|
export declare function forgetPlatform(path?: string): void;
|
|
14
17
|
export declare function platformOrigin(path?: string): string | undefined;
|
package/dist/src/credentials.js
CHANGED
|
@@ -47,9 +47,22 @@ export function forgetToken(origin, path = credentialsPath()) {
|
|
|
47
47
|
}
|
|
48
48
|
/** `volter login`: the hosted platform a person signed the CLI into — its origin, beside the credentials. */
|
|
49
49
|
export function platformPath() { return join(dirname(credentialsPath()), 'platform.json'); }
|
|
50
|
-
export function storePlatform(origin, path = platformPath()) {
|
|
50
|
+
export function storePlatform(origin, path = platformPath(), person) {
|
|
51
51
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
52
|
-
writeFileSync(path, `${JSON.stringify({ url: normalizeOrigin(origin), savedAt: new Date().toISOString() }, null, 2)}\n`, { mode: 0o600 });
|
|
52
|
+
writeFileSync(path, `${JSON.stringify({ url: normalizeOrigin(origin), ...(person ? { person } : {}), savedAt: new Date().toISOString() }, null, 2)}\n`, { mode: 0o600 });
|
|
53
|
+
}
|
|
54
|
+
/** Whom `volter login` signed the CLI in as (the platform's email or id for them): this machine's person, whom a World
|
|
55
|
+
* served here records as acting from its own page (docs/contributing/architecture.md, "Who acted is recorded"). */
|
|
56
|
+
export function platformPerson(path = platformPath()) {
|
|
57
|
+
if (!existsSync(path))
|
|
58
|
+
return undefined;
|
|
59
|
+
try {
|
|
60
|
+
const p = JSON.parse(readFileSync(path, 'utf8')).person;
|
|
61
|
+
return typeof p === 'string' && p !== '' ? p : undefined;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
53
66
|
}
|
|
54
67
|
/** Forget which platform the CLI is signed into. */
|
|
55
68
|
export function forgetPlatform(path = platformPath()) { if (existsSync(path))
|
package/dist/src/world.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// 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
3
|
// way git finds its repository; every method is a name for a world-runtime verb, in the words a
|
|
4
4
|
// user reads. The `volter` command is one client of this class and adds nothing.
|
|
5
|
+
import { platformPerson } from "./credentials.js";
|
|
5
6
|
import { rebaseChangeset, worldBootMarker, listActions, isTwinBookkeeping, pushablePendingActions, pendingActions, applyTwinWrite, twinResources } from '@volter/world-core';
|
|
6
7
|
import { spawnSync } from 'node:child_process';
|
|
7
8
|
import clockForm from '@volter/world-core/world-clock';
|
|
8
|
-
import { activateScript, approveWorldChangeset, branchWorld, clockFile, checkoutWorld, createWorldChangeset, diffWorld, downWorld, fetchFromOrigin, findWorldChangeset, initWorld, listWorldChangesets, listWorldMarks, listWorlds, markWorld, pushWorldChangeset, rebaseWorldChangeset, replayWorldChangeset, resetWorld, runWithWorldEnv, seedWorld, shellWorld, statusWorld, statusWorldChangeset, upWorld, verifyWorldChangeset, worldLedgers, worldOrigin, bindLegacyCredentials, deployWorld, loadWorldConfig, writeWorldConfig, readServeRecord, refreshTwin, serveWorld, serveWorldView, findInstalledPackage, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, credentialPayloadFrom, rootCredentialInput, withoutVaultSignIn, materializeRoots } from '@volter/world-runtime';
|
|
9
|
+
import { activateScript, approveWorldChangeset, branchWorld, clockFile, checkoutWorld, createWorldChangeset, diffWorld, downWorld, fetchFromOrigin, findWorldChangeset, initWorld, listWorldChangesets, listWorldMarks, listWorlds, markWorld, pushWorldChangeset, rebaseWorldChangeset, replayWorldChangeset, resetWorld, runWithWorldEnv, seedWorld, shellWorld, statusWorld, statusWorldChangeset, upWorld, verifyWorldChangeset, worldLedgers, worldOrigin, bindLegacyCredentials, deployWorld, loadWorldConfig, writeWorldConfig, readServeRecord, refreshTwin, serveWorld, serveWorldView, findInstalledPackage, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, credentialPayloadFrom, rootCredentialInput, withoutVaultSignIn, materializeRoots, worldChangesetRules } from '@volter/world-runtime';
|
|
9
10
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
10
11
|
import { dirname, join, resolve } from 'node:path';
|
|
11
12
|
import { requireToken, storeToken } from "./credentials.js";
|
|
@@ -367,7 +368,7 @@ export class World {
|
|
|
367
368
|
/** Serve this world on a port under `/<org>/<world>/`; returns when listening. */
|
|
368
369
|
serve(opts = {}) { return serveWorld(this.name, { root: this.root, ...opts }); }
|
|
369
370
|
/** `volter world view`: this World, its mirrors, its branches and the console on one origin (docs/contributing/architecture.md, "Viewing a World"). */
|
|
370
|
-
view(opts = {}) { return serveWorldView(this.name, { root: this.root, ...opts }); }
|
|
371
|
+
view(opts = {}) { const person = platformPerson(); return serveWorldView(this.name, { root: this.root, ...(person ? { localPerson: person } : {}), ...opts }); }
|
|
371
372
|
// ── remotes ─────────────────────────────────────────────────────────────────────────────
|
|
372
373
|
/** The vendors this world has a twin of, as world.json names them (a twin service's package, else its id). */
|
|
373
374
|
vendors() {
|
|
@@ -447,8 +448,17 @@ export class World {
|
|
|
447
448
|
// ── changesets and push ─────────────────────────────────────────────────────────────────
|
|
448
449
|
/** Cut a changeset from the unpushed changes: the reviewable unit, with the author's message. */
|
|
449
450
|
changeset(opts = {}) {
|
|
450
|
-
|
|
451
|
-
|
|
451
|
+
// a summary is the author's, unless this World's rules take the one generated from its changes; a blank one is none
|
|
452
|
+
const message = opts.message?.trim() || undefined;
|
|
453
|
+
if (message === undefined && worldChangesetRules(this.root).summary !== 'generated')
|
|
454
|
+
throw new Error('volter world changeset: say what and why — volter world changeset -m "<message>"');
|
|
455
|
+
// with no summary, a cut must have something to say: an empty one is a slip (`volter world changeset status`)
|
|
456
|
+
if (message === undefined && !opts.overwrite && this.diff(opts.base).actions.length === 0)
|
|
457
|
+
throw new Error(`Nothing to cut in world "${this.name}": every change is already in a changeset`);
|
|
458
|
+
const name = opts.name ?? (message ? slugName(message, this.changesets()) : nextChangesetName(this.changesets()));
|
|
459
|
+
// cut on this machine: recorded as the person signed in here (`volter login`), else as no one in particular
|
|
460
|
+
const person = platformPerson();
|
|
461
|
+
return createWorldChangeset(this.name, name, { root: this.root, ...(person ? { cutBy: `person:${person}`, owner: person } : {}), ...(message !== undefined ? { message } : {}), ...(opts.base ? { base: opts.base } : {}), ...(opts.verifiers ? { verifiers: opts.verifiers } : {}), ...(opts.overwrite ? { overwrite: true } : {}) });
|
|
452
462
|
}
|
|
453
463
|
changesets() { return listWorldChangesets({ root: this.root, world: this.name }); }
|
|
454
464
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.54",
|
|
4
4
|
"description": "Run your app against a world of twins. `World` and `Repo` — one method per verb: init, up, run, log, diff, reset, branch, checkout, clone, fetch, changeset, push — and the `volter` command, one client of them.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"volter",
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
33
|
-
"@volter/world-core": "2.0.
|
|
34
|
-
"@volter/world-runtime": "2.0.
|
|
33
|
+
"@volter/world-core": "2.0.33",
|
|
34
|
+
"@volter/world-runtime": "2.0.51",
|
|
35
35
|
"zod": "^4.6.5"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@volter/world-console": "2.0.
|
|
38
|
+
"@volter/world-console": "2.0.14"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"@volter/world-console": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@volter/world-console": "2.0.
|
|
46
|
+
"@volter/world-console": "2.0.14"
|
|
47
47
|
},
|
|
48
48
|
"bin": {
|
|
49
49
|
"volter": "dist/src/cli.js"
|
package/src/cli.ts
CHANGED
|
@@ -318,10 +318,9 @@ async function worldCommand(verb: string, args: string[]): Promise<void> {
|
|
|
318
318
|
}
|
|
319
319
|
case 'changeset': {
|
|
320
320
|
const message = value(args, '-m') ?? value(args, '--message');
|
|
321
|
-
|
|
322
|
-
const cs = world.changeset({ ...(pos[0] ? { name: pos[0] } : {}), message, ...(value(args, '--base') ? { base: value(args, '--base')! } : {}), overwrite: flag(args, '--force') });
|
|
321
|
+
const cs = world.changeset({ ...(pos[0] ? { name: pos[0] } : {}), ...(message !== undefined ? { message } : {}), ...(value(args, '--base') ? { base: value(args, '--base')! } : {}), overwrite: flag(args, '--force') });
|
|
323
322
|
if (asJson) { json(cs); return; }
|
|
324
|
-
out(`changeset ${cs.name} ${cs.actions.length} change${cs.actions.length === 1 ? '' : 's'}
|
|
323
|
+
out(`changeset ${cs.name} ${cs.actions.length} change${cs.actions.length === 1 ? '' : 's'}${cs.message ? `\n ${cs.message}` : ''}${cs.narration ? `\n${cs.narration.split('\n').map((l) => ` ${l}`).join('\n')}` : ''}`);
|
|
325
324
|
return;
|
|
326
325
|
}
|
|
327
326
|
case 'view':
|
|
@@ -490,7 +489,7 @@ async function loginCommand(args: string[]): Promise<void> {
|
|
|
490
489
|
const res = await fetch(`${origin}/-/orgs`, { headers: { authorization: `Bearer ${token}` } });
|
|
491
490
|
const body = (await res.json().catch(() => ({}))) as { person?: { email?: string; id?: string }; orgs?: Array<{ slug: string | null; name: string }>; error?: string };
|
|
492
491
|
if (!res.ok) throw new Error(`${origin} answered ${res.status}: ${body.error ?? 'not signed in'}`);
|
|
493
|
-
storeToken(origin, token); storePlatform(origin);
|
|
492
|
+
storeToken(origin, token); storePlatform(origin, undefined, body.person?.email ?? body.person?.id);
|
|
494
493
|
out(`signed in ${origin} as ${body.person?.email ?? body.person?.id ?? 'you'}\norgs ${(body.orgs ?? []).map((o) => o.slug ?? o.name).join(', ') || '(none yet)'}\nnext in your app's folder: volter world init, then volter remote add origin ${(body.orgs ?? [])[0]?.slug ?? '<org>'}/<world> --create`);
|
|
495
494
|
}
|
|
496
495
|
|
package/src/credentials.ts
CHANGED
|
@@ -47,9 +47,15 @@ export function forgetToken(origin: string, path: string = credentialsPath()): b
|
|
|
47
47
|
|
|
48
48
|
/** `volter login`: the hosted platform a person signed the CLI into — its origin, beside the credentials. */
|
|
49
49
|
export function platformPath(): string { return join(dirname(credentialsPath()), 'platform.json'); }
|
|
50
|
-
export function storePlatform(origin: string, path: string = platformPath()): void {
|
|
50
|
+
export function storePlatform(origin: string, path: string = platformPath(), person?: string): void {
|
|
51
51
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
52
|
-
writeFileSync(path, `${JSON.stringify({ url: normalizeOrigin(origin), savedAt: new Date().toISOString() }, null, 2)}\n`, { mode: 0o600 });
|
|
52
|
+
writeFileSync(path, `${JSON.stringify({ url: normalizeOrigin(origin), ...(person ? { person } : {}), savedAt: new Date().toISOString() }, null, 2)}\n`, { mode: 0o600 });
|
|
53
|
+
}
|
|
54
|
+
/** Whom `volter login` signed the CLI in as (the platform's email or id for them): this machine's person, whom a World
|
|
55
|
+
* served here records as acting from its own page (docs/contributing/architecture.md, "Who acted is recorded"). */
|
|
56
|
+
export function platformPerson(path: string = platformPath()): string | undefined {
|
|
57
|
+
if (!existsSync(path)) return undefined;
|
|
58
|
+
try { const p = (JSON.parse(readFileSync(path, 'utf8')) as { person?: unknown }).person; return typeof p === 'string' && p !== '' ? p : undefined; } catch { return undefined; }
|
|
53
59
|
}
|
|
54
60
|
/** Forget which platform the CLI is signed into. */
|
|
55
61
|
export function forgetPlatform(path: string = platformPath()): void { if (existsSync(path)) writeFileSync(path, '{}\n', { mode: 0o600 }); }
|
package/src/world.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// 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
3
|
// way git finds its repository; every method is a name for a world-runtime verb, in the words a
|
|
4
4
|
// user reads. The `volter` command is one client of this class and adds nothing.
|
|
5
|
+
import { platformPerson } from './credentials.ts';
|
|
5
6
|
import { rebaseChangeset, worldBootMarker, type Changeset, type ChangesetApplication, type LedgerDelta, type PerformAction, type RemoteExecute, type TwinAction, type WorldMarker, type ApplyReceipt, listActions, isTwinBookkeeping, pushablePendingActions, pendingActions, applyTwinWrite, twinResources, type TwinResource } from '@volter/world-core';
|
|
6
7
|
import { spawnSync } from 'node:child_process';
|
|
7
8
|
import clockForm from '@volter/world-core/world-clock';
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
statusWorld, statusWorldChangeset, upWorld, verifyWorldChangeset, worldLedgers, worldOrigin,
|
|
11
12
|
bindLegacyCredentials, deployWorld, loadWorldConfig, writeWorldConfig, readServeRecord, refreshTwin, serveWorld, serveWorldView, findInstalledPackage, type ServedWorld, type ViewConsole, type WorldView, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, credentialPayloadFrom, rootCredentialInput, withoutVaultSignIn,
|
|
12
13
|
type DeployTwinOutcome, type MaterializedRoot,
|
|
13
|
-
type ChangesetLocation, type InitOptions, type InitResult, type WorldInstance, materializeRoots } from '@volter/world-runtime';
|
|
14
|
+
type ChangesetLocation, type InitOptions, type InitResult, type WorldInstance, materializeRoots, worldChangesetRules } from '@volter/world-runtime';
|
|
14
15
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
15
16
|
import { dirname, join, resolve } from 'node:path';
|
|
16
17
|
import { requireToken, storeToken } from './credentials.ts';
|
|
@@ -338,7 +339,7 @@ export class World {
|
|
|
338
339
|
/** Serve this world on a port under `/<org>/<world>/`; returns when listening. */
|
|
339
340
|
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 }); }
|
|
340
341
|
/** `volter world view`: this World, its mirrors, its branches and the console on one origin (docs/contributing/architecture.md, "Viewing a World"). */
|
|
341
|
-
view(opts: { port?: number; host?: string; console?: ViewConsole; announce?: (line: string) => void; origins?: boolean; origin?: string } = {}): Promise<WorldView> { return serveWorldView(this.name, { root: this.root, ...opts }); }
|
|
342
|
+
view(opts: { port?: number; host?: string; console?: ViewConsole; announce?: (line: string) => void; origins?: boolean; origin?: string } = {}): Promise<WorldView> { const person = platformPerson(); return serveWorldView(this.name, { root: this.root, ...(person ? { localPerson: person } : {}), ...opts }); }
|
|
342
343
|
|
|
343
344
|
// ── remotes ─────────────────────────────────────────────────────────────────────────────
|
|
344
345
|
/** The vendors this world has a twin of, as world.json names them (a twin service's package, else its id). */
|
|
@@ -411,8 +412,15 @@ export class World {
|
|
|
411
412
|
// ── changesets and push ─────────────────────────────────────────────────────────────────
|
|
412
413
|
/** Cut a changeset from the unpushed changes: the reviewable unit, with the author's message. */
|
|
413
414
|
changeset(opts: { name?: string; message?: string; base?: string; verifiers?: Parameters<typeof createWorldChangeset>[2] extends infer O ? (O extends { verifiers?: infer V } ? V : never) : never; overwrite?: boolean } = {}): Changeset {
|
|
414
|
-
|
|
415
|
-
|
|
415
|
+
// a summary is the author's, unless this World's rules take the one generated from its changes; a blank one is none
|
|
416
|
+
const message = opts.message?.trim() || undefined;
|
|
417
|
+
if (message === undefined && worldChangesetRules(this.root).summary !== 'generated') throw new Error('volter world changeset: say what and why — volter world changeset -m "<message>"');
|
|
418
|
+
// with no summary, a cut must have something to say: an empty one is a slip (`volter world changeset status`)
|
|
419
|
+
if (message === undefined && !opts.overwrite && this.diff(opts.base).actions.length === 0) throw new Error(`Nothing to cut in world "${this.name}": every change is already in a changeset`);
|
|
420
|
+
const name = opts.name ?? (message ? slugName(message, this.changesets()) : nextChangesetName(this.changesets()));
|
|
421
|
+
// cut on this machine: recorded as the person signed in here (`volter login`), else as no one in particular
|
|
422
|
+
const person = platformPerson();
|
|
423
|
+
return createWorldChangeset(this.name, name, { root: this.root, ...(person ? { cutBy: `person:${person}`, owner: person } : {}), ...(message !== undefined ? { message } : {}), ...(opts.base ? { base: opts.base } : {}), ...(opts.verifiers ? { verifiers: opts.verifiers } : {}), ...(opts.overwrite ? { overwrite: true } : {}) });
|
|
416
424
|
}
|
|
417
425
|
changesets(): ChangesetLocation[] { return listWorldChangesets({ root: this.root, world: this.name }); }
|
|
418
426
|
/**
|