diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9fb21813..9d851506 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,335 +1,348 @@ -name: CI/CD Pipeline - -on: - push: - branches: [master] - pull_request: - branches: [master] - -concurrency: - group: ci-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -# Pipeline (sequential, fail-fast): -# go-test → e2e-test → build → deploy → publish -# PRs stop after build. Master continues to deploy + publish. - -jobs: - # ─────────────────────────────────────────────────────────────── - # 1. Go Build & Test - # ─────────────────────────────────────────────────────────────── - go-test: - name: "✅ Go Build & Test" - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Clean Go module cache - run: rm -rf ~/go/pkg/mod 2>/dev/null || true - - - name: Set up Go 1.22 - uses: actions/setup-go@v6 - with: - go-version: '1.22' - cache-dependency-path: | - cmd/server/go.sum - cmd/ingestor/go.sum - - - name: Build and test Go server (with coverage) - run: | - set -e -o pipefail - cd cmd/server - go build . - go test -coverprofile=server-coverage.out ./... 2>&1 | tee server-test.log - echo "--- Go Server Coverage ---" - go tool cover -func=server-coverage.out | tail -1 - - - name: Build and test Go ingestor (with coverage) - run: | - set -e -o pipefail - cd cmd/ingestor - go build . - go test -coverprofile=ingestor-coverage.out ./... 2>&1 | tee ingestor-test.log - echo "--- Go Ingestor Coverage ---" - go tool cover -func=ingestor-coverage.out | tail -1 - - - name: Verify proto syntax - run: | - set -e - sudo apt-get update -qq - sudo apt-get install -y protobuf-compiler - for proto in proto/*.proto; do - echo " ✓ $(basename "$proto")" - protoc --proto_path=proto --descriptor_set_out=/dev/null "$proto" - done - echo "✅ All .proto files are syntactically valid" - - - name: Generate Go coverage badges - if: success() - run: | - mkdir -p .badges - - SERVER_COV="0" - if [ -f cmd/server/server-coverage.out ]; then - SERVER_COV=$(cd cmd/server && go tool cover -func=server-coverage.out | tail -1 | grep -oP '[\d.]+(?=%)') - fi - SERVER_COLOR="red" - if [ "$(echo "$SERVER_COV >= 80" | bc -l 2>/dev/null)" = "1" ]; then SERVER_COLOR="green" - elif [ "$(echo "$SERVER_COV >= 60" | bc -l 2>/dev/null)" = "1" ]; then SERVER_COLOR="yellow"; fi - echo "{\"schemaVersion\":1,\"label\":\"go server coverage\",\"message\":\"${SERVER_COV}%\",\"color\":\"${SERVER_COLOR}\"}" > .badges/go-server-coverage.json - - INGESTOR_COV="0" - if [ -f cmd/ingestor/ingestor-coverage.out ]; then - INGESTOR_COV=$(cd cmd/ingestor && go tool cover -func=ingestor-coverage.out | tail -1 | grep -oP '[\d.]+(?=%)') - fi - INGESTOR_COLOR="red" - if [ "$(echo "$INGESTOR_COV >= 80" | bc -l 2>/dev/null)" = "1" ]; then INGESTOR_COLOR="green" - elif [ "$(echo "$INGESTOR_COV >= 60" | bc -l 2>/dev/null)" = "1" ]; then INGESTOR_COLOR="yellow"; fi - echo "{\"schemaVersion\":1,\"label\":\"go ingestor coverage\",\"message\":\"${INGESTOR_COV}%\",\"color\":\"${INGESTOR_COLOR}\"}" > .badges/go-ingestor-coverage.json - - echo "## Go Coverage" >> $GITHUB_STEP_SUMMARY - echo "| Module | Coverage |" >> $GITHUB_STEP_SUMMARY - echo "|--------|----------|" >> $GITHUB_STEP_SUMMARY - echo "| Server | ${SERVER_COV}% |" >> $GITHUB_STEP_SUMMARY - echo "| Ingestor | ${INGESTOR_COV}% |" >> $GITHUB_STEP_SUMMARY - - - name: Upload Go coverage badges - if: success() - uses: actions/upload-artifact@v6 - with: - name: go-badges - path: .badges/go-*.json - retention-days: 1 - if-no-files-found: ignore - - # ─────────────────────────────────────────────────────────────── - # 2. Playwright E2E Tests (against Go server with fixture DB) - # ─────────────────────────────────────────────────────────────── - e2e-test: - name: "🎭 Playwright E2E Tests" - needs: [go-test] - runs-on: [self-hosted, Linux] - defaults: - run: - shell: bash - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Set up Node.js 22 - uses: actions/setup-node@v5 - with: - node-version: '22' - - - name: Clean Go module cache - run: rm -rf ~/go/pkg/mod 2>/dev/null || true - - - name: Set up Go 1.22 - uses: actions/setup-go@v6 - with: - go-version: '1.22' - cache-dependency-path: cmd/server/go.sum - - - name: Build Go server - run: | - cd cmd/server - go build -o ../../corescope-server . - echo "Go server built successfully" - - - name: Install npm dependencies - run: npm ci --production=false - - - name: Install Playwright browser - run: | - npx playwright install chromium 2>/dev/null || true - npx playwright install-deps chromium 2>/dev/null || true - - - name: Instrument frontend JS for coverage - run: sh scripts/instrument-frontend.sh - - - name: Start Go server with fixture DB - run: | - fuser -k 13581/tcp 2>/dev/null || true - sleep 1 - ./corescope-server -port 13581 -db test-fixtures/e2e-fixture.db -public public-instrumented & - echo $! > .server.pid - for i in $(seq 1 30); do - if curl -sf http://localhost:13581/api/stats > /dev/null 2>&1; then - echo "Server ready after ${i}s" - break - fi - if [ "$i" -eq 30 ]; then - echo "Server failed to start within 30s" - exit 1 - fi - sleep 1 - done - - - name: Run Playwright E2E tests (fail-fast) - run: | - BASE_URL=http://localhost:13581 node test-e2e-playwright.js 2>&1 | tee e2e-output.txt - - # DISABLED: Coverage collector takes 8+ min. E2E tests extract window.__coverage__ directly. - # - name: Collect frontend coverage - # if: success() - # run: | - # BASE_URL=http://localhost:13581 node scripts/collect-frontend-coverage.js 2>&1 | tee fe-coverage-output.txt || true - - - name: Generate frontend coverage badges - if: success() - run: | - E2E_PASS=$(grep -oP '[0-9]+(?=/)' e2e-output.txt | tail -1 || echo "0") - - mkdir -p .badges - if [ -f .nyc_output/frontend-coverage.json ] || [ -f .nyc_output/e2e-coverage.json ]; then - npx nyc report --reporter=text-summary --reporter=text 2>&1 | tee fe-report.txt - FE_COVERAGE=$(grep 'Statements' fe-report.txt | head -1 | grep -oP '[\d.]+(?=%)' || echo "0") - FE_COVERAGE=${FE_COVERAGE:-0} - FE_COLOR="red" - [ "$(echo "$FE_COVERAGE > 50" | bc -l 2>/dev/null)" = "1" ] && FE_COLOR="yellow" - [ "$(echo "$FE_COVERAGE > 80" | bc -l 2>/dev/null)" = "1" ] && FE_COLOR="brightgreen" - echo "{\"schemaVersion\":1,\"label\":\"frontend coverage\",\"message\":\"${FE_COVERAGE}%\",\"color\":\"${FE_COLOR}\"}" > .badges/frontend-coverage.json - echo "## Frontend: ${FE_COVERAGE}% coverage" >> $GITHUB_STEP_SUMMARY - fi - echo "{\"schemaVersion\":1,\"label\":\"e2e tests\",\"message\":\"${E2E_PASS:-0} passed\",\"color\":\"brightgreen\"}" > .badges/e2e-tests.json - - - name: Stop test server - if: success() - run: | - if [ -f .server.pid ]; then - kill $(cat .server.pid) 2>/dev/null || true - rm -f .server.pid - fi - - - name: Upload E2E badges - if: success() - uses: actions/upload-artifact@v6 - with: - name: e2e-badges - path: .badges/ - retention-days: 1 - if-no-files-found: ignore - - # ─────────────────────────────────────────────────────────────── - # 3. Build Docker Image - # ─────────────────────────────────────────────────────────────── - build: - name: "🏗️ Build Docker Image" - needs: [e2e-test] - runs-on: [self-hosted, Linux] - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Set up Node.js 22 - uses: actions/setup-node@v5 - with: - node-version: '22' - - - name: Build Go Docker image - run: | - echo "${GITHUB_SHA::7}" > .git-commit - APP_VERSION=$(node -p "require('./package.json').version") \ - GIT_COMMIT="${GITHUB_SHA::7}" \ - APP_VERSION=$(grep -oP 'APP_VERSION:-\K[^}]+' docker-compose.yml | head -1 || echo "3.0.0") - GIT_COMMIT=$(git rev-parse --short HEAD) - BUILD_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ') - export APP_VERSION GIT_COMMIT BUILD_TIME - docker compose --profile staging-go build staging-go - echo "Built Go staging image ✅" - - # ─────────────────────────────────────────────────────────────── - # 4. Deploy Staging (master only) - # ─────────────────────────────────────────────────────────────── - deploy: - name: "🚀 Deploy Staging" - if: github.event_name == 'push' - needs: [build] - runs-on: [self-hosted, Linux] - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Stop old staging container - run: | - docker stop corescope-staging-go 2>/dev/null || true - docker rm corescope-staging-go 2>/dev/null || true - fuser -k 82/tcp 2>/dev/null || true - - - name: Start staging on port 82 - run: | - docker compose --profile staging-go up -d staging-go - - - name: Healthcheck staging container - run: | - for i in $(seq 1 120); do - HEALTH=$(docker inspect corescope-staging-go --format '{{.State.Health.Status}}' 2>/dev/null || echo "starting") - if [ "$HEALTH" = "healthy" ]; then - echo "Staging healthy after ${i}s" - break - fi - if [ "$i" -eq 120 ]; then - echo "Staging failed health check after 120s" - docker logs corescope-staging-go --tail 50 - exit 1 - fi - sleep 1 - done - - - name: Smoke test staging API - run: | - if curl -sf http://localhost:82/api/stats | grep -q engine; then - echo "Staging verified — engine field present ✅" - else - echo "Staging /api/stats did not return engine field" - exit 1 - fi - - # ─────────────────────────────────────────────────────────────── - # 5. Publish Badges & Summary (master only) - # ─────────────────────────────────────────────────────────────── - publish: - name: "📝 Publish Badges & Summary" - if: github.event_name == 'push' - needs: [deploy] - runs-on: [self-hosted, Linux] - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Download Go coverage badges - continue-on-error: true - uses: actions/download-artifact@v6 - with: - name: go-badges - path: .badges/ - - - name: Download E2E badges - continue-on-error: true - uses: actions/download-artifact@v6 - with: - name: e2e-badges - path: .badges/ - - - name: Publish coverage badges to repo - continue-on-error: true - run: | - git config user.name "github-actions" - git config user.email "actions@github.com" - git remote set-url origin https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git - git add .badges/ -f - git diff --cached --quiet || (git commit -m "ci: update test badges [skip ci]" && git push) || echo "Badge push failed" - - - name: Post deployment summary - run: | - echo "## Staging Deployed ✓" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Commit:** \`$(git rev-parse --short HEAD)\` — $(git log -1 --format=%s)" >> $GITHUB_STEP_SUMMARY +name: CI/CD Pipeline + +on: + push: + branches: [master] + pull_request: + branches: [master] + +concurrency: + group: ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +# Pipeline (sequential, fail-fast): +# go-test → e2e-test → build → deploy → publish +# PRs stop after build. Master continues to deploy + publish. + +jobs: + # ─────────────────────────────────────────────────────────────── + # 1. Go Build & Test + # ─────────────────────────────────────────────────────────────── + go-test: + name: "✅ Go Build & Test" + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Clean Go module cache + run: rm -rf ~/go/pkg/mod 2>/dev/null || true + + - name: Set up Go 1.22 + uses: actions/setup-go@v6 + with: + go-version: '1.22' + cache-dependency-path: | + cmd/server/go.sum + cmd/ingestor/go.sum + + - name: Build and test Go server (with coverage) + run: | + set -e -o pipefail + cd cmd/server + go build . + go test -coverprofile=server-coverage.out ./... 2>&1 | tee server-test.log + echo "--- Go Server Coverage ---" + go tool cover -func=server-coverage.out | tail -1 + + - name: Build and test Go ingestor (with coverage) + run: | + set -e -o pipefail + cd cmd/ingestor + go build . + go test -coverprofile=ingestor-coverage.out ./... 2>&1 | tee ingestor-test.log + echo "--- Go Ingestor Coverage ---" + go tool cover -func=ingestor-coverage.out | tail -1 + + - name: Verify proto syntax + run: | + set -e + sudo apt-get update -qq + sudo apt-get install -y protobuf-compiler + for proto in proto/*.proto; do + echo " ✓ $(basename "$proto")" + protoc --proto_path=proto --descriptor_set_out=/dev/null "$proto" + done + echo "✅ All .proto files are syntactically valid" + + - name: Generate Go coverage badges + if: success() + run: | + mkdir -p .badges + + SERVER_COV="0" + if [ -f cmd/server/server-coverage.out ]; then + SERVER_COV=$(cd cmd/server && go tool cover -func=server-coverage.out | tail -1 | grep -oP '[\d.]+(?=%)') + fi + SERVER_COLOR="red" + if [ "$(echo "$SERVER_COV >= 80" | bc -l 2>/dev/null)" = "1" ]; then SERVER_COLOR="green" + elif [ "$(echo "$SERVER_COV >= 60" | bc -l 2>/dev/null)" = "1" ]; then SERVER_COLOR="yellow"; fi + echo "{\"schemaVersion\":1,\"label\":\"go server coverage\",\"message\":\"${SERVER_COV}%\",\"color\":\"${SERVER_COLOR}\"}" > .badges/go-server-coverage.json + + INGESTOR_COV="0" + if [ -f cmd/ingestor/ingestor-coverage.out ]; then + INGESTOR_COV=$(cd cmd/ingestor && go tool cover -func=ingestor-coverage.out | tail -1 | grep -oP '[\d.]+(?=%)') + fi + INGESTOR_COLOR="red" + if [ "$(echo "$INGESTOR_COV >= 80" | bc -l 2>/dev/null)" = "1" ]; then INGESTOR_COLOR="green" + elif [ "$(echo "$INGESTOR_COV >= 60" | bc -l 2>/dev/null)" = "1" ]; then INGESTOR_COLOR="yellow"; fi + echo "{\"schemaVersion\":1,\"label\":\"go ingestor coverage\",\"message\":\"${INGESTOR_COV}%\",\"color\":\"${INGESTOR_COLOR}\"}" > .badges/go-ingestor-coverage.json + + echo "## Go Coverage" >> $GITHUB_STEP_SUMMARY + echo "| Module | Coverage |" >> $GITHUB_STEP_SUMMARY + echo "|--------|----------|" >> $GITHUB_STEP_SUMMARY + echo "| Server | ${SERVER_COV}% |" >> $GITHUB_STEP_SUMMARY + echo "| Ingestor | ${INGESTOR_COV}% |" >> $GITHUB_STEP_SUMMARY + + - name: Upload Go coverage badges + if: success() + uses: actions/upload-artifact@v6 + with: + name: go-badges + path: .badges/go-*.json + retention-days: 1 + if-no-files-found: ignore + + # ─────────────────────────────────────────────────────────────── + # 2. Playwright E2E Tests (against Go server with fixture DB) + # ─────────────────────────────────────────────────────────────── + e2e-test: + name: "🎭 Playwright E2E Tests" + needs: [go-test] + runs-on: [self-hosted, Linux] + defaults: + run: + shell: bash + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Set up Node.js 22 + uses: actions/setup-node@v5 + with: + node-version: '22' + + - name: Clean Go module cache + run: rm -rf ~/go/pkg/mod 2>/dev/null || true + + - name: Set up Go 1.22 + uses: actions/setup-go@v6 + with: + go-version: '1.22' + cache-dependency-path: cmd/server/go.sum + + - name: Build Go server + run: | + cd cmd/server + go build -o ../../corescope-server . + echo "Go server built successfully" + + - name: Install npm dependencies + run: npm ci --production=false + + - name: Install Playwright browser + run: | + npx playwright install chromium 2>/dev/null || true + npx playwright install-deps chromium 2>/dev/null || true + + - name: Instrument frontend JS for coverage + run: sh scripts/instrument-frontend.sh + + - name: Start Go server with fixture DB + run: | + fuser -k 13581/tcp 2>/dev/null || true + sleep 1 + ./corescope-server -port 13581 -db test-fixtures/e2e-fixture.db -public public-instrumented & + echo $! > .server.pid + for i in $(seq 1 30); do + if curl -sf http://localhost:13581/api/stats > /dev/null 2>&1; then + echo "Server ready after ${i}s" + break + fi + if [ "$i" -eq 30 ]; then + echo "Server failed to start within 30s" + exit 1 + fi + sleep 1 + done + + - name: Run Playwright E2E tests (fail-fast) + run: | + BASE_URL=http://localhost:13581 node test-e2e-playwright.js 2>&1 | tee e2e-output.txt + + # DISABLED: Coverage collector takes 8+ min. E2E tests extract window.__coverage__ directly. + # - name: Collect frontend coverage + # if: success() + # run: | + # BASE_URL=http://localhost:13581 node scripts/collect-frontend-coverage.js 2>&1 | tee fe-coverage-output.txt || true + + - name: Generate frontend coverage badges + if: success() + run: | + E2E_PASS=$(grep -oP '[0-9]+(?=/)' e2e-output.txt | tail -1 || echo "0") + + mkdir -p .badges + if [ -f .nyc_output/frontend-coverage.json ] || [ -f .nyc_output/e2e-coverage.json ]; then + npx nyc report --reporter=text-summary --reporter=text 2>&1 | tee fe-report.txt + FE_COVERAGE=$(grep 'Statements' fe-report.txt | head -1 | grep -oP '[\d.]+(?=%)' || echo "0") + FE_COVERAGE=${FE_COVERAGE:-0} + FE_COLOR="red" + [ "$(echo "$FE_COVERAGE > 50" | bc -l 2>/dev/null)" = "1" ] && FE_COLOR="yellow" + [ "$(echo "$FE_COVERAGE > 80" | bc -l 2>/dev/null)" = "1" ] && FE_COLOR="brightgreen" + echo "{\"schemaVersion\":1,\"label\":\"frontend coverage\",\"message\":\"${FE_COVERAGE}%\",\"color\":\"${FE_COLOR}\"}" > .badges/frontend-coverage.json + echo "## Frontend: ${FE_COVERAGE}% coverage" >> $GITHUB_STEP_SUMMARY + fi + echo "{\"schemaVersion\":1,\"label\":\"e2e tests\",\"message\":\"${E2E_PASS:-0} passed\",\"color\":\"brightgreen\"}" > .badges/e2e-tests.json + + - name: Stop test server + if: success() + run: | + if [ -f .server.pid ]; then + kill $(cat .server.pid) 2>/dev/null || true + rm -f .server.pid + fi + + - name: Upload E2E badges + if: success() + uses: actions/upload-artifact@v6 + with: + name: e2e-badges + path: .badges/ + retention-days: 1 + if-no-files-found: ignore + + # ─────────────────────────────────────────────────────────────── + # 3. Build Docker Image + # ─────────────────────────────────────────────────────────────── + build: + name: "🏗️ Build Docker Image" + needs: [e2e-test] + runs-on: [self-hosted, Linux] + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Node.js 22 + uses: actions/setup-node@v5 + with: + node-version: '22' + + - name: Build Go Docker image + run: | + echo "${GITHUB_SHA::7}" > .git-commit + APP_VERSION=$(node -p "require('./package.json').version") \ + GIT_COMMIT="${GITHUB_SHA::7}" \ + APP_VERSION=$(grep -oP 'APP_VERSION:-\K[^}]+' docker-compose.yml | head -1 || echo "3.0.0") + GIT_COMMIT=$(git rev-parse --short HEAD) + BUILD_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ') + export APP_VERSION GIT_COMMIT BUILD_TIME + docker compose --profile staging-go build staging-go + echo "Built Go staging image ✅" + + # ─────────────────────────────────────────────────────────────── + # 4. Deploy Staging (master only) + # ─────────────────────────────────────────────────────────────── + deploy: + name: "🚀 Deploy Staging" + if: github.event_name == 'push' + needs: [build] + runs-on: [self-hosted, Linux] + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Stop old staging container + run: | + docker compose --profile staging-go stop staging-go 2>/dev/null || true + docker wait corescope-staging-go 2>/dev/null || true + docker rm -f corescope-staging-go 2>/dev/null || true + fuser -k 82/tcp 2>/dev/null || true + for i in $(seq 1 20); do + if ! docker ps --filter "name=^corescope-staging-go$" --format '{{.Names}}' | grep -q '^corescope-staging-go$'; then + echo "Old staging container fully stopped/removed" + break + fi + if [ "$i" -eq 20 ]; then + echo "Timed out waiting for old staging container to exit" + docker ps --filter "name=^corescope-staging-go$" + exit 1 + fi + sleep 1 + done + + - name: Start staging on port 82 + run: | + docker compose --profile staging-go up -d staging-go + + - name: Healthcheck staging container + run: | + for i in $(seq 1 120); do + HEALTH=$(docker inspect corescope-staging-go --format '{{.State.Health.Status}}' 2>/dev/null || echo "starting") + if [ "$HEALTH" = "healthy" ]; then + echo "Staging healthy after ${i}s" + break + fi + if [ "$i" -eq 120 ]; then + echo "Staging failed health check after 120s" + docker logs corescope-staging-go --tail 50 + exit 1 + fi + sleep 1 + done + + - name: Smoke test staging API + run: | + if curl -sf http://localhost:82/api/stats | grep -q engine; then + echo "Staging verified — engine field present ✅" + else + echo "Staging /api/stats did not return engine field" + exit 1 + fi + + # ─────────────────────────────────────────────────────────────── + # 5. Publish Badges & Summary (master only) + # ─────────────────────────────────────────────────────────────── + publish: + name: "📝 Publish Badges & Summary" + if: github.event_name == 'push' + needs: [deploy] + runs-on: [self-hosted, Linux] + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Download Go coverage badges + continue-on-error: true + uses: actions/download-artifact@v6 + with: + name: go-badges + path: .badges/ + + - name: Download E2E badges + continue-on-error: true + uses: actions/download-artifact@v6 + with: + name: e2e-badges + path: .badges/ + + - name: Publish coverage badges to repo + continue-on-error: true + run: | + git config user.name "github-actions" + git config user.email "actions@github.com" + git remote set-url origin https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git + git add .badges/ -f + git diff --cached --quiet || (git commit -m "ci: update test badges [skip ci]" && git push) || echo "Badge push failed" + + - name: Post deployment summary + run: | + echo "## Staging Deployed ✓" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Commit:** \`$(git rev-parse --short HEAD)\` — $(git log -1 --format=%s)" >> $GITHUB_STEP_SUMMARY