mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-12 15:47:27 +08:00
fix: salvage stale PR plugin install fixes
This commit is contained in:
committed by
Affaan Mustafa
parent
8aa8c32d2a
commit
9b385c9e30
@@ -1590,6 +1590,7 @@ Projects built on or inspired by Everything Claude Code:
|
||||
| Project | Description |
|
||||
|---------|-------------|
|
||||
| [EVC](https://github.com/SaigonXIII/evc) | Marketing agent workspace — 42 commands for content operators, brand governance, and multi-channel publishing. [Visual overview](https://saigonxiii.github.io/evc). |
|
||||
| [trading-skills](https://github.com/VictorVVedtion/trading-skills) | 68 trading-themed Claude Code skills with pre-trade review prompts and risk gates inspired by market operators. |
|
||||
|
||||
Built something with ECC? Open a PR to add it here.
|
||||
|
||||
|
||||
@@ -187,28 +187,157 @@ function detectTargetMode(rootDir) {
|
||||
return 'consumer';
|
||||
}
|
||||
|
||||
function findPluginInstall(rootDir) {
|
||||
const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir() || '';
|
||||
const pluginDirs = [
|
||||
'ecc',
|
||||
'ecc@ecc',
|
||||
'everything-claude-code',
|
||||
'everything-claude-code@everything-claude-code',
|
||||
];
|
||||
const candidateRoots = [
|
||||
path.join(rootDir, '.claude', 'plugins'),
|
||||
path.join(rootDir, '.claude', 'plugins', 'marketplaces'),
|
||||
homeDir && path.join(homeDir, '.claude', 'plugins'),
|
||||
homeDir && path.join(homeDir, '.claude', 'plugins', 'marketplaces'),
|
||||
].filter(Boolean);
|
||||
const candidates = candidateRoots.flatMap((pluginsDir) =>
|
||||
pluginDirs.flatMap((pluginDir) => [
|
||||
path.join(pluginsDir, pluginDir, '.claude-plugin', 'plugin.json'),
|
||||
path.join(pluginsDir, pluginDir, 'plugin.json'),
|
||||
])
|
||||
);
|
||||
const ECC_PLUGIN_KEY_PATTERNS = [
|
||||
/^ecc@/i,
|
||||
/^everything-claude-code@/i,
|
||||
];
|
||||
|
||||
return candidates.find(candidate => fs.existsSync(candidate)) || null;
|
||||
const ECC_LEGACY_PLUGIN_DIRS = [
|
||||
'ecc',
|
||||
'ecc@ecc',
|
||||
'everything-claude-code',
|
||||
'everything-claude-code@everything-claude-code',
|
||||
];
|
||||
|
||||
const ECC_CACHE_MARKETPLACES = ['everything-claude-code', 'ecc'];
|
||||
const ECC_CACHE_PLUGIN_NAMES = ['ecc', 'everything-claude-code'];
|
||||
|
||||
function uniquePaths(paths) {
|
||||
return [...new Set(paths.filter(Boolean))];
|
||||
}
|
||||
|
||||
function compareVersionDesc(a, b) {
|
||||
const partsA = String(a).split('.').map(part => parseInt(part, 10) || 0);
|
||||
const partsB = String(b).split('.').map(part => parseInt(part, 10) || 0);
|
||||
const length = Math.max(partsA.length, partsB.length);
|
||||
|
||||
for (let index = 0; index < length; index += 1) {
|
||||
const valueA = partsA[index] || 0;
|
||||
const valueB = partsB[index] || 0;
|
||||
if (valueA !== valueB) {
|
||||
return valueB - valueA;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function findPluginJsonUnder(installRoot) {
|
||||
const pluginJson = path.join(installRoot, '.claude-plugin', 'plugin.json');
|
||||
if (fs.existsSync(pluginJson)) {
|
||||
return pluginJson;
|
||||
}
|
||||
|
||||
const fallback = path.join(installRoot, 'plugin.json');
|
||||
return fs.existsSync(fallback) ? fallback : null;
|
||||
}
|
||||
|
||||
function findPluginInstallFromManifest(installedPluginsPaths) {
|
||||
for (const installedPath of installedPluginsPaths) {
|
||||
if (!fs.existsSync(installedPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const manifest = safeParseJson(safeRead(path.dirname(installedPath), path.basename(installedPath)));
|
||||
if (!manifest || !manifest.plugins) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(manifest.plugins)) {
|
||||
if (!ECC_PLUGIN_KEY_PATTERNS.some(pattern => pattern.test(key))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const entries = Array.isArray(value) ? value : [];
|
||||
for (const entry of entries) {
|
||||
if (!entry || typeof entry.installPath !== 'string' || !entry.installPath.trim()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const installRoot = path.isAbsolute(entry.installPath)
|
||||
? entry.installPath
|
||||
: path.resolve(path.dirname(installedPath), entry.installPath);
|
||||
const hit = findPluginJsonUnder(installRoot);
|
||||
if (hit) {
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function findPluginInstallFlatLayout(candidateRoots) {
|
||||
for (const pluginsDir of candidateRoots) {
|
||||
for (const pluginDir of ECC_LEGACY_PLUGIN_DIRS) {
|
||||
const hit = findPluginJsonUnder(path.join(pluginsDir, pluginDir));
|
||||
if (hit) {
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function findPluginInstallMarketplaceCache(candidateRoots) {
|
||||
for (const pluginsDir of candidateRoots) {
|
||||
for (const marketplace of ECC_CACHE_MARKETPLACES) {
|
||||
for (const pluginName of ECC_CACHE_PLUGIN_NAMES) {
|
||||
const pluginRoot = path.join(pluginsDir, 'cache', marketplace, pluginName);
|
||||
if (!fs.existsSync(pluginRoot)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let versions = [];
|
||||
try {
|
||||
versions = fs
|
||||
.readdirSync(pluginRoot, { withFileTypes: true })
|
||||
.filter(entry => entry.isDirectory())
|
||||
.map(entry => entry.name)
|
||||
.sort(compareVersionDesc);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const version of versions) {
|
||||
const hit = findPluginJsonUnder(path.join(pluginRoot, version));
|
||||
if (hit) {
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function findPluginInstall(rootDir) {
|
||||
const homeDirs = uniquePaths([
|
||||
process.env.HOME,
|
||||
process.env.USERPROFILE,
|
||||
os.homedir(),
|
||||
]);
|
||||
const pluginRoots = uniquePaths([
|
||||
path.join(rootDir, '.claude', 'plugins'),
|
||||
...homeDirs.map(homeDir => path.join(homeDir, '.claude', 'plugins')),
|
||||
]);
|
||||
const installedPluginsPaths = uniquePaths([
|
||||
path.join(rootDir, '.claude', 'plugins', 'installed_plugins.json'),
|
||||
...homeDirs.map(homeDir => path.join(homeDir, '.claude', 'plugins', 'installed_plugins.json')),
|
||||
]);
|
||||
const flatRoots = uniquePaths([
|
||||
...pluginRoots,
|
||||
...pluginRoots.map(pluginsDir => path.join(pluginsDir, 'marketplaces')),
|
||||
]);
|
||||
|
||||
return (
|
||||
findPluginInstallFromManifest(installedPluginsPaths)
|
||||
|| findPluginInstallFlatLayout(flatRoots)
|
||||
|| findPluginInstallMarketplaceCache(pluginRoots)
|
||||
);
|
||||
}
|
||||
|
||||
function getRepoChecks(rootDir) {
|
||||
@@ -735,4 +864,6 @@ if (require.main === module) {
|
||||
module.exports = {
|
||||
buildReport,
|
||||
parseArgs,
|
||||
findPluginInstall,
|
||||
compareVersionDesc,
|
||||
};
|
||||
|
||||
@@ -28,6 +28,13 @@ from datetime import datetime, timedelta, timezone
|
||||
from collections import defaultdict
|
||||
from typing import Optional
|
||||
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
import fcntl
|
||||
_HAS_FCNTL = True
|
||||
|
||||
@@ -9,7 +9,7 @@ const path = require('path');
|
||||
const { execFileSync, spawnSync } = require('child_process');
|
||||
|
||||
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'harness-audit.js');
|
||||
const { parseArgs } = require(SCRIPT);
|
||||
const { parseArgs, findPluginInstall, compareVersionDesc } = require(SCRIPT);
|
||||
|
||||
function createTempDir(prefix) {
|
||||
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
@@ -389,6 +389,87 @@ function runTests() {
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('detects Claude plugin installs from installed_plugins.json', () => {
|
||||
const homeDir = createTempDir('harness-audit-manifest-home-');
|
||||
const projectRoot = createTempDir('harness-audit-manifest-project-');
|
||||
const pluginsDir = path.join(homeDir, '.claude', 'plugins');
|
||||
const installRoot = path.join(pluginsDir, 'cache', 'everything-claude-code', 'ecc', '2.0.0');
|
||||
|
||||
try {
|
||||
fs.mkdirSync(path.join(installRoot, '.claude-plugin'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(installRoot, '.claude-plugin', 'plugin.json'),
|
||||
JSON.stringify({ name: 'ecc', version: '2.0.0' }, null, 2)
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginsDir, 'installed_plugins.json'),
|
||||
JSON.stringify({
|
||||
plugins: {
|
||||
'ecc@everything-claude-code': [
|
||||
{ installPath: path.join('cache', 'everything-claude-code', 'ecc', '2.0.0') },
|
||||
],
|
||||
},
|
||||
}, null, 2)
|
||||
);
|
||||
|
||||
const originalHome = process.env.HOME;
|
||||
process.env.HOME = homeDir;
|
||||
try {
|
||||
const found = findPluginInstall(projectRoot);
|
||||
assert.ok(found);
|
||||
assert.ok(found.includes(`${path.sep}cache${path.sep}everything-claude-code${path.sep}ecc${path.sep}2.0.0${path.sep}`));
|
||||
} finally {
|
||||
if (originalHome === undefined) {
|
||||
delete process.env.HOME;
|
||||
} else {
|
||||
process.env.HOME = originalHome;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cleanup(homeDir);
|
||||
cleanup(projectRoot);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('detects newest Claude plugin install from cache marketplace layout', () => {
|
||||
const homeDir = createTempDir('harness-audit-cache-home-');
|
||||
const projectRoot = createTempDir('harness-audit-cache-project-');
|
||||
const pluginRoot = path.join(homeDir, '.claude', 'plugins', 'cache', 'everything-claude-code', 'ecc');
|
||||
|
||||
try {
|
||||
for (const version of ['1.8.0', '1.10.0']) {
|
||||
fs.mkdirSync(path.join(pluginRoot, version, '.claude-plugin'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, version, '.claude-plugin', 'plugin.json'),
|
||||
JSON.stringify({ name: 'ecc', version }, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
const originalHome = process.env.HOME;
|
||||
process.env.HOME = homeDir;
|
||||
try {
|
||||
const found = findPluginInstall(projectRoot);
|
||||
assert.ok(found);
|
||||
assert.ok(found.includes(`${path.sep}1.10.0${path.sep}`), `expected newest version, got ${found}`);
|
||||
} finally {
|
||||
if (originalHome === undefined) {
|
||||
delete process.env.HOME;
|
||||
} else {
|
||||
process.env.HOME = originalHome;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cleanup(homeDir);
|
||||
cleanup(projectRoot);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('compareVersionDesc orders numeric version components', () => {
|
||||
const versions = ['1.8.0', '1.10.0', '1.9.0', '2.0.0'].sort(compareVersionDesc);
|
||||
assert.deepStrictEqual(versions, ['2.0.0', '1.10.0', '1.9.0', '1.8.0']);
|
||||
assert.doesNotThrow(() => compareVersionDesc('1.0.0-rc.1', '1.0.0'));
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user