mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-20 19:29:58 +08:00
Generate the inline hook root resolver with single-quoted JavaScript literals so Windows Git Bash does not choke on nested escaped double quotes before Node starts. Refresh hooks.json and add regression coverage for parsed hook commands and installed hook manifests.
134 lines
4.9 KiB
JavaScript
134 lines
4.9 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const os = require('os');
|
|
|
|
const CURRENT_PLUGIN_SLUG = 'ecc';
|
|
const LEGACY_PLUGIN_SLUG = 'everything-claude-code';
|
|
const CURRENT_PLUGIN_HANDLE = `${CURRENT_PLUGIN_SLUG}@${CURRENT_PLUGIN_SLUG}`;
|
|
const LEGACY_PLUGIN_HANDLE = `${LEGACY_PLUGIN_SLUG}@${LEGACY_PLUGIN_SLUG}`;
|
|
const PLUGIN_CACHE_SLUGS = [CURRENT_PLUGIN_SLUG, LEGACY_PLUGIN_SLUG];
|
|
const PLUGIN_ROOT_SEGMENTS = [
|
|
[CURRENT_PLUGIN_SLUG],
|
|
[CURRENT_PLUGIN_HANDLE],
|
|
['marketplaces', CURRENT_PLUGIN_SLUG],
|
|
[LEGACY_PLUGIN_SLUG],
|
|
[LEGACY_PLUGIN_HANDLE],
|
|
['marketplaces', LEGACY_PLUGIN_SLUG],
|
|
];
|
|
|
|
/**
|
|
* Resolve the ECC source root directory.
|
|
*
|
|
* Tries, in order:
|
|
* 1. CLAUDE_PLUGIN_ROOT env var (set by Claude Code for hooks, or by user)
|
|
* 2. Standard install location (~/.claude/) — when scripts exist there
|
|
* 3. Known plugin roots under ~/.claude/plugins/ (current + legacy slugs)
|
|
* 4. Plugin cache auto-detection — scans ~/.claude/plugins/cache/{ecc,everything-claude-code}/
|
|
* 5. Fallback to ~/.claude/ (original behaviour)
|
|
*
|
|
* @param {object} [options]
|
|
* @param {string} [options.homeDir] Override home directory (for testing)
|
|
* @param {string} [options.envRoot] Override CLAUDE_PLUGIN_ROOT (for testing)
|
|
* @param {string} [options.probe] Relative path used to verify a candidate root
|
|
* contains ECC scripts. Default: 'scripts/lib/utils.js'
|
|
* @returns {string} Resolved ECC root path
|
|
*/
|
|
function resolveEccRoot(options = {}) {
|
|
const envRoot = options.envRoot !== undefined
|
|
? options.envRoot
|
|
: (process.env.CLAUDE_PLUGIN_ROOT || '');
|
|
|
|
if (envRoot && envRoot.trim()) {
|
|
return envRoot.trim();
|
|
}
|
|
|
|
const homeDir = options.homeDir || os.homedir();
|
|
const claudeDir = path.join(homeDir, '.claude');
|
|
const probe = options.probe || path.join('scripts', 'lib', 'utils.js');
|
|
|
|
// Standard install — files are copied directly into ~/.claude/
|
|
if (fs.existsSync(path.join(claudeDir, probe))) {
|
|
return claudeDir;
|
|
}
|
|
|
|
// Exact legacy plugin install locations. These preserve backwards
|
|
// compatibility without scanning arbitrary plugin trees.
|
|
const legacyPluginRoots = PLUGIN_ROOT_SEGMENTS.map((segments) =>
|
|
path.join(claudeDir, 'plugins', ...segments)
|
|
);
|
|
|
|
for (const candidate of legacyPluginRoots) {
|
|
if (fs.existsSync(path.join(candidate, probe))) {
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
// Plugin cache — Claude Code stores marketplace plugins under
|
|
// ~/.claude/plugins/cache/<plugin-name>/<org>/<version>/
|
|
try {
|
|
for (const slug of PLUGIN_CACHE_SLUGS) {
|
|
const cacheBase = path.join(claudeDir, 'plugins', 'cache', slug);
|
|
const orgDirs = fs.readdirSync(cacheBase, { withFileTypes: true });
|
|
|
|
for (const orgEntry of orgDirs) {
|
|
if (!orgEntry.isDirectory()) continue;
|
|
const orgPath = path.join(cacheBase, orgEntry.name);
|
|
|
|
let versionDirs;
|
|
try {
|
|
versionDirs = fs.readdirSync(orgPath, { withFileTypes: true });
|
|
} catch {
|
|
continue;
|
|
}
|
|
|
|
for (const verEntry of versionDirs) {
|
|
if (!verEntry.isDirectory()) continue;
|
|
const candidate = path.join(orgPath, verEntry.name);
|
|
if (fs.existsSync(path.join(candidate, probe))) {
|
|
return candidate;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
// Plugin cache doesn't exist or isn't readable — continue to fallback
|
|
}
|
|
|
|
return claudeDir;
|
|
}
|
|
|
|
/**
|
|
* Compact inline version for embedding in command .md code blocks.
|
|
*
|
|
* This is the minified form of resolveEccRoot() suitable for use in
|
|
* node -e "..." scripts where require() is not available before the
|
|
* root is known.
|
|
*
|
|
* Usage in commands:
|
|
* const _r = <paste INLINE_RESOLVE>;
|
|
* const sm = require(_r + '/scripts/lib/session-manager');
|
|
*/
|
|
function inlineSingleQuote(value) {
|
|
return `'${String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
}
|
|
|
|
function inlineArray(values) {
|
|
return `[${values.map(inlineSingleQuote).join(',')}]`;
|
|
}
|
|
|
|
function inlineNestedArray(values) {
|
|
return `[${values.map(inlineArray).join(',')}]`;
|
|
}
|
|
|
|
const INLINE_PLUGIN_ROOT_SEGMENTS = inlineNestedArray(PLUGIN_ROOT_SEGMENTS);
|
|
const INLINE_PLUGIN_CACHE_SLUGS = inlineArray(PLUGIN_CACHE_SLUGS);
|
|
|
|
const INLINE_RESOLVE = `(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var s of ${INLINE_PLUGIN_ROOT_SEGMENTS}){var l=p.join(d,'plugins',...s);if(f.existsSync(p.join(l,q)))return l}try{for(var g of ${INLINE_PLUGIN_CACHE_SLUGS}){var b=p.join(d,'plugins','cache',g);for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}}catch(x){}return d})()`;
|
|
|
|
module.exports = {
|
|
resolveEccRoot,
|
|
INLINE_RESOLVE,
|
|
};
|