npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

github-reporadar

v0.11.0

Published

自分のGitHub活動を一晩ぶん眺める計器盤。npx github-reporadarでローカルに開く

Downloads

1,566

Readme

GitHub RepoRadar

多数の個人リポジトリを並行開発する人のための「どこで何を作っていたか」想起ダッシュボード。 GitHub の活動(commit / PR / issue)を期間指定で集約し、Web UI とターミナルの両方から確認できます。

毎日の使い方(自分用の早見表)

平日11:00-19:00は自動で動きます。詳しくは docs/毎日の運用.md

最初に1回だけ。

echo "alias radar='~/dev/env/github-reporadar/ops/reporadar-workday.sh'" >> ~/.zshrc && source ~/.zshrc

以後打つのは3つだけ。

| 打つもの | いつ | |---|---| | radar start | 土日・深夜・時間外に見たいとき | | radar status | 動いているか | | radar stop | 止めるとき |

見る場所は2つだけ。

| 見るもの | 場所 | |---|---| | 画面 | http://127.0.0.1:5177/ | | うまくいかないとき | tail -20 ~/.local/state/github-reporadar/workday.log |

新しいPCではこの3行。gh auth loginが済んでいれば、設定ファイルも自動で作られます。

git clone https://github.com/BoxPistols/github-reporadar.git ~/dev/env/github-reporadar
cd ~/dev/env/github-reporadar
./ops/install.sh

はじめての起動だけ、同期が終わるまで1分ほど画面が開きません。2回目からは1秒で開き、同期は裏で走ります。

機能

Web UI はヘッダーでモードを切り替えます。

  • ダッシュボード: 日次×repo ヒートマップ + repo カード/テーブル。期間(7/30/90日)・ソート(活動量/最終push/放置)・キーワード検索・タグ・Bookmark(★)。 絞り込みは「活動量(期間内 commit 数の下限)」×「最終push(N日以内 / N日以上放置)」の2軸と、 よく使う組み合わせのプリセット(🔥 活発 × 30日以内 / ⚡ 直近7日 / 💤 30日以上放置)。カードから「次にやること」メモ。 モバイルではカードがアコーディオンになり、既定は全て閉じた状態。単一展開/複数展開の切替と「全て開く/全て閉じる」で開き方を選べる。
  • ボード: 総合 Projects。Backlog/Active/Review/Done/Dormant の列に repo や自由タスクのカードを DnD で並べる。
  • 検索: 全 repo の open issue/PR を横断全文検索(日本語対応)+ type/repo/label ファセット + ラベル別件数。
  • Wiki: 自分が関わったConfluenceのページを更新順に並べる一覧。タイトルとスペースでの検索、スペース・「他人が更新」・「要確認」での絞り込み。ローカル版だけで、Atlassianの設定が無ければモード自体を出しません。

User data(タグ/Bookmark/ボード/メモ)の保存先は既定でブラウザローカル(IndexedDB)。ヘッダーの 「☁ 同期フォルダを接続」で OneDrive/iCloud/Dropbox 等の同期フォルダを選ぶと、端末間で共有できます (BE/DB レス、同期は OS のクラウドクライアント任せ)。

必要なもの

  • Node.js 20+ / pnpm
  • gh CLI でログイン済みであること(gh auth login)

トークンは実行時に gh auth token から読むだけで、ファイルには一切保存しません。

npxで使う(インストール不要)

npx github-reporadar            # 同期してからローカルで開く
npx github-reporadar brief      # 端末に要約を出す
npx github-reporadar --help     # フラグの一覧
  • 前提はgh auth login済みのgh CLIだけ。トークンは実行時にgh auth tokenから読み、保存しない
  • データは$XDG_CACHE_HOME~/.cachegithub-reporadar/data/に置く。カレントディレクトリには落とさない(private repo名とコミット文が入るため)。--data-dirで変えられる
  • 待ち受けは127.0.0.1だけ。ポートは既定5177で、使用中なら次を探す
  • --no-syncで手元のデータだけで開く。--days 30で取得期間を変える
  • --org <組織名>で組織のrepoも足せる(繰り返し可)。組織では自分がauthorのコミットと、自分が作ったか担当のopen issue/PRだけを数える。読むだけで、組織側には何も書かない。一覧では組織名/repo名で出る

