Skip to content

Commit b495fa8

Browse files
committed
feat(desktop): publish releases to a dedicated update channel
electron-updater's public GitHub provider resolves the repository's newest release. This repository publishes CLI releases continuously, so that is almost always a CLI release carrying no latest-mac.yml, and every desktop update check fails. Publish desktop releases to PyModel/pythinker-desktop-releases instead, where the latest release always describes the desktop app. Releases are created with releaseType: release because the publisher otherwise creates a draft, and a draft is invisible to the updater. Publishing across repositories needs an installation token; GITHUB_TOKEN cannot reach another repository. There is deliberately no fallback: a fallback would publish to the wrong repository and silently restore the bug this fixes, so a missing secret fails the release. Both jobs now also assert that app-update.yml is present inside the packaged application. Without it a build cannot self-update, and the app reports itself as non-updatable, so shipping one is a silent regression.
1 parent fa82753 commit b495fa8

3 files changed

Lines changed: 51 additions & 5 deletions

File tree

.github/workflows/desktop-release.yml

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ jobs:
4646
working-directory: apps/desktop
4747
run: node --import tsx scripts/stage-runtime.ts
4848

49-
# On a desktop-v* tag, --publish always creates or updates the draft-or-release
50-
# for that tag; contents: write makes GITHUB_TOKEN sufficient.
49+
# On a desktop-v* tag, --publish always creates or updates the release
50+
# in pythinker-desktop-releases with the GitHub App token below.
5151
# Without Developer ID signing secrets, electron-builder publishes an
5252
# ad-hoc/self-signed app. macOS auto-update will not accept unsigned updates,
5353
# but this still proves packaging and the feed shape.
@@ -73,11 +73,29 @@ jobs:
7373
echo 'CSC_IDENTITY_AUTO_DISCOVERY=false' >> "$GITHUB_ENV"
7474
echo 'No macOS signing certificate configured; building unsigned.'
7575
fi
76+
- name: Mint releases-repo token
77+
id: releases_token
78+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # pinned from v3.2.0
79+
with:
80+
app-id: ${{ secrets.DESKTOP_RELEASES_APP_ID }}
81+
private-key: ${{ secrets.DESKTOP_RELEASES_APP_PRIVATE_KEY }}
82+
owner: PyModel
83+
repositories: pythinker-desktop-releases
7684
- name: Package and publish desktop release
7785
working-directory: apps/desktop
7886
run: pnpm exec electron-builder --mac dmg zip --publish always
7987
env:
80-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
88+
GH_TOKEN: ${{ steps.releases_token.outputs.token }}
89+
90+
- name: Verify macOS packaged update configuration
91+
shell: bash
92+
run: |
93+
app_bundle="$(find apps/desktop/dist -maxdepth 2 -type d -name '*.app' -print -quit)"
94+
if [ -z "$app_bundle" ]; then
95+
echo 'macOS application bundle not found' >&2
96+
exit 1
97+
fi
98+
test -f "$app_bundle/Contents/Resources/app-update.yml"
8199
82100
- name: Upload macOS artifacts for manual runs
83101
if: github.event_name == 'workflow_dispatch'
@@ -154,11 +172,23 @@ jobs:
154172
value="${!input:-}"
155173
if [ -n "$value" ]; then printf '%s<<__EOF__\n%s\n__EOF__\n' "$name" "$value" >> "$GITHUB_ENV"; fi
156174
done
175+
- name: Mint releases-repo token
176+
id: releases_token
177+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # pinned from v3.2.0
178+
with:
179+
app-id: ${{ secrets.DESKTOP_RELEASES_APP_ID }}
180+
private-key: ${{ secrets.DESKTOP_RELEASES_APP_PRIVATE_KEY }}
181+
owner: PyModel
182+
repositories: pythinker-desktop-releases
157183
- name: Package and publish desktop release
158184
working-directory: apps/desktop
159185
run: node --import tsx scripts/package-win.ts --publish always
160186
env:
161-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
187+
GH_TOKEN: ${{ steps.releases_token.outputs.token }}
188+
189+
- name: Verify Windows packaged update configuration
190+
shell: bash
191+
run: test -f apps/desktop/dist/win-unpacked/resources/app-update.yml
162192

163193
- name: Upload Windows artifacts for manual runs
164194
if: github.event_name == 'workflow_dispatch'

apps/desktop/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
{
3333
"provider": "github",
3434
"owner": "PyModel",
35-
"repo": "pythinker-code"
35+
"repo": "pythinker-desktop-releases",
36+
"releaseType": "release"
3637
}
3738
],
3839
"afterPack": "./scripts/verify-packaged-runtime.ts",

apps/desktop/tests/packaging-config.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ interface DesktopPackage {
2828
readonly perMachine: boolean
2929
readonly shortcutName: string
3030
}
31+
readonly publish: readonly {
32+
readonly owner: string
33+
readonly provider: string
34+
readonly releaseType: string
35+
readonly repo: string
36+
}[]
3137
readonly productName: string
3238
readonly win: {
3339
readonly icon: string
@@ -58,6 +64,15 @@ describe('desktop packaging configuration', () => {
5864
expect(desktopPackage.build.productName).toBe('Pythinker')
5965
})
6066

67+
it('publishes updates to the dedicated desktop release repository', () => {
68+
expect(desktopPackage.build.publish).toContainEqual({
69+
owner: 'PyModel',
70+
provider: 'github',
71+
releaseType: 'release',
72+
repo: 'pythinker-desktop-releases',
73+
})
74+
})
75+
6176
it('keeps desktop download URLs derived from their published release version', () => {
6277
const siteSource = readFileSync(resolve(repositoryRoot, 'apps/site/src/App.vue'), 'utf8')
6378
const desktopVersionMatch = siteSource.match(/const DESKTOP_VERSION = '([^']+)'/)

0 commit comments

Comments
 (0)