Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e9c0d42
feat(wrangler/cli): add tab completions
AmirSa12 Oct 27, 2025
f5f65e1
chore: update tab to version 0.0.9
AmirSa12 Nov 4, 2025
3e0e733
Merge remote-tracking branch 'upstream/main' into feat/tab-completions
AmirSa12 Nov 9, 2025
4b2b606
refactor(wrangler): use experimental_getWranglerCommands function for…
AmirSa12 Nov 9, 2025
b74ad8d
Update changelog for wrangler tab completions
ascorbic Dec 15, 2025
f0f739b
Merge branch 'main' into feat/tab-completions
ascorbic Dec 15, 2025
768ef7a
Format
ascorbic Dec 15, 2025
e38e836
Merge remote-tracking branch 'upstream/main' into feat/tab-completions
AmirSa12 Jan 5, 2026
be3b023
chore: update bomb.sh/tab to version 0.0.11
AmirSa12 Jan 5, 2026
15d5e4c
Merge branch 'main' into feat/tab-completions
NuroDev Jan 6, 2026
b1e60dd
Merge branch 'main' into feat/tab-completions
NuroDev Jan 7, 2026
412fa27
Merge branch 'main' into feat/tab-completions
NuroDev Jan 8, 2026
f49e16b
add tests
AmirSa12 Jan 8, 2026
20222b6
address Ben's review
AmirSa12 Jan 8, 2026
3437d21
update changeset .md file
AmirSa12 Jan 8, 2026
8c87c89
Merge branch 'main' into feat/tab-completions
NuroDev Jan 8, 2026
77a6ef3
Merge branch 'main' into feat/tab-completions
NuroDev Jan 9, 2026
89757a4
Merge branch 'main' into feat/tab-completions
NuroDev Jan 9, 2026
85ca3e7
Merge branch 'main' into feat/tab-completions
NuroDev Jan 9, 2026
4f5476c
Merge branch 'main' into feat/tab-completions
NuroDev Jan 9, 2026
7752c58
Merge branch 'main' into feat/tab-completions
NuroDev Jan 9, 2026
2f05cd5
Merge branch 'main' into feat/tab-completions
NuroDev Jan 9, 2026
c8d2486
Merge branch 'main' into feat/tab-completions
NuroDev Jan 12, 2026
e8a27f9
Merge branch 'main' into feat/tab-completions
NuroDev Jan 12, 2026
d18b46e
Merge branch 'main' into feat/tab-completions
NuroDev Jan 20, 2026
25bee9d
Removed self-explanitory doc comments
NuroDev Jan 21, 2026
ac25016
Switched to top-level `expect` import for completion tests
NuroDev Jan 21, 2026
149ae0f
Minor linting fixes
NuroDev Jan 21, 2026
137fb10
Added Fish shell to changeset
NuroDev Jan 21, 2026
6fd7f18
Refactored to use dedicated completion command namespace handler
NuroDev Jan 21, 2026
72e17bb
Minor help menu formatting fixes
NuroDev Jan 21, 2026
b4364b0
Minor status label fixes
NuroDev Jan 21, 2026
76fc750
Overhauled `complete` command logic
NuroDev Jan 21, 2026
5efb94b
Merge branch 'main' into feat/tab-completions
NuroDev Jan 21, 2026
a016b97
Fixed test help menu snapshots
NuroDev Jan 21, 2026
e9e79a1
Merge branch 'main' into feat/tab-completions
NuroDev Jan 21, 2026
b2fc39e
Minor `completionArgs` filtering tweak
NuroDev Jan 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rude-cows-cheat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

Add tab completion for commands
1 change: 1 addition & 0 deletions packages/wrangler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.721.0",
"@bomb.sh/tab": "^0.0.11",
"@cloudflare/cli": "workspace:*",
"@cloudflare/containers-shared": "workspace:*",
"@cloudflare/eslint-config-shared": "workspace:*",
Expand Down
118 changes: 118 additions & 0 deletions packages/wrangler/src/complete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// shell completions for wrangler - this dynamically extracts commands from the actual command registry
import t from "@bomb.sh/tab";
import { experimental_getWranglerCommands } from "./experimental-commands-api";
import type { DefinitionTreeNode } from "./core/types";

