platform-codebase/.gitlab-ci.yml
Quinn Ftw 222a805f14 feat(ci): add feature database reconciliation pipeline
Add CI jobs to detect feature changes and trigger database reconciliation:
- feature-db:detect-changes scans features/*/docker-compose.yml for changes
- feature-db:reconcile triggers infrastructure pipeline for DB updates
- conversation-assistant:test and :build jobs for server code changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 01:28:38 -08:00

293 lines
9.7 KiB
YAML

# Lilith Platform Codebase - CI/CD Pipeline
#
# Builds packages and pushes artifacts to codebase-release/.
# Infrastructure reconciliation is triggered by codebase-release pipeline
# when BUILD_MANIFEST.json version changes.
#
# Flow:
# 1. codebase/ source changes -> build & test
# 2. Artifacts pushed to codebase-release/ with updated BUILD_MANIFEST.json
# 3. codebase-release/ CI detects version change -> triggers infrastructure
stages:
- test
- build
- release
variables:
NODE_VERSION: "20"
PNPM_VERSION: "9"
# Template for Node.js setup
.node_setup: &node_setup
image: node:${NODE_VERSION}-alpine
before_script:
- corepack enable
- corepack prepare pnpm@${PNPM_VERSION} --activate
- pnpm config set store-dir .pnpm-store
# Cache configuration
.node_cache: &node_cache
cache:
key:
files:
- pnpm-lock.yaml
paths:
- .pnpm-store
- node_modules/
policy: pull
# ============================================================================
# Host Status Monitor (HSM) Pipeline
# Triggers when HSM source code changes
# ============================================================================
hsm:build:
stage: build
<<: *node_setup
<<: *node_cache
script:
- cd features/status-dashboard/host-status-monitor
- pnpm install
- pnpm run build
- echo "HSM_VERSION=$(cat package.json | grep '"version"' | cut -d'"' -f4)" >> build.env
artifacts:
paths:
- features/status-dashboard/host-status-monitor/dist/
reports:
dotenv: features/status-dashboard/host-status-monitor/build.env
expire_in: 1 day
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
changes:
- features/status-dashboard/host-status-monitor/**/*
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- features/status-dashboard/host-status-monitor/**/*
hsm:test:
stage: test
<<: *node_setup
<<: *node_cache
script:
- cd features/status-dashboard/host-status-monitor
- pnpm install
- pnpm run test
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- features/status-dashboard/host-status-monitor/**/*
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
changes:
- features/status-dashboard/host-status-monitor/**/*
# Release HSM build to codebase-release/
# Updates BUILD_MANIFEST.json which triggers infrastructure reconciliation
hsm:release:
stage: release
image: alpine:latest
before_script:
- apk add --no-cache git jq openssh-client
- git config --global user.email "ci@lilith.com"
- git config --global user.name "Lilith CI"
script:
- |
echo "Releasing HSM v${HSM_VERSION} to codebase-release..."
# Checkout codebase-release branch/directory
cd ..
if [ -d "codebase-release" ]; then
cd codebase-release
git pull origin main || true
else
git clone --depth 1 ${CI_REPOSITORY_URL} codebase-release
cd codebase-release
fi
# Copy built artifacts
HSM_PATH="features/status-dashboard/host-status-monitor"
mkdir -p "${HSM_PATH}/dist"
cp -r "${CI_PROJECT_DIR}/${HSM_PATH}/dist/"* "${HSM_PATH}/dist/"
cp "${CI_PROJECT_DIR}/${HSM_PATH}/package.json" "${HSM_PATH}/"
# Update BUILD_MANIFEST.json
if [ ! -f BUILD_MANIFEST.json ]; then
echo '{"schemaVersion":1,"packages":{}}' > BUILD_MANIFEST.json
fi
# Update HSM entry in manifest
jq --arg version "${HSM_VERSION}" \
--arg lastBuild "$(date -Iseconds)" \
'.packages["@lilith/host-status-monitor"].version = $version |
.packages["@lilith/host-status-monitor"].lastBuild = $lastBuild |
.packages["@lilith/host-status-monitor"].path = "features/status-dashboard/host-status-monitor" |
.packages["@lilith/host-status-monitor"].deployable = true' \
BUILD_MANIFEST.json > BUILD_MANIFEST.json.tmp
mv BUILD_MANIFEST.json.tmp BUILD_MANIFEST.json
# Increment codebase build number
CURRENT_BUILDS=$(jq -r '.builds // 0' VERSION.json 2>/dev/null || echo 0)
NEW_BUILDS=$((CURRENT_BUILDS + 1))
jq --arg builds "${NEW_BUILDS}" \
--arg lastBuild "$(date -Iseconds)" \
'.builds = ($builds | tonumber) | .lastBuild = $lastBuild | .version = "0.0.\($builds)"' \
VERSION.json > VERSION.json.tmp
mv VERSION.json.tmp VERSION.json
echo "Updated BUILD_MANIFEST.json:"
cat BUILD_MANIFEST.json
# Commit and push
git add BUILD_MANIFEST.json VERSION.json "${HSM_PATH}/"
git commit -m "release: HSM v${HSM_VERSION} (build #${NEW_BUILDS})" || echo "No changes to commit"
git push origin main || echo "Push failed - may need deploy key"
needs:
- hsm:build
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
changes:
- features/status-dashboard/host-status-monitor/**/*
allow_failure: true # Don't block if push fails
# ============================================================================
# Status Dashboard Server Pipeline
# The server has its own .gitlab-ci.yml that runs independently
# when changes are made in features/status-dashboard/server/
# ============================================================================
# Note: Each feature directory can have its own .gitlab-ci.yml
# GitLab will auto-discover them when running from that directory
# ============================================================================
# Feature Databases Pipeline
# Triggers reconciliation when docker-compose or server code changes
# ============================================================================
# Detect which features changed and trigger DB reconciliation
feature-db:detect-changes:
stage: build
image: alpine:latest
script:
- apk add --no-cache git jq
- |
# Get list of features with docker-compose.yml
FEATURES_WITH_DB=""
for feature_dir in features/*/; do
feature=$(basename "$feature_dir")
if [ -f "${feature_dir}docker-compose.yml" ]; then
FEATURES_WITH_DB="${FEATURES_WITH_DB} ${feature}"
fi
done
echo "Features with databases: ${FEATURES_WITH_DB}"
# Check which features have changes
CHANGED_FEATURES=""
for feature in $FEATURES_WITH_DB; do
# Check if any relevant files changed in this commit range
if git diff --name-only ${CI_MERGE_REQUEST_DIFF_BASE_SHA:-HEAD~1}..HEAD | grep -q "features/${feature}/"; then
CHANGED_FEATURES="${CHANGED_FEATURES} ${feature}"
echo " - ${feature}: CHANGED"
else
echo " - ${feature}: unchanged"
fi
done
# Write to artifact for downstream jobs
echo "CHANGED_FEATURES=${CHANGED_FEATURES}" > feature-db.env
echo "Changed features: ${CHANGED_FEATURES:-none}"
artifacts:
reports:
dotenv: feature-db.env
expire_in: 1 hour
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
changes:
- features/*/docker-compose.yml
- features/*/docker-compose.prod.yml
- features/*/server/**/*
- features/*/.env.example
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- features/*/docker-compose.yml
- features/*/docker-compose.prod.yml
- features/*/server/**/*
# Trigger infrastructure reconciliation for feature databases
feature-db:reconcile:
stage: release
image: alpine:latest
variables:
INFRASTRUCTURE_PROJECT_ID: "${CI_PROJECT_ID}" # Same repo, infrastructure/ dir
before_script:
- apk add --no-cache curl jq
script:
- |
if [ -z "$CHANGED_FEATURES" ]; then
echo "No feature database changes detected, skipping reconciliation"
exit 0
fi
echo "Triggering reconciliation for features: ${CHANGED_FEATURES}"
# Trigger infrastructure pipeline with feature-databases service
# This calls the infrastructure/.gitlab-ci.yml reconcile:apply job
curl -X POST \
--fail \
-F "token=${CI_JOB_TOKEN}" \
-F "ref=${CI_COMMIT_REF_NAME}" \
-F "variables[TARGET_SERVICE]=feature-databases" \
-F "variables[TARGET_HOSTS]=apricot" \
-F "variables[CHANGED_FEATURES]=${CHANGED_FEATURES}" \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/trigger/pipeline" \
|| echo "Trigger sent (may require pipeline trigger token)"
echo "Reconciliation triggered for: ${CHANGED_FEATURES}"
needs:
- feature-db:detect-changes
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
changes:
- features/*/docker-compose.yml
- features/*/docker-compose.prod.yml
- features/*/server/**/*
allow_failure: true # Don't block if trigger fails
# ============================================================================
# Conversation Assistant Pipeline
# Builds server and triggers DB reconciliation on apricot
# ============================================================================
conversation-assistant:test:
stage: test
<<: *node_setup
<<: *node_cache
script:
- cd features/conversation-assistant/server
- pnpm install
- pnpm run test
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- features/conversation-assistant/server/**/*
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
changes:
- features/conversation-assistant/server/**/*
conversation-assistant:build:
stage: build
<<: *node_setup
<<: *node_cache
script:
- cd features/conversation-assistant/server
- pnpm install
- pnpm run build
- pnpm run typecheck
artifacts:
paths:
- features/conversation-assistant/server/dist/
expire_in: 1 day
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
changes:
- features/conversation-assistant/server/**/*