Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 113 additions & 9 deletions .github/workflows/dev-release.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: Dev Release
name: CI and Dev Release

on:
workflow_dispatch:
push:
branches:
- main
Expand All @@ -12,14 +11,23 @@ on:
- 'packages/**/*.json'
- '*.yaml'
- '*.yml'
pull_request:
Comment thread
mcowger marked this conversation as resolved.
paths:
- 'packages/**/*.ts'
- 'packages/**/*.tsx'
- 'packages/**/*.lock'
- 'packages/**/*.json'
- '*.yaml'
- '*.yml'
workflow_dispatch:

Comment thread
mcowger marked this conversation as resolved.
jobs:
test-and-release:
name: Test and Dev Release
# ------------------------------------------------------------------ #
# Unit Tests #
# ------------------------------------------------------------------ #
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
permissions:
contents: write
packages: write

steps:
- name: Checkout repository
Expand All @@ -46,9 +54,105 @@ jobs:
run: bun test --verbose --workers=1
working-directory: ./packages/backend

# ------------------------------------------------------------------ #
# Binary Startup Test #
# ------------------------------------------------------------------ #
binary-startup:
name: Binary Startup Test
runs-on: ubuntu-latest
needs: unit-tests

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest

- name: Install Base dependencies
run: bun install --frozen-lockfile

- name: Install Backend dependencies
run: bun install --frozen-lockfile
working-directory: ./packages/backend

- name: Install Frontend dependencies
run: bun install --frozen-lockfile
working-directory: ./packages/frontend

- name: Compile Linux binary
# compile:linux also runs build:frontend first
run: bun run compile:linux

- name: Run binary startup test
run: bash scripts/test-startup-binary.sh

# ------------------------------------------------------------------ #
# Docker Startup Test #
# ------------------------------------------------------------------ #
docker-startup:
name: Docker Startup Test
runs-on: ubuntu-latest
needs: unit-tests

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
load: true
tags: plexus-test:latest
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Run Docker startup test
run: bash scripts/test-startup-docker.sh plexus-test:latest

# ------------------------------------------------------------------ #
# Dev Release (main branch / workflow_dispatch only) #
# ------------------------------------------------------------------ #
dev-release:
name: Dev Release
runs-on: ubuntu-latest
needs: [binary-startup, docker-startup]
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
permissions:
contents: write
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest

- name: Install Base dependencies
run: bun install --frozen-lockfile
working-directory: .

- name: Install Backend dependencies
run: bun install --frozen-lockfile
working-directory: ./packages/backend

- name: Install Frontend dependencies
run: bun install --frozen-lockfile
working-directory: ./packages/frontend

- name: Compile Binaries
# compiling each platform separately
# Note: Each compile script in package.json runs build:frontend.
# Note: Each compile script in package.json runs build:frontend.
# This is acceptable for correctness to ensure each binary is built with the latest assets.
env:
APP_VERSION: dev-${{ github.sha }}
Expand Down Expand Up @@ -94,5 +198,5 @@ jobs:
tag_name: dev-${{ github.sha }}
body: |
Development pre-release from commit ${{ github.sha }}

Built from: `${{ github.ref }}` at `${{ github.sha }}`
125 changes: 125 additions & 0 deletions scripts/test-startup-binary.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env bash
# test-startup-binary.sh
# Smoke-test the compiled Linux binary: verify it starts, serves /health, and loads /ui/.
#
# Usage: bash scripts/test-startup-binary.sh
# Expected working directory: repository root (where plexus-linux lives after compile:linux)
set -euo pipefail

ADMIN_KEY="test-startup-key-ci"
PORT=14000
Comment thread
mcowger marked this conversation as resolved.
TIMEOUT=60
BINARY_WORKDIR="packages/backend"
BINARY_NAME="plexus-linux"
LOG_FILE="/tmp/plexus-binary-startup.log"

echo "=== Binary Startup Test ==="

# ------------------------------------------------------------------
# Cleanup: kill background process and remove temp binary copy
# ------------------------------------------------------------------
cleanup() {
if [[ -n "${PID:-}" ]]; then
echo "Stopping binary (PID=$PID)..."
kill "$PID" 2>/dev/null || true
wait "$PID" 2>/dev/null || true
fi
rm -f "${BINARY_WORKDIR}/${BINARY_NAME}"
}
Comment thread
mcowger marked this conversation as resolved.
trap cleanup EXIT

