mirror of
https://github.com/getsentry/sentry-for-ai.git
synced 2026-09-14 14:28:22 +08:00
c62874aedc
Resolves conflicts against the harness build-on-demand refactor (#321), the develop/main deploy split (#317), and the flowmark reformat (#315/#316): - harnesses/index.ts: register the two OpenCode harnesses inside buildHarnesses() and drop their module-level constants, matching the single-construction-path refactor - deploy-plugins.yml: keep main's develop/main branch semantics with the job counts updated for six plugin repos - AGENTS.md / READMEs: fold the OpenCode V1/V2 mentions into main's reflowed prose Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
217 lines
9.1 KiB
YAML
217 lines
9.1 KiB
YAML
# Deploy per-agent 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 integration in a
|
|
# different shape, so this workflow builds each one and deploys it to a
|
|
# dedicated repository whose root is exactly that agent's distribution:
|
|
#
|
|
# claude -> getsentry/plugin-claude
|
|
# cursor -> getsentry/plugin-cursor
|
|
# codex -> getsentry/plugin-codex
|
|
# grok -> getsentry/plugin-grok
|
|
# opencode -> getsentry/plugin-opencode
|
|
# opencode2 -> getsentry/plugin-opencode2
|
|
#
|
|
# 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.
|
|
#
|
|
# Marketplaces and installers consume `getsentry/plugin-<agent>` by git ref. Each
|
|
# job builds its agent's tree from this repo, then commits it onto the target
|
|
# branch of the target repo, replacing the previous contents. The six jobs target
|
|
# six 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 six 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:
|
|
agent: [claude, cursor, codex, grok, opencode, opencode2]
|
|
concurrency:
|
|
group: deploy-plugin-${{ matrix.agent }}-${{ 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: plugin-${{ matrix.agent }}
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
|
|
- name: Set up Node for OpenCode validation
|
|
if: matrix.agent == 'opencode' || matrix.agent == 'opencode2'
|
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Install OpenCode V1 CLI
|
|
if: matrix.agent == 'opencode'
|
|
run: npm install -g opencode-ai@1.18.0
|
|
|
|
- name: Install OpenCode V2 CLI
|
|
if: matrix.agent == 'opencode2'
|
|
run: npm install -g @opencode-ai/cli@0.0.0-next-16420
|
|
|
|
- name: Build and deploy plugin-${{ matrix.agent }}
|
|
env:
|
|
AGENT: ${{ matrix.agent }}
|
|
TARGET_BRANCH: ${{ inputs.target_branch || 'develop' }}
|
|
DIST_TAG: ${{ inputs.dist_tag || '' }}
|
|
GH_TOKEN: ${{ steps.token.outputs.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
TARGET_REPO="plugin-${AGENT}"
|
|
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/${AGENT}/build.sh" "$WORKTREE"
|
|
|
|
# Validate the built tree against the agent's schema/validator before
|
|
# it can be deployed.
|
|
"src/plugins/${AGENT}/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}"
|