feat(wizard): add Linear as PM provider option in dashboard wizard (#… #646
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Deploy (Dev) | |
| on: | |
| push: | |
| branches: | |
| - dev | |
| workflow_dispatch: | |
| env: | |
| REGISTRY: ghcr.io | |
| ROUTER_IMAGE: ghcr.io/mongrel-intelligence/cascade-router | |
| WORKER_IMAGE: ghcr.io/mongrel-intelligence/cascade-worker | |
| DASHBOARD_IMAGE: ghcr.io/mongrel-intelligence/cascade-dashboard | |
| jobs: | |
| build-and-deploy: | |
| name: Build and Deploy (Dev) | |
| runs-on: self-hosted | |
| environment: CI | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Log in to GitHub Container Registry | |
| run: | | |
| echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin | |
| - name: Build and push router image | |
| run: | | |
| docker build \ | |
| --label org.opencontainers.image.revision=${{ github.sha }} \ | |
| -f Dockerfile.router \ | |
| -t ${{ env.ROUTER_IMAGE }}:dev \ | |
| -t ${{ env.ROUTER_IMAGE }}:dev-${{ github.sha }} . | |
| docker push ${{ env.ROUTER_IMAGE }}:dev | |
| docker push ${{ env.ROUTER_IMAGE }}:dev-${{ github.sha }} | |
| - name: Validate router image (smoke test) | |
| run: | | |
| docker run --rm ${{ env.ROUTER_IMAGE }}:dev-${{ github.sha }} \ | |
| node --check dist/router/index.js | |
| - name: Build and push worker image | |
| run: | | |
| docker build \ | |
| --label org.opencontainers.image.revision=${{ github.sha }} \ | |
| -f Dockerfile.worker \ | |
| -t ${{ env.WORKER_IMAGE }}:dev \ | |
| -t ${{ env.WORKER_IMAGE }}:dev-${{ github.sha }} . | |
| docker push ${{ env.WORKER_IMAGE }}:dev | |
| docker push ${{ env.WORKER_IMAGE }}:dev-${{ github.sha }} | |
| - name: Build and push dashboard image | |
| run: | | |
| docker build \ | |
| --label org.opencontainers.image.revision=${{ github.sha }} \ | |
| -f Dockerfile.dashboard \ | |
| -t ${{ env.DASHBOARD_IMAGE }}:dev \ | |
| -t ${{ env.DASHBOARD_IMAGE }}:dev-${{ github.sha }} . | |
| docker push ${{ env.DASHBOARD_IMAGE }}:dev | |
| docker push ${{ env.DASHBOARD_IMAGE }}:dev-${{ github.sha }} | |
| - name: Build and deploy frontend to Cloudflare Pages (dev) | |
| run: | | |
| docker build -f Dockerfile.frontend \ | |
| --build-arg VITE_API_URL=https://dev.api.ca.sca.de.com \ | |
| -t cascade-frontend-dev:build . | |
| # Ensure the Cloudflare Pages project exists (idempotent) | |
| docker run --rm \ | |
| -e CLOUDFLARE_API_TOKEN="${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -e CLOUDFLARE_ACCOUNT_ID="${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" \ | |
| cascade-frontend-dev:build \ | |
| wrangler pages project create cascade-dashboard-dev --production-branch=main || true | |
| docker run --rm \ | |
| -e CLOUDFLARE_API_TOKEN="${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -e CLOUDFLARE_ACCOUNT_ID="${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" \ | |
| cascade-frontend-dev:build \ | |
| wrangler pages deploy dist/web --project-name=cascade-dashboard-dev --branch=main | |
| - name: Run database migrations (dev) | |
| run: | | |
| docker build --target=builder -f Dockerfile.dashboard -t cascade-migrator:dev . | |
| docker run --rm \ | |
| -e DATABASE_URL="${{ secrets.DEV_DATABASE_URL }}" \ | |
| -e DATABASE_SSL=false \ | |
| cascade-migrator:dev \ | |
| ./node_modules/.bin/drizzle-kit migrate | |
| - name: Run trigger config migration (dev) | |
| run: | | |
| docker run --rm \ | |
| -e DATABASE_URL="${{ secrets.DEV_DATABASE_URL }}" \ | |
| -e DATABASE_SSL=false \ | |
| cascade-migrator:dev \ | |
| npx tsx tools/migrate-triggers.ts | |
| - name: Run hooks migration (dev) | |
| run: | | |
| docker run --rm \ | |
| -e DATABASE_URL="${{ secrets.DEV_DATABASE_URL }}" \ | |
| -e DATABASE_SSL=false \ | |
| cascade-migrator:dev \ | |
| npx tsx tools/migrate-hooks.ts --apply | |
| - name: Re-encrypt project credentials with project-scoped AAD (dev) | |
| run: | | |
| docker run --rm \ | |
| --env-file /opt/services/cascade-dev.env \ | |
| -e DATABASE_URL="${{ secrets.DEV_DATABASE_URL }}" \ | |
| -e DATABASE_SSL=false \ | |
| cascade-migrator:dev \ | |
| npx tsx tools/migrate-project-credentials-reencrypt.ts | |
| - name: Configure DATABASE_SSL for dev (self-signed certificate) | |
| run: | | |
| # /opt/services/ is read-only for the runner process (sed -i fails). | |
| # Run a container via the host Docker socket — it mounts the host path | |
| # as a writable bind mount and can modify the file directly. | |
| docker run --rm \ | |
| -v /opt/services:/mnt/services \ | |
| alpine:3 sh -c ' | |
| grep -v "^DATABASE_SSL=" /mnt/services/cascade-dev.env > /tmp/new.env 2>/dev/null || true | |
| echo "DATABASE_SSL=false" >> /tmp/new.env | |
| cp /tmp/new.env /mnt/services/cascade-dev.env | |
| ' | |
| - name: Pull and restart cascade-router-dev | |
| run: | | |
| cd /opt/services | |
| docker compose pull cascade-router-dev | |
| docker compose up -d --force-recreate cascade-router-dev | |
| - name: Verify cascade-router-dev is healthy | |
| run: | | |
| echo "Waiting for cascade-router-dev to start..." | |
| for i in $(seq 1 30); do | |
| if docker inspect cascade-router-dev --format '{{.State.Health.Status}}' 2>/dev/null | grep -q healthy; then | |
| echo "cascade-router-dev is healthy" | |
| exit 0 | |
| fi | |
| if docker inspect cascade-router-dev --format '{{.State.Status}}' 2>/dev/null | grep -q restarting; then | |
| echo "ERROR: cascade-router-dev is crashlooping!" | |
| docker logs cascade-router-dev --tail 20 | |
| exit 1 | |
| fi | |
| sleep 5 | |
| done | |
| echo "ERROR: cascade-router-dev did not become healthy within 150s" | |
| docker logs cascade-router-dev --tail 20 | |
| exit 1 | |
| - name: Verify cascade-router-dev image | |
| run: | | |
| EXPECTED=$(docker image inspect ${{ env.ROUTER_IMAGE }}:dev-${{ github.sha }} --format '{{.Id}}') | |
| RUNNING=$(docker inspect cascade-router-dev --format '{{.Image}}') | |
| if [ "$EXPECTED" != "$RUNNING" ]; then | |
| echo "ERROR: cascade-router-dev is running a stale image!" | |
| echo " Expected (dev-${{ github.sha }}): $EXPECTED" | |
| echo " Running: $RUNNING" | |
| exit 1 | |
| fi | |
| echo "cascade-router-dev image verified (commit ${{ github.sha }})" | |
| - name: Verify cascade-router-dev runtime revision and worker image | |
| run: | | |
| REVISION=$(docker inspect cascade-router-dev --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}') | |
| WORKER_IMAGE=$(docker inspect cascade-router-dev --format '{{range .Config.Env}}{{println .}}{{end}}' | grep '^WORKER_IMAGE=' | cut -d= -f2-) | |
| if [ "$REVISION" != "${{ github.sha }}" ]; then | |
| echo "ERROR: cascade-router-dev revision label does not match deployed commit!" | |
| echo " Expected revision: ${{ github.sha }}" | |
| echo " Running revision: $REVISION" | |
| exit 1 | |
| fi | |
| if [ "$WORKER_IMAGE" != "${{ env.WORKER_IMAGE }}:dev" ]; then | |
| echo "ERROR: cascade-router-dev is configured to launch the wrong worker image!" | |
| echo " Expected worker image: ${{ env.WORKER_IMAGE }}:dev" | |
| echo " Running worker image: $WORKER_IMAGE" | |
| exit 1 | |
| fi | |
| echo "cascade-router-dev runtime verified" | |
| - name: Pull and restart cascade-dashboard-dev | |
| run: | | |
| cd /opt/services | |
| docker compose pull cascade-dashboard-dev | |
| docker compose up -d --force-recreate cascade-dashboard-dev | |
| - name: Verify cascade-dashboard-dev is healthy | |
| run: | | |
| echo "Waiting for cascade-dashboard-dev to start..." | |
| for i in $(seq 1 30); do | |
| if docker inspect cascade-dashboard-dev --format '{{.State.Health.Status}}' 2>/dev/null | grep -q healthy; then | |
| echo "cascade-dashboard-dev is healthy" | |
| exit 0 | |
| fi | |
| if docker inspect cascade-dashboard-dev --format '{{.State.Status}}' 2>/dev/null | grep -q restarting; then | |
| echo "ERROR: cascade-dashboard-dev is crashlooping!" | |
| docker logs cascade-dashboard-dev --tail 20 | |
| exit 1 | |
| fi | |
| sleep 5 | |
| done | |
| echo "ERROR: cascade-dashboard-dev did not become healthy within 150s" | |
| docker logs cascade-dashboard-dev --tail 20 | |
| exit 1 | |
| - name: Verify cascade-dashboard-dev image | |
| run: | | |
| EXPECTED=$(docker image inspect ${{ env.DASHBOARD_IMAGE }}:dev-${{ github.sha }} --format '{{.Id}}') | |
| RUNNING=$(docker inspect cascade-dashboard-dev --format '{{.Image}}') | |
| if [ "$EXPECTED" != "$RUNNING" ]; then | |
| echo "ERROR: cascade-dashboard-dev is running a stale image!" | |
| echo " Expected (dev-${{ github.sha }}): $EXPECTED" | |
| echo " Running: $RUNNING" | |
| exit 1 | |
| fi | |
| echo "cascade-dashboard-dev image verified (commit ${{ github.sha }})" | |
| - name: Verify cascade-dashboard-dev runtime revision | |
| run: | | |
| REVISION=$(docker inspect cascade-dashboard-dev --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}') | |
| if [ "$REVISION" != "${{ github.sha }}" ]; then | |
| echo "ERROR: cascade-dashboard-dev revision label does not match deployed commit!" | |
| echo " Expected revision: ${{ github.sha }}" | |
| echo " Running revision: $REVISION" | |
| exit 1 | |
| fi | |
| echo "cascade-dashboard-dev runtime verified" | |
| - name: Verify worker image tag | |
| run: | | |
| EXPECTED=$(docker image inspect ${{ env.WORKER_IMAGE }}:dev-${{ github.sha }} --format '{{.Id}}') | |
| TAGGED=$(docker image inspect ${{ env.WORKER_IMAGE }}:dev --format '{{.Id}}') | |
| if [ "$EXPECTED" != "$TAGGED" ]; then | |
| echo "ERROR: worker :dev tag does not point to the expected build!" | |
| echo " Expected (dev-${{ github.sha }}): $EXPECTED" | |
| echo " :dev resolves to: $TAGGED" | |
| exit 1 | |
| fi | |
| echo "Worker image verified: :dev tag matches commit ${{ github.sha }}" |