npmには実データを含めない。filesdist/dataを除外し、prepublishOnlyでも消し、npm pack --dry-run/data/が無いことをテストで固定している。

業務での使い方(組織のrepoをまたいで働く人向け)

例として、example-companyという組織でexample-company/storefrontexample-company/billingを触りながら、個人のexample-user/notesも動かしている人の1日で書きます。名前はすべて架空です。

前提と境界

| 項目 | 中身 | |---|---| | 動かす場所 | 自分のPCだけ。npx github-reporadar --org example-company。公開版(github-reporadar.vercel.app)は個人repoだけで、組織は出ない | | なぜ公開版に組織を出さないか | 組織のprivateなrepo名・コミット文・PRタイトルがVercelのサーバーを通ることになる。それを避けるため、組織のデータはあなたのPCとGitHubの間だけで動かす | | 組織への影響 | 読むだけ(GET)。組織側には何も書かない。組織の管理者の許可が要る操作は無い | | 数えるもの | 組織のrepoでは自分がauthorのコミットと、自分が作ったか担当のissue/PRだけ。他人の作業は数に入らない | | データの置き場 | ~/.cache/github-reporadar/data/。共有端末では使わない。消したいときはそのフォルダを削除する | | トークン | gh auth tokenを実行時に読むだけ。ファイルには保存しない。組織がSSOなら、先にgh auth refresh -h github.com -s read:orgで組織を認可しておく |

朝、今日やることを決める

npx github-reporadar --org example-company --days 30

ブラウザが開いたら、フィルターバーの上段を左から見ます。

| 札 | 意味 | 次の一手 | |---|---|---| | レビュー待ちの自分のPR 3 | 自分が出して、ドラフトでなく、まだ承認されていないPRが3件 | 放置されているものはレビュアーに声をかける。「要修正」の札が付いていれば指摘に対応する | | Draftの自分のPR 2 | 自分のDraft PRが2件 | 仕上げてReady for reviewにするか、閉じる | | 自分がレビューするPR 1 | レビューを頼まれているPRが1件 | 他人を待たせているのはこれ。先に片付ける | | 手を付けていない自分のissue 5 | 自分が作ったopen issueで、自分のopen PRがまだ閉じる予定になっていないもの | 今日やるものを1つ選んでブランチを切る |

札を押すと、その仕事があるrepoだけが一覧に残ります。もう一度押すと外れます。

昼、1つのrepoに集中する

一覧でexample-company/storefrontを選ぶと、右の詳細ペインに出ます。

