Skip to content

Adopt the two-phase CI/CD pattern from ProjectTemplate (PR smoke builds + opt-in publish) #722

Description

@ptr727

Port the two-phase CI/CD pattern just implemented in ProjectTemplate (ptr727/ProjectTemplate#97 → #98, merged to develop) into PlexCleaner. This issue captures exactly what was done there so the work can continue here. PlexCleaner's workflow layout is essentially identical to ProjectTemplate's pre-change layout, so the port is mostly mechanical — minus PyPI (PlexCleaner has no PyPI library; its targets are the Docker image and the console executable 7-runtime build).

Goal

Decouple merging from publishing. PRs get fast, path-gated smoke builds; the full multi-arch build/publish moves to a weekly schedule + manual dispatch, with an opt-in switch for publish-on-merge.

Target architecture (mirror of ProjectTemplate #98)

  1. Thread ref / branch / smoke through every reusable task. Branch-derived config (main ⇒ Release/latest, else Debug/develop/prerelease) must key off inputs.branch, never github.ref_name — the weekly publisher builds both branches from one run. get-version-task.yml gains a ref input.
  2. smoke mode: Docker builds linux/amd64 only (skip QEMU); executable builds a representative runtime subset (e.g. linux-x64 + win-x64) instead of the full 7; nothing pushes.
  3. build-release-task.yml: add smoke, ref, branch, and per-target enable_* gates. smoke hard-disables every push (&& !inputs.smoke).
  4. test-pull-request.yml: add a dorny/paths-filter changes job (needs permissions: pull-requests: read), gate per-target smoke jobs on it, keep unit tests always-on, and extend the check-workflow-status aggregator to require the changes job succeeded (so a filter error can't merge a target-changing PR with its smoke skipped). Build-workflow files are intentionally excluded from filters.
  5. publish-release.yml: become the single publisher — push + weekly schedule + workflow_dispatch. A setup job computes the branch list (schedule/dispatch ⇒ both branches, push ⇒ the pushed branch) and a publish gate github.event_name != 'push' || vars.PUBLISH_ON_MERGE == 'true'. Build both branches via a matrix. Absorb publish-periodic-docker-release.yml (delete it). Use a global concurrency group (group: ${{ github.workflow }}, not ref-scoped) + cancel-in-progress: false.
  6. Consolidation: fold unit tests into the PR workflow and delete test-release-task.yml. Default PUBLISH_ON_MERGE unset ⇒ two-phase.

Refinements discovered during the ProjectTemplate Copilot review — bake these in from the start

These were review findings on #98; apply them directly so PlexCleaner doesn't repeat them:

  • Make branch a required input on every task (no ${{ github.ref_name }} default — it's a footgun that would mislabel the develop leg of the matrix).
  • Check out the PR head by SHA in the smoke job: ref: ${{ github.event.pull_request.head.sha || github.ref_name }} (works for fork PRs; head_ref does not).
  • Branch-suffix all upload-artifact names (executable-build-${branch}, per-runtime publish-${branch}-${runtime}, etc.) so both matrix legs coexist in one run without collisions. (PlexCleaner uploads the executable 7z, so this matters; the GitHub release downloads by artifact-id which is unaffected.)
  • Branch-scope the Docker registry cache (buildcache-${branch}, read both branches, write own) and gate cache-to on inputs.push (non-publishing builds never write cache or need write creds).
  • Gate the github-release job on && !inputs.smoke so smoke can never create a release.
  • Skip GitHub release creation when the version tag already exists (gh release view "$SemVer2") — weekly no-op weeks shouldn't churn/fail the release. (Same fix filed for NxWitness: Publisher: skip duplicate GitHub release on no-op weeks and pin release tag to the built commit SHA NxWitness#407.)
  • Pin the release target_commitish to NBGV GitCommitId (immutable, right branch) instead of github.sha (wrong on the develop leg) or a branch name (moving ref). Surface GitCommitId as a get-version output.
  • Docker Hub login: ProjectTemplate consciously keeps always-login (private repo, higher pull rate limits) and documents the trade-off (fork PRs can't run the Docker smoke). Decide per PlexCleaner's visibility — gating login on inputs.push is the alternative.
  • Linting stays editor/workspace + commit gate — no CI lint job. Don't claim actionlint runs in CI; lint workflows locally before pushing.
  • CodeGen cadence: if PlexCleaner has scheduled codegen, move it to daily (cheap under two-phase).

PlexCleaner-specific notes

  • Targets are Docker + executable only (no NuGet/PyPI) — drop those enable_*/jobs/filters.
  • PlexCleaner currently has a redundant merge-time Docker build (built non-pushed in the release path, pushed in the periodic workflow). The unified publisher removes that duplication.
  • Consider whether develop mutable-tag republishing on routine merges should stop (it does under two-phase by default).
  • Per-target modularity + the subsetting checklist should be documented in PlexCleaner's AGENTS.md, same as ProjectTemplate.

Reference

🤖 Filed by Claude Code; ready to continue the work directly in PlexCleaner.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions