mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-30 22:13:28 +08:00
386 lines
16 KiB
JavaScript
386 lines
16 KiB
JavaScript
/**
|
|
* Direct tests for scripts/lib/install-executor.js.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
const {
|
|
applyInstallPlan,
|
|
createLegacyCompatInstallPlan,
|
|
createLegacyInstallPlan,
|
|
createManifestInstallPlan,
|
|
listAvailableLanguages,
|
|
} = require('../../scripts/lib/install-executor');
|
|
|
|
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
|
|
function createTempDir(prefix) {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
}
|
|
|
|
function cleanup(dirPath) {
|
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
}
|
|
|
|
function writeFile(root, relativePath, content = '') {
|
|
const filePath = path.join(root, relativePath);
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
return filePath;
|
|
}
|
|
|
|
function writeJson(root, relativePath, value) {
|
|
writeFile(root, relativePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
}
|
|
|
|
function operationFor(plan, suffix) {
|
|
return plan.operations.find(operation => (
|
|
operation.destinationPath.endsWith(suffix)
|
|
|| operation.sourceRelativePath.split(path.sep).join('/').endsWith(suffix.split(path.sep).join('/'))
|
|
));
|
|
}
|
|
|
|
function writeLegacySourceFixture(root) {
|
|
writeJson(root, 'package.json', { version: '9.8.7' });
|
|
writeFile(root, path.join('rules', 'common', 'coding-style.md'), '# Common\n');
|
|
writeFile(root, path.join('rules', 'common', 'nested', 'shared.md'), '# Shared\n');
|
|
writeFile(root, path.join('rules', 'common', 'node_modules', 'ignored.md'), '# Ignored\n');
|
|
writeFile(root, path.join('rules', 'common', '.git', 'ignored.md'), '# Ignored\n');
|
|
writeFile(root, path.join('rules', 'typescript', 'testing.md'), '# TS\n');
|
|
writeFile(root, path.join('rules', 'python', 'testing.md'), '# Python\n');
|
|
|
|
writeFile(root, path.join('.cursor', 'rules', 'common-style.md'), '# Cursor common\n');
|
|
writeFile(root, path.join('.cursor', 'rules', 'typescript-style.md'), '# Cursor TS\n');
|
|
writeFile(root, path.join('.cursor', 'rules', 'python-style.txt'), '# Not markdown\n');
|
|
writeFile(root, path.join('.cursor', 'agents', 'planner.md'), '# Planner\n');
|
|
writeFile(root, path.join('.cursor', 'skills', 'demo', 'SKILL.md'), '# Demo\n');
|
|
writeFile(root, path.join('.cursor', 'commands', 'plan.md'), '# Plan\n');
|
|
writeFile(root, path.join('.cursor', 'hooks', 'hook.js'), 'process.exit(0);\n');
|
|
writeJson(root, path.join('.cursor', 'hooks.json'), { version: 1, hooks: {} });
|
|
writeJson(root, '.mcp.json', { mcpServers: { github: { command: 'github-mcp' } } });
|
|
|
|
writeFile(root, path.join('commands', 'plan.md'), '# Plan\n');
|
|
writeFile(root, path.join('agents', 'architect.md'), '# Architect\n');
|
|
writeFile(root, path.join('skills', 'demo', 'SKILL.md'), '# Demo\n');
|
|
}
|
|
|
|
function writeManifestSourceFixture(root) {
|
|
writeJson(root, 'package.json', { version: '1.2.3' });
|
|
writeJson(root, path.join('manifests', 'install-modules.json'), {
|
|
version: 7,
|
|
modules: [
|
|
{
|
|
id: 'fixture-core',
|
|
kind: 'fixture',
|
|
description: 'Fixture module',
|
|
paths: [
|
|
'src',
|
|
'standalone.txt',
|
|
'missing.txt',
|
|
path.join('runtime', 'ecc', 'install-state.json'),
|
|
'.claude-plugin',
|
|
],
|
|
targets: ['claude'],
|
|
dependencies: [],
|
|
defaultInstall: true,
|
|
cost: 'light',
|
|
stability: 'stable',
|
|
},
|
|
],
|
|
});
|
|
writeJson(root, path.join('manifests', 'install-profiles.json'), {
|
|
version: 1,
|
|
profiles: {
|
|
minimal: {
|
|
description: 'Minimal fixture profile',
|
|
modules: ['fixture-core'],
|
|
},
|
|
},
|
|
});
|
|
writeFile(root, path.join('src', 'app.js'), 'console.log("app");\n');
|
|
writeFile(root, path.join('src', 'nested', 'feature.js'), 'console.log("feature");\n');
|
|
writeFile(root, path.join('src', 'node_modules', 'ignored.js'), 'console.log("ignored");\n');
|
|
writeFile(root, path.join('src', '.git', 'ignored.js'), 'console.log("ignored");\n');
|
|
writeFile(root, path.join('src', 'nested', 'ecc-install-state.json'), '{}\n');
|
|
writeFile(root, 'standalone.txt', 'standalone\n');
|
|
writeFile(root, path.join('runtime', 'ecc', 'install-state.json'), '{}\n');
|
|
writeJson(root, path.join('.claude-plugin', 'plugin.json'), { name: 'fixture' });
|
|
}
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
console.log(` PASS ${name}`);
|
|
return true;
|
|
} catch (error) {
|
|
console.log(` FAIL ${name}`);
|
|
console.log(` Error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function runTests() {
|
|
console.log('\n=== Testing install-executor.js ===\n');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
if (test('lists legacy and local rule languages while ignoring common', () => {
|
|
const sourceRoot = createTempDir('install-executor-source-');
|
|
try {
|
|
fs.mkdirSync(path.join(sourceRoot, 'rules', 'common'), { recursive: true });
|
|
fs.mkdirSync(path.join(sourceRoot, 'rules', 'zig'), { recursive: true });
|
|
|
|
const languages = listAvailableLanguages(sourceRoot);
|
|
|
|
assert.ok(languages.includes('typescript'));
|
|
assert.ok(languages.includes('zig'));
|
|
assert.ok(!languages.includes('common'));
|
|
assert.deepStrictEqual([...languages].sort(), languages);
|
|
} finally {
|
|
cleanup(sourceRoot);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('rejects unknown legacy install targets before planning', () => {
|
|
assert.throws(
|
|
() => createLegacyInstallPlan({ target: 'not-a-target' }),
|
|
/Unknown install target: not-a-target/
|
|
);
|
|
})) passed++; else failed++;
|
|
|
|
if (test('plans Claude legacy rules with warnings and state preview', () => {
|
|
const sourceRoot = createTempDir('install-executor-source-');
|
|
const homeDir = createTempDir('install-executor-home-');
|
|
const projectRoot = createTempDir('install-executor-project-');
|
|
const claudeRulesDir = path.join(homeDir, 'custom-rules');
|
|
try {
|
|
writeLegacySourceFixture(sourceRoot);
|
|
writeFile(homeDir, path.join('custom-rules', 'existing.md'), '# Existing\n');
|
|
|
|
const plan = createLegacyInstallPlan({
|
|
sourceRoot,
|
|
homeDir,
|
|
projectRoot,
|
|
claudeRulesDir,
|
|
target: 'claude',
|
|
languages: ['typescript', 'missing-lang', '../bad'],
|
|
});
|
|
|
|
assert.strictEqual(plan.mode, 'legacy');
|
|
assert.strictEqual(plan.target, 'claude');
|
|
assert.strictEqual(plan.installRoot, claudeRulesDir);
|
|
assert.ok(plan.warnings.some(warning => warning.includes('files may be overwritten')));
|
|
assert.ok(plan.warnings.some(warning => warning.includes("rules/missing-lang/ does not exist")));
|
|
assert.ok(plan.warnings.some(warning => warning.includes("Invalid language name '../bad'")));
|
|
assert.ok(operationFor(plan, path.join('custom-rules', 'common', 'coding-style.md')));
|
|
assert.ok(operationFor(plan, path.join('custom-rules', 'common', 'nested', 'shared.md')));
|
|
assert.ok(operationFor(plan, path.join('custom-rules', 'typescript', 'testing.md')));
|
|
assert.ok(!plan.operations.some(operation => operation.sourceRelativePath.includes('node_modules')));
|
|
assert.ok(!plan.operations.some(operation => operation.sourceRelativePath.includes('.git')));
|
|
assert.deepStrictEqual(plan.statePreview.request.legacyLanguages, ['typescript', 'missing-lang', '../bad']);
|
|
assert.strictEqual(plan.statePreview.request.legacyMode, true);
|
|
assert.strictEqual(plan.statePreview.source.repoVersion, '9.8.7');
|
|
assert.strictEqual(plan.statePreview.source.manifestVersion, 1);
|
|
} finally {
|
|
cleanup(sourceRoot);
|
|
cleanup(homeDir);
|
|
cleanup(projectRoot);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('plans Cursor legacy assets and JSON merge payloads', () => {
|
|
const sourceRoot = createTempDir('install-executor-source-');
|
|
const projectRoot = createTempDir('install-executor-project-');
|
|
const homeDir = createTempDir('install-executor-home-');
|
|
try {
|
|
writeLegacySourceFixture(sourceRoot);
|
|
|
|
const plan = createLegacyInstallPlan({
|
|
sourceRoot,
|
|
projectRoot,
|
|
homeDir,
|
|
target: 'cursor',
|
|
languages: ['typescript', 'ruby', 'bad/name'],
|
|
});
|
|
|
|
const targetRoot = path.join(projectRoot, '.cursor');
|
|
assert.strictEqual(plan.installRoot, targetRoot);
|
|
assert.ok(operationFor(plan, path.join('.cursor', 'rules', 'common-style.md')));
|
|
assert.ok(operationFor(plan, path.join('.cursor', 'rules', 'typescript-style.md')));
|
|
assert.ok(operationFor(plan, path.join('.cursor', 'agents', 'planner.md')));
|
|
assert.ok(operationFor(plan, path.join('.cursor', 'skills', 'demo', 'SKILL.md')));
|
|
assert.ok(operationFor(plan, path.join('.cursor', 'commands', 'plan.md')));
|
|
assert.ok(operationFor(plan, path.join('.cursor', 'hooks', 'hook.js')));
|
|
assert.ok(operationFor(plan, path.join('.cursor', 'hooks.json')));
|
|
const mergeOperation = plan.operations.find(operation => operation.kind === 'merge-json');
|
|
assert.ok(mergeOperation, 'Should merge shared MCP config into Cursor');
|
|
assert.deepStrictEqual(mergeOperation.mergePayload.mcpServers.github.command, 'github-mcp');
|
|
assert.ok(plan.warnings.some(warning => warning.includes("No Cursor rules for 'ruby'")));
|
|
assert.ok(plan.warnings.some(warning => warning.includes("Invalid language name 'bad/name'")));
|
|
assert.strictEqual(plan.statePreview.target.id, 'cursor-project');
|
|
} finally {
|
|
cleanup(sourceRoot);
|
|
cleanup(projectRoot);
|
|
cleanup(homeDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('surfaces invalid Cursor MCP JSON while planning legacy install', () => {
|
|
const sourceRoot = createTempDir('install-executor-source-');
|
|
const projectRoot = createTempDir('install-executor-project-');
|
|
const homeDir = createTempDir('install-executor-home-');
|
|
try {
|
|
writeLegacySourceFixture(sourceRoot);
|
|
fs.writeFileSync(path.join(sourceRoot, '.mcp.json'), '[]\n', 'utf8');
|
|
|
|
assert.throws(
|
|
() => createLegacyInstallPlan({ sourceRoot, projectRoot, homeDir, target: 'cursor' }),
|
|
/Invalid \.mcp\.json/
|
|
);
|
|
} finally {
|
|
cleanup(sourceRoot);
|
|
cleanup(projectRoot);
|
|
cleanup(homeDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('plans Antigravity legacy files with flattened rule names', () => {
|
|
const sourceRoot = createTempDir('install-executor-source-');
|
|
const projectRoot = createTempDir('install-executor-project-');
|
|
const homeDir = createTempDir('install-executor-home-');
|
|
try {
|
|
writeLegacySourceFixture(sourceRoot);
|
|
writeFile(projectRoot, path.join('.agent', 'rules', 'existing.md'), '# Existing\n');
|
|
|
|
const plan = createLegacyInstallPlan({
|
|
sourceRoot,
|
|
projectRoot,
|
|
homeDir,
|
|
target: 'antigravity',
|
|
languages: ['typescript', 'missing-lang', 'bad/name'],
|
|
});
|
|
|
|
assert.strictEqual(plan.installRoot, path.join(projectRoot, '.agent'));
|
|
assert.ok(plan.warnings.some(warning => warning.includes('files may be overwritten')));
|
|
assert.ok(plan.warnings.some(warning => warning.includes("rules/missing-lang/ does not exist")));
|
|
assert.ok(plan.warnings.some(warning => warning.includes("Invalid language name 'bad/name'")));
|
|
assert.ok(operationFor(plan, path.join('.agent', 'rules', 'common-coding-style.md')));
|
|
assert.ok(operationFor(plan, path.join('.agent', 'rules', 'typescript-testing.md')));
|
|
assert.ok(operationFor(plan, path.join('.agent', 'workflows', 'plan.md')));
|
|
assert.ok(operationFor(plan, path.join('.agent', 'skills', 'architect.md')));
|
|
assert.ok(operationFor(plan, path.join('.agent', 'skills', 'demo', 'SKILL.md')));
|
|
assert.strictEqual(plan.statePreview.target.id, 'antigravity-project');
|
|
} finally {
|
|
cleanup(sourceRoot);
|
|
cleanup(projectRoot);
|
|
cleanup(homeDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('materializes manifest scaffold operations and filters generated runtime state', () => {
|
|
const sourceRoot = createTempDir('install-executor-source-');
|
|
const homeDir = createTempDir('install-executor-home-');
|
|
try {
|
|
writeManifestSourceFixture(sourceRoot);
|
|
|
|
const plan = createManifestInstallPlan({
|
|
sourceRoot,
|
|
homeDir,
|
|
target: 'claude',
|
|
profileId: 'minimal',
|
|
requestIncludeComponentIds: ['capability:fixture'],
|
|
requestExcludeComponentIds: ['capability:skip'],
|
|
warnings: ['fixture warning'],
|
|
});
|
|
|
|
const normalizedSources = plan.operations.map(operation => (
|
|
operation.sourceRelativePath.split(path.sep).join('/')
|
|
));
|
|
assert.ok(normalizedSources.includes('src/app.js'));
|
|
assert.ok(normalizedSources.includes('src/nested/feature.js'));
|
|
assert.ok(normalizedSources.includes('standalone.txt'));
|
|
assert.ok(normalizedSources.includes('.claude-plugin/plugin.json'));
|
|
assert.ok(!normalizedSources.includes('missing.txt'));
|
|
assert.ok(!normalizedSources.includes('runtime/ecc/install-state.json'));
|
|
assert.ok(!normalizedSources.includes('src/nested/ecc-install-state.json'));
|
|
assert.ok(!normalizedSources.some(source => source.includes('node_modules')));
|
|
assert.ok(!normalizedSources.some(source => source.includes('.git')));
|
|
assert.ok(plan.operations.some(operation => (
|
|
operation.sourceRelativePath === path.join('.claude-plugin', 'plugin.json')
|
|
&& operation.destinationPath === path.join(homeDir, '.claude', 'plugin.json')
|
|
)));
|
|
assert.deepStrictEqual(plan.warnings, ['fixture warning']);
|
|
assert.strictEqual(plan.statePreview.request.profile, 'minimal');
|
|
assert.deepStrictEqual(plan.statePreview.request.includeComponents, ['capability:fixture']);
|
|
assert.deepStrictEqual(plan.statePreview.request.excludeComponents, ['capability:skip']);
|
|
assert.strictEqual(plan.statePreview.source.repoVersion, '1.2.3');
|
|
assert.strictEqual(plan.statePreview.source.manifestVersion, 7);
|
|
} finally {
|
|
cleanup(sourceRoot);
|
|
cleanup(homeDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('creates legacy compatibility manifest plans from language selections', () => {
|
|
const projectRoot = createTempDir('install-executor-project-');
|
|
const homeDir = createTempDir('install-executor-home-');
|
|
try {
|
|
const plan = createLegacyCompatInstallPlan({
|
|
sourceRoot: REPO_ROOT,
|
|
projectRoot,
|
|
homeDir,
|
|
target: 'cursor',
|
|
legacyLanguages: ['rust'],
|
|
});
|
|
|
|
assert.strictEqual(plan.mode, 'legacy-compat');
|
|
assert.deepStrictEqual(plan.legacyLanguages, ['rust']);
|
|
assert.ok(plan.selectedModuleIds.includes('framework-language'));
|
|
assert.strictEqual(plan.statePreview.request.legacyMode, true);
|
|
assert.deepStrictEqual(plan.statePreview.request.legacyLanguages, ['rust']);
|
|
assert.deepStrictEqual(plan.statePreview.request.modules, []);
|
|
} finally {
|
|
cleanup(projectRoot);
|
|
cleanup(homeDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('applyInstallPlan re-export applies a manifest plan and writes install state', () => {
|
|
const sourceRoot = createTempDir('install-executor-source-');
|
|
const homeDir = createTempDir('install-executor-home-');
|
|
try {
|
|
writeManifestSourceFixture(sourceRoot);
|
|
const plan = createManifestInstallPlan({
|
|
sourceRoot,
|
|
homeDir,
|
|
target: 'claude',
|
|
profileId: 'minimal',
|
|
});
|
|
|
|
const applied = applyInstallPlan(plan);
|
|
|
|
assert.strictEqual(applied.applied, true);
|
|
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'src', 'app.js')));
|
|
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'standalone.txt')));
|
|
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'plugin.json')));
|
|
const state = JSON.parse(fs.readFileSync(path.join(homeDir, '.claude', 'ecc', 'install-state.json'), 'utf8'));
|
|
assert.strictEqual(state.request.profile, 'minimal');
|
|
assert.deepStrictEqual(state.resolution.selectedModules, ['fixture-core']);
|
|
} finally {
|
|
cleanup(sourceRoot);
|
|
cleanup(homeDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
}
|
|
|
|
runTests();
|