blun-king-cli 9.1.29 → 9.1.31

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 CHANGED
@@ -9,7 +9,7 @@ Voraussetzung ist Node.js 24.15 oder neuer. Die geprüfte Version wird exakt
9
9
  installiert:
10
10
 
11
11
  ```powershell
12
- npm install -g blun-king-cli@9.1.29
12
+ npm install -g blun-king-cli@9.1.31
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -22,6 +22,7 @@ const {
22
22
  seedStandardTools,
23
23
  } = require('./standard-tools-bootstrap');
24
24
  const { CORE_LOADED_MESSAGE } = require('./core-bootstrap');
25
+ const { prepareManagedNodeRuntime } = require('./node-runtime');
25
26
  const { acquireSharedRuntimeLease, tryAcquireUpdateLease } = require('./update-lease');
26
27
  const { runExplicitUpdate, runUpdateNotice } = require('./update-notice');
27
28
  const {
@@ -182,6 +183,33 @@ function installTelegramForProfile(packageRoot, blunDir) {
182
183
  }
183
184
  }
184
185
 
186
+ function spawnManagedLauncher(binary, cwd) {
187
+ return new Promise((resolve, reject) => {
188
+ const child = spawn(binary, process.argv.slice(1), {
189
+ cwd,
190
+ env: process.env,
191
+ stdio: 'inherit',
192
+ windowsHide: true,
193
+ });
194
+ child.once('error', reject);
195
+ child.once('exit', (code, signal) => {
196
+ if (Number.isInteger(code)) {
197
+ resolve(code);
198
+ return;
199
+ }
200
+ if (signal === 'SIGINT') {
201
+ resolve(130);
202
+ return;
203
+ }
204
+ if (signal === 'SIGTERM') {
205
+ resolve(143);
206
+ return;
207
+ }
208
+ resolve(1);
209
+ });
210
+ });
211
+ }
212
+
185
213
  async function runLauncher(options = {}) {
186
214
  const callerCwd = process.cwd();
187
215
  const mode = options.mode || launcherModeFromArgv(process.argv);
@@ -278,6 +306,20 @@ async function runLauncher(options = {}) {
278
306
  return;
279
307
  }
280
308
 
309
+ const privatePaths = resolveLauncherPrivatePaths();
310
+ const nodeRuntime = await prepareManagedNodeRuntime({ blunDir: privatePaths.blunHome });
311
+ if (nodeRuntime.kind === 'failed') {
312
+ process.stderr.write(`${nodeRuntime.message}\n`);
313
+ process.exitCode = 1;
314
+ return;
315
+ }
316
+ if (nodeRuntime.kind === 'managed') {
317
+ process.stdout.write('BLUN hat die passende Node.js-Laufzeit eingerichtet.\n');
318
+ await releaseNotice();
319
+ process.exitCode = await spawnManagedLauncher(nodeRuntime.binary, callerCwd);
320
+ return;
321
+ }
322
+
281
323
  if (ARGS.some((arg) => arg === '--help' || arg === '-h')) {
282
324
  process.stdout.write(launcherHelpText());
283
325
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
@@ -288,7 +330,6 @@ async function runLauncher(options = {}) {
288
330
  return;
289
331
  }
290
332
 
291
- const privatePaths = resolveLauncherPrivatePaths();
292
333
  const blunDir = privatePaths.blunHome;
293
334
  ensurePrivateDirectory(blunDir);
294
335
 
@@ -356,4 +397,9 @@ async function runLauncher(options = {}) {
356
397
  }
357
398
  }
358
399
 
359
- module.exports = { resolveLauncherPrivatePaths, runLauncher, shouldDetachProtectedCore };
400
+ module.exports = {
401
+ resolveLauncherPrivatePaths,
402
+ runLauncher,
403
+ shouldDetachProtectedCore,
404
+ spawnManagedLauncher,
405
+ };
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const https = require('node:https');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const { spawnSync } = require('node:child_process');
9
+
10
+ const NODE_VERSION = '24.15.0';
11
+ const MAX_ARCHIVE_BYTES = 100 * 1024 * 1024;
12
+ const DISTRIBUTIONS = {
13
+ 'darwin-arm64': {
14
+ archive: `node-v${NODE_VERSION}-darwin-arm64.tar.gz`,
15
+ sha256: '372331b969779ab5d15b949884fc6eaf88d5afe87bde8ba881d6400b9100ffc4',
16
+ },
17
+ 'darwin-x64': {
18
+ archive: `node-v${NODE_VERSION}-darwin-x64.tar.gz`,
19
+ sha256: 'ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b',
20
+ },
21
+ };
22
+
23
+ function distributionFor(platform, arch) {
24
+ return DISTRIBUTIONS[`${platform}-${arch}`] || null;
25
+ }
26
+
27
+ function sha256File(file) {
28
+ const hash = crypto.createHash('sha256');
29
+ hash.update(fs.readFileSync(file));
30
+ return hash.digest('hex');
31
+ }
32
+
33
+ function downloadFile(url, destination, redirectsLeft = 3) {
34
+ return new Promise((resolve, reject) => {
35
+ const request = https.get(url, { timeout: 30_000 }, (response) => {
36
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
37
+ response.resume();
38
+ if (redirectsLeft <= 0) {
39
+ reject(new Error('Zu viele Weiterleitungen beim Node.js-Download.'));
40
+ return;
41
+ }
42
+ const redirect = new URL(response.headers.location, url);
43
+ if (redirect.protocol !== 'https:') {
44
+ reject(new Error('Unsichere Weiterleitung beim Node.js-Download abgelehnt.'));
45
+ return;
46
+ }
47
+ downloadFile(redirect.href, destination, redirectsLeft - 1).then(resolve, reject);
48
+ return;
49
+ }
50
+
51
+ if (response.statusCode !== 200) {
52
+ response.resume();
53
+ reject(new Error(`Node.js-Download antwortete mit HTTP ${response.statusCode}.`));
54
+ return;
55
+ }
56
+
57
+ const contentLength = Number(response.headers['content-length'] || 0);
58
+ if (contentLength > MAX_ARCHIVE_BYTES) {
59
+ response.destroy();
60
+ reject(new Error('Node.js-Download ist groesser als erwartet.'));
61
+ return;
62
+ }
63
+
64
+ let received = 0;
65
+ const output = fs.createWriteStream(destination, { flags: 'wx' });
66
+ response.on('data', (chunk) => {
67
+ received += chunk.length;
68
+ if (received > MAX_ARCHIVE_BYTES) {
69
+ response.destroy(new Error('Node.js-Download ist groesser als erwartet.'));
70
+ }
71
+ });
72
+ response.on('error', reject);
73
+ output.on('error', reject);
74
+ output.on('finish', () => output.close(resolve));
75
+ response.pipe(output);
76
+ });
77
+ request.on('error', reject);
78
+ request.on('timeout', () => request.destroy(new Error('Zeitlimit beim Node.js-Download erreicht.')));
79
+ });
80
+ }
81
+
82
+ function usableNode(binary) {
83
+ if (!fs.existsSync(binary)) return false;
84
+ const result = spawnSync(binary, ['--version'], { encoding: 'utf8', timeout: 5_000 });
85
+ return result.status === 0 && result.stdout.trim() === `v${NODE_VERSION}`;
86
+ }
87
+
88
+ async function ensureManagedNode(blunHome, platform, arch) {
89
+ const distribution = distributionFor(platform, arch);
90
+ if (!distribution) {
91
+ throw new Error(
92
+ `Automatische Node.js-Einrichtung wird fuer ${platform}-${arch} noch nicht unterstuetzt.`,
93
+ );
94
+ }
95
+
96
+ const installDir = path.join(
97
+ blunHome,
98
+ 'runtime',
99
+ 'node',
100
+ `v${NODE_VERSION}-${platform}-${arch}`,
101
+ );
102
+ const binary = path.join(installDir, 'bin', 'node');
103
+ if (usableNode(binary)) return binary;
104
+
105
+ const staging = fs.mkdtempSync(path.join(os.tmpdir(), 'blun-node-runtime-'));
106
+ try {
107
+ const archive = path.join(staging, distribution.archive);
108
+ await downloadFile(
109
+ `https://nodejs.org/dist/v${NODE_VERSION}/${distribution.archive}`,
110
+ archive,
111
+ );
112
+
113
+ const actualSha = sha256File(archive);
114
+ if (actualSha !== distribution.sha256) {
115
+ throw new Error('Die SHA-256-Pruefsumme der Node.js-Laufzeit stimmt nicht.');
116
+ }
117
+
118
+ const extractedName = distribution.archive.slice(0, -'.tar.gz'.length);
119
+ const extractedDir = path.join(staging, extractedName);
120
+ const unpacked = spawnSync('/usr/bin/tar', ['-xzf', archive, '-C', staging], {
121
+ encoding: 'utf8',
122
+ timeout: 120_000,
123
+ });
124
+ if (unpacked.status !== 0 || !usableNode(path.join(extractedDir, 'bin', 'node'))) {
125
+ const detail = (unpacked.stderr || '').trim();
126
+ throw new Error(
127
+ `Die Node.js-Laufzeit konnte nicht entpackt werden${detail ? `: ${detail}` : '.'}`,
128
+ );
129
+ }
130
+
131
+ fs.mkdirSync(path.dirname(installDir), { recursive: true });
132
+ if (usableNode(binary)) return binary;
133
+ fs.rmSync(installDir, { recursive: true, force: true });
134
+ fs.renameSync(extractedDir, installDir);
135
+ if (!usableNode(binary)) throw new Error('Die installierte Node.js-Laufzeit startet nicht.');
136
+ return binary;
137
+ } finally {
138
+ fs.rmSync(staging, { recursive: true, force: true });
139
+ }
140
+ }
141
+
142
+ module.exports = {
143
+ DISTRIBUTIONS,
144
+ MAX_ARCHIVE_BYTES,
145
+ NODE_VERSION,
146
+ distributionFor,
147
+ ensureManagedNode,
148
+ sha256File,
149
+ usableNode,
150
+ };
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const { ensureManagedNode } = require('./managed-node');
4
+ const { unsupportedNodeMessage } = require('./node-version');
5
+
6
+ async function prepareManagedNodeRuntime(options = {}) {
7
+ const version = options.version || process.versions.node;
8
+ const platform = options.platform || process.platform;
9
+ const arch = options.arch || process.arch;
10
+ const message = unsupportedNodeMessage(version, platform);
11
+ if (message === null) return { kind: 'current' };
12
+ if (platform !== 'darwin') return { kind: 'failed', message };
13
+
14
+ try {
15
+ const binary = await (options.ensureManagedNode || ensureManagedNode)(
16
+ options.blunDir,
17
+ platform,
18
+ arch,
19
+ );
20
+ return { binary, kind: 'managed' };
21
+ } catch (error) {
22
+ return {
23
+ kind: 'failed',
24
+ message: [
25
+ 'Die automatische Node.js-Einrichtung ist fehlgeschlagen.',
26
+ error && error.message ? error.message : String(error),
27
+ message,
28
+ ].join('\n'),
29
+ };
30
+ }
31
+ }
32
+
33
+ module.exports = { prepareManagedNodeRuntime };
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ const MINIMUM_NODE_VERSION = '24.15.0';
4
+
5
+ function parseNodeVersion(version) {
6
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(String(version));
7
+ return match ? match.slice(1).map(Number) : null;
8
+ }
9
+
10
+ function supportsNodeVersion(version) {
11
+ const actual = parseNodeVersion(version);
12
+ const minimum = parseNodeVersion(MINIMUM_NODE_VERSION);
13
+ if (!actual || !minimum) return false;
14
+
15
+ for (let index = 0; index < minimum.length; index += 1) {
16
+ if (actual[index] > minimum[index]) return true;
17
+ if (actual[index] < minimum[index]) return false;
18
+ }
19
+ return true;
20
+ }
21
+
22
+ function unsupportedNodeMessage(version, platform) {
23
+ if (supportsNodeVersion(version)) return null;
24
+
25
+ const lines = [
26
+ '',
27
+ 'BLUN kann mit dieser Node.js-Version nicht starten.',
28
+ `Installiert: Node.js ${version}`,
29
+ `Benoetigt: Node.js ${MINIMUM_NODE_VERSION} oder neuer`,
30
+ '',
31
+ ];
32
+
33
+ if (platform === 'darwin') {
34
+ lines.push(`Falls die automatische Einrichtung scheitert: nvm install ${MINIMUM_NODE_VERSION}`);
35
+ } else {
36
+ lines.push(`Node.js ${MINIMUM_NODE_VERSION} oder neuer installieren: https://nodejs.org/`);
37
+ }
38
+ lines.push('Danach BLUN aktualisieren: npm i -g blun-king-cli@latest', '');
39
+ return lines.join('\n');
40
+ }
41
+
42
+ module.exports = {
43
+ MINIMUM_NODE_VERSION,
44
+ parseNodeVersion,
45
+ supportsNodeVersion,
46
+ unsupportedNodeMessage,
47
+ };