diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6605b333..d4afd6e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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: @@ -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: @@ -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 @@ -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 }}" diff --git a/github-processes.md b/github-processes.md index 21fd6259..7b8219ce 100644 --- a/github-processes.md +++ b/github-processes.md @@ -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 @@ -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.