ci(test): consolidate backend coverage job (#1830) #1
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Self-host image releases (#980). Cutting a `selfhost-v<semver>` tag builds the multi-arch image, pushes it | |
| # to GHCR with version + latest + sha tags (with provenance + SBOM), and opens a GitHub Release. | |
| # | |
| # git tag selfhost-v0.1.0 && git push origin selfhost-v0.1.0 | |
| # | |
| # Pull: docker pull ghcr.io/<owner>/gittensory-selfhost:0.1.0 | |
| name: release-selfhost | |
| on: | |
| push: | |
| tags: | |
| - "selfhost-v*" | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version to publish (e.g. 0.1.0)" | |
| required: true | |
| permissions: | |
| contents: write # create the GitHub Release | |
| packages: write # push to GHCR | |
| concurrency: | |
| group: release-selfhost-${{ github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 40 | |
| # Environment gate — requires reviewer approval before a release runs (configure under repo Settings > Environments). | |
| environment: release | |
| env: | |
| SENTRY_ORG: jsonbored | |
| SENTRY_PROJECT: gittensory | |
| SENTRY_CLI_PACKAGE: "@sentry/cli@3.6.0" | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Verify release commit is on main | |
| env: | |
| RELEASE_SHA: ${{ github.sha }} | |
| run: | | |
| git fetch --no-tags origin main | |
| if ! git merge-base --is-ancestor "$RELEASE_SHA" origin/main; then | |
| echo "::error::Self-host releases must be cut from a commit reachable from main." | |
| exit 1 | |
| fi | |
| - name: Resolve version | |
| id: version | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| INPUT_VERSION: ${{ github.event.inputs.version }} | |
| REF_NAME: ${{ github.ref_name }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| VERSION="$INPUT_VERSION" | |
| else | |
| case "$REF_NAME" in | |
| selfhost-v*) VERSION="${REF_NAME#selfhost-v}" ;; | |
| *) echo "expected a selfhost-v<semver> tag, got $REF_NAME" >&2; exit 1 ;; | |
| esac | |
| fi | |
| if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "expected semver version X.Y.Z, got $VERSION" >&2 | |
| exit 1 | |
| fi | |
| echo "v=${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "release=gittensory-selfhost@${VERSION}" >> "$GITHUB_OUTPUT" | |
| # Release jobs receive publishing/Sentry credentials, so avoid shared dependency caches here. | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: "24.18.0" | |
| - name: Install deps | |
| run: npm ci --ignore-scripts | |
| - name: Build self-host bundle for release | |
| run: node scripts/build-selfhost.mjs --all | |
| - name: Validate release source map | |
| run: node scripts/validate-selfhost-sourcemap.mjs | |
| - name: Detect Sentry release token | |
| id: sentry | |
| env: | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| run: | | |
| if [ -n "$SENTRY_AUTH_TOKEN" ]; then | |
| echo "enabled=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "enabled=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Require Sentry token for official release | |
| if: github.repository == 'JSONbored/gittensory' && steps.sentry.outputs.enabled != 'true' | |
| run: | | |
| echo "::error::Configure SENTRY_AUTH_TOKEN in the release environment before publishing official self-host images." | |
| exit 1 | |
| - name: Upload Sentry source maps | |
| if: steps.sentry.outputs.enabled == 'true' | |
| env: | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_ORG: ${{ vars.SENTRY_ORG || 'jsonbored' }} | |
| SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT || 'gittensory' }} | |
| SENTRY_URL: ${{ vars.SENTRY_URL }} | |
| SENTRY_RELEASE: ${{ steps.version.outputs.release }} | |
| SENTRY_REPOSITORY: ${{ github.repository }} | |
| SENTRY_COMMIT_SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| test -n "$SENTRY_AUTH_TOKEN" | |
| test -n "$SENTRY_ORG" | |
| test -n "$SENTRY_PROJECT" | |
| if [ -z "${SENTRY_URL:-}" ]; then unset SENTRY_URL; fi | |
| npx -y "$SENTRY_CLI_PACKAGE" releases new "$SENTRY_RELEASE" | |
| npx -y "$SENTRY_CLI_PACKAGE" releases set-commits "$SENTRY_RELEASE" --commit "$SENTRY_REPOSITORY@$SENTRY_COMMIT_SHA" --ignore-missing | |
| npx -y "$SENTRY_CLI_PACKAGE" sourcemaps inject dist | |
| node scripts/validate-selfhost-sourcemap.mjs | |
| npx -y "$SENTRY_CLI_PACKAGE" sourcemaps upload --release="$SENTRY_RELEASE" --validate --wait --strict dist | |
| - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 | |
| - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Image metadata | |
| id: meta | |
| uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/gittensory-selfhost | |
| tags: | | |
| type=raw,value=${{ steps.version.outputs.v }} | |
| type=raw,value=latest | |
| type=sha,format=short | |
| labels: | | |
| org.opencontainers.image.title=gittensory-selfhost | |
| org.opencontainers.image.description=Self-hostable Gittensory review engine | |
| org.opencontainers.image.version=${{ steps.version.outputs.v }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| - name: Build + push (linux/amd64 + linux/arm64) | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: . | |
| target: runtime-prebuilt | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| build-args: | | |
| GITTENSORY_VERSION=${{ steps.version.outputs.release }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| provenance: true | |
| sbom: true | |
| - name: Finalize Sentry release | |
| if: steps.sentry.outputs.enabled == 'true' | |
| env: | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_ORG: ${{ vars.SENTRY_ORG || 'jsonbored' }} | |
| SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT || 'gittensory' }} | |
| SENTRY_URL: ${{ vars.SENTRY_URL }} | |
| SENTRY_RELEASE: ${{ steps.version.outputs.release }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${SENTRY_URL:-}" ]; then unset SENTRY_URL; fi | |
| npx -y "$SENTRY_CLI_PACKAGE" releases finalize "$SENTRY_RELEASE" | |
| - name: Validate Sentry release | |
| env: | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_ORG: ${{ vars.SENTRY_ORG || 'jsonbored' }} | |
| SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT || 'gittensory' }} | |
| SENTRY_URL: ${{ vars.SENTRY_URL }} | |
| SENTRY_RELEASE: ${{ steps.version.outputs.release }} | |
| SENTRY_REPOSITORY: ${{ github.repository }} | |
| SENTRY_COMMIT_SHA: ${{ github.sha }} | |
| SENTRY_REQUIRE_COMMITS: "true" | |
| SENTRY_REQUIRE_DEPLOY: "false" | |
| SENTRY_REQUIRE_FINALIZED: "true" | |
| run: node review-enrichment/scripts/validate-sentry-release.mjs | |
| - name: GitHub Release | |
| if: github.event_name == 'push' | |
| uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 | |
| with: | |
| generate_release_notes: true | |
| body: | | |
| Self-host container image: | |
| ```bash | |
| docker pull ghcr.io/${{ github.repository_owner }}/gittensory-selfhost:${{ steps.version.outputs.v }} | |
| ``` | |
| Multi-arch (linux/amd64 + linux/arm64). See https://gittensory.aethereal.dev/docs/maintainer-self-hosting for setup. | |
| Includes the Claude Code / Codex subscription CLIs by default; credentials stay runtime-only. | |
| Sentry release id baked into the image: `${{ steps.version.outputs.release }}`. |