Repository navigation
Release workflow: downloadable installer plus latest.json for in-app updates #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| // Writes the two files a release publishes besides the installer itself: | ||
| // | ||
| // latest.json what the app's update check reads | ||
| // (releases/latest/download/latest.json): the newest version, | ||
| // its notes, and where to download the installer, with the | ||
| // installer's SHA-256 so an in-app updater can verify what it | ||
| // downloaded. | ||
| // notes.md the release description on GitHub. | ||
| // | ||
| // Notes come from the app's own changelog (gpui/js/dist/changelog.json, | ||
| // produced by gpui/build.rs from gpui/js/lib/changelog.ts), so the release | ||
| // page and the in-app "What's new" always say the same thing. | ||
| // | ||
| // Usage: node make-release-files.mjs <tag> <installer-path> <repo> <out-dir> | ||
|
|
||
| import { createHash } from "node:crypto"; | ||
| import { readFileSync, statSync, writeFileSync } from "node:fs"; | ||
| import path from "node:path"; | ||
|
|
||
| const [tag, installerPath, repo, outDir] = process.argv.slice(2); | ||
| if (!tag || !installerPath || !repo || !outDir) { | ||
| console.error("usage: make-release-files.mjs <tag> <installer-path> <repo> <out-dir>"); | ||
| process.exit(2); | ||
| } | ||
|
|
||
| const version = tag.replace(/^v/, ""); | ||
| const changelog = JSON.parse(readFileSync("gpui/js/dist/changelog.json", "utf8")); | ||
| const entry = changelog.find((item) => item.version === version); | ||
| if (!entry) { | ||
| console.error(`gpui/js/lib/changelog.ts has no entry for ${version}; add one before releasing.`); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const notes = entry.changes.map((change) => `- ${change}`).join("\n"); | ||
| const installer = readFileSync(installerPath); | ||
| const asset = path.basename(installerPath); | ||
|
|
||
| const latest = { | ||
| version, | ||
| notes, | ||
| pub_date: new Date().toISOString(), | ||
| platforms: { | ||
| "windows-x86_64": { | ||
| url: `https://github.com/${repo}/releases/download/${tag}/${asset}`, | ||
| sha256: createHash("sha256").update(installer).digest("hex"), | ||
| size: statSync(installerPath).size, | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| writeFileSync(path.join(outDir, "latest.json"), `${JSON.stringify(latest, null, 2)}\n`); | ||
| writeFileSync(path.join(outDir, "notes.md"), `${notes}\n`); | ||
| console.log(`wrote latest.json and notes.md for ${version}`); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,27 @@ | ||
| name: Release | ||
|
|
||
| # Publishes a GitHub Release with the Windows installer. Push a tag like | ||
| # v0.1.0 (or run this by hand and give it a tag) to cut one. The app's own | ||
| # update check reads releases/latest, so this must publish a real, | ||
| # non-draft, non-prerelease release. | ||
| # Publishes a GitHub Release for a version tag. Each release carries: | ||
| # | ||
| # OpenMouseDesktopSetup.exe the Windows installer. The file name never | ||
| # changes, so | ||
| # releases/latest/download/OpenMouseDesktopSetup.exe | ||
| # is a permanent "download the newest" link. | ||
| # latest.json what the app's update check reads | ||
| # (releases/latest/download/latest.json): version, | ||
| # notes, and the installer's URL and SHA-256. | ||
| # | ||
| # To release: bump `version` in gpui/Cargo.toml (and gpui/installer/Cargo.toml), | ||
| # add that version's entry to gpui/js/lib/changelog.ts, merge, then push a tag | ||
| # like v0.1.0 (or run this by hand with that tag). The tag, Cargo version and | ||
| # changelog entry must all agree or the run stops before publishing. | ||
| # | ||
| # `releases/latest` only resolves to the newest non-draft, non-prerelease | ||
| # release, so this publishes as one. | ||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| tag: | ||
| description: "Tag to release, e.g. v0.1.0" | ||
| description: "Tag to release, e.g. v0.1.0 (created from the selected branch if it does not exist)" | ||
| required: true | ||
| push: | ||
| tags: | ||
|
|
@@ -20,9 +33,22 @@ permissions: | |
| jobs: | ||
| release: | ||
| runs-on: windows-latest | ||
| env: | ||
| TAG: ${{ github.event.inputs.tag || github.ref_name }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Check the tag matches the app version | ||
| shell: bash | ||
| run: | | ||
| version="${TAG#v}" | ||
| cargo_version=$(sed -n 's/^version = "\(.*\)"/\1/p' gpui/Cargo.toml | head -1) | ||
| installer_version=$(sed -n 's/^version = "\(.*\)"/\1/p' gpui/installer/Cargo.toml | head -1) | ||
| if [ "$version" != "$cargo_version" ] || [ "$version" != "$installer_version" ]; then | ||
| echo "::error::Tag $TAG, gpui/Cargo.toml ($cargo_version) and gpui/installer/Cargo.toml ($installer_version) must all be the same version." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: lts/* | ||
|
|
@@ -37,6 +63,10 @@ jobs: | |
| - name: Install JS dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Test | ||
| run: cargo test | ||
| working-directory: gpui | ||
|
|
||
| - name: Build release binary | ||
| run: cargo build --release | ||
| working-directory: gpui | ||
|
|
@@ -46,15 +76,22 @@ jobs: | |
| run: cargo build --release | ||
| working-directory: gpui/installer | ||
|
|
||
| - name: Rename installer | ||
| run: cp gpui/installer/target/release/openmouse-installer.exe OpenMouseDesktopSetup.exe | ||
| - name: Collect release files | ||
| shell: bash | ||
| run: | | ||
| mkdir release | ||
| cp gpui/installer/target/release/openmouse-installer.exe release/OpenMouseDesktopSetup.exe | ||
| node .github/scripts/make-release-files.mjs "$TAG" release/OpenMouseDesktopSetup.exe "$GITHUB_REPOSITORY" release | ||
| cat release/latest.json | ||
|
|
||
| - name: Publish release | ||
| shell: bash | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| TAG: ${{ github.event.inputs.tag || github.ref_name }} | ||
| run: | | ||
| gh release create "$TAG" OpenMouseDesktopSetup.exe \ | ||
| gh release create "$TAG" \ | ||
| release/OpenMouseDesktopSetup.exe release/latest.json \ | ||
| --title "OpenMouse Desktop $TAG" \ | ||
| --notes "See Settings β Changelog in the app for details." | ||
| --notes-file release/notes.md \ | ||
| --target "$GITHUB_SHA" \ | ||
| --latest | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π High Publishing an older maintenance tag unconditionally marks it as the latest release, replacing the update feed with the older installer; - --latestπ Reply "fix it for me" or copy this AI Prompt for your agent:There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ποΈ Data Integrity & Integration | π Major | β‘ Quick win Restrict which releases become latest. A tag such as π€ Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ποΈ Data Integrity & Integration | π Major | β‘ Quick win
π Supported by static analysis
π Script executed:
Repository: OpenMouse-Project/Desktop
Length of output: 4213
π Web query:
official actions/checkout v4 default ref workflow_dispatch github.sha gh release create --target existing tag behaviorπ‘ Result:
Build an existing tag from its tagged commit.
On a manual run,
actions/checkout@v4checks out the selected workflow ref because norefis supplied. IfTAGnames an existing tag and the selected branch has later commits, the workflow builds the branch commit but publishes the assets under the existing tag.--target "$GITHUB_SHA"applies only whengh release createcreates a tag, so it does not retarget an existing tag.Resolve and check out an existing
TAGbefore building, or reject the run when its commit differs fromGITHUB_SHA.π€ Prompt for AI Agents