mirror of
https://github.com/getsentry/sentry-for-ai.git
synced 2026-09-14 14:28:22 +08:00
3c9a06cf5c
Publish the shared Sentry skill library in the vendor-neutral Agent Plugins 1.0.0 layout alongside the existing client-specific builds. Validate the portable manifest, MCP configuration, package containment, and Agent Skills contract before deployment.
212 lines
8.9 KiB
YAML
212 lines
8.9 KiB
YAML
# Deploy per-agent plugin distributions to their own repositories.
|
|
#
|
|
# This repository is the single source of truth (skills + routing metadata,
|
|
# feeding skills.sentry.dev). Each AI assistant needs the plugin in a different
|
|
# shape, so this workflow builds each one and deploys it to a dedicated
|
|
# repository whose ROOT is exactly that agent's plugin:
|
|
#
|
|
# agent-plugin -> getsentry/agent-plugin
|
|
# claude -> getsentry/plugin-claude
|
|
# cursor -> getsentry/plugin-cursor
|
|
# codex -> getsentry/plugin-codex
|
|
# grok -> getsentry/plugin-grok
|
|
#
|
|
# Each plugin repository carries two rolling branches, and which one a run writes
|
|
# is the whole difference between a deploy and a release:
|
|
#
|
|
# develop every push to this repo's main, so its tip is always the latest
|
|
# build. Nothing installs from it by default; it is where a change is
|
|
# visible and testable before it ships.
|
|
# main release tags only, driven by release-plugins.yml. This is the branch
|
|
# consumers resolve -- Anthropic's marketplace pins plugin-claude by
|
|
# SHA, the installer clones plugin-cursor, and the codex/grok CLIs
|
|
# install by repository -- so it moves when we say a version is out,
|
|
# not on every merge.
|
|
#
|
|
# A release also tags the plugin repository (`v<version>`), giving each shipped
|
|
# version an addressable ref for pinning and rollback.
|
|
#
|
|
# Consumers install a distribution repository by git ref. Each job builds its
|
|
# distribution tree from this repo, then commits it onto the target branch of the
|
|
# target repo, replacing the previous contents. The five jobs target five
|
|
# different repos, so they run in parallel without contention.
|
|
#
|
|
# Cross-repo writes use a GitHub App token scoped per-job to a single plugin
|
|
# repo; the default GITHUB_TOKEN cannot push to other repositories. The app must
|
|
# be installed on the org with contents:write on the four plugin repos, its ID
|
|
# stored as the PLUGIN_DEPLOY_APP_ID variable and its private key as the
|
|
# PLUGIN_DEPLOY_KEY secret. Each target repo must already exist with `main` as
|
|
# its default branch; `develop` is branched off it on the first deploy.
|
|
|
|
name: Deploy plugins
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- "src/skills/**"
|
|
- "src/references/**"
|
|
- "src/SKILL_TREE.md"
|
|
- "src/plugins/**"
|
|
- "assets/**"
|
|
- "LICENSE"
|
|
- "*.json"
|
|
- "scripts/**"
|
|
- ".github/workflows/deploy-plugins.yml"
|
|
workflow_dispatch:
|
|
# Called by release-plugins.yml to publish a tagged tree. A tag push cannot
|
|
# drive this directly: `paths` filters do not match a push that adds no
|
|
# commits, so a tag pointing at an existing commit would silently skip the
|
|
# deploy. The release workflow calls in with an explicit ref instead.
|
|
workflow_call:
|
|
inputs:
|
|
ref:
|
|
description: Ref of this repository to build from.
|
|
type: string
|
|
required: true
|
|
target_branch:
|
|
description: Branch to publish onto in each plugin repository.
|
|
type: string
|
|
required: true
|
|
dist_tag:
|
|
description: Tag to create in each plugin repository. Empty creates none.
|
|
type: string
|
|
required: false
|
|
default: ""
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- distribution: agent-plugin
|
|
repository: agent-plugin
|
|
- distribution: claude
|
|
repository: plugin-claude
|
|
- distribution: cursor
|
|
repository: plugin-cursor
|
|
- distribution: codex
|
|
repository: plugin-codex
|
|
- distribution: grok
|
|
repository: plugin-grok
|
|
concurrency:
|
|
group: deploy-${{ matrix.repository }}-${{ inputs.target_branch || 'develop' }}
|
|
cancel-in-progress: false
|
|
steps:
|
|
- name: Checkout source
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ inputs.ref || github.sha }}
|
|
# scripts/dev-version.sh derives the develop stamp from `git describe`,
|
|
# which needs the tags and the commits since the last one.
|
|
fetch-depth: 0
|
|
|
|
- name: Mint deploy token
|
|
id: token
|
|
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
with:
|
|
app-id: ${{ vars.PLUGIN_DEPLOY_APP_ID }}
|
|
private-key: ${{ secrets.PLUGIN_DEPLOY_KEY }}
|
|
owner: getsentry
|
|
repositories: ${{ matrix.repository }}
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
|
|
- name: Build and deploy ${{ matrix.repository }}
|
|
env:
|
|
DISTRIBUTION: ${{ matrix.distribution }}
|
|
TARGET_REPO: ${{ matrix.repository }}
|
|
TARGET_BRANCH: ${{ inputs.target_branch || 'develop' }}
|
|
DIST_TAG: ${{ inputs.dist_tag || '' }}
|
|
GH_TOKEN: ${{ steps.token.outputs.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
WORKTREE="$(mktemp -d)/dist"
|
|
|
|
# Read the built commit out of the checkout rather than github.sha,
|
|
# which in a called workflow reports the caller's commit instead.
|
|
SRC_SHA="$(git rev-parse HEAD)"
|
|
|
|
# A release stamps the version being released; anything else is a
|
|
# develop build and gets labelled with its distance from the last tag.
|
|
if [[ -z "$DIST_TAG" ]]; then
|
|
PLUGIN_VERSION="$(scripts/dev-version.sh)"
|
|
export PLUGIN_VERSION
|
|
echo "::notice::stamping develop build as ${PLUGIN_VERSION}"
|
|
fi
|
|
|
|
# Clone the target repo (lands on its default branch, `main`).
|
|
git clone "https://x-access-token:${GH_TOKEN}@github.com/getsentry/${TARGET_REPO}.git" "$WORKTREE"
|
|
git -C "$WORKTREE" config user.name "github-actions[bot]"
|
|
git -C "$WORKTREE" config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
|
|
# Land on the target branch, branching it off the default branch on the
|
|
# first deploy that needs it.
|
|
if git -C "$WORKTREE" ls-remote --exit-code --heads origin "$TARGET_BRANCH" > /dev/null; then
|
|
git -C "$WORKTREE" checkout "$TARGET_BRANCH"
|
|
else
|
|
echo "::notice::${TARGET_REPO} has no ${TARGET_BRANCH} yet; creating it"
|
|
git -C "$WORKTREE" checkout -b "$TARGET_BRANCH"
|
|
fi
|
|
|
|
# Rewrite the whole tree: clear tracked content (preserve the .git
|
|
# dir), then repopulate from source via the agent's build script.
|
|
git -C "$WORKTREE" rm -rfq --ignore-unmatch .
|
|
"src/plugins/${DISTRIBUTION}/build.sh" "$WORKTREE"
|
|
|
|
# Validate the built tree against the agent's schema/validator before
|
|
# it can be deployed.
|
|
"src/plugins/${DISTRIBUTION}/validate.sh" "$WORKTREE"
|
|
|
|
# Commit only if something changed.
|
|
git -C "$WORKTREE" add -A
|
|
if git -C "$WORKTREE" diff --cached --quiet; then
|
|
echo "::notice::${TARGET_REPO} ${TARGET_BRANCH} unchanged; nothing to commit"
|
|
elif [[ -n "$DIST_TAG" ]]; then
|
|
git -C "$WORKTREE" commit -m "build: release ${DIST_TAG} from getsentry/sentry-for-ai@${SRC_SHA}"
|
|
else
|
|
git -C "$WORKTREE" commit -m "build: deploy from getsentry/sentry-for-ai@${SRC_SHA}"
|
|
fi
|
|
|
|
git -C "$WORKTREE" push origin "$TARGET_BRANCH"
|
|
echo "::notice::deployed ${TARGET_REPO} ${TARGET_BRANCH}"
|
|
|
|
if [[ -z "$DIST_TAG" ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Tag wherever the branch now points, whether or not this run made a
|
|
# commit: a release whose tree already matches main still has to end up
|
|
# tagged, and re-running a failed release has to converge on the tag
|
|
# rather than skip it.
|
|
#
|
|
# An existing tag on a different commit is a conflict, not a converged
|
|
# retry -- skipping it would leave v<version> naming something other
|
|
# than the tree that just shipped. The peeled `^{}` line is what makes
|
|
# the comparison hold for an annotated tag, whose own object sha is not
|
|
# the commit sha.
|
|
TARGET_COMMIT="$(git -C "$WORKTREE" rev-parse HEAD)"
|
|
EXISTING_TAG_COMMIT="$(git -C "$WORKTREE" ls-remote --tags origin \
|
|
"refs/tags/${DIST_TAG}" "refs/tags/${DIST_TAG}^{}" | tail -1 | cut -f1)"
|
|
|
|
if [[ -n "$EXISTING_TAG_COMMIT" ]]; then
|
|
if [[ "$EXISTING_TAG_COMMIT" != "$TARGET_COMMIT" ]]; then
|
|
echo "::error::${TARGET_REPO} ${DIST_TAG} points at ${EXISTING_TAG_COMMIT}, not the ${TARGET_COMMIT} just published"
|
|
exit 1
|
|
fi
|
|
echo "::notice::${TARGET_REPO} already tagged ${DIST_TAG}"
|
|
exit 0
|
|
fi
|
|
|
|
git -C "$WORKTREE" tag "$DIST_TAG"
|
|
git -C "$WORKTREE" push origin "$DIST_TAG"
|
|
echo "::notice::tagged ${TARGET_REPO} ${DIST_TAG}"
|