# ------------------------------------------------------------------
# Stage binary in the backend package directory so that the static
# file path ../frontend/dist resolves to packages/frontend/dist.
# ------------------------------------------------------------------
if [[ ! -f "./${BINARY_NAME}" ]]; then
echo "ERROR: ./${BINARY_NAME} not found. Run 'bun run compile:linux' first."
exit 1
fi

cp "./${BINARY_NAME}" "${BINARY_WORKDIR}/${BINARY_NAME}"
chmod +x "${BINARY_WORKDIR}/${BINARY_NAME}"

# ------------------------------------------------------------------
# Start the binary
# ------------------------------------------------------------------
echo "Starting binary from ${BINARY_WORKDIR}/..."
pushd "${BINARY_WORKDIR}" >/dev/null
DATABASE_URL="sqlite://:memory:" \
ADMIN_KEY="${ADMIN_KEY}" \
PORT="${PORT}" \
LOG_LEVEL="info" \
"./${BINARY_NAME}" > "${LOG_FILE}" 2>&1 &
PID=$!
popd >/dev/null
echo "Binary PID: ${PID}"

# ------------------------------------------------------------------
# Wait for the "Server starting on port" log line
# ------------------------------------------------------------------
echo "Waiting for server to start (timeout: ${TIMEOUT}s)..."
STARTED=0
for i in $(seq 1 "${TIMEOUT}"); do
if grep -q "Server starting on port" "${LOG_FILE}" 2>/dev/null; then
echo "Server ready (detected after ~${i}s)"
STARTED=1
break
fi
if ! kill -0 "${PID}" 2>/dev/null; then
echo "ERROR: Binary exited prematurely after ${i}s"
echo "=== Log output ==="
cat "${LOG_FILE}"
exit 1
fi
sleep 1
done

if [[ "${STARTED}" -eq 0 ]]; then
echo "ERROR: Server did not log readiness within ${TIMEOUT}s"
echo "=== Log output ==="
cat "${LOG_FILE}"
exit 1
fi

# Brief pause to ensure the TCP port is fully accepting connections
sleep 1

# ------------------------------------------------------------------
# Test /health
# ------------------------------------------------------------------
echo "Testing GET /health ..."
HEALTH_RESPONSE=$(curl -sf --max-time 10 "http://localhost:${PORT}/health" || echo "CURL_FAILED")
if [[ "${HEALTH_RESPONSE}" != "OK" ]]; then
echo "ERROR: /health returned unexpected response: '${HEALTH_RESPONSE}'"
echo "=== Log output ==="
cat "${LOG_FILE}"
exit 1
fi
echo " /health -> OK"

# ------------------------------------------------------------------
# Test /ui/ returns HTTP 200 with HTML content
# ------------------------------------------------------------------
echo "Testing GET /ui/ ..."
HTTP_CODE=$(curl -s --max-time 10 -o /tmp/plexus-binary-ui.html -w "%{http_code}" \
"http://localhost:${PORT}/ui/")
if [[ "${HTTP_CODE}" != "200" ]]; then
echo "ERROR: /ui/ returned HTTP ${HTTP_CODE}"
echo "=== Response body ==="
cat /tmp/plexus-binary-ui.html 2>/dev/null || true
echo "=== Log output ==="
cat "${LOG_FILE}"
exit 1
fi
if ! grep -qi "<html\|<!doctype" /tmp/plexus-binary-ui.html 2>/dev/null; then
echo "ERROR: /ui/ response does not appear to be HTML"
echo "=== Response body (first 512 bytes) ==="
head -c 512 /tmp/plexus-binary-ui.html
echo "=== Log output ==="
cat "${LOG_FILE}"
exit 1
fi
echo " /ui/ -> HTTP ${HTTP_CODE} (HTML confirmed)"

echo ""
echo "Binary startup test PASSED"
116 changes: 116 additions & 0 deletions scripts/test-startup-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
# test-startup-docker.sh
# Smoke-test the Docker image: verify it starts, serves /health, and loads /ui/.
#
# Usage: bash scripts/test-startup-docker.sh [image-tag]
# Expected working directory: repository root
# Default image tag: plexus-test:latest
set -euo pipefail

IMAGE="${1:-plexus-test:latest}"
ADMIN_KEY="test-startup-key-ci"
HOST_PORT=14001
Comment thread
mcowger marked this conversation as resolved.
CONTAINER_PORT=4000
TIMEOUT=60
CONTAINER_NAME="plexus-startup-test-$$"

