This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
The Upsun CLI is a Go-based command-line interface for Upsun. The CLI is a hybrid system that wraps a legacy PHP CLI (located in the legacy/ subdirectory) while providing new Go-based commands. It supports multiple vendors through build tags and configuration files.
Build a single binary for your platform:
make singleBuild a snapshot for all platforms:
make snapshotRun tests:
make test
# or directly:
GOEXPERIMENT=jsonv2 go test -v -race -cover -count=1 ./...Run linters:
make lint
# or individual linters:
make lint-gomod
make lint-golangciFormat code:
go fmt ./...Tidy dependencies:
go mod tidyRun a single test:
go test -v -run TestName ./path/to/packageThe CLI operates as a wrapper around a legacy PHP CLI:
- Go layer: Handles new commands (init, list, version, config:install, project:convert) and core infrastructure
- PHP layer: Legacy commands are proxied through
internal/legacy/CLIWrapper - The PHP CLI (platform.phar) is embedded at build time via go:embed
Entry Point: cmd/platform/main.go
- Loads configuration from YAML (embedded or external)
- Sets up Viper for environment variable handling
- Delegates to commands package
Commands: commands/
root.go: Root command that sets up the Cobra CLI and delegates to legacy CLI when needed- Native Go commands: init, list, version, config:install, project:convert, completion
- Unrecognized commands are passed to the legacy PHP CLI
Configuration: internal/config/
schema.go: Config struct definition with validation tagsversion.go: Uses runtime/debug to get version info from VCS- Supports vendorization through embedded YAML configs:
config_upsun.go: Default (no build tags) - Upsun CLIconfig_vendor.go: Requires-tags vendor- Custom vendor CLI
- Config can be loaded from external files for testing/development
Legacy Integration: internal/legacy/ and legacy/
internal/legacy/legacy.go: CLIWrapper that manages PHP binary and phar executionlegacy/: The legacy PHP CLI codebase (subtree merged from legacy-cli 5.x)- PHP binaries are embedded per platform via go:embed and build tags
- Uses file locking to prevent concurrent initialization
- Copies PHP binary and phar to cache directory on first run
API Client: internal/api/
- HTTP client for interacting with Upsun API
- Handles authentication, organizations, and resource management
Authentication: internal/auth/
- JWT handling and OAuth2 flow
- Custom transport for API authentication
Project Initialization: internal/init/
- AI-powered project configuration generation
- Integrates with whatsun library for codebase analysis
Multi-Vendor Support:
- Uses Go build tags to compile different binaries:
- Default (no tags): Upsun CLI
-tags vendor: Custom vendor CLI
- Configuration is embedded at compile time
- GoReleaser builds multiple variants (upsun, platform, vendor-specific)
PHP Binary Handling:
- PHP binaries are downloaded from upsun/cli-php-builds releases
- All platforms use static binaries built with static-php-cli
- Supported platforms: linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64
- Extensions included: curl, filter, openssl, pcntl (Unix), phar, posix (Unix), zlib
- Windows requires
cacert.pemfor OpenSSL (embedded separately)
Downloading PHP Binaries:
# Download PHP for current platform only (for development)
make php
# Download all PHP binaries (for release builds)
make download-phpUpgrading PHP Version:
- Trigger the build workflow at upsun/cli-php-builds with the new PHP version
- Update
PHP_VERSIONin the Makefile - Run
make phpto download the new binary - Test and release
Tests use github.com/stretchr/testify for assertions. Table-driven tests are preferred with a "cases" slice containing simple test case structs.
The CLI uses Viper for configuration. Environment variables use the prefix defined in the config (UPSUN_CLI_ or PLATFORM_CLI_). The prefix is set in the config YAML.
When the root command receives arguments it doesn't recognize, it passes them to the legacy PHP CLI via CLIWrapper.Exec(). The PHP binary and phar are extracted to a cache directory on first use.
To build a vendor-specific CLI:
make vendor-snapshot VENDOR_NAME='Vendor Name' VENDOR_BINARY='vendorcli'
make vendor-release VENDOR_NAME='Vendor Name' VENDOR_BINARY='vendorcli'This requires a config file at internal/config/embedded-config.yaml (downloaded at build time).
Version information is obtained from Go's runtime/debug package (VCS info embedded at build time):
internal/config.Version: Git tag/version (from VCS or module version)internal/config.Commit: Git commit hash (from vcs.revision)internal/config.Date: Build date (from vcs.time)
PHP version is still injected via ldflags:
internal/legacy.PHPVersion: PHP version embedded
The CLI checks for updates from GitHub releases (when Wrapper.GitHubRepo is set in config). This runs in a background goroutine and prints a message after command execution.