serene-pub/.github/workflows/release.yml
2026-07-19 02:36:04 -07:00

329 lines
15 KiB
YAML

name: Release
on:
push:
tags:
- "v*.*.*"
- "v*.*.*-dev"
- "v*.*.*-alpha"
- "v*.*.*-beta"
- "v*.*.*-pr-*"
- "v*.*.*-rc-*"
jobs:
build-and-release:
strategy:
matrix:
include:
- target: linux-x64
os: ubuntu-latest
# - target: linux-arm64
# os: ubuntu-latest
# NOTE: GitHub-hosted runners don't offer native
# linux-arm64 hardware, and there's no cross-compile
# setup here (no QEMU, no npm_config_arch/--target_arch
# overrides) — `npm install` on an x64 ubuntu-latest
# runner resolves x64-arch native optional deps (eg.
# sharp, a transitive dep of @huggingface/transformers,
# which src/lib/server/embedding/ actually imports) and
# bundles them into a zip labeled "linux-arm64",
# producing an artifact that's broken on real arm64
# hardware. Disabled until real cross-compilation is
# set up.
# - target: linux-ppc64
# os: ubuntu-latest
# NOTE: same cross-arch issue as linux-arm64 above.
# - target: linux-arm
# os: ubuntu-latest
# NOTE: Node.js does not provide armv7l builds
- target: macos-x64
# NOT macos-latest: GitHub's "-latest" label now points
# to Apple Silicon (macos-15, arm64) — building the
# x64 target there would resolve x64-labeled but
# actually-arm64 native optional deps (eg. sharp, a
# transitive dep of @huggingface/transformers) via
# npm's platform/arch-matching install logic, silently
# shipping an arm64 binary inside a "macos-x64" zip
# that would fail to load on real Intel Macs.
# NOT macos-latest-large either — that's a paid
# "larger runner" (GitHub Team/Enterprise only,
# triggered a billing requirement when tried).
# macos-15-intel is the plain, free-tier Intel runner
# that matches macos-latest's current OS version
# (macOS 15) — just x64 instead of arm64, no paid plan
# needed.
os: macos-15-intel
- target: macos-arm64
os: macos-latest
- target: windows-x64
os: windows-latest
# - target: windows-arm64
# os: windows-latest
# NOTE: same cross-arch native-dependency issue as
# linux-arm64 above — windows-latest is x64, and there's
# no cross-compile setup, so this would ship an x64
# sharp binary inside a "windows-arm64" zip. Disabled
# until real cross-compilation is set up.
runs-on: ${{ matrix.os }}
env:
NODE_OPTIONS: "--max-old-space-size=4096"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Generate release title
id: release_title
shell: bash
run: |
TAG="${{ github.ref_name }}"
VERSION="${TAG#v}" # Remove 'v' prefix
# Extract base version and suffix
if [[ "$VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+)(-(.+))?$ ]]; then
BASE_VERSION="${BASH_REMATCH[1]}"
SUFFIX="${BASH_REMATCH[3]}"
if [ -z "$SUFFIX" ]; then
# No suffix - formal release
TITLE="$BASE_VERSION Release"
IS_PRERELEASE="false"
RUN_TESTS="true"
elif [ "$SUFFIX" = "dev" ]; then
# Development release
TITLE="$BASE_VERSION Development Release"
IS_PRERELEASE="true"
RUN_TESTS="false"
elif [ "$SUFFIX" = "alpha" ]; then
# Alpha release
TITLE="$BASE_VERSION Alpha Release"
IS_PRERELEASE="true"
RUN_TESTS="true"
elif [ "$SUFFIX" = "beta" ]; then
# Beta release (not a pre-release)
TITLE="$BASE_VERSION Beta Release"
IS_PRERELEASE="false"
RUN_TESTS="true"
elif [[ "$SUFFIX" =~ ^pr-([0-9]+)$ ]]; then
# Pre-release build - these are the tag pattern currently
# shipped as real releases, so run the full test suite
# just like any other release.
PR_NUM="${BASH_REMATCH[1]}"
TITLE="$BASE_VERSION Pre-Release Build $PR_NUM"
IS_PRERELEASE="true"
RUN_TESTS="true"
elif [[ "$SUFFIX" =~ ^rc-([0-9]+)$ ]]; then
# Release candidate
RC_NUM="${BASH_REMATCH[1]}"
TITLE="$BASE_VERSION Release Candidate $RC_NUM"
IS_PRERELEASE="true"
RUN_TESTS="true"
else
# Unknown suffix
TITLE="$VERSION"
IS_PRERELEASE="true"
RUN_TESTS="false"
fi
else
# Invalid version format
TITLE="$VERSION"
IS_PRERELEASE="true"
RUN_TESTS="false"
fi
echo "title=$TITLE" >> $GITHUB_OUTPUT
echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT
echo "run_tests=$RUN_TESTS" >> $GITHUB_OUTPUT
echo "base_version=$BASE_VERSION" >> $GITHUB_OUTPUT
echo "Release Title: $TITLE"
echo "Is Pre-release: $IS_PRERELEASE"
echo "Run Tests: $RUN_TESTS"
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Install dependencies
shell: bash
run: |
node -e "const fs = require('fs'); try { fs.unlinkSync('package-lock.json'); } catch (e) { /* ignore */ }"
for i in 1 2 3; do npm install --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 && break || sleep 15; done
- name: Prepare SvelteKit
run: npx @sveltejs/kit sync
- name: Run typecheck
# Non-blocking: the codebase currently carries a large pre-existing
# type-error backlog (see project history) that predates this gate.
# Errors still surface in the log for visibility; revisit making
# this blocking again once the backlog is cleared.
continue-on-error: true
run: npm run check
- name: Run tests
if: steps.release_title.outputs.run_tests == 'true'
run: npm test
- name: Build app
run: npm run build
- name: Create platform-specific executables and icons
run: node scripts/create-executables.js
- name: Prepare production dependencies
shell: bash
run: |
# Install production dependencies
npm install --production
npm rebuild
# Clean node_modules with modclean, preserving license files
npx modclean --run --patterns="default:safe,default:caution,default:danger" --ignore="**/LICENSE,**/COPYING,**/NOTICE,**/README*,**/COPYRIGHT,**/AUTHORS,**/CONTRIBUTORS"
- name: Download Node.js runtime for target platform
shell: bash
run: |
echo "Downloading Node.js for ${{ matrix.target }}..."
mkdir -p temp-node
cd temp-node
case "${{ matrix.target }}" in
linux-x64)
NODE_URL="https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-x64.tar.xz"
curl -L -o node.tar.xz "$NODE_URL" || { echo "Failed to download"; exit 1; }
tar -xf node.tar.xz
cp node-v24.18.0-linux-x64/bin/node ../node
;;
linux-arm64)
NODE_URL="https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-arm64.tar.xz"
curl -L -o node.tar.xz "$NODE_URL" || { echo "Failed to download"; exit 1; }
tar -xf node.tar.xz
cp node-v24.18.0-linux-arm64/bin/node ../node
;;
linux-ppc64)
NODE_URL="https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-ppc64le.tar.xz"
curl -L -o node.tar.xz "$NODE_URL" || { echo "Failed to download"; exit 1; }
tar -xf node.tar.xz
cp node-v24.18.0-linux-ppc64le/bin/node ../node
;;
macos-x64)
NODE_URL="https://nodejs.org/dist/v24.18.0/node-v24.18.0-darwin-x64.tar.gz"
curl -L -o node.tar.gz "$NODE_URL" || { echo "Failed to download"; exit 1; }
tar -xzf node.tar.gz
cp node-v24.18.0-darwin-x64/bin/node ../node
;;
macos-arm64)
NODE_URL="https://nodejs.org/dist/v24.18.0/node-v24.18.0-darwin-arm64.tar.gz"
curl -L -o node.tar.gz "$NODE_URL" || { echo "Failed to download"; exit 1; }
tar -xzf node.tar.gz
cp node-v24.18.0-darwin-arm64/bin/node ../node
;;
windows-x64)
NODE_URL="https://nodejs.org/dist/v24.18.0/node-v24.18.0-win-x64.zip"
curl -L -o node.zip "$NODE_URL" || { echo "Failed to download"; exit 1; }
unzip -q node.zip
cp node-v24.18.0-win-x64/node.exe ../node.exe
;;
windows-arm64)
NODE_URL="https://nodejs.org/dist/v24.18.0/node-v24.18.0-win-arm64.zip"
curl -L -o node.zip "$NODE_URL" || { echo "Failed to download"; exit 1; }
unzip -q node.zip
cp node-v24.18.0-win-arm64/node.exe ../node.exe
;;
esac
cd ..
rm -rf temp-node
# Verify the Node.js binary was downloaded
if [[ "${{ matrix.target }}" == windows-* ]]; then
if [ ! -f "node.exe" ]; then
echo "ERROR: Failed to download node.exe"
exit 1
fi
echo "Downloaded node.exe ($(du -h node.exe | cut -f1))"
else
if [ ! -f "node" ]; then
echo "ERROR: Failed to download node binary"
exit 1
fi
chmod +x node
echo "Downloaded node binary ($(du -h node | cut -f1))"
fi
- name: Bundle for ${{ matrix.target }}
run: node scripts/bundle-dist.js ${{ matrix.target }}
- name: Create production environment example
shell: bash
run: |
cd dist
DIST_DIR=$(ls -d serene-pub-*-${{ matrix.target }} 2>/dev/null | head -1)
if [ -z "$DIST_DIR" ]; then
echo "Error: No directory found matching serene-pub-*-${{ matrix.target }}"
ls -la
exit 1
fi
cat > "$DIST_DIR/.env.example" << EOF
# Serene Pub ${{ steps.release_title.outputs.base_version }} - Production Configuration
# Copy this file to .env in the same directory to customize settings
# ===========================================
# APPLICATION DATA DIRECTORY
# ===========================================
# Override default data directory location
# Default: OS-appropriate directory (~/.local/share/SerenePub on Linux, %APPDATA%/SerenePub on Windows, ~/Library/Application Support/SerenePub on macOS)
# Example for custom location:
# SERENE_PUB_DATA_DIR=/path/to/custom/data
# ===========================================
# SERVER CONFIGURATION
# ===========================================
# WebSocket server port
# Default: 3001
# SOCKETS_PORT=3001
# HTTP server port (main application)
# Default: 3000
# PORT=3000
# Disable automatic browser opening
# Default: Opens browser automatically on startup
# Set to 1 to disable auto-open
# SERENE_AUTO_OPEN=1
EOF
- name: Zip dist directory (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
cd dist
$distDir = Get-ChildItem -Directory -Name "serene-pub-*-${{ matrix.target }}" | Select-Object -First 1
if (-not $distDir) {
Write-Error "No directory found matching serene-pub-*-${{ matrix.target }}"
Get-ChildItem
exit 1
}
Compress-Archive -Path $distDir -DestinationPath "../serene-pub-${{ github.ref_name }}-${{ matrix.target }}.zip"
- name: Zip dist directory (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
cd dist
DIST_DIR=$(ls -d serene-pub-*-${{ matrix.target }} 2>/dev/null | head -1)
if [ -z "$DIST_DIR" ]; then
echo "Error: No directory found matching serene-pub-*-${{ matrix.target }}"
ls -la
exit 1
fi
zip -r "../serene-pub-${{ github.ref_name }}-${{ matrix.target }}.zip" "$DIST_DIR"
- name: Upload release assets
uses: softprops/action-gh-release@v2
with:
name: ${{ steps.release_title.outputs.title }}
files: serene-pub-*.zip
prerelease: ${{ steps.release_title.outputs.is_prerelease }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}