blun-king-cli 9.1.9 → 9.1.11

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/bin/blun.js CHANGED
@@ -13,6 +13,7 @@ const LEASE_PROTOCOL_VERSION = 'BLUN_UPDATE_LEASE_V3';
13
13
  const LEASE_PROBE_TIMEOUT_MS = 500;
14
14
  const MAX_POSIX_SOCKET_PATH_BYTES = 96;
15
15
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
16
+ const LEASE_CONNECTIONS = Symbol('blunLeaseConnections');
16
17
 
17
18
  function deriveLeaseEndpoint(role) {
18
19
  const realpath = fs.realpathSync.native || fs.realpathSync;
@@ -62,25 +63,28 @@ function ensurePrivateSocketDirectory(endpoint) {
62
63
  }
63
64
 
64
65
  function closeServer(server) {
65
- return new Promise((resolve) => {
66
- if (!server.listening) {
67
- resolve();
68
- return;
69
- }
66
+ for (const socket of server[LEASE_CONNECTIONS] || []) {
70
67
  try {
71
- server.close(() => resolve());
72
- } catch {
73
- resolve();
74
- }
75
- });
68
+ socket.destroy();
69
+ } catch {}
70
+ }
71
+ if (!server.listening) return Promise.resolve();
72
+ try {
73
+ server.close();
74
+ server.unref?.();
75
+ } catch {}
76
+ return Promise.resolve();
76
77
  }
77
78
 
