name: Dev — Build & local smoke test # Validates that the build compiles and the server starts cleanly. # No GCP deployment — this is the inner dev loop gate. # Merge to stage when you want a real environment. # # Build approach: pull ci-base from Artifact Registry (has elb + elc + runtime # at /opt/el), extract the SDK onto the runner host, then run elb build. # elb compiles each .el source independently — no combined mega-file, no OOM. # Output: dist/neuron-landing (linux/amd64). Dockerfile.stage COPYs it directly. # # For pull_request events: secrets are not injected, so ci-base can't be pulled. # Fall back to committed bin/elb-linux-amd64 + bin/elc-linux-amd64 + runtime/. # No docker cache (no Artifact Registry auth), but the full build + smoke test runs. on: push: branches: [dev] paths: - 'src/**' - 'dist/**' - 'runtime/**' - 'manifest.el' - 'Dockerfile.stage' - '.gitea/workflows/dev.yaml' - '.gitea/workflows/stage.yaml' - '.gitea/workflows/deploy.yaml' pull_request: branches: [dev] paths: - 'src/**' - 'dist/**' - 'runtime/**' - 'manifest.el' - 'Dockerfile.stage' - '.gitea/workflows/dev.yaml' - '.gitea/workflows/stage.yaml' - '.gitea/workflows/deploy.yaml' workflow_dispatch: jobs: build-smoke: runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: read id-token: write steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 2 # ── GCP auth (push/workflow_dispatch only) ──────────────────────────── # pull_request events don't get secrets injected. GCP auth is skipped # for PRs — El SDK comes from committed bin/ + runtime/ instead. - name: Authenticate to GCP if: github.event_name != 'pull_request' uses: google-github-actions/auth@v2 with: credentials_json: ${{ secrets.GCP_SA_KEY }} - name: Set up gcloud SDK if: github.event_name != 'pull_request' uses: google-github-actions/setup-gcloud@v2 with: project_id: neuron-785695 - name: Configure docker auth for Artifact Registry if: github.event_name != 'pull_request' run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet - name: Prune Docker to reclaim disk run: | # Remove stopped containers, dangling images, unused volumes/networks. # Do NOT prune build cache — that keeps Docker builds fast and under # the ~26min runner restart window. Selective pruning frees ~4-5GB # which is enough to prevent overlay2 "no space left on device" errors. docker container prune -f 2>&1 || true docker image prune -f 2>&1 || true docker volume prune -f 2>&1 || true df -h / # ── El SDK setup ────────────────────────────────────────────────────── # Push builds: extract elb + elc + runtime from ci-base (always latest). # PR builds: use committed bin/elb-linux-amd64 + bin/elc-linux-amd64 + runtime/. - name: Extract El SDK from ci-base (push builds) if: github.event_name != 'pull_request' run: | set -euo pipefail docker pull us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base:dev CID=$(docker create us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base:dev) sudo mkdir -p /opt/el docker cp "$CID:/opt/el" /opt/ docker rm "$CID" echo "ELB=/opt/el/dist/bin/elb" >> "$GITHUB_ENV" echo "ELC=/opt/el/dist/platform/elc" >> "$GITHUB_ENV" echo "EL_RUNTIME=$GITHUB_WORKSPACE/runtime" >> "$GITHUB_ENV" - name: Set up El SDK from committed bin/ (PR builds) if: github.event_name == 'pull_request' run: | set -euo pipefail DEST="${{ github.workspace }}/../foundation-el" mkdir -p "$DEST/dist/bin" "$DEST/dist/platform" "$DEST/el-compiler/runtime" cp bin/elb-linux-amd64 "$DEST/dist/bin/elb" cp bin/elc-linux-amd64 "$DEST/dist/platform/elc" chmod +x "$DEST/dist/bin/elb" "$DEST/dist/platform/elc" cp runtime/el_runtime.c "$DEST/el-compiler/runtime/" cp runtime/el_runtime.h "$DEST/el-compiler/runtime/" cp runtime/el_runtime.js "$DEST/el-compiler/runtime/" echo "ELB=$DEST/dist/bin/elb" >> "$GITHUB_ENV" echo "ELC=$DEST/dist/platform/elc" >> "$GITHUB_ENV" echo "EL_RUNTIME=$DEST/el-compiler/runtime" >> "$GITHUB_ENV" # ── Build neuron-web binary ─────────────────────────────────────────── - name: Build neuron-web with elb run: | set -uo pipefail echo "ELB=$ELB ELC=$ELC EL_RUNTIME=$EL_RUNTIME" ls -la "$ELB" "$ELC" stdbuf -oL "$ELB" \ --elc="$ELC" \ --runtime="$EL_RUNTIME" 2>&1 | tee /tmp/elb.log ELB_EXIT=${PIPESTATUS[0]} if [ "$ELB_EXIT" -eq 0 ]; then echo "Binary: $(ls -lh dist/neuron-landing)" fi exit "$ELB_EXIT" - name: Dump full elb output (on failure) if: failure() run: | echo "=== full elb output ===" cat /tmp/elb.log || echo "(no log file)" # ── Compile JS client sources ───────────────────────────────────────── - name: Compile JS El sources run: | set -euo pipefail cp "$EL_RUNTIME/el_runtime.js" src/js/ mkdir -p dist/js for f in src/js/*.el; do [ -f "$f" ] || continue name=$(basename "$f" .el) "$ELC" --target=js --bundle --minify --obfuscate "$f" > "dist/js/${name}.js" echo " compiled: $f -> dist/js/${name}.js" done rm -f src/js/el_runtime.js # ── Docker build + smoke test ───────────────────────────────────────── # # PR builds: binary is compiled by committed bin/elb-linux-amd64 which # may lag behind the current El SDK. Smoke-testing that binary is # unreliable (glibc mismatch in Docker; potential codegen differences # when run directly). PRs only need to prove the code *compiles* and # the Docker image *builds* — the authoritative runtime check runs on # push to dev (ci-base SDK, always current). - name: Compute image tag id: tag run: echo "tag=dev-${GITHUB_SHA:0:8}" >> "$GITHUB_OUTPUT" - name: Touch HTML placeholder files run: touch src/index.html src/about.html src/terms.html src/enterprise-terms.html - name: Create soul-demo placeholder # Dockerfile.stage COPYs dist/soul-demo. We only need the binary to exist # for the Docker build to succeed; the real binary is compiled in stage CI. run: | touch dist/soul-demo chmod +x dist/soul-demo - name: Build Docker image (local only — no push) run: | set -euo pipefail TAG="${{ steps.tag.outputs.tag }}" CACHE_ARGS="" if [ "${{ github.event_name }}" != "pull_request" ]; then CACHE_ARGS="--cache-from us-central1-docker.pkg.dev/neuron-785695/neuron-marketing/marketing:latest" fi docker build \ --build-arg BUILDKIT_INLINE_CACHE=1 \ $CACHE_ARGS \ -f Dockerfile.stage \ -t "marketing:${TAG}" \ . - name: Local smoke test # Push builds only: binary compiled from ci-base is current and # compatible with the runner glibc. Skipped for pull_request events # because the committed bin/elb may produce a binary that requires # a newer glibc than what the runner environment provides. if: github.event_name != 'pull_request' run: | set -euo pipefail PORT=8080 dist/neuron-landing & SERVER_PID=$! for i in $(seq 1 15); do STATUS=$(curl -sSo /dev/null -w "%{http_code}" --max-time 5 http://localhost:8080/ || echo "000") echo "Attempt $i/15: HTTP $STATUS" if [ "$STATUS" = "200" ]; then echo "Dev smoke test PASSED" kill "$SERVER_PID" 2>/dev/null || true exit 0 fi sleep 3 done kill "$SERVER_PID" 2>/dev/null || true echo "Dev smoke test FAILED" exit 1