name: Go Verification on: push: branches: [main] paths-ignore: - '**.md' - 'docs/**' - 'LICENSE' - '.gitignore' - 'skill/**' - 'plugin/**' pull_request: branches: [main] paths-ignore: - '**.md' - 'docs/**' - 'LICENSE' - '.gitignore' - 'skill/**' - 'plugin/**' env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true permissions: contents: read jobs: # Check which files changed to conditionally run jobs changes: name: Detect Changes runs-on: ubuntu-latest if: github.event.pull_request.draft == false outputs: go: ${{ steps.filter.outputs.go }} steps: - uses: actions/checkout@v5 - uses: dorny/paths-filter@v3 id: filter with: filters: | go: - '**.go' - 'go.mod' - 'go.sum' - 'cmd/**' - 'internal/**' build: name: Build & Vet needs: changes runs-on: ubuntu-latest steps: - name: Check for Go changes id: check run: | if [ "${{ needs.changes.outputs.go }}" = "true" ]; then echo "Go files detected" echo "has_go_changes=true" >> $GITHUB_OUTPUT else echo "⏭️ No Go files changed - docs/workflow-only changes detected" echo "has_go_changes=false" >> $GITHUB_OUTPUT fi - uses: actions/checkout@v5 - uses: actions/setup-go@v6 if: steps.check.outputs.has_go_changes == 'true' with: go-version: '1.26' - name: Download deps if: steps.check.outputs.has_go_changes == 'true' run: go mod download - name: Format check if: steps.check.outputs.has_go_changes == 'true' run: | unformatted=$(gofmt -l .) if [ -n "$unformatted" ]; then echo "Files not formatted:" echo "$unformatted" exit 1 fi - name: Vet if: steps.check.outputs.has_go_changes == 'true' run: go vet ./... - name: Build if: steps.check.outputs.has_go_changes == 'true' run: go build -o pinchtab ./cmd/pinchtab - name: Test with coverage if: steps.check.outputs.has_go_changes == 'true' run: go test ./... -v -count=1 -coverprofile=coverage.out -covermode=atomic - name: Coverage summary if: steps.check.outputs.has_go_changes == 'true' run: | coverage=$(go tool cover -func=coverage.out | tail -1) echo "### Coverage" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "$coverage" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY lint: name: Lint needs: changes runs-on: ubuntu-latest steps: - name: Check for Go changes id: check run: | if [ "${{ needs.changes.outputs.go }}" = "true" ]; then echo "has_go_changes=true" >> $GITHUB_OUTPUT else echo "⏭️ No Go files changed, skipping lint" echo "has_go_changes=false" >> $GITHUB_OUTPUT fi - uses: actions/checkout@v5 - uses: actions/setup-go@v6 if: steps.check.outputs.has_go_changes == 'true' with: go-version: '1.26' - uses: oven-sh/setup-bun@v2 if: needs.changes.outputs.go == 'true' with: bun-version: latest - name: Install tygo if: needs.changes.outputs.go == 'true' run: go install github.com/gzuidhof/tygo@latest - name: Build dashboard if: needs.changes.outputs.go == 'true' run: | cd dashboard && bun install --frozen-lockfile && cd .. ./scripts/build-dashboard.sh - name: golangci-lint if: steps.check.outputs.has_go_changes == 'true' uses: golangci/golangci-lint-action@v7 with: version: v2.9.0 security: name: Security Scan needs: changes runs-on: ubuntu-latest steps: - name: Check for Go changes id: check run: | if [ "${{ needs.changes.outputs.go }}" = "true" ]; then echo "has_go_changes=true" >> $GITHUB_OUTPUT else echo "⏭️ No Go files changed, skipping security scan" echo "has_go_changes=false" >> $GITHUB_OUTPUT fi - uses: actions/checkout@v5 - uses: actions/setup-go@v6 if: steps.check.outputs.has_go_changes == 'true' with: go-version: '1.26' - name: Install gosec if: steps.check.outputs.has_go_changes == 'true' run: go install github.com/securego/gosec/v2/cmd/gosec@latest - name: Run gosec if: steps.check.outputs.has_go_changes == 'true' run: gosec -exclude=G301,G302,G304,G306,G404,G107,G115,G703,G704,G705,G706 -fmt=json -out=gosec-results.json ./... || true - name: Check critical findings if: steps.check.outputs.has_go_changes == 'true' run: | # Fail on G112 (Slowloris), G204 (command injection) ISSUES=$(cat gosec-results.json | jq '[.Issues[] | select(.rule_id == "G112" or .rule_id == "G204")] | length') echo "Critical issues: $ISSUES" cat gosec-results.json | jq '.Stats' if [ "$ISSUES" -gt 0 ]; then cat gosec-results.json | jq '.Issues[] | select(.rule_id == "G112" or .rule_id == "G204")' exit 1 fi