Files
28e53a0bc1 feat(install): add guided multi-harness installer (#2649)
* 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>
2026-08-06 15:39:49 -04:00

120 lines
3.8 KiB
JavaScript

'use strict';
const assert = require('assert');
const path = require('path');
const { spawnSync } = require('child_process');
const { version } = require('../../package.json');
const repoRoot = path.resolve(__dirname, '..', '..');
const eccScript = path.join(repoRoot, 'scripts', 'ecc.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;
}
}
function runEcc(args, env = {}) {
return spawnSync(process.execPath, [eccScript, ...args], {
cwd: repoRoot,
encoding: 'utf8',
env: { ...process.env, NO_COLOR: '1', ...env },
});
}
function containsTerminalControlBytes(value) {
return Array.from(value).some(character => {
const codePoint = character.codePointAt(0);
return codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f);
});
}
console.log('\n=== ECC welcome command tests ===\n');
test('ecc welcome renders the install artwork for captured agent output', () => {
const result = runEcc(['welcome']);
assert.strictEqual(result.status, 0, result.stderr);
assert.match(result.stdout, /Welcome to ECC!/);
assert.ok(result.stdout.includes(`v${version}`));
assert.match(result.stdout, /GitHub:\s+https:\/\/github\.com\/affaan-m\/ECC/);
assert.match(result.stdout, /Discord:\s+https:\/\/discord\.gg\/36yGMHGFbR/);
assert.strictEqual(result.stderr, '');
});
test('ecc welcome disables ANSI color when stdout is redirected', () => {
const env = { ...process.env, TERM: 'xterm-256color' };
delete env.NO_COLOR;
const result = spawnSync(process.execPath, [eccScript, 'welcome'], {
cwd: repoRoot,
encoding: 'utf8',
env,
});
assert.strictEqual(result.status, 0, result.stderr);
assert.strictEqual(result.stdout.includes('\u001b['), false);
});
test('ecc welcome supports explicit update and configured outcomes', () => {
const cases = [
['updated', /ECC is updated/],
['configured', /ECC is configured/],
['migrated', /ECC is configured/],
['resumed', /ECC is configured/],
['already-migrated', /ECC is configured/],
];
for (const [action, expected] of cases) {
const result = runEcc(['welcome', '--action', action]);
assert.strictEqual(result.status, 0, result.stderr);
assert.match(result.stdout, expected);
}
});
test('ecc welcome renders a provider-verified installed version', () => {
const result = runEcc(['welcome', '--version', '2.1.0']);
assert.strictEqual(result.status, 0, result.stderr);
assert.match(result.stdout, /v2\.1\.0/);
});
test('ecc welcome rejects unsafe version text', () => {
const result = runEcc(['welcome', '--version', '2.1.0\u001b[31m']);
assert.strictEqual(result.status, 1);
assert.match(result.stderr, /Invalid --version value/);
assert.strictEqual(containsTerminalControlBytes(result.stderr.trimEnd()), false);
assert.strictEqual(result.stdout, '');
});
test('ecc welcome keeps parser error output free of terminal control bytes', () => {
const actionResult = runEcc(['welcome', '--action', 'broken\u001b[31m']);
const argumentResult = runEcc(['welcome', '--bad\u001b[31m']);
for (const result of [actionResult, argumentResult]) {
assert.strictEqual(result.status, 1);
assert.strictEqual(containsTerminalControlBytes(result.stderr.trimEnd()), false);
assert.strictEqual(result.stdout, '');
}
});
test('ecc welcome rejects unknown actions before rendering', () => {
const result = runEcc(['welcome', '--action', 'broken']);
assert.strictEqual(result.status, 1);
assert.match(result.stderr, /Invalid --action value/);
assert.strictEqual(result.stdout, '');
});
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}\n`);
if (failed > 0) process.exit(1);