Public Access
313 lines
10 KiB
Python
313 lines
10 KiB
Python
"""CLI entry point for devx — routes to subcommands.
|
|
|
|
Usage::
|
|
|
|
devx ci <command> [args] # CI/CD automation scripts
|
|
devx tools <command> [args] # Development tools
|
|
devx molecule <command> [args] # Molecule testing tools (optional)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
|
|
import click
|
|
|
|
from devx.i18n import _
|
|
|
|
|
|
def _run_module(module_path: str, args: list[str]) -> None:
|
|
"""Run a devx module's main() with the given args."""
|
|
import importlib
|
|
|
|
module = importlib.import_module(module_path)
|
|
# Set sys.argv so click sees the right arguments
|
|
old_argv = sys.argv
|
|
sys.argv = [module_path] + args
|
|
try:
|
|
main_fn = getattr(module, "main", None)
|
|
if main_fn is None:
|
|
raise click.ClickException(_("Module {mod} has no main() function", mod=module_path))
|
|
if callable(main_fn):
|
|
main_fn(args=args, standalone_mode=False) # type: ignore[arg-type]
|
|
else:
|
|
# It's a click command object
|
|
main_fn.main(args=args, prog_name=module_path, standalone_mode=False) # type: ignore[attr-defined]
|
|
except click.exceptions.Abort:
|
|
sys.exit(1)
|
|
except click.ClickException as e:
|
|
e.show()
|
|
sys.exit(e.exit_code)
|
|
finally:
|
|
sys.argv = old_argv
|
|
|
|
|
|
@click.group()
|
|
@click.version_option()
|
|
def cli() -> None:
|
|
"""devx — reusable development and CI/CD tools."""
|
|
|
|
|
|
@cli.group()
|
|
def ci() -> None:
|
|
"""CI/CD automation commands."""
|
|
|
|
|
|
@ci.command("auto-merge")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_auto_merge(args: tuple[str, ...]) -> None:
|
|
"""Auto-merge PR when all CI checks pass."""
|
|
_run_module("devx.ci.auto_merge", list(args))
|
|
|
|
|
|
@ci.command("check-translations")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_check_translations(args: tuple[str, ...]) -> None:
|
|
"""Check translation files for gaps and dead keys."""
|
|
_run_module("devx.ci.check_translations", list(args))
|
|
|
|
|
|
@ci.command("classify-changes")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_classify_changes(args: tuple[str, ...]) -> None:
|
|
"""Classify git changes as user-facing or workflow-only."""
|
|
_run_module("devx.ci.classify_changes", list(args))
|
|
|
|
|
|
@ci.command("detect-release-commit")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_detect_release_commit(args: tuple[str, ...]) -> None:
|
|
"""Detect whether the latest git commit is a release commit."""
|
|
_run_module("devx.ci.detect_release_commit", list(args))
|
|
|
|
|
|
@ci.command("discover-runners")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_discover_runners(args: tuple[str, ...]) -> None:
|
|
"""Discover available Gitea Actions runners."""
|
|
_run_module("devx.ci.discover_runners", list(args))
|
|
|
|
|
|
@ci.command("doc-coverage")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_doc_coverage(args: tuple[str, ...]) -> None:
|
|
"""Check documentation coverage for CLI commands and modules."""
|
|
_run_module("devx.ci.doc_coverage", list(args))
|
|
|
|
|
|
@ci.command("lint-docs")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_lint_docs(args: tuple[str, ...]) -> None:
|
|
"""Lint documentation files for structure, links, and quality."""
|
|
_run_module("devx.ci.lint_docs", list(args))
|
|
|
|
|
|
@ci.command("notify-failure")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_notify_failure(args: tuple[str, ...]) -> None:
|
|
"""Create a Gitea issue when a CI workflow fails."""
|
|
_run_module("devx.ci.notify_failure", list(args))
|
|
|
|
|
|
@ci.command("post-merge")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_post_merge(args: tuple[str, ...]) -> None:
|
|
"""Update Vikunja task after a merge to master."""
|
|
_run_module("devx.ci.post_merge", list(args))
|
|
|
|
|
|
@ci.command("pr-review")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_pr_review(args: tuple[str, ...]) -> None:
|
|
"""Run automated PR review."""
|
|
_run_module("devx.ci.pr_review", list(args))
|
|
|
|
|
|
@ci.command("publish")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_publish(args: tuple[str, ...]) -> None:
|
|
"""Build package, publish to registry, and create Gitea release."""
|
|
_run_module("devx.ci.publish", list(args))
|
|
|
|
|
|
@ci.command("push-badges")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_push_badges(args: tuple[str, ...]) -> None:
|
|
"""Generate badge SVG files and push to the badges branch."""
|
|
_run_module("devx.ci.push_badges", list(args))
|
|
|
|
|
|
@ci.command("release")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_release(args: tuple[str, ...]) -> None:
|
|
"""Automated release: calculate next version, update files, tag, push."""
|
|
_run_module("devx.ci.release", list(args))
|
|
|
|
|
|
@ci.command("sync-wiki")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_sync_wiki(args: tuple[str, ...]) -> None:
|
|
"""Sync documentation from docs/ to the Gitea wiki."""
|
|
_run_module("devx.ci.sync_wiki", list(args))
|
|
|
|
|
|
@ci.command("validate-commit-msg")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_validate_commit_msg(args: tuple[str, ...]) -> None:
|
|
"""Validate commit messages for conventional commit format."""
|
|
_run_module("devx.ci.validate_commit_msg", list(args))
|
|
|
|
|
|
@ci.command("distribute-files")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_distribute_files(args: tuple[str, ...]) -> None:
|
|
"""Distribute files across parallel runners (round-robin)."""
|
|
_run_module("devx.ci.distribute_files", list(args))
|
|
|
|
|
|
@ci.command("integration-guard")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_integration_guard(args: tuple[str, ...]) -> None:
|
|
"""Run pytest with cross-runner failure detection."""
|
|
_run_module("devx.ci.integration_guard", list(args))
|
|
|
|
|
|
@ci.command("cancel-superseded-runs")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_cancel_superseded_runs(args: tuple[str, ...]) -> None:
|
|
"""Cancel superseded CI runs for the same PR branch."""
|
|
_run_module("devx.ci.cancel_superseded_runs", list(args))
|
|
|
|
|
|
@ci.command("check-workflow-artifact-deps")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_check_workflow_artifact_deps(args: tuple[str, ...]) -> None:
|
|
"""Check that artifact download jobs depend on upload jobs."""
|
|
_run_module("devx.ci.check_workflow_artifact_deps", list(args))
|
|
|
|
|
|
@ci.command("check-workflow-tofu-init")
|
|
@click.argument("args", nargs=-1)
|
|
def ci_check_workflow_tofu_init(args: tuple[str, ...]) -> None:
|
|
"""Check that workflow jobs using tofu state have a tofu-init step."""
|
|
_run_module("devx.ci.check_workflow_tofu_init", list(args))
|
|
|
|
|
|
@cli.group()
|
|
def tools() -> None:
|
|
"""Development tool commands."""
|
|
|
|
|
|
@tools.command("check-test-speed")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_check_test_speed(args: tuple[str, ...]) -> None:
|
|
"""Run unit tests and enforce a maximum execution-time budget."""
|
|
_run_module("devx.tools.check_test_speed", list(args))
|
|
|
|
|
|
@tools.command("configure-repo")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_configure_repo(args: tuple[str, ...]) -> None:
|
|
"""Configure repository: branch protection + labels via Gitea API."""
|
|
_run_module("devx.tools.configure_repo", list(args))
|
|
|
|
|
|
@tools.command("generate-badges")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_generate_badges(args: tuple[str, ...]) -> None:
|
|
"""Generate self-contained SVG badge files from project metrics."""
|
|
_run_module("devx.tools.generate_badges", list(args))
|
|
|
|
|
|
@tools.command("generate-cliff-config")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_generate_cliff_config(args: tuple[str, ...]) -> None:
|
|
"""Generate a cliff.toml configuration file for the project."""
|
|
_run_module("devx.tools.generate_cliff_config", list(args))
|
|
|
|
|
|
@tools.command("install-checkmake")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_install_checkmake(args: tuple[str, ...]) -> None:
|
|
"""Install checkmake if not already present."""
|
|
_run_module("devx.tools.install_checkmake", list(args))
|
|
|
|
|
|
@tools.command("install-tools")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_install_tools(args: tuple[str, ...]) -> None:
|
|
"""Install CI/CD development tools (actionlint, git-cliff, tea, etc.)."""
|
|
_run_module("devx.tools.install_tools", list(args))
|
|
|
|
|
|
@tools.command("setup")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_setup(args: tuple[str, ...]) -> None:
|
|
"""Project setup: install Python deps and pre-commit hooks."""
|
|
_run_module("devx.tools.setup", list(args))
|
|
|
|
|
|
@tools.command("rebase")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_rebase(args: tuple[str, ...]) -> None:
|
|
"""Rebase current branch onto origin/master and force-push."""
|
|
_run_module("devx.tools.rebase", list(args))
|
|
|
|
|
|
@tools.command("pr-rebase")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_pr_rebase(args: tuple[str, ...]) -> None:
|
|
"""Rebase a PR's head branch onto master via Gitea API (server-side)."""
|
|
_run_module("devx.tools.pr_rebase", list(args))
|
|
|
|
|
|
@tools.command("check-docker-init")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_check_docker_init(args: tuple[str, ...]) -> None:
|
|
"""Check that Docker Compose services with healthchecks have init: true."""
|
|
_run_module("devx.tools.check_docker_init", list(args))
|
|
|
|
|
|
@tools.command("check-ansible-set-fact-to-json")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_check_ansible_set_fact_to_json(args: tuple[str, ...]) -> None:
|
|
"""Check that Ansible set_fact tasks don't misuse to_json."""
|
|
_run_module("devx.tools.check_ansible_set_fact_to_json", list(args))
|
|
|
|
|
|
@tools.command("check-alert-rules")
|
|
@click.argument("args", nargs=-1)
|
|
def tools_check_alert_rules(args: tuple[str, ...]) -> None:
|
|
"""Validate rendered Prometheus alert rules with promtool."""
|
|
_run_module("devx.tools.check_alert_rules", list(args))
|
|
|
|
|
|
@cli.group()
|
|
def molecule() -> None:
|
|
"""Molecule testing commands (requires devx[molecule])."""
|
|
|
|
|
|
@molecule.command("distribute")
|
|
@click.argument("args", nargs=-1)
|
|
def molecule_distribute(args: tuple[str, ...]) -> None:
|
|
"""Distribute molecule test pairs across parallel runners."""
|
|
_run_module("devx.molecule.distribute_molecule", list(args))
|
|
|
|
|
|
@molecule.command("discover-runners")
|
|
@click.argument("args", nargs=-1)
|
|
def molecule_discover_runners(args: tuple[str, ...]) -> None:
|
|
"""Discover available Gitea Actions runners for molecule tests."""
|
|
_run_module("devx.molecule.discover_runners", list(args))
|
|
|
|
|
|
@molecule.command("all")
|
|
@click.argument("args", nargs=-1)
|
|
def molecule_all(args: tuple[str, ...]) -> None:
|
|
"""Run all molecule scenarios on all supported OS platforms."""
|
|
_run_module("devx.molecule.molecule_all", list(args))
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
cli()
|