import type { CommandSpec } from 'lib/commands/spec.js' import { renderBashCompletion } from './render-bash.js' import { renderFishCompletion } from './render-fish.js' import { renderZshCompletion } from './render-zsh.js' export const completionShells = ['bash', 'fish', 'zsh'] as const export type CompletionShell = (typeof completionShells)[number] export const isCompletionShell = (shell: unknown): shell is CompletionShell => completionShells.includes(shell as CompletionShell) const renderers: Record string> = { bash: renderBashCompletion, fish: renderFishCompletion, zsh: renderZshCompletion, } export const renderCompletion = ( shell: CompletionShell, spec: CommandSpec, ): string => renderers[shell](spec) /** * Render the completion loader installed by system packages. The loader is * embedded in the CLI and printed by `seam completion --loader `. * * The loader runs 'seam completion' the first time the shell completes a seam * command, so installed completions always match the CLI's current Seam API * schema instead of the schema packaged at release time. Each shell * loads its completion file on demand, so the CLI runs once per shell session * at first completion, never at shell startup. * * The loader degrades to no completions when the seam command is missing or * does not produce a completion script: a script is only evaluated when it * starts with the exact first line 'seam completion' generates, so nothing * else the CLI may print, e.g., 'Not logged in' from a version without the * completion command, is ever evaluated as shell code. */ export const renderCompletionStub = (shell: CompletionShell): string => stubs[shell] export const renderCompletionEval = (shell: CompletionShell): string => { const loader = `seam completion --loader ${shell} 2> /dev/null` return shell === 'fish' ? `${loader} | source` : `eval "$(${loader})"` } /** * First line of each generated completion script, which the loaders require * before evaluating one. Must match the output of {@link renderCompletion}. */ export const completionScriptSentinels: Record = { bash: '# bash completion for the seam command.', fish: '# fish completion for the seam command.', zsh: '#compdef seam', } const stubHeader = (shell: CompletionShell): string => `# ${shell} completion loader for the seam command. # # Generated by @seamapi/cli. Loads completions from the CLI on first use, so # they always match the CLI's current Seam API schema. Requires the seam # command on PATH. Print the underlying script with 'seam completion ${shell}'.` const stubs: Record = { bash: `${stubHeader('bash')} # # Install to /usr/share/bash-completion/completions/seam _seam_completion_loader() { local script script="$(seam completion bash 2> /dev/null)" # Evaluate only a completion script, never anything else the CLI printed. case "$script" in '${completionScriptSentinels.bash}'*) eval "$script" ;; *) return 1 ;; esac return 124 } if ((BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 1))); then complete -F _seam_completion_loader seam else _seam_completion_loader || : fi `, fish: `${stubHeader('fish')} # # Install to /usr/share/fish/vendor_completions.d/seam.fish if command --query seam set -l __seam_completion_script (seam completion fish 2> /dev/null | string collect) # Source only a completion script, never anything else the CLI printed. if string match --quiet '${completionScriptSentinels.fish}*' -- $__seam_completion_script printf '%s\\n' $__seam_completion_script | source end end `, zsh: `#compdef seam ${stubHeader('zsh')} # # Install to a directory in fpath as _seam _seam() { local script script="$(seam completion zsh 2> /dev/null)" # Evaluate only a completion script, never anything else the CLI printed. # The script ends by dispatching on funcstack, so evaluating it while this # _seam runs both redefines _seam and completes the in-flight request. if [[ "$script" == '${completionScriptSentinels.zsh}'* ]]; then eval "$script" fi } if (( $+functions[compdef] )); then compdef _seam seam fi if (( \${funcstack[(I)_seam]} )); then _seam "$@" fi `, }