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.
This commit is contained in:
jackwener committed 2026-03-17 16:53:07 +08:00
1 parent eb2c3fdf89
commit 8263a06a85
1 file changed
+29 -8
+29 -8
View File
@@ -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) { function ensureZshFpath(completionsDir, zshrcPath) {
const fpathLine = `fpath=(${completionsDir} $fpath)`; const fpathLine = `fpath=(${completionsDir} $fpath)`;
const autoloadLine = `autoload -Uz compinit && compinit`; const autoloadLine = `autoload -Uz compinit && compinit`;
const marker = '# opencli completion';
if (!existsSync(zshrcPath)) { if (!existsSync(zshrcPath)) {
writeFileSync(zshrcPath, `${fpathLine}\n${autoloadLine}\n`, 'utf8'); writeFileSync(zshrcPath, `${marker}\n${fpathLine}\n${autoloadLine}\n`, 'utf8');
return; return;
} }
const content = readFileSync(zshrcPath, 'utf8'); const content = readFileSync(zshrcPath, 'utf8');
// Check if completions dir is already in fpath // Already configured — nothing to do
if (content.includes(completionsDir)) { if (content.includes(completionsDir)) {
return; // already configured return;
} }
// Append fpath configuration // Find the first line that triggers compinit (direct call or oh-my-zsh source)
let addition = `\n# opencli completion\n${fpathLine}\n`; const lines = content.split('\n');
if (!content.includes('compinit')) { let insertIdx = -1;
addition += `${autoloadLine}\n`; 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 ─────────────────────────────────────────────────────────────────── // ── Main ───────────────────────────────────────────────────────────────────