-
-
Notifications
You must be signed in to change notification settings - Fork 27k
fix: harden hook portability and plugin docs #371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,7 +32,16 @@ Add to your `opencode.json`: | |
| "plugin": ["ecc-universal"] | ||
| } | ||
| ``` | ||
| After installation, the `ecc-install` CLI becomes available: | ||
|
|
||
| This loads the ECC OpenCode plugin module from npm: | ||
| - hook/event integrations | ||
| - bundled custom tools exported by the plugin | ||
|
|
||
| It does **not** auto-register the full ECC command/agent/instruction catalog in your project config. For the full OpenCode setup, either: | ||
| - run OpenCode inside this repository, or | ||
| - copy the relevant `.opencode/commands/`, `.opencode/prompts/`, `.opencode/instructions/`, and the `instructions`, `agent`, and `command` config entries into your own project | ||
|
|
||
| After installation, the `ecc-install` CLI is also available: | ||
|
|
||
| ```bash | ||
| npx ecc-install typescript | ||
|
Comment on lines
+44
to
47
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's check if the .opencode/README.md file exists and examine the relevant section
find . -name "README.md" -path "*opencode*" | head -20Repository: affaan-m/everything-claude-code Length of output: 96 🏁 Script executed: # Also, let's look for the specific file mentioned
git ls-files | grep -i "opencode" | grep -i readmeRepository: affaan-m/everything-claude-code Length of output: 94 🏁 Script executed: # Check the repository structure around opencode directory
fd "README" | grep -E "(opencode|ecc)" | head -20Repository: affaan-m/everything-claude-code Length of output: 57 🏁 Script executed: # Read the .opencode/README.md file to see the actual content around lines 44-47
cat -n .opencode/README.md | head -60Repository: affaan-m/everything-claude-code Length of output: 2064 🏁 Script executed: # Check if there are other examples of package manager invocation patterns in documentation
rg "npm|bun|yarn|pnpm" .opencode/README.md | head -20Repository: affaan-m/everything-claude-code Length of output: 460 🏁 Script executed: # Also check the main README for how package managers are handled
rg -A2 -B2 "npx|bunx|yarn" README.md | head -30Repository: affaan-m/everything-claude-code Length of output: 853 Provide package-manager-specific examples for the Line 15 documents support for "npm/bun/yarn", but the example on line 47 uses only 🤖 Prompt for AI Agents |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,11 +10,34 @@ | |
| * Fails silently if no formatter is found or installed. | ||
| */ | ||
|
|
||
| const { execFileSync } = require('child_process'); | ||
| const { execFileSync, spawnSync } = require('child_process'); | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const { getPackageManager } = require('../lib/package-manager'); | ||
|
|
||
| const MAX_STDIN = 1024 * 1024; // 1MB limit | ||
| const BIOME_CONFIGS = ['biome.json', 'biome.jsonc']; | ||
| const PRETTIER_CONFIGS = [ | ||
| '.prettierrc', | ||
| '.prettierrc.json', | ||
| '.prettierrc.json5', | ||
| '.prettierrc.js', | ||
| '.prettierrc.cjs', | ||
| '.prettierrc.mjs', | ||
| '.prettierrc.ts', | ||
| '.prettierrc.cts', | ||
| '.prettierrc.mts', | ||
| '.prettierrc.yml', | ||
| '.prettierrc.yaml', | ||
| '.prettierrc.toml', | ||
| 'prettier.config.js', | ||
| 'prettier.config.cjs', | ||
| 'prettier.config.mjs', | ||
| 'prettier.config.ts', | ||
| 'prettier.config.cts', | ||
| 'prettier.config.mts', | ||
| ]; | ||
| const PROJECT_ROOT_MARKERS = ['package.json', ...BIOME_CONFIGS, ...PRETTIER_CONFIGS]; | ||
| let data = ''; | ||
| process.stdin.setEncoding('utf8'); | ||
|
|
||
|
|
@@ -27,50 +50,102 @@ process.stdin.on('data', chunk => { | |
|
|
||
| function findProjectRoot(startDir) { | ||
| let dir = startDir; | ||
| while (dir !== path.dirname(dir)) { | ||
| if (fs.existsSync(path.join(dir, 'package.json'))) return dir; | ||
| dir = path.dirname(dir); | ||
| let fallbackDir = null; | ||
|
|
||
| while (true) { | ||
| if (detectFormatter(dir)) { | ||
| return dir; | ||
| } | ||
|
|
||
| if (!fallbackDir && PROJECT_ROOT_MARKERS.some(marker => fs.existsSync(path.join(dir, marker)))) { | ||
| fallbackDir = dir; | ||
| } | ||
|
|
||
| const parentDir = path.dirname(dir); | ||
| if (parentDir === dir) break; | ||
| dir = parentDir; | ||
| } | ||
| return startDir; | ||
|
|
||
| return fallbackDir || startDir; | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| function detectFormatter(projectRoot) { | ||
| const biomeConfigs = ['biome.json', 'biome.jsonc']; | ||
| for (const cfg of biomeConfigs) { | ||
| for (const cfg of BIOME_CONFIGS) { | ||
| if (fs.existsSync(path.join(projectRoot, cfg))) return 'biome'; | ||
| } | ||
|
|
||
| const prettierConfigs = [ | ||
| '.prettierrc', | ||
| '.prettierrc.json', | ||
| '.prettierrc.js', | ||
| '.prettierrc.cjs', | ||
| '.prettierrc.mjs', | ||
| '.prettierrc.yml', | ||
| '.prettierrc.yaml', | ||
| '.prettierrc.toml', | ||
| 'prettier.config.js', | ||
| 'prettier.config.cjs', | ||
| 'prettier.config.mjs', | ||
| ]; | ||
| for (const cfg of prettierConfigs) { | ||
| for (const cfg of PRETTIER_CONFIGS) { | ||
| if (fs.existsSync(path.join(projectRoot, cfg))) return 'prettier'; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| function getFormatterCommand(formatter, filePath) { | ||
| const npxBin = process.platform === 'win32' ? 'npx.cmd' : 'npx'; | ||
| function getRunnerBin(bin) { | ||
| if (process.platform !== 'win32') return bin; | ||
| if (bin === 'npx') return 'npx.cmd'; | ||
| if (bin === 'pnpm') return 'pnpm.cmd'; | ||
| if (bin === 'yarn') return 'yarn.cmd'; | ||
| if (bin === 'bunx') return 'bunx.cmd'; | ||
| return bin; | ||
| } | ||
|
|
||
| function getFormatterRunner(projectRoot) { | ||
| const pm = getPackageManager({ projectDir: projectRoot }); | ||
| const execCmd = pm?.config?.execCmd || 'npx'; | ||
| const [bin = 'npx', ...prefix] = execCmd.split(/\s+/).filter(Boolean); | ||
|
|
||
| return { | ||
| bin: getRunnerBin(bin), | ||
| prefix | ||
| }; | ||
| } | ||
|
|
||
| function getFormatterCommand(formatter, filePath, projectRoot) { | ||
| const runner = getFormatterRunner(projectRoot); | ||
|
|
||
| if (formatter === 'biome') { | ||
| return { bin: npxBin, args: ['@biomejs/biome', 'format', '--write', filePath] }; | ||
| return { | ||
| bin: runner.bin, | ||
| args: [...runner.prefix, '@biomejs/biome', 'format', '--write', filePath] | ||
| }; | ||
| } | ||
| if (formatter === 'prettier') { | ||
| return { bin: npxBin, args: ['prettier', '--write', filePath] }; | ||
| return { | ||
| bin: runner.bin, | ||
| args: [...runner.prefix, 'prettier', '--write', filePath] | ||
| }; | ||
|
Comment on lines
+93
to
+117
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "post-edit-format.js" -type fRepository: affaan-m/everything-claude-code Length of output: 110 🏁 Script executed: find . -name "package-manager.js" -type fRepository: affaan-m/everything-claude-code Length of output: 107 🏁 Script executed: wc -l ./scripts/hooks/post-edit-format.jsRepository: affaan-m/everything-claude-code Length of output: 114 🏁 Script executed: cat -n ./scripts/hooks/post-edit-format.jsRepository: affaan-m/everything-claude-code Length of output: 5699 🏁 Script executed: cat -n ./scripts/lib/package-manager.jsRepository: affaan-m/everything-claude-code Length of output: 15103 Prefer the project-local formatter before
Possible fix function getFormatterCommand(formatter, filePath, projectRoot) {
+ const localBinName = process.platform === 'win32'
+ ? (formatter === 'biome' ? 'biome.cmd' : 'prettier.cmd')
+ : (formatter === 'biome' ? 'biome' : 'prettier');
+ const localBin = path.join(projectRoot, 'node_modules', '.bin', localBinName);
+
+ if (fs.existsSync(localBin)) {
+ return formatter === 'biome'
+ ? { bin: localBin, args: ['format', '--write', filePath] }
+ : { bin: localBin, args: ['--write', filePath] };
+ }
+
const runner = getFormatterRunner(projectRoot);
if (formatter === 'biome') {
return {
bin: runner.bin,🤖 Prompt for AI Agents |
||
| } | ||
| return null; | ||
| } | ||
|
|
||
| function runFormatterCommand(cmd, projectRoot) { | ||
| if (process.platform === 'win32' && cmd.bin.endsWith('.cmd')) { | ||
| const result = spawnSync(cmd.bin, cmd.args, { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Potential command injection on Windows: Prompt for AI agents |
||
| cwd: projectRoot, | ||
| shell: true, | ||
| stdio: 'pipe', | ||
| timeout: 15000 | ||
| }); | ||
|
|
||
| if (result.error) { | ||
| throw result.error; | ||
| } | ||
|
|
||
| if (typeof result.status === 'number' && result.status !== 0) { | ||
| throw new Error(result.stderr?.toString() || `Formatter exited with status ${result.status}`); | ||
| } | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| execFileSync(cmd.bin, cmd.args, { | ||
| cwd: projectRoot, | ||
| stdio: ['pipe', 'pipe', 'pipe'], | ||
| timeout: 15000 | ||
| }); | ||
| } | ||
|
|
||
| process.stdin.on('end', () => { | ||
| try { | ||
| const input = JSON.parse(data); | ||
|
|
@@ -80,14 +155,10 @@ process.stdin.on('end', () => { | |
| try { | ||
| const projectRoot = findProjectRoot(path.dirname(path.resolve(filePath))); | ||
| const formatter = detectFormatter(projectRoot); | ||
| const cmd = getFormatterCommand(formatter, filePath); | ||
| const cmd = getFormatterCommand(formatter, filePath, projectRoot); | ||
|
|
||
| if (cmd) { | ||
| execFileSync(cmd.bin, cmd.args, { | ||
| cwd: projectRoot, | ||
| stdio: ['pipe', 'pipe', 'pipe'], | ||
| timeout: 15000 | ||
| }); | ||
| runFormatterCommand(cmd, projectRoot); | ||
| } | ||
| } catch { | ||
| // Formatter not installed, file missing, or failed — non-blocking | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Include
skills/in the manual-copy instructions.Line 42 still leaves out the
skills/assets that the documentedinstructionsentries reference later in this README (skills/.../SKILL.mdon Lines 193-195). As written, the “full OpenCode setup” path can still produce missing instruction files.✏️ Suggested wording
🤖 Prompt for AI Agents