From be8c1d624f96aaab7ff04e455a13e8f158a4debd Mon Sep 17 00:00:00 2001 From: winlifes <110321103+Winlifes@users.noreply.github.com> Date: Wed, 20 May 2026 20:51:33 -0700 Subject: [PATCH] Fix GitHub release notes workflow --- CHANGELOG.md | 4 ++ RELEASE_CHECKLIST.md | 5 ++- RELEASE_WORKFLOW.md | 12 +++++- scripts/release.js | 94 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70c1991..2b66996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project follows a simple changelog format inspired by [Keep a Changelog](ht ## [Unreleased] +### Fixed + +- Fixed the GitHub Release workflow so release pages use generated changelog-style `RELEASE_NOTES.md` instead of the artifact installation README. + ## [0.3.3] - 2026-05-20 ### Added diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index 137b747..3a77c64 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -20,6 +20,7 @@ Use this checklist before publishing a new release of Funplay MCP for Cocos. - [ ] `npm run registry:validate` passes when `mcp-publisher` is available - [ ] `npm run release:package` creates `releases//` - [ ] `shasum -a 256 -c releases//SHA256SUMS.txt` passes +- [ ] `releases//RELEASE_NOTES.md` is organized by change type, such as Added/Optimized/Changed/Fixed ## 3. Package Contents @@ -30,7 +31,7 @@ Use this checklist before publishing a new release of Funplay MCP for Cocos. - [ ] The zip includes docs: `README.md`, `README_CN.md`, `CHANGELOG.md`, `CONTRIBUTING.md`, and `LICENSE` - [ ] The zip does not contain `.git/`, `.github/`, `.DS_Store`, `node_modules/`, `Library/`, `Temp/`, `dist/`, `build/`, `test/`, or `scripts/` - [ ] `release-manifest.json` references the correct GitHub download URL -- [ ] `SHA256SUMS.txt` includes the zip, manifest, and release README +- [ ] `SHA256SUMS.txt` includes the zip, manifest, generated release notes, and release README ## 4. Cocos Smoke Test @@ -72,7 +73,7 @@ Use this checklist before publishing a new release of Funplay MCP for Cocos. - [ ] Tag is `v` - [ ] GitHub Release title is `v` - [ ] GitHub Release includes the zip, manifest, checksum file, and release README -- [ ] Public GitHub Release page renders the release notes and assets correctly +- [ ] Public GitHub Release page uses `RELEASE_NOTES.md` and renders the release notes/assets correctly ## 8. Publish diff --git a/RELEASE_WORKFLOW.md b/RELEASE_WORKFLOW.md index dc2c026..46879bf 100644 --- a/RELEASE_WORKFLOW.md +++ b/RELEASE_WORKFLOW.md @@ -175,7 +175,7 @@ If creating a new release: gh release create v \ -R FunplayAI/funplay-cocos-mcp \ --title "v" \ - --notes-file /path/to/release-notes.md \ + --notes-file releases//RELEASE_NOTES.md \ releases//Funplay.CocosMcp.v.zip \ releases//release-manifest.json \ releases//SHA256SUMS.txt \ @@ -194,6 +194,14 @@ gh release upload v \ releases//README.md ``` +If the release body needs to be refreshed without replacing assets: + +```bash +gh release edit v \ + -R FunplayAI/funplay-cocos-mcp \ + --notes-file releases//RELEASE_NOTES.md +``` + ### 7. Verify GitHub Release ```bash @@ -202,7 +210,7 @@ gh release view v \ --json url,assets,isDraft,isPrerelease,publishedAt ``` -Confirm the release has all four assets. +Confirm the release has all four assets and the public release body is organized by change type. ### 8. Publish To npm diff --git a/scripts/release.js b/scripts/release.js index 58ccab7..d007b31 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -101,6 +101,7 @@ function main() { console.log(`- ${artifacts.zipName}`); console.log('- release-manifest.json'); console.log('- SHA256SUMS.txt'); + console.log('- RELEASE_NOTES.md'); console.log('- README.md'); return; } @@ -270,9 +271,13 @@ function packageRelease(context) { const readmePath = path.join(releaseDir, 'README.md'); fs.writeFileSync(readmePath, buildReleaseReadme(context, manifest)); + const releaseNotesPath = path.join(releaseDir, 'RELEASE_NOTES.md'); + fs.writeFileSync(releaseNotesPath, buildGitHubReleaseNotes(context, manifest)); + const checksums = [ checksumLine(zipPath, zipName), checksumLine(manifestPath, 'release-manifest.json'), + checksumLine(releaseNotesPath, 'RELEASE_NOTES.md'), checksumLine(readmePath, 'README.md') ].join(''); fs.writeFileSync(path.join(releaseDir, 'SHA256SUMS.txt'), checksums); @@ -362,6 +367,95 @@ shasum -a 256 -c SHA256SUMS.txt `; } +function buildGitHubReleaseNotes(context, manifest) { + const sections = parseChangelogSections(context.changelogNotes); + const rendered = []; + const used = new Set(); + const order = [ + ['Added', '新增'], + ['Optimized', '优化'], + ['Changed', '修改'], + ['Fixed', '修复'], + ['Security', '安全'], + ['Deprecated', '弃用'], + ['Removed', '移除'] + ]; + + for (const [sourceHeading, displayHeading] of order) { + const section = sections.find((item) => item.heading.toLowerCase() === sourceHeading.toLowerCase()); + if (!section) { + continue; + } + used.add(section.heading); + rendered.push(`## ${displayHeading}`, '', ...section.lines, ''); + } + + for (const section of sections) { + if (used.has(section.heading)) { + continue; + } + rendered.push(`## ${section.heading}`, '', ...section.lines, ''); + } + + const zip = manifest.artifacts.extensionZip; + return [ + `# Funplay MCP for Cocos ${context.tag}`, + '', + ...rendered, + '## 发布资产', + '', + `- \`${zip.file}\` - Cocos Creator extension package.`, + '- `release-manifest.json` - Machine-readable release metadata.', + '- `SHA256SUMS.txt` - SHA-256 checksums for release artifacts.', + '', + '## 校验', + '', + '```bash', + 'shasum -a 256 -c SHA256SUMS.txt', + '```', + '' + ].join('\n'); +} + +function parseChangelogSections(notes) { + const sections = []; + let current = null; + + for (const line of String(notes || '').split(/\r?\n/)) { + const heading = /^###\s+(.+?)\s*$/.exec(line); + if (heading) { + current = { + heading: heading[1].trim(), + lines: [] + }; + sections.push(current); + continue; + } + + if (current) { + current.lines.push(line); + } + } + + return sections + .map((section) => ({ + heading: section.heading, + lines: trimBlankLines(section.lines) + })) + .filter((section) => section.lines.length > 0); +} + +function trimBlankLines(lines) { + const trimmed = lines.slice(); + while (trimmed.length && !trimmed[0].trim()) { + trimmed.shift(); + } + while (trimmed.length && !trimmed[trimmed.length - 1].trim()) { + trimmed.pop(); + } + return trimmed; +} + function validateArchivePaths(paths) { const bad = []; const prefix = `${PACKAGE_DIR_NAME}/`;