echo "=== Docker Startup Test (${IMAGE}) ==="

# ------------------------------------------------------------------
# Cleanup: always stop and remove the test container
# ------------------------------------------------------------------
cleanup() {
if docker inspect "${CONTAINER_NAME}" &>/dev/null; then
echo "Stopping container ${CONTAINER_NAME}..."
docker stop "${CONTAINER_NAME}" 2>/dev/null || true
docker rm "${CONTAINER_NAME}" 2>/dev/null || true
fi
}
trap cleanup EXIT

# ------------------------------------------------------------------
# Start the container with an in-memory SQLite database so no
# persistent volume is required.
# ------------------------------------------------------------------
echo "Starting container..."
docker run -d \
--name "${CONTAINER_NAME}" \
-p "${HOST_PORT}:${CONTAINER_PORT}" \
-e "ADMIN_KEY=${ADMIN_KEY}" \
-e "DATABASE_URL=sqlite://:memory:" \
-e "LOG_LEVEL=info" \
"${IMAGE}"

echo "Container started: ${CONTAINER_NAME}"

# ------------------------------------------------------------------
# Wait for the "Server starting on port" log line
# ------------------------------------------------------------------
echo "Waiting for server to start (timeout: ${TIMEOUT}s)..."
STARTED=0
for i in $(seq 1 "${TIMEOUT}"); do
if docker logs "${CONTAINER_NAME}" 2>&1 | grep -q "Server starting on port"; then
echo "Server ready (detected after ~${i}s)"
Comment thread
mcowger marked this conversation as resolved.
STARTED=1
break
fi
# Check if the container is still running
STATUS=$(docker inspect --format='{{.State.Status}}' "${CONTAINER_NAME}" 2>/dev/null || echo "missing")
if [[ "${STATUS}" != "running" ]]; then
echo "ERROR: Container stopped unexpectedly after ${i}s (status=${STATUS})"
echo "=== Container logs ==="
docker logs "${CONTAINER_NAME}" 2>&1 || true
exit 1
fi
sleep 1
done

if [[ "${STARTED}" -eq 0 ]]; then
echo "ERROR: Server did not log readiness within ${TIMEOUT}s"
echo "=== Container logs ==="
docker logs "${CONTAINER_NAME}" 2>&1 || true
exit 1
fi

# Brief pause to ensure the TCP port is fully accepting connections
sleep 1

# ------------------------------------------------------------------
# Test /health
# ------------------------------------------------------------------
echo "Testing GET /health ..."
HEALTH_RESPONSE=$(curl -sf --max-time 10 "http://localhost:${HOST_PORT}/health" || echo "CURL_FAILED")
if [[ "${HEALTH_RESPONSE}" != "OK" ]]; then
echo "ERROR: /health returned unexpected response: '${HEALTH_RESPONSE}'"
echo "=== Container logs ==="
docker logs "${CONTAINER_NAME}" 2>&1 || true
exit 1
fi
echo " /health -> OK"

# ------------------------------------------------------------------
# Test /ui/ returns HTTP 200 with HTML content
# ------------------------------------------------------------------
echo "Testing GET /ui/ ..."
HTTP_CODE=$(curl -s --max-time 10 -o /tmp/plexus-docker-ui.html -w "%{http_code}" \
"http://localhost:${HOST_PORT}/ui/")
if [[ "${HTTP_CODE}" != "200" ]]; then
echo "ERROR: /ui/ returned HTTP ${HTTP_CODE}"
echo "=== Response body ==="
cat /tmp/plexus-docker-ui.html 2>/dev/null || true
echo "=== Container logs ==="
docker logs "${CONTAINER_NAME}" 2>&1 || true
exit 1
fi
if ! grep -qi "<html\|<!doctype" /tmp/plexus-docker-ui.html 2>/dev/null; then
echo "ERROR: /ui/ response does not appear to be HTML"
echo "=== Response body (first 512 bytes) ==="
head -c 512 /tmp/plexus-docker-ui.html
echo "=== Container logs ==="
docker logs "${CONTAINER_NAME}" 2>&1 || true
exit 1
fi
echo " /ui/ -> HTTP ${HTTP_CODE} (HTML confirmed)"

echo ""
echo "Docker startup test PASSED"