open-design/.github/workflows/release-beta.yml
PerishFire bfcafc81fd
feat(pack): add Windows portable zip target alongside NSIS installer (#2937)
Adds a new `--to zip` (and `--to all`) tools-pack Windows build target that
produces a portable `.zip` from the cached `win-unpacked` tree using the
bundled 7z. The zip lays files at the archive root so users can extract it
anywhere and launch `Open Design.exe` without going through the NSIS
installer, addressing the no-install download request.

Release plumbing is updated to publish the portable zip and its sha256
beside the existing installer on R2 for beta, preview, and stable channels
(default on, gated by `WINDOWS_INCLUDE_ZIP`/`WIN_INCLUDE_ZIP`). The
electron-updater `latest.yml` feed continues to point only at the
installer; the zip is a manual-download convenience and is intentionally
excluded from the in-app updater.

Closes #1121

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)

Co-authored-by: libertecode <libertecode@proton.me>
2026-05-26 06:14:44 +00:00

854 lines
35 KiB
YAML

name: release-beta
on:
workflow_dispatch:
inputs:
enable_mac:
description: "Build and publish mac arm64 beta artifacts."
required: true
type: boolean
default: true
enable_win:
description: "Build and publish Windows x64 beta artifacts."
required: true
type: boolean
default: true
enable_mac_intel:
description: "Build and publish macOS Intel x64 (unsigned) beta artifacts."
required: true
type: boolean
default: false
enable_linux:
description: "Build and publish Linux x64 AppImage/checksum to R2 only; no updater feed is published yet."
required: true
type: boolean
default: false
permissions:
actions: write
contents: read
concurrency:
group: open-design-release-beta
cancel-in-progress: false
env:
OPEN_DESIGN_TELEMETRY_RELAY_URL: ${{ vars.OPEN_DESIGN_TELEMETRY_RELAY_URL }}
# PostHog product-analytics ingest. Both vars must be defined as
# repository/organization secrets/vars for official builds to ship with
# analytics enabled. PR builds and forks run without these — the daemon's
# /api/analytics/config short-circuits to enabled=false in that case and
# no events leave the user's machine.
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ vars.POSTHOG_HOST }}
# PostHog Error tracking sourcemap upload. Personal API key (phx_...) and
# project ID let tools-pack's web-sourcemaps step ship browser sourcemaps
# to PostHog after `next build` and before the .map files are stripped
# from the packaged bundle. Missing in PR/fork builds → upload is skipped
# and the helper still strips .map to keep source out of the installer.
POSTHOG_CLI_API_KEY: ${{ secrets.POSTHOG_CLI_API_KEY }}
POSTHOG_CLI_PROJECT_ID: ${{ vars.POSTHOG_CLI_PROJECT_ID }}
jobs:
metadata:
name: Prepare beta metadata
if: github.repository == 'nexu-io/open-design'
runs-on: ubuntu-latest
env:
OPEN_DESIGN_BETA_METADATA_URL: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}/beta/latest/metadata.json
outputs:
asset_version_suffix: ${{ steps.beta.outputs.asset_version_suffix }}
base_version: ${{ steps.beta.outputs.base_version }}
beta_version: ${{ steps.beta.outputs.beta_version }}
branch: ${{ steps.beta.outputs.branch }}
commit: ${{ steps.beta.outputs.commit }}
release_name: ${{ steps.beta.outputs.release_name }}
state_source: ${{ steps.beta.outputs.state_source }}
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
- name: Validate beta publish inputs
run: |
set -euo pipefail
if [ "${{ inputs.enable_mac }}" != "true" ] && [ "${{ inputs.enable_win }}" != "true" ] && [ "${{ inputs.enable_mac_intel }}" != "true" ] && [ "${{ inputs.enable_linux }}" != "true" ]; then
echo "release-beta requires at least one platform to be enabled" >&2
exit 1
fi
- name: Validate R2 release access
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: "true"
CLOUDFLARE_R2_RELEASES_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
CLOUDFLARE_R2_RELEASES_URL: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
R2_ACCESS_PROBE_NAME: release-beta
RELEASE_CHANNEL: beta
run: bash .github/scripts/release/r2/check.sh
- name: Prepare beta release metadata
id: beta
run: node --experimental-strip-types ./scripts/release-beta.ts
build_mac:
name: Build beta mac arm64
needs: metadata
if: ${{ inputs.enable_mac }}
runs-on: macos-14
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup pnpm
uses: pnpm/action-setup@v5
with:
version: 10.33.2
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Verify mac Electron framework symlinks
run: |
set -euo pipefail
electron_dist="$(node -e 'const path = require("node:path"); const { createRequire } = require("node:module"); const requireFromDesktop = createRequire(path.join(process.cwd(), "apps/desktop/package.json")); const electron = requireFromDesktop.resolve("electron"); process.stdout.write(path.join(path.dirname(electron), "dist"));')"
framework="$electron_dist/Electron.app/Contents/Frameworks/Electron Framework.framework"
for link in \
"$framework/Electron Framework" \
"$framework/Helpers" \
"$framework/Libraries" \
"$framework/Resources" \
"$framework/Versions/Current"; do
if [ ! -L "$link" ]; then
echo "Expected Electron framework symlink, got non-symlink: $link" >&2
ls -la "$framework" >&2 || true
ls -la "$framework/Versions" >&2 || true
exit 1
fi
done
- name: Prepare Apple signing certificate
env:
APPLE_SIGNING_CERTIFICATE_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_BASE64 }}
APPLE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }}
run: |
set -euo pipefail
cert_path="$RUNNER_TEMP/open-design-signing.p12"
if ! printf '%s' "$APPLE_SIGNING_CERTIFICATE_BASE64" | base64 --decode > "$cert_path" 2>/dev/null; then
printf '%s' "$APPLE_SIGNING_CERTIFICATE_BASE64" | base64 -D > "$cert_path"
fi
{
echo "CSC_LINK=$cert_path"
echo "CSC_KEY_PASSWORD=$APPLE_SIGNING_CERTIFICATE_PASSWORD"
} >> "$GITHUB_ENV"
- name: Build beta mac artifacts
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
set -euo pipefail
tools_pack_dir="$RUNNER_TEMP/tools-pack"
build_json_path="$RUNNER_TEMP/mac-tools-pack-build.json"
build_log_path="$RUNNER_TEMP/mac-tools-pack-build.log"
rm -rf "$tools_pack_dir"
: > "$build_log_path"
build_args=(
exec tools-pack mac build
--dir "$tools_pack_dir"
--namespace release-beta
--portable
--app-version "${{ needs.metadata.outputs.beta_version }}"
--mac-compression normal
--to dmg
--json
--signed
)
if build_output="$(pnpm "${build_args[@]}" 2> >(tee -a "$build_log_path" >&2))"; then
printf '%s\n' "$build_output" | tee "$build_json_path"
else
build_status=$?
printf '%s\n' "$build_output"
exit "$build_status"
fi
- name: Capture mac framework diagnostics
if: ${{ failure() }}
continue-on-error: true
run: |
set -euo pipefail
output="$RUNNER_TEMP/mac-framework-diagnostics.txt"
source_resolve_log="$RUNNER_TEMP/mac-framework-source-resolve.err"
source_framework="$(node -e 'const path = require("node:path"); const { createRequire } = require("node:module"); const requireFromDesktop = createRequire(path.join(process.cwd(), "apps/desktop/package.json")); const electron = requireFromDesktop.resolve("electron"); process.stdout.write(path.join(path.dirname(electron), "dist", "Electron.app", "Contents", "Frameworks", "Electron Framework.framework"));' 2>"$source_resolve_log" || true)"
built_framework="$RUNNER_TEMP/tools-pack/out/mac/namespaces/release-beta/builder/mac-arm64/Open Design Beta.app/Contents/Frameworks/Electron Framework.framework"
dump_framework() {
local label="$1"
local framework="$2"
echo "## $label"
echo "path=$framework"
if [ ! -e "$framework" ] && [ ! -L "$framework" ]; then
echo "missing"
return 0
fi
echo "### top-level"
ls -la "$framework" || true
echo "### symlinks"
find "$framework" -maxdepth 4 -type l -print0 | while IFS= read -r -d '' link; do
printf '%s -> %s\n' "$link" "$(readlink "$link")"
done || true
echo "### selected stat"
for path in \
"$framework" \
"$framework/Electron Framework" \
"$framework/Versions" \
"$framework/Versions/Current" \
"$framework/Versions/Current/Electron Framework" \
"$framework/Versions/A" \
"$framework/Versions/A/Electron Framework" \
"$framework/Resources" \
"$framework/Versions/A/Resources/Info.plist"; do
if [ -e "$path" ] || [ -L "$path" ]; then
stat -f '%Sp %HT %N' "$path" || true
else
echo "missing: $path"
fi
done
echo "### plist"
plutil -p "$framework/Versions/A/Resources/Info.plist" 2>&1 || true
echo "### codesign display"
codesign --display --verbose=4 "$framework/Electron Framework" 2>&1 || true
codesign --display --verbose=4 "$framework/Versions/Current/Electron Framework" 2>&1 || true
codesign --display --verbose=4 "$framework/Versions/A/Electron Framework" 2>&1 || true
codesign --display --verbose=4 "$framework" 2>&1 || true
}
{
date -u
if [ -n "$source_framework" ]; then
dump_framework "source Electron Framework" "$source_framework"
else
echo "## source Electron Framework"
echo "resolve failed"
cat "$source_resolve_log" || true
fi
dump_framework "built Electron Framework" "$built_framework"
} > "$output"
cat "$output"
- name: Upload mac build diagnostics
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: open-design-beta-mac-build-diagnostics
path: |
${{ runner.temp }}/mac-tools-pack-build.log
${{ runner.temp }}/mac-tools-pack-build.json
${{ runner.temp }}/mac-framework-diagnostics.txt
if-no-files-found: warn
- name: Smoke beta mac packaged runtime
working-directory: e2e
env:
OD_PACKAGED_E2E_BUILD_JSON_PATH: ${{ runner.temp }}/mac-tools-pack-build.json
OD_PACKAGED_E2E_BUILD_LOG_PATH: ${{ runner.temp }}/mac-tools-pack-build.log
OD_PACKAGED_E2E_MAC: "1"
OD_PACKAGED_E2E_NAMESPACE: release-beta
OD_PACKAGED_E2E_RELEASE_CHANNEL: beta
OD_PACKAGED_E2E_RELEASE_VERSION: ${{ needs.metadata.outputs.beta_version }}
OD_PACKAGED_E2E_REPORT_DIR: ${{ runner.temp }}/release-report/mac
OD_PACKAGED_E2E_TOOLS_PACK_DIR: ${{ runner.temp }}/tools-pack
run: |
set -euo pipefail
pnpm exec tsx scripts/release-smoke.ts mac specs/mac.spec.ts
- name: Upload mac e2e spec report
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: open-design-beta-mac-e2e-report
path: ${{ runner.temp }}/release-report/mac
if-no-files-found: warn
- name: Prepare beta assets
id: assets
env:
ASSET_VERSION_SUFFIX: ${{ needs.metadata.outputs.asset_version_suffix }}
CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
MAC_ARTIFACT_MODE: dmg-only
RELEASE_CHANNEL: beta
RELEASE_NOTES: Open Design beta ${{ needs.metadata.outputs.beta_version }}${{ needs.metadata.outputs.asset_version_suffix }}
RELEASE_VERSION: ${{ needs.metadata.outputs.beta_version }}
TOOLS_PACK_NAMESPACE: release-beta
run: bash .github/scripts/release/assets/mac.sh
- name: Publish beta mac assets to R2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: "true"
CLOUDFLARE_R2_RELEASES_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
CLOUDFLARE_R2_RELEASES_URL: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
ASSET_VERSION_SUFFIX: ${{ needs.metadata.outputs.asset_version_suffix }}
MAC_ARTIFACT_MODE: dmg-only
RELEASE_CHANNEL: beta
RELEASE_PLATFORM: mac
RELEASE_SIGNED: "true"
RELEASE_VERSION: ${{ needs.metadata.outputs.beta_version }}
run: node --experimental-strip-types .github/scripts/release/r2/publish-platform.ts
- name: Upload mac publish manifest
uses: actions/upload-artifact@v7
with:
name: open-design-beta-mac-publish-manifest
path: ${{ runner.temp }}/release-platform-manifests/mac.json
build_mac_intel:
name: Build beta mac x64
needs: metadata
if: ${{ inputs.enable_mac_intel }}
runs-on: macos-15-intel
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup pnpm
uses: pnpm/action-setup@v5
with:
version: 10.33.2
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Apply beta package version
run: npm pkg set "version=${{ needs.metadata.outputs.beta_version }}" --prefix apps/packaged
- name: Build beta mac intel artifacts
run: |
set -euo pipefail
pnpm exec tools-pack mac build \
--dir "$RUNNER_TEMP/tools-pack" \
--namespace release-beta-intel \
--portable \
--mac-compression maximum \
--to all \
--json
- name: Prepare beta mac intel assets
id: assets
env:
ASSET_VERSION_SUFFIX: .unsigned
CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
RELEASE_CHANNEL: beta
RELEASE_VERSION: ${{ needs.metadata.outputs.beta_version }}
TOOLS_PACK_NAMESPACE: release-beta-intel
run: bash .github/scripts/release/assets/mac-intel.sh
- name: Publish beta mac intel assets to R2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: "true"
CLOUDFLARE_R2_RELEASES_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
CLOUDFLARE_R2_RELEASES_URL: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
ASSET_VERSION_SUFFIX: ${{ needs.metadata.outputs.asset_version_suffix }}
MAC_INTEL_ASSET_SUFFIX: .unsigned
RELEASE_CHANNEL: beta
RELEASE_PLATFORM: mac-intel
RELEASE_VERSION: ${{ needs.metadata.outputs.beta_version }}
run: node --experimental-strip-types .github/scripts/release/r2/publish-platform.ts
- name: Upload mac intel publish manifest
uses: actions/upload-artifact@v7
with:
name: open-design-beta-mac-intel-publish-manifest
path: ${{ runner.temp }}/release-platform-manifests/macIntel.json
build_win:
name: Build beta win x64
needs: metadata
if: ${{ inputs.enable_win }}
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup pnpm
uses: pnpm/action-setup@v5
with:
version: 10.33.2
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Compute Windows tools-pack cache key
id: win_tools_pack_cache_key
shell: pwsh
env:
WIN_TOOLS_PACK_ORIGIN_KEY: ${{ hashFiles('package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml', 'apps/daemon/**', 'apps/web/**', 'apps/desktop/**', 'apps/packaged/**', 'packages/agui-adapter/**', 'packages/contracts/**', 'packages/plugin-runtime/**', 'packages/sidecar-proto/**', 'packages/sidecar/**', 'packages/platform/**', 'tools/pack/bin/**', 'tools/pack/package.json', 'tools/pack/resources/**', 'tools/pack/src/**', 'tools/pack/tsconfig.json', 'assets/community-pets/**', 'assets/frames/**', 'craft/**', 'design-systems/**', 'design-templates/**', 'plugins/_official/**', 'plugins/registry/**', 'prompt-templates/**', 'skills/**', '.github/workflows/release-beta.yml', '.github/scripts/release/cache/win.ps1') }}
run: |
if ([string]::IsNullOrWhiteSpace($env:WIN_TOOLS_PACK_ORIGIN_KEY)) {
throw "Windows tools-pack cache origin key is empty"
}
$prefix = "tools-pack-win-v7-beta-$env:RUNNER_OS-"
"origin=$env:WIN_TOOLS_PACK_ORIGIN_KEY" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"prefix=$prefix" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"key=$prefix$env:WIN_TOOLS_PACK_ORIGIN_KEY" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
- name: Restore Windows tools-pack cache
id: win_tools_pack_cache_restore
uses: actions/cache/restore@v5
continue-on-error: true
with:
path: ${{ runner.temp }}/tools-pack-cache
key: ${{ steps.win_tools_pack_cache_key.outputs.key }}
restore-keys: |
${{ steps.win_tools_pack_cache_key.outputs.prefix }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup NSIS
shell: pwsh
run: |
if ((Get-Command makensis.exe -ErrorAction SilentlyContinue) -or (Test-Path "C:\Program Files (x86)\NSIS\makensis.exe")) {
exit 0
}
choco install nsis -y --no-progress
- name: Build beta windows artifacts
id: win_tools_pack_build
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$toolsPackDir = "${{ runner.temp }}/tools-pack"
$cacheDir = "${{ runner.temp }}/tools-pack-cache"
$buildJsonPath = Join-Path $env:RUNNER_TEMP "windows-tools-pack-build.json"
$buildArgs = @(
"exec", "tools-pack", "win", "build",
"--dir", $toolsPackDir,
"--cache-dir", $cacheDir,
"--namespace", "release-beta-win",
"--portable",
"--app-version", "${{ needs.metadata.outputs.beta_version }}",
"--to", "all",
"--json"
)
"cache_failed=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
try {
$buildOutput = pnpm @buildArgs
if ($LASTEXITCODE -ne 0) {
throw "Windows tools-pack cached build exited with code $LASTEXITCODE"
}
} catch {
Write-Warning "Windows tools-pack cached build failed; removing restored cache and retrying without cache."
"cache_failed=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $cacheDir
$buildOutput = pnpm exec tools-pack win build `
--dir $toolsPackDir `
--namespace release-beta-win `
--portable `
--app-version "${{ needs.metadata.outputs.beta_version }}" `
--to all `
--json
if ($LASTEXITCODE -ne 0) {
throw "Windows tools-pack uncached fallback build exited with code $LASTEXITCODE"
}
}
$buildOutput | Set-Content -Path $buildJsonPath
$buildOutput
- name: Delete failed Windows tools-pack cache
if: ${{ steps.win_tools_pack_build.outputs.cache_failed == 'true' && steps.win_tools_pack_cache_restore.outputs.cache-matched-key != '' }}
shell: pwsh
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
$matchedKey = "${{ steps.win_tools_pack_cache_restore.outputs.cache-matched-key }}"
$caches = @(gh cache list --key $matchedKey --limit 100 --json id,key,ref | ConvertFrom-Json | Where-Object { $_.key -eq $matchedKey })
foreach ($cache in $caches) {
gh cache delete $cache.id
}
"deletedFailedCacheKey=$matchedKey count=$($caches.Count)"
- name: Smoke beta windows packaged runtime
working-directory: e2e
env:
OD_PACKAGED_E2E_BUILD_JSON_PATH: ${{ runner.temp }}/windows-tools-pack-build.json
OD_PACKAGED_E2E_WIN: "1"
OD_PACKAGED_E2E_WIN_VERIFY_REINSTALL: "0"
OD_PACKAGED_E2E_NAMESPACE: release-beta-win
OD_PACKAGED_E2E_RELEASE_CHANNEL: beta
OD_PACKAGED_E2E_RELEASE_VERSION: ${{ needs.metadata.outputs.beta_version }}
OD_PACKAGED_E2E_REPORT_DIR: ${{ runner.temp }}/release-report/win
OD_PACKAGED_E2E_TOOLS_PACK_DIR: ${{ runner.temp }}/tools-pack
run: |
$ErrorActionPreference = "Stop"
pnpm exec tsx scripts/release-smoke.ts win specs/win.spec.ts
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
- name: Upload windows e2e spec report
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: open-design-beta-win-e2e-report
path: ${{ runner.temp }}/release-report/win
if-no-files-found: warn
- name: Prepare windows beta assets
shell: pwsh
env:
ASSET_VERSION_SUFFIX: ${{ needs.metadata.outputs.asset_version_suffix }}
CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
RELEASE_CHANNEL: beta
RELEASE_NOTES: Open Design beta ${{ needs.metadata.outputs.beta_version }}.unsigned
RELEASE_VERSION: ${{ needs.metadata.outputs.beta_version }}
TOOLS_PACK_NAMESPACE: release-beta-win
WINDOWS_ASSET_SUFFIX: .unsigned
run: ./.github/scripts/release/assets/win.ps1
- name: Publish beta windows assets to R2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: "true"
CLOUDFLARE_R2_RELEASES_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
CLOUDFLARE_R2_RELEASES_URL: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
ASSET_VERSION_SUFFIX: ${{ needs.metadata.outputs.asset_version_suffix }}
RELEASE_CHANNEL: beta
RELEASE_PLATFORM: win
RELEASE_VERSION: ${{ needs.metadata.outputs.beta_version }}
WIN_ASSET_SUFFIX: .unsigned
run: node --experimental-strip-types .github/scripts/release/r2/publish-platform.ts
- name: Upload windows publish manifest
uses: actions/upload-artifact@v7
with:
name: open-design-beta-win-publish-manifest
path: ${{ runner.temp }}/release-platform-manifests/win.json
build_linux:
name: Build beta linux x64
needs: metadata
if: ${{ inputs.enable_linux }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup pnpm
uses: pnpm/action-setup@v5
with:
version: 10.33.2
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install dependencies
run: pnpm install --frozen-lockfile
# `--containerized` builds the AppImage inside the electronuserland/builder
# Docker image (glibc 2.27 baseline) so the resulting binary runs on older
# distros than ubuntu-latest's glibc 2.39. Docker is preinstalled on the
# GitHub-hosted ubuntu-latest runner, so no extra setup is required.
- name: Build beta linux artifacts
run: |
set -euo pipefail
tools_pack_dir="$RUNNER_TEMP/tools-pack"
report_dir="$RUNNER_TEMP/release-report/linux"
build_json_path="$report_dir/tools-pack.json"
build_log_path="$report_dir/tools-pack.log"
rm -rf "$tools_pack_dir"
mkdir -p "$report_dir"
: > "$build_log_path"
build_args=(
exec tools-pack linux build
--dir "$tools_pack_dir"
--namespace release-beta-linux
--portable
--app-version "${{ needs.metadata.outputs.beta_version }}"
--to appimage
--containerized
--json
)
if build_output="$(pnpm "${build_args[@]}" 2> >(tee -a "$build_log_path" >&2))"; then
printf '%s\n' "$build_output" | tee "$build_json_path"
node -e 'const fs = require("node:fs"); JSON.parse(fs.readFileSync(process.argv[1], "utf8"));' "$build_json_path"
else
build_status=$?
printf '%s\n' "$build_output" | tee "$build_json_path"
exit "$build_status"
fi
- name: Smoke beta linux AppImage runtime
working-directory: e2e
env:
OD_PACKAGED_E2E_LINUX_APPIMAGE: "1"
OD_PACKAGED_E2E_NAMESPACE: release-beta-linux
OD_PACKAGED_E2E_SCREENSHOT_PATH: ${{ runner.temp }}/release-report/linux/screenshots/open-design-linux-smoke.png
OD_PACKAGED_E2E_TOOLS_PACK_DIR: ${{ runner.temp }}/tools-pack
run: |
set -euo pipefail
report_dir="$RUNNER_TEMP/release-report/linux"
mkdir -p "$report_dir/screenshots"
cat > "$report_dir/manifest.json" <<EOF
{
"platform": "linux",
"spec": "specs/linux.spec.ts",
"namespace": "release-beta-linux",
"screenshot": "screenshots/open-design-linux-smoke.png",
"githubRunId": "$GITHUB_RUN_ID",
"githubRunAttempt": "$GITHUB_RUN_ATTEMPT",
"commit": "$GITHUB_SHA"
}
EOF
sudo apt-get update 2>&1 | tee "$report_dir/apt-get-update.log"
sudo apt-get install -y xvfb 2>&1 | tee "$report_dir/apt-get-install-xvfb.log"
xvfb-run -a pnpm test specs/linux.spec.ts 2>&1 | tee "$report_dir/vitest.log"
- name: Upload linux e2e spec report
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: open-design-beta-linux-e2e-report
path: ${{ runner.temp }}/release-report/linux
if-no-files-found: warn
- name: Prepare linux beta assets
env:
LINUX_ASSET_SUFFIX: .unsigned
RELEASE_VERSION: ${{ needs.metadata.outputs.beta_version }}
TOOLS_PACK_NAMESPACE: release-beta-linux
run: bash .github/scripts/release/assets/linux.sh
- name: Publish beta linux assets to R2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: "true"
CLOUDFLARE_R2_RELEASES_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
CLOUDFLARE_R2_RELEASES_URL: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
ASSET_VERSION_SUFFIX: ${{ needs.metadata.outputs.asset_version_suffix }}
LINUX_ASSET_SUFFIX: .unsigned
RELEASE_CHANNEL: beta
RELEASE_PLATFORM: linux
RELEASE_VERSION: ${{ needs.metadata.outputs.beta_version }}
run: node --experimental-strip-types .github/scripts/release/r2/publish-platform.ts
- name: Upload linux publish manifest
uses: actions/upload-artifact@v7
with:
name: open-design-beta-linux-publish-manifest
path: ${{ runner.temp }}/release-platform-manifests/linux.json
publish:
name: Publish beta metadata to R2
needs:
- metadata
- build_mac
- build_mac_intel
- build_win
- build_linux
if: >-
${{
always() &&
!cancelled() &&
needs.metadata.result == 'success' &&
(inputs.enable_mac || inputs.enable_win || inputs.enable_mac_intel || inputs.enable_linux)
}}
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ github.token }}
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: "true"
CLOUDFLARE_R2_RELEASES_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
CLOUDFLARE_R2_RELEASES_URL: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
ASSET_VERSION_SUFFIX: ${{ needs.metadata.outputs.asset_version_suffix }}
BASE_VERSION: ${{ needs.metadata.outputs.base_version }}
BETA_VERSION: ${{ needs.metadata.outputs.beta_version }}
BRANCH_NAME: ${{ needs.metadata.outputs.branch }}
ENABLE_LINUX: ${{ inputs.enable_linux }}
ENABLE_MAC: ${{ inputs.enable_mac }}
ENABLE_MAC_INTEL: ${{ inputs.enable_mac_intel }}
ENABLE_WIN: ${{ inputs.enable_win }}
LINUX_RESULT: ${{ needs.build_linux.result }}
MAC_INTEL_RESULT: ${{ needs.build_mac_intel.result }}
MAC_RESULT: ${{ needs.build_mac.result }}
RELEASE_CHANNEL: beta
RELEASE_VERSION: ${{ needs.metadata.outputs.beta_version }}
RELEASE_SIGNED: "true"
STATE_SOURCE: ${{ needs.metadata.outputs.state_source }}
WIN_RESULT: ${{ needs.build_win.result }}
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Download mac publish manifest
if: ${{ inputs.enable_mac && needs.build_mac.result == 'success' }}
uses: actions/download-artifact@v8
with:
name: open-design-beta-mac-publish-manifest
path: ${{ runner.temp }}/release-platform-manifests
- name: Download mac intel publish manifest
if: ${{ inputs.enable_mac_intel && needs.build_mac_intel.result == 'success' }}
uses: actions/download-artifact@v8
with:
name: open-design-beta-mac-intel-publish-manifest
path: ${{ runner.temp }}/release-platform-manifests
- name: Download windows publish manifest
if: ${{ inputs.enable_win && needs.build_win.result == 'success' }}
uses: actions/download-artifact@v8
with:
name: open-design-beta-win-publish-manifest
path: ${{ runner.temp }}/release-platform-manifests
- name: Download linux publish manifest
if: ${{ inputs.enable_linux && needs.build_linux.result == 'success' }}
uses: actions/download-artifact@v8
with:
name: open-design-beta-linux-publish-manifest
path: ${{ runner.temp }}/release-platform-manifests
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
- name: Publish beta metadata to R2
id: r2
run: node --experimental-strip-types .github/scripts/release/r2/publish-beta-metadata.ts
- name: Verify R2 beta metadata
env:
R2_METADATA_URL: ${{ steps.r2.outputs.version_metadata_url }}
run: node --experimental-strip-types .github/scripts/release/r2/verify-beta-metadata.ts
- name: Publish summary
env:
R2_METADATA_URL: ${{ steps.r2.outputs.version_metadata_url }}
run: node --experimental-strip-types .github/scripts/release/r2/summary-beta.ts >> "$GITHUB_STEP_SUMMARY"
- name: Cleanup workflow artifacts
if: ${{ success() && steps.r2.outputs.release_state == 'complete' }}
run: bash .github/scripts/release/github/cleanup-artifacts.sh
runtime_trace:
name: Runtime trace
needs:
- metadata
- build_mac
- build_mac_intel
- build_win
- build_linux
- publish
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Summarize workflow runtime
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.run_id }}
run: |
set -euo pipefail
run_json="$RUNNER_TEMP/run.json"
gh run view "$RUN_ID" --repo "$GITHUB_REPOSITORY" --json conclusion,createdAt,databaseId,displayTitle,event,headBranch,jobs,updatedAt,url > "$run_json"
jq -r '
def parse_ts: sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601;
def seconds($start; $end):
if ($start and $end) then (($end | parse_ts) - ($start | parse_ts)) else null end;
def fmt($seconds):
if $seconds == null then "n/a"
elif $seconds >= 60 then "\(((($seconds / 60) * 10 | round) / 10))m"
else "\(($seconds | round))s"
end;
def row($cells): "| \($cells | join(" | ")) |";
.jobs as $jobs |
[
"## Runtime trace",
"",
"Run: [\(.displayTitle)](\(.url))",
"Event: `\(.event)`",
"Branch: `\(.headBranch)`",
"Elapsed: \(fmt(seconds(.createdAt; .updatedAt)))",
"",
"### Jobs",
"| Job | Result | Duration | Slowest step |",
"| --- | --- | ---: | --- |",
(
$jobs
| sort_by(seconds(.startedAt; .completedAt) // 0)
| reverse
| .[]
| select(.conclusion != "skipped")
| (
[(.steps // [])[] | select(.startedAt and .completedAt and .conclusion != "skipped") | {name, duration: seconds(.startedAt; .completedAt)}]
| max_by(.duration // 0)
) as $slow
| row([.name, (.conclusion // .status), fmt(seconds(.startedAt; .completedAt)), "\($slow.name // "n/a") (\(fmt($slow.duration)))"])
),
"",
"### Slowest steps",
"| Step | Job | Duration |",
"| --- | --- | ---: |",
(
[
$jobs[] as $job
| ($job.steps // [])[]
| select(.startedAt and .completedAt and .conclusion != "skipped")
| {job: $job.name, name, duration: seconds(.startedAt; .completedAt)}
]
| sort_by(.duration // 0)
| reverse
| .[0:20][]
| row([.name, .job, fmt(.duration)])
)
][]
' "$run_json" >> "$GITHUB_STEP_SUMMARY"