@volter/world-runtime 3.0.40 → 3.0.41

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.
@@ -16,6 +16,8 @@ export type Version = {
16
16
  by: string;
17
17
  reason?: string;
18
18
  commit?: string;
19
+ integrity?: string;
20
+ submission?: string;
19
21
  };
20
22
  export type Entry = {
21
23
  vendor: string;
@@ -29,9 +31,8 @@ export declare const readSources: (index: string) => Source[];
29
31
  export declare function readVendors(index: string): Entry[];
30
32
  /** A package's vendor: `@volter/twin-<vendor>`, or the part after `twin-` in any scope. */
31
33
  export declare const vendorOf: (name: string) => string;
32
- /** Each vendor's default twin: its official package, else its live outside package; the
33
- * highest live version of it. */
34
- export declare function defaults(index: string): Record<string, {
34
+ /** An explicit recommendation, or a vendor's sole live implementation. Publisher versions are independent. */
35
+ export declare function defaults(index: string, vendors?: Iterable<string>): Record<string, {
35
36
  name: string;
36
37
  version: string;
37
38
  source: string;
@@ -1,7 +1,7 @@
1
1
  // The catalog index as the runtime reads it (docs/contributing/architecture.md, "The catalog: where twins come from"):
2
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.
3
+ // `volter-ai/twin-catalog` repository, published as the npm package `@volter/twin-catalog`). The catalog owns
4
+ // validation and publication; `init` reads defaults from the installed artifact.
5
5
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  /** The index as npm carries it. */
@@ -17,21 +17,30 @@ const semver = (v) => v.split(/[.-]/).slice(0, 3).map((n) => Number(n) || 0);
17
17
  const newer = (a, b) => { const x = semver(a); const y = semver(b); for (let i = 0; i < 3; i++)
18
18
  if (x[i] !== y[i])
19
19
  return x[i] - y[i]; return 0; };
20
- /** Each vendor's default twin: its official package, else its live outside package; the
21
- * highest live version of it. */
22
- export function defaults(index) {
20
+ /** An explicit recommendation, or a vendor's sole live implementation. Publisher versions are independent. */
21
+ export function defaults(index, vendors) {
23
22
  const sources = new Map(readSources(index).map((s) => [s.name, s]));
24
- const rank = (s) => (!s ? 0 : s.official ? 2 : 1);
23
+ const recommendationFile = join(index, 'recommendations.json');
24
+ const recommendations = existsSync(recommendationFile) ? JSON.parse(readFileSync(recommendationFile, 'utf8')) : {};
25
+ const revocationsFile = join(index, 'revocations.json');
26
+ const revocations = existsSync(revocationsFile) ? JSON.parse(readFileSync(revocationsFile, 'utf8')) : [];
25
27
  const out = {};
28
+ const requested = vendors === undefined ? undefined : new Set(vendors);
26
29
  for (const e of readVendors(index)) {
30
+ if (requested && !requested.has(e.vendor))
31
+ continue;
27
32
  const candidates = e.packages
28
- .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] }))
29
- .filter((c) => c.live && c.s)
30
- .sort((a, b) => rank(b.s) - rank(a.s));
31
- const best = candidates[0];
32
- // an outside twin is a default only where no official twin is
33
- if (best && (best.s.official || candidates.every((c) => !c.s.official)))
33
+ .map((p) => ({ p, s: sources.get(p.source), live: p.versions.filter((v) => v.status === 'live' && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(v.version) && !revocations.some((r) => r.package === p.name && r.version === v.version)).sort((a, b) => newer(b.version, a.version))[0] }))
34
+ .filter((c) => c.live && c.s);
35
+ const recommendation = recommendations[e.vendor];
36
+ const best = recommendation ? candidates.find((c) => c.p.name === recommendation) : candidates.length === 1 ? candidates[0] : undefined;
37
+ if (!best && (recommendation || candidates.length > 1))
38
+ throw new Error(`${e.vendor}: catalog needs an explicit live package recommendation; publishers' versions cannot choose it`);
39
+ if (best) {
40
+ if (!/^@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/.test(best.p.name) || !best.p.name.startsWith(`${best.s.scope}/`))
41
+ throw new Error(`${e.vendor}: invalid catalog package identity`);
34
42
  out[e.vendor] = { name: best.p.name, version: best.live.version, source: best.p.source };
43
+ }
35
44
  }
36
45
  return out;
37
46
  }
