Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ jobs:
platform: mac
target: dmg
arch: arm64
- label: macOS x64
runner: macos-13
platform: mac
target: dmg
arch: x64
- label: Linux x64
runner: ubuntu-24.04
platform: linux
Expand Down Expand Up @@ -223,8 +228,11 @@ jobs:
"$AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME" \
"$AZURE_TRUSTED_SIGNING_PUBLISHER_NAME"; then
args+=(--signed --require-signed)
elif [[ "${{ needs.preflight.outputs.release_channel }}" == "stable" ]]; then
echo "Stable Windows releases require Azure Trusted Signing secrets." >&2
exit 1
else
echo "Azure Trusted Signing secrets not configured; building unsigned Windows artifact." >&2
echo "Azure Trusted Signing secrets not configured; building unsigned Windows prerelease artifact." >&2
fi
fi

Expand Down Expand Up @@ -375,6 +383,13 @@ jobs:
merge-multiple: true
path: release-assets

- name: Merge macOS updater manifests
run: >
node scripts/merge-mac-update-manifests.ts
release-assets/latest-mac.yml
release-assets/latest-mac-x64.yml
release-assets/latest-mac.yml

- name: Stage release documentation
env:
RELEASE_VERSION: ${{ needs.preflight.outputs.version }}
Expand Down
29 changes: 21 additions & 8 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

Canonical release process documentation for OK Code.

**Last updated:** 2026-04-20
**Last updated:** 2026-04-25

## Overview

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

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

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

Expand Down Expand Up @@ -79,31 +79,44 @@ bun run release:validate <version>

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.

### One-shot release shipping

Use the end-to-end release command for the normal desktop + CLI train:

```bash
bun run release:ship <version>
```

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.

## Platform matrix

Blocking stable matrix:

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

Non-blocking compatibility lane:
Optional manual rebuild lane:

| Surface | Workflow | Artifact |
| --------- | --------------------------------------------------------------------------- | --------- |
| macOS x64 | [`release-intel-compat.yml`](../.github/workflows/release-intel-compat.yml) | Intel DMG |
| Surface | Workflow | Artifact |
| --------- | --------------------------------------------------------------------------- | ----------------- |
| macOS x64 | [`release-intel-compat.yml`](../.github/workflows/release-intel-compat.yml) | Intel DMG rebuild |

## Desktop release requirements

- Build artifacts with `bun run dist:desktop:artifact`.
- Refuse macOS stable release builds unless signing and notarization secrets are present.
- Refuse Windows stable release builds unless Azure Trusted Signing secrets are present.
- Publish both macOS arm64 and x64 DMG/ZIP payloads from the main `release.yml` workflow.
- Merge `latest-mac.yml` and `latest-mac-x64.yml` into one published `latest-mac.yml` before creating the GitHub Release.
- Validate packaged outputs before upload:
- macOS: DMG exists and updater manifest exists
- macOS: both arch-specific DMGs exist and updater manifests are present
- Windows: installer exists
- Linux: AppImage exists
- Keep `bun run test:desktop-smoke` and `bun run release:smoke` green before tagging.
Expand Down
208 changes: 208 additions & 0 deletions docs/superpowers/plans/2026-04-25-release-ship-and-ota.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# Release Ship And OTA Implementation Plan

> **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.

**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.

**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.

**Tech Stack:** Node.js scripts, GitHub CLI, GitHub Actions YAML, Vitest, Bun workspace scripts

---

### Task 1: Add the release shipping orchestrator

**Files:**

- Create: `scripts/release-ship.ts`
- Modify: `package.json`
- Test: `scripts/release-ship.test.ts`

- [ ] **Step 1: Write the failing tests for release shipping orchestration**

```ts
describe("release-ship", () => {
it("runs validation, preparation, waits for release workflow, and verifies OTA assets", () => {
// Assert the command order and that the verifier checks the live release.
});

it("fails when the published release is missing required OTA coverage", () => {
// Assert missing x64 mac assets or merged manifest causes a throw.
});
});
```

- [ ] **Step 2: Run the script tests to verify the new cases fail**

Run: `bun run --cwd scripts test`
Expected: FAIL with missing `scripts/release-ship.ts` exports or missing test expectations.

- [ ] **Step 3: Implement `scripts/release-ship.ts` with explicit phases**

```ts
run("node", ["scripts/pre-release-validate.ts", version, "--ci"]);
run("node", ["scripts/prepare-release.ts", version, "--skip-checks"]);
const runId = await waitForWorkflowRun({ workflow: "release.yml", tag: `v${version}` });
await watchWorkflowRun(runId);
await verifyPublishedReleaseAssets({ tag: `v${version}` });
```

- [ ] **Step 4: Add a single package entry point**

```json
"release:ship": "node scripts/release-ship.ts"
```

- [ ] **Step 5: Re-run the script tests**

Run: `bun run --cwd scripts test`
Expected: PASS for the new `release-ship` tests.

### Task 2: Make stable desktop publishing fail closed on OTA completeness

**Files:**

- Modify: `.github/workflows/release.yml`
- Modify: `scripts/validate-release-assets.ts`
- Test: `scripts/validate-release-assets.test.ts`
- Test: `scripts/release-smoke.ts`

- [ ] **Step 1: Write the failing asset validation tests**

```ts
it("requires both macOS arm64 and x64 desktop payloads for coordinated releases", () => {
assert.throws(() => validateReleaseAssets([...missingX64]), /macOS x64 DMG/);
});

it("requires a merged latest-mac.yml manifest alongside the dual-arch payloads", () => {
assert.throws(() => validateReleaseAssets([...missingManifest]), /macOS updater manifest/);
});
```

