@volter/world 3.0.179 → 3.0.180
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/README.md +1 -1
- package/dist/examples/customer-onboarding/README.md +3 -3
- package/dist/examples/customer-onboarding/package.json +13 -5
- package/dist/examples/customer-onboarding/pnpm-workspace.yaml +6 -0
- package/dist/examples/customer-onboarding/public-files.json +2 -2
- package/dist/examples/index.json +3 -0
- package/dist/examples/integration-branches/README.md +8 -8
- package/dist/examples/integration-branches/package.json +13 -5
- package/dist/examples/integration-branches/pnpm-workspace.yaml +6 -0
- package/dist/examples/integration-branches/public-files.json +2 -2
- package/dist/examples/workplace-agent/README.md +3 -3
- package/dist/examples/workplace-agent/package.json +13 -5
- package/dist/examples/workplace-agent/pnpm-workspace.yaml +6 -0
- package/dist/examples/workplace-agent/public-files.json +2 -2
- package/dist/skills/volter-world/AGENTS.md +7 -2
- package/dist/src/agent-prompt.js +2 -2
- package/dist/src/cli.js +4 -4
- package/dist/src/update-notice.js +1 -1
- package/dist/src/world.d.ts +1 -1
- package/dist/src/world.js +7 -5
- package/examples/customer-onboarding/README.md +3 -3
- package/examples/customer-onboarding/package.json +13 -5
- package/examples/customer-onboarding/pnpm-workspace.yaml +6 -0
- package/examples/customer-onboarding/public-files.json +2 -2
- package/examples/index.json +3 -0
- package/examples/integration-branches/README.md +8 -8
- package/examples/integration-branches/package.json +13 -5
- package/examples/integration-branches/pnpm-workspace.yaml +6 -0
- package/examples/integration-branches/public-files.json +2 -2
- package/examples/workplace-agent/README.md +3 -3
- package/examples/workplace-agent/package.json +13 -5
- package/examples/workplace-agent/pnpm-workspace.yaml +6 -0
- package/examples/workplace-agent/public-files.json +2 -2
- package/package.json +14 -6
- package/skills/volter-world/AGENTS.md +7 -2
- package/src/agent-prompt.ts +2 -2
- package/src/cli.ts +4 -4
- package/src/update-notice.ts +1 -1
- package/src/world.ts +7 -4
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Run your app against a world of twins. This package is the `volter` command and
|
|
|
4
4
|
it: `World` and `TwinLog`, one method per verb.
|
|
5
5
|
|
|
6
6
|
```bash
|
|
7
|
-
|
|
7
|
+
pnpm add -D --package-import-method=hardlink --config.nodeLinker=hoisted @volter/world @volter/twin-stripe
|
|
8
8
|
volter world init # reads the app, writes .volter/world.json beside the code
|
|
9
9
|
volter world up # starts the twins, loads their default data
|
|
10
10
|
volter world run -- npm test
|
|
@@ -13,12 +13,12 @@ You need Node 22.6+, npm and registry access during installation. The browser pa
|
|
|
13
13
|
Copy this starter into a new folder:
|
|
14
14
|
|
|
15
15
|
```console
|
|
16
|
-
$ npx @volter/world@3.0.
|
|
16
|
+
$ npx @volter/world@3.0.180 example customer-onboarding --dir onboarding
|
|
17
17
|
$ cd onboarding
|
|
18
|
-
$
|
|
18
|
+
$ pnpm install
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
The command copies source; it neither installs dependencies nor starts a World. Its manifest pins the CLI, core, runtime, console, twins and SDKs. The
|
|
21
|
+
The command copies source; it neither installs dependencies nor starts a World. Its manifest pins the CLI, core, runtime, console, twins and SDKs. The copied starter retains its native pnpm settings. Its first install creates a lockfile; later installs use `pnpm install --frozen-lockfile`. The [package manifest](./package.json) identifies the selected dependency versions. Keep your own lockfile with the application when sharing it.
|
|
22
22
|
|
|
23
23
|
Read [onboarding.mjs](./onboarding.mjs) for the application and [run.mjs](./run.mjs) for the command entry. Empty keys in [.env.example](./.env.example) tell init what the app reads; init supplies throwaway credentials. Do not add real keys.
|
|
24
24
|
|
|
@@ -11,12 +11,20 @@
|
|
|
11
11
|
"pg": "8.16.3"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@volter/world": "3.0.
|
|
15
|
-
"@volter/world-core": "3.0.
|
|
16
|
-
"@volter/world-runtime": "3.0.
|
|
17
|
-
"@volter/world-console": "3.0.
|
|
14
|
+
"@volter/world": "3.0.180",
|
|
15
|
+
"@volter/world-core": "3.0.161",
|
|
16
|
+
"@volter/world-runtime": "3.0.177",
|
|
17
|
+
"@volter/world-console": "3.0.152",
|
|
18
18
|
"@volter/twin-stripe": "3.0.2",
|
|
19
19
|
"@volter/twin-resend": "1.0.1",
|
|
20
|
-
"@volter/world-ui": "1.0.
|
|
20
|
+
"@volter/world-ui": "1.0.161"
|
|
21
|
+
},
|
|
22
|
+
"packageManager": "pnpm@12.15.0",
|
|
23
|
+
"devEngines": {
|
|
24
|
+
"packageManager": {
|
|
25
|
+
"name": "pnpm",
|
|
26
|
+
"version": "12.15.0",
|
|
27
|
+
"onFail": "error"
|
|
28
|
+
}
|
|
21
29
|
}
|
|
22
30
|
}
|
package/dist/examples/index.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"run.mjs",
|
|
15
15
|
"server.mjs",
|
|
16
16
|
"style.css",
|
|
17
|
+
"pnpm-workspace.yaml",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
]
|
|
19
20
|
},
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
"read-slack-mail.mjs",
|
|
34
35
|
"reset-receipts.mjs",
|
|
35
36
|
"seed.mjs",
|
|
37
|
+
"pnpm-workspace.yaml",
|
|
36
38
|
"LICENSE"
|
|
37
39
|
]
|
|
38
40
|
},
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
"package.json",
|
|
48
50
|
"public-files.json",
|
|
49
51
|
"receipt.mjs",
|
|
52
|
+
"pnpm-workspace.yaml",
|
|
50
53
|
"LICENSE"
|
|
51
54
|
]
|
|
52
55
|
}
|
|
@@ -4,18 +4,18 @@ title: Shared baseline, workers and integration
|
|
|
4
4
|
|
|
5
5
|
# Shared baseline, workers and integration
|
|
6
6
|
|
|
7
|
-
Give two workers the same Stripe baseline, let them change different existing customers in isolated Worlds, then send their changesets to a separate integration World. This local example needs Node 22.6+,
|
|
7
|
+
Give two workers the same Stripe baseline, let them change different existing customers in isolated Worlds, then send their changesets to a separate integration World. This local example needs Node 22.6+, pnpm 12.15.0 and registry access during preparation. It requires no platform account, shared host or real vendor key.
|
|
8
8
|
|
|
9
|
-
[package.json](./package.json)
|
|
9
|
+
[package.json](./package.json) identifies the selected dependency versions. Each copied starter installs exact manifest pins and keeps the lockfile created by its first installation for frozen reentry. [customer.mjs](./customer.mjs) uses Stripe's real SDK. [receipt.mjs](./receipt.mjs) shows the current World, configured remotes, tracked origin, unpushed work and each changeset's actual destination.
|
|
10
10
|
|
|
11
11
|
## Make the baseline
|
|
12
12
|
|
|
13
13
|
Use an empty parent folder. Create the baseline starter there:
|
|
14
14
|
|
|
15
15
|
```console
|
|
16
|
-
$ npx @volter/world@3.0.
|
|
16
|
+
$ npx @volter/world@3.0.180 example integration-branches --dir baseline
|
|
17
17
|
$ cd baseline
|
|
18
|
-
$
|
|
18
|
+
$ pnpm install
|
|
19
19
|
$ ./node_modules/.bin/volter world init --name baseline --twins stripe
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -38,16 +38,16 @@ The stopped baseline retains its history and can supply local path remotes. Defa
|
|
|
38
38
|
Create three more copies in the same parent folder:
|
|
39
39
|
|
|
40
40
|
```console
|
|
41
|
-
$ npx @volter/world@3.0.
|
|
42
|
-
$ npx @volter/world@3.0.
|
|
43
|
-
$ npx @volter/world@3.0.
|
|
41
|
+
$ npx @volter/world@3.0.180 example integration-branches --dir integration
|
|
42
|
+
$ npx @volter/world@3.0.180 example integration-branches --dir worker-a
|
|
43
|
+
$ npx @volter/world@3.0.180 example integration-branches --dir worker-b
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
In `integration`, then `worker-a`, then `worker-b`, install dependencies and initialize with the folder's name. Before a first boot or app write, link and pull the baseline:
|
|
47
47
|
|
|
48
48
|
```console
|
|
49
49
|
$ cd integration
|
|
50
|
-
$
|
|
50
|
+
$ pnpm install --frozen-lockfile
|
|
51
51
|
$ ./node_modules/.bin/volter world init --name integration --twins stripe
|
|
52
52
|
$ ./node_modules/.bin/volter remote add origin ../baseline
|
|
53
53
|
$ ./node_modules/.bin/volter world pull
|
|
@@ -9,11 +9,19 @@
|
|
|
9
9
|
"stripe": "17.7.0"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@volter/world": "3.0.
|
|
13
|
-
"@volter/world-core": "3.0.
|
|
14
|
-
"@volter/world-runtime": "3.0.
|
|
15
|
-
"@volter/world-console": "3.0.
|
|
12
|
+
"@volter/world": "3.0.180",
|
|
13
|
+
"@volter/world-core": "3.0.161",
|
|
14
|
+
"@volter/world-runtime": "3.0.177",
|
|
15
|
+
"@volter/world-console": "3.0.152",
|
|
16
16
|
"@volter/twin-stripe": "3.0.2",
|
|
17
|
-
"@volter/world-ui": "1.0.
|
|
17
|
+
"@volter/world-ui": "1.0.161"
|
|
18
|
+
},
|
|
19
|
+
"packageManager": "pnpm@12.15.0",
|
|
20
|
+
"devEngines": {
|
|
21
|
+
"packageManager": {
|
|
22
|
+
"name": "pnpm",
|
|
23
|
+
"version": "12.15.0",
|
|
24
|
+
"onFail": "error"
|
|
25
|
+
}
|
|
18
26
|
}
|
|
19
27
|
}
|
|
@@ -19,13 +19,13 @@ A catalog page may select a newer default. Keep this example's manifest and inst
|
|
|
19
19
|
together; its recorded workflow does not establish behavior for a different release.
|
|
20
20
|
|
|
21
21
|
```console
|
|
22
|
-
$ npx @volter/world@3.0.
|
|
22
|
+
$ npx @volter/world@3.0.180 example workplace-agent --dir workplace
|
|
23
23
|
$ cd workplace
|
|
24
|
-
$
|
|
24
|
+
$ pnpm install
|
|
25
25
|
$ ./node_modules/.bin/volter world init --name workplace --twins github,linear,slack
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Read [package.json](./package.json)
|
|
28
|
+
Read [package.json](./package.json) and [clients.mjs](./clients.mjs) for exact tool, twin and SDK versions. Keep the supplied native pnpm settings and the lockfile created by installation with the application; later installs use `pnpm install --frozen-lockfile`. Review the three configured vendors and synthetic credentials before booting. No model vendor is selected by this starter.
|
|
29
29
|
|
|
30
30
|
```console
|
|
31
31
|
$ ./node_modules/.bin/volter world up
|
|
@@ -11,13 +11,21 @@
|
|
|
11
11
|
"@slack/web-api": "7.19.0"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@volter/world": "3.0.
|
|
15
|
-
"@volter/world-core": "3.0.
|
|
16
|
-
"@volter/world-runtime": "3.0.
|
|
17
|
-
"@volter/world-console": "3.0.
|
|
14
|
+
"@volter/world": "3.0.180",
|
|
15
|
+
"@volter/world-core": "3.0.161",
|
|
16
|
+
"@volter/world-runtime": "3.0.177",
|
|
17
|
+
"@volter/world-console": "3.0.152",
|
|
18
18
|
"@volter/twin-github": "3.0.9",
|
|
19
19
|
"@volter/twin-linear": "3.0.5",
|
|
20
20
|
"@volter/twin-slack": "3.0.8",
|
|
21
|
-
"@volter/world-ui": "1.0.
|
|
21
|
+
"@volter/world-ui": "1.0.161"
|
|
22
|
+
},
|
|
23
|
+
"packageManager": "pnpm@12.15.0",
|
|
24
|
+
"devEngines": {
|
|
25
|
+
"packageManager": {
|
|
26
|
+
"name": "pnpm",
|
|
27
|
+
"version": "12.15.0",
|
|
28
|
+
"onFail": "error"
|
|
29
|
+
}
|
|
22
30
|
}
|
|
23
31
|
}
|
|
@@ -26,8 +26,13 @@ volter world log # what the app did at each vendor
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Show the person the vendors `init` detected, and why, before keeping them. Keys the app reads
|
|
29
|
-
(named in `.env.example`) get throwaway values in the World's env; never
|
|
30
|
-
a
|
|
29
|
+
(named in `.env.example`) for selected twins get throwaway values in the World's env; never
|
|
30
|
+
replace a twin's credential with a real one to get a test past validation. An intentionally
|
|
31
|
+
omitted vendor uses its ordinary production SDK/API and the caller's own credential at launch,
|
|
32
|
+
subject to sandbox and network policy. Remove that vendor's fake values and endpoint overrides;
|
|
33
|
+
keep live credentials out of the config and generated World env. Follow
|
|
34
|
+
[turn a service off](../../docs/guides/turn-a-service-off.md), including its credential-launch order.
|
|
35
|
+
`volter world view --no-open` runs the World with its
|
|
31
36
|
dashboard and prints its link for the person; it keeps running, so start it in the background,
|
|
32
37
|
and stop a World you started with `up` first (`down` keeps its state).
|
|
33
38
|
|
package/dist/src/agent-prompt.js
CHANGED
|
@@ -8,8 +8,8 @@ export function agentPrompt(o = {}) {
|
|
|
8
8
|
'Set up Volter World for the workflow I want to run in this app. Select only the vendors that workflow deliberately substitutes with local twins. Keep the app\'s real SDKs.',
|
|
9
9
|
'',
|
|
10
10
|
o.tools
|
|
11
|
-
? '1. You have the Volter World tools (volter mcp). Keep an app\'s existing pinned World tools. If the `volter` command is missing, install it: `
|
|
12
|
-
: '1. Keep the app\'s pinned World tools if present, running `./node_modules/.bin/volter` from the app folder after installing its locked dependencies. A missing local binary stops without fetching another package. Otherwise install the command: `
|
|
11
|
+
? '1. You have the Volter World tools (volter mcp). Keep an app\'s existing pinned World tools. If the `volter` command is missing, install it: `pnpm add -g --package-import-method=hardlink --config.nodeLinker=hoisted @volter/world`.'
|
|
12
|
+
: '1. Keep the app\'s pinned World tools if present, running `./node_modules/.bin/volter` from the app folder after installing its locked dependencies. A missing local binary stops without fetching another package. Otherwise install the command: `pnpm add -g --package-import-method=hardlink --config.nodeLinker=hoisted @volter/world`. Install the agent tooling with `volter agents install --yes`.',
|
|
13
13
|
`2. Read the app's manifests, env names and any .volter/world.json. Show me the likely vendors and why, before keeping them. Preserve a saved World and its state; do not initialize it again to resume. For a new World, choose the exact catalog releases and initialize only the selected vendors with ${cmd('volter world init --twins <selected-vendors> --source <vendor>=<package>', 'world_init with vendors and sources')}.`,
|
|
14
14
|
`3. ${o.tools ? 'Start it with world_up' : 'Start it: `volter world up`'}, then run my usual app command inside it: ${cmd('volter world run -- <the app command>', 'world_run')}. Use throwaway credentials. Diagnose a failure as routing, data, scenario, unsupported operation or runtime; preserve the refusal's evidence rather than bypassing it.`,
|
|
15
15
|
`4. Tell me what the app did at each vendor (${cmd('volter world log', 'world_log')}), and give me the dashboard link (${o.tools ? 'world_view' : '`volter world view --no-open`, started in the background: it keeps running'}).`,
|
package/dist/src/cli.js
CHANGED
|
@@ -515,7 +515,7 @@ async function worldCommand(verb, args) {
|
|
|
515
515
|
// the read token's own one-line way in, for handing to someone who should look and not touch
|
|
516
516
|
// (not on a local World: any of its pages can open a write session there, so a read-only link restricts nothing)
|
|
517
517
|
const readOnly = view.console && !(local && view.origin) ? `${view.console}#token=${encodeURIComponent(view.readToken)}` : null;
|
|
518
|
-
out(`${local && view.origin ? `apps the World's tokens are in .volter/token and .volter/token.read (apps and scripts present them; the browser needs none)` : verb === 'serve' ? '' : `token ${view.token}\nread ${view.readToken}`}${readOnly ? `\nread-only view ${readOnly}` : ''}${view.console ? '' : '\n(no console installed: `
|
|
518
|
+
out(`${local && view.origin ? `apps the World's tokens are in .volter/token and .volter/token.read (apps and scripts present them; the browser needs none)` : verb === 'serve' ? '' : `token ${view.token}\nread ${view.readToken}`}${readOnly ? `\nread-only view ${readOnly}` : ''}${view.console ? '' : '\n(no console installed: `pnpm add -D --package-import-method=hardlink --config.nodeLinker=hoisted @volter/world-console` to look around in a browser)'}${view.origin ? '\n(the World opens at its own *.localhost origin; if your browser cannot reach *.localhost, run again with --no-origins)' : ''}`);
|
|
519
519
|
if (open && verb === 'view' && !args.includes('--no-open'))
|
|
520
520
|
openInBrowser(open);
|
|
521
521
|
await waitForServeShutdown(() => view.stop());
|
|
@@ -699,7 +699,7 @@ async function twinCommand(args) {
|
|
|
699
699
|
}
|
|
700
700
|
const dir = resolveCatalog(process.cwd()).packDir(vendor);
|
|
701
701
|
if (dir === undefined)
|
|
702
|
-
throw new Error(`No twin "${vendor}" here — \`
|
|
702
|
+
throw new Error(`No twin "${vendor}" here — \`pnpm add -D --package-import-method=hardlink --config.nodeLinker=hoisted @volter/twin-${vendor}\`, or run from a checkout that has it`);
|
|
703
703
|
if (rest.length === 0 || rest[0].startsWith('-'))
|
|
704
704
|
throw new Error(`volter twin ${vendor}: which verb? serve | root | credential | refresh`);
|
|
705
705
|
const cli = packCli(dir);
|
|
@@ -718,7 +718,7 @@ function remoteCli() {
|
|
|
718
718
|
const here = resolve(import.meta.dir, '..', '..', 'world-host', 'src', 'cli.ts');
|
|
719
719
|
if (existsSync(here))
|
|
720
720
|
return here;
|
|
721
|
-
throw new Error('volter remote: the hosting product (@volter/world-host) is not installed — `
|
|
721
|
+
throw new Error('volter remote: the hosting product (@volter/world-host) is not installed — `pnpm add -D --package-import-method=hardlink --config.nodeLinker=hoisted @volter/world-host`');
|
|
722
722
|
}
|
|
723
723
|
/** `volter login <platform url>`: the CLI signed into the hosted platform as you, through the browser (the device
|
|
724
724
|
* authorization grant, as `gh auth login`): the platform shows a code, you approve it there signed in as yourself,
|
|
@@ -1067,7 +1067,7 @@ async function installDetectedTwins(app, install, selection = {}) {
|
|
|
1067
1067
|
});
|
|
1068
1068
|
const has = (file) => existsSync(join(app, file));
|
|
1069
1069
|
// the app's own package manager, by its lockfile; npm where there is none
|
|
1070
|
-
const invocation = has('bun.lock') || has('bun.lockb') ? ['bun', 'add', '-d'] : has('pnpm-lock.yaml') ? ['pnpm', 'add', '-D'] : has('yarn.lock') ? ['yarn', 'add', '-D'] : ['npm', 'install', '-D'];
|
|
1070
|
+
const invocation = has('bun.lock') || has('bun.lockb') ? ['bun', 'add', '-d'] : has('pnpm-lock.yaml') ? ['pnpm', 'add', '-D', '--package-import-method=hardlink', '--config.nodeLinker=hoisted'] : has('yarn.lock') ? ['yarn', 'add', '-D'] : ['npm', 'install', '-D'];
|
|
1071
1071
|
const command = [...invocation, ...pkgs].join(' ');
|
|
1072
1072
|
out(`detected ${vendors.map((v) => `${v} (${(found.get(v) ?? []).slice(0, 2).join(', ')})`).join('; ')}`);
|
|
1073
1073
|
out(`twins ${pkgs.join(' ')}`);
|
|
@@ -61,6 +61,6 @@ export function startUpdateCheck(args, env = process.env) {
|
|
|
61
61
|
return async () => {
|
|
62
62
|
const latest = await Promise.race([check, new Promise((r) => setTimeout(() => r(null), 800))]);
|
|
63
63
|
if (latest && newer(latest, here))
|
|
64
|
-
process.stderr.write(`\nvolter ${latest} is out (you have ${here}):
|
|
64
|
+
process.stderr.write(`\nvolter ${latest} is out (you have ${here}): pnpm add -g --package-import-method=hardlink --config.nodeLinker=hoisted @volter/world\n`);
|
|
65
65
|
};
|
|
66
66
|
}
|
package/dist/src/world.d.ts
CHANGED
|
@@ -79,7 +79,7 @@ export declare class World {
|
|
|
79
79
|
/** Whether `from` (default: the cwd) is inside a world. */
|
|
80
80
|
static find(from?: string): World | null;
|
|
81
81
|
/** `volter world init --bare <org>/<world> --twins a,b`: a world with no app — a package.json naming the
|
|
82
|
-
* twins (installed with
|
|
82
|
+
* twins (installed with pnpm), then the world over them, served as `/<org>/<world>/`. */
|
|
83
83
|
static initBare(dir: string, served: string, twins: string[], opts?: {
|
|
84
84
|
install?: boolean;
|
|
85
85
|
force?: boolean;
|
package/dist/src/world.js
CHANGED
|
@@ -82,7 +82,7 @@ export class World {
|
|
|
82
82
|
return root === null ? null : new World(currentBranch(root), root);
|
|
83
83
|
}
|
|
84
84
|
/** `volter world init --bare <org>/<world> --twins a,b`: a world with no app — a package.json naming the
|
|
85
|
-
* twins (installed with
|
|
85
|
+
* twins (installed with pnpm), then the world over them, served as `/<org>/<world>/`. */
|
|
86
86
|
static initBare(dir, served, twins, opts = {}) {
|
|
87
87
|
if (!/^[a-z0-9_-]+\/[a-z0-9_-]+$/i.test(served))
|
|
88
88
|
throw new Error(`a bare world is named <org>/<world>, got ${served}`);
|
|
@@ -90,11 +90,13 @@ export class World {
|
|
|
90
90
|
throw new Error('a bare world needs its twins: --twins github,slack');
|
|
91
91
|
mkdirSync(dir, { recursive: true });
|
|
92
92
|
const pkgPath = join(dir, 'package.json');
|
|
93
|
-
if (!existsSync(pkgPath))
|
|
94
|
-
writeFileSync(pkgPath, `${JSON.stringify({ name: served.split('/')[1], private: true, devDependencies: Object.fromEntries(twins.map((t) => [`@volter/twin-${t}`, '*'])) }, null, 2)}\n`);
|
|
93
|
+
if (!existsSync(pkgPath)) {
|
|
94
|
+
writeFileSync(pkgPath, `${JSON.stringify({ name: served.split('/')[1], private: true, packageManager: 'pnpm@12.15.0', devEngines: { packageManager: { name: 'pnpm', version: '12.15.0', onFail: 'error' } }, devDependencies: Object.fromEntries(twins.map((t) => [`@volter/twin-${t}`, '*'])) }, null, 2)}\n`);
|
|
95
|
+
writeFileSync(join(dir, 'pnpm-workspace.yaml'), 'packages:\n - .\npackageImportMethod: hardlink\nnodeLinker: hoisted\nautoInstallPeers: true\nstrictPeerDependencies: false\n');
|
|
96
|
+
}
|
|
95
97
|
if (opts.install !== false && !twins.every((t) => findInstalledPackage(dir, `@volter/twin-${t}`))) {
|
|
96
|
-
//
|
|
97
|
-
const cmd =
|
|
98
|
+
// Owned bare Worlds use the native store on their volume, independent of the runtime.
|
|
99
|
+
const cmd = ['pnpm', 'add', '--save-dev', '--package-import-method=hardlink', '--config.nodeLinker=hoisted'];
|
|
98
100
|
const r = spawnSync(cmd[0], [...cmd.slice(1), ...twins.map((t) => `@volter/twin-${t}`)], { windowsHide: true, cwd: dir, stdio: 'inherit', env: withoutVaultSignIn(process.env) });
|
|
99
101
|
if (r.status !== 0)
|
|
100
102
|
throw new Error(`installing the twins failed (${cmd.join(' ')} exited ${r.status})`);
|
|
@@ -13,12 +13,12 @@ You need Node 22.6+, npm and registry access during installation. The browser pa
|
|
|
13
13
|
Copy this starter into a new folder:
|
|
14
14
|
|
|
15
15
|
```console
|
|
16
|
-
$ npx @volter/world@3.0.
|
|
16
|
+
$ npx @volter/world@3.0.180 example customer-onboarding --dir onboarding
|
|
17
17
|
$ cd onboarding
|
|
18
|
-
$
|
|
18
|
+
$ pnpm install
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
The command copies source; it neither installs dependencies nor starts a World. Its manifest pins the CLI, core, runtime, console, twins and SDKs. The
|
|
21
|
+
The command copies source; it neither installs dependencies nor starts a World. Its manifest pins the CLI, core, runtime, console, twins and SDKs. The copied starter retains its native pnpm settings. Its first install creates a lockfile; later installs use `pnpm install --frozen-lockfile`. The [package manifest](./package.json) identifies the selected dependency versions. Keep your own lockfile with the application when sharing it.
|
|
22
22
|
|
|
23
23
|
Read [onboarding.mjs](./onboarding.mjs) for the application and [run.mjs](./run.mjs) for the command entry. Empty keys in [.env.example](./.env.example) tell init what the app reads; init supplies throwaway credentials. Do not add real keys.
|
|
24
24
|
|
|
@@ -11,12 +11,20 @@
|
|
|
11
11
|
"pg": "8.16.3"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@volter/world": "3.0.
|
|
15
|
-
"@volter/world-core": "3.0.
|
|
16
|
-
"@volter/world-runtime": "3.0.
|
|
17
|
-
"@volter/world-console": "3.0.
|
|
14
|
+
"@volter/world": "3.0.180",
|
|
15
|
+
"@volter/world-core": "3.0.161",
|
|
16
|
+
"@volter/world-runtime": "3.0.177",
|
|
17
|
+
"@volter/world-console": "3.0.152",
|
|
18
18
|
"@volter/twin-stripe": "3.0.2",
|
|
19
19
|
"@volter/twin-resend": "1.0.1",
|
|
20
|
-
"@volter/world-ui": "1.0.
|
|
20
|
+
"@volter/world-ui": "1.0.161"
|
|
21
|
+
},
|
|
22
|
+
"packageManager": "pnpm@12.15.0",
|
|
23
|
+
"devEngines": {
|
|
24
|
+
"packageManager": {
|
|
25
|
+
"name": "pnpm",
|
|
26
|
+
"version": "12.15.0",
|
|
27
|
+
"onFail": "error"
|
|
28
|
+
}
|
|
21
29
|
}
|
|
22
30
|
}
|
package/examples/index.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"run.mjs",
|
|
15
15
|
"server.mjs",
|
|
16
16
|
"style.css",
|
|
17
|
+
"pnpm-workspace.yaml",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
]
|
|
19
20
|
},
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
"read-slack-mail.mjs",
|
|
34
35
|
"reset-receipts.mjs",
|
|
35
36
|
"seed.mjs",
|
|
37
|
+
"pnpm-workspace.yaml",
|
|
36
38
|
"LICENSE"
|
|
37
39
|
]
|
|
38
40
|
},
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
"package.json",
|
|
48
50
|
"public-files.json",
|
|
49
51
|
"receipt.mjs",
|
|
52
|
+
"pnpm-workspace.yaml",
|
|
50
53
|
"LICENSE"
|
|
51
54
|
]
|
|
52
55
|
}
|
|
@@ -4,18 +4,18 @@ title: Shared baseline, workers and integration
|
|
|
4
4
|
|
|
5
5
|
# Shared baseline, workers and integration
|
|
6
6
|
|
|
7
|
-
Give two workers the same Stripe baseline, let them change different existing customers in isolated Worlds, then send their changesets to a separate integration World. This local example needs Node 22.6+,
|
|
7
|
+
Give two workers the same Stripe baseline, let them change different existing customers in isolated Worlds, then send their changesets to a separate integration World. This local example needs Node 22.6+, pnpm 12.15.0 and registry access during preparation. It requires no platform account, shared host or real vendor key.
|
|
8
8
|
|
|
9
|
-
[package.json](./package.json)
|
|
9
|
+
[package.json](./package.json) identifies the selected dependency versions. Each copied starter installs exact manifest pins and keeps the lockfile created by its first installation for frozen reentry. [customer.mjs](./customer.mjs) uses Stripe's real SDK. [receipt.mjs](./receipt.mjs) shows the current World, configured remotes, tracked origin, unpushed work and each changeset's actual destination.
|
|
10
10
|
|
|
11
11
|
## Make the baseline
|
|
12
12
|
|
|
13
13
|
Use an empty parent folder. Create the baseline starter there:
|
|
14
14
|
|
|
15
15
|
```console
|
|
16
|
-
$ npx @volter/world@3.0.
|
|
16
|
+
$ npx @volter/world@3.0.180 example integration-branches --dir baseline
|
|
17
17
|
$ cd baseline
|
|
18
|
-
$
|
|
18
|
+
$ pnpm install
|
|
19
19
|
$ ./node_modules/.bin/volter world init --name baseline --twins stripe
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -38,16 +38,16 @@ The stopped baseline retains its history and can supply local path remotes. Defa
|
|
|
38
38
|
Create three more copies in the same parent folder:
|
|
39
39
|
|
|
40
40
|
```console
|
|
41
|
-
$ npx @volter/world@3.0.
|
|
42
|
-
$ npx @volter/world@3.0.
|
|
43
|
-
$ npx @volter/world@3.0.
|
|
41
|
+
$ npx @volter/world@3.0.180 example integration-branches --dir integration
|
|
42
|
+
$ npx @volter/world@3.0.180 example integration-branches --dir worker-a
|
|
43
|
+
$ npx @volter/world@3.0.180 example integration-branches --dir worker-b
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
In `integration`, then `worker-a`, then `worker-b`, install dependencies and initialize with the folder's name. Before a first boot or app write, link and pull the baseline:
|
|
47
47
|
|
|
48
48
|
```console
|
|
49
49
|
$ cd integration
|
|
50
|
-
$
|
|
50
|
+
$ pnpm install --frozen-lockfile
|
|
51
51
|
$ ./node_modules/.bin/volter world init --name integration --twins stripe
|
|
52
52
|
$ ./node_modules/.bin/volter remote add origin ../baseline
|
|
53
53
|
$ ./node_modules/.bin/volter world pull
|
|
@@ -9,11 +9,19 @@
|
|
|
9
9
|
"stripe": "17.7.0"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@volter/world": "3.0.
|
|
13
|
-
"@volter/world-core": "3.0.
|
|
14
|
-
"@volter/world-runtime": "3.0.
|
|
15
|
-
"@volter/world-console": "3.0.
|
|
12
|
+
"@volter/world": "3.0.180",
|
|
13
|
+
"@volter/world-core": "3.0.161",
|
|
14
|
+
"@volter/world-runtime": "3.0.177",
|
|
15
|
+
"@volter/world-console": "3.0.152",
|
|
16
16
|
"@volter/twin-stripe": "3.0.2",
|
|
17
|
-
"@volter/world-ui": "1.0.
|
|
17
|
+
"@volter/world-ui": "1.0.161"
|
|
18
|
+
},
|
|
19
|
+
"packageManager": "pnpm@12.15.0",
|
|
20
|
+
"devEngines": {
|
|
21
|
+
"packageManager": {
|
|
22
|
+
"name": "pnpm",
|
|
23
|
+
"version": "12.15.0",
|
|
24
|
+
"onFail": "error"
|
|
25
|
+
}
|
|
18
26
|
}
|
|
19
27
|
}
|
|
@@ -19,13 +19,13 @@ A catalog page may select a newer default. Keep this example's manifest and inst
|
|
|
19
19
|
together; its recorded workflow does not establish behavior for a different release.
|
|
20
20
|
|
|
21
21
|
```console
|
|
22
|
-
$ npx @volter/world@3.0.
|
|
22
|
+
$ npx @volter/world@3.0.180 example workplace-agent --dir workplace
|
|
23
23
|
$ cd workplace
|
|
24
|
-
$
|
|
24
|
+
$ pnpm install
|
|
25
25
|
$ ./node_modules/.bin/volter world init --name workplace --twins github,linear,slack
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Read [package.json](./package.json)
|
|
28
|
+
Read [package.json](./package.json) and [clients.mjs](./clients.mjs) for exact tool, twin and SDK versions. Keep the supplied native pnpm settings and the lockfile created by installation with the application; later installs use `pnpm install --frozen-lockfile`. Review the three configured vendors and synthetic credentials before booting. No model vendor is selected by this starter.
|
|
29
29
|
|
|
30
30
|
```console
|
|
31
31
|
$ ./node_modules/.bin/volter world up
|
|
@@ -11,13 +11,21 @@
|
|
|
11
11
|
"@slack/web-api": "7.19.0"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@volter/world": "3.0.
|
|
15
|
-
"@volter/world-core": "3.0.
|
|
16
|
-
"@volter/world-runtime": "3.0.
|
|
17
|
-
"@volter/world-console": "3.0.
|
|
14
|
+
"@volter/world": "3.0.180",
|
|
15
|
+
"@volter/world-core": "3.0.161",
|
|
16
|
+
"@volter/world-runtime": "3.0.177",
|
|
17
|
+
"@volter/world-console": "3.0.152",
|
|
18
18
|
"@volter/twin-github": "3.0.9",
|
|
19
19
|
"@volter/twin-linear": "3.0.5",
|
|
20
20
|
"@volter/twin-slack": "3.0.8",
|
|
21
|
-
"@volter/world-ui": "1.0.
|
|
21
|
+
"@volter/world-ui": "1.0.161"
|
|
22
|
+
},
|
|
23
|
+
"packageManager": "pnpm@12.15.0",
|
|
24
|
+
"devEngines": {
|
|
25
|
+
"packageManager": {
|
|
26
|
+
"name": "pnpm",
|
|
27
|
+
"version": "12.15.0",
|
|
28
|
+
"onFail": "error"
|
|
29
|
+
}
|
|
22
30
|
}
|
|
23
31
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.180",
|
|
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",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
34
|
-
"@volter/world-core": "3.0.
|
|
35
|
-
"@volter/world-runtime": "3.0.
|
|
34
|
+
"@volter/world-core": "3.0.161",
|
|
35
|
+
"@volter/world-runtime": "3.0.177",
|
|
36
36
|
"zod": "^4.6.5"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@volter/world-console": "3.0.
|
|
39
|
+
"@volter/world-console": "3.0.152"
|
|
40
40
|
},
|
|
41
41
|
"peerDependenciesMeta": {
|
|
42
42
|
"@volter/world-console": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@volter/world-console": "3.0.
|
|
47
|
+
"@volter/world-console": "3.0.152"
|
|
48
48
|
},
|
|
49
49
|
"bin": {
|
|
50
50
|
"volter": "dist/src/cli.js"
|
|
@@ -54,5 +54,13 @@
|
|
|
54
54
|
"prepack": "node ../../scripts/prepare-world-examples.mjs && node ./scripts/ship-skill.mjs in && node ../../scripts/publish/prepare-publish.mjs prepack",
|
|
55
55
|
"postpack": "node ../../scripts/publish/prepare-publish.mjs postpack && node ./scripts/ship-skill.mjs out"
|
|
56
56
|
},
|
|
57
|
-
"homepage": "https://world.volter.ai/docs/getting-started"
|
|
57
|
+
"homepage": "https://world.volter.ai/docs/getting-started",
|
|
58
|
+
"packageManager": "pnpm@12.15.0",
|
|
59
|
+
"devEngines": {
|
|
60
|
+
"packageManager": {
|
|
61
|
+
"name": "pnpm",
|
|
62
|
+
"version": "12.15.0",
|
|
63
|
+
"onFail": "error"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
58
66
|
}
|
|
@@ -26,8 +26,13 @@ volter world log # what the app did at each vendor
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Show the person the vendors `init` detected, and why, before keeping them. Keys the app reads
|
|
29
|
-
(named in `.env.example`) get throwaway values in the World's env; never
|
|
30
|
-
a
|
|
29
|
+
(named in `.env.example`) for selected twins get throwaway values in the World's env; never
|
|
30
|
+
replace a twin's credential with a real one to get a test past validation. An intentionally
|
|
31
|
+
omitted vendor uses its ordinary production SDK/API and the caller's own credential at launch,
|
|
32
|
+
subject to sandbox and network policy. Remove that vendor's fake values and endpoint overrides;
|
|
33
|
+
keep live credentials out of the config and generated World env. Follow
|
|
34
|
+
[turn a service off](../../docs/guides/turn-a-service-off.md), including its credential-launch order.
|
|
35
|
+
`volter world view --no-open` runs the World with its
|
|
31
36
|
dashboard and prints its link for the person; it keeps running, so start it in the background,
|
|
32
37
|
and stop a World you started with `up` first (`down` keeps its state).
|
|
33
38
|
|
package/src/agent-prompt.ts
CHANGED
|
@@ -18,8 +18,8 @@ export function agentPrompt(o: AgentPromptOptions = {}): string {
|
|
|
18
18
|
'Set up Volter World for the workflow I want to run in this app. Select only the vendors that workflow deliberately substitutes with local twins. Keep the app\'s real SDKs.',
|
|
19
19
|
'',
|
|
20
20
|
o.tools
|
|
21
|
-
? '1. You have the Volter World tools (volter mcp). Keep an app\'s existing pinned World tools. If the `volter` command is missing, install it: `
|
|
22
|
-
: '1. Keep the app\'s pinned World tools if present, running `./node_modules/.bin/volter` from the app folder after installing its locked dependencies. A missing local binary stops without fetching another package. Otherwise install the command: `
|
|
21
|
+
? '1. You have the Volter World tools (volter mcp). Keep an app\'s existing pinned World tools. If the `volter` command is missing, install it: `pnpm add -g --package-import-method=hardlink --config.nodeLinker=hoisted @volter/world`.'
|
|
22
|
+
: '1. Keep the app\'s pinned World tools if present, running `./node_modules/.bin/volter` from the app folder after installing its locked dependencies. A missing local binary stops without fetching another package. Otherwise install the command: `pnpm add -g --package-import-method=hardlink --config.nodeLinker=hoisted @volter/world`. Install the agent tooling with `volter agents install --yes`.',
|
|
23
23
|
`2. Read the app's manifests, env names and any .volter/world.json. Show me the likely vendors and why, before keeping them. Preserve a saved World and its state; do not initialize it again to resume. For a new World, choose the exact catalog releases and initialize only the selected vendors with ${cmd('volter world init --twins <selected-vendors> --source <vendor>=<package>', 'world_init with vendors and sources')}.`,
|
|
24
24
|
`3. ${o.tools ? 'Start it with world_up' : 'Start it: `volter world up`'}, then run my usual app command inside it: ${cmd('volter world run -- <the app command>', 'world_run')}. Use throwaway credentials. Diagnose a failure as routing, data, scenario, unsupported operation or runtime; preserve the refusal's evidence rather than bypassing it.`,
|
|
25
25
|
`4. Tell me what the app did at each vendor (${cmd('volter world log', 'world_log')}), and give me the dashboard link (${o.tools ? 'world_view' : '`volter world view --no-open`, started in the background: it keeps running'}).`,
|
package/src/cli.ts
CHANGED
|
@@ -389,7 +389,7 @@ async function worldCommand(verb: string, args: string[]): Promise<void> {
|
|
|
389
389
|
// the read token's own one-line way in, for handing to someone who should look and not touch
|
|
390
390
|
// (not on a local World: any of its pages can open a write session there, so a read-only link restricts nothing)
|
|
391
391
|
const readOnly = view.console && !(local && view.origin) ? `${view.console}#token=${encodeURIComponent(view.readToken)}` : null;
|
|
392
|
-
out(`${local && view.origin ? `apps the World's tokens are in .volter/token and .volter/token.read (apps and scripts present them; the browser needs none)` : verb === 'serve' ? '' : `token ${view.token}\nread ${view.readToken}`}${readOnly ? `\nread-only view ${readOnly}` : ''}${view.console ? '' : '\n(no console installed: `
|
|
392
|
+
out(`${local && view.origin ? `apps the World's tokens are in .volter/token and .volter/token.read (apps and scripts present them; the browser needs none)` : verb === 'serve' ? '' : `token ${view.token}\nread ${view.readToken}`}${readOnly ? `\nread-only view ${readOnly}` : ''}${view.console ? '' : '\n(no console installed: `pnpm add -D --package-import-method=hardlink --config.nodeLinker=hoisted @volter/world-console` to look around in a browser)'}${view.origin ? '\n(the World opens at its own *.localhost origin; if your browser cannot reach *.localhost, run again with --no-origins)' : ''}`);
|
|
393
393
|
if (open && verb === 'view' && !args.includes('--no-open')) openInBrowser(open);
|
|
394
394
|
await waitForServeShutdown(() => view.stop());
|
|
395
395
|
process.exit(0);
|
|
@@ -512,7 +512,7 @@ async function twinCommand(args: string[]): Promise<void> {
|
|
|
512
512
|
return;
|
|
513
513
|
}
|
|
514
514
|
const dir = resolveCatalog(process.cwd()).packDir(vendor);
|
|
515
|
-
if (dir === undefined) throw new Error(`No twin "${vendor}" here — \`
|
|
515
|
+
if (dir === undefined) throw new Error(`No twin "${vendor}" here — \`pnpm add -D --package-import-method=hardlink --config.nodeLinker=hoisted @volter/twin-${vendor}\`, or run from a checkout that has it`);
|
|
516
516
|
if (rest.length === 0 || rest[0]!.startsWith('-')) throw new Error(`volter twin ${vendor}: which verb? serve | root | credential | refresh`);
|
|
517
517
|
const cli = packCli(dir); if (cli === undefined) throw new Error(`No cli for twin "${vendor}" (its manifest names no bin)`);
|
|
518
518
|
process.exitCode = await passthrough([process.execPath, cli, ...rest]);
|
|
@@ -524,7 +524,7 @@ function remoteCli(): string {
|
|
|
524
524
|
if (installed !== undefined) { const cli = packCli(installed); if (cli !== undefined) return cli; }
|
|
525
525
|
const here = resolve(import.meta.dir, '..', '..', 'world-host', 'src', 'cli.ts');
|
|
526
526
|
if (existsSync(here)) return here;
|
|
527
|
-
throw new Error('volter remote: the hosting product (@volter/world-host) is not installed — `
|
|
527
|
+
throw new Error('volter remote: the hosting product (@volter/world-host) is not installed — `pnpm add -D --package-import-method=hardlink --config.nodeLinker=hoisted @volter/world-host`');
|
|
528
528
|
}
|
|
529
529
|
|
|
530
530
|
/** `volter login <platform url>`: the CLI signed into the hosted platform as you, through the browser (the device
|
|
@@ -791,7 +791,7 @@ async function installDetectedTwins(app: string, install: boolean, selection: {
|
|
|
791
791
|
});
|
|
792
792
|
const has = (file: string): boolean => existsSync(join(app, file));
|
|
793
793
|
// the app's own package manager, by its lockfile; npm where there is none
|
|
794
|
-
const invocation = has('bun.lock') || has('bun.lockb') ? ['bun', 'add', '-d'] : has('pnpm-lock.yaml') ? ['pnpm', 'add', '-D'] : has('yarn.lock') ? ['yarn', 'add', '-D'] : ['npm', 'install', '-D'];
|
|
794
|
+
const invocation = has('bun.lock') || has('bun.lockb') ? ['bun', 'add', '-d'] : has('pnpm-lock.yaml') ? ['pnpm', 'add', '-D', '--package-import-method=hardlink', '--config.nodeLinker=hoisted'] : has('yarn.lock') ? ['yarn', 'add', '-D'] : ['npm', 'install', '-D'];
|
|
795
795
|
const command = [...invocation, ...pkgs].join(' ');
|
|
796
796
|
out(`detected ${vendors.map((v) => `${v} (${(found.get(v) ?? []).slice(0, 2).join(', ')})`).join('; ')}`);
|
|
797
797
|
out(`twins ${pkgs.join(' ')}`);
|
package/src/update-notice.ts
CHANGED
|
@@ -47,6 +47,6 @@ export function startUpdateCheck(args: string[], env: NodeJS.ProcessEnv = proces
|
|
|
47
47
|
: Promise.resolve(kept?.latest ?? null);
|
|
48
48
|
return async () => {
|
|
49
49
|
const latest = await Promise.race([check, new Promise<null>((r) => setTimeout(() => r(null), 800))]);
|
|
50
|
-
if (latest && newer(latest, here)) process.stderr.write(`\nvolter ${latest} is out (you have ${here}):
|
|
50
|
+
if (latest && newer(latest, here)) process.stderr.write(`\nvolter ${latest} is out (you have ${here}): pnpm add -g --package-import-method=hardlink --config.nodeLinker=hoisted @volter/world\n`);
|
|
51
51
|
};
|
|
52
52
|
}
|
package/src/world.ts
CHANGED
|
@@ -99,16 +99,19 @@ export class World {
|
|
|
99
99
|
return root === null ? null : new World(currentBranch(root), root);
|
|
100
100
|
}
|
|
101
101
|
/** `volter world init --bare <org>/<world> --twins a,b`: a world with no app — a package.json naming the
|
|
102
|
-
* twins (installed with
|
|
102
|
+
* twins (installed with pnpm), then the world over them, served as `/<org>/<world>/`. */
|
|
103
103
|
static initBare(dir: string, served: string, twins: string[], opts: { install?: boolean; force?: boolean } = {}): { world: World; result: InitResult } {
|
|
104
104
|
if (!/^[a-z0-9_-]+\/[a-z0-9_-]+$/i.test(served)) throw new Error(`a bare world is named <org>/<world>, got ${served}`);
|
|
105
105
|
if (twins.length === 0) throw new Error('a bare world needs its twins: --twins github,slack');
|
|
106
106
|
mkdirSync(dir, { recursive: true });
|
|
107
107
|
const pkgPath = join(dir, 'package.json');
|
|
108
|
-
if (!existsSync(pkgPath))
|
|
108
|
+
if (!existsSync(pkgPath)) {
|
|
109
|
+
writeFileSync(pkgPath, `${JSON.stringify({ name: served.split('/')[1], private: true, packageManager: 'pnpm@12.15.0', devEngines: { packageManager: { name: 'pnpm', version: '12.15.0', onFail: 'error' } }, devDependencies: Object.fromEntries(twins.map((t) => [`@volter/twin-${t}`, '*'])) }, null, 2)}\n`);
|
|
110
|
+
writeFileSync(join(dir, 'pnpm-workspace.yaml'), 'packages:\n - .\npackageImportMethod: hardlink\nnodeLinker: hoisted\nautoInstallPeers: true\nstrictPeerDependencies: false\n');
|
|
111
|
+
}
|
|
109
112
|
if (opts.install !== false && !twins.every((t) => findInstalledPackage(dir, `@volter/twin-${t}`))) {
|
|
110
|
-
//
|
|
111
|
-
const cmd =
|
|
113
|
+
// Owned bare Worlds use the native store on their volume, independent of the runtime.
|
|
114
|
+
const cmd = ['pnpm', 'add', '--save-dev', '--package-import-method=hardlink', '--config.nodeLinker=hoisted'];
|
|
112
115
|
const r = spawnSync(cmd[0]!, [...cmd.slice(1), ...twins.map((t) => `@volter/twin-${t}`)], { windowsHide: true, cwd: dir, stdio: 'inherit', env: withoutVaultSignIn(process.env) });
|
|
113
116
|
if (r.status !== 0) throw new Error(`installing the twins failed (${cmd.join(' ')} exited ${r.status})`);
|
|
114
117
|
}
|