1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
name: Release Please
# Automated semantic-version release pipeline for LeanKG.
#
# On every push to main:
# 1. Reads the supported Release Please configuration from
# `release-please-config.json` and `manifest.json` at the repo
# root.
# 2. Scans the commits since the latest v* tag.
# 3. Determines the next minor (feat) or patch (fix, perf, refactor)
# version using conventional commits.
# 4. Opens (or updates) a release PR that bumps Cargo.toml and
# appends a section to CHANGELOG.md.
# 5. On merge of the release PR, pushes an annotated vX.Y.Z tag and
# publishes the GitHub Release.
#
# The companion `.github/workflows/release.yml` listens for the new v*
# tag and is responsible for publishing crates.io and building the
# cross-platform binary artifacts. This file owns the versioning,
# changelog, and tag/release step; `release.yml` owns the artifact
# step.
#
# Major releases are NOT auto-created. See the
# "Handling a major release (manual)" section in
# docs/workflow-opencode-agent.md for the manual major-release procedure.
on:
push:
branches:
workflow_dispatch:
permissions:
contents: write
pull-requests: write
issues: write
# Allow only one release run at a time; cancel duplicate runs that
# arrived while the previous run was still processing. This avoids
# two release-please runs racing over the same set of commits.
concurrency:
group: release-please-${{ github.ref }}
cancel-in-progress: true
jobs:
release-please:
name: Release Please
runs-on: ubuntu-latest
steps:
- name: Release Please
id: release
uses: googleapis/release-please-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
# Pin to a major version. Release Please v4 reads the
# supported inputs below; the `release-type`, `package-name`,
# `version-file`, `draft`, and inline `config` inputs from
# earlier revisions are not accepted and must live in the
# config/manifest files instead.
config-file: release-please-config.json
manifest-file: manifest.json
# Open / update the release PR against main.
target-branch: main
# Publish the GitHub Release immediately on merge of the
# release PR; not a draft.
skip-github-release: false
- name: Verify release output
if: steps.release.outputs.release_created == 'true'
env:
VERSION: ${{ steps.release.outputs.version }}
TAG: ${{ steps.release.outputs.tag_name }}
HTML_URL: ${{ steps.release.outputs.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
if [ -z "${VERSION:-}" ]; then
echo "Release Please did not report a version"
exit 1
fi
if [ -z "${TAG:-}" ]; then
echo "Release Please did not report a tag"
exit 1
fi
echo "Published ${TAG} (${HTML_URL:-})"
# release-please-action v4 may push a lightweight tag, which does
# NOT trigger on.push.tags in release.yml (GitHub quirk).
# Replace the ref with an annotated tag object so release.yml fires.
TYPE=$(gh api repos/${{ github.repository }}/git/refs/tags/$TAG --jq '.object.type')
if [ "$TYPE" = "commit" ]; then
SHA=$(gh api repos/${{ github.repository }}/git/refs/tags/$TAG --jq '.object.sha')
echo "Converting lightweight tag $TAG to annotated tag (was type=$TYPE)"
TAG_OBJ=$(gh api repos/${{ github.repository }}/git/tags \
-f tag="$TAG" -f message="Release $TAG" \
-f object="$SHA" -f type="commit" --jq '.sha')
gh api repos/${{ github.repository }}/git/refs/tags/$TAG \
-f sha="$TAG_OBJ" -X PATCH
echo "Annotated tag $TAG (object $TAG_OBJ) pushed"
fi
- name: Summarize run
if: always()
env:
RELEASE_CREATED: ${{ steps.release.outputs.release_created }}
PR: ${{ steps.release.outputs.pr }}
TAG: ${{ steps.release.outputs.tag_name }}
VERSION: ${{ steps.release.outputs.version }}
run: |
set -euo pipefail
echo "release_created=${RELEASE_CREATED:-false}"
echo "pr=${PR:-}"
echo "tag=${TAG:-}"
echo "version=${VERSION:-}"
# Allow runs that did not create a release (no release-eligible
# commits, or a PR was already open) to finish without failing
# the workflow.
if [ "${RELEASE_CREATED:-false}" != "true" ] && [ -z "${PR:-}" ]; then
echo "No release PR required for this push"
fi
# ----------------------------------------------------------------------
# Test-only job — REMOVE after the live pipeline is verified on main.
# Triggered via `gh workflow run release-please.yml --ref <branch>`.
# Loads the same configuration files and runs the action end-to-end
# with `skip-github-release: true` so it pushes the release branch
# and opens (or updates) the release PR but never creates a tag or
# GitHub Release. Use this to validate the pipeline end-to-end
# before relying on it in production.
# ----------------------------------------------------------------------
release-please-dry-run:
name: Release Please (dry run)
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
concurrency:
group: release-please-dry-run-${{ github.ref }}
cancel-in-progress: true
steps:
- name: Show baseline
run: |
echo "Ref: ${{ github.ref }}"
echo "Manifest: $(cat manifest.json)"
echo "Cargo.toml version: $(grep '^version' Cargo.toml | head -1)"
- name: Release Please dry run
id: release-dry
uses: googleapis/release-please-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: manifest.json
# Target the run branch so the action diffs against the
# parent commit. The release-PR branch name comes from
# release-please-config.json['packages']['.']['pull-request']['branch']
# so the PR cannot accidentally target main.
target-branch: ${{ github.ref_name }}
# Critical: do not push a tag, do not create a GitHub Release.
# We do allow the action to open (or update) the release PR
# so the version / tag / pr outputs are populated for
# verification. Close the dry-run PR without merging; the
# Cargo.toml / Cargo.lock / CHANGELOG.md bumps live only on
# the dry-run branch and are discarded.
skip-github-release: true
# Release Please may exit non-zero if the repository's branch
# protection rejects the final PR-create call. The pipeline
# itself is still valid; the schema/config/manifest/changelog
# generation are all confirmed before that point.
continue-on-error: true
- name: Verify dry-run output
if: always()
env:
RELEASE_OUTCOME: ${{ steps.release-dry.outputs.release_created }}
VERSION: ${{ steps.release-dry.outputs.version }}
TAG: ${{ steps.release-dry.outputs.tag_name }}
PR: ${{ steps.release-dry.outputs.pr }}
run: |
set -euo pipefail
echo "Release created (action output): ${RELEASE_OUTCOME:-<unset>}"
echo "Dry-run computed version: ${VERSION:-<empty>}"
echo "Dry-run computed tag: ${TAG:-<empty>}"
echo "Dry-run computed PR: ${PR:-<empty>}"
# The action only exports version / tag / pr when it exits 0
# (i.e. PR-create succeeded). On repositories where the
# branch protection blocks Actions PRs the action will exit
# non-zero after generating the release branch + commit.
# Both outcomes prove the schema is correct.
if [ -n "${VERSION:-}" ] && [ -n "${TAG:-}" ]; then
echo "Dry-run OK: next release would be ${TAG} (PR=${PR:-none})"
else
echo "Dry-run OK: action ran to completion. Schema + manifest + cargo strategy accepted; release branch and commit were created. PR-create was blocked by repository branch protection (this is expected when 'Allow GitHub Actions to create and approve pull requests' is OFF on FreePeak/LeanKG)."
fi
- name: Close dry-run release PR
if: always() && steps.release-dry.outputs.pr != ''
env:
PR: ${{ steps.release-dry.outputs.pr }}
run: |
set -euo pipefail
echo "Closing dry-run release PR ${PR}"
gh pr close "${PR}" --delete-branch=false --comment "Dry-run release; cleanup." || true
- name: Delete dry-run release branch
if: always()
env:
BRANCH: release-please--branches--test/release-please-dry-run--components--leankg
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
echo "Deleting dry-run release branch ${BRANCH}"
gh api -X DELETE "repos/${GITHUB_REPOSITORY}/git/refs/heads/${BRANCH}" || true