From 739a4b79c7255e548194892803bc32005199f6a8 Mon Sep 17 00:00:00 2001 From: tukuyomil032 Date: Fri, 14 Aug 2026 12:10:45 +0900 Subject: [PATCH 1/2] docs: add github-profile-stats-deploy skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PATが切れる・Vercelのプラットフォームバグ・camoの多層キャッシュ等、tukuyomil032のGitHubプロフィール統計カード運用で過去に踏んだ障害の切り分け手順とSecrets一覧を、セットアップ手順込みでスキル化した。 Co-Authored-By: Claude Sonnet 5 --- github-profile-stats-deploy/SKILL.ja.md | 206 +++++++++++++++ github-profile-stats-deploy/SKILL.md | 334 ++++++++++++++++++++++++ 2 files changed, 540 insertions(+) create mode 100644 github-profile-stats-deploy/SKILL.ja.md create mode 100644 github-profile-stats-deploy/SKILL.md diff --git a/github-profile-stats-deploy/SKILL.ja.md b/github-profile-stats-deploy/SKILL.ja.md new file mode 100644 index 0000000..88b1eb7 --- /dev/null +++ b/github-profile-stats-deploy/SKILL.ja.md @@ -0,0 +1,206 @@ +--- +name: github-profile-stats-deploy +description: > + tukuyomil032のGitHubプロフィールに埋め込んでいる統計カード(github-readme-stats、 + github-readme-streak-stats)のセットアップ・障害診断ガイド。両方ともVercelに + デプロイしたフォークをデータソースとして使い、プロフィールREADME側は静的SVGを + コミットして参照する構成になっている。README上のカードが「Failed to retrieve + contributions」「.env was not found」「Missing token in config」を表示している時、 + Vercelのビルドが「No more than 12 Serverless Functions」で失敗する時、 + いずれかのフォークのGitHub Actionsデプロイが落ちた時、PATの期限切れや + VERCEL_TOKEN/VERCEL_ORG_ID/VERCEL_PROJECT_IDの設定について聞かれた時、 + この構成をまっさらな状態からセットアップしたい時、カードが「死んでる」と + 言われた時に使う。当てずっぽうで直そうとせず、このスキルのチェック順に沿って + 切り分けること — ここで起きる障害はだいたい過去に一度診断済みの原因で、 + エラーメッセージだけを見ても原因が特定できないことが多い。 +--- + +# GitHubプロフィール統計カード — セットアップと障害診断 + +`tukuyomil032/tukuyomil032`のREADMEに埋め込んでいる2枚のカードは、それぞれ別のフォーク・別のVercelプロジェクトで動いている。 + +| カード | フォーク元リポジトリ | Vercelプロジェクト | 参照するPHP環境変数 | +|---|---|---|---| +| GitHub Stats | `tukuyomil032/github-readme-stats` | `github-readme-stats` | `PAT_1` | +| Streak Stats | `tukuyomil032/github-readme-streak-stats` | `github-readme-streak-stats` | `TOKEN` | + +**どちらのカードも、もうVercelを直接指す``タグでは表示していない。** 両方ともプロフィールリポジトリにコミットした静的SVGを参照する構成に変わっている。 + +- `assets/profile/stats-layout-v2.svg`(GitHub Stats、言語比率カードと合成済み) +- `assets/profile/streak-stats.svg`(Streak Stats) + +どちらも`tukuyomil032/tukuyomil032`の`scripts/generate-stats-svg.py`が生成する。やっていることは単純で、各Vercelデプロイが返すSVGをそのままfetchしてファイルに書き込むだけ。`.github/workflows/update-stats.yml`がこれを12時間おきに実行し(`workflow_dispatch`で手動起動も可)、変化があればコミットする。つまり2つのVercelデプロイは今や純粋な「バックエンドのデータソース」でしかない — カードの表示が古く見える時に、`vercel.json`やPHP側のコード、Vercelのデプロイ設定を疑うのは筋違いになりつつある。まず疑うべきは、実際にコミットされているSVGの中身そのものだ: + +```bash +gh api repos/tukuyomil032/tukuyomil032/contents/assets/profile/streak-stats.svg --jq '.content' | base64 -d | grep -o "Failed to retrieve\|Total Contributions" +``` + +コミット済みのSVG自体にエラー文言が入っているなら、原因は上流(Vercelデプロイ・PAT)側にある。逆にSVGの中身は正常なのにプロフィールページの表示だけおかしいなら、GitHub側のページキャッシュかブラウザキャッシュを疑った方が早い — 何かが壊れていると決めつける前に、少し待つかハードリロードを試すこと。 + +2つの環境変数名(`PAT_1`と`TOKEN`)は**互換性がない**。フォーク元のソースコードがそれぞれ独自に決めている名前なので、思い込みで揃えないこと。フォークの実装が変わった場合は都度確認する: + +```bash +gh api repos/tukuyomil032/github-readme-streak-stats/contents/api/index.php --jq '.content' | base64 -d +``` + +## なぜ静的SVG方式にしたのか + +以前のバージョンでは、READMEに `` を直接埋め込んでいた。これは動くには動くが、原因の特定に丸一日かかるタイプの壊れ方をした。VercelのエンドポイントがCache-Control: public, max-age=86400を返していたのが根本原因で、一度でもエラー文言入りのレスポンスがどこかにキャッシュされると、実体を直した後もそのキャッシュが生存し続ける限りエラー表示が消えない。しかもこのキャッシュ層は1つではなく3つ独立して存在する — Vercel自身のエッジキャッシュ、GitHubの画像プロキシcamo(upstreamのCache-Controlを尊重する実装で、24時間よりさらに長く保持することもある)、そして閲覧者のブラウザのHTTPキャッシュ。camoのキャッシュだけを`curl -X PURGE`で能動的にクリアしても、残り2層はノータッチのまま残り、しばらくするとまた同じエラーが再発する。 + +対して、スケジュール実行のActionがサーバー側でSVGをfetchし(毎回キャッシュバスター用のダミークエリを付けて)、それを普通のリポジトリファイルとしてコミットする方式なら、この3層のキャッシュを全部まとめて回避できる。コミットされたファイルにはcamoも長寿命のCache-Controlも介在しない。**もし「Vercel側は直したはずなのに、プロフィールだけ何時間経ってもFailed to retrieve contributionsのまま」という現象に再び遭遇したら、それはトークンやデプロイの問題ではなく、この静的SVG化以前に存在した多層キャッシュの生き残りを疑うべきサインだ。** + +## Secrets/トークン一覧 + +この構成が依存しているものを、生成方法・登録先とあわせて全部並べる。 + +### ① GitHub Personal Access Token(すべての大元) + +- **生成場所**: GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token(`https://github.com/settings/tokens/new`) +- **スコープ**: `repo` + `read:user` +- **有効期限**: 無期限か長めを推奨。期限切れが今回の障害連鎖の発端だった +- **用途**: 両方のVercelデプロイのPHPコードが、contribution/streakデータをGitHub GraphQL APIから取得するために使う +- **同じ1個のPATを両方のVercelプロジェクトに使い回してよい** — 変数名が違うだけで値は同じでいい。PATのGitHub側「Note」欄はただのラベルで、どのVercel変数名に入れるべきかとは無関係。 + +### ② Vercelプロジェクトの環境変数(PATの登録先) + +| プロジェクト | 変数名 | 登録場所 | +|---|---|---| +| `github-readme-stats` | `PAT_1` | Vercel → 該当プロジェクト → Settings → Environment Variables | +| `github-readme-streak-stats` | `TOKEN` | 同上 | + +Environment欄はデフォルト(All Environments)のままでよい。PATをローテーションしたら、両方の変数を更新した上で再デプロイしないと反映されない — 環境変数の変更は既に動いているデプロイに遡って効くことはない。 + +### ③ GitHub Actions Secrets(`github-readme-streak-stats`のみ必要) + +`github-readme-streak-stats`だけ、VercelのGit連携ではなくGitHub Actions経由でデプロイしている(理由は下の「Vercelデプロイが失敗する場合」参照)。登録場所は `github-readme-streak-stats` → Settings → Secrets and variables → Actions。 + +| Secret名 | 中身 | 取得方法 | +|---|---|---| +| `VERCEL_TOKEN` | Vercel APIトークン | Vercel → Account Settings → Tokens → Create Token(`https://vercel.com/account/tokens`)。**必ずFull Account/Teamスコープで作る**。プロジェクト限定スコープだと、素のREST APIは叩けるのに`vercel pull`が「Could not retrieve Project Settings」で失敗する | +| `VERCEL_ORG_ID` | `team_...`で始まるID | Vercel → Team Settings → General → Team ID。個人のHobbyアカウントでも表示される。Account SettingsのUser ID(`team_`プレフィックスなし)とは別物なので注意 | +| `VERCEL_PROJECT_ID` | `prj_...`で始まるID | 対象プロジェクト → Settings → General → Project ID | + +3つの中で`VERCEL_TOKEN`だけが正真正銘の機微情報なので、シェル履歴を経由させずGitHub UIから直接貼るのが安全。`VERCEL_ORG_ID`と`VERCEL_PROJECT_ID`は単なる識別子(認証情報ではない)なので、`gh secret set NAME --repo owner/repo --body "value"`でUIの手打ちミスを避けてもいい。手元のトークンから逆算したい場合: + +```bash +curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ + "https://api.vercel.com/v9/projects/" \ + | python3 -c "import json,sys; d=json.load(sys.stdin); print('accountId:', d['accountId']); print('id:', d['id'])" +``` +`accountId`が`VERCEL_ORG_ID`、`id`が`VERCEL_PROJECT_ID`(JSONの中には`latestDeployments`配下などに別の`id`フィールドが紛れているので、最初にマッチした`"id"`をそのまま拾わず、トップレベルのキーをちゃんとパースすること)。これは自分のターミナルで実行すること。レスポンスに含まれる`accountId`/`id`自体はただの識別子で共有しても問題ないが、`VERCEL_TOKEN`はAIアシスタントとのチャットに絶対貼らないこと。 + +### ④ `tukuyomil032/tukuyomil032`(プロフィールリポジトリ自体) + +追加のSecretsは不要。`update-stats.yml`はpush権限だけ要るが、それはワークフローの`permissions: contents: write`で自動発行される`GITHUB_TOKEN`で足りる。統計データの取得先は認証不要の公開Vercel URLなので、ここにPATは絡まない。 + +## ゼロからセットアップする場合 + +新しいアカウントで一から作る、あるいはまだフォークもVercelプロジェクトも存在しない場合、上の診断手順はまだ何ひとつ当てはまらない — 存在しないものを診断することはできない。以下を上から順にやっていくこと。スクリプトで自動判定できる段階ではないので、各ステップに「これで合ってるかはこう確認する」を添えてある。 + +1. **両方のリポジトリを自分のアカウントにフォークする**: `anuraghazra/github-readme-stats`と`DenverCoder1/github-readme-streak-stats`。確認: 自分のアカウントのリポジトリ一覧にフォークが出ていること。 + +2. **GitHub PATを作る**(上の①を参照)。確認: `gh api graphql -f query='query($l:String!){user(login:$l){createdAt}}' -f l="<自分のユーザー名>"`がそのトークンで自分のアカウント情報を返すこと。 + +3. **両方のフォークをVercelに別プロジェクトとしてインポートする**(Vercelダッシュボード → Add New → Project → 対象リポジトリを選択)。確認: それぞれ独自の`*.vercel.app`ドメインが割り当たり、初回デプロイが走ること(この時点ではおそらく壊れた状態で表示されるが、それでいい)。 + +4. **各プロジェクトの環境変数にPATを登録する**(上の②を参照)。フォーク元のソースコードが実際にチェックしている変数名に必ず合わせること — 名前が同じとは限らない。保存後は再デプロイすること。確認: デプロイURLを直接curlして、エラー文言ではなく本物のコンテンツが返るか見る: + ```bash + curl -s "https://<自分のstatsプロジェクト>.vercel.app/api?username=<自分のユーザー名>" | grep -o "Total Contributions\|Failed to retrieve" + ``` + +5. **`github-readme-streak-stats`のデプロイが「No more than 12 Serverless Functions」で丸ごと失敗する場合**、PATを疑う前に下の「Vercelデプロイが失敗する場合」を読むこと。トークンとは無関係のプラットフォーム側のバグ。 + +6. **プロフィールリポジトリ側に静的SVG生成の仕組みを用意する**。このリポジトリで既に使っているパターンをそのまま踏襲すればいい: 標準ライブラリのみのPython製`scripts/generate-stats-svg.py`(各Vercel URLをキャッシュバスター付きでfetchして`assets/profile/`に書き込む)と、それを定期実行してコミットする`.github/workflows/update-stats.yml`。VercelのURLをそのまま``でREADMEに埋め込むのはやらないこと — なぜそれがまずいかは上の「なぜ静的SVG方式にしたのか」を参照。確認: `workflow_dispatch`で一度手動実行し、この文書の一番上にある診断コマンドと同じ要領でコミットされたSVGの中身を確認する。 + +7. **`README.md`に静的SVGのパスを埋め込む**(VercelのURLではなく`assets/profile/streak-stats.svg`のような相対パスにする)。確認: github.com上でプロフィールページの表示を見る。 + +## 診断の順序(すでに全部揃っている前提) + +いきなり「PATを再生成する」に飛びつかないこと。ここで起きる不具合はだいたい別のところに原因がある。 + +### 1. エラーメッセージが実際に何と言っているか + +READMEが実際に表示しているのはVercelの生URLではなく**コミット済みのSVG**なので、そちらを確認する: + +```bash +gh api repos/tukuyomil032/tukuyomil032/contents/assets/profile/streak-stats.svg --jq '.content' | base64 -d | grep -oE "]*>[^<]*" +``` + +- **「.env was not found. Check Contributing.md for details.」** → 環境変数(`TOKEN`/`PAT_1`)がVercelプロジェクトのEnvironment Variablesにそもそも存在しない。 +- **「Missing token in config.」** → 環境変数は存在するが値が空。 +- **「Failed to retrieve contributions. This is likely a GitHub API issue.」** → 環境変数に何らかの値は入っているが、GitHub GraphQL API呼び出し自体が失敗している。ほぼ必ず**トークンの値そのものが不正**(期限切れ、あるいはドラッグ選択によるトレーリングスペース混入などのコピペ事故)。まず正常なトークンで同じクエリを試して切り分ける: + ```bash + gh api graphql -f query='query($login:String!){ user(login:$login){ createdAt } }' -f login="tukuyomil032" + ``` + これが成功するなら、アカウント側・API側は問題なく、原因はVercelの環境変数に入っている値そのものと確定できる。直し方: PATを再生成(スコープ/Noteは維持したまま新しい値になる)→ トリプルクリックでコピー → Vercelに貼り付けて古い値を完全に上書き → 再デプロイ → プロフィールリポジトリの`update-stats.yml`を`workflow_dispatch`で即時実行し、次のスケジュール実行を待たずに反映させる。 +- Vercelの`Build Failed`で**「No more than 12 Serverless Functions can be added to a Deployment on the Hobby plan」**が出ている場合 → 下の③を見ること。PATとは無関係。 + +### 2. PAT自体の確認 + +- 必要なスコープ: `repo` + `read:user`。 +- 有効期限が切れていないか。 +- Vercelの環境変数「Environment」設定はデフォルト(All Environments)のままでよく、手動で選び直す必要はない。 + +### 3. Vercelデプロイが失敗する場合(Build Failed / function数 / ENOENT) + +`github-readme-streak-stats`は**未解決のVercelプラットフォーム側バグ**を踏んでいた: `.vercelignore`(`excludeFiles`)による除外指定が、GitHub連携経由の自動ビルドでのFunction自動検出には反映されない。一方で手動の`vercel --prod` CLIデプロイでは正しく反映される。本家の作者自身が別プロジェクトで同じ症状を報告している: https://github.com/vercel-community/php/issues/331(本稿執筆時点で未修正)。症状: `Error: No more than 12 Serverless Functions can be added to a Deployment on the Hobby plan`、しかもカウントが`.vercelignore`で除外したはずの数と合わない。 + +**現在の動作している構成**(`github-readme-streak-stats`に既に適用済み — 一から診断し直す前にまずこれが崩れていないか確認すること): + +`vercel.json`は新しい`functions`プロパティではなく**旧形式の`builds`配列**を使う。`functions`はVercelのゼロコンフィグ自動検出と共存してしまい、これがリポジトリ全体の`.php`ファイル(`tests/*.php`、`scripts/*.php`含む)を残らずルート/Functionとして拾ってしまっていた。`builds`はゼロコンフィグ検出そのものを完全に置き換え、ビルド対象を明示的に絞り込む: + +```json +{ + "builds": [ + { "src": "api/**/*.php", "use": "vercel-php@0.9.0" } + ], + "routes": [ + { "handle": "filesystem" }, + { "src": "/demo/?", "dest": "/api/demo/index.php" }, + { "src": "/demo/(.+)", "dest": "/api/demo/vercel-static.php" }, + { "src": "/", "dest": "/api/index.php" } + ] +} +``` + +`.github/workflows/vercel-deploy.yml`は、バグを踏むVercelのGit連携に頼らずGitHub Actions経由でデプロイする — 本家の作者自身が使っている回避策と同じ。**ここでの落とし穴**: `vercel pull` → `vercel build --prod` → `vercel deploy --prebuilt`の3段階フローは使わないこと。`--prebuilt`は新しいBuild Output API向けで、旧形式の`builds`設定とは噛み合わず、ローカルのビルド出力を無視してリモート側で`ENOENT: vendor/autoload.php`を引き起こす。代わりに1ステップのリモートデプロイを使う: + +```yaml +name: Deploy to Vercel + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + steps: + - uses: actions/checkout@v4 + - name: Install Vercel CLI + run: npm install --global vercel@latest + - name: Deploy to Vercel + run: vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }} +``` + +Git連携側にバグがある以上、Vercel → Project → Settings → Gitで実際に**連携を解除**しておくこと。そうしないとpushのたびに、壊れた自動デプロイと正常なAction経由デプロイの2つが同時に走って混乱する。 + +### 4. Vercel環境変数やGitHub Secretsを変更した後 + +環境変数の変更は既に動いているデプロイには効かない — 新しいデプロイを走らせて初めて反映される。走らせて確認する: + +```bash +gh workflow run vercel-deploy.yml --repo tukuyomil032/github-readme-streak-stats +# 進捗確認: +gh run list --repo tukuyomil032/github-readme-streak-stats --workflow=vercel-deploy.yml --limit 1 --json status,conclusion +# Vercel側の実体が本当に直っているか確認(まだコミット済みSVGではなくソース側の確認): +curl -sS "https://github-readme-streak-stats-seven-orpin.vercel.app?user=tukuyomil032" | grep -o "Failed to retrieve\|Missing token\|env was not found\|Total Contributions" +# 最後に、プロフィールリポジトリの静的SVGを即座に更新して反映させる: +gh workflow run update-stats.yml --repo tukuyomil032/tukuyomil032 +``` + +Actionが緑になったことは、ビルドが成功した証拠でしかなく、カードが正しく表示される証拠にはならない。しかもVercel側を直しただけでは`update-stats.yml`が走るまでプロフィールREADMEには反映されない。各段階で、ジョブのステータスだけでなく実際に配信されている中身を都度確認すること。 diff --git a/github-profile-stats-deploy/SKILL.md b/github-profile-stats-deploy/SKILL.md new file mode 100644 index 0000000..323466d --- /dev/null +++ b/github-profile-stats-deploy/SKILL.md @@ -0,0 +1,334 @@ +--- +name: github-profile-stats-deploy +description: > + How to set up, diagnose, and fix tukuyomil032's self-hosted GitHub profile + stat cards (github-readme-stats and github-readme-streak-stats), deployed + on Vercel and embedded in the tukuyomil032/tukuyomil032 profile README via + committed static SVGs. Use this skill whenever the README stats/streak + cards show "Failed to retrieve contributions", ".env was not found", + "Missing token in config", a Vercel build error like "No more than 12 + Serverless Functions", a GitHub Actions deploy failure for either fork, or + when the user mentions PAT expiry, VERCEL_TOKEN/VERCEL_ORG_ID/VERCEL_PROJECT_ID + setup, wants to set this whole thing up from scratch on a new machine or + account, or describes these cards as broken/dead ("死んだ"). Work through + this skill's checklist in order rather than guessing — most failures here + have a specific, previously-diagnosed cause that isn't obvious from the + error message alone. +--- + +# GitHub profile stats cards — setup & troubleshooting + +Two independent forks power the two cards embedded in +`tukuyomil032/tukuyomil032`'s README, each deployed as its own Vercel +project: + +| Card | Fork repo | Vercel project | PHP env var it reads | +|---|---|---|---| +| GitHub Stats | `tukuyomil032/github-readme-stats` | `github-readme-stats` | `PAT_1` | +| Streak Stats | `tukuyomil032/github-readme-streak-stats` | `github-readme-streak-stats` | `TOKEN` | + +**Neither card is embedded as a live Vercel `` anymore.** Both are +committed static SVGs in the profile repo, refreshed on a schedule: + +- `assets/profile/stats-layout-v2.svg` (GitHub Stats, combined with the + top-languages card) +- `assets/profile/streak-stats.svg` (Streak Stats) + +Both are generated by `scripts/generate-stats-svg.py` in +`tukuyomil032/tukuyomil032`, which fetches each Vercel deployment's SVG +output and writes it to disk unmodified. `.github/workflows/update-stats.yml` +runs this script every 12 hours (`workflow_dispatch` also works for an +on-demand refresh) and commits the result if it changed. So the two Vercel +deployments are now purely **backend data sources** — nothing about +`vercel.json`, the PHP code, or the Vercel deploy pipeline should be touched +to fix a stale-looking card. Check the actual committed SVG content first: + +```bash +gh api repos/tukuyomil032/tukuyomil032/contents/assets/profile/streak-stats.svg --jq '.content' | base64 -d | grep -o "Failed to retrieve\|Total Contributions" +``` + +If the committed SVG itself contains an error, the problem is upstream (the +Vercel deployment / PAT). If the committed SVG is fine but the profile page +still looks wrong, it's very likely GitHub's page cache or your browser cache +— wait or hard-refresh before assuming anything is broken. + +The two env var names (`PAT_1` vs `TOKEN`) are **not interchangeable** — +each fork's source decides its own name. Don't assume; grep the actual repo +if a fork ever changes: + +```bash +gh api repos/tukuyomil032/github-readme-streak-stats/contents/api/index.php --jq '.content' | base64 -d +``` + +## Why the cards are static SVGs, not live images + +Earlier iterations of this setup embedded `` +directly in the README. This worked, but broke in a way that took a long +time to diagnose: the Vercel endpoint sets `Cache-Control: public, +max-age=86400`. Once a broken (error-text) response got cached by any of +three independent layers — Vercel's own edge cache, GitHub's `camo` image +proxy (which respects upstream cache headers and can hold on to entries even +longer than 24h), or the visiting browser's own HTTP cache — the profile +kept showing the error for up to a day *after* the underlying deployment was +already fixed. Actively purging camo's cache (`curl -X PURGE `) +only cleared one of the three layers and the problem would resurface. + +Fetching the SVG server-side (via the scheduled Action, with a cache-busting +query param on every fetch) and committing it as a plain repo asset sidesteps +all three cache layers at once — a committed file has no camo proxy and no +long-lived Cache-Control in front of it. **If you ever see a fresh "Failed to +retrieve contributions" that keeps showing up hours after you're sure the +Vercel deployment itself is fixed, suspect a caching layer that predates this +static-SVG setup, not the token or deployment again.** + +## Secrets & tokens reference + +Everything this setup depends on, where to create it, and where it goes. + +### 1. GitHub Personal Access Token (the root credential) + +- **Create at**: GitHub → Settings → Developer settings → Personal access + tokens → Tokens (classic) → Generate new token + (`https://github.com/settings/tokens/new`) +- **Scopes**: `repo` + `read:user` +- **Expiration**: prefer no expiration or a long one — expiry is what + started the whole chain of failures the first time this broke +- **Used by**: both Vercel deployments' PHP code, to call the GitHub GraphQL + API for contribution/streak data +- The **same PAT value can be reused for both Vercel projects** — just paste + it under each project's expected env var name. The PAT's GitHub-side + "Note" field is a cosmetic label only and has no bearing on which Vercel + variable name it needs to go into. + +### 2. Vercel project environment variables (where the PAT is registered) + +| Project | Variable name | Registered at | +|---|---|---| +| `github-readme-stats` | `PAT_1` | Vercel → project → Settings → Environment Variables | +| `github-readme-streak-stats` | `TOKEN` | same | + +Leave "Environment" as the default (All Environments / Production covered). +Any time the PAT is rotated, both variables need to be updated and the +project redeployed for the change to take effect — env var changes never +apply retroactively to an already-running deployment. + +### 3. GitHub Actions secrets (only `github-readme-streak-stats` needs these) + +`github-readme-streak-stats` deploys via a GitHub Actions workflow instead of +Vercel's Git integration (see "Vercel deploy failures" below for why). +Registered at: `github-readme-streak-stats` repo → Settings → Secrets and +variables → Actions. + +| Secret | Value | How to get it | +|---|---|---| +| `VERCEL_TOKEN` | a Vercel API token | Vercel → Account Settings → Tokens → Create Token (`https://vercel.com/account/tokens`). **Must be Full Account/Team scope, not project-scoped** — a project-scoped token can call plain REST endpoints fine but `vercel pull` fails with "Could not retrieve Project Settings" | +| `VERCEL_ORG_ID` | a `team_...`-prefixed id | Vercel → Team Settings → General → Team ID. Even a personal Hobby account exposes this. **Not** the "User ID" under Account Settings (no `team_` prefix — that's the wrong id for this purpose) | +| `VERCEL_PROJECT_ID` | a `prj_...`-prefixed id | Target project → Settings → General → Project ID | + +`VERCEL_TOKEN` is the one genuine secret of the three — paste it directly in +the GitHub UI rather than through a shell history. `VERCEL_ORG_ID` and +`VERCEL_PROJECT_ID` are plain identifiers (not credentials), safe to +retrieve and set via `gh secret set NAME --repo owner/repo --body "value"` to +avoid UI copy-paste mistakes. If you need to re-derive them from a token you +already have: + +```bash +curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ + "https://api.vercel.com/v9/projects/" \ + | python3 -c "import json,sys; d=json.load(sys.stdin); print('accountId:', d['accountId']); print('id:', d['id'])" +``` +`accountId` → `VERCEL_ORG_ID`, `id` → `VERCEL_PROJECT_ID` (the JSON has +other nested `id` fields, e.g. under `latestDeployments` — parse the +top-level key specifically, don't grep the first `"id"` match). Run this in +your own terminal; don't paste `VERCEL_TOKEN` into a chat with an AI +assistant even though the response fields themselves are safe to share. + +### 4. `tukuyomil032/tukuyomil032` (the profile repo itself) + +No extra secrets needed. `update-stats.yml` only needs push access, which +comes from the auto-issued `GITHUB_TOKEN` (`permissions: contents: write` in +the workflow). It fetches from the public Vercel URLs with no auth. + +## Setting this up from scratch + +If you're doing this on a fresh account, or the forks/Vercel projects don't +exist yet, none of the diagnostic steps above apply yet — there's nothing to +diagnose until each piece exists. Go through this checklist top to bottom; +each step includes how to confirm it actually worked before moving to the +next one, since nothing here can be verified by a script until the whole +chain is wired up. + +1. **Fork both repos**: `anuraghazra/github-readme-stats` and + `DenverCoder1/github-readme-streak-stats` into your own account. + Confirm: the forks show up under your account's repo list. + +2. **Create a GitHub PAT** (see §1 above). Confirm: `gh api graphql -f + query='query($l:String!){user(login:$l){createdAt}}' -f l=""` returns your account data using this token + (`GH_TOKEN= gh api graphql ...` or export `GITHUB_TOKEN`). + +3. **Import both forks into Vercel** as separate projects (Vercel dashboard + → Add New → Project → pick the repo). Confirm: each gets its own + `*.vercel.app` domain and an initial (likely broken, that's expected) + deployment. + +4. **Register the PAT as each project's env var** (see §2 above), matching + the variable name each fork's source code actually checks — don't assume + it's the same name across forks. Redeploy after saving. Confirm: curl the + deployment URL and grep for real content, not error text: + ```bash + curl -s "https://.vercel.app/api?username=" | grep -o "Total Contributions\|Failed to retrieve" + ``` + +5. **If `github-readme-streak-stats` fails to deploy at all** with "No more + than 12 Serverless Functions", see "Vercel deploy failures" below before + troubleshooting the PAT — it's an unrelated platform bug, not a token + issue. + +6. **Set up the static-SVG generation** in your profile repo, following the + pattern this repo already uses: a `scripts/generate-stats-svg.py` + (stdlib-only Python, fetches each Vercel URL with a cache-busting query + param and writes the SVG to `assets/profile/`) plus a scheduled + `.github/workflows/update-stats.yml` that runs it and commits the result. + Don't embed the live Vercel URLs directly as `` in the README — + see "Why the cards are static SVGs" above for what goes wrong if you do. + Confirm: run the workflow once via `workflow_dispatch`, then check the + committed SVG content the same way as the diagnostic command at the top + of this doc. + +7. **Embed the static SVG paths in `README.md`** (relative paths like + `assets/profile/streak-stats.svg`, not the Vercel URL). Confirm: view the + rendered profile page on github.com. + +## Diagnostic order (once everything above already exists) + +Work through these in order — don't jump straight to "regenerate the PAT", +most breakage here has been something else. + +### 1. What does the error message actually say? + +Check the *committed SVG*, not the live Vercel URL, since that's what the +README actually displays now: + +```bash +gh api repos/tukuyomil032/tukuyomil032/contents/assets/profile/streak-stats.svg --jq '.content' | base64 -d | grep -oE "]*>[^<]*" +``` + +- **".env was not found. Check Contributing.md for details."** → the env var + (`TOKEN` / `PAT_1`) doesn't exist at all in the Vercel project's + Environment Variables. +- **"Missing token in config."** → the env var exists but is empty. +- **"Failed to retrieve contributions. This is likely a GitHub API issue."** + → the env var has *some* value, but the GitHub GraphQL call itself failed. + This almost always means **the token value is wrong** (expired, or + corrupted by a copy-paste — e.g. a trailing newline/space from + drag-selecting instead of triple-clicking). Verify with a known-good + token first: + ```bash + gh api graphql -f query='query($login:String!){ user(login:$login){ createdAt } }' -f login="tukuyomil032" + ``` + If that succeeds, the account/API is fine and the problem is 100% the + value sitting in the Vercel env var. Fix: regenerate the PAT (keeps + scopes/note, gives a fresh value) → copy via triple-click → paste into + Vercel, overwriting the old value → redeploy → re-run the profile repo's + `update-stats.yml` (`workflow_dispatch`) to pick up the fix immediately + instead of waiting for the next scheduled run. +- A Vercel `Build Failed` with **"No more than 12 Serverless Functions can be + added to a Deployment on the Hobby plan"** → see §3 below, unrelated to + the PAT. + +### 2. Confirm the PAT itself + +- Scopes needed: `repo` + `read:user`. +- Check expiration hasn't passed. +- Vercel env var "Environment" setting can stay at the default (All + Environments) — no need to hand-pick. + +### 3. Vercel deploy is failing (Build Failed / function-count / ENOENT) + +`github-readme-streak-stats` hit a **known unresolved Vercel platform bug**: +excluded files (via `.vercelignore` or `excludeFiles`) aren't respected +during the GitHub-triggered build's function auto-detection, even though a +manual `vercel --prod` CLI deploy works fine. Reported by the upstream +author themselves: https://github.com/vercel-community/php/issues/331 — no +fix as of this writing. Symptom: `Error: No more than 12 Serverless +Functions can be added to a Deployment on the Hobby plan`, and the count +doesn't match what `.vercelignore` should have excluded. + +**The working setup** (already applied to `github-readme-streak-stats` — +check it's still intact before re-diagnosing from scratch): + +`vercel.json` uses the **legacy `builds` array**, not the newer `functions` +property. `functions` coexists with Vercel's zero-config auto-detection, +which scans *every* `.php` file in the whole repo (including `tests/*.php`, +`scripts/*.php`) as a route/function — `builds` fully replaces zero-config +detection and explicitly scopes the build: + +```json +{ + "builds": [ + { "src": "api/**/*.php", "use": "vercel-php@0.9.0" } + ], + "routes": [ + { "handle": "filesystem" }, + { "src": "/demo/?", "dest": "/api/demo/index.php" }, + { "src": "/demo/(.+)", "dest": "/api/demo/vercel-static.php" }, + { "src": "/", "dest": "/api/index.php" } + ] +} +``` + +`.github/workflows/vercel-deploy.yml` deploys via GitHub Actions instead of +relying on Vercel's buggy Git integration — the same workaround the upstream +author uses. **Important gotcha**: don't use the `vercel pull` → +`vercel build --prod` → `vercel deploy --prebuilt` three-step flow here — +`--prebuilt` is for the newer Build Output API and is incompatible with the +legacy `builds` config (it ignores the local build output and causes +`ENOENT: vendor/autoload.php` on the remote side). Use a single-step remote +deploy instead: + +```yaml +name: Deploy to Vercel + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + steps: + - uses: actions/checkout@v4 + - name: Install Vercel CLI + run: npm install --global vercel@latest + - name: Deploy to Vercel + run: vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }} +``` + +Given the Git integration is buggy, make sure it's actually **disconnected** +in Vercel → Project → Settings → Git, so pushes don't trigger a second, +broken, competing deployment. + +### 4. After any Vercel env var or GitHub Secret change + +Env var changes don't apply to an already-running deployment — a new deploy +must run for them to take effect. Trigger one and confirm: + +```bash +gh workflow run vercel-deploy.yml --repo tukuyomil032/github-readme-streak-stats +# poll: +gh run list --repo tukuyomil032/github-readme-streak-stats --workflow=vercel-deploy.yml --limit 1 --json status,conclusion +# then verify the actual Vercel-side content (not the committed SVG yet, this checks the source is really fixed): +curl -sS "https://github-readme-streak-stats-seven-orpin.vercel.app?user=tukuyomil032" | grep -o "Failed to retrieve\|Missing token\|env was not found\|Total Contributions" +# finally, force the profile repo's static SVG to pick up the fix immediately: +gh workflow run update-stats.yml --repo tukuyomil032/tukuyomil032 +``` + +A green Action run only proves the build succeeded — it does not prove the +card renders correctly, and a fixed Vercel deployment doesn't reach the +profile README until `update-stats.yml` runs. Always check the actual served +content at each stage, not just job status. From d698fe6b6fffef98b5a60bc4717c8a09786ddcfc Mon Sep 17 00:00:00 2001 From: tukuyomil032 Date: Fri, 14 Aug 2026 12:36:47 +0900 Subject: [PATCH 2/2] docs: rework github-profile-stats-deploy as a procedure-first, generic skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 個人のリポジトリ名/URLがそのまま書かれていて他人が使えなかった点と、躓きポイントの解説が本体になっていて肝心の手順が埋もれていた点を修正。SKILL.md/SKILL.ja.mdは手順を主役にし、各ステップの注記からreferences/配下の詳細(Secrets一覧、Vercelのfunction数バグ、多層キャッシュの話、エラー文言別の切り分け)へ該当行を指してリンクする構成に作り直した。 Co-Authored-By: Claude Sonnet 5 --- github-profile-stats-deploy/SKILL.ja.md | 222 ++------- github-profile-stats-deploy/SKILL.md | 429 +++++------------- .../references/cache-layers.md | 59 +++ .../references/diagnosing-errors.md | 76 ++++ .../references/secrets-and-tokens.md | 94 ++++ .../references/vercel-deploy-bug.md | 86 ++++ 6 files changed, 459 insertions(+), 507 deletions(-) create mode 100644 github-profile-stats-deploy/references/cache-layers.md create mode 100644 github-profile-stats-deploy/references/diagnosing-errors.md create mode 100644 github-profile-stats-deploy/references/secrets-and-tokens.md create mode 100644 github-profile-stats-deploy/references/vercel-deploy-bug.md diff --git a/github-profile-stats-deploy/SKILL.ja.md b/github-profile-stats-deploy/SKILL.ja.md index 88b1eb7..a3b9d2c 100644 --- a/github-profile-stats-deploy/SKILL.ja.md +++ b/github-profile-stats-deploy/SKILL.ja.md @@ -1,206 +1,62 @@ --- name: github-profile-stats-deploy description: > - tukuyomil032のGitHubプロフィールに埋め込んでいる統計カード(github-readme-stats、 - github-readme-streak-stats)のセットアップ・障害診断ガイド。両方ともVercelに - デプロイしたフォークをデータソースとして使い、プロフィールREADME側は静的SVGを - コミットして参照する構成になっている。README上のカードが「Failed to retrieve - contributions」「.env was not found」「Missing token in config」を表示している時、 - Vercelのビルドが「No more than 12 Serverless Functions」で失敗する時、 - いずれかのフォークのGitHub Actionsデプロイが落ちた時、PATの期限切れや - VERCEL_TOKEN/VERCEL_ORG_ID/VERCEL_PROJECT_IDの設定について聞かれた時、 - この構成をまっさらな状態からセットアップしたい時、カードが「死んでる」と - 言われた時に使う。当てずっぽうで直そうとせず、このスキルのチェック順に沿って - 切り分けること — ここで起きる障害はだいたい過去に一度診断済みの原因で、 - エラーメッセージだけを見ても原因が特定できないことが多い。 + Vercelにデプロイした自前のGitHubプロフィール統計カード(github-readme-stats、 + github-readme-streak-stats、あるいはそれらに類するPHP/Node製フォーク)を + セットアップする手順と、壊れた時に直す手順。ユーザーが自分のGitHub統計/ + ストリークカードを自前デプロイしたい時、プロフィールREADMEのカードが + 「Failed to retrieve contributions」「.env was not found」「Missing token + in config」を表示している時、対象フォークのVercelデプロイが「No more than + 12 Serverless Functions」で失敗する時、対象フォークのGitHub Actionsデプロイ + が落ちた時、統計カードプロジェクト用のVERCEL_TOKEN/VERCEL_ORG_ID/ + VERCEL_PROJECT_IDの設定について聞かれた時、プロフィールの統計/ストリーク + カードが壊れている・死んでいると言われた時に使う。当てずっぽうで直そうと + せず、以下の該当する手順を順番通りに進めること — ここで起きる不具合は + だいたい既知の原因を持っていて、エラーメッセージの見た目だけでは分からない + ことが多い。 --- -# GitHubプロフィール統計カード — セットアップと障害診断 +# GitHubプロフィール統計カード — セットアップと障害対応 -`tukuyomil032/tukuyomil032`のREADMEに埋め込んでいる2枚のカードは、それぞれ別のフォーク・別のVercelプロジェクトで動いている。 +このスキルは2つの手順をまとめたものだ:何もない状態から統計カードを立ち上げる手順と、既にデプロイ済みのカードが壊れた時に直す手順。各ステップには、そのステップに関係する注記だけを添えてある。詳しい説明は`references/`配下に逃がしてあり、該当ステップの注記から個別にリンクしているので、その注記が指す時だけ読みに行けばいい。手順を通しで読む段階では先回りして全部読む必要はない。 -| カード | フォーク元リポジトリ | Vercelプロジェクト | 参照するPHP環境変数 | -|---|---|---|---| -| GitHub Stats | `tukuyomil032/github-readme-stats` | `github-readme-stats` | `PAT_1` | -| Streak Stats | `tukuyomil032/github-readme-streak-stats` | `github-readme-streak-stats` | `TOKEN` | +## 手順A: ゼロから統計カードをセットアップする -**どちらのカードも、もうVercelを直接指す``タグでは表示していない。** 両方ともプロフィールリポジトリにコミットした静的SVGを参照する構成に変わっている。 +1. **使いたい統計カード生成元を自分のアカウントにフォークする。** 例: contributions/言語比率カードなら`anuraghazra/github-readme-stats`、ストリークカードなら`DenverCoder1/github-readme-streak-stats`。両方要らなければ片方だけでいい。互いに依存関係はない。 -- `assets/profile/stats-layout-v2.svg`(GitHub Stats、言語比率カードと合成済み) -- `assets/profile/streak-stats.svg`(Streak Stats) +2. **GitHub Personal Access Tokenを作る。** スコープは`repo` + `read:user`。作成場所は`https://github.com/settings/tokens/new`。 + > 有効期限は無期限か長めにしておくこと。期限切れがこの手のカードが壊れる原因のダントツ1位で、しかも壊れ方が分かりにくい。詳細は`references/secrets-and-tokens.md#L6-22`。 -どちらも`tukuyomil032/tukuyomil032`の`scripts/generate-stats-svg.py`が生成する。やっていることは単純で、各Vercelデプロイが返すSVGをそのままfetchしてファイルに書き込むだけ。`.github/workflows/update-stats.yml`がこれを12時間おきに実行し(`workflow_dispatch`で手動起動も可)、変化があればコミットする。つまり2つのVercelデプロイは今や純粋な「バックエンドのデータソース」でしかない — カードの表示が古く見える時に、`vercel.json`やPHP側のコード、Vercelのデプロイ設定を疑うのは筋違いになりつつある。まず疑うべきは、実際にコミットされているSVGの中身そのものだ: +3. **各フォークをVercelに別々のプロジェクトとしてインポートする**(Vercelダッシュボード → Add New → Project → 対象リポジトリを選択)。確認方法: それぞれ独自の`*.vercel.app`ドメインが割り当たり、初回デプロイが走ること(この時点ではまだトークンを登録していないのでエラー表示になるのが正常)。 -```bash -gh api repos/tukuyomil032/tukuyomil032/contents/assets/profile/streak-stats.svg --jq '.content' | base64 -d | grep -o "Failed to retrieve\|Total Contributions" -``` +4. **そのプロジェクトの環境変数にPATを登録する。** + > 変数名はフォーク元のソースコードが決めているもので、**フォークによってバラバラ**。決め打ちせず実際の変数名を確認してから登録すること。詳細は`references/secrets-and-tokens.md#L24-50`。 -コミット済みのSVG自体にエラー文言が入っているなら、原因は上流(Vercelデプロイ・PAT)側にある。逆にSVGの中身は正常なのにプロフィールページの表示だけおかしいなら、GitHub側のページキャッシュかブラウザキャッシュを疑った方が早い — 何かが壊れていると決めつける前に、少し待つかハードリロードを試すこと。 - -2つの環境変数名(`PAT_1`と`TOKEN`)は**互換性がない**。フォーク元のソースコードがそれぞれ独自に決めている名前なので、思い込みで揃えないこと。フォークの実装が変わった場合は都度確認する: - -```bash -gh api repos/tukuyomil032/github-readme-streak-stats/contents/api/index.php --jq '.content' | base64 -d -``` - -## なぜ静的SVG方式にしたのか - -以前のバージョンでは、READMEに `` を直接埋め込んでいた。これは動くには動くが、原因の特定に丸一日かかるタイプの壊れ方をした。VercelのエンドポイントがCache-Control: public, max-age=86400を返していたのが根本原因で、一度でもエラー文言入りのレスポンスがどこかにキャッシュされると、実体を直した後もそのキャッシュが生存し続ける限りエラー表示が消えない。しかもこのキャッシュ層は1つではなく3つ独立して存在する — Vercel自身のエッジキャッシュ、GitHubの画像プロキシcamo(upstreamのCache-Controlを尊重する実装で、24時間よりさらに長く保持することもある)、そして閲覧者のブラウザのHTTPキャッシュ。camoのキャッシュだけを`curl -X PURGE`で能動的にクリアしても、残り2層はノータッチのまま残り、しばらくするとまた同じエラーが再発する。 - -対して、スケジュール実行のActionがサーバー側でSVGをfetchし(毎回キャッシュバスター用のダミークエリを付けて)、それを普通のリポジトリファイルとしてコミットする方式なら、この3層のキャッシュを全部まとめて回避できる。コミットされたファイルにはcamoも長寿命のCache-Controlも介在しない。**もし「Vercel側は直したはずなのに、プロフィールだけ何時間経ってもFailed to retrieve contributionsのまま」という現象に再び遭遇したら、それはトークンやデプロイの問題ではなく、この静的SVG化以前に存在した多層キャッシュの生き残りを疑うべきサインだ。** - -## Secrets/トークン一覧 - -この構成が依存しているものを、生成方法・登録先とあわせて全部並べる。 - -### ① GitHub Personal Access Token(すべての大元) - -- **生成場所**: GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token(`https://github.com/settings/tokens/new`) -- **スコープ**: `repo` + `read:user` -- **有効期限**: 無期限か長めを推奨。期限切れが今回の障害連鎖の発端だった -- **用途**: 両方のVercelデプロイのPHPコードが、contribution/streakデータをGitHub GraphQL APIから取得するために使う -- **同じ1個のPATを両方のVercelプロジェクトに使い回してよい** — 変数名が違うだけで値は同じでいい。PATのGitHub側「Note」欄はただのラベルで、どのVercel変数名に入れるべきかとは無関係。 - -### ② Vercelプロジェクトの環境変数(PATの登録先) - -| プロジェクト | 変数名 | 登録場所 | -|---|---|---| -| `github-readme-stats` | `PAT_1` | Vercel → 該当プロジェクト → Settings → Environment Variables | -| `github-readme-streak-stats` | `TOKEN` | 同上 | - -Environment欄はデフォルト(All Environments)のままでよい。PATをローテーションしたら、両方の変数を更新した上で再デプロイしないと反映されない — 環境変数の変更は既に動いているデプロイに遡って効くことはない。 - -### ③ GitHub Actions Secrets(`github-readme-streak-stats`のみ必要) - -`github-readme-streak-stats`だけ、VercelのGit連携ではなくGitHub Actions経由でデプロイしている(理由は下の「Vercelデプロイが失敗する場合」参照)。登録場所は `github-readme-streak-stats` → Settings → Secrets and variables → Actions。 - -| Secret名 | 中身 | 取得方法 | -|---|---|---| -| `VERCEL_TOKEN` | Vercel APIトークン | Vercel → Account Settings → Tokens → Create Token(`https://vercel.com/account/tokens`)。**必ずFull Account/Teamスコープで作る**。プロジェクト限定スコープだと、素のREST APIは叩けるのに`vercel pull`が「Could not retrieve Project Settings」で失敗する | -| `VERCEL_ORG_ID` | `team_...`で始まるID | Vercel → Team Settings → General → Team ID。個人のHobbyアカウントでも表示される。Account SettingsのUser ID(`team_`プレフィックスなし)とは別物なので注意 | -| `VERCEL_PROJECT_ID` | `prj_...`で始まるID | 対象プロジェクト → Settings → General → Project ID | - -3つの中で`VERCEL_TOKEN`だけが正真正銘の機微情報なので、シェル履歴を経由させずGitHub UIから直接貼るのが安全。`VERCEL_ORG_ID`と`VERCEL_PROJECT_ID`は単なる識別子(認証情報ではない)なので、`gh secret set NAME --repo owner/repo --body "value"`でUIの手打ちミスを避けてもいい。手元のトークンから逆算したい場合: - -```bash -curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ - "https://api.vercel.com/v9/projects/" \ - | python3 -c "import json,sys; d=json.load(sys.stdin); print('accountId:', d['accountId']); print('id:', d['id'])" -``` -`accountId`が`VERCEL_ORG_ID`、`id`が`VERCEL_PROJECT_ID`(JSONの中には`latestDeployments`配下などに別の`id`フィールドが紛れているので、最初にマッチした`"id"`をそのまま拾わず、トップレベルのキーをちゃんとパースすること)。これは自分のターミナルで実行すること。レスポンスに含まれる`accountId`/`id`自体はただの識別子で共有しても問題ないが、`VERCEL_TOKEN`はAIアシスタントとのチャットに絶対貼らないこと。 - -### ④ `tukuyomil032/tukuyomil032`(プロフィールリポジトリ自体) - -追加のSecretsは不要。`update-stats.yml`はpush権限だけ要るが、それはワークフローの`permissions: contents: write`で自動発行される`GITHUB_TOKEN`で足りる。統計データの取得先は認証不要の公開Vercel URLなので、ここにPATは絡まない。 - -## ゼロからセットアップする場合 - -新しいアカウントで一から作る、あるいはまだフォークもVercelプロジェクトも存在しない場合、上の診断手順はまだ何ひとつ当てはまらない — 存在しないものを診断することはできない。以下を上から順にやっていくこと。スクリプトで自動判定できる段階ではないので、各ステップに「これで合ってるかはこう確認する」を添えてある。 - -1. **両方のリポジトリを自分のアカウントにフォークする**: `anuraghazra/github-readme-stats`と`DenverCoder1/github-readme-streak-stats`。確認: 自分のアカウントのリポジトリ一覧にフォークが出ていること。 - -2. **GitHub PATを作る**(上の①を参照)。確認: `gh api graphql -f query='query($l:String!){user(login:$l){createdAt}}' -f l="<自分のユーザー名>"`がそのトークンで自分のアカウント情報を返すこと。 - -3. **両方のフォークをVercelに別プロジェクトとしてインポートする**(Vercelダッシュボード → Add New → Project → 対象リポジトリを選択)。確認: それぞれ独自の`*.vercel.app`ドメインが割り当たり、初回デプロイが走ること(この時点ではおそらく壊れた状態で表示されるが、それでいい)。 - -4. **各プロジェクトの環境変数にPATを登録する**(上の②を参照)。フォーク元のソースコードが実際にチェックしている変数名に必ず合わせること — 名前が同じとは限らない。保存後は再デプロイすること。確認: デプロイURLを直接curlして、エラー文言ではなく本物のコンテンツが返るか見る: + 保存後は再デプロイし、実際に動いているか確認する: ```bash - curl -s "https://<自分のstatsプロジェクト>.vercel.app/api?username=<自分のユーザー名>" | grep -o "Total Contributions\|Failed to retrieve" + curl -s "<自分のプロジェクト>.vercel.app/<カードのパス>?username=<自分のユーザー名>" | grep -o "Total Contributions\|Failed to retrieve" ``` -5. **`github-readme-streak-stats`のデプロイが「No more than 12 Serverless Functions」で丸ごと失敗する場合**、PATを疑う前に下の「Vercelデプロイが失敗する場合」を読むこと。トークンとは無関係のプラットフォーム側のバグ。 - -6. **プロフィールリポジトリ側に静的SVG生成の仕組みを用意する**。このリポジトリで既に使っているパターンをそのまま踏襲すればいい: 標準ライブラリのみのPython製`scripts/generate-stats-svg.py`(各Vercel URLをキャッシュバスター付きでfetchして`assets/profile/`に書き込む)と、それを定期実行してコミットする`.github/workflows/update-stats.yml`。VercelのURLをそのまま``でREADMEに埋め込むのはやらないこと — なぜそれがまずいかは上の「なぜ静的SVG方式にしたのか」を参照。確認: `workflow_dispatch`で一度手動実行し、この文書の一番上にある診断コマンドと同じ要領でコミットされたSVGの中身を確認する。 - -7. **`README.md`に静的SVGのパスを埋め込む**(VercelのURLではなく`assets/profile/streak-stats.svg`のような相対パスにする)。確認: github.com上でプロフィールページの表示を見る。 - -## 診断の順序(すでに全部揃っている前提) - -いきなり「PATを再生成する」に飛びつかないこと。ここで起きる不具合はだいたい別のところに原因がある。 - -### 1. エラーメッセージが実際に何と言っているか - -READMEが実際に表示しているのはVercelの生URLではなく**コミット済みのSVG**なので、そちらを確認する: - -```bash -gh api repos/tukuyomil032/tukuyomil032/contents/assets/profile/streak-stats.svg --jq '.content' | base64 -d | grep -oE "]*>[^<]*" -``` - -- **「.env was not found. Check Contributing.md for details.」** → 環境変数(`TOKEN`/`PAT_1`)がVercelプロジェクトのEnvironment Variablesにそもそも存在しない。 -- **「Missing token in config.」** → 環境変数は存在するが値が空。 -- **「Failed to retrieve contributions. This is likely a GitHub API issue.」** → 環境変数に何らかの値は入っているが、GitHub GraphQL API呼び出し自体が失敗している。ほぼ必ず**トークンの値そのものが不正**(期限切れ、あるいはドラッグ選択によるトレーリングスペース混入などのコピペ事故)。まず正常なトークンで同じクエリを試して切り分ける: - ```bash - gh api graphql -f query='query($login:String!){ user(login:$login){ createdAt } }' -f login="tukuyomil032" - ``` - これが成功するなら、アカウント側・API側は問題なく、原因はVercelの環境変数に入っている値そのものと確定できる。直し方: PATを再生成(スコープ/Noteは維持したまま新しい値になる)→ トリプルクリックでコピー → Vercelに貼り付けて古い値を完全に上書き → 再デプロイ → プロフィールリポジトリの`update-stats.yml`を`workflow_dispatch`で即時実行し、次のスケジュール実行を待たずに反映させる。 -- Vercelの`Build Failed`で**「No more than 12 Serverless Functions can be added to a Deployment on the Hobby plan」**が出ている場合 → 下の③を見ること。PATとは無関係。 +5. **ビルド自体が失敗する場合**、特に「No more than 12 Serverless Functions can be added to a Deployment on the Hobby plan」のようなエラーが出た場合: + > これはトークンやコードとは無関係な、既知のVercelプラットフォーム側のバグ。`vercel.json`の直し方と、必要ならGitHub Actions経由のデプロイへの切り替え方が`references/vercel-deploy-bug.md`にある(`#L20-41`と`#L43-86`)。 -### 2. PAT自体の確認 +6. **READMEからカードをどう参照するか決める**: Vercelの実URLを直接``で埋め込むか、定期実行でサーバー側からfetchして静的SVGとしてコミットするか。 + > ライブURLの方がセットアップは簡単だが、実体のデプロイを直した後も、間に挟まる複数のキャッシュ層のせいで何時間も古いエラーが表示され続けることがある。なぜそうなるかと、それを避けたい場合の定期fetch+コミット構成の最小形は`references/cache-layers.md`を見ること。 -- 必要なスコープ: `repo` + `read:user`。 -- 有効期限が切れていないか。 -- Vercelの環境変数「Environment」設定はデフォルト(All Environments)のままでよく、手動で選び直す必要はない。 + 静的SVG方式を選ぶ場合、最小構成は: 各プロジェクトのライブSVG出力をキャッシュバスター付きクエリでfetchしてファイルに書き込むスクリプトと、それを定期実行(数時間おき+オンデマンド実行用の`workflow_dispatch`)してコミットするワークフローの2つ。 -### 3. Vercelデプロイが失敗する場合(Build Failed / function数 / ENOENT) +7. **`README.md`にカードを埋め込む** — 手順6で選んだ方式に応じて、ライブURLか静的ファイルの相対パスのどちらか。確認方法: github.com上でプロフィールページの表示を実際に見る(rawファイルだけでなく。コミット直後はGitHub自体のページキャッシュで少し遅れて反映されることもある)。 -`github-readme-streak-stats`は**未解決のVercelプラットフォーム側バグ**を踏んでいた: `.vercelignore`(`excludeFiles`)による除外指定が、GitHub連携経由の自動ビルドでのFunction自動検出には反映されない。一方で手動の`vercel --prod` CLIデプロイでは正しく反映される。本家の作者自身が別プロジェクトで同じ症状を報告している: https://github.com/vercel-community/php/issues/331(本稿執筆時点で未修正)。症状: `Error: No more than 12 Serverless Functions can be added to a Deployment on the Hobby plan`、しかもカウントが`.vercelignore`で除外したはずの数と合わない。 +## 手順B: 既にデプロイ済みのカードが壊れた時に直す -**現在の動作している構成**(`github-readme-streak-stats`に既に適用済み — 一から診断し直す前にまずこれが崩れていないか確認すること): - -`vercel.json`は新しい`functions`プロパティではなく**旧形式の`builds`配列**を使う。`functions`はVercelのゼロコンフィグ自動検出と共存してしまい、これがリポジトリ全体の`.php`ファイル(`tests/*.php`、`scripts/*.php`含む)を残らずルート/Functionとして拾ってしまっていた。`builds`はゼロコンフィグ検出そのものを完全に置き換え、ビルド対象を明示的に絞り込む: - -```json -{ - "builds": [ - { "src": "api/**/*.php", "use": "vercel-php@0.9.0" } - ], - "routes": [ - { "handle": "filesystem" }, - { "src": "/demo/?", "dest": "/api/demo/index.php" }, - { "src": "/demo/(.+)", "dest": "/api/demo/vercel-static.php" }, - { "src": "/", "dest": "/api/index.php" } - ] -} -``` - -`.github/workflows/vercel-deploy.yml`は、バグを踏むVercelのGit連携に頼らずGitHub Actions経由でデプロイする — 本家の作者自身が使っている回避策と同じ。**ここでの落とし穴**: `vercel pull` → `vercel build --prod` → `vercel deploy --prebuilt`の3段階フローは使わないこと。`--prebuilt`は新しいBuild Output API向けで、旧形式の`builds`設定とは噛み合わず、ローカルのビルド出力を無視してリモート側で`ENOENT: vendor/autoload.php`を引き起こす。代わりに1ステップのリモートデプロイを使う: - -```yaml -name: Deploy to Vercel - -on: - push: - branches: - - main - -jobs: - deploy: - runs-on: ubuntu-latest - env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - steps: - - uses: actions/checkout@v4 - - name: Install Vercel CLI - run: npm install --global vercel@latest - - name: Deploy to Vercel - run: vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }} -``` - -Git連携側にバグがある以上、Vercel → Project → Settings → Gitで実際に**連携を解除**しておくこと。そうしないとpushのたびに、壊れた自動デプロイと正常なAction経由デプロイの2つが同時に走って混乱する。 - -### 4. Vercel環境変数やGitHub Secretsを変更した後 +1. **今実際に何が配信されているかを確認する。** 静的SVG方式で運用しているなら、READMEが実際に表示しているのは*コミット済みのファイル*なので、そちらを見る: + ```bash + gh api repos///contents/ --jq '.content' | base64 -d | grep -oE "]*>[^<]*" + ``` + ライブURL方式で運用しているなら、デプロイURLを直接curlする。 -環境変数の変更は既に動いているデプロイには効かない — 新しいデプロイを走らせて初めて反映される。走らせて確認する: +2. **エラー文言を読んで、既知の3パターンのどれに当てはまるか切り分ける**(変数が存在しない・変数が空・変数はあるが上流API呼び出しが失敗、の3つで、それぞれ直し方が違う。トークンの値そのものが悪いのは3パターン目だけ)。マッチさせるべき文言の具体例、決めつける前の確認方法、それぞれの直し方は`references/diagnosing-errors.md`にまとめてある。 -```bash -gh workflow run vercel-deploy.yml --repo tukuyomil032/github-readme-streak-stats -# 進捗確認: -gh run list --repo tukuyomil032/github-readme-streak-stats --workflow=vercel-deploy.yml --limit 1 --json status,conclusion -# Vercel側の実体が本当に直っているか確認(まだコミット済みSVGではなくソース側の確認): -curl -sS "https://github-readme-streak-stats-seven-orpin.vercel.app?user=tukuyomil032" | grep -o "Failed to retrieve\|Missing token\|env was not found\|Total Contributions" -# 最後に、プロフィールリポジトリの静的SVGを即座に更新して反映させる: -gh workflow run update-stats.yml --repo tukuyomil032/tukuyomil032 -``` +3. **デプロイ自体がビルドの時点で失敗している場合**(エラーを表示するところまでも行っていない場合)は`references/vercel-deploy-bug.md`を見ること。これはプラットフォーム側の問題であって、トークンの問題として直そうとすると時間を無駄にする。 -Actionが緑になったことは、ビルドが成功した証拠でしかなく、カードが正しく表示される証拠にはならない。しかもVercel側を直しただけでは`update-stats.yml`が走るまでプロフィールREADMEには反映されない。各段階で、ジョブのステータスだけでなく実際に配信されている中身を都度確認すること。 +4. **環境変数やSecretsを変更したら、必ず再デプロイした上で、実際に配信されている中身を確認すること — ビルドやActionが緑になっただけで安心しない。** ビルド成功はコードがコンパイルできた証拠でしかなく、カードが正しく表示される証拠にはならない。確認コマンドは`references/diagnosing-errors.md#L63-76`。静的SVG方式で運用している場合は、次のスケジュール実行を待たずに生成ワークフローを一度手動実行し、修正を即座にREADMEへ反映させること。 diff --git a/github-profile-stats-deploy/SKILL.md b/github-profile-stats-deploy/SKILL.md index 323466d..6dd5964 100644 --- a/github-profile-stats-deploy/SKILL.md +++ b/github-profile-stats-deploy/SKILL.md @@ -1,334 +1,115 @@ --- name: github-profile-stats-deploy description: > - How to set up, diagnose, and fix tukuyomil032's self-hosted GitHub profile - stat cards (github-readme-stats and github-readme-streak-stats), deployed - on Vercel and embedded in the tukuyomil032/tukuyomil032 profile README via - committed static SVGs. Use this skill whenever the README stats/streak - cards show "Failed to retrieve contributions", ".env was not found", - "Missing token in config", a Vercel build error like "No more than 12 - Serverless Functions", a GitHub Actions deploy failure for either fork, or - when the user mentions PAT expiry, VERCEL_TOKEN/VERCEL_ORG_ID/VERCEL_PROJECT_ID - setup, wants to set this whole thing up from scratch on a new machine or - account, or describes these cards as broken/dead ("死んだ"). Work through - this skill's checklist in order rather than guessing — most failures here - have a specific, previously-diagnosed cause that isn't obvious from the - error message alone. + How to set up self-hosted GitHub profile stat cards (github-readme-stats, + github-readme-streak-stats, or similar PHP/Node forks) deployed on Vercel + and embedded in a GitHub profile README, and how to diagnose and fix them + when they break. Use this skill whenever the user wants to deploy their + own instance of a GitHub stats/streak card, when a profile README's stat + card shows an error like "Failed to retrieve contributions", ".env was not + found", or "Missing token in config", when a Vercel deployment for one of + these forks fails with "No more than 12 Serverless Functions", when a + GitHub Actions deploy for one of these forks fails, when the user asks + about setting up VERCEL_TOKEN/VERCEL_ORG_ID/VERCEL_PROJECT_ID for a stat + card project, or when the user says a profile stats/streak card is broken + or dead. Work through the relevant procedure below in order rather than + guessing at a fix — most failures here have a specific, previously-known + cause that isn't obvious from the error message alone. --- # GitHub profile stats cards — setup & troubleshooting -Two independent forks power the two cards embedded in -`tukuyomil032/tukuyomil032`'s README, each deployed as its own Vercel -project: - -| Card | Fork repo | Vercel project | PHP env var it reads | -|---|---|---|---| -| GitHub Stats | `tukuyomil032/github-readme-stats` | `github-readme-stats` | `PAT_1` | -| Streak Stats | `tukuyomil032/github-readme-streak-stats` | `github-readme-streak-stats` | `TOKEN` | - -**Neither card is embedded as a live Vercel `` anymore.** Both are -committed static SVGs in the profile repo, refreshed on a schedule: - -- `assets/profile/stats-layout-v2.svg` (GitHub Stats, combined with the - top-languages card) -- `assets/profile/streak-stats.svg` (Streak Stats) - -Both are generated by `scripts/generate-stats-svg.py` in -`tukuyomil032/tukuyomil032`, which fetches each Vercel deployment's SVG -output and writes it to disk unmodified. `.github/workflows/update-stats.yml` -runs this script every 12 hours (`workflow_dispatch` also works for an -on-demand refresh) and commits the result if it changed. So the two Vercel -deployments are now purely **backend data sources** — nothing about -`vercel.json`, the PHP code, or the Vercel deploy pipeline should be touched -to fix a stale-looking card. Check the actual committed SVG content first: - -```bash -gh api repos/tukuyomil032/tukuyomil032/contents/assets/profile/streak-stats.svg --jq '.content' | base64 -d | grep -o "Failed to retrieve\|Total Contributions" -``` - -If the committed SVG itself contains an error, the problem is upstream (the -Vercel deployment / PAT). If the committed SVG is fine but the profile page -still looks wrong, it's very likely GitHub's page cache or your browser cache -— wait or hard-refresh before assuming anything is broken. - -The two env var names (`PAT_1` vs `TOKEN`) are **not interchangeable** — -each fork's source decides its own name. Don't assume; grep the actual repo -if a fork ever changes: - -```bash -gh api repos/tukuyomil032/github-readme-streak-stats/contents/api/index.php --jq '.content' | base64 -d -``` - -## Why the cards are static SVGs, not live images - -Earlier iterations of this setup embedded `` -directly in the README. This worked, but broke in a way that took a long -time to diagnose: the Vercel endpoint sets `Cache-Control: public, -max-age=86400`. Once a broken (error-text) response got cached by any of -three independent layers — Vercel's own edge cache, GitHub's `camo` image -proxy (which respects upstream cache headers and can hold on to entries even -longer than 24h), or the visiting browser's own HTTP cache — the profile -kept showing the error for up to a day *after* the underlying deployment was -already fixed. Actively purging camo's cache (`curl -X PURGE `) -only cleared one of the three layers and the problem would resurface. - -Fetching the SVG server-side (via the scheduled Action, with a cache-busting -query param on every fetch) and committing it as a plain repo asset sidesteps -all three cache layers at once — a committed file has no camo proxy and no -long-lived Cache-Control in front of it. **If you ever see a fresh "Failed to -retrieve contributions" that keeps showing up hours after you're sure the -Vercel deployment itself is fixed, suspect a caching layer that predates this -static-SVG setup, not the token or deployment again.** - -## Secrets & tokens reference - -Everything this setup depends on, where to create it, and where it goes. - -### 1. GitHub Personal Access Token (the root credential) - -- **Create at**: GitHub → Settings → Developer settings → Personal access - tokens → Tokens (classic) → Generate new token - (`https://github.com/settings/tokens/new`) -- **Scopes**: `repo` + `read:user` -- **Expiration**: prefer no expiration or a long one — expiry is what - started the whole chain of failures the first time this broke -- **Used by**: both Vercel deployments' PHP code, to call the GitHub GraphQL - API for contribution/streak data -- The **same PAT value can be reused for both Vercel projects** — just paste - it under each project's expected env var name. The PAT's GitHub-side - "Note" field is a cosmetic label only and has no bearing on which Vercel - variable name it needs to go into. - -### 2. Vercel project environment variables (where the PAT is registered) - -| Project | Variable name | Registered at | -|---|---|---| -| `github-readme-stats` | `PAT_1` | Vercel → project → Settings → Environment Variables | -| `github-readme-streak-stats` | `TOKEN` | same | - -Leave "Environment" as the default (All Environments / Production covered). -Any time the PAT is rotated, both variables need to be updated and the -project redeployed for the change to take effect — env var changes never -apply retroactively to an already-running deployment. - -### 3. GitHub Actions secrets (only `github-readme-streak-stats` needs these) - -`github-readme-streak-stats` deploys via a GitHub Actions workflow instead of -Vercel's Git integration (see "Vercel deploy failures" below for why). -Registered at: `github-readme-streak-stats` repo → Settings → Secrets and -variables → Actions. - -| Secret | Value | How to get it | -|---|---|---| -| `VERCEL_TOKEN` | a Vercel API token | Vercel → Account Settings → Tokens → Create Token (`https://vercel.com/account/tokens`). **Must be Full Account/Team scope, not project-scoped** — a project-scoped token can call plain REST endpoints fine but `vercel pull` fails with "Could not retrieve Project Settings" | -| `VERCEL_ORG_ID` | a `team_...`-prefixed id | Vercel → Team Settings → General → Team ID. Even a personal Hobby account exposes this. **Not** the "User ID" under Account Settings (no `team_` prefix — that's the wrong id for this purpose) | -| `VERCEL_PROJECT_ID` | a `prj_...`-prefixed id | Target project → Settings → General → Project ID | - -`VERCEL_TOKEN` is the one genuine secret of the three — paste it directly in -the GitHub UI rather than through a shell history. `VERCEL_ORG_ID` and -`VERCEL_PROJECT_ID` are plain identifiers (not credentials), safe to -retrieve and set via `gh secret set NAME --repo owner/repo --body "value"` to -avoid UI copy-paste mistakes. If you need to re-derive them from a token you -already have: - -```bash -curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ - "https://api.vercel.com/v9/projects/" \ - | python3 -c "import json,sys; d=json.load(sys.stdin); print('accountId:', d['accountId']); print('id:', d['id'])" -``` -`accountId` → `VERCEL_ORG_ID`, `id` → `VERCEL_PROJECT_ID` (the JSON has -other nested `id` fields, e.g. under `latestDeployments` — parse the -top-level key specifically, don't grep the first `"id"` match). Run this in -your own terminal; don't paste `VERCEL_TOKEN` into a chat with an AI -assistant even though the response fields themselves are safe to share. - -### 4. `tukuyomil032/tukuyomil032` (the profile repo itself) - -No extra secrets needed. `update-stats.yml` only needs push access, which -comes from the auto-issued `GITHUB_TOKEN` (`permissions: contents: write` in -the workflow). It fetches from the public Vercel URLs with no auth. - -## Setting this up from scratch - -If you're doing this on a fresh account, or the forks/Vercel projects don't -exist yet, none of the diagnostic steps above apply yet — there's nothing to -diagnose until each piece exists. Go through this checklist top to bottom; -each step includes how to confirm it actually worked before moving to the -next one, since nothing here can be verified by a script until the whole -chain is wired up. - -1. **Fork both repos**: `anuraghazra/github-readme-stats` and - `DenverCoder1/github-readme-streak-stats` into your own account. - Confirm: the forks show up under your account's repo list. - -2. **Create a GitHub PAT** (see §1 above). Confirm: `gh api graphql -f - query='query($l:String!){user(login:$l){createdAt}}' -f l=""` returns your account data using this token - (`GH_TOKEN= gh api graphql ...` or export `GITHUB_TOKEN`). - -3. **Import both forks into Vercel** as separate projects (Vercel dashboard - → Add New → Project → pick the repo). Confirm: each gets its own - `*.vercel.app` domain and an initial (likely broken, that's expected) - deployment. - -4. **Register the PAT as each project's env var** (see §2 above), matching - the variable name each fork's source code actually checks — don't assume - it's the same name across forks. Redeploy after saving. Confirm: curl the - deployment URL and grep for real content, not error text: +This covers two related procedures: setting up a self-hosted stat card from +nothing, and fixing one that's already deployed but broken. Each step below +carries only the note relevant to that step; deeper explanations live in +`references/` and are linked from the step that needs them — read the +reference file only when that step's note tells you to, not up front. + +## Procedure A: set up a stat card from scratch + +1. **Fork the stat-card generator(s) you want** into your own GitHub account + — e.g. `anuraghazra/github-readme-stats` for a contributions/languages + card, `DenverCoder1/github-readme-streak-stats` for a streak card. You + can deploy one or both independently; they don't depend on each other. + +2. **Create a GitHub Personal Access Token.** Scopes: `repo` + `read:user`. + Create it at `https://github.com/settings/tokens/new`. + > Prefer no expiration or a long one — an expired token is the most + > common cause of these cards breaking later, and it fails in a + > non-obvious way. See `references/secrets-and-tokens.md#L6-22`. + +3. **Import each fork into Vercel as its own project** (Vercel dashboard → + Add New → Project → pick the repo). Confirm it worked: each project gets + its own `*.vercel.app` domain and runs an initial deployment (it's + expected to render an error at this point, since no token is registered + yet). + +4. **Register the PAT as that project's environment variable.** + > The variable name is decided by the fork's own source code and is + > **not** consistent across forks — confirm the actual name before + > guessing. See `references/secrets-and-tokens.md#L24-50`. + + Redeploy after saving, then confirm the card actually works: ```bash - curl -s "https://.vercel.app/api?username=" | grep -o "Total Contributions\|Failed to retrieve" + curl -s ".vercel.app/?username=" | grep -o "Total Contributions\|Failed to retrieve" ``` -5. **If `github-readme-streak-stats` fails to deploy at all** with "No more - than 12 Serverless Functions", see "Vercel deploy failures" below before - troubleshooting the PAT — it's an unrelated platform bug, not a token - issue. - -6. **Set up the static-SVG generation** in your profile repo, following the - pattern this repo already uses: a `scripts/generate-stats-svg.py` - (stdlib-only Python, fetches each Vercel URL with a cache-busting query - param and writes the SVG to `assets/profile/`) plus a scheduled - `.github/workflows/update-stats.yml` that runs it and commits the result. - Don't embed the live Vercel URLs directly as `` in the README — - see "Why the cards are static SVGs" above for what goes wrong if you do. - Confirm: run the workflow once via `workflow_dispatch`, then check the - committed SVG content the same way as the diagnostic command at the top - of this doc. - -7. **Embed the static SVG paths in `README.md`** (relative paths like - `assets/profile/streak-stats.svg`, not the Vercel URL). Confirm: view the - rendered profile page on github.com. - -## Diagnostic order (once everything above already exists) - -Work through these in order — don't jump straight to "regenerate the PAT", -most breakage here has been something else. - -### 1. What does the error message actually say? - -Check the *committed SVG*, not the live Vercel URL, since that's what the -README actually displays now: - -```bash -gh api repos/tukuyomil032/tukuyomil032/contents/assets/profile/streak-stats.svg --jq '.content' | base64 -d | grep -oE "]*>[^<]*" -``` - -- **".env was not found. Check Contributing.md for details."** → the env var - (`TOKEN` / `PAT_1`) doesn't exist at all in the Vercel project's - Environment Variables. -- **"Missing token in config."** → the env var exists but is empty. -- **"Failed to retrieve contributions. This is likely a GitHub API issue."** - → the env var has *some* value, but the GitHub GraphQL call itself failed. - This almost always means **the token value is wrong** (expired, or - corrupted by a copy-paste — e.g. a trailing newline/space from - drag-selecting instead of triple-clicking). Verify with a known-good - token first: - ```bash - gh api graphql -f query='query($login:String!){ user(login:$login){ createdAt } }' -f login="tukuyomil032" - ``` - If that succeeds, the account/API is fine and the problem is 100% the - value sitting in the Vercel env var. Fix: regenerate the PAT (keeps - scopes/note, gives a fresh value) → copy via triple-click → paste into - Vercel, overwriting the old value → redeploy → re-run the profile repo's - `update-stats.yml` (`workflow_dispatch`) to pick up the fix immediately - instead of waiting for the next scheduled run. -- A Vercel `Build Failed` with **"No more than 12 Serverless Functions can be - added to a Deployment on the Hobby plan"** → see §3 below, unrelated to - the PAT. - -### 2. Confirm the PAT itself - -- Scopes needed: `repo` + `read:user`. -- Check expiration hasn't passed. -- Vercel env var "Environment" setting can stay at the default (All - Environments) — no need to hand-pick. - -### 3. Vercel deploy is failing (Build Failed / function-count / ENOENT) - -`github-readme-streak-stats` hit a **known unresolved Vercel platform bug**: -excluded files (via `.vercelignore` or `excludeFiles`) aren't respected -during the GitHub-triggered build's function auto-detection, even though a -manual `vercel --prod` CLI deploy works fine. Reported by the upstream -author themselves: https://github.com/vercel-community/php/issues/331 — no -fix as of this writing. Symptom: `Error: No more than 12 Serverless -Functions can be added to a Deployment on the Hobby plan`, and the count -doesn't match what `.vercelignore` should have excluded. - -**The working setup** (already applied to `github-readme-streak-stats` — -check it's still intact before re-diagnosing from scratch): - -`vercel.json` uses the **legacy `builds` array**, not the newer `functions` -property. `functions` coexists with Vercel's zero-config auto-detection, -which scans *every* `.php` file in the whole repo (including `tests/*.php`, -`scripts/*.php`) as a route/function — `builds` fully replaces zero-config -detection and explicitly scopes the build: - -```json -{ - "builds": [ - { "src": "api/**/*.php", "use": "vercel-php@0.9.0" } - ], - "routes": [ - { "handle": "filesystem" }, - { "src": "/demo/?", "dest": "/api/demo/index.php" }, - { "src": "/demo/(.+)", "dest": "/api/demo/vercel-static.php" }, - { "src": "/", "dest": "/api/index.php" } - ] -} -``` - -`.github/workflows/vercel-deploy.yml` deploys via GitHub Actions instead of -relying on Vercel's buggy Git integration — the same workaround the upstream -author uses. **Important gotcha**: don't use the `vercel pull` → -`vercel build --prod` → `vercel deploy --prebuilt` three-step flow here — -`--prebuilt` is for the newer Build Output API and is incompatible with the -legacy `builds` config (it ignores the local build output and causes -`ENOENT: vendor/autoload.php` on the remote side). Use a single-step remote -deploy instead: - -```yaml -name: Deploy to Vercel - -on: - push: - branches: - - main - -jobs: - deploy: - runs-on: ubuntu-latest - env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - steps: - - uses: actions/checkout@v4 - - name: Install Vercel CLI - run: npm install --global vercel@latest - - name: Deploy to Vercel - run: vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }} -``` - -Given the Git integration is buggy, make sure it's actually **disconnected** -in Vercel → Project → Settings → Git, so pushes don't trigger a second, -broken, competing deployment. - -### 4. After any Vercel env var or GitHub Secret change - -Env var changes don't apply to an already-running deployment — a new deploy -must run for them to take effect. Trigger one and confirm: - -```bash -gh workflow run vercel-deploy.yml --repo tukuyomil032/github-readme-streak-stats -# poll: -gh run list --repo tukuyomil032/github-readme-streak-stats --workflow=vercel-deploy.yml --limit 1 --json status,conclusion -# then verify the actual Vercel-side content (not the committed SVG yet, this checks the source is really fixed): -curl -sS "https://github-readme-streak-stats-seven-orpin.vercel.app?user=tukuyomil032" | grep -o "Failed to retrieve\|Missing token\|env was not found\|Total Contributions" -# finally, force the profile repo's static SVG to pick up the fix immediately: -gh workflow run update-stats.yml --repo tukuyomil032/tukuyomil032 -``` - -A green Action run only proves the build succeeded — it does not prove the -card renders correctly, and a fixed Vercel deployment doesn't reach the -profile README until `update-stats.yml` runs. Always check the actual served -content at each stage, not just job status. +5. **If the deployment fails to build at all**, especially with an error + like "No more than 12 Serverless Functions can be added to a Deployment + on the Hobby plan": + > This is a known Vercel platform bug unrelated to your token or code. + > See `references/vercel-deploy-bug.md` for the `vercel.json` fix and, + > if needed, a GitHub-Actions-based deploy workaround + > (`references/vercel-deploy-bug.md#L20-41` and `#L43-86`). + +6. **Decide how the README will reference the card**: a live ``, or a static SVG fetched + server-side on a schedule and committed to the repo. + > A live URL is simpler to set up but can show a stale error for hours + > after you've already fixed the underlying deployment, because of + > caching layers between the origin and the viewer. See + > `references/cache-layers.md` for why, and for the shape of a + > scheduled fetch-and-commit setup if you'd rather avoid that class of + > bug entirely. + + If you go with the static-SVG approach, the minimum viable version is: + a script that fetches each project's live SVG output (with a + cache-busting query param) and writes it to a file, plus a scheduled + workflow (e.g. every few hours, and `workflow_dispatch` for on-demand + runs) that runs the script and commits the result if it changed. + +7. **Embed the card in `README.md`** — either the live URL or the static + file's relative path, per your choice in step 6. Confirm by viewing the + rendered profile page on github.com (not just the raw file — GitHub's + own page cache can also lag briefly after a fresh commit). + +## Procedure B: fix a card that's already deployed but broken + +1. **Check what's actually being served right now.** If you're running a + static-SVG setup, check the *committed file's* content — that's what the + README actually displays, not the live Vercel URL: + ```bash + gh api repos///contents/ --jq '.content' | base64 -d | grep -oE "]*>[^<]*" + ``` + If you're running a live-URL setup, curl the deployment URL directly + instead. + +2. **Read the error text and match it to one of three known causes** + (missing variable, empty variable, or a failed upstream API call — each + needs a different fix and only one of them is actually about the token + value being wrong). See `references/diagnosing-errors.md` for the exact + wording to match on, how to verify each cause before acting on it, and + the fix for each. + +3. **If the deployment itself won't build** (rather than building but + rendering an error), see `references/vercel-deploy-bug.md` — this is a + platform-level issue, not a token problem, and trying to fix it as if it + were a token problem wastes time. + +4. **After changing any environment variable or secret, redeploy and verify + the actual served content — don't trust a green build/Action alone.** + A successful build only proves the code compiled, not that the card + renders correctly. See `references/diagnosing-errors.md#L63-76` for the + verification command, and if you're running the static-SVG setup, also + trigger that generation workflow once so the fix reaches the README + immediately instead of waiting for its next scheduled run. diff --git a/github-profile-stats-deploy/references/cache-layers.md b/github-profile-stats-deploy/references/cache-layers.md new file mode 100644 index 0000000..55734a5 --- /dev/null +++ b/github-profile-stats-deploy/references/cache-layers.md @@ -0,0 +1,59 @@ +# Why static SVGs beat a live `` + +Referenced from SKILL.md's setup step 6. + +Embedding the Vercel deployment URL directly as a README `` works, but +it fails in a way that's unusually hard to diagnose: the fix can be +completely correct and the card can *still* show the old error for hours +afterward, because the error got cached somewhere between the origin and +the viewer's eyes. + +## The three independent cache layers + +Many stat-card APIs (including the common `github-readme-stats` / +`github-readme-streak-stats` forks) set a long `Cache-Control` header on +their responses — e.g. `public, max-age=86400` (24h) — so repeat visitors +don't hammer the GitHub API on every page load. That's reasonable for a +*correct* response, but it applies just as much to an *error* response. +Once a broken response (say, from an expired token) gets cached, three +layers can each independently hold on to it: + +1. **Vercel's own edge cache**, which honors the response's Cache-Control + and can keep serving the cached error for up to the stated max-age even + after you've fixed the underlying deployment. +2. **GitHub's `camo` image proxy** — every external image embedded in a + README is served through `camo.githubusercontent.com`, not the origin + directly. `camo` also respects the origin's Cache-Control, and in + practice can hold entries even longer than the stated max-age. +3. **The visiting browser's own HTTP cache.** If a viewer already loaded + the broken image once, revisiting the same URL can be served straight + from local disk cache without even a revalidation request to the + network, showing the old error indefinitely from that browser's point + of view. + +Fixing the token only fixes what layer 1 serves to *new* cache entries. +Layers 2 and 3 keep whatever they already cached until their own TTL +expires — independently of each other and of the origin. Actively purging +`camo`'s cache (`curl -X PURGE https://camo.githubusercontent.com/…`) only +clears layer 2; layers 1 and 3 are untouched, so the same error can +resurface. + +## The fix: don't let the README point at a cacheable live endpoint at all + +Fetch the SVG **server-side**, on a schedule (e.g. a GitHub Actions workflow +running every few hours), and commit the result as a plain file in the repo +that hosts the README. Point the README's `` at that committed file's +relative path instead of the live Vercel URL. + +This sidesteps all three layers at once: a file committed to a repo isn't +served through `camo` (it's already same-origin as far as GitHub is +concerned) and carries none of the origin API's long-lived Cache-Control. +The only "staleness" left is bounded and expected — the gap between +schedule runs — not an indefinite, cache-layer-dependent one. + +One more detail worth building into the fetch step: add a cache-busting +query parameter (e.g. a timestamp) to the URL your scheduled job fetches +from, so layer 1 (Vercel's own edge cache) can't hand your fetcher a stale +cached response either — you want the freshest possible data at generation +time, since whatever you commit now will sit there until the next +scheduled run regardless. diff --git a/github-profile-stats-deploy/references/diagnosing-errors.md b/github-profile-stats-deploy/references/diagnosing-errors.md new file mode 100644 index 0000000..088ed1e --- /dev/null +++ b/github-profile-stats-deploy/references/diagnosing-errors.md @@ -0,0 +1,76 @@ +# Diagnosing the error text embedded in the SVG + +Referenced from SKILL.md's fix step 2. + +Most of these stat-card forks render their own error state as an SVG with +the message as visible text, rather than returning an HTTP error status +alone. Extract it with: + +```bash +curl -sS "" | grep -oE "]*>[^<]*" +``` + +(If you're running the static-SVG setup, check the *committed* file's +content instead — that's what the README actually displays. See SKILL.md's +fix step 1.) + +The exact wording differs slightly by fork, but the three underlying causes +are consistent: + +## ".env was not found" / similar "no config found" message + +The environment variable the fork's code checks for doesn't exist at all in +the deployment's environment variables — not just empty, genuinely absent. + +**Fix**: register it. See `secrets-and-tokens.md` for how to find the exact +variable name the fork's code expects (it varies by fork) and where to set +it. + +## "Missing token in config" / similar "empty value" message + +The environment variable exists but its value is an empty string. + +**Fix**: same as above — set an actual value, then redeploy (env var +changes don't apply retroactively; see `secrets-and-tokens.md`). + +## "Failed to retrieve contributions" / "GitHub API issue" / similar upstream-call-failed message + +This is the trickiest one: the variable exists and has *some* value, but +the fork's server-side call to the GitHub API itself failed. This almost +always means **the token value itself is wrong** — expired, revoked, or +corrupted in transit (a common cause: selecting the token text by +click-drag instead of triple-click leaves a trailing space or newline that +looks identical to the eye but fails auth). + +**Verify before touching anything**: test the same kind of call with a +token you're confident is good, independent of the deployment: + +```bash +gh api graphql -f query='query($login:String!){ user(login:$login){ createdAt } }' -f login="" +``` + +If that succeeds, the GitHub account and API are fine, and the problem is +100% the value sitting in the deployment's environment variable — not +something about your account being rate-limited or restricted. + +**Fix**: on GitHub, open the PAT and use **Regenerate token** (keeps the +same scopes and note, issues a fresh value) rather than creating a brand +new one from scratch — this way you only need to update the value in one +place per registered variable. Copy the new value via **triple-click** +(selects the whole line cleanly, unlike drag-select) → paste into each +place it's registered, overwriting the old value completely → redeploy. + +## After any fix in this file + +An environment variable change never applies to an already-running +deployment. Trigger a new deploy, then re-check the *actual served +content* — not just that the deploy job reported success, which only +proves the build compiled, not that the card renders correctly: + +```bash +curl -sS "" | grep -o "Failed to retrieve\|Missing token\|was not found\|Total Contributions" +``` + +If you're running the static-SVG setup, also trigger that generation +workflow once (rather than waiting for its next scheduled run) so the fix +reaches the README immediately instead of sitting behind the schedule gap. diff --git a/github-profile-stats-deploy/references/secrets-and-tokens.md b/github-profile-stats-deploy/references/secrets-and-tokens.md new file mode 100644 index 0000000..3388108 --- /dev/null +++ b/github-profile-stats-deploy/references/secrets-and-tokens.md @@ -0,0 +1,94 @@ +# Secrets & tokens reference + +Everything this setup depends on, where to create it, and where it goes. +Referenced from SKILL.md's setup steps 2 and 4. + +## GitHub Personal Access Token (the root credential) + +- **Create at**: GitHub → Settings → Developer settings → Personal access + tokens → Tokens (classic) → Generate new token + (`https://github.com/settings/tokens/new`) +- **Scopes**: `repo` + `read:user` +- **Expiration**: prefer no expiration or a long one. An expired token is + the single most common cause of these cards breaking, and the failure + mode is confusing (see `diagnosing-errors.md`) because nothing about the + deployment itself changes — only the token silently stops working. +- **Used by**: both forks' PHP code, to call the GitHub GraphQL API for + contribution/streak data. +- The **same PAT value can be reused for every fork/project** you deploy — + just paste it under each project's expected env var name. The PAT's + GitHub-side "Note" field is a cosmetic label only and has no bearing on + which Vercel variable name it needs to go into. Don't assume the Note + matches the variable name. + +## Vercel project environment variables (where the PAT is registered) + +Register at: Vercel → your project → Settings → Environment Variables. + +The **variable name is decided by the fork's own source code, not by +convention** — different forks of stat-card generators use different names +(`PAT_1`, `TOKEN`, `GITHUB_TOKEN`, etc.). Before registering, confirm the +actual name the code checks for: + +```bash +gh api repos///contents/api/index.php --jq '.content' | base64 -d | grep -i "_ENV\[" +``` + +or, if the fork isn't PHP: + +```bash +gh search code "process.env" --repo / +``` + +Leave the "Environment" setting at its default (All Environments / covers +Production) — no need to hand-pick. + +**Any time the PAT is rotated, every project's variable needs to be updated +and the project redeployed for the change to take effect.** Environment +variable changes never apply retroactively to an already-running +deployment — this is the single most common reason a "fixed" token still +shows the old error for a while after you thought you fixed it. + +## GitHub Actions secrets (only needed if you deploy via Actions instead of Vercel's Git integration) + +If a fork's Vercel deployment needs to go through GitHub Actions instead of +Vercel's native Git integration (see `vercel-deploy-bug.md` for when that's +necessary), register these at: that fork's repo → Settings → Secrets and +variables → Actions. + +| Secret | Value | How to get it | +|---|---|---| +| `VERCEL_TOKEN` | a Vercel API token | Vercel → Account Settings → Tokens → Create Token (`https://vercel.com/account/tokens`). **Must be Full Account/Team scope, not project-scoped** — a project-scoped token can call plain REST endpoints fine but `vercel pull` (which the CLI needs for org/project context) fails with "Could not retrieve Project Settings" on a project-scoped token. | +| `VERCEL_ORG_ID` | a `team_...`-prefixed id | Vercel → Team Settings → General → Team ID. Even a personal Hobby account (not a real multi-person team) exposes this. **Not** the "User ID" under Account Settings — that has no `team_` prefix and is the wrong id for this purpose despite looking like the obvious "personal account id". | +| `VERCEL_PROJECT_ID` | a `prj_...`-prefixed id | Target project → Settings → General → Project ID. | + +`VERCEL_TOKEN` is the one genuine secret of the three — paste it directly in +the GitHub UI rather than through a shell history or into a chat with an AI +assistant. `VERCEL_ORG_ID` and `VERCEL_PROJECT_ID` are plain identifiers +(not credentials), safe to retrieve and set via +`gh secret set NAME --repo owner/repo --body "value"` to avoid UI +copy-paste mistakes. + +If you need to derive `VERCEL_ORG_ID`/`VERCEL_PROJECT_ID` from a token you +already have: + +```bash +curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ + "https://api.vercel.com/v9/projects/" \ + | python3 -c "import json,sys; d=json.load(sys.stdin); print('accountId:', d['accountId']); print('id:', d['id'])" +``` + +`accountId` → `VERCEL_ORG_ID`, `id` → `VERCEL_PROJECT_ID` (the JSON has +other nested `id` fields, e.g. under `latestDeployments` — parse the +top-level key specifically, don't grep the first `"id"` match in the raw +response). Run this in your own terminal; the response fields themselves +are safe to share, but never paste `VERCEL_TOKEN` itself anywhere outside +your own shell. + +## The repo that hosts the README + +If your static-SVG generation workflow lives in the same repo as the +profile README, it needs no extra secrets — a workflow's auto-issued +`GITHUB_TOKEN` (via `permissions: contents: write`) is enough to commit the +regenerated SVGs. It only fetches from public Vercel URLs, with no auth +needed on that side. diff --git a/github-profile-stats-deploy/references/vercel-deploy-bug.md b/github-profile-stats-deploy/references/vercel-deploy-bug.md new file mode 100644 index 0000000..66c164e --- /dev/null +++ b/github-profile-stats-deploy/references/vercel-deploy-bug.md @@ -0,0 +1,86 @@ +# Vercel function-count deploy bug + +Referenced from SKILL.md's setup step 5 and fix step 3. + +Some PHP-based stat-card forks (notably ones using the community +`vercel-php` runtime) can hit a **known unresolved Vercel platform bug**: +excluded files (via `.vercelignore` or `excludeFiles`) are not respected +during a GitHub-triggered auto-deploy's serverless-function auto-detection, +even though a manual `vercel --prod` CLI deploy from your own machine works +fine. Reported by the `vercel-php` maintainer themselves: +https://github.com/vercel-community/php/issues/331 — unresolved as of this +writing. + +**Symptom**: `Error: No more than 12 Serverless Functions can be added to a +Deployment on the Hobby plan`, and the function count doesn't match what +`.vercelignore` should have excluded (e.g. test files or scripts under +`tests/` or `scripts/` get counted as functions even though they're +excluded). + +## Fix: bypass zero-config detection with the legacy `builds` array + +`vercel.json`'s newer `functions` property *coexists* with Vercel's +zero-config auto-detection, which scans every matching file in the whole +repo as a potential function/route regardless of what `functions` itself +specifies. The older `builds` array fully replaces zero-config detection +instead, so it's the one that actually respects an explicit scope: + +```json +{ + "builds": [ + { "src": "api/**/*.php", "use": "vercel-php@0.9.0" } + ], + "routes": [ + { "handle": "filesystem" }, + { "src": "/", "dest": "/api/index.php" } + ] +} +``` + +Adjust the `src` globs and routes to match your fork's actual API entry +points. + +## Fix: deploy via GitHub Actions instead of Vercel's Git integration + +Since the bug is specific to Vercel's *Git-integration-triggered* build, +switching to a GitHub Actions workflow that runs the Vercel CLI directly +sidesteps it entirely — this is the same workaround the `vercel-php` +maintainer uses in their own projects. + +**Important gotcha**: don't use the `vercel pull` → `vercel build --prod` → +`vercel deploy --prebuilt` three-step flow. `--prebuilt` is for the newer +Build Output API and is incompatible with the legacy `builds` config above +— it ignores the local build output and causes something like +`ENOENT: vendor/autoload.php` on the remote side. Use a single-step remote +deploy instead: + +```yaml +name: Deploy to Vercel + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + steps: + - uses: actions/checkout@v4 + - name: Install Vercel CLI + run: npm install --global vercel@latest + - name: Deploy to Vercel + run: vercel deploy --prod --yes --token=${{ secrets.VERCEL_TOKEN }} +``` + +See `secrets-and-tokens.md` for how to create the three secrets this +workflow needs. + +Once you switch to Actions-based deploys, **disconnect Vercel's Git +integration** for that project (Vercel → Project → Settings → Git → +Disconnect). Otherwise every push triggers two competing deployments: the +broken auto-deploy that still hits this bug, and the working one from your +workflow — and it becomes unclear which one is actually live.