mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-18 13:23:33 +08:00
Automate release notes ingestion for tagged desktop releases
The release workflow was still publishing a hard-coded GitHub Release body,
which forced manual copy-paste of each version's markdown after the build
finished. This change makes tagged releases load release-notes/vX.Y.Z.md
from the tagged commit and fail fast when the file or version/tag alignment
is missing. The local release script now enforces the same contract and
stages the matching markdown file into the release commit automatically.
Constraint: GitHub Releases must be generated from the tagged commit so the workflow can only read files already present at that revision
Rejected: Keep a hard-coded releaseBody in workflow | still requires manual release-page editing every version
Rejected: Store release notes only outside git state | workflow cannot read local-only markdown during tag-triggered builds
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep release-notes/vX.Y.Z.md as the canonical source for GitHub Release bodies and preserve the tag/version validation gate
Tested: ruby -e 'require "yaml"; YAML.load_file(".github/workflows/release-desktop.yml"); puts "YAML OK"'
Tested: bun run scripts/release.ts 0.1.5 --dry
Tested: git diff --check
Not-tested: End-to-end GitHub Actions release run against a pushed tag
This commit is contained in:
parent
16d716e773
commit
1ac2239111
58
.github/workflows/release-desktop.yml
vendored
58
.github/workflows/release-desktop.yml
vendored
@ -61,6 +61,40 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Read version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(jq -r '.version' desktop/src-tauri/tauri.conf.json)
|
||||
echo "value=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate tag matches version
|
||||
if: github.event_name == 'push'
|
||||
shell: bash
|
||||
run: |
|
||||
EXPECTED_TAG="v${{ steps.version.outputs.value }}"
|
||||
if [ "${GITHUB_REF_NAME}" != "$EXPECTED_TAG" ]; then
|
||||
echo "::error::Tag ${GITHUB_REF_NAME} does not match app version ${EXPECTED_TAG}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Load release notes
|
||||
id: release_notes
|
||||
shell: bash
|
||||
run: |
|
||||
NOTES_FILE="release-notes/v${{ steps.version.outputs.value }}.md"
|
||||
if [ ! -f "$NOTES_FILE" ]; then
|
||||
echo "::error::Missing release notes file: $NOTES_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo 'body<<__RELEASE_NOTES__'
|
||||
cat "$NOTES_FILE"
|
||||
echo
|
||||
echo '__RELEASE_NOTES__'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# ── System dependencies (Linux) ──────────────────────────
|
||||
- name: Install Linux dependencies
|
||||
if: contains(matrix.platform, 'ubuntu')
|
||||
@ -134,29 +168,7 @@ jobs:
|
||||
tauriScript: bunx tauri
|
||||
tagName: v__VERSION__
|
||||
releaseName: 'Claude Code Haha v__VERSION__'
|
||||
releaseBody: |
|
||||
## Claude Code Haha v__VERSION__
|
||||
|
||||
### Downloads
|
||||
|
||||
| Platform | File |
|
||||
|----------|------|
|
||||
| macOS (Apple Silicon) | `.dmg` |
|
||||
| macOS (Intel) | `.dmg` |
|
||||
| Windows | `.exe` (NSIS installer) |
|
||||
| Linux | `.deb` |
|
||||
|
||||
### First-time installation
|
||||
|
||||
**macOS**: This build is unsigned. If you see "app is damaged" or "unidentified developer", run:
|
||||
```bash
|
||||
xattr -cr /Applications/Claude\ Code\ Haha.app
|
||||
```
|
||||
|
||||
**Windows**: If SmartScreen blocks the app, click "More info" → "Run anyway".
|
||||
|
||||
---
|
||||
See [Installation Guide](https://github.com/NanmiCoder/cc-haha/blob/main/docs/desktop/04-installation.md) for details.
|
||||
releaseBody: ${{ steps.release_notes.outputs.body }}
|
||||
releaseDraft: ${{ github.event_name == 'workflow_dispatch' && inputs.draft || false }}
|
||||
prerelease: false
|
||||
args: ${{ matrix.tauri_args }} --config src-tauri/tauri.release-ci.json
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
* bun run scripts/release.ts patch --dry # preview without changes
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
const root = path.resolve(import.meta.dir, '..')
|
||||
@ -43,6 +43,10 @@ function getCurrentVersion(): string {
|
||||
return tauriConf.version
|
||||
}
|
||||
|
||||
function getReleaseNotesPath(version: string): string {
|
||||
return path.join(root, 'release-notes', `v${version}.md`)
|
||||
}
|
||||
|
||||
function bumpVersion(current: string, bump: string): string {
|
||||
if (/^\d+\.\d+\.\d+$/.test(bump)) {
|
||||
return bump
|
||||
@ -88,9 +92,11 @@ if (!bumpArg) {
|
||||
|
||||
const current = getCurrentVersion()
|
||||
const next = bumpVersion(current, bumpArg)
|
||||
const releaseNotesPath = getReleaseNotesPath(next)
|
||||
|
||||
console.log(`\n Version: ${current} → ${next}`)
|
||||
console.log(` Tag: v${next}`)
|
||||
console.log(` Notes: ${path.relative(root, releaseNotesPath)}`)
|
||||
console.log(` Dry run: ${dryRun}\n`)
|
||||
|
||||
if (dryRun) {
|
||||
@ -98,9 +104,16 @@ if (dryRun) {
|
||||
for (const file of VERSION_FILES) {
|
||||
console.log(` - ${path.relative(root, file.path)}`)
|
||||
}
|
||||
console.log(` - ${path.relative(root, releaseNotesPath)} ${existsSync(releaseNotesPath) ? '(present)' : '(missing)'}`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (!existsSync(releaseNotesPath)) {
|
||||
console.error(`Missing release notes file: ${path.relative(root, releaseNotesPath)}`)
|
||||
console.error(`Create it before releasing so GitHub Release can use it automatically.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Update version in all files
|
||||
for (const file of VERSION_FILES) {
|
||||
const content = readFileSync(file.path, 'utf-8')
|
||||
@ -122,6 +135,7 @@ await run([
|
||||
'desktop/src-tauri/tauri.conf.json',
|
||||
'desktop/src-tauri/Cargo.toml',
|
||||
'desktop/src-tauri/Cargo.lock',
|
||||
path.relative(root, releaseNotesPath),
|
||||
])
|
||||
await run(['git', 'commit', '-m', `release: v${next}`])
|
||||
await run(['git', 'tag', '-a', `v${next}`, '-m', `Release v${next}`])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user