Skip to content

refactor(api): wire listIssuesPaginated through @sentry/api SDK for t… #1363

refactor(api): wire listIssuesPaginated through @sentry/api SDK for t…

refactor(api): wire listIssuesPaginated through @sentry/api SDK for t… #1363

Workflow file for this run

name: Build
on:
push:
branches: [main, release/**]
pull_request:
workflow_call:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
# packages:write is needed for publish-nightly to push to GHCR
permissions:
contents: read
packages: write
env:
# Commit timestamp used for deterministic nightly version strings.
# Defined at workflow level so build-binary and publish-nightly always agree.
COMMIT_TIMESTAMP: ${{ github.event.head_commit.timestamp }}
jobs:
changes:
name: Detect Changes
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
skill: ${{ steps.filter.outputs.skill == 'true' || startsWith(github.ref, 'refs/heads/release/') }}
code: ${{ steps.filter.outputs.code == 'true' || startsWith(github.ref, 'refs/heads/release/') }}
build-targets: ${{ steps.targets.outputs.matrix }}
nightly-version: ${{ steps.nightly.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: filter
with:
filters: |
skill:
- 'src/**'
- 'docs/**'
- 'plugins/**'
- 'script/generate-skill.ts'
code:
- 'src/**'
- 'test/**'
- 'script/**'
- 'patches/**'
- 'docs/**'
- 'plugins/**'
- 'package.json'
- 'bun.lock'
- '.github/workflows/ci.yml'
- name: Compute build matrix
id: targets
run: |
{
echo 'matrix<<MATRIX_EOF'
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
# PRs only need linux-x64 for smoke test and e2e — skip macOS/Windows
echo '{"include":[
{"target":"linux-x64", "os":"ubuntu-latest", "can-test":true}
]}'
else
# main, release/**, workflow_call: full cross-platform matrix
echo '{"include":[
{"target":"darwin-arm64", "os":"macos-latest", "can-test":true},
{"target":"linux-x64", "os":"ubuntu-latest", "can-test":true},
{"target":"windows-x64", "os":"windows-latest","can-test":true},
{"target":"darwin-x64", "os":"macos-latest", "can-test":false},
{"target":"linux-arm64", "os":"ubuntu-latest", "can-test":false}
]}'
fi
echo 'MATRIX_EOF'
} >> "$GITHUB_OUTPUT"
- name: Compute nightly version
id: nightly
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: |
TS=$(date -d "$COMMIT_TIMESTAMP" +%s)
CURRENT=$(jq -r .version package.json)
VERSION=$(echo "$CURRENT" | sed "s/-dev\.[0-9]*$/-dev.${TS}/")
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Nightly version: ${VERSION}"
check-skill:
name: Check SKILL.md
needs: [changes]
if: needs.changes.outputs.skill == 'true'
runs-on: ubuntu-latest
steps:
- name: Get auth token
id: token
# Fork PRs don't have access to secrets, so this step is skipped
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'
uses: actions/create-github-app-token@v2.2.1
with:
app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }}
private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.token.outputs.token || github.token }}
ref: ${{ github.head_ref || github.ref_name }}
- uses: oven-sh/setup-bun@v2
- uses: actions/cache@v4
id: cache
with:
path: node_modules
key: node-modules-${{ hashFiles('bun.lock', 'patches/**') }}
- if: steps.cache.outputs.cache-hit != 'true'
run: bun install --frozen-lockfile
- name: Check SKILL.md
id: check
run: bun run check:skill
continue-on-error: true
- name: Auto-commit regenerated SKILL.md
if: steps.check.outcome == 'failure' && steps.token.outcome == 'success'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add plugins/sentry-cli/skills/sentry-cli/SKILL.md
git commit -m "chore: regenerate SKILL.md"
git push
- name: Fail for fork PRs with stale SKILL.md
if: steps.check.outcome == 'failure' && steps.token.outcome != 'success'
run: |
echo "::error::SKILL.md is out of date. Run 'bun run generate:skill' locally and commit the result."
exit 1
lint:
name: Lint & Typecheck
needs: [changes]
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/cache@v4
id: cache
with:
path: node_modules
key: node-modules-${{ hashFiles('bun.lock', 'patches/**') }}
- if: steps.cache.outputs.cache-hit != 'true'
run: bun install --frozen-lockfile
- run: bun run lint
- run: bun run typecheck
- run: bun run check:deps
test-unit:
name: Unit Tests
needs: [changes]
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
pull-requests: write
statuses: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/cache@v4
id: cache
with:
path: node_modules
key: node-modules-${{ hashFiles('bun.lock', 'patches/**') }}
- if: steps.cache.outputs.cache-hit != 'true'
run: bun install --frozen-lockfile
- name: Unit Tests
run: bun run test:unit
- name: Upload Coverage
uses: getsentry/codecov-action@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
build-binary:
name: Build Binary (${{ matrix.target }})
needs: [changes, lint, test-unit]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.changes.outputs.build-targets) }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/cache@v4
id: cache
with:
path: node_modules
key: node-modules-${{ matrix.os }}-${{ hashFiles('bun.lock', 'patches/**') }}
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
run: |
# Retry logic for Windows Bun patch bug (ENOTEMPTY errors)
for i in 1 2 3; do
if bun install --frozen-lockfile; then
exit 0
fi
echo "Attempt $i failed, clearing Bun cache and retrying..."
bun pm cache rm 2>/dev/null || true
done
echo "All install attempts failed"
exit 1
- name: Set nightly version
# Inject the nightly version (computed once in the changes job) into
# package.json before the build so it gets baked into the binary.
if: needs.changes.outputs.nightly-version != ''
shell: bash
run: |
jq --arg v "${{ needs.changes.outputs.nightly-version }}" '.version = $v' package.json > package.json.tmp
mv package.json.tmp package.json
- name: Build
env:
SENTRY_CLIENT_ID: ${{ vars.SENTRY_CLIENT_ID }}
# Set on main/release branches so build.ts runs binpunch + creates .gz
RELEASE_BUILD: ${{ github.event_name != 'pull_request' && '1' || '' }}
run: bun run build --target ${{ matrix.target }}
- name: Smoke test
if: matrix.can-test
shell: bash
run: |
if [[ "${{ matrix.target }}" == "windows-x64" ]]; then
./dist-bin/sentry-windows-x64.exe --help
else
./dist-bin/sentry-${{ matrix.target }} --help
fi
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: sentry-${{ matrix.target }}
path: |
dist-bin/sentry-*
!dist-bin/*.gz
- name: Upload compressed artifact
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v4
with:
name: sentry-${{ matrix.target }}-gz
path: dist-bin/*.gz
publish-nightly:
name: Publish Nightly to GHCR
# Only run on pushes to main, not on PRs or release branches
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: [changes, build-binary]
runs-on: ubuntu-latest
steps:
- name: Download compressed artifacts
uses: actions/download-artifact@v4
with:
pattern: sentry-*-gz
path: artifacts
merge-multiple: true
- name: Install ORAS CLI
run: |
VERSION=1.2.3
EXPECTED_SHA256="b4efc97a91f471f323f193ea4b4d63d8ff443ca3aab514151a30751330852827"
TARBALL="oras_${VERSION}_linux_amd64.tar.gz"
curl -sfLo "$TARBALL" "https://github.com/oras-project/oras/releases/download/v${VERSION}/${TARBALL}"
echo "${EXPECTED_SHA256} ${TARBALL}" | sha256sum -c -
tar -xz -C /usr/local/bin oras < "$TARBALL"
rm "$TARBALL"
- name: Log in to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Push to GHCR
# Push from inside the artifacts directory so ORAS records bare
# filenames (e.g. "sentry-linux-x64.gz") as layer titles, not
# "artifacts/sentry-linux-x64.gz". The CLI matches layers by
# filename in findLayerByFilename().
working-directory: artifacts
run: |
VERSION="${{ needs.changes.outputs.nightly-version }}"
oras push ghcr.io/getsentry/cli:nightly \
--artifact-type application/vnd.sentry.cli.nightly \
--annotation "org.opencontainers.image.source=https://github.com/getsentry/cli" \
--annotation "version=${VERSION}" \
*.gz
test-e2e:
name: E2E Tests
needs: [build-binary]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/cache@v4
id: cache
with:
path: node_modules
key: node-modules-${{ hashFiles('bun.lock', 'patches/**') }}
- if: steps.cache.outputs.cache-hit != 'true'
run: bun install --frozen-lockfile
- name: Download Linux binary
uses: actions/download-artifact@v4
with:
name: sentry-linux-x64
path: dist-bin
- name: Make binary executable
run: chmod +x dist-bin/sentry-linux-x64
- name: E2E Tests
env:
SENTRY_CLI_BINARY: ${{ github.workspace }}/dist-bin/sentry-linux-x64
run: bun run test:e2e
build-npm:
name: Build npm Package (Node ${{ matrix.node }})
needs: [lint, test-unit]
runs-on: ubuntu-latest
environment: ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && 'production' || '' }}
strategy:
fail-fast: false
matrix:
node: ["22", "24"]
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- uses: actions/cache@v4
id: cache
with:
path: node_modules
key: node-modules-${{ hashFiles('bun.lock', 'patches/**') }}
- if: steps.cache.outputs.cache-hit != 'true'
run: bun install --frozen-lockfile
- name: Bundle
env:
SENTRY_CLIENT_ID: ${{ vars.SENTRY_CLIENT_ID }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: bun run bundle
- name: Smoke test (Node.js)
run: node dist/bin.cjs --help
- run: npm pack
- name: Upload artifact
if: matrix.node == '22'
uses: actions/upload-artifact@v4
with:
name: npm-package
path: "*.tgz"
build-docs:
name: Build Docs
needs: [lint]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Build Docs
working-directory: docs
run: |
bun install --frozen-lockfile
bun run build
- name: Package Docs
run: |
cp .nojekyll docs/dist/
cd docs/dist && zip -r ../../gh-pages.zip .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: gh-pages
path: gh-pages.zip
ci-status:
name: CI Status
if: always()
needs: [changes, check-skill, build-binary, build-npm, build-docs, test-e2e, publish-nightly]
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Check CI status
run: |
# Check for explicit failures or cancellations in all jobs
# publish-nightly is skipped on PRs (if: github.ref == 'refs/heads/main') — that's expected
results="${{ needs.check-skill.result }} ${{ needs.build-binary.result }} ${{ needs.build-npm.result }} ${{ needs.build-docs.result }} ${{ needs.test-e2e.result }} ${{ needs.publish-nightly.result }}"
for result in $results; do
if [[ "$result" == "failure" || "$result" == "cancelled" ]]; then
echo "::error::CI failed"
exit 1
fi
done
# Detect upstream failures: if changes were detected but jobs were skipped,
# it means an upstream job failed (skipped jobs cascade to dependents)
if [[ "${{ needs.changes.outputs.code }}" == "true" && "${{ needs.test-e2e.result }}" == "skipped" ]]; then
echo "::error::CI failed - upstream job failed causing test-e2e to be skipped"
exit 1
fi
if [[ "${{ needs.changes.outputs.skill }}" == "true" && "${{ needs.check-skill.result }}" == "skipped" ]]; then
echo "::error::CI failed - upstream job failed causing check-skill to be skipped"
exit 1
fi
echo "CI passed"