Skip to content

Commit 86e1eda

Browse files
committed
Ship dual-arch releases with OTA verification
- add one-shot release shipping command - build and validate macOS arm64 and x64 artifacts - merge and verify published macOS updater manifests
1 parent 2473ca7 commit 86e1eda

10 files changed

Lines changed: 749 additions & 18 deletions

.github/workflows/release.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ jobs:
117117
platform: mac
118118
target: dmg
119119
arch: arm64
120+
- label: macOS x64
121+
runner: macos-13
122+
platform: mac
123+
target: dmg
124+
arch: x64
120125
- label: Linux x64
121126
runner: ubuntu-24.04
122127
platform: linux
@@ -223,8 +228,11 @@ jobs:
223228
"$AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME" \
224229
"$AZURE_TRUSTED_SIGNING_PUBLISHER_NAME"; then
225230
args+=(--signed --require-signed)
231+
elif [[ "${{ needs.preflight.outputs.release_channel }}" == "stable" ]]; then
232+
echo "Stable Windows releases require Azure Trusted Signing secrets." >&2
233+
exit 1
226234
else
227-
echo "Azure Trusted Signing secrets not configured; building unsigned Windows artifact." >&2
235+
echo "Azure Trusted Signing secrets not configured; building unsigned Windows prerelease artifact." >&2
228236
fi
229237
fi
230238
@@ -375,6 +383,13 @@ jobs:
375383
merge-multiple: true
376384
path: release-assets
377385

386+
- name: Merge macOS updater manifests
387+
run: >
388+
node scripts/merge-mac-update-manifests.ts
389+
release-assets/latest-mac.yml
390+
release-assets/latest-mac-x64.yml
391+
release-assets/latest-mac.yml
392+
378393
- name: Stage release documentation
379394
env:
380395
RELEASE_VERSION: ${{ needs.preflight.outputs.version }}

docs/release.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
Canonical release process documentation for OK Code.
44

5-
**Last updated:** 2026-04-20
5+
**Last updated:** 2026-04-25
66

77
## Overview
88

99
The next stable train ships one semver across desktop, CLI, and iOS surfaces:
1010

11-
- macOS arm64 desktop DMG plus updater metadata
11+
- macOS arm64 and x64 desktop DMGs plus updater metadata
1212
- Windows x64 signed NSIS installer
1313
- Linux x64 AppImage
1414
- iOS TestFlight build from the same release tag, dispatched separately
@@ -19,7 +19,7 @@ The next stable train ships one semver across desktop, CLI, and iOS surfaces:
1919
## Defaults
2020

2121
- iOS is TestFlight-only for this release train.
22-
- Intel mac is non-blocking and runs in the separate `Desktop Intel Compatibility` workflow.
22+
- Both macOS architectures are blocking for the main desktop release, and the published `latest-mac.yml` manifest must contain arm64 and x64 payloads.
2323
- Android is non-blocking.
2424
- Windows stable support requires signing. Do not ship unsigned Windows artifacts as stable.
2525

@@ -79,31 +79,44 @@ bun run release:validate <version>
7979

8080
This checks documentation completeness, version alignment, git state, iOS project version, and optionally runs all quality gates. Use `--skip-quality` for a docs-only pass or `--ci` for CI pipelines.
8181

82+
### One-shot release shipping
83+
84+
Use the end-to-end release command for the normal desktop + CLI train:
85+
86+
```bash
87+
bun run release:ship <version>
88+
```
89+
90+
This command runs local preflight, invokes release preparation, pushes the release tag, waits for `release.yml`, and verifies the published GitHub Release assets plus the merged macOS OTA manifest before returning success.
91+
8292
## Platform matrix
8393

8494
Blocking stable matrix:
8595

