mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-06 14:51:08 +00:00
The generated zsh completion script called compdef at source time, which fails with 'command not found: compdef' when loaded before compinit. Replace with a deferred registration that tries immediately, and if compdef is not yet available, queues a self-removing precmd hook that retries on first prompt. Handles repeated sourcing (deduped hook entry) and shells that never run compinit (completion simply never registers, matching zsh model). Add real zsh integration test verifying no compdef error on source and successful registration after compinit. Fixes #14289
107 lines
3.8 KiB
TypeScript
107 lines
3.8 KiB
TypeScript
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";
|
|
|
|
function createCompletionProgram(): Command {
|
|
const program = new Command();
|
|
program.name("openclaw");
|
|
program.description("CLI root");
|
|
program.option("-v, --verbose", "Verbose output");
|
|
|
|
const gateway = program.command("gateway").description("Gateway commands");
|
|
gateway.option("--force", "Force the action");
|
|
|
|
gateway.command("status").description("Show gateway status").option("--json", "JSON output");
|
|
gateway.command("restart").description("Restart gateway");
|
|
|
|
return program;
|
|
}
|
|
|
|
describe("completion-cli", () => {
|
|
it("generates zsh functions for nested subcommands", () => {
|
|
const script = getCompletionScript("zsh", createCompletionProgram());
|
|
|
|
expect(script).toContain("_openclaw_gateway()");
|
|
expect(script).toContain("(status) _openclaw_gateway_status ;;");
|
|
expect(script).toContain("(restart) _openclaw_gateway_restart ;;");
|
|
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());
|
|
|
|
expect(script).toContain("if ($commandPath -eq 'gateway') {");
|
|
expect(script).toContain("if ($commandPath -eq 'gateway status') {");
|
|
expect(script).not.toContain("if ($commandPath -eq 'openclaw gateway') {");
|
|
expect(script).toContain("$completions = @('status','restart','--force')");
|
|
});
|
|
|
|
it("generates fish completions for root and nested command contexts", () => {
|
|
const script = getCompletionScript("fish", createCompletionProgram());
|
|
|
|
expect(script).toContain(
|
|
'complete -c openclaw -n "__fish_use_subcommand" -a "gateway" -d \'Gateway commands\'',
|
|
);
|
|
expect(script).toContain(
|
|
'complete -c openclaw -n "__fish_seen_subcommand_from gateway" -a "status" -d \'Show gateway status\'',
|
|
);
|
|
expect(script).toContain(
|
|
"complete -c openclaw -n \"__fish_seen_subcommand_from gateway\" -l force -d 'Force the action'",
|
|
);
|
|
});
|
|
});
|