mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
* refactor(repo): move OpenWork apps into apps and ee layout Rebase the monorepo layout migration onto the latest dev changes so the moved app, desktop, share, and cloud surfaces keep working from their new paths. Carry the latest deeplink, token persistence, build, Vercel, and docs updates forward to avoid stale references and broken deploy tooling. * chore(repo): drop generated desktop artifacts Ignore the moved Tauri target and sidecar paths so local cargo checks do not pollute the branch. Remove the accidentally committed outputs from the repo while keeping the layout migration intact. * fix(release): drop built server cli artifact Stop tracking the locally built apps/server/cli binary so generated server outputs do not leak into commits. Also update the release workflow to check the published scoped package name for @openwork/server before deciding whether npm publish is needed. * fix(workspace): add stable CLI bin wrappers Point the server and router package bins at committed wrapper scripts so workspace installs can create shims before dist outputs exist. Keep the wrappers compatible with built binaries and source checkouts to avoid Vercel install warnings without changing runtime behavior.
987 lines
37 KiB
YAML
987 lines
37 KiB
YAML
name: Release App
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*"
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: "Tag to release (e.g., v0.1.2). Leave empty to use current ref."
|
|
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"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
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: false
|
|
build_tauri:
|
|
description: "Build desktop (Tauri) artifacts"
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
publish_sidecars:
|
|
description: "Build + upload openwork-orchestrator sidecar release assets"
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
publish_npm:
|
|
description: "Publish openwork-orchestrator/openwork-server/opencode-router to npm if versions changed"
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
|
|
permissions:
|
|
contents: write
|
|
pull-requests: write
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
resolve-release:
|
|
name: Resolve Release Metadata
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
outputs:
|
|
release_tag: ${{ steps.resolve.outputs.release_tag }}
|
|
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_tauri: ${{ steps.resolve.outputs.build_tauri }}
|
|
publish_sidecars: ${{ steps.resolve.outputs.publish_sidecars }}
|
|
publish_npm: ${{ steps.resolve.outputs.publish_npm }}
|
|
steps:
|
|
- name: Resolve metadata
|
|
id: resolve
|
|
shell: bash
|
|
env:
|
|
INPUT_TAG: ${{ github.event.inputs.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_TAURI: ${{ github.event.inputs.build_tauri }}
|
|
INPUT_PUBLISH_SIDECARS: ${{ github.event.inputs.publish_sidecars }}
|
|
INPUT_PUBLISH_NPM: ${{ github.event.inputs.publish_npm }}
|
|
DEFAULT_PUBLISH_SIDECARS: ${{ vars.RELEASE_PUBLISH_SIDECARS }}
|
|
DEFAULT_PUBLISH_NPM: ${{ vars.RELEASE_PUBLISH_NPM }}
|
|
DEFAULT_NOTARIZE: ${{ vars.MACOS_NOTARIZE }}
|
|
DEFAULT_BUILD_TAURI: ${{ vars.RELEASE_BUILD_TAURI }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
TAG_INPUT="${INPUT_TAG:-}"
|
|
if [ -n "$TAG_INPUT" ]; then
|
|
if [[ "$TAG_INPUT" == v* ]]; then
|
|
TAG="$TAG_INPUT"
|
|
else
|
|
TAG="v$TAG_INPUT"
|
|
fi
|
|
else
|
|
TAG="${GITHUB_REF_NAME}"
|
|
fi
|
|
|
|
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
|
|
echo "Invalid release tag: $TAG (expected 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
|
|
RELEASE_BODY="See the assets to download this version and install."
|
|
fi
|
|
|
|
draft="${INPUT_DRAFT:-}"
|
|
if [ -z "$draft" ]; then
|
|
if [ "${GITHUB_EVENT_NAME}" = "push" ]; then
|
|
# Keep tag-triggered releases out of /releases/latest until assets + latest.json are ready.
|
|
draft="true"
|
|
else
|
|
draft="false"
|
|
fi
|
|
fi
|
|
prerelease="${INPUT_PRERELEASE:-false}"
|
|
notarize="${INPUT_NOTARIZE:-}"
|
|
if [ -z "$notarize" ]; then
|
|
notarize="${DEFAULT_NOTARIZE:-true}"
|
|
fi
|
|
|
|
build_tauri="${INPUT_BUILD_TAURI:-}"
|
|
if [ -z "$build_tauri" ]; then
|
|
build_tauri="${DEFAULT_BUILD_TAURI:-true}"
|
|
fi
|
|
|
|
publish_sidecars="${INPUT_PUBLISH_SIDECARS:-}"
|
|
if [ -z "$publish_sidecars" ]; then
|
|
publish_sidecars="${DEFAULT_PUBLISH_SIDECARS:-true}"
|
|
fi
|
|
publish_npm="${INPUT_PUBLISH_NPM:-}"
|
|
if [ -z "$publish_npm" ]; then
|
|
publish_npm="${DEFAULT_PUBLISH_NPM:-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_name=$RELEASE_NAME" >> "$GITHUB_OUTPUT"
|
|
echo "draft=$draft" >> "$GITHUB_OUTPUT"
|
|
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
|
|
echo "notarize=$notarize" >> "$GITHUB_OUTPUT"
|
|
echo "build_tauri=$build_tauri" >> "$GITHUB_OUTPUT"
|
|
echo "publish_sidecars=$publish_sidecars" >> "$GITHUB_OUTPUT"
|
|
echo "publish_npm=$publish_npm" >> "$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
|
|
|
|
DRAFT_FLAG=""
|
|
PRERELEASE_FLAG=""
|
|
if [ "${{ steps.resolve.outputs.draft }}" = "true" ]; then
|
|
DRAFT_FLAG="--draft"
|
|
fi
|
|
if [ "${{ steps.resolve.outputs.prerelease }}" = "true" ]; then
|
|
PRERELEASE_FLAG="--prerelease"
|
|
fi
|
|
|
|
gh release create "${{ steps.resolve.outputs.release_tag }}" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--title "${{ steps.resolve.outputs.release_name }}" \
|
|
--notes-file "$BODY_FILE" \
|
|
$DRAFT_FLAG $PRERELEASE_FLAG
|
|
|
|
verify-release:
|
|
name: Verify Release Versions
|
|
needs: resolve-release
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
env:
|
|
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ env.RELEASE_TAG }}
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
|
|
- name: Verify tag matches app versions
|
|
run: node scripts/release/verify-tag.mjs --tag "$RELEASE_TAG"
|
|
|
|
- name: Release review (strict)
|
|
run: node scripts/release/review.mjs --strict
|
|
|
|
publish-tauri:
|
|
name: Build + Publish (${{ matrix.target }})
|
|
needs: [resolve-release, verify-release]
|
|
if: needs.resolve-release.outputs.build_tauri == 'true'
|
|
# Set OPENWORK_LINUX_X64_RUNNER_LABEL to route only the Linux x86_64 build to a larger runner.
|
|
runs-on: ${{ matrix.target == 'x86_64-unknown-linux-gnu' && vars.OPENWORK_LINUX_X64_RUNNER_LABEL != '' && vars.OPENWORK_LINUX_X64_RUNNER_LABEL || matrix.platform }}
|
|
timeout-minutes: 360
|
|
env:
|
|
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
|
|
RELEASE_NAME: ${{ needs.resolve-release.outputs.release_name }}
|
|
RELEASE_BODY: ${{ needs.resolve-release.outputs.release_body }}
|
|
RELEASE_DRAFT: ${{ needs.resolve-release.outputs.draft }}
|
|
RELEASE_PRERELEASE: ${{ needs.resolve-release.outputs.prerelease }}
|
|
MACOS_NOTARIZE: ${{ needs.resolve-release.outputs.notarize }}
|
|
# Ensure Tauri's beforeBuildCommand (prepare:sidecar) uses our fork.
|
|
OPENCODE_GITHUB_REPO: ${{ vars.OPENCODE_GITHUB_REPO || 'anomalyco/opencode' }}
|
|
OPENCODE_VERSION: ${{ vars.OPENCODE_VERSION || '1.2.20' }}
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- platform: macos-14
|
|
os_type: macos
|
|
target: aarch64-apple-darwin
|
|
args: "--target aarch64-apple-darwin --bundles dmg,app"
|
|
- platform: macos-14
|
|
os_type: macos
|
|
target: x86_64-apple-darwin
|
|
args: "--target x86_64-apple-darwin --bundles dmg,app"
|
|
- platform: ubuntu-22.04
|
|
os_type: linux
|
|
target: x86_64-unknown-linux-gnu
|
|
args: "--target x86_64-unknown-linux-gnu --bundles deb,rpm"
|
|
- platform: ubuntu-22.04-arm
|
|
os_type: linux
|
|
target: aarch64-unknown-linux-gnu
|
|
args: "--target aarch64-unknown-linux-gnu --bundles deb,rpm"
|
|
- platform: windows-2022
|
|
os_type: windows
|
|
target: x86_64-pc-windows-msvc
|
|
args: "--target x86_64-pc-windows-msvc --bundles msi"
|
|
|
|
steps:
|
|
- name: Log runner selection
|
|
shell: bash
|
|
run: |
|
|
echo "Requested larger runner label: ${RUNNER_LABEL:-<unset>}"
|
|
echo "Effective runs-on: ${EFFECTIVE_RUNS_ON}"
|
|
env:
|
|
RUNNER_LABEL: ${{ vars.OPENWORK_LINUX_X64_RUNNER_LABEL }}
|
|
EFFECTIVE_RUNS_ON: ${{ matrix.target == 'x86_64-unknown-linux-gnu' && vars.OPENWORK_LINUX_X64_RUNNER_LABEL != '' && vars.OPENWORK_LINUX_X64_RUNNER_LABEL || matrix.platform }}
|
|
|
|
- name: Enable git long paths (Windows)
|
|
if: matrix.os_type == 'windows'
|
|
shell: pwsh
|
|
run: git config --global core.longpaths true
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ env.RELEASE_TAG }}
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v4
|
|
with:
|
|
version: 10.27.0
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v1
|
|
with:
|
|
bun-version: "1.3.6"
|
|
|
|
- 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@v4
|
|
continue-on-error: true
|
|
with:
|
|
path: ${{ steps.pnpm-store.outputs.path }}
|
|
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-pnpm-
|
|
|
|
- name: Cache cargo
|
|
uses: actions/cache@v4
|
|
continue-on-error: true
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
apps/desktop/src-tauri/target
|
|
key: ${{ runner.os }}-cargo-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-cargo-
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile --prefer-offline
|
|
|
|
- name: Install OpenTUI x64 core (macOS x86_64)
|
|
if: matrix.os_type == 'macos' && matrix.target == 'x86_64-apple-darwin'
|
|
run: pnpm add -w --ignore-workspace-root-check @opentui/core-darwin-x64@0.1.77
|
|
|
|
- name: Install Linux build dependencies
|
|
if: matrix.os_type == 'linux'
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
libgtk-3-dev \
|
|
libglib2.0-dev \
|
|
libayatana-appindicator3-dev \
|
|
libsoup-3.0-dev \
|
|
libwebkit2gtk-4.1-dev \
|
|
libssl-dev \
|
|
rpm \
|
|
libdbus-1-dev \
|
|
librsvg2-dev
|
|
|
|
- name: Setup Rust
|
|
uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
|
|
- name: Resolve OpenCode version
|
|
id: opencode-version
|
|
shell: bash
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
OPENCODE_GITHUB_REPO: ${{ vars.OPENCODE_GITHUB_REPO || 'anomalyco/opencode' }}
|
|
OPENCODE_VERSION: ${{ vars.OPENCODE_VERSION || '1.2.20' }}
|
|
run: |
|
|
node <<'NODE' >> "$GITHUB_OUTPUT"
|
|
const fs = require('fs');
|
|
const repo = (process.env.OPENCODE_GITHUB_REPO || 'anomalyco/opencode').trim() || 'anomalyco/opencode';
|
|
|
|
async function resolveLatest() {
|
|
const token = (process.env.GITHUB_TOKEN || '').trim();
|
|
const headers = {
|
|
'Accept': 'application/vnd.github+json',
|
|
'X-GitHub-Api-Version': '2022-11-28',
|
|
'User-Agent': 'openwork-ci',
|
|
};
|
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
|
|
// Prefer API, but fall back to the web "latest" redirect if rate-limited (403) or otherwise blocked.
|
|
try {
|
|
const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, { headers });
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
const tag = (typeof data.tag_name === 'string' ? data.tag_name : '').trim();
|
|
let v = tag.startsWith('v') ? tag.slice(1) : tag;
|
|
v = v.trim();
|
|
if (v) return v;
|
|
}
|
|
if (res.status !== 403) {
|
|
throw new Error(`Failed to resolve latest OpenCode version (HTTP ${res.status})`);
|
|
}
|
|
} catch {
|
|
// continue to fallback
|
|
}
|
|
|
|
const web = await fetch(`https://github.com/${repo}/releases/latest`, {
|
|
headers: { 'User-Agent': 'openwork-ci' },
|
|
redirect: 'follow',
|
|
});
|
|
const url = (web && web.url) ? String(web.url) : '';
|
|
const match = url.match(/\/tag\/v([^/?#]+)/);
|
|
if (!match) throw new Error('Failed to resolve latest OpenCode version (web redirect).');
|
|
return match[1];
|
|
}
|
|
|
|
async function main() {
|
|
const pkg = JSON.parse(fs.readFileSync('./apps/desktop/package.json', 'utf8'));
|
|
const configuredRaw = (process.env.OPENCODE_VERSION || pkg.opencodeVersion || '').toString().trim();
|
|
if (configuredRaw && configuredRaw.toLowerCase() !== 'latest') {
|
|
const normalized = configuredRaw.startsWith('v') ? configuredRaw.slice(1) : configuredRaw;
|
|
const resolved = normalized.trim();
|
|
if (process.env.GITHUB_ENV) {
|
|
fs.appendFileSync(process.env.GITHUB_ENV, `OPENCODE_VERSION=${resolved}\n`);
|
|
}
|
|
console.log('version=' + resolved);
|
|
return;
|
|
}
|
|
const latest = await resolveLatest();
|
|
if (process.env.GITHUB_ENV) {
|
|
fs.appendFileSync(process.env.GITHUB_ENV, `OPENCODE_VERSION=${latest}\n`);
|
|
}
|
|
console.log('version=' + latest);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|
|
NODE
|
|
|
|
- name: Download OpenCode sidecar
|
|
shell: bash
|
|
env:
|
|
OPENCODE_VERSION: ${{ steps.opencode-version.outputs.version }}
|
|
OPENCODE_GITHUB_REPO: ${{ vars.OPENCODE_GITHUB_REPO || 'anomalyco/opencode' }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
case "${{ matrix.target }}" in
|
|
aarch64-apple-darwin)
|
|
opencode_asset="opencode-darwin-arm64.zip"
|
|
;;
|
|
x86_64-apple-darwin)
|
|
opencode_asset="opencode-darwin-x64-baseline.zip"
|
|
;;
|
|
x86_64-unknown-linux-gnu)
|
|
opencode_asset="opencode-linux-x64-baseline.tar.gz"
|
|
;;
|
|
aarch64-unknown-linux-gnu)
|
|
opencode_asset="opencode-linux-arm64.tar.gz"
|
|
;;
|
|
x86_64-pc-windows-msvc)
|
|
opencode_asset="opencode-windows-x64-baseline.zip"
|
|
;;
|
|
*)
|
|
echo "Unsupported target: ${{ matrix.target }}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
repo="${OPENCODE_GITHUB_REPO:-anomalyco/opencode}"
|
|
url="https://github.com/${repo}/releases/download/v${OPENCODE_VERSION}/${opencode_asset}"
|
|
tmp_dir="$RUNNER_TEMP/opencode"
|
|
extract_dir="$tmp_dir/extracted"
|
|
rm -rf "$tmp_dir"
|
|
mkdir -p "$extract_dir"
|
|
curl -fsSL --retry 5 --retry-all-errors --retry-delay 2 -o "$tmp_dir/$opencode_asset" "$url"
|
|
|
|
if [[ "$opencode_asset" == *.tar.gz ]]; then
|
|
tar -xzf "$tmp_dir/$opencode_asset" -C "$extract_dir"
|
|
else
|
|
if command -v unzip >/dev/null 2>&1; then
|
|
unzip -q "$tmp_dir/$opencode_asset" -d "$extract_dir"
|
|
elif command -v 7z >/dev/null 2>&1; then
|
|
7z x "$tmp_dir/$opencode_asset" -o"$extract_dir" >/dev/null
|
|
else
|
|
echo "No unzip utility available" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ -f "$extract_dir/opencode" ]; then
|
|
bin_path="$extract_dir/opencode"
|
|
elif [ -f "$extract_dir/opencode.exe" ]; then
|
|
bin_path="$extract_dir/opencode.exe"
|
|
else
|
|
echo "OpenCode binary not found in archive" >&2
|
|
ls -la "$extract_dir"
|
|
exit 1
|
|
fi
|
|
|
|
target_name="opencode-${{ matrix.target }}"
|
|
if [ "${{ matrix.os_type }}" = "windows" ]; then
|
|
target_name="${target_name}.exe"
|
|
fi
|
|
|
|
mkdir -p apps/desktop/src-tauri/sidecars
|
|
cp "$bin_path" "apps/desktop/src-tauri/sidecars/${target_name}"
|
|
chmod 755 "apps/desktop/src-tauri/sidecars/${target_name}"
|
|
|
|
- name: Write notary API key
|
|
if: matrix.os_type == 'macos' && env.MACOS_NOTARIZE == 'true'
|
|
env:
|
|
APPLE_NOTARY_API_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_API_KEY_P8_BASE64 }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
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: Build + upload (notarized)
|
|
if: matrix.os_type == 'macos' && env.MACOS_NOTARIZE == 'true'
|
|
uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
|
|
env:
|
|
CI: true
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
# Tauri updater signing
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
|
|
# macOS signing
|
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CODESIGN_CERT_P12_BASE64 }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CODESIGN_CERT_PASSWORD }}
|
|
|
|
# macOS notarization (App Store Connect API key)
|
|
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 }}
|
|
with:
|
|
tagName: ${{ env.RELEASE_TAG }}
|
|
releaseName: ${{ env.RELEASE_NAME }}
|
|
releaseBody: ${{ env.RELEASE_BODY }}
|
|
releaseDraft: ${{ env.RELEASE_DRAFT == 'true' }}
|
|
prerelease: ${{ env.RELEASE_PRERELEASE == 'true' }}
|
|
projectPath: apps/desktop
|
|
tauriScript: pnpm exec tauri -vvv
|
|
args: ${{ matrix.args }}
|
|
retryAttempts: 3
|
|
uploadUpdaterJson: false
|
|
updaterJsonPreferNsis: true
|
|
releaseAssetNamePattern: openwork-desktop-[platform]-[arch][ext]
|
|
|
|
- name: Build + upload
|
|
if: matrix.os_type != 'macos' || env.MACOS_NOTARIZE != 'true'
|
|
uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
|
|
env:
|
|
CI: true
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
# Tauri updater signing
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
|
|
# macOS signing
|
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CODESIGN_CERT_P12_BASE64 }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CODESIGN_CERT_PASSWORD }}
|
|
with:
|
|
tagName: ${{ env.RELEASE_TAG }}
|
|
releaseName: ${{ env.RELEASE_NAME }}
|
|
releaseBody: ${{ env.RELEASE_BODY }}
|
|
releaseDraft: ${{ env.RELEASE_DRAFT == 'true' }}
|
|
prerelease: ${{ env.RELEASE_PRERELEASE == 'true' }}
|
|
projectPath: apps/desktop
|
|
tauriScript: pnpm exec tauri -vvv
|
|
args: ${{ matrix.args }}
|
|
retryAttempts: 3
|
|
uploadUpdaterJson: false
|
|
updaterJsonPreferNsis: true
|
|
releaseAssetNamePattern: openwork-desktop-[platform]-[arch][ext]
|
|
|
|
- name: Verify versions.json bundled (macOS)
|
|
if: success() && matrix.os_type == 'macos'
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
case "${{ matrix.target }}" in
|
|
aarch64-apple-darwin)
|
|
asset_arch="aarch64"
|
|
;;
|
|
x86_64-apple-darwin)
|
|
asset_arch="x64"
|
|
;;
|
|
*)
|
|
echo "Unexpected target for macOS verify: ${{ matrix.target }}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
tmp_dir="$RUNNER_TEMP/openwork-bundle-verify"
|
|
rm -rf "$tmp_dir"
|
|
mkdir -p "$tmp_dir"
|
|
|
|
gh release download "${RELEASE_TAG}" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--pattern "openwork-desktop-darwin-${asset_arch}.app.tar.gz" \
|
|
--dir "$tmp_dir"
|
|
|
|
tar -xzf "$tmp_dir/openwork-desktop-darwin-${asset_arch}.app.tar.gz" -C "$tmp_dir"
|
|
|
|
app_path="$tmp_dir/OpenWork.app"
|
|
manifest_path="$app_path/Contents/MacOS/versions.json"
|
|
|
|
if [ ! -f "$manifest_path" ]; then
|
|
echo "ERROR: versions.json missing from app bundle: $manifest_path" >&2
|
|
echo "Hint: ensure apps/desktop/src-tauri/tauri.conf.json bundles sidecars/versions.json" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Found bundled versions.json at $manifest_path"
|
|
|
|
publish-updater-json:
|
|
name: Publish consolidated latest.json
|
|
needs: [resolve-release, verify-release, publish-tauri]
|
|
if: needs.resolve-release.outputs.build_tauri == 'true'
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
env:
|
|
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ env.RELEASE_TAG }}
|
|
fetch-depth: 0
|
|
|
|
- name: Generate latest.json from release assets
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
node scripts/release/generate-latest-json.mjs \
|
|
--tag "$RELEASE_TAG" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--output "$RUNNER_TEMP/latest.json"
|
|
|
|
- name: Upload latest.json
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh release upload "$RELEASE_TAG" "$RUNNER_TEMP/latest.json#latest.json" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--clobber
|
|
|
|
release-orchestrator-sidecars:
|
|
name: Build + Upload openwork-orchestrator Sidecars
|
|
needs: [resolve-release, verify-release]
|
|
if: needs.resolve-release.outputs.publish_sidecars == 'true'
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
env:
|
|
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ env.RELEASE_TAG }}
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v4
|
|
with:
|
|
version: 10.27.0
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v1
|
|
with:
|
|
bun-version: "1.3.6"
|
|
|
|
- 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@v4
|
|
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
|
|
|
|
- name: Resolve sidecar versions
|
|
id: sidecar-versions
|
|
shell: bash
|
|
run: |
|
|
node -e "const fs=require('fs'); const orchestrator=JSON.parse(fs.readFileSync('apps/orchestrator/package.json','utf8')); const server=JSON.parse(fs.readFileSync('apps/server/package.json','utf8')); const opencodeRouter=JSON.parse(fs.readFileSync('apps/opencode-router/package.json','utf8')); console.log('orchestrator=' + orchestrator.version); console.log('server=' + server.version); console.log('opencodeRouter=' + opencodeRouter.version);" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Resolve SOURCE_DATE_EPOCH
|
|
id: source-date
|
|
shell: bash
|
|
run: |
|
|
epoch=$(git show -s --format=%ct "${RELEASE_TAG}")
|
|
echo "epoch=$epoch" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Check openwork-orchestrator release
|
|
id: orchestrator-release
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
tag="openwork-orchestrator-v${{ steps.sidecar-versions.outputs.orchestrator }}"
|
|
if gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
|
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Build sidecar artifacts
|
|
env:
|
|
SOURCE_DATE_EPOCH: ${{ steps.source-date.outputs.epoch }}
|
|
run: pnpm --filter openwork-orchestrator build:sidecars
|
|
|
|
- name: Release review (strict)
|
|
env:
|
|
SOURCE_DATE_EPOCH: ${{ steps.source-date.outputs.epoch }}
|
|
run: node scripts/release/review.mjs --strict
|
|
|
|
- name: Create openwork-orchestrator release
|
|
if: steps.orchestrator-release.outputs.exists != 'true'
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
version="${{ steps.sidecar-versions.outputs.orchestrator }}"
|
|
tag="openwork-orchestrator-v${version}"
|
|
notes="Sidecar bundle for openwork-orchestrator v${version}.\n\nopenwork-server: ${{ steps.sidecar-versions.outputs.server }}\nopencodeRouter: ${{ steps.sidecar-versions.outputs.opencodeRouter }}"
|
|
gh release create "$tag" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--title "openwork-orchestrator v${version}" \
|
|
--notes "$notes" \
|
|
--latest=false
|
|
|
|
- name: Upload sidecar assets
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
tag="openwork-orchestrator-v${{ steps.sidecar-versions.outputs.orchestrator }}"
|
|
gh release upload "$tag" apps/orchestrator/dist/sidecars/* --repo "$GITHUB_REPOSITORY" --clobber
|
|
|
|
publish-npm:
|
|
name: Publish npm packages
|
|
needs: [resolve-release, verify-release, release-orchestrator-sidecars]
|
|
if: |
|
|
always() &&
|
|
needs.resolve-release.result == 'success' &&
|
|
needs.verify-release.result == 'success' &&
|
|
(needs.release-orchestrator-sidecars.result == 'success' || needs.release-orchestrator-sidecars.result == 'skipped') &&
|
|
needs.resolve-release.outputs.publish_npm == 'true'
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
env:
|
|
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ env.RELEASE_TAG }}
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v4
|
|
with:
|
|
version: 10.27.0
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v1
|
|
with:
|
|
bun-version: "1.3.6"
|
|
|
|
- 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@v4
|
|
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
|
|
|
|
- name: Resolve package versions
|
|
id: package-versions
|
|
shell: bash
|
|
run: |
|
|
node -e "const fs=require('fs'); const orchestrator=JSON.parse(fs.readFileSync('apps/orchestrator/package.json','utf8')); const server=JSON.parse(fs.readFileSync('apps/server/package.json','utf8')); const opencodeRouter=JSON.parse(fs.readFileSync('apps/opencode-router/package.json','utf8')); console.log('orchestrator=' + orchestrator.version); console.log('server=' + server.version); console.log('opencodeRouter=' + opencodeRouter.version);" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Check npm versions
|
|
id: npm-versions
|
|
shell: bash
|
|
env:
|
|
ORCHESTRATOR_VERSION: ${{ steps.package-versions.outputs.orchestrator }}
|
|
SERVER_VERSION: ${{ steps.package-versions.outputs.server }}
|
|
OPENCODE_ROUTER_VERSION: ${{ steps.package-versions.outputs.opencodeRouter }}
|
|
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.
|
|
orchestrator_current="$(npm view openwork-orchestrator version 2>/dev/null || true)"
|
|
server_current="$(npm view @openwork/server version 2>/dev/null || true)"
|
|
opencodeRouter_current="$(npm view opencode-router version 2>/dev/null || true)"
|
|
|
|
if [ "$orchestrator_current" = "$ORCHESTRATOR_VERSION" ]; then
|
|
echo "publish_orchestrator=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "publish_orchestrator=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
if [ "$server_current" = "$SERVER_VERSION" ]; then
|
|
echo "publish_server=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "publish_server=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
if [ "$opencodeRouter_current" = "$OPENCODE_ROUTER_VERSION" ]; then
|
|
echo "publish_opencodeRouter=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "publish_opencodeRouter=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
publish_any=false
|
|
if [ "$orchestrator_current" != "$ORCHESTRATOR_VERSION" ] || [ "$server_current" != "$SERVER_VERSION" ] || [ "$opencodeRouter_current" != "$OPENCODE_ROUTER_VERSION" ]; then
|
|
publish_any=true
|
|
fi
|
|
echo "publish_any=$publish_any" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Ensure npm auth
|
|
id: npm-auth
|
|
shell: bash
|
|
env:
|
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
PUBLISH_ANY: ${{ steps.npm-versions.outputs.publish_any }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [ "${PUBLISH_ANY}" != "true" ]; then
|
|
echo "enabled=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
if [ -z "${NPM_TOKEN:-}" ]; then
|
|
echo "NPM_TOKEN not set; skipping npm publish."
|
|
echo "enabled=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
npm config set //registry.npmjs.org/:_authToken "$NPM_TOKEN"
|
|
echo "enabled=true" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Publish openwork-server
|
|
if: steps.npm-auth.outputs.enabled == 'true' && steps.npm-versions.outputs.publish_server == 'true'
|
|
run: pnpm --filter @openwork/server publish --access public --no-git-checks
|
|
|
|
- name: Publish opencode-router
|
|
if: steps.npm-auth.outputs.enabled == 'true' && steps.npm-versions.outputs.publish_opencodeRouter == 'true'
|
|
run: pnpm --filter opencode-router publish --access public --no-git-checks
|
|
|
|
- name: Publish openwork-orchestrator
|
|
if: steps.npm-auth.outputs.enabled == 'true' && steps.npm-versions.outputs.publish_orchestrator == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
ORCHESTRATOR_VERSION: ${{ steps.package-versions.outputs.orchestrator }}
|
|
run: |
|
|
set -euo pipefail
|
|
tag="openwork-orchestrator-v${ORCHESTRATOR_VERSION}"
|
|
if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
|
echo "openwork-orchestrator sidecar release $tag not found. Publish sidecars before openwork-orchestrator." >&2
|
|
exit 1
|
|
fi
|
|
pnpm --filter openwork-orchestrator build:bin:all
|
|
node apps/orchestrator/scripts/publish-npm.mjs
|
|
|
|
aur-publish:
|
|
name: Publish AUR
|
|
needs: [resolve-release, publish-tauri, publish-release]
|
|
if: |
|
|
always() &&
|
|
needs.resolve-release.result == 'success' &&
|
|
(needs.publish-tauri.result == 'success' || needs.publish-tauri.result == 'skipped') &&
|
|
(needs.publish-release.result == 'success' || needs.publish-release.result == 'skipped')
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
permissions:
|
|
contents: write
|
|
env:
|
|
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
|
|
steps:
|
|
- name: Checkout dev
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: dev
|
|
fetch-depth: 0
|
|
|
|
- name: Update AUR packaging files
|
|
run: scripts/aur/update-aur.sh "$RELEASE_TAG"
|
|
|
|
- name: Commit packaging update to dev
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if ! git status --porcelain -- packaging/aur/PKGBUILD packaging/aur/.SRCINFO | grep -q .; then
|
|
echo "AUR packaging already up to date in dev."
|
|
exit 0
|
|
fi
|
|
|
|
version="${RELEASE_TAG#v}"
|
|
git add packaging/aur/PKGBUILD packaging/aur/.SRCINFO
|
|
git -c user.name="OpenWork Release Bot" \
|
|
-c user.email="release-bot@users.noreply.github.com" \
|
|
commit -m "chore(aur): update PKGBUILD for ${version}"
|
|
git push origin HEAD:dev
|
|
|
|
- name: Publish to AUR
|
|
env:
|
|
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
|
AUR_REPO: ${{ vars.AUR_REPO || 'openwork' }}
|
|
AUR_SKIP_UPDATE: "1"
|
|
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"
|
|
|
|
publish-release:
|
|
name: Publish GitHub Release
|
|
needs:
|
|
- resolve-release
|
|
- verify-release
|
|
- publish-tauri
|
|
- publish-updater-json
|
|
- release-orchestrator-sidecars
|
|
- publish-npm
|
|
if: |
|
|
always() &&
|
|
needs.resolve-release.outputs.draft == 'true' &&
|
|
needs.resolve-release.result == 'success' &&
|
|
needs.verify-release.result == 'success' &&
|
|
(needs.publish-tauri.result == 'success' || needs.publish-tauri.result == 'skipped') &&
|
|
(needs.publish-updater-json.result == 'success' || needs.publish-updater-json.result == 'skipped') &&
|
|
(needs.release-orchestrator-sidecars.result == 'success' || needs.release-orchestrator-sidecars.result == 'skipped') &&
|
|
(needs.publish-npm.result == 'success' || needs.publish-npm.result == 'skipped')
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
env:
|
|
RELEASE_TAG: ${{ needs.resolve-release.outputs.release_tag }}
|
|
RELEASE_PRERELEASE: ${{ needs.resolve-release.outputs.prerelease }}
|
|
steps:
|
|
- name: Publish release after assets are ready
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
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
|