Files
openwork/.github/workflows/release-macos-aarch64.yml

1232 lines
52 KiB
YAML

name: Release App
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
benchmark_only:
description: "Build the release matrix for timing only; never create tags or publish artifacts"
required: false
type: boolean
default: false
bump:
description: "Cut a fresh release: bump from the highest v* tag and tag origin/dev HEAD"
required: false
type: choice
options:
- patch
- minor
- major
default: patch
version:
description: "Explicit version for a fresh release (X.Y.Z, overrides bump)"
required: false
type: string
tag:
description: "Existing tag to re-run for recovery (e.g., v0.1.2). Skips tag creation."
required: false
type: string
release_name:
description: "Release title (defaults to OpenWork <tag>)"
required: false
type: string
release_body:
description: "Release notes body in Markdown (defaults to a short placeholder)"
required: false
type: string
draft:
description: "Create the GitHub Release as a draft until assets are ready (publish-release flips it public)"
required: false
type: boolean
default: true
prerelease:
description: "Mark the GitHub Release as a prerelease"
required: false
type: boolean
default: false
notarize:
description: "Notarize macOS builds (requires Apple team configured)"
required: false
type: boolean
default: true
publish_npm:
description: "Publish openwork-server to npm if its version changed"
required: false
type: boolean
default: true
publish_daytona_snapshot:
description: "Build + push Daytona worker snapshot"
required: false
type: boolean
default: true
build_electron:
description: "Build + publish Electron desktop artifacts (macOS/Linux/Windows)"
required: false
type: boolean
default: true
sign_windows:
description: "Sign Windows Electron installers with Azure Artifact Signing"
required: false
type: boolean
default: true
permissions:
actions: read
contents: write
concurrency:
# Tag pushes dedupe per tag ref; dispatches keyed on the recovery tag or the
# run id so cutting a fresh release never cancels another in-flight release.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref || inputs.tag || github.run_id }}
cancel-in-progress: true
jobs:
resolve-production:
name: Resolve Release Metadata
runs-on: blacksmith-4vcpu-ubuntu-2404
# WARDEN_PRIVATE_KEY lives in this environment; its deployment policy
# (dev + v* tags) means the key is only reachable from reviewed code —
# a feature-branch workflow edit cannot mint the tag-push token.
environment: warden-clearance
# Fresh dispatch cuts push their tag with the diff-warden app token, and
# app-token pushes DO retrigger this workflow via push (unlike
# GITHUB_TOKEN). The dispatch run that created the tag owns the release,
# so the duplicate push run skips here — and with it every job that
# needs resolve-release.
if: github.event.inputs.benchmark_only != 'true' && (github.event_name != 'push' || github.actor != 'diff-warden[bot]')
outputs:
release_tag: ${{ steps.resolve.outputs.release_tag }}
release_version: ${{ steps.resolve.outputs.release_version }}
release_mode: ${{ steps.tag.outputs.release_mode }}
release_name: ${{ steps.resolve.outputs.release_name }}
release_body: ${{ steps.resolve.outputs.release_body }}
draft: ${{ steps.resolve.outputs.draft }}
prerelease: ${{ steps.resolve.outputs.prerelease }}
notarize: ${{ steps.resolve.outputs.notarize }}
build_electron: ${{ steps.resolve.outputs.build_electron }}
sign_windows: ${{ steps.resolve.outputs.sign_windows }}
publish_npm: ${{ steps.resolve.outputs.publish_npm }}
publish_daytona_snapshot: ${{ steps.resolve.outputs.publish_daytona_snapshot }}
benchmark_only: "false"
steps:
# Tag creation was admin-only before the app-token flow (the v* tag
# ruleset gated it at push time). The app token would let ANY dispatch
# release, so restore the old bar here: dispatching actor must be a
# repo admin, or explicitly listed in the RELEASE_MANAGERS repo
# variable (comma-separated GitHub logins). Tag pushes skip this —
# they are still gated by the ruleset itself. This check lives on a
# protected branch, and the token mint below is environment-gated to
# dev + v* refs, so a feature-branch copy of this workflow can neither
# drop the check nor reach the key.
- name: Require release permission
if: github.event_name == 'workflow_dispatch'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
ACTOR: ${{ github.actor }}
RELEASE_MANAGERS: ${{ vars.RELEASE_MANAGERS }}
run: |
set -euo pipefail
for allowed in ${RELEASE_MANAGERS//,/ }; do
if [ "$allowed" = "$ACTOR" ]; then
echo "${ACTOR} is listed in RELEASE_MANAGERS."
exit 0
fi
done
permission="$(gh api "repos/${GITHUB_REPOSITORY}/collaborators/${ACTOR}/permission" --jq .permission)"
if [ "$permission" != "admin" ]; then
echo "::error::Release dispatch is restricted: ${ACTOR} has '${permission}' but needs repo admin (or a RELEASE_MANAGERS listing)." >&2
exit 1
fi
echo "${ACTOR} is a repo admin."
- name: Checkout
uses: actions/checkout@v6
with:
# Tag resolution compares against every v* tag, and fresh dispatch
# releases tag origin/dev HEAD — both need full history + tags.
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
# The built-in GitHub Actions app cannot be a ruleset bypass actor
# (GitHub rejects it), so fresh cuts push their tag as the org-owned
# diff-warden app, which is on the v* ruleset bypass list.
- name: Mint tag-push token
id: tag-token
if: github.event_name == 'workflow_dispatch' && github.event.inputs.tag == ''
continue-on-error: true
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
with:
app-id: ${{ secrets.WARDEN_APP_ID }}
private-key: ${{ secrets.WARDEN_PRIVATE_KEY }}
- name: Resolve release tag
id: tag
shell: bash
env:
INPUT_TAG: ${{ github.event.inputs.tag }}
INPUT_VERSION: ${{ github.event.inputs.version }}
INPUT_BUMP: ${{ github.event.inputs.bump }}
TAG_PUSH_TOKEN: ${{ steps.tag-token.outputs.token }}
run: |
set -euo pipefail
if [ "${GITHUB_EVENT_NAME}" = "push" ]; then
# Manual tag-first push (admin expedited path). The tag already
# exists; the Expedited Release Audit workflow covers tags whose
# commit is not on dev.
TAG="${GITHUB_REF_NAME}"
MODE="fresh"
elif [ -n "${INPUT_TAG:-}" ]; then
TAG="${INPUT_TAG}"
if [[ "$TAG" != v* ]]; then
TAG="v${TAG}"
fi
if ! git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
echo "Recovery dispatch requires an existing tag; ${TAG} was not found." >&2
exit 1
fi
MODE="recovery"
else
# Fresh release with no commits anywhere: versions live only in
# git tags, so compute the next one and tag origin/dev HEAD.
VERSION="${INPUT_VERSION:-}"
if [ -n "$VERSION" ]; then
VERSION="${VERSION#v}"
else
VERSION="$(node scripts/release/versions.mjs next --bump "${INPUT_BUMP:-patch}")"
fi
TAG="v${VERSION}"
MODE="fresh"
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
echo "Tag ${TAG} already exists. Re-run with tag=${TAG} (recovery) instead." >&2
exit 1
fi
git fetch origin dev
SHA="$(git rev-parse origin/dev)"
# Create the tag ref via REST as the diff-warden app (a v* tag
# ruleset bypass actor). Ruleset bypass attributes REST ref
# creation to the app; a git-over-https push with the same
# installation token is NOT attributed and gets rejected —
# proven by the tag-push smoke — so keep this a REST call. The
# app-created tag retriggers this workflow, but the duplicate
# run is skipped by the actor guard on this job.
if [ -n "${TAG_PUSH_TOKEN:-}" ]; then
if ! GH_TOKEN="${TAG_PUSH_TOKEN}" gh api -X POST "repos/${GITHUB_REPOSITORY}/git/refs" \
-f ref="refs/tags/${TAG}" -f sha="${SHA}"; then
echo "::error::Creating ${TAG} was rejected by the v* tag ruleset. Ensure the diff-warden app is on its bypass list (Settings → Rules → Protect v* release tags), or push the tag manually as an admin and rerun." >&2
exit 1
fi
else
echo "::warning::diff-warden app token unavailable (WARDEN_APP_ID / WARDEN_PRIVATE_KEY in the warden-clearance environment); trying GITHUB_TOKEN."
git tag "$TAG" "$SHA"
if ! git push origin "refs/tags/${TAG}"; then
echo "::error::Pushing ${TAG} was rejected by the v* tag ruleset. Push the tag manually as an admin and rerun." >&2
exit 1
fi
fi
echo "Created ${TAG} at ${SHA} (origin/dev HEAD)."
fi
echo "release_tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "release_mode=${MODE}" >> "$GITHUB_OUTPUT"
- name: Resolve metadata
id: resolve
shell: bash
env:
RESOLVED_TAG: ${{ steps.tag.outputs.release_tag }}
INPUT_RELEASE_NAME: ${{ github.event.inputs.release_name }}
INPUT_RELEASE_BODY: ${{ github.event.inputs.release_body }}
INPUT_DRAFT: ${{ github.event.inputs.draft }}
INPUT_PRERELEASE: ${{ github.event.inputs.prerelease }}
INPUT_NOTARIZE: ${{ github.event.inputs.notarize }}
INPUT_BUILD_ELECTRON: ${{ github.event.inputs.build_electron }}
INPUT_SIGN_WINDOWS: ${{ github.event.inputs.sign_windows }}
INPUT_PUBLISH_NPM: ${{ github.event.inputs.publish_npm }}
INPUT_PUBLISH_DAYTONA_SNAPSHOT: ${{ github.event.inputs.publish_daytona_snapshot }}
DEFAULT_PUBLISH_NPM: ${{ vars.RELEASE_PUBLISH_NPM }}
DEFAULT_PUBLISH_DAYTONA_SNAPSHOT: ${{ vars.RELEASE_PUBLISH_DAYTONA_SNAPSHOT }}
DEFAULT_NOTARIZE: ${{ vars.MACOS_NOTARIZE }}
DEFAULT_BUILD_ELECTRON: ${{ vars.RELEASE_PUBLISH_ELECTRON }}
DEFAULT_SIGN_WINDOWS: ${{ vars.RELEASE_SIGN_WINDOWS }}
run: |
set -euo pipefail
TAG="${RESOLVED_TAG}"
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid release tag: $TAG (expected stable vX.Y.Z)" >&2
exit 1
fi
RELEASE_NAME_INPUT="${INPUT_RELEASE_NAME:-}"
if [ -n "$RELEASE_NAME_INPUT" ]; then
RELEASE_NAME="$RELEASE_NAME_INPUT"
else
RELEASE_NAME="OpenWork $TAG"
fi
RELEASE_BODY_INPUT="${INPUT_RELEASE_BODY:-}"
if [ -n "$RELEASE_BODY_INPUT" ]; then
RELEASE_BODY="$RELEASE_BODY_INPUT"
else
printf -v RELEASE_BODY '%s\n\nOpenWork %s desktop release.\n\n%s\n%s\n%s\n%s' \
"## What's new" \
"$TAG" \
"- Public artifacts use the openwork-* naming convention." \
"- Cloud artifacts use openwork-cloud-* and require sign-in without enterprise activation." \
"- Enterprise artifacts use openwork-enterprise-* while retaining the standard OpenWork app identity." \
"- Public, Cloud, and enterprise Electron builds use separate updater manifests on this release."
fi
# A release must never be public while its artifacts are still
# building: default to draft for every entry path (tag push AND
# fresh dispatch). publish-release flips it public at the end.
draft="${INPUT_DRAFT:-true}"
prerelease="${INPUT_PRERELEASE:-false}"
notarize="${INPUT_NOTARIZE:-}"
if [ -z "$notarize" ]; then
notarize="${DEFAULT_NOTARIZE:-true}"
fi
build_electron="${INPUT_BUILD_ELECTRON:-}"
if [ -z "$build_electron" ]; then
build_electron="${DEFAULT_BUILD_ELECTRON:-true}"
fi
sign_windows="${INPUT_SIGN_WINDOWS:-}"
if [ -z "$sign_windows" ]; then
sign_windows="${DEFAULT_SIGN_WINDOWS:-false}"
fi
if [ "$sign_windows" = "true" ]; then
WINDOWS_SIGNED_NOTE='*Windows installers are signed using Microsoft Artifact Signing.*'
printf -v RELEASE_BODY '%s\n\n%s' "$RELEASE_BODY" "$WINDOWS_SIGNED_NOTE"
else
WINDOWS_UNSIGNED_NOTE='*Windows installer is temporarily unsigned while production code signing is being finalized.*'
printf -v RELEASE_BODY '%s\n\n%s' "$RELEASE_BODY" "$WINDOWS_UNSIGNED_NOTE"
fi
publish_npm="${INPUT_PUBLISH_NPM:-}"
if [ -z "$publish_npm" ]; then
publish_npm="${DEFAULT_PUBLISH_NPM:-true}"
fi
publish_daytona_snapshot="${INPUT_PUBLISH_DAYTONA_SNAPSHOT:-}"
if [ -z "$publish_daytona_snapshot" ]; then
publish_daytona_snapshot="${DEFAULT_PUBLISH_DAYTONA_SNAPSHOT:-true}"
fi
TAG="${TAG//$'\n'/}"
TAG="${TAG//$'\r'/}"
RELEASE_NAME="${RELEASE_NAME//$'\n'/ }"
RELEASE_NAME="${RELEASE_NAME//$'\r'/ }"
echo "release_tag=$TAG" >> "$GITHUB_OUTPUT"
echo "release_version=${TAG#v}" >> "$GITHUB_OUTPUT"
echo "release_name=$RELEASE_NAME" >> "$GITHUB_OUTPUT"
echo "draft=$draft" >> "$GITHUB_OUTPUT"
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
echo "notarize=$notarize" >> "$GITHUB_OUTPUT"
echo "build_electron=$build_electron" >> "$GITHUB_OUTPUT"
echo "sign_windows=$sign_windows" >> "$GITHUB_OUTPUT"
echo "publish_npm=$publish_npm" >> "$GITHUB_OUTPUT"
echo "publish_daytona_snapshot=$publish_daytona_snapshot" >> "$GITHUB_OUTPUT"
{
echo "release_body<<__OPENWORK_RELEASE_BODY_EOF__"
printf '%s\n' "$RELEASE_BODY"
echo "__OPENWORK_RELEASE_BODY_EOF__"
} >> "$GITHUB_OUTPUT"
- name: Create release if missing
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
BODY_FILE="$RUNNER_TEMP/release_body.md"
printf '%s\n' "${{ steps.resolve.outputs.release_body }}" > "$BODY_FILE"
if gh release view "${{ steps.resolve.outputs.release_tag }}" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "Release ${{ steps.resolve.outputs.release_tag }} already exists; skipping create."
exit 0
fi
PRERELEASE_FLAG=""
if [ "${{ steps.resolve.outputs.prerelease }}" = "true" ]; then
PRERELEASE_FLAG="--prerelease"
fi
# A new release must never be public while its artifacts are still
# building. The final publish-release job is the only place that
# removes draft status, after every required asset has been checked.
gh release create "${{ steps.resolve.outputs.release_tag }}" \
--repo "$GITHUB_REPOSITORY" \
--title "${{ steps.resolve.outputs.release_name }}" \
--notes-file "$BODY_FILE" \
--draft $PRERELEASE_FLAG
resolve-benchmark:
name: Resolve Benchmark Metadata
if: github.event.inputs.benchmark_only == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
outputs:
release_tag: ${{ steps.resolve.outputs.release_tag }}
release_version: ${{ steps.resolve.outputs.release_version }}
release_mode: ${{ steps.resolve.outputs.release_mode }}
notarize: ${{ steps.resolve.outputs.notarize }}
build_electron: ${{ steps.resolve.outputs.build_electron }}
sign_windows: ${{ steps.resolve.outputs.sign_windows }}
publish_npm: ${{ steps.resolve.outputs.publish_npm }}
publish_daytona_snapshot: ${{ steps.resolve.outputs.publish_daytona_snapshot }}
benchmark_only: ${{ steps.resolve.outputs.benchmark_only }}
prerelease: ${{ steps.resolve.outputs.prerelease }}
steps:
- name: Resolve non-publishing benchmark context
id: resolve
shell: bash
run: |
set -euo pipefail
{
echo "release_tag=${GITHUB_SHA}"
echo "release_version=0.0.0-benchmark.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}"
echo "release_mode=benchmark"
echo "notarize=true"
echo "build_electron=true"
echo "sign_windows=true"
echo "publish_npm=false"
echo "publish_daytona_snapshot=false"
echo "benchmark_only=true"
echo "prerelease=true"
} >> "$GITHUB_OUTPUT"
resolve-release:
name: Select Release Context
needs: [resolve-production, resolve-benchmark]
if: |
always() &&
(needs.resolve-production.result == 'success' || needs.resolve-benchmark.result == 'success')
runs-on: blacksmith-4vcpu-ubuntu-2404
outputs:
release_tag: ${{ needs.resolve-production.outputs.release_tag || needs.resolve-benchmark.outputs.release_tag }}
release_version: ${{ needs.resolve-production.outputs.release_version || needs.resolve-benchmark.outputs.release_version }}
release_mode: ${{ needs.resolve-production.outputs.release_mode || needs.resolve-benchmark.outputs.release_mode }}
notarize: ${{ needs.resolve-production.outputs.notarize || needs.resolve-benchmark.outputs.notarize }}
build_electron: ${{ needs.resolve-production.outputs.build_electron || needs.resolve-benchmark.outputs.build_electron }}
sign_windows: ${{ needs.resolve-production.outputs.sign_windows || needs.resolve-benchmark.outputs.sign_windows }}
publish_npm: ${{ needs.resolve-production.outputs.publish_npm || needs.resolve-benchmark.outputs.publish_npm }}
publish_daytona_snapshot: ${{ needs.resolve-production.outputs.publish_daytona_snapshot || needs.resolve-benchmark.outputs.publish_daytona_snapshot }}
benchmark_only: ${{ needs.resolve-production.outputs.benchmark_only || needs.resolve-benchmark.outputs.benchmark_only }}
prerelease: ${{ needs.resolve-production.outputs.prerelease || needs.resolve-benchmark.outputs.prerelease }}
steps:
- run: true
verify-release:
name: Verify Release Versions
needs: resolve-release
# Benchmark mode intentionally skips resolve-production. Override the
# implicit success() check so that skipped sibling does not propagate
# through the selected release context and suppress verification.
if: always() && needs.resolve-release.result == 'success'
runs-on: blacksmith-4vcpu-ubuntu-2404
env:
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
RELEASE_MODE: ${{ needs.resolve-release.outputs.release_mode }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ env.RELEASE_TAG }}
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
- name: Verify release tag
if: needs.resolve-release.outputs.benchmark_only != 'true'
run: node scripts/release/verify-tag.mjs --tag "$RELEASE_TAG" --mode "$RELEASE_MODE"
- name: Release review (strict)
run: node scripts/release/review.mjs --strict
publish-electron-unix:
name: Build + publish Electron (${{ matrix.artifact }})
needs: [resolve-release, verify-release]
if: |
always() &&
needs.resolve-release.result == 'success' &&
needs.verify-release.result == 'success' &&
needs.resolve-release.outputs.build_electron == 'true'
runs-on: ${{ matrix.platform }}
timeout-minutes: 120
env:
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
RELEASE_VERSION: ${{ needs.resolve-release.outputs.release_version }}
MACOS_NOTARIZE: ${{ needs.resolve-release.outputs.notarize }}
SIGN_WINDOWS: ${{ needs.resolve-release.outputs.sign_windows }}
BENCHMARK_ONLY: ${{ needs.resolve-release.outputs.benchmark_only }}
# A filtered frozen install already materializes the complete desktop
# dependency closure. Without this, pnpm may "repair" the intentionally
# partial workspace with a second all-workspace install before scripts.
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN: "false"
OPENWORK_DESKTOP_SENTRY_DSN: ${{ secrets.OPENWORK_DESKTOP_SENTRY_DSN || vars.OPENWORK_DESKTOP_SENTRY_DSN }}
OPENWORK_DESKTOP_SENTRY_TRACES_SAMPLE_RATE: ${{ vars.OPENWORK_DESKTOP_SENTRY_TRACES_SAMPLE_RATE || '0.01' }}
strategy:
fail-fast: false
matrix:
include:
- platform: blacksmith-12vcpu-macos-15
os_type: macos
artifact: electron-macos-arm64
builder_config: electron-builder.yml
asset_prefix: openwork
manifest_pattern: latest*.yml
electron_args: "--mac --arm64"
target_triple: aarch64-apple-darwin
- platform: blacksmith-12vcpu-macos-15
os_type: macos
artifact: electron-macos-x64
builder_config: electron-builder.yml
asset_prefix: openwork
manifest_pattern: latest*.yml
electron_args: "--mac --x64"
target_triple: x86_64-apple-darwin
- platform: blacksmith-16vcpu-ubuntu-2204
os_type: linux
artifact: electron-linux-x64
builder_config: electron-builder.yml
asset_prefix: openwork
manifest_pattern: latest*.yml
electron_args: "--linux --x64"
target_triple: x86_64-unknown-linux-gnu
# electron-builder can package ARM64 without executing the target
# binaries. Cross-package on the faster x64 compression runner; the
# workspace installs both CPU variants of optional native packages.
- platform: blacksmith-16vcpu-ubuntu-2204
os_type: linux
artifact: electron-linux-arm64
builder_config: electron-builder.yml
asset_prefix: openwork
manifest_pattern: latest*.yml
electron_args: "--linux --arm64"
target_triple: aarch64-unknown-linux-gnu
- platform: blacksmith-12vcpu-macos-15
os_type: macos
artifact: electron-cloud-macos-arm64
builder_config: electron-builder.cloud.yml
asset_prefix: openwork-cloud
manifest_pattern: cloud*.yml
electron_args: "--mac --arm64"
target_triple: aarch64-apple-darwin
- platform: blacksmith-12vcpu-macos-15
os_type: macos
artifact: electron-cloud-macos-x64
builder_config: electron-builder.cloud.yml
asset_prefix: openwork-cloud
manifest_pattern: cloud*.yml
electron_args: "--mac --x64"
target_triple: x86_64-apple-darwin
- platform: blacksmith-16vcpu-ubuntu-2204
os_type: linux
artifact: electron-cloud-linux-x64
builder_config: electron-builder.cloud.yml
asset_prefix: openwork-cloud
manifest_pattern: cloud*.yml
electron_args: "--linux --x64"
target_triple: x86_64-unknown-linux-gnu
- platform: blacksmith-16vcpu-ubuntu-2204
os_type: linux
artifact: electron-cloud-linux-arm64
builder_config: electron-builder.cloud.yml
asset_prefix: openwork-cloud
manifest_pattern: cloud*.yml
electron_args: "--linux --arm64"
target_triple: aarch64-unknown-linux-gnu
- platform: blacksmith-12vcpu-macos-15
os_type: macos
artifact: electron-enterprise-macos-arm64
builder_config: electron-builder.enterprise.yml
asset_prefix: openwork-enterprise
manifest_pattern: enterprise*.yml
electron_args: "--mac --arm64"
target_triple: aarch64-apple-darwin
- platform: blacksmith-12vcpu-macos-15
os_type: macos
artifact: electron-enterprise-macos-x64
builder_config: electron-builder.enterprise.yml
asset_prefix: openwork-enterprise
manifest_pattern: enterprise*.yml
electron_args: "--mac --x64"
target_triple: x86_64-apple-darwin
- platform: blacksmith-16vcpu-ubuntu-2204
os_type: linux
artifact: electron-enterprise-linux-x64
builder_config: electron-builder.enterprise.yml
asset_prefix: openwork-enterprise
manifest_pattern: enterprise*.yml
electron_args: "--linux --x64"
target_triple: x86_64-unknown-linux-gnu
- platform: blacksmith-16vcpu-ubuntu-2204
os_type: linux
artifact: electron-enterprise-linux-arm64
builder_config: electron-builder.enterprise.yml
asset_prefix: openwork-enterprise
manifest_pattern: enterprise*.yml
electron_args: "--linux --arm64"
target_triple: aarch64-unknown-linux-gnu
steps: &electron-build-steps
- name: Checkout
uses: actions/checkout@v6
with:
# Source is always pinned to the tag. Recovery reruns pick up
# workflow-file fixes from the dispatched ref automatically (the
# workflow definition runs from github.ref), while the shipped code
# stays exactly what the tag names.
ref: ${{ env.RELEASE_TAG }}
fetch-depth: 1
- name: Enable git long paths (Windows)
if: matrix.os_type == 'windows'
shell: pwsh
run: git config --global core.longpaths true
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 11.4.0
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
# Bun >= 1.3.10 is required by the server build and package-time helpers.
bun-version: 1.3.10
- name: Get pnpm store path
id: pnpm-store
shell: bash
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
- name: Cache pnpm store
uses: actions/cache@v5
continue-on-error: true
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ${{ runner.os }}-electron-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-electron-pnpm-
- name: Install dependencies
run: pnpm --filter @openwork/desktop... --filter @openwork/app... --filter openwork-server... install --frozen-lockfile --prefer-offline
# The repo commits a permanent 0.0.0-dev placeholder; the release
# version exists only as the git tag. Stamp it into the workspace so
# electron-builder, app.getVersion(), and the Vite bundle carry it.
# Pre-redesign tags carry real committed versions and no stamp script —
# skipping is exactly the old behavior for them.
- name: Stamp release version
shell: bash
run: |
set -euo pipefail
if [ -f scripts/release/stamp-version.mjs ]; then
node scripts/release/stamp-version.mjs --version "$RELEASE_VERSION"
else
echo "Pre-redesign tag: committed package.json versions are in effect."
fi
- name: Write Electron notary API key (macOS)
if: matrix.os_type == 'macos' && env.MACOS_NOTARIZE == 'true'
env:
APPLE_NOTARY_API_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_API_KEY_P8_BASE64 }}
APPLE_NOTARY_API_KEY_ID: ${{ secrets.APPLE_NOTARY_API_KEY_ID }}
APPLE_NOTARY_API_ISSUER_ID: ${{ secrets.APPLE_NOTARY_API_ISSUER_ID }}
APPLE_CODESIGN_CERT_P12_BASE64: ${{ secrets.APPLE_CODESIGN_CERT_P12_BASE64 }}
APPLE_CODESIGN_CERT_PASSWORD: ${{ secrets.APPLE_CODESIGN_CERT_PASSWORD }}
run: |
set -euo pipefail
missing=()
for name in \
APPLE_NOTARY_API_KEY_P8_BASE64 \
APPLE_NOTARY_API_KEY_ID \
APPLE_NOTARY_API_ISSUER_ID \
APPLE_CODESIGN_CERT_P12_BASE64 \
APPLE_CODESIGN_CERT_PASSWORD; do
if [ -z "${!name:-}" ]; then
missing+=("$name")
fi
done
if [ "${#missing[@]}" -gt 0 ]; then
printf 'Missing Electron macOS notarization/signing secrets: %s\n' "${missing[*]}" >&2
exit 1
fi
NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey.p8"
printf '%s' "$APPLE_NOTARY_API_KEY_P8_BASE64" | base64 --decode > "$NOTARY_KEY_PATH"
chmod 600 "$NOTARY_KEY_PATH"
echo "NOTARY_KEY_PATH=$NOTARY_KEY_PATH" >> "$GITHUB_ENV"
- name: Reject unnotarized macOS Electron release
if: matrix.os_type == 'macos' && env.MACOS_NOTARIZE != 'true'
shell: bash
run: |
echo "macOS Electron release assets must be notarized. Re-run with notarize=true or set MACOS_NOTARIZE=true." >&2
exit 1
- name: Build Electron app
shell: bash
env:
# TARGET tells prepare-sidecar.mjs which architecture's opencode binary
# to download (critical for cross-arch builds like x64 on arm64 mac).
TARGET: ${{ matrix.target_triple }}
run: pnpm --filter @openwork/desktop build:electron
- name: Package Electron (macOS, signed + notarized)
if: matrix.os_type == 'macos' && env.MACOS_NOTARIZE == 'true'
env:
CSC_LINK: ${{ secrets.APPLE_CODESIGN_CERT_P12_BASE64 }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CODESIGN_CERT_PASSWORD }}
APPLE_API_KEY: ${{ secrets.APPLE_NOTARY_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_NOTARY_API_ISSUER_ID }}
APPLE_API_KEY_PATH: ${{ env.NOTARY_KEY_PATH }}
run: |
set -euo pipefail
pnpm --dir apps/desktop exec electron-builder \
--config ${{ matrix.builder_config }} \
${{ matrix.electron_args }} \
--publish never
- name: Package Electron (Linux)
if: matrix.os_type == 'linux'
run: |
set -euo pipefail
pnpm --dir apps/desktop exec electron-builder \
--config ${{ matrix.builder_config }} \
${{ matrix.electron_args }} \
--publish never
- name: Package Electron (Windows)
if: matrix.os_type == 'windows'
shell: bash
run: |
set -euo pipefail
pnpm --dir apps/desktop exec electron-builder \
--config ${{ matrix.builder_config }} \
${{ matrix.electron_args }} \
--publish never
# Belt-and-braces: a broken or skipped stamp would otherwise ship
# 0.0.0-dev artifacts silently. Every packaged build must produce at
# least one artifact carrying the release version (also holds for
# pre-redesign tags — their committed version equals the tag).
- name: Assert artifact versions match the tag
shell: bash
run: |
set -euo pipefail
shopt -s nullglob
matches=(apps/desktop/dist-electron/*"$RELEASE_VERSION"*)
if [ ${#matches[@]} -eq 0 ]; then
echo "No packaged artifact carries version $RELEASE_VERSION — version stamping failed?" >&2
ls apps/desktop/dist-electron >&2 || true
exit 1
fi
printf 'Version-checked artifact: %s\n' "${matches[@]}"
- name: Use unsigned Windows installer fallback
if: matrix.os_type == 'windows' && env.SIGN_WINDOWS != 'true'
shell: bash
run: |
set -euo pipefail
echo "::warning::Publishing unsigned Windows installer because SIGN_WINDOWS is not enabled."
# electron-builder may still emit latest*.yml for a custom distribution.
# Normalize each non-public feed so it cannot update into another flavor.
- name: Normalize custom distribution updater manifests
if: matrix.asset_prefix != 'openwork'
shell: bash
run: |
set -euo pipefail
cd apps/desktop/dist-electron
shopt -s nullglob
channel="${{ matrix.asset_prefix }}"
channel="${channel#openwork-}"
for manifest in latest*.yml; do
target="${channel}${manifest#latest}"
if [ -e "$target" ]; then
rm -f "$manifest"
else
mv "$manifest" "$target"
fi
done
manifests=("${channel}"*.yml)
if [ ${#manifests[@]} -eq 0 ]; then
echo "${channel} build produced no updater manifest." >&2
exit 1
fi
printf '%s\n' "${manifests[@]}"
- name: Validate Azure Artifact Signing configuration
if: matrix.os_type == 'windows' && env.SIGN_WINDOWS == 'true'
shell: pwsh
env:
AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID || secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID || secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID || secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_ARTIFACT_SIGNING_ENDPOINT: ${{ vars.AZURE_ARTIFACT_SIGNING_ENDPOINT || secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
AZURE_ARTIFACT_SIGNING_ACCOUNT: ${{ vars.AZURE_ARTIFACT_SIGNING_ACCOUNT || secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT }}
AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE: ${{ vars.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE || secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE }}
run: |
$required = @(
"AZURE_CLIENT_ID",
"AZURE_TENANT_ID",
"AZURE_SUBSCRIPTION_ID",
"AZURE_ARTIFACT_SIGNING_ENDPOINT",
"AZURE_ARTIFACT_SIGNING_ACCOUNT",
"AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE"
)
$missing = $required | Where-Object { -not [Environment]::GetEnvironmentVariable($_) }
if ($missing.Count -gt 0) {
throw "Missing windows-signing environment variables: $($missing -join ', ')"
}
$dist = Join-Path $env:GITHUB_WORKSPACE "apps\desktop\dist-electron"
$installers = Get-ChildItem $dist -Filter "*.exe"
if ($installers.Count -ne 1) {
throw "Expected one unsigned Windows installer, found $($installers.Count)"
}
- name: Azure login with GitHub OIDC
if: matrix.os_type == 'windows' && env.SIGN_WINDOWS == 'true'
uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3.0.1
with:
client-id: ${{ vars.AZURE_CLIENT_ID || secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID || secrets.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID || secrets.AZURE_SUBSCRIPTION_ID }}
- name: Sign Windows installer
if: matrix.os_type == 'windows' && env.SIGN_WINDOWS == 'true'
uses: azure/artifact-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2
with:
endpoint: ${{ vars.AZURE_ARTIFACT_SIGNING_ENDPOINT || secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
signing-account-name: ${{ vars.AZURE_ARTIFACT_SIGNING_ACCOUNT || secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT }}
certificate-profile-name: ${{ vars.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE || secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE }}
files-folder: ${{ github.workspace }}\apps\desktop\dist-electron
files-folder-filter: exe
files-folder-recurse: false
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: Verify Windows signature
if: matrix.os_type == 'windows' && env.SIGN_WINDOWS == 'true'
shell: pwsh
run: |
$dist = Join-Path $env:GITHUB_WORKSPACE "apps\desktop\dist-electron"
$installers = Get-ChildItem $dist -Filter "*.exe"
if ($installers.Count -ne 1) {
throw "Expected one signed Windows installer, found $($installers.Count)"
}
$signature = Get-AuthenticodeSignature $installers[0].FullName
Write-Host "$($installers[0].Name): $($signature.Status) - $($signature.SignerCertificate.Subject)"
if ($signature.Status -ne "Valid") {
throw "$($installers[0].Name) signature is $($signature.Status)"
}
- name: Refresh signed installer blockmap and manifest
if: matrix.os_type == 'windows' && env.SIGN_WINDOWS == 'true'
shell: bash
run: node scripts/release/refresh-signed-windows-artifacts.mjs apps/desktop/dist-electron 1
- name: Upload Electron release assets
if: env.BENCHMARK_ONLY != 'true'
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
shopt -s nullglob
assets=(apps/desktop/dist-electron/${{ matrix.asset_prefix }}-*)
if [ ${#assets[@]} -eq 0 ]; then
echo "No Electron release assets found in apps/desktop/dist-electron" >&2
exit 1
fi
gh release upload "$RELEASE_TAG" "${assets[@]}" --repo "$GITHUB_REPOSITORY" --clobber
- name: Retain benchmark Electron assets
if: env.BENCHMARK_ONLY == 'true'
uses: actions/upload-artifact@v4
with:
name: benchmark-${{ matrix.artifact }}
path: |
apps/desktop/dist-electron/${{ matrix.asset_prefix }}-*
apps/desktop/dist-electron/${{ matrix.manifest_pattern }}
if-no-files-found: error
retention-days: 1
- name: Upload Electron updater manifest artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: apps/desktop/dist-electron/${{ matrix.manifest_pattern }}
if-no-files-found: error
publish-electron-windows:
name: Build + publish Electron (${{ matrix.artifact }})
needs: [resolve-release, verify-release]
if: |
always() &&
needs.resolve-release.result == 'success' &&
needs.verify-release.result == 'success' &&
needs.resolve-release.outputs.build_electron == 'true'
runs-on: ${{ matrix.platform }}
environment: windows-signing
timeout-minutes: 120
permissions:
actions: read
contents: write
id-token: write
env:
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
RELEASE_VERSION: ${{ needs.resolve-release.outputs.release_version }}
MACOS_NOTARIZE: ${{ needs.resolve-release.outputs.notarize }}
SIGN_WINDOWS: ${{ needs.resolve-release.outputs.sign_windows }}
BENCHMARK_ONLY: ${{ needs.resolve-release.outputs.benchmark_only }}
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN: "false"
OPENWORK_DESKTOP_SENTRY_DSN: ${{ secrets.OPENWORK_DESKTOP_SENTRY_DSN || vars.OPENWORK_DESKTOP_SENTRY_DSN }}
OPENWORK_DESKTOP_SENTRY_TRACES_SAMPLE_RATE: ${{ vars.OPENWORK_DESKTOP_SENTRY_TRACES_SAMPLE_RATE || '0.01' }}
strategy:
fail-fast: false
matrix:
include:
- platform: blacksmith-8vcpu-windows-2025
os_type: windows
artifact: electron-windows-x64
builder_config: electron-builder.yml
asset_prefix: openwork
manifest_pattern: latest*.yml
electron_args: "--win --x64"
target_triple: x86_64-pc-windows-msvc
# electron-builder cross-packages ARM64 on the faster x64 runner.
# pnpm-workspace.yaml installs both Windows CPU variants so native
# optional dependencies remain correct in the ARM64 artifact.
- platform: blacksmith-8vcpu-windows-2025
os_type: windows
artifact: electron-windows-arm64
builder_config: electron-builder.yml
asset_prefix: openwork
manifest_pattern: latest*.yml
electron_args: "--win --arm64"
target_triple: aarch64-pc-windows-msvc
- platform: blacksmith-8vcpu-windows-2025
os_type: windows
artifact: electron-cloud-windows-x64
builder_config: electron-builder.cloud.yml
asset_prefix: openwork-cloud
manifest_pattern: cloud*.yml
electron_args: "--win --x64"
target_triple: x86_64-pc-windows-msvc
- platform: blacksmith-8vcpu-windows-2025
os_type: windows
artifact: electron-cloud-windows-arm64
builder_config: electron-builder.cloud.yml
asset_prefix: openwork-cloud
manifest_pattern: cloud*.yml
electron_args: "--win --arm64"
target_triple: aarch64-pc-windows-msvc
- platform: blacksmith-8vcpu-windows-2025
os_type: windows
artifact: electron-enterprise-windows-x64
builder_config: electron-builder.enterprise.yml
asset_prefix: openwork-enterprise
manifest_pattern: enterprise*.yml
electron_args: "--win --x64"
target_triple: x86_64-pc-windows-msvc
- platform: blacksmith-8vcpu-windows-2025
os_type: windows
artifact: electron-enterprise-windows-arm64
builder_config: electron-builder.enterprise.yml
asset_prefix: openwork-enterprise
manifest_pattern: enterprise*.yml
electron_args: "--win --arm64"
target_triple: aarch64-pc-windows-msvc
steps: *electron-build-steps
publish-release:
name: Publish Electron Assets + GitHub Release
needs:
- resolve-release
- verify-release
- publish-electron-unix
- publish-electron-windows
- publish-npm
- publish-daytona-snapshot
# publish-daytona-snapshot is allowed to fail without blocking the release:
# the sandbox snapshot is rebuildable after the fact, while installable
# desktop artifacts should never be held back by it.
if: |
always() &&
needs.resolve-release.result == 'success' &&
needs.verify-release.result == 'success' &&
needs.resolve-release.outputs.benchmark_only != 'true' &&
(needs.resolve-release.outputs.build_electron != 'true' || needs.publish-electron-unix.result == 'success') &&
(needs.resolve-release.outputs.build_electron != 'true' || needs.publish-electron-windows.result == 'success') &&
(needs.publish-npm.result == 'success' || needs.publish-npm.result == 'skipped') &&
needs.publish-daytona-snapshot.result != 'cancelled'
runs-on: blacksmith-4vcpu-ubuntu-2404
env:
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
RELEASE_PRERELEASE: ${{ needs.resolve-release.outputs.prerelease }}
steps:
- name: Checkout
if: needs.resolve-release.outputs.build_electron == 'true'
uses: actions/checkout@v6
with:
ref: ${{ env.RELEASE_TAG }}
fetch-depth: 1
- name: Setup Node
if: needs.resolve-release.outputs.build_electron == 'true'
uses: actions/setup-node@v6
with:
node-version: 24
- name: Download Electron updater manifest artifacts
if: needs.resolve-release.outputs.build_electron == 'true'
uses: actions/download-artifact@v4
with:
pattern: electron-*
path: ${{ runner.temp }}/electron-dist
- name: Upload merged updater manifests
if: needs.resolve-release.outputs.build_electron == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: node scripts/release/publish-electron-assets.mjs --manifests-only "${{ runner.temp }}/electron-dist" "$RELEASE_TAG"
- name: Publish release after assets are ready
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
# Dispatch-based recovery runs default draft=false but may target a
# draft release created by an earlier tag push; always flip drafts
# here so AUR and updater feeds see public download URLs.
is_draft="$(gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json isDraft -q .isDraft)"
if [ "$is_draft" != "true" ]; then
echo "Release $RELEASE_TAG is already published; nothing to do."
exit 0
fi
if [ "${RELEASE_PRERELEASE}" = "true" ]; then
gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --draft=false --prerelease
else
gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --draft=false --latest
fi
prepare-npm:
name: Prepare openwork-server npm package
needs: [resolve-release, verify-release]
if: |
always() &&
needs.resolve-release.result == 'success' &&
needs.verify-release.result == 'success' &&
needs.resolve-release.outputs.benchmark_only != 'true' &&
needs.resolve-release.outputs.publish_npm == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
outputs:
publish_server: ${{ steps.npm-versions.outputs.publish_server }}
env:
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
RELEASE_VERSION: ${{ needs.resolve-release.outputs.release_version }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ env.RELEASE_TAG }}
fetch-depth: 1
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 11.4.0
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
# Bun builds the openwork-server executable before npm publish.
bun-version: "1.3.10"
- name: Get pnpm store path
id: pnpm-store
shell: bash
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
- name: Cache pnpm store
uses: actions/cache@v5
continue-on-error: true
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ubuntu-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
ubuntu-pnpm-
- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline
# openwork-server publishes with the tag-derived version; the committed
# package.json holds the permanent 0.0.0-dev placeholder. Pre-redesign
# tags carry real committed versions and no stamp script.
- name: Stamp release version
shell: bash
run: |
set -euo pipefail
if [ -f scripts/release/stamp-version.mjs ]; then
node scripts/release/stamp-version.mjs --version "$RELEASE_VERSION"
else
echo "Pre-redesign tag: committed package.json versions are in effect."
fi
- name: Check npm versions
id: npm-versions
shell: bash
env:
SERVER_VERSION: ${{ env.RELEASE_VERSION }}
run: |
set -euo pipefail
# npm view exits non-zero for packages that don't exist yet (404).
# Treat missing packages as "not published" so release can publish them.
server_current="$(npm view openwork-server version 2>/dev/null || true)"
if [ "$server_current" = "$SERVER_VERSION" ]; then
echo "publish_server=false" >> "$GITHUB_OUTPUT"
echo "publish_any=false" >> "$GITHUB_OUTPUT"
else
echo "publish_server=true" >> "$GITHUB_OUTPUT"
echo "publish_any=true" >> "$GITHUB_OUTPUT"
fi
- name: Prepare openwork-server
if: steps.npm-versions.outputs.publish_server == 'true'
run: pnpm --filter openwork-server publish:npm --dry-run
- name: Upload npm package
if: steps.npm-versions.outputs.publish_server == 'true'
uses: actions/upload-artifact@v4
with:
name: openwork-server-npm-${{ env.RELEASE_VERSION }}
path: apps/server/dist/npm
if-no-files-found: error
publish-npm:
name: Publish openwork-server
needs: [resolve-release, verify-release, prepare-npm]
if: |
always() &&
needs.resolve-release.result == 'success' &&
needs.verify-release.result == 'success' &&
needs.prepare-npm.result == 'success' &&
needs.prepare-npm.outputs.publish_server == 'true'
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
id-token: write
env:
RELEASE_VERSION: ${{ needs.resolve-release.outputs.release_version }}
steps:
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
- name: Download npm package
uses: actions/download-artifact@v4
with:
name: openwork-server-npm-${{ env.RELEASE_VERSION }}
path: ${{ runner.temp }}/openwork-server-npm
- name: Publish openwork-server
run: npm publish "$RUNNER_TEMP/openwork-server-npm" --access public --provenance
publish-daytona-snapshot:
name: Build + Push Daytona Snapshot
needs: [resolve-release, verify-release, publish-npm]
if: |
always() &&
needs.resolve-release.result == 'success' &&
needs.verify-release.result == 'success' &&
needs.resolve-release.outputs.benchmark_only != 'true' &&
(needs.publish-npm.result == 'success' || needs.publish-npm.result == 'skipped') &&
needs.resolve-release.outputs.publish_daytona_snapshot == 'true'
uses: ./.github/workflows/release-daytona-snapshot.yml
with:
tag: ${{ needs.resolve-release.outputs.release_tag }}
secrets: inherit
aur-publish:
name: Publish AUR
needs: [resolve-release, publish-electron-unix, publish-release]
if: |
always() &&
needs.resolve-release.result == 'success' &&
needs.resolve-release.outputs.benchmark_only != 'true' &&
needs.publish-electron-unix.result == 'success' &&
needs.publish-release.result == 'success'
# AUR is a community distribution channel hosted on aur.archlinux.org.
# Outages there (e.g. "The AUR is down due to maintenance", 2026-08-04)
# must not mark an otherwise-published release run as failed.
continue-on-error: true
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
env:
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
steps:
- name: Checkout dev
uses: actions/checkout@v6
with:
ref: dev
fetch-depth: 0
# packaging/aur holds a version-agnostic template (pkgver=0.0.0).
# publish-aur.sh renders the real version + checksums from the published
# release assets in the CI workspace and pushes ONLY to the AUR remote —
# the commit to aur.archlinux.org is AUR's publish protocol, and nothing
# is ever committed to this repo.
- name: Publish to AUR
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
AUR_REPO: ${{ vars.AUR_REPO || 'openwork' }}
run: |
set -euo pipefail
if [ -z "${AUR_SSH_PRIVATE_KEY:-}" ]; then
echo "AUR_SSH_PRIVATE_KEY not set; skipping publish to AUR."
exit 0
fi
scripts/aur/publish-aur.sh "$RELEASE_TAG"