blun-king-cli 9.1.625 → 9.1.628-pruefpaket.modules.r7

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.
@@ -1,77 +1,93 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
- const { pathToFileURL } = require('node:url');
6
-
7
- const { acquireSharedRuntimeLease } = require('./update-lease');
8
- const { installWindowsNodeCrashDump } = require('./windows-node-crash-dump.cjs');
9
- const { recordRuntimeExit } = require('./runtime-exit-ledger.cjs');
10
- const { isStandaloneHelpRequest } = require('./launcher-mode');
11
-
12
- const CORE_LOADED_MESSAGE = 'blun-core-bootstrap-loaded';
13
-
14
- async function runCoreBootstrap() {
15
- const informational = isStandaloneHelpRequest(process.argv.slice(2));
16
- let diagnostics;
17
- if (!informational) {
18
- try { diagnostics = installWindowsNodeCrashDump({ homeDir: process.env.BLUN_HOME }); } catch {}
19
- }
20
- const packageRoot = path.resolve(__dirname, '..');
21
- const mainPath = path.join(packageRoot, 'blun.mjs');
22
- let leaseResult;
23
-
24
- try {
25
- leaseResult = await acquireSharedRuntimeLease({ packageRoot });
26
- if (!leaseResult.acquired) throw new Error('RUNTIME_PROTECTION_UNAVAILABLE');
27
- leaseResult.lease.assertOwned();
28
- process.argv = [process.execPath, mainPath, ...process.argv.slice(2)];
29
- await import(pathToFileURL(mainPath).href);
30
- leaseResult.lease.assertOwned();
31
- } catch (error) {
32
- if (!informational) {
33
- try {
34
- if (diagnostics?.enabled) diagnostics.write(error, 'bootstrap-failure');
35
- else recordRuntimeExit({ homeDir: process.env.BLUN_HOME, source: 'bootstrap',
36
- kind: 'bootstrap-failure', phase: 'startup', error });
37
- } catch {}
38
- }
39
- try { diagnostics?.dispose(); } catch {}
40
- try { await leaseResult?.lease?.release(); }
41
- catch (leaseError) {
42
- if (!informational) {
43
- try {
44
- recordRuntimeExit({ homeDir: process.env.BLUN_HOME, source: 'bootstrap',
45
- kind: 'lease-release-failure', phase: 'cleanup', error: leaseError });
46
- } catch {}
47
- }
48
- }
49
- throw error;
50
- }
51
-
52
- // The bundled CLI loads package assets lazily after module evaluation. Keep
53
- // the install lease for this process's lifetime, without keeping print/help
54
- // commands alive solely because of the owner server.
55
- leaseResult.lease.unref();
56
-
57
- if (process.connected) {
58
- try {
59
- process.send({ type: CORE_LOADED_MESSAGE }, () => {
60
- try {
61
- process.disconnect();
62
- } catch {}
63
- });
64
- } catch {}
65
- }
66
- }
67
-
68
- if (require.main === module) {
69
- // oxlint-disable-next-line unicorn/prefer-top-level-await -- Worker entry is CommonJS.
70
- void runCoreBootstrap().catch((error) => {
71
- const message = error && error.message ? error.message : String(error);
72
- fs.writeSync(process.stderr.fd, `BLUN Start fehlgeschlagen: ${message}\n`);
73
- process.exit(1);
74
- });
75
- }
76
-
77
- module.exports = { CORE_LOADED_MESSAGE, runCoreBootstrap };
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { pathToFileURL } = require('node:url');
6
+
7
+ const { acquireSharedRuntimeLease } = require('./update-lease');
8
+ const { installWindowsNodeCrashDump } = require('./windows-node-crash-dump.cjs');
9
+ const { recordRuntimeExit } = require('./runtime-exit-ledger.cjs');
10
+ const { isStandaloneHelpRequest, isReadOnlyInspectionRequest } = require('./launcher-mode');
11
+
12
+ const CORE_LOADED_MESSAGE = 'blun-core-bootstrap-loaded';
13
+
14
+ function bindInteractiveTerminalOwner() {
15
+ if (process.platform !== 'win32' || process.env.BLUN_TERMINAL_OWNER !== '1' || !process.channel) return;
16
+ const stop = () => {
17
+ setTimeout(() => process.exit(143), 5000).unref();
18
+ if (!process.emit('SIGTERM')) process.exit(143);
19
+ };
20
+ process.once('disconnect', stop);
21
+ if (!process.connected) stop();
22
+ }
23
+
24
+ async function runCoreBootstrap() {
25
+ bindInteractiveTerminalOwner();
26
+ const args = process.argv.slice(2);
27
+ const informational = isStandaloneHelpRequest(args) || isReadOnlyInspectionRequest(args);
28
+ let diagnostics;
29
+ if (!informational) {
30
+ try { diagnostics = installWindowsNodeCrashDump({ homeDir: process.env.BLUN_HOME }); } catch {}
31
+ }
32
+ const packageRoot = path.resolve(__dirname, '..');
33
+ const mainPath = path.join(packageRoot, 'blun.mjs');
34
+ let leaseResult;
35
+
36
+ try {
37
+ leaseResult = await acquireSharedRuntimeLease({ packageRoot });
38
+ if (!leaseResult.acquired) throw new Error('RUNTIME_PROTECTION_UNAVAILABLE');
39
+ leaseResult.lease.assertOwned();
40
+ process.argv = [process.execPath, mainPath, ...process.argv.slice(2)];
41
+ await import(pathToFileURL(mainPath).href);
42
+ leaseResult.lease.assertOwned();
43
+ } catch (error) {
44
+ if (!informational) {
45
+ try {
46
+ if (diagnostics?.enabled) diagnostics.write(error, 'bootstrap-failure');
47
+ else recordRuntimeExit({ homeDir: process.env.BLUN_HOME, source: 'bootstrap',
48
+ kind: 'bootstrap-failure', phase: 'startup', error });
49
+ } catch {}
50
+ }
51
+ try { diagnostics?.dispose(); } catch {}
52
+ try { await leaseResult?.lease?.release(); }
53
+ catch (leaseError) {
54
+ if (!informational) {
55
+ try {
56
+ recordRuntimeExit({ homeDir: process.env.BLUN_HOME, source: 'bootstrap',
57
+ kind: 'lease-release-failure', phase: 'cleanup', error: leaseError });
58
+ } catch {}
59
+ }
60
+ }
61
+ throw error;
62
+ }
63
+
64
+ // The bundled CLI loads package assets lazily after module evaluation. Keep
65
+ // the install lease for this process's lifetime, without keeping print/help
66
+ // commands alive solely because of the owner server.
67
+ leaseResult.lease.unref();
68
+
69
+ if (process.connected) {
70
+ try {
71
+ process.send({ type: CORE_LOADED_MESSAGE }, () => {
72
+ if (process.env.BLUN_TERMINAL_OWNER === '1') {
73
+ process.channel?.unref();
74
+ return;
75
+ }
76
+ try {
77
+ process.disconnect();
78
+ } catch {}
79
+ });
80
+ } catch {}
81
+ }
82
+ }
83
+
84
+ if (require.main === module) {
85
+ // oxlint-disable-next-line unicorn/prefer-top-level-await -- Worker entry is CommonJS.
86
+ void runCoreBootstrap().catch((error) => {
87
+ const message = error && error.message ? error.message : String(error);
88
+ fs.writeSync(process.stderr.fd, `BLUN Start fehlgeschlagen: ${message}\n`);
89
+ process.exit(1);
90
+ });
91
+ }
92
+
93
+ module.exports = { CORE_LOADED_MESSAGE, runCoreBootstrap };
@@ -1,93 +1,126 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
-
6
- const LAUNCHER_MODES = Object.freeze({
7
- BLUN: 'blun',
8
- KING: 'king',
9
- });
10
-
11
- const LAUNCHER_HELP_TEXT = [
12
- '',
13
- 'Launchers:',
14
- ' blun Starts the CLI without automatically attaching Telegram.',
15
- ' king Starts the same CLI and automatically attaches the configured Telegram channel.',
16
- ' Both use the same version, account, model, and commands.',
17
- ' blun tools list shows optional tools; enable writes only explicit choices.',
18
- '',
19
- ].join('\n');
20
-
21
- function launcherModeFromArgv(argv) {
22
- const entry = argv[1] || '';
23
- const filename = entry.replaceAll('\\', '/').split('/').pop() || '';
24
- const command = filename.replace(/\.(?:c|m)?js$/i, '').toLowerCase();
25
- return command === LAUNCHER_MODES.KING ? LAUNCHER_MODES.KING : LAUNCHER_MODES.BLUN;
26
- }
27
-
28
- function runtimePolicyEnvironment(blunDir) {
29
- if (blunDir === undefined) return {};
30
- let policy;
31
- try {
32
- if (!path.isAbsolute(blunDir)) throw new Error('Invalid home');
33
- const file = path.join(blunDir, 'runtime-policy.json');
34
- const stat = fs.lstatSync(file);
35
- if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 4096) throw new Error('Invalid file');
36
- policy = JSON.parse(fs.readFileSync(file, 'utf8'));
37
- } catch (error) {
38
- if (error.code === 'ENOENT') return {};
39
- throw new Error('Invalid BLUN runtime policy');
40
- }
41
- const review = policy?.responseReview;
42
- if (policy?.schema !== 'blun.runtime-policy/v1'
43
- || Object.keys(policy).sort().join(',') !== 'responseReview,schema'
44
- || !review || Object.keys(review).sort().join(',') !== 'guardVersion,language,required'
45
- || review.required !== true || typeof review.language !== 'string'
46
- || !/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(review.language)
47
- || /^(auto|all)$/i.test(review.language)
48
- || typeof review.guardVersion !== 'string' || !/^\d+\.\d+\.\d+$/.test(review.guardVersion)) {
49
- throw new Error('Invalid BLUN runtime policy');
50
- }
51
- return {
52
- BLUN_LANGUAGE_GUARD_MANDATORY: '1',
53
- BLUN_LANGUAGE_GUARD_LANGUAGE: review.language,
54
- BLUN_LANGUAGE_GUARD_VERSION: review.guardVersion,
55
- BLUN_TELEGRAM_LANGUAGE_GATE: 'required',
56
- BLUN_TELEGRAM_OUTPUT_LOCALE: review.language,
57
- AGENTSPINE_STATE_DIR: path.join(blunDir, 'state', 'agent-spine'),
58
- };
59
- }
60
-
61
- function createLauncherEnvironment(sourceEnv, mode, publicPackageVersion, blunDir) {
62
- return {
63
- ...sourceEnv,
64
- ...runtimePolicyEnvironment(blunDir),
65
- BLUN_NO_AUTO_UPDATE: '1',
66
- BLUN_PUBLIC_PACKAGE_VERSION: publicPackageVersion,
67
- BLUN_TELEGRAM_ATTACH: mode === LAUNCHER_MODES.KING ? 'on' : 'off',
68
- BLUN_TELEGRAM_HEADLESS: 'off',
69
- };
70
- }
71
-
72
- function isNpmPackageUpdateRequest(args) {
73
- if (!Array.isArray(args) || args.length !== 1) return false;
74
- const command = String(args[0] || '').toLowerCase();
75
- return command === 'update' || command === 'upgrade';
76
- }
77
-
78
- function isStandaloneHelpRequest(args) {
79
- return Array.isArray(args) && args.length === 1 && (args[0] === '--help' || args[0] === '-h');
80
- }
81
-
82
- function launcherHelpText() {
83
- return LAUNCHER_HELP_TEXT;
84
- }
85
-
86
- module.exports = {
87
- LAUNCHER_MODES,
88
- createLauncherEnvironment,
89
- isNpmPackageUpdateRequest,
90
- isStandaloneHelpRequest,
91
- launcherHelpText,
92
- launcherModeFromArgv,
93
- };
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { parseArgs } = require('node:util');
6
+
7
+ const LAUNCHER_MODES = Object.freeze({
8
+ BLUN: 'blun',
9
+ KING: 'king',
10
+ });
11
+
12
+ const SETUP_INSPECTION_OPTIONS = new Set([
13
+ '--json', '--reference', '--proposal', '--validate', '--document', '--query', '--target',
14
+ ]);
15
+ const CONFIG_INSPECTION_COMMANDS = new Set(['validate', 'inspect', 'preview']);
16
+
17
+ const LAUNCHER_HELP_TEXT = [
18
+ '',
19
+ 'Launchers:',
20
+ ' blun Starts the CLI without automatically attaching Telegram.',
21
+ ' king Starts the same CLI and automatically attaches the configured Telegram channel.',
22
+ ' Both use the same version, account, model, and commands.',
23
+ ' blun tools list shows optional tools; enable writes only explicit choices.',
24
+ '',
25
+ ].join('\n');
26
+
27
+ function launcherModeFromArgv(argv) {
28
+ const entry = argv[1] || '';
29
+ const filename = entry.replaceAll('\\', '/').split('/').pop() || '';
30
+ const command = filename.replace(/\.(?:c|m)?js$/i, '').toLowerCase();
31
+ return command === LAUNCHER_MODES.KING ? LAUNCHER_MODES.KING : LAUNCHER_MODES.BLUN;
32
+ }
33
+
34
+ function runtimePolicyEnvironment(blunDir) {
35
+ if (blunDir === undefined) return {};
36
+ let policy;
37
+ try {
38
+ if (!path.isAbsolute(blunDir)) throw new Error('Invalid home');
39
+ const file = path.join(blunDir, 'runtime-policy.json');
40
+ const stat = fs.lstatSync(file);
41
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 4096) throw new Error('Invalid file');
42
+ policy = JSON.parse(fs.readFileSync(file, 'utf8'));
43
+ } catch (error) {
44
+ if (error.code === 'ENOENT') return {};
45
+ throw new Error('Invalid BLUN runtime policy');
46
+ }
47
+ const review = policy?.responseReview;
48
+ if (policy?.schema !== 'blun.runtime-policy/v1'
49
+ || Object.keys(policy).sort().join(',') !== 'responseReview,schema'
50
+ || !review || Object.keys(review).sort().join(',') !== 'guardVersion,language,required'
51
+ || review.required !== true || typeof review.language !== 'string'
52
+ || !/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(review.language)
53
+ || /^(auto|all)$/i.test(review.language)
54
+ || typeof review.guardVersion !== 'string' || !/^\d+\.\d+\.\d+$/.test(review.guardVersion)) {
55
+ throw new Error('Invalid BLUN runtime policy');
56
+ }
57
+ return {
58
+ BLUN_LANGUAGE_GUARD_MANDATORY: '1',
59
+ BLUN_LANGUAGE_GUARD_LANGUAGE: review.language,
60
+ BLUN_LANGUAGE_GUARD_VERSION: review.guardVersion,
61
+ BLUN_TELEGRAM_LANGUAGE_GATE: 'required',
62
+ BLUN_TELEGRAM_OUTPUT_LOCALE: review.language,
63
+ AGENTSPINE_STATE_DIR: path.join(blunDir, 'state', 'agent-spine'),
64
+ };
65
+ }
66
+
67
+ function createLauncherEnvironment(sourceEnv, mode, publicPackageVersion, blunDir) {
68
+ return {
69
+ ...sourceEnv,
70
+ ...runtimePolicyEnvironment(blunDir),
71
+ BLUN_NO_AUTO_UPDATE: '1',
72
+ BLUN_PUBLIC_PACKAGE_VERSION: publicPackageVersion,
73
+ BLUN_TELEGRAM_ATTACH: mode === LAUNCHER_MODES.KING ? 'on' : 'off',
74
+ BLUN_TELEGRAM_HEADLESS: 'off',
75
+ };
76
+ }
77
+
78
+ function isNpmPackageUpdateRequest(args) {
79
+ if (!Array.isArray(args) || args.length !== 1) return false;
80
+ const command = String(args[0] || '').toLowerCase();
81
+ return command === 'update' || command === 'upgrade';
82
+ }
83
+
84
+ function isStandaloneHelpRequest(args) {
85
+ return Array.isArray(args) && args.length === 1 && (args[0] === '--help' || args[0] === '-h');
86
+ }
87
+
88
+ function isReadOnlySetupRequest(args) {
89
+ return Array.isArray(args) && args[0] === 'setup'
90
+ && args.slice(1).some(value => typeof value === 'string' && SETUP_INSPECTION_OPTIONS.has(value.split('=', 1)[0]));
91
+ }
92
+
93
+ function isReadOnlyInspectionRequest(args) {
94
+ return isReadOnlySetupRequest(args)
95
+ || (Array.isArray(args) && args[0] === 'config' && CONFIG_INSPECTION_COMMANDS.has(args[1]))
96
+ || (Array.isArray(args) && args[0] === 'docs')
97
+ || isReadOnlyDoctorRequest(args);
98
+ }
99
+
100
+ function isReadOnlyDoctorRequest(args) {
101
+ if (!Array.isArray(args) || args[0] !== 'doctor') return false;
102
+ try {
103
+ const { positionals } = parseArgs({ args: args.slice(1), allowPositionals: true,
104
+ options: { json: { type: 'boolean' }, schema: { type: 'boolean' }, help: { type: 'boolean', short: 'h' } } });
105
+ if (positionals.length === 0) return true;
106
+ if (positionals[0] === 'config' || positionals[0] === 'tui') return positionals.length <= 2;
107
+ return positionals[0] === 'mcp' && positionals.length === 2;
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+
113
+ function launcherHelpText() {
114
+ return LAUNCHER_HELP_TEXT;
115
+ }
116
+
117
+ module.exports = {
118
+ LAUNCHER_MODES,
119
+ createLauncherEnvironment,
120
+ isNpmPackageUpdateRequest,
121
+ isStandaloneHelpRequest,
122
+ isReadOnlySetupRequest,
123
+ isReadOnlyInspectionRequest,
124
+ launcherHelpText,
125
+ launcherModeFromArgv,
126
+ };
@@ -13,6 +13,7 @@ const {
13
13
  createLauncherEnvironment,
14
14
  isNpmPackageUpdateRequest,
15
15
  isStandaloneHelpRequest,
16
+ isReadOnlyInspectionRequest,
16
17
  launcherHelpText,
17
18
  launcherModeFromArgv,
18
19
  } = require('./launcher-mode');
@@ -50,6 +51,34 @@ function resolveLauncherPrivatePaths(options = {}) {
50
51
  const env = options.env || process.env;
51
52
  const platform = options.platform || process.platform;
52
53
  const configuredHome = String(env.BLUN_HOME || '').trim();
54
+ const stateDir = String(env.BLUN_STATE_DIR || '').trim();
55
+ if (stateDir) {
56
+ const paths = platform === 'win32' ? path.win32 : path.posix;
57
+ if (!paths.isAbsolute(stateDir) || (platform === 'win32' && !/^[A-Za-z]:[\\/]/u.test(stateDir))) {
58
+ throw new Error('BLUN_STATE_DIR must be an absolute local path.');
59
+ }
60
+ const blunHome = paths.resolve(stateDir);
61
+ const fsImpl = options.fsImpl || fs;
62
+ let ancestor = blunHome;
63
+ for (;;) {
64
+ try {
65
+ const stat = fsImpl.lstatSync(ancestor);
66
+ const realpath = fsImpl.realpathSync.native || fsImpl.realpathSync;
67
+ const canonical = realpath(ancestor);
68
+ const normalize = platform === 'win32' ? normalizeWindowsPath : paths.normalize;
69
+ if (!stat.isDirectory() || stat.isSymbolicLink() || normalize(canonical) !== normalize(ancestor)) {
70
+ throw new Error('BLUN_STATE_DIR must not traverse a symbolic link or junction.');
71
+ }
72
+ break;
73
+ } catch (error) {
74
+ if (error.code !== 'ENOENT') throw error;
75
+ const parent = paths.dirname(ancestor);
76
+ if (parent === ancestor) throw error;
77
+ ancestor = parent;
78
+ }
79
+ }
80
+ return { blunHome, userHome: (options.homedir || os.homedir)() };
81
+ }
53
82
  if (platform !== 'win32') {
54
83
  const userHome = (options.homedir || os.homedir)();
55
84
  return {
@@ -102,13 +131,14 @@ function shouldDetachProtectedCore(options = {}) {
102
131
  }
103
132
 
104
133
  function spawnProtectedCore(args, env, cwd) {
134
+ const terminalOwner = process.platform === 'win32' && process.stdin.isTTY === true;
105
135
  const child = spawn(
106
136
  process.execPath,
107
137
  [path.join(PKG, 'bin', 'core-bootstrap.js'), ...args],
108
138
  {
109
139
  cwd,
110
140
  detached: shouldDetachProtectedCore(),
111
- env,
141
+ env: { ...env, BLUN_TERMINAL_OWNER: terminalOwner ? '1' : '0' },
112
142
  stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
113
143
  windowsHide: true,
114
144
  },
@@ -146,6 +176,7 @@ function spawnProtectedCore(args, env, cwd) {
146
176
  child.on('message', (message) => {
147
177
  if (message?.type !== CORE_LOADED_MESSAGE) return;
148
178
  settleLoaded(true);
179
+ if (terminalOwner) return;
149
180
  try {
150
181
  child.disconnect();
151
182
  } catch {}
@@ -367,6 +398,22 @@ async function runLauncher(options = {}) {
367
398
  return;
368
399
  }
369
400
 
401
+ if (isReadOnlyInspectionRequest(ARGS)) {
402
+ const compatibilityError = unsupportedNodeMessage(process.versions.node, process.platform);
403
+ if (compatibilityError !== null) {
404
+ process.stderr.write(`${compatibilityError}\n`);
405
+ process.exitCode = 1;
406
+ return;
407
+ }
408
+ const blunDir = resolveLauncherPrivatePaths().blunHome;
409
+ require('./verify-bundled-agent-sources.cjs').verifyBundledAgentSources(PKG);
410
+ const env = createLauncherEnvironment(process.env, LAUNCHER_MODES.BLUN, readPackageVersion(), blunDir);
411
+ env.BLUN_HOME = blunDir;
412
+ if (String(process.env.BLUN_STATE_DIR || '').trim()) env.BLUN_SHARED_HOME = blunDir;
413
+ process.exitCode = await runInformationalCore(ARGS, env, callerCwd, releaseNotice);
414
+ return;
415
+ }
416
+
370
417
  let privatePaths;
371
418
  const getPrivatePaths = () => {
372
419
  privatePaths ||= resolveLauncherPrivatePaths();
@@ -451,13 +498,31 @@ async function runLauncher(options = {}) {
451
498
  installTelegramForProfile(PKG, blunDir);
452
499
  }
453
500
 
454
- const nativeModules = repairConfiguredNativeModules({
455
- blunDir,
456
- callerCwd,
457
- onRepairStart() {
458
- process.stdout.write('BLUN richtet native Node.js-Module fuer die neue Laufzeit ein.\n');
459
- },
460
- });
501
+ const nativeOwner = new AbortController();
502
+ let nativeStopSignal;
503
+ const nativeSignals = new Map();
504
+ for (const signal of ['SIGHUP', 'SIGINT', 'SIGTERM']) {
505
+ const handler = () => { nativeStopSignal = signal; nativeOwner.abort(); };
506
+ nativeSignals.set(signal, handler);
507
+ process.once(signal, handler);
508
+ }
509
+ let nativeModules;
510
+ try {
511
+ nativeModules = await repairConfiguredNativeModules({
512
+ blunDir,
513
+ callerCwd,
514
+ signal: nativeOwner.signal,
515
+ onRepairStart() {
516
+ process.stdout.write('BLUN richtet native Node.js-Module fuer die neue Laufzeit ein.\n');
517
+ },
518
+ });
519
+ } catch (error) {
520
+ if (!nativeOwner.signal.aborted) throw error;
521
+ process.exitCode = nativeStopSignal === 'SIGINT' ? 130 : nativeStopSignal === 'SIGHUP' ? 129 : 143;
522
+ return;
523
+ } finally {
524
+ for (const [signal, handler] of nativeSignals) process.removeListener(signal, handler);
525
+ }
461
526
  if (nativeModules.repaired.length > 0) {
462
527
  process.stdout.write('Native Node.js-Module sind wieder einsatzbereit.\n');
463
528
  }
@@ -465,6 +530,7 @@ async function runLauncher(options = {}) {
465
530
  // --- 6. Start -----------------------------------------------------------
466
531
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion(), blunDir);
467
532
  env.BLUN_HOME = blunDir;
533
+ if (String(process.env.BLUN_STATE_DIR || '').trim()) env.BLUN_SHARED_HOME = blunDir;
468
534
  process.exitCode = await superviseProtectedCore(
469
535
  ARGS,
470
536
  env,
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ const { spawn } = require('node:child_process');
4
+ const path = require('node:path');
5
+ const { StringDecoder } = require('node:string_decoder');
6
+
7
+ const MAX_DIAGNOSTIC_BYTES = 1024 * 1024;
8
+
9
+ async function stopOwnedProcessTree(child) {
10
+ if (!child.pid || child.exitCode !== null || child.signalCode !== null) return;
11
+ if (process.platform !== 'win32') {
12
+ try { process.kill(-child.pid, 'SIGKILL'); } catch { child.kill('SIGKILL'); }
13
+ return;
14
+ }
15
+ await new Promise(resolve => {
16
+ const fallback = () => {
17
+ if (child.exitCode === null && child.signalCode === null) {
18
+ try { child.kill('SIGKILL'); } catch {}
19
+ }
20
+ resolve();
21
+ };
22
+ const killer = spawn(path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'taskkill.exe'),
23
+ ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
24
+ killer.once('error', fallback);
25
+ killer.once('close', fallback);
26
+ });
27
+ }
28
+
29
+ async function runNativeCommand(command, args, { cwd, signal, spawnImpl = spawn }) {
30
+ signal?.throwIfAborted();
31
+ return new Promise((resolve, reject) => {
32
+ const child = spawnImpl(command, args, { cwd, windowsHide: true,
33
+ detached: process.platform !== 'win32', stdio: ['ignore', 'pipe', 'pipe'] });
34
+ let error;
35
+ let stopping;
36
+ let cancelled = false;
37
+ const collect = stream => {
38
+ const decoder = new StringDecoder('utf8');
39
+ let bytes = 0;
40
+ let text = '';
41
+ let truncated = false;
42
+ stream.on('data', chunk => {
43
+ const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
44
+ const kept = data.subarray(0, Math.max(0, MAX_DIAGNOSTIC_BYTES - bytes));
45
+ bytes += kept.length;
46
+ truncated ||= kept.length !== data.length;
47
+ if (kept.length) text += decoder.write(kept);
48
+ });
49
+ return () => text + decoder.end() + (truncated ? '\n[NATIVE_DIAGNOSTIC_OUTPUT_TRUNCATED]' : '');
50
+ };
51
+ const stdout = collect(child.stdout);
52
+ const stderr = collect(child.stderr);
53
+ const stop = () => {
54
+ cancelled = true;
55
+ stopping ??= stopOwnedProcessTree(child).catch(stopError => {
56
+ error = stopError;
57
+ try { child.kill('SIGKILL'); } catch {}
58
+ });
59
+ };
60
+ signal?.addEventListener('abort', stop, { once: true });
61
+ child.once('error', spawnError => { error = spawnError; });
62
+ child.once('close', async (status, exitSignal) => {
63
+ signal?.removeEventListener('abort', stop);
64
+ await stopping;
65
+ if (cancelled) reject(new DOMException('Native command cancelled', 'AbortError'));
66
+ else resolve({ status, signal: exitSignal, stdout: stdout(), stderr: stderr(), error });
67
+ });
68
+ if (signal?.aborted) stop();
69
+ });
70
+ }
71
+
72
+ module.exports = { runNativeCommand };