## Summary - package `packages/server` as a standalone desktop executable so Electron and Tauri no longer depend on a system-installed Node runtime in production - align Electron and Tauri startup logic around launching the packaged server, resolving binaries from the user shell, and bundling the same server resources into both desktop apps - replace the workspace instance proxy path that used `@fastify/reply-from` with a direct streaming proxy so packaged standalone builds can talk to spawned `opencode` instances correctly ## Why Desktop production builds were still depending on a user-provided Node runtime to launch `packages/server`, which made packaging less self-contained and created different behavior across machines. While moving to a standalone server executable, we also found that Bun-compiled standalone builds could start `opencode` successfully but failed when proxying requests to those instances through `reply-from`. The goal of this change is to make desktop production startup self-contained, keep Electron and Tauri behavior aligned, and restore correct communication with local `opencode` instances in packaged builds. ## What Changed - added a standalone build path for `packages/server` and bundle `codenomad-server` into desktop resources - updated Electron production startup to resolve and launch the standalone server executable - updated Tauri production startup to resolve and launch the standalone server executable with matching cwd and shell behavior - added runtime path helpers so the packaged server can reliably find its bundled UI, auth templates, config template, and package metadata - improved bare binary resolution so commands like `opencode` can be resolved from the user's login shell environment - upgraded the server stack to newer Fastify-compatible packages needed for the standalone/runtime work - replaced the workspace instance proxy implementation with a direct streaming proxy for requests to spawned `opencode` instances - updated Electron and Tauri build/prebuild scripts to generate and package the standalone server, while also repairing missing platform-specific optional binaries during packaging ## Benefits - desktop production builds no longer require Node to be installed on the user's system - Electron and Tauri now use the same packaged server model in production, reducing platform drift - packaged desktop apps can successfully create workspaces, launch `opencode`, and proxy health/session traffic to those instances - the server bundle is more self-contained and resilient to different launch environments - desktop packaging is more predictable because the required server executable is built and bundled as part of the app build flow
861 lines
30 KiB
YAML
861 lines
30 KiB
YAML
name: Build and Upload Binaries
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
ref:
|
|
description: "Git ref (branch, tag, or SHA) to build from"
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
version:
|
|
description: "Version to apply to workspace packages (release builds)"
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
tag:
|
|
description: "Git tag to upload assets to (release builds)"
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
release_name:
|
|
description: "Release name (unused here, for context)"
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
upload:
|
|
description: "Upload built artifacts to the GitHub release"
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
upload_actions_artifacts:
|
|
description: "Upload built artifacts to GitHub Actions run artifacts"
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
actions_artifacts_retention_days:
|
|
description: "Retention (days) for GitHub Actions artifacts"
|
|
required: false
|
|
default: 7
|
|
type: number
|
|
actions_artifacts_name_prefix:
|
|
description: "Optional prefix for Actions artifact names"
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
set_versions:
|
|
description: "Run npm version to set workspace versions"
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
|
|
# Permissions are intentionally omitted here so callers can choose
|
|
# least-privilege (e.g. dev CI uses read-only; releases grant write).
|
|
|
|
env:
|
|
NODE_VERSION: 22
|
|
|
|
jobs:
|
|
build-macos:
|
|
runs-on: macos-15-intel
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VERSION: ${{ inputs.version }}
|
|
TAG: ${{ inputs.tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
|
|
- name: Set workspace versions
|
|
if: ${{ inputs.set_versions && inputs.version != '' }}
|
|
shell: bash
|
|
env:
|
|
NPM_CONFIG_FETCH_RETRIES: 5
|
|
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: 20000
|
|
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: 120000
|
|
run: |
|
|
set -euo pipefail
|
|
for attempt in 1 2 3; do
|
|
if npm version "${VERSION}" --workspaces --include-workspace-root --no-git-tag-version --allow-same-version; then
|
|
exit 0
|
|
fi
|
|
echo "npm version failed (attempt $attempt/3); retrying..." >&2
|
|
sleep $((attempt * 10))
|
|
done
|
|
exit 1
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspaces --include=optional
|
|
|
|
- name: Ensure rollup native binary
|
|
run: npm install @rollup/rollup-darwin-x64 --no-save
|
|
|
|
- name: Build macOS binaries (Electron)
|
|
run: npm run build:mac --workspace @neuralnomads/codenomad-electron-app
|
|
|
|
- name: Ad-hoc sign Electron macOS app bundles (seal resources)
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
release_root="packages/electron-app/release"
|
|
apps=()
|
|
while IFS= read -r -d '' app; do
|
|
apps+=("$app")
|
|
done < <(find "$release_root" -type d -name 'CodeNomad.app' -print0)
|
|
|
|
if [ "${#apps[@]}" -eq 0 ]; then
|
|
echo "No CodeNomad.app found under $release_root" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# GitHub macOS runners typically have no signing identity. Without any signature,
|
|
# the shipped .app can fail Gatekeeper with:
|
|
# code has no resources but signature indicates they must be present
|
|
# Ad-hoc signing seals bundle resources and makes the signature internally consistent.
|
|
if security find-identity -p codesigning -v | grep -q "0 valid identities found"; then
|
|
echo "No valid macOS codesigning identity found; applying ad-hoc signature"
|
|
for app in "${apps[@]}"; do
|
|
echo "codesign (adhoc): $app"
|
|
codesign --force --deep --sign - "$app"
|
|
codesign --verify --deep --strict --verbose=2 "$app"
|
|
done
|
|
else
|
|
echo "macOS codesigning identity present; skipping ad-hoc signing"
|
|
fi
|
|
|
|
- name: Repackage Electron macOS zips (ditto)
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
# Prefer the workflow-provided version; fall back to package.json.
|
|
VERSION_TO_USE="${VERSION:-}"
|
|
if [ -z "$VERSION_TO_USE" ]; then
|
|
VERSION_TO_USE=$(node -p "require('./packages/electron-app/package.json').version")
|
|
fi
|
|
|
|
release_root="packages/electron-app/release"
|
|
# macOS GitHub runners ship /bin/bash 3.2 which doesn't support `shopt -s globstar`.
|
|
# Use find to locate built app bundles instead of ** globs.
|
|
apps=()
|
|
while IFS= read -r -d '' app; do
|
|
apps+=("$app")
|
|
done < <(find "$release_root" -type d -name 'CodeNomad.app' -print0)
|
|
if [ "${#apps[@]}" -eq 0 ]; then
|
|
echo "No CodeNomad.app found under $release_root" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for app in "${apps[@]}"; do
|
|
bundle_dir=$(basename "$(dirname "$app")")
|
|
arch="x64"
|
|
if [[ "$bundle_dir" == *"arm64"* ]]; then
|
|
arch="arm64"
|
|
fi
|
|
|
|
out_zip="$release_root/CodeNomad-${VERSION_TO_USE}-mac-${arch}.zip"
|
|
rm -f "$out_zip"
|
|
echo "ditto -ck: $app -> $out_zip"
|
|
ditto -ck --sequesterRsrc --keepParent "$app" "$out_zip"
|
|
done
|
|
|
|
- name: Validate Electron macOS codesign (unzipped)
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
|
|
tmp_dir=$(mktemp -d)
|
|
trap 'rm -rf "$tmp_dir"' EXIT
|
|
|
|
zips=(packages/electron-app/release/CodeNomad-*-mac-*.zip)
|
|
if [ "${#zips[@]}" -eq 0 ]; then
|
|
echo "No Electron macOS zip artifacts found to validate" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for zip in "${zips[@]}"; do
|
|
echo "Validating codesign for: $zip"
|
|
extract_dir="$tmp_dir/$(basename "$zip" .zip)"
|
|
mkdir -p "$extract_dir"
|
|
|
|
# Use ditto for extraction as well to preserve bundle metadata.
|
|
ditto -x -k "$zip" "$extract_dir"
|
|
|
|
app_path=""
|
|
for candidate in "$extract_dir"/*.app "$extract_dir"/*/*.app; do
|
|
if [ -d "$candidate" ]; then
|
|
app_path="$candidate"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -z "$app_path" ]; then
|
|
echo "No .app found after extracting $zip" >&2
|
|
exit 1
|
|
fi
|
|
|
|
codesign --verify --deep --strict --verbose=2 "$app_path"
|
|
done
|
|
|
|
- name: Upload release assets
|
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
for file in packages/electron-app/release/*.zip packages/electron-app/release/*.AppImage; do
|
|
[ -f "$file" ] || continue
|
|
echo "Uploading $file"
|
|
gh release upload "$TAG" "$file" --clobber
|
|
done
|
|
|
|
- name: Upload Actions artifacts (Electron macOS)
|
|
if: ${{ inputs.upload_actions_artifacts }}
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ inputs.actions_artifacts_name_prefix }}electron-macos
|
|
path: packages/electron-app/release/*.zip
|
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
|
if-no-files-found: error
|
|
|
|
build-windows:
|
|
runs-on: windows-2025
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VERSION: ${{ inputs.version }}
|
|
TAG: ${{ inputs.tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
|
|
- name: Set workspace versions
|
|
if: ${{ inputs.set_versions && inputs.version != '' }}
|
|
run: npm version ${{ env.VERSION }} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version
|
|
shell: bash
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspaces --include=optional
|
|
|
|
- name: Ensure rollup native binary
|
|
run: npm install @rollup/rollup-win32-x64-msvc --no-save
|
|
|
|
- name: Build Windows binaries (Electron)
|
|
run: npm run build:win --workspace @neuralnomads/codenomad-electron-app
|
|
|
|
- name: Upload release assets
|
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
|
shell: pwsh
|
|
run: |
|
|
Get-ChildItem -Path "packages/electron-app/release" -Filter *.zip -File | ForEach-Object {
|
|
Write-Host "Uploading $($_.FullName)"
|
|
gh release upload $env:TAG $_.FullName --clobber
|
|
}
|
|
|
|
- name: Upload Actions artifacts (Electron Windows)
|
|
if: ${{ inputs.upload_actions_artifacts }}
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ inputs.actions_artifacts_name_prefix }}electron-windows
|
|
path: packages/electron-app/release/*.zip
|
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
|
if-no-files-found: error
|
|
|
|
build-linux:
|
|
runs-on: ubuntu-24.04
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VERSION: ${{ inputs.version }}
|
|
TAG: ${{ inputs.tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
|
|
- name: Set workspace versions
|
|
if: ${{ inputs.set_versions && inputs.version != '' }}
|
|
run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspaces --include=optional
|
|
|
|
- name: Ensure rollup native binary
|
|
run: npm install @rollup/rollup-linux-x64-gnu --no-save
|
|
|
|
- name: Build Linux binaries (Electron)
|
|
run: npm run build:linux --workspace @neuralnomads/codenomad-electron-app
|
|
|
|
- name: Upload release assets
|
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
for file in packages/electron-app/release/*.zip packages/electron-app/release/*.AppImage; do
|
|
[ -f "$file" ] || continue
|
|
echo "Uploading $file"
|
|
gh release upload "$TAG" "$file" --clobber
|
|
done
|
|
|
|
- name: Upload Actions artifacts (Electron Linux)
|
|
if: ${{ inputs.upload_actions_artifacts }}
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ inputs.actions_artifacts_name_prefix }}electron-linux
|
|
path: |
|
|
packages/electron-app/release/*.zip
|
|
packages/electron-app/release/*.AppImage
|
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
|
if-no-files-found: error
|
|
|
|
build-tauri-macos:
|
|
runs-on: macos-15-intel
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VERSION: ${{ inputs.version }}
|
|
TAG: ${{ inputs.tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
|
|
- name: Setup Rust (Tauri)
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- name: Set workspace versions
|
|
if: ${{ inputs.set_versions && inputs.version != '' }}
|
|
run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspaces --include=optional
|
|
|
|
- name: Ensure rollup native binary
|
|
run: npm install @rollup/rollup-darwin-x64 --no-save
|
|
|
|
- name: Prebuild (Tauri)
|
|
run: npm run prebuild --workspace @codenomad/tauri-app
|
|
|
|
- name: Ensure tauri native binary
|
|
working-directory: packages/tauri-app
|
|
run: |
|
|
set -euo pipefail
|
|
for attempt in 1 2 3; do
|
|
if [ "$attempt" -gt 1 ]; then
|
|
echo "Retrying Tauri CLI install (attempt $attempt)..."
|
|
fi
|
|
npm install @tauri-apps/cli@2.10.1 @tauri-apps/cli-darwin-x64@2.10.1 --no-save --no-audit --no-fund --workspaces=false
|
|
node -e "require('@tauri-apps/cli'); console.log('Tauri CLI loaded')" && exit 0
|
|
done
|
|
echo "Tauri CLI failed to load after retries" >&2
|
|
exit 1
|
|
|
|
- name: Build macOS bundle (Tauri)
|
|
working-directory: packages/tauri-app
|
|
run: npm exec -- tauri build
|
|
|
|
- name: Package Tauri artifacts (macOS)
|
|
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
|
|
run: |
|
|
set -euo pipefail
|
|
BUNDLE_ROOT="packages/tauri-app/target/release/bundle"
|
|
ARTIFACT_DIR="packages/tauri-app/release-tauri"
|
|
rm -rf "$ARTIFACT_DIR"
|
|
mkdir -p "$ARTIFACT_DIR"
|
|
if [ -d "$BUNDLE_ROOT/macos/CodeNomad.app" ]; then
|
|
ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-x64.zip"
|
|
fi
|
|
|
|
- name: Upload Actions artifacts (Tauri macOS)
|
|
if: ${{ inputs.upload_actions_artifacts }}
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ inputs.actions_artifacts_name_prefix }}tauri-macos
|
|
path: packages/tauri-app/release-tauri/*.zip
|
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
|
if-no-files-found: warn
|
|
|
|
- name: Upload Tauri release assets (macOS)
|
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
for file in packages/tauri-app/release-tauri/*.zip; do
|
|
[ -f "$file" ] || continue
|
|
echo "Uploading $file"
|
|
gh release upload "$TAG" "$file" --clobber
|
|
done
|
|
|
|
build-tauri-macos-arm64:
|
|
runs-on: macos-26
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VERSION: ${{ inputs.version }}
|
|
TAG: ${{ inputs.tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
|
|
- name: Setup Rust (Tauri)
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- name: Set workspace versions
|
|
if: ${{ inputs.set_versions && inputs.version != '' }}
|
|
run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspaces --include=optional
|
|
|
|
- name: Ensure rollup native binary
|
|
run: npm install @rollup/rollup-darwin-arm64 --no-save
|
|
|
|
- name: Prebuild (Tauri)
|
|
run: npm run prebuild --workspace @codenomad/tauri-app
|
|
|
|
- name: Ensure tauri native binary
|
|
working-directory: packages/tauri-app
|
|
run: |
|
|
set -euo pipefail
|
|
for attempt in 1 2 3; do
|
|
if [ "$attempt" -gt 1 ]; then
|
|
echo "Retrying Tauri CLI install (attempt $attempt)..."
|
|
fi
|
|
npm install @tauri-apps/cli@2.10.1 @tauri-apps/cli-darwin-arm64@2.10.1 --no-save --no-audit --no-fund --workspaces=false
|
|
node -e "require('@tauri-apps/cli'); console.log('Tauri CLI loaded')" && exit 0
|
|
done
|
|
echo "Tauri CLI failed to load after retries" >&2
|
|
exit 1
|
|
|
|
- name: Build macOS bundle (Tauri, arm64)
|
|
working-directory: packages/tauri-app
|
|
run: npm exec -- tauri build
|
|
|
|
- name: Package Tauri artifacts (macOS arm64)
|
|
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
|
|
run: |
|
|
set -euo pipefail
|
|
BUNDLE_ROOT="packages/tauri-app/target/release/bundle"
|
|
ARTIFACT_DIR="packages/tauri-app/release-tauri"
|
|
rm -rf "$ARTIFACT_DIR"
|
|
mkdir -p "$ARTIFACT_DIR"
|
|
if [ -d "$BUNDLE_ROOT/macos/CodeNomad.app" ]; then
|
|
ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-arm64.zip"
|
|
fi
|
|
|
|
- name: Upload Actions artifacts (Tauri macOS arm64)
|
|
if: ${{ inputs.upload_actions_artifacts }}
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ inputs.actions_artifacts_name_prefix }}tauri-macos-arm64
|
|
path: packages/tauri-app/release-tauri/*.zip
|
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
|
if-no-files-found: warn
|
|
|
|
- name: Upload Tauri release assets (macOS arm64)
|
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
for file in packages/tauri-app/release-tauri/*.zip; do
|
|
[ -f "$file" ] || continue
|
|
echo "Uploading $file"
|
|
gh release upload "$TAG" "$file" --clobber
|
|
done
|
|
|
|
build-tauri-windows:
|
|
runs-on: windows-2025
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VERSION: ${{ inputs.version }}
|
|
TAG: ${{ inputs.tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
|
|
- name: Setup Rust (Tauri)
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- name: Set workspace versions
|
|
if: ${{ inputs.set_versions && inputs.version != '' }}
|
|
run: npm version ${{ env.VERSION }} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version
|
|
shell: bash
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspaces --include=optional
|
|
|
|
- name: Ensure rollup native binary
|
|
run: npm install @rollup/rollup-win32-x64-msvc --no-save
|
|
|
|
- name: Prebuild (Tauri)
|
|
run: npm run prebuild --workspace @codenomad/tauri-app
|
|
|
|
- name: Ensure tauri native binary
|
|
shell: bash
|
|
working-directory: packages/tauri-app
|
|
run: |
|
|
set -euo pipefail
|
|
for attempt in 1 2 3; do
|
|
if [ "$attempt" -gt 1 ]; then
|
|
echo "Retrying Tauri CLI install (attempt $attempt)..."
|
|
fi
|
|
npm install @tauri-apps/cli@2.10.1 @tauri-apps/cli-win32-x64-msvc@2.10.1 --no-save --no-audit --no-fund --workspaces=false
|
|
node -e "require('@tauri-apps/cli'); console.log('Tauri CLI loaded')" && exit 0
|
|
done
|
|
echo "Tauri CLI failed to load after retries" >&2
|
|
exit 1
|
|
|
|
- name: Build Windows bundle (Tauri)
|
|
shell: bash
|
|
working-directory: packages/tauri-app
|
|
run: npm exec -- tauri build
|
|
|
|
- name: Package Tauri artifacts (Windows)
|
|
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
|
|
shell: pwsh
|
|
run: |
|
|
$bundleRoot = "packages/tauri-app/target/release/bundle"
|
|
$artifactDir = "packages/tauri-app/release-tauri"
|
|
if (Test-Path $artifactDir) { Remove-Item $artifactDir -Recurse -Force }
|
|
New-Item -ItemType Directory -Path $artifactDir | Out-Null
|
|
$exe = Get-ChildItem -Path $bundleRoot -Recurse -File -Filter *.exe | Select-Object -First 1
|
|
if ($null -ne $exe) {
|
|
$dest = Join-Path $artifactDir ("CodeNomad-Tauri-$env:VERSION-windows-x64.zip")
|
|
Compress-Archive -Path $exe.Directory.FullName -DestinationPath $dest -Force
|
|
}
|
|
|
|
- name: Upload Actions artifacts (Tauri Windows)
|
|
if: ${{ inputs.upload_actions_artifacts }}
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ inputs.actions_artifacts_name_prefix }}tauri-windows
|
|
path: packages/tauri-app/release-tauri/*.zip
|
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
|
if-no-files-found: warn
|
|
|
|
- name: Upload Tauri release assets (Windows)
|
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
|
shell: pwsh
|
|
run: |
|
|
if (Test-Path "packages/tauri-app/release-tauri") {
|
|
Get-ChildItem -Path "packages/tauri-app/release-tauri" -Filter *.zip -File | ForEach-Object {
|
|
Write-Host "Uploading $($_.FullName)"
|
|
gh release upload $env:TAG $_.FullName --clobber
|
|
}
|
|
}
|
|
|
|
build-tauri-linux:
|
|
runs-on: ubuntu-24.04
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VERSION: ${{ inputs.version }}
|
|
TAG: ${{ inputs.tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
|
|
- name: Setup Rust (Tauri)
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- name: Install Linux build dependencies (Tauri)
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
build-essential \
|
|
pkg-config \
|
|
xdg-utils \
|
|
libgtk-3-dev \
|
|
libglib2.0-dev \
|
|
libwebkit2gtk-4.1-dev \
|
|
libsoup-3.0-dev \
|
|
libayatana-appindicator3-dev \
|
|
librsvg2-dev
|
|
|
|
- name: Set workspace versions
|
|
if: ${{ inputs.set_versions && inputs.version != '' }}
|
|
run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspaces --include=optional
|
|
|
|
- name: Ensure rollup native binary
|
|
run: npm install @rollup/rollup-linux-x64-gnu --no-save
|
|
|
|
- name: Prebuild (Tauri)
|
|
run: npm run prebuild --workspace @codenomad/tauri-app
|
|
|
|
- name: Ensure tauri native binary
|
|
working-directory: packages/tauri-app
|
|
run: |
|
|
set -euo pipefail
|
|
for attempt in 1 2 3; do
|
|
if [ "$attempt" -gt 1 ]; then
|
|
echo "Retrying Tauri CLI install (attempt $attempt)..."
|
|
fi
|
|
# Tauri CLI 2.10.1 regresses Linux AppImage bundling in CI; keep Linux on the last known-good CLI.
|
|
npm install @tauri-apps/cli@2.9.4 @tauri-apps/cli-linux-x64-gnu@2.9.4 --no-save --no-audit --no-fund --workspaces=false
|
|
node -e "require('@tauri-apps/cli'); console.log('Tauri CLI loaded')" && exit 0
|
|
done
|
|
echo "Tauri CLI failed to load after retries" >&2
|
|
exit 1
|
|
|
|
- name: Build Linux bundle (Tauri)
|
|
working-directory: packages/tauri-app
|
|
run: npm exec -- tauri build
|
|
|
|
- name: Package Tauri artifacts (Linux)
|
|
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
|
|
run: |
|
|
set -euo pipefail
|
|
SEARCH_ROOT="packages/tauri-app/target"
|
|
ARTIFACT_DIR="packages/tauri-app/release-tauri"
|
|
rm -rf "$ARTIFACT_DIR"
|
|
mkdir -p "$ARTIFACT_DIR"
|
|
shopt -s nullglob globstar
|
|
|
|
find_one() {
|
|
find "$SEARCH_ROOT" -type f -iname "$1" | head -n1
|
|
}
|
|
|
|
appimage=$(find_one "*.AppImage")
|
|
deb=$(find_one "*.deb")
|
|
rpm=$(find_one "*.rpm")
|
|
|
|
if [ -z "$appimage" ] || [ -z "$deb" ] || [ -z "$rpm" ]; then
|
|
echo "Missing bundle(s): appimage=${appimage:-none} deb=${deb:-none} rpm=${rpm:-none}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
cp "$appimage" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.AppImage"
|
|
cp "$deb" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.deb"
|
|
cp "$rpm" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.rpm"
|
|
|
|
- name: Upload Actions artifacts (Tauri Linux)
|
|
if: ${{ inputs.upload_actions_artifacts }}
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ inputs.actions_artifacts_name_prefix }}tauri-linux
|
|
path: packages/tauri-app/release-tauri/*
|
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
|
if-no-files-found: warn
|
|
|
|
- name: Upload Tauri release assets (Linux)
|
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
for file in packages/tauri-app/release-tauri/*; do
|
|
[ -f "$file" ] || continue
|
|
echo "Uploading $file"
|
|
gh release upload "$TAG" "$file" --clobber
|
|
done
|
|
|
|
build-tauri-linux-arm64:
|
|
if: ${{ false }}
|
|
runs-on: ubuntu-24.04
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VERSION: ${{ inputs.version }}
|
|
TAG: ${{ inputs.tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup QEMU
|
|
uses: docker/setup-qemu-action@v3
|
|
with:
|
|
platforms: linux/arm64
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
|
|
- name: Setup Rust (Tauri)
|
|
uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: aarch64-unknown-linux-gnu
|
|
|
|
- name: Install Linux build dependencies (Tauri)
|
|
run: |
|
|
sudo dpkg --add-architecture arm64
|
|
sudo tee /etc/apt/sources.list.d/arm64.list >/dev/null <<'EOF'
|
|
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble main restricted universe multiverse
|
|
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble-updates main restricted universe multiverse
|
|
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble-security main restricted universe multiverse
|
|
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble-backports main restricted universe multiverse
|
|
EOF
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
build-essential \
|
|
pkg-config \
|
|
xdg-utils \
|
|
gcc-aarch64-linux-gnu \
|
|
g++-aarch64-linux-gnu \
|
|
libgtk-3-dev:arm64 \
|
|
libglib2.0-dev:arm64 \
|
|
libwebkit2gtk-4.1-dev:arm64 \
|
|
libsoup-3.0-dev:arm64 \
|
|
libayatana-appindicator3-dev:arm64 \
|
|
librsvg2-dev:arm64
|
|
|
|
- name: Set workspace versions
|
|
run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspaces --include=optional
|
|
|
|
- name: Ensure rollup native binary
|
|
run: npm install @rollup/rollup-linux-arm64-gnu --no-save
|
|
|
|
- name: Build Linux bundle (Tauri arm64)
|
|
env:
|
|
TAURI_BUILD_TARGET: aarch64-unknown-linux-gnu
|
|
PKG_CONFIG_PATH: /usr/lib/aarch64-linux-gnu/pkgconfig
|
|
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
|
|
CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++
|
|
AR_aarch64_unknown_linux_gnu: aarch64-linux-gnu-ar
|
|
run: npm run build --workspace @codenomad/tauri-app
|
|
|
|
- name: Package Tauri artifacts (Linux arm64)
|
|
run: |
|
|
set -euo pipefail
|
|
SEARCH_ROOT="packages/tauri-app/target"
|
|
ARTIFACT_DIR="packages/tauri-app/release-tauri"
|
|
rm -rf "$ARTIFACT_DIR"
|
|
mkdir -p "$ARTIFACT_DIR"
|
|
shopt -s nullglob globstar
|
|
first_artifact=$(find "$SEARCH_ROOT" -type f \( -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" -o -name "*.tar.gz" \) | head -n1)
|
|
fallback_bin="$SEARCH_ROOT/release/codenomad-tauri"
|
|
if [ -n "$first_artifact" ]; then
|
|
zip -j "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.zip" "$first_artifact"
|
|
elif [ -f "$fallback_bin" ]; then
|
|
zip -j "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.zip" "$fallback_bin"
|
|
else
|
|
echo "No bundled artifact found under $SEARCH_ROOT and no binary at $fallback_bin" >&2
|
|
exit 1
|
|
fi
|
|
|
|
|
|
- name: Upload Tauri release assets (Linux arm64)
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
for file in packages/tauri-app/release-tauri/*.zip; do
|
|
[ -f "$file" ] || continue
|
|
echo "Uploading $file"
|
|
gh release upload "$TAG" "$file" --clobber
|
|
done
|
|
|
|
|
|
build-linux-rpm:
|
|
runs-on: ubuntu-24.04
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VERSION: ${{ inputs.version }}
|
|
TAG: ${{ inputs.tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
|
|
- name: Install rpm packaging dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y rpm ruby ruby-dev build-essential
|
|
sudo gem install --no-document fpm
|
|
|
|
- name: Set workspace versions
|
|
if: ${{ inputs.set_versions && inputs.version != '' }}
|
|
run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version
|
|
|
|
- name: Install project dependencies
|
|
run: npm ci --workspaces --include=optional
|
|
|
|
- name: Ensure rollup native binary
|
|
run: npm install @rollup/rollup-linux-x64-gnu --no-save
|
|
|
|
- name: Build Linux RPM binaries
|
|
run: npm run build:linux-rpm --workspace @neuralnomads/codenomad-electron-app
|
|
|
|
- name: Upload RPM release assets
|
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
for file in packages/electron-app/release/*.rpm; do
|
|
[ -f "$file" ] || continue
|
|
echo "Uploading $file"
|
|
gh release upload "$TAG" "$file" --clobber
|
|
done
|
|
|
|
- name: Upload Actions artifacts (Electron Linux RPM)
|
|
if: ${{ inputs.upload_actions_artifacts }}
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ inputs.actions_artifacts_name_prefix }}electron-linux-rpm
|
|
path: packages/electron-app/release/*.rpm
|
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
|
if-no-files-found: error
|