@volter/world-runtime 2.0.1 → 2.0.2

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.
Files changed (63) hide show
  1. package/dist/src/app-url.js +1 -1
  2. package/dist/src/catalog.js +1 -1
  3. package/dist/src/cli.js +3 -3
  4. package/dist/src/console-apart.d.ts +1 -0
  5. package/dist/src/console-apart.js +7 -0
  6. package/dist/src/covers.js +4 -2
  7. package/dist/src/host-worker.js +2 -9
  8. package/dist/src/host.js +2 -9
  9. package/dist/src/import-module.d.ts +1 -0
  10. package/dist/src/import-module.js +16 -0
  11. package/dist/src/index.d.ts +7 -2
  12. package/dist/src/index.js +4 -1
  13. package/dist/src/infra-cli.js +2 -2
  14. package/dist/src/init.d.ts +1 -1
  15. package/dist/src/init.js +3 -0
  16. package/dist/src/local-branches.d.ts +37 -0
  17. package/dist/src/local-branches.js +193 -0
  18. package/dist/src/pglite-backing.js +8 -5
  19. package/dist/src/prerequisites.js +1 -1
  20. package/dist/src/process-groups.js +1 -1
  21. package/dist/src/redirect-proxy.js +2 -2
  22. package/dist/src/root.d.ts +23 -0
  23. package/dist/src/root.js +22 -10
  24. package/dist/src/run-task.js +1 -1
  25. package/dist/src/runtime.js +15 -15
  26. package/dist/src/schema.d.ts +5 -0
  27. package/dist/src/schema.js +10 -1
  28. package/dist/src/served-world.d.ts +196 -9
  29. package/dist/src/served-world.js +847 -103
  30. package/dist/src/service-recorder.js +1 -1
  31. package/dist/src/storage-capacity.js +2 -2
  32. package/dist/src/up-task.js +1 -1
  33. package/dist/src/world-origins.d.ts +13 -0
  34. package/dist/src/world-origins.js +37 -0
  35. package/dist/src/world-view.d.ts +25 -0
  36. package/dist/src/world-view.js +110 -0
  37. package/package.json +6 -5
  38. package/src/app-url.ts +1 -1
  39. package/src/catalog.ts +1 -1
  40. package/src/cli.ts +3 -3
  41. package/src/console-apart.ts +7 -1
  42. package/src/covers.ts +4 -2
  43. package/src/host-worker.ts +2 -1
  44. package/src/host.ts +2 -1
  45. package/src/import-module.ts +9 -0
  46. package/src/index.ts +7 -2
  47. package/src/infra-cli.ts +2 -2
  48. package/src/init.ts +3 -0
  49. package/src/local-branches.ts +173 -0
  50. package/src/pglite-backing.ts +8 -5
  51. package/src/prerequisites.ts +1 -1
  52. package/src/process-groups.ts +1 -1
  53. package/src/redirect-proxy.ts +2 -2
  54. package/src/root.ts +27 -2
  55. package/src/run-task.ts +1 -1
  56. package/src/runtime.ts +15 -15
  57. package/src/schema.ts +11 -1
  58. package/src/served-world.ts +762 -85
  59. package/src/service-recorder.ts +1 -1
  60. package/src/storage-capacity.ts +2 -2
  61. package/src/up-task.ts +1 -1
  62. package/src/world-origins.ts +38 -0
  63. package/src/world-view.ts +100 -0
