Skip to content

Release Gajae Code App #5

Release Gajae Code App

Release Gajae Code App #5

Workflow file for this run

name: Release Gajae App
on:
workflow_dispatch:
permissions:
contents: read
jobs:
release-policy:
name: Validate protected release ref
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Require main branch
env:
RELEASE_REF: ${{ github.ref }}
run: |
if [ "$RELEASE_REF" != "refs/heads/main" ]; then
echo "Releases may only be dispatched from refs/heads/main." >&2
exit 1
fi
build:
name: Build Node 22 Linux x64 server artifact
needs: release-policy
timeout-minutes: 30
runs-on: ubuntu-22.04
permissions:
contents: read
outputs:
artifact_name: ${{ steps.metadata.outputs.artifact_name }}
release_tag: ${{ steps.metadata.outputs.release_tag }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
architecture: x64
cache: npm
- name: Define canonical release asset
id: metadata
run: |
set -euo pipefail
case "$(node --version)" in
v22.*) ;;
*) echo "Node 22 is required." >&2; exit 1 ;;
esac
case "$(getconf GNU_LIBC_VERSION)" in
"glibc 2.35") ;;
*) echo "The release artifact must be built on glibc 2.35." >&2; exit 1 ;;
esac
VERSION="$(node -p "require('./package.json').version")"
ASSET_NAME="gajae-app-server-${VERSION}-linux-x64-node22.tar.gz"
{
echo "artifact_name=$ASSET_NAME"
echo "release_tag=v$VERSION"
} >> "$GITHUB_OUTPUT"
- name: Seed verified ripgrep install cache
run: node scripts/release/prime-ripgrep-cache.mjs
- name: Install dependencies
run: npm ci
- name: Fetch pinned Bun runtime
run: node scripts/fetch-bun.mjs
- name: Verify source
run: npm run verify
- name: Build server artifact
run: npm run server:bundle
- name: Stage and verify canonical release assets
env:
ASSET_NAME: ${{ steps.metadata.outputs.artifact_name }}
run: |
set -euo pipefail
shopt -s nullglob
archives=(release/server/*.tar.gz)
if [ "${#archives[@]}" -ne 1 ]; then
echo "Expected exactly one generated server archive." >&2
exit 1
fi
source_archive="${archives[0]}"
if [ "$(basename "$source_archive")" != "$ASSET_NAME" ]; then
echo "Generated archive name does not match canonical release asset: $source_archive" >&2
exit 1
fi
source_checksum="${source_archive}.sha256"
test -f "$source_checksum"
(
cd "$(dirname "$source_archive")"
sha256sum --check "$(basename "$source_checksum")"
)
mkdir release-assets
cp "$source_archive" "release-assets/$ASSET_NAME"
(
cd release-assets
sha256sum "$ASSET_NAME" > "$ASSET_NAME.sha256"
sha256sum --check "$ASSET_NAME.sha256"
)
assets=(release-assets/*)
if [ "${#assets[@]}" -ne 2 ] ||
[ ! -f "release-assets/$ASSET_NAME" ] ||
[ ! -f "release-assets/$ASSET_NAME.sha256" ]; then
echo "Release staging must contain only the canonical archive and checksum." >&2
exit 1
fi
- name: Smoke extracted server artifact
env:
ASSET_NAME: ${{ steps.metadata.outputs.artifact_name }}
run: |
set -euo pipefail
mkdir extracted
tar -xzf "release-assets/$ASSET_NAME" -C extracted
test -f extracted/package.json
expected_version="${ASSET_NAME#gajae-app-server-}"
expected_version="${expected_version%-linux-x64-node22.tar.gz}"
actual_version="$(node -p "require('./extracted/package.json').version")"
test "$actual_version" = "$expected_version"
node extracted/scripts/gajae-app-runtime.mjs --help
- name: Upload canonical server release assets
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: gajae-app-server-release
path: release-assets/*
if-no-files-found: error
desktop-macos:
name: Build macOS arm64 desktop DMG
needs: release-policy
timeout-minutes: 60
runs-on: macos-14
permissions:
contents: read
outputs:
artifact_name: ${{ steps.metadata.outputs.artifact_name }}
release_tag: ${{ steps.metadata.outputs.release_tag }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
architecture: arm64
cache: npm
- name: Define canonical desktop release asset
id: metadata
run: |
set -euo pipefail
test "$(uname -s)" = "Darwin"
test "$(uname -m)" = "arm64"
case "$(node --version)" in
v22.*) ;;
*) echo "Node 22 is required." >&2; exit 1 ;;
esac
RELEASE_VERSION="$(node -p "require('./package.json').version")"
DESKTOP_VERSION="$(node -p "require('./package.json').desktopVersion")"
ASSET_NAME="gajae-app-desktop-${RELEASE_VERSION}-macos-arm64.dmg"
{
echo "artifact_name=$ASSET_NAME"
echo "desktop_version=$DESKTOP_VERSION"
echo "release_tag=v$RELEASE_VERSION"
} >> "$GITHUB_OUTPUT"
- name: Set up Rust
run: |
set -euo pipefail
rustup toolchain install 1.85.1 --profile minimal
rustup default 1.85.1
rustup target add aarch64-apple-darwin
rustc --version
- name: Seed verified ripgrep install cache
run: node scripts/release/prime-ripgrep-cache.mjs
- name: Install dependencies
run: npm ci
- name: Build and verify embedded macOS server payload
run: npm run server:payload:macos
- name: Build ad-hoc signed Tauri app bundle
run: env -u CI npm run tauri -- build --bundles app
- name: Build canonical installable DMG
env:
RELEASE_VERSION: ${{ steps.metadata.outputs.release_tag }}
run: npm run desktop:dmg:macos -- --artifact-version "${RELEASE_VERSION#v}"
- name: Stage and verify canonical desktop assets
env:
ASSET_NAME: ${{ steps.metadata.outputs.artifact_name }}
run: |
set -euo pipefail
SOURCE="src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/$ASSET_NAME"
test -f "$SOURCE"
test -f "$SOURCE.sha256"
(
cd "$(dirname "$SOURCE")"
shasum -a 256 --check "$(basename "$SOURCE.sha256")"
)
mkdir release-assets
cp "$SOURCE" "$SOURCE.sha256" release-assets/
assets=(release-assets/*)
if [ "${#assets[@]}" -ne 2 ] ||
[ ! -f "release-assets/$ASSET_NAME" ] ||
[ ! -f "release-assets/$ASSET_NAME.sha256" ]; then
echo "Desktop release staging must contain only the canonical DMG and checksum." >&2
exit 1
fi
hdiutil verify "release-assets/$ASSET_NAME"
- name: Smoke the app installed from the DMG
env:
ASSET_NAME: ${{ steps.metadata.outputs.artifact_name }}
DESKTOP_VERSION: ${{ steps.metadata.outputs.desktop_version }}
run: |
set -euo pipefail
MOUNT_POINT="$RUNNER_TEMP/gajae-app-dmg"
mkdir "$MOUNT_POINT"
hdiutil attach "release-assets/$ASSET_NAME" -nobrowse -readonly -mountpoint "$MOUNT_POINT"
trap 'hdiutil detach "$MOUNT_POINT"' EXIT
APP="$MOUNT_POINT/Gajae App.app"
test -d "$APP"
codesign --verify --deep --strict "$APP"
test "$(/usr/libexec/PlistBuddy -c 'Print CFBundleIdentifier' "$APP/Contents/Info.plist")" = "app.gajae.desktop"
test "$(/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' "$APP/Contents/Info.plist")" = "$DESKTOP_VERSION"
lipo "$APP/Contents/MacOS/gajae-app-desktop" -verify_arch arm64
lipo "$APP/Contents/MacOS/gajae-app-server" -verify_arch arm64
node scripts/release/smoke-packaged-server.mjs --tauri-app "$APP"
hdiutil detach "$MOUNT_POINT"
trap - EXIT
- name: Upload canonical desktop release assets
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: gajae-app-desktop-release
path: release-assets/*
if-no-files-found: error
ubuntu-24-compatibility:
name: Smoke server artifact on Ubuntu 24.04
needs: build
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
architecture: x64
- name: Download canonical server release assets
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: gajae-app-server-release
path: release-assets
- name: Verify checksum and smoke extracted artifact
env:
ASSET_NAME: ${{ needs.build.outputs.artifact_name }}
run: |
set -euo pipefail
shopt -s nullglob
assets=(release-assets/*)
if [ "${#assets[@]}" -ne 2 ] ||
[ ! -f "release-assets/$ASSET_NAME" ] ||
[ ! -f "release-assets/$ASSET_NAME.sha256" ]; then
echo "Compatibility job received non-canonical release assets." >&2
exit 1
fi
(
cd release-assets
sha256sum --check "$ASSET_NAME.sha256"
)
mkdir extracted
tar -xzf "release-assets/$ASSET_NAME" -C extracted
test -f extracted/package.json
expected_version="${ASSET_NAME#gajae-app-server-}"
expected_version="${expected_version%-linux-x64-node22.tar.gz}"
actual_version="$(node -p "require('./extracted/package.json').version")"
test "$actual_version" = "$expected_version"
node extracted/scripts/gajae-app-runtime.mjs --help
(
cd extracted
node -e "const Database=require('better-sqlite3'); const pty=require('node-pty'); const db=new Database(':memory:'); db.exec('select 1'); db.close(); const child=pty.spawn(process.execPath,['-e','process.exit(0)']); child.onExit(() => process.exit(0)); setTimeout(() => process.exit(1),5000)"
node -e "const {spawnSync}=require('node:child_process'); const rg=require('@vscode/ripgrep').rgPath; const result=spawnSync(rg,['--version'],{stdio:'inherit'}); process.exit(result.status ?? 1)"
)
export HOME="$RUNNER_TEMP/gajae-app-home"
mkdir -p "$HOME"
SERVER_PORT=39001 HOST=127.0.0.1 node extracted/scripts/gajae-app-runtime.mjs start >"$RUNNER_TEMP/gajae-app-server.log" 2>&1 &
server_pid=$!
trap 'kill "$server_pid" 2>/dev/null || true' EXIT
for _ in {1..30}; do
if curl --fail --silent http://127.0.0.1:39001/health >/dev/null; then
break
fi
sleep 1
done
curl --fail --silent http://127.0.0.1:39001/health
kill "$server_pid"
wait "$server_pid" || true
trap - EXIT
publish:
name: Publish GitHub Release
needs:
- build
- desktop-macos
- ubuntu-24-compatibility
runs-on: ubuntu-22.04
environment: release
timeout-minutes: 15
permissions:
contents: write
steps:
- name: Download canonical server release assets
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: gajae-app-server-release
path: release-assets
- name: Download canonical desktop release assets
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: gajae-app-desktop-release
path: release-assets
- name: Verify publish inputs
env:
SERVER_ASSET_NAME: ${{ needs.build.outputs.artifact_name }}
DESKTOP_ASSET_NAME: ${{ needs.desktop-macos.outputs.artifact_name }}
SERVER_RELEASE_TAG: ${{ needs.build.outputs.release_tag }}
DESKTOP_RELEASE_TAG: ${{ needs.desktop-macos.outputs.release_tag }}
run: |
set -euo pipefail
if [ "$SERVER_RELEASE_TAG" != "$DESKTOP_RELEASE_TAG" ]; then
echo "Server and desktop release tags do not match." >&2
exit 1
fi
shopt -s nullglob
assets=(release-assets/*)
if [ "${#assets[@]}" -ne 4 ] ||
[ ! -f "release-assets/$SERVER_ASSET_NAME" ] ||
[ ! -f "release-assets/$SERVER_ASSET_NAME.sha256" ] ||
[ ! -f "release-assets/$DESKTOP_ASSET_NAME" ] ||
[ ! -f "release-assets/$DESKTOP_ASSET_NAME.sha256" ]; then
echo "Refusing to publish non-canonical release assets." >&2
exit 1
fi
(
cd release-assets
sha256sum --check "$SERVER_ASSET_NAME.sha256"
sha256sum --check "$DESKTOP_ASSET_NAME.sha256"
)
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
SERVER_ASSET_NAME: ${{ needs.build.outputs.artifact_name }}
DESKTOP_ASSET_NAME: ${{ needs.desktop-macos.outputs.artifact_name }}
RELEASE_TAG: ${{ needs.build.outputs.release_tag }}
run: |
set -euo pipefail
prerelease_args=()
if [[ "$RELEASE_TAG" == *-* ]]; then
prerelease_args+=(--prerelease)
fi
gh release create "$RELEASE_TAG" \
"release-assets/$SERVER_ASSET_NAME" \
"release-assets/$SERVER_ASSET_NAME.sha256" \
"release-assets/$DESKTOP_ASSET_NAME" \
"release-assets/$DESKTOP_ASSET_NAME.sha256" \
--repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" \
--title "Gajae App $RELEASE_TAG" \
"${prerelease_args[@]}" \
--generate-notes