photoprism/internal/commands
Keith Martin b6687b6c29
Tests: Improve isolation and add fixes #5263
* Tests: fix random selection of deleted record causing test failure
* Tests: fix test folders being left after test
* Tests: ensure that required records are available
* Tests: make each package execute against it's own testdata folder to ensure no interpackage test failures
* Tests: add unit tests of CleanupTestFolder
* Tests:  Allow defer in TestMain
* Tests: rename testMain to runTestMain
2026-07-12 05:47:21 +02:00
..
catalog Links: Use canonical trailing-slash form for website URLs 2026-06-24 17:20:30 +02:00
testdata Config: Improve low storage detection #5613 2026-05-29 01:45:04 +02:00
AGENTS.md ACL: Generate CLI role & auth-provider help from role tables #5667 2026-06-16 11:57:44 +02:00
auth.go CLI: Added JWT issuance and diagnostics sub commands #5230 2025-09-26 02:38:49 +02:00
auth_add.go Auth: Gate app passwords behind a settings feature flag #5647 2026-06-09 17:36:24 +00:00
auth_add_test.go Auth: Gate app passwords behind a settings feature flag #5647 2026-06-09 17:36:24 +00:00
auth_jwt.go CLI: Added JWT issuance and diagnostics sub commands #5230 2025-09-26 02:38:49 +02:00
auth_jwt_inspect.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
auth_jwt_issue.go Cluster: Refactor request/response structs and JSON serialization 2025-10-18 17:42:22 +02:00
auth_jwt_keys.go Logs: Add package pkg/log/status to provide generic outcome constants 2025-10-21 14:42:05 +02:00
auth_jwt_status.go Logs: Add package pkg/log/status to provide generic outcome constants 2025-10-21 14:42:05 +02:00
auth_jwt_test.go Cluster: Rename the "tenant" node role to "instance" for accuracy 2026-02-23 04:27:43 +01:00
auth_list.go Storage: Propagate insufficient-storage to CLI exit and index 507 #5613 2026-05-26 23:34:33 +00:00
auth_list_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
auth_remove.go CLI: Add cluster operations and management commands #98 2025-09-16 18:09:09 +02:00
auth_remove_test.go Tests: Refactor command tests 2024-12-09 15:11:39 +01:00
auth_reset.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
auth_reset_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
auth_show.go CLI: Add RunWithTestContext function for command tests #3168 2024-12-06 09:08:38 +01:00
auth_show_test.go Tests: Refactor command tests 2024-12-09 15:11:39 +01:00
auth_test.go Tests: Refactor command tests 2024-12-09 15:11:39 +01:00
backup.go Storage: Propagate insufficient-storage to CLI exit and index 507 #5613 2026-05-26 23:34:33 +00:00
cameras.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
cameras_test.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
cleanup.go CLI: Refactor "dry-run" and "yes" command flags to use helper functions 2025-10-09 15:41:35 +02:00
cli_args.go CLI: Reject flags placed after positional arguments #5604 2026-05-22 11:54:54 +00:00
cli_args_test.go FFmpeg: Use atomic.Pointer for thread-safe Exclude list access #5617 2026-05-26 15:57:59 +00:00
clients.go ACL: Generate CLI role & auth-provider help from role tables #5667 2026-06-16 11:57:44 +02:00
clients_add.go CLI: Reject flags placed after positional arguments #5604 2026-05-22 11:54:54 +00:00
clients_add_test.go Auth: Add alias for RoleNone and improve unit tests coverage #98 2025-09-18 17:10:39 +02:00
clients_flags_test.go Auth: Add cluster_admin role with admin-tier and federation helpers 2026-06-03 17:12:59 +02:00
clients_list.go CLI: Add RunWithTestContext function for command tests #3168 2024-12-06 09:08:38 +01:00
clients_list_test.go Backend: Upgrade github.com/olekukonko/tablewriter from v0.0.5 to v1.0.7 2025-06-24 12:48:38 +02:00
clients_mod.go CLI: Reject flags placed after positional arguments #5604 2026-05-22 11:54:54 +00:00
clients_mod_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
clients_remove.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
clients_remove_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
clients_reset.go CLI: Add cluster operations and management commands #98 2025-09-16 18:09:09 +02:00
clients_reset_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
clients_show.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
clients_show_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
cluster.go Security: Improve credential handling across the cluster tooling #98 2025-10-20 00:06:17 +02:00
cluster_exit_codes_test.go Cluster: Rename the "tenant" node role to "instance" for accuracy 2026-02-23 04:27:43 +01:00
cluster_health.go Config: Move Portal flag to ClientConfig struct 2025-10-15 23:32:54 +02:00
cluster_helpers.go Commands: Resolve low-risk golangci-lint findings 2026-05-06 07:50:37 +00:00
cluster_join_token.go Security: Improve credential handling across the cluster tooling #98 2025-10-20 00:06:17 +02:00
cluster_join_token_test.go Config: Gate node roles by edition and align tests/lint 2026-02-26 15:37:55 +01:00
cluster_nodes_list.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
cluster_nodes_mod.go Cluster: Add a per-instance node DisplayName field 2026-06-09 07:23:05 +00:00
cluster_nodes_mod_test.go Cluster: Add a per-instance node DisplayName field 2026-06-09 07:23:05 +00:00
cluster_nodes_remove.go Cluster: Improve API endpoint and CLI command logs 2025-10-21 16:51:24 +02:00
cluster_nodes_rotate.go Commands: Resolve low-risk golangci-lint findings 2026-05-06 07:50:37 +00:00
cluster_nodes_show.go Cluster: Improve API endpoint and CLI command logs 2025-10-21 16:51:24 +02:00
cluster_register.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
cluster_register_http_test.go Config: Centralize SQL driver identifiers in pkg/dsn #5588 2026-05-17 14:47:43 +00:00
cluster_summary.go Cluster: Add AppName, AppVersion and Theme request/response fields #98 2025-10-19 12:44:21 +02:00
cluster_test.go Cluster: Rename the "tenant" node role to "instance" for accuracy 2026-02-23 04:27:43 +01:00
cluster_theme_pull.go Config: Gate node roles by edition and align tests/lint 2026-02-26 15:37:55 +01:00
cluster_theme_pull_oauth_test.go CLI: Refactor cluster_helpers.go and cluster_theme_pull_oauth_test.go 2025-10-19 22:16:07 +02:00
cluster_theme_pull_unzip_test.go Config: Gate node roles by edition and align tests/lint 2026-02-26 15:37:55 +01:00
commands.go Links: Use canonical trailing-slash form for website URLs 2026-06-24 17:20:30 +02:00
commands_test.go Tests: Improve isolation and add fixes #5263 2026-07-12 05:47:21 +02:00
config.go Commands: Prevent config command tests from polluting the originals directory 2026-05-30 16:13:21 +00:00
connect.go CLI: Add RunWithTestContext function for command tests #3168 2024-12-06 09:08:38 +01:00
convert.go CLI: Standardize path validation and exit codes #5457 2026-02-23 23:00:05 +01:00
copy.go CLI: Standardize path validation and exit codes #5457 2026-02-23 23:00:05 +01:00
download.go Video: Name the matched exclude entry and apply it to transcode #5617 2026-05-30 21:50:55 +00:00
download_e2e_test.go Tests: Improve isolation and add fixes #5263 2026-07-12 05:47:21 +02:00
download_format_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
download_help_test.go CLI: Improve photoprism dl command with additional flags #5261 2025-10-12 19:41:08 +02:00
download_impl.go Video: Name the matched exclude entry and apply it to transcode #5617 2026-05-30 21:50:55 +00:00
download_impl_test.go Commands: Count file download failures & pluralize logs 2026-05-06 07:50:27 +00:00
download_method.go CLI: Add "photoprism dl --format-sort" flag and dl-method env variable 2025-09-26 15:51:50 +02:00
edition.go CLI: Add RunWithTestContext function for command tests #3168 2024-12-06 09:08:38 +01:00
edition_test.go Tests: Refactor command tests 2024-12-09 15:11:39 +01:00
faces.go Commands: Prevent config command tests from polluting the originals directory 2026-05-30 16:13:21 +00:00
faces_config.go Commands: Prevent config command tests from polluting the originals directory 2026-05-30 16:13:21 +00:00
faces_config_test.go Commands: Prevent config command tests from polluting the originals directory 2026-05-30 16:13:21 +00:00
find.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
find_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
flags.go CLI: Refactor authentication subcommand flags 2025-10-23 09:04:30 +02:00
import.go Storage: Propagate insufficient-storage to CLI exit and index 507 #5613 2026-05-26 23:34:33 +00:00
index.go Storage: Propagate insufficient-storage to CLI exit and index 507 #5613 2026-05-26 23:34:33 +00:00
index_test.go Commands: Remove unstable assertion from index_test.go 2023-10-10 14:39:04 +02:00
jwt_helpers.go Config: Move Portal flag to ClientConfig struct 2025-10-15 23:32:54 +02:00
lenses.go Metadata: Add Lens Make and Model updates via CLI & API #5644 #5656 2026-06-15 09:37:32 +02:00
lenses_test.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
mcp.go MCP: Gate stdio on DisableMCP and add scope regression test #5024 2026-05-10 11:54:34 +00:00
mcp_test.go FFmpeg: Use atomic.Pointer for thread-safe Exclude list access #5617 2026-05-26 15:57:59 +00:00
messages.go Commands: Count file download failures & pluralize logs 2026-05-06 07:50:27 +00:00
messages_test.go Commands: Count file download failures & pluralize logs 2026-05-06 07:50:27 +00:00
migrate.go CLI: Add RunWithTestContext function for command tests #3168 2024-12-06 09:08:38 +01:00
migrations.go Commands: Prevent config command tests from polluting the originals directory 2026-05-30 16:13:21 +00:00
moments.go CLI: Add RunWithTestContext function for command tests #3168 2024-12-06 09:08:38 +01:00
optimize.go AI: Add "photoprism vision" CLI subcommands #127 #1090 2025-04-11 02:11:53 +02:00
passwd.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
passwd_test.go Tests: Refactor command tests 2024-12-09 15:11:39 +01:00
path_args.go CLI: Standardize path validation and exit codes #5457 2026-02-23 23:00:05 +01:00
path_args_test.go CLI: Standardize path validation and exit codes #5457 2026-02-23 23:00:05 +01:00
places.go CLI: Add cluster operations and management commands #98 2025-09-16 18:09:09 +02:00
purge.go CLI: Standardize path validation and exit codes #5457 2026-02-23 23:00:05 +01:00
README.md Storage: Propagate insufficient-storage to CLI exit and index 507 #5613 2026-05-26 23:34:33 +00:00
report.go Security: Improve rate limit and HTTP proxy configuration #98 2022-10-12 14:57:33 +02:00
reset.go Auth: Extend user accounts with custom scope setting 2025-10-22 19:58:56 +02:00
restore.go Storage: Propagate insufficient-storage to CLI exit and index 507 #5613 2026-05-26 23:34:33 +00:00
show.go Auth: Add "photoprism show scopes" command to list all supported scopes 2025-10-23 09:25:08 +02:00
show_commands.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
show_commands_test.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
show_config.go Commands: Prevent config command tests from polluting the originals directory 2026-05-30 16:13:21 +00:00
show_config_options.go CLI: Flatten config options output when using the "--json" flag #5220 2025-09-21 16:52:56 +02:00
show_config_options_test.go Server: Add HTTP security hardening config options #5471 2026-03-04 10:12:57 +01:00
show_config_yaml.go CLI: Flatten config options output when using the "--json" flag #5220 2025-09-21 16:52:56 +02:00
show_config_yaml_test.go Config: Exclude inline non-flag fields from config-yaml report 2026-03-05 04:37:29 +01:00
show_file_formats.go CLI: Flatten config options output when using the "--json" flag #5220 2025-09-21 16:52:56 +02:00
show_json_test.go Config: Improve low storage detection #5613 2026-05-29 01:45:04 +02:00
show_metadata.go CLI: Flatten config options output when using the "--json" flag #5220 2025-09-21 16:52:56 +02:00
show_scopes.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
show_search_filters.go CLI: Flatten config options output when using the "--json" flag #5220 2025-09-21 16:52:56 +02:00
show_sources.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
show_test.go Config: Improve low storage detection #5613 2026-05-29 01:45:04 +02:00
show_thumb_sizes.go CLI: Flatten config options output when using the "--json" flag #5220 2025-09-21 16:52:56 +02:00
show_thumb_sizes_test.go Tests: Refactor command tests 2024-12-09 15:11:39 +01:00
show_video_sizes.go CLI: Flatten config options output when using the "--json" flag #5220 2025-09-21 16:52:56 +02:00
show_video_sizes_test.go Tests: Refactor command tests 2024-12-09 15:11:39 +01:00
start.go Server: Add HTTP security hardening config options #5471 2026-03-04 10:12:57 +01:00
status.go Config: Gate node roles by edition and align tests/lint 2026-02-26 15:37:55 +01:00
stop.go Commands: Prevent config command tests from polluting the originals directory 2026-05-30 16:13:21 +00:00
thumbs.go CLI: Standardize path validation and exit codes #5457 2026-02-23 23:00:05 +01:00
UPGRADE.md CLI: Document urfave/cli v2 to v3 upgrade analysis and decision 2026-04-25 10:31:38 +02:00
users.go ACL: Generate CLI role & auth-provider help from role tables #5667 2026-06-16 11:57:44 +02:00
users_add.go CLI: Reject flags placed after positional arguments #5604 2026-05-22 11:54:54 +00:00
users_add_test.go Tests: Refactor command tests 2024-12-09 15:11:39 +01:00
users_flags_test.go ACL: Generate CLI role & auth-provider help from role tables #5667 2026-06-16 11:57:44 +02:00
users_legacy.go CLI: Add RunWithTestContext function for command tests #3168 2024-12-06 09:08:38 +01:00
users_legacy_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
users_list.go CLI: Add RunWithTestContext function for command tests #3168 2024-12-06 09:08:38 +01:00
users_list_test.go CLI: Add RunWithTestContext function for command tests #3168 2024-12-06 09:08:38 +01:00
users_mod.go CLI: Reject flags placed after positional arguments #5604 2026-05-22 11:54:54 +00:00
users_mod_test.go OIDC: Surface account-collision reconcile hint and add --auth-issuer 2026-06-10 15:31:34 +00:00
users_remove.go CLI: Add cluster operations and management commands #98 2025-09-16 18:09:09 +02:00
users_remove_test.go Tests: Refactor command tests 2024-12-09 15:11:39 +01:00
users_reset.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
users_reset_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
users_show.go CLI: Add RunWithTestContext function for command tests #3168 2024-12-06 09:08:38 +01:00
users_show_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
users_test.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
version.go CLI: Add RunWithTestContext function for command tests #3168 2024-12-06 09:08:38 +01:00
version_test.go Tests: Refactor command tests 2024-12-09 15:11:39 +01:00
video_helpers.go Config: Gate node roles by edition and align tests/lint 2026-02-26 15:37:55 +01:00
video_helpers_test.go CLI: Adjust search query used in "photoprism video" subcommands 2025-12-23 15:17:55 +01:00
video_index.go CLI: Adjust search query used in "photoprism video" subcommands 2025-12-23 15:17:55 +01:00
video_info.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
video_ls.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
video_remux.go Video: Name the matched exclude entry and apply it to transcode #5617 2026-05-30 21:50:55 +00:00
video_remux_test.go Config: Improve low storage detection #5613 2026-05-29 01:45:04 +02:00
video_search.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
video_storage.go CLI: Add "photoprism video" subcommands 2025-12-21 17:36:57 +01:00
video_transcode.go Video: Name the matched exclude entry and apply it to transcode #5617 2026-05-30 21:50:55 +00:00
video_trim.go Commands: Improve video failure summaries 2026-05-06 08:01:33 +00:00
video_trim_test.go CLI: Improve "photoprism video" subcommands 2025-12-21 18:47:19 +01:00
videos.go CLI: Adjust search query used in "photoprism video" subcommands 2025-12-23 15:17:55 +01:00
vision.go CI: Apply Go linter recommendations to "internal/commands" package #5330 2025-11-22 17:55:26 +01:00
vision_list.go AI: Improve model configuration and documentation #5123 #5232 #5322 2025-11-24 14:41:13 +01:00
vision_reset.go CLI: Ensure vision reset command and worker use same default src #5233 2025-10-01 08:52:28 +02:00
vision_reset_test.go CLI: Add "photoprism vision reset" command to reset metadata #5233 2025-09-29 18:59:52 +02:00
vision_run.go CLI: Refactor "dry-run" and "yes" command flags to use helper functions 2025-10-09 15:41:35 +02:00
vision_save.go Backend: Run "make fmt-go" 2026-03-01 14:07:14 +01:00
vision_sources.go Faces: Change search to return all face markers, regardless of src #5167 2025-10-07 20:01:43 +02:00
vision_sources_test.go CLI: Refactor "photoprism vision" subcommands #5233 2025-09-29 21:39:36 +02: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.Command to the PhotoPrism slice in commands.go.
  • Provide a localized Before hook when a command needs configuration loading or authentication checks that differ from the defaults.
  • Reuse helpers from internal/config for option binding instead of reimplementing flag parsing. Field definitions belong in the shared flag modules so photoprism show config-options stays accurate.
  • Prefer storing command-specific implementations in <name>_impl.go files that can be imported in tests. Invoke the implementation from the Action function 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 to flags.go first and then consume it from each command instead of hand-coding variants.

