diff --git a/.changeset/shell-completions-feature.md b/.changeset/shell-completions-feature.md new file mode 100644 index 000000000000..2ba735adc035 --- /dev/null +++ b/.changeset/shell-completions-feature.md @@ -0,0 +1,18 @@ +--- +"wrangler": minor +--- + +Add `wrangler completions` command for shell completion scripts (bash, zsh, fish) + +Usage: + +```bash +# Bash +wrangler completions bash >> ~/.bashrc + +# Zsh +wrangler completions zsh >> ~/.zshrc + +# Fish +wrangler completions fish > ~/.config/fish/completions/wrangler.fish +``` diff --git a/packages/wrangler/src/__tests__/completions.test.ts b/packages/wrangler/src/__tests__/completions.test.ts new file mode 100644 index 000000000000..1b42c6a5b6e9 --- /dev/null +++ b/packages/wrangler/src/__tests__/completions.test.ts @@ -0,0 +1,264 @@ +import { execSync } from "node:child_process"; +import { describe, test } from "vitest"; +import { mockConsoleMethods } from "./helpers/mock-console"; +import { runInTempDir } from "./helpers/run-in-tmp"; +import { runWrangler } from "./helpers/run-wrangler"; + +function shellAvailable(shell: string): boolean { + try { + execSync(`which ${shell}`, { stdio: "ignore" }); + return true; + } catch { + return false; + } +} + +describe("wrangler", () => { + describe("completions", () => { + const std = mockConsoleMethods(); + runInTempDir(); + + test("should show available shells in help", async ({ expect }) => { + const result = runWrangler("completions --help"); + + await expect(result).resolves.toBeUndefined(); + expect(std.out).toContain("Generate shell completion scripts"); + expect(std.out).toContain("wrangler completions bash"); + expect(std.out).toContain("wrangler completions zsh"); + expect(std.out).toContain("wrangler completions fish"); + }); + + // ========================================================================= + // __complete command tests + // ========================================================================= + describe("__complete", () => { + test("should return top-level commands", async ({ expect }) => { + await runWrangler("__complete wrangler"); + + expect(std.out).toContain("deploy\t"); + expect(std.out).toContain("dev\t"); + expect(std.out).toContain("kv\t"); + }); + + test("should return subcommands for namespace", async ({ expect }) => { + // Empty string signals "complete after kv" + await runWrangler('__complete wrangler kv ""'); + + expect(std.out).toContain("namespace\t"); + expect(std.out).toContain("key\t"); + }); + + test("should filter by prefix", async ({ expect }) => { + await runWrangler("__complete wrangler kv na"); + + expect(std.out).toContain("namespace\t"); + expect(std.out).not.toContain("key\t"); + }); + + test("should return flags when prefix is --", async ({ expect }) => { + // Empty string after dev, then filter by -- + await runWrangler('__complete wrangler dev "" --'); + + expect(std.out).toContain("--port\t"); + expect(std.out).toContain("--config\t"); + }); + + test("should exclude hidden commands", async ({ expect }) => { + await runWrangler("__complete wrangler"); + + // "check" namespace is hidden + expect(std.out).not.toMatch(/^check\t/m); + }); + + test("should handle deeply nested commands", async ({ expect }) => { + // Empty string signals "complete after http" + await runWrangler('__complete wrangler queues consumer http ""'); + + expect(std.out).toContain("add\t"); + expect(std.out).toContain("remove\t"); + }); + + test("should output tab-separated format", async ({ expect }) => { + await runWrangler("__complete wrangler"); + + // Each line should be "value\tdescription" + const lines = std.out.trim().split("\n"); + for (const line of lines) { + expect(line).toMatch(/^[^\t]+\t.*$/); + } + }); + + test("should include global flags", async ({ expect }) => { + await runWrangler("__complete wrangler --"); + + expect(std.out).toContain("--help\t"); + expect(std.out).toContain("--config\t"); + }); + + test("should skip flags when building command path", async ({ + expect, + }) => { + // --binding should be skipped, so we're completing flags for "d1 create" + // Use -- to prevent yargs from parsing --binding as a flag for __complete + await runWrangler('__complete -- wrangler d1 create --binding foo ""'); + + expect(std.out).toContain("--name\t"); + // Should not re-suggest d1 or create + expect(std.out).not.toMatch(/^d1\t/m); + expect(std.out).not.toMatch(/^create\t/m); + }); + + test("should skip flag values when building command path", async ({ + expect, + }) => { + // Both --config and its value should be skipped + // Use -- to prevent yargs from parsing --config as a flag for __complete + await runWrangler( + '__complete -- wrangler --config wrangler.toml kv ""' + ); + + expect(std.out).toContain("namespace\t"); + expect(std.out).toContain("key\t"); + }); + }); + + // ========================================================================= + // Shell script generation tests + // ========================================================================= + const shells = ["bash", "zsh", "fish"] as const; + + describe.each(shells)("%s", (shell) => { + test("should output script with begin/end markers", async ({ + expect, + }) => { + await runWrangler(`completions ${shell}`); + + expect(std.out).toContain("###-begin-wrangler-completions-###"); + expect(std.out).toContain("###-end-wrangler-completions-###"); + }); + + test("should show shell-specific description in help", async ({ + expect, + }) => { + await runWrangler(`completions ${shell} --help`); + + expect(std.out).toContain(`Generate ${shell} completion script`); + }); + + test("should reference wrangler __complete", async ({ expect }) => { + await runWrangler(`completions ${shell}`); + + expect(std.out).toContain("wrangler __complete"); + }); + }); + + // ========================================================================= + // Shell syntax validation tests + // ========================================================================= + describe("bash", () => { + test.skipIf(!shellAvailable("bash"))( + "should generate valid bash syntax", + async ({ expect }) => { + await runWrangler("completions bash"); + + // bash -n checks syntax without executing + expect(() => { + execSync("bash -n", { input: std.out }); + }).not.toThrow(); + } + ); + + test("should define _wrangler_completions function", async ({ + expect, + }) => { + await runWrangler("completions bash"); + + expect(std.out).toContain("_wrangler_completions()"); + }); + + test("should register completion with complete builtin", async ({ + expect, + }) => { + await runWrangler("completions bash"); + + expect(std.out).toContain( + "complete -o default -F _wrangler_completions wrangler" + ); + }); + }); + + describe("zsh", () => { + test.skipIf(!shellAvailable("zsh"))( + "should generate valid zsh syntax", + async ({ expect }) => { + await runWrangler("completions zsh"); + + // zsh -n checks syntax without executing + expect(() => { + execSync("zsh -n", { input: std.out }); + }).not.toThrow(); + } + ); + + test("should start with #compdef directive", async ({ expect }) => { + await runWrangler("completions zsh"); + + expect(std.out).toContain("#compdef wrangler"); + }); + + test("should use _describe for completions", async ({ expect }) => { + await runWrangler("completions zsh"); + + expect(std.out).toContain("_describe 'wrangler' completions"); + }); + + test("should register with compdef", async ({ expect }) => { + await runWrangler("completions zsh"); + + expect(std.out).toContain("compdef _wrangler wrangler"); + }); + }); + + describe("fish", () => { + test.skipIf(!shellAvailable("fish"))( + "should generate valid fish syntax", + async ({ expect }) => { + await runWrangler("completions fish"); + + // fish -n checks syntax without executing + expect(() => { + execSync("fish -n", { input: std.out }); + }).not.toThrow(); + } + ); + + test("should define __wrangler_prepare_completions function", async ({ + expect, + }) => { + await runWrangler("completions fish"); + + expect(std.out).toContain("function __wrangler_prepare_completions"); + }); + + test("should register completion with complete builtin", async ({ + expect, + }) => { + await runWrangler("completions fish"); + + expect(std.out).toContain("complete -c wrangler -f -n"); + expect(std.out).toContain("$__wrangler_comp_results"); + }); + + test("should capture both completed tokens and current token", async ({ + expect, + }) => { + await runWrangler("completions fish"); + + // commandline -opc gets completed tokens + expect(std.out).toContain("commandline -opc"); + // commandline -ct gets current token being typed + expect(std.out).toContain("commandline -ct"); + }); + }); + }); +}); diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts index d8bd922e9d84..4184a43cdcd5 100644 --- a/packages/wrangler/src/__tests__/index.test.ts +++ b/packages/wrangler/src/__tests__/index.test.ts @@ -72,6 +72,7 @@ describe("wrangler", () => { wrangler logout 🚪 Logout from Cloudflare wrangler whoami 🕵️ Retrieve your user information wrangler auth 🔐 Manage authentication + wrangler completions ⌨️ Generate shell completion scripts GLOBAL FLAGS -c, --config Path to Wrangler configuration file [string] @@ -137,6 +138,7 @@ describe("wrangler", () => { wrangler logout 🚪 Logout from Cloudflare wrangler whoami 🕵️ Retrieve your user information wrangler auth 🔐 Manage authentication + wrangler completions ⌨️ Generate shell completion scripts GLOBAL FLAGS -c, --config Path to Wrangler configuration file [string] diff --git a/packages/wrangler/src/completions/commands.ts b/packages/wrangler/src/completions/commands.ts new file mode 100644 index 000000000000..6db0d0bac97e --- /dev/null +++ b/packages/wrangler/src/completions/commands.ts @@ -0,0 +1,95 @@ +import { createCommand, createNamespace } from "../core/create-command"; +import { logger } from "../logger"; +import { handleComplete } from "./complete-handler"; +import { getBashScript, getFishScript, getZshScript } from "./scripts"; + +export const completionsNamespace = createNamespace({ + metadata: { + description: "⌨️ Generate shell completion scripts", + owner: "Workers: Authoring and Testing", + status: "stable", + epilogue: `Installation: + bash: wrangler completions bash >> ~/.bashrc + zsh: wrangler completions zsh >> ~/.zshrc + fish: wrangler completions fish > ~/.config/fish/completions/wrangler.fish`, + }, +}); + +export const completionsBashCommand = createCommand({ + metadata: { + description: "Generate bash completion script", + owner: "Workers: Authoring and Testing", + status: "stable", + }, + behaviour: { + printBanner: false, + provideConfig: false, + }, + handler() { + logger.log(getBashScript()); + }, +}); + +export const completionsZshCommand = createCommand({ + metadata: { + description: "Generate zsh completion script", + owner: "Workers: Authoring and Testing", + status: "stable", + }, + behaviour: { + printBanner: false, + provideConfig: false, + }, + handler() { + logger.log(getZshScript()); + }, +}); + +export const completionsFishCommand = createCommand({ + metadata: { + description: "Generate fish completion script", + owner: "Workers: Authoring and Testing", + status: "stable", + }, + behaviour: { + printBanner: false, + provideConfig: false, + }, + handler() { + logger.log(getFishScript()); + }, +}); + +export const completeCommand = createCommand({ + metadata: { + description: "Output completions for shell integration", + owner: "Workers: Authoring and Testing", + status: "stable", + hidden: true, // Not shown in --help + }, + behaviour: { + printBanner: false, + provideConfig: false, + }, + positionalArgs: ["args"], + args: { + args: { + type: "string", + array: true, + description: "Command line words to complete", + }, + }, + handler(args) { + // When -- is used, yargs puts args in _ instead of the positional array + // and includes the command name "__complete" as first element + let completionArgs = + args.args && args.args.length > 0 + ? args.args + : (args as unknown as { _: string[] })._ ?? []; + // Filter out __complete if it's the first arg (happens with --) + if (completionArgs[0] === "__complete") { + completionArgs = completionArgs.slice(1); + } + handleComplete(completionArgs); + }, +}); diff --git a/packages/wrangler/src/completions/complete-handler.ts b/packages/wrangler/src/completions/complete-handler.ts new file mode 100644 index 000000000000..c25d75ac1961 --- /dev/null +++ b/packages/wrangler/src/completions/complete-handler.ts @@ -0,0 +1,216 @@ +import { globalFlags } from "../global-flags"; +import { logger } from "../logger"; +import { getDefinitionTree } from "./registry-store"; +import type { DefinitionTree, NamedArgDefinitions } from "../core/types"; + +interface CompletionResult { + value: string; + description: string; +} + +/** + * Handle __complete command - outputs tab-separated completions to stdout. + * Called by shell completion scripts at runtime. + * + * @param args - Command line words (e.g., ["wrangler", "kv", "na"]) + */ +export function handleComplete(args: string[]): void { + // args[0] = "wrangler", args[1..n] = subcommands/partial input + const words = args.slice(1); // Remove "wrangler" + const current = words[words.length - 1] || ""; // Word being completed + + // Build command path, skipping flags and their values + const commandPath: string[] = []; + const completedWords = words.slice(0, -1); + for (let i = 0; i < completedWords.length; i++) { + const word = completedWords[i]; + if (word.startsWith("-")) { + // Skip flag and potentially its value (next word if not a flag) + const nextWord = completedWords[i + 1]; + if (nextWord && !nextWord.startsWith("-")) { + i++; // Skip the flag's value + } + } else { + commandPath.push(word); + } + } + + const tree = getDefinitionTree(); + const completions = getCompletions(tree, commandPath, current); + + for (const c of completions) { + // Tab-separated format: "value\tdescription" + // Shell parses this natively - no escaping needed + logger.log(`${c.value}\t${c.description}`); + } +} + +function getCompletions( + tree: DefinitionTree, + commandPath: string[], + prefix: string +): CompletionResult[] { + const results: CompletionResult[] = []; + + // Navigate to current position in command tree + let node = tree; + for (const segment of commandPath) { + const child = node.get(segment); + if (!child) { + return results; // Invalid path + } + node = child.subtree; + } + + // If prefix starts with "-", complete flags + if (prefix.startsWith("-")) { + results.push(...getFlagCompletions(tree, commandPath, prefix)); + } else { + // Complete subcommands + for (const [name, child] of node.entries()) { + const def = child.definition; + if (!def || def.metadata?.hidden || def.type === "alias") { + continue; + } + + if (name.startsWith(prefix)) { + results.push({ + value: name, + description: sanitizeDescription(def.metadata?.description ?? ""), + }); + } + } + } + + // Add flag completions if not already completing a flag + if (!prefix.startsWith("-")) { + results.push(...getFlagCompletions(tree, commandPath, "")); + } + + return results; +} + +function getFlagCompletions( + tree: DefinitionTree, + commandPath: string[], + prefix: string +): CompletionResult[] { + const results: CompletionResult[] = []; + + // Get command-specific flags + const cmdFlags = getCommandFlags(tree, commandPath); + for (const flag of cmdFlags) { + const longFlag = `--${flag.name}`; + if (longFlag.startsWith(prefix)) { + results.push({ + value: longFlag, + description: sanitizeDescription(flag.description), + }); + } + } + + // Add global flags + for (const name of Object.keys(globalFlags)) { + const def = globalFlags[name as keyof typeof globalFlags]; + + // Skip hidden flags + if ("hidden" in def && def.hidden) { + continue; + } + + const longFlag = `--${name}`; + if (longFlag.startsWith(prefix)) { + results.push({ + value: longFlag, + description: sanitizeDescription(def.describe ?? ""), + }); + } + } + + // Add --help which is provided by yargs + if ("--help".startsWith(prefix)) { + results.push({ + value: "--help", + description: "Show help", + }); + } + + return results; +} + +interface FlagMeta { + name: string; + description: string; +} + +function getCommandFlags( + tree: DefinitionTree, + commandPath: string[] +): FlagMeta[] { + const flags: FlagMeta[] = []; + + // Navigate to the command + let node = tree; + let def = null; + for (const segment of commandPath) { + const child = node.get(segment); + if (!child) { + return flags; + } + def = child.definition; + node = child.subtree; + } + + if (!def || def.type !== "command") { + return flags; + } + + const args: NamedArgDefinitions = def.args ?? {}; + + for (const name of Object.keys(args)) { + const arg = args[name]; + if (!arg || arg.hidden) { + continue; + } + + const description = + typeof arg.describe === "string" + ? arg.describe + : typeof arg.description === "string" + ? arg.description + : ""; + + flags.push({ + name, + description, + }); + } + + return flags; +} + +/** + * Sanitize description - remove special chars that could break output + */ +function sanitizeDescription(desc: string): string { + return ( + desc + // Remove ANSI color codes + // eslint-disable-next-line no-control-regex + .replace(/\x1b\[[0-9;]*m/g, "") + // Remove status badges + .replace(/\s*\[(?:experimental|alpha|private-beta|open-beta)\]/gi, "") + // Remove default values and deprecated + .replace(/\s*\[default:[^\]]*\]/gi, "") + .replace(/\s*\[deprecated\]/gi, "") + // Remove parenthetical asides + .replace(/\s*\([^)]*\)/g, "") + // Remove tabs (our delimiter) + .replace(/\t/g, " ") + // Remove newlines + .replace(/\n/g, " ") + .trim() + // Limit to 80 chars to prevent terminal overflow + .slice(0, 80) + ); +} diff --git a/packages/wrangler/src/completions/index.ts b/packages/wrangler/src/completions/index.ts new file mode 100644 index 000000000000..8900fe32684e --- /dev/null +++ b/packages/wrangler/src/completions/index.ts @@ -0,0 +1,9 @@ +export { + completionsNamespace, + completionsBashCommand, + completionsZshCommand, + completionsFishCommand, + completeCommand, +} from "./commands"; + +export { setDefinitionTree, getDefinitionTree } from "./registry-store"; diff --git a/packages/wrangler/src/completions/registry-store.ts b/packages/wrangler/src/completions/registry-store.ts new file mode 100644 index 000000000000..12ecb91396b7 --- /dev/null +++ b/packages/wrangler/src/completions/registry-store.ts @@ -0,0 +1,18 @@ +import type { DefinitionTree } from "../core/types"; + +/** + * Module-level store for the command registry definition tree. + * This is set during CLI initialization and accessed by the fish completions handler. + */ +let definitionTree: DefinitionTree | undefined; + +export function setDefinitionTree(tree: DefinitionTree): void { + definitionTree = tree; +} + +export function getDefinitionTree(): DefinitionTree { + if (!definitionTree) { + throw new Error("Definition tree not initialized"); + } + return definitionTree; +} diff --git a/packages/wrangler/src/completions/scripts.ts b/packages/wrangler/src/completions/scripts.ts new file mode 100644 index 000000000000..a068d30c1cde --- /dev/null +++ b/packages/wrangler/src/completions/scripts.ts @@ -0,0 +1,88 @@ +/** + * Generate dynamic bash completion script. + * Calls `wrangler __complete` at runtime for completions. + */ +export function getBashScript(): string { + return `###-begin-wrangler-completions-### +# +# wrangler bash completion +# +# Installation: wrangler completions bash >> ~/.bashrc +# + +_wrangler_completions() { + local cur="\${COMP_WORDS[COMP_CWORD]}" + local IFS=$'\\n' + + # Call wrangler __complete with all words + # Use -- to stop yargs from parsing flags in the completion args + local completions + completions=$(wrangler __complete -- "\${COMP_WORDS[@]}" 2>/dev/null) + + # Parse tab-separated output (value\\tdescription) + # Bash complete only uses the value part + COMPREPLY=() + while IFS=$'\\t' read -r value desc; do + if [[ "$value" == "$cur"* ]]; then + COMPREPLY+=("$value") + fi + done <<< "$completions" +} + +complete -o default -F _wrangler_completions wrangler +###-end-wrangler-completions-###`; +} + +/** + * Generate dynamic zsh completion script. + * Calls `wrangler __complete` at runtime for completions. + */ +export function getZshScript(): string { + return `#compdef wrangler +###-begin-wrangler-completions-### +# +# wrangler zsh completion +# +# Installation: wrangler completions zsh >> ~/.zshrc +# + +_wrangler() { + local -a completions + local line + + # Call wrangler __complete with current words + # Use -- to stop yargs from parsing flags in the completion args + while IFS=$'\\t' read -r value desc; do + completions+=("\${value}:\${desc}") + done < <(wrangler __complete -- "\${words[@]}" 2>/dev/null) + + _describe 'wrangler' completions +} + +compdef _wrangler wrangler +###-end-wrangler-completions-###`; +} + +/** + * Generate dynamic fish completion script. + * Calls `wrangler __complete` at runtime for completions. + */ +export function getFishScript(): string { + return `###-begin-wrangler-completions-### +# +# wrangler fish completion +# +# Installation: wrangler completions fish > ~/.config/fish/completions/wrangler.fish +# + +function __wrangler_prepare_completions + set -l tokens (commandline -opc) + set -l current (commandline -ct) + # Use -- to stop yargs from parsing flags in the completion args + set --global __wrangler_comp_results (wrangler __complete -- $tokens $current 2>/dev/null) + return 0 +end + +complete -c wrangler -f -n '__wrangler_prepare_completions' -a '$__wrangler_comp_results' +###-end-wrangler-completions-###`; +} diff --git a/packages/wrangler/src/global-flags.ts b/packages/wrangler/src/global-flags.ts new file mode 100644 index 000000000000..195ebfc0cb85 --- /dev/null +++ b/packages/wrangler/src/global-flags.ts @@ -0,0 +1,53 @@ +/** + * Global flags available on all wrangler commands. + * Extracted to a shared module for use by completions and the CLI parser. + */ +export const globalFlags = { + v: { + describe: "Show version number", + alias: "version", + type: "boolean", + }, + cwd: { + describe: + "Run as if Wrangler was started in the specified directory instead of the current working directory", + type: "string", + requiresArg: true, + }, + config: { + alias: "c", + describe: "Path to Wrangler configuration file", + type: "string", + requiresArg: true, + }, + env: { + alias: "e", + describe: + "Environment to use for operations, and for selecting .env and .dev.vars files", + type: "string", + requiresArg: true, + }, + "env-file": { + describe: + "Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files", + type: "string", + array: true, + requiresArg: true, + }, + "experimental-provision": { + describe: `Experimental: Enable automatic resource provisioning`, + type: "boolean", + default: true, + hidden: true, + alias: ["x-provision"], + }, + "experimental-auto-create": { + describe: "Automatically provision draft bindings with new resources", + type: "boolean", + default: true, + hidden: true, + alias: "x-auto-create", + }, +} as const; + +export type GlobalFlags = typeof globalFlags; diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 3fcb21eba4a3..1dd748b5986a 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -26,6 +26,14 @@ import { } from "./cert/cert"; import { checkNamespace, checkStartupCommand } from "./check/commands"; import { cloudchamber } from "./cloudchamber"; +import { + completeCommand, + completionsBashCommand, + completionsFishCommand, + completionsNamespace, + completionsZshCommand, + setDefinitionTree, +} from "./completions"; import { getDefaultEnvFiles, loadDotEnv } from "./config/dot-env"; import { containers } from "./containers"; import { demandSingleValue } from "./core"; @@ -59,6 +67,7 @@ import { dispatchNamespaceRenameCommand, } from "./dispatch-namespace"; import { docs } from "./docs"; +import { globalFlags } from "./global-flags"; import { helloWorldGetCommand, helloWorldNamespace, @@ -355,53 +364,6 @@ if (proxy) { } export function createCLIParser(argv: string[]) { - const globalFlags = { - v: { - describe: "Show version number", - alias: "version", - type: "boolean", - }, - cwd: { - describe: - "Run as if Wrangler was started in the specified directory instead of the current working directory", - type: "string", - requiresArg: true, - }, - config: { - alias: "c", - describe: "Path to Wrangler configuration file", - type: "string", - requiresArg: true, - }, - env: { - alias: "e", - describe: - "Environment to use for operations, and for selecting .env and .dev.vars files", - type: "string", - requiresArg: true, - }, - "env-file": { - describe: - "Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files", - type: "string", - array: true, - requiresArg: true, - }, - "experimental-provision": { - describe: `Experimental: Enable automatic resource provisioning`, - type: "boolean", - default: true, - hidden: true, - alias: ["x-provision"], - }, - "experimental-auto-create": { - describe: "Automatically provision draft bindings with new resources", - type: "boolean", - default: true, - hidden: true, - alias: "x-auto-create", - }, - } as const; // Type check result against CommonYargsOptions to make sure we've included // all common options const wrangler: CommonYargsArgv = makeCLI(argv) @@ -1617,6 +1579,33 @@ export function createCLIParser(argv: string[]) { ]); registry.registerNamespace("build"); + registry.define([ + { + command: "wrangler completions", + definition: completionsNamespace, + }, + { + command: "wrangler completions bash", + definition: completionsBashCommand, + }, + { + command: "wrangler completions zsh", + definition: completionsZshCommand, + }, + { + command: "wrangler completions fish", + definition: completionsFishCommand, + }, + { + command: "wrangler __complete", + definition: completeCommand, + }, + ]); + registry.registerNamespace("completions"); + + // Store definition tree for completions generation + setDefinitionTree(registry.getDefinitionTreeRoot().subtree); + // This set to false to allow overwrite of default behaviour wrangler.version(false);