Skip to content

fix(ci): fix set-commits --auto and add checkout/URL to sentry-release workflow #2978

fix(ci): fix set-commits --auto and add checkout/URL to sentry-release workflow

fix(ci): fix set-commits --auto and add checkout/URL to sentry-release workflow #2978

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
# 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 }}
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@v6
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
skill:
- 'src/**'
- 'docs/**'
- 'plugins/**'
- 'script/generate-skill.ts'
- 'script/eval-skill.ts'
- 'test/skill-eval/**'
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 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 }}
ref: ${{ github.head_ref || github.ref_name }}
- uses: oven-sh/setup-bun@v2
- uses: actions/cache@v5
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: Generate API Schema
run: bun run generate:schema
- name: Check skill files
id: check-skill
run: bun run check:skill
continue-on-error: true
- name: Check command docs
id: check-docs
run: bun run check:command-docs
continue-on-error: true
- name: Auto-commit regenerated files
if: (steps.check-skill.outcome == 'failure' || steps.check-docs.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/ docs/public/.well-known/skills/index.json docs/src/content/docs/commands/
git diff --cached --quiet || (git commit -m "chore: regenerate skill files and command docs" && git push)
- name: Fail for fork PRs with stale generated files
if: (steps.check-skill.outcome == 'failure' || steps.check-docs.outcome == 'failure') && steps.token.outcome != 'success'
run: |
echo "::error::Generated files are out of date. Run 'bun run generate:skill' and 'bun run generate:command-docs' locally and commit the result."
exit 1
eval-skill:
name: Eval SKILL.md
needs: [changes]
if: needs.changes.outputs.skill == 'true'
runs-on: ubuntu-latest
steps:
# For fork PRs: check if eval has already passed via commit status
- name: Detect fork
id: detect-fork
run: |
if [[ "${{ github.event_name }}" == "pull_request" && "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then
echo "is_fork=true" >> "$GITHUB_OUTPUT"
fi
- name: Check fork eval status
if: steps.detect-fork.outputs.is_fork == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
SHA="${{ github.event.pull_request.head.sha }}"
STATUS=$(gh api "repos/${{ github.repository }}/commits/$SHA/statuses" \
--jq '[.[] | select(.context == "eval-skill/fork")] | first | .state // "none"')
if [[ "$STATUS" != "success" ]]; then
echo "::error::Fork PR modifies skill files but eval has not passed for commit $SHA."
echo "::error::A maintainer must review the code and add the 'eval-skill' label."
exit 1
fi
echo "Fork eval passed for $SHA"
# For internal PRs: run the eval directly
- uses: actions/checkout@v6
if: steps.detect-fork.outputs.is_fork != 'true'
- uses: oven-sh/setup-bun@v2
if: steps.detect-fork.outputs.is_fork != 'true'
- uses: actions/cache@v5
if: steps.detect-fork.outputs.is_fork != 'true'
id: cache
with:
path: node_modules
key: node-modules-${{ hashFiles('bun.lock', 'patches/**') }}
- if: steps.detect-fork.outputs.is_fork != 'true' && steps.cache.outputs.cache-hit != 'true'
run: bun install --frozen-lockfile
- name: Eval SKILL.md
if: steps.detect-fork.outputs.is_fork != 'true'
run: bun run eval:skill
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Upload eval results
if: always() && steps.detect-fork.outputs.is_fork != 'true'
uses: actions/upload-artifact@v7
with:
name: skill-eval-results
path: test/skill-eval/results.json
lint:
name: Lint & Typecheck
needs: [changes]
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
- uses: actions/cache@v5
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 generate:schema
- run: bun run lint
- run: bun run typecheck
- run: bun run check:deps
- run: bun run check:errors
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: oven-sh/setup-bun@v2
- uses: actions/cache@v5
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: Generate API Schema
run: bun run generate:schema
- name: Unit Tests
run: bun run test:unit
- name: Isolated Tests
run: bun run test:isolated --coverage --coverage-reporter=lcov --coverage-dir=coverage-isolated
- name: Merge Coverage Reports
run: bun run script/merge-lcov.ts coverage/lcov.info coverage-isolated/lcov.info > coverage/merged.lcov
- name: Coverage Report
uses: getsentry/codecov-action@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: ./coverage/merged.lcov
informational-patch: ${{ github.event_name == 'push' }}
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@v6
- uses: oven-sh/setup-bun@v2
- uses: actions/cache@v5
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 }}
# Sourcemap upload to Sentry (non-fatal, skipped when token is absent)
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
# 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@v7
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@v7
with:
name: sentry-${{ matrix.target }}-gz
path: 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
steps:
- name: Install ORAS CLI
# Only needed on main (to fetch previous nightly from GHCR)
if: github.ref == 'refs/heads/main'
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: Install zig-bsdiff
run: |
VERSION=0.1.19
EXPECTED_SHA256="9f1ac75a133ee09883ad2096a86d57791513de5fc6f262dfadee8dcee94a71b9"
TARBALL="zig-bsdiff-linux-x64.tar.gz"
curl -sfLo "$TARBALL" "https://github.com/blackboardsh/zig-bsdiff/releases/download/v${VERSION}/${TARBALL}"
echo "${EXPECTED_SHA256} ${TARBALL}" | sha256sum -c -
tar -xz -C /usr/local/bin < "$TARBALL"
rm "$TARBALL"
- 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: Download previous nightly binaries (main)
if: github.ref == 'refs/heads/main'
run: |
VERSION="${{ needs.changes.outputs.nightly-version }}"
REPO="ghcr.io/getsentry/cli"
echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io -u ${{ github.actor }} --password-stdin
# Find previous nightly tag
TAGS=$(oras repo tags "${REPO}" 2>/dev/null | grep '^nightly-' | sort -V || echo "")
PREV_TAG=""
for tag in $TAGS; do
# Current tag may not exist yet (publish-nightly hasn't run),
# but that's fine — we want the latest existing nightly tag
if [ "$tag" = "nightly-${VERSION}" ]; then
break
fi
PREV_TAG="$tag"
done
if [ -z "$PREV_TAG" ]; then
echo "No previous versioned nightly found, skipping patches"
exit 0
fi
echo "HAS_PREV=true" >> "$GITHUB_ENV"
echo "Previous nightly: ${PREV_TAG}"
# Download and decompress previous nightly binaries
mkdir -p old-binaries
PREV_MANIFEST_JSON=$(oras manifest fetch "${REPO}:${PREV_TAG}")
echo "$PREV_MANIFEST_JSON" | jq -r '.layers[] | select(.annotations["org.opencontainers.image.title"] | endswith(".gz")) | .annotations["org.opencontainers.image.title"] + " " + .digest' | while read -r filename digest; do
basename="${filename%.gz}"
echo " Downloading previous ${basename}..."
oras blob fetch "${REPO}@${digest}" --output "old-binaries/${filename}"
gunzip -f "old-binaries/${filename}"
done
- name: Download previous release binaries (release/**)
if: startsWith(github.ref, 'refs/heads/release/')
env:
GH_TOKEN: ${{ github.token }}
run: |
PREV_TAG=$(gh api "repos/${{ github.repository }}/releases?per_page=5" \
--jq '[.[] | select(.prerelease == false and .draft == false)] | .[0].tag_name // empty')
if [ -z "$PREV_TAG" ]; then
echo "No previous stable release found — skipping patch generation"
exit 0
fi
echo "HAS_PREV=true" >> "$GITHUB_ENV"
echo "Previous release: ${PREV_TAG}"
shopt -s nullglob
mkdir -p old-binaries
for gz in new-binaries/*.gz; do
name=$(basename "${gz%.gz}")
echo " Downloading ${name}.gz from ${PREV_TAG}..."
gh release download "${PREV_TAG}" \
--repo "${{ github.repository }}" \
--pattern "${name}.gz" \
--dir old-binaries || echo " Warning: not found, skipping"
done
gunzip old-binaries/*.gz 2>/dev/null || true
- name: Generate delta patches
if: env.HAS_PREV == 'true'
run: |
mkdir -p patches
GENERATED=0
for new_binary in new-binaries/sentry-*; do
name=$(basename "$new_binary")
case "$name" in *.gz) continue ;; esac
old_binary="old-binaries/${name}"
[ -f "$old_binary" ] || continue
echo "Generating patch: ${name}.patch"
bsdiff "$old_binary" "$new_binary" "patches/${name}.patch" --use-zstd
patch_size=$(stat --printf='%s' "patches/${name}.patch")
new_size=$(stat --printf='%s' "$new_binary")
ratio=$(awk "BEGIN { printf \"%.1f\", ($patch_size / $new_size) * 100 }")
echo " ${patch_size} bytes (${ratio}% of binary)"
GENERATED=$((GENERATED + 1))
done
echo "Generated ${GENERATED} patches"
- name: Upload patch artifacts
if: env.HAS_PREV == '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: 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 binaries 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
- name: Tag versioned nightly
# Create an immutable versioned tag via zero-copy oras tag.
# This enables patch chain resolution to find specific nightly versions.
run: |
VERSION="${{ needs.changes.outputs.nightly-version }}"
oras tag ghcr.io/getsentry/cli:nightly "nightly-${VERSION}"
- name: Push delta patches to GHCR
# Upload pre-generated patches from generate-patches job as a
# :patch-<version> manifest with SHA-256 annotations.
# Failure here is non-fatal — delta upgrades are optional.
if: steps.download-patches.outcome == 'success'
continue-on-error: true
run: |
VERSION="${{ needs.changes.outputs.nightly-version }}"
REPO="ghcr.io/getsentry/cli"
# Compute SHA-256 annotations from new binaries and collect patch files
shopt -s nullglob
ANNOTATIONS=""
PATCH_FILES=""
for patch_file in patches/*.patch; do
basename_patch=$(basename "$patch_file" .patch)
new_binary="binaries/${basename_patch}"
if [ -f "$new_binary" ]; then
sha256=$(sha256sum "$new_binary" | cut -d' ' -f1)
ANNOTATIONS="${ANNOTATIONS} --annotation sha256-${basename_patch}=${sha256}"
fi
PATCH_FILES="${PATCH_FILES} $(basename "$patch_file")"
done
if [ -z "$PATCH_FILES" ]; then
echo "No patches to push"
exit 0
fi
# Find from-version by listing GHCR nightly tags
TAGS=$(oras repo tags "${REPO}" 2>/dev/null | grep '^nightly-' | sort -V || echo "")
PREV_TAG=""
for tag in $TAGS; do
if [ "$tag" = "nightly-${VERSION}" ]; then break; fi
PREV_TAG="$tag"
done
if [ -z "$PREV_TAG" ]; then
echo "No previous nightly tag found, skipping patch push"
exit 0
fi
PREV_VERSION="${PREV_TAG#nightly-}"
cd patches
eval oras push "${REPO}:patch-${VERSION}" \
--artifact-type application/vnd.sentry.cli.patch \
--annotation "from-version=${PREV_VERSION}" \
${ANNOTATIONS} \
${PATCH_FILES}
echo "Pushed patch manifest: patch-${VERSION} (from ${PREV_VERSION})"
test-e2e:
name: E2E Tests
needs: [build-binary]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
- uses: actions/cache@v5
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@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: bun run generate:schema
- 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@v6
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
- uses: actions/cache@v5
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@v7
with:
name: npm-package
path: "*.tgz"
build-docs:
name: Build Docs
needs: [lint]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- 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@v7
with:
name: gh-pages
path: gh-pages.zip
ci-status:
name: CI Status
if: always()
needs: [changes, check-skill, eval-skill, build-binary, build-npm, build-docs, test-e2e, generate-patches, publish-nightly]
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Check CI status
run: |
# Check for explicit failures or cancellations in all jobs
# generate-patches and publish-nightly are skipped on PRs — that's expected
results="${{ needs.check-skill.result }} ${{ needs.eval-skill.result }} ${{ needs.build-binary.result }} ${{ needs.build-npm.result }} ${{ needs.build-docs.result }} ${{ needs.test-e2e.result }} ${{ needs.generate-patches.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
if [[ "${{ needs.changes.outputs.skill }}" == "true" && "${{ needs.eval-skill.result }}" == "skipped" ]]; then
echo "::error::CI failed - upstream job failed causing eval-skill to be skipped"
exit 1
fi
echo "CI passed"