mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-11 16:24:11 +01:00
Commands Package Guide
Overview
The commands package hosts the CLI implementation for the PhotoPrism binary. Command wiring begins in commands.go, where each *cli.Command is registered on the shared slice consumed by cmd/photoprism/photoprism.go. Supporting utilities such as flag builders, shared error handling, and helper structs are colocated with their related command files. Keep commands cohesive: each file should focus on a single functional area (for example, download.go for the downloader entry point and download_impl.go for reusable logic). Whenever you introduce new commands, align naming with existing patterns and expose --json or --yes options when automation benefits from them.
How Commands Are Registered
- Add a new
*cli.Commandto thePhotoPrismslice incommands.go. - Provide a localized
Beforehook when a command needs configuration loading or authentication checks that differ from the defaults. - Reuse helpers from
internal/configfor option binding instead of reimplementing flag parsing. Field definitions belong in the shared flag modules sophotoprism show config-optionsstays accurate. - Prefer storing command-specific implementations in
<name>_impl.gofiles that can be imported in tests. Invoke the implementation from theActionfunction to avoid duplicating logic between CLI entry points and tests. - When adding commonly reused flags, call the shared helper constructors in
flags.go(YesFlag(),DryRunFlag(), role helpers, etc.) so identical options behave consistently across commands. If you need a new reusable flag, add it toflags.gofirst and then consume it from each command instead of hand-coding variants.
Command Implementation Patterns
- Construct filesystem paths with
filepath.Joinand rely on permission constants frompkg/fs(fs.ModeDir,fs.ModeFile, and friends) when writing to disk. - Follow the overwrite policy used by media helpers: require explicit confirmation (
forceflags) before replacing non-empty files. Where replacements are expected, open destinations withO_WRONLY|O_CREATE|O_TRUNC. - Use shared logging through
event.Lograther than directfmtprinting. Sensitive information such as secrets or tokens must never be logged. - When integrating configuration options, call the accessors on
*config.Config(for example,conf.ClusterUUID()) rather than mutating option structs directly. - For HTTP interactions, depend on the safe download helpers in
pkg/http/safeor the specialized wrappers ininternal/thumb/avatarto inherit timeout, size, and SSRF protection defaults.
Configuration & Flags Integration
- Define new options in
internal/config/options.gowith the appropriate struct tags (yaml,json,flag) so they propagate to YAML, CLI, and API layers consistently. - Surface CLI flags in
internal/config/flags.goto keep environment variable mappings aligned. Commands should callconf.ApplyCliContext()once to hydrate configuration from parsed flags. - Respect precedence rules: defaults < CLI/environment <
options.yml. Commands that generate configuration must setc.Options().OptionsYamlbefore persisting so changes appear in reports. - When emitting command catalogs or help output, reuse the catalog builders in
internal/commands/cataloginstead of crafting ad-hoc Markdown or JSON.
Testing Strategy
- Place tests beside their sources (
<name>_test.go) and group related assertions usingt.Run("CaseName", ...)subtests. Subtest names should use PascalCase for readability. - Execute focused suites with
go test ./internal/commands -run '<Name>' -count=1during development. For broader coverage,make test-goexercises backend packages under SQLite. - Wrap CLI runs with
RunWithTestContext(cmd, args)sourfave/cliexit codes do not callos.Exitduring tests. If you only need to inspect the exit status, invokecmd.Action(ctx)directly and assertcli.ExitCoder. - Build configurations through helpers. Use
config.NewTestConfig("commands")when migrations and fixtures are required,config.NewMinimalTestConfig(t.TempDir())when the test needs only filesystem scaffolding, orconfig.NewMinimalTestConfigWithDb("commands", t.TempDir())for an isolated SQLite schema without heavy fixtures. - Initialize test directories via
conf.InitializeTestData()when constructing custom configs so Originals, Import, Cache, and Temp paths exist before tests interact with the filesystem. - Prefer deterministic fixtures: generate entity IDs via helpers such as
rnd.GenerateUID(entity.PhotoUID)orrnd.UUIDv7()instead of hard-coded strings.
Focused Test Runs
- Download workflow:
go test ./internal/commands -run 'DownloadImpl|DownloadHelp' -count=1 - Auth and user management:
go test ./internal/commands -run 'Auth|Users' -count=1 - Cluster operations:
go test ./internal/commands -run 'Cluster' -count=1 - Full package smoke test:
go test ./internal/commands -count=1 - Backend-wide validation:
go test ./internal/service/cluster/registry -count=1andgo test ./internal/api -run 'Cluster' -count=1ensure CLI and API stay in sync before release.
CLI & Test Utilities
- Stub external binaries such as
yt-dlpwith lightweight shell scripts that honor--dump-single-jsonand--printrequests. Support environment variables likeYTDLP_ARGS_LOG,YTDLP_OUTPUT_FILE, andYTDLP_DUMMY_CONTENTto capture arguments, create deterministic artifacts, and avoid duplicate detection in importer flows. - Disable FFmpeg during tests that focus on command construction by setting
conf.Options().FFmpegBin = "/bin/false"andconf.Settings().Index.Convert = false. - When asserting HTTP responses, rely on header constants from
pkg/http/header(for example,header.ContentTypeZip) to keep expectations aligned with middleware. - For role and scope checks, reuse helpers in
internal/auth/aclsuch asacl.ParseRole,acl.ScopePermits, andacl.ScopeAttrPermitsinstead of duplicating logic inside commands.
Preflight Checklist
- Formatting and Swagger:
make fmt-goandmake swag-fmt swag - Build binaries:
go build ./... - Run targeted suites before merging:
go test ./internal/commands -run '<Name>' -count=1 - Execute integration-focused checks when touching cluster or API DTOs:
go test ./internal/commands -run 'ClusterRegister|ClusterNodesRotate' -count=1andgo test ./internal/api -run 'Cluster' -count=1 - Regenerate command catalogs when flag definitions change:
photoprism show commands --json --nested(or the Markdown default) should reflect the new entries without manual editing.