From 8263a06a85a2080da2d26db0602064615446d68d Mon Sep 17 00:00:00 2001 From: jackwener Date: Tue, 17 Mar 2026 16:53:07 +0800 Subject: [PATCH] fix(completion): insert fpath before compinit in .zshrc The postinstall script was appending the fpath line at the end of .zshrc, but compinit (called earlier by oh-my-zsh or directly) would have already finished scanning. This caused zsh completion to silently fail for most users. Now the script detects the first compinit / oh-my-zsh source line and inserts the fpath entry before it, ensuring completion works immediately. --- scripts/postinstall.js | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 7e955d5..7fefda6 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -70,30 +70,51 @@ function ensureDir(dir) { } /** - * Ensure fpath contains the custom completions directory in .zshrc + * Ensure fpath contains the custom completions directory in .zshrc. + * + * Key detail: the fpath line MUST appear BEFORE the first `compinit` call, + * otherwise compinit won't scan our completions directory. This is critical + * for oh-my-zsh users (source $ZSH/oh-my-zsh.sh calls compinit internally). */ function ensureZshFpath(completionsDir, zshrcPath) { const fpathLine = `fpath=(${completionsDir} $fpath)`; const autoloadLine = `autoload -Uz compinit && compinit`; + const marker = '# opencli completion'; if (!existsSync(zshrcPath)) { - writeFileSync(zshrcPath, `${fpathLine}\n${autoloadLine}\n`, 'utf8'); + writeFileSync(zshrcPath, `${marker}\n${fpathLine}\n${autoloadLine}\n`, 'utf8'); return; } const content = readFileSync(zshrcPath, 'utf8'); - // Check if completions dir is already in fpath + // Already configured — nothing to do if (content.includes(completionsDir)) { - return; // already configured + return; } - // Append fpath configuration - let addition = `\n# opencli completion\n${fpathLine}\n`; - if (!content.includes('compinit')) { - addition += `${autoloadLine}\n`; + // Find the first line that triggers compinit (direct call or oh-my-zsh source) + const lines = content.split('\n'); + let insertIdx = -1; + for (let i = 0; i < lines.length; i++) { + const trimmed = lines[i].trim(); + // Skip comment-only lines + if (trimmed.startsWith('#')) continue; + if (/compinit/.test(trimmed) || /source\s+.*oh-my-zsh\.sh/.test(trimmed)) { + insertIdx = i; + break; + } + } + + if (insertIdx !== -1) { + // Insert fpath BEFORE the compinit / oh-my-zsh source line + lines.splice(insertIdx, 0, marker, fpathLine); + writeFileSync(zshrcPath, lines.join('\n'), 'utf8'); + } else { + // No compinit found — append fpath + compinit at the end + let addition = `\n${marker}\n${fpathLine}\n${autoloadLine}\n`; + appendFileSync(zshrcPath, addition, 'utf8'); } - appendFileSync(zshrcPath, addition, 'utf8'); } // ── Main ───────────────────────────────────────────────────────────────────