ReviewOS

stacks/uplink

fix(release): a v* tag publishes a release with no DMG, so every download 404s

#32
Closed glennmichael123 opened this 9 days ago ·
9 days ago

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 on ubuntu-latest (.github/workflows/release.yml#L13), and its only substantive step is stacksjs/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 create inside app/Commands/Uplink.ts#L229, uploading the two names built at app/Commands/Uplink.ts#L219 and app/Commands/Uplink.ts#L221. That path is macOS only, since it shells out to buddy build:desktop and buddy 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 at README.md#L41, and the homepage's three buttons at resources/views/index.stx#L183, resources/views/index.stx#L198 and resources/views/index.stx#L322, all reading downloadUrl from resources/views/index.stx#L49, whose value is the committed URL at resources/data/release.json#L5. One asset-less release moves latest and 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#L41 through package.json#L44 still expose release:patch, release:calendar, release:minor and release:major, and .github/CONTRIBUTING.md#L124 tells contributors that buddy release triggers the Release workflow.
  • The same collision already decides the release body. app/Commands/Uplink.ts#L229 passes a fixed --notes string 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#L1 is a single heading with no entries below it.

Proposal

  1. Make one path own both tagging and publishing. Either .github/workflows/release.yml gains a macos-latest job that builds, signs, notarizes and attaches Uplink.dmg, or app/Commands/Uplink.ts stays the only publisher and the workflow stops creating releases for v*.
  2. Whichever path stays, add a gate to .github/workflows/release.yml that reads the release for the pushed tag and fails the run when it carries no Uplink.dmg asset. A DMG-less publish should be a red run, not a silent 404.
  3. Pick one writer for the release body: drop --notes from app/Commands/Uplink.ts#L229 and let action-releaser write it, or remove the action-releaser step and keep the notes in the command.
  4. Have the surviving release flow write generated entries into CHANGELOG.md and 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 create and 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 no ubuntu-latest job can produce the asset. A workflow that attaches the DMG runs on macos-latest with the certificate and the notarytool profile as secrets.
  • Keep both asset names. releases/latest/download/Uplink.dmg is the contract README.md#L41 and resources/views/index.stx#L49 depend on, and the versioned Uplink-<version>.dmg keeps older releases findable. Do not collapse them to one.
  • A draft must not trip the gate. app/Commands/Uplink.ts#L188 can publish as a draft, which never becomes latest. 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 no Uplink.dmg asset. Either the run attaches it or the run fails red.
  • https://github.com/stacksjs/uplink/releases/latest/download/Uplink.dmg resolves 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.yml and app/Commands/Uplink.ts.
  • CHANGELOG.md has a committed entry for the newest release rather than only the heading.

Sign in to comment on this issue.