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
24 changes: 23 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ on:
required: false
type: boolean
default: false
secrets:
RELEASE_APP_ID:
description: |
GitHub App ID used to mint a short-lived token that can bypass branch
protection on `main`. Required when `@semantic-release/git` is in the
plugins list and the default branch has a "PRs required" ruleset.
Optional; if unset, the job falls back to the built-in GITHUB_TOKEN
(which can't push to protected branches).
required: false
RELEASE_APP_PRIVATE_KEY:
description: |
PEM private key for the GitHub App referenced by RELEASE_APP_ID.
Required if RELEASE_APP_ID is set.
required: false

jobs:
publish:
Expand All @@ -49,6 +63,14 @@ jobs:
issues: write
pull-requests: write
steps:
- name: Mint release token (GitHub App)
id: app-token
if: ${{ secrets.RELEASE_APP_ID != '' }}
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}

- name: Checkout caller repo
uses: actions/checkout@v4
with:
Expand Down Expand Up @@ -85,4 +107,4 @@ jobs:
extra_plugins: ${{ inputs.extra-plugins }}
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
29 changes: 29 additions & 0 deletions docs/secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ What secrets the reusables need, where they should live, and how to configure th
| `VERCEL_TOKEN` | `deploy.yml` | **org** | Vercel personal or org-scoped token |
| `VERCEL_ORG_ID` | `deploy.yml` | **org** | Same across all Refokus Vercel projects |
| `VERCEL_PROJECT_ID` | `deploy.yml` | **repo** | Unique per Vercel project |
| `RELEASE_APP_ID` | `release.yml` | **org or repo** | Optional. GitHub App ID for branch-protection bypass on `main`. Required when using `@semantic-release/git` against a branch with a "PRs required" ruleset. |
| `RELEASE_APP_PRIVATE_KEY` | `release.yml` | **org or repo** | Optional. PEM private key paired with `RELEASE_APP_ID`. |

`GITHUB_TOKEN` covers what we used to need a PAT for: cloning the public `refokus-agency/platform` reusables (no auth needed for public repos), authenticating `.npmrc` for `@refokus-agency/*` packages on GitHub Packages, and tagging/publishing in `release.yml`. The caller declares the scopes via `permissions:` (`contents`, `packages`).

Expand Down Expand Up @@ -92,6 +94,33 @@ Unique per Vercel project. Configured **per repo**.

You can also find the project ID in the Vercel dashboard: Project → Settings → General → "Project ID".

## Release bypass with a GitHub App

`release.yml` runs `semantic-release`, which by default uses `@semantic-release/git` to push a commit (CHANGELOG, version bump) back to `main`. The built-in `GITHUB_TOKEN` cannot push to `main` if the branch is protected by a ruleset that requires PRs — pushes are rejected with `GH013: Repository rule violations found`.

The fix is to mint a short-lived token from a GitHub App that is on the ruleset's bypass list, and use that token for `semantic-release`. This is scoped to the release job only — Dependabot PR runs use `pull_request` workflows that don't touch this token, so this setup doesn't reintroduce the secret-exposure pattern that pushed the project off PATs.

### Setup

1. **Create a GitHub App** at the org level: `https://github.com/organizations/refokus-agency/settings/apps/new`.
- Repository permissions: `contents: read and write`, `issues: read and write`, `pull-requests: read and write`, `metadata: read`.
- Webhook: disabled.
- Where can be installed: **Only this account** (`refokus-agency`).
2. **Generate a private key** (`.pem`) on the app's settings page and download it.
3. **Install the app** on the repos that need release bypass.
4. **Add the app to the bypass list** of the org-level ruleset on `main`: `https://github.com/organizations/refokus-agency/settings/rules` → ruleset → Bypass list → Add bypass → select the app → mode "Always". Requires org-owner permissions.
5. **Set the secrets** on the repo (or org level if you want to share the app across many repos):
```bash
gh secret set RELEASE_APP_ID --repo <owner>/<repo> --body "<app-id>"
gh secret set RELEASE_APP_PRIVATE_KEY --repo <owner>/<repo> < path/to/key.pem
```
On Windows / PowerShell:
```powershell
Get-Content path/to/key.pem -Raw | gh secret set RELEASE_APP_PRIVATE_KEY --repo <owner>/<repo>
```

If both secrets are present when `release.yml` runs, the job mints an installation token and uses it as `GITHUB_TOKEN` for `semantic-release`. If either is missing, the job falls back to the built-in `GITHUB_TOKEN` — fine for repos without branch protection or that don't use `@semantic-release/git`.

## Rotating secrets

When a Vercel token expires or is compromised:
Expand Down
18 changes: 18 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ Common failure modes when using the centralized workflows, and how to fix them.
- Verify `.releaserc` (or `release` key in `package.json`) exists and configures the GitHub Packages plugin.
- Check the logs — semantic-release is verbose about why it didn't release.

## Semantic-release fails with "GH013: Repository rule violations found"

**Symptoms:** the release job fails at the `prepare` step of `@semantic-release/git` with:

```
remote: error: GH013: Repository rule violations found for refs/heads/main.
- Changes must be made through a pull request.
```

**Cause:** `@semantic-release/git` tries to push the version-bump + CHANGELOG commit directly to `main`, but the branch is protected by a ruleset that requires PRs. The built-in `GITHUB_TOKEN` cannot bypass that ruleset.

**Fix:** configure a GitHub App with branch-protection bypass and pass its credentials to the release job. See [secrets.md → Release bypass with a GitHub App](secrets.md#release-bypass-with-a-github-app) for the full setup.

If you don't want to set up a GitHub App, two alternatives:

- Remove `@semantic-release/git` from the plugins list in `.releaserc.json`. The release still creates a tag, GitHub Release, and publishes the package — only the version-bump commit and CHANGELOG.md aren't pushed back to the repo.
- Switch to `release-please` (PR-based, no direct push to `main`). `platform` itself uses release-please for the same reason.

## Caching seems ineffective

**Symptoms:** every run re-downloads all dependencies.
Expand Down