chore(release): prepare 0.61.0 (#232) #45
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: Update Homebrew tap | |
| on: | |
| push: | |
| tags: | |
| - "v[0-9]+.[0-9]+.[0-9]+" | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version to push to the tap (e.g. 0.27.0)" | |
| required: true | |
| type: string | |
| concurrency: | |
| group: homebrew-tap-${{ github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| jobs: | |
| update: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| env: | |
| TAP_OWNER: Pythoughts-labs | |
| TAP_REPO: homebrew-pythinker | |
| steps: | |
| - name: Checkout source repo | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pinned from v6.0.2 | |
| - name: Resolve version | |
| id: ver | |
| env: | |
| GITHUB_REF: ${{ github.ref }} | |
| INPUT_VERSION: ${{ inputs.version }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "$GITHUB_REF" =~ ^refs/tags/v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then | |
| version="${BASH_REMATCH[1]}" | |
| elif [[ -n "$INPUT_VERSION" ]]; then | |
| version="$INPUT_VERSION" | |
| else | |
| echo "::error::No version source available" >&2 | |
| exit 1 | |
| fi | |
| echo "version=${version}" >> "$GITHUB_OUTPUT" | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # pinned from v6.2.0 | |
| with: | |
| python-version: "3.13" | |
| - name: Generate native formula | |
| env: | |
| PKG_VERSION: ${{ steps.ver.outputs.version }} | |
| run: | | |
| set -euxo pipefail | |
| mkdir -p out/Formula | |
| deadline=$(( $(date +%s) + 30 * 60 )) | |
| until python packages/homebrew-tap/generate-formula.py \ | |
| --version "$PKG_VERSION" \ | |
| --template packages/homebrew-tap/pythinker-code.rb.tmpl \ | |
| --output out/Formula/pythinker-code.rb; do | |
| if [ "$(date +%s)" -gt "$deadline" ]; then | |
| echo "::error::Native release assets for ${PKG_VERSION} were not ready within 30 minutes" >&2 | |
| exit 1 | |
| fi | |
| echo "Native release assets not ready for ${PKG_VERSION}; sleeping 30s" | |
| sleep 30 | |
| done | |
| echo "--- generated formula head ---" | |
| head -40 out/Formula/pythinker-code.rb | |
| # Mint a short-lived installation token for an org-owned GitHub App that | |
| # has Contents: Read and write on the tap repo. This replaces a personal | |
| # PAT: the App is owned by the org (survives member/org changes), its token | |
| # expires in ~1h (minted fresh each run), and it is scoped to only the tap | |
| # repo. owner/repositories are required because the App must reach a repo | |
| # other than the one this workflow runs in. | |
| - name: Mint GitHub App token for the tap repo | |
| id: app-token | |
| env: | |
| APP_ID: ${{ secrets.HOMEBREW_TAP_APP_ID }} | |
| APP_PRIVATE_KEY: ${{ secrets.HOMEBREW_TAP_APP_PRIVATE_KEY }} | |
| TAP_OWNER: ${{ env.TAP_OWNER }} | |
| TAP_REPO: ${{ env.TAP_REPO }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${APP_ID:-}" ] || [ -z "${APP_PRIVATE_KEY:-}" ]; then | |
| echo "::error::No tap GitHub App credentials available. Configure HOMEBREW_TAP_APP_ID and HOMEBREW_TAP_APP_PRIVATE_KEY, and install the App on ${TAP_OWNER}/${TAP_REPO} with Contents: Read and write." >&2 | |
| exit 1 | |
| fi | |
| b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; } | |
| key_file=$(mktemp) | |
| trap 'rm -f "$key_file"' EXIT | |
| printf '%s\n' "$APP_PRIVATE_KEY" > "$key_file" | |
| chmod 600 "$key_file" | |
| now=$(date +%s) | |
| header=$(printf '{"alg":"RS256","typ":"JWT"}' | b64url) | |
| payload=$(jq -nc --argjson iat "$((now - 60))" --argjson exp "$((now + 540))" --arg iss "$APP_ID" '{iat:$iat,exp:$exp,iss:$iss}' | b64url) | |
| unsigned="${header}.${payload}" | |
| signature=$(printf '%s' "$unsigned" | openssl dgst -sha256 -sign "$key_file" | b64url) | |
| jwt="${unsigned}.${signature}" | |
| installation_id=$(curl --fail-with-body -sS \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Authorization: Bearer ${jwt}" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${TAP_OWNER}/${TAP_REPO}/installation" \ | |
| | jq -er '.id') | |
| token=$(jq -nc --arg repo "$TAP_REPO" '{repositories:[$repo],permissions:{contents:"write"}}' \ | |
| | curl --fail-with-body -sS \ | |
| -X POST \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Authorization: Bearer ${jwt}" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/app/installations/${installation_id}/access_tokens" \ | |
| -d @- \ | |
| | jq -er '.token') | |
| echo "::add-mask::$token" | |
| echo "token=$token" >> "$GITHUB_OUTPUT" | |
| - name: Sync formula into tap repo (handles empty repo on first run) | |
| env: | |
| PKG_VERSION: ${{ steps.ver.outputs.version }} | |
| TAP_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${TAP_TOKEN:-}" ]; then | |
| echo "::error::No tap token available — the GitHub App token mint produced an empty value. Confirm HOMEBREW_TAP_APP_ID and HOMEBREW_TAP_APP_PRIVATE_KEY secrets are set and the App is installed on ${TAP_OWNER}/${TAP_REPO} with Contents: Read and write, then re-run this workflow." >&2 | |
| exit 1 | |
| fi | |
| # We do NOT use actions/checkout for the tap repo because on the | |
| # very first run the tap is empty and has no refs/heads/main yet. | |
| # `git init` + `git fetch || true` + `git push -u origin main` | |
| # works for both the empty case (creates main) and the populated | |
| # case (fast-forwards to current main, then adds a new commit). | |
| rm -rf tap-repo | |
| mkdir tap-repo | |
| cd tap-repo | |
| git init -q -b main | |
| git remote add origin \ | |
| "https://x-access-token:${TAP_TOKEN}@github.com/${TAP_OWNER}/${TAP_REPO}.git" | |
| # Pull whatever's on main if anything (no-op on the inaugural run). | |
| if git ls-remote --exit-code --heads origin main >/dev/null 2>&1; then | |
| git fetch --depth 1 origin main | |
| git reset --hard FETCH_HEAD | |
| fi | |
| mkdir -p Formula | |
| cp ../out/Formula/pythinker-code.rb Formula/pythinker-code.rb | |
| if [ ! -f README.md ]; then | |
| cat > README.md <<EOF | |
| # homebrew-pythinker | |
| Homebrew tap for [Pythinker Code](https://github.com/Pythoughts-labs/pythinker-code). | |
| \`\`\`sh | |
| brew install Pythoughts-labs/pythinker/pythinker-code | |
| \`\`\` | |
| This tap is auto-updated by the | |
| [homebrew-tap.yml](https://github.com/Pythoughts-labs/pythinker-code/blob/main/.github/workflows/homebrew-tap.yml) | |
| workflow on every \`v*.*.*\` tag. Do not hand-edit \`Formula/*\` — your | |
| edits will be overwritten on the next release. | |
| EOF | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add Formula/pythinker-code.rb README.md | |
| if git diff --cached --quiet; then | |
| echo "Formula already up to date for ${PKG_VERSION}; nothing to push." | |
| exit 0 | |
| fi | |
| git commit -m "pythinker-code ${PKG_VERSION}" \ | |
| -m "Auto-updated by Pythinker-Code/.github/workflows/homebrew-tap.yml" | |
| git push -u origin HEAD:main |