feat(selfhost): gate priority label on linked-issue label propagation… #2
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
| # Orb image releases (#980). Cutting an `orb-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 orb-v0.1.0 && git push origin orb-v0.1.0 | |
| # | |
| # Pull: docker pull ghcr.io/<owner>/gittensory-selfhost:orb-v0.1.0 | |
| # | |
| # Prerelease tags (#1937): orb-v0.1.0-rc.1 / orb-v0.1.0-beta.1 run the identical pipeline but never move | |
| # `latest` and are marked prerelease on the GitHub Release -- for beta-testing an image before it becomes | |
| # the stable/latest recommendation. | |
| # | |
| # git tag orb-v0.1.0-rc.1 && git push origin orb-v0.1.0-rc.1 | |
| name: release-orb | |
| on: | |
| push: | |
| tags: | |
| - "orb-v*" | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version to publish (e.g. 0.1.0, or a prerelease 0.1.0-rc.1 / 0.1.0-beta.1)" | |
| required: true | |
| permissions: | |
| contents: write # create the GitHub Release | |
| packages: write # push to GHCR | |
| concurrency: | |
| group: release-orb-${{ 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 | |
| orb-v*) VERSION="${REF_NAME#orb-v}" ;; | |
| *) echo "expected an orb-v<semver> tag, got $REF_NAME" >&2; exit 1 ;; | |
| esac | |
| fi | |
| # #1937: a stable X.Y.Z tag is the only kind that ever moved `latest` or an unmarked GitHub | |
| # Release; a prerelease tag (X.Y.Z-rc.N / X.Y.Z-beta.N) publishes the SAME image/provenance/SBOM/ | |
| # Sentry pipeline below, just flagged as prerelease and never pushed under `latest` (see the | |
| # "Resolve image tags" and "GitHub Release" steps). | |
| if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-(rc|beta)\.[0-9]+)?$'; then | |
| echo "expected semver version X.Y.Z, or a prerelease X.Y.Z-rc.N / X.Y.Z-beta.N, got $VERSION" >&2 | |
| exit 1 | |
| fi | |
| PRERELEASE=false | |
| if printf '%s' "$VERSION" | grep -Eq -- '-(rc|beta)\.[0-9]+$'; then | |
| PRERELEASE=true | |
| fi | |
| { | |
| echo "v=${VERSION}" | |
| echo "tag=orb-v${VERSION}" | |
| echo "release=gittensory-orb@${VERSION}" | |
| echo "prerelease=${PRERELEASE}" | |
| } >> "$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 Orb 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 }} | |
| # #1937: `latest` must never move to a prerelease build -- an operator who blindly pulls `latest` | |
| # for a trial should always land on the newest STABLE image, not an in-flight rc/beta. | |
| - name: Resolve image tags | |
| id: tags | |
| env: | |
| PRERELEASE: ${{ steps.version.outputs.prerelease }} | |
| VERSION_TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| { | |
| echo "list<<GTORBTAGS" | |
| echo "type=raw,value=${VERSION_TAG}" | |
| if [ "$PRERELEASE" != "true" ]; then | |
| echo "type=raw,value=latest" | |
| fi | |
| echo "type=sha,format=short" | |
| echo "GTORBTAGS" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Image metadata | |
| id: meta | |
| uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/gittensory-selfhost | |
| tags: ${{ steps.tags.outputs.list }} | |
| labels: | | |
| org.opencontainers.image.title=gittensory-orb | |
| org.opencontainers.image.description=Self-hostable Gittensory review engine | |
| org.opencontainers.image.version=${{ steps.version.outputs.tag }} | |
| 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 | |
| # Matches selfhost.yml's CI build (same Dockerfile, same default GHA cache scope), so a release | |
| # can inherit layers selfhost.yml already built and cached for the identical commit -- without | |
| # this, every release rebuilt runtime-base from scratch under QEMU emulation for arm64, the most | |
| # expensive leg (#2502). | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - 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' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REF_NAME: ${{ github.ref_name }} | |
| RELEASE_VERSION: ${{ steps.version.outputs.v }} | |
| RELEASE_TAG: ${{ steps.version.outputs.tag }} | |
| RELEASE_ID: ${{ steps.version.outputs.release }} | |
| REPOSITORY_OWNER: ${{ github.repository_owner }} | |
| PRERELEASE: ${{ steps.version.outputs.prerelease }} | |
| run: | | |
| set -euo pipefail | |
| NOTES="$(cat <<EOF | |
| Gittensory Orb container image: | |
| \`\`\`bash | |
| docker pull ghcr.io/${REPOSITORY_OWNER}/gittensory-selfhost:${RELEASE_TAG} | |
| \`\`\` | |
| 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: \`${RELEASE_ID}\`. | |
| EOF | |
| )" | |
| # #1937: a prerelease tag never becomes the repo's "Latest release" and is visibly marked as such | |
| # on GitHub -- distinct from the image-tag `latest` decision above (Resolve image tags), which | |
| # this flag also drives at the version-resolution step. | |
| PRERELEASE_ARGS=() | |
| if [ "$PRERELEASE" = "true" ]; then | |
| PRERELEASE_ARGS=(--prerelease --latest=false) | |
| fi | |
| if gh release view "$REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then | |
| gh release edit "$REF_NAME" --repo "$GITHUB_REPOSITORY" \ | |
| --title "gittensory-orb ${RELEASE_TAG}" \ | |
| --notes "$NOTES" \ | |
| "${PRERELEASE_ARGS[@]}" | |
| else | |
| gh release create "$REF_NAME" --repo "$GITHUB_REPOSITORY" \ | |
| --verify-tag \ | |
| --title "gittensory-orb ${RELEASE_TAG}" \ | |
| "${PRERELEASE_ARGS[@]}" \ | |
| --notes "$NOTES" \ | |
| --generate-notes | |
| fi |