From dc304360704f851cd243bb6a5dc7abda15a4ddd5 Mon Sep 17 00:00:00 2001 From: Pawan Sedara Date: Tue, 21 Jul 2026 01:11:32 +1000 Subject: [PATCH] fix: align preview release automation --- .github/workflows/ci.yml | 6 ++-- README.md | 6 ++++ docs/RELEASE.md | 34 +++++++++++++++++--- docs/ROADMAP.md | 1 + scripts/package-macos-app.sh | 2 +- scripts/publish-github-release.sh | 5 +++ scripts/release-macos-app.sh | 2 +- scripts/release-version.sh | 16 ++++++++++ tests/macos_app_bundle.rs | 3 ++ tests/release_scripts.rs | 52 +++++++++++++++++++++++++++++++ 10 files changed, 118 insertions(+), 9 deletions(-) create mode 100755 scripts/release-version.sh create mode 100644 tests/release_scripts.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf3494c..60736da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install Rust components run: rustup component add rustfmt clippy @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install Rust components run: rustup component add rustfmt clippy @@ -54,7 +54,7 @@ jobs: run: scripts/package-macos-app.sh - name: Upload app package - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: plainlink-macos-app path: | diff --git a/README.md b/README.md index d72f24a..c052f25 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,12 @@ This creates `dist/packages/PlainLink--macos-.zip` and a `.sha256 This zip is for developer-preview testing. macOS Gatekeeper will warn because the app is not signed with a Developer ID certificate or notarized by Apple. +For a preview-tagged artifact, pass the preview version explicitly: + +```sh +PLAINLINK_RELEASE_VERSION=v0.1.0-preview.2 scripts/package-macos-app.sh +``` + To compile a safe subset from an external source and write a manifest: ```sh diff --git a/docs/RELEASE.md b/docs/RELEASE.md index fd6cff2..4198541 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -11,11 +11,18 @@ Current status: release automation exists, but this repository does not currentl Use this for technical testers: ```sh -scripts/package-macos-app.sh +PLAINLINK_RELEASE_VERSION=v0.1.0-preview.2 scripts/package-macos-app.sh ``` This produces an unsigned zip and checksum in `dist/packages/`. macOS Gatekeeper will warn users because the app is not signed with a Developer ID certificate and is not notarized. +Preview artifacts include the preview suffix in the filename, for example: + +```text +dist/packages/PlainLink-0.1.0-preview.2-macos-arm64.zip +dist/packages/PlainLink-0.1.0-preview.2-macos-arm64.zip.sha256 +``` + ### Signed Regular-User Release Use this only when a release machine has Apple Developer Program credentials: @@ -69,6 +76,16 @@ The script: - verifies Gatekeeper assessment, - repackages the stapled app and writes a SHA-256 checksum. +## Version Source + +Packaging scripts use `scripts/release-version.sh` as the single source of truth: + +1. `PLAINLINK_RELEASE_VERSION`, if set. +2. The exact Git tag at `HEAD`, if one exists. +3. The Cargo package version as a local development fallback. + +Use `PLAINLINK_RELEASE_VERSION` when building a preview artifact from a release branch or before checking out the preview tag. Use an exact tag checkout for final release builds. + ## Publish GitHub Release After the signed zip and checksum exist, create and push the tag: @@ -79,10 +96,19 @@ git push origin v0.1.0 scripts/publish-github-release.sh v0.1.0 ``` +For preview releases, the publish script expects preview-named artifacts and falls back to the base version notes if preview-specific notes do not exist: + +```sh +PLAINLINK_RELEASE_VERSION=v0.1.0-preview.2 scripts/package-macos-app.sh +git tag -a v0.1.0-preview.2 -m "PlainLink v0.1.0 Preview 2" +git push origin v0.1.0-preview.2 +scripts/publish-github-release.sh v0.1.0-preview.2 +``` + The publish script creates a draft GitHub Release with: -- `dist/packages/PlainLink-0.1.0-macos-.zip` -- `dist/packages/PlainLink-0.1.0-macos-.zip.sha256` -- release notes from `docs/releases/v0.1.0.md` +- `dist/packages/PlainLink--macos-.zip` +- `dist/packages/PlainLink--macos-.zip.sha256` +- release notes from `docs/releases/.md`, or `docs/releases/v.md` when preview-specific notes do not exist Review the draft release, confirm the checksum, confirm whether the artifact is signed/notarized or explicitly labeled unsigned preview, then publish it. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 735859e..75dde26 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -13,6 +13,7 @@ - Stable user install/uninstall and doctor checks. - Unsigned macOS release packaging for testers and CI artifacts. - Signed/notarized release automation. +- Tag-aware preview artifact naming. - Conservative ClearURLs rule importer. - Import manifests and fixture verification for generated rules. - Human-readable default rules. diff --git a/scripts/package-macos-app.sh b/scripts/package-macos-app.sh index 8474c62..a08d0b2 100755 --- a/scripts/package-macos-app.sh +++ b/scripts/package-macos-app.sh @@ -5,7 +5,7 @@ ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) APP_NAME="PlainLink" APP_DIR="$ROOT_DIR/dist/$APP_NAME.app" PACKAGE_DIR="$ROOT_DIR/dist/packages" -VERSION=$(awk -F '=' '$1 ~ /^[[:space:]]*version[[:space:]]*$/ { gsub(/[ "]/, "", $2); print $2; exit }' "$ROOT_DIR/Cargo.toml") +VERSION=$("$ROOT_DIR/scripts/release-version.sh") ARCH=$(uname -m) ARTIFACT_NAME="$APP_NAME-$VERSION-macos-$ARCH.zip" ZIP_PATH="$PACKAGE_DIR/$ARTIFACT_NAME" diff --git a/scripts/publish-github-release.sh b/scripts/publish-github-release.sh index 2f5a89c..54d71c4 100755 --- a/scripts/publish-github-release.sh +++ b/scripts/publish-github-release.sh @@ -4,6 +4,7 @@ set -eu ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) TAG="${1:-v0.1.0}" VERSION="${TAG#v}" +BASE_VERSION="${VERSION%%-*}" APP_NAME="PlainLink" ARCH=$(uname -m) PACKAGE_DIR="$ROOT_DIR/dist/packages" @@ -11,6 +12,10 @@ ZIP_PATH="$PACKAGE_DIR/$APP_NAME-$VERSION-macos-$ARCH.zip" SHA_PATH="$ZIP_PATH.sha256" NOTES_PATH="$ROOT_DIR/docs/releases/$TAG.md" +if [ ! -f "$NOTES_PATH" ]; then + NOTES_PATH="$ROOT_DIR/docs/releases/v$BASE_VERSION.md" +fi + command -v gh >/dev/null 2>&1 || { echo "gh is required to publish a GitHub Release." >&2 exit 1 diff --git a/scripts/release-macos-app.sh b/scripts/release-macos-app.sh index 9994ac1..6a80d43 100755 --- a/scripts/release-macos-app.sh +++ b/scripts/release-macos-app.sh @@ -5,7 +5,7 @@ ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) APP_NAME="PlainLink" APP_DIR="$ROOT_DIR/dist/$APP_NAME.app" PACKAGE_DIR="$ROOT_DIR/dist/packages" -VERSION=$(awk -F '=' '$1 ~ /^[[:space:]]*version[[:space:]]*$/ { gsub(/[ "]/, "", $2); print $2; exit }' "$ROOT_DIR/Cargo.toml") +VERSION=$("$ROOT_DIR/scripts/release-version.sh") ARCH=$(uname -m) ARTIFACT_NAME="$APP_NAME-$VERSION-macos-$ARCH.zip" ZIP_PATH="$PACKAGE_DIR/$ARTIFACT_NAME" diff --git a/scripts/release-version.sh b/scripts/release-version.sh new file mode 100755 index 0000000..83cbefa --- /dev/null +++ b/scripts/release-version.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu + +ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) + +if [ "${PLAINLINK_RELEASE_VERSION:-}" ]; then + printf '%s\n' "${PLAINLINK_RELEASE_VERSION#v}" + exit 0 +fi + +if TAG=$(git -C "$ROOT_DIR" describe --tags --exact-match 2>/dev/null); then + printf '%s\n' "${TAG#v}" + exit 0 +fi + +awk -F '=' '$1 ~ /^[[:space:]]*version[[:space:]]*$/ { gsub(/[ "]/, "", $2); print $2; exit }' "$ROOT_DIR/Cargo.toml" diff --git a/tests/macos_app_bundle.rs b/tests/macos_app_bundle.rs index 490bda4..f3fdbdf 100644 --- a/tests/macos_app_bundle.rs +++ b/tests/macos_app_bundle.rs @@ -16,6 +16,8 @@ fn macos_menu_app_assets_are_wired() { fs::read_to_string("scripts/release-macos-app.sh").expect("release script should exist"); let publish_script = fs::read_to_string("scripts/publish-github-release.sh") .expect("publish release script should exist"); + let release_version = + fs::read_to_string("scripts/release-version.sh").expect("release version should exist"); let release_docs = fs::read_to_string("docs/RELEASE.md").expect("release docs should exist"); let release_notes = fs::read_to_string("docs/releases/v0.1.0.md").expect("v0.1.0 release notes should exist"); @@ -68,6 +70,7 @@ fn macos_menu_app_assets_are_wired() { assert!(release_script.contains("spctl --assess")); assert!(publish_script.contains("gh release create")); assert!(publish_script.contains("--verify-tag")); + assert!(release_version.contains("git -C \"$ROOT_DIR\" describe")); assert!(release_docs.contains("Developer ID Application")); assert!(release_docs.contains("notarytool store-credentials")); assert!(release_notes.contains("PlainLink v0.1.0")); diff --git a/tests/release_scripts.rs b/tests/release_scripts.rs new file mode 100644 index 0000000..3c386ba --- /dev/null +++ b/tests/release_scripts.rs @@ -0,0 +1,52 @@ +use std::fs; +use std::process::Command; + +#[test] +fn release_scripts_share_tag_aware_versioning() { + let release_version = fs::read_to_string("scripts/release-version.sh") + .expect("release version helper should exist"); + let package_script = + fs::read_to_string("scripts/package-macos-app.sh").expect("package script should exist"); + let release_script = + fs::read_to_string("scripts/release-macos-app.sh").expect("release script should exist"); + let publish_script = fs::read_to_string("scripts/publish-github-release.sh") + .expect("publish script should exist"); + + assert!(release_version.contains("PLAINLINK_RELEASE_VERSION")); + assert!(release_version.contains("git -C \"$ROOT_DIR\" describe --tags --exact-match")); + assert!(package_script.contains("VERSION=$(\"$ROOT_DIR/scripts/release-version.sh\")")); + assert!(release_script.contains("VERSION=$(\"$ROOT_DIR/scripts/release-version.sh\")")); + assert!(publish_script.contains("BASE_VERSION=\"${VERSION%%-*}\"")); + assert!(publish_script.contains("docs/releases/$TAG.md")); + assert!(publish_script.contains("docs/releases/v$BASE_VERSION.md")); +} + +#[test] +fn release_version_override_keeps_preview_suffixes() { + let output = Command::new("scripts/release-version.sh") + .env("PLAINLINK_RELEASE_VERSION", "v0.1.0-preview.2") + .output() + .expect("release-version helper should run"); + + assert!( + output.status.success(), + "release-version failed:\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!( + String::from_utf8(output.stdout).expect("version should be UTF-8"), + "0.1.0-preview.2\n" + ); +} + +#[test] +fn ci_uses_current_github_action_versions() { + let workflow = + fs::read_to_string(".github/workflows/ci.yml").expect("CI workflow should exist"); + + assert!(workflow.contains("uses: actions/checkout@v7")); + assert!(workflow.contains("uses: actions/upload-artifact@v7")); + assert!(!workflow.contains("uses: actions/checkout@v4")); + assert!(!workflow.contains("uses: actions/upload-artifact@v4")); +}