- [ ] **Step 2: Run the script tests to verify the new asset expectations fail**

Run: `bun run --cwd scripts test`
Expected: FAIL because `validateReleaseAssets` does not yet require dual-arch mac assets.

- [ ] **Step 3: Tighten release asset validation**

```ts
{
label: "macOS arm64 DMG",
matches: (assetName) => assetName.endsWith("-arm64.dmg"),
}
```

- [ ] **Step 4: Update `release.yml` to build both macOS architectures and merge manifests before publish**

```yml
- label: macOS arm64
runner: macos-14
platform: mac
arch: arm64
- label: macOS x64
runner: macos-13
platform: mac
arch: x64
```

```yml
- name: Merge macOS update manifests
run: node scripts/merge-mac-update-manifests.ts release-assets/latest-mac.yml release-assets/latest-mac-x64.yml release-assets/latest-mac.yml
```

- [ ] **Step 5: Update release smoke fixtures to model the new dual-arch mac release contract**

```ts
writeReleaseAssetFixtures([
"OK-Code-9.9.9-smoke.0-arm64.dmg",
"OK-Code-9.9.9-smoke.0-x64.dmg",
"OK-Code-9.9.9-smoke.0-arm64.zip",
"OK-Code-9.9.9-smoke.0-x64.zip",
]);
```

- [ ] **Step 6: Re-run the script tests**

Run: `bun run --cwd scripts test`
Expected: PASS for updated asset validation and smoke coverage.

### Task 3: Verify the live published release contract

**Files:**

- Modify: `scripts/release-ship.ts`
- Test: `scripts/release-ship.test.ts`
- Modify: `docs/release.md`

- [ ] **Step 1: Add failing tests for published release inspection**

```ts
it("checks GitHub Release assets for dual-arch mac OTA payloads after workflow success", () => {
// Assert `gh release view` or `gh api` output is parsed and validated.
});
```

- [ ] **Step 2: Run the script tests to verify the published-release checks fail**

Run: `bun run --cwd scripts test`
Expected: FAIL until `release-ship.ts` validates live release assets.

- [ ] **Step 3: Implement release verification and operator-facing docs**

```ts
const assets = await listReleaseAssets(tag);
validateReleaseAssets(assets);
validateMergedMacManifest(await downloadReleaseAsset(tag, "latest-mac.yml"));
```

```md
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.
```

- [ ] **Step 4: Re-run script tests**

Run: `bun run --cwd scripts test`
Expected: PASS for release shipping orchestration and live-release verification logic.

### Task 4: Workspace verification

**Files:**

- Modify: `package.json`
- Modify: `scripts/package.json`
- Modify: `.github/workflows/release.yml`
- Modify: `docs/release.md`
- Modify: `scripts/release-smoke.ts`
- Modify: `scripts/validate-release-assets.ts`
- Modify: `scripts/validate-release-assets.test.ts`
- Create: `scripts/release-ship.ts`
- Create: `scripts/release-ship.test.ts`

- [ ] **Step 1: Run formatting**

Run: `bun run fmt`
Expected: exit 0

- [ ] **Step 2: Run lint**

Run: `bun run lint`
Expected: exit 0

- [ ] **Step 3: Run typecheck**

Run: `bun run typecheck`
Expected: exit 0

- [ ] **Step 4: Run focused script tests and release smoke**

Run: `bun run --cwd scripts test`
Expected: PASS

Run: `bun run release:smoke`
Expected: PASS

- [ ] **Step 5: Run the full required workspace verification**

Run: `bun run fmt && bun run lint && bun run typecheck`
Expected: all exit 0
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"release:prepare": "node scripts/prepare-release.ts",
"release:validate": "node scripts/pre-release-validate.ts",
"release:smoke": "node scripts/release-smoke.ts",
"release:ship": "node scripts/release-ship.ts",
"dist:mobile:build": "bun run --cwd apps/mobile build",
"dist:mobile:sync:ios": "cd apps/mobile && bunx cap sync ios --deployment",
"patch:capacitor-local-notifications": "node scripts/patch-capacitor-local-notifications.ts",
Expand Down
5 changes: 2 additions & 3 deletions scripts/prepare-release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,10 +388,10 @@ Step-by-step playbook for the v${version} release. Each phase must complete befo
- [ ] \`okcode-CHANGELOG.md\` is attached.
- [ ] \`okcode-RELEASE-NOTES.md\` is attached.
- [ ] \`okcode-ASSETS-MANIFEST.md\` is attached.
- [ ] macOS release artifacts are attached: DMG, ZIP, updater manifest, and blockmaps.
- [ ] macOS arm64 release artifacts are attached: DMG, ZIP, updater manifest coverage, and blockmaps.
- [ ] macOS x64 release artifacts are attached: DMG, ZIP, updater manifest coverage, and blockmaps.
- [ ] Linux release artifacts are attached: AppImage and updater manifest if generated.
- [ ] Windows release artifacts are attached: installer, updater manifest, and blockmaps.
- [ ] If the Intel compatibility workflow is run, confirm the x64 macOS DMG is attached separately.

## Phase 2: Post-release verification

Expand All @@ -405,7 +405,6 @@ Step-by-step playbook for the v${version} release. Each phase must complete befo

## Phase 3: Follow-through

- [ ] Trigger the Intel compatibility workflow if macOS x64 artifacts are required for this train.
- [ ] Update external release references or announcements.
- [ ] Monitor reports for regressions in provider onboarding, auth flows, release packaging, and cross-platform install/update behavior.
`;
Expand Down
Loading
Loading