repomatic.cli.main module

The repomatic command-line group and its shared plumbing.

The commands live in the domain modules beside this one. The group imports none of them at startup: COMMANDS names each command by the import path of its command object, so a run imports this module and the one module that holds the command it invokes. The help screen is the exception, since it lists the summary of every command.

What stays here is what the domain modules share: the group, its help sections, the options more than one module declares, the base parameter type and the helpers command bodies call.

Caution

Nuitka compiles only the modules it sees imported, and an import path is a string. The [tool.nuitka] table of pyproject.toml includes the whole repomatic.cli package for that reason, so the binary ships every command module this table names.

repomatic.cli.main.COMMANDS: dict[str, LazySubcommand] = {'apply-labels': LazySubcommand(import_path='repomatic.cli.github.apply_labels_cmd', section=Section(GitHub issues & PRs, is_sorted=True), fallback_to_default_section=True), 'audit': LazySubcommand(import_path='repomatic.cli.deps.audit', section=Section(Linting & checks, is_sorted=True), fallback_to_default_section=True), 'broken-links': LazySubcommand(import_path='repomatic.cli.github.broken_links', section=Section(GitHub issues & PRs, is_sorted=True), fallback_to_default_section=True), 'bump-version': LazySubcommand(import_path='repomatic.cli.release.bump_version_cmd', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'cache': LazySubcommand(import_path='repomatic.cli.tools.cache', section=Section(Tools, is_sorted=True), fallback_to_default_section=True), 'cancel-runs': LazySubcommand(import_path='repomatic.cli.ci.cancel_runs', section=Section(CI & runners, is_sorted=True), fallback_to_default_section=True), 'changelog': LazySubcommand(import_path='repomatic.cli.release.changelog', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'ci-status': LazySubcommand(import_path='repomatic.cli.ci.ci_status', section=Section(CI & runners, is_sorted=True), fallback_to_default_section=True), 'cloudflare-pages': LazySubcommand(import_path='repomatic.cli.site.cloudflare_pages', section=Section(Site & docs, is_sorted=True), fallback_to_default_section=True), 'cloudflare-r2': LazySubcommand(import_path='repomatic.cli.site.cloudflare_r2', section=Section(Site & docs, is_sorted=True), fallback_to_default_section=True), 'create-release': LazySubcommand(import_path='repomatic.cli.release.create_release_cmd', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'fix-awesome-toc': LazySubcommand(import_path='repomatic.cli.repo.fix_awesome_toc_cmd', section=Section(Linting & checks, is_sorted=True), fallback_to_default_section=True), 'format-images': LazySubcommand(import_path='repomatic.cli.repo.format_images', section=Section(Linting & checks, is_sorted=True), fallback_to_default_section=True), 'git-tag': LazySubcommand(import_path='repomatic.cli.release.git_tag', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'init': LazySubcommand(import_path='repomatic.cli.repo.init', section=Section(Project setup, is_sorted=True), fallback_to_default_section=True), 'job-timings': LazySubcommand(import_path='repomatic.cli.ci.job_timings', section=Section(CI & runners, is_sorted=True), fallback_to_default_section=True), 'lint-changelog': LazySubcommand(import_path='repomatic.cli.release.lint_changelog', section=Section(Linting & checks, is_sorted=True), fallback_to_default_section=True), 'lint-coverage': LazySubcommand(import_path='repomatic.cli.ci.lint_coverage', section=Section(Linting & checks, is_sorted=True), fallback_to_default_section=True), 'lint-deps': LazySubcommand(import_path='repomatic.cli.deps.lint_deps', section=Section(Linting & checks, is_sorted=True), fallback_to_default_section=True), 'lint-repo': LazySubcommand(import_path='repomatic.cli.repo.lint_repo', section=Section(Linting & checks, is_sorted=True), fallback_to_default_section=True), 'list-skills': LazySubcommand(import_path='repomatic.cli.repo.list_skills', section=Section(Project setup, is_sorted=True), fallback_to_default_section=True), 'lock-threads': LazySubcommand(import_path='repomatic.cli.github.lock_threads', section=Section(GitHub issues & PRs, is_sorted=True), fallback_to_default_section=True), 'pack-attestation': LazySubcommand(import_path='repomatic.cli.release.pack_attestation_cmd', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'pack-binaries': LazySubcommand(import_path='repomatic.cli.release.pack_binaries', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'pack-manpages': LazySubcommand(import_path='repomatic.cli.release.pack_manpages_cmd', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'pack-plugin': LazySubcommand(import_path='repomatic.cli.release.pack_plugin_cmd', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'pr-body': LazySubcommand(import_path='repomatic.cli.github.pr_body', section=Section(GitHub issues & PRs, is_sorted=True), fallback_to_default_section=True), 'pr-sync': LazySubcommand(import_path='repomatic.cli.github.pr_sync', section=Section(GitHub issues & PRs, is_sorted=True), fallback_to_default_section=True), 'prepare-release': LazySubcommand(import_path='repomatic.cli.release.prepare_release', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'prune-caches': LazySubcommand(import_path='repomatic.cli.ci.prune_caches_cmd', section=Section(CI & runners, is_sorted=True), fallback_to_default_section=True), 'run': LazySubcommand(import_path='repomatic.cli.tools.run', section=Section(Tools, is_sorted=True), fallback_to_default_section=True), 'sample-metrics': LazySubcommand(import_path='repomatic.cli.repo.sample_metrics', section=Section(Forge sampling, is_sorted=True), fallback_to_default_section=True), 'scan-virustotal': LazySubcommand(import_path='repomatic.cli.release.scan_virustotal', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'setup-guide': LazySubcommand(import_path='repomatic.cli.github.setup_guide', section=Section(GitHub issues & PRs, is_sorted=True), fallback_to_default_section=True), 'show-config': LazySubcommand(import_path='repomatic.cli.repo.show_config', section=Section(Project setup, is_sorted=True), fallback_to_default_section=True), 'show-metadata': LazySubcommand(import_path='repomatic.cli.repo.show_metadata', section=Section(Project setup, is_sorted=True), fallback_to_default_section=True), 'show-test-matrix': LazySubcommand(import_path='repomatic.cli.ci.show_test_matrix', section=Section(Project setup, is_sorted=True), fallback_to_default_section=True), 'sponsor-label': LazySubcommand(import_path='repomatic.cli.github.sponsor_label', section=Section(GitHub issues & PRs, is_sorted=True), fallback_to_default_section=True), 'squashed-release': LazySubcommand(import_path='repomatic.cli.release.squashed_release', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'sync-action-pins': LazySubcommand(import_path='repomatic.cli.deps.sync_action_pins', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-binaries': LazySubcommand(import_path='repomatic.cli.release.sync_binaries', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-bumpversion': LazySubcommand(import_path='repomatic.cli.release.sync_bumpversion', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-dep-sources': LazySubcommand(import_path='repomatic.cli.deps.sync_dep_sources', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-deps': LazySubcommand(import_path='repomatic.cli.deps.sync_deps', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-dev-release': LazySubcommand(import_path='repomatic.cli.release.sync_dev_release_cmd', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-github-releases': LazySubcommand(import_path='repomatic.cli.release.sync_github_releases_cmd', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-gitignore': LazySubcommand(import_path='repomatic.cli.repo.sync_gitignore', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-labels': LazySubcommand(import_path='repomatic.cli.github.sync_labels', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-mailmap': LazySubcommand(import_path='repomatic.cli.repo.sync_mailmap', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-runner-images': LazySubcommand(import_path='repomatic.cli.ci.sync_runner_images', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-tool-versions': LazySubcommand(import_path='repomatic.cli.deps.sync_tool_versions', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-uv-lock': LazySubcommand(import_path='repomatic.cli.deps.sync_uv_lock', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'sync-workflow-pins': LazySubcommand(import_path='repomatic.cli.deps.sync_workflow_pins', section=Section(Sync, is_sorted=True), fallback_to_default_section=True), 'unsubscribe-threads': LazySubcommand(import_path='repomatic.cli.github.unsubscribe_threads_cmd', section=Section(GitHub issues & PRs, is_sorted=True), fallback_to_default_section=True), 'update-checksums': LazySubcommand(import_path='repomatic.cli.tools.update_checksums', section=Section(Tools, is_sorted=True), fallback_to_default_section=True), 'update-dep-graph': LazySubcommand(import_path='repomatic.cli.deps.update_dep_graph', section=Section(Project setup, is_sorted=True), fallback_to_default_section=True), 'update-docs': LazySubcommand(import_path='repomatic.cli.site.update_docs_cmd', section=Section(Site & docs, is_sorted=True), fallback_to_default_section=True), 'verify-binary': LazySubcommand(import_path='repomatic.cli.release.verify_binary', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True), 'wait-pypi': LazySubcommand(import_path='repomatic.cli.release.wait_pypi', section=Section(Release & versioning, is_sorted=True), fallback_to_default_section=True)}

Every repomatic command, keyed by name, with the import path of its object.

Grouped by help section, in the order the help screen lists the sections: the group registers each section the first time an entry here names it.

repomatic.cli.main.subcommand(*, params=(), **kwargs)[source]

Declare a repomatic command, carrying only the options it declares.

The command decorator of click-extra adds the options of a root command (--config, --verbosity, --version and the rest) to every command it builds. A command of the group gets those from repomatic instead.

Parameters:
  • params (Iterable[Parameter]) – Options built ahead of the command, like a --sort-by option. Click extends the list it gets, so each call copies it.

  • kwargs (Any) – Passed on to the command decorator of click-extra.

Return type:

Callable[[Callable[..., Any]], Command]

repomatic.cli.main.exit_if_disabled(ctx, key)[source]

Exit successfully when a [tool.repomatic] feature flag is off.

The shared guard of every sync command: a disabled feature is a normal, configured state, so the command logs the flag and exits 0 instead of failing the workflow that invoked it.

Reads the flag through is_enabled(), so a call site names the key once, as the user writes it.

Parameters:
  • ctx (Context) – The Click context to read the configuration from and exit through.

  • key (str) – The dotted [tool.repomatic] key, like labels.sync.

Raises:

ValueError – When key names no setting.

Return type:

None

repomatic.cli.main.log_output_target(subject, output)[source]

Log where a command is about to write subject.

Every command that honors an --output path narrates the destination the same way, distinguishing the stdout case (-) so the log names the stream instead of a literal dash.

Parameters:
  • subject (str) – What is being written, as a noun phrase ("metadata", "PR body").

  • output (Path) – The resolved --output path.

Return type:

None

repomatic.cli.main.write_output(subject, content, output)[source]

Write content to an --output path, then close the file.

Logs the destination first, through log_output_target(), then hands the write to write_text_output(), the one function that owns how an --output stream is opened and closed.

Parameters:
  • subject (str) – What is being written, as a noun phrase, for the log line.

  • content (str) – The text to write. A trailing newline is added.

  • output (Path) – The destination path, or - for stdout.

Return type:

None

repomatic.cli.main.output_option(help_text, *, default=None)[source]

-o/--output: the file a command writes, or - for stdout.

Parameters:
  • help_text (str) – What the command writes there.

  • default (str | None) – The destination when the option is absent. None lets the command decide, from its configuration or by writing nothing.

Return type:

Callable[[FC], FC]

repomatic.cli.main.report_options(help_text, *, default=None)[source]

--output with the --output-format it is written in.

Parameters:
  • help_text (str) – What the command writes to --output.

  • default (str | None) – The --output destination when the option is absent.

Return type:

Callable[[FC], FC]

repomatic.cli.main.dry_run_option(*, default)[source]

--dry-run/--live.

Parameters:

default (bool) – True for a command that changes nothing unless asked to, False for one that CI runs to make the change.

Return type:

Callable[[FC], FC]

repomatic.cli.main.resolve_repo(value, ctx=None)[source]

Return value, else the repository of the current checkout.

Parameters:
  • value (str | None) – The --repo value, which Click reads from $GITHUB_REPOSITORY when the option is absent.

  • ctx (Context | None) – The context the usage error names.

Raises:

click.UsageError – Neither --repo, $GITHUB_REPOSITORY nor the checkout names a repository.

Return type:

str

repomatic.cli.main.repo_option(help_text='Repository in owner/repo format.', *, detect=True, deferred=False)[source]

--repo, read from $GITHUB_REPOSITORY when absent.

Parameters:
  • help_text (str) – What the command uses the repository for.

  • detect (bool) – Also fall back to the repository of the current checkout, and refuse to run when nothing names one. Off for a command that also works without a repository.

  • deferred (bool) – Leave that fallback to the command body, which calls resolve_repo() after its exit_if_disabled() guard. A parameter callback runs before the body, so a disabled sync would otherwise refuse to run without a repository instead of exiting 0.

Return type:

Callable[[FC], FC]

class repomatic.cli.main.ProjectChoice[source]

Bases: LazyChoice

A Choice whose accepted values are the project’s, read at render time.

A Choice declared at import can only carry what every repository has in common, which for most of these options is nothing: the dependency groups, the workflow files and the matrix axes are all the project’s own. Resolving them when Click renders the help (or a completion list) is what lets the option name them, and keeps the lookup out of every other command’s startup.

Two rules make that safe to do mid-render:

  • Nothing may fail or speak. A traceback replaces the text the reader asked for, and a warning prepends noise to it, so resolve runs with logging off and any exception degrades to no values at all.

  • No values means “cannot tell”, not “nothing is valid”. An empty Choice refuses every value including the option’s own default, so a project this type cannot read would take the whole command down with it. The type accepts anything instead, and renders the plain metavar its name gives rather than an empty [].

A subclass that knows a floor its project cannot go below declares it as fallback, and never reaches the pass-through above.

Every access resolves again, on purpose: a type instance lives as long as its command, which outlives the working directory a run started in.

Keep the load callable, which is called on each read of choices.

Parameters:
  • load – returns the accepted values. Called on each access of choices, so a cheap loader, or a cached one, is expected.

  • case_sensitive – as for click.Choice.

fallback: tuple[str, ...] = ()

Values to offer when resolve finds none. Empty means accept anything.

resolve()[source]

The values this type accepts, read from the project.

Return type:

tuple[str, ...]

convert(value, param, ctx)[source]

For a given value from the parser, normalize it and find its matching normalized value in the list of choices. Then return the matched “original” choice.

get_metavar(param, ctx)[source]

Returns the metavar default for this param if it provides one.

Return type:

str | None