Skip to content

feat: eliminate useEffect anti-patterns — TanStack Query + declarativ… #483

feat: eliminate useEffect anti-patterns — TanStack Query + declarativ…

feat: eliminate useEffect anti-patterns — TanStack Query + declarativ… #483

Workflow file for this run

name: Build
permissions:
contents: read
packages: write
on:
push:
tags:
- "*"
jobs:
build:
runs-on: ubuntu-latest
environment: Dev
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
###############################################################
## Frontend
###############################################################
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
working-directory: ./frontend
run: npm ci
- name: Create public directory
working-directory: ./backend
run: mkdir -p src/public
- name: Build frontend
working-directory: ./frontend
run: npm run build
- name: Prune frontend dependencies
working-directory: ./frontend
run: npm prune --production
- name: Save Node dependencies cache
uses: actions/cache@v4
with:
path: |
**/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
###############################################################
## Backend
###############################################################
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Cache Python dependencies
id: cache-python
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
working-directory: ./backend
run: |
env | sort
pip install --upgrade pip
pip install uv
uv sync --frozen --no-cache --no-dev --extra api
- name: Save Python dependencies cache
if: steps.cache-python.outputs.cache-hit != 'true'
id: save-cache-python
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
###############################################################
## Docker
###############################################################
- name: Copy Docker README and LICENSE to backend
run: |
cp docker/README.md backend/README.md
cp LICENSE backend/LICENSE
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USERNAME || github.actor }}
password: ${{ secrets.GHCR_PAT }}
- name: Build and push API image
working-directory: ./backend
run: |
TAG=${GITHUB_REF#refs/tags/}
docker build --target api \
-t ghcr.io/ruska-ai/orchestra-api:$TAG \
-t ghcr.io/ruska-ai/orchestra-api:latest \
-t ghcr.io/ruska-ai/orchestra:$TAG \
-t ghcr.io/ruska-ai/orchestra:latest \
.
docker push ghcr.io/ruska-ai/orchestra-api:$TAG
docker push ghcr.io/ruska-ai/orchestra-api:latest
docker push ghcr.io/ruska-ai/orchestra:$TAG
docker push ghcr.io/ruska-ai/orchestra:latest
- name: Build and push Worker image
working-directory: ./backend
run: |
TAG=${GITHUB_REF#refs/tags/}
docker build --target worker \
-t ghcr.io/ruska-ai/orchestra-worker:$TAG \
-t ghcr.io/ruska-ai/orchestra-worker:latest \
.
docker push ghcr.io/ruska-ai/orchestra-worker:$TAG
docker push ghcr.io/ruska-ai/orchestra-worker:latest
- name: Report image sizes
run: |
echo "## Docker Image Sizes" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Image | Size |" >> $GITHUB_STEP_SUMMARY
echo "|-------|------|" >> $GITHUB_STEP_SUMMARY
docker images ghcr.io/ruska-ai/orchestra-api --format "| API | {{.Size}} |" | head -1 >> $GITHUB_STEP_SUMMARY
docker images ghcr.io/ruska-ai/orchestra-worker --format "| Worker | {{.Size}} |" | head -1 >> $GITHUB_STEP_SUMMARY
deploy:
needs: build
runs-on: ubuntu-latest
environment: Dev
steps:
- name: Deploy to VM
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_USER: ${{ secrets.SSH_USER }}
GHCR_USERNAME: ${{ secrets.GHCR_USERNAME || github.actor }}
GHCR_TOKEN: ${{ secrets.GHCR_PAT }}
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/orchestra
run: |
# ---------------------------------------------------
# Setup SSH
# ---------------------------------------------------
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan github.com >> ~/.ssh/known_hosts
ssh-keyscan -H $SSH_HOST >> ~/.ssh/known_hosts
TAG=${{ github.ref_name }}
# ---------------------------------------------------
# SSH into the server and execute deployment
# ---------------------------------------------------
ssh -i ~/.ssh/id_rsa $SSH_USER@$SSH_HOST "
set -e # Exit on error
cd ~/agent_api
echo '--- Logging into GHCR ---'
echo \"$GHCR_TOKEN\" | docker login ghcr.io -u \"$GHCR_USERNAME\" --password-stdin
echo '--- Pulling new images ---'
docker pull $GHCR_IMAGE-api:$TAG
docker pull $GHCR_IMAGE-worker:$TAG
echo '--- Starting staging container (graphchat_new) on port 8006 ---'
docker stop graphchat_new 2>/dev/null || true
docker rm graphchat_new 2>/dev/null || true
docker run -d \
--name graphchat_new \
--network graphchat_default \
--env-file ./backend/.env \
-p 8006:8000 \
$GHCR_IMAGE-api:$TAG
echo '--- Waiting a few seconds for startup ---'
sleep 10
echo '--- Health check on staging container (graphchat_new) ---'
for i in {1..3}; do
if curl -f http://localhost:8006/api/info; then
echo 'New container on port 8006 is healthy!'
break
else
echo \"Attempt \$i failed. Waiting 10 seconds before retry...\"
sleep 10
if [ \$i -eq 3 ]; then
echo 'All retry attempts failed.'
exit 1
fi
fi
done
echo '--- Stopping and removing old container (graphchat) if it exists ---'
docker stop graphchat || true
docker rm graphchat || true
echo '--- Stopping and removing staging container (graphchat_new) ---'
docker stop graphchat_new 2>/dev/null || true
docker rm graphchat_new 2>/dev/null || true
echo '--- Running new container on production port (8005) as graphchat ---'
docker run -d \
--name graphchat \
--network graphchat_default \
--restart always \
--env-file ./backend/.env \
-e APP_VERSION=$TAG \
-e VITE_APP_VERSION=$TAG \
-p 8005:8000 \
$GHCR_IMAGE-api:$TAG
echo '--- Ensuring redis7 is on graphchat_default network ---'
docker network connect graphchat_default redis7 2>/dev/null || echo 'redis7 already on network or not found'
echo '--- Stopping and removing old worker container if it exists ---'
docker stop graphchat_worker 2>/dev/null || true
docker rm graphchat_worker 2>/dev/null || true
echo '--- Starting worker container ---'
docker run -d \
--name graphchat_worker \
--network graphchat_default \
--restart always \
--env-file ./backend/.env \
-e DISTRIBUTED_WORKERS=true \
-e REDIS_URL=redis://redis7:6379/0 \
-e DB_POOL_MIN_SIZE=1 \
-e DB_POOL_MAX_SIZE=5 \
--memory=1g \
--cpus=1 \
$GHCR_IMAGE-worker:$TAG
echo '--- Worker deployment complete ---'
echo '--- Cleaning up old images ---'
docker system prune -a --filter \"until=1h\" -f
echo '--- Deployment successful! ---'
"