🐙

GitHub APIを使ってリポジトリ管理を自動化してみた

に公開2

きっかけ:積み重なった練習リポジトリの山

最近、自分のGitHubを見返したら、昔の練習リポジトリが山のように残っていました。
ひとつずつ削除するのは正直めんどくさいし、間違えて大事なものを消してしまいそうで怖い…。

そこで「ワンクリックで整理できるツールがあればいいのに」と思い、Angularで作ってみました。
この記事では、その開発経緯と仕組みを紹介します。

作ったもの:GitHub Repository Cleaner

デモ: https://tetsufromtw.github.io/repo-cleaner-angular/
GitHub: https://github.com/tetsufromtw/repo-cleaner-angular

主な機能

  • 🔍 スマートフィルタリング - 言語、スター数、最終更新日での絞り込み
  • 一括操作 - 複数リポジトリを選択して一括アーカイブ/削除
  • 🛡️ 安全設計 - 削除前の確認ダイアログで誤操作防止
  • 🔄 リアルタイム更新 - 操作後すぐにUIが更新

なぜ100%クライアントサイドにしたのか

最初に決めた設計方針は「バックエンドを一切使わない」ことでした。理由は:

  1. セキュリティ - ユーザーのGitHub Tokenを預かる責任を負いたくない
  2. シンプルさ - サーバー管理やDBが不要
  3. 透明性 - すべての処理がブラウザ内で完結
// Token管理はlocalStorageのみ
setToken(token: string): void {
  this.token = token;
  localStorage.setItem(this.STORAGE_KEY, token);
}

// APIリクエストも直接GitHub APIへ
private makeRequest<T>(endpoint: string): Observable<T> {
  const url = `${this.baseURL}${endpoint}`;
  const headers = new HttpHeaders({
    'Authorization': `Bearer ${this.token}`,
    'Accept': 'application/vnd.github.v3+json'
  });
  
  return this.http.get<T>(url, { headers });
}

実装のポイント

1. 大量リポジトリの効率的な取得

GitHub APIは1回のリクエストで最大100件しか取得できません。大量のリポジトリがある場合はページネーションが必要です。

fetchAllPages(endpoint: string, params: any): Observable<any[]> {
  return new Observable(observer => {
    let allItems: any[] = [];
    let currentPage = 1;
    
    const fetchPage = () => {
      const pageParams = { ...params, page: currentPage, per_page: 100 };
      const url = `${endpoint}?${new URLSearchParams(pageParams)}`;
      
      this.makeRequest<any[]>(url).subscribe({
        next: (items) => {
          allItems.push(...items);
          
          if (items.length < 100) {
            observer.next(allItems);
            observer.complete();
          } else {
            currentPage++;
            fetchPage();
          }
        },
        error: (err) => observer.error(err)
      });
    };
    
    fetchPage();
  });
}

2. Rate Limitを考慮したバッチ処理

GitHubのAPI制限(1時間5000回)を考慮し、並行実行数を制限しています。

batchOperation(
  repositories: GitHubRepository[],
  operation: RepositoryOperation,
  concurrency = 2 // 並行実行数を制限
): Observable<BatchOperationResult> {
  // 同時実行数を制限したパラレル処理
  return new Observable(observer => {
    let running = 0;
    let index = 0;
    
    const processNext = () => {
      while (running < concurrency && index < repositories.length) {
        const repo = repositories[index++];
        running++;
        
        this.executeSingleOperation(repo, operation).subscribe({
          next: (result) => {
            running--;
            // 結果を蓄積
            processNext();
          }
        });
      }
    };
    
    processNext();
  });
}

3. Angular Signalsでの状態管理

Angular 20の新しいSignals APIを使って、リアクティブな状態管理を実現しています。

export class RepositoryStore {
  private readonly _repositories = signal<GitHubRepository[]>([]);
  private readonly _selectedIds = signal<Set<number>>(new Set());
  private readonly _filters = signal<RepositoryFilters>({});
  
  // フィルタリングされたリポジトリ (computed)
  readonly filteredRepositories = computed(() => {
    const repos = this._repositories();
    const filters = this._filters();
    
    return this.applyFilters(repos, filters);
  });
  
  // 選択されたリポジトリ (computed)
  readonly selectedRepositories = computed(() => {
    const repos = this.filteredRepositories();
    const selectedIds = this._selectedIds();
    
    return repos.filter(repo => selectedIds.has(repo.id));
  });
}

4. 削除後の即座なUI更新

API経由でリストを再取得するのではなく、成功した操作の結果をもとにローカル状態を即座に更新しています。

async deleteRepository(repositoryId: number): Promise<void> {
  const result = await this.executeBatchOperation({
    type: 'delete',
    repositoryIds: [repositoryId]
  });
  
  if (result.success.length > 0) {
    // 削除成功時は即座にローカル状態から削除
    const successIds = result.success.map(repo => repo.id);
    const currentRepos = this._repositories();
    const updatedRepos = currentRepos.filter(repo => !successIds.includes(repo.id));
    
    this._repositories.set(updatedRepos);
  }
}

開発環境とデプロイ

# 開発環境セットアップ
git clone https://github.com/tetsufromtw/repo-cleaner-angular.git
cd repo-cleaner-angular
npm install
npm start

# GitHub Pagesへのデプロイ用ビルド
npm run build:github-pages

GitHub Actionsを使った自動デプロイも設定済みです。masterブランチにpushすると自動的にGitHub Pagesにデプロイされます。

使ってみた感想

実際に自分のGitHubで使ってみたところ:

  • 効率性: 50個のリポジトリを削除するのに1分もかからない
  • 安心感: 削除前の確認ダイアログで誤操作を防止
  • 視覚的: フィルタリングで目的のリポジトリをすぐ見つけられる

学習用に作った大量のテストリポジトリが一掃できて、GitHubプロフィールがすっきりしました。

こんな人におすすめ

  • 学生・初学者: 練習用リポジトリの整理
  • 開発者: 実験用プロジェクトのクリーンアップ
  • チーム管理者: 組織リポジトリの定期整理

今後の改善予定

  • 復元機能(アーカイブ解除)の強化
  • より詳細なフィルタリング条件
  • 操作履歴の保存
  • エクスポート機能(CSV、JSON)

おわりに

実は私は元々フロントエンド専門ではなく、Angularも今回初挑戦でした。そのため、フロントエンドのベストプラクティスや最適化の観点で見落としがあるかもしれません。

もしコードレビューしていただける方や、機能改善のアイデアをお持ちの方がいらっしゃいましたら、ぜひ:

  • Issue: バグ報告や機能提案
  • Pull Request: コード改善やバグ修正
  • Fork: 自由にカスタマイズして使ってください

GitHub APIを使った自動化ツール、作ってみると意外と楽しいものです。皆さんもぜひ挑戦してみてください!


デモ: https://tetsufromtw.github.io/repo-cleaner-angular/
ソースコード: https://github.com/tetsufromtw/repo-cleaner-angular

Discussion

カモノカモノ

localstorageにtokenを保存するのはXSS攻撃に対して脆弱です。悪意のある拡張機能、urlスクリプト等では簡単に強力な権限を持つtokenが盗まれてしまう可能性があります。セキュアな実装を行わなければサイトが悪用されてしまうため、適切な対応をお勧めします。

1
テツデステツデス

ご指摘いただき、誠にありがとうございます!
本日中に修正対応を行います!!!