import os from "node:os"; import path from "node:path"; import { resolveCliName } from "../cli/cli-name.js"; import { installCompletion } from "../cli/completion-cli.js"; import type { ShellCompletionStatus } from "../commands/doctor-completion.js"; import { checkShellCompletionStatus, ensureCompletionCacheExists, } from "../commands/doctor-completion.js"; import { pathExists } from "../utils.js"; import type { WizardFlow } from "./onboarding.types.js"; import type { WizardPrompter } from "./prompts.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"; } // Best-effort. PowerShell profile path varies; restart hint is still correct. return "$PROFILE"; } function formatReloadHint(shell: ShellCompletionStatus["shell"], profileHint: string): string { if (shell === "powershell") { return "Restart your shell (or reload your PowerShell profile)."; } return `Restart your shell or run: source ${profileHint}`; } export async function setupOnboardingShellCompletion(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: `Enable ${completionStatus.shell} shell completion for ${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( `Failed to generate completion cache. Run \`${cliName} completion --install\` later.`, "Shell completion", ); return; } // Install to shell profile await deps.installCompletion(completionStatus.shell, true, cliName); const profileHint = await resolveProfileHint(completionStatus.shell); await params.prompter.note( `Shell completion installed. ${formatReloadHint(completionStatus.shell, profileHint)}`, "Shell completion", ); } // Case 4: Both profile and cache exist (using cached version) - all good, nothing to do }