8696
| Surface | Runner | Artifact | Blocking |
8797
| ----------- | -------------- | --------------------------------------- | -------- |
8898
| macOS arm64 | `macos-14` | signed/notarized DMG + updater metadata | yes |
99+
| macOS x64 | `macos-13` | signed/notarized DMG + updater metadata | yes |
89100
| Windows x64 | `windows-2022` | signed NSIS installer | yes |
90101
| Linux x64 | `ubuntu-24.04` | AppImage | yes |
91102
| iOS | `macos-14` | TestFlight upload | separate |
92103
| CLI | `ubuntu-24.04` | npm publish | yes |
93104

94-
Non-blocking compatibility lane:
105+
Optional manual rebuild lane:
95106

96-
| Surface | Workflow | Artifact |
97-
| --------- | --------------------------------------------------------------------------- | --------- |
98-
| macOS x64 | [`release-intel-compat.yml`](../.github/workflows/release-intel-compat.yml) | Intel DMG |
107+
| Surface | Workflow | Artifact |
108+
| --------- | --------------------------------------------------------------------------- | ----------------- |
109+
| macOS x64 | [`release-intel-compat.yml`](../.github/workflows/release-intel-compat.yml) | Intel DMG rebuild |
99110

100111
## Desktop release requirements
101112

