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 +1 -1
- package/bin/launcher-runtime.js +48 -2
- package/bin/managed-node.js +150 -0
- package/bin/node-runtime.js +33 -0
- package/bin/node-version.js +47 -0
- package/blun.mjs +521 -187
- package/package.json +1 -1
package/README.md
CHANGED
package/bin/launcher-runtime.js
CHANGED
|
@@ -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 = {
|
|
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
|
+
};
|