Repository navigation
184 lines (165 loc) · 7.48 KB
/
Copy pathhomebrew-tap.yml
File metadata and controls
184 lines (165 loc) · 7.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
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: PyModel
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/PyModel/pythinker-cli).
\`\`\`sh
brew install PyModel/pythinker/pythinker-code
\`\`\`
This tap is auto-updated by the
[homebrew-tap.yml](https://github.com/PyModel/pythinker-cli/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