ReviewOS

also looking at this

stacks/uplink

fix(release): one publisher, and a red run when the DMG is missing

#58
Merged glennmichael123 wants to merge fix/one-release-publisher into main
3 files +93 -36
app/Commands/Uplink.tsmodified+29-7
Changes to app/Commands/Uplink.ts
@@ -41,6 +41,13 @@ async function openMessages(config: ReturnType<typeof loadConfig>, appDir: strin
4141 * key, and the tables runs are recorded in. Both steps are idempotent, so
4242 * install can always run them.
4343 */
44const CHANGELOG_HEADING = '# Uplink Changelog'
45
46/** A changelog file without its heading, so two of them can be stacked. */
47function changelogBody(markdown: string): string {
48 return markdown.replace(CHANGELOG_HEADING, '').trim()
49}
50
4451function prepareApp(appDir: string): void {
4552 const buddy = `${appDir}/buddy`
4653 if (!existsSync(`${appDir}/.env`)) {
@@ -243,20 +250,35 @@ export default defineCommand((cli) => {
243250 url: 'https://github.com/stacksjs/uplink/releases/latest/download/Uplink.dmg',
244251 }, null, 2)}\n`)
245252
253 // `buddy changelog` rewrites the file with the commits since the last
254 // tag and nothing else, so the older entries are carried over by hand.
255 // A changelog that forgets every release but the newest is not one.
256 const changelogPath = `${appDir}/CHANGELOG.md`
257 const kept = changelogBody(await Bun.file(changelogPath).text())
258 step(['./buddy', 'changelog', '--no-interaction'])
259 const generated = changelogBody(await Bun.file(changelogPath).text())
260 const sections = [`## v${version}\n`, `${generated}\n`, kept && `${kept}\n`].filter(Boolean)
261 await Bun.write(changelogPath, `${CHANGELOG_HEADING}\n\n${sections.join('\n')}`)
262
246263 // Committed and pushed here rather than left as a note to whoever is
247264 // releasing. `gh` cuts the tag from the remote tip, so this has to land
248 // first or the tagged commit does not contain its own release metadata,
249 // and the site keeps advertising the previous version until somebody
250 // remembers. Skipped when nothing changed, so re-running a release for
251 // the same version is not an error.
252 const pending = Bun.spawnSync(['git', 'status', '--porcelain', 'resources/data/release.json'], { cwd: appDir }).stdout.toString().trim()
265 // first or the tagged commit does not contain its own release notes or
266 // the version the site advertises. Skipped when nothing changed, so
267 // re-running a release for the same version is not an error.
268 const pending = Bun.spawnSync(['git', 'status', '--porcelain', 'resources/data/release.json', 'CHANGELOG.md'], { cwd: appDir }).stdout.toString().trim()
253269 if (pending) {
254 step(['git', 'add', 'resources/data/release.json'])
270 step(['git', 'add', 'resources/data/release.json', 'CHANGELOG.md'])
255271 step(['git', 'commit', '-m', `chore: Uplink ${version} is out`])
256272 step(['git', 'push'])
257273 }
258274
259 step(['gh', 'release', 'create', `v${version}`, stable, built, '--title', `Uplink ${version}`, '--notes', `Uplink ${version} for macOS 13 or later. Signed with a Developer ID and notarized by Apple.`, ...(options.draft ? ['--draft'] : [])])
275 // The only thing that writes a release body. The workflow used to create
276 // a second release for the same tag with its own generated notes, so
277 // which text a reader got came down to which of the two finished last.
278 const notesPath = `${appDir}/storage/framework/desktop-dmg/notes.md`
279 await Bun.write(notesPath, `Uplink ${version} for macOS 13 or later. Signed with a Developer ID and notarized by Apple.\n\n${generated}\n`)
280
281 step(['gh', 'release', 'create', `v${version}`, stable, built, '--title', `Uplink ${version}`, '--notes-file', notesPath, ...(options.draft ? ['--draft'] : [])])
260282 process.exit(0)
261283 })
262284