mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 21:53:28 +08:00
* Add install.ps1 PowerShell wrapper and tests Add a Windows-native PowerShell wrapper (install.ps1) that resolves symlinks and delegates to the Node-based installer runtime. Update README with PowerShell usage examples and cross-platform npx entrypoint guidance. Point the ecc-install bin to the Node installer (scripts/install-apply.js) in package.json (and refresh package-lock), include install.ps1 in package files, and add tests: a new install-ps1.test.js and a tweak to install-sh.test.js to skip on Windows. These changes provide native Windows installer support while keeping npm-compatible cross-platform invocation. * Improve tests for Windows HOME/USERPROFILE Make tests more cross-platform by ensuring HOME and USERPROFILE are kept in sync and by normalizing test file paths for display. - tests/lib/session-adapters.test.js: set USERPROFILE when temporarily setting HOME and restore previous USERPROFILE on teardown. - tests/run-all.js: use a normalized displayPath (forward-slash separated) for logging and error messages so output is consistent across platforms. - tests/scripts/ecc.test.js & tests/scripts/session-inspect.test.js: build envOverrides from options.env and add HOME <-> USERPROFILE fallbacks so spawned child processes receive both variables when only one is provided. These changes prevent test failures and inconsistent logs on Windows where USERPROFILE is used instead of HOME. * Fix Windows paths and test flakiness Improve cross-platform behavior and test stability. - Remove unused createLegacyInstallPlan import from install-lifecycle.js. - Change resolveInstallConfigPath to use path.normalize(path.join(cwd, configPath)) to produce normalized relative paths. - Tests: add toBashPath and normalizedRelativePath helpers to normalize Windows paths for bash and comparisons. - Make cleanupTestDir retry rmSync on transient Windows errors (EPERM/EBUSY/ENOTEMPTY) with short backoff using sleepMs. - Ensure spawned test processes receive USERPROFILE and convert repo/detect paths to bash format when invoking bash. These changes reduce Windows-specific failures and flakiness in the test suite and tidy up a small unused import.
224 lines
7.6 KiB
JavaScript
224 lines
7.6 KiB
JavaScript
/**
|
|
* Tests for scripts/lib/install-targets/registry.js
|
|
*/
|
|
|
|
const assert = require('assert');
|
|
const path = require('path');
|
|
|
|
const {
|
|
getInstallTargetAdapter,
|
|
listInstallTargetAdapters,
|
|
planInstallTargetScaffold,
|
|
} = require('../../scripts/lib/install-targets/registry');
|
|
|
|
function normalizedRelativePath(value) {
|
|
return String(value || '').replace(/\\/g, '/');
|
|
}
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
console.log(` \u2713 ${name}`);
|
|
return true;
|
|
} catch (error) {
|
|
console.log(` \u2717 ${name}`);
|
|
console.log(` Error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function runTests() {
|
|
console.log('\n=== Testing install-target adapters ===\n');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
if (test('lists supported target adapters', () => {
|
|
const adapters = listInstallTargetAdapters();
|
|
const targets = adapters.map(adapter => adapter.target);
|
|
assert.ok(targets.includes('claude'), 'Should include claude target');
|
|
assert.ok(targets.includes('cursor'), 'Should include cursor target');
|
|
assert.ok(targets.includes('antigravity'), 'Should include antigravity target');
|
|
assert.ok(targets.includes('codex'), 'Should include codex target');
|
|
assert.ok(targets.includes('opencode'), 'Should include opencode target');
|
|
})) passed++; else failed++;
|
|
|
|
if (test('resolves cursor adapter root and install-state path from project root', () => {
|
|
const adapter = getInstallTargetAdapter('cursor');
|
|
const projectRoot = '/workspace/app';
|
|
const root = adapter.resolveRoot({ projectRoot });
|
|
const statePath = adapter.getInstallStatePath({ projectRoot });
|
|
|
|
assert.strictEqual(root, path.join(projectRoot, '.cursor'));
|
|
assert.strictEqual(statePath, path.join(projectRoot, '.cursor', 'ecc-install-state.json'));
|
|
})) passed++; else failed++;
|
|
|
|
if (test('resolves claude adapter root and install-state path from home dir', () => {
|
|
const adapter = getInstallTargetAdapter('claude');
|
|
const homeDir = '/Users/example';
|
|
const root = adapter.resolveRoot({ homeDir, repoRoot: '/repo/ecc' });
|
|
const statePath = adapter.getInstallStatePath({ homeDir, repoRoot: '/repo/ecc' });
|
|
|
|
assert.strictEqual(root, path.join(homeDir, '.claude'));
|
|
assert.strictEqual(statePath, path.join(homeDir, '.claude', 'ecc', 'install-state.json'));
|
|
})) passed++; else failed++;
|
|
|
|
if (test('plans scaffold operations and flattens native target roots', () => {
|
|
const repoRoot = path.join(__dirname, '..', '..');
|
|
const projectRoot = '/workspace/app';
|
|
const modules = [
|
|
{
|
|
id: 'platform-configs',
|
|
paths: ['.cursor', 'mcp-configs'],
|
|
},
|
|
{
|
|
id: 'rules-core',
|
|
paths: ['rules'],
|
|
},
|
|
];
|
|
|
|
const plan = planInstallTargetScaffold({
|
|
target: 'cursor',
|
|
repoRoot,
|
|
projectRoot,
|
|
modules,
|
|
});
|
|
|
|
assert.strictEqual(plan.adapter.id, 'cursor-project');
|
|
assert.strictEqual(plan.targetRoot, path.join(projectRoot, '.cursor'));
|
|
assert.strictEqual(plan.installStatePath, path.join(projectRoot, '.cursor', 'ecc-install-state.json'));
|
|
|
|
const flattened = plan.operations.find(operation => operation.sourceRelativePath === '.cursor');
|
|
const preserved = plan.operations.find(operation => (
|
|
normalizedRelativePath(operation.sourceRelativePath) === 'rules/common/coding-style.md'
|
|
));
|
|
|
|
assert.ok(flattened, 'Should include .cursor scaffold operation');
|
|
assert.strictEqual(flattened.strategy, 'sync-root-children');
|
|
assert.strictEqual(flattened.destinationPath, path.join(projectRoot, '.cursor'));
|
|
|
|
assert.ok(preserved, 'Should include flattened rules scaffold operations');
|
|
assert.strictEqual(preserved.strategy, 'flatten-copy');
|
|
assert.strictEqual(
|
|
preserved.destinationPath,
|
|
path.join(projectRoot, '.cursor', 'rules', 'common-coding-style.md')
|
|
);
|
|
})) passed++; else failed++;
|
|
|
|
if (test('plans cursor rules with flat namespaced filenames to avoid rule collisions', () => {
|
|
const repoRoot = path.join(__dirname, '..', '..');
|
|
const projectRoot = '/workspace/app';
|
|
|
|
const plan = planInstallTargetScaffold({
|
|
target: 'cursor',
|
|
repoRoot,
|
|
projectRoot,
|
|
modules: [
|
|
{
|
|
id: 'rules-core',
|
|
paths: ['rules'],
|
|
},
|
|
],
|
|
});
|
|
|
|
assert.ok(
|
|
plan.operations.some(operation => (
|
|
normalizedRelativePath(operation.sourceRelativePath) === 'rules/common/coding-style.md'
|
|
&& operation.destinationPath === path.join(projectRoot, '.cursor', 'rules', 'common-coding-style.md')
|
|
)),
|
|
'Should flatten common rules into namespaced files'
|
|
);
|
|
assert.ok(
|
|
plan.operations.some(operation => (
|
|
normalizedRelativePath(operation.sourceRelativePath) === 'rules/typescript/testing.md'
|
|
&& operation.destinationPath === path.join(projectRoot, '.cursor', 'rules', 'typescript-testing.md')
|
|
)),
|
|
'Should flatten language rules into namespaced files'
|
|
);
|
|
assert.ok(
|
|
!plan.operations.some(operation => (
|
|
operation.destinationPath === path.join(projectRoot, '.cursor', 'rules', 'common', 'coding-style.md')
|
|
)),
|
|
'Should not preserve nested rule directories for cursor installs'
|
|
);
|
|
})) passed++; else failed++;
|
|
|
|
if (test('plans antigravity remaps for workflows, skills, and flat rules', () => {
|
|
const repoRoot = path.join(__dirname, '..', '..');
|
|
const projectRoot = '/workspace/app';
|
|
|
|
const plan = planInstallTargetScaffold({
|
|
target: 'antigravity',
|
|
repoRoot,
|
|
projectRoot,
|
|
modules: [
|
|
{
|
|
id: 'commands-core',
|
|
paths: ['commands'],
|
|
},
|
|
{
|
|
id: 'agents-core',
|
|
paths: ['agents'],
|
|
},
|
|
{
|
|
id: 'rules-core',
|
|
paths: ['rules'],
|
|
},
|
|
],
|
|
});
|
|
|
|
assert.ok(
|
|
plan.operations.some(operation => (
|
|
operation.sourceRelativePath === 'commands'
|
|
&& operation.destinationPath === path.join(projectRoot, '.agent', 'workflows')
|
|
)),
|
|
'Should remap commands into workflows'
|
|
);
|
|
assert.ok(
|
|
plan.operations.some(operation => (
|
|
operation.sourceRelativePath === 'agents'
|
|
&& operation.destinationPath === path.join(projectRoot, '.agent', 'skills')
|
|
)),
|
|
'Should remap agents into skills'
|
|
);
|
|
assert.ok(
|
|
plan.operations.some(operation => (
|
|
normalizedRelativePath(operation.sourceRelativePath) === 'rules/common/coding-style.md'
|
|
&& operation.destinationPath === path.join(projectRoot, '.agent', 'rules', 'common-coding-style.md')
|
|
)),
|
|
'Should flatten common rules for antigravity'
|
|
);
|
|
})) passed++; else failed++;
|
|
|
|
if (test('exposes validate and planOperations on adapters', () => {
|
|
const claudeAdapter = getInstallTargetAdapter('claude');
|
|
const cursorAdapter = getInstallTargetAdapter('cursor');
|
|
|
|
assert.strictEqual(typeof claudeAdapter.planOperations, 'function');
|
|
assert.strictEqual(typeof claudeAdapter.validate, 'function');
|
|
assert.deepStrictEqual(
|
|
claudeAdapter.validate({ homeDir: '/Users/example', repoRoot: '/repo/ecc' }),
|
|
[]
|
|
);
|
|
|
|
assert.strictEqual(typeof cursorAdapter.planOperations, 'function');
|
|
assert.strictEqual(typeof cursorAdapter.validate, 'function');
|
|
assert.deepStrictEqual(
|
|
cursorAdapter.validate({ projectRoot: '/workspace/app', repoRoot: '/repo/ecc' }),
|
|
[]
|
|
);
|
|
})) passed++; else failed++;
|
|
|
|
if (test('throws on unknown target adapter', () => {
|
|
assert.throws(
|
|
() => getInstallTargetAdapter('ghost-target'),
|
|
/Unknown install target adapter/
|
|
);
|
|
})) passed++; else failed++;
|
|
|
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
}
|
|
|
|
runTests();
|