@voiden/runner 0.1.0-beta.5 → 2.1.0
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 +117 -95
- package/dist/cliElectron.d.ts +1 -1
- package/dist/index.js +197 -51
- package/dist/index.js.map +1 -1
- package/dist/plugins/community.d.ts +4 -3
- package/dist/plugins/community.d.ts.map +1 -1
- package/dist/plugins/community.js +23 -19
- package/dist/plugins/community.js.map +1 -1
- package/dist/plugins/loader.d.ts +8 -0
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +78 -7
- package/dist/plugins/loader.js.map +1 -1
- package/dist/plugins/registry.d.ts +26 -21
- package/dist/plugins/registry.d.ts.map +1 -1
- package/dist/plugins/registry.js +69 -46
- package/dist/plugins/registry.js.map +1 -1
- package/dist/plugins/registryCache.d.ts +28 -0
- package/dist/plugins/registryCache.d.ts.map +1 -0
- package/dist/plugins/registryCache.js +95 -0
- package/dist/plugins/registryCache.js.map +1 -0
- package/dist/plugins/store.d.ts +6 -1
- package/dist/plugins/store.d.ts.map +1 -1
- package/dist/plugins/store.js +21 -1
- package/dist/plugins/store.js.map +1 -1
- package/dist/plugins/updateCheck.d.ts +14 -0
- package/dist/plugins/updateCheck.d.ts.map +1 -0
- package/dist/plugins/updateCheck.js +46 -0
- package/dist/plugins/updateCheck.js.map +1 -0
- package/dist/report/mail.d.ts +1 -0
- package/dist/report/mail.d.ts.map +1 -1
- package/dist/report/mail.js +199 -35
- package/dist/report/mail.js.map +1 -1
- package/dist/runner.d.ts +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +19 -2
- package/dist/runner.js.map +1 -1
- package/dist/runtimeVars.d.ts +5 -0
- package/dist/runtimeVars.d.ts.map +1 -1
- package/dist/runtimeVars.js +36 -2
- package/dist/runtimeVars.js.map +1 -1
- package/package.json +10 -5
package/dist/index.js
CHANGED
|
@@ -9,9 +9,11 @@ import { runVoidFile } from './runner.js';
|
|
|
9
9
|
import { loadEnabledPlugins } from './plugins/loader.js';
|
|
10
10
|
import { exportToCsv } from './report/csv.js';
|
|
11
11
|
import { sendMailReport } from './report/mail.js';
|
|
12
|
-
import {
|
|
12
|
+
import { getCorePlugins, findPlugin } from './plugins/registry.js';
|
|
13
|
+
import { downloadCoreRunner } from './plugins/loader.js';
|
|
13
14
|
import { fetchCommunityPlugins, findCommunityPlugin, hasCommunityRunner, installCommunityRunner, } from './plugins/community.js';
|
|
14
|
-
import { installPlugin, uninstallPlugin, setPluginEnabled, getAllInstalledPlugins, readStore, STORE_DIR, } from './plugins/store.js';
|
|
15
|
+
import { installPlugin, uninstallPlugin, setPluginEnabled, setPluginVersion, getAllInstalledPlugins, readStore, STORE_DIR, } from './plugins/store.js';
|
|
16
|
+
import { checkForPluginUpdates } from './plugins/updateCheck.js';
|
|
15
17
|
import { appendSessionResults, loadSessionResults, clearSession, } from './session.js';
|
|
16
18
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
19
|
// Helpers
|
|
@@ -279,6 +281,29 @@ function printRunSummaryJson(results, totalMs, activePlugins) {
|
|
|
279
281
|
console.log(JSON.stringify(output, null, 2));
|
|
280
282
|
}
|
|
281
283
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
284
|
+
// Plugin update notices — surfaced after `run` and `plugin` commands so users
|
|
285
|
+
// learn about newer registry versions without having to run `plugin list`.
|
|
286
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
287
|
+
function printUpdateNotice(updates) {
|
|
288
|
+
if (updates.length === 0)
|
|
289
|
+
return;
|
|
290
|
+
console.log();
|
|
291
|
+
console.log(chalk.yellow(` ⬆ ${updates.length} plugin update${updates.length !== 1 ? 's' : ''} available`));
|
|
292
|
+
for (const u of updates) {
|
|
293
|
+
console.log(chalk.gray(` ${chalk.bold(u.id.padEnd(24))} ${u.installedVersion} → ${chalk.green(u.latestVersion)}`));
|
|
294
|
+
}
|
|
295
|
+
console.log(chalk.gray(` Run: voiden-runner plugin update --all`));
|
|
296
|
+
}
|
|
297
|
+
/** Best-effort update check — never throws, never blocks command output on failure. */
|
|
298
|
+
async function notifyPluginUpdates() {
|
|
299
|
+
try {
|
|
300
|
+
printUpdateNotice(await checkForPluginUpdates());
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
// Informational only — ignore failures (e.g. offline)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
282
307
|
// CLI
|
|
283
308
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
284
309
|
const pkgPath = resolve(join(dirname(fileURLToPath(import.meta.url)), '../package.json'));
|
|
@@ -308,7 +333,7 @@ program
|
|
|
308
333
|
.option('--fail-on-error', 'Exit with code 1 if any request fails (runs all files first)')
|
|
309
334
|
.option('--verbose', 'Print plugin and script logs')
|
|
310
335
|
.option('--json', 'Output results as JSON (suppresses normal output — useful for CI pipelines)')
|
|
311
|
-
.option('--no-session', 'Completely stateless run
|
|
336
|
+
.option('--no-session', 'Completely stateless run — no variables are loaded from disk, shared between files, or saved')
|
|
312
337
|
.option('--output-json <file>', 'Write the full result object to a JSON file — pass to the next CLI, script, or tool')
|
|
313
338
|
.option('--csv <path>', 'Export full report (request + response headers, bodies, assertions) to a CSV file')
|
|
314
339
|
.option('--mail', 'Send HTML report to address specified in VOIDEN_MAIL_TO env')
|
|
@@ -383,10 +408,6 @@ program
|
|
|
383
408
|
console.error(chalk.red(' ✗ Mail error: no recipient found. Please provide --mail-to or set VOIDEN_MAIL_TO.'));
|
|
384
409
|
process.exit(1);
|
|
385
410
|
}
|
|
386
|
-
if (!opts.csv) {
|
|
387
|
-
console.error(chalk.red(' ✗ Mail error: no CSV generated. Mail requires --csv flag.'));
|
|
388
|
-
process.exit(1);
|
|
389
|
-
}
|
|
390
411
|
if (!smtpHost) {
|
|
391
412
|
console.error(chalk.red(' ✗ Mail keys are missing. Please provide SMTP configuration (VOIDEN_SMTP_HOST).'));
|
|
392
413
|
process.exit(1);
|
|
@@ -401,7 +422,7 @@ program
|
|
|
401
422
|
const runtimeVars = {};
|
|
402
423
|
// Load persisted runtime variables if not disabled
|
|
403
424
|
const VARS_PATH = join(STORE_DIR, '.process.env.json');
|
|
404
|
-
if (
|
|
425
|
+
if (opts.session && existsSync(VARS_PATH)) {
|
|
405
426
|
try {
|
|
406
427
|
const data = JSON.parse(readFileSync(VARS_PATH, 'utf-8'));
|
|
407
428
|
Object.assign(runtimeVars, data);
|
|
@@ -419,7 +440,10 @@ program
|
|
|
419
440
|
const file = resolvedFiles[i];
|
|
420
441
|
const stopSpinner = opts.json ? () => { } : startSpinner(`[${i + 1}/${resolvedFiles.length}] ${basename(file)}`);
|
|
421
442
|
try {
|
|
422
|
-
|
|
443
|
+
// --no-session: each file is fully isolated — no vars flow from one file to another.
|
|
444
|
+
// Session mode: all files share runtimeVars so captured vars chain across files.
|
|
445
|
+
const fileVars = opts.session ? runtimeVars : {};
|
|
446
|
+
const { results } = await runVoidFile(file, { env, verbose: opts.verbose, runtimeVars: fileVars, activePlugins });
|
|
423
447
|
stopSpinner();
|
|
424
448
|
for (const { result } of results) {
|
|
425
449
|
if (!result.success)
|
|
@@ -450,12 +474,12 @@ program
|
|
|
450
474
|
}
|
|
451
475
|
}
|
|
452
476
|
// Save session results if not disabled
|
|
453
|
-
if (
|
|
477
|
+
if (opts.session) {
|
|
454
478
|
appendSessionResults(allResults);
|
|
455
479
|
}
|
|
456
480
|
const totalMs = Date.now() - runStart;
|
|
457
481
|
// Save runtime variables if not disabled
|
|
458
|
-
if (
|
|
482
|
+
if (opts.session && Object.keys(runtimeVars).length > 0) {
|
|
459
483
|
try {
|
|
460
484
|
mkdirSync(STORE_DIR, { recursive: true });
|
|
461
485
|
writeFileSync(VARS_PATH, JSON.stringify(runtimeVars, null, 2), 'utf-8');
|
|
@@ -489,6 +513,30 @@ program
|
|
|
489
513
|
console.error(chalk.red(` ✗ Failed to write CSV: ${err?.message ?? String(err)}`));
|
|
490
514
|
}
|
|
491
515
|
}
|
|
516
|
+
// ── Output JSON to file (before mail so it can be attached) ──────────────
|
|
517
|
+
let savedJsonPath;
|
|
518
|
+
if (opts.outputJson) {
|
|
519
|
+
const jsonData = {
|
|
520
|
+
summary: {
|
|
521
|
+
total: allResults.length,
|
|
522
|
+
passed: allResults.filter(r => r.result.success).length,
|
|
523
|
+
failed: allResults.filter(r => !r.result.success).length,
|
|
524
|
+
totalDurationMs: totalMs,
|
|
525
|
+
activePlugins,
|
|
526
|
+
},
|
|
527
|
+
requests: allResults.map(r => ({ file: r.file, ...r.result })),
|
|
528
|
+
};
|
|
529
|
+
try {
|
|
530
|
+
mkdirSync(dirname(opts.outputJson), { recursive: true });
|
|
531
|
+
writeFileSync(opts.outputJson, JSON.stringify(jsonData, null, 2) + '\n', 'utf-8');
|
|
532
|
+
savedJsonPath = opts.outputJson;
|
|
533
|
+
if (!opts.json)
|
|
534
|
+
console.log(chalk.gray(` ↳ Results written to ${opts.outputJson}`));
|
|
535
|
+
}
|
|
536
|
+
catch (err) {
|
|
537
|
+
console.error(chalk.red(` ✗ Failed to write JSON: ${err?.message ?? String(err)}`));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
492
540
|
// ── Email report ──────────────────────────────────────────────────────────
|
|
493
541
|
if (mailTo) {
|
|
494
542
|
console.log(chalk.gray(` ↑ Sending report to ${mailTo} …`));
|
|
@@ -503,6 +551,7 @@ program
|
|
|
503
551
|
smtpUser: smtpUser,
|
|
504
552
|
smtpPass: smtpPass,
|
|
505
553
|
csvPath: savedCsvPath,
|
|
554
|
+
jsonPath: savedJsonPath,
|
|
506
555
|
});
|
|
507
556
|
console.log(chalk.green(` ✓ Report sent to ${mailTo}`));
|
|
508
557
|
}
|
|
@@ -510,23 +559,6 @@ program
|
|
|
510
559
|
console.error(chalk.red(` ✗ Failed to send email: ${err?.message ?? String(err)}`));
|
|
511
560
|
}
|
|
512
561
|
}
|
|
513
|
-
// ── Output JSON to file ───────────────────────────────────────────────────
|
|
514
|
-
if (opts.outputJson) {
|
|
515
|
-
const jsonData = {
|
|
516
|
-
summary: {
|
|
517
|
-
total: allResults.length,
|
|
518
|
-
passed: allResults.filter(r => r.result.success).length,
|
|
519
|
-
failed: allResults.filter(r => !r.result.success).length,
|
|
520
|
-
totalDurationMs: Date.now() - runStart,
|
|
521
|
-
activePlugins,
|
|
522
|
-
},
|
|
523
|
-
requests: allResults.map(r => ({ file: r.file, ...r.result })),
|
|
524
|
-
};
|
|
525
|
-
mkdirSync(dirname(opts.outputJson), { recursive: true });
|
|
526
|
-
writeFileSync(opts.outputJson, JSON.stringify(jsonData, null, 2) + '\n', 'utf-8');
|
|
527
|
-
if (!opts.json)
|
|
528
|
-
console.log(chalk.gray(` ↳ Results written to ${opts.outputJson}`));
|
|
529
|
-
}
|
|
530
562
|
const shouldFail = (opts.failOnError || stopOnFailure) && anyFailed;
|
|
531
563
|
if (shouldFail && !opts.json) {
|
|
532
564
|
const failedCount = allResults.filter(r => !r.result.success).length;
|
|
@@ -534,6 +566,9 @@ program
|
|
|
534
566
|
console.log(chalk.gray(' (use this exit code in your shell script to abort on failure)'));
|
|
535
567
|
console.log();
|
|
536
568
|
}
|
|
569
|
+
// Surface plugin update notices — skipped in --json mode so output stays machine-readable
|
|
570
|
+
if (!opts.json)
|
|
571
|
+
await notifyPluginUpdates();
|
|
537
572
|
process.exit(shouldFail ? 1 : 0);
|
|
538
573
|
});
|
|
539
574
|
// ── voiden-runner session ─────────────────────────────────────────────────────
|
|
@@ -635,8 +670,9 @@ reportCmd
|
|
|
635
670
|
.alias('gen')
|
|
636
671
|
.option('-e, --env <path>', 'Path to .env or .yaml file for SMTP configuration')
|
|
637
672
|
.option('--csv <path>', 'Export session results to a CSV file')
|
|
638
|
-
.option('--
|
|
639
|
-
.option('--mail
|
|
673
|
+
.option('--output-json <file>', 'Write full result object to a JSON file (also attached to email if --mail is used)')
|
|
674
|
+
.option('--mail', 'Send HTML report to VOIDEN_MAIL_TO (attaches --csv and/or --output-json if provided)')
|
|
675
|
+
.option('--mail-to <address>', 'Send HTML report to this email address (attaches --csv and/or --output-json if provided)')
|
|
640
676
|
.option('--mail-from <address>', 'Sender address for the report email')
|
|
641
677
|
.option('--mail-subject <subject>', 'Email subject line')
|
|
642
678
|
.option('--smtp-host <host>', 'SMTP server host')
|
|
@@ -669,18 +705,14 @@ reportCmd
|
|
|
669
705
|
console.error(chalk.red(' ✗ Mail error: no recipient found. Please provide --mail-to or set VOIDEN_MAIL_TO.'));
|
|
670
706
|
process.exit(1);
|
|
671
707
|
}
|
|
672
|
-
if (!opts.csv) {
|
|
673
|
-
console.error(chalk.red(' ✗ Mail error: no CSV generated. Mail requires --csv flag.'));
|
|
674
|
-
process.exit(1);
|
|
675
|
-
}
|
|
676
708
|
const smtpHost = opts.smtpHost || env.VOIDEN_SMTP_HOST;
|
|
677
709
|
if (!smtpHost) {
|
|
678
710
|
console.error(chalk.red(' ✗ Mail keys are missing. Please provide SMTP configuration (VOIDEN_SMTP_HOST).'));
|
|
679
711
|
process.exit(1);
|
|
680
712
|
}
|
|
681
713
|
}
|
|
682
|
-
if (!opts.csv && !mailTo) {
|
|
683
|
-
console.log(chalk.gray(` Session has ${results.length} accumulated results. Specify --csv to generate a report.`));
|
|
714
|
+
if (!opts.csv && !opts.outputJson && !mailTo) {
|
|
715
|
+
console.log(chalk.gray(` Session has ${results.length} accumulated results. Specify --csv or --output-json to generate a report.`));
|
|
684
716
|
return;
|
|
685
717
|
}
|
|
686
718
|
let savedCsvPath;
|
|
@@ -693,6 +725,26 @@ reportCmd
|
|
|
693
725
|
console.error(chalk.red(` ✗ Failed to write CSV: ${err?.message ?? String(err)}`));
|
|
694
726
|
}
|
|
695
727
|
}
|
|
728
|
+
let savedJsonPath;
|
|
729
|
+
if (opts.outputJson) {
|
|
730
|
+
const jsonData = {
|
|
731
|
+
summary: {
|
|
732
|
+
total: results.length,
|
|
733
|
+
passed: results.filter(r => r.result.success).length,
|
|
734
|
+
failed: results.filter(r => !r.result.success).length,
|
|
735
|
+
},
|
|
736
|
+
requests: results.map(r => ({ file: r.file, ...r.result })),
|
|
737
|
+
};
|
|
738
|
+
try {
|
|
739
|
+
mkdirSync(dirname(opts.outputJson), { recursive: true });
|
|
740
|
+
writeFileSync(opts.outputJson, JSON.stringify(jsonData, null, 2) + '\n', 'utf-8');
|
|
741
|
+
savedJsonPath = opts.outputJson;
|
|
742
|
+
console.log(chalk.gray(` ↳ Results written to ${opts.outputJson}`));
|
|
743
|
+
}
|
|
744
|
+
catch (err) {
|
|
745
|
+
console.error(chalk.red(` ✗ Failed to write JSON: ${err?.message ?? String(err)}`));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
696
748
|
if (mailTo) {
|
|
697
749
|
// SMTP settings — read from CLI options or environment
|
|
698
750
|
const smtpHost = opts.smtpHost || env.VOIDEN_SMTP_HOST || process.env.VOIDEN_SMTP_HOST;
|
|
@@ -717,6 +769,7 @@ reportCmd
|
|
|
717
769
|
smtpUser: smtpUser,
|
|
718
770
|
smtpPass: smtpPass,
|
|
719
771
|
csvPath: savedCsvPath,
|
|
772
|
+
jsonPath: savedJsonPath,
|
|
720
773
|
});
|
|
721
774
|
console.log(chalk.green(` ✓ Report sent to ${mailTo}`));
|
|
722
775
|
}
|
|
@@ -740,13 +793,14 @@ pluginCmd
|
|
|
740
793
|
' voiden-runner plugin install apyhub-explorer\n')
|
|
741
794
|
.option('--all', 'Install all core plugins (community plugins must be installed by name)')
|
|
742
795
|
.action(async (names, opts) => {
|
|
796
|
+
const corePlugins = await getCorePlugins();
|
|
743
797
|
const communityPlugins = await fetchCommunityPlugins();
|
|
744
798
|
const targets = opts.all
|
|
745
|
-
?
|
|
799
|
+
? corePlugins.map(p => p.name)
|
|
746
800
|
: names;
|
|
747
801
|
if (targets.length === 0) {
|
|
748
802
|
console.error(chalk.red('Specify plugin name(s) or use --all'));
|
|
749
|
-
console.log(chalk.gray(' Core: ' +
|
|
803
|
+
console.log(chalk.gray(' Core: ' + corePlugins.map(p => p.name).join(', ')));
|
|
750
804
|
if (communityPlugins.length > 0) {
|
|
751
805
|
console.log(chalk.gray(' Community (install by name): ' + communityPlugins.map(p => p.id).join(', ')));
|
|
752
806
|
}
|
|
@@ -754,7 +808,7 @@ pluginCmd
|
|
|
754
808
|
}
|
|
755
809
|
let installedCount = 0;
|
|
756
810
|
for (const name of targets) {
|
|
757
|
-
const coreDef = findPlugin(name);
|
|
811
|
+
const coreDef = await findPlugin(name);
|
|
758
812
|
const commDef = !coreDef ? findCommunityPlugin(name, communityPlugins) : undefined;
|
|
759
813
|
if (!coreDef && !commDef) {
|
|
760
814
|
console.log(chalk.yellow(` ⚠ Unknown plugin "${name}" — skipped`));
|
|
@@ -769,6 +823,7 @@ pluginCmd
|
|
|
769
823
|
process.stdout.write('\r' + chalk.yellow(` ⚠ No runner.js in release for "${name}" — skipped\n`));
|
|
770
824
|
continue;
|
|
771
825
|
}
|
|
826
|
+
setPluginVersion(name, commDef.version);
|
|
772
827
|
process.stdout.write('\r' + ' '.repeat(60) + '\r'); // clear the line
|
|
773
828
|
}
|
|
774
829
|
catch (err) {
|
|
@@ -777,7 +832,7 @@ pluginCmd
|
|
|
777
832
|
}
|
|
778
833
|
}
|
|
779
834
|
const description = coreDef ? coreDef.description : commDef.description;
|
|
780
|
-
const fresh = installPlugin(name);
|
|
835
|
+
const fresh = installPlugin(name, coreDef?.version ?? commDef?.version);
|
|
781
836
|
if (fresh) {
|
|
782
837
|
console.log(chalk.green(` ✓ Installed`) + chalk.bold(` ${name}`) + chalk.gray(` — ${description}`));
|
|
783
838
|
installedCount++;
|
|
@@ -790,6 +845,80 @@ pluginCmd
|
|
|
790
845
|
console.log();
|
|
791
846
|
console.log(chalk.gray(` ${installedCount} plugin(s) installed. State saved to ~/.voiden/plugins.json`));
|
|
792
847
|
}
|
|
848
|
+
await notifyPluginUpdates();
|
|
849
|
+
});
|
|
850
|
+
// voiden-runner plugin update [names...] --all
|
|
851
|
+
pluginCmd
|
|
852
|
+
.command('update [names...]')
|
|
853
|
+
.description('Update installed plugins to the latest registry version\n\n' +
|
|
854
|
+
' --all updates every installed plugin that has a newer version available.\n\n' +
|
|
855
|
+
' Examples:\n' +
|
|
856
|
+
' voiden-runner plugin update --all\n' +
|
|
857
|
+
' voiden-runner plugin update voiden-scripting\n' +
|
|
858
|
+
' voiden-runner plugin update apyhub-explorer voiden-scripting\n')
|
|
859
|
+
.option('--all', 'Update every installed plugin that has a newer version available')
|
|
860
|
+
.action(async (names, opts) => {
|
|
861
|
+
const updates = await checkForPluginUpdates();
|
|
862
|
+
if (updates.length === 0) {
|
|
863
|
+
console.log(chalk.gray(' All installed plugins are up to date.'));
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const targets = opts.all ? updates.map(u => u.id) : names;
|
|
867
|
+
if (targets.length === 0) {
|
|
868
|
+
console.log(chalk.yellow(` ${updates.length} update${updates.length !== 1 ? 's' : ''} available — specify plugin name(s) or use --all:`));
|
|
869
|
+
printUpdateNotice(updates);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const corePlugins = await getCorePlugins();
|
|
873
|
+
const communityPlugins = await fetchCommunityPlugins();
|
|
874
|
+
let updatedCount = 0;
|
|
875
|
+
for (const name of targets) {
|
|
876
|
+
const update = updates.find(u => u.id === name);
|
|
877
|
+
if (!update) {
|
|
878
|
+
const known = readStore().installedPlugins[name];
|
|
879
|
+
console.log(known
|
|
880
|
+
? chalk.gray(` · ${name} is already up to date — skipped`)
|
|
881
|
+
: chalk.yellow(` ⚠ Plugin "${name}" is not installed — skipped`));
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
process.stdout.write(` ↓ Updating ${chalk.bold(name)} to v${update.latestVersion} …`);
|
|
885
|
+
try {
|
|
886
|
+
if (update.type === 'core') {
|
|
887
|
+
const def = corePlugins.find(p => p.name === name);
|
|
888
|
+
if (!def)
|
|
889
|
+
throw new Error('plugin no longer in core registry');
|
|
890
|
+
const ok = await downloadCoreRunner(def.name, def.repo, def.runnerAsset, def.version, false);
|
|
891
|
+
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
892
|
+
if (!ok) {
|
|
893
|
+
console.log(chalk.red(` ✗ No "${def.runnerAsset}" asset in release v${update.latestVersion} for "${name}"`));
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
const def = communityPlugins.find(p => p.id === name);
|
|
899
|
+
if (!def)
|
|
900
|
+
throw new Error('plugin no longer in community registry');
|
|
901
|
+
const result = await installCommunityRunner(def);
|
|
902
|
+
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
903
|
+
if (result === 'no-runner') {
|
|
904
|
+
console.log(chalk.red(` ✗ No runner.js in release v${update.latestVersion} for "${name}"`));
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
setPluginVersion(name, update.latestVersion);
|
|
908
|
+
}
|
|
909
|
+
console.log(chalk.green(` ✓ Updated`) + chalk.bold(` ${name}`) +
|
|
910
|
+
chalk.gray(` ${update.installedVersion} → ${update.latestVersion}`));
|
|
911
|
+
updatedCount++;
|
|
912
|
+
}
|
|
913
|
+
catch (err) {
|
|
914
|
+
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
915
|
+
console.log(chalk.red(` ✗ Failed to update "${name}": ${err?.message ?? String(err)}`));
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (updatedCount > 0) {
|
|
919
|
+
console.log();
|
|
920
|
+
console.log(chalk.gray(` ${updatedCount} plugin(s) updated.`));
|
|
921
|
+
}
|
|
793
922
|
});
|
|
794
923
|
// voiden-runner plugin uninstall <name>
|
|
795
924
|
pluginCmd
|
|
@@ -854,31 +983,34 @@ pluginCmd
|
|
|
854
983
|
' voiden-runner plugin disable voiden-scripting\n' +
|
|
855
984
|
' voiden-runner plugin disable --all\n')
|
|
856
985
|
.option('--all', 'Disable all plugins (core and community)')
|
|
857
|
-
.action((name, opts) => {
|
|
986
|
+
.action(async (name, opts) => {
|
|
987
|
+
const corePlugins = await getCorePlugins();
|
|
858
988
|
if (opts.all) {
|
|
859
989
|
// Disable all core plugins
|
|
860
|
-
for (const def of
|
|
990
|
+
for (const def of corePlugins) {
|
|
861
991
|
setPluginEnabled(def.name, false);
|
|
862
992
|
console.log(chalk.yellow(` · Disabled`) + ` ${def.name}`);
|
|
863
993
|
}
|
|
864
994
|
// Disable all installed community plugins
|
|
865
995
|
const store = readStore();
|
|
866
|
-
const
|
|
996
|
+
const coreNames = new Set(corePlugins.map(p => p.name));
|
|
997
|
+
const communityNames = Object.keys(store.installedPlugins).filter(n => !coreNames.has(n));
|
|
867
998
|
for (const n of communityNames) {
|
|
868
999
|
setPluginEnabled(n, false);
|
|
869
1000
|
console.log(chalk.yellow(` · Disabled`) + ` ${n}`);
|
|
870
1001
|
}
|
|
871
|
-
const total =
|
|
1002
|
+
const total = corePlugins.length + communityNames.length;
|
|
872
1003
|
console.log(chalk.gray(` ${total} plugin(s) disabled.`));
|
|
873
1004
|
return;
|
|
874
1005
|
}
|
|
875
1006
|
if (!name) {
|
|
876
1007
|
console.error(chalk.red(' Specify a plugin name or use --all'));
|
|
877
1008
|
process.exit(1);
|
|
1009
|
+
return;
|
|
878
1010
|
}
|
|
879
1011
|
setPluginEnabled(name, false);
|
|
880
1012
|
console.log(chalk.yellow(` · Disabled`) + ` ${name}`);
|
|
881
|
-
if (findPlugin(name)) {
|
|
1013
|
+
if (await findPlugin(name)) {
|
|
882
1014
|
console.log(chalk.gray(` Core plugin disabled. Re-enable with: voiden-runner plugin enable ${name}`));
|
|
883
1015
|
}
|
|
884
1016
|
});
|
|
@@ -888,17 +1020,26 @@ pluginCmd
|
|
|
888
1020
|
.description('List all available and installed plugins')
|
|
889
1021
|
.action(async () => {
|
|
890
1022
|
const store = readStore();
|
|
1023
|
+
const corePlugins = await getCorePlugins();
|
|
891
1024
|
const communityPlugins = await fetchCommunityPlugins();
|
|
1025
|
+
// Per-plugin "update available" badge — compares the version recorded at
|
|
1026
|
+
// install/download time against the registry's current version.
|
|
1027
|
+
const updateBadge = (id, latestVersion) => {
|
|
1028
|
+
const installedVersion = store.installedPlugins[id]?.version;
|
|
1029
|
+
if (!installedVersion || installedVersion === latestVersion)
|
|
1030
|
+
return '';
|
|
1031
|
+
return chalk.yellow(` ⬆ v${latestVersion} available`);
|
|
1032
|
+
};
|
|
892
1033
|
console.log();
|
|
893
|
-
console.log(chalk.bold(' Core plugins') + chalk.gray(' (
|
|
1034
|
+
console.log(chalk.bold(' Core plugins') + chalk.gray(' (from VoidenHQ/plugin-registry)'));
|
|
894
1035
|
console.log(DIVIDER);
|
|
895
|
-
for (const def of
|
|
1036
|
+
for (const def of corePlugins) {
|
|
896
1037
|
const record = store.installedPlugins[def.name];
|
|
897
1038
|
const isDisabled = record !== undefined && !record.enabled;
|
|
898
1039
|
const statusBadge = isDisabled
|
|
899
1040
|
? chalk.yellow(' · disabled')
|
|
900
1041
|
: chalk.green(' ✓ enabled');
|
|
901
|
-
console.log(` ${chalk.bold(def.name.padEnd(24))}${statusBadge}`);
|
|
1042
|
+
console.log(` ${chalk.bold(def.name.padEnd(24))}${statusBadge}${updateBadge(def.name, def.version)}`);
|
|
902
1043
|
console.log(chalk.gray(` ${def.description}`));
|
|
903
1044
|
}
|
|
904
1045
|
// ── Community plugins ───────────────────────────────────────────────────
|
|
@@ -908,7 +1049,7 @@ pluginCmd
|
|
|
908
1049
|
console.log(DIVIDER);
|
|
909
1050
|
}
|
|
910
1051
|
else {
|
|
911
|
-
console.log(chalk.bold(' Community plugins') + chalk.gray(' (
|
|
1052
|
+
console.log(chalk.bold(' Community plugins') + chalk.gray(' (from VoidenHQ/plugin-registry)'));
|
|
912
1053
|
console.log(DIVIDER);
|
|
913
1054
|
for (const def of communityPlugins) {
|
|
914
1055
|
const installed = store.installedPlugins[def.id];
|
|
@@ -923,14 +1064,14 @@ pluginCmd
|
|
|
923
1064
|
statusBadge = chalk.yellow(' · disabled');
|
|
924
1065
|
}
|
|
925
1066
|
const runnerBadge = hasCommunityRunner(def.id) ? '' : chalk.gray(' [no runner]');
|
|
926
|
-
console.log(` ${chalk.bold(def.id.padEnd(24))}${statusBadge}${runnerBadge}` +
|
|
1067
|
+
console.log(` ${chalk.bold(def.id.padEnd(24))}${statusBadge}${runnerBadge}${updateBadge(def.id, def.version)}` +
|
|
927
1068
|
chalk.gray(` v${def.version}`) +
|
|
928
1069
|
chalk.gray(` by ${def.author}`));
|
|
929
1070
|
console.log(chalk.gray(` ${def.description}`));
|
|
930
1071
|
}
|
|
931
1072
|
}
|
|
932
1073
|
const knownIds = new Set([
|
|
933
|
-
...
|
|
1074
|
+
...corePlugins.map(p => p.name),
|
|
934
1075
|
...communityPlugins.map(p => p.id),
|
|
935
1076
|
]);
|
|
936
1077
|
const extras = getAllInstalledPlugins().filter(p => !knownIds.has(p.name));
|
|
@@ -943,6 +1084,11 @@ pluginCmd
|
|
|
943
1084
|
console.log(` ${chalk.bold(p.name.padEnd(24))}${badge}`);
|
|
944
1085
|
}
|
|
945
1086
|
}
|
|
1087
|
+
const updates = await checkForPluginUpdates();
|
|
1088
|
+
if (updates.length > 0) {
|
|
1089
|
+
console.log();
|
|
1090
|
+
console.log(chalk.gray(` Run: voiden-runner plugin update --all (${updates.length} update${updates.length !== 1 ? 's' : ''} available)`));
|
|
1091
|
+
}
|
|
946
1092
|
console.log();
|
|
947
1093
|
});
|
|
948
1094
|
program.parse();
|