Implement tak v0 - minimal claims tracker #293
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
| name: Release Go Projects | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - "**/*.go" | |
| - "**/go.mod" | |
| - "**/go.sum" | |
| - ".github/workflows/release.yml" | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| paths: | |
| - "**/*.go" | |
| - "**/go.mod" | |
| - "**/go.sum" | |
| - ".github/workflows/release.yml" | |
| workflow_dispatch: | |
| inputs: | |
| projects: | |
| description: "Comma or newline separated list of project directories to release (e.g. ingest,want)" | |
| required: false | |
| default: "" | |
| jobs: | |
| # First job: detect which Go projects need releases | |
| detect: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| projects: ${{ steps.find-projects.outputs.projects }} | |
| has_projects: ${{ steps.find-projects.outputs.has_projects }} | |
| mirror_projects: ${{ steps.mirror-config.outputs.projects }} | |
| mirror_metadata: ${{ steps.mirror-config.outputs.metadata }} | |
| mirror_repo: ${{ steps.mirror-config.outputs.repo }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Find Go projects | |
| id: find-projects | |
| run: | | |
| python3 scripts/detect_go_projects.py \ | |
| --event "${{ github.event_name }}" \ | |
| --before-sha "${{ github.event.before }}" \ | |
| --pr-base "${GITHUB_EVENT_PULL_REQUEST_BASE_REF}" \ | |
| --ref-sha "${{ github.sha }}" \ | |
| --manual-projects "${GITHUB_EVENT_INPUTS_PROJECTS}" \ | |
| --output "$GITHUB_OUTPUT" | |
| env: | |
| GITHUB_EVENT_PULL_REQUEST_BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| GITHUB_EVENT_INPUTS_PROJECTS: ${{ github.event.inputs.projects }} | |
| - name: Load release mirror config | |
| id: mirror-config | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| from pathlib import Path | |
| import tomllib | |
| data = tomllib.loads(Path("release-mirror.toml").read_text()) | |
| mirror = data.get("mirror", {}) | |
| projects = [] | |
| metadata = {} | |
| for entry in mirror.get("projects", []): | |
| name = entry.get("name") | |
| if not name: | |
| continue | |
| projects.append(name) | |
| metadata[name] = entry | |
| output = os.environ["GITHUB_OUTPUT"] | |
| with open(output, "a", encoding="utf-8") as fh: | |
| fh.write(f"projects={json.dumps(projects)}\n") | |
| fh.write(f"metadata={json.dumps(metadata)}\n") | |
| fh.write(f"repo={mirror.get('public_repo', '')}\n") | |
| PY | |
| # Second job: build and release each project | |
| release: | |
| needs: detect | |
| if: needs.detect.outputs.has_projects == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| project: ${{ fromJson(needs.detect.outputs.projects) }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Set up Go | |
| uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 | |
| with: | |
| go-version: "1.24.7" | |
| cache-dependency-path: ${{ matrix.project }}/go.sum | |
| - name: Determine version and branch | |
| id: version | |
| run: | | |
| PROJECT="${MATRIX_PROJECT}" | |
| # Determine branch/ref name | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| # For PRs, use pr-NUMBER format | |
| REF="pr-${{ github.event.pull_request.number }}" | |
| else | |
| # For push to main | |
| REF="main" | |
| fi | |
| # Sanitize ref name (replace / with -) | |
| REF_SAFE="${REF//\//-}" | |
| # Find existing tags for this project+ref combination | |
| TAG_PREFIX="${PROJECT}--${REF_SAFE}." | |
| EXISTING_TAGS=$(git tag -l "${TAG_PREFIX}*" | sort -V) | |
| if [ -z "$EXISTING_TAGS" ]; then | |
| # No existing tags, start with .1 | |
| VERSION_NUM=1 | |
| else | |
| # Get the highest version number | |
| LAST_TAG=$(printf '%s\n' "$EXISTING_TAGS" | tail -n 1) | |
| prefix="$TAG_PREFIX" | |
| LAST_NUM="${LAST_TAG#"$prefix"}" | |
| VERSION_NUM=$((LAST_NUM + 1)) | |
| fi | |
| TAG="${PROJECT}--${REF_SAFE}.${VERSION_NUM}" | |
| VERSION="${REF_SAFE}.${VERSION_NUM}" | |
| { | |
| echo "project=$PROJECT" | |
| echo "ref=$REF" | |
| echo "ref_safe=$REF_SAFE" | |
| echo "tag=$TAG" | |
| echo "version=$VERSION" | |
| echo "version_num=$VERSION_NUM" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "Building ${PROJECT} version ${VERSION} (tag: ${TAG})" | |
| env: | |
| MATRIX_PROJECT: ${{ matrix.project }} | |
| - name: Build binaries | |
| id: build | |
| run: | | |
| set -euo pipefail | |
| PROJECT="${MATRIX_PROJECT}" | |
| VERSION="${STEPS_VERSION_OUTPUTS_VERSION}" | |
| if [ -f "$PROJECT/cmd/main.go" ]; then | |
| BUILD_TARGET="./cmd" | |
| else | |
| BUILD_TARGET="." | |
| fi | |
| mkdir -p dist | |
| DIST_DIR="$(pwd)/dist" | |
| PLATFORMS=( | |
| "linux/amd64" | |
| "linux/arm64" | |
| "darwin/amd64" | |
| "darwin/arm64" | |
| ) | |
| for platform in "${PLATFORMS[@]}"; do | |
| IFS='/' read -r GOOS GOARCH <<< "$platform" | |
| OUTPUT_NAME="${PROJECT}-${VERSION}-${GOOS}-${GOARCH}" | |
| echo "Building for $GOOS/$GOARCH..." | |
| BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| LDFLAGS="-X main.Version=${VERSION} -X main.GitCommit=${{ github.sha }} -X main.BuildTime=${BUILD_TIME}" | |
| if ! (cd "$PROJECT" && GOOS="$GOOS" GOARCH="$GOARCH" go build -ldflags="$LDFLAGS" -o "$DIST_DIR/$OUTPUT_NAME" "$BUILD_TARGET"); then | |
| echo "Failed to build for $GOOS/$GOARCH" >&2 | |
| exit 1 | |
| fi | |
| echo "Built: dist/$OUTPUT_NAME" | |
| done | |
| ls -lh dist/ | |
| env: | |
| MATRIX_PROJECT: ${{ matrix.project }} | |
| STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }} | |
| - name: Package Homebrew archives | |
| if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' && contains(fromJson(needs.detect.outputs.mirror_projects), matrix.project) }} | |
| run: | | |
| set -euo pipefail | |
| PROJECT="${MATRIX_PROJECT}" | |
| VERSION="${STEPS_VERSION_OUTPUTS_VERSION}" | |
| for binary in dist/"${PROJECT}-${VERSION}-"*; do | |
| if [[ ! -f "$binary" ]]; then | |
| continue | |
| fi | |
| if [[ "$binary" == *.tar.gz ]]; then | |
| continue | |
| fi | |
| prefix="dist/${PROJECT}-${VERSION}-" | |
| platform="${binary#"$prefix"}" | |
| tmpdir="$(mktemp -d)" | |
| cp "$binary" "$tmpdir/${PROJECT}" | |
| tarball="dist/${PROJECT}-${VERSION}-${platform}.tar.gz" | |
| ( | |
| cd "$tmpdir" | |
| tar -czf "$OLDPWD/$tarball" "${PROJECT}" | |
| ) | |
| rm -rf "$tmpdir" | |
| echo "Created $tarball" | |
| done | |
| env: | |
| MATRIX_PROJECT: ${{ matrix.project }} | |
| STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }} | |
| - name: Create Release | |
| uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 | |
| with: | |
| tag_name: ${{ steps.version.outputs.tag }} | |
| name: "${{ matrix.project }} ${{ steps.version.outputs.version }}" | |
| body: | | |
| Release of **${{ matrix.project }}** version `${{ steps.version.outputs.version }}` | |
| Built from: ${{ github.event_name == 'pull_request' && format('PR #{0}', github.event.pull_request.number) || 'main branch' }} | |
| Commit: ${{ github.sha }} | |
| ## Installation | |
| Download the appropriate binary for your platform, make it executable, and move it to your PATH: | |
| ```bash | |
| # Linux AMD64 | |
| wget https://github.com/${{ github.repository }}/releases/download/${{ steps.version.outputs.tag }}/${{ matrix.project }}-${{ steps.version.outputs.version }}-linux-amd64 | |
| chmod +x ${{ matrix.project }}-${{ steps.version.outputs.version }}-linux-amd64 | |
| sudo mv ${{ matrix.project }}-${{ steps.version.outputs.version }}-linux-amd64 /usr/local/bin/${{ matrix.project }} | |
| # macOS ARM64 (M1/M2) | |
| wget https://github.com/${{ github.repository }}/releases/download/${{ steps.version.outputs.tag }}/${{ matrix.project }}-${{ steps.version.outputs.version }}-darwin-arm64 | |
| chmod +x ${{ matrix.project }}-${{ steps.version.outputs.version }}-darwin-arm64 | |
| sudo mv ${{ matrix.project }}-${{ steps.version.outputs.version }}-darwin-arm64 /usr/local/bin/${{ matrix.project }} | |
| ``` | |
| ## Available Binaries | |
| - Linux: amd64, arm64 | |
| - macOS: amd64 (Intel), arm64 (Apple Silicon) | |
| files: dist/* | |
| draft: false | |
| prerelease: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Checkout Homebrew tap repository | |
| if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' && contains(fromJson(needs.detect.outputs.mirror_projects), matrix.project) }} | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| repository: ${{ needs.detect.outputs.mirror_repo }} | |
| token: ${{ secrets.PUBLIC_RELEASE_TOKEN }} | |
| path: tap | |
| persist-credentials: false | |
| - name: Update Homebrew formula | |
| if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' && contains(fromJson(needs.detect.outputs.mirror_projects), matrix.project) }} | |
| working-directory: tap | |
| env: | |
| PROJECT: ${{ matrix.project }} | |
| VERSION: ${{ steps.version.outputs.version }} | |
| TAG: ${{ steps.version.outputs.tag }} | |
| MIRROR_REPO: ${{ needs.detect.outputs.mirror_repo }} | |
| MIRROR_METADATA: ${{ needs.detect.outputs.mirror_metadata }} | |
| SOURCE_REPO: ${{ github.repository }} | |
| run: | | |
| python3 - <<'PY' | |
| import hashlib | |
| import json | |
| import os | |
| from pathlib import Path | |
| project = os.environ["PROJECT"] | |
| version = os.environ["VERSION"] | |
| tag = os.environ["TAG"] | |
| mirror_repo = os.environ["MIRROR_REPO"] | |
| source_repo = os.environ["SOURCE_REPO"] | |
| metadata = json.loads(os.environ["MIRROR_METADATA"]) | |
| project_meta = metadata.get(project, {}) | |
| binary_name = project_meta.get("binary", project) | |
| def escape(value: str) -> str: | |
| return value.replace("\\", "\\\\").replace("\"", "\\\"") | |
| desc = escape(project_meta.get("desc", f"{project} CLI")) | |
| homepage = escape(project_meta.get("homepage", f"https://github.com/{source_repo}")) | |
| test_args = project_meta.get("test_args", ["--help"]) | |
| class_name = project_meta.get("formula_class") | |
| if not class_name: | |
| class_name = "".join(part.capitalize() for part in project.split("-")) | |
| dist_dir = Path(os.getcwd()).parent / "dist" | |
| artifacts = {} | |
| for archive in dist_dir.glob(f"{project}-{version}-*.tar.gz"): | |
| name = archive.name | |
| platform = name.split(f"{project}-{version}-", 1)[1].removesuffix(".tar.gz") | |
| digest = hashlib.sha256(archive.read_bytes()).hexdigest() | |
| artifacts[platform] = { | |
| "filename": name, | |
| "sha256": digest, | |
| } | |
| if not artifacts: | |
| raise SystemExit(f"No packaged Homebrew archives found for {project}") | |
| def url_for(filename: str) -> str: | |
| return f"https://github.com/{source_repo}/releases/download/{tag}/{filename}" | |
| def block_lines(os_name: str, arch: str): | |
| entry = artifacts.get(f"{os_name}-{arch}") | |
| if not entry: | |
| return None | |
| return [ | |
| f'url "{url_for(entry["filename"])}"', | |
| f'sha256 "{entry["sha256"]}"', | |
| ] | |
| mac_blocks = { | |
| "arm64": block_lines("darwin", "arm64"), | |
| "amd64": block_lines("darwin", "amd64"), | |
| } | |
| linux_blocks = { | |
| "arm64": block_lines("linux", "arm64"), | |
| "amd64": block_lines("linux", "amd64"), | |
| } | |
| lines = [ | |
| f"class {class_name} < Formula", | |
| f" desc \"{desc}\"", | |
| f" homepage \"{homepage}\"", | |
| f" version \"{version}\"", | |
| ] | |
| def append_os_section(buffer, os_name: str, blocks: dict): | |
| order = ["arm64", "amd64"] | |
| entries = [] | |
| for arch_name in order: | |
| block = blocks.get(arch_name) | |
| if not block: | |
| continue | |
| cpu_method = "arm?" if arch_name == "arm64" else "intel?" | |
| entries.append((cpu_method, block)) | |
| if not entries: | |
| return | |
| buffer.append(f" on_{os_name} do") | |
| for cpu_method, block in entries: | |
| buffer.append(f" if Hardware::CPU.{cpu_method}") | |
| for line in block: | |
| buffer.append(f" {line}") | |
| buffer.append(" end") | |
| buffer.append(" end") | |
| append_os_section(lines, "macos", mac_blocks) | |
| append_os_section(lines, "linux", linux_blocks) | |
| test_args_ruby = ", ".join([f'"{arg}"' for arg in test_args]) if test_args else '"--help"' | |
| lines.extend([ | |
| " def install", | |
| f" bin.install \"{binary_name}\"", | |
| " end", | |
| "", | |
| " test do", | |
| f" system \"#{{bin}}/{binary_name}\", {test_args_ruby}", | |
| " end", | |
| "end", | |
| ]) | |
| formula_dir = Path("Formula") | |
| formula_dir.mkdir(parents=True, exist_ok=True) | |
| formula_path = formula_dir / f"{project}.rb" | |
| formula_path.write_text("\n".join(lines) + "\n", encoding="utf-8") | |
| PY | |
| - name: Run brew audit | |
| if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' && contains(fromJson(needs.detect.outputs.mirror_projects), matrix.project) }} | |
| env: | |
| HOMEBREW_NO_AUTO_UPDATE: "1" | |
| MATRIX_PROJECT: ${{ matrix.project }} | |
| run: | | |
| eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" | |
| brew tap-new --no-git neongreen/mono || true | |
| TAP_DIR="$(brew --repository)/Library/Taps/neongreen/homebrew-mono" | |
| rm -rf "$TAP_DIR" | |
| ln -s "${GITHUB_WORKSPACE}/tap" "$TAP_DIR" | |
| brew audit --formula "neongreen/mono/${MATRIX_PROJECT}" | |
| - name: Commit Homebrew tap update | |
| if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' && contains(fromJson(needs.detect.outputs.mirror_projects), matrix.project) }} | |
| working-directory: tap | |
| env: | |
| PROJECT: ${{ matrix.project }} | |
| VERSION: ${{ steps.version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| git config user.name "mono-release-bot" | |
| git config user.email "mono-release-bot@example.com" | |
| git fetch origin | |
| git checkout main | |
| git pull --rebase origin main | |
| git add "Formula/${PROJECT}.rb" | |
| if git diff --staged --quiet; then | |
| echo "No Homebrew changes to commit" | |
| exit 0 | |
| fi | |
| git commit -m "brew: update ${PROJECT} to ${VERSION}" | |
| git push origin HEAD:main |