import os from "node:os"; import path from "node:path"; import { resolveCliName } from "../cli/cli-name.js"; import { formatCompletionReloadCommand, installCompletion, resolveCompletionProfilePath, } from "../cli/completion-runtime.js"; import type { ShellCompletionStatus } from "../commands/doctor-completion.js"; import { checkShellCompletionStatus, ensureCompletionCacheExists, } from "../commands/doctor-completion.js"; import { pathExists } from "../utils.js"; import { t } from "./i18n/index.js"; import type { WizardPrompter } from "./prompts.js"; import type { WizardFlow } from "./setup.types.js"; type CompletionDeps = { resolveCliName: () => string; checkShellCompletionStatus: (binName: string) => Promise; ensureCompletionCacheExists: (binName: string) => Promise; installCompletion: (shell: string, yes: boolean, binName?: string) => Promise; }; async function resolveProfileHint(shell: ShellCompletionStatus["shell"]): Promise { const home = process.env.HOME || os.homedir(); if (shell === "zsh") { return "~/.zshrc"; } if (shell === "bash") { const bashrc = path.join(home, ".bashrc"); return (await pathExists(bashrc)) ? "~/.bashrc" : "~/.bash_profile"; } if (shell === "fish") { return "~/.config/fish/config.fish"; } return resolveCompletionProfilePath("powershell"); } function formatReloadHint(shell: ShellCompletionStatus["shell"], profileHint: string): string { if (shell === "powershell") { return t("wizard.completion.reloadPowerShell", { command: formatCompletionReloadCommand("powershell", profileHint), }); } return t("wizard.completion.reloadShell", { profile: profileHint }); } export async function setupWizardShellCompletion(params: { flow: WizardFlow; prompter: Pick; deps?: Partial; }): Promise { const deps: CompletionDeps = { resolveCliName, checkShellCompletionStatus, ensureCompletionCacheExists, installCompletion, ...params.deps, }; const cliName = deps.resolveCliName(); const completionStatus = await deps.checkShellCompletionStatus(cliName); if (completionStatus.usesSlowPattern) { // Case 1: Profile uses slow dynamic pattern - silently upgrade to cached version const cacheGenerated = await deps.ensureCompletionCacheExists(cliName); if (cacheGenerated) { await deps.installCompletion(completionStatus.shell, true, cliName); } return; } if (completionStatus.profileInstalled && !completionStatus.cacheExists) { // Case 2: Profile has completion but no cache - auto-fix silently await deps.ensureCompletionCacheExists(cliName); return; } if (!completionStatus.profileInstalled) { // Case 3: No completion at all const shouldInstall = params.flow === "quickstart" ? true : await params.prompter.confirm({ message: t("wizard.completion.enable", { shell: completionStatus.shell, cli: cliName, }), initialValue: true, }); if (!shouldInstall) { return; } // Generate cache first (required for fast shell startup) const cacheGenerated = await deps.ensureCompletionCacheExists(cliName); if (!cacheGenerated) { await params.prompter.note( t("wizard.completion.cacheFailed", { command: `${cliName} completion --install` }), t("wizard.completion.title"), ); return; } // Install to shell profile await deps.installCompletion(completionStatus.shell, true, cliName); const profileHint = await resolveProfileHint(completionStatus.shell); await params.prompter.note( t("wizard.completion.installed", { reloadHint: formatReloadHint(completionStatus.shell, profileHint), }), t("wizard.completion.title"), ); } // Case 4: Both profile and cache exist (using cached version) - all good, nothing to do }