chore(release): prepare v0.8.0 prerelease

This commit is contained in:
Kayshen-X 2026-07-08 22:16:43 +08:00
parent 6c98da3ea2
commit 613878e18e
3 changed files with 240 additions and 24 deletions

View file

@ -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; <v> = 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

34
RELEASE_NOTES/v0.8.0.md Normal file
View file

@ -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-<arch>-mac.dmg`, plus raw desktop and `op` CLI archives.
- Windows: `OpenPencil-0.8.0-<arch>-win-setup.exe`, plus portable desktop and `op` CLI archives.
- Linux: `OpenPencil-0.8.0-<arch>-linux.AppImage`, `OpenPencil-0.8.0-<arch>-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.

View file

@ -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<String> {
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<String> {
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!(