fix(dashboards): Remove references to removed datasets #6260
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
| 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 | |
| # issues:write is needed for generate-patches to file issues on failure | |
| permissions: | |
| contents: read | |
| issues: write | |
| 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 }} | |
| # SENTRY_CLIENT_ID is baked into the binary at build time. Fork PRs can't | |
| # read repo vars (getsentry org policy); fall back to a dummy. The resulting | |
| # binary is only smoke-tested (--help) and never shipped, so any non-empty | |
| # value works; tests tolerate the dummy via test/preload.ts. | |
| SENTRY_CLIENT_ID: ${{ vars.SENTRY_CLIENT_ID || 'ci-fork-pr-dummy' }} | |
| # Disable io_uring in libuv — GitHub Actions runners may use kernels that | |
| # don't fully support it, causing SIGABRT in Node.js processes. | |
| # See: https://github.com/actions/runner-images/issues/13602 | |
| UV_USE_IO_URING: "0" | |
| # CI Node.js versions, pinned to exact patches and used as the single source | |
| # of truth for every actions/setup-node step (referenced as | |
| # ${{ env.NODE_VERSION_xx }}). Node 24.17.0 / 22.23.0 (CVE-2026-48931's | |
| # http.Agent fix) added a `data` listener on idle agent sockets that makes | |
| # keep-alive fetch reuse throw false ERR_STREAM_PREMATURE_CLOSE | |
| # ("Premature close" — hit by the skill-eval planner talking to external | |
| # APIs). Fixed in 24.18.0 / 22.23.1 (nodejs/node#64004). A floating major | |
| # ("24"/"22") silently reuses the runner's pre-cached buggy patch, so pin the | |
| # exact patched versions here and bump them in one place. | |
| # | |
| # Node 20 is the WASM-SQLite floor: the npm package supports Node >= 18, and | |
| # on < 22.15 (which lacks node:sqlite) it falls back to the bundled | |
| # node-sqlite3-wasm driver. The npm-package job runs a Node 20 matrix entry | |
| # so that fallback path is exercised in CI rather than only in production. | |
| NODE_VERSION_20: "20.20.2" | |
| NODE_VERSION_22: "22.23.1" | |
| NODE_VERSION_24: "24.18.0" | |
| 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/') }} | |
| codemod: ${{ steps.filter.outputs.codemod == 'true' || startsWith(github.ref, 'refs/heads/release/') }} | |
| build-targets: ${{ steps.targets.outputs.matrix }} | |
| nightly-version: ${{ steps.nightly.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dorny/paths-filter@v4 | |
| id: filter | |
| with: | |
| filters: | | |
| skill: | |
| - 'packages/cli/src/**' | |
| - 'apps/cli-docs/**' | |
| - 'packages/cli/package.json' | |
| - 'packages/cli/README.md' | |
| - 'packages/cli/DEVELOPMENT.md' | |
| - 'packages/cli/script/generate-skill.ts' | |
| - 'packages/cli/script/generate-command-docs.ts' | |
| - 'packages/cli/script/generate-docs-sections.ts' | |
| - 'packages/cli/script/eval-skill.ts' | |
| - 'packages/cli/test/skill-eval/**' | |
| code: | |
| - 'packages/cli/src/**' | |
| - 'packages/cli/test/**' | |
| - 'packages/cli/script/**' | |
| - 'packages/cli/patches/**' | |
| - 'apps/cli-docs/**' | |
| - 'packages/cli/plugins/**' | |
| - 'packages/cli/package.json' | |
| - 'pnpm-lock.yaml' | |
| - '.github/workflows/ci.yml' | |
| codemod: | |
| - 'codemods/**' | |
| - '.github/workflows/ci.yml' | |
| - name: Compute build matrix | |
| id: targets | |
| run: | | |
| { | |
| echo 'matrix<<MATRIX_EOF' | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| # PRs build linux-x64 (smoke test + e2e) and darwin-arm64. The | |
| # darwin-arm64 build runs natively on macOS so the CoreUI-based | |
| # Assets.car pixel extractor is compiled and exercised on PRs, not | |
| # just on main/release. | |
| echo '{"include":[ | |
| {"target":"linux-x64", "os":"ubuntu-latest", "can-test":true}, | |
| {"target":"darwin-arm64", "os":"macos-latest", "can-test":true} | |
| ]}' | |
| else | |
| # main, release/**, workflow_call: full cross-platform matrix. | |
| # darwin-arm64 builds natively on macOS for V8 code cache (~15% faster startup). | |
| # All other targets cross-compile from ubuntu-latest via fossilize. | |
| echo '{"include":[ | |
| {"target":"darwin-arm64", "os":"macos-latest", "can-test":true}, | |
| {"target":"darwin-x64", "os":"ubuntu-latest", "can-test":false}, | |
| {"target":"linux-x64", "os":"ubuntu-latest", "can-test":true}, | |
| {"target":"linux-arm64", "os":"ubuntu-latest", "can-test":false}, | |
| {"target":"windows-x64", "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 packages/cli/package.json) | |
| VERSION=$(echo "$CURRENT" | sed "s/-dev\.[0-9]*$/-dev.${TS}/") | |
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "Nightly version: ${VERSION}" | |
| codemod-test: | |
| name: Codemod tests | |
| needs: [changes] | |
| if: needs.changes.outputs.codemod == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_22 }} | |
| # jssg fixtures live under codemods/sentry-v3-to-v4/tests/<case>/. The | |
| # codemod CLI is pinned for reproducible runs (bump deliberately). | |
| - name: Run jssg codemod tests | |
| working-directory: codemods/sentry-v3-to-v4 | |
| run: npx codemod@1.12.7 jssg test -l typescript ./scripts/codemod.ts | |
| check-generated: | |
| name: Validate generated files | |
| 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@v3 | |
| with: | |
| app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} | |
| private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} | |
| - uses: actions/checkout@v6 | |
| with: | |
| token: ${{ steps.token.outputs.token || github.token }} | |
| # Same-repo PRs (token step succeeded): check out the branch head so | |
| # the auto-commit step can push regenerated docs back. Fork PRs leave | |
| # `ref` empty so checkout defaults to GITHUB_REF (the pull_request | |
| # merge SHA, always fetchable from the base repo with github.token). | |
| ref: ${{ steps.token.outcome == 'success' && (github.head_ref || github.ref_name) || '' }} | |
| - uses: pnpm/action-setup@v4 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_22 }} | |
| - uses: actions/cache@v5 | |
| id: cache | |
| with: | |
| path: | | |
| node_modules | |
| packages/*/node_modules | |
| apps/*/node_modules | |
| key: node-modules-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'packages/cli/patches/**') }} | |
| - if: steps.cache.outputs.cache-hit != 'true' | |
| run: pnpm install --frozen-lockfile | |
| - name: Generate API Schema | |
| run: pnpm run generate:schema | |
| - name: Generate docs and skill files | |
| run: pnpm run generate:docs | |
| - name: Validate fragments | |
| run: pnpm run check:fragments | |
| - name: Check skill files | |
| id: check-skill | |
| run: | | |
| if git diff --quiet packages/cli/plugins/sentry-cli/skills/sentry-cli/; then | |
| echo "Skill files are up to date" | |
| else | |
| echo "stale=true" >> "$GITHUB_OUTPUT" | |
| echo "Skill files are out of date" | |
| fi | |
| - name: Check docs sections | |
| id: check-sections | |
| run: | | |
| if git diff --quiet packages/cli/README.md packages/cli/DEVELOPMENT.md apps/cli-docs/src/content/docs/contributing.md apps/cli-docs/src/content/docs/self-hosted.md apps/cli-docs/src/content/docs/getting-started.mdx; then | |
| echo "Docs sections are up to date" | |
| else | |
| echo "stale=true" >> "$GITHUB_OUTPUT" | |
| echo "Docs sections are out of date" | |
| fi | |
| - name: Auto-commit regenerated files | |
| if: (steps.check-skill.outputs.stale == 'true' || steps.check-sections.outputs.stale == 'true') && 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 packages/cli/plugins/sentry-cli/skills/sentry-cli/ packages/cli/README.md packages/cli/DEVELOPMENT.md apps/cli-docs/src/content/docs/contributing.md apps/cli-docs/src/content/docs/self-hosted.md apps/cli-docs/src/content/docs/getting-started.mdx | |
| git diff --cached --quiet || (git commit -m "chore: regenerate docs" && git push) | |
| - name: Fail for fork PRs with stale generated files | |
| if: (steps.check-skill.outputs.stale == 'true' || steps.check-sections.outputs.stale == 'true') && steps.token.outcome != 'success' | |
| run: | | |
| echo "::error::Generated files are out of date. Run 'pnpm run generate:docs' 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@v6 | |
| - uses: pnpm/action-setup@v4 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_22 }} | |
| - uses: actions/cache@v5 | |
| id: cache | |
| with: | |
| path: | | |
| node_modules | |
| packages/*/node_modules | |
| apps/*/node_modules | |
| key: node-modules-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'packages/cli/patches/**') }} | |
| - if: steps.cache.outputs.cache-hit != 'true' | |
| run: pnpm install --frozen-lockfile | |
| - run: pnpm run generate:schema | |
| - run: pnpm run lint | |
| - run: pnpm run typecheck | |
| - run: pnpm run check:deps | |
| - run: pnpm run check:errors | |
| - run: pnpm run check:patches | |
| - run: pnpm run check:stale-refs | |
| 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@v6 | |
| - uses: pnpm/action-setup@v4 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_24 }} | |
| - uses: actions/cache@v5 | |
| id: cache | |
| with: | |
| path: | | |
| node_modules | |
| packages/*/node_modules | |
| apps/*/node_modules | |
| key: node-modules-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'packages/cli/patches/**') }} | |
| - if: steps.cache.outputs.cache-hit != 'true' | |
| run: pnpm install --frozen-lockfile | |
| - name: Generate API Schema | |
| run: pnpm run generate:schema | |
| - name: Unit Tests | |
| run: pnpm run test:unit | |
| - name: Coverage Report | |
| uses: getsentry/codecov-action@main | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| files: ./coverage/lcov.info | |
| informational-patch: ${{ github.event_name == 'push' }} | |
| build-binary: | |
| name: Build Binary (${{ matrix.target }}) | |
| needs: [changes, lint, test-unit] | |
| runs-on: ${{ matrix.os }} | |
| # SENTRY_AUTH_TOKEN is scoped to the production environment so sourcemap | |
| # upload works on main/release branches. Without this, every binary build | |
| # skips the upload and Sentry stack traces show minified names (e.g., xE). | |
| environment: ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && 'production' || '' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.changes.outputs.build-targets) }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v4 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_22 }} | |
| - uses: actions/cache@v5 | |
| id: cache | |
| with: | |
| path: | | |
| node_modules | |
| packages/*/node_modules | |
| apps/*/node_modules | |
| key: node-modules-${{ matrix.os }}-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'packages/cli/patches/**') }} | |
| - name: Install dependencies | |
| if: steps.cache.outputs.cache-hit != 'true' | |
| shell: bash | |
| run: pnpm install --frozen-lockfile | |
| - name: Setup codesign dependencies | |
| env: | |
| APPLE_CERT_DATA: ${{ secrets.APPLE_CERT_DATA }} | |
| APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} | |
| shell: bash | |
| run: | | |
| # Install rcodesign (platform-aware binary + checksum tool) | |
| if [[ "$RUNNER_OS" == "macOS" && "$RUNNER_ARCH" == "ARM64" ]]; then | |
| RCODESIGN_ARCHIVE="apple-codesign-0.29.0-aarch64-apple-darwin.tar.gz" | |
| RCODESIGN_SHA256="d1a532150adaf90048260d76359261aa716abafc45c53c5dc18845029184334a" | |
| elif [[ "$RUNNER_OS" == "macOS" ]]; then | |
| RCODESIGN_ARCHIVE="apple-codesign-0.29.0-x86_64-apple-darwin.tar.gz" | |
| RCODESIGN_SHA256="14ef11bedd51a8d95eafd767939ae96d5900e5a61511bef75bb21db6e7c74140" | |
| else | |
| RCODESIGN_ARCHIVE="apple-codesign-0.29.0-x86_64-unknown-linux-musl.tar.gz" | |
| RCODESIGN_SHA256="dbe85cedd8ee4217b64e9a0e4c2aef92ab8bcaaa41f20bde99781ff02e600002" | |
| fi | |
| curl -L "https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.29.0/${RCODESIGN_ARCHIVE}" -o 'rcodesign.tar.gz' | |
| echo "${RCODESIGN_SHA256} rcodesign.tar.gz" | shasum -a 256 -c | |
| tar -xzf rcodesign.tar.gz --strip-components=1 | |
| mv rcodesign /usr/local/bin/rcodesign | |
| rm rcodesign.tar.gz | |
| # Decode Apple signing credentials | |
| if [ -n "$APPLE_CERT_DATA" ]; then | |
| echo "$APPLE_CERT_DATA" | base64 --decode > /tmp/certs.p12 | |
| echo 'APPLE_CERT_PATH=/tmp/certs.p12' >> $GITHUB_ENV | |
| fi | |
| if [ -n "$APPLE_API_KEY" ]; then | |
| echo "$APPLE_API_KEY" | base64 -d > /tmp/apple_key.json | |
| cat /tmp/apple_key.json | jq .private_key -r > /tmp/apple_key.pem | |
| echo "APPLE_API_KEY_ISSUER_ID=$(cat /tmp/apple_key.json | jq .issuer_id -r | tr -d '\n\r')" >> $GITHUB_ENV | |
| echo "APPLE_API_KEY_ID=$(cat /tmp/apple_key.json | jq .key_id -r | tr -d '\n\r')" >> $GITHUB_ENV | |
| echo "APPLE_API_KEY_P8_PATH=/tmp/apple_key.pem" >> $GITHUB_ENV | |
| echo 'APPLE_API_KEY_PATH=/tmp/apple_key.json' >> $GITHUB_ENV | |
| fi | |
| - 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' packages/cli/package.json > packages/cli/package.json.tmp | |
| mv packages/cli/package.json.tmp packages/cli/package.json | |
| - name: Build | |
| env: | |
| # Environment-scoped (production) — must be set at step level to | |
| # resolve correctly; workflow-level env evaluates before the job's | |
| # environment: is applied. | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| # Set on main/release branches so build.ts creates .gz archives | |
| RELEASE_BUILD: ${{ github.event_name != 'pull_request' && '1' || '' }} | |
| # Codesigning: only on main/release pushes (fork PRs lack secrets) | |
| FOSSILIZE_SIGN: ${{ github.event_name == 'push' && (github.ref_name == 'main' || startsWith(github.ref_name, 'release/')) && 'y' || 'n' }} | |
| APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: pnpm run build -- --target ${{ matrix.target }} | |
| - name: Smoke test | |
| if: matrix.can-test | |
| shell: bash | |
| run: | | |
| if [[ "${{ matrix.target }}" == "windows-x64" ]]; then | |
| ./packages/cli/dist-bin/sentry-windows-x64.exe --help | |
| else | |
| ./packages/cli/dist-bin/sentry-${{ matrix.target }} --help | |
| fi | |
| - name: Smoke test (deep — SQLite, telemetry, auth DB) | |
| if: matrix.can-test | |
| shell: bash | |
| env: | |
| SENTRY_AUTH_TOKEN: "" | |
| SENTRY_TOKEN: "" | |
| run: | | |
| if [[ "${{ matrix.target }}" == "windows-x64" ]]; then | |
| BIN=./packages/cli/dist-bin/sentry-windows-x64.exe | |
| else | |
| BIN=./packages/cli/dist-bin/sentry-${{ matrix.target }} | |
| fi | |
| # auth status without a token exercises SQLite init, schema | |
| # migrations, telemetry lazy import, and the CJS require chain. | |
| # Expected: exit 10 (AUTH_NOT_AUTHENTICATED), NOT a crash/syntax error. | |
| OUTPUT=$($BIN auth status 2>&1) && EXIT_CODE=$? || EXIT_CODE=$? | |
| if [[ $EXIT_CODE -ne 10 ]]; then | |
| echo "::error::Expected exit code 10 (not authenticated), got $EXIT_CODE" | |
| echo "$OUTPUT" | |
| exit 1 | |
| fi | |
| if ! echo "$OUTPUT" | grep -qi "not authenticated"; then | |
| echo "::error::Expected 'not authenticated' in output, got:" | |
| echo "$OUTPUT" | |
| exit 1 | |
| fi | |
| - name: Verify code signature (darwin) | |
| # Only runs when signing was active (main/release pushes) and target is darwin. | |
| # The smoke test alone does NOT catch invalid signatures — AMFI only SIGKILLs | |
| # quarantined (downloaded) binaries, not freshly-built ones on the build machine. | |
| if: >- | |
| startsWith(matrix.target, 'darwin') && | |
| github.event_name == 'push' && | |
| (github.ref_name == 'main' || startsWith(github.ref_name, 'release/')) | |
| shell: bash | |
| run: | | |
| BIN=./packages/cli/dist-bin/sentry-${{ matrix.target }} | |
| echo "Verifying code signature on $BIN..." | |
| rcodesign verify "$BIN" | |
| - name: Upload binary artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: sentry-${{ matrix.target }} | |
| path: | | |
| packages/cli/dist-bin/sentry-* | |
| !packages/cli/dist-bin/*.gz | |
| - name: Upload compressed artifact | |
| if: github.event_name != 'pull_request' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: sentry-${{ matrix.target }}-gz | |
| path: packages/cli/dist-bin/*.gz | |
| generate-patches: | |
| name: Generate Delta Patches | |
| needs: [changes, build-binary] | |
| # Only on main (nightlies) and release branches (stable) — skip PRs | |
| if: github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| # `from-version`: the previous version the patches were actually generated | |
| # FROM, as computed by the binpatch action. Carried through to | |
| # publish-nightly so it stamps the `from-version` annotation on the | |
| # pushed patch manifest with the SAME value. See | |
| # https://github.com/BYK/binpatch/blob/main/website/src/content/docs/security.md#from-version-annotation-trust | |
| # | |
| # Note: the `release/**` path uses `generate-release` (different mode) and | |
| # its `from-version` is captured only for the `Upload patch artifacts` step | |
| # gate below. `publish-nightly` only runs on `main`, so the release-branch | |
| # from-version is intentionally not exposed as a job output. | |
| outputs: | |
| from-version: ${{ steps.gen-ghcr.outputs.from-version }} | |
| steps: | |
| - name: Download current binaries | |
| uses: actions/download-artifact@v8 | |
| with: | |
| # Use sentry-*-* to match platform binaries (sentry-linux-x64, etc.) | |
| # and their -gz variants, but not sentry-patches | |
| pattern: sentry-*-* | |
| path: new-binaries | |
| merge-multiple: true | |
| - name: Generate delta patches (nightly) | |
| id: gen-ghcr | |
| if: github.ref == 'refs/heads/main' | |
| uses: BYK/binpatch/action@0.4.2 | |
| with: | |
| mode: generate-ghcr | |
| version: ${{ needs.changes.outputs.nightly-version }} | |
| registry: ghcr.io | |
| repo: getsentry/cli | |
| binary-glob: 'sentry-*' | |
| new-binaries-dir: new-binaries | |
| new-gz-dir: new-binaries | |
| patches-dir: patches | |
| - name: Generate delta patches (stable) | |
| id: gen-release | |
| if: startsWith(github.ref, 'refs/heads/release/') | |
| uses: BYK/binpatch/action@0.4.2 | |
| with: | |
| mode: generate-release | |
| repo: ${{ github.repository }} | |
| binary-glob: 'sentry-*' | |
| new-binaries-dir: new-binaries | |
| new-gz-dir: new-binaries | |
| patches-dir: patches | |
| - name: Upload patch artifacts | |
| if: steps.gen-ghcr.outputs.has-patches == 'true' || steps.gen-release.outputs.has-patches == 'true' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: sentry-patches | |
| path: patches/*.patch | |
| - name: File issue on failure | |
| if: failure() | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| TITLE="Delta patch generation failed" | |
| BODY="The \`generate-patches\` job failed on [\`${GITHUB_REF_NAME}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}). | |
| **Branch:** \`${GITHUB_REF_NAME}\` | |
| **Commit:** ${GITHUB_SHA} | |
| **Run:** ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" | |
| # Check for existing open issue with same title to avoid duplicates | |
| EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open --search "$TITLE" --json number --jq '.[0].number // empty') | |
| if [ -n "$EXISTING" ]; then | |
| echo "Issue #${EXISTING} already open, adding comment" | |
| gh issue comment "$EXISTING" --repo "$GITHUB_REPOSITORY" --body "$BODY" | |
| else | |
| gh issue create --repo "$GITHUB_REPOSITORY" --title "$TITLE" --body "$BODY" | |
| fi | |
| 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, generate-patches] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download compressed artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: sentry-*-gz | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Download uncompressed artifacts (for SHA-256 computation) | |
| uses: actions/download-artifact@v8 | |
| with: | |
| # Use sentry-*-* to match platform binaries (sentry-linux-x64, etc.) | |
| # but not sentry-patches (which is downloaded separately) | |
| pattern: sentry-*-* | |
| path: binaries | |
| merge-multiple: true | |
| - name: Download patch artifacts | |
| uses: actions/download-artifact@v8 | |
| continue-on-error: true | |
| id: download-patches | |
| with: | |
| name: sentry-patches | |
| path: patches | |
| - name: Push to GHCR (binpatch action) | |
| uses: BYK/binpatch/action@0.4.2 | |
| with: | |
| mode: publish-ghcr | |
| version: ${{ needs.changes.outputs.nightly-version }} | |
| registry: ghcr.io | |
| repo: getsentry/cli | |
| binary-glob: 'sentry-*' | |
| artifacts-dir: artifacts | |
| binaries-dir: binaries | |
| patches-dir: patches | |
| # CRITICAL: stamp the annotation with the value the generate step | |
| # actually used, NOT a re-derived tag listing. binpatch verifies | |
| # only the final output SHA, so a mismatched `from-version` annotation | |
| # silently bypasses the user's expected upgrade path. See | |
| # https://github.com/BYK/binpatch/blob/main/website/src/content/docs/security.md#from-version-annotation-trust | |
| from-version: ${{ needs.generate-patches.outputs.from-version }} | |
| test-e2e: | |
| name: E2E Tests | |
| needs: [build-binary, changes] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v4 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| # Pinned to a patched 24 — the skill-eval planner makes keep-alive | |
| # HTTPS calls and would hit the ERR_STREAM_PREMATURE_CLOSE regression | |
| # on a floating "24". See NODE_VERSION_24 in the top-level env block. | |
| node-version: ${{ env.NODE_VERSION_24 }} | |
| - uses: actions/cache@v5 | |
| id: cache | |
| with: | |
| path: | | |
| node_modules | |
| packages/*/node_modules | |
| apps/*/node_modules | |
| key: node-modules-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'packages/cli/patches/**') }} | |
| - if: steps.cache.outputs.cache-hit != 'true' | |
| run: pnpm install --frozen-lockfile | |
| - name: Download Linux binary | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: sentry-linux-x64 | |
| path: dist-bin | |
| - name: Make binary executable | |
| run: chmod +x dist-bin/sentry-linux-x64 | |
| - name: Generate API Schema | |
| run: pnpm run generate:schema | |
| - name: E2E Tests | |
| env: | |
| SENTRY_CLI_BINARY: ${{ github.workspace }}/dist-bin/sentry-linux-x64 | |
| # No eval provider key here: the skill eval runs in the dedicated | |
| # `eval-skill` job (non-forks) and `eval-skill-fork.yml` (forks). The | |
| # inline skill-eval e2e test stays for local runs and auto-skips in | |
| # CI when no key is present, so it never double-runs the eval. | |
| run: pnpm run test:e2e | |
| eval-skill: | |
| name: Eval SKILL.md | |
| needs: [changes] | |
| # Skill eval for non-fork PRs and pushes. Fork PRs are handled separately | |
| # by eval-skill-fork.yml (they can't read secrets on pull_request, so they | |
| # go through a label-gated pull_request_target flow). Fork PRs skip here | |
| # because head.repo.fork is true; same-repo PRs and pushes have secrets. | |
| if: >- | |
| needs.changes.outputs.skill == 'true' | |
| && (github.event_name != 'pull_request' | |
| || github.event.pull_request.head.repo.fork == false) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v4 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| # Pinned to a patched 24 — the skill-eval planner makes keep-alive | |
| # HTTPS calls and would hit the ERR_STREAM_PREMATURE_CLOSE regression | |
| # on a floating "24". See NODE_VERSION_24 in the top-level env block. | |
| node-version: ${{ env.NODE_VERSION_24 }} | |
| - uses: actions/cache@v5 | |
| id: cache | |
| with: | |
| path: | | |
| node_modules | |
| packages/*/node_modules | |
| apps/*/node_modules | |
| key: node-modules-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'packages/cli/patches/**') }} | |
| - if: steps.cache.outputs.cache-hit != 'true' | |
| run: pnpm install --frozen-lockfile | |
| - name: Generate docs and skill files | |
| run: pnpm run generate:schema && pnpm run generate:docs | |
| - name: Eval SKILL.md | |
| run: pnpm run eval:skill | |
| env: | |
| OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| build-npm: | |
| name: Build npm Package (smoke 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: ["20", "22", "24"] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v4 | |
| # Build under our development Node floor (22.15+). The build tooling | |
| # (tsx loader hooks in script/require-shim.mjs) uses node:module APIs | |
| # only present on 22.15+, so the bundle is always produced on a modern | |
| # Node — matching how we actually publish. The matrix Node version is | |
| # only used to *run* the produced artifact below. | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_22 }} | |
| - uses: actions/cache@v5 | |
| id: cache | |
| with: | |
| path: | | |
| node_modules | |
| packages/*/node_modules | |
| apps/*/node_modules | |
| key: node-modules-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'packages/cli/patches/**') }} | |
| - if: steps.cache.outputs.cache-hit != 'true' | |
| run: pnpm install --frozen-lockfile | |
| - name: Bundle | |
| env: | |
| # Environment-scoped (production) — see note in build-binary. | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| run: pnpm run bundle | |
| # Switch to the matrix Node to run the produced artifact. This is the | |
| # consumer's perspective: Node 20 exercises the WASM SQLite fallback | |
| # (no node:sqlite before 22.15); 22/24 use the native driver. | |
| - uses: actions/setup-node@v6 | |
| with: | |
| # Matrix entries stay bare majors ("20"/"22"/"24") for the job name | |
| # and the `matrix.node == '22'` artifact guard below; map each to its | |
| # exact patched version from the central env block here. | |
| node-version: ${{ matrix.node == '24' && env.NODE_VERSION_24 || matrix.node == '20' && env.NODE_VERSION_20 || env.NODE_VERSION_22 }} | |
| - name: Smoke test (Node.js) | |
| env: | |
| # Clear auth tokens — on main the job inherits SENTRY_AUTH_TOKEN | |
| # from the production environment (for the Bundle step's sourcemap | |
| # upload). The smoke tests must not use it. | |
| SENTRY_AUTH_TOKEN: "" | |
| SENTRY_TOKEN: "" | |
| # Use a temp config dir so the WASM SQLite driver (Node 20) can | |
| # create and open the database reliably. The runner's ~/.sentry | |
| # may not exist under the switched Node 20 runtime. | |
| SENTRY_CONFIG_DIR: ${{ runner.temp }}/.sentry-smoke | |
| run: node packages/cli/dist/bin.cjs --help | |
| - name: Smoke test (Node.js — deep) | |
| shell: bash | |
| env: | |
| SENTRY_AUTH_TOKEN: "" | |
| SENTRY_TOKEN: "" | |
| SENTRY_CONFIG_DIR: ${{ runner.temp }}/.sentry-smoke | |
| run: | | |
| # auth status without a token exercises SQLite init, schema | |
| # migrations, telemetry lazy import, and the CJS require chain. | |
| # On Node 20 this runs entirely through the bundled WASM SQLite | |
| # driver. Expected: exit 10 (AUTH_NOT_AUTHENTICATED), NOT a crash. | |
| OUTPUT=$(node packages/cli/dist/bin.cjs auth status 2>&1) && EXIT_CODE=$? || EXIT_CODE=$? | |
| if [[ $EXIT_CODE -ne 10 ]]; then | |
| echo "::error::Expected exit code 10 (not authenticated), got $EXIT_CODE" | |
| echo "$OUTPUT" | |
| exit 1 | |
| fi | |
| if ! echo "$OUTPUT" | grep -qi "not authenticated"; then | |
| echo "::error::Expected 'not authenticated' in output, got:" | |
| echo "$OUTPUT" | |
| exit 1 | |
| fi | |
| - run: npm pack | |
| working-directory: packages/cli | |
| - name: Upload artifact | |
| if: matrix.node == '22' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: npm-package | |
| path: "packages/cli/*.tgz" | |
| build-docs: | |
| name: Build Docs | |
| needs: [lint, build-binary] | |
| runs-on: ubuntu-latest | |
| # SENTRY_AUTH_TOKEN is scoped to the production environment. Needed by | |
| # the "Inject debug IDs and upload sourcemaps" step below. | |
| environment: ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && 'production' || '' }} | |
| # Hoisted to job level (not step) so the `if: env.SENTRY_AUTH_TOKEN != ''` | |
| # guard on the sourcemap-upload step can see it. Job-level env is resolved | |
| # after `environment:` is applied, so the production-scoped secret resolves | |
| # correctly. | |
| env: | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v4 | |
| # Astro 6 requires Node >= 22.12. Pin an explicit version so the docs | |
| # build doesn't rely on whatever ships on the runner image. | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_24 }} | |
| - uses: actions/cache@v5 | |
| id: cache | |
| with: | |
| path: | | |
| node_modules | |
| packages/*/node_modules | |
| apps/*/node_modules | |
| key: node-modules-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'packages/cli/patches/**') }} | |
| - if: steps.cache.outputs.cache-hit != 'true' | |
| run: pnpm install --frozen-lockfile | |
| - name: Get CLI version | |
| id: version | |
| run: echo "version=$(node -p 'require("./packages/cli/package.json").version')" >> "$GITHUB_OUTPUT" | |
| - name: Download compiled CLI binary | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: sentry-linux-x64 | |
| path: dist-bin | |
| - name: Make binary executable | |
| run: chmod +x dist-bin/sentry-linux-x64 | |
| - name: Generate docs content | |
| run: pnpm run generate:schema && pnpm run generate:docs | |
| - name: Build Docs | |
| working-directory: apps/cli-docs | |
| env: | |
| PUBLIC_SENTRY_ENVIRONMENT: production | |
| SENTRY_RELEASE: ${{ steps.version.outputs.version }} | |
| PUBLIC_SENTRY_RELEASE: ${{ steps.version.outputs.version }} | |
| run: | | |
| pnpm install --frozen-lockfile | |
| pnpm run build | |
| # Inject debug IDs and upload sourcemaps. The inject step adds | |
| # //# debugId= and the _sentryDebugIds IIFE to deployed JS files. | |
| # Both steps require SENTRY_AUTH_TOKEN (the CLI checks auth on startup). | |
| - name: Inject debug IDs and upload sourcemaps | |
| if: github.event_name == 'push' && env.SENTRY_AUTH_TOKEN != '' | |
| env: | |
| SENTRY_ORG: sentry | |
| SENTRY_PROJECT: cli-website | |
| run: | | |
| ./dist-bin/sentry-linux-x64 sourcemap inject apps/cli-docs/dist/ | |
| ./dist-bin/sentry-linux-x64 sourcemap upload apps/cli-docs/dist/ \ | |
| --release "${{ steps.version.outputs.version }}" \ | |
| --url-prefix "~/" | |
| # Remove .map files — they were uploaded to Sentry but shouldn't | |
| # be deployed to production. | |
| - name: Remove sourcemaps from output | |
| run: find apps/cli-docs/dist -name '*.map' -delete | |
| # Package the built site using Vercel's Build Output API so Craft's | |
| # release-gated Vercel target can deploy it without rebuilding. | |
| - name: Package Docs | |
| run: | | |
| OUTPUT_DIR="$RUNNER_TEMP/vercel-output" | |
| rm -rf "$OUTPUT_DIR" | |
| mkdir -p "$OUTPUT_DIR/.vercel/output/static" | |
| cp -R apps/cli-docs/dist/. "$OUTPUT_DIR/.vercel/output/static/" | |
| printf '%s\n' '{"version":3}' > "$OUTPUT_DIR/.vercel/output/config.json" | |
| (cd "$OUTPUT_DIR" && zip -qr "$GITHUB_WORKSPACE/vercel.zip" .vercel) | |
| - name: Upload docs artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: vercel | |
| path: vercel.zip | |
| ci-status: | |
| name: CI Status | |
| if: always() | |
| needs: [changes, check-generated, build-binary, build-npm, build-docs, test-e2e, eval-skill, generate-patches, codemod-test] | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| steps: | |
| - name: Check CI status | |
| run: | | |
| # Check for explicit failures or cancellations in all jobs | |
| # generate-patches is skipped on PRs — that's expected | |
| # publish-nightly is excluded: it's infrastructure (GHCR push), not code quality | |
| # eval-skill is skipped on fork PRs (handled by eval-skill-fork.yml) | |
| # and when skill files didn't change — a "skipped" result is fine, we | |
| # only fail on an actual failure/cancellation. | |
| results="${{ needs.check-generated.result }} ${{ needs.build-binary.result }} ${{ needs.build-npm.result }} ${{ needs.build-docs.result }} ${{ needs.test-e2e.result }} ${{ needs.eval-skill.result }} ${{ needs.generate-patches.result }} ${{ needs.codemod-test.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-generated.result }}" == "skipped" ]]; then | |
| echo "::error::CI failed - upstream job failed causing check-generated to be skipped" | |
| exit 1 | |
| fi | |
| if [[ "${{ needs.changes.outputs.codemod }}" == "true" && "${{ needs.codemod-test.result }}" == "skipped" ]]; then | |
| echo "::error::CI failed - upstream job failed causing codemod-test to be skipped" | |
| exit 1 | |
| fi | |
| echo "CI passed" |