Skip to content

chore: bump version to 1.3.23 #41

chore: bump version to 1.3.23

chore: bump version to 1.3.23 #41

Workflow file for this run

name: Build (Windows, Linux & macOS)
on:
push:
tags: ["v*"]
workflow_dispatch:
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
bundles: nsis
artifact-name: RexaDB-Windows-x64
- os: macos-14
bundles: dmg,app
artifact-name: RexaDB-macOS-arm64
- os: macos-15-intel
bundles: dmg,app
artifact-name: RexaDB-macOS-Intel
runs-on: ${{ matrix.os }}
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Restore .env
shell: bash
env:
APP_ENV_FILE: ${{ secrets.APP_ENV_FILE }}
run: |
printf '%s' "$APP_ENV_FILE" > .env
echo "Restored .env ($(grep -c '=' .env) entries)"
- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libssl-dev \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev \
libfuse2 \
patchelf \
xvfb
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/cache@v4
with:
path: |
node_modules
key: bun-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-
- run: bun install
- name: Install server dependencies
run: |
cd server
bun install
- run: bun run scripts/build-server.mjs
- name: Build Oracle bridge
shell: bash
run: bun run scripts/build-oracle-bridge.mjs
- name: Verify Oracle bridge resource
shell: bash
run: |
if [ "$RUNNER_OS" = "Windows" ]; then BIN="resources/oracle-bridge/rexadb-oracle-bridge.exe"; else BIN="resources/oracle-bridge/rexadb-oracle-bridge"; fi
if [ -f "$BIN" ]; then echo " ✅ $BIN ($(wc -c < "$BIN") bytes)"; else echo " ❌ MISSING: $BIN"; exit 1; fi
- name: Verify sidecar binary (all platforms)
shell: bash
run: |
echo "===== Sidecar binary check ====="
case "$RUNNER_OS" in
Linux)
SIDECAR="src-tauri/binaries/rexadb-server-x86_64-unknown-linux-gnu"
;;
Windows)
SIDECAR="src-tauri/binaries/rexadb-server-x86_64-pc-windows-msvc.exe"
;;
macOS)
case "$(uname -m)" in
arm64) SIDECAR="src-tauri/binaries/rexadb-server-aarch64-apple-darwin" ;;
x86_64) SIDECAR="src-tauri/binaries/rexadb-server-x86_64-apple-darwin" ;;
esac
;;
esac
echo " Expected: $SIDECAR"
if [ -f "$SIDECAR" ]; then
SIZE=$(wc -c < "$SIDECAR")
echo " ✅ Found: $SIDECAR ($SIZE bytes)"
ls -lh "$SIDECAR"
else
echo " ❌ MISSING: $SIDECAR"
echo " Contents of binaries/:"
ls -la src-tauri/binaries/ 2>/dev/null || echo " (binaries dir not found)"
exit 1
fi
- name: Build Java bridge (Docker)
if: runner.os == 'Linux'
run: bash resources/java-bridge/build.sh --docker
- uses: actions/setup-java@v4
if: runner.os == 'Windows'
with:
distribution: 'temurin'
java-version: '21'
- uses: actions/setup-java@v4
if: runner.os == 'macOS'
with:
distribution: 'temurin'
java-version: '21'
- name: Build Java bridge
if: runner.os == 'Windows'
run: bash resources/java-bridge/build.sh
shell: bash
- name: Build Java bridge (macOS)
if: runner.os == 'macOS'
run: bash resources/java-bridge/build.sh
- name: Verify Java bridge resources (Windows)
if: runner.os == 'Windows'
shell: bash
run: |
echo "===== Java bridge resource check ====="
echo " RUNNER_OS: $RUNNER_OS"
echo " PWD: $(pwd)"
echo ""
echo "--- bridge.jar ---"
JAR="resources/java-bridge/dist/bridge.jar"
if [ -f "$JAR" ]; then
SIZE=$(wc -c < "$JAR")
echo " ✅ $JAR ($SIZE bytes)"
else
echo " ❌ MISSING: $JAR"
echo " Contents of dist/:"
ls -la resources/java-bridge/dist/ 2>/dev/null || echo " (dist dir not found)"
echo " Contents of java-bridge/:"
ls -la resources/java-bridge/ 2>/dev/null || echo " (java-bridge dir not found)"
exit 1
fi
echo ""
echo "--- JRE ---"
JRE_BIN="resources/java-bridge/dist/jre/bin/java.exe"
if [ -f "$JRE_BIN" ]; then
SIZE=$(wc -c < "$JRE_BIN")
echo " ✅ $JRE_BIN ($SIZE bytes)"
else
echo " ❌ MISSING: $JRE_BIN"
echo " Build script may have skipped jlink (jmods not found)."
echo " Contents of dist/jre/:"
ls -laR resources/java-bridge/dist/jre/ 2>/dev/null || echo " (jre dir not found)"
echo " Contents of dist/:"
ls -la resources/java-bridge/dist/ 2>/dev/null || echo " (dist dir not found)"
echo ""
echo " Attempting jlink with JAVA_HOME=$JAVA_HOME ..."
if [ -n "$JAVA_HOME" ]; then
JMODS="$JAVA_HOME/jmods"
if [ -d "$JMODS" ]; then
echo " Found jmods at $JMODS, running jlink..."
rm -rf resources/java-bridge/dist/jre
jlink --module-path "$JMODS" \
--add-modules java.base,java.sql,jdk.crypto.ec,java.management \
--output resources/java-bridge/dist/jre \
--strip-debug --compress=2 --no-header-files --no-man-pages
if [ -f "$JRE_BIN" ]; then
echo " ✅ jlink succeeded: $JRE_BIN"
else
echo " ❌ jlink FAILED"
exit 1
fi
else
echo " jmods not found at $JMODS"
echo " Looking for JDK installation..."
ls -la "$JAVA_HOME/" 2>/dev/null || echo " JAVA_HOME not accessible"
exit 1
fi
else
echo " JAVA_HOME not set"
exit 1
fi
fi
echo ""
echo "--- jre/lib/modules check ---"
MODULES="resources/java-bridge/dist/jre/lib/modules"
if [ -f "$MODULES" ]; then
SIZE=$(wc -c < "$MODULES")
echo " ✅ modules file ($SIZE bytes)"
else
echo " ❌ MISSING: $MODULES"
echo " Contents of jre/lib/:"
ls -la resources/java-bridge/dist/jre/lib/ 2>/dev/null || echo " (lib/ dir not found)"
echo " Contents of jre/:"
ls -la resources/java-bridge/dist/jre/ 2>/dev/null || echo " (jre dir not found)"
exit 1
fi
echo ""
echo "===== Java bridge check PASSED ====="
- name: Import Apple code signing certificate (macOS)
if: runner.os == 'macOS'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
echo "Creating keychain..."
KEYCHAIN_RUNNER="$RUNNER_TEMP/AppSigning.keychain-db"
KEYCHAIN_PASS="runner-keychain-password"
security create-keychain -p "$KEYCHAIN_PASS" "$KEYCHAIN_RUNNER"
security set-keychain-settings -lut 21600 "$KEYCHAIN_RUNNER"
echo "Decoding certificate..."
echo "$APPLE_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/certificate.p12"
echo "Importing certificate..."
security import "$RUNNER_TEMP/certificate.p12" \
-P "$APPLE_CERTIFICATE_PASSWORD" \
-A -t cert -f pkcs12 \
-k "$KEYCHAIN_RUNNER"
echo "Configuring keychain..."
security list-keychains -d user -s "$KEYCHAIN_RUNNER" login.keychain-db
security set-key-partition-list \
-S apple-tool:,apple:,codesign: -s \
-k "$KEYCHAIN_PASS" "$KEYCHAIN_RUNNER" 2>/dev/null || true
echo "Verifying identity...\n\n"
security find-identity -v -p codesigning "$KEYCHAIN_RUNNER"
- name: Sign Oracle bridge (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
# Tauri copies bundle resources as-is: an unsigned Mach-O binary in
# Contents/Resources fails Apple notarization ("binary is not
# signed", no secure timestamp, no hardened runtime). Sign it here
# with the same Developer ID identity so the bundle notarizes.
BIN="resources/oracle-bridge/rexadb-oracle-bridge"
if [ ! -f "$BIN" ]; then
echo " ❌ MISSING: $BIN (run build-oracle-bridge first)"
exit 1
fi
IDENTITY=$(node -p "require('./src-tauri/tauri.conf.json').bundle.macOS.signingIdentity")
echo " Signing $BIN with: $IDENTITY"
codesign --force --options runtime --timestamp --sign "$IDENTITY" "$BIN"
codesign --verify --verbose=2 "$BIN"
echo " ✅ Oracle bridge signed"
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
args: --bundles ${{ matrix.bundles }}
- name: Verify bundle artifacts (Windows)
if: runner.os == 'Windows'
shell: bash
run: |
echo "===== Bundle artifact verification ====="
echo ""
# --- 1. Main binary ---
echo "--- App binary ---"
BINARY="src-tauri/target/release/rexa-db.exe"
if [ -f "$BINARY" ]; then
SIZE=$(wc -c < "$BINARY")
echo " ✅ $BINARY ($SIZE bytes)"
else
echo " ❌ MISSING: $BINARY"
echo " Contents of target/release/:"
ls src-tauri/target/release/rexa-db* 2>/dev/null || echo " (no rexa-db files)"
exit 1
fi
echo ""
# --- 2. NSIS installer(s) ---
echo "--- NSIS installer ---"
INSTALLERS=0
while IFS= read -r f; do
SIZE=$(wc -c < "$f")
echo " 📦 $f ($SIZE bytes)"
INSTALLERS=$((INSTALLERS + 1))
done < <(find src-tauri/target/release/bundle -name "*.exe" -o -name "*.msi" 2>/dev/null)
if [ "$INSTALLERS" -eq 0 ]; then
echo " ❌ No installers found in bundle/"
echo " Bundle tree:"
find src-tauri/target/release/bundle -type f 2>/dev/null || echo " (bundle dir not found)"
exit 1
fi
echo ""
# --- 3. Sidecar in bundle staging ---
echo "--- Sidecar in bundle staging ---"
SIDECAR_BUNDLE="src-tauri/target/release/bundle/nsis/rexadb-server-x86_64-pc-windows-msvc.exe"
if [ -f "$SIDECAR_BUNDLE" ]; then
SIZE=$(wc -c < "$SIDECAR_BUNDLE")
echo " ✅ $SIDECAR_BUNDLE ($SIZE bytes)"
else
echo " ℹ️ Not at expected path (may be embedded in installer by Tauri)"
echo " Searching for sidecar in bundle/:"
find src-tauri/target/release/bundle -name "*server*" -type f 2>/dev/null || echo " (none found)"
fi
echo ""
# --- 4. Full bundle tree ---
echo "--- Complete bundle tree ---"
find src-tauri/target/release/bundle -type f 2>/dev/null | while IFS= read -r f; do
SIZE=$(wc -c < "$f")
echo " $f ($SIZE bytes)"
done
echo ""
echo "===== Bundle verification PASSED ====="
- name: Pre-download AppImage tools (Linux only)
if: runner.os == 'Linux'
run: |
set -euo pipefail
mkdir -p "$HOME/.cache/tauri" "$HOME/.local/bin"
curl -sL \
https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage \
-o "$HOME/.cache/tauri/linuxdeploy-x86_64.AppImage"
chmod +x "$HOME/.cache/tauri/linuxdeploy-x86_64.AppImage"
curl -sL \
https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/releases/download/continuous/linuxdeploy-plugin-gtk-x86_64.AppImage \
-o "$HOME/.cache/tauri/linuxdeploy-plugin-gtk-x86_64.AppImage"
chmod +x "$HOME/.cache/tauri/linuxdeploy-plugin-gtk-x86_64.AppImage"
curl -sL \
https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage \
-o "$HOME/.local/bin/appimagetool-x86_64.AppImage"
chmod +x "$HOME/.local/bin/appimagetool-x86_64.AppImage"
- name: Build AppImage (Linux only)
if: runner.os == 'Linux'
env:
APPIMAGE_EXTRACT_AND_RUN: 1
run: |
set -euo pipefail
VERSION=$(node -p "require('./src-tauri/tauri.conf.json').version")
bash scripts/build-appimage.sh --skip-tauri-build --skip-bridge
APPIMAGE="${GITHUB_WORKSPACE}/RexaDB_${VERSION}_x86_64.AppImage"
if [ ! -f "$APPIMAGE" ]; then
echo "ERROR: AppImage not created by build-appimage.sh"
exit 1
fi
# Sign for updater
printf '%s' "$TAURI_SIGNING_PRIVATE_KEY" > /tmp/rexadb-sign-key
unset TAURI_SIGNING_PRIVATE_KEY
export TAURI_SIGNING_PRIVATE_KEY_PATH=/tmp/rexadb-sign-key
"$GITHUB_WORKSPACE/node_modules/.bin/tauri" signer sign \
-p "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \
"$APPIMAGE"
echo "Signed: ${APPIMAGE}.sig"
# Move to CI-expected location for subsequent steps
BUNDLE_DIR="src-tauri/target/release/bundle/appimage"
mkdir -p "$BUNDLE_DIR"
mv "$APPIMAGE" "$BUNDLE_DIR/"
mv "${APPIMAGE}.sig" "$BUNDLE_DIR/"
- name: Generate updater platform snippet
shell: bash
run: |
set -euo pipefail
VERSION=$(node -p "require('./src-tauri/tauri.conf.json').version")
TAG="v${VERSION}"
if [ "${{ runner.os }}" = "Linux" ]; then
TARGET="linux-x86_64"
BUNDLE=$(find src-tauri/target/release/bundle/appimage -name "*.AppImage" | head -1)
elif [ "${{ runner.os }}" = "macOS" ]; then
ARCH=$(uname -m)
if [ "$ARCH" = "arm64" ]; then
TARGET="darwin-aarch64"
MAC_ARCH="aarch64"
else
TARGET="darwin-x86_64"
MAC_ARCH="x64"
fi
# Tauri names the updater bundle generically (RexaDB.app.tar.gz).
# Rename it to the 1.3.5 convention so release + latest.json use
# versioned, arch-specific filenames and the two macOS arches don't collide.
ORIG_BUNDLE=$(find src-tauri/target/release/bundle/macos -name "*.app.tar.gz" | head -1)
if [ -n "$ORIG_BUNDLE" ] && [ -f "${ORIG_BUNDLE}.sig" ]; then
BUNDLE_DIR="$(dirname "$ORIG_BUNDLE")"
BUNDLE="${BUNDLE_DIR}/RexaDB_${VERSION}_${MAC_ARCH}.app.tar.gz"
mv "$ORIG_BUNDLE" "$BUNDLE"
mv "${ORIG_BUNDLE}.sig" "${BUNDLE}.sig"
echo "Renamed updater bundle to ${BUNDLE}"
else
BUNDLE=""
fi
else
TARGET="windows-x86_64"
BUNDLE=$(find src-tauri/target/release/bundle/nsis -name "*.exe" | head -1)
fi
SIG="${BUNDLE}.sig"
if [ -n "$BUNDLE" ] && [ -f "$SIG" ]; then
SIGNATURE=$(cat "$SIG")
PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
SNIPPET="src-tauri/target/release/platform.${TARGET}.json"
URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/${BUNDLE##*/}"
printf '{\n "version": "%s",\n "pub_date": "%s",\n "platforms": {\n "%s": {\n "url": "%s",\n "signature": "%s"\n }\n }\n}\n' \
"$VERSION" "$PUB_DATE" "$TARGET" "$URL" "$SIGNATURE" > "$SNIPPET"
echo "Generated platform snippet for ${TARGET}"
echo " URL: ${URL}"
else
echo "No updater bundle/sig found, skipping platform snippet"
fi
- name: Sign DMG for release (macOS only)
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
# 1.3.5 ships a Tauri updater signature for each .dmg; produce one to match.
printf '%s' "$TAURI_SIGNING_PRIVATE_KEY" > /tmp/rexadb-dmg-sign-key
unset TAURI_SIGNING_PRIVATE_KEY
export TAURI_SIGNING_PRIVATE_KEY_PATH=/tmp/rexadb-dmg-sign-key
DMG_DIR="src-tauri/target/release/bundle/dmg"
for dmg in "$DMG_DIR"/*.dmg; do
[ -e "$dmg" ] || { echo "no dmg found in $DMG_DIR"; exit 1; }
if [ -f "${dmg}.sig" ]; then
echo "already signed: ${dmg}.sig"
continue
fi
echo "Signing ${dmg} for updater..."
"$GITHUB_WORKSPACE/node_modules/.bin/tauri" signer sign \
-p "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" "$dmg"
echo "Signed: ${dmg}.sig"
done
- name: Smoke test
shell: bash
run: |
set -euo pipefail
SIDECAR_OK=false
if [ "$RUNNER_OS" = "Windows" ]; then
BINARY="src-tauri/target/release/rexa-db.exe"
else
BINARY="src-tauri/target/release/rexa-db"
fi
echo "════════════════════════════════════════════"
echo " SMOKE TEST — ${RUNNER_OS}"
echo "════════════════════════════════════════════"
if [ ! -f "$BINARY" ]; then
echo " ❌ FAIL: Binary not found at ${BINARY}"
exit 1
fi
echo " Binary: ${BINARY}"
file "$BINARY" 2>/dev/null || true
echo ""
LOGFILE="/tmp/rexadb-smoke.${RUNNER_OS}.log"
# Set up resource paths so unbundled binary can find bridge.jar/jre
export RESOURCEDIR="${GITHUB_WORKSPACE}/resources/java-bridge/dist"
# Copy sidecar alongside the binary for Tauri shell plugin resolution
if [ "$RUNNER_OS" = "Linux" ] && [ -f "src-tauri/binaries/rexadb-server-x86_64-unknown-linux-gnu" ]; then
cp "src-tauri/binaries/rexadb-server-x86_64-unknown-linux-gnu" \
"$(dirname "$BINARY")/rexadb-server-x86_64-unknown-linux-gnu"
elif [ "$RUNNER_OS" = "Windows" ] && [ -f "src-tauri/binaries/rexadb-server-x86_64-pc-windows-msvc.exe" ]; then
cp "src-tauri/binaries/rexadb-server-x86_64-pc-windows-msvc.exe" \
"$(dirname "$BINARY")/rexadb-server-x86_64-pc-windows-msvc.exe"
fi
if [ "$RUNNER_OS" = "Linux" ]; then
echo " Launching app via xvfb-run (timeout: 12s)..."
EXIT_CODE=0
export WEBKIT_DISABLE_DMABUF_RENDERER=1
timeout 12 xvfb-run -a "$BINARY" > "$LOGFILE" 2>&1 || EXIT_CODE=$?
else
echo " Launching app (timeout: 10s)..."
"$BINARY" > "$LOGFILE" 2>&1 &
APP_PID=$!
echo " PID: ${APP_PID}"
SIDECAR_OK=false
for i in $(seq 1 9); do
if curl -sf http://127.0.0.1:3867/health > /dev/null 2>&1; then
echo " ✅ Sidecar /health responded on port 3867 (took ~${i}s)"
SIDECAR_OK=true
break
fi
sleep 1
done
if [ "$SIDECAR_OK" = "false" ]; then
echo " ⚠️ Sidecar /health did NOT respond (may be blocked by Defender)"
fi
sleep 1
if kill -0 $APP_PID 2>/dev/null; then
kill $APP_PID 2>/dev/null || true
wait $APP_PID 2>/dev/null || true
EXIT_CODE=0
else
wait $APP_PID 2>/dev/null || true
EXIT_CODE=$?
fi
fi
echo ""
echo " ── Result ──"
if [ "$EXIT_CODE" -eq 124 ] || [ "$EXIT_CODE" -eq 0 ]; then
echo " ✅ PASS: App stayed alive for the full test duration"
if [ "$SIDECAR_OK" = "true" ]; then
echo " ✅ PASS: Sidecar health endpoint responded"
else
echo " ⚠️ WARN: Sidecar health endpoint did NOT respond"
fi
RESULT=0
else
echo " ❌ FAIL: App exited with code ${EXIT_CODE}"
RESULT=$EXIT_CODE
fi
echo ""
echo "════════════════════════════════════════════"
echo " APPLICATION LOG OUTPUT"
echo "════════════════════════════════════════════"
if [ -s "$LOGFILE" ]; then
cat "$LOGFILE"
else
echo " (no output captured)"
fi
echo "════════════════════════════════════════════"
exit $RESULT
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact-name }}
path: |
src-tauri/target/release/bundle/**/*.exe
src-tauri/target/release/bundle/**/*.dmg
src-tauri/target/release/bundle/**/*.app.tar.gz
src-tauri/target/release/bundle/**/*.sig
src-tauri/target/release/platform.*.json
if-no-files-found: error
- name: Upload to release
if: startsWith(github.ref, 'refs/tags/')
shell: bash
env:
GH_TOKEN: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
TAG="${GITHUB_REF_NAME}"
REPO="${{ github.repository }}"
echo "Uploading artifacts for ${TAG} to ${REPO}..."
FILES=()
while IFS= read -r f; do FILES+=("$f"); done < <(
find src-tauri/target/release/bundle \
\( -name "*.deb" -o -name "*.AppImage" -o -name "*.exe" \
-o -name "*.msi" -o -name "*.rpm" -o -name "*.dmg" \
-o -name "*.app.tar.gz" -o -name "*.sig" \) -type f \
! -name "control.tar.gz" ! -name "data.tar.gz" 2>/dev/null
)
if [ ${#FILES[@]} -eq 0 ]; then
echo "No artifacts found to upload"
exit 1
fi
echo "Found ${#FILES[@]} artifact(s):"
printf ' %s\n' "${FILES[@]}"
# Upload directly; create draft release if it doesn't exist yet
gh release upload "$TAG" --repo "$REPO" --clobber "${FILES[@]}" 2>&1 && exit 0
echo "Release doesn't exist yet, creating draft..."
gh release create "$TAG" --repo "$REPO" \
--title "RexaDB ${TAG}" --draft --generate-notes 2>&1 || true
gh release upload "$TAG" --repo "$REPO" --clobber "${FILES[@]}" 2>&1
merge:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: build
permissions:
contents: write
env:
GH_TOKEN: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: Assemble latest.json and SHA256SUMS.txt
shell: bash
run: |
set -euo pipefail
VERSION=$(node -p "require('./src-tauri/tauri.conf.json').version")
TAG="v${VERSION}"
REPO="${{ github.repository }}"
ART_DIR="artifacts"
# Merge the per-platform updater snippets into a single latest.json
SNIPPETS=( "$ART_DIR"/platform.*.json )
if [ ${#SNIPPETS[@]} -eq 0 ] || [ ! -f "${SNIPPETS[0]}" ]; then
echo "No platform snippets found"
exit 1
fi
TMP_JSON=$(mktemp)
node -e "
const fs=require('fs');
const j={version:'$VERSION',notes:'RexaDB $VERSION',pub_date:'$(date -u +'%Y-%m-%dT%H:%M:%SZ')',platforms:{}};
fs.writeFileSync('$TMP_JSON',JSON.stringify(j,null,2)+'\n');
"
# merge_one() reads a snippet file (any path) and folds its platform
# entry into $TMP_JSON. Parsing uses fs.readFileSync+JSON.parse so it
# works regardless of file extension or absolute/relative path.
merge_one() {
local SNIP_FILE="$1"
local TMP="$2"
local target url sig
target=$(node -e "
const s=JSON.parse(require('fs').readFileSync('$SNIP_FILE','utf8'));
process.stdout.write(Object.keys(s.platforms)[0]);
")
url=$(node -e "
const s=JSON.parse(require('fs').readFileSync('$SNIP_FILE','utf8'));
process.stdout.write(s.platforms['$target'].url);
")
sig=$(node -e "
const s=JSON.parse(require('fs').readFileSync('$SNIP_FILE','utf8'));
process.stdout.write(s.platforms['$target'].signature);
")
node -e "
const fs=require('fs');
const j=JSON.parse(fs.readFileSync('$TMP','utf8'));
j.platforms['$target']={url:'$url',signature:'$sig'};
fs.writeFileSync('$TMP',JSON.stringify(j,null,2)+'\n');
"
echo "Merged $target"
}
for snip in "${SNIPPETS[@]}"; do
merge_one "$snip" "$TMP_JSON"
done
# Fetch the Linux snippet produced by the separate Arch AppImage workflow.
# That workflow runs concurrently, so poll the release until it appears
# (up to ~8 min) before giving up on the linux-x86_64 updater entry.
LINUX_SNIP=$(mktemp)
for attempt in $(seq 1 48); do
if gh release download "$TAG" --repo "$REPO" --pattern "platform.linux-x86_64.json" --dir /tmp/linux-snip --clobber >/dev/null 2>&1 && \
[ -s /tmp/linux-snip/platform.linux-x86_64.json ]; then
cp /tmp/linux-snip/platform.linux-x86_64.json "$LINUX_SNIP"
echo "Found Linux snippet (attempt ${attempt})"
break
fi
sleep 10
done
if [ -s "$LINUX_SNIP" ]; then
merge_one "$LINUX_SNIP" "$TMP_JSON"
echo "linux-x86_64 merged from release"
else
echo "WARNING: Linux updater snippet not found on release; latest.json will lack linux-x86_64"
fi
node -e "
const fs=require('fs');
const j=JSON.parse(fs.readFileSync('$TMP_JSON','utf8'));
j.platforms=Object.fromEntries(Object.entries(j.platforms).sort());
fs.writeFileSync('$TMP_JSON',JSON.stringify(j,null,2)+'\n');
"
cp "$TMP_JSON" latest.json
echo "latest.json written ($(node -e "console.log(Object.keys(JSON.parse(require('fs').readFileSync('latest.json','utf8')).platforms).length)") platforms)"
# Download the Linux artifacts produced by the separate Arch AppImage
# workflow (AppImage, its sig, and .deb) so they can be hashed too.
# That workflow runs concurrently, so poll the release until all three
# appear (up to ~8 min). NOTE: the case must include *.AppImage.sig —
# v1.3.6 shipped a 13-line SHA256SUMS.txt because the old
# '*.AppImage|*.deb' pattern silently skipped the .sig.
mkdir -p /tmp/dl
for attempt in $(seq 1 48); do
while IFS= read -r name; do
case "$name" in
*.AppImage|*.AppImage.sig|*.deb)
if [ ! -s "/tmp/dl/$name" ]; then
gh release download "$TAG" --repo "$REPO" --pattern "$name" --dir /tmp/dl --clobber >/dev/null 2>&1 || true
fi
;;
esac
done < <(gh release view "$TAG" --repo "$REPO" --json assets --jq '.assets[].name' 2>/dev/null | grep -E '\.AppImage$|\.AppImage\.sig$|\.deb$' || true)
if [ -s "/tmp/dl/RexaDB_${VERSION}_x86_64.AppImage" ] && \
[ -s "/tmp/dl/RexaDB_${VERSION}_x86_64.AppImage.sig" ] && \
[ -s "/tmp/dl/RexaDB_${VERSION}_amd64.deb" ]; then
echo "All 3 Linux assets present (attempt ${attempt})"
break
fi
sleep 10
done
echo "Linux assets in /tmp/dl:"
ls -la /tmp/dl/ 2>/dev/null || true
# Generate SHA256SUMS.txt over every release asset (1.3.5 format):
# all binaries + their .sig files + latest.json, sorted by filename.
# Excludes platform.*.json (not shipped) and SHA256SUMS.txt itself.
: > SHA256SUMS.txt
add_hashes() {
local dir="$1"
find "$dir" -type f \
! -name "platform.*.json" \
! -name "SHA256SUMS.txt" \
! -name "control.tar.gz" ! -name "data.tar.gz" 2>/dev/null | while IFS= read -r f; do
case "$f" in
*.dmg|*.dmg.sig|*.app.tar.gz|*.app.tar.gz.sig|*.exe|*.exe.sig|*.AppImage|*.AppImage.sig|*.deb)
base=$(basename "$f")
printf '%s %s\n' "$(sha256sum "$f" | cut -d' ' -f1)" "$base" >> SHA256SUMS.txt
;;
esac
done
}
add_hashes "$ART_DIR"
add_hashes /tmp/dl
# Hash the just-assembled latest.json (matches 1.3.5 which lists it)
printf '%s %s\n' "$(sha256sum latest.json | cut -d' ' -f1)" "latest.json" >> SHA256SUMS.txt
sort -k2 -o SHA256SUMS.txt SHA256SUMS.txt
echo "SHA256SUMS.txt lines: $(wc -l < SHA256SUMS.txt)"
echo "Uploading latest.json + SHA256SUMS.txt to $REPO $TAG..."
gh release upload "$TAG" --repo "$REPO" --clobber latest.json SHA256SUMS.txt 2>&1
# Delete the temporary per-platform snippet carriers so the final
# release contains exactly the 1.3.5 artifact set (no platform.*.json).
for name in $(gh release view "$TAG" --repo "$REPO" --json assets --jq '.assets[].name' 2>/dev/null | grep '^platform\.' || true); do
gh release delete-asset "$TAG" --repo "$REPO" "$name" 2>&1 || true
done