| 節 | 中身 | |---|---| | レビュー待ちの自分のPR | 番号と題名。番号を押すとGitHubのPRが開く。CHANGES_REQUESTEDのものには「要修正」の札 | | 自分がレビューするPR | 依頼されているPR。番号からGitHubへ | | 直近のコミット | 自分のコミットの1行目。(#1419)のような番号はそのissue/PRへのリンク | | 次にやること | このrepoのメモ。ブラウザの中に保存され、「メモを他のPCと共有」を押していれば選んだフォルダにも書かれる |

キーワードで探すときはCmd+K(Windows/LinuxはCtrl+K)か/で検索欄に飛びます。Escapeで消して離れます。

夕、端末で振り返る

npx github-reporadar brief --org example-company --days 7 --private

直近7日のコミット数、最終push、最新コミットの1行目をrepoごとに1行で出します。日報の下書きに使えます。

個人と組織を分けて見る

--orgを付けなければ個人repoだけです。組織だけを見たいときは、フィルターバーの検索欄にexample-company/と入れると、その組織のrepoだけに絞れます。一覧では別ownerのrepoがexample-company/storefrontのように組織名つきで出るので、個人のnotesと見分けられます。

一日中開いておく(受信箱)

npx github-reporadar --org example-company --watch 1m

--watchを付けると、開いたままその間隔でopen issue/PRを取り直します。同期のたびに前回との差分から「自分に起きたこと」をevents.json(データdir)に足していきます。

毎回はopen issue/PRとレビュー状態だけを取り、コミットは30分ごとにまとめて取ります。GitHubのAPIは認証済みで5000回/時なので、実測にもとづく目安はこうです。

| 間隔 | 1時間の消費(完全同期を含む) | 上限に対して | |---|---|---| | 30s | 4,430 | 89%。ghコマンドを併用する余裕が少ない | | 1m | 2,390 | 48%。おすすめ | | 5m | 700 | 14% |

実測値は1回あたり、軽い同期がREST 34回で6秒、完全同期がREST 175回で13秒(42 repo、うち組織5)。repo数が増えると完全同期の側が伸びます。30秒未満は受け付けません。

| 出来事 | 条件 | |---|---| | レビュー依頼が来た | 他人のPRで、レビューを頼まれた人に自分が新しく入った | | 要修正が付いた / 承認された / コメントが付いた | 自分のPRに他人のレビューが1つ増えた。自分自身のレビューは数えない | | 対応済み、再レビュー待ち | 要修正の自分のPRに、最後のレビューより新しい自分のコミットが載った | | 閉じられた | 自分のopen PRが一覧から消えた(マージかクローズ) |

初回の同期では何も出しません。溜まっていた分を一斉に出すと、新しく起きたことが埋もれるためです。画面のバッジと通知は次の版で入ります。

既定では組織のrepoの出来事だけを出します。個人repoのbotレビューが「あなたの番」を埋めるためです。ConfluenceとSlackは仕事の側なので残ります。「組織だけ」を押すと個人repoも出ます。

出す順は「誰が止まっているか」で分け、同じ組の中では組織のrepoを先に置きます。Confluenceの更新は「Wikiの更新」として独立した組にし、「あなたの番」の下、「知らせ」の上に置きます。GitHubの知らせと同じ組に混ぜると、他人が直したデザインルールが数十行の下に沈んで見えなくなります。個人のrepoは下にまとまります。依存更新のPRのように、botが起こしたレビュー依頼は「他人を待たせている」には入れません。人は止まっていないためです。

一覧は「何が起きたか」ではなく「どこで起きたか」で1行にします。同じPRにレビューが7回付いても1行で、見出しは最新のもの、残りは「ほか6」と添えます。1つのPRを7行に割ると、1行ずつ既読にしても同じPRの別の行が残り、読んでも減らないように見えるためです。

読んだものはその場で一覧から消えます。1件ずつの「既読」と、リンクを開いたときの既読があり、どちらもその行にまとまっている出来事をまとめて消します。それより古い未読は残ります。「すべて既読」は時刻で一気に消します。消えたものは「履歴」で見られます。

平日の勤務時間だけ動かす(macOS)

launchdで平日11:00に起動し、19:00に停止します。土日は動きません。

git clone https://github.com/BoxPistols/github-reporadar.git
cd github-reporadar
mkdir -p ~/.config/github-reporadar
cat > ~/.config/github-reporadar/workday.env <<'ENV'
RR_ORGS="example-company"
RR_WATCH=20m
RR_PORT=5177
ENV
./ops/install.sh

設定ファイルはリポジトリの外(~/.config/github-reporadar/workday.env)に置きます。組織名を含むので、リポジトリには入れません。

| コマンド | 中身 | |---|---| | ./ops/install.sh | launchdに登録。平日11:00起動 / 19:00停止 | | ./ops/install.sh uninstall | 登録を解除 | | ./ops/reporadar-workday.sh start / stop / status | 手で起動・停止・確認 |

ログは~/.local/state/github-reporadar/workday.logです。二重起動はしません。ghにログインしていなければ起動せず、理由をログに残します。

20分間隔なら、1時間あたりのGitHub APIの消費は約313回で上限の6%です(軽い同期が約1.5回、完全同期が約1.5回)。8時間動かしても上限には触れません。

launchdは対話シェルのPATHを継がないので、install.shが実行時のPATHをplistに焼き込みます。nodeghの場所を変えたら登録し直してください。

毎日の運用

登録したあとは、こちらから何もしなくても動きます。

| 時刻 | 起きること | |---|---| | 平日11:00 | 起動して完全同期を1回。以後20分ごとにopen issue/PRを取り直す | | 20分ごと | 受信箱に差分が溜まる。ブラウザを開いていれば通知が出る | | 30分ごと | コミットも含めた完全同期 | | 平日19:00 | 停止 | | 土日 | 動かない |

自分でやることはこれだけです。

  1. 朝、http://127.0.0.1:5177/ を開く。ブックマークしておく
  2. フィルターバーの札で今日やることを決める(レビュー待ちの自分のPR、自分がレビューするPR、Draft、手を付けていないissue)
  3. 受信箱に未読が付いたら開く。番号を押すとGitHubへ飛び、その1件は一覧から消える。まとめて消すときは「すべて既読」、振り返るときは「履歴」
  4. 迷ったら壁打ちで「今日の作業順を決めたい」と聞く

初回だけ、受信箱の「通知を許可」を1回押してください。ブラウザを閉じている間の出来事もevents.jsonに溜まるので、翌朝に開けば読めます。

様子がおかしいとき

| 症状 | 見るところ | |---|---| | 画面が開かない | ./ops/reporadar-workday.sh status。動いていなければstart | | 数字が古い | ログの末尾。tail -20 ~/.local/state/github-reporadar/workday.log | | 受信箱が空のまま | 差分は前回の同期との比較で出る。初回同期の直後は必ず空。1回目の20分を待つ | | 「gh にログインしていません」 | gh auth login。組織がSSOならgh auth refresh -h github.com -s read:orgも | | 組織のrepoが出ない | 設定のRR_ORGScat ~/.config/github-reporadar/workday.env | | 通知が来ない | ブラウザの通知許可。受信箱を開いて「通知を許可」が出ていないか | | 起動しなくなった | nodeghの場所が変わった可能性。./ops/install.shで登録し直す |

更新とやめかた

./ops/reporadar-workday.sh restart   # 新しい版に入れ替える(npxが@latestを取り直す)
./ops/install.sh uninstall           # 自動起動をやめる
./ops/reporadar-workday.sh stop      # いま動いているものを止める

新しい版は次の11:00の起動から自動で使われます。すぐ入れ替えたいときだけrestartします。

Confluenceとの齟齬を出す(Atlassian連携)

ページを書いたあとに実装が進んだものを「Wiki要確認」として出します。本文と題に出てくるrepo名を拾い、そのrepoのpushがページ更新より新しければ挙げます。自分が関わったページと、見張っているフォルダの配下が対象です。他人が書いたページには「他人のページ」と添えます。

前提はこうです。値そのものは実装が正で、ページはその意図と使い方を書く場所です。だから食い違いが出たときに直す先はページのほうです。押すとそのページが開きます。

1ページ1行です。1枚のページが4つのrepoに触れていれば、repoは遅れの大きい順に並べて「ほか3」と添えます。repoごとに行を割ると、同じ題が何度も並びます。

「確認」を押すとその行は消え、数が減ります。押した時点でいちばん新しかったpushを覚えていて、そのあとに実装が進めばまた出ます。スプリントの記録のように一度読んだら読み直さない文書には「もう見ない」を使います。こちらはpushが来ても二度と出しません。Wikiのページは閉じることがなく残り続けるので、GitHubのPRのように「解決したら終わり」になりません。だから人が見て「いまは直さない」と決めたものを数から外せるようにしています。消したものは「履歴」で見られます。

トークンはファイルに置かず、macOSのキーチェーンから実行時に取ります(ghと同じ考え方)。JiraとConfluenceは同じサイトと同じトークンを使います。

# 1. Atlassianでトークンを作る
open https://id.atlassian.com/manage-profile/security/api-tokens

# 2. キーチェーンに入れる(<>は置き換える)
security add-generic-password -s github-reporadar-atlassian -a "<メールアドレス>" -w "<APIトークン>"

# 3. 設定に足す
cat >> ~/.config/github-reporadar/workday.env <<'ENV'
RR_ATLASSIAN_SITE=example.atlassian.net
[email protected]
ENV

RR_CONFLUENCE_SPACES="EXAM OTHER"でスペースを絞れます。省略すると自分が関わった全ページです。

決まった場所を見張る

RR_CONFLUENCE_FOLDERS="2149875783"(--confluence-folder)を足すと、そのフォルダの配下は自分が関わっていないページも取ります。誰かが足したページは「Confluenceにページが追加」、直したページは「Confluenceのページが更新」として受信箱に出ます。IDの代わりにフォルダのURLをそのまま貼っても通ります。

自分が関わったページだけを取る既定では、他人が新しく作ったページは取得の対象にすら入りません。設計やUIのルールのように、置き場所は決まっているが書き手が自分とは限らないものは、場所のほうを指定します。

追加として出すのは、前回の同期より後に作られたページだけです。見張る場所を足した日に、過去のページが一斉に鳴るのを避けるためです。齟齬(Wiki要確認)の対象は今までどおり自分が関わったページだけで、見張っている場所のページは件数に混ざりません。

拾うのは、そのPCが取得しているrepoの名前だけです。知らない語をrepo名として扱うことはありません。名前だけで拾うのは、記号か数字を含むもの(sdpf-thing)か8文字以上のものです。uidesignのように普通の単語と同じ綴りのrepoは、owner付き(example-org/design)かGitHubのURLで書かれているときだけ拾います。本文の地の文に当たるのを避けるためです。

別のownerに同じ名前のrepoがあるときも、名前だけでは決めずowner付きを求めます。

ページ更新からpushまで24時間未満のものは出しません。書いた直後の手直しを齟齬として数えないためです。

リンクのURLはタグの属性に入るので、リンクの文字が「リポジトリ」でも中のGitHubのURLからrepoを拾います。

自分が関わったページを誰かが更新したら、受信箱にも出します。自分の更新では鳴りません。

Slackのメンションを受信箱に出す

自分あてのメンションが付いたら受信箱に出します。残すのは、どのチャンネルで誰が呼んだかと発言へのリンクだけです。発言の本文は保存しません。壁打ちへも渡しません。

security add-generic-password -s github-reporadar-slack -a slack -w "<ユーザートークン>"

要るスコープはsearch:readだけです。トークンが無ければ何もしません。

トークンはSlackアプリを作って自分のアカウントに入れる形で取ります。手元のslack CLI(v4.6.0)のトークンでは足りません。認証は通りますが検索がmissing_scopeで弾かれます(実測)。企業のワークスペースではアプリの導入に管理者の承認が要ることがあります。

取りに行くのは自分が関わったページの最大200件です。更新の新しい順に並ぶので、50件ずつ続きを取って最後まで辿ります。1回で打ち切ると、長く放置されたページから先に落ちます。

Jiraの課題も見る

自分が担当か報告者の未完了課題を取り込み、壁打ちの文脈に入れます。GitHubのPRと並べて「今日の作業順」を相談できます。

Confluenceと同じ設定(RR_ATLASSIAN_SITE / RR_ATLASSIAN_EMAIL)で動きます。RR_JIRA_PROJECTS="EXAM OTHER"でプロジェクトを絞れます。省略すると自分に関わる全プロジェクトです。

設定かトークンが無ければ、JiraもConfluenceも何もしません。取得に失敗しても同期そのものは止まらず、GitHub側の結果は残ります。

壁打ちの範囲を「個人開発」にしているときは、Jiraの課題もConfluenceの齟齬も文脈に入れません。仕事のものなので前提が混ざります。

運用の方針

| 方針 | 理由 | |---|---| | 「リアルタイム」はポーリング(--watch)で作る | GitHubのWebhookは受け口のサーバーが要り、ローカル完結と矛盾する。毎回の同期をopen issue/PRだけに絞ってREST 34回にしたので、1分間隔でも上限の半分で収まる(実測)。コミットは30分ごと | | 出来事は前回との差分から作る | GitHubの通知APIは組織の全件が混ざり、自分のPRに関することだけを取り出しにくい。前回のissues.jsonと比べるだけなら追加のAPIも権限も要らない | | 組織はローカルだけ | 上の「前提と境界」のとおり。公開版に足すなら組織管理者への説明と明示同意の画面が先 | | 通知はブラウザのNotification API(次の版) | ページを開いている間に出る。OSの通知センターまで欲しい場合はCLI側から出す案があるが、まずはブラウザで足りるかを見る | | 受信箱を壁打ちに渡す(その次の版) | 「今日の作業順」「この指摘への対応の優先度」を、受信箱と仕事の状態を文脈にして相談する |

よくある疑問

| 疑問 | 答え | |---|---| | 組織のコミット数が少ない | GitHubはコミットのメールアドレスをアカウントに結んで数える。会社のメールで作業しているなら、GitHubのSettings → Emailsにそのアドレスを足す。過去分も結び付く | | 「レビュー待ち」なのに承認されているPRが出る | 同期が古い。npx github-reporadar --org example-companyを打ち直す。画面は同期のたびに作り直される | | 公開版でも組織を見たい | 現状は対象外(上の「前提と境界」)。要望があれば、組織の管理者への説明と明示同意の画面を先に作る | | 他の人にこの画面を見せたい | 見せない。データには組織のprivateな情報が入っている。共有するなら、briefの出力から必要な行だけ写す |

使い方

pnpm install
pnpm sync          # GitHub から直近90日の活動を取得して public/data/ にキャッシュ
pnpm dev           # Web UI (http://localhost:5173)
pnpm brief         # ターミナルで簡易確認

CLI オプション

pnpm brief --days 7 --sort stale --limit 20
# --days    7|30|90 (default 30)
# --sort    active(活動量順) | recent(最終push順) | stale(放置順)
# --limit   表示件数 (default 15)
# --private private repo も表示 (既定は public のみ。Web UI はヘッダーのチェックボックスで切替)
pnpm sync --days 30   # 取得期間の変更

構成

scripts/sync.ts     collector: repo ごとの REST commits API → public/data/*.json
scripts/brief.ts    CLI: キャッシュを読んでターミナルに要約表示
src/                viewer: Vite + React + Tailwind v4 (キャッシュを読むだけの純フロント)
src/lib/aggregate.ts  期間集計の純関数 (vitest でテスト)

データソースについて

GraphQL contributionsCollection は使いません。private リポジトリの内訳を restrictedContributionsCount に集約して返す (repo 別内訳から欠落する) ため、 repo ごとの REST commits API を一次ソースにしています。

  • 対象: 期間内に push があった 自分が owner の repo のみ(組織・共同 repo は収集しない)

  • bot (author.type === 'Bot') を除く全コミットを集計。期間内にコミットの無い repo は除外

  • private repo はローカルキャッシュにのみ保存し、表示は既定オフ (Web はトグル、CLI は --private)。 公開ホスト版は既定では private を取得しない(下の「private を含める」参照)

  • 日付は実行環境のローカルタイムゾーンで日別化 (UTC 境界ずれを回避)

  • 制約: デフォルトブランチのみ (未マージのブランチ作業は反映されない)

  • キャッシュ (public/data/) には private リポジトリ名・コミットメッセージ・issue/PR タイトルが含まれるため gitignore 済み

  • viewer はネットワークアクセスなし。GitHub API を叩くのは collector のみ

ローカル版のデプロイ注意

ローカル版(VITE_HOSTED 無し)は公開ホストにデプロイしないこと。public/data/ のキャッシュに private リポジトリ名・コミット文・issue タイトルが入るため。public/data/dist/ は gitignore + .vercelignore 済み(git 連携デプロイにはデータが入らない=空画面になる)。 公開したい場合は下の「公開ホスト版(GitHub OAuth)」を使う(各ユーザーが自分のデータを見る方式で安全)。

公開ホスト版(GitHub OAuth + Vercel)

各ユーザーが自分の GitHub でログインし、自分の public リポジトリの活動を見られる公開版 (api/ の Vercel Functions)。VITE_HOSTED=1 のときこのモードになる。他人がアクセスしても その人自身の public データしか見えず、あなたのデータは見えない。

仕組み / セキュリティ

  • OAuth トークンは httpOnly + Secure Cookie(ブラウザ JS から読めない=XSS 耐性)。client_secret は Vercel Function のみ
  • OAuth state で CSRF 対策、最小権限(空スコープ)
  • 各ユーザーのデータはそのユーザーのトークンで 実行時に GitHub API から取得(/api/data)。静的キャッシュは持たない
  • User data(タグ/ボード/メモ)は各ブラウザの IndexedDB(端末ローカル)
  • 既定は public のみ(空スコープ)。private は明示同意した人だけ(下記)

private を含める(明示同意)

既定のサインインは空スコープなので、public しか見えない。private も見たい場合は サインイン画面の「private も含めてサインイン」か、ヘッダーの「🔒 private を有効化」から /api/auth/login?private=1 に進み、GitHub の認可画面で repo スコープを許可する。

  • 許可の意思は OAuth の state に載せて往復し、セッション(暗号化 cookie)に privateOptIn として残す
  • /api/data が private を取るのは privateOptIn かつ 実際に付与されたスコープに repo があるときだけ。 OAuth App のスコープはユーザーごとに累積する(一度 repo を許可すると、以後は空スコープを要求しても repo 付きのトークンが返る)ため、「読めること」を同意の証拠にしない
  • 許可すると private の repo 名・コミット文・issue タイトルがこのホストを経由する(表示されるのは本人のデータのみ)
  • 取り消したいときはサインアウトする。サインアウトは認可自体を revoke するので、 次回は public だけのサインインを選び直せる

デプロイ Runbook(この repo の本番: github-reporadar.vercel.app)

この repo は private のため、実際の URL・手順をここに記載する(secret は記載しない)。

現在の状態(2026-08-31)

  • 本番 URL: https://github-reporadar.vercel.app
  • Git連携は接続済み。mainへのpushで本番へ自動デプロイされる(手動デプロイは不要)
  • 環境変数(Production)設定済み: VITE_HOSTED=1 / APP_URL=https://github-reporadar.vercel.app
  • 未設定(あなたの操作待ち): GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET / SESSION_SECRET
  • 未設定の間、/api/auth/loginは503の「サーバー未設定」ページを返す。これは正しい状態
  • 3つのうち1つでも欠けると503のままgetConfig()は4つ揃って初めて設定済みと見なす (APP_URLを含む)。SESSION_SECRETはセッションcookieの暗号鍵で、手順2で自分で作る

注意: 2026-08-31まで、本番の/api/*は全て500(FUNCTION_INVOCATION_FAILED)だった。 原因はenv未設定ではなく、ESMの相対importに拡張子が無かったこと。 ローカル検証(tsx/vite)は拡張子なしを解決するため再現せず、Vercel上のNode ESMだけで落ちていた。 修正済み。api/esmImports.test.tsが同じ壊れ方を止める。

GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET はどこかに存在する値ではなく、下の手順1で OAuth App を作った瞬間に GitHub が発行する。 Client Secret は発行直後の1回しか表示されない (控え損ねたら、同じ画面で新しい secret を再発行して古い方を失効させる)。

残りの手順

  1. GitHub OAuth App を作成 → https://github.com/settings/applications/new (一覧は https://github.com/settings/developers の "OAuth Apps")

    • Application name: 任意(例RepoRadar)。GitHubはGitHubで始まる名前を拒否する
    • Homepage URL: https://github-reporadar.vercel.app
    • Authorization callback URL: https://github-reporadar.vercel.app/api/auth/callback
    • Allow wildcard matching / Enable Device Flowはオフのまま
    • 「Expire user access tokens」という項目は探さなくてよい。これはGitHub Appのラベルで、 OAuth Appの設定画面には無い。OAuthアプリのアクセストークンは既定で無期限に発行される
    • 作成後の画面に Client ID が表示される。Client Secret は 「Generate a new client secret」を押して発行し、その場でコピーする
  2. Vercelに3つの環境変数を追加(Production) → https://vercel.com/asagiri/github-reporadar/settings/environment-variables

    • GITHUB_CLIENT_ID = 上のClient ID
    • GITHUB_CLIENT_SECRET = 上のClient Secret(secretはここ(Vercel)にのみ入れる。gitやチャットに貼らない)
    • SESSION_SECRET = 自分で生成する乱数。セッションcookieをAES-GCMで封じる鍵で、 GitHubから貰う値ではない。openssl rand -base64 32の出力をそのまま貼る (この値を変えると全員が一斉にサインアウトする。復旧手段は再サインインのみ)
  3. 再デプロイ(VITE_HOSTEDと違いGITHUB_CLIENT_*は実行時変数だが、 env追加だけでは既存のデプロイに反映されないので再ビルドする)

    • Git連携は接続済みなので、mainへpushすれば自動デプロイされる
    • 手元から流すならvercel --prod --yes(要vercel login)
    • 最新デプロイがgit由来になったので、ダッシュボードのRedeployも使える

これで https://github-reporadar.vercel.app が「GitHub でサインイン」画面になり、ログイン後に各自の public 活動が見える。設定前にアクセスすると「このサーバーは OAuth 未設定です」と表示される (サインインを押して行き止まりにならないよう、未設定を検知して案内を出す)。

マージ前ゲート(CIが止まっている間の代替)

GitHub ActionsはFreeプランだとprivateリポジトリで月2000分の枠がある。使い切ると ジョブが起動せず、PRのチェックは「テスト失敗」ではなく未実行のまま赤くなる。 その間は手元のpnpm verify(test → typecheck → build)が唯一の関門になる。

pnpm verify

.githooks/pre-pushが同じものをpush前に走らせる。クローン直後は1度だけ紐付けが要る。

git config core.hooksPath .githooks

飛ばしたい時はgit push --no-verify。実Chromeのe2e(pnpm e2e)はpublic/data/が 要るのでこのゲートには入れていない。

ローカル ⇔ 本番の切替

  • ローカル(pnpm dev/preview): VITE_HOSTED 無し → 静的 /data/*.json(pnpm sync で生成)を読む
  • 本番(Vercel): VITE_HOSTED=1 → OAuth + /api/data(実行時 GitHub 取得)

開発

pnpm test        # vitest
pnpm typecheck   # tsc -b --noEmit
pnpm build       # 型チェック + ビルド(ローカル版)
pnpm e2e         # 本番ビルドを実 Chrome で開き、メモがリロード後も残るかを検証

pnpm test / pnpm typecheck / pnpm build は PR と main への push で CI(.github/workflows/ci.yml)が実行する。 pnpm e2epublic/data/ のキャッシュ(生成に gh トークンが必要)を前提とするため CI では回さない。 UI を変える PR は、CI 緑だけでなく実ブラウザでの表示確認までをマージ条件にする。