78
79
  function listenForLease(endpoint) {
79
80
  return new Promise((resolve, reject) => {
80
81
  const server = net.createServer((socket) => {
82
+ server[LEASE_CONNECTIONS].add(socket);
83
+ socket.once('close', () => server[LEASE_CONNECTIONS].delete(socket));
81
84
  socket.on('error', () => {});
82
85
  socket.end(endpoint.magic);
83
86
  });
87
+ server[LEASE_CONNECTIONS] = new Set();
84
88
  const onError = (error) => {
85
89
  server.removeListener('error', onError);
86
90
  reject(error);
@@ -253,35 +257,127 @@ async function installLeaseIsClear() {
253
257
  }
254
258
  }
255
259
 
260
+ function connectRuntimeBroker(endpoint) {
261
+ return new Promise((resolve) => {
262
+ let settled = false;
263
+ let received = '';
264
+ let socket;
265
+ const finish = (result) => {
266
+ if (settled) return;
267
+ settled = true;
268
+ clearTimeout(timer);
269
+ if (!result.acquired) socket?.destroy();
270
+ resolve(result);
271
+ };
272
+ const timer = setTimeout(
273
+ () => finish({ acquired: false, reason: 'unavailable' }),
274
+ LEASE_PROBE_TIMEOUT_MS,
275
+ );
276
+ timer.unref?.();
277
+ try {
278
+ socket = net.createConnection({ path: endpoint.path });
279
+ socket.setEncoding('utf8');
280
+ socket.once('connect', () => socket.write(endpoint.magic));
281
+ socket.on('data', (chunk) => {
282
+ received += String(chunk);
283
+ if (!received.includes('\n')) return;
284
+ if (received !== endpoint.magic) {
285
+ finish({ acquired: false, reason: 'unavailable' });
286
+ return;
287
+ }
288
+ let owned = true;
289
+ let released = false;
290
+ socket.once('close', () => { owned = false; });
291
+ socket.once('error', () => { owned = false; });
292
+ finish({
293
+ acquired: true,
294
+ lease: Object.freeze({
295
+ assertOwned() {
296
+ if (!owned || socket.destroyed) throw new Error('RUNTIME_LEASE_LOST');
297
+ },
298
+ async release() {
299
+ if (released) return;
300
+ released = true;
301
+ owned = false;
302
+ if (socket.destroyed) return;
303
+ socket.destroy();
304
+ },
305
+ }),
306
+ });
307
+ });
308
+ socket.once('error', () => finish({ acquired: false, reason: 'unavailable' }));
309
+ socket.once('close', () => finish({ acquired: false, reason: 'unavailable' }));
310
+ } catch {
311
+ finish({ acquired: false, reason: 'unavailable' });
312
+ }
313
+ });
314
+ }
315
+
316
+ async function connectExistingRuntimeLease() {
317
+ const endpoint = deriveLeaseEndpoint('runtime');
318
+ const deadline = Date.now() + 3_000;
319
+ while (Date.now() < deadline) {
320
+ const connected = await connectRuntimeBroker(endpoint);
321
+ if (connected.acquired) return connected;
322
+ await new Promise((resolve) => { setTimeout(resolve, 25); });
323
+ }
324
+ return { acquired: false, reason: 'unavailable' };
325
+ }
326
+
256
327
  async function main() {
257
328
  const explicitUpdateRequest = process.argv.length === 3
258
329
  && /^(?:update|upgrade)$/iu.test(process.argv[2] || '');
259
- const result = await acquireNoticeLease();
260
- if (!result.acquired) {
261
- process.stderr.write(explicitUpdateRequest
262
- ? 'BLUN laeuft noch. Beende die laufende Konsole und starte das Update erneut.\n'
263
- : 'BLUN laeuft bereits in einer anderen Konsole.\n');
330
+ let noticeResult = await acquireNoticeLease();
331
+ if (!noticeResult.acquired && explicitUpdateRequest) {
332
+ process.stderr.write('BLUN laeuft noch. Beende die laufenden Konsolen und starte das Update erneut.\n');
264
333
  process.exitCode = 1;
265
334
  return;
266
335
  }
336
+ let runtimeResult;
267
337
  try {
268
- if (!await installLeaseIsClear()) {
269
- process.stderr.write(explicitUpdateRequest
270
- ? 'BLUN laeuft noch. Beende die laufende Konsole und starte das Update erneut.\n'
271
- : 'BLUN laeuft bereits in einer anderen Konsole.\n');
272
- process.exitCode = 1;
273
- return;
338
+ if (explicitUpdateRequest) {
339
+ if (!await installLeaseIsClear()) {
340
+ process.stderr.write('BLUN laeuft noch. Beende die laufenden Konsolen und starte das Update erneut.\n');
341
+ process.exitCode = 1;
342
+ return;
343
+ }
344
+ } else {
345
+ const installClear = await installLeaseIsClear();
346
+ if (!installClear || !noticeResult.acquired) {
347
+ runtimeResult = await connectExistingRuntimeLease();
348
+ if (!runtimeResult.acquired && !installClear) {
349
+ if (!await installLeaseIsClear()) {
350
+ process.stderr.write('Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
351
+ process.exitCode = 1;
352
+ return;
353
+ }
354
+ runtimeResult = undefined;
355
+ }
356
+ if (!runtimeResult?.acquired && !noticeResult.acquired) {
357
+ noticeResult = await acquireNoticeLease();
358
+ if (!noticeResult.acquired) {
359
+ process.stderr.write('Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
360
+ process.exitCode = 1;
361
+ return;
362
+ }
363
+ }
364
+ }
274
365
  }
275
- result.lease.assertOwned();
366
+ noticeResult.lease?.assertOwned();
367
+ runtimeResult?.lease?.assertOwned();
276
368
  const { runLauncher } = require('./launcher-runtime');
277
- result.lease.assertOwned();
369
+ noticeResult.lease?.assertOwned();
370
+ runtimeResult?.lease?.assertOwned();
278
371
  const filename = path.basename(__filename).toLowerCase();
279
372
  await runLauncher({
280
373
  mode: filename.startsWith('king') ? 'king' : 'blun',
281
- noticeLease: result.lease,
374
+ noticeLease: noticeResult.lease,
375
+ runtimeLease: runtimeResult?.lease,
376
+ skipUpdateNotice: runtimeResult?.acquired === true,
282
377
  });
283
378
  } finally {
284
- await result.lease.release();
379
+ await runtimeResult?.lease?.release();
380
+ await noticeResult.lease?.release();
285
381
  }
286
382
  }
287
383
 
@@ -4,15 +4,15 @@ const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
  const { pathToFileURL } = require('node:url');
6
6
 
7
- const { tryAcquireUpdateLease } = require('./update-lease');
7
+ const { acquireSharedRuntimeLease } = require('./update-lease');
8
8
 
9
9
  const CORE_LOADED_MESSAGE = 'blun-core-bootstrap-loaded';
10
10
 
11
11
  async function runCoreBootstrap() {
12
12
  const packageRoot = path.resolve(__dirname, '..');
13
13
  const mainPath = path.join(packageRoot, 'blun.mjs');
14
- const leaseResult = await tryAcquireUpdateLease({ packageRoot, role: 'install' });
15
- if (!leaseResult.acquired) process.exit(75);
14
+ const leaseResult = await acquireSharedRuntimeLease({ packageRoot });
15
+ if (!leaseResult.acquired) throw new Error('RUNTIME_PROTECTION_UNAVAILABLE');
16
16
 
17
17
  try {
18
18
  leaseResult.lease.assertOwned();
package/bin/king.js CHANGED
@@ -13,6 +13,7 @@ const LEASE_PROTOCOL_VERSION = 'BLUN_UPDATE_LEASE_V3';
13
13
  const LEASE_PROBE_TIMEOUT_MS = 500;
14
14
  const MAX_POSIX_SOCKET_PATH_BYTES = 96;
15
15
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
16
+ const LEASE_CONNECTIONS = Symbol('blunLeaseConnections');
16
17
 
17
18
  function deriveLeaseEndpoint(role) {
18
19
  const realpath = fs.realpathSync.native || fs.realpathSync;
@@ -62,25 +63,28 @@ function ensurePrivateSocketDirectory(endpoint) {
62
63
  }
63
64
 
64
65
  function closeServer(server) {
65
- return new Promise((resolve) => {
66
- if (!server.listening) {
67
- resolve();
68
- return;
69
- }
66
+ for (const socket of server[LEASE_CONNECTIONS] || []) {
70
67
  try {
71
- server.close(() => resolve());
72
- } catch {
73
- resolve();
74
- }
75
- });
68
+ socket.destroy();
69
+ } catch {}
70
+ }
71
+ if (!server.listening) return Promise.resolve();
72
+ try {
73
+ server.close();
74
+ server.unref?.();
75
+ } catch {}
76
+ return Promise.resolve();
76
77
  }
77
78
 
78
79
  function listenForLease(endpoint) {
79
80
  return new Promise((resolve, reject) => {
80
81
  const server = net.createServer((socket) => {
82
+ server[LEASE_CONNECTIONS].add(socket);
83
+ socket.once('close', () => server[LEASE_CONNECTIONS].delete(socket));
81
84
  socket.on('error', () => {});
82
85
  socket.end(endpoint.magic);
83
86
  });
87
+ server[LEASE_CONNECTIONS] = new Set();
84
88
  const onError = (error) => {
85
89
  server.removeListener('error', onError);
86
90
  reject(error);
@@ -253,35 +257,127 @@ async function installLeaseIsClear() {
253
257
  }
254
258
  }
255
259
 
260
+ function connectRuntimeBroker(endpoint) {
261
+ return new Promise((resolve) => {
262
+ let settled = false;
263
+ let received = '';
264
+ let socket;
265
+ const finish = (result) => {
266
+ if (settled) return;
267
+ settled = true;
268
+ clearTimeout(timer);
269
+ if (!result.acquired) socket?.destroy();
270
+ resolve(result);
271
+ };
272
+ const timer = setTimeout(
273
+ () => finish({ acquired: false, reason: 'unavailable' }),
274
+ LEASE_PROBE_TIMEOUT_MS,
275
+ );
276
+ timer.unref?.();
277
+ try {
278
+ socket = net.createConnection({ path: endpoint.path });
279
+ socket.setEncoding('utf8');
280
+ socket.once('connect', () => socket.write(endpoint.magic));
281
+ socket.on('data', (chunk) => {
282
+ received += String(chunk);
283
+ if (!received.includes('\n')) return;
284
+ if (received !== endpoint.magic) {
285
+ finish({ acquired: false, reason: 'unavailable' });
286
+ return;
287
+ }
288
+ let owned = true;
289
+ let released = false;
290
+ socket.once('close', () => { owned = false; });
291
+ socket.once('error', () => { owned = false; });
292
+ finish({
293
+ acquired: true,
294
+ lease: Object.freeze({
295
+ assertOwned() {
296
+ if (!owned || socket.destroyed) throw new Error('RUNTIME_LEASE_LOST');
297
+ },
298
+ async release() {
299
+ if (released) return;
300
+ released = true;
301
+ owned = false;
302
+ if (socket.destroyed) return;
303
+ socket.destroy();
304
+ },
305
+ }),
306
+ });
307
+ });
308
+ socket.once('error', () => finish({ acquired: false, reason: 'unavailable' }));
309
+ socket.once('close', () => finish({ acquired: false, reason: 'unavailable' }));
310
+ } catch {
311
+ finish({ acquired: false, reason: 'unavailable' });
312
+ }
313
+ });
314
+ }
315
+
316
+ async function connectExistingRuntimeLease() {
317
+ const endpoint = deriveLeaseEndpoint('runtime');
318
+ const deadline = Date.now() + 3_000;
319
+ while (Date.now() < deadline) {
320
+ const connected = await connectRuntimeBroker(endpoint);
321
+ if (connected.acquired) return connected;
322
+ await new Promise((resolve) => { setTimeout(resolve, 25); });
323
+ }
324
+ return { acquired: false, reason: 'unavailable' };
325
+ }
326
+
256
327
  async function main() {
257
328
  const explicitUpdateRequest = process.argv.length === 3
258
329
  && /^(?:update|upgrade)$/iu.test(process.argv[2] || '');
259
- const result = await acquireNoticeLease();
260
- if (!result.acquired) {
261
- process.stderr.write(explicitUpdateRequest
262
- ? 'BLUN laeuft noch. Beende die laufende Konsole und starte das Update erneut.\n'
263
- : 'BLUN laeuft bereits in einer anderen Konsole.\n');
330
+ let noticeResult = await acquireNoticeLease();
331
+ if (!noticeResult.acquired && explicitUpdateRequest) {
332
+ process.stderr.write('BLUN laeuft noch. Beende die laufenden Konsolen und starte das Update erneut.\n');
264
333
  process.exitCode = 1;
265
334
  return;
266
335
  }
336
+ let runtimeResult;
267
337
  try {
268
- if (!await installLeaseIsClear()) {
269
- process.stderr.write(explicitUpdateRequest
270
- ? 'BLUN laeuft noch. Beende die laufende Konsole und starte das Update erneut.\n'
271
- : 'BLUN laeuft bereits in einer anderen Konsole.\n');
272
- process.exitCode = 1;
273
- return;
338
+ if (explicitUpdateRequest) {
339
+ if (!await installLeaseIsClear()) {
340
+ process.stderr.write('BLUN laeuft noch. Beende die laufenden Konsolen und starte das Update erneut.\n');
341
+ process.exitCode = 1;
342
+ return;
343
+ }
344
+ } else {
345
+ const installClear = await installLeaseIsClear();
346
+ if (!installClear || !noticeResult.acquired) {
347
+ runtimeResult = await connectExistingRuntimeLease();
348
+ if (!runtimeResult.acquired && !installClear) {
349
+ if (!await installLeaseIsClear()) {
350
+ process.stderr.write('Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
351
+ process.exitCode = 1;
352
+ return;
353
+ }
354
+ runtimeResult = undefined;
355
+ }
356
+ if (!runtimeResult?.acquired && !noticeResult.acquired) {
357
+ noticeResult = await acquireNoticeLease();
358
+ if (!noticeResult.acquired) {
359
+ process.stderr.write('Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
360
+ process.exitCode = 1;
361
+ return;
362
+ }
363
+ }
364
+ }
274
365
  }
275
- result.lease.assertOwned();
366
+ noticeResult.lease?.assertOwned();
367
+ runtimeResult?.lease?.assertOwned();
276
368
  const { runLauncher } = require('./launcher-runtime');
277
- result.lease.assertOwned();
369
+ noticeResult.lease?.assertOwned();
370
+ runtimeResult?.lease?.assertOwned();
278
371
  const filename = path.basename(__filename).toLowerCase();
279
372
  await runLauncher({
280
373
  mode: filename.startsWith('king') ? 'king' : 'blun',
281
- noticeLease: result.lease,
374
+ noticeLease: noticeResult.lease,
375
+ runtimeLease: runtimeResult?.lease,
376
+ skipUpdateNotice: runtimeResult?.acquired === true,
282
377
  });
283
378
  } finally {
284
- await result.lease.release();
379
+ await runtimeResult?.lease?.release();
380
+ await noticeResult.lease?.release();
285
381
  }
286
382
  }
287
383
 
File without changes
@@ -22,7 +22,7 @@ const {
22
22
  seedStandardTools,
23
23
  } = require('./standard-tools-bootstrap');
24
24
  const { CORE_LOADED_MESSAGE } = require('./core-bootstrap');
25
- const { tryAcquireUpdateLease } = require('./update-lease');
25
+ const { acquireSharedRuntimeLease, tryAcquireUpdateLease } = require('./update-lease');
26
26
  const { runExplicitUpdate, runUpdateNotice } = require('./update-notice');
27
27
  const {
28
28
  ensurePrivateDirectory,
@@ -187,7 +187,9 @@ async function runLauncher(options = {}) {
187
187
  const mode = options.mode || launcherModeFromArgv(process.argv);
188
188
  const explicitUpdateRequest = isNpmPackageUpdateRequest(ARGS);
189
189
  const explicitToolsRequest = String(ARGS[0] || '').toLowerCase() === 'tools';
190
- const noticeResult = options.noticeLease
190
+ const noticeResult = options.skipUpdateNotice === true
191
+ ? { acquired: true, lease: { assertOwned() {}, async release() {} } }
192
+ : options.noticeLease
191
193
  ? { acquired: true, lease: options.noticeLease }
192
194
  : await tryAcquireUpdateLease({ packageRoot: PKG, role: 'notice' });
193
195
  if (!noticeResult.acquired) {
@@ -198,23 +200,27 @@ async function runLauncher(options = {}) {
198
200
  return;
199
201
  }
200
202
  const noticeLease = noticeResult.lease;
203
+ let runtimeLease = options.runtimeLease;
201
204
  let noticeReleased = false;
202
205
  const releaseNotice = async () => {
203
206
  if (noticeReleased) return;
204
207
  noticeReleased = true;
205
208
  await noticeLease.release();
209
+ await runtimeLease?.release();
206
210
  };
207
211
 
208
212
  try {
209
- const installProbe = await tryAcquireUpdateLease({ packageRoot: PKG, role: 'install' });
210
- if (!installProbe.acquired) {
211
- if (explicitUpdateRequest) {
212
- process.stderr.write('Das Update konnte nicht verifiziert werden.\n');
213
+ if (!runtimeLease) {
214
+ const installProbe = await tryAcquireUpdateLease({ packageRoot: PKG, role: 'install' });
215
+ if (!installProbe.acquired) {
216
+ process.stderr.write(explicitUpdateRequest
217
+ ? 'Das Update konnte nicht verifiziert werden.\n'
218
+ : 'Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
213
219
  process.exitCode = 1;
220
+ return;
214
221
  }
215
- return;
222
+ await installProbe.lease.release();
216
223
  }
217
- await installProbe.lease.release();
218
224
 
219
225
  if (explicitToolsRequest) {
220
226
  const action = String(ARGS[1] || '').toLowerCase();
@@ -287,19 +293,31 @@ async function runLauncher(options = {}) {
287
293
  ensurePrivateDirectory(blunDir);
288
294
 
289
295
  // --- 1. Oeffentlicher npm-Update-Dialog. -------------------------------
290
- const updateResult = await runUpdateNotice({
291
- currentVersion: readPackageVersion(),
292
- argv: ARGS,
293
- env: process.env,
294
- stdin: process.stdin,
295
- stdout: process.stdout,
296
- stderr: process.stderr,
297
- blunDir,
298
- packageRoot: PKG,
299
- noticeLease,
300
- });
296
+ const updateResult = options.skipUpdateNotice === true
297
+ ? { kind: 'continue', reason: 'notice_busy' }
298
+ : await runUpdateNotice({
299
+ currentVersion: readPackageVersion(),
300
+ argv: ARGS,
301
+ env: process.env,
302
+ stdin: process.stdin,
303
+ stdout: process.stdout,
304
+ stderr: process.stderr,
305
+ blunDir,
306
+ packageRoot: PKG,
307
+ noticeLease,
308
+ });
301
309
  if (updateResult.kind === 'installed' || updateResult.kind === 'update_in_progress') return;
302
310
 
311
+ if (!runtimeLease) {
312
+ const runtimeResult = await acquireSharedRuntimeLease({ packageRoot: PKG });
313
+ if (!runtimeResult.acquired) {
314
+ process.stderr.write('Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
315
+ process.exitCode = 1;
316
+ return;
317
+ }
318
+ runtimeLease = runtimeResult.lease;
319
+ }
320
+
303
321
  // --- 2. Bestehende Konfiguration absichern -------------------------------
304
322
  // Ein frischer Start bleibt absichtlich ohne statischen Anbieter. /login
305
323
  // provisioniert King ueber den BLUN-OAuth-Server.
File without changes
File without changes
File without changes