ReviewOS

also looking at this

stacks/uplink

fix(release): derive the app version instead of copying it

#53
Merged glennmichael123 wants to merge fix/one-version-source into main
5 files +73 -2
.github/scripts/check-release-version.tsadded+44-0
Changes to .github/scripts/check-release-version.ts
@@ -0,0 +1,44 @@
1#!/usr/bin/env bun
2import process from 'node:process'
3import { infoPlist } from '../../app/Uplink/service'
4import pkg from '../../package.json'
5import release from '../../resources/data/release.json'
6
7/**
8 * The version lives in package.json. Everything else derives it, and this says
9 * so out loud in CI.
10 *
11 * It used to live in four places. Two of them drifted: the homepage advertised
12 * 0.1.0 while the tree built 0.1.1, and the bundle `uplink:install` writes
13 * reported 0.1.0 whatever it was built from. Nothing failed, because nothing
14 * compared them (#29).
15 *
16 * `release.json` is written and committed by `buddy uplink:release`, so if this
17 * fails the usual cause is a version bumped without a release. The homepage is
18 * advertising the previous build until one happens, which is worth knowing.
19 */
20
21const problems: string[] = []
22
23if (release.version !== pkg.version) {
24 problems.push(
25 `resources/data/release.json says ${release.version}, package.json says ${pkg.version}.`,
26 ' The homepage download button reads release.json, so it is advertising a different build.',
27 ' `buddy uplink:release` writes and commits that file: run a release, or put the version back.',
28 )
29}
30
31if (!infoPlist().includes(`<string>${pkg.version}</string>`)) {
32 problems.push(
33 `The Info.plist in app/Uplink/service.ts does not carry ${pkg.version}.`,
34 ' It should interpolate pkg.version rather than naming a version of its own.',
35 )
36}
37
38if (problems.length > 0) {
39 console.error('The app version is not consistent:\n')
40 console.error(problems.join('\n'))
41 process.exit(1)
42}
43
44console.log(`Version ${pkg.version} agrees across package.json, release.json and the Info.plist.`)
.github/workflows/ci.ymlmodified+5-0
Changes to .github/workflows/ci.yml
@@ -50,6 +50,11 @@ jobs:
5050 - name: Lint
5151 run: bun buddy lint
5252
53 # The version lives in package.json; everything else derives it. Two
54 # copies had already drifted before anything compared them (#29).
55 - name: Check the app version is consistent
56 run: bun run release:check
57
5358 typecheck:
5459 runs-on: ${{ matrix.os }}
5560
app/Commands/Uplink.tsmodified+15-1
Changes to app/Commands/Uplink.ts
@@ -234,13 +234,27 @@ export default defineCommand((cli) => {
234234 const built = `${appDir}/storage/framework/desktop-dmg/Uplink-${version}.dmg`
235235 const stable = `${appDir}/storage/framework/desktop-dmg/Uplink.dmg`
236236 copyFileSync(built, stable)
237 // The homepage's download button states the version and size; commit this.
237 // What the homepage's download button reads.
238238 await Bun.write(`${appDir}/resources/data/release.json`, `${JSON.stringify({
239239 version,
240240 bytes: Bun.file(built).size,
241241 minimumMacOS: '13',
242242 url: 'https://github.com/stacksjs/uplink/releases/latest/download/Uplink.dmg',
243243 }, null, 2)}\n`)
244
245 // Committed and pushed here rather than left as a note to whoever is
246 // releasing. `gh` cuts the tag from the remote tip, so this has to land
247 // first or the tagged commit does not contain its own release metadata,
248 // and the site keeps advertising the previous version until somebody
249 // remembers. Skipped when nothing changed, so re-running a release for
250 // the same version is not an error.
251 const pending = Bun.spawnSync(['git', 'status', '--porcelain', 'resources/data/release.json'], { cwd: appDir }).stdout.toString().trim()
252 if (pending) {
253 step(['git', 'add', 'resources/data/release.json'])
254 step(['git', 'commit', '-m', `chore: Uplink ${version} is out`])
255 step(['git', 'push'])
256 }
257
244258 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'] : [])])
245259 process.exit(0)
246260 })
app/Uplink/service.tsmodified+8-1
Changes to app/Uplink/service.ts
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, rmSync } from 'node:fs'
22import { homedir } from 'node:os'
33import { join } from 'node:path'
44import process from 'node:process'
5import pkg from '../../package.json'
56
67/**
78 * Installs Uplink as a login item that stays running: an `Uplink.app` bundle
@@ -43,6 +44,12 @@ function xml(value: string): string {
4344 return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
4445}
4546
47/**
48 * The bundle `buddy uplink:install` writes, from source. Its version comes from
49 * package.json for the same reason the launcher's does: a second copy is a
50 * copy that goes stale, and this one had, so an install from a 0.1.4 tree
51 * reported itself as 0.1.0 to macOS and to anyone reading Get Info.
52 */
4653export function infoPlist(): string {
4754 return `<?xml version="1.0" encoding="UTF-8"?>
4855<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -53,7 +60,7 @@ export function infoPlist(): string {
5360 <key>CFBundleDisplayName</key><string>Uplink (source)</string>
5461 <key>CFBundleExecutable</key><string>Uplink</string>
5562 <key>CFBundlePackageType</key><string>APPL</string>
56 <key>CFBundleShortVersionString</key><string>0.1.0</string>
63 <key>CFBundleShortVersionString</key><string>${xml(pkg.version)}</string>
5764 <key>CFBundleVersion</key><string>1</string>
5865 <key>LSUIElement</key><true/>
5966 <key>LSBackgroundOnly</key><true/>
package.jsonmodified+1-0
Changes to package.json
@@ -23,6 +23,7 @@
2323 "deploy": "./buddy deploy",
2424 "lint": "./buddy lint",
2525 "lint:fix": "./buddy lint:fix",
26 "release:check": "bun .github/scripts/check-release-version.ts",
2627 "format": "./buddy format",
2728 "format:check": "./buddy format:check",
2829 "serve": "./buddy serve",