From c72fe7cfbd8a5f677329e9f2d96a02e7a190ab18 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 21 Sep 2026 18:46:06 -0400 Subject: [PATCH 1/5] feat(release-please): add config location action Signed-off-by: Yordis Prieto --- .github/release-please-config.json | 4 ++ .github/release-please-manifest.json | 3 +- .github/workflows/ci.yml | 3 + actions/release-please/README.md | 10 ++++ actions/release-please/config/README.md | 65 +++++++++++++++++++++ actions/release-please/config/action.yml | 73 ++++++++++++++++++++++++ 6 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 actions/release-please/README.md create mode 100644 actions/release-please/config/README.md create mode 100644 actions/release-please/config/action.yml diff --git a/.github/release-please-config.json b/.github/release-please-config.json index d316166..8e61635 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -23,6 +23,10 @@ "actions/semconv/pull-request": { "component": "semconv-pull-request", "initial-version": "0.0.1" + }, + "actions/release-please/config": { + "component": "release-please-config", + "initial-version": "0.0.1" } }, "plugins": [ diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json index d1eae3b..42a1506 100644 --- a/.github/release-please-manifest.json +++ b/.github/release-please-manifest.json @@ -1,3 +1,4 @@ { - "actions/semconv/pull-request": "0.0.1" + "actions/semconv/pull-request": "0.0.1", + "actions/release-please/config": "0.0.0" } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a308c8..339d38d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,3 +27,6 @@ jobs: - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 - run: mise run github:actions:ci:lint + + # The repository holds itself to the convention it ships. + - uses: ./actions/release-please/config diff --git a/actions/release-please/README.md b/actions/release-please/README.md new file mode 100644 index 0000000..9d7834e --- /dev/null +++ b/actions/release-please/README.md @@ -0,0 +1,10 @@ +# release-please + +Actions that keep [release-please][release-please] set up the same way in every +repository. + +| Action | Enforces | +| --- | --- | +| [`config`](config) | Where the configuration and manifest live | + +[release-please]: https://github.com/googleapis/release-please diff --git a/actions/release-please/config/README.md b/actions/release-please/config/README.md new file mode 100644 index 0000000..9d74367 --- /dev/null +++ b/actions/release-please/config/README.md @@ -0,0 +1,65 @@ +# release-please/config + +Fails a repository whose release-please configuration is not where every +repository keeps it: + +``` +.github/release-please-config.json +.github/release-please-manifest.json +``` + +## Usage + +The action reads the checked out working tree through `git ls-files`, so it +needs a checkout and nothing else. + +```yaml +name: CI + +on: pull_request + +permissions: {} + +jobs: + release-please-config: + name: Validate Release Please Config + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@ + with: + persist-credentials: false + - uses: TrogonStack/github-actions/actions/release-please/config@ +``` + +## What it checks + +The configuration and the manifest exist at those two paths and parse as JSON. + +No other copy is tracked anywhere in the repository. A copy left behind after a +move is worse than never moving, because release-please reads the one the +workflow names and the other rots unnoticed. + +Every workflow that runs `release-please-action` passes exactly those two paths +as `config-file` and `manifest-file`. Files in the right place and a workflow +still pointing at the old ones is a repository that has stopped releasing. + +A repository that does not use release-please passes without doing anything, so +the action is safe to put in a shared CI workflow. + +## Why .github + +Both organizations already keep these files in `.github/`, and release-please +never discovers them by convention, the paths are passed explicitly in every +workflow. The location is a house convention, so the only thing worth enforcing +is that it is the same one everywhere. + +The leading-dot spelling, `.github/.release-please-config.json`, is rejected on +purpose. The files already sit inside a dot-directory, so the second dot only +hides them from `ls`. + +## Inputs + +None. One location for every repository is the reason this action exists. diff --git a/actions/release-please/config/action.yml b/actions/release-please/config/action.yml new file mode 100644 index 0000000..a5bbb8c --- /dev/null +++ b/actions/release-please/config/action.yml @@ -0,0 +1,73 @@ +name: Validate Release Please Config +description: >- + Fails a repository whose release-please configuration is not where every + repository keeps it. +author: TrogonStack + +runs: + using: composite + steps: + - name: Validate release-please configuration + shell: bash + run: | + set -euo pipefail + + config=.github/release-please-config.json + manifest=.github/release-please-manifest.json + failed=0 + + fail() { + printf '::error file=%s::%s\n' "$1" "$2" + failed=1 + } + + # A copy left behind after a move is worse than never moving, because + # release-please reads the one the workflow names and the other rots. + strays=$(git ls-files \ + | grep -E '(^|/)\.?release-please-(config|manifest)\.json$' \ + | grep -vxF "$config" \ + | grep -vxF "$manifest" || true) + + workflows=$(git ls-files '.github/workflows/*.yml' '.github/workflows/*.yaml') + releasing='' + for workflow in $workflows; do + if grep -q 'release-please-action' "$workflow"; then + releasing="${releasing} ${workflow}" + fi + done + + if [ -z "$releasing" ] && [ -z "$strays" ] && [ ! -f "$config" ] && [ ! -f "$manifest" ]; then + echo "This repository does not use release-please, so there is nothing to place." + exit 0 + fi + + for file in "$config" "$manifest"; do + if [ ! -f "$file" ]; then + fail "$file" "Missing. Every repository keeps release-please configuration here." + elif ! python3 -c 'import json,sys; json.load(open(sys.argv[1]))' "$file" 2>/dev/null; then + fail "$file" "Not valid JSON." + fi + done + + for stray in $strays; do + fail "$stray" "Release-please configuration belongs in .github/ only." + done + + for workflow in $releasing; do + for pair in "config-file:$config" "manifest-file:$manifest"; do + key=${pair%%:*} + want=${pair#*:} + got=$(grep -E "^[[:space:]]*${key}:" "$workflow" \ + | head -1 \ + | sed -E "s/^[[:space:]]*${key}:[[:space:]]*//" \ + | tr -d "\"'" || true) + + if [ -z "$got" ]; then + fail "$workflow" "Does not set ${key}. It must be ${want}." + elif [ "$got" != "$want" ]; then + fail "$workflow" "Sets ${key} to ${got}. It must be ${want}." + fi + done + done + + exit "$failed" From cbe487a4d626680a0df4d8c00139f942d852607a Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 21 Sep 2026 20:30:04 -0400 Subject: [PATCH 2/5] chore: start the new action at the version we use Signed-off-by: Yordis Prieto --- .github/release-please-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json index 42a1506..a3b2008 100644 --- a/.github/release-please-manifest.json +++ b/.github/release-please-manifest.json @@ -1,4 +1,4 @@ { "actions/semconv/pull-request": "0.0.1", - "actions/release-please/config": "0.0.0" + "actions/release-please/config": "0.0.1" } From 434ee747578904e6c2072ca22e23c5ed0cad9a85 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 21 Sep 2026 20:35:59 -0400 Subject: [PATCH 3/5] feat(release-please): wrap release-please with the shared configuration Signed-off-by: Yordis Prieto --- .github/release-please-config.json | 4 +- .github/release-please-manifest.json | 2 +- .github/workflows/ci.yml | 3 - .github/workflows/release-please.yml | 11 +-- actions/release-please/README.md | 106 +++++++++++++++++++++-- actions/release-please/action.yml | 75 ++++++++++++++++ actions/release-please/config/README.md | 65 -------------- actions/release-please/config/action.yml | 73 ---------------- 8 files changed, 185 insertions(+), 154 deletions(-) create mode 100644 actions/release-please/action.yml delete mode 100644 actions/release-please/config/README.md delete mode 100644 actions/release-please/config/action.yml diff --git a/.github/release-please-config.json b/.github/release-please-config.json index 8e61635..0a38fad 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -24,8 +24,8 @@ "component": "semconv-pull-request", "initial-version": "0.0.1" }, - "actions/release-please/config": { - "component": "release-please-config", + "actions/release-please": { + "component": "release-please", "initial-version": "0.0.1" } }, diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json index a3b2008..a8cbd5b 100644 --- a/.github/release-please-manifest.json +++ b/.github/release-please-manifest.json @@ -1,4 +1,4 @@ { "actions/semconv/pull-request": "0.0.1", - "actions/release-please/config": "0.0.1" + "actions/release-please": "0.0.0" } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 339d38d..7a308c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,3 @@ jobs: - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 - run: mise run github:actions:ci:lint - - # The repository holds itself to the convention it ships. - - uses: ./actions/release-please/config diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 3611326..7a5d4a5 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -15,10 +15,11 @@ jobs: contents: write pull-requests: write steps: - # The PAT rather than github.token, so the release pull request and the - # tag it produces trigger the workflows that react to them. - - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # The repository releases itself with the action it ships. + - uses: ./actions/release-please with: token: ${{ secrets.GH_PAT_RELEASE_PLEASE_ACTION }} - config-file: .github/release-please-config.json - manifest-file: .github/release-please-manifest.json diff --git a/actions/release-please/README.md b/actions/release-please/README.md index 9d7834e..74d7a9c 100644 --- a/actions/release-please/README.md +++ b/actions/release-please/README.md @@ -1,10 +1,106 @@ # release-please -Actions that keep [release-please][release-please] set up the same way in every -repository. +Cuts releases with [release-please][release-please] from the configuration +every repository shares. -| Action | Enforces | -| --- | --- | -| [`config`](config) | Where the configuration and manifest live | +## Usage + +```yaml +name: Release Please + +on: + push: + branches: [main] + +permissions: {} + +jobs: + release-please: + name: Release Please + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@ + with: + persist-credentials: false + - uses: TrogonStack/github-actions/actions/release-please@ + with: + token: ${{ secrets.GH_PAT_RELEASE_PLEASE_ACTION }} +``` + +The token must be the organization's personal access token rather than +`github.token`, because a tag pushed with `github.token` does not trigger the +workflows that react to a release. + +## Configuration + +The configuration is a file in your repository, not an input here, because +release-please fetches it from the branch over the API rather than from the +checkout. The action defaults to the two paths every repository uses: + +``` +.github/release-please-config.json +.github/release-please-manifest.json +``` + +Start a repository from this configuration and change only `packages`: + +```json +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "simple", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "include-component-in-tag": true, + "include-v-in-tag": true, + "tag-separator": "@", + "separate-pull-requests": true, + "changelog-sections": [ + { "type": "feat", "section": "Features", "hidden": false }, + { "type": "fix", "section": "Bug Fixes", "hidden": false } + ], + "packages": { + ".": { "component": "your-component", "initial-version": "0.0.1" } + }, + "plugins": [{ "type": "sentence-case" }], + "draft": false, + "draft-pull-request": false, + "prerelease": false, + "signoff": "SHT Bot <61149376+sht-bot@users.noreply.github.com>" +} +``` + +The matching manifest starts every package at the sentinel: + +```json +{ ".": "0.0.0" } +``` + +`0.0.0` means "never released" to release-please, which is what makes it honour +`initial-version`. Any other starting value is read as a real previous release +and gets bumped instead, so the first tag skips the version you asked for. + +## Outputs + +`releases_created`, `paths_released`, `prs_created` and `prs` are the ones a +monorepo reads. `release_created`, `tag_name`, `sha`, `major`, `minor`, +`patch`, `html_url` and `upload_url` are set only when the repository releases +a single package. + +Release-please also sets per path outputs named after the path itself. A +composite action cannot forward a name it does not know in advance, so a +monorepo that needs them should read the `paths_released` array instead. + +## Inputs + +| Input | Default | Description | +| --- | --- | --- | +| `token` | required | Opens the release pull request and pushes the tag. | +| `config-file` | `.github/release-please-config.json` | Where the configuration lives. | +| `manifest-file` | `.github/release-please-manifest.json` | Where the manifest lives. | +| `target-branch` | detected | Branch to release from. | [release-please]: https://github.com/googleapis/release-please diff --git a/actions/release-please/action.yml b/actions/release-please/action.yml new file mode 100644 index 0000000..e2492ae --- /dev/null +++ b/actions/release-please/action.yml @@ -0,0 +1,75 @@ +name: Release Please +description: >- + Cuts releases with release-please from the configuration every repository + shares. +author: TrogonStack + +inputs: + token: + description: >- + Token used to open the release pull request and push the tag. A personal + access token rather than github.token, so the tag it produces triggers + the workflows that react to it. + required: true + config-file: + description: Where the release-please configuration lives. + required: false + default: .github/release-please-config.json + manifest-file: + description: Where the release-please manifest lives. + required: false + default: .github/release-please-manifest.json + target-branch: + description: Branch to open the release pull request against. Detected by default. + required: false + default: '' + +outputs: + releases_created: + description: Whether any release was created. + value: ${{ steps.release-please.outputs.releases_created }} + paths_released: + description: JSON array of the paths released, which is what a monorepo reads. + value: ${{ steps.release-please.outputs.paths_released }} + prs_created: + description: Whether any release pull request was created. + value: ${{ steps.release-please.outputs.prs_created }} + prs: + description: JSON array of the release pull requests opened or updated. + value: ${{ steps.release-please.outputs.prs }} + release_created: + description: Whether a release was created. Single package repositories only. + value: ${{ steps.release-please.outputs.release_created }} + tag_name: + description: The tag pushed. Single package repositories only. + value: ${{ steps.release-please.outputs.tag_name }} + sha: + description: The commit the release points at. Single package repositories only. + value: ${{ steps.release-please.outputs.sha }} + major: + description: Major version released. Single package repositories only. + value: ${{ steps.release-please.outputs.major }} + minor: + description: Minor version released. Single package repositories only. + value: ${{ steps.release-please.outputs.minor }} + patch: + description: Patch version released. Single package repositories only. + value: ${{ steps.release-please.outputs.patch }} + html_url: + description: URL of the release. Single package repositories only. + value: ${{ steps.release-please.outputs.html_url }} + upload_url: + description: Release asset upload URL. Single package repositories only. + value: ${{ steps.release-please.outputs.upload_url }} + +runs: + using: composite + steps: + - name: Release Please + id: release-please + uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + with: + token: ${{ inputs.token }} + config-file: ${{ inputs.config-file }} + manifest-file: ${{ inputs.manifest-file }} + target-branch: ${{ inputs.target-branch }} diff --git a/actions/release-please/config/README.md b/actions/release-please/config/README.md deleted file mode 100644 index 9d74367..0000000 --- a/actions/release-please/config/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# release-please/config - -Fails a repository whose release-please configuration is not where every -repository keeps it: - -``` -.github/release-please-config.json -.github/release-please-manifest.json -``` - -## Usage - -The action reads the checked out working tree through `git ls-files`, so it -needs a checkout and nothing else. - -```yaml -name: CI - -on: pull_request - -permissions: {} - -jobs: - release-please-config: - name: Validate Release Please Config - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read - steps: - - uses: actions/checkout@ - with: - persist-credentials: false - - uses: TrogonStack/github-actions/actions/release-please/config@ -``` - -## What it checks - -The configuration and the manifest exist at those two paths and parse as JSON. - -No other copy is tracked anywhere in the repository. A copy left behind after a -move is worse than never moving, because release-please reads the one the -workflow names and the other rots unnoticed. - -Every workflow that runs `release-please-action` passes exactly those two paths -as `config-file` and `manifest-file`. Files in the right place and a workflow -still pointing at the old ones is a repository that has stopped releasing. - -A repository that does not use release-please passes without doing anything, so -the action is safe to put in a shared CI workflow. - -## Why .github - -Both organizations already keep these files in `.github/`, and release-please -never discovers them by convention, the paths are passed explicitly in every -workflow. The location is a house convention, so the only thing worth enforcing -is that it is the same one everywhere. - -The leading-dot spelling, `.github/.release-please-config.json`, is rejected on -purpose. The files already sit inside a dot-directory, so the second dot only -hides them from `ls`. - -## Inputs - -None. One location for every repository is the reason this action exists. diff --git a/actions/release-please/config/action.yml b/actions/release-please/config/action.yml deleted file mode 100644 index a5bbb8c..0000000 --- a/actions/release-please/config/action.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Validate Release Please Config -description: >- - Fails a repository whose release-please configuration is not where every - repository keeps it. -author: TrogonStack - -runs: - using: composite - steps: - - name: Validate release-please configuration - shell: bash - run: | - set -euo pipefail - - config=.github/release-please-config.json - manifest=.github/release-please-manifest.json - failed=0 - - fail() { - printf '::error file=%s::%s\n' "$1" "$2" - failed=1 - } - - # A copy left behind after a move is worse than never moving, because - # release-please reads the one the workflow names and the other rots. - strays=$(git ls-files \ - | grep -E '(^|/)\.?release-please-(config|manifest)\.json$' \ - | grep -vxF "$config" \ - | grep -vxF "$manifest" || true) - - workflows=$(git ls-files '.github/workflows/*.yml' '.github/workflows/*.yaml') - releasing='' - for workflow in $workflows; do - if grep -q 'release-please-action' "$workflow"; then - releasing="${releasing} ${workflow}" - fi - done - - if [ -z "$releasing" ] && [ -z "$strays" ] && [ ! -f "$config" ] && [ ! -f "$manifest" ]; then - echo "This repository does not use release-please, so there is nothing to place." - exit 0 - fi - - for file in "$config" "$manifest"; do - if [ ! -f "$file" ]; then - fail "$file" "Missing. Every repository keeps release-please configuration here." - elif ! python3 -c 'import json,sys; json.load(open(sys.argv[1]))' "$file" 2>/dev/null; then - fail "$file" "Not valid JSON." - fi - done - - for stray in $strays; do - fail "$stray" "Release-please configuration belongs in .github/ only." - done - - for workflow in $releasing; do - for pair in "config-file:$config" "manifest-file:$manifest"; do - key=${pair%%:*} - want=${pair#*:} - got=$(grep -E "^[[:space:]]*${key}:" "$workflow" \ - | head -1 \ - | sed -E "s/^[[:space:]]*${key}:[[:space:]]*//" \ - | tr -d "\"'" || true) - - if [ -z "$got" ]; then - fail "$workflow" "Does not set ${key}. It must be ${want}." - elif [ "$got" != "$want" ]; then - fail "$workflow" "Sets ${key} to ${got}. It must be ${want}." - fi - done - done - - exit "$failed" From a84108592b726bbdf021dd52668584423947160e Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 21 Sep 2026 20:40:51 -0400 Subject: [PATCH 4/5] chore: start the release-please action at 0.0.1 Signed-off-by: Yordis Prieto --- .github/release-please-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json index a8cbd5b..3ef0f52 100644 --- a/.github/release-please-manifest.json +++ b/.github/release-please-manifest.json @@ -1,4 +1,4 @@ { "actions/semconv/pull-request": "0.0.1", - "actions/release-please": "0.0.0" + "actions/release-please": "0.0.1" } From ef1d337fe0c17984c177060ff0d29d4aefb3bb58 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 21 Sep 2026 20:42:02 -0400 Subject: [PATCH 5/5] feat(release-please): fix the configuration location instead of taking it Signed-off-by: Yordis Prieto --- actions/release-please/README.md | 12 ++++++------ actions/release-please/action.yml | 19 ++++--------------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/actions/release-please/README.md b/actions/release-please/README.md index 74d7a9c..6ff612d 100644 --- a/actions/release-please/README.md +++ b/actions/release-please/README.md @@ -37,15 +37,18 @@ workflows that react to a release. ## Configuration -The configuration is a file in your repository, not an input here, because -release-please fetches it from the branch over the API rather than from the -checkout. The action defaults to the two paths every repository uses: +The configuration is a file in your repository, because release-please fetches +it from the branch over the API rather than from the checkout. Where it lives +is fixed: ``` .github/release-please-config.json .github/release-please-manifest.json ``` +Those paths are not inputs. One location for every repository is the reason +this action exists; a knob invites back the drift it was built to remove. + Start a repository from this configuration and change only `packages`: ```json @@ -99,8 +102,5 @@ monorepo that needs them should read the `paths_released` array instead. | Input | Default | Description | | --- | --- | --- | | `token` | required | Opens the release pull request and pushes the tag. | -| `config-file` | `.github/release-please-config.json` | Where the configuration lives. | -| `manifest-file` | `.github/release-please-manifest.json` | Where the manifest lives. | -| `target-branch` | detected | Branch to release from. | [release-please]: https://github.com/googleapis/release-please diff --git a/actions/release-please/action.yml b/actions/release-please/action.yml index e2492ae..027fd0d 100644 --- a/actions/release-please/action.yml +++ b/actions/release-please/action.yml @@ -11,18 +11,6 @@ inputs: access token rather than github.token, so the tag it produces triggers the workflows that react to it. required: true - config-file: - description: Where the release-please configuration lives. - required: false - default: .github/release-please-config.json - manifest-file: - description: Where the release-please manifest lives. - required: false - default: .github/release-please-manifest.json - target-branch: - description: Branch to open the release pull request against. Detected by default. - required: false - default: '' outputs: releases_created: @@ -70,6 +58,7 @@ runs: uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 with: token: ${{ inputs.token }} - config-file: ${{ inputs.config-file }} - manifest-file: ${{ inputs.manifest-file }} - target-branch: ${{ inputs.target-branch }} + # These paths are deliberately not inputs: one location for every + # repository is the reason this action exists. + config-file: .github/release-please-config.json + manifest-file: .github/release-please-manifest.json