Command Implementation Patterns

  • Construct filesystem paths with filepath.Join and rely on permission constants from pkg/fs (fs.ModeDir, fs.ModeFile, and friends) when writing to disk.
  • Follow the overwrite policy used by media helpers: require explicit confirmation (force flags) before replacing non-empty files. Where replacements are expected, open destinations with O_WRONLY|O_CREATE|O_TRUNC.
  • Use shared logging through event.Log rather than direct fmt printing. 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/safe or the specialized wrappers in internal/thumb/avatar to inherit timeout, size, and SSRF protection defaults.

Positional Arguments & Flag Order

urfave/cli v2 delegates flag parsing to the Go stdlib flag package, which stops parsing at the first non-flag token. For any subcommand that takes a positional argument (for example photoprism users mod USERNAME --role guest), flags placed after the positional are not parsed — they are returned as additional positionals and ctx.IsSet(...) reports false for each of them.

Without guarding for this, an action that conditionally applies values via if ctx.IsSet("role") { ... } will silently no-op while still logging success.

Mitigation Helper

Call commands.RejectTrailingFlags(ctx) near the top of every leaf action whose CLI shape is Action <positional> [--flags...]. The helper returns a clear flag "--name" must appear before positional arguments error when it detects a flag-like token in ctx.Args().Tail(), so the user is told to re-order rather than seeing a silent no-op. Pair it with commands.TrailingFlagToken(ctx) when the action needs to inspect the offending token before deciding what to do.

