Skip to content

Commit 3dd7b59

Browse files
committed
release: harden v0.24.0 rollout
1 parent 516baad commit 3dd7b59

8 files changed

Lines changed: 204 additions & 70 deletions

File tree

.github/workflows/release.yml

Lines changed: 25 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,6 @@ on:
1010
description: "Release version (for example 1.2.3 or v1.2.3)"
1111
required: true
1212
type: string
13-
publish_cli:
14-
description: "Publish the okcodes CLI to npm after the desktop release finalizes"
15-
required: false
16-
default: false
17-
type: boolean
1813

1914
permissions:
2015
contents: write
@@ -228,6 +223,9 @@ jobs:
228223
"$AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME" \
229224
"$AZURE_TRUSTED_SIGNING_PUBLISHER_NAME"; then
230225
args+=(--signed --require-signed)
226+
elif [[ "${{ needs.preflight.outputs.is_prerelease }}" == "false" ]]; then
227+
echo "Missing Azure Trusted Signing secrets for a stable Windows release." >&2
228+
exit 1
231229
else
232230
echo "Azure Trusted Signing secrets not configured; building unsigned Windows artifact." >&2
233231
fi
@@ -288,12 +286,8 @@ jobs:
288286
name: iOS signing preflight
289287
needs: [preflight]
290288
runs-on: ubuntu-24.04
291-
outputs:
292-
enabled: ${{ steps.check.outputs.enabled }}
293-
missing: ${{ steps.check.outputs.missing }}
294289
steps:
295-
- id: check
296-
name: Check iOS signing secrets
290+
- name: Check iOS signing secrets
297291
shell: bash
298292
env:
299293
IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }}
@@ -325,21 +319,17 @@ jobs:
325319
fi
326320
done
327321
328-
if (( ${#missing[@]} == 0 )); then
329-
echo "enabled=true" >> "$GITHUB_OUTPUT"
330-
echo "missing=" >> "$GITHUB_OUTPUT"
331-
echo "All required iOS signing secrets are configured."
332-
else
322+
if (( ${#missing[@]} > 0 )); then
333323
missing_csv="$(IFS=,; echo "${missing[*]}")"
334-
echo "enabled=false" >> "$GITHUB_OUTPUT"
335-
echo "missing=$missing_csv" >> "$GITHUB_OUTPUT"
336-
echo "Skipping iOS TestFlight because the following required secrets are missing: $missing_csv"
324+
echo "Missing required iOS signing secrets: $missing_csv" >&2
325+
exit 1
337326
fi
338327
328+
echo "All required iOS signing secrets are configured."
329+
339330
ios_testflight:
340331
name: iOS TestFlight
341332
needs: [preflight, ios_signing_preflight]
342-
if: ${{ needs.ios_signing_preflight.outputs.enabled == 'true' }}
343333
runs-on: macos-14
344334
env:
345335
RELEASE_VERSION: ${{ needs.preflight.outputs.version }}
@@ -525,20 +515,19 @@ jobs:
525515
526516
publish_cli:
527517
name: Publish CLI
528-
needs: [preflight, finalize]
529-
if: ${{ !cancelled() && needs.finalize.result == 'success' && github.event_name == 'workflow_dispatch' && inputs.publish_cli }}
530-
continue-on-error: true
518+
needs: [preflight, desktop_build, ios_signing_preflight, ios_testflight]
519+
if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.desktop_build.result == 'success' && needs.ios_signing_preflight.result == 'success' && needs.ios_testflight.result == 'success' }}
531520
runs-on: ubuntu-24.04
532521
env:
533522
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
534523
OKCODE_COMMIT_HASH: ${{ github.sha }}
535524
OKCODE_BUILD_TIMESTAMP: ${{ needs.preflight.outputs.build_timestamp }}
536525
OKCODE_RELEASE_CHANNEL: ${{ needs.preflight.outputs.release_channel }}
537526
steps:
538-
- name: Checkout finalized main
527+
- name: Checkout release commit
539528
uses: actions/checkout@v6
540529
with:
541-
ref: main
530+
ref: ${{ needs.preflight.outputs.ref }}
542531
fetch-depth: 0
543532

544533
- name: Setup Bun
@@ -555,29 +544,14 @@ jobs:
555544
- name: Install dependencies
556545
run: bun install --frozen-lockfile
557546

558-
- name: Assert package versions match stated release version
559-
env:
560-
RELEASE_VERSION: ${{ needs.preflight.outputs.version }}
547+
- name: Validate npm publish token
548+
shell: bash
561549
run: |
562-
node <<'NODE'
563-
const fs = require('fs');
564-
const files = [
565-
'apps/server/package.json',
566-
'apps/desktop/package.json',
567-
'apps/web/package.json',
568-
'apps/mobile/package.json',
569-
'packages/contracts/package.json',
570-
];
571-
const expected = process.env.RELEASE_VERSION;
572-
const mismatches = files.filter((file) => {
573-
const version = JSON.parse(fs.readFileSync(file, 'utf8')).version;
574-
return version !== expected;
575-
});
576-
if (mismatches.length > 0) {
577-
console.error(`Package version mismatch for release ${expected}: ${mismatches.join(', ')}`);
578-
process.exit(1);
579-
}
580-
NODE
550+
set -euo pipefail
551+
[[ -n "$NODE_AUTH_TOKEN" ]] || { echo "Missing secret NODE_AUTH_TOKEN" >&2; exit 1; }
552+
553+
- name: Align package versions to release version
554+
run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}"
581555

582556
- name: Build CLI package
583557
run: bun run build --filter=@okcode/web --filter=okcodes
@@ -607,8 +581,8 @@ jobs:
607581
608582
release:
609583
name: Publish GitHub Release
610-
needs: [preflight, desktop_build, ios_signing_preflight, ios_testflight]
611-
if: ${{ always() && !cancelled() && needs.preflight.result == 'success' && needs.desktop_build.result == 'success' && needs.ios_signing_preflight.result == 'success' && (needs.ios_testflight.result == 'success' || needs.ios_testflight.result == 'skipped') }}
584+
needs: [preflight, desktop_build, ios_signing_preflight, ios_testflight, publish_cli]
585+
if: ${{ always() && !cancelled() && needs.preflight.result == 'success' && needs.desktop_build.result == 'success' && needs.ios_signing_preflight.result == 'success' && needs.ios_testflight.result == 'success' && needs.publish_cli.result == 'success' }}
612586
runs-on: ubuntu-24.04
613587
steps:
614588
- name: Checkout
@@ -643,6 +617,9 @@ jobs:
643617
cp "$notes" release-assets/okcode-RELEASE-NOTES.md
644618
cp "$manifest" release-assets/okcode-ASSETS-MANIFEST.md
645619
620+
- name: Validate release asset completeness
621+
run: node scripts/validate-release-assets.ts release-assets
622+
646623
- name: Publish release
647624
uses: softprops/action-gh-release@v2
648625
with:

docs/releases/v0.24.0.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Theme presets, markdown rendering unification, project icon support, and active-
2121

2222
## Upgrade and install
2323

24-
- **CLI:** `npm install -g okcodes@0.24.0` (after the package is published to npm manually).
24+
- **CLI:** `npm install -g okcodes@0.24.0` once the coordinated release workflow finishes.
2525
- **Desktop:** Download from [GitHub Releases](https://github.com/OpenKnots/okcode/releases/tag/v0.24.0). Filenames are listed in [assets.md](v0.24.0/assets.md).
2626
- **iOS:** Available via TestFlight (uploaded automatically by the Release iOS workflow).
2727

docs/releases/v0.24.0/assets.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# v0.24.0 — Release assets (manifest)
22

3-
Binaries are **not** stored in this git repository; they are attached to the [GitHub Release for `v0.24.0`](https://github.com/OpenKnots/okcode/releases/tag/v0.24.0) by the [Release Desktop workflow](../../.github/workflows/release.yml).
3+
Binaries are **not** stored in this git repository; they are attached to the [GitHub Release for `v0.24.0`](https://github.com/OpenKnots/okcode/releases/tag/v0.24.0) by the [Release workflow](../../.github/workflows/release.yml).
44

55
The GitHub Release also includes **documentation attachments** (same content as in-repo, stable filenames for download):
66

docs/releases/v0.24.0/rollout-checklist.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Step-by-step playbook for the v0.24.0 release. Each phase must complete before a
3535
- [ ] Push the release-prep commit to `main`.
3636
- [ ] Create and push tag `v0.24.0`.
3737
- [ ] Verify the coordinated `release.yml` workflow starts.
38-
- [ ] Monitor the pipeline through Preflight, Desktop builds, iOS signing preflight, optional iOS TestFlight, Publish GitHub Release, Finalize release, and optional CLI publish if started through manual dispatch.
38+
- [ ] Monitor the pipeline through Preflight, Desktop builds, iOS signing preflight, iOS TestFlight, Publish CLI, Publish GitHub Release, and Finalize release.
3939

4040
### Asset verification
4141

scripts/prepare-release.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ ${highlights || "- See changelog for detailed changes."}
263263
264264
## Upgrade and install
265265
266-
- **CLI:** \`npm install -g okcodes@${version}\` (after the package is published to npm manually).
266+
- **CLI:** \`npm install -g okcodes@${version}\` once the coordinated release workflow finishes.
267267
- **Desktop:** Download from [GitHub Releases](${REPO_URL}/releases/tag/v${version}). Filenames are listed in [assets.md](v${version}/assets.md).
268268
- **iOS:** Available via TestFlight (uploaded automatically by the Release iOS workflow).
269269
@@ -282,7 +282,7 @@ OK Code remains early work in progress. Expect rough edges around session recove
282282
function generateAssetManifest(version: string): string {
283283
return `# v${version} — Release assets (manifest)
284284
285-
Binaries are **not** stored in this git repository; they are attached to the [GitHub Release for \`v${version}\`](${REPO_URL}/releases/tag/v${version}) by the [Release Desktop workflow](../../.github/workflows/release.yml).
285+
Binaries are **not** stored in this git repository; they are attached to the [GitHub Release for \`v${version}\`](${REPO_URL}/releases/tag/v${version}) by the [Release workflow](../../.github/workflows/release.yml).
286286
287287
The GitHub Release also includes **documentation attachments** (same content as in-repo, stable filenames for download):
288288
@@ -378,7 +378,7 @@ Step-by-step playbook for the v${version} release. Each phase must complete befo
378378
- [ ] Push the release-prep commit to \`main\`.
379379
- [ ] Create and push tag \`v${version}\`.
380380
- [ ] Verify the coordinated \`release.yml\` workflow starts.
381-
- [ ] Monitor the pipeline through Preflight, Desktop builds, iOS signing preflight, optional iOS TestFlight, Publish GitHub Release, Finalize release, and optional CLI publish if started through manual dispatch.
381+
- [ ] Monitor the pipeline through Preflight, Desktop builds, iOS signing preflight, iOS TestFlight, Publish CLI, Publish GitHub Release, and Finalize release.
382382
383383
### Asset verification
384384
@@ -885,12 +885,6 @@ async function main(): Promise<void> {
885885
log("--", "Would commit release documentation.");
886886
log("--", `Would create tag: ${tag}`);
887887
log("--", `Would push tag: ${tag}`);
888-
if (fullMatrix) {
889-
log(
890-
"--",
891-
`Would optionally publish the CLI after release via: gh workflow run release.yml -f version=${version} -f publish_cli=true`,
892-
);
893-
}
894888
} else {
895889
log("--", "Skipping commit/tag/push (--skip-commit).");
896890
}
@@ -934,11 +928,7 @@ async function main(): Promise<void> {
934928
if (fullMatrix) {
935929
log(
936930
"!!",
937-
"The current release workflow already runs the full platform matrix on tag push. Use workflow_dispatch with publish_cli=true only if you need the optional CLI publish lane.",
938-
);
939-
log(
940-
"!!",
941-
`Optional CLI publish dispatch: gh workflow run release.yml -f version=${version} -f publish_cli=true`,
931+
"The current release workflow already runs the full platform matrix on tag push. The --full-matrix flag is deprecated and no longer changes release behavior.",
942932
);
943933
}
944934
}
@@ -971,11 +961,6 @@ async function main(): Promise<void> {
971961
console.log(` 3. git commit -m "release: prepare v${version}"`);
972962
console.log(` 4. git push origin main`);
973963
console.log(` 5. git tag ${tag} && git push origin ${tag}`);
974-
if (fullMatrix) {
975-
console.log(
976-
` 6. Optional CLI publish dispatch: gh workflow run release.yml -f version=${version} -f publish_cli=true`,
977-
);
978-
}
979964
console.log("");
980965
}
981966
}

scripts/release-smoke.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,27 @@ releaseDate: '2026-03-08T10:36:07.540Z'
7070
return { arm64Path, x64Path };
7171
}
7272

73+
function writeReleaseAssetFixtures(targetRoot: string): void {
74+
const assetDirectory = resolve(targetRoot, "release-assets");
75+
mkdirSync(assetDirectory, { recursive: true });
76+
77+
for (const assetName of [
78+
"OK-Code-9.9.9-smoke.0-arm64.dmg",
79+
"OK-Code-9.9.9-smoke.0-arm64.zip",
80+
"OK-Code-9.9.9-smoke.0-arm64.zip.blockmap",
81+
"OK-Code-9.9.9-smoke.0.AppImage",
82+
"OK-Code-9.9.9-smoke.0.exe",
83+
"OK-Code-9.9.9-smoke.0.exe.blockmap",
84+
"latest-linux.yml",
85+
"latest.yml",
86+
"okcode-CHANGELOG.md",
87+
"okcode-RELEASE-NOTES.md",
88+
"okcode-ASSETS-MANIFEST.md",
89+
]) {
90+
writeFileSync(resolve(assetDirectory, assetName), `${assetName}\n`);
91+
}
92+
}
93+
7394
function assertContains(haystack: string, needle: string, message: string): void {
7495
if (!haystack.includes(needle)) {
7596
throw new Error(message);
@@ -108,6 +129,7 @@ try {
108129
);
109130

110131
const { arm64Path, x64Path } = writeMacManifestFixtures(tempRoot);
132+
writeReleaseAssetFixtures(tempRoot);
111133
execFileSync(
112134
process.execPath,
113135
[resolve(repoRoot, "scripts/merge-mac-update-manifests.ts"), arm64Path, x64Path],
@@ -129,6 +151,15 @@ try {
129151
"Merged manifest is missing the x64 asset.",
130152
);
131153

154+
execFileSync(
155+
process.execPath,
156+
[resolve(repoRoot, "scripts/validate-release-assets.ts"), resolve(tempRoot, "release-assets")],
157+
{
158+
cwd: repoRoot,
159+
stdio: "inherit",
160+
},
161+
);
162+
132163
console.log("Release smoke checks passed.");
133164
} finally {
134165
rmSync(tempRoot, { recursive: true, force: true });
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { assert, describe, it } from "@effect/vitest";
2+
3+
import { validateReleaseAssets } from "./validate-release-assets.ts";
4+
5+
describe("validateReleaseAssets", () => {
6+
const completeAssetSet = [
7+
"OK-Code-0.24.0-arm64.dmg",
8+
"OK-Code-0.24.0-arm64.zip",
9+
"OK-Code-0.24.0-arm64.zip.blockmap",
10+
"OK-Code-0.24.0.AppImage",
11+
"OK-Code-0.24.0.exe",
12+
"OK-Code-0.24.0.exe.blockmap",
13+
"latest-mac.yml",
14+
"latest-linux.yml",
15+
"latest.yml",
16+
"okcode-CHANGELOG.md",
17+
"okcode-RELEASE-NOTES.md",
18+
"okcode-ASSETS-MANIFEST.md",
19+
];
20+
21+
it("accepts a complete coordinated release asset set", () => {
22+
assert.doesNotThrow(() => validateReleaseAssets(completeAssetSet));
23+
});
24+
25+
it("rejects releases that are missing a required desktop asset class", () => {
26+
assert.throws(
27+
() => validateReleaseAssets(completeAssetSet.filter((asset) => asset !== "latest-linux.yml")),
28+
/Missing required release assets: linux updater manifest/,
29+
);
30+
});
31+
32+
it("rejects releases that are missing required documentation attachments", () => {
33+
assert.throws(
34+
() =>
35+
validateReleaseAssets(
36+
completeAssetSet.filter((asset) => asset !== "okcode-ASSETS-MANIFEST.md"),
37+
),
38+
/Missing required release assets: asset manifest attachment/,
39+
);
40+
});
41+
});

0 commit comments

Comments
 (0)