Part of #6.
Two separate things publish a release for the same tag, and only one of them can attach the app. The workflow creates the release on Linux; the DMG is uploaded by a command a maintainer runs by hand on a Mac. Nothing connects the two and nothing checks the result, so a tag that arrives without the manual step still produces the release GitHub then calls latest.
What is there today
- The Release workflow fires on any
v*tag (.github/workflows/release.yml#L6), runs onubuntu-latest(.github/workflows/release.yml#L13), and its only substantive step isstacksjs/action-releaser(.github/workflows/release.yml#L40). It creates a release and attaches nothing. No file in.github/workflows/mentions a DMG at all. - The DMG is attached solely by
gh release createinsideapp/Commands/Uplink.ts#L229, uploading the two names built atapp/Commands/Uplink.ts#L219andapp/Commands/Uplink.ts#L221. That path is macOS only, since it shells out tobuddy build:desktopandbuddy build:dmg(app/Commands/Uplink.ts#L208), and it refuses to run without a Developer ID identity and a notarytool profile (app/Commands/Uplink.ts#L196). - Every download link in the project resolves through
releases/latest/download/Uplink.dmg: the README button atREADME.md#L41, and the homepage's three buttons atresources/views/index.stx#L183,resources/views/index.stx#L198andresources/views/index.stx#L322, all readingdownloadUrlfromresources/views/index.stx#L49, whose value is the committed URL atresources/data/release.json#L5. One asset-less release moveslatestand all four 404, with nothing failing anywhere to say so. - Such a tag is easy to create with no Mac in the loop.
package.json#L41throughpackage.json#L44still exposerelease:patch,release:calendar,release:minorandrelease:major, and.github/CONTRIBUTING.md#L124tells contributors thatbuddy releasetriggers the Release workflow. - The same collision already decides the release body.
app/Commands/Uplink.ts#L229passes a fixed--notesstring while action-releaser generates a changelog for the same tag, and a successful Release run exists for v0.1.0 next to the manual publish, so which text a reader gets comes down to ordering.CHANGELOG.md#L1is a single heading with no entries below it.
Proposal
- Make one path own both tagging and publishing. Either
.github/workflows/release.ymlgains amacos-latestjob that builds, signs, notarizes and attachesUplink.dmg, orapp/Commands/Uplink.tsstays the only publisher and the workflow stops creating releases forv*. - Whichever path stays, add a gate to
.github/workflows/release.ymlthat reads the release for the pushed tag and fails the run when it carries noUplink.dmgasset. A DMG-less publish should be a red run, not a silent 404. - Pick one writer for the release body: drop
--notesfromapp/Commands/Uplink.ts#L229and let action-releaser write it, or remove the action-releaser step and keep the notes in the command. - Have the surviving release flow write generated entries into
CHANGELOG.mdand commit them, so the repository carries the history the release page shows.
Requirements
- The asset gate runs after the upload, not at tag push. For v0.1.0 the manual
gh release createand the workflow run started within a second of each other, so a check that fires immediately on the tag push races the upload. Poll the release for the tag, or run the gate in a job that depends on the job that attaches the asset. - Signing stays on macOS. The publish needs a Developer ID identity and a notarytool profile (
app/Commands/Uplink.ts#L196), so noubuntu-latestjob can produce the asset. A workflow that attaches the DMG runs onmacos-latestwith the certificate and the notarytool profile as secrets. - Keep both asset names.
releases/latest/download/Uplink.dmgis the contractREADME.md#L41andresources/views/index.stx#L49depend on, and the versionedUplink-<version>.dmgkeeps older releases findable. Do not collapse them to one. - A draft must not trip the gate.
app/Commands/Uplink.ts#L188can publish as a draft, which never becomeslatest. The gate should skip drafts rather than fail on them. - Do not leave the tag behind on failure. If the gate fails, the tag already exists, so the fix has to say what the maintainer does next: delete the tag and the release, or attach the asset to the existing one.
Acceptance
- Pushing a
v*tag cannot end with a published, non-draft release that has noUplink.dmgasset. Either the run attaches it or the run fails red. https://github.com/stacksjs/uplink/releases/latest/download/Uplink.dmgresolves after a publish, so the README link and the three homepage buttons all download the app.- Exactly one place writes the release body, and a reviewer can name that place by reading
.github/workflows/release.ymlandapp/Commands/Uplink.ts. CHANGELOG.mdhas a committed entry for the newest release rather than only the heading.