mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-14 02:10:07 +08:00
fix: salvage stale PR plugin install fixes
This commit is contained in:
parent
8aa8c32d2a
commit
9b385c9e30
@ -1590,6 +1590,7 @@ Projects built on or inspired by Everything Claude Code:
|
|||||||
| Project | Description |
|
| 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). |
|
| [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.
|
Built something with ECC? Open a PR to add it here.
|
||||||
|
|
||||||
|
|||||||
@ -187,28 +187,157 @@ function detectTargetMode(rootDir) {
|
|||||||
return 'consumer';
|
return 'consumer';
|
||||||
}
|
}
|
||||||
|
|
||||||
function findPluginInstall(rootDir) {
|
const ECC_PLUGIN_KEY_PATTERNS = [
|
||||||
const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir() || '';
|
/^ecc@/i,
|
||||||
const pluginDirs = [
|
/^everything-claude-code@/i,
|
||||||
'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'),
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
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) {
|
function getRepoChecks(rootDir) {
|
||||||
@ -735,4 +864,6 @@ if (require.main === module) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
buildReport,
|
buildReport,
|
||||||
parseArgs,
|
parseArgs,
|
||||||
|
findPluginInstall,
|
||||||
|
compareVersionDesc,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -28,6 +28,13 @@ from datetime import datetime, timedelta, timezone
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Optional
|
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:
|
try:
|
||||||
import fcntl
|
import fcntl
|
||||||
_HAS_FCNTL = True
|
_HAS_FCNTL = True
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const path = require('path');
|
|||||||
const { execFileSync, spawnSync } = require('child_process');
|
const { execFileSync, spawnSync } = require('child_process');
|
||||||
|
|
||||||
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'harness-audit.js');
|
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'harness-audit.js');
|
||||||
const { parseArgs } = require(SCRIPT);
|
const { parseArgs, findPluginInstall, compareVersionDesc } = require(SCRIPT);
|
||||||
|
|
||||||
function createTempDir(prefix) {
|
function createTempDir(prefix) {
|
||||||
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||||
@ -389,6 +389,87 @@ function runTests() {
|
|||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})) 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}`);
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||||
process.exit(failed > 0 ? 1 : 0);
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user