[DIMM-200, DIMM-221, DIMM-222] playwright-mcp self-recovers from chro… #181
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: Dev Build | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: false | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - feature/wip | |
| - feature/add-web | |
| - feature/web | |
| workflow_dispatch: | |
| inputs: | |
| skip_nix_cache: | |
| description: 'Skip nix cache (genesis mode — full rebuild, no pre-seeding)' | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| packages: write | |
| id-token: write | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| NIX_CACHE_IMAGE: ${{ inputs.skip_nix_cache == true && 'public.ecr.aws/docker/library/debian:trixie-slim' || 'ghcr.io/dimmkirr/devcell:v0.0.0-ultimate' }} | |
| ECR_REGISTRY: public.ecr.aws/w1l3v2k8/devcell | |
| jobs: | |
| secrets: | |
| name: Detect Secrets | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: gitleaks/gitleaks-action@v2 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| docker-build: | |
| name: Docker Build (${{ matrix.arch }}) | |
| needs: secrets | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: ubuntu-latest | |
| arch: amd64 | |
| platform: linux/amd64 | |
| - runner: ubuntu-24.04-arm | |
| arch: arm64 | |
| platform: linux/arm64 | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set lowercase image name | |
| run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV | |
| - name: Free disk space | |
| run: | | |
| sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL | |
| sudo docker image prune -af | |
| df -h | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| with: | |
| buildkitd-config-inline: | | |
| [worker.oci] | |
| max-parallelism = 4 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ secrets.AWS_ECR_ROLE_ARN }} | |
| aws-region: us-east-1 | |
| - name: Log in to ECR Public | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| with: | |
| registry-type: public | |
| - name: Pre-pull nix-cache as OCI layout | |
| if: inputs.skip_nix_cache != true | |
| run: | | |
| skopeo copy --src-tls-verify=true \ | |
| docker://${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-ultimate \ | |
| oci:/tmp/nix-cache-oci:latest | |
| echo "NIX_CACHE_OCI=/tmp/nix-cache-oci" >> "$GITHUB_ENV" | |
| - name: Build and push ci group (GHCR) | |
| run: >- | |
| docker buildx bake | |
| --set '*.output=type=image,push=true,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true' | |
| ${{ env.NIX_CACHE_OCI && format('--set ultimate.contexts.nix-cache=oci-layout://{0}', env.NIX_CACHE_OCI) || '' }} | |
| ci | |
| env: | |
| VERSION: v0.0.0-${{ matrix.arch }} | |
| PLATFORMS: ${{ matrix.platform }} | |
| REGISTRY: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }} | |
| CACHE_ARCH: -${{ matrix.arch }} | |
| - name: Push to ECR Public | |
| run: | | |
| GHCR=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }} | |
| ECR=${{ env.ECR_REGISTRY }} | |
| V=v0.0.0-${{ matrix.arch }} | |
| for suffix in "" "-core" "-ultimate"; do | |
| SRC="${GHCR}:${V}${suffix}" | |
| DST="${ECR}:${V}${suffix}" | |
| docker buildx imagetools create -t "${DST}" "${SRC}" | |
| done | |
| docker-test: | |
| name: Docker Test (${{ matrix.arch }}, ${{ matrix.variant }}) | |
| needs: docker-build | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: ubuntu-latest | |
| arch: amd64 | |
| variant: base | |
| test_run: "TestBaseImage|TestDotenv" | |
| timeout: 600s | |
| - runner: ubuntu-24.04-arm | |
| arch: arm64 | |
| variant: base | |
| test_run: "TestBaseImage|TestDotenv" | |
| timeout: 600s | |
| - runner: ubuntu-latest | |
| arch: amd64 | |
| variant: ultimate | |
| test_run: "" | |
| timeout: 1200s | |
| - runner: ubuntu-24.04-arm | |
| arch: arm64 | |
| variant: ultimate | |
| test_run: "" | |
| timeout: 1200s | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set lowercase image name | |
| run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: go.mod | |
| cache-dependency-path: go.sum | |
| - name: Pull test images | |
| run: | | |
| docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-${{ matrix.variant }} | |
| docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-core | |
| - name: Run container tests | |
| env: | |
| DEVCELL_TEST_IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-${{ matrix.variant }} | |
| DEVCELL_BASE_IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-core | |
| DEVCELL_TEST_BASE_IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-core | |
| DEVCELL_USER_IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-${{ matrix.variant }} | |
| DEVCELL_TEST_PROJECT_DIR: ${{ runner.temp }}/devcell-test | |
| MCP_SECRET_TEST_PASSWORD: fake-secret-ci-value | |
| MCP_SECRET_GITHUB_TOKEN: fake-token-ci-value | |
| run: | | |
| if [ -n "${{ matrix.test_run }}" ]; then | |
| go test -v -run "${{ matrix.test_run }}" -timeout ${{ matrix.timeout }} ./test/... | |
| else | |
| go test -v -timeout ${{ matrix.timeout }} ./test/... | |
| fi | |
| docker-manifest: | |
| name: Docker Manifests | |
| needs: [docker-build] | |
| if: always() && needs.docker-build.result == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Set lowercase image name | |
| run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV | |
| env: | |
| IMAGE_NAME: ${{ github.repository }} | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ secrets.AWS_ECR_ROLE_ARN }} | |
| aws-region: us-east-1 | |
| - name: Log in to ECR Public | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| with: | |
| registry-type: public | |
| - name: Create GHCR manifests | |
| run: | | |
| R=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }} | |
| for suffix in "" "-core" "-ultimate"; do | |
| docker buildx imagetools create \ | |
| -t "${R}:dev${suffix}" \ | |
| -t "${R}:latest${suffix}" \ | |
| -t "${R}:v0.0.0${suffix}" \ | |
| "${R}:v0.0.0-amd64${suffix}" \ | |
| "${R}:v0.0.0-arm64${suffix}" | |
| done | |
| - name: Create ECR Public manifests | |
| run: | | |
| ECR=${{ env.ECR_REGISTRY }} | |
| for suffix in "" "-core" "-ultimate"; do | |
| docker buildx imagetools create \ | |
| -t "${ECR}:dev${suffix}" \ | |
| -t "${ECR}:latest${suffix}" \ | |
| -t "${ECR}:v0.0.0${suffix}" \ | |
| "${ECR}:v0.0.0-amd64${suffix}" \ | |
| "${ECR}:v0.0.0-arm64${suffix}" | |
| done | |
| pure-build: | |
| name: Pure Image (${{ matrix.arch }}, ${{ matrix.stack }}) | |
| needs: secrets | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { runner: ubuntu-latest, arch: amd64, stack: base } | |
| - { runner: ubuntu-latest, arch: amd64, stack: ultimate } | |
| - { runner: ubuntu-24.04-arm, arch: arm64, stack: base } | |
| - { runner: ubuntu-24.04-arm, arch: arm64, stack: ultimate } | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 90 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set lowercase image name | |
| run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV | |
| - name: Free disk space | |
| run: | | |
| sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL | |
| sudo docker image prune -af | |
| df -h | |
| - name: Install Nix | |
| uses: DeterminateSystems/nix-installer-action@v14 | |
| with: | |
| extra-conf: | | |
| experimental-features = nix-command flakes | |
| sandbox = false | |
| filter-syscalls = false | |
| max-substitution-jobs = 128 | |
| http-connections = 128 | |
| # Caches /nix/store between runs. Cache hit (flake.lock + nixhome unchanged) → instant restore; | |
| # partial restore on miss via restore-prefixes-first-match seeds substituted layers so we only | |
| # re-download what actually changed. GC caps the on-disk store at 5 GB so the GHA cache stays small. | |
| - name: Cache /nix/store | |
| uses: nix-community/cache-nix-action@v6 | |
| with: | |
| primary-key: nix-${{ matrix.arch }}-${{ matrix.stack }}-${{ hashFiles('nixhome/flake.lock', 'nixhome/**/*.nix') }} | |
| restore-prefixes-first-match: nix-${{ matrix.arch }}-${{ matrix.stack }}- | |
| purge: true | |
| purge-prefixes: nix-${{ matrix.arch }}-${{ matrix.stack }}- | |
| purge-created: 604800 # 7 days | |
| purge-primary-key: never | |
| gc-max-store-size-linux: 5000000000 # 5 GB | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ secrets.AWS_ECR_ROLE_ARN }} | |
| aws-region: us-east-1 | |
| - name: Log in to ECR Public | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| with: | |
| registry-type: public | |
| - name: Install go-task | |
| uses: arduino/setup-task@v2 | |
| with: | |
| version: 3.x | |
| repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build + push pure image to GHCR | |
| run: task image:pure:push:${{ matrix.stack }} | |
| env: | |
| # Override Taskfile defaults so the task uses CI-installed nix directly, | |
| # no sudo (single-user nix), and pushes to the per-arch tag. | |
| ARCH: ${{ matrix.arch }} | |
| STACK: ${{ matrix.stack }} | |
| REGISTRY: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }} | |
| TAG: v0.0.0-${{ matrix.arch }}-${{ matrix.stack }}-pure | |
| SUDO: "" | |
| NIX: nix | |
| DEVCELL_BUILD_DATE: ${{ github.event.head_commit.timestamp }} | |
| DEVCELL_BUILD_REV: ${{ github.sha }} | |
| - name: Mirror pure image to ECR Public | |
| run: task image:mirror | |
| env: | |
| SRC: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-${{ matrix.stack }}-pure | |
| DST: ${{ env.ECR_REGISTRY }}:v0.0.0-${{ matrix.arch }}-${{ matrix.stack }}-pure | |
| pure-manifest: | |
| name: Pure Image Manifests | |
| needs: pure-build | |
| if: always() && needs.pure-build.result == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set lowercase image name | |
| run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV | |
| env: | |
| IMAGE_NAME: ${{ github.repository }} | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ secrets.AWS_ECR_ROLE_ARN }} | |
| aws-region: us-east-1 | |
| - name: Log in to ECR Public | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| with: | |
| registry-type: public | |
| - name: Install go-task | |
| uses: arduino/setup-task@v2 | |
| with: | |
| version: 3.x | |
| repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Stitch multi-arch GHCR manifests (pure) | |
| run: | | |
| for stack in base ultimate; do | |
| task image:manifest \ | |
| REPO=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }} \ | |
| VERSION=v0.0.0 \ | |
| SUFFIX=-${stack}-pure \ | |
| ARCHES=amd64,arm64 | |
| done | |
| - name: Stitch multi-arch ECR Public manifests (pure) | |
| run: | | |
| for stack in base ultimate; do | |
| task image:manifest \ | |
| REPO=${{ env.ECR_REGISTRY }} \ | |
| VERSION=v0.0.0 \ | |
| SUFFIX=-${stack}-pure \ | |
| ARCHES=amd64,arm64 | |
| done | |
| cell-build: | |
| name: Cell CLI Dev Build | |
| needs: secrets | |
| runs-on: ubuntu-latest | |
| env: | |
| RELEASE_VERSION: v0.0.0 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: go.mod | |
| cache-dependency-path: go.sum | |
| - name: Set SHORT_SHA | |
| run: echo "SHORT_SHA=$(echo ${GITHUB_SHA} | cut -c1-8)" >> $GITHUB_ENV | |
| - name: Delete existing tag | |
| run: | | |
| git config --global user.email "dmitry@atd.sh" | |
| git config --global user.name "Dmitry Kireev" | |
| git tag -d "${{ env.RELEASE_VERSION }}" || echo "No local tag to delete" | |
| git push origin :refs/tags/${{ env.RELEASE_VERSION }} || echo "No remote tag to delete" | |
| - name: Add tag | |
| run: | | |
| git tag -a ${{ env.RELEASE_VERSION }} -m "Development release" | |
| git push origin ${{ github.ref_name }} | |
| - name: Run GoReleaser | |
| uses: goreleaser/goreleaser-action@v6 | |
| with: | |
| distribution: goreleaser | |
| version: "~> v2" | |
| args: release --clean -f .goreleaser.dev.yaml | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} | |
| SHORT_SHA: ${{ env.SHORT_SHA }} | |
| - name: Publish release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: gh release edit "${{ env.RELEASE_VERSION }}" --draft=false --latest=false --prerelease | |
| e2e-install: | |
| name: E2E Install (${{ matrix.arch }}) | |
| needs: [cell-build, docker-manifest] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: ubuntu-latest | |
| arch: amd64 | |
| - runner: ubuntu-24.04-arm | |
| arch: arm64 | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Download cell binary | |
| run: | | |
| TARBALL="cell-linux-${{ matrix.arch }}.tar.gz" | |
| URL="https://github.com/DimmKirr/devcell/releases/download/v0.0.0/${TARBALL}" | |
| echo "Downloading: ${URL}" | |
| curl -fsSL -o "${TARBALL}" "${URL}" | |
| tar xzf "${TARBALL}" | |
| chmod +x cell | |
| sudo mv cell /usr/local/bin/cell | |
| - name: Verify cell binary | |
| run: | | |
| cell --help | |
| echo "--- cell binary OK ---" | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Run cell claude --version (full pipeline) | |
| env: | |
| DEVCELL_NIXHOME_PATH: ${{ github.workspace }}/nixhome | |
| run: | | |
| # Simulate a new user in a fresh project dir | |
| mkdir -p /tmp/e2e-project && cd /tmp/e2e-project | |
| # --plain-text: disable spinners for CI | |
| # Flow: scaffold devcell.toml → pull base image → build user image → start container → print claude version | |
| OUTPUT=$(cell --plain-text claude --version 2>&1) || true | |
| echo "$OUTPUT" | |
| # Assert cell version string is present | |
| if echo "$OUTPUT" | grep -qE "cell\s+v?[0-9]+\.[0-9]+\.[0-9]+"; then | |
| echo "--- PASS: cell version found ---" | |
| else | |
| echo "--- FAIL: cell version string not found in output ---" | |
| exit 1 | |
| fi | |
| # Assert the image was built (user image should exist now) | |
| if echo "$OUTPUT" | grep -qiE "(building|built|image)"; then | |
| echo "--- PASS: image build occurred ---" | |
| else | |
| echo "--- WARN: no build output detected (image may have been cached) ---" | |
| fi | |
| brew-install: | |
| name: Brew Install (${{ matrix.arch }}) | |
| needs: [cell-build] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: ubuntu-latest | |
| arch: amd64 | |
| - runner: ubuntu-24.04-arm | |
| arch: arm64 | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Install Homebrew | |
| run: | | |
| /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" < /dev/null | |
| echo "/home/linuxbrew/.linuxbrew/bin" >> $GITHUB_PATH | |
| - name: Brew install devcell-dev | |
| run: | | |
| brew tap DimmKirr/tap | |
| brew install devcell-dev || true | |
| # Verify binary was actually linked despite potential broken pipe | |
| if ! command -v cell &>/dev/null; then | |
| echo "Binary not found, retrying..." | |
| brew install devcell-dev | |
| fi | |
| - name: Verify version | |
| run: | | |
| INSTALLED=$(cell --version) | |
| echo "Installed: ${INSTALLED}" | |
| if echo "${INSTALLED}" | grep -qE "^cell version [0-9]+\.[0-9]+\.[0-9]+"; then | |
| echo "--- PASS: cell version OK ---" | |
| else | |
| echo "--- FAIL: unexpected version output ---" | |
| exit 1 | |
| fi | |