102113
- Build artifacts with `bun run dist:desktop:artifact`.
103114
- Refuse macOS stable release builds unless signing and notarization secrets are present.
104115
- Refuse Windows stable release builds unless Azure Trusted Signing secrets are present.
116+
- Publish both macOS arm64 and x64 DMG/ZIP payloads from the main `release.yml` workflow.
117+
- Merge `latest-mac.yml` and `latest-mac-x64.yml` into one published `latest-mac.yml` before creating the GitHub Release.
105118
- Validate packaged outputs before upload:
106-
- macOS: DMG exists and updater manifest exists
119+
- macOS: both arch-specific DMGs exist and updater manifests are present
107120
- Windows: installer exists
108121
- Linux: AppImage exists
109122
- Keep `bun run test:desktop-smoke` and `bun run release:smoke` green before tagging.
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# Release Ship And OTA Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Add a single release command that prepares and ships a release end-to-end, then verifies the published desktop OTA assets before reporting success.
6+
7+
**Architecture:** Keep existing release preparation and validation scripts as building blocks, then add a thin orchestration script that runs preflight, drives the tag push path, waits for the GitHub Actions release workflow, and verifies the live GitHub Release contents through `gh`. Tighten the GitHub Actions release workflow so stable desktop publishing always includes both macOS architectures and a merged updater manifest, with asset validation failing closed if OTA coverage is incomplete.
8+
9+
**Tech Stack:** Node.js scripts, GitHub CLI, GitHub Actions YAML, Vitest, Bun workspace scripts
10+
11+
---
12+
13+
### Task 1: Add the release shipping orchestrator
14+
15+
**Files:**
16+
17+
- Create: `scripts/release-ship.ts`
18+
- Modify: `package.json`
19+
- Test: `scripts/release-ship.test.ts`
20+
21+
- [ ] **Step 1: Write the failing tests for release shipping orchestration**
22+
23+
```ts
24+
describe("release-ship", () => {
25+
it("runs validation, preparation, waits for release workflow, and verifies OTA assets", () => {
26+
// Assert the command order and that the verifier checks the live release.
27+
});
28+
29+
it("fails when the published release is missing required OTA coverage", () => {
30+
// Assert missing x64 mac assets or merged manifest causes a throw.
31+
});
32+
});
33+
```
34+
35+
- [ ] **Step 2: Run the script tests to verify the new cases fail**
36+
37+
Run: `bun run --cwd scripts test`
38+
Expected: FAIL with missing `scripts/release-ship.ts` exports or missing test expectations.
39+
40+
- [ ] **Step 3: Implement `scripts/release-ship.ts` with explicit phases**
41+
42+
```ts
43+
run("node", ["scripts/pre-release-validate.ts", version, "--ci"]);
44+
run("node", ["scripts/prepare-release.ts", version, "--skip-checks"]);
45+
const runId = await waitForWorkflowRun({ workflow: "release.yml", tag: `v${version}` });
46+
await watchWorkflowRun(runId);
47+
await verifyPublishedReleaseAssets({ tag: `v${version}` });
48+
```
49+
50+
- [ ] **Step 4: Add a single package entry point**
51+
52+
```json
53+
"release:ship": "node scripts/release-ship.ts"
54+
```
55+
56+
- [ ] **Step 5: Re-run the script tests**
57+
58+
Run: `bun run --cwd scripts test`
59+
Expected: PASS for the new `release-ship` tests.
60+
61+
### Task 2: Make stable desktop publishing fail closed on OTA completeness
62+
63+
**Files:**
64+
65+
- Modify: `.github/workflows/release.yml`
66+
- Modify: `scripts/validate-release-assets.ts`
67+
- Test: `scripts/validate-release-assets.test.ts`
68+
- Test: `scripts/release-smoke.ts`
69+
70+
- [ ] **Step 1: Write the failing asset validation tests**
71+
72+
```ts
73+
it("requires both macOS arm64 and x64 desktop payloads for coordinated releases", () => {
74+
assert.throws(() => validateReleaseAssets([...missingX64]), /macOS x64 DMG/);
75+
});
76+
77+
it("requires a merged latest-mac.yml manifest alongside the dual-arch payloads", () => {
78+
assert.throws(() => validateReleaseAssets([...missingManifest]), /macOS updater manifest/);
79+
});
80+
```
81+
82+
- [ ] **Step 2: Run the script tests to verify the new asset expectations fail**
83+
84+
Run: `bun run --cwd scripts test`
85+
Expected: FAIL because `validateReleaseAssets` does not yet require dual-arch mac assets.
86+
87+
- [ ] **Step 3: Tighten release asset validation**
88+
89+
```ts
90+
{
91+
label: "macOS arm64 DMG",
92+
matches: (assetName) => assetName.endsWith("-arm64.dmg"),
93+
}
94+
```
95+
96+
- [ ] **Step 4: Update `release.yml` to build both macOS architectures and merge manifests before publish**
97+
98+
```yml
99+
- label: macOS arm64
100+
runner: macos-14
101+
platform: mac
102+
arch: arm64
103+
- label: macOS x64
104+
runner: macos-13
105+
platform: mac
106+
arch: x64
107+
```
108+
109+
```yml
110+
- name: Merge macOS update manifests
111+
run: node scripts/merge-mac-update-manifests.ts release-assets/latest-mac.yml release-assets/latest-mac-x64.yml release-assets/latest-mac.yml
112+
```
113+
114+
- [ ] **Step 5: Update release smoke fixtures to model the new dual-arch mac release contract**
115+
116+
```ts
117+
writeReleaseAssetFixtures([
118+
"OK-Code-9.9.9-smoke.0-arm64.dmg",
119+
"OK-Code-9.9.9-smoke.0-x64.dmg",
120+
"OK-Code-9.9.9-smoke.0-arm64.zip",
121+
"OK-Code-9.9.9-smoke.0-x64.zip",
122+
]);
123+
```
124+
125+
- [ ] **Step 6: Re-run the script tests**
126+
127+
Run: `bun run --cwd scripts test`
128+
Expected: PASS for updated asset validation and smoke coverage.
129+
130+
### Task 3: Verify the live published release contract
131+
132+
**Files:**
133+
134+
- Modify: `scripts/release-ship.ts`
135+
- Test: `scripts/release-ship.test.ts`
136+
- Modify: `docs/release.md`
137+
138+
- [ ] **Step 1: Add failing tests for published release inspection**
139+
140+
```ts
141+
it("checks GitHub Release assets for dual-arch mac OTA payloads after workflow success", () => {
142+
// Assert `gh release view` or `gh api` output is parsed and validated.
143+
});
144+
```
145+
146+
- [ ] **Step 2: Run the script tests to verify the published-release checks fail**
147+
148+
Run: `bun run --cwd scripts test`
149+
Expected: FAIL until `release-ship.ts` validates live release assets.
150+
151+
- [ ] **Step 3: Implement release verification and operator-facing docs**
152+
153+
```ts
154+
const assets = await listReleaseAssets(tag);
155+
validateReleaseAssets(assets);
156+
validateMergedMacManifest(await downloadReleaseAsset(tag, "latest-mac.yml"));
157+
```
158+
159+
```md
160+
Run `bun run release:ship <version>` to perform local preflight, push the tag, wait for `release.yml`, and verify OTA assets on the published GitHub Release.
161+
```
162+
163+
- [ ] **Step 4: Re-run script tests**
164+
165+
Run: `bun run --cwd scripts test`
166+
Expected: PASS for release shipping orchestration and live-release verification logic.
167+
168+
### Task 4: Workspace verification
169+
170+
**Files:**
171+
172+
- Modify: `package.json`
173+
- Modify: `scripts/package.json`
174+
- Modify: `.github/workflows/release.yml`
175+
- Modify: `docs/release.md`
176+
- Modify: `scripts/release-smoke.ts`
177+
- Modify: `scripts/validate-release-assets.ts`
178+
- Modify: `scripts/validate-release-assets.test.ts`
179+
- Create: `scripts/release-ship.ts`
180+
- Create: `scripts/release-ship.test.ts`
181+
182+
- [ ] **Step 1: Run formatting**
183+
184+
Run: `bun run fmt`
185+
Expected: exit 0
186+
187+
- [ ] **Step 2: Run lint**
188+
189+
Run: `bun run lint`
190+
Expected: exit 0
191+
192+
- [ ] **Step 3: Run typecheck**
193+
194+
Run: `bun run typecheck`
195+
Expected: exit 0
196+
197+
- [ ] **Step 4: Run focused script tests and release smoke**
198+
199+
Run: `bun run --cwd scripts test`
200+
Expected: PASS
201+
202+
Run: `bun run release:smoke`
203+
Expected: PASS
204+
205+
- [ ] **Step 5: Run the full required workspace verification**
206+
207+
Run: `bun run fmt && bun run lint && bun run typecheck`
208+
Expected: all exit 0

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"release:prepare": "node scripts/prepare-release.ts",
5555
"release:validate": "node scripts/pre-release-validate.ts",
5656
"release:smoke": "node scripts/release-smoke.ts",
57+
"release:ship": "node scripts/release-ship.ts",
5758
"dist:mobile:build": "bun run --cwd apps/mobile build",
5859
"dist:mobile:sync:ios": "cd apps/mobile && bunx cap sync ios --deployment",
5960
"patch:capacitor-local-notifications": "node scripts/patch-capacitor-local-notifications.ts",

