This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Wave CLI is a command-line tool for the Wave container provisioning service. It allows users to:
- Build container images on-demand from Dockerfiles or Conda/CRAN packages
- Augment existing containers with additional layers
- Build containers for specific platforms (linux/amd64, linux/arm64)
- Push containers to registries and enable Singularity format
- Mirror containers between registries
- Scan containers for security vulnerabilities
The CLI is built using Java 17 (with Java 21 toolchain) and compiles to a native binary using GraalVM.
App.java (io.seqera.wave.cli.App)
- Main entry point implementing
Runnable - Uses Picocli for CLI argument parsing with extensive
@Optionannotations - Orchestrates the entire request lifecycle: validation → request creation → submission → response handling
- Handles multiple input modes: containerfile, image, conda packages, CRAN packages, layers
- Main flow:
main()→validateArgs()→run()→createRequest()→client().submit()
Client.java (io.seqera.wave.cli.Client)
- HTTP client wrapper using Java 11+ HttpClient
- Implements retry logic with Failsafe library for resilient API calls
- Key methods:
submit()- Submit container build requestsinspect()- Inspect container metadataawaitCompletion()- Poll for build completion statusserviceInfo()- Get Wave service version info
Request/Response Flow
- User provides CLI options (image, containerfile, packages, etc.)
Appvalidates arguments and prepares context (build context, layers, config)- Creates
SubmitContainerTokenRequestwith all specifications Clientsubmits to Wave API endpoint- Returns
SubmitContainerTokenResponsewith container image URL - Optional:
--awaitflag polls until build completes
- cli/ - Main application logic and CLI interface
- cli/model/ - Extended models wrapping Wave API types (e.g.,
ContainerInspectResponseEx) - cli/util/ - Utilities (BuildInfo, YamlHelper, Checkers, DurationConverter)
- cli/json/ - JSON serialization using Moshi
- cli/exception/ - Custom exceptions (IllegalCliArgumentException, BadClientResponseException, etc.)
- cli/config/ - Configuration objects (RetryOpts, CondaOpts, CranOpts)
The project compiles to a native binary with specific configuration:
- Native image config files in
app/conf/(reflect-config.json, jni-config.json, etc.) - These are critical for reflection-based libraries (Moshi, Picocli)
- When adding new API model classes that use reflection, update
reflect-config.json - Use
--agentlibmode (configured in build.gradle) to auto-generate configs during development
# Compile Java sources
./gradlew assemble
# Run all tests (Spock framework in Groovy)
./gradlew test
# Compile and run tests
./gradlew check
# Run a single test class
./gradlew test --tests AppTest
# Run a single test method
./gradlew test --tests "AppTest.should fail when specifying mirror registry and container file"# Build native binary (requires GraalVM Java 21)
./gradlew app:nativeCompile
# Run the native binary
./app/build/native/nativeCompile/wave --version
# Build shadow JAR (fat JAR with all dependencies)
./gradlew shadowJar# Run via Gradle
./gradlew run --args="-i alpine"
# Run the shadow JAR
java -jar app/build/libs/wave.jar -i alpine
# Run native binary after compilation
./app/build/native/nativeCompile/wave -i alpine# View runtime dependencies
./gradlew app:dependencies --configuration runtimeClasspath
# View compile dependencies
./gradlew app:dependencies --configuration compileClasspath- Uses Spock Framework (Groovy-based BDD testing)
- Test files in
app/src/test/groovy/with.groovyextension - Main test:
AppTest.groovycovers CLI argument validation and request creation
def 'should describe what the test does'() {
given:
def app = new App()
String[] args = ["--option", "value"]
when:
new CommandLine(app).parseArgs(args)
app.validateArgs()
then:
// assertions or expected exceptions
}- Use
CommandLine.parseArgs()to simulate CLI input - Call
app.validateArgs()to trigger validation logic - Use
thrown()to verify exceptions:def e = thrown(IllegalCliArgumentException) - Mock-free approach: tests primarily verify argument parsing and validation logic
- Boolean flags like
--mirror,--freeze,--singularitydo NOT take values - Correct:
--mirror - Incorrect:
--mirror true(causes UnmatchedArgumentException)
The CLI supports three package ecosystems (mutually exclusive):
- Conda:
--conda-packageor--conda-filewithCondaOpts - CRAN:
--cran-packagewithCranOpts - Each has base image and run command customization options
- Build context (
--context) requires a containerfile (-f) - Context and layers are packaged as gzip tar archives using
Packerutility - Max sizes enforced: 5MB for build context, 1MB per layer, 10MB total for layers
.dockerignoresupport viaDockerIgnoreFilter
- Default: prints only the container image URL
--output jsonor--output yaml: structured output--await: waits for build completion, returns status in output
- Primary dependency:
io.seqera:wave-api(currently 1.28.0) - Provides request/response models:
SubmitContainerTokenRequest,ContainerInspectRequest, etc. - API endpoint configurable via
--wave-endpoint(default: https://wave.seqera.io)
- Optional Tower token (
--tower-token) for authenticated builds - Required for
--build-repo(persistent container storage) - Tower endpoint defaults to Seqera Platform Cloud: https://api.cloud.seqera.io
- Add
@Optionfield toApp.java - Update
validateArgs()with validation logic - Update
createRequest()to pass option to Wave API - Add tests in
AppTest.groovy - Update usage examples in
app/src/main/resources/io/seqera/wave/cli/usage-examples.txt
- Update
wave-apidependency version inapp/build.gradle - Add reflection config to
app/conf/reflect-config.jsonfor any classes using reflection - Create extended model in
cli/model/if additional logic needed (seeContainerInspectResponseEx)
- Run application with
--agentlib:native-image-agent=config-merge-dir=app/conf/to auto-generate configs - Manually verify and commit updated config files in
app/conf/ - Test native build after changes:
./gradlew app:nativeCompile
- Use
IllegalCliArgumentExceptionfor validation errors (caught in main and printed to stderr) - Use
BadClientResponseExceptionfor API response errors - Use
ClientConnectionExceptionfor network/connection issues - All exceptions exit with code 1
TOWER_ACCESS_TOKEN- default for--tower-tokenTOWER_API_ENDPOINT- default for--tower-endpointTOWER_WORKSPACE_ID- default for--tower-workspace-idWAVE_ENDPOINT- default for--wave-endpoint
- Custom
DurationConverterfor Picocli - Supports:
10m,2s,1h, etc. - Used by
--awaitoption (default: 15 minutes)
- Update the local Git repo and fetch latest tags
- Update the
VERSIONfile with a semantic version. - Update the README with the new version number.
- Update the
changelog.txt file with changes against previous release. Usegit log --oneline v..HEAD` to determine the changes to be added. - Commit VERSION and changelog.txt file adding the tag
[release]in the commit comment first line. - Git push to upstream master branch.