diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 02f2ec9..f62a559 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -3,7 +3,7 @@ name: check on: pull_request: push: - branches: [main] + branches: [main, develop] jobs: check: diff --git a/.github/workflows/main-guard.yml b/.github/workflows/main-guard.yml new file mode 100644 index 0000000..7555fd3 --- /dev/null +++ b/.github/workflows/main-guard.yml @@ -0,0 +1,35 @@ +name: 'Main: guard' + +# main is the release target and stays the default branch (it is what +# `npx skills add JarvusInnovations/specops` installs). Only the Release PR +# from develop, or a hotfix/* branch, may merge into it. This check is +# required on main, so a feature PR opened against main cannot merge; the +# companion main-retarget workflow moves such PRs onto develop on open. + +on: + pull_request: + branches: [ main ] + types: [ opened, edited, reopened, synchronize ] + +jobs: + head-is-develop: + name: PR into main comes from develop + runs-on: ubuntu-latest + steps: + - name: Check the head branch + env: + HEAD: ${{ github.head_ref }} + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + BASE_REPO: ${{ github.repository }} + run: | + if [ "$HEAD_REPO" != "$BASE_REPO" ]; then + echo "::error::PRs into main must come from this repository's develop branch (the Release PR) or a hotfix/* branch, not from a fork. Retarget this PR to develop." + exit 1 + fi + case "$HEAD" in + develop|hotfix/*) echo "ok: head is $HEAD" ;; + *) + echo "::error::PRs into main must come from develop (the Release PR) or a hotfix/* branch; this one comes from $HEAD. Retarget it to develop." + exit 1 + ;; + esac diff --git a/.github/workflows/main-retarget.yml b/.github/workflows/main-retarget.yml new file mode 100644 index 0000000..ba8ea1a --- /dev/null +++ b/.github/workflows/main-retarget.yml @@ -0,0 +1,33 @@ +name: 'Main: retarget stray PRs to develop' + +# main is the default branch, so `gh pr create` and the web UI open PRs +# against it by default. Anything that is not the Release PR (head: develop) +# or a hotfix/* branch belongs on develop; move it there on open and say so. +# +# pull_request_target runs with write permission on the base repository. +# This job never checks out or executes the PR's code; it only calls the API. + +on: + pull_request_target: + branches: [ main ] + types: [ opened ] + +permissions: + pull-requests: write + +jobs: + retarget: + if: >- + github.event.pull_request.head.repo.full_name != github.repository + || (github.event.pull_request.head.ref != 'develop' + && !startsWith(github.event.pull_request.head.ref, 'hotfix/')) + runs-on: ubuntu-latest + steps: + - name: Move the PR onto develop + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + run: | + gh pr edit "$PR" --repo "$REPO" --base develop + gh pr comment "$PR" --repo "$REPO" --body "Retargeted to \`develop\`. \`main\` only receives the \`Release: v*\` PR from \`develop\` (and \`hotfix/*\` branches); everything else merges to \`develop\` and ships with the next release. Your diff is unchanged and nothing else is needed." diff --git a/.github/workflows/release-prepare.yml b/.github/workflows/release-prepare.yml new file mode 100644 index 0000000..0a34fe3 --- /dev/null +++ b/.github/workflows/release-prepare.yml @@ -0,0 +1,20 @@ +name: 'Release: Prepare PR' + +on: + push: + branches: [ develop ] + +permissions: + contents: read + pull-requests: write + +jobs: + release-prepare: + + runs-on: ubuntu-latest + steps: + + - uses: JarvusInnovations/infra-components@channels/github-actions/release-prepare/latest + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + release-branch: main diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml new file mode 100644 index 0000000..d65269f --- /dev/null +++ b/.github/workflows/release-publish.yml @@ -0,0 +1,16 @@ +name: 'Release: Publish PR' + +on: + pull_request: + branches: [ main ] + types: [ closed ] + +jobs: + release-publish: + + runs-on: ubuntu-latest + steps: + + - uses: JarvusInnovations/infra-components@channels/github-actions/release-publish/latest + with: + github-token: ${{ secrets.BOT_GITHUB_TOKEN }} diff --git a/.github/workflows/release-validate.yml b/.github/workflows/release-validate.yml new file mode 100644 index 0000000..eaf9c58 --- /dev/null +++ b/.github/workflows/release-validate.yml @@ -0,0 +1,16 @@ +name: 'Release: Validate PR' + +on: + pull_request: + branches: [ main ] + types: [ opened, edited, reopened, synchronize ] + +jobs: + release-validate: + + runs-on: ubuntu-latest + steps: + + - uses: JarvusInnovations/infra-components@channels/github-actions/release-validate/latest + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 234a012..58a601f 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,12 @@ bun run type-check The bundle is committed and marked `linguist-generated`; commit it together with any `src/cli/` change (`bun run check` enforces this). +## Contributing and releases + +Branch from `develop` and open your PR against `develop`. `main` is the release target: the only PR that merges into it is the `Release: v*` PR that the release automation opens from `develop`, and merging that PR tags and publishes the release. A PR opened against `main` by mistake is retargeted to `develop` automatically and can't merge there anyway. + +`main` stays the default branch because it is what `npx skills add JarvusInnovations/specops` installs, so a fresh install always gets the latest release rather than unreleased work. + ## What's inside | Path | What it is |