scripts/prepare-release.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,10 @@ Step-by-step playbook for the v${version} release. Each phase must complete befo
388388
- [ ] \`okcode-CHANGELOG.md\` is attached.
389389
- [ ] \`okcode-RELEASE-NOTES.md\` is attached.
390390
- [ ] \`okcode-ASSETS-MANIFEST.md\` is attached.
391-
- [ ] macOS release artifacts are attached: DMG, ZIP, updater manifest, and blockmaps.
391+
- [ ] macOS arm64 release artifacts are attached: DMG, ZIP, updater manifest coverage, and blockmaps.
392+
- [ ] macOS x64 release artifacts are attached: DMG, ZIP, updater manifest coverage, and blockmaps.
392393
- [ ] Linux release artifacts are attached: AppImage and updater manifest if generated.
393394
- [ ] Windows release artifacts are attached: installer, updater manifest, and blockmaps.
394-
- [ ] If the Intel compatibility workflow is run, confirm the x64 macOS DMG is attached separately.
395395
396396
## Phase 2: Post-release verification
397397
@@ -405,7 +405,6 @@ Step-by-step playbook for the v${version} release. Each phase must complete befo
405405
406406
## Phase 3: Follow-through
407407
408-
- [ ] Trigger the Intel compatibility workflow if macOS x64 artifacts are required for this train.
409408
- [ ] Update external release references or announcements.
410409
- [ ] Monitor reports for regressions in provider onboarding, auth flows, release packaging, and cross-platform install/update behavior.
411410
`;

0 commit comments

Comments
 (0)