blun-king-cli 9.1.22 → 9.1.24

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
@@ -313,9 +313,9 @@ function connectRuntimeBroker(endpoint) {
313
313
  });
314
314
  }
315
315
 
316
- async function connectExistingRuntimeLease() {
316
+ async function connectExistingRuntimeLease(timeoutMs = 3_000) {
317
317
  const endpoint = deriveLeaseEndpoint('runtime');
318
- const deadline = Date.now() + 3_000;
318
+ const deadline = Date.now() + timeoutMs;
319
319
  while (Date.now() < deadline) {
320
320
  const connected = await connectRuntimeBroker(endpoint);
321
321
  if (connected.acquired) return connected;
@@ -328,18 +328,17 @@ async function main() {
328
328
  const explicitUpdateRequest = process.argv.length === 3
329
329
  && /^(?:update|upgrade)$/iu.test(process.argv[2] || '');
330
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');
333
- process.exitCode = 1;
334
- return;
335
- }
336
331
  let runtimeResult;
337
332
  try {
338
333
  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;
334
+ runtimeResult = await connectExistingRuntimeLease(750);
335
+ if (!runtimeResult.acquired) {
336
+ runtimeResult = undefined;
337
+ if (!noticeResult.acquired || !await installLeaseIsClear()) {
338
+ process.stderr.write('Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
339
+ process.exitCode = 1;
340
+ return;
341
+ }
343
342
  }
344
343
  } else {
345
344
  const installClear = await installLeaseIsClear();
@@ -373,7 +372,7 @@ async function main() {
373
372
  mode: filename.startsWith('king') ? 'king' : 'blun',
374
373
  noticeLease: noticeResult.lease,
375
374
  runtimeLease: runtimeResult?.lease,
376
- skipUpdateNotice: runtimeResult?.acquired === true,
375
+ skipUpdateNotice: runtimeResult?.acquired === true || !noticeResult.acquired,
377
376
  });
378
377
  } finally {
379
378
  await runtimeResult?.lease?.release();
package/bin/king.js CHANGED
@@ -313,9 +313,9 @@ function connectRuntimeBroker(endpoint) {
313
313
  });
314
314
  }
315
315
 
316
- async function connectExistingRuntimeLease() {
316
+ async function connectExistingRuntimeLease(timeoutMs = 3_000) {
317
317
  const endpoint = deriveLeaseEndpoint('runtime');
318
- const deadline = Date.now() + 3_000;
318
+ const deadline = Date.now() + timeoutMs;
319
319
  while (Date.now() < deadline) {
320
320
  const connected = await connectRuntimeBroker(endpoint);
321
321
  if (connected.acquired) return connected;
@@ -328,18 +328,17 @@ async function main() {
328
328
  const explicitUpdateRequest = process.argv.length === 3
329
329
  && /^(?:update|upgrade)$/iu.test(process.argv[2] || '');
330
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');
333
- process.exitCode = 1;
334
- return;
335
- }
336
331
  let runtimeResult;
337
332
  try {
338
333
  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;
334
+ runtimeResult = await connectExistingRuntimeLease(750);
335
+ if (!runtimeResult.acquired) {
336
+ runtimeResult = undefined;
337
+ if (!noticeResult.acquired || !await installLeaseIsClear()) {
338
+ process.stderr.write('Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
339
+ process.exitCode = 1;
340
+ return;
341
+ }
343
342
  }
344
343
  } else {
345
344
  const installClear = await installLeaseIsClear();
@@ -373,7 +372,7 @@ async function main() {
373
372
  mode: filename.startsWith('king') ? 'king' : 'blun',
374
373
  noticeLease: noticeResult.lease,
375
374
  runtimeLease: runtimeResult?.lease,
376
- skipUpdateNotice: runtimeResult?.acquired === true,
375
+ skipUpdateNotice: runtimeResult?.acquired === true || !noticeResult.acquired,
377
376
  });
378
377
  } finally {
379
378
  await runtimeResult?.lease?.release();
@@ -210,7 +210,7 @@ async function runLauncher(options = {}) {
210
210
  };
211
211
 
212
212
  try {
213
- if (!runtimeLease) {
213
+ if (!runtimeLease && !explicitUpdateRequest) {
214
214
  const installProbe = await tryAcquireUpdateLease({ packageRoot: PKG, role: 'install' });
215
215
  if (!installProbe.acquired) {
216
216
  process.stderr.write(explicitUpdateRequest
@@ -24,6 +24,7 @@ const SNOOZE_MS = 7 * 24 * 60 * 60 * 1000;
24
24
  const REQUEST_TIMEOUT_MS = 2_500;
25
25
  const STATE_FILE = 'update-notice.json';
26
26
  const UPDATE_SKIP_FILE = 'update-skip-version.json';
27
+ const PENDING_UPDATE_FILE = 'update-pending.json';
27
28
  const UPDATE_DIRECTORY = 'updates';
28
29
  const UPDATE_KEY_ESCAPE_TIMEOUT_MS = 500;
29
30
  const UPDATE_SIGNAL_EXIT_CODES = Object.freeze({
@@ -441,6 +442,59 @@ function writeSkippedVersion(blunDir, skippedVersion) {
441
442
  }
442
443
  }
443
444
 
445
+ function pendingUpdatePath(blunDir) {
446
+ return path.join(blunDir, PENDING_UPDATE_FILE);
447
+ }
448
+
449
+ function readPendingUpdate(blunDir) {
450
+ try {
451
+ const filePath = pendingUpdatePath(blunDir);
452
+ if (!fs.existsSync(filePath)) return undefined;
453
+ securePrivateFile(filePath);
454
+ const stat = fs.lstatSync(filePath);
455
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1_024) return undefined;
456
+ const record = exactDataRecord(JSON.parse(fs.readFileSync(filePath, 'utf8')), [
457
+ 'targetVersion',
458
+ 'version',
459
+ ]);
460
+ if (!record || record.version !== 1 || !parseSemver(record.targetVersion)) return undefined;
461
+ return record.targetVersion;
462
+ } catch {
463
+ return undefined;
464
+ }
465
+ }
466
+
467
+ function writePendingUpdate(blunDir, targetVersion) {
468
+ if (!parseSemver(targetVersion)) throw new Error('INVALID_UPDATE_VERSION');
469
+ ensurePrivateDirectory(blunDir);
470
+ const filePath = pendingUpdatePath(blunDir);
471
+ const temporaryPath = path.join(
472
+ blunDir,
473
+ `.${PENDING_UPDATE_FILE}.${process.pid}.${randomUUID()}.tmp`,
474
+ );
475
+ try {
476
+ writePrivateFile(temporaryPath, `${JSON.stringify({
477
+ version: 1,
478
+ targetVersion,
479
+ })}\n`);
480
+ const handle = fs.openSync(temporaryPath, 'r+');
481
+ try {
482
+ fs.fsyncSync(handle);
483
+ } finally {
484
+ fs.closeSync(handle);
485
+ }
486
+ fs.renameSync(temporaryPath, filePath);
487
+ securePrivateFile(filePath);
488
+ } catch (error) {
489
+ fs.rmSync(temporaryPath, { force: true });
490
+ throw error;
491
+ }
492
+ }
493
+
494
+ function clearPendingUpdate(blunDir) {
495
+ fs.rmSync(pendingUpdatePath(blunDir), { force: true });
496
+ }
497
+
444
498
  function isInteractiveUpdateStart({ argv, env, stdin, stdout }) {
445
499
  const isOrdinaryStartup = Array.isArray(argv)
446
500
  && (argv.length === 0
@@ -1145,6 +1199,19 @@ function explicitFailure(stderr, reason, exitCode) {
1145
1199
  };
1146
1200
  }
1147
1201
 
1202
+ function queueExplicitUpdate(blunDir, release, stdout, stderr) {
1203
+ try {
1204
+ ensurePrivateDirectory(blunDir);
1205
+ writePendingUpdate(blunDir, release.version);
1206
+ } catch {
1207
+ return explicitFailure(stderr, 'private_home_unavailable');
1208
+ }
1209
+ stdout.write(
1210
+ `Update ${release.version} vorgemerkt. Es wird beim ersten freien Start automatisch installiert.\n`,
1211
+ );
1212
+ return { kind: 'queued', version: release.version };
1213
+ }
1214
+
1148
1215
  async function runUpdateFlow(options, explicitUpdate) {
1149
1216
  const {
1150
1217
  currentVersion,
@@ -1164,8 +1231,13 @@ async function runUpdateFlow(options, explicitUpdate) {
1164
1231
  : { kind: 'continue', reason: 'invalid_current_version' };
1165
1232
  }
1166
1233
 
1234
+ let pendingVersion = readPendingUpdate(blunDir);
1235
+ if (pendingVersion !== undefined && compareSemver(currentVersion, pendingVersion) >= 0) {
1236
+ clearPendingUpdate(blunDir);
1237
+ pendingVersion = undefined;
1238
+ }
1167
1239
  const now = (options.now || Date.now)();
1168
- if (!explicitUpdate && readSnoozeState(blunDir, now)) {
1240
+ if (!explicitUpdate && pendingVersion === undefined && readSnoozeState(blunDir, now)) {
1169
1241
  return { kind: 'continue', reason: 'snoozed' };
1170
1242
  }
1171
1243
 
@@ -1198,20 +1270,11 @@ async function runUpdateFlow(options, explicitUpdate) {
1198
1270
  let installerSession;
1199
1271
 
1200
1272
  try {
1201
- if (!explicitUpdate && readSnoozeState(blunDir, (options.now || Date.now)())) {
1273
+ if (!explicitUpdate
1274
+ && pendingVersion === undefined
1275
+ && readSnoozeState(blunDir, (options.now || Date.now)())) {
1202
1276
  return { kind: 'continue', reason: 'snoozed' };
1203
1277
  }
1204
- const initialInstallLease = await probeInstallLease(options, packageRoot);
1205
- if (initialInstallLease === 'busy') {
1206
- return explicitUpdate
1207
- ? explicitFailure(stderr, 'update_in_progress')
1208
- : { kind: 'update_in_progress' };
1209
- }
1210
- if (initialInstallLease !== 'clear') {
1211
- return explicitUpdate
1212
- ? explicitFailure(stderr, 'lease_unavailable')
1213
- : { kind: 'continue', reason: 'lease_unavailable' };
1214
- }
1215
1278
  let sources;
1216
1279
  try {
1217
1280
  sources = await (options.loadReleaseSources || loadReleaseSources)();
@@ -1229,10 +1292,22 @@ async function runUpdateFlow(options, explicitUpdate) {
1229
1292
  ? explicitNoTarget(stdout, reason)
1230
1293
  : { kind: 'continue', reason };
1231
1294
  }
1232
- if (!explicitUpdate && readSkippedVersion(blunDir) === release.version) {
1295
+ const queuedForRelease = pendingVersion === release.version;
1296
+ const initialInstallLease = await probeInstallLease(options, packageRoot);
1297
+ if (initialInstallLease === 'busy') {
1298
+ return explicitUpdate
1299
+ ? queueExplicitUpdate(blunDir, release, stdout, stderr)
1300
+ : { kind: 'update_in_progress' };
1301
+ }
1302
+ if (initialInstallLease !== 'clear') {
1303
+ return explicitUpdate
1304
+ ? explicitFailure(stderr, 'lease_unavailable')
1305
+ : { kind: 'continue', reason: 'lease_unavailable' };
1306
+ }
1307
+ if (!explicitUpdate && !queuedForRelease && readSkippedVersion(blunDir) === release.version) {
1233
1308
  return { kind: 'continue', reason: 'skipped_version' };
1234
1309
  }
1235
- if (explicitUpdate) {
1310
+ if (explicitUpdate || queuedForRelease) {
1236
1311
  try {
1237
1312
  ensurePrivateDirectory(blunDir);
1238
1313
  } catch {
@@ -1249,7 +1324,7 @@ async function runUpdateFlow(options, explicitUpdate) {
1249
1324
  const prePromptInstallLease = await probeInstallLease(options, packageRoot);
1250
1325
  if (prePromptInstallLease === 'busy') {
1251
1326
  return explicitUpdate
1252
- ? explicitFailure(stderr, 'update_in_progress')
1327
+ ? queueExplicitUpdate(blunDir, release, stdout, stderr)
1253
1328
  : { kind: 'update_in_progress' };
1254
1329
  }
1255
1330
  if (prePromptInstallLease !== 'clear') {
@@ -1272,7 +1347,7 @@ async function runUpdateFlow(options, explicitUpdate) {
1272
1347
  }
1273
1348
  }
1274
1349
  let action;
1275
- if (explicitUpdate) {
1350
+ if (explicitUpdate || queuedForRelease) {
1276
1351
  action = 'install';
1277
1352
  } else {
1278
1353
  try {
@@ -1337,6 +1412,7 @@ async function runUpdateFlow(options, explicitUpdate) {
1337
1412
  }
1338
1413
  if (installResult?.code === 0 && installResult?.verified === true) {
1339
1414
  updateProgress(4, 'Installation geprüft');
1415
+ clearPendingUpdate(blunDir);
1340
1416
  stdout.write('Update erfolgreich installiert. Starte BLUN Code neu, um die neue Version zu verwenden.\n');
1341
1417
  return { kind: 'installed', version: release.version };
1342
1418
  }