@volter/world-runtime 3.0.160 → 3.0.162

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.
@@ -0,0 +1,3 @@
1
+ /** Resolve only the known npm Bun layout, never execute or interpret a command
2
+ * script. Keep explicit paths, other commands and non-Windows behavior intact. */
3
+ export declare function serviceExecutable(command: string, env: NodeJS.ProcessEnv): string;
@@ -0,0 +1,38 @@
1
+ // Process services run without a shell. On Windows npm's Bun command is a .cmd
2
+ // shim, whose native package executable must be selected before Node spawns it.
3
+ import { readFileSync, statSync } from 'node:fs';
4
+ import { join, resolve } from 'node:path';
5
+ const file = (path) => {
6
+ try {
7
+ return statSync(path).isFile();
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ };
13
+ /** Resolve only the known npm Bun layout, never execute or interpret a command
14
+ * script. Keep explicit paths, other commands and non-Windows behavior intact. */
15
+ export function serviceExecutable(command, env) {
16
+ if (process.platform !== 'win32' || command.toLowerCase() !== 'bun')
17
+ return command;
18
+ const path = Object.entries(env).find(([key]) => key.toUpperCase() === 'PATH')?.[1] ?? '';
19
+ const directories = [process.cwd(), ...path.split(';').map((value) => value.replace(/^"(.*)"$/, '$1'))];
20
+ for (const directory of directories) {
21
+ const native = resolve(directory, 'bun.exe');
22
+ if (file(native))
23
+ return native;
24
+ if (!file(resolve(directory, 'bun.cmd')))
25
+ continue;
26
+ const installed = resolve(directory, 'node_modules', 'bun');
27
+ const executable = join(installed, 'bin', 'bun.exe');
28
+ if (!file(executable))
29
+ continue;
30
+ try {
31
+ const manifest = JSON.parse(readFileSync(join(installed, 'package.json'), 'utf8'));
32
+ if (manifest.name === 'bun' && manifest.bin?.bun === 'bin/bun.exe')
33
+ return executable;
34
+ }
35
+ catch { /* An incomplete installation follows the ordinary spawn-error path. */ }
36
+ }
37
+ return command;
38
+ }
@@ -17,6 +17,7 @@
17
17
  // The World's NODE_OPTIONS (the injector) is for the service, not this parent: it arrives renamed and is restored.
18
18
  import { signalChild } from './process-signal.mjs';
19
19
  import { errorMessage } from "./error-message.js";
20
+ import { serviceExecutable } from "./service-command.js";
20
21
  import { spawn } from 'node:child_process';
21
22
  import { constants } from 'node:os';
22
23
  import { appendFileSync, closeSync, fstatSync, openSync, readSync, statSync, truncateSync, writeFileSync } from 'node:fs';
@@ -61,7 +62,7 @@ const record = (entry, then) => {
61
62
  process.stdout.write(`${logEnd().atLineStart ? '' : '\n'}${line}`, () => then?.());
62
63
  };
63
64
  const started = Date.now();
64
- const child = spawn(command, args, { windowsHide: true, stdio: 'inherit', env });
65
+ const child = spawn(serviceExecutable(command, env), args, { windowsHide: true, stdio: 'inherit', env });
65
66
  if (child.pid !== undefined)
66
67
  record({ event: 'world.service.started', pid: child.pid, parentPid: process.pid });
67
68
  /** The log's last `bytes` (from a line start) as the rotation `<log>.1`: however far a runaway writer overshot the cap,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-runtime",
3
- "version": "3.0.160",
3
+ "version": "3.0.162",
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",
@@ -70,7 +70,7 @@
70
70
  "node": ">=22.3"
71
71
  },
72
72
  "peerDependencies": {
73
- "@volter/world-core": "3.0.152",
73
+ "@volter/world-core": "3.0.154",
74
74
  "@volter/world-console": "3.0.150"
75
75
  },
76
76
  "dependencies": {
@@ -80,7 +80,7 @@
80
80
  "@volter/browser-runtime": "0.1.104",
81
81
  "@volter/browser-sandboxes": "0.1.84",
82
82
  "@volter/world-access": "3.0.1",
83
- "@volter/world-core": "3.0.152",
83
+ "@volter/world-core": "3.0.154",
84
84
  "modern-screenshot": "4.7.0",
85
85
  "mongodb": "~6.20.0",
86
86
  "pg-gateway": "0.3.0-beta.4",
@@ -0,0 +1,29 @@
1
+ // Process services run without a shell. On Windows npm's Bun command is a .cmd
2
+ // shim, whose native package executable must be selected before Node spawns it.
3
+ import { readFileSync, statSync } from 'node:fs';
4
+ import { join, resolve } from 'node:path';
5
+
6
+ const file = (path: string): boolean => {
7
+ try { return statSync(path).isFile(); } catch { return false; }
8
+ };
9
+
10
+ /** Resolve only the known npm Bun layout, never execute or interpret a command
11
+ * script. Keep explicit paths, other commands and non-Windows behavior intact. */
12
+ export function serviceExecutable(command: string, env: NodeJS.ProcessEnv): string {
13
+ if (process.platform !== 'win32' || command.toLowerCase() !== 'bun') return command;
14
+ const path = Object.entries(env).find(([key]) => key.toUpperCase() === 'PATH')?.[1] ?? '';
15
+ const directories = [process.cwd(), ...path.split(';').map((value) => value.replace(/^"(.*)"$/, '$1'))];
16
+ for (const directory of directories) {
17
+ const native = resolve(directory, 'bun.exe');
18
+ if (file(native)) return native;
19
+ if (!file(resolve(directory, 'bun.cmd'))) continue;
20
+ const installed = resolve(directory, 'node_modules', 'bun');
21
+ const executable = join(installed, 'bin', 'bun.exe');
22
+ if (!file(executable)) continue;
23
+ try {
24
+ const manifest = JSON.parse(readFileSync(join(installed, 'package.json'), 'utf8')) as { name?: unknown; bin?: { bun?: unknown } };
25
+ if (manifest.name === 'bun' && manifest.bin?.bun === 'bin/bun.exe') return executable;
26
+ } catch { /* An incomplete installation follows the ordinary spawn-error path. */ }
27
+ }
28
+ return command;
29
+ }
@@ -17,6 +17,7 @@
17
17
  // The World's NODE_OPTIONS (the injector) is for the service, not this parent: it arrives renamed and is restored.
18
18
  import { signalChild } from './process-signal.mjs';
19
19
  import { errorMessage } from './error-message.ts';
20
+ import { serviceExecutable } from './service-command.ts';
20
21
  import { spawn } from 'node:child_process';
21
22
  import { constants } from 'node:os';
22
23
  import { appendFileSync, closeSync, fstatSync, openSync, readSync, statSync, truncateSync, writeFileSync } from 'node:fs';
@@ -48,7 +49,7 @@ const record = (entry: Record<string, unknown>, then?: () => void): void => {
48
49
  };
49
50
 
50
51
  const started = Date.now();
51
- const child = spawn(command, args, { windowsHide: true, stdio: 'inherit', env });
52
+ const child = spawn(serviceExecutable(command, env), args, { windowsHide: true, stdio: 'inherit', env });
52
53
  if (child.pid !== undefined) record({ event: 'world.service.started', pid: child.pid, parentPid: process.pid });
53
54
 
54
55
  /** The log's last `bytes` (from a line start) as the rotation `<log>.1`: however far a runaway writer overshot the cap,