docs: add git self-sync to persistent agent definitions (Stories 81.1… #3310
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: [main] | |
| push: | |
| branches: [main] | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| # Detect which files changed to skip expensive jobs on docs-only PRs. | |
| # Push-to-main always runs the full suite (no path filtering). | |
| changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| code: ${{ steps.filter.outputs.code }} | |
| docker: ${{ steps.filter.outputs.docker }} | |
| perf: ${{ steps.filter.outputs.perf }} | |
| scripts: ${{ steps.filter.outputs.scripts }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 | |
| id: filter | |
| with: | |
| filters: | | |
| code: | |
| - '**.go' | |
| - 'go.mod' | |
| - 'go.sum' | |
| - '.golangci.yml' | |
| - '.github/workflows/**' | |
| - 'scripts/hooks/**' | |
| docker: | |
| - 'Dockerfile*' | |
| - 'docker-compose*' | |
| perf: | |
| - 'internal/core/**' | |
| - 'internal/adapters/textfile/**' | |
| - 'go.mod' | |
| scripts: | |
| - 'scripts/**' | |
| - 'justfile' | |
| # Lightweight pass for non-code PRs so required checks aren't stuck pending. | |
| skip-pass: | |
| name: CI Skipped (no code changes) | |
| needs: changes | |
| if: github.event_name == 'pull_request' && needs.changes.outputs.code != 'true' && needs.changes.outputs.docker != 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - run: echo "No code changes — Go CI skipped." | |
| # ShellCheck lints shell scripts for common bugs (unquoted vars, missing error | |
| # handling, bashisms in sh scripts, etc.). Currently informational — does NOT | |
| # block merge. To promote to required: | |
| # 1. Fix existing ShellCheck warnings in scripts/*.sh | |
| # 2. Remove `continue-on-error: true` below | |
| # 3. Add "ShellCheck" to the branch ruleset's required status checks | |
| shellcheck: | |
| name: ShellCheck | |
| needs: changes | |
| if: needs.changes.outputs.scripts == 'true' || github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install ShellCheck | |
| run: sudo apt-get update && sudo apt-get install -y shellcheck | |
| - name: Lint top-level scripts | |
| run: shellcheck scripts/*.sh | |
| - name: Lint hook scripts | |
| run: shellcheck scripts/hooks/*.sh | |
| quality-gate: | |
| name: Quality Gate | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' || needs.changes.outputs.docker == 'true' || github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| cache-dependency-path: go.sum | |
| - name: Install gofumpt | |
| run: go install mvdan.cc/gofumpt@v0.7.0 | |
| - name: Check formatting (gofumpt) | |
| run: | | |
| UNFORMATTED=$(gofumpt -l .) | |
| if [ -n "$UNFORMATTED" ]; then | |
| echo "::error::Files need formatting with gofumpt:" | |
| echo "$UNFORMATTED" | |
| exit 1 | |
| fi | |
| - name: Run go vet | |
| run: go vet ./... | |
| - name: Run govulncheck | |
| run: | | |
| go install golang.org/x/vuln/cmd/govulncheck@latest | |
| govulncheck ./... | |
| - name: Run golangci-lint | |
| uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9 | |
| with: | |
| version: v2.10.1 | |
| - name: Run tests with coverage and race detector | |
| run: go test ./... -v -count=1 -race -coverprofile=coverage.out -covermode=atomic | |
| - name: Display coverage summary | |
| run: go tool cover -func=coverage.out | |
| - name: Enforce coverage floor | |
| env: | |
| COVERAGE_THRESHOLD: '75' | |
| run: | | |
| COVERAGE=$(go tool cover -func=coverage.out | grep '^total:' | awk '{print $NF}' | tr -d '%') | |
| echo "Total coverage: ${COVERAGE}% (threshold: ${COVERAGE_THRESHOLD}%)" | |
| if [ "$(echo "$COVERAGE < $COVERAGE_THRESHOLD" | bc -l)" -eq 1 ]; then | |
| echo "::error::Coverage ${COVERAGE}% is below the ${COVERAGE_THRESHOLD}% threshold" | |
| exit 1 | |
| fi | |
| - name: Post coverage comment on PR | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const { execSync } = require('child_process'); | |
| const summary = execSync('go tool cover -func=coverage.out').toString(); | |
| const totalLine = summary.split('\n').find(l => l.startsWith('total:')); | |
| const coverage = totalLine ? totalLine.match(/[\d.]+%/)?.[0] : 'unknown'; | |
| const body = `## Coverage Report\n\n**Total coverage: ${coverage}**\n\n<details>\n<summary>Package breakdown</summary>\n\n\`\`\`\n${summary}\`\`\`\n</details>`; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const existing = comments.find(c => c.body.startsWith('## Coverage Report')); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } | |
| - name: Validate build | |
| run: go build -o /dev/null ./cmd/threedoors | |
| benchmarks: | |
| name: Performance Benchmarks | |
| needs: changes | |
| if: github.event_name == 'push' || (needs.changes.outputs.code == 'true' && needs.changes.outputs.perf == 'true') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| cache-dependency-path: go.sum | |
| - name: Run benchmarks | |
| run: go test -bench=. -benchmem -count=3 -run='^$' ./internal/core/ ./internal/adapters/textfile/ | tee benchmark-results.txt | |
| - name: Validate NFR13 (<100ms threshold) | |
| run: go test -run='NFR13' -v -count=1 ./internal/core/ ./internal/adapters/textfile/ | |
| - name: Upload benchmark results | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: benchmark-results | |
| path: benchmark-results.txt | |
| retention-days: 90 | |
| test-docker-e2e: | |
| name: Docker E2E Tests | |
| if: github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Cache Docker layers | |
| uses: actions/cache@v5 | |
| with: | |
| path: /tmp/.buildx-cache | |
| key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile.test', 'go.mod', 'go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-buildx- | |
| - name: Build Docker test image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: Dockerfile.test | |
| load: true | |
| tags: threedoors-test:latest | |
| cache-from: type=local,src=/tmp/.buildx-cache | |
| cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max | |
| - name: Create test-results directory | |
| run: mkdir -p test-results | |
| - name: Run Docker E2E tests | |
| run: docker compose -f docker-compose.test.yml run --rm -T test | |
| - name: Check for golden file diffs | |
| if: always() | |
| run: | | |
| if git diff --name-only | grep -q '\.golden$'; then | |
| echo "::warning::Golden file changes detected:" | |
| git diff -- '*.golden' | |
| else | |
| echo "No golden file changes detected." | |
| fi | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: docker-e2e-results | |
| path: test-results/ | |
| retention-days: 14 | |
| # Rotate buildx cache to prevent unbounded growth | |
| - name: Rotate cache | |
| if: always() | |
| run: | | |
| rm -rf /tmp/.buildx-cache | |
| mv /tmp/.buildx-cache-new /tmp/.buildx-cache 2>/dev/null || true | |
| build-binaries: | |
| name: Build Binaries | |
| needs: quality-gate | |
| if: github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| cache-dependency-path: go.sum | |
| - name: Generate version | |
| id: version | |
| run: | | |
| VERSION="0.1.0-alpha.$(date -u +%Y%m%d).$(date -u +%H%M%S).${GITHUB_SHA::7}" | |
| TAG="alpha-$(date -u +%Y%m%d)-$(date -u +%H%M%S)-${GITHUB_SHA::7}" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| - name: Build binaries | |
| run: | | |
| LDFLAGS="-X main.version=${{ steps.version.outputs.version }}" | |
| GOOS=darwin GOARCH=arm64 go build -ldflags "$LDFLAGS" -o threedoors-darwin-arm64 ./cmd/threedoors | |
| GOOS=darwin GOARCH=amd64 go build -ldflags "$LDFLAGS" -o threedoors-darwin-amd64 ./cmd/threedoors | |
| GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o threedoors-linux-amd64 ./cmd/threedoors | |
| - name: Build alpha binaries | |
| run: | | |
| LDFLAGS="-X main.version=${{ steps.version.outputs.version }} -X main.channel=alpha" | |
| GOOS=darwin GOARCH=arm64 go build -ldflags "$LDFLAGS" -o threedoors-a-darwin-arm64 ./cmd/threedoors | |
| GOOS=darwin GOARCH=amd64 go build -ldflags "$LDFLAGS" -o threedoors-a-darwin-amd64 ./cmd/threedoors | |
| GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o threedoors-a-linux-amd64 ./cmd/threedoors | |
| - name: Upload binaries | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: binaries | |
| path: threedoors-* | |
| retention-days: 14 | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| tag: ${{ steps.version.outputs.tag }} | |
| sign-and-notarize: | |
| name: Sign & Notarize | |
| needs: build-binaries | |
| if: github.event_name == 'push' && vars.SIGNING_ENABLED == 'true' | |
| environment: release | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download binaries | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: binaries | |
| - name: Import certificates | |
| env: | |
| APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_INSTALLER_CERTIFICATE_P12: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_P12 }} | |
| APPLE_INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_PASSWORD }} | |
| run: | | |
| # Create temporary keychain | |
| security create-keychain -p "" build.keychain | |
| security default-keychain -s build.keychain | |
| security unlock-keychain -p "" build.keychain | |
| # Import application certificate | |
| echo "$APPLE_CERTIFICATE_P12" | base64 --decode > cert.p12 | |
| security import cert.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign | |
| rm cert.p12 | |
| # Import installer certificate (for pkg signing) | |
| echo "$APPLE_INSTALLER_CERTIFICATE_P12" | base64 --decode > installer-cert.p12 | |
| security import installer-cert.p12 -k build.keychain -P "$APPLE_INSTALLER_CERTIFICATE_PASSWORD" -T /usr/bin/pkgbuild -T /usr/bin/productbuild -T /usr/bin/productsign | |
| rm installer-cert.p12 | |
| # Install Apple Developer ID G2 intermediate certificate | |
| curl -sfo /tmp/DeveloperIDG2CA.cer https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer | |
| security add-certificates -k build.keychain /tmp/DeveloperIDG2CA.cer | |
| rm /tmp/DeveloperIDG2CA.cer | |
| # Allow Apple tools (codesign, pkgbuild, productsign) to access keychain | |
| security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain | |
| - name: Verify certificate import | |
| run: | | |
| echo "Listing signing identities:" | |
| security find-identity -v build.keychain | |
| echo "" | |
| echo "Checking for installer identity:" | |
| security find-identity -v build.keychain | grep -i "installer" || echo "WARNING: No installer identity found in keychain" | |
| - name: Sign darwin binaries | |
| env: | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| run: | | |
| codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" --identifier "com.arcavenae.threedoors" --timestamp threedoors-darwin-arm64 | |
| codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" --identifier "com.arcavenae.threedoors" --timestamp threedoors-darwin-amd64 | |
| codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" --identifier "com.arcavenae.threedoors-a" --timestamp threedoors-a-darwin-arm64 | |
| codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" --identifier "com.arcavenae.threedoors-a" --timestamp threedoors-a-darwin-amd64 | |
| - name: Verify signatures | |
| run: | | |
| codesign --verify --deep --strict threedoors-darwin-arm64 | |
| codesign --verify --deep --strict threedoors-darwin-amd64 | |
| codesign --verify --deep --strict threedoors-a-darwin-arm64 | |
| codesign --verify --deep --strict threedoors-a-darwin-amd64 | |
| - name: Build app bundles | |
| run: | | |
| VERSION="${{ needs.build-binaries.outputs.version }}" | |
| chmod +x scripts/create-app.sh | |
| ./scripts/create-app.sh threedoors-darwin-arm64 "$VERSION" . | |
| mv ThreeDoors.app ThreeDoors-arm64.app | |
| ./scripts/create-app.sh threedoors-darwin-amd64 "$VERSION" . | |
| mv ThreeDoors.app ThreeDoors-amd64.app | |
| - name: Sign app bundles | |
| env: | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| run: | | |
| codesign --force --deep --options runtime --sign "$APPLE_SIGNING_IDENTITY" --timestamp ThreeDoors-arm64.app | |
| codesign --force --deep --options runtime --sign "$APPLE_SIGNING_IDENTITY" --timestamp ThreeDoors-amd64.app | |
| - name: Build dmg images | |
| run: | | |
| VERSION="${{ needs.build-binaries.outputs.version }}" | |
| chmod +x scripts/create-dmg.sh | |
| ./scripts/create-dmg.sh ThreeDoors-arm64.app "$VERSION" threedoors-arm64.dmg | |
| ./scripts/create-dmg.sh ThreeDoors-amd64.app "$VERSION" threedoors-amd64.dmg | |
| - name: Build pkg installers | |
| env: | |
| APPLE_INSTALLER_IDENTITY: ${{ secrets.APPLE_INSTALLER_IDENTITY }} | |
| run: | | |
| # Verify installer identity exists in keychain before building | |
| if ! security find-identity -v build.keychain | grep -q "$APPLE_INSTALLER_IDENTITY"; then | |
| echo "::error::APPLE_INSTALLER_IDENTITY not found in keychain. Expected format: 'Developer ID Installer: Name (TeamID)'" | |
| echo "Available identities:" | |
| security find-identity -v build.keychain | |
| exit 1 | |
| fi | |
| VERSION="${{ needs.build-binaries.outputs.version }}" | |
| chmod +x scripts/create-pkg.sh | |
| ./scripts/create-pkg.sh threedoors-darwin-arm64 "$VERSION" "$APPLE_INSTALLER_IDENTITY" threedoors-arm64.pkg | |
| ./scripts/create-pkg.sh threedoors-darwin-amd64 "$VERSION" "$APPLE_INSTALLER_IDENTITY" threedoors-amd64.pkg | |
| - name: Notarize and staple all artifacts | |
| env: | |
| APPLE_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_NOTARIZATION_APPLE_ID }} | |
| APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} | |
| APPLE_NOTARIZATION_TEAM_ID: ${{ secrets.APPLE_NOTARIZATION_TEAM_ID }} | |
| run: | | |
| for ARTIFACT in threedoors-arm64.pkg threedoors-amd64.pkg threedoors-arm64.dmg threedoors-amd64.dmg; do | |
| echo "Notarizing $ARTIFACT..." | |
| xcrun notarytool submit "$ARTIFACT" \ | |
| --apple-id "$APPLE_NOTARIZATION_APPLE_ID" \ | |
| --password "$APPLE_NOTARIZATION_PASSWORD" \ | |
| --team-id "$APPLE_NOTARIZATION_TEAM_ID" \ | |
| --wait --timeout 14400 | |
| xcrun stapler staple "$ARTIFACT" | |
| done | |
| - name: Upload signed binaries and installers | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: signed-binaries | |
| path: | | |
| threedoors-darwin-arm64 | |
| threedoors-darwin-amd64 | |
| threedoors-a-darwin-arm64 | |
| threedoors-a-darwin-amd64 | |
| threedoors-arm64.pkg | |
| threedoors-amd64.pkg | |
| threedoors-arm64.dmg | |
| threedoors-amd64.dmg | |
| retention-days: 14 | |
| - name: Cleanup keychain | |
| if: always() | |
| run: security delete-keychain build.keychain || true | |
| release: | |
| name: Create Release | |
| needs: [build-binaries, sign-and-notarize] | |
| if: github.event_name == 'push' && !cancelled() && needs.build-binaries.result == 'success' | |
| environment: release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| # Try signed binaries first, fall back to unsigned | |
| - name: Download signed binaries | |
| if: needs.sign-and-notarize.result == 'success' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: signed-binaries | |
| - name: Download unsigned binaries (fallback) | |
| if: needs.sign-and-notarize.result != 'success' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: binaries | |
| - name: Download all binaries (for linux) | |
| if: needs.sign-and-notarize.result == 'success' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: binaries | |
| path: unsigned-binaries | |
| - name: Copy linux binaries from unsigned artifacts | |
| if: needs.sign-and-notarize.result == 'success' | |
| run: | | |
| cp unsigned-binaries/threedoors-linux-amd64 . | |
| cp unsigned-binaries/threedoors-a-linux-amd64 . | |
| rm -rf unsigned-binaries | |
| - name: List release files | |
| run: ls -la threedoors-* 2>/dev/null || true | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ needs.build-binaries.outputs.tag }} | |
| VERSION: ${{ needs.build-binaries.outputs.version }} | |
| COMMIT: ${{ github.sha }} | |
| SIGNED: ${{ needs.sign-and-notarize.result == 'success' && 'Yes (Apple Developer ID)' || 'No (unsigned)' }} | |
| run: | | |
| BODY="Automated alpha release from merge to main. | |
| **Version:** ${VERSION} | |
| **Commit:** ${COMMIT} | |
| **Signed:** ${SIGNED} | |
| **Binaries:** | |
| - \`threedoors-darwin-arm64\` — macOS Apple Silicon | |
| - \`threedoors-darwin-amd64\` — macOS Intel | |
| - \`threedoors-linux-amd64\` — Linux x86_64 | |
| - \`threedoors-a-darwin-arm64\` — Alpha macOS Apple Silicon | |
| - \`threedoors-a-darwin-amd64\` — Alpha macOS Intel | |
| - \`threedoors-a-linux-amd64\` — Alpha Linux x86_64" | |
| if [ "$SIGNED" = "Yes (Apple Developer ID)" ]; then | |
| BODY="${BODY} | |
| - \`threedoors-arm64.pkg\` — macOS Installer (Apple Silicon) | |
| - \`threedoors-amd64.pkg\` — macOS Installer (Intel) | |
| - \`threedoors-arm64.dmg\` — macOS Disk Image (Apple Silicon) | |
| - \`threedoors-amd64.dmg\` — macOS Disk Image (Intel)" | |
| fi | |
| BODY="${BODY} | |
| **Homebrew (alpha):** \`brew install arcavenae/tap/threedoors-a\`" | |
| ASSETS=() | |
| for f in threedoors-*; do | |
| [ -f "$f" ] && ASSETS+=("$f") | |
| done | |
| gh release create "$TAG" \ | |
| --title "Alpha ${TAG}" \ | |
| --notes "$BODY" \ | |
| --prerelease \ | |
| "${ASSETS[@]}" | |
| # Gated by repository variable — set vars.ALPHA_TAP_ENABLED to 'true' | |
| # in GitHub Settings > Actions > Variables to activate alpha formula publishing. | |
| # The alpha GitHub release is always created; only the tap push is controlled. | |
| - name: Update alpha Homebrew formula | |
| if: vars.ALPHA_TAP_ENABLED == 'true' | |
| env: | |
| HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.build-binaries.outputs.version }}" | |
| TAG="${{ needs.build-binaries.outputs.tag }}" | |
| BASE_URL="https://github.com/arcavenae/ThreeDoors/releases/download/${TAG}" | |
| # Compute SHA256 checksums for alpha binaries | |
| SHA_ARM64=$(sha256sum threedoors-a-darwin-arm64 | cut -d' ' -f1) | |
| SHA_AMD64=$(sha256sum threedoors-a-darwin-amd64 | cut -d' ' -f1) | |
| SHA_LINUX=$(sha256sum threedoors-a-linux-amd64 | cut -d' ' -f1) | |
| # Generate formula | |
| cat > threedoors-a.rb <<FORMULA | |
| class ThreedoorsA < Formula | |
| desc "TUI task manager — alpha channel (updated on every main push)" | |
| homepage "https://github.com/arcavenae/ThreeDoors" | |
| version "${VERSION}" | |
| license "MIT" | |
| if OS.mac? && Hardware::CPU.arm? | |
| url "${BASE_URL}/threedoors-a-darwin-arm64" | |
| sha256 "${SHA_ARM64}" | |
| elsif OS.mac? | |
| url "${BASE_URL}/threedoors-a-darwin-amd64" | |
| sha256 "${SHA_AMD64}" | |
| elsif OS.linux? | |
| url "${BASE_URL}/threedoors-a-linux-amd64" | |
| sha256 "${SHA_LINUX}" | |
| end | |
| def install | |
| if OS.mac? && Hardware::CPU.arm? | |
| bin.install "threedoors-a-darwin-arm64" => "threedoors-a" | |
| elsif OS.mac? | |
| bin.install "threedoors-a-darwin-amd64" => "threedoors-a" | |
| elsif OS.linux? | |
| bin.install "threedoors-a-linux-amd64" => "threedoors-a" | |
| end | |
| end | |
| test do | |
| assert_match "ThreeDoors", shell_output("#{bin}/threedoors-a --version 2>&1") | |
| end | |
| end | |
| FORMULA | |
| # Remove leading whitespace from heredoc indentation | |
| sed -i 's/^ //' threedoors-a.rb | |
| # Validate Ruby syntax before pushing | |
| ruby -c threedoors-a.rb | |
| # Clone tap repo, update formula, push | |
| git clone "https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/arcavenae/homebrew-tap.git" tap-repo | |
| mkdir -p tap-repo/Formula | |
| cp threedoors-a.rb tap-repo/Formula/threedoors-a.rb | |
| cd tap-repo | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Formula/threedoors-a.rb | |
| if git diff --cached --quiet; then | |
| echo "Formula unchanged, skipping push" | |
| else | |
| git commit -m "chore(formula): update threedoors-a to ${TAG}" | |
| git push origin main | |
| fi | |
| - name: Clean up old alpha releases | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh release list --limit 200 \ | |
| | grep 'alpha-' \ | |
| | tail -n +31 \ | |
| | awk '{print $1}' \ | |
| | xargs -I{} gh release delete {} --yes --cleanup-tag | |