export function setupCompletions() {
const { registry, globalFlags } = experimental_getWranglerCommands();

// global flags that work on every command
for (const [flagName, flagDef] of Object.entries(globalFlags)) {
// skip hidden flags
if ("hidden" in flagDef && flagDef.hidden) continue;

const description = flagDef.describe || "";
t.option(flagName, description);

if ("alias" in flagDef && flagDef.alias) {
const aliases = Array.isArray(flagDef.alias)
? flagDef.alias
: [flagDef.alias];
for (const alias of aliases) {
t.option(alias, `Alias for --${flagName}`);
}
}
}

// recursively add commands from the registry tree
function addCommandsFromTree(
node: DefinitionTreeNode,
parentPath: string[] = []
) {
for (const [name, childNode] of node.subtree.entries()) {
const commandPath = [...parentPath, name];
const commandName = commandPath.join(" ");

if (childNode.definition) {
const def = childNode.definition;
let description = "";

if (def.metadata?.description) {
description = def.metadata.description;
}

if (
def.metadata?.status &&
def.metadata.status !== "stable" &&
!def.metadata.hidden
) {
const statusLabels = {
experimental: "[experimental]",
alpha: "[alpha]",
"private-beta": "[private-beta]",
"open-beta": "[open-beta]",
};
const statusLabel =
statusLabels[def.metadata.status as keyof typeof statusLabels];
if (statusLabel) {
description = `${description} ${statusLabel}`;
}
}

if (!def.metadata?.hidden) {
const cmd = t.command(commandName, description);

if (def.type === "command" && "args" in def) {
const args = def.args || {};
for (const [argName, argDef] of Object.entries(args)) {
if (argDef.hidden) continue;

const argDescription = argDef.describe || "";

if (argDef.choices && Array.isArray(argDef.choices)) {
cmd.option(argName, argDescription, (complete) => {
for (const choice of argDef.choices as string[]) {
complete(choice, choice);
}
});
} else {
cmd.option(argName, argDescription);
}

if (argDef.alias) {
const aliases = Array.isArray(argDef.alias)
? argDef.alias
: [argDef.alias];
for (const alias of aliases) {
cmd.option(alias, `Alias for --${argName}`);
}
}
}
}
}
}

if (childNode.subtree.size > 0) {
addCommandsFromTree(childNode, commandPath);
}
}
}

addCommandsFromTree(registry);

return t;
}
// Handle completion requests from the shell
export function handleCompletion(args: string[]) {
const shell = args[0];

if (shell === "--") {
// Parse completion request from shell
setupCompletions();
t.parse(args.slice(1));
} else {
// Generate shell completion script
setupCompletions();
t.setup("wrangler", "wrangler", shell);
}
}
7 changes: 7 additions & 0 deletions packages/wrangler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from "./cert/cert";
import { checkNamespace, checkStartupCommand } from "./check/commands";
import { cloudchamber } from "./cloudchamber";
import { handleCompletion } from "./complete";
import { getDefaultEnvFiles, loadDotEnv } from "./config/dot-env";
import { containers } from "./containers";
import { demandSingleValue } from "./core";
Expand Down Expand Up @@ -1630,6 +1631,12 @@ export function createCLIParser(argv: string[]) {
export async function main(argv: string[]): Promise<void> {
setupSentry();

// Handle shell completion requests
if (argv[0] === "complete") {
handleCompletion(argv.slice(1));
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way we can instead make this follow the existing command format / structure.
That way we can have the wrangler complete command itself have:

  • Auto completion
  • Suggestions for shells it supports
  • A help menu

We can also reference this code from another PR on how to implement this.


checkMacOSVersion({ shouldThrow: false });

const startTime = Date.now();
Expand Down
23 changes: 23 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.