-
Notifications
You must be signed in to change notification settings - Fork 14
docs: add release checklist #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| # PawWork Release Checklist | ||
|
|
||
| Use this checklist for stable PawWork desktop releases. | ||
|
|
||
| ## 1. Prepare | ||
|
|
||
| - Confirm the release PR targets `dev` and all required CI checks are green. | ||
| - Confirm the version bump is merged into `dev`. | ||
| - Confirm `dev` is up to date locally: | ||
|
|
||
| ```bash | ||
| git switch dev | ||
| git pull --ff-only | ||
| ``` | ||
|
|
||
| - Confirm the release tag does not already exist: | ||
|
|
||
| ```bash | ||
| git fetch origin --tags | ||
| git tag -l vX.Y.Z | ||
| gh release view vX.Y.Z --repo Astro-Han/pawwork | ||
| ``` | ||
|
|
||
| ## 2. Draft Release Notes Before Publishing | ||
|
|
||
| Create or update the GitHub Release body before publishing the release. Use English first, direct user download links, then a short Chinese section. | ||
|
|
||
| ```md | ||
| ## Downloads | ||
|
|
||
| - [macOS Apple Silicon](https://github.com/Astro-Han/pawwork/releases/download/vX.Y.Z/pawwork-mac-arm64.dmg) | ||
| - [macOS Intel](https://github.com/Astro-Han/pawwork/releases/download/vX.Y.Z/pawwork-mac-x64.dmg) | ||
| - [Windows](https://github.com/Astro-Han/pawwork/releases/download/vX.Y.Z/pawwork-win-x64.exe) | ||
|
|
||
| ## Highlights | ||
|
|
||
| - User-facing changes, bug fixes, or packaging fixes. | ||
|
|
||
| ## Runtime And Maintenance | ||
|
|
||
| - Build, updater, notarization, dependency, or CI maintenance. | ||
|
|
||
| ## Verification | ||
|
|
||
| - macOS Apple Silicon submit/finalize completed successfully, including notarization. | ||
| - macOS Intel submit/finalize completed successfully, including notarization. | ||
| - Windows x64 release build completed successfully. | ||
| - vX.Y.Z is published as the latest stable release. | ||
|
|
||
| ## 中文版本 | ||
|
|
||
|
Astro-Han marked this conversation as resolved.
|
||
| ### 下载 | ||
|
|
||
| - [macOS Apple 芯片](https://github.com/Astro-Han/pawwork/releases/download/vX.Y.Z/pawwork-mac-arm64.dmg) | ||
| - [macOS Intel 芯片](https://github.com/Astro-Han/pawwork/releases/download/vX.Y.Z/pawwork-mac-x64.dmg) | ||
| - [Windows](https://github.com/Astro-Han/pawwork/releases/download/vX.Y.Z/pawwork-win-x64.exe) | ||
|
|
||
|
Astro-Han marked this conversation as resolved.
|
||
| ### 主要更新 | ||
|
|
||
| - 用一句话概括主要变化。 | ||
| ``` | ||
|
|
||
| Do not rely on the GitHub Assets list as the primary download UI. It mixes user installers with updater metadata, so direct links make the intended downloads clear. | ||
|
|
||
| ## 3. Build Release Artifacts | ||
|
|
||
| Submit macOS notarization for both architectures: | ||
|
|
||
| ```bash | ||
| gh workflow run build.yml --repo Astro-Han/pawwork --ref dev -f phase=submit -f channel=prod -f target=macos -f arch=arm64 | ||
| gh workflow run build.yml --repo Astro-Han/pawwork --ref dev -f phase=submit -f channel=prod -f target=macos -f arch=x64 | ||
| ``` | ||
|
|
||
| Record each submit run's source run ID, source run attempt, source ref, source sha, workflow ref, workflow sha, and Apple submission ID from the workflow summary. | ||
|
|
||
| The submit workflow summary should include values like this: | ||
|
|
||
| ```text | ||
| source_run_id: 123456789 | ||
| source_run_attempt: 1 | ||
| source_ref: dev | ||
| source_sha: 0123456789abcdef0123456789abcdef01234567 | ||
| source_workflow_ref: dev | ||
| source_workflow_sha: 0123456789abcdef0123456789abcdef01234567 | ||
|
Astro-Han marked this conversation as resolved.
|
||
| submission_id: 00000000-0000-0000-0000-000000000000 | ||
| ``` | ||
|
|
||
| Finalize each macOS architecture with the exact command emitted by its submit workflow summary. If writing the commands manually, keep the arm64 and x64 values separate: | ||
|
|
||
| For arm64, replace `ARM64_SOURCE_RUN_ID` with the `source_run_id` value from the arm64 submit summary, and apply the same mapping for the other `ARM64_` placeholders. Repeat with the x64 submit summary for the `X64_` placeholders. | ||
|
|
||
| ```bash | ||
| gh workflow run build.yml --repo Astro-Han/pawwork --ref ARM64_SOURCE_WORKFLOW_REF -f phase=finalize -f channel=prod -f arch=arm64 -f source_run_id=ARM64_SOURCE_RUN_ID -f source_run_attempt=ARM64_SOURCE_RUN_ATTEMPT -f source_ref=ARM64_SOURCE_REF -f source_sha=ARM64_SOURCE_SHA -f source_workflow_ref=ARM64_SOURCE_WORKFLOW_REF -f source_workflow_sha=ARM64_SOURCE_WORKFLOW_SHA -f submission_id=ARM64_SUBMISSION_ID | ||
| gh workflow run build.yml --repo Astro-Han/pawwork --ref X64_SOURCE_WORKFLOW_REF -f phase=finalize -f channel=prod -f arch=x64 -f source_run_id=X64_SOURCE_RUN_ID -f source_run_attempt=X64_SOURCE_RUN_ATTEMPT -f source_ref=X64_SOURCE_REF -f source_sha=X64_SOURCE_SHA -f source_workflow_ref=X64_SOURCE_WORKFLOW_REF -f source_workflow_sha=X64_SOURCE_WORKFLOW_SHA -f submission_id=X64_SUBMISSION_ID | ||
| ``` | ||
|
|
||
| Build and publish the Windows installer: | ||
|
|
||
| ```bash | ||
| gh workflow run build.yml --repo Astro-Han/pawwork --ref dev -f phase=full -f channel=prod -f target=windows -f arch=x64 | ||
| ``` | ||
|
|
||
|
Astro-Han marked this conversation as resolved.
|
||
| ## 4. Publish | ||
|
|
||
| Verify the draft release has all expected user-facing installers before publishing: | ||
|
|
||
| ```bash | ||
| gh release view vX.Y.Z --repo Astro-Han/pawwork --json isDraft,isPrerelease,assets,url | ||
| ``` | ||
|
|
||
| Publish the release as the latest stable release: | ||
|
|
||
| ```bash | ||
| gh release edit vX.Y.Z --repo Astro-Han/pawwork --draft=false --latest --prerelease=false | ||
| ``` | ||
|
|
||
| ## 5. Post-Release Verification | ||
|
|
||
| Run the verification helper: | ||
|
|
||
| ```bash | ||
| export GH_TOKEN="$(gh auth token)" | ||
| bun packages/desktop-electron/scripts/verify-release.ts vX.Y.Z | ||
| ``` | ||
|
|
||
| `GH_TOKEN` is recommended so GitHub API requests use the authenticated rate limit. | ||
|
|
||
| The helper verifies: | ||
|
|
||
| - The GitHub Release is not a draft. | ||
| - The GitHub Release is not a prerelease. | ||
| - `pawwork-mac-arm64.dmg` exists. | ||
| - `pawwork-mac-x64.dmg` exists. | ||
| - `pawwork-win-x64.exe` exists. | ||
| - updater `.zip` and `.blockmap` assets exist. | ||
| - `latest.yml` points to `pawwork-win-x64.exe`. | ||
| - `latest-mac.yml` includes both `pawwork-mac-arm64.zip` and `pawwork-mac-x64.zip`. | ||
|
|
||
| Keep `.zip`, `.blockmap`, and `latest*.yml` assets unless updater requirements are proven safe without them. | ||
|
|
||
| If verification fails, check the reported missing or malformed asset first, rerun only the affected build phase, and publish the release only after the verification helper passes. | ||
|
|
||
| ## 6. Close Release Issues | ||
|
|
||
| Only close release-blocking issues after post-release verification passes. Leave a short comment with the release link and the verified artifact names. | ||
248 changes: 248 additions & 0 deletions
248
packages/desktop-electron/scripts/verify-release.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| import { afterEach, describe, expect, test } from "bun:test" | ||
|
|
||
| import { | ||
| fetchJson, | ||
| fetchText, | ||
| normalizeTag, | ||
| parseUpdaterFileUrls, | ||
| verifyReleasePayload, | ||
| type GithubRelease, | ||
| } from "./verify-release" | ||
|
|
||
| const originalFetch = globalThis.fetch | ||
|
|
||
| afterEach(() => { | ||
| globalThis.fetch = originalFetch | ||
| }) | ||
|
|
||
| const baseRelease: GithubRelease = { | ||
|
Astro-Han marked this conversation as resolved.
|
||
| tag_name: "v0.2.6", | ||
| draft: false, | ||
| prerelease: false, | ||
| assets: [ | ||
| { | ||
| name: "pawwork-mac-arm64.dmg", | ||
| browser_download_url: "https://example.com/pawwork-mac-arm64.dmg", | ||
| }, | ||
| { | ||
| name: "pawwork-mac-arm64.zip", | ||
| browser_download_url: "https://example.com/pawwork-mac-arm64.zip", | ||
| }, | ||
| { | ||
| name: "pawwork-mac-arm64.zip.blockmap", | ||
| browser_download_url: "https://example.com/pawwork-mac-arm64.zip.blockmap", | ||
| }, | ||
| { | ||
| name: "pawwork-mac-x64.dmg", | ||
| browser_download_url: "https://example.com/pawwork-mac-x64.dmg", | ||
| }, | ||
| { | ||
| name: "pawwork-mac-x64.zip", | ||
| browser_download_url: "https://example.com/pawwork-mac-x64.zip", | ||
| }, | ||
| { | ||
| name: "pawwork-mac-x64.zip.blockmap", | ||
| browser_download_url: "https://example.com/pawwork-mac-x64.zip.blockmap", | ||
| }, | ||
| { | ||
| name: "pawwork-win-x64.exe", | ||
| browser_download_url: "https://example.com/pawwork-win-x64.exe", | ||
| }, | ||
| { | ||
| name: "pawwork-win-x64.exe.blockmap", | ||
| browser_download_url: "https://example.com/pawwork-win-x64.exe.blockmap", | ||
| }, | ||
| { | ||
| name: "latest.yml", | ||
| browser_download_url: "https://example.com/latest.yml", | ||
| }, | ||
| { | ||
| name: "latest-mac.yml", | ||
| browser_download_url: "https://example.com/latest-mac.yml", | ||
| }, | ||
| ], | ||
| } | ||
|
|
||
| describe("verify-release", () => { | ||
| test("normalizes release tags", () => { | ||
| expect(normalizeTag("0.2.6")).toBe("v0.2.6") | ||
| expect(normalizeTag("v0.2.6")).toBe("v0.2.6") | ||
| expect(() => normalizeTag("vv0.2.6")).toThrow("Invalid release tag") | ||
| expect(() => normalizeTag("abc")).toThrow("Invalid release tag") | ||
| }) | ||
|
|
||
| test("parses updater file urls and path entries", () => { | ||
| expect( | ||
| parseUpdaterFileUrls(`version: 0.2.6 | ||
| files: | ||
| - url: pawwork-mac-arm64.zip | ||
| size: 1 | ||
| - url: pawwork-mac-x64.zip | ||
| size: 2 | ||
| path: pawwork-mac-arm64.zip | ||
| `), | ||
| ).toEqual(["pawwork-mac-arm64.zip", "pawwork-mac-x64.zip", "pawwork-mac-arm64.zip"]) | ||
| }) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| test("parses quoted updater file urls and path entries", () => { | ||
| expect( | ||
| parseUpdaterFileUrls(`files: | ||
| - url: "pawwork-mac-arm64.zip" | ||
| - url: 'pawwork-mac-x64.zip' # Intel macOS updater asset | ||
| - url: "pawwork-mac#arm64.zip" | ||
| path: "pawwork-win-x64.exe" # Windows updater asset | ||
| `), | ||
| ).toEqual(["pawwork-mac-arm64.zip", "pawwork-mac-x64.zip", "pawwork-mac#arm64.zip", "pawwork-win-x64.exe"]) | ||
| }) | ||
|
|
||
| test("keeps inline comments outside escaped quoted values", () => { | ||
| expect( | ||
| parseUpdaterFileUrls(String.raw`files: | ||
| - url: "pawwork-mac\"arm64.zip" # comment | ||
| - url: "pawwork-mac\\" | ||
| path: pawwork-win-x64.exe | ||
| `), | ||
| ).toEqual([String.raw`pawwork-mac\"arm64.zip`, String.raw`pawwork-mac\\`, "pawwork-win-x64.exe"]) | ||
| }) | ||
|
|
||
| test("accepts a stable release with expected assets and updater metadata", () => { | ||
| expect( | ||
| verifyReleasePayload({ | ||
| release: baseRelease, | ||
| latestYml: "files:\n - url: pawwork-win-x64.exe\n", | ||
| latestMacYml: "files:\n - url: pawwork-mac-arm64.zip\n - url: pawwork-mac-x64.zip\n", | ||
| }), | ||
| ).toEqual([]) | ||
| }) | ||
|
|
||
| test("accepts updater metadata entries with full download URLs", () => { | ||
| expect( | ||
| verifyReleasePayload({ | ||
| release: baseRelease, | ||
| latestYml: "files:\n - url: https://github.com/Astro-Han/pawwork/releases/download/v0.2.6/pawwork-win-x64.exe\n", | ||
| latestMacYml: | ||
| "files:\n - url: https://github.com/Astro-Han/pawwork/releases/download/v0.2.6/pawwork-mac-arm64.zip\n - url: https://github.com/Astro-Han/pawwork/releases/download/v0.2.6/pawwork-mac-x64.zip\n", | ||
| }), | ||
| ).toEqual([]) | ||
| }) | ||
|
|
||
| test("reports missing macOS updater architecture metadata", () => { | ||
| expect( | ||
| verifyReleasePayload({ | ||
| release: baseRelease, | ||
| latestYml: "files:\n - url: pawwork-win-x64.exe\n", | ||
| latestMacYml: "files:\n - url: pawwork-mac-x64.zip\n", | ||
| }), | ||
| ).toContain("latest-mac.yml does not include pawwork-mac-arm64.zip") | ||
| }) | ||
|
|
||
| test("reports updater metadata that points to a missing asset", () => { | ||
| expect( | ||
| verifyReleasePayload({ | ||
| release: { | ||
| ...baseRelease, | ||
| assets: baseRelease.assets.filter((asset) => asset.name !== "pawwork-mac-arm64.zip"), | ||
| }, | ||
| latestYml: "files:\n - url: pawwork-win-x64.exe\n", | ||
| latestMacYml: "files:\n - url: pawwork-mac-arm64.zip\n - url: pawwork-mac-x64.zip\n", | ||
| }), | ||
| ).toContain("latest-mac.yml references missing release asset: pawwork-mac-arm64.zip") | ||
| }) | ||
|
|
||
| test("reports missing installer and updater sidecar assets", () => { | ||
| const failures = verifyReleasePayload({ | ||
| release: { | ||
| ...baseRelease, | ||
| assets: baseRelease.assets.filter( | ||
| (asset) => asset.name !== "pawwork-mac-arm64.dmg" && asset.name !== "pawwork-win-x64.exe.blockmap", | ||
| ), | ||
| }, | ||
| latestYml: "files:\n - url: pawwork-win-x64.exe\n", | ||
| latestMacYml: "files:\n - url: pawwork-mac-arm64.zip\n - url: pawwork-mac-x64.zip\n", | ||
| }) | ||
|
|
||
| expect(failures).toContain("Missing release asset: pawwork-mac-arm64.dmg") | ||
| expect(failures).toContain("Missing release asset: pawwork-win-x64.exe.blockmap") | ||
| }) | ||
|
|
||
| test("reports missing updater metadata assets without requiring metadata downloads", () => { | ||
| const failures = verifyReleasePayload({ | ||
| release: { | ||
| ...baseRelease, | ||
| assets: baseRelease.assets.filter((asset) => asset.name !== "latest.yml" && asset.name !== "latest-mac.yml"), | ||
| }, | ||
| latestYml: "", | ||
| latestMacYml: "", | ||
| }) | ||
|
|
||
| expect(failures).toContain("Missing release asset: latest.yml") | ||
| expect(failures).toContain("Missing release asset: latest-mac.yml") | ||
| expect(failures).toContain("latest.yml does not include pawwork-win-x64.exe") | ||
| expect(failures).toContain("latest-mac.yml does not include pawwork-mac-arm64.zip") | ||
| expect(failures).toContain("latest-mac.yml does not include pawwork-mac-x64.zip") | ||
| }) | ||
|
|
||
| test("reports draft releases", () => { | ||
| expect( | ||
| verifyReleasePayload({ | ||
| release: { ...baseRelease, draft: true }, | ||
| latestYml: "files:\n - url: pawwork-win-x64.exe\n", | ||
| latestMacYml: "files:\n - url: pawwork-mac-arm64.zip\n - url: pawwork-mac-x64.zip\n", | ||
| }), | ||
| ).toContain("Release v0.2.6 is still a draft") | ||
| }) | ||
|
|
||
| test("reports prerelease releases", () => { | ||
| expect( | ||
| verifyReleasePayload({ | ||
| release: { ...baseRelease, prerelease: true }, | ||
| latestYml: "files:\n - url: pawwork-win-x64.exe\n", | ||
| latestMacYml: "files:\n - url: pawwork-mac-arm64.zip\n - url: pawwork-mac-x64.zip\n", | ||
| }), | ||
| ).toContain("Release v0.2.6 is marked as a prerelease") | ||
| }) | ||
|
|
||
| test("reports malformed updater metadata as missing required updater entries", () => { | ||
| const failures = verifyReleasePayload({ | ||
| release: baseRelease, | ||
| latestYml: "files:\n - broken: pawwork-win-x64.exe\n", | ||
| latestMacYml: "files:\n - broken: pawwork-mac-arm64.zip\n", | ||
| }) | ||
|
|
||
| expect(failures).toContain("latest.yml does not include pawwork-win-x64.exe") | ||
| expect(failures).toContain("latest-mac.yml does not include pawwork-mac-arm64.zip") | ||
| expect(failures).toContain("latest-mac.yml does not include pawwork-mac-x64.zip") | ||
| }) | ||
|
|
||
| test("fetchText reports GitHub rate limit headers on HTTP errors", async () => { | ||
| globalThis.fetch = (() => | ||
| Promise.resolve( | ||
| new Response("rate limited", { | ||
| status: 403, | ||
| statusText: "Forbidden", | ||
| headers: { | ||
| "x-ratelimit-remaining": "0", | ||
| "x-ratelimit-reset": "1234567890", | ||
| }, | ||
| }), | ||
| )) as typeof fetch | ||
|
|
||
| await expect(fetchText("https://api.github.com/example")).rejects.toThrow("rate limit remaining: 0") | ||
| }) | ||
|
|
||
| test("fetchText reports network failures with the request URL", async () => { | ||
| globalThis.fetch = (() => Promise.reject(new Error("socket hang up"))) as typeof fetch | ||
|
|
||
| await expect(fetchText("https://api.github.com/example")).rejects.toThrow( | ||
| "Failed to fetch https://api.github.com/example: socket hang up", | ||
| ) | ||
| }) | ||
|
|
||
| test("fetchJson reports invalid JSON with the request URL", async () => { | ||
| globalThis.fetch = (() => Promise.resolve(new Response("not json", { status: 200 }))) as typeof fetch | ||
|
|
||
| await expect(fetchJson("https://api.github.com/example")).rejects.toThrow( | ||
| "Failed to parse JSON from https://api.github.com/example", | ||
| ) | ||
| }) | ||
| }) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.