diff --git a/src/cli/completion-cli.test.ts b/src/cli/completion-cli.test.ts index d2f34b0e8cb..efad2b34de7 100644 --- a/src/cli/completion-cli.test.ts +++ b/src/cli/completion-cli.test.ts @@ -1,3 +1,7 @@ +import { spawnSync } from "node:child_process"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; import { Command } from "commander"; import { describe, expect, it } from "vitest"; import { getCompletionScript } from "./completion-cli.js"; @@ -27,6 +31,56 @@ describe("completion-cli", () => { expect(script).toContain("--force[Force the action]"); }); + it("defers zsh registration until compinit is available", async () => { + if (process.platform === "win32") { + return; + } + + const probe = spawnSync("zsh", ["-fc", "exit 0"], { encoding: "utf8" }); + if (probe.error) { + if ("code" in probe.error && probe.error.code === "ENOENT") { + return; + } + throw probe.error; + } + + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-zsh-completion-")); + try { + const scriptPath = path.join(tempDir, "openclaw.zsh"); + await fs.writeFile(scriptPath, getCompletionScript("zsh", createCompletionProgram()), "utf8"); + + const result = spawnSync( + "zsh", + [ + "-fc", + ` + source ${JSON.stringify(scriptPath)} + [[ -z "\${_comps[openclaw]-}" ]] || exit 10 + [[ "\${precmd_functions[(r)_openclaw_register_completion]}" = "_openclaw_register_completion" ]] || exit 11 + autoload -Uz compinit + compinit -C + _openclaw_register_completion + [[ -z "\${precmd_functions[(r)_openclaw_register_completion]}" ]] || exit 12 + [[ "\${_comps[openclaw]-}" = "_openclaw_root_completion" ]] + `, + ], + { + encoding: "utf8", + env: { + ...process.env, + HOME: tempDir, + ZDOTDIR: tempDir, + }, + }, + ); + + expect(result.stderr).not.toContain("command not found: compdef"); + expect(result.status).toBe(0); + } finally { + await fs.rm(tempDir, { recursive: true, force: true }); + } + }); + it("generates PowerShell command paths without the executable prefix", () => { const script = getCompletionScript("powershell", createCompletionProgram()); diff --git a/src/cli/completion-cli.ts b/src/cli/completion-cli.ts index f25cd63dcdf..0d9fd947013 100644 --- a/src/cli/completion-cli.ts +++ b/src/cli/completion-cli.ts @@ -411,7 +411,23 @@ _${rootCmd}_root_completion() { ${generateZshSubcommands(program, rootCmd)} -compdef _${rootCmd}_root_completion ${rootCmd} +_${rootCmd}_register_completion() { + if (( ! $+functions[compdef] )); then + return 0 + fi + + compdef _${rootCmd}_root_completion ${rootCmd} + precmd_functions=(\${precmd_functions:#_${rootCmd}_register_completion}) + unfunction _${rootCmd}_register_completion 2>/dev/null +} + +_${rootCmd}_register_completion +if (( ! $+functions[compdef] )); then + typeset -ga precmd_functions + if [[ -z "\${precmd_functions[(r)_${rootCmd}_register_completion]}" ]]; then + precmd_functions+=(_${rootCmd}_register_completion) + fi +fi `; return script; }