@volter/world-runtime 2.0.54 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/known-external-services.json +5 -4
- package/dist/src/actions-runner-cli.d.ts +2 -0
- package/dist/src/actions-runner-cli.js +25 -0
- package/dist/src/actions-runner.d.ts +18 -0
- package/dist/src/actions-runner.js +281 -0
- package/dist/src/ancestry.js +1 -1
- package/dist/src/app-url.js +1 -1
- package/dist/src/backings.d.ts +29 -0
- package/dist/src/backings.js +45 -0
- package/dist/src/branch.js +3 -2
- package/dist/src/browser-proxy-cli.js +1 -1
- package/dist/src/catalog-index.d.ts +38 -0
- package/dist/src/catalog-index.js +37 -0
- package/dist/src/catalog.d.ts +16 -3
- package/dist/src/catalog.js +69 -26
- package/dist/src/changeset.d.ts +1 -1
- package/dist/src/changeset.js +9 -11
- package/dist/src/cli-args.d.ts +4 -0
- package/dist/src/cli-args.js +18 -0
- package/dist/src/cli-commands.d.ts +1 -0
- package/dist/src/cli-commands.js +1216 -0
- package/dist/src/cli.js +8 -1300
- package/dist/src/configs.js +1 -1
- package/dist/src/console-apart.d.ts +2 -2
- package/dist/src/console-apart.js +5 -5
- package/dist/src/consumers.js +1 -1
- package/dist/src/covers.d.ts +1 -1
- package/dist/src/covers.js +94 -30
- package/dist/src/env-output.d.ts +1 -1
- package/dist/src/env-output.js +2 -2
- package/dist/src/fixture-env.js +18 -26
- package/dist/src/host-cli.js +4 -2
- package/dist/src/host-fault-fixture.js +6 -0
- package/dist/src/host-ready.d.ts +8 -2
- package/dist/src/host-ready.js +16 -4
- package/dist/src/host-worker.js +6 -1
- package/dist/src/host.d.ts +2 -0
- package/dist/src/host.js +9 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +2 -2
- package/dist/src/init.d.ts +33 -23
- package/dist/src/init.js +228 -96
- package/dist/src/lifecycle-record.js +2 -2
- package/dist/src/local-branches.js +1 -1
- package/dist/src/origin.d.ts +1 -1
- package/dist/src/origin.js +12 -10
- package/dist/src/pack-facts.d.ts +30 -6
- package/dist/src/pack-facts.js +13 -3
- package/dist/src/pglite-backing.js +2 -1
- package/dist/src/placeholder.d.ts +1 -1
- package/dist/src/placeholder.js +12 -4
- package/dist/src/project-inspect.d.ts +1 -1
- package/dist/src/project-inspect.js +2 -2
- package/dist/src/redirect-proxy.d.ts +5 -0
- package/dist/src/redirect-proxy.js +8 -1
- package/dist/src/redis-backing.js +4 -2
- package/dist/src/reflect.js +1 -1
- package/dist/src/root.d.ts +11 -33
- package/dist/src/root.js +28 -59
- package/dist/src/runtime-test-support.js +1 -1
- package/dist/src/runtime.d.ts +7 -1
- package/dist/src/runtime.js +317 -53
- package/dist/src/schema.d.ts +27 -5
- package/dist/src/schema.js +13 -12
- package/dist/src/served-world.d.ts +11 -35
- package/dist/src/served-world.js +54 -141
- package/dist/src/tail.js +1 -1
- package/dist/src/up-cli.d.ts +3 -0
- package/dist/src/up-cli.js +55 -0
- package/dist/src/websocket-relay.d.ts +1 -1
- package/dist/src/world-origins.d.ts +1 -1
- package/dist/src/world-origins.js +1 -1
- package/dist/src/world-view.d.ts +1 -1
- package/dist/src/world-view.js +2 -2
- package/known-external-services.json +5 -4
- package/package.json +11 -11
- package/src/actions-runner-cli.ts +24 -0
- package/src/actions-runner.ts +244 -0
- package/src/ancestry.ts +1 -1
- package/src/app-url.ts +1 -1
- package/src/backings.ts +55 -0
- package/src/branch.ts +3 -2
- package/src/browser-proxy-cli.ts +1 -1
- package/src/catalog-index.ts +44 -0
- package/src/catalog.ts +67 -24
- package/src/changeset.ts +10 -13
- package/src/cli-args.ts +17 -0
- package/src/cli-commands.ts +1070 -0
- package/src/cli.ts +9 -1139
- package/src/configs.ts +1 -1
- package/src/console-apart.ts +5 -5
- package/src/consumers.ts +1 -1
- package/src/covers.ts +83 -30
- package/src/env-output.ts +2 -2
- package/src/fixture-env.ts +18 -26
- package/src/host-cli.ts +4 -2
- package/src/host-ready.ts +13 -4
- package/src/host-worker.ts +6 -2
- package/src/host.ts +11 -2
- package/src/index.ts +2 -2
- package/src/init.ts +222 -101
- package/src/lifecycle-record.ts +2 -2
- package/src/local-branches.ts +1 -1
- package/src/origin.ts +12 -10
- package/src/pack-facts.ts +22 -6
- package/src/pglite-backing.ts +2 -1
- package/src/placeholder.ts +13 -4
- package/src/project-inspect.ts +2 -2
- package/src/redirect-proxy.ts +9 -1
- package/src/redis-backing.ts +4 -2
- package/src/reflect.ts +1 -1
- package/src/root.ts +33 -65
- package/src/runtime-test-support.ts +1 -1
- package/src/runtime.ts +261 -55
- package/src/schema.ts +26 -13
- package/src/served-world.ts +50 -130
- package/src/tail.ts +1 -1
- package/src/up-cli.ts +55 -0
- package/src/websocket-relay.ts +1 -1
- package/src/world-origins.ts +1 -1
- package/src/world-view.ts +3 -3
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// The catalog index as the runtime reads it (docs/contributing/architecture.md, "The catalog: where twins come from"):
|
|
2
|
+
// its sources, its vendors' packages and versions, and each vendor's default twin. The index is a directory of data (the
|
|
3
|
+
// `volter-ai/twin-catalog` repository, published as the npm package `@volter/twin-catalog`); scripts/twin-catalog.ts
|
|
4
|
+
// checks and writes it, and `init` reads the defaults from the installed package.
|
|
5
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
/** The index as npm carries it. */
|
|
9
|
+
export const CATALOG_INDEX_PACKAGE = '@volter/twin-catalog';
|
|
10
|
+
|
|
11
|
+
/** A source; `workflow` is the file under `.github/workflows/` its packages are released by (release.yml unless named). */
|
|
12
|
+
export type Source = { name: string; repository: string; scope: string; official: boolean; protocol: '3'; workflow?: string };
|
|
13
|
+
export type Version = { version: string; status: 'live' | 'pending' | 'rejected'; at: string; by: string; reason?: string; commit?: string };
|
|
14
|
+
export type Entry = { vendor: string; packages: Array<{ name: string; source: string; versions: Version[] }> };
|
|
15
|
+
|
|
16
|
+
export const readSources = (index: string): Source[] => JSON.parse(readFileSync(join(index, 'sources.json'), 'utf8')) as Source[];
|
|
17
|
+
export function readVendors(index: string): Entry[] {
|
|
18
|
+
const dir = join(index, 'vendors');
|
|
19
|
+
return existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith('.json')).sort().map((f) => JSON.parse(readFileSync(join(dir, f), 'utf8')) as Entry) : [];
|
|
20
|
+
}
|
|
21
|
+
/** A package's vendor: `@volter/twin-<vendor>`, or the part after `twin-` in any scope. */
|
|
22
|
+
export const vendorOf = (name: string): string => name.replace(/^@[^/]+\//, '').replace(/^twin-/, '');
|
|
23
|
+
|
|
24
|
+
const semver = (v: string): number[] => v.split(/[.-]/).slice(0, 3).map((n) => Number(n) || 0);
|
|
25
|
+
const newer = (a: string, b: string): number => { const x = semver(a); const y = semver(b); for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i]! - y[i]!; return 0; };
|
|
26
|
+
|
|
27
|
+
/** Each vendor's default twin: its official package, else its live outside package; the
|
|
28
|
+
* highest live version of it. */
|
|
29
|
+
export function defaults(index: string): Record<string, { name: string; version: string; source: string }> {
|
|
30
|
+
const sources = new Map(readSources(index).map((s) => [s.name, s]));
|
|
31
|
+
const rank = (s: Source | undefined): number => (!s ? 0 : s.official ? 2 : 1);
|
|
32
|
+
const out: Record<string, { name: string; version: string; source: string }> = {};
|
|
33
|
+
for (const e of readVendors(index)) {
|
|
34
|
+
const candidates = e.packages
|
|
35
|
+
.map((p) => ({ p, s: sources.get(p.source), live: p.versions.filter((v) => v.status === 'live').sort((a, b) => newer(b.version, a.version))[0] }))
|
|
36
|
+
.filter((c) => c.live && c.s)
|
|
37
|
+
.sort((a, b) => rank(b.s) - rank(a.s));
|
|
38
|
+
const best = candidates[0];
|
|
39
|
+
// an outside twin is a default only where no official twin is
|
|
40
|
+
if (best && (best.s!.official || candidates.every((c) => !c.s!.official))) out[e.vendor] = { name: best.p.name, version: best.live!.version, source: best.p.source };
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
package/src/catalog.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// The catalog — where the twins a world can run come from (docs/concepts/worlds.md
|
|
2
|
-
// #the-config-and-the-running-world). Two sources answer one interface:
|
|
3
|
-
//
|
|
2
|
+
// #the-config-and-the-running-world). Two sources answer one interface: a pack-repository CHECKOUT and an
|
|
3
|
+
// INSTALLED catalog (`@volter/twin-<vendor>` packages under
|
|
4
4
|
// `node_modules`, walked up from the world root — hoisting-proof, and independent of the
|
|
5
5
|
// packages' `exports` maps, which do not expose `package.json`). A world born in an app repo
|
|
6
6
|
// names its twins as packages and the runtime resolves them at boot; a world born here names the
|
|
7
|
-
// checkout
|
|
7
|
+
// selected checkout paths.
|
|
8
8
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
10
|
import { dirname, join, resolve } from 'node:path';
|
|
11
|
+
import { overlayPackFacts, type PackFacts } from './pack-facts.ts';
|
|
12
|
+
import { installedPackageDir, isDerivedPack, packRepository } from '@volter/world-core/runtime';
|
|
11
13
|
|
|
12
14
|
export const TWIN_PACKAGE_PREFIX = '@volter/twin-';
|
|
13
15
|
|
|
14
|
-
/** Packages under the `@volter/twin-` prefix that are NOT twins. */
|
|
15
|
-
|
|
16
16
|
export function twinPackageName(vendor: string): string { return `${TWIN_PACKAGE_PREFIX}${vendor}`; }
|
|
17
17
|
|
|
18
18
|
export type CatalogKind = 'checkout' | 'installed';
|
|
@@ -29,15 +29,22 @@ export type Catalog = {
|
|
|
29
29
|
version(vendor: string): string | undefined;
|
|
30
30
|
/** The birth stamp: the checkout's git sha, or a digest of the installed versions. */
|
|
31
31
|
sha(): string;
|
|
32
|
+
/** Where the vendor's pack comes from: this checkout or an installed package (a merged catalog's sources differ per
|
|
33
|
+
* vendor, so a World boots each twin the way its own source serves it). */
|
|
34
|
+
kindOf(vendor: string): CatalogKind;
|
|
32
35
|
};
|
|
33
36
|
|
|
37
|
+
/** A derived (Protocol 3) pack carries the surface derive-pack generated from its spec. */
|
|
38
|
+
const major = (version: string | undefined): number => Number(/^(\d+)/.exec(version ?? '')?.[1] ?? 0);
|
|
39
|
+
|
|
34
40
|
function packageVersion(dir: string): string | undefined {
|
|
35
41
|
const p = join(dir, 'package.json');
|
|
36
42
|
if (!existsSync(p)) return undefined;
|
|
37
43
|
try { const v = (JSON.parse(readFileSync(p, 'utf8')) as { version?: unknown }).version; return typeof v === 'string' ? v : undefined; } catch { return undefined; }
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
/** A twin a catalog lists: a derived pack with its CLI. */
|
|
47
|
+
function isPackDir(dir: string): boolean { return packCli(dir) !== undefined && isDerivedPack(dir); }
|
|
41
48
|
|
|
42
49
|
type Manifest = { name?: string; exports?: unknown; main?: string; bin?: Record<string, string> | string };
|
|
43
50
|
function manifest(dir: string): Manifest | undefined { const p = join(dir, 'package.json'); if (!existsSync(p)) return undefined; try { return JSON.parse(readFileSync(p, 'utf8')) as Manifest; } catch { return undefined; } }
|
|
@@ -62,16 +69,7 @@ export function packCli(dir: string): string | undefined {
|
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
/** The nearest `node_modules/<name>` above `from` that holds a package.json — node's own walk. */
|
|
65
|
-
export
|
|
66
|
-
let dir = resolve(from);
|
|
67
|
-
for (;;) {
|
|
68
|
-
const candidate = join(dir, 'node_modules', ...name.split('/'));
|
|
69
|
-
if (existsSync(join(candidate, 'package.json'))) return candidate;
|
|
70
|
-
const up = dirname(dir);
|
|
71
|
-
if (up === dir) return undefined;
|
|
72
|
-
dir = up;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
72
|
+
export const findInstalledPackage = installedPackageDir;
|
|
75
73
|
|
|
76
74
|
/** Every installed `@volter/twin-<vendor>` visible from `from`, nearest wins. */
|
|
77
75
|
function installedTwins(from: string): Map<string, string> {
|
|
@@ -83,6 +81,7 @@ function installedTwins(from: string): Map<string, string> {
|
|
|
83
81
|
for (const entry of readdirSync(scope, { withFileTypes: true })) {
|
|
84
82
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
85
83
|
const name = `@volter/${entry.name}`;
|
|
84
|
+
if (name === '@volter/twin-standard') continue; // the platform's standard is tooling, not a vendor pack
|
|
86
85
|
if (!name.startsWith(TWIN_PACKAGE_PREFIX)) continue; // the platform's packages are `@volter/world-*`: outside the prefix by name
|
|
87
86
|
const vendor = name.slice(TWIN_PACKAGE_PREFIX.length);
|
|
88
87
|
const packDir = join(scope, entry.name);
|
|
@@ -103,17 +102,18 @@ function fnv1a(text: string): string {
|
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
export function checkoutCatalog(root: string): Catalog {
|
|
106
|
-
const packsDir = resolve(root
|
|
105
|
+
const packsDir = resolve(root);
|
|
107
106
|
const vendors = (): string[] => readdirSync(packsDir, { withFileTypes: true })
|
|
108
|
-
.filter((entry) => entry.isDirectory() && isPackDir(join(packsDir, entry.name)))
|
|
107
|
+
.filter((entry) => (entry.isDirectory() || entry.isSymbolicLink()) && manifest(join(packsDir, entry.name))?.name === twinPackageName(entry.name) && isPackDir(join(packsDir, entry.name)))
|
|
109
108
|
.map((entry) => entry.name)
|
|
110
109
|
.sort();
|
|
111
110
|
return {
|
|
112
111
|
kind: 'checkout',
|
|
113
112
|
root: resolve(root),
|
|
114
113
|
vendors,
|
|
115
|
-
packDir: (vendor) => (isPackDir(join(packsDir, vendor)) ? join(packsDir, vendor) : undefined),
|
|
114
|
+
packDir: (vendor) => (/^[a-z][a-z0-9-]*$/.test(vendor) && manifest(join(packsDir, vendor))?.name === twinPackageName(vendor) && isPackDir(join(packsDir, vendor)) ? join(packsDir, vendor) : undefined),
|
|
116
115
|
version: (vendor) => packageVersion(join(packsDir, vendor)),
|
|
116
|
+
kindOf: () => 'checkout',
|
|
117
117
|
sha: () => {
|
|
118
118
|
const proc = spawnSync('git', ['-C', root, 'rev-parse', 'HEAD'], { windowsHide: true, encoding: 'utf8' });
|
|
119
119
|
const sha = proc.status === 0 ? proc.stdout.trim() : '';
|
|
@@ -130,22 +130,65 @@ export function installedCatalog(root: string): Catalog {
|
|
|
130
130
|
vendors: () => [...twins.keys()].sort(),
|
|
131
131
|
packDir: (vendor) => twins.get(vendor),
|
|
132
132
|
version: (vendor) => { const dir = twins.get(vendor); return dir === undefined ? undefined : packageVersion(dir); },
|
|
133
|
+
kindOf: () => 'installed',
|
|
133
134
|
sha: () => `installed-${fnv1a([...twins.keys()].sort().map((v) => `${v}@${packageVersion(twins.get(v)!) ?? '?'}`).join(','))}`,
|
|
134
135
|
};
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* One catalog over several sources (docs/contributing/architecture.md, "The catalog: where twins come from"): each
|
|
140
|
+
* vendor is served by the pack of the higher major; on a tie the earlier source keeps it. `kind` is the first source's.
|
|
141
|
+
*/
|
|
142
|
+
export function mergedCatalog(sources: Catalog[]): Catalog {
|
|
143
|
+
const chosen = new Map<string, Catalog>();
|
|
144
|
+
for (const source of sources) {
|
|
145
|
+
for (const vendor of source.vendors()) {
|
|
146
|
+
const held = chosen.get(vendor);
|
|
147
|
+
if (held === undefined) { chosen.set(vendor, source); continue; }
|
|
148
|
+
if (major(source.version(vendor)) > major(held.version(vendor))) chosen.set(vendor, source);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const of = (vendor: string): Catalog | undefined => chosen.get(vendor);
|
|
152
|
+
return {
|
|
153
|
+
kind: sources[0]!.kind,
|
|
154
|
+
root: sources[0]!.root,
|
|
155
|
+
vendors: () => [...chosen.keys()].sort(),
|
|
156
|
+
packDir: (vendor) => of(vendor)?.packDir(vendor),
|
|
157
|
+
version: (vendor) => of(vendor)?.version(vendor),
|
|
158
|
+
kindOf: (vendor) => of(vendor)?.kindOf(vendor) ?? sources[0]!.kind,
|
|
159
|
+
sha: () => sources.map((s) => s.sha()).join('+'),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Overlays one facts file on the kernel's (a file that does not read is skipped: the kernel's facts stand). */
|
|
164
|
+
export function overlayPackFactsFile(file: string): void {
|
|
165
|
+
try { overlayPackFacts((JSON.parse(readFileSync(file, 'utf8')) as { packs?: Record<string, PackFacts> }).packs ?? {}); } catch { /* the kernel's facts stand */ }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** The facts file a released pack carries (`generated/pack-facts.json`, written by pack-release), when it has one. */
|
|
169
|
+
export function packFactsFile(dir: string): string | undefined {
|
|
170
|
+
const file = join(dir, 'generated', 'pack-facts.json');
|
|
171
|
+
return existsSync(file) ? file : undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The catalog for a World root: its pack repository (or the one beside its platform checkout), merged with installed
|
|
175
|
+
* packages. Only the selected packs' own facts are overlaid on the kernel's. */
|
|
138
176
|
export function resolveCatalog(root: string): Catalog {
|
|
139
177
|
const resolved = resolve(root);
|
|
140
|
-
if (existsSync(join(resolved, 'packages', 'twin'))) return checkoutCatalog(resolved);
|
|
141
178
|
const installed = installedCatalog(resolved);
|
|
142
|
-
|
|
179
|
+
const repo = packRepository(resolved);
|
|
180
|
+
const catalog = repo === undefined ? installed : mergedCatalog([checkoutCatalog(repo), installed]);
|
|
181
|
+
for (const vendor of catalog.vendors()) {
|
|
182
|
+
const file = packFactsFile(catalog.packDir(vendor)!);
|
|
183
|
+
if (file) overlayPackFactsFile(file);
|
|
184
|
+
}
|
|
185
|
+
if (catalog.vendors().length === 0) {
|
|
143
186
|
throw new Error(
|
|
144
|
-
`No twins found for ${resolved}: no
|
|
187
|
+
`No twins found for ${resolved}: no pack repository and no \`@volter/twin-<vendor>\` package installed above it. `
|
|
145
188
|
+ `Install the twins your app needs (e.g. \`bun add -d @volter/twin-stripe\`) and run init again.`,
|
|
146
189
|
);
|
|
147
190
|
}
|
|
148
|
-
return
|
|
191
|
+
return catalog;
|
|
149
192
|
}
|
|
150
193
|
|
|
151
194
|
/** The pack directory a service boots from: its `package` resolved from the world root at boot. */
|
package/src/changeset.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { worldChangesetRules } from './configs.ts';
|
|
2
|
-
import { blobDigest, captureHistory, captureParentHistory, getActiveBlobStore, getActiveWorldStore, originHead,
|
|
2
|
+
import { blobDigest, captureHistory, captureParentHistory, getActiveBlobStore, getActiveWorldStore, originHead, withHistoryLocks, worldPaths, type HistoryReference } from '@volter/world-core/runtime';
|
|
3
3
|
// `volter-world mark` / `diff` / `changeset` — the WORLD-side verbs over the changeset
|
|
4
4
|
// primitive (docs/concepts/the-model.md v0). Thin, exactly like `covers` and `tail` are thin: all
|
|
5
5
|
// the state logic (markers, deltas, content hashing, replay) lives in @volter/world-core's
|
|
@@ -44,14 +44,14 @@ import {
|
|
|
44
44
|
CHANGESET_KIND,
|
|
45
45
|
MARKER_KIND,
|
|
46
46
|
WORLD_BOOT_MARKER_ID,
|
|
47
|
-
} from '@volter/world-core';
|
|
48
|
-
import type { ApplyReceipt, Receipt, ChangesetApplication, RebaseReport, RebaseTarget, Changeset, ChangesetApproval, ChangesetReadiness, ChangesetVerification, ChangesetVerifier, LedgerDelta, LedgerRef, ReplayReport, ReplayTarget, WorldMarker } from '@volter/world-core';
|
|
47
|
+
} from '@volter/world-core/runtime';
|
|
48
|
+
import type { ApplyReceipt, Receipt, ChangesetApplication, RebaseReport, RebaseTarget, Changeset, ChangesetApproval, ChangesetReadiness, ChangesetVerification, ChangesetVerifier, LedgerDelta, LedgerRef, ReplayReport, ReplayTarget, WorldMarker } from '@volter/world-core/runtime';
|
|
49
49
|
import { instanceDir, listWorlds, saveWorldInstance, statusWorld } from './runtime.ts';
|
|
50
50
|
import { fetchFromOrigin } from './origin.ts';
|
|
51
51
|
import type { WorldInstance } from './schema.ts';
|
|
52
52
|
import { fetchReady, isPathRemote, landChangeset, landReceipts, localWorld, TOKEN_HEADER } from './served-world.ts';
|
|
53
53
|
import { loadWorldChecks } from './root.ts';
|
|
54
|
-
import { originEntries, parentEntries, readBranchMeta, readTree, rebaseBranch, runChecks, wholeLog, writeBranchMeta } from '@volter/world-core';
|
|
54
|
+
import { originEntries, parentEntries, readBranchMeta, readTree, rebaseBranch, runChecks, wholeLog, writeBranchMeta } from '@volter/world-core/runtime';
|
|
55
55
|
|
|
56
56
|
export type WorldRootOptions = { root?: string };
|
|
57
57
|
|
|
@@ -68,7 +68,7 @@ function requireWorld(name: string, root: string): WorldInstance {
|
|
|
68
68
|
} catch (error) {
|
|
69
69
|
if (!/World instance not found/.test(String((error as Error).message))) throw error;
|
|
70
70
|
const known = listWorlds(root).map((world) => world.name);
|
|
71
|
-
throw new Error(`World "${name}" not found in ${root}${known.length ? ` (known worlds: ${known.join(', ')})` : ' (no worlds here)'} —
|
|
71
|
+
throw new Error(`World "${name}" not found in ${root}${known.length ? ` (known worlds: ${known.join(', ')})` : ' (no worlds here)'} — start it with \`volter world up\` in the app's folder (the operator's own: \`volter-world up <config> --env-out <path> --name ${name}\`)`);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -215,7 +215,7 @@ export function createWorldChangeset(
|
|
|
215
215
|
// re-cutting an existing name (overwrite) starts from that changeset's own base, not from its cut
|
|
216
216
|
const base = options.base ?? (getActiveWorldStore().exists(path) ? readChangesetFile(path).base : undefined);
|
|
217
217
|
const delta = diffWorld(world, { root, ...(base === undefined ? {} : { base }) });
|
|
218
|
-
// where the parent log stands for each twin the changeset touches (
|
|
218
|
+
// where the parent log stands for each twin the changeset touches (the cut position a rebase reads from)
|
|
219
219
|
const cutInstance = requireWorld(world, root);
|
|
220
220
|
const cut: Record<string, HistoryReference> = {};
|
|
221
221
|
for (const a of delta.actions) if (!(a.service in cut)) { const cr = join(cutInstance.dirs.data, a.service); const st = a.stateService ?? a.service; cut[a.service] = captureParentHistory(st, cr); }
|
|
@@ -614,13 +614,10 @@ export function rebaseWorldChangeset(name: string, options: WorldRootOptions & {
|
|
|
614
614
|
const root = resolveRoot(options.root);
|
|
615
615
|
const located = findWorldChangeset(name, { root, ...(options.world ? { world: options.world } : {}) });
|
|
616
616
|
// the branch first: its pointer moves to the parent's current position (what a fetch brought in)
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
const controlRoot = join(inst.dirs.data, service);
|
|
622
|
-
rebaseBranch(st, controlRoot, { origin: Boolean(readBranchMeta(st, controlRoot)?.origin) });
|
|
623
|
-
}
|
|
617
|
+
const inst = requireWorld(located.world, root);
|
|
618
|
+
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) }));
|
|
619
|
+
withHistoryLocks(moved.map(({ st, controlRoot }) => worldPaths(st, controlRoot).dir), () => {
|
|
620
|
+
for (const { st, controlRoot } of moved) rebaseBranch(st, controlRoot, { origin: Boolean(readBranchMeta(st, controlRoot)?.origin) });
|
|
624
621
|
});
|
|
625
622
|
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`);
|
|
626
623
|
const instance = requireWorld(located.world, root);
|
package/src/cli-args.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** The command line's option readers, shared by `up` (./up-cli.ts) and every other command (./cli-commands.ts). */
|
|
2
|
+
|
|
3
|
+
/** An option's value, written `--name value` or `--name=value`. */
|
|
4
|
+
export function optionValue(args: string[], name: string, fallback = ''): string {
|
|
5
|
+
const joined = args.find((arg) => arg.startsWith(`${name}=`));
|
|
6
|
+
if (joined !== undefined) return joined.slice(name.length + 1);
|
|
7
|
+
const index = args.indexOf(name);
|
|
8
|
+
return index >= 0 && index + 1 < args.length ? args[index + 1]! : fallback;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function ownerOption(args: string[]): string | undefined {
|
|
12
|
+
const index = args.indexOf('--owner');
|
|
13
|
+
if (index < 0) return undefined;
|
|
14
|
+
const label = args[index + 1];
|
|
15
|
+
if (!label || label.startsWith('--')) throw new Error('--owner requires a task label');
|
|
16
|
+
return label;
|
|
17
|
+
}
|