mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-17 21:15:40 +02:00
* feat(install): add guided Claude plugin setup * fix: support Claude command shims on Windows * feat: support safe Claude plugin scope migration * fix(install): preserve interactive setup terminal * fix(install): auto-migrate setup scope changes * feat(install): add guided multi-harness installer * fix(install): sync Yarn binary metadata * fix(install): handle wizard EOF on Node 18 * ci: allow installer matrix tests to finish * test(install): allow slower PowerShell delegation * fix(install): harden guided provider reconciliation * test(install): harden packaged and local compatibility * chore: prepare guided installer release 2.2.0 * fix(install): report refreshed Codex marketplace state * fix(install): verify managed content provenance * test(install): allow empty Yarn smoke fixture * test(install): invoke Windows package shims safely * fix(install): close cross-platform release gaps * fix(install): require trusted GitHub origins * fix(install): preserve hook profile precedence * refactor(install): centralize trusted GitHub origins * ci: retrigger workflow run after merge of main Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
78 lines
2.1 KiB
JavaScript
78 lines
2.1 KiB
JavaScript
'use strict';
|
|
|
|
const { spawn } = require('child_process');
|
|
|
|
const CLEAR_LINE = '\r\x1b[2K';
|
|
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
const FRAME_INTERVAL_MS = 80;
|
|
|
|
function runAnimator(label, options = {}) {
|
|
const output = options.output || process.stdout;
|
|
const schedule = options.schedule || setInterval;
|
|
const clearSchedule = options.clearSchedule || clearInterval;
|
|
const onDisconnect = options.onDisconnect
|
|
|| (handler => process.on('disconnect', handler));
|
|
const exit = options.exit || (code => process.exit(code));
|
|
let frameIndex = 1;
|
|
const timer = schedule(() => {
|
|
output.write(`\r${FRAMES[frameIndex]} ${label}`);
|
|
frameIndex = (frameIndex + 1) % FRAMES.length;
|
|
}, FRAME_INTERVAL_MS);
|
|
|
|
onDisconnect(() => {
|
|
clearSchedule(timer);
|
|
exit(0);
|
|
});
|
|
}
|
|
|
|
function startTerminalSpinner(label, options = {}) {
|
|
const output = options.output || process.stdout;
|
|
const spawnProcess = options.spawnProcess || spawn;
|
|
const onAnimatorError = options.onAnimatorError;
|
|
output.write(`${FRAMES[0]} ${label}`);
|
|
|
|
let animator;
|
|
try {
|
|
animator = spawnProcess(
|
|
process.execPath,
|
|
[__filename, '--animate', label],
|
|
{
|
|
stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
|
|
}
|
|
);
|
|
animator.on?.('error', error => {
|
|
// Preserve a stable visible fallback if the child cannot animate.
|
|
output.write(`\r${FRAMES[0]} ${label}`);
|
|
onAnimatorError?.(error);
|
|
});
|
|
} catch {
|
|
// The first frame still provides visible progress if animation cannot start.
|
|
}
|
|
|
|
let stopped = false;
|
|
return {
|
|
stop() {
|
|
if (stopped) return;
|
|
stopped = true;
|
|
animator?.once?.('close', () => {
|
|
// A child can render between kill() and close; clear that final frame.
|
|
output.write(CLEAR_LINE);
|
|
});
|
|
animator?.kill();
|
|
output.write(CLEAR_LINE);
|
|
},
|
|
};
|
|
}
|
|
|
|
// c8 ignore next 3 -- exercised as the independently instrumented child process.
|
|
if (require.main === module && process.argv[2] === '--animate') {
|
|
runAnimator(process.argv[3] || 'Working...');
|
|
}
|
|
|
|
module.exports = {
|
|
CLEAR_LINE,
|
|
FRAMES,
|
|
runAnimator,
|
|
startTerminalSpinner,
|
|
};
|