Helper behavior:

  • Single-dash - and the -- terminator are treated as positionals, not flags, so commands like photoprism backup - (write to stdout) and photoprism foo bar -- --literal keep working.
  • Unknown flags placed before the positional are not the helper's concern — urfave/cli raises its own "flag provided but not defined" error in that path.
  • For commands that use a flag-based identifier instead of a positional, still call the helper after the existence check so trailing flags surface as a usage error rather than a silent ignore.

The underlying parser limitation is tracked as a known issue for a broader fix; until a global arg-reorder pass lands, all new leaf actions that accept a positional MUST call RejectTrailingFlags before applying flag values.

Exit Codes

Wrap errors in cli.Exit(err, <code>) so the binary terminates with a non-zero status. A plain return err is logged but exits 0, which hides failures from CI and shell scripts. Pick the code from the table below; cluster, JWT, and Portal commands set the precedent.

Code Meaning Typical Use
0 Success, or user-initiated cancel normal completion; ErrCanceled from a long-running operation
1 Runtime/execution failure DB or I/O error, backup/restore failure, indexing failure, InitConfig error
2 Input validation or precondition failure missing/invalid flag, RejectTrailingFlags, read-only mode, malformed identifier
3 Resource not found user, node, theme, or other named entity does not exist
4 Authentication or authorization failure Portal returned 401 to the CLI
5 Forbidden / conflict Portal returned 403 or 409 to the CLI
6 Rate limited Portal returned 429 to the CLI

