Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 58 additions & 14 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@ name: Release
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: Release tag (for example v1.1.1)
required: true
type: string
prerelease:
description: Publish to npm with the beta dist-tag
required: false
type: boolean
default: false

concurrency:
group: release-${{ github.event.release.tag_name }}
group: release-${{ github.event.release.tag_name || inputs.tag }}
cancel-in-progress: false

jobs:
Expand All @@ -14,6 +25,17 @@ jobs:
permissions:
contents: write
steps:
- name: Validate secrets
run: |
if [ -z "${{ secrets.RELEASE_SSH_PRIVATE_KEY }}" ]; then
echo "::error::Missing RELEASE_SSH_PRIVATE_KEY. Add the SSH private key you use for commit signing (same as locally)."
exit 1
fi
if [ -z "${{ secrets.NPM_TOKEN }}" ]; then
echo "::error::Missing NPM_TOKEN."
exit 1
fi

- name: Checkout main
uses: actions/checkout@v4
with:
Expand All @@ -23,7 +45,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 24
registry-url: https://registry.npmjs.org/

- name: Install dependencies
Expand All @@ -37,35 +59,57 @@ jobs:

- name: Resolve release version
id: version
env:
EVENT_NAME: ${{ github.event_name }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_PRERELEASE: ${{ github.event.release.prerelease }}
INPUT_TAG: ${{ github.event.inputs.tag }}
INPUT_PRERELEASE: ${{ github.event.inputs.prerelease }}
run: |
TAG="${{ github.event.release.tag_name }}"
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
TAG="$INPUT_TAG"
if [ "$INPUT_PRERELEASE" = "true" ]; then
IS_PRERELEASE=true
else
IS_PRERELEASE=false
fi
else
TAG="$RELEASE_TAG"
if [ "$RELEASE_PRERELEASE" = "true" ]; then
IS_PRERELEASE=true
else
IS_PRERELEASE=false
fi
fi

VERSION="${TAG#v}"
if [ -z "$VERSION" ] || [ "$VERSION" = "$TAG" ]; then
echo "::error::Release tag must start with 'v' (for example v1.2.0)."
exit 1
fi

echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
if [ "$IS_PRERELEASE" = "true" ]; then
echo "npm-dist-tag=beta" >> "$GITHUB_OUTPUT"
else
echo "npm-dist-tag=latest" >> "$GITHUB_OUTPUT"
fi

# main requires verified commits; unsigned github-actions[bot] commits were the
# original release workflow failure mode
- name: Import release signing key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.RELEASE_GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.RELEASE_GPG_PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: Configure SSH commit signing
run: |
mkdir -p ~/.ssh
printf '%s\n' "${{ secrets.RELEASE_SSH_PRIVATE_KEY }}" > ~/.ssh/release_signing_key
chmod 600 ~/.ssh/release_signing_key
ssh-keygen -y -f ~/.ssh/release_signing_key > ~/.ssh/release_signing_key.pub
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/release_signing_key.pub
git config --global commit.gpgsign true

- name: Bump version and commit
run: |
RELEASE_NAME="${{ vars.RELEASE_USER_NAME }}"
if [ -z "$RELEASE_NAME" ]; then
RELEASE_NAME="GSF Release Bot"
RELEASE_NAME="Narek Hovhannisyan"
fi
git config user.name "$RELEASE_NAME"
git config user.email "${{ vars.RELEASE_USER_EMAIL }}"
Expand Down
17 changes: 9 additions & 8 deletions github-processes.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#### `main`
- target branch for PRs and releases
- PRs can be merged into `main` with two core team reviews, one being QA
- pushing directly to `main` is forbidden - all changes are by PR (except the automated version-bump commit from the release workflow)
- pushing directly to `main` is forbidden - all changes are by PR (except the signed version-bump commit from the release workflow)
- PRs will not be merged if they do not pass CI/CD
- npm packages are published from `main` when a GitHub Release is published

Expand Down Expand Up @@ -76,17 +76,18 @@ These repositories still use two branches: `main` and `release`.
2. In GitHub, go to **Releases → Draft a new release**.
3. Create a tag on `main` using the `v` prefix (for example `v1.2.0` or `v1.2.0-beta.0`).
4. Check **Set as a pre-release** for beta releases. This publishes to npm with the `beta` dist-tag; stable releases use `latest`.
5. Publish the release. CI runs tests, bumps `package.json` / `package-lock.json` on `main`, commits the change, and publishes to npm.
5. Publish the release. CI runs tests, bumps `package.json` / `package-lock.json` on `main`, pushes a signed commit, and publishes to npm.

The version-bump commit is GPG-signed in CI so it satisfies branch protection on `main`. Configure these repository settings first:
The version-bump commit uses the same SSH signing key as local commits. Configure these repository settings first:

- **Variable** `RELEASE_USER_EMAIL` — email on the GitHub account that owns the signing key
- **Variable** `RELEASE_USER_NAME` — name used for release commits (for example `GSF Release Bot`)
- **Secret** `RELEASE_GPG_PRIVATE_KEY` — armored GPG private key for that account
- **Secret** `RELEASE_GPG_PASSPHRASE` — key passphrase, if any (omit or leave empty if none)
- **Variable** `RELEASE_USER_EMAIL` — email on your GitHub account (for example `narhovhannisian@gmail.com`)
- **Variable** `RELEASE_USER_NAME` — optional; name used for release commits
- **Secret** `RELEASE_SSH_PRIVATE_KEY` — the same SSH **private** key you use locally for commit signing
- **Secret** `NPM_TOKEN` — npm publish token

The matching GPG public key must be added to the GitHub account for `RELEASE_USER_EMAIL`.
The matching SSH **public** key must be added to GitHub under **Settings → SSH and GPG keys → New SSH key → Signing Key**.

To retry a failed release without publishing a new GitHub Release, go to **Actions → Release → Run workflow**, enter the tag (for example `v1.1.1`), and run it.

We use [semantic versioning](https://semver.org/) to number our releases.

Expand Down
Loading