diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e176a2e..470151b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,3 +71,44 @@ jobs: files: coverage.xml fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} + + deploy-smoke: + # Catches breakages that the Python-only matrix can't see — e.g. + # PR #6 round 1 shipped a Dockerfile whose runtime stage left the + # venv installed in editable mode pointing at a non-existent /src, + # so `import pypi_winnow_downloads` failed at startup. Lint, type + # checks, and pytest were all green; the bug only surfaced when QA + # ran the container by hand. This job exercises the deploy/ + # examples that the docs in deploy/README.md tell users to copy. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Build Docker image + run: docker build -f deploy/docker/Dockerfile -t pypi-winnow-downloads:smoke . + + - name: Verify entrypoint resolves (winnow-collect --help) + # Override the Dockerfile's ENTRYPOINT so we can pass --help + # without also passing the hardcoded --config flag (which would + # require a config file the container doesn't ship). Exit 0 from + # --help is the smoke signal that the venv links resolve and + # `import pypi_winnow_downloads` works. + run: docker run --rm --entrypoint /opt/venv/bin/winnow-collect pypi-winnow-downloads:smoke --help + + - name: Validate Compose example syntax + env: + # compose.yml.example uses ${BADGE_HOST:?…} so `compose config` + # errors without it. Any non-empty placeholder works. + BADGE_HOST: badges.example.com + run: docker compose -f deploy/docker/compose.yml.example config + + - name: Validate Caddyfile example syntax + # Mount the deploy/caddy/ directory into the official caddy:2 + # image and run `caddy validate` against the example. Catches + # syntax-level breakage and confirms the Caddyfile parses with + # the version users will most likely run. + run: | + docker run --rm \ + -v "${GITHUB_WORKSPACE}/deploy/caddy:/etc/caddy:ro" \ + caddy:2 \ + caddy validate --config /etc/caddy/Caddyfile.example --adapter caddyfile diff --git a/CHANGELOG.md b/CHANGELOG.md index f52b1aa..498d622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **`deploy-smoke` CI job** in `.github/workflows/ci.yml` exercises the four `deploy/` example artifacts that the Python-only matrix can't reach: builds the multi-stage Dockerfile, runs the container with overridden entrypoint to assert `winnow-collect --help` exits 0 (the bug class that took down PR #6 round 1 — venv installed in editable mode pointing at a non-existent `/src` so `import pypi_winnow_downloads` failed at startup), validates `deploy/docker/compose.yml.example` with `docker compose config` (`BADGE_HOST` substitution), and validates `deploy/caddy/Caddyfile.example` via `caddy validate` inside the official `caddy:2` image. Catches Dockerfile / compose / Caddyfile breakage before users hit it. Skips `systemd-analyze` on the timer because the referenced `.service` declares a binary at `/usr/local/bin/winnow-collect` that doesn't exist on a fresh CI runner. Closes #7. - **`.github/PULL_REQUEST_TEMPLATE.md`** auto-fills new human-authored PRs with Summary, Test plan, and CHANGELOG sections matching this repo's CI commands (`uv run pytest --cov`, `uv run ruff check src/ tests/`, `uv run ruff format --check src/ tests/`, `uv run mypy src/pypi_winnow_downloads/`). Dependabot bypasses PR templates by design and is handled separately by the new auto-CHANGELOG workflow. - **`.github/workflows/dependabot-changelog.yml`** auto-appends a `## [Unreleased]` → `### Changed` entry to Dependabot-authored PRs so they satisfy the per-PR CHANGELOG rule without manual intervention. Runs on `pull_request_target`, filters to `dependabot[bot]`, mints a GitHub App installation token via `actions/create-github-app-token`, fetches metadata via `dependabot/fetch-metadata@v3.1.0` (the v3 line fixed empty `prevVersion`/`newVersion` on grouped PRs), and pushes the CHANGELOG commit under the `cmeans-claude-dev[bot]` identity. The App-token push is the load-bearing piece: `secrets.GITHUB_TOKEN`-authored pushes do NOT re-trigger required `pull_request` checks (lint, typecheck, test) under GitHub's anti-loop policy, which would leave Dependabot PRs un-mergeable under the `main-protection` ruleset's required-status-checks rule. Loop guard skips when the last commit is already by the bot; idempotency guard skips when the PR number is already referenced in `CHANGELOG.md`. Operator must configure two repo secrets (`BOT_APP_ID`, `BOT_APP_PRIVATE_KEY`) before the workflow can run. Validated end-to-end on `cmeans/mcp-synology` (PRs #57 + #58 + #60 + #61, the latter being live verification on a real grouped Dependabot bump). Cross-repo playbook lives in awareness `dependabot-pr-hygiene-playbook`.