fix(ci): airlock smoke test runs on PRs and binds broker to IPAddress… #528
Workflow file for this run
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
| # This workflow builds and publishes Docker images and native CLI binaries. | ||
| # Tests run first on all triggers. Publishing only happens on main branch. | ||
| name: Build and Publish | ||
| run-name: "Build #${{ github.run_number }} (${{ github.event_name }})" | ||
| on: | ||
| push: | ||
| branches: ["main"] | ||
| pull_request: | ||
| branches: ["main"] | ||
| schedule: | ||
| # Runs nightly at 3:15 AM UTC to check for package updates. | ||
| - cron: "15 3 * * *" | ||
| workflow_dispatch: # Allows the workflow to be run manually from the Actions tab | ||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} | ||
| cancel-in-progress: true | ||
| # Sets the minimum required permissions for the entire workflow for security. | ||
| permissions: | ||
| contents: write | ||
| packages: write | ||
| jobs: | ||
| # Run CLI tests on all platforms | ||
| test-cli: | ||
| name: Test CLI (${{ matrix.os }}) | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| os: [ubuntu-24.04, macos-latest, macos-15-intel, windows-latest] | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 | ||
| with: | ||
| global-json-file: global.json | ||
| - name: Restore dependencies | ||
| run: dotnet restore | ||
| - name: Build | ||
| run: dotnet build --no-restore | ||
| - name: Test | ||
| run: dotnet test --no-build --verbosity normal | ||
| # CLI integration tests on all platforms | ||
| test-cli-integration: | ||
| name: Integration Tests (${{ matrix.os }}) | ||
| needs: [test-cli] | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| os: [ubuntu-24.04, macos-latest, macos-15-intel, windows-latest] | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 | ||
| with: | ||
| global-json-file: global.json | ||
| - name: Make test script executable (Unix) | ||
| if: runner.os != 'Windows' | ||
| run: chmod +x tests/integration/test_cli.sh | ||
| - name: Run CLI integration tests (Unix) | ||
| if: runner.os != 'Windows' | ||
| run: ./tests/integration/test_cli.sh | ||
| - name: Run CLI integration tests (Windows) | ||
| if: runner.os == 'Windows' | ||
| shell: pwsh | ||
| run: ./tests/integration/test_cli.ps1 | ||
| # Test shell function scripts can be sourced and call the native binary | ||
| test-shell-functions: | ||
| name: Shell Functions (${{ matrix.os }}) | ||
| needs: [test-cli] | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| os: [ubuntu-24.04, macos-latest, macos-15-intel, windows-latest] | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 | ||
| with: | ||
| global-json-file: global.json | ||
| - name: Build CLI binary | ||
| run: dotnet publish app/CopilotHere.csproj -c Release -o publish/shell-test --nologo | ||
| - name: Test Bash script syntax | ||
| if: runner.os != 'Windows' | ||
| run: bash -n copilot_here.sh | ||
| - name: Test Bash source and copilot_here --help | ||
| if: runner.os != 'Windows' | ||
| shell: bash | ||
| run: | | ||
| export COPILOT_HERE_BIN="$PWD/publish/shell-test/copilot_here" | ||
| chmod +x "$COPILOT_HERE_BIN" | ||
| source ./copilot_here.sh | ||
| # Run --help and verify output contains expected text | ||
| OUTPUT=$(copilot_here --help 2>&1) | ||
| echo "$OUTPUT" | ||
| echo "$OUTPUT" | grep -q "GitHub Copilot CLI" | ||
| - name: Test Bash copilot_here --version | ||
| if: runner.os != 'Windows' | ||
| shell: bash | ||
| run: | | ||
| export COPILOT_HERE_BIN="$PWD/publish/shell-test/copilot_here" | ||
| source ./copilot_here.sh | ||
| OUTPUT=$(copilot_here --version 2>&1) | ||
| echo "$OUTPUT" | ||
| # Verify version format YYYY.MM.DD | ||
| echo "$OUTPUT" | grep -qE "^[0-9]{4}\.[0-9]{2}\.[0-9]{2}" | ||
| - name: Test Zsh source and copilot_here --help | ||
| if: runner.os == 'macOS' | ||
| shell: zsh {0} | ||
| run: | | ||
| export COPILOT_HERE_BIN="$PWD/publish/shell-test/copilot_here" | ||
| source ./copilot_here.sh | ||
| OUTPUT=$(copilot_here --help 2>&1) | ||
| echo "$OUTPUT" | ||
| echo "$OUTPUT" | grep -q "GitHub Copilot CLI" | ||
| - name: Test PowerShell script syntax | ||
| if: runner.os == 'Windows' | ||
| shell: pwsh | ||
| run: | | ||
| $errors = $null | ||
| $null = [System.Management.Automation.Language.Parser]::ParseFile("$PWD/copilot_here.ps1", [ref]$null, [ref]$errors) | ||
| if ($errors.Count -gt 0) { | ||
| $errors | ForEach-Object { Write-Error $_.Message } | ||
| exit 1 | ||
| } | ||
| Write-Host "✓ PowerShell syntax is valid" | ||
| - name: Test PowerShell source and Copilot-Here --help | ||
| if: runner.os == 'Windows' | ||
| shell: pwsh | ||
| run: | | ||
| $env:COPILOT_HERE_BIN = "$PWD\publish\shell-test\copilot_here.exe" | ||
| . .\copilot_here.ps1 | ||
| $output = & $env:COPILOT_HERE_BIN --help 2>&1 | Out-String | ||
| Write-Host $output | ||
| if ($output -notmatch "GitHub Copilot CLI") { | ||
| Write-Error "Expected 'GitHub Copilot CLI' in output" | ||
| exit 1 | ||
| } | ||
| - name: Test PowerShell copilot_here --version | ||
| if: runner.os == 'Windows' | ||
| shell: pwsh | ||
| run: | | ||
| $env:COPILOT_HERE_BIN = "$PWD\publish\shell-test\copilot_here.exe" | ||
| . .\copilot_here.ps1 | ||
| $output = & $env:COPILOT_HERE_BIN --version 2>&1 | Out-String | ||
| Write-Host $output | ||
| if ($output -notmatch "^\d{4}\.\d{2}\.\d{2}") { | ||
| Write-Error "Expected version format YYYY.MM.DD" | ||
| exit 1 | ||
| } | ||
| # Airlock tests - Linux only (Docker with Linux containers required) | ||
| test-airlock: | ||
| name: Test Airlock | ||
| runs-on: ubuntu-24.04 | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 | ||
| with: | ||
| global-json-file: global.json | ||
| - name: Build images locally | ||
| run: | | ||
| chmod +x ./dev-build.sh | ||
| ./dev-build.sh | ||
| - name: Make test script executable | ||
| run: chmod +x tests/integration/test_airlock.sh | ||
| - name: Run airlock integration tests | ||
| run: ./tests/integration/test_airlock.sh --use-local | ||
| # Live-Docker smoke tests for the brokered Docker socket (--dind). | ||
| # Runs on every PR and push: starts the real DockerSocketBroker against | ||
| # the runner's Docker daemon, executes docker commands inside a workload | ||
| # container that points DOCKER_HOST at the broker, and asserts both the | ||
| # happy path (docker version, docker run alpine) AND the Phase 2 body | ||
| # inspection rejections (privileged, forbidden host binds). | ||
| # | ||
| # No copilot_here images required — uses the public docker:cli + alpine | ||
| # images so this stage is fast (~1 minute) and unblocked from the main | ||
| # build matrices. | ||
| integration-tests-broker: | ||
| name: Integration Tests (Broker) | ||
| needs: [test-cli] | ||
| runs-on: ubuntu-24.04 | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 | ||
| with: | ||
| global-json-file: global.json | ||
| - name: Pre-pull test images | ||
| run: | | ||
| docker pull docker:cli | ||
| docker pull alpine:3.21 | ||
| - name: Run integration tests | ||
| env: | ||
| RUN_LIVE_DOCKER_TESTS: "1" | ||
| working-directory: tests/CopilotHere.IntegrationTests | ||
| run: dotnet run --configuration Release | ||
| # Build proxy Rust binary natively on each architecture (much faster than QEMU/Docker) | ||
| build-proxy-binary: | ||
| name: Build Proxy Binary (${{ matrix.arch }}) | ||
| needs: [test-cli-integration, test-shell-functions, test-airlock, integration-tests-broker] | ||
| runs-on: ${{ matrix.runner }} | ||
| if: github.ref == 'refs/heads/main' | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - arch: amd64 | ||
| runner: ubuntu-24.04 | ||
| target: x86_64-unknown-linux-musl | ||
| - arch: arm64 | ||
| runner: ubuntu-24.04-arm | ||
| target: aarch64-unknown-linux-musl | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Install Rust | ||
| uses: dtolnay/rust-toolchain@6d9817901c499d6b02debbb57edb38d33daa680b # stable | ||
| - name: Install musl tools | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y musl-tools musl-dev | ||
| - name: Add musl target | ||
| run: rustup target add ${{ matrix.target }} | ||
| - name: Build proxy binary | ||
| working-directory: proxy | ||
| run: | | ||
| cargo build --release --target ${{ matrix.target }} | ||
| cp target/${{ matrix.target }}/release/secure-proxy ../secure-proxy-${{ matrix.arch }} | ||
| - name: Upload proxy binary | ||
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | ||
| with: | ||
| name: proxy-binary-${{ matrix.arch }} | ||
| path: secure-proxy-${{ matrix.arch }} | ||
| if-no-files-found: error | ||
| # Build proxy Docker image using pre-built binaries | ||
| build-proxy: | ||
| name: Build Proxy Image | ||
| needs: [build-proxy-binary] | ||
| runs-on: ubuntu-24.04 | ||
| if: github.ref == 'refs/heads/main' | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Set lowercase repository name | ||
| id: repo | ||
| run: echo "name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT | ||
| - name: Download proxy binaries | ||
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | ||
| with: | ||
| pattern: proxy-binary-* | ||
| path: proxy-binaries | ||
| merge-multiple: true | ||
| - name: Prepare binaries for Docker | ||
| run: | | ||
| mv proxy-binaries/secure-proxy-amd64 proxy-amd64 | ||
| mv proxy-binaries/secure-proxy-arm64 proxy-arm64 | ||
| chmod +x proxy-amd64 proxy-arm64 | ||
| ls -la proxy-* | ||
| - name: Set up QEMU | ||
| uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 | ||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 | ||
| - name: Log in to the Container registry | ||
| uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Build and push proxy image (amd64) | ||
| uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 | ||
| with: | ||
| context: . | ||
| file: ./docker/Dockerfile.proxy-runtime | ||
| platforms: linux/amd64 | ||
| push: true | ||
| provenance: false | ||
| sbom: false | ||
| tags: ghcr.io/${{ steps.repo.outputs.name }}:proxy-amd64 | ||
| labels: project=copilot_here | ||
| - name: Build and push proxy image (arm64) | ||
| uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 | ||
| with: | ||
| context: . | ||
| file: ./docker/Dockerfile.proxy-runtime | ||
| platforms: linux/arm64 | ||
| push: true | ||
| provenance: false | ||
| sbom: false | ||
| tags: ghcr.io/${{ steps.repo.outputs.name }}:proxy-arm64 | ||
| labels: project=copilot_here | ||
| - name: Create and push multi-arch manifest | ||
| run: | | ||
| docker manifest create ghcr.io/${{ steps.repo.outputs.name }}:proxy \ | ||
| ghcr.io/${{ steps.repo.outputs.name }}:proxy-amd64 \ | ||
| ghcr.io/${{ steps.repo.outputs.name }}:proxy-arm64 | ||
| docker manifest push ghcr.io/${{ steps.repo.outputs.name }}:proxy | ||
| docker manifest create ghcr.io/${{ steps.repo.outputs.name }}:proxy-sha-${{ github.sha }} \ | ||
| ghcr.io/${{ steps.repo.outputs.name }}:proxy-amd64 \ | ||
| ghcr.io/${{ steps.repo.outputs.name }}:proxy-arm64 | ||
| docker manifest push ghcr.io/${{ steps.repo.outputs.name }}:proxy-sha-${{ github.sha }} | ||
| # Verify generated Dockerfiles are up to date | ||
| verify-generated-dockerfiles: | ||
| name: Verify Generated Dockerfiles | ||
| runs-on: ubuntu-24.04 | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Verify generated Dockerfiles match config | ||
| run: pwsh docker/verify-generated.ps1 | ||
| # Fetch latest dependency versions for image builds | ||
| prepare-versions: | ||
| name: Prepare Versions | ||
| needs: [test-cli-integration, test-shell-functions, test-airlock, integration-tests-broker] | ||
| runs-on: ubuntu-24.04 | ||
| if: github.ref == 'refs/heads/main' | ||
| outputs: | ||
| repo_name: ${{ steps.repo.outputs.name }} | ||
| copilot_version: ${{ steps.versions.outputs.copilot_version }} | ||
| playwright_version: ${{ steps.versions.outputs.playwright_version }} | ||
| csharp_ls_version: ${{ steps.versions.outputs.csharp_ls_version }} | ||
| dotnet_8_version: ${{ steps.versions.outputs.dotnet_8_version }} | ||
| dotnet_9_version: ${{ steps.versions.outputs.dotnet_9_version }} | ||
| dotnet_10_version: ${{ steps.versions.outputs.dotnet_10_version }} | ||
| steps: | ||
| - name: Set lowercase repository name | ||
| id: repo | ||
| run: echo "name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT | ||
| - name: Get latest dependency versions | ||
| id: versions | ||
| run: | | ||
| COPILOT_VERSION=$(npm view @github/copilot version) | ||
| echo "copilot_version=$COPILOT_VERSION" >> $GITHUB_OUTPUT | ||
| PLAYWRIGHT_VERSION=$(npm view playwright version) | ||
| echo "playwright_version=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT | ||
| DOTNET_8_VERSION=$(curl -s https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/8.0/releases.json | jq -r '."latest-sdk"') | ||
| echo "dotnet_8_version=$DOTNET_8_VERSION" >> $GITHUB_OUTPUT | ||
| DOTNET_9_VERSION=$(curl -s https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/9.0/releases.json | jq -r '."latest-sdk"') | ||
| echo "dotnet_9_version=$DOTNET_9_VERSION" >> $GITHUB_OUTPUT | ||
| DOTNET_10_VERSION=$(curl -s https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/10.0/releases.json | jq -r '."latest-sdk"') | ||
| echo "dotnet_10_version=$DOTNET_10_VERSION" >> $GITHUB_OUTPUT | ||
| CSHARP_LS_VERSION=$(curl -s https://api.nuget.org/v3-flatcontainer/csharp-ls/index.json | jq -r '.versions[-1]') | ||
| echo "csharp_ls_version=$CSHARP_LS_VERSION" >> $GITHUB_OUTPUT | ||
| # Build all container images in parallel (each builds independently from node:20-slim) | ||
| build-images: | ||
| name: Build ${{ matrix.image }} | ||
| needs: [prepare-versions, verify-generated-dockerfiles] | ||
| runs-on: ubuntu-24.04 | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - image: default | ||
| dockerfile: ./docker/generated/Dockerfile.default | ||
| tags_extra: | | ||
| ghcr.io/${{ needs.prepare-versions.outputs.repo_name }}:latest | ||
| ghcr.io/${{ needs.prepare-versions.outputs.repo_name }}:copilot-latest | ||
| build_args: "COPILOT_VERSION=$COPILOT_VERSION" | ||
| - image: rust | ||
| dockerfile: ./docker/generated/Dockerfile.rust | ||
| build_args: "COPILOT_VERSION=$COPILOT_VERSION" | ||
| - image: golang | ||
| dockerfile: ./docker/generated/Dockerfile.golang | ||
| build_args: "COPILOT_VERSION=$COPILOT_VERSION" | ||
| - image: playwright | ||
| dockerfile: ./docker/generated/Dockerfile.playwright | ||
| build_args: "COPILOT_VERSION=$COPILOT_VERSION\nPLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" | ||
| - image: dotnet-8 | ||
| dockerfile: ./docker/generated/Dockerfile.dotnet-8 | ||
| build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_8_VERSION=$DOTNET_8_VERSION" | ||
| - image: dotnet-9 | ||
| dockerfile: ./docker/generated/Dockerfile.dotnet-9 | ||
| build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_9_VERSION=$DOTNET_9_VERSION" | ||
| - image: dotnet-10 | ||
| dockerfile: ./docker/generated/Dockerfile.dotnet-10 | ||
| build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_10_VERSION=$DOTNET_10_VERSION\nCSHARP_LS_VERSION=$CSHARP_LS_VERSION" | ||
| - image: dotnet | ||
| dockerfile: ./docker/generated/Dockerfile.dotnet | ||
| build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_8_VERSION=$DOTNET_8_VERSION\nDOTNET_SDK_9_VERSION=$DOTNET_9_VERSION\nDOTNET_SDK_10_VERSION=$DOTNET_10_VERSION\nCSHARP_LS_VERSION=$CSHARP_LS_VERSION" | ||
| - image: dotnet-playwright | ||
| dockerfile: ./docker/generated/Dockerfile.dotnet-playwright | ||
| build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_8_VERSION=$DOTNET_8_VERSION\nDOTNET_SDK_9_VERSION=$DOTNET_9_VERSION\nDOTNET_SDK_10_VERSION=$DOTNET_10_VERSION\nPLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION\nCSHARP_LS_VERSION=$CSHARP_LS_VERSION" | ||
| - image: dotnet-rust | ||
| dockerfile: ./docker/generated/Dockerfile.dotnet-rust | ||
| build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_8_VERSION=$DOTNET_8_VERSION\nDOTNET_SDK_9_VERSION=$DOTNET_9_VERSION\nDOTNET_SDK_10_VERSION=$DOTNET_10_VERSION\nCSHARP_LS_VERSION=$CSHARP_LS_VERSION" | ||
| - image: java | ||
| dockerfile: ./docker/generated/Dockerfile.java | ||
| build_args: "COPILOT_VERSION=$COPILOT_VERSION" | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Set up QEMU | ||
| uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 | ||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 | ||
| - name: Log in to the Container registry | ||
| uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Prepare build args | ||
| id: args | ||
| run: | | ||
| ARGS='${{ matrix.build_args }}' | ||
| ARGS="${ARGS//\$COPILOT_VERSION/${{ needs.prepare-versions.outputs.copilot_version }}}" | ||
| ARGS="${ARGS//\$PLAYWRIGHT_VERSION/${{ needs.prepare-versions.outputs.playwright_version }}}" | ||
| ARGS="${ARGS//\$DOTNET_8_VERSION/${{ needs.prepare-versions.outputs.dotnet_8_version }}}" | ||
| ARGS="${ARGS//\$DOTNET_9_VERSION/${{ needs.prepare-versions.outputs.dotnet_9_version }}}" | ||
| ARGS="${ARGS//\$DOTNET_10_VERSION/${{ needs.prepare-versions.outputs.dotnet_10_version }}}" | ||
| ARGS="${ARGS//\$CSHARP_LS_VERSION/${{ needs.prepare-versions.outputs.csharp_ls_version }}}" | ||
| echo "build_args<<EOF" >> $GITHUB_OUTPUT | ||
| echo -e "$ARGS" >> $GITHUB_OUTPUT | ||
| echo "EOF" >> $GITHUB_OUTPUT | ||
| - name: Prepare tags | ||
| id: tags | ||
| run: | | ||
| REPO="${{ needs.prepare-versions.outputs.repo_name }}" | ||
| IMAGE="${{ matrix.image }}" | ||
| SHA="${{ github.sha }}" | ||
| TAGS="ghcr.io/${REPO}:copilot-${IMAGE} | ||
| ghcr.io/${REPO}:copilot-${IMAGE}-sha-${SHA}" | ||
| # Add variant shorthand tag (e.g., :rust, :dotnet) | ||
| if [[ "$IMAGE" != "default" ]]; then | ||
| TAGS="${TAGS} | ||
| ghcr.io/${REPO}:${IMAGE}" | ||
| fi | ||
| # Add extra tags (e.g., :latest for the default image) | ||
| EXTRA_TAGS="${{ matrix.tags_extra }}" | ||
| if [[ -n "$EXTRA_TAGS" ]]; then | ||
| TAGS="${TAGS} | ||
| ${EXTRA_TAGS}" | ||
| fi | ||
| echo "tags<<EOF" >> $GITHUB_OUTPUT | ||
| echo "$TAGS" >> $GITHUB_OUTPUT | ||
| echo "EOF" >> $GITHUB_OUTPUT | ||
| - name: Build and push ${{ matrix.image }} image | ||
| uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 | ||
| with: | ||
| context: . | ||
| file: ${{ matrix.dockerfile }} | ||
| platforms: linux/amd64,linux/arm64 | ||
| push: true | ||
| tags: ${{ steps.tags.outputs.tags }} | ||
| labels: project=copilot_here | ||
| build-args: ${{ steps.args.outputs.build_args }} | ||
| cache-from: type=registry,ref=ghcr.io/${{ needs.prepare-versions.outputs.repo_name }}:copilot-${{ matrix.image }},mode=max | ||
| cache-to: type=inline | ||
| # Full airlock + DinD smoke test using the freshly published proxy and | ||
| # default app images. This is the unblocker test from issue #20: it spins | ||
| # up a real airlock compose project, points the workload at the broker via | ||
| # the proxy bridge, and verifies that spawned siblings get the airlock | ||
| # NetworkMode injected via Phase 2 body inspection. | ||
| # | ||
| # Only runs on main because it depends on build-proxy and build-images | ||
| # having pushed the canonical sha-tagged images. PRs are covered by the | ||
| # cheaper integration-tests-broker job above. | ||
| integration-tests-airlock: | ||
| name: Integration Tests (Airlock) | ||
| needs: [build-proxy, build-images, prepare-versions] | ||
| runs-on: ubuntu-24.04 | ||
| if: github.ref == 'refs/heads/main' | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 | ||
| with: | ||
| global-json-file: global.json | ||
| - name: Log in to the Container registry | ||
| uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Pre-pull test images | ||
| run: | | ||
| docker pull alpine:3.21 | ||
| docker pull ghcr.io/${{ needs.prepare-versions.outputs.repo_name }}:proxy-sha-${{ github.sha }} | ||
| docker pull ghcr.io/${{ needs.prepare-versions.outputs.repo_name }}:copilot-default-sha-${{ github.sha }} | ||
| - name: Run integration tests (airlock + dind) | ||
| env: | ||
| RUN_LIVE_DOCKER_TESTS: "1" | ||
| COPILOT_HERE_PROXY_IMAGE: ghcr.io/${{ needs.prepare-versions.outputs.repo_name }}:proxy-sha-${{ github.sha }} | ||
| COPILOT_HERE_APP_IMAGE: ghcr.io/${{ needs.prepare-versions.outputs.repo_name }}:copilot-default-sha-${{ github.sha }} | ||
| working-directory: tests/CopilotHere.IntegrationTests | ||
| run: dotnet run --configuration Release | ||
| # Full airlock + DinD smoke test. Spins up a real airlock compose project, | ||
| # points the workload at the broker via the proxy bridge, and verifies that | ||
| # spawned siblings get the airlock NetworkMode injected via Phase 2 body | ||
| # inspection. This is the unblocker test from issue #20. | ||
| # | ||
| # Builds the proxy + default app images LOCALLY via dev-build.sh so the job | ||
| # can run on every PR without depending on the published ghcr tags (which | ||
| # are main-only). On the order of ~5 minutes for the image build, plus | ||
| # ~30 seconds for the test itself. | ||
| integration-tests-airlock: | ||
| name: Integration Tests (Airlock) | ||
| needs: [test-cli] | ||
| runs-on: ubuntu-24.04 | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 | ||
| with: | ||
| global-json-file: global.json | ||
| - name: Pre-pull alpine (sibling target) | ||
| run: docker pull alpine:3.21 | ||
| - name: Build proxy + default app images locally | ||
| run: | | ||
| chmod +x ./dev-build.sh | ||
| ./dev-build.sh | ||
| - name: Run integration tests (airlock + dind) | ||
| env: | ||
| RUN_LIVE_DOCKER_TESTS: "1" | ||
| # dev-build.sh tags the proxy as :proxy and the default app image | ||
| # as :latest / :copilot-latest. Point the smoke test at those | ||
| # local tags via the override env vars (the test skips when both | ||
| # are unset to avoid running against random pulls). | ||
| COPILOT_HERE_PROXY_IMAGE: ghcr.io/gordonbeeming/copilot_here:proxy | ||
| COPILOT_HERE_APP_IMAGE: ghcr.io/gordonbeeming/copilot_here:latest | ||
| working-directory: tests/CopilotHere.IntegrationTests | ||
| run: dotnet run --configuration Release | ||
| # Compute version from VERSION file + run number as revision | ||
| compute-version: | ||
| name: Compute Version | ||
| runs-on: ubuntu-24.04 | ||
| outputs: | ||
| version: ${{ steps.compute.outputs.version }} | ||
| short_sha: ${{ steps.compute.outputs.short_sha }} | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Compute version with revision | ||
| id: compute | ||
| run: | | ||
| BASE_VERSION=$(cat VERSION | tr -d '[:space:]') | ||
| VERSION="${BASE_VERSION}.${{ github.run_number }}" | ||
| echo "version=$VERSION" >> $GITHUB_OUTPUT | ||
| SHORT_SHA="${GITHUB_SHA:0:7}" | ||
| echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT | ||
| echo "Computed version: $VERSION (run_number=${{ github.run_number }})" | ||
| # Publish .NET Tool to NuGet | ||
| publish-nuget: | ||
| name: Publish NuGet | ||
| needs: [test-cli-integration, test-shell-functions, test-airlock, integration-tests-broker, compute-version] | ||
| runs-on: ubuntu-24.04 | ||
| if: github.event_name != 'schedule' && github.ref == 'refs/heads/main' | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 | ||
| with: | ||
| global-json-file: global.json | ||
| - name: Pack as .NET tool | ||
| run: dotnet pack app/CopilotHere.csproj -c Release -p:PackAsTool=true -p:CopilotHereVersion=${{ needs.compute-version.outputs.version }} --nologo | ||
| - name: Push to NuGet | ||
| run: dotnet nuget push "app/bin/Release/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate | ||
| # Summary job | ||
| publish-summary: | ||
| name: Publish Summary | ||
| needs: | ||
| [ | ||
| build-images, | ||
| build-proxy, | ||
| integration-tests-airlock, | ||
| release-cli, | ||
| publish-nuget, | ||
| update-winget, | ||
| ] | ||
| if: always() && (needs.build-images.result != 'skipped' || needs.release-cli.result != 'skipped') | ||
| runs-on: ubuntu-24.04 | ||
| steps: | ||
| - name: Workflow Summary | ||
| run: | | ||
| echo "## Workflow Summary" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY | ||
| echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY | ||
| echo "**Tests:** Passed (required before build)" >> $GITHUB_STEP_SUMMARY | ||
| if [[ "${{ needs.build-images.result }}" == "success" ]]; then | ||
| echo "**Images Published:**" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`latest\`, \`copilot-latest\` (default)" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`proxy\`" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "**Variants (with copilot- prefix):**" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`rust\`, \`copilot-rust\`" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`golang\`, \`copilot-golang\`" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`playwright\`, \`copilot-playwright\`" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`dotnet\`, \`copilot-dotnet\`" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`dotnet-8\`, \`copilot-dotnet-8\`" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`dotnet-9\`, \`copilot-dotnet-9\`" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`dotnet-10\`, \`copilot-dotnet-10\`" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`dotnet-playwright\`, \`copilot-dotnet-playwright\`" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`dotnet-rust\`, \`copilot-dotnet-rust\`" >> $GITHUB_STEP_SUMMARY | ||
| echo "- \`java\`, \`copilot-java\`" >> $GITHUB_STEP_SUMMARY | ||
| else | ||
| echo "**Images:** No changes or skipped publishing" >> $GITHUB_STEP_SUMMARY | ||
| fi | ||
| if [[ "${{ needs.release-cli.result }}" == "success" ]]; then | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "**CLI Binaries Published:**" >> $GITHUB_STEP_SUMMARY | ||
| echo "- linux-x64, linux-arm64" >> $GITHUB_STEP_SUMMARY | ||
| echo "- osx-x64, osx-arm64" >> $GITHUB_STEP_SUMMARY | ||
| echo "- win-x64, win-arm64" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "**Package Managers:**" >> $GITHUB_STEP_SUMMARY | ||
| echo "- Homebrew tap updated" >> $GITHUB_STEP_SUMMARY | ||
| echo "- WinGet manifest submitted" >> $GITHUB_STEP_SUMMARY | ||
| fi | ||
| if [[ "${{ needs.publish-nuget.result }}" == "success" ]]; then | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "**.NET Tool:** Published to nuget.org" >> $GITHUB_STEP_SUMMARY | ||
| fi | ||
| # Build native CLI binaries for all platforms | ||
| build-cli: | ||
| name: Build CLI ${{ matrix.rid }} | ||
| needs: [test-cli-integration, test-shell-functions, test-airlock, integration-tests-broker, compute-version] | ||
| runs-on: ${{ matrix.os }} | ||
| if: github.event_name != 'schedule' && github.ref == 'refs/heads/main' | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - os: ubuntu-24.04 | ||
| rid: linux-x64 | ||
| binary: copilot_here | ||
| - os: ubuntu-24.04-arm | ||
| rid: linux-arm64 | ||
| binary: copilot_here | ||
| - os: macos-15-intel | ||
| rid: osx-x64 | ||
| binary: copilot_here | ||
| - os: macos-latest | ||
| rid: osx-arm64 | ||
| binary: copilot_here | ||
| - os: windows-latest | ||
| rid: win-x64 | ||
| binary: copilot_here.exe | ||
| - os: windows-11-arm | ||
| rid: win-arm64 | ||
| binary: copilot_here.exe | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 | ||
| with: | ||
| global-json-file: global.json | ||
| - name: Stamp version into source files | ||
| shell: bash | ||
| run: | | ||
| chmod +x scripts/stamp-version.sh | ||
| ./scripts/stamp-version.sh "${{ needs.compute-version.outputs.version }}" | ||
| - name: Publish AOT binary | ||
| run: dotnet publish app/CopilotHere.csproj -c Release -r ${{ matrix.rid }} -p:CopilotHereVersion=${{ needs.compute-version.outputs.version }} -o publish/${{ matrix.rid }} | ||
| - name: Upload artifact | ||
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | ||
| with: | ||
| name: copilot_here-${{ matrix.rid }} | ||
| path: publish/${{ matrix.rid }}/${{ matrix.binary }} | ||
| if-no-files-found: error | ||
| # Create release with CLI binaries | ||
| release-cli: | ||
| name: Release CLI | ||
| needs: [build-cli, compute-version] | ||
| runs-on: ubuntu-24.04 | ||
| if: github.event_name != 'schedule' && github.ref == 'refs/heads/main' | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 | ||
| with: | ||
| global-json-file: global.json | ||
| - name: Download all artifacts | ||
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | ||
| with: | ||
| path: artifacts | ||
| - name: Prepare release assets | ||
| run: | | ||
| mkdir -p release | ||
| for rid in linux-x64 linux-arm64 osx-x64 osx-arm64 win-x64 win-arm64; do | ||
| if [[ "$rid" == win-* ]]; then | ||
| binary="copilot_here.exe" | ||
| else | ||
| binary="copilot_here" | ||
| fi | ||
| # Create archive | ||
| cd artifacts/copilot_here-$rid | ||
| if [[ "$rid" == win-* ]]; then | ||
| zip ../../release/copilot_here-$rid.zip $binary | ||
| else | ||
| chmod +x $binary | ||
| tar -czvf ../../release/copilot_here-$rid.tar.gz $binary | ||
| fi | ||
| cd ../.. | ||
| done | ||
| # Include shell scripts and install scripts in release | ||
| cp copilot_here.sh release/ | ||
| cp copilot_here.ps1 release/ | ||
| cp install.sh release/ | ||
| cp install.ps1 release/ | ||
| ls -la release/ | ||
| - name: Stamp version into release shell scripts | ||
| run: | | ||
| chmod +x scripts/stamp-version.sh | ||
| ./scripts/stamp-version.sh "${{ needs.compute-version.outputs.version }}" | ||
| - name: Create versioned release | ||
| uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 | ||
| with: | ||
| tag_name: cli-v${{ needs.compute-version.outputs.version }}-${{ needs.compute-version.outputs.short_sha }} | ||
| name: CLI v${{ needs.compute-version.outputs.version }} (${{ needs.compute-version.outputs.short_sha }}) | ||
| body: | | ||
| ## Native AOT CLI Binary | ||
| Commit: ${{ github.sha }} | ||
| ### Download | ||
| | Platform | Architecture | Download | | ||
| |----------|--------------|----------| | ||
| | Linux | x64 | [copilot_here-linux-x64.tar.gz](https://github.com/${{ github.repository }}/releases/download/cli-v${{ needs.compute-version.outputs.version }}-${{ needs.compute-version.outputs.short_sha }}/copilot_here-linux-x64.tar.gz) | | ||
| | Linux | ARM64 | [copilot_here-linux-arm64.tar.gz](https://github.com/${{ github.repository }}/releases/download/cli-v${{ needs.compute-version.outputs.version }}-${{ needs.compute-version.outputs.short_sha }}/copilot_here-linux-arm64.tar.gz) | | ||
| | macOS | x64 | [copilot_here-osx-x64.tar.gz](https://github.com/${{ github.repository }}/releases/download/cli-v${{ needs.compute-version.outputs.version }}-${{ needs.compute-version.outputs.short_sha }}/copilot_here-osx-x64.tar.gz) | | ||
| | macOS | ARM64 | [copilot_here-osx-arm64.tar.gz](https://github.com/${{ github.repository }}/releases/download/cli-v${{ needs.compute-version.outputs.version }}-${{ needs.compute-version.outputs.short_sha }}/copilot_here-osx-arm64.tar.gz) | | ||
| | Windows | x64 | [copilot_here-win-x64.zip](https://github.com/${{ github.repository }}/releases/download/cli-v${{ needs.compute-version.outputs.version }}-${{ needs.compute-version.outputs.short_sha }}/copilot_here-win-x64.zip) | | ||
| | Windows | ARM64 | [copilot_here-win-arm64.zip](https://github.com/${{ github.repository }}/releases/download/cli-v${{ needs.compute-version.outputs.version }}-${{ needs.compute-version.outputs.short_sha }}/copilot_here-win-arm64.zip) | | ||
| ### Installation | ||
| See [README](https://github.com/${{ github.repository }}#installation) for installation instructions. | ||
| files: release/* | ||
| draft: false | ||
| prerelease: false | ||
| make_latest: false | ||
| - name: Delete existing cli-latest tag | ||
| run: | | ||
| gh release delete cli-latest --yes || true | ||
| git push origin :refs/tags/cli-latest || true | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Create latest release (for easy download) | ||
| uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 | ||
| with: | ||
| tag_name: cli-latest | ||
| name: CLI v${{ needs.compute-version.outputs.version }} | ||
| body: | | ||
| ## Latest Native AOT CLI Binary | ||
| **Version:** ${{ needs.compute-version.outputs.version }} | ||
| **Commit:** ${{ github.sha }} | ||
| This release is automatically updated with the latest CLI build. | ||
| For versioned releases, see the [releases page](https://github.com/${{ github.repository }}/releases). | ||
| ### Installation | ||
| See [README](https://github.com/${{ github.repository }}#installation) for installation instructions. | ||
| files: release/* | ||
| draft: false | ||
| prerelease: false | ||
| make_latest: true | ||
| # --- Homebrew tap update --- | ||
| - name: Compute SHA256 for Homebrew | ||
| id: sha256 | ||
| run: | | ||
| echo "osx_arm64=$(sha256sum release/copilot_here-osx-arm64.tar.gz | awk '{print $1}')" >> $GITHUB_OUTPUT | ||
| echo "osx_x64=$(sha256sum release/copilot_here-osx-x64.tar.gz | awk '{print $1}')" >> $GITHUB_OUTPUT | ||
| echo "linux_arm64=$(sha256sum release/copilot_here-linux-arm64.tar.gz | awk '{print $1}')" >> $GITHUB_OUTPUT | ||
| echo "linux_x64=$(sha256sum release/copilot_here-linux-x64.tar.gz | awk '{print $1}')" >> $GITHUB_OUTPUT | ||
| - name: Update Homebrew formula | ||
| env: | ||
| HOMEBREW_TAP_DEPLOY_KEY: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }} | ||
| run: | | ||
| TAG="cli-v${{ needs.compute-version.outputs.version }}-${{ needs.compute-version.outputs.short_sha }}" | ||
| VERSION="${{ needs.compute-version.outputs.version }}" | ||
| # Configure SSH for deploy key | ||
| mkdir -p ~/.ssh | ||
| echo "$HOMEBREW_TAP_DEPLOY_KEY" > ~/.ssh/homebrew_tap_key | ||
| chmod 600 ~/.ssh/homebrew_tap_key | ||
| ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null | ||
| export GIT_SSH_COMMAND="ssh -i ~/.ssh/homebrew_tap_key -o StrictHostKeyChecking=no" | ||
| # Clone the tap repo | ||
| git clone git@github.com:GordonBeeming/homebrew-tap.git /tmp/homebrew-tap | ||
| # Generate the formula | ||
| cat > /tmp/homebrew-tap/Formula/copilot_here.rb << FORMULA_EOF | ||
| # typed: false | ||
| # frozen_string_literal: true | ||
| class CopilotHere < Formula | ||
| desc "Run GitHub Copilot CLI in a sandboxed Docker container" | ||
| homepage "https://github.com/GordonBeeming/copilot_here" | ||
| version "$VERSION" | ||
| license "FSL-1.1-MIT" | ||
| on_macos do | ||
| if Hardware::CPU.arm? | ||
| url "https://github.com/GordonBeeming/copilot_here/releases/download/$TAG/copilot_here-osx-arm64.tar.gz" | ||
| sha256 "${{ steps.sha256.outputs.osx_arm64 }}" | ||
| else | ||
| url "https://github.com/GordonBeeming/copilot_here/releases/download/$TAG/copilot_here-osx-x64.tar.gz" | ||
| sha256 "${{ steps.sha256.outputs.osx_x64 }}" | ||
| end | ||
| end | ||
| on_linux do | ||
| if Hardware::CPU.arm? | ||
| url "https://github.com/GordonBeeming/copilot_here/releases/download/$TAG/copilot_here-linux-arm64.tar.gz" | ||
| sha256 "${{ steps.sha256.outputs.linux_arm64 }}" | ||
| else | ||
| url "https://github.com/GordonBeeming/copilot_here/releases/download/$TAG/copilot_here-linux-x64.tar.gz" | ||
| sha256 "${{ steps.sha256.outputs.linux_x64 }}" | ||
| end | ||
| end | ||
| def install | ||
| bin.install "copilot_here" | ||
| end | ||
| def caveats | ||
| <<~EOS | ||
| copilot_here requires Docker, Podman, or OrbStack to be installed and running. | ||
| To enable the shell function wrapper, run: | ||
| copilot_here --install-shells | ||
| Or manually source the shell script in your profile: | ||
| Bash/Zsh: source "\$(brew --prefix)/share/copilot_here/copilot_here.sh" | ||
| EOS | ||
| end | ||
| test do | ||
| assert_match version.to_s, shell_output("\#{bin}/copilot_here --version") | ||
| end | ||
| end | ||
| FORMULA_EOF | ||
| # Remove leading whitespace from heredoc | ||
| sed -i 's/^ //' /tmp/homebrew-tap/Formula/copilot_here.rb | ||
| # Generate the cask (macOS only, bypasses CLT check) | ||
| mkdir -p /tmp/homebrew-tap/Casks | ||
| cat > /tmp/homebrew-tap/Casks/copilot-here.rb << CASK_EOF | ||
| # typed: false | ||
| # frozen_string_literal: true | ||
| cask "copilot-here" do | ||
| version "$VERSION" | ||
| on_arm do | ||
| url "https://github.com/GordonBeeming/copilot_here/releases/download/$TAG/copilot_here-osx-arm64.tar.gz" | ||
| sha256 "${{ steps.sha256.outputs.osx_arm64 }}" | ||
| end | ||
| on_intel do | ||
| url "https://github.com/GordonBeeming/copilot_here/releases/download/$TAG/copilot_here-osx-x64.tar.gz" | ||
| sha256 "${{ steps.sha256.outputs.osx_x64 }}" | ||
| end | ||
| name "copilot_here" | ||
| desc "Run GitHub Copilot CLI in a sandboxed Docker container" | ||
| homepage "https://github.com/GordonBeeming/copilot_here" | ||
| binary "copilot_here" | ||
| caveats <<~EOS | ||
| copilot_here requires Docker, Podman, or OrbStack to be installed and running. | ||
| To enable the shell function wrapper, run: | ||
| copilot_here --install-shells | ||
| Or manually source the shell script in your profile: | ||
| Bash/Zsh: source "\$(brew --prefix)/share/copilot_here/copilot_here.sh" | ||
| EOS | ||
| end | ||
| CASK_EOF | ||
| # Remove leading whitespace from cask heredoc | ||
| sed -i 's/^ //' /tmp/homebrew-tap/Casks/copilot-here.rb | ||
| # Commit and push | ||
| cd /tmp/homebrew-tap | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| git add Formula/copilot_here.rb Casks/copilot-here.rb | ||
| git commit -m "Update copilot_here to $VERSION" | ||
| git push | ||
| # Clean up | ||
| rm -f ~/.ssh/homebrew_tap_key | ||
| # Update WinGet manifest (requires Windows for wingetcreate) | ||
| update-winget: | ||
| name: Update WinGet | ||
| needs: [release-cli, compute-version] | ||
| runs-on: windows-latest | ||
| if: github.event_name != 'schedule' && github.ref == 'refs/heads/main' | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| - name: Update WinGet manifest | ||
| shell: pwsh | ||
| env: | ||
| WINGET_PAT: ${{ secrets.WINGET_PAT }} | ||
| run: | | ||
| iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe | ||
| $tag = "cli-v${{ needs.compute-version.outputs.version }}-${{ needs.compute-version.outputs.short_sha }}" | ||
| $version = "${{ needs.compute-version.outputs.version }}" | ||
| $urlX64 = "https://github.com/${{ github.repository }}/releases/download/$tag/copilot_here-win-x64.zip" | ||
| $urlArm64 = "https://github.com/${{ github.repository }}/releases/download/$tag/copilot_here-win-arm64.zip" | ||
| # wingetcreate update requires the manifest to already exist in microsoft/winget-pkgs. | ||
| # The first submission must be done manually (see packaging/SETUP.md). | ||
| .\wingetcreate.exe update GordonBeeming.CopilotHere ` | ||
| --version $version ` | ||
| --urls $urlX64 $urlArm64 ` | ||
| --submit --token $env:WINGET_PAT | ||