From c1371aba60a3803e757ca885ef573e98dd3ff0d1 Mon Sep 17 00:00:00 2001 From: so1ve Date: Sun, 14 Dec 2025 20:21:43 +0800 Subject: [PATCH 1/7] init --- packages/plugin-completions/debug.txt | 254 +++++++++++++++++++++++ packages/plugin-completions/package.json | 1 + packages/plugin-completions/src/a.mjs | 35 ++++ packages/plugin-completions/src/index.ts | 210 ++++++++++--------- pnpm-lock.yaml | 23 ++ 5 files changed, 423 insertions(+), 100 deletions(-) create mode 100644 packages/plugin-completions/debug.txt create mode 100644 packages/plugin-completions/src/a.mjs diff --git a/packages/plugin-completions/debug.txt b/packages/plugin-completions/debug.txt new file mode 100644 index 00000000..2133796d --- /dev/null +++ b/packages/plugin-completions/debug.txt @@ -0,0 +1,254 @@ +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: zsh +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["a"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["versi"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: powershell +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: powershell +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["asdfs"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["a"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: powershell +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: powershell +extra: [""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["test","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["test","-a",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["test",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["test",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["test","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: [""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["complete","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["help","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["help","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["help","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["help","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["help",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: [""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete",""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete","-V="] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete","-V="] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["complete","-V="] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["version","--"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["version","--"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["version","--"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["version","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["version","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["version","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["version","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["version","-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: [""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: ["--"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +flagName: version, short: V, desc: Prints current version, boolean: true +flagName: help, short: h, desc: Show help, boolean: true +extra: [""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["--"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["--"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: [] +ctx.flags.shell: undefined +ctx.parameters.shell: powershell +extra: [""] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["-"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined +extra: ["--"] +ctx.flags.shell: undefined +ctx.parameters.shell: undefined diff --git a/packages/plugin-completions/package.json b/packages/plugin-completions/package.json index 8339d713..a1564ad9 100644 --- a/packages/plugin-completions/package.json +++ b/packages/plugin-completions/package.json @@ -45,6 +45,7 @@ "access": "public" }, "dependencies": { + "@bomb.sh/tab": "^0.0.10", "@pnpm/tabtab": "^0.5.4" }, "devDependencies": { diff --git a/packages/plugin-completions/src/a.mjs b/packages/plugin-completions/src/a.mjs new file mode 100644 index 00000000..39e8b9a8 --- /dev/null +++ b/packages/plugin-completions/src/a.mjs @@ -0,0 +1,35 @@ +import { Cli, Types, completionsPlugin, friendlyErrorPlugin } from "clerc"; + +Cli() + .name("my-cli") + .scriptName("my-cli") + .version("1.0.0") + .description("My CLI with completions") + // .use(completionsPlugin()) + // .use(friendlyErrorPlugin()) + // .parse(); + // 啊离开了课件课件 + // 12313213123213 + .use(friendlyErrorPlugin()) + .use(completionsPlugin()) + .command("test", "Test command", { + parameters: [ + "", + { + key: "", + type: Types.Enum("a", "b", "c"), + }, + { + key: "[range]", + type: Types.Range(1, 10), + }, + ], + flags: { + bbb: { + required: true, + type: Types.Regex(/^a.*z$/, "starts with a ends with z"), + alias: "a", + }, + }, + }) + .parse(); diff --git a/packages/plugin-completions/src/index.ts b/packages/plugin-completions/src/index.ts index f5cd7275..5ef52316 100644 --- a/packages/plugin-completions/src/index.ts +++ b/packages/plugin-completions/src/index.ts @@ -1,8 +1,8 @@ -import type { Plugin } from "@clerc/core"; -import { Types, definePlugin } from "@clerc/core"; -import tabtab, { getShellFromEnv } from "@pnpm/tabtab"; +import { writeFileSync } from "node:fs"; -import { getCompletion } from "./complete"; +import t from "@bomb.sh/tab"; +import type { ClercFlagsDefinition, Plugin } from "@clerc/core"; +import { Types, definePlugin, normalizeFlagValue } from "@clerc/core"; declare module "@clerc/core" { export interface CommandCustomOptions { @@ -22,8 +22,8 @@ declare module "@clerc/core" { export interface CompletionsPluginOptions { /** - * Whether to register the `completions install` and `completions uninstall` commands. - * @default true + * @deprecated `tab` does not require install/uninstall management commands. + * This option is kept for backward compatibility and has no effect. */ managementCommands?: boolean; } @@ -33,73 +33,102 @@ export const completionsPlugin = ( ): Plugin => definePlugin({ setup: (cli) => { - const { managementCommands = true } = options; + // kept for backwards compatibility; no longer used + void options; cli.store.help?.addGroup({ commands: [["completions", "Completions"]], }); - const supportedShellEnum = Types.Enum(...tabtab.SUPPORTED_SHELLS); + const supportedShellEnum = Types.Enum( + "zsh", + "bash", + "fish", + "powershell", + ); - if (managementCommands) { - cli - .command("completions install", "Install shell completions", { - help: { - group: "completions", - }, - flags: { - shell: { - description: "Shell type", - type: supportedShellEnum, - }, - }, - parameters: [ - { - key: "[shell]", - description: "Shell type", - type: supportedShellEnum, - }, - ], - }) - .on("completions install", async (ctx) => { - const shell = ctx.parameters.shell ?? ctx.flags.shell; - if (!shell) { - throw new Error( - "Please specify the shell type via the --shell flag or the [shell] parameter.", - ); - } - if (!tabtab.SUPPORTED_SHELLS.includes(shell)) { - throw new Error( - `Unsupported shell: ${shell}. Supported shells are: ${tabtab.SUPPORTED_SHELLS.join( - ", ", - )}.`, - ); + function resetTab() { + // `t` is a singleton. Clear previous state to avoid duplicates across invocations. + t.commands.clear(); + t.options.clear(); + t.arguments.clear(); + t.completions = []; + } + + function pickShortAlias(value: unknown): string | undefined { + if (!value) { + return; + } + const aliases = Array.isArray(value) ? value : [value]; + for (const a of aliases) { + if (typeof a === "string" && a.length === 1) { + return a; + } + } + } + + function isBooleanType(type: unknown): boolean { + if (!type) { + return true; + } + if (type === Boolean) { + return true; + } + if (Array.isArray(type) && type.length === 1 && type[0] === Boolean) { + return true; + } + + return false; + } + + function buildTabModel() { + resetTab(); + + // Global flags + for (const [flagName, def] of Object.entries( + cli._globalFlags as ClercFlagsDefinition, + )) { + const normalized = normalizeFlagValue(def); + const desc = normalized.description ?? ""; + t.option(flagName, desc); + } + + // Commands + command-level flags + for (const cmd of cli._commands.values()) { + if (cmd.completions?.show === false) { + continue; + } + + const command = t.command(cmd.name, cmd.description ?? ""); + const flags = cmd.flags ?? {}; + for (const [flagName, def] of Object.entries(flags)) { + const normalized = + typeof def === "function" || Array.isArray(def) + ? { type: def } + : def; + const short = pickShortAlias((normalized as any).alias); + const desc = (normalized as any).description ?? ""; + const boolean = isBooleanType((normalized as any).type); + if (boolean) { + if (short) { + command.option(flagName, desc, short); + } else { + command.option(flagName, desc); + } + } else { + function handler() {} + if (short) { + command.option(flagName, desc, handler as any, short); + } else { + command.option(flagName, desc, handler as any); + } } - await tabtab.install({ - name: cli._scriptName, - completer: cli._scriptName, - shell, - }); - }); - - cli - .command("completions uninstall", "Uninstall shell completions", { - help: { - group: "completions", - }, - }) - .on("completions uninstall", async () => { - await tabtab.uninstall({ - name: cli._scriptName, - }); - }); + } + } } cli - .command("completions", "Generate completions script", { - help: { - group: "completions", - }, + .command("complete", "Generate shell completion scripts", { flags: { shell: { description: "Shell type", @@ -109,52 +138,33 @@ export const completionsPlugin = ( parameters: [ { key: "[shell]", - description: "Shell type", + description: "Shell type (zsh, bash, fish, powershell)", type: supportedShellEnum, }, + "--", ], }) - .on("completions", async (ctx) => { + .on("complete", async (ctx) => { + const extra = ctx.rawParsed.doubleDash ?? []; + writeFileSync( + "debug.txt", + `extra: ${JSON.stringify(extra)}\nctx.flags.shell: ${ctx.flags.shell}\nctx.parameters.shell: ${ctx.parameters.shell}\n`, + { flag: "a" }, + ); + const shell = ctx.parameters.shell ?? ctx.flags.shell; - if (!shell) { - throw new Error( - "Please specify the shell type via the --shell flag or the [shell] parameter.", - ); - } - if (!tabtab.SUPPORTED_SHELLS.includes(shell)) { - throw new Error( - `Unsupported shell: ${shell}. Supported shells are: ${tabtab.SUPPORTED_SHELLS.join( - ", ", - )}.`, - ); - } - const script = await tabtab.getCompletionScript({ - name: cli._scriptName, - completer: cli._scriptName, - shell, - }); - console.log(script); - }); - cli - .command("completion-server", "Handle completions", { - help: { - show: false, - }, - completions: { - show: false, - }, - }) - .on("completion-server", async () => { - const env = tabtab.parseEnv(process.env); - if (!env.complete) { + buildTabModel(); + + if (shell) { + // Generate shell script + t.setup(cli._name, "node src/a.mjs", shell); + return; } - const shell = getShellFromEnv(process.env); - const candidates = await getCompletion(cli, env); - - tabtab.log(candidates, shell); + // Completion request (everything after `--`) + t.parse(extra); }); return cli; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82f364f9..fbaeb293 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -201,6 +201,9 @@ importers: packages/plugin-completions: dependencies: + '@bomb.sh/tab': + specifier: ^0.0.10 + version: 0.0.10(cac@6.7.14)(citty@0.1.6) '@pnpm/tabtab': specifier: ^0.5.4 version: 0.5.4 @@ -391,6 +394,21 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@bomb.sh/tab@0.0.10': + resolution: {integrity: sha512-6ALS2rh/4LKn0Yxwm35V6LcgQuSiECHbqQo7+9g4rkgGyXZ0siOc8K+IuWIq/4u0Zkv2mevP9QSqgKhGIvLJMw==} + hasBin: true + peerDependencies: + cac: ^6.7.14 + citty: ^0.1.6 + commander: ^13.1.0 + peerDependenciesMeta: + cac: + optional: true + citty: + optional: true + commander: + optional: true + '@clerc/core@0.44.0': resolution: {integrity: sha512-o8RgXNcMRoHRujSw9OPDMxqrmoNk7HG0XAZkjZgOrSyIfRXCf85VLyHGBT3XmaOrPEGY964h02ZxMVFdp8RnNQ==} @@ -4638,6 +4656,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@bomb.sh/tab@0.0.10(cac@6.7.14)(citty@0.1.6)': + optionalDependencies: + cac: 6.7.14 + citty: 0.1.6 + '@clerc/core@0.44.0': dependencies: '@clerc/utils': 0.44.0(@clerc/core@0.44.0) From ad5141040e4d87fd0df89a0b78f2ede5d9fa3fe0 Mon Sep 17 00:00:00 2001 From: so1ve Date: Sun, 14 Dec 2025 20:52:58 +0800 Subject: [PATCH 2/7] should work --- packages/plugin-completions/src/a.mjs | 35 ----- packages/plugin-completions/src/complete.ts | 125 ----------------- packages/plugin-completions/src/index.ts | 146 ++++---------------- packages/plugin-completions/src/t.ts | 37 +++++ 4 files changed, 63 insertions(+), 280 deletions(-) delete mode 100644 packages/plugin-completions/src/a.mjs delete mode 100644 packages/plugin-completions/src/complete.ts create mode 100644 packages/plugin-completions/src/t.ts diff --git a/packages/plugin-completions/src/a.mjs b/packages/plugin-completions/src/a.mjs deleted file mode 100644 index 39e8b9a8..00000000 --- a/packages/plugin-completions/src/a.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import { Cli, Types, completionsPlugin, friendlyErrorPlugin } from "clerc"; - -Cli() - .name("my-cli") - .scriptName("my-cli") - .version("1.0.0") - .description("My CLI with completions") - // .use(completionsPlugin()) - // .use(friendlyErrorPlugin()) - // .parse(); - // 啊离开了课件课件 - // 12313213123213 - .use(friendlyErrorPlugin()) - .use(completionsPlugin()) - .command("test", "Test command", { - parameters: [ - "", - { - key: "", - type: Types.Enum("a", "b", "c"), - }, - { - key: "[range]", - type: Types.Range(1, 10), - }, - ], - flags: { - bbb: { - required: true, - type: Types.Regex(/^a.*z$/, "starts with a ends with z"), - alias: "a", - }, - }, - }) - .parse(); diff --git a/packages/plugin-completions/src/complete.ts b/packages/plugin-completions/src/complete.ts deleted file mode 100644 index e1260782..00000000 --- a/packages/plugin-completions/src/complete.ts +++ /dev/null @@ -1,125 +0,0 @@ -import type { Clerc, ClercFlagsDefinition } from "@clerc/core"; -import { DOUBLE_DASH, normalizeFlagValue, resolveCommand } from "@clerc/core"; -import { formatFlagName } from "@clerc/utils"; -import type { CompletionItem, ParseEnvResult } from "@pnpm/tabtab"; - -function splitCommand(cmd: string) { - const args: string[] = []; - let current = ""; - let quote: string | null = null; - let escape = false; - - for (const char of cmd) { - if (escape) { - current += char; - escape = false; - continue; - } - if (char === "\\") { - escape = true; - continue; - } - if (quote) { - if (char === quote) { - quote = null; - } else { - current += char; - } - } else { - if (char === '"' || char === "'") { - quote = char; - } else if (/\s/.test(char)) { - if (current) { - args.push(current); - current = ""; - } - } else { - current += char; - } - } - } - if (current) { - args.push(current); - } - - return args; -} - -export async function getCompletion( - cli: Clerc, - env: ParseEnvResult, -): Promise { - const finishedArgv = env.partial.slice( - 0, - env.partial.length - env.lastPartial.length, - ); - const inputArgv = splitCommand(finishedArgv).slice(1); - - if (inputArgv.includes(DOUBLE_DASH)) { - return []; - } - - const [command, commandName] = resolveCommand(cli._commands, inputArgv); - - const lastPartial = env.lastPartial; - const isOption = lastPartial.startsWith("-"); - - if (isOption) { - const flags: ClercFlagsDefinition = command - ? { ...cli._globalFlags, ...command.flags } - : cli._globalFlags; - - const candidates: CompletionItem[] = []; - for (const [name, def] of Object.entries(flags)) { - const normalized = normalizeFlagValue(def); - const names = [name]; - if (normalized.short) { - names.push(normalized.short); - } - for (const name of names) { - candidates.push({ - name: formatFlagName(name), - description: normalized.description, - }); - } - } - - return candidates; - } - - const candidates: CompletionItem[] = []; - let prefix = ""; - if (commandName) { - const matchedParts = commandName.split(" "); - const remainingArgs = inputArgv.slice(matchedParts.length); - prefix = `${[command.name, ...remainingArgs].join(" ")} `; - } else { - prefix = inputArgv.length > 0 ? `${inputArgv.join(" ")} ` : ""; - } - - for (const command of cli._commands.values()) { - if (command.completions?.show === false) { - continue; - } - - if (command.name.startsWith(prefix)) { - const remaining = command.name.slice(prefix.length); - // Only suggest the next word - const nextWord = remaining.split(" ")[0]; - if (nextWord) { - candidates.push({ - name: nextWord, - description: command.description, - }); - } - } - } - - // Deduplicate - const uniqueCandidates = new Map(); - for (const c of candidates) { - uniqueCandidates.set(c.name, c); - } - - return [...uniqueCandidates.values()]; -} diff --git a/packages/plugin-completions/src/index.ts b/packages/plugin-completions/src/index.ts index 5ef52316..c9515688 100644 --- a/packages/plugin-completions/src/index.ts +++ b/packages/plugin-completions/src/index.ts @@ -1,8 +1,8 @@ -import { writeFileSync } from "node:fs"; - import t from "@bomb.sh/tab"; -import type { ClercFlagsDefinition, Plugin } from "@clerc/core"; -import { Types, definePlugin, normalizeFlagValue } from "@clerc/core"; +import type { Plugin } from "@clerc/core"; +import { Types, definePlugin } from "@clerc/core"; + +import { buildTabModel } from "./t"; declare module "@clerc/core" { export interface CommandCustomOptions { @@ -20,26 +20,9 @@ declare module "@clerc/core" { } } -export interface CompletionsPluginOptions { - /** - * @deprecated `tab` does not require install/uninstall management commands. - * This option is kept for backward compatibility and has no effect. - */ - managementCommands?: boolean; -} - -export const completionsPlugin = ( - options: CompletionsPluginOptions = {}, -): Plugin => +export const completionsPlugin = (): Plugin => definePlugin({ setup: (cli) => { - // kept for backwards compatibility; no longer used - void options; - - cli.store.help?.addGroup({ - commands: [["completions", "Completions"]], - }); - const supportedShellEnum = Types.Enum( "zsh", "bash", @@ -47,88 +30,8 @@ export const completionsPlugin = ( "powershell", ); - function resetTab() { - // `t` is a singleton. Clear previous state to avoid duplicates across invocations. - t.commands.clear(); - t.options.clear(); - t.arguments.clear(); - t.completions = []; - } - - function pickShortAlias(value: unknown): string | undefined { - if (!value) { - return; - } - const aliases = Array.isArray(value) ? value : [value]; - for (const a of aliases) { - if (typeof a === "string" && a.length === 1) { - return a; - } - } - } - - function isBooleanType(type: unknown): boolean { - if (!type) { - return true; - } - if (type === Boolean) { - return true; - } - if (Array.isArray(type) && type.length === 1 && type[0] === Boolean) { - return true; - } - - return false; - } - - function buildTabModel() { - resetTab(); - - // Global flags - for (const [flagName, def] of Object.entries( - cli._globalFlags as ClercFlagsDefinition, - )) { - const normalized = normalizeFlagValue(def); - const desc = normalized.description ?? ""; - t.option(flagName, desc); - } - - // Commands + command-level flags - for (const cmd of cli._commands.values()) { - if (cmd.completions?.show === false) { - continue; - } - - const command = t.command(cmd.name, cmd.description ?? ""); - const flags = cmd.flags ?? {}; - for (const [flagName, def] of Object.entries(flags)) { - const normalized = - typeof def === "function" || Array.isArray(def) - ? { type: def } - : def; - const short = pickShortAlias((normalized as any).alias); - const desc = (normalized as any).description ?? ""; - const boolean = isBooleanType((normalized as any).type); - if (boolean) { - if (short) { - command.option(flagName, desc, short); - } else { - command.option(flagName, desc); - } - } else { - function handler() {} - if (short) { - command.option(flagName, desc, handler as any, short); - } else { - command.option(flagName, desc, handler as any); - } - } - } - } - } - cli - .command("complete", "Generate shell completion scripts", { + .command("completions", "Generate shell completion scripts", { flags: { shell: { description: "Shell type", @@ -141,30 +44,33 @@ export const completionsPlugin = ( description: "Shell type (zsh, bash, fish, powershell)", type: supportedShellEnum, }, - "--", ], }) - .on("complete", async (ctx) => { - const extra = ctx.rawParsed.doubleDash ?? []; - writeFileSync( - "debug.txt", - `extra: ${JSON.stringify(extra)}\nctx.flags.shell: ${ctx.flags.shell}\nctx.parameters.shell: ${ctx.parameters.shell}\n`, - { flag: "a" }, - ); - + .on("completions", async (ctx) => { const shell = ctx.parameters.shell ?? ctx.flags.shell; - buildTabModel(); + if (!shell) { + throw new Error( + "Shell type is required. Please provide it via --shell flag or [shell] parameter.", + ); + } - if (shell) { - // Generate shell script - t.setup(cli._name, "node src/a.mjs", shell); + buildTabModel(cli._globalFlags, cli._commands); - return; - } + t.setup(cli._name, "node src/a.mjs complete", shell); + }); + + cli + .command("complete", { + help: { show: false }, + completions: { show: false }, + parameters: ["--", "[input...]"], + }) + .on("complete", async (ctx) => { + buildTabModel(cli._globalFlags, cli._commands); - // Completion request (everything after `--`) - t.parse(extra); + const { input } = ctx.parameters; + t.parse(input); }); return cli; diff --git a/packages/plugin-completions/src/t.ts b/packages/plugin-completions/src/t.ts new file mode 100644 index 00000000..6a5c1daf --- /dev/null +++ b/packages/plugin-completions/src/t.ts @@ -0,0 +1,37 @@ +import t from "@bomb.sh/tab"; +import type { ClercFlagsDefinition, CommandsMap } from "clerc"; +import { normalizeFlagValue } from "clerc"; + +export function resetTab(): void { + t.commands.clear(); + t.options.clear(); + t.arguments.clear(); + t.completions = []; +} + +export function buildTabModel( + globalFlags: ClercFlagsDefinition, + commands: CommandsMap, +): void { + resetTab(); + + for (const [flagName, def] of Object.entries(globalFlags)) { + const normalized = normalizeFlagValue(def); + const desc = normalized.description ?? ""; + t.option(flagName, desc, normalized.short); + } + + for (const cmd of commands.values()) { + if (cmd.completions?.show === false) { + continue; + } + + const command = t.command(cmd.name, cmd.description ?? ""); + const flags = cmd.flags ?? {}; + for (const [flagName, def] of Object.entries(flags)) { + const normalized = normalizeFlagValue(def); + const desc = normalized.description ?? ""; + command.option(flagName, desc, normalized.short); + } + } +} From b7bf436ab3b7d40d496313b2e3c0e7c2331fd8f8 Mon Sep 17 00:00:00 2001 From: so1ve Date: Sun, 14 Dec 2025 20:55:25 +0800 Subject: [PATCH 3/7] register global flags --- packages/plugin-completions/src/t.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/plugin-completions/src/t.ts b/packages/plugin-completions/src/t.ts index 6a5c1daf..06f6dc14 100644 --- a/packages/plugin-completions/src/t.ts +++ b/packages/plugin-completions/src/t.ts @@ -1,3 +1,4 @@ +import type { Command } from "@bomb.sh/tab"; import t from "@bomb.sh/tab"; import type { ClercFlagsDefinition, CommandsMap } from "clerc"; import { normalizeFlagValue } from "clerc"; @@ -9,17 +10,24 @@ export function resetTab(): void { t.completions = []; } -export function buildTabModel( +function registerGlobalFlags( globalFlags: ClercFlagsDefinition, - commands: CommandsMap, + tc: Command, ): void { - resetTab(); - for (const [flagName, def] of Object.entries(globalFlags)) { const normalized = normalizeFlagValue(def); const desc = normalized.description ?? ""; - t.option(flagName, desc, normalized.short); + tc.option(flagName, desc, normalized.short); } +} + +export function buildTabModel( + globalFlags: ClercFlagsDefinition, + commands: CommandsMap, +): void { + resetTab(); + + registerGlobalFlags(globalFlags, t); for (const cmd of commands.values()) { if (cmd.completions?.show === false) { @@ -27,8 +35,9 @@ export function buildTabModel( } const command = t.command(cmd.name, cmd.description ?? ""); - const flags = cmd.flags ?? {}; - for (const [flagName, def] of Object.entries(flags)) { + registerGlobalFlags(globalFlags, command); + + for (const [flagName, def] of Object.entries(cmd.flags ?? {})) { const normalized = normalizeFlagValue(def); const desc = normalized.description ?? ""; command.option(flagName, desc, normalized.short); From abb54a14d0479f2f87377fa477d1205c05347dd6 Mon Sep 17 00:00:00 2001 From: so1ve Date: Sun, 14 Dec 2025 21:01:25 +0800 Subject: [PATCH 4/7] fix script executable --- packages/plugin-completions/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-completions/src/index.ts b/packages/plugin-completions/src/index.ts index c9515688..529b5dfc 100644 --- a/packages/plugin-completions/src/index.ts +++ b/packages/plugin-completions/src/index.ts @@ -57,7 +57,7 @@ export const completionsPlugin = (): Plugin => buildTabModel(cli._globalFlags, cli._commands); - t.setup(cli._name, "node src/a.mjs complete", shell); + t.setup(cli._scriptName, cli._scriptName, shell); }); cli From bfeb53727234287e9c07b38631e8aa1750f026aa Mon Sep 17 00:00:00 2001 From: so1ve Date: Sun, 14 Dec 2025 21:03:45 +0800 Subject: [PATCH 5/7] fix test --- .../plugin-completions/test/commands.test.ts | 118 ++------ .../plugin-completions/test/complete.test.ts | 270 ------------------ .../test/plugin-completions.test.ts | 59 +--- 3 files changed, 31 insertions(+), 416 deletions(-) delete mode 100644 packages/plugin-completions/test/complete.test.ts diff --git a/packages/plugin-completions/test/commands.test.ts b/packages/plugin-completions/test/commands.test.ts index 5a5467c2..f9f3cb51 100644 --- a/packages/plugin-completions/test/commands.test.ts +++ b/packages/plugin-completions/test/commands.test.ts @@ -1,19 +1,22 @@ +import t from "@bomb.sh/tab"; import { TestBaseCli } from "@clerc/test-utils"; -import tabtab, { getShellFromEnv } from "@pnpm/tabtab"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { completionsPlugin } from "../src"; -vi.mock("@pnpm/tabtab", () => ({ +vi.mock("@bomb.sh/tab", () => ({ default: { - install: vi.fn(), - uninstall: vi.fn(), - getCompletionScript: vi.fn().mockResolvedValue("mock script"), - parseEnv: vi.fn(), - log: vi.fn(), - SUPPORTED_SHELLS: ["bash", "zsh", "fish"], + setup: vi.fn(), + parse: vi.fn(), + commands: new Map(), + options: new Map(), + arguments: new Map(), + completions: [], + command: vi.fn().mockReturnValue({ + option: vi.fn(), + }), + option: vi.fn(), }, - getShellFromEnv: vi.fn(), })); describe("plugin-completions/commands", () => { @@ -21,71 +24,29 @@ describe("plugin-completions/commands", () => { vi.clearAllMocks(); }); - describe("completions install", () => { - it("should install completions", async () => { - const cli = TestBaseCli().use(completionsPlugin()); - - await cli.parse(["completions", "install", "--shell", "bash"]); - - expect(tabtab.install).toHaveBeenCalledWith({ - name: "test", - completer: "test", - shell: "bash", - }); - }); - - it("should throw error if shell is missing", async () => { - await expect(async () => { - await TestBaseCli() - .use(completionsPlugin()) - .parse(["completions", "install"]); - }).rejects.toThrow("Please specify the shell type"); - }); - - it("should throw error if shell is unsupported", async () => { - await expect(async () => { - await TestBaseCli() - .use(completionsPlugin()) - .parse(["completions", "install", "--shell", "invalid"]); - }).rejects.toThrowErrorMatchingInlineSnapshot( - "[Error: Invalid value: invalid. Must be one of: bash, zsh, fish]", - ); - }); - }); - - describe("completions uninstall", () => { - it("should uninstall completions", async () => { + describe("completions", () => { + it("should setup completions with shell parameter", async () => { const cli = TestBaseCli().use(completionsPlugin()); - await cli.parse(["completions", "uninstall"]); + await cli.parse(["completions", "bash"]); - expect(tabtab.uninstall).toHaveBeenCalledWith({ - name: "test", - }); + expect(t.setup).toHaveBeenCalledWith("test", "test", "bash"); }); - }); - describe("completions", () => { - it("should generate completion script", async () => { - const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + it("should setup completions with shell flag", async () => { const cli = TestBaseCli().use(completionsPlugin()); - await cli.parse(["completions", "--shell", "bash"]); + await cli.parse(["completions", "--shell", "zsh"]); - expect(tabtab.getCompletionScript).toHaveBeenCalledWith({ - name: "test", - completer: "test", - shell: "bash", - }); - expect(consoleSpy).toHaveBeenCalledWith("mock script"); - - consoleSpy.mockRestore(); + expect(t.setup).toHaveBeenCalledWith("test", "test", "zsh"); }); it("should throw error if shell is missing", async () => { await expect(async () => { await TestBaseCli().use(completionsPlugin()).parse(["completions"]); - }).rejects.toThrow("Please specify the shell type"); + }).rejects.toThrow( + "Shell type is required. Please provide it via --shell flag or [shell] parameter.", + ); }); it("should throw error if shell is unsupported", async () => { @@ -94,47 +55,28 @@ describe("plugin-completions/commands", () => { .use(completionsPlugin()) .parse(["completions", "--shell", "invalid"]); }).rejects.toThrowErrorMatchingInlineSnapshot( - "[Error: Invalid value: invalid. Must be one of: bash, zsh, fish]", + "[Error: Invalid value: invalid. Must be one of: zsh, bash, fish, powershell]", ); }); }); - describe("completion-server", () => { - it("should handle completion request", async () => { + describe("complete", () => { + it("should parse completion input", async () => { const cli = TestBaseCli() .use(completionsPlugin()) .command("commit", "Commit changes"); - vi.mocked(tabtab.parseEnv).mockReturnValue({ - complete: true, - words: ["test", "com"], - point: 8, - partial: "test com", - lastPartial: "com", - line: "test com", - } as any); - - vi.mocked(getShellFromEnv).mockReturnValue("bash"); - - await cli.parse(["completion-server"]); + await cli.parse(["complete", "--", "test", "com"]); - expect(tabtab.log).toHaveBeenCalled(); - - const candidates = vi.mocked(tabtab.log).mock.calls[0][0] as any[]; - - expect(candidates.some((c) => c.name === "commit")).toBeTruthy(); + expect(t.parse).toHaveBeenCalledWith(["test", "com"]); }); - it("should do nothing if not in completion mode", async () => { + it("should handle empty input", async () => { const cli = TestBaseCli().use(completionsPlugin()); - vi.mocked(tabtab.parseEnv).mockReturnValue({ - complete: false, - } as any); - - await cli.parse(["completion-server"]); + await cli.parse(["complete", "--"]); - expect(tabtab.log).not.toHaveBeenCalled(); + expect(t.parse).toHaveBeenCalledWith([]); }); }); }); diff --git a/packages/plugin-completions/test/complete.test.ts b/packages/plugin-completions/test/complete.test.ts deleted file mode 100644 index 7df6390c..00000000 --- a/packages/plugin-completions/test/complete.test.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { TestBaseCli } from "@clerc/test-utils"; -import { describe, expect, it } from "vitest"; - -import { getCompletion } from "../src/complete"; - -describe("plugin-completions/complete", () => { - describe("getCompletion", () => { - it("should return empty array when -- is present", async () => { - const cli = TestBaseCli(); - const env = { - partial: "test --", - lastPartial: "", - complete: true, - words: ["test", "--"], - point: 8, - }; - // TODO: remove type assertion after fixing types - const result = await getCompletion(cli, env as any); - - expect(Array.isArray(result)).toBeTruthy(); - expect(result).toHaveLength(0); - }); - - it("should return flag options when input starts with -", async () => { - const cli = TestBaseCli() - .globalFlag("verbose", "Enable verbose mode", { - type: Boolean, - short: "v", - }) - .globalFlag("output", "Output file", { - type: String, - short: "o", - }) - .command("build", "Build the project", { - flags: { - minify: { - description: "Minify output", - type: Boolean, - }, - }, - }); - - const env = { - partial: "test build -", - lastPartial: "-", - complete: true, - words: ["test", "build", "-"], - point: 12, - }; - - const result = await getCompletion(cli, env as any); - - const names = result.map((c) => c.name); - - expect(names).toContain("--verbose"); - expect(names).toContain("-v"); - expect(names).toContain("--output"); - expect(names).toContain("-o"); - expect(names).toContain("--minify"); - }); - - it("should return command completions for non-option input", async () => { - const cli = TestBaseCli() - .command("build", "Build the project") - .command("dev", "Start dev server") - .command("test", "Run tests"); - - const env = { - partial: "test ", - lastPartial: "", - complete: true, - words: ["test"], - point: 5, - }; - - const result = await getCompletion(cli, env as any); - - const names = result.map((c) => c.name); - - expect(names).toContain("build"); - expect(names).toContain("dev"); - expect(names).toContain("test"); - }); - - it("should filter commands by prefix", async () => { - const cli = TestBaseCli() - .command("build", "Build the project") - .command("bundle", "Bundle the project") - .command("dev", "Start dev server"); - - const env = { - partial: "test bu", - lastPartial: "bu", - complete: true, - words: ["test", "bu"], - point: 8, - }; - - const result = await getCompletion(cli, env as any); - - const names = result.map((c) => c.name); - - expect(names).toContain("build"); - expect(names).toContain("bundle"); - }); - - it("should exclude commands with show: false", async () => { - const cli = TestBaseCli() - .command("build", "Build the project") - .command("hidden", "Hidden command", { - completions: { - show: false, - }, - }); - - const env = { - partial: "test ", - lastPartial: "", - complete: true, - words: ["test"], - point: 5, - }; - - const result = await getCompletion(cli, env as any); - - const names = result.map((c) => c.name); - - expect(names).toContain("build"); - expect(names).not.toContain("hidden"); - }); - - it("should deduplicate completion candidates", async () => { - const cli = TestBaseCli() - .command("build", "Build the project") - .command("build:prod", "Build for production") - .command("build:dev", "Build for development"); - - const env = { - partial: "test build", - lastPartial: "build", - complete: true, - words: ["test", "build"], - point: 10, - }; - - const result = await getCompletion(cli, env as any); - - // Count how many times "build" appears - const buildCount = result.filter((c) => c.name === "build").length; - - expect(buildCount).toBe(1); - }); - - it("should return global flags for root level option completion", async () => { - const cli = TestBaseCli() - .globalFlag("help", "Show help", { - type: Boolean, - short: "h", - }) - .command("test", "Test command"); - - const env = { - partial: "test -", - lastPartial: "-", - complete: true, - words: ["test", "-"], - point: 6, - }; - - const result = await getCompletion(cli, env as any); - - const names = result.map((c) => c.name); - - expect(names).toContain("--help"); - expect(names).toContain("-h"); - }); - - it("should handle multi-word command names", async () => { - const cli = TestBaseCli() - .command("git commit", "Commit changes") - .command("git push", "Push changes") - .command("npm install", "Install packages"); - - const env = { - partial: "test git ", - lastPartial: "", - complete: true, - words: ["test", "git", ""], - point: 9, - }; - - const result = await getCompletion(cli, env as any); - - const names = result.map((c) => c.name); - - expect(names).toContain("commit"); - expect(names).toContain("push"); - expect(names).not.toContain("install"); - }); - - it("should return suggestions when partial matches multi-word commands", async () => { - const cli = TestBaseCli() - .command("git commit", "Commit changes") - .command("git push", "Push changes"); - - const env = { - partial: "test git c", - lastPartial: "c", - complete: true, - words: ["test", "git", "c"], - point: 9, - }; - - const result = await getCompletion(cli, env as any); - - // Should suggest "commit" as the next word - const names = result.map((c) => c.name); - - expect(names.length).toBeGreaterThan(0); - }); - - it("should handle quoted strings in input", async () => { - const cli = TestBaseCli().command("commit", "Commit"); - - const env = { - partial: 'test "some arg" ', - lastPartial: "", - complete: true, - words: ["test", '"some arg"', ""], - point: 16, - }; - - await getCompletion(cli, env as any); - }); - - it("should suggest subcommands after resolved command", async () => { - const cli = TestBaseCli() - .command("git", "Git") - .command("git commit", "Commit"); - - const env = { - partial: "test git ", - lastPartial: "", - complete: true, - words: ["test", "git", ""], - point: 9, - }; - - const result = await getCompletion(cli, env as any); - - const names = result.map((c) => c.name); - - expect(names).toContain("commit"); - }); - - it("should handle escaped characters", async () => { - const cli = TestBaseCli().command("commit", "Commit"); - - const env = { - partial: 'test "some\\ arg" ', - lastPartial: "", - complete: true, - words: ["test", '"some\\ arg"', ""], - point: 17, - }; - - await getCompletion(cli, env as any); - }); - }); -}); diff --git a/packages/plugin-completions/test/plugin-completions.test.ts b/packages/plugin-completions/test/plugin-completions.test.ts index 045d344d..98ef285f 100644 --- a/packages/plugin-completions/test/plugin-completions.test.ts +++ b/packages/plugin-completions/test/plugin-completions.test.ts @@ -8,21 +8,8 @@ describe("plugin-completions", () => { const cli = TestBaseCli().use(completionsPlugin()); // Check if commands are registered - expect(cli._commands.has("completions install")).toBeTruthy(); - expect(cli._commands.has("completions uninstall")).toBeTruthy(); + expect(cli._commands.has("complete")).toBeTruthy(); expect(cli._commands.has("completions")).toBeTruthy(); - expect(cli._commands.has("completion-server")).toBeTruthy(); - }); - - it("should not register management commands when disabled", () => { - const cli = TestBaseCli().use( - completionsPlugin({ managementCommands: false }), - ); - - expect(cli._commands.has("completions install")).toBeFalsy(); - expect(cli._commands.has("completions uninstall")).toBeFalsy(); - expect(cli._commands.has("completions")).toBeTruthy(); - expect(cli._commands.has("completion-server")).toBeTruthy(); }); it("should have completions command with proper configuration", () => { @@ -33,48 +20,4 @@ describe("plugin-completions", () => { expect(completionsCmd?.flags).toBeDefined(); expect(completionsCmd?.flags?.shell).toBeDefined(); }); - - it("should have completion-server command hidden from help and completion itself", () => { - const cli = TestBaseCli().use(completionsPlugin()); - const completionServerCmd = cli._commands.get("completion-server"); - - expect(completionServerCmd).toBeDefined(); - expect(completionServerCmd?.help?.show).toBeFalsy(); - expect(completionServerCmd?.completions?.show).toBeFalsy(); - }); - - it("should support command completions options", () => { - const cli = TestBaseCli() - .use(completionsPlugin()) - .command("test", "test command", { - completions: { - show: false, - }, - }); - - const cmd = cli._commands.get("test"); - - expect(cmd?.completions?.show).toBeFalsy(); - }); - - it("should have install and uninstall commands with proper configuration", () => { - const cli = TestBaseCli().use(completionsPlugin()); - - const installCmd = cli._commands.get("completions install"); - - expect(installCmd).toBeDefined(); - expect(installCmd?.flags).toBeDefined(); - expect(installCmd?.flags?.shell).toBeDefined(); - - const uninstallCmd = cli._commands.get("completions uninstall"); - - expect(uninstallCmd).toBeDefined(); - }); - - it("should return cli instance from plugin setup", () => { - const cli = TestBaseCli(); - const result = cli.use(completionsPlugin()); - - expect(result).toBe(cli); - }); }); From 6b80550d7916bcd467d60640da98c4d2c2956525 Mon Sep 17 00:00:00 2001 From: so1ve Date: Sun, 14 Dec 2025 21:05:48 +0800 Subject: [PATCH 6/7] update package json --- packages/plugin-completions/package.json | 3 +- pnpm-lock.yaml | 37 ------------------------ 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/packages/plugin-completions/package.json b/packages/plugin-completions/package.json index a1564ad9..4446eecd 100644 --- a/packages/plugin-completions/package.json +++ b/packages/plugin-completions/package.json @@ -45,8 +45,7 @@ "access": "public" }, "dependencies": { - "@bomb.sh/tab": "^0.0.10", - "@pnpm/tabtab": "^0.5.4" + "@bomb.sh/tab": "^0.0.10" }, "devDependencies": { "@clerc/core": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fbaeb293..32565c2c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -204,9 +204,6 @@ importers: '@bomb.sh/tab': specifier: ^0.0.10 version: 0.0.10(cac@6.7.14)(citty@0.1.6) - '@pnpm/tabtab': - specifier: ^0.5.4 - version: 0.5.4 devDependencies: '@clerc/core': specifier: workspace:* @@ -1050,10 +1047,6 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@pnpm/tabtab@0.5.4': - resolution: {integrity: sha512-bWLDlHsBlgKY/05wDN/V3ETcn5G2SV/SiA2ZmNvKGGlmVX4G5li7GRDhHcgYvHJHyJ8TUStqg2xtHmCs0UbAbg==} - engines: {node: '>=18'} - '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -1916,10 +1909,6 @@ packages: alien-signals@3.1.1: resolution: {integrity: sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA==} - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2316,10 +2305,6 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -4261,10 +4246,6 @@ packages: synckit: optional: true - untildify@4.0.0: - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} - engines: {node: '>=8'} - update-browserslist-db@1.1.4: resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true @@ -5194,15 +5175,6 @@ snapshots: '@pkgr/core@0.2.9': {} - '@pnpm/tabtab@0.5.4': - dependencies: - debug: 4.4.3 - enquirer: 2.4.1 - minimist: 1.2.8 - untildify: 4.0.0 - transitivePeerDependencies: - - supports-color - '@polka/url@1.0.0-next.29': {} '@quansync/fs@0.1.5': @@ -6183,8 +6155,6 @@ snapshots: alien-signals@3.1.1: {} - ansi-colors@4.1.3: {} - ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -6572,11 +6542,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.2 - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - entities@4.5.0: {} entities@6.0.1: {} @@ -8970,8 +8935,6 @@ snapshots: optionalDependencies: synckit: 0.11.11 - untildify@4.0.0: {} - update-browserslist-db@1.1.4(browserslist@4.28.0): dependencies: browserslist: 4.28.0 From 5030766f8d270babae7be53a45fcbe2eb3e9624c Mon Sep 17 00:00:00 2001 From: so1ve Date: Sun, 14 Dec 2025 21:06:25 +0800 Subject: [PATCH 7/7] doc --- docs/official-plugins/plugin-completions.md | 2 +- docs/zh/official-plugins/plugin-completions.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/official-plugins/plugin-completions.md b/docs/official-plugins/plugin-completions.md index 26196bb1..35e28d96 100644 --- a/docs/official-plugins/plugin-completions.md +++ b/docs/official-plugins/plugin-completions.md @@ -4,7 +4,7 @@ title: Completions Plugin # @clerc/plugin-completions -A plugin that adds command-line auto-completion functionality to your CLI. Based on [@pnpm/tabtab](https://github.com/pnpm/tabtab) +A plugin that adds command-line auto-completion functionality to your CLI. Based on [@bomb.sh/tab](https://github.com/bombshell-dev/tab). ## 📦 Installation diff --git a/docs/zh/official-plugins/plugin-completions.md b/docs/zh/official-plugins/plugin-completions.md index 2cce44df..ef3bd9d6 100644 --- a/docs/zh/official-plugins/plugin-completions.md +++ b/docs/zh/official-plugins/plugin-completions.md @@ -4,7 +4,7 @@ title: 自动补全插件 # @clerc/plugin-completions -为您的 CLI 添加命令行自动补全功能的插件。基于 [@pnpm/tabtab](https://github.com/pnpm/tabtab) +为您的 CLI 添加命令行自动补全功能的插件。基于 [@bomb.sh/tab](https://github.com/bombshell-dev/tab) ## 📦 安装