Skip to content

Commit 30b3ee5

Browse files
ci(release): restore uinaf-releaser version push-back (#18)
* ci(release): restore uinaf-releaser version push-back Add @semantic-release/git bump commits authored by uinaf-releaser, mint App tokens for git/GitHub writes, and keep npm on Trusted Publishing. Rulesets bypass the App while humans still require signed commits. Co-authored-by: Cursor <cursoragent@cursor.com> * ci(release): address Copilot feedback on App identity Scope the App token from github context, resolve the bot user id at runtime for linked noreply emails, and gate release to the canonical repo. Co-authored-by: Cursor <cursoragent@cursor.com> * ci(release): drop canonical-repo name gate Forks already lack the release Environment App credentials; hard-coding the repository name is unnecessary. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 07d9420 commit 30b3ee5

3 files changed

Lines changed: 71 additions & 20 deletions

File tree

.github/workflows/release.yml

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ name: Release
33
# Canonical verify -> release on push to main. Publishing uses npm Trusted
44
# Publishing (OIDC): npmjs.com lists this workflow filename (release.yml)
55
# and the "release" environment as the package's trusted publisher — see
6-
# docs/releasing.md. Versioning is tag-only (no bump-back commits: the main
7-
# ruleset requires signed commits, so semantic-release computes the version
8-
# from Conventional Commits and stamps it into the published tarball; the
9-
# checked-in package.json version is not authoritative).
6+
# docs/releasing.md. Version push-back and GitHub Release writes use a
7+
# short-lived uinaf-releaser installation token (rulesets bypass that App
8+
# for unsigned bot commits/tags while humans still require signed commits).
109

1110
on:
1211
push:
@@ -16,6 +15,7 @@ permissions: {}
1615

1716
jobs:
1817
verify:
18+
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
1919
runs-on: ubuntu-latest
2020
timeout-minutes: 10
2121
permissions:
@@ -39,11 +39,13 @@ jobs:
3939
- run: vp run verify # vp check + vp test + vp pack + cli smoke
4040

4141
secrets:
42+
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
4243
permissions:
4344
contents: read
4445
uses: ./.github/workflows/secrets.yml
4546

4647
release:
48+
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
4749
needs: [verify, secrets]
4850
runs-on: ubuntu-latest
4951
timeout-minutes: 15
@@ -53,7 +55,7 @@ jobs:
5355
group: release-${{ github.repository }}-main
5456
cancel-in-progress: false
5557
permissions:
56-
contents: write # GitHub release + v* tag
58+
contents: read
5759
id-token: write # OIDC for npm trusted publishing
5860
steps:
5961
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -72,6 +74,39 @@ jobs:
7274
node-version-file: ".node-version"
7375
run-install: |
7476
- args: ["--frozen-lockfile"]
77+
- name: Create release bot token
78+
id: release-bot
79+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
80+
with:
81+
app-id: ${{ vars.UINAF_RELEASE_APP_ID }}
82+
private-key: ${{ secrets.UINAF_RELEASE_APP_PRIVATE_KEY }}
83+
owner: ${{ github.repository_owner }}
84+
repositories: ${{ github.event.repository.name }}
85+
permission-contents: write
86+
- name: Authorize release writes
87+
env:
88+
GH_TOKEN: ${{ steps.release-bot.outputs.token }}
89+
run: gh auth setup-git
90+
# GitHub links bot commits when the noreply email uses `{user-id}+{slug}[bot]@…`.
91+
- name: Resolve release bot identity
92+
id: release-bot-identity
93+
env:
94+
GH_TOKEN: ${{ steps.release-bot.outputs.token }}
95+
APP_SLUG: ${{ steps.release-bot.outputs.app-slug }}
96+
run: echo "user-id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
7597
- uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6.0.0
98+
with:
99+
extra_plugins: |
100+
@semantic-release/commit-analyzer@13.0.1
101+
@semantic-release/release-notes-generator@14.1.1
102+
@semantic-release/npm@13.1.5
103+
@semantic-release/git@10.0.1
104+
@semantic-release/github@12.0.8
105+
conventional-changelog-conventionalcommits@9.3.1
76106
env:
77-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
107+
GITHUB_TOKEN: ${{ steps.release-bot.outputs.token }}
108+
GH_TOKEN: ${{ steps.release-bot.outputs.token }}
109+
GIT_AUTHOR_NAME: ${{ steps.release-bot.outputs.app-slug }}[bot]
110+
GIT_AUTHOR_EMAIL: ${{ steps.release-bot-identity.outputs.user-id }}+${{ steps.release-bot.outputs.app-slug }}[bot]@users.noreply.github.com
111+
GIT_COMMITTER_NAME: ${{ steps.release-bot.outputs.app-slug }}[bot]
112+
GIT_COMMITTER_EMAIL: ${{ steps.release-bot-identity.outputs.user-id }}+${{ steps.release-bot.outputs.app-slug }}[bot]@users.noreply.github.com

.releaserc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
"@semantic-release/commit-analyzer",
66
"@semantic-release/release-notes-generator",
77
"@semantic-release/npm",
8+
[
9+
"@semantic-release/git",
10+
{
11+
"assets": ["package.json"],
12+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
13+
}
14+
],
815
[
916
"@semantic-release/github",
1017
{

docs/releasing.md

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,43 @@
33
Releases are **fully automatic**: every push to `main` runs verification and
44
then semantic-release, which computes the next version from Conventional
55
Commits (`fix:` → patch, `feat:` → minor, `BREAKING CHANGE:` → major),
6-
publishes to npm, and creates the `v*` tag and GitHub release with notes.
7-
Commits that don't warrant a release (`docs:`, `chore:`, `test:`, …) publish
8-
nothing.
6+
publishes to npm, bumps `package.json`, and creates the `v*` tag and GitHub
7+
release with notes. Commits that don't warrant a release (`docs:`, `chore:`,
8+
`test:`, …) publish nothing. Release bump commits include `[skip ci]` so they
9+
do not re-enter verify/release.
910

1011
Publishing uses **npm Trusted Publishing (OIDC)**: GitHub Actions proves its
1112
identity to npm per-run and provenance attestations are generated
1213
automatically. No npm token exists in this repository, its secrets, or any
1314
maintainer machine.
1415

15-
## Versioning is tag-only
16+
GitHub Release and version push-back commits are authored by
17+
`uinaf-releaser[bot]` via a short-lived App installation token minted in the
18+
`release` Environment (`UINAF_RELEASE_APP_ID` /
19+
`UINAF_RELEASE_APP_PRIVATE_KEY`). The `protect-main` and
20+
`protect-release-tags` rulesets still require signed commits/tags for humans,
21+
and grant an Integration bypass to that App for unsigned bot writeback.
1622

17-
The `main` ruleset requires signed commits, so there are no bot bump-back
18-
commits: the checked-in `package.json` version is **not authoritative**.
19-
Full source checkouts use the greater of that placeholder and the latest
20-
reachable strict `vX.Y.Z` tag; builds bake that effective version into the CLI.
21-
Shallow clones and source archives fail with a tag-history instruction instead
22-
of silently stamping the placeholder version.
23-
Semantic-release derives the next version, stamps `package.json` before the
24-
prepack gate, and therefore bakes the release version into the published
25-
tarball. Look up the released version with
26-
`npm view @uinaf/workspace-kit version` or the latest tag, not package.json.
23+
## Versioning
24+
25+
`@semantic-release/git` commits the bumped `package.json` back to `main`.
26+
Full source checkouts still resolve the greater of the checked-in manifest and
27+
the latest reachable strict `vX.Y.Z` tag (see `src/version.ts`); builds bake
28+
that effective version into the CLI. Shallow clones and source archives fail
29+
with a tag-history instruction instead of silently stamping a stale
30+
placeholder. Look up the released version with
31+
`npm view @uinaf/workspace-kit version` or the latest tag when in doubt.
2732

2833
## Configuration record (already done)
2934

3035
- Trusted publisher registered on npm for `@uinaf/workspace-kit`:
3136
repository `uinaf/workspace-kit`, workflow `release.yml`, environment
3237
`release`, permission `publish`.
3338
- GitHub `release` environment restricted to `main` branch runs.
39+
- `release` Environment holds `UINAF_RELEASE_APP_ID` (variable) and
40+
`UINAF_RELEASE_APP_PRIVATE_KEY` (secret) for git/GitHub writeback.
41+
- Rulesets bypass Integration `uinaf-releaser` (`4474917`) for bot push-back
42+
and release tags.
3443
- `v0.1.0` was the one-time manual bootstrap publish (trusted publishing
3544
requires an existing package); it carries no provenance. Every CI-published
3645
version does.

0 commit comments

Comments
 (0)