github について
github はアプリケーションのテスト向けの、状態を保持する GitHub REST API エミュレーターです。変更はメモリ内に保存され、関連エンティティにも反映されます。リポジトリ、Issue、プルリクエスト、Actions などを扱います。
主な機能
- 01GitHub REST API の模擬
- 02関連エンティティにも反映するインメモリ状態
- 03リポジトリ、Issue、プルリクエストに対応
- 04Actions、Checks、Releases をカバー
- 05OAuth、アプリトークン、Webhook に対応
活用シーン
- 01GitHub API クライアントのテスト
- 02Issue とプルリクエストのワークフロー検証
- 03OAuth ログイン連携のテスト
- 04GitHub App と Webhook の動作確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vercel-labs/ emulate --skill githubインストール後、Claude Code で/githubを入力して読み込まれたか確認します。
詳細
GitHub API エミュレーター
状態を完全に保持する GitHub REST API エミュレーションです。作成、更新、削除はメモリ内に保存され、関連するエンティティにも反映されます。
起動
ローカルリスナーは、デフォルトで 127.0.0.1 にバインドされます。コンテナーやほかのマシンからアクセスするには、CLI に --host 0.0.0.0 を渡すか、createEmulator に hostname: '0.0.0.0' を渡します。クライアントからアクセス可能な URL を通知するには、--base-url または baseUrl を設定します。
# GitHub only
npx emulate --service github
# Default port
# http://localhost:4001
または、プログラムから実行します。
import { createEmulator } from 'emulate'
const github = await createEmulator({ service: 'github', port: 4001 })
// github.url === 'http://localhost:4001'
プログラムから GitHub App を使用する場合は、private_key を省略し、インスタンスから生成された RSA キーを読み取ります。
const github = await createEmulator({
service: 'github',
port: 4001,
seed: {
github: {
users: [{ login: 'octocat' }],
apps: [{
app_id: 12345,
slug: 'my-github-app',
name: 'My GitHub App',
installations: [{ installation_id: 100, account: 'octocat' }],
}],
},
},
})
const privateKey = github.generatedSecrets.find(
secret => secret.kind === 'github.app_private_key' && secret.id === '12345',
)?.value
キーは github.reset() を実行しても維持されます。明示的に指定したキーは generatedSecrets に含まれません。
Next.js および Nuxt のアダプターも、省略されたキーを生成します。返されたハンドラーを保持し、サーバー専用の generatedSecrets() メソッドを呼び出してください。永続化を設定すると、コールドスタート後も ID が維持されます。スナップショットには署名キーが含まれるため、非公開にしてください。また、カスタムの永続化バックエンドには、アトミックな initialize() セマンティクスの実装を必須としてください。
CLI では、プライベートな配信ファイルをリクエストする場合に限り、private_key を省略します。
npx emulate start --service github --seed emulate.config.yaml \
--generated-secrets-file .emulate-secrets.json
保存先は存在していてはいけません。emulate は継承された ACL を削除し、実効的な所有者のみのアクセス権を検証してから、リスナーや portless エイリアスの起動前に完全な JSON を書き込みます。処理可能な起動エラーが発生した場合、実行中に作成されたファイルは削除されます。強制終了時には完全なファイルが残ることがあります。その場合、どの実行も使用していないことを確認してから、手動で削除してください。ファイルから generatedSecrets を読み取った後は、ソース管理の対象外にしてください。Linux では、acl パッケージの setfacl と getfacl が必要です。アクセス制御を検証できない場合、このフラグはフェイルクローズで動作し、Windows ではサポートされません。--generated-secrets-file を指定しない場合、CLI のシードファイルには引き続き private_key が必要です。
認証
トークンは Authorization: Bearer <token> または Authorization: token <token> として渡します。
curl http://localhost:4001/user \
-H "Authorization: Bearer test_token_admin"
公開リポジトリのエンドポイントは、認証なしで利用できます。プライベートリポジトリと書き込み操作には、有効なトークンが必要です。トークンが指定されていない場合、リクエストは最初にシードされたユーザーにフォールバックします。
インストールアクセストークンは、リポジトリへの書き込み時に、設定された GitHub App ボットとして機能します。リポジトリの所有権、選択されたリポジトリへのアクセス権、要求された App の権限は引き続き適用されます。プルリクエストのマージには、ベースリポジトリで contents: write が必要です。プルリクエストのブランチ更新には、プルリクエストのリポジトリで pull_requests: write、ヘッドリポジトリで contents: write が必要です。
GitHub App JWT
生成されたシークレットを使用せずに CLI を使う場合は、明示的で有効な秘密鍵をシード設定で App に指定してください。{ iss: "<app_id>" } を含む JWT に RS256 で署名します。エミュレーターは署名を検証し、App を解決します。
github:
apps:
- app_id: 12345
slug: my-github-app
name: My GitHub App
permissions:
contents: read
issues: write
events: [push, pull_request]
webhook_url: http://localhost:8080/github/webhook
webhook_secret: my-webhook-secret
description: My CI/CD bot
installations:
- installation_id: 100
account: my-org
repository_selection: all
permissions:
contents: read
events: [push]
repositories: [my-org/org-repo]
この例では、プログラムからの利用とアダプターでの利用を想定し、意図的に private_key を省略しています。この場合、エミュレーターが RSA キーを生成し、generatedSecrets を通じて公開します。CLI で使用する場合は、--generated-secrets-file <path> を指定してプライベートな配信ファイルをリクエストするか、有効なキーを独自に用意してください。このフラグを指定しない場合、CLI のシードファイルには private_key が必要です。プレースホルダーの PEM は使用しないでください。
アプリの接続先をエミュレーターに設定する
環境変数
GITHUB_EMULATOR_URL=http://localhost:4001
Octokit
import { Octokit } from '@octokit/rest'
const octokit = new Octokit({
baseUrl: process.env.GITHUB_EMULATOR_URL ?? 'https://api.github.com',
auth: 'test_token_admin',
})
OAuth URL のマッピング
| GitHub の実際の URL | エミュレーターの URL |
|---|---|
https://github.com/login/oauth/authorize | $GITHUB_EMULATOR_URL/login/oauth/authorize |
https://github.com/login/oauth/access_token | $GITHUB_EMULATOR_URL/login/oauth/access_token |
https://api.github.com/user | $GITHUB_EMULATOR_URL/user |
Auth.js / NextAuth.js
import GitHub from '@auth/core/providers/github'
GitHub({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
authorization: {
url: `${process.env.GITHUB_EMULATOR_URL}/login/oauth/authorize`,
},
token: {
url: `${process.env.GITHUB_EMULATOR_URL}/login/oauth/access_token`,
},
userinfo: {
url: `${process.env.GITHUB_EMULATOR_URL}/user`,
},
})
シード設定
tokens:
test_token_admin:
login: admin
scopes: [repo, user, admin:org, admin:repo_hook]
github:
users:
- login: octocat
name: The Octocat
email: [email protected]
bio: I am the Octocat
company: GitHub
location: San Francisco
blog: https://github.blog
twitter_username: github
site_admin: false
orgs:
- login: my-org
name: My Organization
description: A test organization
email: [email protected]
members:
- login: octocat
role: admin
repos:
- owner: octocat
name: hello-world
description: My first repository
language: JavaScript
topics: [hello, world]
default_branch: main
private: false
- owner: my-org
name: org-repo
description: An organization repository
language: TypeScript
oauth_apps:
- client_id: Iv1.abc123
client_secret: secret_abc123
name: My Web App
redirect_uris:
- http://localhost:3000/api/auth/callback/github
組織の members は省略可能です。各エントリは login でシード済みユーザーを参照します。role のデフォルト値は member で、admin は組織の管理者ロールに対応します。不明なユーザーは無視されます。シードされたメンバーシップは合成された members チームを使用し、組織のプライベートリポジトリへのアクセス権を付与します。
auto_init: false が設定されていない限り、リポジトリはコミット、ブランチ、README とともに自動的に初期化されます。
ページネーション
すべての一覧エンドポイントは、Link ヘッダーとともに page および per_page クエリパラメーターをサポートします。
curl "http://localhost:4001/repos/octocat/hello-world/issues?page=1&per_page=10" \
-H "Authorization: Bearer $TOKEN"
API エンドポイント
ユーザー
# Authenticated user
curl http://localhost:4001/user -H "Authorization: Bearer $TOKEN"
# Update profile
curl -X PATCH http://localhost:4001/user \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"bio": "Hello!"}'
# Get user by username
curl http://localhost:4001/users/octocat
# List users
curl http://localhost:4001/users
# User repos / orgs / followers / following
curl http://localhost:4001/users/octocat/repos
curl http://localhost:4001/users/octocat/orgs
curl http://localhost:4001/users/octocat/followers
curl http://localhost:4001/users/octocat/following
# User hovercard
curl http://localhost:4001/users/octocat/hovercard
# User emails
curl http://localhost:4001/user/emails -H "Authorization: Bearer $TOKEN"
リポジトリ
# Get repo
curl http://localhost:4001/repos/octocat/hello-world
# Get repo by numeric ID
curl http://localhost:4001/repositories/1
# Create user repo
curl -X POST http://localhost:4001/user/repos \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "new-repo", "private": false}'
# Create org repo
curl -X POST http://localhost:4001/orgs/my-org/repos \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "org-project"}'
# Update repo
curl -X PATCH http://localhost:4001/repos/octocat/hello-world \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"description": "Updated description"}'
# Delete repo (cascades issues, PRs, etc.)
curl -X DELETE http://localhost:4001/repos/octocat/hello-world \
-H "Authorization: Bearer $TOKEN"
# Topics, languages, contributors, forks, collaborators, tags, transfer
コンテンツとコミット履歴
# Read a file or list a directory at a branch, tag, or commit
curl "http://localhost:4001/repos/octocat/hello-world/contents/README.md?ref=main"
# Request raw bytes from a file Contents or README response with GitHub's raw media type
curl "http://localhost:4001/repos/octocat/hello-world/contents/README.md?ref=main" \
-H "Accept: application/vnd.github.raw+json"
# Download raw file content from the URL advertised by contents and commit responses
curl http://localhost:4001/octocat/hello-world/raw/main/README.md
# Create or update a file and commit the change
curl -X PUT http://localhost:4001/repos/octocat/hello-world/contents/notes.txt \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": "Update notes", "content": "aGVsbG8K"}'
# List commits, get a commit with file stats, or compare refs
curl http://localhost:4001/repos/octocat/hello-world/commits
curl http://localhost:4001/repos/octocat/hello-world/commits/main
curl http://localhost:4001/repos/octocat/hello-world/compare/v1.0.0...main
Issue
# List issues (filter by state, labels, assignee, milestone, creator, since)
curl "http://localhost:4001/repos/octocat/hello-world/issues?state=open&labels=bug"
# Create issue
curl -X POST http://localhost:4001/repos/octocat/hello-world/issues \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "Bug report", "body": "Details here", "labels": ["bug"]}'
# Get / update / lock / unlock / timeline / events / assignees
プルリクエスト
# List PRs
curl "http://localhost:4001/repos/octocat/hello-world/pulls?state=open"
# Create PR
curl -X POST http://localhost:4001/repos/octocat/hello-world/pulls \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "Feature", "head": "feature-branch", "base": "main"}'
# Merge PR (enforces branch protection)
curl -X PUT http://localhost:4001/repos/octocat/hello-world/pulls/1/merge \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"merge_method": "squash"}'
# Commits, files, requested reviewers, update branch
コメント
# Issue comments: full CRUD
curl http://localhost:4001/repos/octocat/hello-world/issues/1/comments
curl -X POST http://localhost:4001/repos/octocat/hello-world/issues/1/comments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"body": "Looks good!"}'
# Comment by ID (cross-resource)
curl http://localhost:4001/repos/octocat/hello-world/issues/comments/1
# PR review comments
curl http://localhost:4001/repos/octocat/hello-world/pulls/1/comments
# Commit comments
curl http://localhost:4001/repos/octocat/hello-world/commits/abc123/comments
# Repo-wide comment listings
curl http://localhost:4001/repos/octocat/hello-world/issues/comments
curl http://localhost:4001/repos/octocat/hello-world/pulls/comments
curl http://localhost:4001/repos/octocat/hello-world/comments
レビュー
# List / create / get / update / submit / dismiss reviews
curl http://localhost:4001/repos/octocat/hello-world/pulls/1/reviews
curl -X POST http://localhost:4001/repos/octocat/hello-world/pulls/1/reviews \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"event": "APPROVE", "body": "LGTM"}'
ラベルとマイルストーン
ラベルとマイルストーンの CRUD 操作をすべてサポートします。Issue へのラベルの追加と削除、すべてのラベルの置き換えができます。マイルストーンのラベル一覧を取得できます。
ブランチと Git データ
# List branches
curl http://localhost:4001/repos/octocat/hello-world/branches
# Branch protection CRUD (status checks, PR reviews, enforce admins)
curl -X PUT http://localhost:4001/repos/octocat/hello-world/branches/main/protection \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"required_status_checks": {"strict": true, "contexts": ["ci"]}}'
# Refs, commits, trees (recursive), blobs, tags, matching-refs
Organization と Team
# List all orgs / user's orgs / get org / update org
curl http://localhost:4001/organizations
curl http://localhost:4001/user/orgs -H "Authorization: Bearer $TOKEN"
curl http://localhost:4001/orgs/my-org
curl -X PATCH http://localhost:4001/orgs/my-org \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"description": "Updated org"}'
# Org members: list, get, remove
curl http://localhost:4001/orgs/my-org/members
curl http://localhost:4001/orgs/my-org/members/octocat
curl -X DELETE http://localhost:4001/orgs/my-org/members/octocat \
-H "Authorization: Bearer $TOKEN"
# Org memberships: get, set (invite/update role)
curl http://localhost:4001/orgs/my-org/memberships/octocat -H "Authorization: Bearer $TOKEN"
curl -X PUT http://localhost:4001/orgs/my-org/memberships/octocat \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"role": "admin"}'
# Teams: CRUD
curl http://localhost:4001/orgs/my-org/teams
curl -X POST http://localhost:4001/orgs/my-org/teams \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "engineering", "privacy": "closed"}'
# Team members and memberships
curl http://localhost:4001/orgs/my-org/teams/engineering/members
curl -X PUT http://localhost:4001/orgs/my-org/teams/engineering/memberships/octocat \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"role": "maintainer"}'
# Team repos: list, add, remove
curl http://localhost:4001/orgs/my-org/teams/engineering/repos
curl -X PUT http://localhost:4001/orgs/my-org/teams/engineering/repos/my-org/org-repo \
-H "Authorization: Bearer $TOKEN"
# Legacy team endpoints by ID
curl http://localhost:4001/teams/1
curl http://localhost:4001/teams/1/members
GitHub Apps
# Get authenticated app (requires JWT auth)
curl http://localhost:4001/app \
-H "Authorization: Bearer <jwt>"
# List app installations
curl http://localhost:4001/app/installations \
-H "Authorization: Bearer <jwt>"
# Get installation
curl http://localhost:4001/app/installations/100 \
-H "Authorization: Bearer <jwt>"
# Create installation access token (mints ghs_... token)
curl -X POST http://localhost:4001/app/installations/100/access_tokens \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"permissions": {"contents": "read"}}'
# Find installation for repo / org / user
curl http://localhost:4001/repos/my-org/org-repo/installation
curl http://localhost:4001/orgs/my-org/installation
curl http://localhost:4001/users/octocat/installation
App の Webhook 配信:イベントが発生すると、エミュレーターは設定された webhook_url に event_callback ペイロードを X-GitHub-Event および X-Hub-Signature-256 ヘッダーとともに POST します。
リリース
# Create release
curl -X POST http://localhost:4001/repos/octocat/hello-world/releases \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"tag_name": "v1.0.0", "name": "v1.0.0"}'
# List, get, latest, by tag, generate notes
# Release assets: list, upload
curl http://localhost:4001/repos/octocat/hello-world/releases/1/assets
curl -X POST http://localhost:4001/repos/octocat/hello-world/releases/1/assets \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/octet-stream" \
-H "name: binary.zip" \
--data-binary @binary.zip
Webhook
# Create webhook (real HTTP delivery on state changes)
curl -X POST http://localhost:4001/repos/octocat/hello-world/hooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"config": {"url": "http://localhost:8080/webhook"}, "events": ["push", "pull_request"]}'
# Full CRUD, ping, test, deliveries
# Org webhooks also supported
検索
# Search repositories
curl "http://localhost:4001/search/repositories?q=language:JavaScript+user:octocat"
# Search issues and PRs
curl "http://localhost:4001/search/issues?q=repo:octocat/hello-world+is:open"
# Search users, code, commits, topics, labels
Actions
# Workflows: list, get, enable/disable, dispatch
# Workflow runs: list, get, cancel, rerun, delete, logs
# Jobs: list, get, logs
# Artifacts: list, get, delete
# Secrets: repo + org CRUD
Checks
# Create check run
curl -X POST http://localhost:4001/repos/octocat/hello-world/check-runs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "CI", "head_sha": "abc123", "status": "completed", "conclusion": "success"}'
# Check suites: create, get, rerequest, preferences, list by ref. Ref based lookups accept branch and tag refs containing slashes.
# Check runs: list for suite, annotations. Ref based lookups accept branch and tag refs containing slashes.
# Automatic suite status rollup from check run results
ref および suite ごとの check-run 一覧は、ページネーション用の Link ヘッダーとともに page および per_page(デフォルトは 30、最大は 100)をサポートします。ref の一覧では、ページネーションの前に check_name、status、filter が適用されます。total_count はフィルター適用後の全件数を示します。
installation token で作成された Checks には、API レスポンスと Webhook に GitHub App が含まれます。suite は App とコミットごとに分けられ、filter=latest を指定すると、App と check 名ごとに最新の run のみが返されます。ユーザートークンで作成された Checks の app は null になります。リクエストボディの app_id では、帰属先を設定または変更できません。
OAuth
# Authorize (browser flow, shows user picker)
# GET /login/oauth/authorize?client_id=...&redirect_uri=...&scope=...&state=...
# Token exchange
curl -X POST http://localhost:4001/login/oauth/access_token \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"client_id": "Iv1.abc123", "client_secret": "secret_abc123", "code": "<code>"}'
# User emails
curl http://localhost:4001/user/emails -H "Authorization: Bearer $TOKEN"
# OAuth app management (settings)
curl http://localhost:4001/settings/applications -H "Authorization: Bearer $TOKEN"
curl http://localhost:4001/settings/connections/applications/Iv1.abc123 -H "Authorization: Bearer $TOKEN"
# Revoke OAuth app
curl -X POST http://localhost:4001/settings/connections/applications/Iv1.abc123/revoke \
-H "Authorization: Bearer $TOKEN"
その他
curl http://localhost:4001/rate_limit
curl http://localhost:4001/meta
curl http://localhost:4001/emojis
curl http://localhost:4001/versions
curl http://localhost:4001/octocat
curl http://localhost:4001/zen
一般的なパターン
リポジトリ、Issue、プルリクエストの作成
TOKEN="test_token_admin"
BASE="http://localhost:4001"
# Create repo
curl -X POST $BASE/user/repos \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "my-project"}'
# Create issue
curl -X POST $BASE/repos/admin/my-project/issues \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "First issue"}'
# Create PR
curl -X POST $BASE/repos/admin/my-project/pulls \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "First PR", "head": "feature", "base": "main"}'
GitHub App のインストールトークンフロー
# 1. Sign a JWT with { iss: "12345" } using the app's private key (RS256)
# 2. Create an installation access token
curl -X POST $BASE/app/installations/100/access_tokens \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"permissions": {"contents": "read", "issues": "write"}}'
# Returns { "token": "ghs_...", ... }
# 3. Use the installation token to call API endpoints
curl $BASE/repos/my-org/org-repo \
-H "Authorization: Bearer ghs_..."
# Inspect minted installation-token metadata without token values
curl $BASE/_emulate/installation-tokens
検査用ルートはエミュレーター固有のものです。トークン値やトークン由来の識別子を受け取ったり返したりすることなく、App、インストール、アカウント、権限、リポジトリアクセス、発行、期限切れ、ライフサイクルのステータスを一覧表示します。有効期限は情報として表示され、認可の動作には影響しません。
OAuth フロー
- ユーザーを
$GITHUB_EMULATOR_URL/login/oauth/authorize?client_id=...&redirect_uri=...&scope=user+repo&state=...にリダイレクトします - ユーザーはエミュレーターの UI でシード済みユーザーを選択します
- エミュレーターは
?code=...&state=...を付けてリダイレクトします POST /login/oauth/access_tokenでコードをトークンに交換します- トークンを使って API エンドポイントを呼び出します
ビルトインと並行して使うカスタムエミュレーター
npx emulate init --custom inventory を使って、サードパーティ API エミュレーターとテスト用のひな形を作成します。emulate の defineConfig を使い、{ emulator: "github" } などのビルトイン項目とともに emulate.config.ts に登録します。npx emulate start --watch を実行すると、インポートが再読み込みされ、表示された /_emulate URL でカスタム状態を確認できます。再読み込みが成功すると、実行状態はシードにリセットされます。既存のフラット形式のシード設定も引き続き使用できます。--config は明示的に指定したファイルを選択します。サードパーティ API エミュレーターの作成とテストについては、https://emulate.dev/docs/custom-emulators を参照してください。