Repository navigation
chore: drop devcontainer comments that only repeat the line below #171
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: CI | |
| on: | |
| pull_request: | |
| branches: [master] | |
| # Deliberately unfiltered: "CI complete" below is the required status check | |
| # for master, and a required check that never runs blocks a PR forever -- a | |
| # docs-only PR would deadlock. Image builds are still skipped per-image via | |
| # detect-changes; the always-on jobs are lint-only and take seconds. | |
| concurrency: | |
| group: ci-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ── Lint Dockerfiles with hadolint ────────────────────────────────── | |
| lint-dockerfiles: | |
| name: Hadolint · ${{ matrix.dockerfile }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| dockerfile: | |
| - bun/.devcontainer/Dockerfile | |
| - claude-bun/.devcontainer/Dockerfile | |
| - claude-code/.devcontainer/Dockerfile | |
| - hugo-bun/.devcontainer/Dockerfile | |
| - hugo-bun-node/.devcontainer/Dockerfile | |
| - ralphex-fe/Dockerfile | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Run hadolint | |
| uses: hadolint/hadolint-action@v3.0.0 | |
| with: | |
| dockerfile: ${{ matrix.dockerfile }} | |
| # ── Lint GitHub Actions workflows with actionlint ─────────────────── | |
| lint-workflows: | |
| name: Actionlint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Run actionlint | |
| uses: raven-actions/actionlint@v2 | |
| # ── Detect which images have changed files ────────────────────────── | |
| detect-changes: | |
| name: Detect changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| has-changes: ${{ steps.set-matrix.outputs.has-changes }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Check changed paths | |
| uses: dorny/paths-filter@v4 | |
| id: filter | |
| with: | |
| filters: | | |
| bun: | |
| - 'bun/**' | |
| claude-bun: | |
| - 'claude-bun/**' | |
| claude-code: | |
| - 'claude-code/**' | |
| hugo-bun: | |
| - 'hugo-bun/**' | |
| hugo-bun-node: | |
| - 'hugo-bun-node/**' | |
| ralphex-fe: | |
| - 'ralphex-fe/**' | |
| - name: Build matrix from changes | |
| id: set-matrix | |
| env: | |
| CHANGED_BUN: ${{ steps.filter.outputs.bun }} | |
| CHANGED_CLAUDE_BUN: ${{ steps.filter.outputs.claude-bun }} | |
| CHANGED_CLAUDE_CODE: ${{ steps.filter.outputs.claude-code }} | |
| CHANGED_HUGO_BUN: ${{ steps.filter.outputs.hugo-bun }} | |
| CHANGED_HUGO_BUN_NODE: ${{ steps.filter.outputs.hugo-bun-node }} | |
| CHANGED_RALPHEX: ${{ steps.filter.outputs.ralphex-fe }} | |
| run: | | |
| INCLUDES="[]" | |
| add_image() { | |
| local image="$1" context="$2" dockerfile="$3" verify="$4" target="${5:-}" | |
| INCLUDES=$(echo "$INCLUDES" | jq -c \ | |
| --arg img "$image" \ | |
| --arg ctx "$context" \ | |
| --arg df "$dockerfile" \ | |
| --arg v "$verify" \ | |
| --arg tgt "$target" \ | |
| '. + [{"image":$img,"context":$ctx,"dockerfile":$df,"verify":$v,"target":$tgt}]') | |
| } | |
| if [ "$CHANGED_BUN" = "true" ]; then | |
| add_image "bun" \ | |
| "bun/.devcontainer" \ | |
| "bun/.devcontainer/Dockerfile" \ | |
| "bun --version" | |
| fi | |
| if [ "$CHANGED_CLAUDE_BUN" = "true" ]; then | |
| add_image "claude-bun" \ | |
| "claude-bun/.devcontainer" \ | |
| "claude-bun/.devcontainer/Dockerfile" \ | |
| "bun --version && gh --version && zsh --version && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k" | |
| fi | |
| if [ "$CHANGED_CLAUDE_CODE" = "true" ]; then | |
| add_image "claude-code" \ | |
| "claude-code/.devcontainer" \ | |
| "claude-code/.devcontainer/Dockerfile" \ | |
| "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \ | |
| "default" | |
| add_image "claude-code-sandbox" \ | |
| "claude-code/.devcontainer" \ | |
| "claude-code/.devcontainer/Dockerfile" \ | |
| "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \ | |
| "sandbox" | |
| fi | |
| if [ "$CHANGED_HUGO_BUN" = "true" ]; then | |
| add_image "hugo-bun" \ | |
| "hugo-bun/.devcontainer" \ | |
| "hugo-bun/.devcontainer/Dockerfile" \ | |
| "bun --version && hugo version" | |
| fi | |
| if [ "$CHANGED_HUGO_BUN_NODE" = "true" ]; then | |
| add_image "hugo-bun-node" \ | |
| "hugo-bun-node/.devcontainer" \ | |
| "hugo-bun-node/.devcontainer/Dockerfile" \ | |
| "bun --version && hugo version && node --version" | |
| fi | |
| if [ "$CHANGED_RALPHEX" = "true" ]; then | |
| add_image "ralphex-fe" \ | |
| "ralphex-fe" \ | |
| "ralphex-fe/Dockerfile" \ | |
| "bun --version && hugo version && go version && docker --version && /srv/ralphex --version && rtk --version && test -r /etc/claude-code/managed-settings.json && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/" | |
| fi | |
| if [ "$INCLUDES" = "[]" ]; then | |
| { | |
| echo "has-changes=false" | |
| echo "matrix={\"include\":[]}" | |
| } >> "$GITHUB_OUTPUT" | |
| else | |
| { | |
| echo "has-changes=true" | |
| echo "matrix=$(echo "$INCLUDES" | jq -c '{include:.}')" | |
| } >> "$GITHUB_OUTPUT" | |
| fi | |
| # ── Build changed images (amd64 only) and verify installed tools ──── | |
| # arm64 is tested on native runners at merge time — no QEMU emulation in CI. | |
| build-and-verify: | |
| name: Build · ${{ matrix.image }} | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.has-changes == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.detect-changes.outputs.matrix) }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Build amd64 image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: ${{ matrix.context }} | |
| file: ${{ matrix.dockerfile }} | |
| target: ${{ matrix.target }} | |
| platforms: linux/amd64 | |
| load: true | |
| tags: ${{ matrix.image }}:test | |
| cache-from: type=gha,scope=${{ matrix.image }}-amd64 | |
| cache-to: type=gha,mode=max,scope=${{ matrix.image }}-amd64 | |
| - name: Verify tools (amd64) | |
| env: | |
| IMAGE_TAG: ${{ matrix.image }}:test | |
| VERIFY_CMD: ${{ matrix.verify }} | |
| run: docker run --rm --entrypoint sh "$IMAGE_TAG" -c "$VERIFY_CMD" | |
| # -- Single required status check for branch protection ------------------ | |
| # Aggregates every job above so master needs exactly one required check. | |
| # Passes when each job succeeded or was legitimately skipped (path-filtered | |
| # builds), and fails if any job failed or was cancelled. | |
| # | |
| # Any job added to this workflow must also be added to `needs` below: this is | |
| # the only required check on master and Renovate auto-merges on it, so an | |
| # unwatched job would go red while the gate stayed green. The first step | |
| # enforces that rather than trusting anyone to remember. | |
| ci-complete: | |
| name: CI complete | |
| # always(), not !cancelled(): GitHub counts a *skipped* required check as | |
| # passing, so !cancelled() would let a cancelled run report a green gate. | |
| if: always() | |
| needs: [lint-dockerfiles, lint-workflows, detect-changes, build-and-verify] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Verify every job is watched | |
| env: | |
| WATCHED: ${{ toJSON(needs) }} | |
| run: | | |
| missing="" | |
| for job in $(yq '.jobs | keys | .[]' .github/workflows/ci.yml); do | |
| [ "$job" = "ci-complete" ] && continue | |
| jq -e --arg j "$job" 'has($j)' <<< "$WATCHED" > /dev/null || missing="$missing $job" | |
| done | |
| if [ -n "$missing" ]; then | |
| echo "::error::Jobs missing from ci-complete.needs:$missing" | |
| exit 1 | |
| fi | |
| - name: Check results of all CI jobs | |
| env: | |
| WATCHED: ${{ toJSON(needs) }} | |
| run: | | |
| jq -r 'to_entries[] | "\(.key): \(.value.result)"' <<< "$WATCHED" | |
| bad=$(jq -r ' | |
| to_entries[] | |
| | select(.value.result != "success" and .value.result != "skipped") | |
| | "\(.key) (\(.value.result))"' <<< "$WATCHED") | |
| if [ -n "$bad" ]; then | |
| echo "::error::CI jobs did not pass: $(echo "$bad" | tr '\n' ' ')" | |
| exit 1 | |
| fi | |
| echo "All CI jobs passed or were skipped." |