fix(repo): pythinker-cli rename sweep + feat(installer): hosted Windo… #60
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: Dispatch pythinker-home sync | |
| # Triggers a pythinker-home website sync when the install scripts or README | |
| # change on main. This is a best-effort latency optimization, NOT the source of | |
| # truth: pythinker-home re-syncs on its own daily cron (sync-upstream-products | |
| # @ 04:17 UTC) using its own GITHUB_TOKEN, so a failed dispatch here never | |
| # leaves the website stale. The job therefore degrades gracefully (warns + | |
| # alerts, exits 0) instead of red-lining main when the org-owned | |
| # pythinker-release-bot App is missing, uninstalled, or rotated. | |
| # | |
| # Release promotion's post-release sync lives in promote-release.yml: a | |
| # GITHUB_TOKEN-created release never fires a workflow, so the old | |
| # `release: published` trigger here was dead code that never ran. | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - README.md | |
| - scripts/install.ps1 | |
| - scripts/install-native.sh | |
| - .github/workflows/dispatch-pythinker-home-sync.yml | |
| jobs: | |
| dispatch: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| env: | |
| DISPATCH_OWNER: Pythoughts-labs | |
| DISPATCH_REPO: pythinker-home | |
| steps: | |
| # Mint a short-lived installation token for the org-owned | |
| # pythinker-release-bot App (Contents: write on pythinker-home only). | |
| # `continue-on-error` is deliberate: a missing/rotated App must not fail | |
| # the run — the next step detects the empty token and degrades gracefully. | |
| - name: Mint GitHub App token for pythinker-home | |
| id: app-token | |
| continue-on-error: true | |
| uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # pinned from v2.2.2 | |
| with: | |
| app-id: ${{ secrets.PYTHINKER_RELEASE_BOT_APP_ID }} | |
| private-key: ${{ secrets.PYTHINKER_RELEASE_BOT_APP_PRIVATE_KEY }} | |
| owner: ${{ env.DISPATCH_OWNER }} | |
| repositories: ${{ env.DISPATCH_REPO }} | |
| permission-contents: write | |
| - name: Trigger pythinker-home sync (best-effort) | |
| env: | |
| DISPATCH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| TOKEN_OUTCOME: ${{ steps.app-token.outcome }} | |
| SOURCE_REPO: ${{ github.repository }} | |
| RELEASE_TAG: ${{ github.sha }} | |
| DISPATCH_OWNER: ${{ env.DISPATCH_OWNER }} | |
| DISPATCH_REPO: ${{ env.DISPATCH_REPO }} | |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| # No `set -e`: anticipated failures degrade gracefully (warn + alert + | |
| # exit 0) so a broken App never red-lines main. The daily cron in | |
| # pythinker-home is the real sync guarantee. | |
| set -uo pipefail | |
| degrade() { | |
| reason="$1" | |
| echo "::warning title=pythinker-home sync skipped::${reason}" | |
| { | |
| echo "### :warning: pythinker-home sync dispatch skipped" | |
| echo "" | |
| echo "${reason}" | |
| echo "" | |
| echo "**Non-blocking:** pythinker-home re-syncs on its daily cron (\`sync-upstream-products\` @ 04:17 UTC) using its own token, so the website is not stale." | |
| echo "" | |
| echo "**Restore the fast path:** recreate/install the **pythinker-release-bot** App on \`${DISPATCH_OWNER}\` with *Contents: write* on \`${DISPATCH_REPO}\`, then update the \`PYTHINKER_RELEASE_BOT_APP_ID\` and \`PYTHINKER_RELEASE_BOT_APP_PRIVATE_KEY\` secrets." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| if [ -n "${SLACK_WEBHOOK_URL:-}" ]; then | |
| alert=$(jq -n --arg run_url "$RUN_URL" --arg repo "$SOURCE_REPO" --arg reason "$reason" \ | |
| '{"text":":warning: *pythinker-home sync dispatch skipped (non-blocking)*","attachments":[{"color":"warning","fields":[{"title":"Repo","value":$repo,"short":true},{"title":"Reason","value":$reason,"short":false},{"title":"Run","value":"<\($run_url)|View logs>","short":false}]}]}') | |
| curl -sS -X POST -H "Content-Type: application/json" -d "$alert" "$SLACK_WEBHOOK_URL" || true | |
| fi | |
| exit 0 | |
| } | |
| if [ "${TOKEN_OUTCOME}" != "success" ] || [ -z "${DISPATCH_TOKEN:-}" ]; then | |
| degrade "Could not mint a pythinker-release-bot App token (token step outcome: ${TOKEN_OUTCOME}). The App is likely missing/uninstalled on ${DISPATCH_OWNER}, or its credentials are stale." | |
| fi | |
| payload=$(jq -n --arg source_repo "$SOURCE_REPO" --arg tag "$RELEASE_TAG" \ | |
| '{"event_type":"sync-pythinker-products","client_payload":{"source_repo":$source_repo,"tag":$tag}}') | |
| # repository_dispatch returns 204 on success. Retry transient errors; | |
| # a persistent failure degrades (the cron still backstops the sync). | |
| resp=$(mktemp) | |
| code="000" | |
| for attempt in 1 2 3; do | |
| code=$(curl -sS -o "$resp" -w '%{http_code}' \ | |
| -X POST \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Authorization: Bearer $DISPATCH_TOKEN" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${DISPATCH_OWNER}/${DISPATCH_REPO}/dispatches" \ | |
| -d "$payload") || code="000" | |
| if [ "$code" = "204" ]; then | |
| echo "Dispatched sync-pythinker-products to ${DISPATCH_OWNER}/${DISPATCH_REPO} (HTTP 204)." | |
| exit 0 | |
| fi | |
| echo "Dispatch attempt ${attempt} returned HTTP ${code}: $(head -c 200 "$resp")" | |
| [ "$attempt" -lt 3 ] && sleep $((attempt * 3)) || true | |
| done | |
| degrade "repository_dispatch to ${DISPATCH_OWNER}/${DISPATCH_REPO} failed after 3 attempts (last HTTP ${code}): $(head -c 200 "$resp")" |