blun-king-cli 9.1.18 → 9.1.20
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/update-notice.js +106 -7
- package/blun.mjs +2265 -410
- package/package.json +1 -1
package/bin/update-notice.js
CHANGED
|
@@ -23,6 +23,7 @@ const FALLBACK_MANIFEST_URL = 'https://chat.blun.ai/blun-code-version.json';
|
|
|
23
23
|
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
|
+
const UPDATE_SKIP_FILE = 'update-skip-version.json';
|
|
26
27
|
const UPDATE_DIRECTORY = 'updates';
|
|
27
28
|
const UPDATE_KEY_ESCAPE_TIMEOUT_MS = 500;
|
|
28
29
|
const UPDATE_SIGNAL_EXIT_CODES = Object.freeze({
|
|
@@ -382,6 +383,57 @@ function writeSnoozeState(blunDir, now = Date.now()) {
|
|
|
382
383
|
}
|
|
383
384
|
}
|
|
384
385
|
|
|
386
|
+
function skippedVersionPath(blunDir) {
|
|
387
|
+
return path.join(blunDir, UPDATE_SKIP_FILE);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function readSkippedVersion(blunDir) {
|
|
391
|
+
try {
|
|
392
|
+
const filePath = skippedVersionPath(blunDir);
|
|
393
|
+
if (!fs.existsSync(filePath)) return undefined;
|
|
394
|
+
securePrivateFile(filePath);
|
|
395
|
+
const stat = fs.lstatSync(filePath);
|
|
396
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1_024) return undefined;
|
|
397
|
+
const record = exactDataRecord(JSON.parse(fs.readFileSync(filePath, 'utf8')), [
|
|
398
|
+
'skippedVersion',
|
|
399
|
+
'version',
|
|
400
|
+
]);
|
|
401
|
+
if (!record || record.version !== 1 || !parseSemver(record.skippedVersion)) {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
return record.skippedVersion;
|
|
405
|
+
} catch {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function writeSkippedVersion(blunDir, skippedVersion) {
|
|
411
|
+
if (!parseSemver(skippedVersion)) throw new Error('INVALID_UPDATE_VERSION');
|
|
412
|
+
ensurePrivateDirectory(blunDir);
|
|
413
|
+
const filePath = skippedVersionPath(blunDir);
|
|
414
|
+
const temporaryPath = path.join(
|
|
415
|
+
blunDir,
|
|
416
|
+
`.${UPDATE_SKIP_FILE}.${process.pid}.${randomUUID()}.tmp`,
|
|
417
|
+
);
|
|
418
|
+
try {
|
|
419
|
+
writePrivateFile(temporaryPath, `${JSON.stringify({
|
|
420
|
+
version: 1,
|
|
421
|
+
skippedVersion,
|
|
422
|
+
})}\n`);
|
|
423
|
+
const handle = fs.openSync(temporaryPath, 'r+');
|
|
424
|
+
try {
|
|
425
|
+
fs.fsyncSync(handle);
|
|
426
|
+
} finally {
|
|
427
|
+
fs.closeSync(handle);
|
|
428
|
+
}
|
|
429
|
+
fs.renameSync(temporaryPath, filePath);
|
|
430
|
+
securePrivateFile(filePath);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
385
437
|
function isInteractiveUpdateStart({ argv, env, stdin, stdout }) {
|
|
386
438
|
return Array.isArray(argv)
|
|
387
439
|
&& argv.length === 0
|
|
@@ -394,6 +446,19 @@ function installCommandText(version) {
|
|
|
394
446
|
return `npm i -g ${PACKAGE_NAME}@${version}`;
|
|
395
447
|
}
|
|
396
448
|
|
|
449
|
+
function createUpdateProgress(output) {
|
|
450
|
+
const enabled = output?.isTTY === true;
|
|
451
|
+
const phaseCount = 4;
|
|
452
|
+
const width = 20;
|
|
453
|
+
return (phase, label) => {
|
|
454
|
+
if (!enabled) return;
|
|
455
|
+
const completed = Math.max(0, Math.min(phaseCount, phase));
|
|
456
|
+
const filled = Math.round((completed / phaseCount) * width);
|
|
457
|
+
const bar = `${'#'.repeat(filled)}${'-'.repeat(width - filled)}`;
|
|
458
|
+
output.write(`Update [${bar}] ${completed}/${phaseCount} ${label}\n`);
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
397
462
|
function promptUpdateAction({ currentVersion, release, input, output, processRef = process }) {
|
|
398
463
|
return new Promise((resolve, reject) => {
|
|
399
464
|
let selected = 1;
|
|
@@ -403,14 +468,22 @@ function promptUpdateAction({ currentVersion, release, input, output, processRef
|
|
|
403
468
|
let pendingInput = '';
|
|
404
469
|
const decoder = new StringDecoder('utf8');
|
|
405
470
|
const wasRaw = input.isRaw === true;
|
|
406
|
-
const actions = [
|
|
471
|
+
const actions = [
|
|
472
|
+
{
|
|
473
|
+
choice: 'install',
|
|
474
|
+
label: `Jetzt aktualisieren (führt \`${installCommandText(release.version)}\` aus)`,
|
|
475
|
+
},
|
|
476
|
+
{ choice: 'postpone', label: 'In sieben Tagen erneut fragen' },
|
|
477
|
+
{ choice: 'skip-version', label: 'Diese Version überspringen' },
|
|
478
|
+
];
|
|
407
479
|
const signalHandlers = new Map();
|
|
408
480
|
|
|
409
481
|
const renderActions = (refresh) => {
|
|
410
|
-
if (refresh) output.write(
|
|
482
|
+
if (refresh) output.write(`\u001B[${actions.length + 1}F`);
|
|
411
483
|
for (let index = 0; index < actions.length; index += 1) {
|
|
412
|
-
output.write(`\r\u001B[2K ${selected === index ? '>' : ' '} ${actions[index]}\n`);
|
|
484
|
+
output.write(`\r\u001B[2K ${selected === index ? '>' : ' '} ${index + 1}. ${actions[index].label}\n`);
|
|
413
485
|
}
|
|
486
|
+
output.write('\r\u001B[2KEnter drücken, um fortzufahren\n');
|
|
414
487
|
};
|
|
415
488
|
const cleanup = () => {
|
|
416
489
|
if (escapeTimer !== undefined) clearTimeout(escapeTimer);
|
|
@@ -505,11 +578,18 @@ function promptUpdateAction({ currentVersion, release, input, output, processRef
|
|
|
505
578
|
}
|
|
506
579
|
if (first === '\r' || first === '\n') {
|
|
507
580
|
pendingInput = pendingInput.slice(1);
|
|
508
|
-
finish(selected
|
|
581
|
+
finish(actions[selected].choice);
|
|
509
582
|
return;
|
|
510
583
|
}
|
|
511
584
|
if (first !== '\u001B') {
|
|
585
|
+
const numericSelection = Number(first) - 1;
|
|
512
586
|
pendingInput = pendingInput.slice(1);
|
|
587
|
+
if (Number.isInteger(numericSelection)
|
|
588
|
+
&& numericSelection >= 0
|
|
589
|
+
&& numericSelection < actions.length) {
|
|
590
|
+
selected = numericSelection;
|
|
591
|
+
renderActions(true);
|
|
592
|
+
}
|
|
513
593
|
continue;
|
|
514
594
|
}
|
|
515
595
|
if (pendingInput.length > 1
|
|
@@ -526,13 +606,13 @@ function promptUpdateAction({ currentVersion, release, input, output, processRef
|
|
|
526
606
|
}
|
|
527
607
|
clearEscapeTimer();
|
|
528
608
|
if (sequence === '\u001B[A' || sequence === '\u001B[D') {
|
|
529
|
-
selected =
|
|
609
|
+
selected = (selected - 1 + actions.length) % actions.length;
|
|
530
610
|
pendingInput = pendingInput.slice(sequence.length);
|
|
531
611
|
renderActions(true);
|
|
532
612
|
continue;
|
|
533
613
|
}
|
|
534
614
|
if (sequence === '\u001B[B' || sequence === '\u001B[C') {
|
|
535
|
-
selected = 1;
|
|
615
|
+
selected = (selected + 1) % actions.length;
|
|
536
616
|
pendingInput = pendingInput.slice(sequence.length);
|
|
537
617
|
renderActions(true);
|
|
538
618
|
continue;
|
|
@@ -560,7 +640,7 @@ function promptUpdateAction({ currentVersion, release, input, output, processRef
|
|
|
560
640
|
}
|
|
561
641
|
output.write(`\nNeues Update verfügbar (${currentVersion} -> ${release.version})\n`);
|
|
562
642
|
if (release.notesUrl) output.write(`Versionshinweise: ${release.notesUrl}\n`);
|
|
563
|
-
output.write(
|
|
643
|
+
output.write('\n');
|
|
564
644
|
cursorHidden = true;
|
|
565
645
|
output.write('\u001B[?25l');
|
|
566
646
|
renderActions(false);
|
|
@@ -1121,6 +1201,9 @@ async function runUpdateFlow(options, explicitUpdate) {
|
|
|
1121
1201
|
? explicitNoTarget(stdout, reason)
|
|
1122
1202
|
: { kind: 'continue', reason };
|
|
1123
1203
|
}
|
|
1204
|
+
if (!explicitUpdate && readSkippedVersion(blunDir) === release.version) {
|
|
1205
|
+
return { kind: 'continue', reason: 'skipped_version' };
|
|
1206
|
+
}
|
|
1124
1207
|
if (explicitUpdate) {
|
|
1125
1208
|
try {
|
|
1126
1209
|
ensurePrivateDirectory(blunDir);
|
|
@@ -1177,6 +1260,14 @@ async function runUpdateFlow(options, explicitUpdate) {
|
|
|
1177
1260
|
}
|
|
1178
1261
|
if (action !== 'install') {
|
|
1179
1262
|
if (installerSession?.ready) await installerSession.cancel();
|
|
1263
|
+
if (action === 'skip-version') {
|
|
1264
|
+
try {
|
|
1265
|
+
writeSkippedVersion(blunDir, release.version);
|
|
1266
|
+
} catch {
|
|
1267
|
+
// A failed preference write must not block the existing console.
|
|
1268
|
+
}
|
|
1269
|
+
return { kind: 'continue', reason: 'skipped_version' };
|
|
1270
|
+
}
|
|
1180
1271
|
try {
|
|
1181
1272
|
writeSnoozeState(blunDir, (options.now || Date.now)());
|
|
1182
1273
|
} catch {
|
|
@@ -1185,6 +1276,10 @@ async function runUpdateFlow(options, explicitUpdate) {
|
|
|
1185
1276
|
return { kind: 'continue', reason: 'postponed' };
|
|
1186
1277
|
}
|
|
1187
1278
|
|
|
1279
|
+
const updateProgress = createUpdateProgress(stdout);
|
|
1280
|
+
updateProgress(1, 'Update geprüft');
|
|
1281
|
+
updateProgress(2, 'Installer bereit');
|
|
1282
|
+
|
|
1188
1283
|
try {
|
|
1189
1284
|
lease.assertOwned();
|
|
1190
1285
|
} catch {
|
|
@@ -1193,6 +1288,7 @@ async function runUpdateFlow(options, explicitUpdate) {
|
|
|
1193
1288
|
: { kind: 'update_in_progress' };
|
|
1194
1289
|
}
|
|
1195
1290
|
let installResult;
|
|
1291
|
+
updateProgress(3, 'Paket wird installiert');
|
|
1196
1292
|
try {
|
|
1197
1293
|
installResult = options.installVersion
|
|
1198
1294
|
? await options.installVersion(release.version)
|
|
@@ -1208,6 +1304,7 @@ async function runUpdateFlow(options, explicitUpdate) {
|
|
|
1208
1304
|
: { kind: 'update_in_progress' };
|
|
1209
1305
|
}
|
|
1210
1306
|
if (installResult?.code === 0 && installResult?.verified === true) {
|
|
1307
|
+
updateProgress(4, 'Installation geprüft');
|
|
1211
1308
|
stdout.write('Update erfolgreich installiert. Starte BLUN Code neu, um die neue Version zu verwenden.\n');
|
|
1212
1309
|
return { kind: 'installed', version: release.version };
|
|
1213
1310
|
}
|
|
@@ -1258,10 +1355,12 @@ module.exports = {
|
|
|
1258
1355
|
parseFallbackManifest,
|
|
1259
1356
|
parseRegistryDocument,
|
|
1260
1357
|
promptUpdateAction,
|
|
1358
|
+
readSkippedVersion,
|
|
1261
1359
|
readSnoozeState,
|
|
1262
1360
|
requestTrustedJson,
|
|
1263
1361
|
resolveEffectiveRelease,
|
|
1264
1362
|
runExplicitUpdate,
|
|
1265
1363
|
runUpdateNotice,
|
|
1364
|
+
writeSkippedVersion,
|
|
1266
1365
|
writeSnoozeState,
|
|
1267
1366
|
};
|