@@ -1,15 +1,15 @@
1
1
  // The catalog — where the twins a world can run come from (docs/concepts/worlds.md
2
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
3
+ // INSTALLED catalog (independently named pack 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
7
  // selected checkout paths.
8
8
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
9
9
  import { spawnSync } from 'node:child_process';
10
- import { dirname, join, resolve } from 'node:path';
10
+ import { join, resolve } from 'node:path';
11
11
  import { overlayPackFacts } from "./pack-facts.js";
12
- import { installedPackageDir, isDerivedPack, packRepository } from '@volter/world-core/runtime';
12
+ import { installedPackageDir, installedVendorPacks, isDerivedPack, packRepository } from '@volter/world-core/runtime';
13
13
  export const TWIN_PACKAGE_PREFIX = '@volter/twin-';
14
14
  export function twinPackageName(vendor) { return `${TWIN_PACKAGE_PREFIX}${vendor}`; }
15
15
  /** A derived (Protocol 3) pack carries the surface derive-pack generated from its spec. */
@@ -56,34 +56,6 @@ export function packCli(dir) {
56
56
  }
57
57
  /** The nearest `node_modules/<name>` above `from` that holds a package.json — node's own walk. */
58
58
  export const findInstalledPackage = installedPackageDir;
59
- /** Every installed `@volter/twin-<vendor>` visible from `from`, nearest wins. */
60
- function installedTwins(from) {
61
- const found = new Map();
62
- let dir = resolve(from);
63
- for (;;) {
64
- const scope = join(dir, 'node_modules', '@volter');
65
- if (existsSync(scope)) {
66
- for (const entry of readdirSync(scope, { withFileTypes: true })) {
67
- if (!entry.isDirectory() && !entry.isSymbolicLink())
68
- continue;
69
- const name = `@volter/${entry.name}`;
70
- if (name === '@volter/twin-standard')
71
- continue; // the platform's standard is tooling, not a vendor pack
72
- if (!name.startsWith(TWIN_PACKAGE_PREFIX))
73
- continue; // the platform's packages are `@volter/world-*`: outside the prefix by name
74
- const vendor = name.slice(TWIN_PACKAGE_PREFIX.length);
75
- const packDir = join(scope, entry.name);
76
- if (!found.has(vendor) && isPackDir(packDir))
77
- found.set(vendor, packDir);
78
- }
79
- }
80
- const up = dirname(dir);
81
- if (up === dir)
82
- break;
83
- dir = up;
84
- }
85
- return found;
86
- }
87
59
  function fnv1a(text) {
88
60
  let h = 0x811c9dc5;
89
61
  for (let i = 0; i < text.length; i += 1) {
@@ -113,7 +85,7 @@ export function checkoutCatalog(root) {
113
85
  };
114
86
  }
115
87
  export function installedCatalog(root) {
116
- const twins = installedTwins(root);
88
+ const twins = new Map([...installedVendorPacks(root)].filter(([, dir]) => packCli(dir) !== undefined));
117
89
  return {
118
90
  kind: 'installed',
119
91
  root: resolve(root),
@@ -137,6 +109,17 @@ export function mergedCatalog(sources) {
137
109
  chosen.set(vendor, source);
138
110
  continue;
139
111
  }
112
+ const heldName = manifest(held.packDir(vendor))?.name;
113
+ const nextName = manifest(source.packDir(vendor))?.name;
114
+ if (heldName !== nextName) {
115
+ if (source.kindOf(vendor) === 'installed' && held.kindOf(vendor) === 'checkout') {
116
+ chosen.set(vendor, source);
117
+ continue;
118
+ }
119
+ if (source.kindOf(vendor) === 'checkout' && held.kindOf(vendor) === 'installed')
120
+ continue;
121
+ throw new Error(`${vendor}: multiple package implementations (${heldName}, ${nextName}); choose one explicitly`);
122
+ }
140
123
  if (major(source.version(vendor)) > major(held.version(vendor)))
141
124
  chosen.set(vendor, source);
142
125
  }
@@ -55,3 +55,4 @@ export { assertWorldStateRemovable } from './ancestry.js';
55
55
  export { withoutVaultSignIn } from './runtime.js';
56
56
  export { errorMessage } from './error-message.js';
57
57
  export { materializeBrowserFiles, browsersDir, worldBrowser, worldBrowsers, putWorldBrowser, deleteWorldBrowser, inheritWorldBrowsers, type WorldBrowser } from './browsers.js';
58
+ export { CATALOG_INDEX_PACKAGE, defaults as catalogDefaults } from './catalog-index.js';
package/dist/src/index.js CHANGED
@@ -39,3 +39,4 @@ export { assertWorldStateRemovable } from "./ancestry.js";
39
39
  export { withoutVaultSignIn } from "./runtime.js";
40
40
  export { errorMessage } from "./error-message.js";
41
41
  export { materializeBrowserFiles, browsersDir, worldBrowser, worldBrowsers, putWorldBrowser, deleteWorldBrowser, inheritWorldBrowsers } from "./browsers.js";
42
+ export { CATALOG_INDEX_PACKAGE, defaults as catalogDefaults } from "./catalog-index.js";
package/dist/src/init.js CHANGED
@@ -38,7 +38,7 @@ import { PROTOCOL_MAJOR, stateDirName } from '@volter/world-core/runtime';
38
38
  import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
39
39
  import { coverWorld, detectRepoVendors, envNameVendor, formatCoverageReport, injectorEnvNameForKey, injectorVendorKeysFor, isCredentialShapedEnvName, registryAcknowledgedReason, } from "./covers.js";
40
40
  import { fakeEnvValue, isAppKeyName, isGoogleOAuthClientEnvName, isGoogleServiceAccountEnvName } from "./fixture-env.js";
41
- import { findInstalledPackage, packCli, packEntry, resolveCatalog, twinPackageName } from "./catalog.js";
41
+ import { findInstalledPackage, packCli, packEntry, resolveCatalog } from "./catalog.js";
42
42
  import { CATALOG_INDEX_PACKAGE, defaults as catalogDefaults } from "./catalog-index.js";
43
43
  import { projectEnvReads, projectManifestDirs } from "./project-inspect.js";
44
44
  import { overlayEndpointEnv, packFacts } from "./pack-facts.js";
@@ -523,12 +523,6 @@ export function planWorldInit(name, repoPath, options = {}) {
523
523
  // the catalog index's defaults (the installed `@volter/twin-catalog`), which name the twin to install for a vendor
524
524
  // this catalog lacks
525
525
  const indexDir = findInstalledPackage(root, CATALOG_INDEX_PACKAGE);
526
- const indexDefaults = (() => { try {
527
- return indexDir === undefined ? {} : catalogDefaults(indexDir);
528
- }
529
- catch {
530
- return {};
531
- } })();
532
526
  // A pack's starters, copied VISIBLY into the world dir: the author's to edit or delete (copy = ownership; never
533
527
  // baked into the pack). Its handlers starter is the answering turns its own life scripts (journeys/scenario.json, the
534
528
  // World's handler format, its faults left out); its default data is src/semantics/seed.ts.
@@ -570,6 +564,7 @@ export function planWorldInit(name, repoPath, options = {}) {
570
564
  continue;
571
565
  }
572
566
  if (!catalog.has(vendor)) {
567
+ const indexDefaults = indexDir === undefined ? {} : catalogDefaults(indexDir, [vendor]);
573
568
  vendors.push({
574
569
  vendor,
575
570
  pack: null,
@@ -631,7 +626,7 @@ export function planWorldInit(name, repoPath, options = {}) {
631
626
  // inside this checkout it is the checkout-relative path, as the cookbook and the gates expect.
632
627
  const boot = catalogSource.kindOf(vendor) === 'installed'
633
628
  ? {
634
- package: twinPackageName(vendor),
629
+ package: JSON.parse(readFileSync(join(packDir, 'package.json'), 'utf8')).name,
635
630
  ...(handlersArg !== null ? { args: ['--scenario', handlersArg] } : {}),
636
631
  ...(serveExport !== undefined ? { colocate: { export: serveExport, ...(handlersArg !== null ? { scenarioPath: handlersArg } : {}) } } : {}),
637
632
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-runtime",
3
- "version": "3.0.40",
3
+ "version": "3.0.41",
4
4
  "description": "World configs for twins: boot named local runtimes, allocate ports, generate world.env/instance.json, and run apps against fake-key twin worlds.",
5
5
  "keywords": [
6
6
  "twin",
@@ -69,14 +69,14 @@
69
69
  "node": ">=22.3"
70
70
  },
71
71
  "peerDependencies": {
72
- "@volter/world-core": "3.0.40",
73
- "@volter/world-console": "3.0.35"
72
+ "@volter/world-core": "3.0.41",
73
+ "@volter/world-console": "3.0.36"
74
74
  },
75
75
  "dependencies": {
76
76
  "@electric-sql/pglite": "0.5.8",
77
77
  "@electric-sql/pglite-pgvector": "0.0.9",
78
78
  "@volter/world-access": "3.0.1",
79
- "@volter/world-core": "3.0.40",
79
+ "@volter/world-core": "3.0.41",
80
80
  "mongodb": "~6.20.0",
81
81
  "pg-gateway": "0.3.0-beta.4",
82
82
  "smol-toml": "^1.8.0",
@@ -1,7 +1,7 @@
1
1
  // The catalog index as the runtime reads it (docs/contributing/architecture.md, "The catalog: where twins come from"):
2
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.
3
+ // `volter-ai/twin-catalog` repository, published as the npm package `@volter/twin-catalog`). The catalog owns
4
+ // validation and publication; `init` reads defaults from the installed artifact.
5
5
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
 
@@ -10,7 +10,7 @@ export const CATALOG_INDEX_PACKAGE = '@volter/twin-catalog';
10
10
 
11
11
  /** A source; `workflow` is the file under `.github/workflows/` its packages are released by (release.yml unless named). */
12
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 };
13
+ export type Version = { version: string; status: 'live' | 'pending' | 'rejected'; at: string; by: string; reason?: string; commit?: string; integrity?: string; submission?: string };
14
14
  export type Entry = { vendor: string; packages: Array<{ name: string; source: string; versions: Version[] }> };
15
15
 
16
16
  export const readSources = (index: string): Source[] => JSON.parse(readFileSync(join(index, 'sources.json'), 'utf8')) as Source[];
@@ -24,21 +24,27 @@ export const vendorOf = (name: string): string => name.replace(/^@[^/]+\//, '').
24
24
  const semver = (v: string): number[] => v.split(/[.-]/).slice(0, 3).map((n) => Number(n) || 0);
25
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
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 }> {
27
+ /** An explicit recommendation, or a vendor's sole live implementation. Publisher versions are independent. */
28
+ export function defaults(index: string, vendors?: Iterable<string>): Record<string, { name: string; version: string; source: string }> {
30
29
  const sources = new Map(readSources(index).map((s) => [s.name, s]));
31
- const rank = (s: Source | undefined): number => (!s ? 0 : s.official ? 2 : 1);
30
+ const recommendationFile = join(index, 'recommendations.json');
31
+ const recommendations = existsSync(recommendationFile) ? JSON.parse(readFileSync(recommendationFile, 'utf8')) as Record<string, string> : {};
32
+ const revocationsFile = join(index, 'revocations.json');
33
+ const revocations = existsSync(revocationsFile) ? JSON.parse(readFileSync(revocationsFile, 'utf8')) as Array<{ package: string; version: string }> : [];
32
34
  const out: Record<string, { name: string; version: string; source: string }> = {};
35
+ const requested = vendors === undefined ? undefined : new Set(vendors);
33
36
  for (const e of readVendors(index)) {
37
+ if (requested && !requested.has(e.vendor)) continue;
34
38
  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 };
39
+ .map((p) => ({ p, s: sources.get(p.source), live: p.versions.filter((v) => v.status === 'live' && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(v.version) && !revocations.some((r) => r.package === p.name && r.version === v.version)).sort((a, b) => newer(b.version, a.version))[0] }))
40
+ .filter((c) => c.live && c.s);
41
+ const recommendation = recommendations[e.vendor];
42
+ const best = recommendation ? candidates.find((c) => c.p.name === recommendation) : candidates.length === 1 ? candidates[0] : undefined;
43
+ if (!best && (recommendation || candidates.length > 1)) throw new Error(`${e.vendor}: catalog needs an explicit live package recommendation; publishers' versions cannot choose it`);
44
+ if (best) {
45
+ if (!/^@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/.test(best.p.name) || !best.p.name.startsWith(`${best.s!.scope}/`)) throw new Error(`${e.vendor}: invalid catalog package identity`);
46
+ out[e.vendor] = { name: best.p.name, version: best.live!.version, source: best.p.source };
47
+ }
41
48
  }
42
49
  return out;
43
50
  }
44
-
package/src/catalog.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  // The catalog — where the twins a world can run come from (docs/concepts/worlds.md
2
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
3
+ // INSTALLED catalog (independently named pack 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
7
  // selected checkout paths.
8
8
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
9
9
  import { spawnSync } from 'node:child_process';
10
- import { dirname, join, resolve } from 'node:path';
10
+ import { join, resolve } from 'node:path';
11
11
  import { overlayPackFacts, type PackFacts } from './pack-facts.ts';
12
- import { installedPackageDir, isDerivedPack, packRepository } from '@volter/world-core/runtime';
12
+ import { installedPackageDir, installedVendorPacks, isDerivedPack, packRepository } from '@volter/world-core/runtime';
13
13
 
14
14
  export const TWIN_PACKAGE_PREFIX = '@volter/twin-';
15
15
 
@@ -71,30 +71,6 @@ export function packCli(dir: string): string | undefined {
71
71
  /** The nearest `node_modules/<name>` above `from` that holds a package.json — node's own walk. */
72
72
  export const findInstalledPackage = installedPackageDir;
73
73
 
74
- /** Every installed `@volter/twin-<vendor>` visible from `from`, nearest wins. */
75
- function installedTwins(from: string): Map<string, string> {
76
- const found = new Map<string, string>();
77
- let dir = resolve(from);
78
- for (;;) {
79
- const scope = join(dir, 'node_modules', '@volter');
80
- if (existsSync(scope)) {
81
- for (const entry of readdirSync(scope, { withFileTypes: true })) {
82
- if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
83
- const name = `@volter/${entry.name}`;
84
- if (name === '@volter/twin-standard') continue; // the platform's standard is tooling, not a vendor pack
85
- if (!name.startsWith(TWIN_PACKAGE_PREFIX)) continue; // the platform's packages are `@volter/world-*`: outside the prefix by name
86
- const vendor = name.slice(TWIN_PACKAGE_PREFIX.length);
87
- const packDir = join(scope, entry.name);
88
- if (!found.has(vendor) && isPackDir(packDir)) found.set(vendor, packDir);
89
- }
90
- }
91
- const up = dirname(dir);
92
- if (up === dir) break;
93
- dir = up;
94
- }
95
- return found;
96
- }
97
-
98
74
  function fnv1a(text: string): string {
99
75
  let h = 0x811c9dc5;
100
76
  for (let i = 0; i < text.length; i += 1) { h ^= text.charCodeAt(i); h = Math.imul(h, 0x01000193) >>> 0; }
@@ -123,7 +99,7 @@ export function checkoutCatalog(root: string): Catalog {
123
99
  }
124
100
 
125
101
  export function installedCatalog(root: string): Catalog {
126
- const twins = installedTwins(root);
102
+ const twins = new Map([...installedVendorPacks(root)].filter(([, dir]) => packCli(dir) !== undefined));
127
103
  return {
128
104
  kind: 'installed',
129
105
  root: resolve(root),
@@ -145,6 +121,13 @@ export function mergedCatalog(sources: Catalog[]): Catalog {
145
121
  for (const vendor of source.vendors()) {
146
122
  const held = chosen.get(vendor);
147
123
  if (held === undefined) { chosen.set(vendor, source); continue; }
124
+ const heldName = manifest(held.packDir(vendor)!)?.name;
125
+ const nextName = manifest(source.packDir(vendor)!)?.name;
126
+ if (heldName !== nextName) {
127
+ if (source.kindOf(vendor) === 'installed' && held.kindOf(vendor) === 'checkout') { chosen.set(vendor, source); continue; }
128
+ if (source.kindOf(vendor) === 'checkout' && held.kindOf(vendor) === 'installed') continue;
129
+ throw new Error(`${vendor}: multiple package implementations (${heldName}, ${nextName}); choose one explicitly`);
130
+ }
148
131
  if (major(source.version(vendor)) > major(held.version(vendor))) chosen.set(vendor, source);
149
132
  }
150
133
  }
package/src/index.ts CHANGED
@@ -180,3 +180,4 @@ export { withoutVaultSignIn } from './runtime.ts';
180
180
  export { errorMessage } from './error-message.ts';
181
181
 
182
182
  export { materializeBrowserFiles, browsersDir, worldBrowser, worldBrowsers, putWorldBrowser, deleteWorldBrowser, inheritWorldBrowsers, type WorldBrowser } from './browsers.ts';
183
+ export { CATALOG_INDEX_PACKAGE, defaults as catalogDefaults } from './catalog-index.ts';
package/src/init.ts CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  type CoverageReport,
49
49
  } from './covers.ts';
50
50
  import { fakeEnvValue, isAppKeyName, isGoogleOAuthClientEnvName, isGoogleServiceAccountEnvName } from './fixture-env.ts';
51
- import { findInstalledPackage, packCli, packEntry, resolveCatalog, twinPackageName, type Catalog } from './catalog.ts';
51
+ import { findInstalledPackage, packCli, packEntry, resolveCatalog, type Catalog } from './catalog.ts';
52
52
  import { CATALOG_INDEX_PACKAGE, defaults as catalogDefaults } from './catalog-index.ts';
53
53
  import { projectEnvReads, projectManifestDirs } from './project-inspect.ts';
54
54
  import { overlayEndpointEnv, packFacts } from './pack-facts.ts';
@@ -726,7 +726,6 @@ export function planWorldInit(name: string, repoPath: string, options: InitOptio
726
726
  // the catalog index's defaults (the installed `@volter/twin-catalog`), which name the twin to install for a vendor
727
727
  // this catalog lacks
728
728
  const indexDir = findInstalledPackage(root, CATALOG_INDEX_PACKAGE);
729
- const indexDefaults = (() => { try { return indexDir === undefined ? {} : catalogDefaults(indexDir); } catch { return {}; } })();
730
729
  // A pack's starters, copied VISIBLY into the world dir: the author's to edit or delete (copy = ownership; never
731
730
  // baked into the pack). Its handlers starter is the answering turns its own life scripts (journeys/scenario.json, the
732
731
  // World's handler format, its faults left out); its default data is src/semantics/seed.ts.
@@ -765,6 +764,7 @@ export function planWorldInit(name: string, repoPath: string, options: InitOptio
765
764
  continue;
766
765
  }
767
766
  if (!catalog.has(vendor)) {
767
+ const indexDefaults = indexDir === undefined ? {} : catalogDefaults(indexDir, [vendor]);
768
768
  vendors.push({
769
769
  vendor,
770
770
  pack: null,
@@ -821,7 +821,7 @@ export function planWorldInit(name: string, repoPath: string, options: InitOptio
821
821
  // inside this checkout it is the checkout-relative path, as the cookbook and the gates expect.
822
822
  const boot: Pick<WorldServiceConfig, 'package' | 'command' | 'args' | 'colocate'> = catalogSource.kindOf(vendor) === 'installed'
823
823
  ? {
824
- package: twinPackageName(vendor),
824
+ package: (JSON.parse(readFileSync(join(packDir, 'package.json'), 'utf8')) as { name: string }).name,
825
825
  ...(handlersArg !== null ? { args: ['--scenario', handlersArg] } : {}),
826
826
  ...(serveExport !== undefined ? { colocate: { export: serveExport, ...(handlersArg !== null ? { scenarioPath: handlersArg } : {}) } } : {}),
827
827
  }