GitHub Releases
crossbind app publish github builds a desktop app and puts what installs it into the GitHub release of its version, tagged v and package.json's version. With app.updater the release takes the update too, so an app whose app.updater.url points at the release updates from the newest one GitHub has out.
Publish
-p names the platform, macos, linux or windows; without it, CROSSBIND_APP_PLATFORM, else this machine's. A Mac builds all three, the Linux and Windows apps in crossbind's Docker images:
The release takes the files of each installer: a macOS one under the app's name with its platform and arch, such as <name>-darwin-<arch>.dmg, so one release holds both arches', and the others under their own names, as the Debian package, the tar.gz, the Windows setup and the zip. Two files of one name stop the publish before any goes up.
A repository without a release of the tag gets one, a draft until its files are up. It is tagged at the commit GITHUB_SHA names in a workflow of the repository itself; outside a workflow, at the project's own commit, where its git origin is the repository; and else at the repository's default branch. GitHub writes its notes from the changes since the last release, and a version such as 1.2.3-beta.1 makes a prerelease.
A publish takes a release another one made during its build, as the jobs of a matrix do, and publishes that make theirs at once agree on the oldest. A file of a name the release has already takes the old one's place, so a publish runs again as it did, unless the repository makes its releases immutable.
Updates from the release
With app.updater the release takes the update too: its manifest and the file it names, which CROSSBIND_UPDATER_PRIVATE_KEY signs. Point the updater at the newest release:
A release that is out holds the files its publishes have put up so far, and the newest one out is the one apps update from. So put each platform's app up with --draft first, as above, then the release out with a publish without it, gh release edit v<version> --draft=false or GitHub's page.
GitHub makes the release it puts out the latest unless it has a newer version out, so a fix of an older line leaves the newest one latest. It makes no prerelease the latest, so apps update to none, and an app fetches no release of a private repository, which takes a token. The publish warns of both, and where app.updater.url is not the newest release's address.
The token and the repository
GitHub takes the release from a token with write access to the repository's contents: GH_TOKEN or GITHUB_TOKEN, else the sign-in of the GitHub CLI, gh. The build gets neither, as it writes nothing to GitHub. The repository is CROSSBIND_GITHUB_REPOSITORY, else GITHUB_REPOSITORY, which GitHub Actions sets, else package.json's repository. Before the build, the publish names every one of them that is missing, and the updater's private key where app.updater takes one.
From CI
In a workflow, the job's own token does: give it to the step as GITHUB_TOKEN: ${{ github.token }}, with the job's permissions: contents: write, and with app.updater the private key as a secret in CROSSBIND_UPDATER_PRIVATE_KEY. A hosted macOS runner has no Docker, so a macOS app's job builds from the bridges a Linux job makes, as the TestFlight workflow does.
winget and the Microsoft Store take the Windows setups from each release that is out.
The github extension is this publisher; one of your own listed under that name takes its place (Extensions). app.md is the reference behind this page.