From 613878e18e2021961ecf3cb56ee4c51fdf0957a1 Mon Sep 17 00:00:00 2001 From: Kayshen-X Date: Wed, 8 Jul 2026 22:16:43 +0800 Subject: [PATCH] chore(release): prepare v0.8.0 prerelease --- .github/workflows/rust-release.yml | 170 ++++++++++++++++++--- RELEASE_NOTES/v0.8.0.md | 34 +++++ crates/op-host-desktop/src/update_check.rs | 60 +++++++- 3 files changed, 240 insertions(+), 24 deletions(-) create mode 100644 RELEASE_NOTES/v0.8.0.md diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index bb70b9994..d22772374 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -1,7 +1,8 @@ name: Rust release artifacts # Triggered on tag push (v*) — builds release binaries across desktop targets, -# packages real installers per platform, and assembles a GitHub Release draft. +# packages real installers per platform, builds the web Docker image and SDK +# tarballs, and assembles a GitHub Release draft. # # Artifact matrix (per platform; = tag version, arch token = x64/arm64 to # match electron-builder's `${productName}-${version}-${arch}-…` convention @@ -58,16 +59,23 @@ name: Rust release artifacts # The `build` job builds the real `op-host-desktop` crate (winit + skia-safe # runner that drives the Rust editor); its shipped executable keeps the # stable `openpencil-desktop` name. The `op` CLI (crates/op-cli, binary name -# `op`) is built in the same cargo invocation. The Rust web bundle is built by -# `.github/workflows/wasm-bundle-build.yml` as a separate deployable CanvasKit -# artifact; this workflow currently keeps the native desktop release matrix -# independent from that web artifact. +# `op`) is built in the same cargo invocation. The web Docker job builds the +# Rust web host from `Dockerfile.web-rust`, and the SDK job packages the +# wasm-backed `packages/op-web-sdk*` workspace tarballs. +# +# The standalone `.github/workflows/wasm-bundle-build.yml` still gates the +# CanvasKit web bundle on push/PR. This release workflow only assembles the +# tagged, publishable artifacts. on: push: tags: ['v*'] workflow_dispatch: +permissions: + contents: write + packages: write + jobs: build: name: ${{ matrix.label }} @@ -340,21 +348,108 @@ jobs: OpenPencil-*-linux.deb if-no-files-found: ignore - # Web bundle release note: - # The deployable Rust web bundle is produced by - # `.github/workflows/wasm-bundle-build.yml`, which builds the current - # CanvasKit feature set, runs `tools/check-wasm-bundle.sh`, size-gates the - # wasm output, and uploads the `op-web-bundle` artifact. This desktop release - # workflow still ships only native desktop + CLI artifacts; attach or merge the - # `op-web-bundle` artifact here only when tagged releases are meant to publish - # the standalone web bundle from the same draft. + web-docker: + name: Build and push web Docker image + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + outputs: + image: ${{ steps.meta.outputs.image }} + tag: ${{ steps.meta.outputs.tag }} + digest: ${{ steps.build.outputs.digest }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: docker/setup-buildx-action@v3 + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + - name: Compute image metadata + id: meta + shell: bash + run: | + version="${GITHUB_REF_NAME#v}" + image="ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/openpencil-web" + tag="${image}:v${version}" + echo "image=${image}" >> "$GITHUB_OUTPUT" + echo "tag=${tag}" >> "$GITHUB_OUTPUT" + - name: Build and push + id: build + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile.web-rust + push: true + tags: ${{ steps.meta.outputs.tag }} + + sdk-packages: + name: Build web SDK npm tarballs + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: '1.94' + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + with: + shared-key: web-sdk-release + - uses: oven-sh/setup-bun@v2 + - name: Install wasm-bindgen-cli (pinned to Cargo.lock) + shell: bash + run: | + version="$(awk '/^name = "wasm-bindgen"$/{found=1; next} found && /^version = /{gsub(/[" ]/,"",$3); print $3; exit}' Cargo.lock)" + if [ -z "$version" ]; then + echo "::error::could not resolve wasm-bindgen version from Cargo.lock" + exit 1 + fi + cargo install wasm-bindgen-cli --version "$version" --locked + - name: Install binaryen + run: | + BINARYEN_VERSION=version_123 + curl -fsSL \ + "https://github.com/WebAssembly/binaryen/releases/download/${BINARYEN_VERSION}/binaryen-${BINARYEN_VERSION}-x86_64-linux.tar.gz" \ + | tar -xz + echo "${GITHUB_WORKSPACE}/binaryen-${BINARYEN_VERSION}/bin" >> "$GITHUB_PATH" + - name: Install package workspace dependencies + working-directory: packages + run: bun install --frozen-lockfile + - name: Build wasm-backed SDK packages + shell: bash + run: | + set -euo pipefail + cd packages/op-web-sdk + bun run sync-wasm + bun run build + cd ../op-web-sdk-react + bun run build + cd ../op-web-sdk-vue + bun run build + - name: Pack npm tarballs + shell: bash + run: | + set -euo pipefail + mkdir -p sdk-packages + for pkg in op-web-sdk op-web-sdk-react op-web-sdk-vue; do + (cd "packages/${pkg}" && bun pm pack --destination "${GITHUB_WORKSPACE}/sdk-packages") + done + ls -la sdk-packages + - name: Upload SDK tarballs + uses: actions/upload-artifact@v4 + with: + name: openpencil-web-sdk-packages + path: sdk-packages/*.tgz + if-no-files-found: error release-draft: name: Create / update GitHub Release draft - # Native release artifacts only. The deployable Rust web bundle is built by - # wasm-bundle-build.yml and can be attached separately when release policy - # calls for publishing the web bundle from this draft. - needs: [build] + needs: [build, web-docker, sdk-packages] runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') steps: @@ -363,16 +458,53 @@ jobs: with: path: dist - name: Flatten artifacts + shell: bash run: | + set -euo pipefail mkdir -p release-files find dist -type f \ \( -name "*.tar.gz" -o -name "*.zip" -o -name "*.dmg" \ - -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" \) \ + -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" \ + -o -name "*.tgz" \) \ -exec cp {} release-files/ \; ls -la release-files + shopt -s nullglob + native=(release-files/OpenPencil-* release-files/openpencil-desktop-*) + cli=(release-files/op-cli-*) + sdk=(release-files/*.tgz) + if [ "${#native[@]}" -eq 0 ]; then + echo "::error::missing native desktop artifacts" + exit 1 + fi + if [ "${#cli[@]}" -eq 0 ]; then + echo "::error::missing op-cli artifacts" + exit 1 + fi + if [ "${#sdk[@]}" -ne 3 ]; then + echo "::error::expected 3 SDK tarballs, found ${#sdk[@]}" + exit 1 + fi + - name: Prepare release body + shell: bash + run: | + set -euo pipefail + version="${GITHUB_REF_NAME#v}" + note="RELEASE_NOTES/v${version}.md" + if [ ! -f "$note" ]; then + echo "::error::missing release note: $note" + exit 1 + fi + cp "$note" release-body.md + { + printf '\n## Web Docker Image\n\n' + printf -- '- Image: `%s`\n' '${{ needs.web-docker.outputs.tag }}' + printf -- '- Digest: `%s`\n' '${{ needs.web-docker.outputs.digest }}' + } >> release-body.md - name: Create / update GitHub Release uses: softprops/action-gh-release@v2 with: draft: true + prerelease: true + make_latest: false + body_path: release-body.md files: release-files/* - generate_release_notes: true diff --git a/RELEASE_NOTES/v0.8.0.md b/RELEASE_NOTES/v0.8.0.md new file mode 100644 index 000000000..6809a3d67 --- /dev/null +++ b/RELEASE_NOTES/v0.8.0.md @@ -0,0 +1,34 @@ +# OpenPencil v0.8.0 + +`v0.8.0` is a pre-release for the Rust OpenPencil stack. + +## Highlights + +- Rust desktop app is now the primary OpenPencil experience, with installer artifacts for macOS, Windows, and Linux. +- `op` CLI ships as standalone release artifacts for the same desktop target matrix. +- Rust web host is published as a GHCR Docker image for tagged releases. +- Web SDK tarballs are attached for `@zseven-w/op-web-sdk`, `@zseven-w/op-web-sdk-react`, and `@zseven-w/op-web-sdk-vue`. +- Desktop update checks can discover pre-release GitHub releases, so `v0.8.0` can be offered before it is promoted to a stable release. + +## Release Artifacts + +- macOS: `OpenPencil-0.8.0--mac.dmg`, plus raw desktop and `op` CLI archives. +- Windows: `OpenPencil-0.8.0--win-setup.exe`, plus portable desktop and `op` CLI archives. +- Linux: `OpenPencil-0.8.0--linux.AppImage`, `OpenPencil-0.8.0--linux.deb`, plus raw desktop and `op` CLI archives. +- Web SDK: npm tarballs for the base SDK, React adapter, and Vue adapter. + +## Docker + +The release workflow publishes the web image to GHCR as: + +```sh +docker pull ghcr.io/zseven-w/openpencil-web:v0.8.0 +``` + +Because this is a pre-release, the workflow does not publish or update a `latest` tag. + +## Notes + +- The retired TypeScript apps and `pen-*` packages are not part of this release line. +- The web SDK remains a read-only `.op` viewer SDK backed by the Rust wasm bundle. +- Desktop auto-update currently checks for available releases and opens the platform installer path; it does not yet perform a signed in-place self-update. diff --git a/crates/op-host-desktop/src/update_check.rs b/crates/op-host-desktop/src/update_check.rs index cd94b532d..df261ee6d 100644 --- a/crates/op-host-desktop/src/update_check.rs +++ b/crates/op-host-desktop/src/update_check.rs @@ -33,9 +33,11 @@ pub fn releases_url() -> String { format!("https://github.com/{GITHUB_OWNER}/{GITHUB_REPO}/releases") } -/// `releases/latest` API endpoint. +/// Releases list API endpoint. GitHub's `releases/latest` endpoint excludes +/// prereleases, so the desktop updater uses the list feed and picks the first +/// non-draft release itself. fn latest_release_api() -> String { - format!("https://api.github.com/repos/{GITHUB_OWNER}/{GITHUB_REPO}/releases/latest") + format!("https://api.github.com/repos/{GITHUB_OWNER}/{GITHUB_REPO}/releases?per_page=20") } /// Background release-API probe. One request per `spawn`; the @@ -99,8 +101,9 @@ impl UpdateProbe { } } -/// Query `releases/latest` and classify the running build against -/// it. Blocking — only ever called on the probe worker thread. +/// Query the releases list and classify the running build against the newest +/// non-draft release, including prereleases. Blocking — only ever called on the +/// probe worker thread. fn check_latest_release() -> UpdateStatus { let Some(tag) = fetch_latest_tag() else { return UpdateStatus::Error; @@ -142,10 +145,26 @@ fn fetch_latest_tag() -> Option { return None; } let json: serde_json::Value = resp.json().await.ok()?; - json.get("tag_name")?.as_str().map(str::to_string) + select_latest_release_tag(&json) }) } +/// Select the newest published release tag from GitHub's releases list. +/// GitHub returns newest releases first. Drafts are not visible to users and +/// must not drive update prompts; prereleases are visible and are intentionally +/// allowed for v0.8.0. +fn select_latest_release_tag(json: &serde_json::Value) -> Option { + json.as_array()? + .iter() + .filter(|release| { + !release + .get("draft") + .and_then(serde_json::Value::as_bool) + .unwrap_or(false) + }) + .find_map(|release| release.get("tag_name")?.as_str().map(str::to_string)) +} + /// Download the platform installer for `version` on a worker thread /// and open it when done (macOS mounts the DMG, Windows launches the /// NSIS setup, Linux reveals the AppImage after `chmod +x`). Any @@ -359,6 +378,37 @@ mod tests { assert!(is_newer("0.9.0-rc.1", "0.8.0")); } + #[test] + fn releases_api_uses_list_endpoint_so_prereleases_are_visible() { + assert_eq!( + latest_release_api(), + "https://api.github.com/repos/ZSeven-W/openpencil/releases?per_page=20" + ); + } + + #[test] + fn release_list_selection_keeps_prerelease_and_skips_drafts() { + let feed = serde_json::json!([ + { + "tag_name": "v0.9.0", + "draft": true, + "prerelease": false + }, + { + "tag_name": "v0.8.0", + "draft": false, + "prerelease": true + }, + { + "tag_name": "v0.7.5", + "draft": false, + "prerelease": false + } + ]); + + assert_eq!(select_latest_release_tag(&feed).as_deref(), Some("v0.8.0")); + } + #[test] fn releases_url_points_at_the_publish_repo() { assert_eq!(