feat: race two sandboxes on one trigger and get two pull requests (is… #14
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
| # Publish @edgehero/pi-dispatch-admin (npm) and cut its GitHub Release when admin/package.json's version | |
| # changes on main. This is the EXTENSION's pipeline: it tags admin-v*. The whole TOOL's releases (v*) are cut | |
| # by repo-release.yml off the ROOT package.json version — the two never share a tag. | |
| # | |
| # main is branch-protected (PR + 1 approving review required), so a push here is an already-approved merge — | |
| # the human gate is the merge, not this workflow (consistent with CONST-MERGE-NEVER-AUTOMATIC: nothing here | |
| # merges anything). Publishing is idempotent: it fires only when admin/package.json's version is not already | |
| # on npm, so re-runs, reverts, and unrelated pushes are no-ops. Bump the version in your PR to ship a release. | |
| # | |
| # Required repo secret: NPM_TOKEN — an npm *automation* (or granular, "bypass 2FA") token that can publish to | |
| # the @edgehero scope. Interactive 2FA cannot run in CI; the token is how publishing works here. | |
| # Optional repo secret: ANTHROPIC_API_KEY — enables AI-written release notes; without it, the release falls | |
| # back to a plain commit list. Optional repo variable: RELEASE_MODEL (default: claude-sonnet-5). | |
| name: release | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "admin/**" | |
| - ".github/workflows/release.yml" | |
| - ".github/scripts/release-notes.mjs" | |
| # Manual re-run (available once this file is on the default branch): re-runs are safe — publish/release | |
| # skip when the version is already on npm / the tag exists. | |
| workflow_dispatch: {} | |
| permissions: | |
| contents: write # create tags + releases | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # full history for the release-notes commit range | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22.19" | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Install | |
| run: npm ci | |
| - name: Test the package being shipped | |
| run: npm run test --workspace admin | |
| - name: Resolve version + decide publish/release | |
| id: v | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| V=$(node -p "require('./admin/package.json').version") | |
| NAME=$(node -p "require('./admin/package.json').name") | |
| echo "version=$V" >> "$GITHUB_OUTPUT" | |
| echo "name=$NAME" >> "$GITHUB_OUTPUT" | |
| if npm view "$NAME@$V" version >/dev/null 2>&1; then | |
| echo "publish=no" >> "$GITHUB_OUTPUT"; echo "$NAME@$V already on npm — skip publish" | |
| else | |
| echo "publish=yes" >> "$GITHUB_OUTPUT" | |
| fi | |
| # The admin EXTENSION's GitHub releases are tagged admin-v* so the tool's own releases own the v* | |
| # namespace (see repo-release.yml). npm publish above is unaffected — it keys off the package version. | |
| if gh release view "admin-v$V" >/dev/null 2>&1; then | |
| echo "release=no" >> "$GITHUB_OUTPUT"; echo "release admin-v$V already exists — skip release" | |
| else | |
| echo "release=yes" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Publish to npm | |
| if: steps.v.outputs.publish == 'yes' | |
| run: npm publish --workspace admin --access public | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| - name: Build release notes | |
| if: steps.v.outputs.release == 'yes' | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| RELEASE_MODEL: ${{ vars.RELEASE_MODEL }} | |
| # This pipeline ships the EXTENSION; repo-release.yml passes the tool's own name for the v* line. | |
| PRODUCT: "@edgehero/pi-dispatch-admin" | |
| PRODUCT_DESC: the operator console for pi-dispatch, shipped as a pi extension | |
| run: | | |
| PREV=$(git tag --list 'admin-v*' --sort=-v:refname | head -n1) | |
| RANGE=${PREV:+$PREV..HEAD} | |
| COMMITS=$(git log $RANGE --no-merges --pretty=format:'- %s' -- admin/ | head -n 60) | |
| if [ -n "$ANTHROPIC_API_KEY" ] && COMMITS="$COMMITS" VERSION="v${{ steps.v.outputs.version }}" \ | |
| node .github/scripts/release-notes.mjs > /tmp/notes.md 2>/tmp/notes.err; then | |
| echo "release notes: AI-written" | |
| else | |
| echo "release notes: AI unavailable ($(head -n1 /tmp/notes.err 2>/dev/null)) — using commit list" | |
| # Capped like the AI notes are, and the remainder counted rather than dropped silently. | |
| TOTAL=$(printf '%s\n' "$COMMITS" | wc -l | tr -d ' ') | |
| { | |
| echo "### Changes"; echo | |
| printf '%s\n' "$COMMITS" | head -n 12 | |
| if [ "$TOTAL" -gt 12 ]; then printf -- '- …and %s more\n' "$((TOTAL - 12))"; fi | |
| } > /tmp/notes.md | |
| fi | |
| # Appended on BOTH paths -- this link is what earns the brevity above. | |
| if [ -n "$PREV" ]; then | |
| printf '\n**Full changelog**: %s/%s/compare/%s...admin-v%s\n' \ | |
| "${{ github.server_url }}" "${{ github.repository }}" "$PREV" "${{ steps.v.outputs.version }}" >> /tmp/notes.md | |
| fi | |
| - name: Create GitHub Release | |
| if: steps.v.outputs.release == 'yes' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh release create "admin-v${{ steps.v.outputs.version }}" \ | |
| --title "@edgehero/pi-dispatch-admin v${{ steps.v.outputs.version }}" \ | |
| --notes-file /tmp/notes.md |