diff --git a/.changeset/completion-install-tips.md b/.changeset/completion-install-tips.md new file mode 100644 index 00000000..616cadeb --- /dev/null +++ b/.changeset/completion-install-tips.md @@ -0,0 +1,5 @@ +--- +"clerk": patch +--- + +Fix shell completion install tips so they work on fresh systems. The `clerk doctor` zsh remedy now leads with `eval "$(clerk completion zsh)"` and points to `clerk completion --help` for the file-based install method, and the fish remedy prefixes `mkdir -p ~/.config/fish/completions` before writing. The zsh completion script's install banner now tells users to `mkdir -p ~/.zfunc` before writing the completion file. diff --git a/packages/cli-core/src/cli-program.ts b/packages/cli-core/src/cli-program.ts index 4205b577..c480d4b6 100644 --- a/packages/cli-core/src/cli-program.ts +++ b/packages/cli-core/src/cli-program.ts @@ -501,6 +501,7 @@ Tutorial — enable completions for your shell: # Then add to ~/.zshrc: fpath=(~/.zfunc $fpath); autoload -Uz compinit && compinit Fish: + $ mkdir -p ~/.config/fish/completions $ clerk completion fish > ~/.config/fish/completions/clerk.fish # Auto-discovered PowerShell: diff --git a/packages/cli-core/src/commands/completion/README.md b/packages/cli-core/src/commands/completion/README.md index d0cf5554..cc81eb1c 100644 --- a/packages/cli-core/src/commands/completion/README.md +++ b/packages/cli-core/src/commands/completion/README.md @@ -43,6 +43,7 @@ clerk completion zsh > ~/.zfunc/_clerk ```sh # Fish auto-discovers completion files — just save it +mkdir -p ~/.config/fish/completions clerk completion fish > ~/.config/fish/completions/clerk.fish ``` diff --git a/packages/cli-core/src/commands/completion/index.test.ts b/packages/cli-core/src/commands/completion/index.test.ts index a92fde55..76079900 100644 --- a/packages/cli-core/src/commands/completion/index.test.ts +++ b/packages/cli-core/src/commands/completion/index.test.ts @@ -62,10 +62,30 @@ describe("shell script generators", () => { expect(generateZsh(BINARY)).toContain("_describe"); }); + test("zsh banner instructs users to mkdir ~/.zfunc before writing", () => { + const script = generateZsh(BINARY); + const mkdirIdx = script.indexOf("mkdir -p ~/.zfunc"); + const writeIdx = script.indexOf(`${BINARY} completion zsh > ~/.zfunc/_${BINARY}`); + expect(mkdirIdx).toBeGreaterThan(-1); + expect(writeIdx).toBeGreaterThan(-1); + expect(mkdirIdx).toBeLessThan(writeIdx); + }); + test("fish uses complete command", () => { expect(generateFish(BINARY)).toContain(`complete -c ${BINARY}`); }); + test("fish banner instructs users to mkdir ~/.config/fish/completions before writing", () => { + const script = generateFish(BINARY); + const mkdirIdx = script.indexOf("mkdir -p ~/.config/fish/completions"); + const writeIdx = script.indexOf( + `${BINARY} completion fish > ~/.config/fish/completions/${BINARY}.fish`, + ); + expect(mkdirIdx).toBeGreaterThan(-1); + expect(writeIdx).toBeGreaterThan(-1); + expect(mkdirIdx).toBeLessThan(writeIdx); + }); + test("powershell uses Register-ArgumentCompleter", () => { expect(generatePowershell(BINARY)).toContain("Register-ArgumentCompleter"); }); diff --git a/packages/cli-core/src/commands/completion/shells/fish.ts b/packages/cli-core/src/commands/completion/shells/fish.ts index c1c397c0..55354fa8 100644 --- a/packages/cli-core/src/commands/completion/shells/fish.ts +++ b/packages/cli-core/src/commands/completion/shells/fish.ts @@ -8,6 +8,7 @@ export function generate(binaryName: string): string { return `# Fish completion for ${binaryName} # Save to: +# mkdir -p ~/.config/fish/completions # ${binaryName} completion fish > ~/.config/fish/completions/${binaryName}.fish function __${binaryName}_complete diff --git a/packages/cli-core/src/commands/completion/shells/zsh.ts b/packages/cli-core/src/commands/completion/shells/zsh.ts index 591cbc0c..6769521b 100644 --- a/packages/cli-core/src/commands/completion/shells/zsh.ts +++ b/packages/cli-core/src/commands/completion/shells/zsh.ts @@ -14,6 +14,7 @@ export function generate(binaryName: string): string { # Add to ~/.zshrc: # eval "$(${binaryName} completion zsh)" # Or save to a file in your $fpath: +# mkdir -p ~/.zfunc # ${binaryName} completion zsh > ~/.zfunc/_${binaryName} # # Then ensure ~/.zshrc contains: # # fpath=(~/.zfunc $fpath) diff --git a/packages/cli-core/src/commands/doctor/checks.ts b/packages/cli-core/src/commands/doctor/checks.ts index 5c377c7d..0f4592fa 100644 --- a/packages/cli-core/src/commands/doctor/checks.ts +++ b/packages/cli-core/src/commands/doctor/checks.ts @@ -369,7 +369,8 @@ const SHELL_COMPLETION: Record< > = { fish: { isInstalled: (home) => Bun.file(join(home, ".config/fish/completions/clerk.fish")).exists(), - remedy: "Run `clerk completion fish > ~/.config/fish/completions/clerk.fish`", + remedy: + "Run `mkdir -p ~/.config/fish/completions && clerk completion fish > ~/.config/fish/completions/clerk.fish`", }, bash: { isInstalled: (home) => @@ -381,7 +382,7 @@ const SHELL_COMPLETION: Record< (await fileContains([join(home, ".zshrc")], "clerk completion")) || (await Bun.file(join(home, ".zfunc/_clerk")).exists()), remedy: - 'Add `eval "$(clerk completion zsh)"` to your ~/.zshrc, or run `clerk completion zsh > ~/.zfunc/_clerk`', + 'Add `eval "$(clerk completion zsh)"` to your ~/.zshrc (run `clerk completion --help` for other install methods)', }, }; diff --git a/packages/cli-core/src/commands/doctor/doctor.test.ts b/packages/cli-core/src/commands/doctor/doctor.test.ts index 25c2a46a..44ae628b 100644 --- a/packages/cli-core/src/commands/doctor/doctor.test.ts +++ b/packages/cli-core/src/commands/doctor/doctor.test.ts @@ -667,4 +667,21 @@ describe("checkShellCompletion", () => { fix: false, }); }); + + test("zsh remedy leads with the eval form (safe on fresh systems)", async () => { + process.env.SHELL = "/bin/zsh"; + process.env.HOME = tempDir; + const result = await checkShellCompletion(); + expect(result.remedy).toContain('eval "$(clerk completion zsh)"'); + // The raw fpath one-liner was unsafe — it fails when ~/.zfunc doesn't exist + // and silently no-ops without fpath/compinit setup in ~/.zshrc. + expect(result.remedy).not.toMatch(/clerk completion zsh > ~\/\.zfunc\/_clerk/); + }); + + test("fish remedy creates the completions directory before writing", async () => { + process.env.SHELL = "/usr/bin/fish"; + process.env.HOME = tempDir; + const result = await checkShellCompletion(); + expect(result.remedy).toContain("mkdir -p ~/.config/fish/completions"); + }); });