Skip to content

publish: getsentry/junior@0.78.0 #11317

publish: getsentry/junior@0.78.0

publish: getsentry/junior@0.78.0 #11317

Workflow file for this run

name: Publish
on:
issues:
types: [labeled]
concurrency:
# Use the issue title (e.g. "publish: getsentry/foo@1.2.3") so duplicate
# issues for the same repo@version share a concurrency group.
group: ${{ github.event.issue.title }}
cancel-in-progress: false
permissions:
contents: read
issues: write
packages: write
jobs:
# When accepted is added to a publish issue:
# - Add ci-pending (and remove ci-failed if retrying)
# - Enable the poller via CI_POLLER_HAS_PENDING=true
# - Comment on the issue
# - Trigger the poller immediately so we don't wait for the next cron tick
# The publish job below requires ci-ready, so it will not fire until the
# poller flips ci-pending → ci-ready (which also prevents publishing without
# CI verification in the auto-approve race).
waiting-for-ci:
runs-on: ubuntu-latest
name: Waiting for CI
environment: production
if: >-
github.event.label.name == 'accepted'
&& github.event.issue.state == 'open'
&& startsWith(github.event.issue.title, 'publish: ')
steps:
- name: Get auth token
id: token
uses: actions/create-github-app-token@v3
with:
client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
private-key: ${{ secrets.SENTRY_INTERNAL_APP_PRIVATE_KEY }}
# Reset to a clean ci-pending state:
# - Remove ci-failed (retries after CI was fixed)
# - Remove ci-ready (retries after a publish failure — if we leave
# ci-ready, the poller's later --add-label won't generate a
# labeled event and publish.yml would never fire)
# - Add ci-pending
# Label ops are idempotent: --add/--remove don't fail if already
# added/removed.
- name: Mark ci-pending
env:
GH_TOKEN: ${{ steps.token.outputs.token }}
run: |
gh issue edit "${{ github.event.issue.number }}" \
-R "$GITHUB_REPOSITORY" \
--remove-label "ci-failed" \
--remove-label "ci-ready" \
--add-label "ci-pending"
- name: Comment on issue
env:
GH_TOKEN: ${{ github.token }}
run: |
if [[ "${{ contains(github.event.issue.labels.*.name, 'ci-failed') }}" == "true" ]]; then
body="Retrying — CI was previously failed. Checking CI status now."
else
body="Approved. Checking CI status on the release branch. Publishing will start automatically when CI passes."
fi
gh issue comment "${{ github.event.issue.number }}" \
-R "$GITHUB_REPOSITORY" \
--body "$body"
# Best-effort: enable the cron poller. Uses a dedicated app since
# sentry-internal-app lacks actions_variables:write.
- name: Get poller app token
id: poller-token
continue-on-error: true
uses: actions/create-github-app-token@v3
with:
client-id: ${{ vars.CI_POLLER_APP_CLIENT_ID }}
private-key: ${{ secrets.CI_POLLER_APP_PRIVATE_KEY }}
- name: Enable cron poller
if: steps.poller-token.outcome == 'success'
env:
GH_TOKEN: ${{ steps.poller-token.outputs.token }}
run: |
gh variable set CI_POLLER_HAS_PENDING -R "$GITHUB_REPOSITORY" -b "true"
# Trigger the CI poller immediately instead of waiting for the next cron tick.
# Uses the app token — GITHUB_TOKEN workflow_dispatch events are suppressed.
- name: Trigger CI poller
env:
GH_TOKEN: ${{ steps.token.outputs.token }}
run: |
gh workflow run ci-poller.yml -R "$GITHUB_REPOSITORY"
publish:
runs-on: ubuntu-latest
environment: production
name: Publish a new version
# Publish when ci-ready is present (added by the poller after CI passes).
# Fires ONLY on ci-ready label events — not accepted — to avoid racing
# with waiting-for-ci on the same event. The poller always adds ci-ready
# after checking CI (even if ci-ready was already present, waiting-for-ci
# removes it first so a fresh labeled event fires), so this gate is
# guaranteed to trigger on the happy path.
if: >-
github.event.issue.state == 'open'
&& github.event.label.name == 'ci-ready'
&& contains(github.event.issue.labels.*.name, 'accepted')
&& contains(github.event.issue.labels.*.name, 'ci-ready')
&& !contains(github.event.issue.labels.*.name, 'ci-pending')
&& !contains(github.event.issue.labels.*.name, 'ci-failed')
timeout-minutes: 90
env:
SENTRY_DSN: "https://303a687befb64dc2b40ce4c96de507c5@o1.ingest.sentry.io/6183838"
steps:
- name: Get repo contents
uses: actions/checkout@v6
with:
path: .__publish__
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn
cache-dependency-path: .__publish__/yarn.lock
- name: Install yarn dependencies
run: yarn install --cwd ".__publish__"
- name: Parse and set inputs
id: inputs
run: node .__publish__/src/publish/inputs.js
- name: Inform start
if: steps.inputs.outcome == 'success'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node .__publish__/src/publish/post-workflow-details.js
# Setting the target repo branch will cause the craft config (.craft.yml) to be taken from the checked out branch
# By default, we check out the default branch of the repo.
# If you need to maintain diverging craft configs on different branches, add your repo and the merge target branch
# (i.e. the branch craft will merge the release branch into) into the if condition below.
- name: Set target repo checkout branch
# Note: Branches registered here MUST BE protected in the target repo!
if: |
fromJSON(steps.inputs.outputs.result).repo == 'sentry-migr8' && fromJSON(steps.inputs.outputs.result).merge_target == 'tmp-merge-target' ||
fromJSON(steps.inputs.outputs.result).repo == 'sentry-javascript' && fromJSON(steps.inputs.outputs.result).merge_target == 'v9' ||
fromJSON(steps.inputs.outputs.result).repo == 'sentry-javascript' && fromJSON(steps.inputs.outputs.result).merge_target == 'v8' ||
fromJSON(steps.inputs.outputs.result).repo == 'sentry-javascript' && fromJSON(steps.inputs.outputs.result).merge_target == 'v7' ||
fromJSON(steps.inputs.outputs.result).repo == 'sentry-javascript' && fromJSON(steps.inputs.outputs.result).merge_target == 'master' ||
fromJSON(steps.inputs.outputs.result).repo == 'sentry-python' && fromJSON(steps.inputs.outputs.result).merge_target == 'alpha' ||
fromJSON(steps.inputs.outputs.result).repo == 'sentry-wizard' && fromJSON(steps.inputs.outputs.result).merge_target == '1.x' ||
false
id: target-repo-branch
env:
MERGE_TARGET: ${{ fromJSON(steps.inputs.outputs.result).merge_target }}
REPO: ${{ fromJSON(steps.inputs.outputs.result).repo }}
run: |
echo "taking craft config from branch \"$MERGE_TARGET\" in \"$REPO\""
echo "target_repo_branch=$MERGE_TARGET" >> "$GITHUB_OUTPUT"
- name: Get Release Bot auth token
id: token
uses: actions/create-github-app-token@v3
with:
client-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }}
private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }}
owner: getsentry # create token that have access to all repos
- uses: actions/checkout@v6
name: Check out target repo
if: ${{ steps.inputs.outputs.result }}
with:
path: __repo__
ref: ${{ steps.target-repo-branch.outputs.target_repo_branch || ''}}
repository: getsentry/${{ fromJSON(steps.inputs.outputs.result).repo }}
token: ${{ steps.token.outputs.token }}
fetch-depth: 0
- name: Set targets
shell: bash
if: fromJSON(steps.inputs.outputs.result).targets
env:
CRAFT_PUBLISH_REPO: ${{ fromJSON(steps.inputs.outputs.result).repo }}
CRAFT_PUBLISH_PATH: ${{ fromJSON(steps.inputs.outputs.result).path }}
CRAFT_PUBLISH_VERSION: ${{ fromJSON(steps.inputs.outputs.result).version }}
CRAFT_PUBLISH_TARGETS_JSON: ${{ toJSON(fromJSON(steps.inputs.outputs.result).targets) }}
run: |
# Render the "already published" JSON.
payload="$(jq -n --argjson source "$CRAFT_PUBLISH_TARGETS_JSON" '[{($source[]): true }] | add | {"published": (. // {}) }')"
# Write the state file to the safe location Craft reads from
# (getsentry/craft#797, released in 2.26.0). Craft sees cwd
# `/github/workspace/__repo__/<path>` inside the container;
# the state dir is pinned to `$GITHUB_WORKSPACE/.craft-state`
# (mapped to `/github/workspace/.craft-state` in the
# container) via XDG_STATE_HOME. That path is outside
# __repo__/ so repo contents cannot pre-populate it.
#
# We hash the CONTAINER cwd (what Craft's Node process sees),
# not the runner host path. CRAFT_PUBLISH_PATH is either "."
# (root) or "./subdir/..." (monorepo); after bash `cd
# __repo__/<path>` and Node's process.cwd() canonicalisation,
# the cwd is `/github/workspace/__repo__` (root) or
# `/github/workspace/__repo__/subdir/...` (monorepo).
case "$CRAFT_PUBLISH_PATH" in
.|./) container_cwd="/github/workspace/__repo__" ;;
./*) container_cwd="/github/workspace/__repo__/${CRAFT_PUBLISH_PATH#./}" ;;
*) container_cwd="/github/workspace/__repo__/${CRAFT_PUBLISH_PATH}" ;;
esac
# Strip any trailing slash to match Node's canonicalisation.
container_cwd="${container_cwd%/}"
cwd_hash="$(printf %s "$container_cwd" | sha1sum | cut -c1-12)"
sanitise() { printf %s "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9._-]\+/_/g; s/^_\+//; s/_\+$//'; }
owner_sanitised="$(sanitise getsentry)"
repo_sanitised="$(sanitise "$CRAFT_PUBLISH_REPO")"
version_sanitised="$(sanitise "$CRAFT_PUBLISH_VERSION")"
state_dir="$GITHUB_WORKSPACE/.craft-state/craft"
state_file="$state_dir/publish-state-${owner_sanitised}-${repo_sanitised}-${cwd_hash}-${version_sanitised}.json"
mkdir -p "$state_dir"
printf %s "$payload" > "$state_file"
echo "Wrote state file: $state_file"
- uses: docker://getsentry/craft:latest
name: Publish using Craft
with:
entrypoint: /bin/bash
args: >-
-e
-c "
export HOME=/root &&
cd __repo__/${{ fromJSON(steps.inputs.outputs.result).path }} &&
exec craft publish ${{ fromJSON(steps.inputs.outputs.result).version }}
"
env:
# Pin Craft's publish-state directory to a path outside
# __repo__/ so repo contents cannot pre-populate it. See the
# `Set targets` step above.
XDG_STATE_HOME: /github/workspace/.craft-state
CRAFT_MERGE_TARGET: ${{ fromJSON(steps.inputs.outputs.result).merge_target }}
CRAFT_LOG_LEVEL: ${{ vars.CRAFT_LOG_LEVEL || 'Info' }}
CRAFT_DRY_RUN: ${{ fromJSON(steps.inputs.outputs.result).dry_run }}
GIT_COMMITTER_NAME: sentry-release-bot[bot]
GIT_AUTHOR_NAME: sentry-release-bot[bot]
EMAIL: 180476844+sentry-release-bot[bot]@users.noreply.github.com
GITHUB_TOKEN: ${{ steps.token.outputs.token }}
# We need to use separate tokens for GHCR.IO and GitHub API access
# Because we can only access ghcr.io with GITHUB_TOKEN but that token
# cannot do other cross-repo operations like our Release Bot App
# Thanks GitHub
DOCKER_GHCR_IO_USERNAME: x-access-token # for ghcr.io auth
DOCKER_GHCR_IO_PASSWORD: ${{ secrets.GITHUB_TOKEN }} # for ghcr.io auth
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
CRAFT_GCS_TARGET_CREDS_JSON: ${{ secrets.CRAFT_GCS_TARGET_CREDS_JSON }}
CRAFT_GCS_STORE_CREDS_JSON: ${{ secrets.CRAFT_GCS_STORE_CREDS_JSON }}
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
DOCKER_USERNAME: sentrybuilder
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
TWINE_VERBOSE: "1"
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
NUGET_API_TOKEN: ${{ secrets.NUGET_API_TOKEN }}
POWERSHELL_API_KEY: ${{ secrets.POWERSHELL_API_KEY }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
PUBDEV_ACCESS_TOKEN: ${{ secrets.PUBDEV_ACCESS_TOKEN }}
PUBDEV_REFRESH_TOKEN: ${{ secrets.PUBDEV_REFRESH_TOKEN }}
- name: Update completed targets and remove label
if: ${{ cancelled() || failure() }}
env:
PUBLISH_ARGS: ${{ steps.inputs.outputs.result }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node .__publish__/src/publish/update-issue.js
- name: Inform about cancellation
if: ${{ cancelled() }}
env:
PUBLISH_ARGS: ${{ steps.inputs.outputs.result }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node .__publish__/src/publish/post-result.js cancelled
- name: Inform about failure
if: ${{ failure() }}
env:
PUBLISH_ARGS: ${{ steps.inputs.outputs.result }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node .__publish__/src/publish/post-result.js failure
- name: Close on success
if: ${{ success() }}
env:
PUBLISH_ARGS: ${{ steps.inputs.outputs.result }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node .__publish__/src/publish/post-result.js success