Skip to content

feat(completion): answer command and option names in Go - #209

Open
pjcdawkins wants to merge 6 commits into
mainfrom
cli-200-shell-completion-go
Open

pjcdawkins wants to merge 6 commits into
mainfrom
cli-200-shell-completion-go

Conversation

@pjcdawkins

Copy link
Copy Markdown
Contributor

Shell completion called the legacy PHP CLI on every Tab. _complete now suggests command and option names in Go, in the output format of Symfony's completion scripts for bash, zsh and fish, so installed scripts keep working without being regenerated. PHP is only started for argument and option values (projects, environments, etc.).

  • Command names include native commands (e.g. init, lint) and exclude commands disabled by config.
  • Option names come from the embedded legacy command index, or from Cobra flags for native commands.
  • Option descriptions are flattened to one line: multi-line descriptions broke zsh and fish suggestions.
  • Hidden options are not suggested.

The command index is now generated for each embedded config (commands-upsun.json, commands-platformsh.json, commands-vendor.json) and embedded with the matching build tag. Before, it reflected the legacy CLI's default config, so config-dependent commands and options (e.g. resources:*, --no-resources) did not match the Upsun CLI.

Go leaves completion to PHP when the index may not apply: with an external config (CLI_CONFIG_FILE), a user config file, or an API_, APPLICATION_ or EXPERIMENTAL_ environment override. Project- and OS-dependent visibility (environment:drush, tunnel:open on Windows) is not reflected.

Integration tests run the generated scripts in bash, zsh and fish; CI installs those shells.

🤖 Generated with Claude Code

pjcdawkins and others added 5 commits October 9, 2026 11:02
The completion scripts call `_complete` on each Tab, which used to start
the legacy PHP CLI every time. Command and option names are now suggested
in Go, from the embedded legacy command index and the Cobra commands, in
the output format of Symfony's completion for bash, zsh and fish. The
installed scripts keep working without being regenerated.

PHP is only started for argument and option values (projects,
environments, etc.), or for requests that Go does not parse.

Other changes:
- Native commands (e.g. init, lint) are now suggested, and commands
  disabled by config are not.
- Option descriptions are flattened to one line, as a multi-line
  description broke zsh and fish suggestions.
- Hidden options are not suggested.
- Integration tests run the generated scripts in bash, zsh and fish.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The command index was generated with the legacy CLI's default config, so
config-dependent commands and options did not match the Upsun or
Platform.sh CLIs: for example, resources:get was marked hidden and
backup:restore lacked --no-resources in the Upsun CLI. Completion now
relies on the index, so an index is generated for each embedded config
(commands-upsun.json, commands-platformsh.json, commands-vendor.json)
and embedded with the matching build tag.

Experiments are no longer enabled when generating it, so that it lists
what the default config offers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The embedded index reflects the default config. A user config file, or
an API_, APPLICATION_ or EXPERIMENTAL_ environment override, can hide
commands, enable experiments or change options, so command and option
names are then completed by the legacy CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An external config (CLI_CONFIG_FILE, as used by alternative CLIs from
config:install) can change the legacy commands and options, so the
embedded index only applies to the embedded config.

The shell integration test now runs the CLI with its embedded config.
Vendor builds also generate the default indexes, which the completion
script hook needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@upsun-dispatch upsun-dispatch Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Reviewed — No blocking findings · 🔵 2 minor points

🔍 Full review · 14 files reviewed

🔵 Minor points

  • Makefile:40 — legacy-index gets the env prefix from the first line in the config that matches ^ env_prefix: (2-space indent, value matching [A-Z0-9_]*). The repository configs work because application.env_prefix comes before service.env_prefix. The vendor embedded-config.yaml is downloaded at build time, and nothing guarantees its layout. If its service: block comes first, the recipe picks the service prefix (e.g. PLATFORM_), so NO_LEGACY_WARNING and APPLICATION_VERSION are set under the wrong names and have no effect when the vendor index is generated. If the file uses another indent width, test -n fails and make vendor-release/vendor-snapshot stops. A small Go or PHP helper that parses the YAML for application.env_prefix would avoid both cases.
  • Makefile:42 — The old index recipe set EXPERIMENTAL_ALL_EXPERIMENTS=1 on purpose so that every command was indexed. The new recipe drops it, so experiment-gated commands such as server:* (ServerCommandBase::isEnabled checks enable_local_server) are no longer in the index. Completion guards against this with legacyConfigOverridden, but the index also feeds expandAbbreviation in root.go and help.go, which run unconditionally. When a user enables experiments in ~/.upsun-cli/config.yaml, the legacy CLI has commands that the ambiguity check never sees. An abbreviation that the legacy CLI would reject as ambiguous can then be expanded to a native command. No current native name collides with these commands, so nothing fails today. A future native or experimental command could collide.
Verification
  • The legacy CustomJsonDescriptor writes empty option maps as {}, leaves out hidden options and global options, and flattens descriptions, so the map[string]Option decode and Definition doc comment are correct.
  • Each commands_*.go build tag pairs with the matching config_*.go tag (!platformsh && !vendor, platformsh && !vendor, vendor), so the embedded index always matches the embedded config.
  • writeSuggestions matches Symfony 7.4's output format for each shell: bash prints values only with a trailing newline, zsh and fish add a tab before the description, and fish has no trailing newline.
  • The Symfony scripts drop empty input words, so parseCompleteRequest rejecting an empty -i value never sends normal requests to PHP; after a space, current == len(tokens), which the code treats as a free cursor.
  • resolveAbbreviation now returns an index instead of a pointer, and both expandAbbreviation call sites are updated to match.

New unit tests in commands/complete_test.go (parsing, Go completion, output format, override detection) and TestIsEmbedded run in the CI test job (make test). TestShellCompletion runs the generated scripts in bash, zsh and fish in the integration-test job, which now installs those shells. No test covers the Makefile index recipe with a vendor config.

Review details
  • Commit: bf54757
  • Model: claude-opus-5-5

Review 1 of 10 for this pull request · View the full run

The legacy index recipe took the first "env_prefix:" line of the
config, which depends on the layout of the vendor config downloaded at
build time. It now parses the config like the CLI does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@upsun-dispatch upsun-dispatch Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Reviewed — No new issues found · 1 still open

🔁 Incremental · 2 files reviewed

Outstanding from earlier reviews:

  • 🔵 Makefile:41: Abbreviation expansion stops matching the legacy CLI once experiments are enabled. — The legacy-index recipe still doesn't set EXPERIMENTAL_ALL_EXPERIMENTS, so experiment-gated commands stay out of the index that expandAbbreviation uses. (first raised)
Verification
  • config.FromYAML validates Application.EnvPrefix as required, so scripts/env-prefix cannot print an empty prefix. That makes up for dropping the old test -n "$prefix" guard.
  • Building scripts/env-prefix without build tags pulls in config_upsun.go, which embeds upsun-cli.yaml. That file is checked in, so go run works without embedded-config.yaml even for the vendor index.
  • In legacy-index, a non-zero exit from go run inside $$(...) breaks the && chain. The recipe then exits with that status and still deletes the temp dir.

Nothing in this increment tests scripts/env-prefix. It only runs when the Makefile index targets build, i.e. in make single/snapshot/release and in the CI jobs that call them. config_test.go already covers the required validation of EnvPrefix that the script relies on.

Review details

Review 2 of 10 for this pull request · View the full run

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant