Skip to content

Latest commit

 

History

History
235 lines (173 loc) · 13.3 KB

File metadata and controls

235 lines (173 loc) · 13.3 KB

Generate native zsh completion for dokku

You are writing a zsh-only completion script for the Dokku CLI. Use the installed dokku binary and the configured local harness (or any live Dokku client context) so goals and argument values are discovered from the current Dokku system—not hard-coded.

Deliverable

Produce a single zsh completion implementation that:

  1. Registers for the command name dokku (compdef and/or #compdef + fpath, as appropriate for zinit / oh-my-zsh style loading).
  2. Completes goals after dokku (e.g. apps:create, config:set, postgres:info, redis:create).
  3. Completes each goal's arguments contextually: app names, network names, plugin service instances, domains, config keys, flags, enumerated flag values, and other values exposed by the connected Dokku system.
  4. Stays pure zsh — no bashcompinit, no bash-completion polyfills, no wrapping the upstream bash script.
  5. Discovers goals and candidate values dynamically from a live dokku. Newly installed plugins and newly created or removed resources must appear without editing the completion file.
  6. Never embeds inventories of server resources. App names, networks, services, domains, keys, and similar candidates must always originate from queries against the current Dokku context.

Write the completer to completions/_dokku in this repo (#compdef dokku at the top). The plugin loader (zsh-dokku.plugin.zsh) only adds completions/ to fpath; do not rely on source-time compdef unless you document why.

Environment you should use

This repo provides a local Dokku playground. Prefer it when available:

just bootstrap_local_dokku   # reset + start harness + example app + redis + postgres
# mise: PATH includes ./bin so `dokku` is the docker-exec wrapper
dokku --quiet help --all
dokku plugin:list
dokku apps:list
Env Meaning
DOKKU_CONTAINER Compose container (dokku-completion-harness)
DOKKU_HOST dokku-completion-harness.orb.local (remote client)
DOKKU_PORT 22 via OrbStack

If the harness is down, any working client is fine: Homebrew dokku with DOKKU_HOST / DOKKU_PORT, or a git remote named dokku.

Verify the client is configured before trusting help output. Unconfigured remote client prints a setup error that includes an indented i.e. line—naive parsers will treat that as a goal.

Source of truth for goals

Match upstream bash completion’s idea of “commands”:

# Official bash completer:
# https://github.com/dokku/dokku/blob/master/contrib/bash-completion

dokku --quiet help --all | awk '/^    /{ print $1 }'
  • Indented help lines are goal tokens (plugin:subcommand or bare names like version).
  • Plugins add more colon-separated goals automatically when installed on the server.
  • Filter aggressively: only tokens matching something like
    ^[a-z][a-z0-9_-]*(:[a-z0-9_-]+)*$
    so setup prose (i.e.) never becomes a match.

Inspect live output yourself (dokku --quiet help --all, plugin:list, sample postgres: / redis: lines) before coding.

Source of truth for argument values

Use the selected goal's live help signature to determine its argument shape, then query the connected Dokku system for candidates at the current argument position. Inspect the real output and supported flags before choosing a parser; do not assume headers or formats.

Examples of the intended discovery paths:

Argument kind Live source to inspect/query
Apps dokku apps:list and its machine-readable/plain-output options
Networks dokku network:list and its supported output options
Postgres services dokku postgres:list
Redis services dokku redis:list
Other plugin services The installed plugin namespace's live help and *:list command when available
Domains The relevant live domains:* query for the selected/current app
Config keys The relevant live config:* query after resolving the app or --global context
Flags and enumerated values The selected goal's live help/usage output

These commands are examples of discovery paths, not permission to hard-code their returned values. Prefer stable machine-readable output when the installed Dokku/plugin version offers it; otherwise parse the inspected live output conservatively.

Completion must be positional and context-aware. For example:

  • dokku apps:destroy <Tab> completes current app names.
  • dokku apps:list <Tab> completes --format; it must not complete app names because the live signature is apps:list [--format stdout|json].
  • A goal with an optional app and flags, such as dokku builds:list <Tab>, may offer both live app names and valid flags at the same position.
  • dokku postgres:info <Tab> completes current Postgres service names.
  • dokku postgres:link <Tab> completes Postgres services, while the following argument completes apps.
  • dokku config:get <app> <Tab> completes keys for that app.
  • A goal expecting a network completes networks from the current Dokku system.

Plugin argument support must be extensible. Discover installed namespaces and their goal signatures from live help. When a plugin exposes a resource-listing command, use it rather than maintaining a static service inventory.

Hard requirements (zsh)

Use compadd, not _describe

Goals contain colons (apps:create). _describe treats the first : as name:description and will mangle them. Offer the flat list with compadd (e.g. via _wanted + compadd -a).

Do not model space-separated subcommands

Real Dokku is dokku apps:create, not dokku apps create. Community completers that assume spaces are wrong for this CLI.

Guard bad / empty output

Before caching or completing goals or argument values:

  1. Non-zero exit or empty stdout → fail (do not clobber a good cache).
  2. Reject output that looks like the unconfigured-client message (DOKKU_HOST, git remote add dokku).
  3. Validate and re-filter every candidate type when loading a cache so old pollution self-heals.
  4. Treat headings, status messages, warnings, and setup prose as non-candidates.
  5. Never fall back to invented or statically bundled resource names when a live query fails.

Dynamic argument dispatch

  • Parse the selected colon-shaped goal and CURRENT argument position.
  • Use earlier words when a query depends on context, such as the selected app, service, or --global.
  • Obtain argument shapes and available flags from live help wherever practical.
  • Do not assume every goal accepts an app. Follow the live signature exactly: list-all goals such as apps:list have no app positional, while goals such as apps:destroy do.
  • Offer valid flags even when the current word is empty; users should not need to type - before discovering options. If both a resource argument and flags are valid at the current position, add both candidate groups.
  • Once words[2] is a recognized goal, the completer owns the remaining command line. Terminal positions, intentionally unsupported values, empty live queries, and failed safe lookups must return a handled/success state with zero matches when appropriate. Do not return failure and allow compsys to fall back to top-level Dokku goals, files, or unrelated “primary” completions.
  • Distinguish “no candidates at this valid position” from “this is not a Dokku completion state.” Graceful no-match behavior means no invented matches and no unrelated fallback matches.
  • It is acceptable to encode small amounts of goal-to-resource grammar when Dokku exposes no machine-readable argument schema. It is not acceptable to encode the resource values themselves.
  • Query plugin namespaces generically where possible so newly installed service plugins can participate without adding their instance names—or ideally their namespace—to the source.
  • Quote all user-selected arguments passed into lookup commands and ensure completion lookups cannot become interactive or destructive.

Caching

help --all and resource-list commands may hit SSH or a container. Avoid unnecessary calls, but do not let caching turn the completion into a stale static inventory.

  • Suggested path: ${XDG_CACHE_HOME:-$HOME/.cache}/dokku/completion
  • Goal/help TTL may be on the order of days (e.g. 7).
  • Resource-value caches must use a much shorter TTL appropriate for mutable data, or query live on each completion when latency is acceptable.
  • Cache entries must be separated by Dokku connection context so values from one host are never offered for another host.
  • Include the candidate type and any required parent context in resource cache keys (for example, config keys for a specific app).
  • Document force refresh: rm -f ~/.cache/dokku/completion
  • Atomic write (temp file + mv) so a failed refresh leaves the previous cache intact.

Upstream bash uses /var/cache/dokku-completion (server-oriented); prefer an XDG cache path on developer machines.

Registration

  • Gate on (( $+commands[dokku] || $+aliases[dokku] )) (or equivalent) so the script no-ops when dokku is absent.
  • Register _dokku for dokku. If load order is post-compinit (zinit wait'0c'), compdef _dokku dokku and _comps[dokku]=_dokku at source time is enough—no zcompdump rebuild required for that style.

Scope

Required scope:

  • Flat list of goals for the first argument after dokku.
  • Core + all installed plugins via live help.
  • Context-aware completion for app names and multi-argument goals.
  • Dynamic network, domain, config-key, and plugin service-instance completion when the connected system exposes a query for them.
  • Flags and enumerated values discoverable from live goal usage.
  • Graceful no-match behavior for values that Dokku cannot expose safely or reliably.

Do not declare the implementation complete with goal-only completion. Argument completion backed by the current Dokku system is part of the deliverable.

What to avoid

Approach Why not
Source official bash script under bashcompinit Needs helpers zsh does not provide (_get_comp_words_by_ref, etc.); high polyfill cost
Static goals, resources, plugin namespaces, or candidate values Rots when Dokku or server state changes; misses server-specific plugins and resources
MenkeTechnologies-style space-separated _dokku Wrong CLI shape (apps create vs apps:create)
_describe for colon goals Breaks on first :
Completing resource-shaped placeholders with guessed examples Invents values that may not exist on the connected Dokku system

Acceptance checks

Run these against the harness (or any live Dokku):

# 1. Help yields real goals including plugins
dokku --quiet help --all | awk '/^    /{ print $1 }' | grep -E '^(apps:|postgres:|redis:)' | head

# 2. After loading your completer in a zsh with compsys:
#    dokku <Tab>   → apps:create, postgres:…, redis:…, etc.
#    dokku apps:<Tab> or partial prefixes filter correctly

# 3. Dynamic arguments come from the harness:
#    dokku apps:destroy <Tab>       → includes the current example app
#    dokku postgres:info <Tab>      → includes the current Postgres service
#    dokku redis:info <Tab>         → includes the current Redis service
#    applicable network goals       → include live network names

# 4. Multi-argument dispatch uses the right resource type:
#    dokku postgres:link <service> <Tab> → current app names
#    app-scoped config completion        → keys from the selected app

# 5. Goal signatures, blank-word flags, and terminal states are respected:
#    dokku apps:list <Tab>       → --format, not app names/goals/files
#    dokku apps:list --format <Tab> → stdout and json
#    dokku builds:list <Tab>     → live apps plus valid flags
#    dokku version <Tab>         → no top-level-goal or filename fallback
#
#    Automated tests should assert both the candidate set and the completion
#    function's return status so an empty handled state cannot regress into
#    compsys fallback.

# 6. Create/remove a test resource, refresh or wait for the short TTL, and
#    verify candidates change without editing the completion source.

# 7. Unconfigured clients and failed goal/resource queries do not invent junk
#    matches, leak another host's values, or offer setup prose such as "i.e."

# 8. Force cache rebuild works
rm -f "${XDG_CACHE_HOME:-$HOME/.cache}/dokku/completion"
# Tab again should repopulate without errors

Optional: echo ${_comps[dokku]} → your completer function name.

References

What Link
Official bash completion https://github.com/dokku/dokku/blob/master/contrib/bash-completion
Remote client docs https://dokku.com/docs/deployment/remote-commands/
Upstream zsh request dokku/dokku#6023
Community static zsh (reference only) https://github.com/MenkeTechnologies/zsh-more-completions

Output format

Write the completion as zsh source ready to load. Comment the cache paths, refresh behavior, connection-context isolation, dynamic lookup strategy, and any value types the installed Dokku cannot expose safely. Prefer clear function names (_dokku, _dokku_refresh_completion_cache, _dokku_complete_apps, …). Do not emit bash. Do not depend on this monorepo’s Justfile/compose at runtime—only use them to inspect the live CLI while authoring.