Skip to content

Merge pull request #1946 from AxiomCodeAI/dev #30

Merge pull request #1946 from AxiomCodeAI/dev

Merge pull request #1946 from AxiomCodeAI/dev #30

Workflow file for this run

# ─────────────────────────────────────────────────────────────────────────────
# Every version that lands on main gets a tag and a draft release, and dev is
# brought up to date with main (the sync-dev job at the end).
#
# A push to main whose version has no tag yet is a release. CI on that push builds
# the engines on all five platforms and runs the five-platform e2e; only when that
# run is GREEN does this workflow tag the commit it tested v<version> and open a
# DRAFT GitHub release with notes generated
# from the pull requests since the previous tag. Nothing is published here.
#
# Publishing is a person's decision: review the draft, then press Publish. That
# `release: published` event runs publish-npm.yml, which ships the binaries and
# the tarball of that same green CI run: nothing is compiled twice (#1350).
#
# A version that is already tagged finds its tag present and does nothing.
# ─────────────────────────────────────────────────────────────────────────────
name: release
on:
push:
branches: [main] # sync-dev
workflow_run: # tag: after CI on main finished
workflows: [CI]
types: [completed]
branches: [main]
workflow_dispatch:
concurrency:
group: release-main
cancel-in-progress: false
permissions:
contents: write
jobs:
tag:
# the CI run of a PUSH to main, and only a green one: a red release build drafts nothing
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'workflow_run' && github.event.workflow_run.event == 'push' &&
github.event.workflow_run.conclusion == 'success')
runs-on: ubuntu-24.04
env:
SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.SHA }}
fetch-depth: 0
- name: the bot this job writes as
id: bot
uses: ./.github/actions/bot
with:
app-id: ${{ vars.AXIOMCODE_BOT_APP_ID }}
private-key: ${{ secrets.AXIOMCODE_BOT_PRIVATE_KEY }}
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: every manifest carries the same version
run: node .github/scripts/version.mjs check
- name: tag and draft the release
env:
GH_TOKEN: ${{ steps.bot.outputs.token }}
BOT_NAME: ${{ steps.bot.outputs.name }}
BOT_EMAIL: ${{ steps.bot.outputs.email }}
run: |
set -euo pipefail
version="$(node .github/scripts/version.mjs get)"
tag="v$version"
if git ls-remote --exit-code --tags origin "refs/tags/$tag" >/dev/null; then
echo "$tag already exists — this push did not change the version"
exit 0
fi
prerelease=""
case "$version" in *-*) prerelease="--prerelease";; esac
# The previous tag bounds the generated notes; the first release has none.
prev="$(git tag --list 'v*' --sort=-v:refname | head -1)"
start=""; [ -n "$prev" ] && start="--notes-start-tag $prev"
# The tag is pushed here rather than left to the release: a DRAFT release
# does not create its tag until it is published, so the check above would
# never see it and every later push would draft again. A tag pushed with
# the bot's token starts no workflow here either: nothing runs on a tag push, and
# publishing waits for a person to publish the draft.
git config user.name "$BOT_NAME"
git config user.email "$BOT_EMAIL"
git tag -a "$tag" -m "$tag" "$SHA"
git -c http.https://github.com/.extraheader= push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "refs/tags/$tag"
gh release create "$tag" --draft --verify-tag --title "$tag" \
--generate-notes $start $prerelease
echo "::notice::drafted $tag — review it under Releases and publish it to ship to npm"
# ── keep dev on top of main ─────────────────────────────────────────────────
# A promotion from dev lands on main as a merge commit that dev does not have
# yet, and a hotfix straight to main is a commit dev lacks too; merging main
# back into dev records that they are already there. A promotion merges cleanly (same content both sides); a hotfix
# that went straight to main and touches lines dev has since changed does not,
# and then nothing is pushed: an issue says how to resolve it by hand.
sync-dev:
if: github.event_name == 'push'
runs-on: ubuntu-24.04
permissions:
contents: write
issues: write
steps:
- uses: actions/checkout@v4
with:
ref: dev
fetch-depth: 0
- name: the bot this job writes as
id: bot
uses: ./.github/actions/bot
with:
app-id: ${{ vars.AXIOMCODE_BOT_APP_ID }}
private-key: ${{ secrets.AXIOMCODE_BOT_PRIVATE_KEY }}
- name: merge main into dev
id: merge
env:
BOT_TOKEN: ${{ steps.bot.outputs.token }}
BOT_NAME: ${{ steps.bot.outputs.name }}
BOT_EMAIL: ${{ steps.bot.outputs.email }}
run: |
set -uo pipefail
git config user.name "$BOT_NAME"
git config user.email "$BOT_EMAIL"
git fetch origin main
if git merge-base --is-ancestor origin/main HEAD; then
echo "dev already contains main"; exit 0
fi
if git merge --no-edit -m "Merge main into dev (${GITHUB_SHA::8})" origin/main; then
git -c http.https://github.com/.extraheader= push "https://x-access-token:${BOT_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:dev
echo "dev now contains main at ${GITHUB_SHA::8}"
else
git diff --name-only --diff-filter=U > /tmp/conflicts.txt
git merge --abort
echo "conflict=1" >> "$GITHUB_OUTPUT"
echo "::error::main does not merge cleanly into dev"
exit 1
fi
# One open issue at most: a later conflicting push comments on it.
- name: say how to resolve it
if: failure() && steps.merge.outputs.conflict == '1'
env:
GH_TOKEN: ${{ steps.bot.outputs.token }}
run: |
set -uo pipefail
title="main does not merge cleanly into dev"
{
echo "Merging main (${GITHUB_SHA::8}) into dev conflicts in:"
echo; echo '```'; cat /tmp/conflicts.txt; echo '```'; echo
echo "Resolve it locally (dev takes direct pushes):"
echo; echo '```'
echo "git fetch origin && git checkout dev && git pull"
echo "git merge origin/main"
echo "# fix the files above, then"
echo "git add -A && git commit && git push origin dev"
echo '```'; echo
echo "Close this once dev is pushed; the next push to main retries on its own."
echo; echo "Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
} > /tmp/body.md
existing="$(gh issue list --state open --search "\"$title\" in:title" --json number --jq '.[0].number' || true)"
if [ -n "$existing" ]; then
gh issue comment "$existing" --body-file /tmp/body.md
else
gh issue create --title "$title" --body-file /tmp/body.md
fi