urfave/cli's default ExitErrHandler calls os.Exit(c.ExitCode()) only for values that implement cli.ExitCoder. A bare error flows up to main(), which logs it and returns normally — that is, exits 0. Use cli.Exit(...) whenever a non-zero status matters; reserve plain return err for helpers that propagate to a caller which itself wraps the result.

For long-running operations (indexing, importing, backup) that may be canceled by the user, return the underlying status.ErrCanceled (or a wrapper) without cli.Exit so the CLI exits 0, and use cli.Exit(err, 1) for status.ErrInsufficientStorage and other runtime failures so scripts and CI can detect them.

Configuration & Flags Integration

  • Define new options in internal/config/options.go with the appropriate struct tags (yaml, json, flag) so they propagate to YAML, CLI, and API layers consistently.
  • Surface CLI flags in internal/config/flags.go to keep environment variable mappings aligned. Commands should call conf.ApplyCliContext() once to hydrate configuration from parsed flags.
  • Respect precedence rules: defaults < CLI/environment < options.yml. Commands that generate configuration must set c.Options().OptionsYaml before persisting so changes appear in reports.
  • When emitting command catalogs or help output, reuse the catalog builders in internal/commands/catalog instead of crafting ad-hoc Markdown or JSON.

Testing Strategy

  • Place tests beside their sources (<name>_test.go) and group related assertions using t.Run("CaseName", ...) subtests. Subtest names should use PascalCase for readability.
  • Execute focused suites with go test ./internal/commands -run '<Name>' -count=1 during development. For broader coverage, make test-go exercises backend packages under SQLite.
  • Wrap CLI runs with RunWithTestContext(cmd, args) so urfave/cli exit codes do not call os.Exit during tests. If you only need to inspect the exit status, invoke cmd.Action(ctx) directly and assert cli.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, or config.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) or rnd.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=1 and go test ./internal/api -run 'Cluster' -count=1 ensure CLI and API stay in sync before release.

CLI & Test Utilities

  • Stub external binaries such as yt-dlp with lightweight shell scripts that honor --dump-single-json and --print requests. Support environment variables like YTDLP_ARGS_LOG, YTDLP_OUTPUT_FILE, and YTDLP_DUMMY_CONTENT to 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" and conf.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/acl such as acl.ParseRole, acl.ScopePermits, and acl.ScopeAttrPermits instead of duplicating logic inside commands.

Preflight Checklist

  • Formatting and Swagger: make fmt-go and make 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=1 and go 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.