@@ -62,8 +62,10 @@ export function infraConnections(services: InfraService[], env: Record<string, s
62
62
 
63
63
  // this module's directory under Node as under Bun: `volter-world-infra` is a node-shebang bin, where
64
64
  // `import.meta.dir` (Bun's) is undefined
65
- const HERE = fileURLToPath(new URL('.', import.meta.url));
66
- const HOST = join(HERE, 'pglite-host.mjs');
65
+ // lazy: a module that builds a URL from import.meta.url when it loads fails to load in workerd, which bundles this
66
+ // module into the hosted World (it never runs these services there)
67
+ const here = (): string => fileURLToPath(new URL('.', import.meta.url));
68
+ const hostScript = (): string => join(here(), 'pglite-host.mjs');
67
69
  const READY_TIMEOUT_MS = 60_000;
68
70
  const MONGODB_TWIN = '@volter/twin-mongodb';
69
71
 
@@ -76,7 +78,7 @@ function pidPath(dataDir: string, kind: string): string {
76
78
 
77
79
  /** The MongoDB twin's cli: the package installed above the World's config (an app repo), else the one
78
80
  * this runtime was installed with (its dependency, or the checkout's workspace link). */
79
- export function mongodbTwinCli(worldConfig?: string, froms: string[] = [...(worldConfig ? [dirname(worldConfig)] : []), HERE]): string {
81
+ export function mongodbTwinCli(worldConfig?: string, froms: string[] = [...(worldConfig ? [dirname(worldConfig)] : []), here()]): string {
80
82
  for (const from of froms) {
81
83
  const dir = findInstalledPackage(from, MONGODB_TWIN);
82
84
  const cli = dir === undefined ? undefined : packCli(dir);
@@ -89,7 +91,7 @@ export function mongodbTwinCli(worldConfig?: string, froms: string[] = [...(worl
89
91
  * process's own runtime; a checkout's is TypeScript whose kernel needs more than Node's type
90
92
  * stripping, so it runs under Bun (this process's, or the one on PATH) — the repository's toolchain —
91
93
  * and without Bun it is refused by name rather than started to fail. */
92
- export function twinRunner(cli: string, bunOnPath: () => boolean = () => spawnSync('bun', ['--version'], { stdio: 'ignore', timeout: 10_000 }).status === 0, underBun = typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined'): string {
94
+ export function twinRunner(cli: string, bunOnPath: () => boolean = () => spawnSync('bun', ['--version'], { stdio: 'ignore', timeout: 10_000, windowsHide: true }).status === 0, underBun = typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined'): string {
93
95
  if (!cli.endsWith('.ts') || underBun) return process.execPath;
94
96
  if (bunOnPath()) return 'bun';
95
97
  throw new Error(`a checkout's MongoDB twin runs under bun, which is not on PATH (${cli} is TypeScript that Node cannot run)`);
@@ -101,7 +103,7 @@ function hostArgv(service: InfraService, serviceData: string): string[] {
101
103
  const cli = mongodbTwinCli(process.env.VOLTER_WORLD_CONFIG);
102
104
  return [twinRunner(cli), cli, 'serve', '--port', String(service.hostPort), '--root', serviceData];
103
105
  }
104
- return ['node', HOST, '--port', String(service.hostPort), '--data', serviceData];
106
+ return ['node', hostScript(), '--port', String(service.hostPort), '--data', serviceData];
105
107
  }
106
108
 
107
109
  function alive(pid: number): boolean {
@@ -160,6 +162,7 @@ export async function pgliteUp(services: InfraService[], dataDir: string): Promi
160
162
  // a real fd, not a pipe: the host must outlive this process untethered
161
163
  const logFd = openSync(log, 'a');
162
164
  const child = spawn(command!, args, {
165
+ windowsHide: true,
163
166
  detached: true,
164
167
  stdio: ['ignore', logFd, logFd],
165
168
  });
@@ -15,7 +15,7 @@ type RunResult = { status: number | null; stdout: string; stderr: string };
15
15
  type Runner = (cmd: string, args: string[]) => RunResult;
16
16
 
17
17
  const realRunner: Runner = (cmd, args) => {
18
- const result = spawnSync(cmd, args, { encoding: 'utf8' });
18
+ const result = spawnSync(cmd, args, { windowsHide: true, encoding: 'utf8' });
19
19
  return {
20
20
  status: result.status,
21
21
  stdout: result.stdout || '',
@@ -13,7 +13,7 @@ export function survivingOwnedGroups(pids: number[]): number[] {
13
13
  }
14
14
  });
15
15
  if (!candidates.length || process.platform === 'win32') return candidates;
16
- const result = spawnSync('ps', ['-axo', 'pid=,pgid=,stat='], {
16
+ const result = spawnSync('ps', ['-axo', 'pid=,pgid=,stat='], { windowsHide: true,
17
17
  encoding: 'utf8', timeout: 1000, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024,
18
18
  });
19
19
  if (result.status !== 0 || result.error) return candidates;
@@ -192,12 +192,12 @@ export function opensslAvailable(): boolean {
192
192
  // process start rather than a same-process `process.env.PATH` mutation made mid-test. Never set
193
193
  // this env var in real usage.
194
194
  if (process.env.VOLTER_TEST_NO_OPENSSL === '1') return false;
195
- const r = spawnSync('openssl', ['version'], { stdio: 'ignore' });
195
+ const r = spawnSync('openssl', ['version'], { windowsHide: true, stdio: 'ignore' });
196
196
  return r.status === 0;
197
197
  }
198
198
 
199
199
  function runOpenssl(args: string[]): string {
200
- const r = spawnSync('openssl', args, { encoding: 'utf8' });
200
+ const r = spawnSync('openssl', args, { windowsHide: true, encoding: 'utf8' });
201
201
  if (r.status !== 0) throw new Error(`openssl ${args[0]} failed: ${(r.stderr || r.stdout || '').trim()}`);
202
202
  return r.stdout;
203
203
  }
package/src/root.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  import { loadWorldConfig, writeWorldConfig } from './configs.ts';
17
17
  import { changesetReadiness, collectObservations, type Observation, foldObservations, loadChecks, openRootCredential, performEntries, rootCustody, protocolStanding, userKek, type DeployReport, type ObserveReport } from '@volter/world-core';
18
18
  import { findWorldChangeset, listWorldChangesets } from './changeset.ts';
19
+ import { importModule } from './import-module.ts';
19
20
  import { statusWorld } from './runtime.ts';
20
21
  import { findInstalledPackage, twinPackageName } from './catalog.ts';
21
22
  import type { WorldConfig } from './schema.ts';
@@ -120,7 +121,7 @@ async function packModule(vendor: string, from: string): Promise<Record<string,
120
121
  const registered = getPack(vendor);
121
122
  if (registered) return { pack: registered };
122
123
  const entry = packEntryPath(vendor, from);
123
- return entry ? ((await import(entry)) as Record<string, unknown>) : null;
124
+ return entry ? await importModule(entry) : null;
124
125
  }
125
126
  /** The pack's module itself, installed beside the World or in this checkout: its every export (a
126
127
  * mirror's shell and client), where the registry holds only the descriptor. */
@@ -137,8 +138,32 @@ export async function packMirrorExports(vendor: string, from: string): Promise<R
137
138
  if (!entry) return null;
138
139
  const dir = dirname(entry);
139
140
  const file = existsSync(dir) ? readdirSync(dir).find((f) => /-mirror-ui\.(ts|js)$/.test(f) && !f.includes('.test.')) : undefined;
140
- return (await import(file ? join(dir, file) : entry)) as Record<string, unknown>;
141
+ return importModule(file ? join(dir, file) : entry);
141
142
  }
143
+ /** A pack's mirror as the pack builds it (`<vendor>MirrorHtml`, `build<Vendor>MirrorClient`,
144
+ * `<vendor>MirrorStyles`), found the way a World finds the pack; null when the pack has none. The
145
+ * client builds at serve under Bun and is served prebuilt under Node, by the pack's own builder. */
146
+ export type PackMirror = { html: () => string; client: () => Promise<string>; styles?: () => Promise<string>; /** the account and a credential its screens sign in with (`<vendor>MirrorSignIn`): the account found in the twin's
147
+ * state at `root`, the credential minted for it through the twin's own doors (`twin`), never a stored one read back */
148
+ signIn?: (as: string, ctx: MirrorSignInContext) => Promise<{ account: string; token: string } | null> | { account: string; token: string } | null };
149
+ /** What a pack's `<vendor>MirrorSignIn` is given: the twin's state root (to find the account) and the twin's own doors
150
+ * (to mint a credential for it, as the World's seed does through `/_twin/…`). */
151
+ export type MirrorSignInContext = { root: string; twin: (path: string, init?: RequestInit) => Promise<Response> };
152
+ export async function packMirror(vendor: string, from: string): Promise<PackMirror | null> {
153
+ const installed = findInstalledPackage(from, twinPackageName(vendor));
154
+ const dir = installed ?? resolve(import.meta.dir, '..', '..', 'twin', vendor);
155
+ // the pack's mirror module, as the hosted build reads it (its index need not re-export every builder)
156
+ const src = join(dir, 'src');
157
+ const file = existsSync(src) ? readdirSync(src).find((f) => f.endsWith('-mirror-ui.ts')) : undefined;
158
+ const entry = file ? join(src, file) : installed ? packEntry(installed) : join(src, 'index.ts');
159
+ if (!existsSync(entry)) return null;
160
+ const mod = await importModule(entry);
161
+ const pick = (re: RegExp): ((...a: unknown[]) => unknown) | undefined => { const k = Object.keys(mod).find((key) => re.test(key) && typeof mod[key] === 'function'); return k ? mod[k] as (...a: unknown[]) => unknown : undefined; };
162
+ const html = pick(/^\w+MirrorHtml$/); const client = pick(/^build\w+MirrorClient$/); const styles = pick(/^\w+MirrorStyles$/); const signIn = pick(/^\w+MirrorSignIn$/);
163
+ if (!html || !client) return null;
164
+ return { html: html as () => string, client: client as () => Promise<string>, ...(styles ? { styles: styles as () => Promise<string> } : {}), ...(signIn ? { signIn: signIn as PackMirror['signIn'] } : {}) };
165
+ }
166
+
142
167
  /** The real state system's adapters for a vendor: registered with the pack (protocol 2), else the
143
168
  * pack's anchored exports (protocol 1: `perform<Name>Action`, `sync<Name>FromRemote`). */
144
169
  export type IngestAdapter = (request: Request, ctx: { root?: string; secret?: string }) => Response | Promise<Response>;
package/src/run-task.ts CHANGED
@@ -13,7 +13,7 @@ export type RunTaskReply = { result: RunTaskResult } | { error: string };
13
13
  * The task process is outside the caller's POSIX process group and owns boot through teardown. */
14
14
  export function superviseWorldRun(configId: string, command: string[], options: RunWorldOptions): Promise<RunTaskResult> {
15
15
  return new Promise((done, reject) => {
16
- const child = spawn(process.execPath, [siblingScript(import.meta.url, 'run-task-worker')], {
16
+ const child = spawn(process.execPath, [siblingScript(import.meta.url, 'run-task-worker')], { windowsHide: true,
17
17
  cwd: process.cwd(), env: process.env, detached: process.platform !== 'win32',
18
18
  stdio: ['inherit', 'inherit', 'inherit', 'ipc'], serialization: 'json',
19
19
  });
package/src/runtime.ts CHANGED
@@ -63,7 +63,7 @@ function spawnRecorded(serviceId: string, command: string, args: string[], cwd:
63
63
  const recorderEnv: NodeJS.ProcessEnv = { ...env, VOLTER_WORLD_SERVICE_ID: serviceId, VOLTER_WORLD_SERVICE_LOG: log,
64
64
  VOLTER_WORLD_SERVICE_LOG_DIR: logDir, VOLTER_WORLD_EVENTS: worldEventsPath(dirname(log)) };
65
65
  if (recorderEnv.NODE_OPTIONS !== undefined) { recorderEnv.VOLTER_WORLD_SERVICE_NODE_OPTIONS = recorderEnv.NODE_OPTIONS; delete recorderEnv.NODE_OPTIONS; }
66
- const child = spawn(process.execPath, [siblingScript(import.meta.url, 'service-recorder'), command, ...args], {
66
+ const child = spawn(process.execPath, [siblingScript(import.meta.url, 'service-recorder'), command, ...args], { windowsHide: true,
67
67
  cwd,
68
68
  env: recorderEnv,
69
69
  detached: true,
@@ -662,7 +662,7 @@ function processStarts(pids: number[]): Map<number, string> {
662
662
  }
663
663
  return starts;
664
664
  }
665
- const result = spawnSync('ps', ['-o', 'pid=,lstart=', '-p', pids.join(',')], { encoding: 'utf8', timeout: 1000, killSignal: 'SIGKILL', env: { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' } });
665
+ const result = spawnSync('ps', ['-o', 'pid=,lstart=', '-p', pids.join(',')], { windowsHide: true, encoding: 'utf8', timeout: 1000, killSignal: 'SIGKILL', env: { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' } });
666
666
  for (const line of (result.stdout ?? '').split('\n')) {
667
667
  const match = /^\s*(\d+)\s+(.+?)\s*$/.exec(line);
668
668
  if (match) starts.set(Number(match[1]), match[2]!);
@@ -802,7 +802,7 @@ async function runExternalCommand(
802
802
  if (!bin) throw new Error(`External service "${serviceId}": ${phase} command is empty`);
803
803
  if (!commandExists(bin)) throw new Error(`External service "${serviceId}": \`${bin}\` not found on PATH (the ${phase} command's tool is not installed)`);
804
804
  abort?.throwIfAborted();
805
- const child = spawn(bin, args, { cwd, env, detached: commandProcessGroup, stdio: ['ignore', 'pipe', 'pipe'] });
805
+ const child = spawn(bin, args, { windowsHide: true, cwd, env, detached: commandProcessGroup, stdio: ['ignore', 'pipe', 'pipe'] });
806
806
  const lifetime = commandLifetime(child, abort, true);
807
807
  let stdout = ''; let stderr = ''; let failure: Error | undefined;
808
808
  const collect = (chunk: Buffer, out: boolean) => {
@@ -843,7 +843,7 @@ async function runExternalUp(
843
843
  const chunks: string[] = [];
844
844
  try {
845
845
  abort?.throwIfAborted();
846
- const child = spawn(bin, args, { cwd, env, detached: commandProcessGroup, stdio: ['ignore', 'pipe', 'pipe'] });
846
+ const child = spawn(bin, args, { windowsHide: true, cwd, env, detached: commandProcessGroup, stdio: ['ignore', 'pipe', 'pipe'] });
847
847
  const lifetime = commandLifetime(child, abort, true);
848
848
  let failure: unknown;
849
849
  child.on('error', error => { failure = error; });
@@ -2208,11 +2208,11 @@ export function ownCommand(command: string[]): string[] {
2208
2208
  }
2209
2209
 
2210
2210
  function onPath(command: string): boolean {
2211
- return spawnSync('which', [command], { stdio: 'ignore', env: process.env }).status === 0;
2211
+ return spawnSync('which', [command], { windowsHide: true, stdio: 'ignore', env: process.env }).status === 0;
2212
2212
  }
2213
2213
 
2214
2214
  function resolveWithPublicDns(hostname: string): string | undefined {
2215
- const result = spawnSync('dig', ['+short', '@1.1.1.1', hostname, 'A'], {
2215
+ const result = spawnSync('dig', ['+short', '@1.1.1.1', hostname, 'A'], { windowsHide: true,
2216
2216
  encoding: 'utf8',
2217
2217
  });
2218
2218
  if (result.status !== 0) return undefined;
@@ -2252,7 +2252,7 @@ async function verifyPublicUrl(
2252
2252
  || (isIP(normalizedHostname) === 4 && normalizedHostname.split('.')[0] === '127');
2253
2253
  if (local) {
2254
2254
  const target = publicHealthUrl(publicUrl, path);
2255
- const result = spawnSync('curl', ['--noproxy', '*', '-sS', '-o', '-', '-w', '\n%{http_code}', '--max-time', String(Math.max(1, Math.ceil(timeoutMs / 1000))), target], { encoding: 'utf8' });
2255
+ const result = spawnSync('curl', ['--noproxy', '*', '-sS', '-o', '-', '-w', '\n%{http_code}', '--max-time', String(Math.max(1, Math.ceil(timeoutMs / 1000))), target], { windowsHide: true, encoding: 'utf8' });
2256
2256
  const separator = result.stdout.lastIndexOf('\n');
2257
2257
  const body = separator >= 0 ? result.stdout.slice(0, separator).trim() : result.stdout.trim();
2258
2258
  const status = Number(separator >= 0 ? result.stdout.slice(separator + 1).trim() : '0');
@@ -2286,7 +2286,7 @@ async function verifyPublicUrl(
2286
2286
  `${hostname}:443:${resolvedIp}`,
2287
2287
  target,
2288
2288
  ],
2289
- { encoding: 'utf8' },
2289
+ { windowsHide: true, encoding: 'utf8' },
2290
2290
  );
2291
2291
  if (result.status === 0) {
2292
2292
  const output = result.stdout;
@@ -2348,7 +2348,7 @@ async function shareWorldReserved(name: string, options: ShareWorldOptions): Pro
2348
2348
  // Each spawned tunnel owns one log generation. Truncating before spawn prevents a repeated
2349
2349
  // share from accepting the previous process's URL handshake as the new process's endpoint.
2350
2350
  const out = openPrivateLog(log, 'w');
2351
- const child = spawn(tunnel.command, tunnel.args, {
2351
+ const child = spawn(tunnel.command, tunnel.args, { windowsHide: true,
2352
2352
  cwd: root,
2353
2353
  env: process.env,
2354
2354
  detached: true,
@@ -2523,7 +2523,7 @@ async function probeExternalOnce(
2523
2523
  return { ok: false, message: `readiness command \`${ready.command}\` not found on PATH` };
2524
2524
  }
2525
2525
  const [probeBin, ...probeArgs] = ownCommand([ready.command, ...(ready.args ?? [])]);
2526
- const result = spawnSync(probeBin!, probeArgs, {
2526
+ const result = spawnSync(probeBin!, probeArgs, { windowsHide: true,
2527
2527
  cwd, encoding: 'utf8', env, timeout: DOCTOR_EXTERNAL_PROBE_TIMEOUT_MS, maxBuffer: 64 * 1024 * 1024,
2528
2528
  });
2529
2529
  if (result.status === 0) return { ok: true, message: 'probe ok (command)' };
@@ -2878,7 +2878,7 @@ export function runWithWorldEnv(name: string, command: string[], root = process.
2878
2878
  if (command.length === 0) throw new Error('Missing command after --');
2879
2879
  const attached = worldAttachedCommandEnv(name, root, options.verbose);
2880
2880
  return new Promise((resolveExit) => {
2881
- const child = spawn(command[0]!, command.slice(1), {
2881
+ const child = spawn(command[0]!, command.slice(1), { windowsHide: true,
2882
2882
  cwd: resolve(options.cwd ?? root),
2883
2883
  env: attached.env,
2884
2884
  stdio: 'inherit',
@@ -2951,7 +2951,7 @@ export async function attachWorld(name: string, command: string[], root = proces
2951
2951
  let retired = true;
2952
2952
  try {
2953
2953
  return await new Promise<number>((done, reject) => {
2954
- const child = spawn(command[0]!, command.slice(1), { cwd: resolve(options.cwd ?? root), env: attached.env, stdio: 'inherit', detached: commandProcessGroup });
2954
+ const child = spawn(command[0]!, command.slice(1), { windowsHide: true, cwd: resolve(options.cwd ?? root), env: attached.env, stdio: 'inherit', detached: commandProcessGroup });
2955
2955
  const lifetime = commandLifetime(child);
2956
2956
  let failure: unknown;
2957
2957
  record.consumerPid = child.pid;
@@ -3032,7 +3032,7 @@ async function runWithWorldEnvLogged(name: string, command: string[], root: stri
3032
3032
  let spawnError: string | undefined;
3033
3033
  let child;
3034
3034
  try {
3035
- child = spawn(command[0]!, command.slice(1), {
3035
+ child = spawn(command[0]!, command.slice(1), { windowsHide: true,
3036
3036
  cwd: resolve(options.cwd ?? root),
3037
3037
  env: attached.env,
3038
3038
  stdio: ['inherit', 'pipe', 'pipe'],
@@ -3114,7 +3114,7 @@ export async function shellWorld(name: string, root = process.cwd()): Promise<nu
3114
3114
  process.stderr.write(`world '${name}' active in a subshell — vendor calls hit the twins (env-only; install openssl for ambient https redirect). type 'exit' to leave.\n`);
3115
3115
  }
3116
3116
  try {
3117
- const result = spawnSync(shell, ['-i'], { cwd, env, stdio: 'inherit' });
3117
+ const result = spawnSync(shell, ['-i'], { windowsHide: true, cwd, env, stdio: 'inherit' });
3118
3118
  return result.status ?? 0;
3119
3119
  } finally {
3120
3120
  if (proxy) {
@@ -3207,7 +3207,7 @@ function startProxyDaemonOwned(name: string, root: string, caCertPath: string, e
3207
3207
  mkdirSync(dirname(proxyLogPath(root, name)), { recursive: true, mode: 0o700 });
3208
3208
  const proxyOut = openPrivateLog(proxyLogPath(root, name), 'a');
3209
3209
  try {
3210
- child = spawn(process.execPath, [proxyDaemonEntry(), name, '--root', root, ...(envFile ? [`--env-file=${envFile}`] : [])], {
3210
+ child = spawn(process.execPath, [proxyDaemonEntry(), name, '--root', root, ...(envFile ? [`--env-file=${envFile}`] : [])], { windowsHide: true,
3211
3211
  cwd: root, env: process.env, detached: true, stdio: ['ignore', proxyOut, proxyOut],
3212
3212
  });
3213
3213
  } finally { closeSync(proxyOut); }
package/src/schema.ts CHANGED
@@ -173,6 +173,9 @@ export type WorldServiceConfig = {
173
173
  * to loopback services directly while presenting a disguised world to the app/user.
174
174
  */
175
175
  controlPlane?: boolean;
176
+ /** The account a vendor's own screens (its mirror) open signed in as: its handle, username or email, which the pack
177
+ * resolves against the accounts this World holds (`<vendor>MirrorSignIn`). Nothing secret is written here. */
178
+ signIn?: { as: string };
176
179
  injectEnv?: string;
177
180
  /**
178
181
  * Additional env vars exported into the generated world env after this service starts.
@@ -704,7 +707,7 @@ export function assertWorldSelection(value: unknown, path: string): asserts valu
704
707
 
705
708
  function serviceV2ToLegacy(value: unknown, path: string): WorldServiceConfig {
706
709
  if (!record(value)) throw new Error(`World service must be an object in ${path}`);
707
- assertKeys(value, ['id', 'type', 'description', 'source', 'execution', 'endpoint', 'bindings', 'root'], `Service ${String(value.id ?? '')}`, path, true);
710
+ assertKeys(value, ['id', 'type', 'description', 'source', 'execution', 'endpoint', 'bindings', 'root', 'signIn'], `Service ${String(value.id ?? '')}`, path, true);
708
711
  const source = value.source === undefined ? {} : value.source;
709
712
  const execution = value.execution === undefined ? {} : value.execution;
710
713
  const endpoint = value.endpoint === undefined ? {} : value.endpoint;
@@ -721,6 +724,11 @@ function serviceV2ToLegacy(value: unknown, path: string): WorldServiceConfig {
721
724
  assertKeys(environment, ['values'], 'service.execution.environment', path);
722
725
  if (typeof value.id !== 'string' || value.id === '') throw new Error(`World service must define string id in ${path}`);
723
726
  if (value.description !== undefined && typeof value.description !== 'string') throw new Error(`Service "${value.id}" description must be a string in ${path}`);
727
+ if (value.signIn !== undefined) {
728
+ if (!record(value.signIn)) throw new Error(`Service "${value.id}" signIn must be an object in ${path}`);
729
+ assertKeys(value.signIn, ['as'], `Service "${value.id}" signIn`, path);
730
+ if (typeof value.signIn.as !== 'string' || value.signIn.as.trim() === '') throw new Error(`Service "${value.id}" signIn.as must name an account (a handle, username or email) in ${path}`);
731
+ }
724
732
  if (value.description !== undefined && value['//'] !== undefined) throw new Error(`Service "${value.id}" must not set both description and // in ${path}`);
725
733
  if (execution.cwd !== undefined && typeof execution.cwd !== 'string') throw new Error(`Service "${value.id}" execution.cwd must be a string in ${path}`);
726
734
  if (execution.controlPlane !== undefined && typeof execution.controlPlane !== 'boolean') throw new Error(`Service "${value.id}" execution.controlPlane must be a boolean in ${path}`);
@@ -770,6 +778,7 @@ function serviceV2ToLegacy(value: unknown, path: string): WorldServiceConfig {
770
778
  ...(bindings.injectEnvTemplates === undefined ? {} : { injectEnvTemplates: bindings.injectEnvTemplates as Record<string, string> }),
771
779
  ...(bindings.cliRedirect === undefined ? {} : { cliRedirect: bindings.cliRedirect as Record<string, string> }),
772
780
  ...(value.root === undefined ? {} : { root: value.root as WorldServiceConfig['root'] }),
781
+ ...(value.signIn === undefined ? {} : { signIn: { as: (value.signIn as { as: string }).as } }),
773
782
  ...(external === undefined ? {} : { external }),
774
783
  };
775
784
  if (type !== 'external' && bindings.discover !== undefined) throw new Error(`Service "${legacy.id}" bindings.discover is only valid for external services in ${path}`);
@@ -889,6 +898,7 @@ function serviceToV2(service: WorldServiceConfig): Record<string, unknown> {
889
898
  ...(Object.keys(endpoint).length ? { endpoint } : {}),
890
899
  ...(Object.keys(bindings).length ? { bindings } : {}),
891
900
  ...(service.root === undefined ? {} : { root: service.root }),
901
+ ...(service.signIn === undefined ? {} : { signIn: service.signIn }),
892
902
  };
893
903
  }
894
904