Skip to content

Release Candidate Verification & Quality Gates

Every release of UltimaScraper passes through strict automated gates before publishing container images or distribution wheels.


Release Check Pipeline

The primary release validation script is located at scripts/release/check.sh.

# Run local release verification suite
./scripts/release/check.sh

# Include isolated Docker acceptance test
./scripts/release/check.sh --docker

Automated Stages

  1. Backend Test Suite:
  2. Executes pytest with coverage thresholds across all core modules, write queue handlers, and job execution engines.
  3. WebUI Build & Test:
  4. npm ci clean package installation.
  5. npm audit --omit=dev --audit-level=high security vulnerability scanning.
  6. npm run lint ESLint code style and hygiene validation.
  7. npm run typecheck:tests TypeScript strict type checking.
  8. npm run test:unit Vitest component and logic tests.
  9. npm run build:production Vite static production asset bundle generation.
  10. npm run test:smoke Browser smoke verification.
  11. Documentation Strictness Gate:
  12. zensical build --strict: Builds the entire documentation site with strict error enforcement. The build fails if any dead links, syntax anomalies, or missing pages are detected.
  13. Distribution Artifacts:
  14. Builds distribution wheels and packages for release staging.
  15. Isolated Docker Acceptance (Optional --docker):
  16. Spins up ephemeral test containers, initializes a temporary PostgreSQL and Redis instance, exercises the setup wizard, verifies API health, and tears down cleanly.

Tagging & Image Publishing

  1. Version Alignment: Ensure pyproject.toml and package.json reflect the target version (e.g. 0.1.0).
  2. Git Tagging: Create and push an annotated git tag matching the version:
    git tag -a v0.1.0 -m "Release v0.1.0"
    git push origin v0.1.0
    
  3. Automated CI Build: The GitHub Actions release workflow (.github/workflows/release.yml) builds multi-architecture images for both linux/amd64 and linux/arm64 and publishes them to ghcr.io/ultimahoarder/ultimascraper/{ultimascraper,backend,worker,webui}, the images Docker Compose and the setup wizard use.
  4. WebUI package: The GitHub Actions workflow .github/workflows/webui-package.yml publishes ultima-scraper-webui-server to PyPI, which WebUIs installed with uv tool install update from. See Updating the WebUI.

Image tags

Trigger Tags pushed Notes
Tag vX.Y.Z (must match pyproject.toml) X.Y.Z, X.Y, latest, sha-<commit> A release version is never overwritten; the run fails if X.Y.Z already exists.
Push to main (or a manual run) X.Y.Z-edge.<commit>, edge, sha-<commit> A prerelease of the coming version. It never writes X.Y.Z or latest.

Pin a release (0.1.0) for real deployments. Use edge to try main on a test host, for example with Upgrade under Backends → Deployments.

The workflow runs on GitHub's own runners and pushes with the run's own token, so each ghcr package must give the UltimaScraper repository write access (package settings → Manage Actions access). It needs no secrets.

The WebUI package workflow publishes through PyPI's trusted publishing: the ultima-scraper-webui-server project on PyPI trusts the UltimaHoarder/UltimaScraper repository's webui-package.yml workflow in the pypi environment, so it needs no token.

origin fetches from GitHub and pushes to both GitHub and the Forgejo backup mirror, so git push origin (or the push button in VS Code) updates both. Forgejo runs no workflows.

Build cache

The workflow keeps BuildKit's cache in ghcr.io/ultimahoarder/ultimascraper/buildcache, one tag per image. Every build reads it and every push writes it back, so the next run reuses each unchanged layer.

The Dockerfiles are ordered for it:

  • the system packages and the third-party dependencies are rebuilt only when the base image, docker/requirements.lock or docker/webui-requirements.lock change;
  • a build's own code (the first-party wheels, main.py, the WebUI server) is pure Python, installed once on the build host's own platform and copied into every image as a small layer, so arm64 does not build it under QEMU;
  • version and revision build arguments are declared after the heavy steps, because a build argument used by a RUN step makes that step miss the cache whenever its value changes.

So a typical main build redoes only the first-party layers. The first run, or a run after a lock file changes, builds everything.

Images are pushed one at a time: the full, backend and worker images share most layers, so each later image reuses what the first one uploaded.