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>
175 lines
4.9 KiB
JavaScript
175 lines
4.9 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const path = require('path');
|
|
const { spawnSync } = require('child_process');
|
|
|
|
const {
|
|
CLEAR_LINE,
|
|
FRAMES,
|
|
runAnimator,
|
|
startTerminalSpinner,
|
|
} = require('../../scripts/lib/terminal-spinner');
|
|
|
|
const spinnerModule = path.join(
|
|
__dirname,
|
|
'..',
|
|
'..',
|
|
'scripts',
|
|
'lib',
|
|
'terminal-spinner.js'
|
|
);
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
console.log(` ✓ ${name}`);
|
|
passed += 1;
|
|
} catch (error) {
|
|
console.log(` ✗ ${name}`);
|
|
console.log(` Error: ${error.message}`);
|
|
failed += 1;
|
|
}
|
|
}
|
|
|
|
console.log('\n=== Terminal spinner tests ===\n');
|
|
|
|
test('animator advances frames and exits when its parent disconnects', () => {
|
|
const writes = [];
|
|
let tick;
|
|
let disconnect;
|
|
let cleared;
|
|
let exitCode;
|
|
const timer = Symbol('timer');
|
|
|
|
runAnimator('Applying ECC setup...', {
|
|
clearSchedule: value => { cleared = value; },
|
|
exit: code => { exitCode = code; },
|
|
onDisconnect: handler => { disconnect = handler; },
|
|
output: { write: value => writes.push(value) },
|
|
schedule: (callback, interval) => {
|
|
assert.strictEqual(interval, 80);
|
|
tick = callback;
|
|
return timer;
|
|
},
|
|
});
|
|
|
|
tick();
|
|
tick();
|
|
assert.deepStrictEqual(writes, [
|
|
`\r${FRAMES[1]} Applying ECC setup...`,
|
|
`\r${FRAMES[2]} Applying ECC setup...`,
|
|
]);
|
|
disconnect();
|
|
assert.strictEqual(cleared, timer);
|
|
assert.strictEqual(exitCode, 0);
|
|
});
|
|
|
|
test('spinner renders immediately and clears again after animator termination', () => {
|
|
const writes = [];
|
|
const spawnCalls = [];
|
|
const handlers = {};
|
|
const animatorErrors = [];
|
|
let killCount = 0;
|
|
const child = {
|
|
kill: () => { killCount += 1; },
|
|
on: (event, handler) => {
|
|
assert.strictEqual(event, 'error');
|
|
handlers[event] = handler;
|
|
},
|
|
once: (event, handler) => { handlers[event] = handler; },
|
|
};
|
|
const spinner = startTerminalSpinner('Applying ECC setup...', {
|
|
onAnimatorError: error => animatorErrors.push(error.message),
|
|
output: { write: value => writes.push(value) },
|
|
spawnProcess: (...args) => {
|
|
spawnCalls.push(args);
|
|
return child;
|
|
},
|
|
});
|
|
|
|
assert.strictEqual(writes[0], `${FRAMES[0]} Applying ECC setup...`);
|
|
assert.strictEqual(spawnCalls.length, 1);
|
|
assert.strictEqual(spawnCalls[0][0], process.execPath);
|
|
assert.deepStrictEqual(spawnCalls[0][1].slice(1), [
|
|
'--animate',
|
|
'Applying ECC setup...',
|
|
]);
|
|
assert.strictEqual(typeof handlers.error, 'function');
|
|
handlers.error(new Error('animation unavailable'));
|
|
assert.deepStrictEqual(animatorErrors, ['animation unavailable']);
|
|
|
|
spinner.stop();
|
|
writes.push(`\r${FRAMES[2]} late frame`);
|
|
handlers.close();
|
|
spinner.stop();
|
|
assert.strictEqual(killCount, 1);
|
|
assert.strictEqual(writes.at(-1), CLEAR_LINE);
|
|
assert.deepStrictEqual(writes.slice(-3), [
|
|
CLEAR_LINE,
|
|
`\r${FRAMES[2]} late frame`,
|
|
CLEAR_LINE,
|
|
]);
|
|
});
|
|
|
|
test('spinner keeps a visible first frame when the animator cannot launch', () => {
|
|
const writes = [];
|
|
const spinner = startTerminalSpinner('Applying ECC setup...', {
|
|
output: { write: value => writes.push(value) },
|
|
spawnProcess: () => { throw new Error('spawn unavailable'); },
|
|
});
|
|
|
|
spinner.stop();
|
|
assert.deepStrictEqual(writes, [
|
|
`${FRAMES[0]} Applying ECC setup...`,
|
|
CLEAR_LINE,
|
|
]);
|
|
});
|
|
|
|
test('real animator advances independently and cannot write after cleanup', () => {
|
|
const source = `
|
|
const { startTerminalSpinner } = require(${JSON.stringify(spinnerModule)});
|
|
const spinner = startTerminalSpinner('Applying ECC setup...');
|
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250);
|
|
spinner.stop();
|
|
`;
|
|
const result = spawnSync(process.execPath, ['-e', source], {
|
|
encoding: 'utf8',
|
|
timeout: 3000,
|
|
});
|
|
|
|
assert.ifError(result.error);
|
|
assert.strictEqual(result.status, 0, result.stderr);
|
|
assert.match(result.stdout, new RegExp(`${FRAMES[0]} Applying ECC setup`));
|
|
assert.match(result.stdout, new RegExp(`${FRAMES[1]} Applying ECC setup`));
|
|
const clearIndex = result.stdout.lastIndexOf(CLEAR_LINE);
|
|
assert.ok(clearIndex > 0, 'real animator should clear its line');
|
|
assert.doesNotMatch(
|
|
result.stdout.slice(clearIndex + CLEAR_LINE.length),
|
|
/Applying ECC setup/,
|
|
'real animator should not render after cleanup'
|
|
);
|
|
});
|
|
|
|
test('real animator exits when its parent process disappears', () => {
|
|
const source = `
|
|
const { startTerminalSpinner } = require(${JSON.stringify(spinnerModule)});
|
|
startTerminalSpinner('Applying ECC setup...');
|
|
process.exit(0);
|
|
`;
|
|
const result = spawnSync(process.execPath, ['-e', source], {
|
|
encoding: 'utf8',
|
|
timeout: 3000,
|
|
});
|
|
|
|
assert.ifError(result.error);
|
|
assert.strictEqual(result.status, 0, result.stderr);
|
|
assert.match(result.stdout, new RegExp(`${FRAMES[0]} Applying ECC setup`));
|
|
});
|
|
|
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
|
process.exit(failed > 0 ? 1 : 0);
|