chub-dev 0.1.0 → 0.1.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1560 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: octokit
|
|
3
|
+
description: "Official GitHub SDK for JavaScript providing REST API, GraphQL API, authentication, and App support via Octokit packages."
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "javascript"
|
|
6
|
+
versions: "5.0.5"
|
|
7
|
+
updated-on: "2026-03-01"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "github,octokit,rest,graphql,api"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# GitHub Octokit.js SDK Coding Guide
|
|
13
|
+
|
|
14
|
+
## 1. Golden Rule
|
|
15
|
+
|
|
16
|
+
**Always use the official Octokit packages from GitHub.** The main `octokit` package is recommended for most use cases as it includes REST API, GraphQL API, authentication, App support, and recommended plugins out of the box.
|
|
17
|
+
|
|
18
|
+
**Never use deprecated or unofficial GitHub API libraries.**
|
|
19
|
+
|
|
20
|
+
To view available Octokit packages and their details:
|
|
21
|
+
```bash
|
|
22
|
+
npm view octokit
|
|
23
|
+
npm view @octokit/core
|
|
24
|
+
npm view @octokit/rest
|
|
25
|
+
npm view @octokit/graphql
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 2. Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install octokit
|
|
32
|
+
# Or: yarn add octokit
|
|
33
|
+
# Or: pnpm add octokit
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For specific components, use `@octokit/rest`, `@octokit/graphql`, or `@octokit/core`.
|
|
37
|
+
|
|
38
|
+
**Environment Variables:**
|
|
39
|
+
```bash
|
|
40
|
+
# Personal Access Token (classic or fine-grained)
|
|
41
|
+
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
|
|
42
|
+
|
|
43
|
+
# For GitHub Apps
|
|
44
|
+
GITHUB_APP_ID=123456
|
|
45
|
+
GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n..."
|
|
46
|
+
GITHUB_INSTALLATION_ID=789012
|
|
47
|
+
|
|
48
|
+
# For OAuth Apps
|
|
49
|
+
GITHUB_CLIENT_ID=Iv1.xxxxxxxxxxxx
|
|
50
|
+
GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxx
|
|
51
|
+
|
|
52
|
+
# GitHub Enterprise Server (optional)
|
|
53
|
+
GITHUB_API_URL=https://github.mycompany.com/api/v3
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 3. Initialization
|
|
57
|
+
|
|
58
|
+
### Basic Authentication with Personal Access Token
|
|
59
|
+
```javascript
|
|
60
|
+
import { Octokit } from "octokit";
|
|
61
|
+
|
|
62
|
+
// Using environment variable
|
|
63
|
+
const octokit = new Octokit({
|
|
64
|
+
auth: process.env.GITHUB_TOKEN
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Or explicit token
|
|
68
|
+
const octokit = new Octokit({
|
|
69
|
+
auth: "ghp_xxxxxxxxxxxxxxxxxxxx"
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Unauthenticated Requests
|
|
74
|
+
```javascript
|
|
75
|
+
import { Octokit } from "octokit";
|
|
76
|
+
|
|
77
|
+
// Lower rate limits (60 requests/hour)
|
|
78
|
+
const octokit = new Octokit();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### GitHub App Authentication
|
|
82
|
+
```javascript
|
|
83
|
+
import { Octokit } from "octokit";
|
|
84
|
+
import { createAppAuth } from "@octokit/auth-app";
|
|
85
|
+
|
|
86
|
+
const octokit = new Octokit({
|
|
87
|
+
authStrategy: createAppAuth,
|
|
88
|
+
auth: {
|
|
89
|
+
appId: process.env.GITHUB_APP_ID,
|
|
90
|
+
privateKey: process.env.GITHUB_PRIVATE_KEY,
|
|
91
|
+
installationId: process.env.GITHUB_INSTALLATION_ID
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### OAuth App Authentication
|
|
97
|
+
```javascript
|
|
98
|
+
import { Octokit } from "octokit";
|
|
99
|
+
import { createOAuthAppAuth } from "@octokit/auth-oauth-app";
|
|
100
|
+
|
|
101
|
+
const octokit = new Octokit({
|
|
102
|
+
authStrategy: createOAuthAppAuth,
|
|
103
|
+
auth: {
|
|
104
|
+
clientId: process.env.GITHUB_CLIENT_ID,
|
|
105
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### GitHub Actions Authentication
|
|
111
|
+
```javascript
|
|
112
|
+
import { Octokit } from "octokit";
|
|
113
|
+
|
|
114
|
+
// In GitHub Actions, use the built-in token
|
|
115
|
+
const octokit = new Octokit({
|
|
116
|
+
auth: process.env.GITHUB_TOKEN // Available in all workflows
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### GitHub Enterprise Server
|
|
121
|
+
```javascript
|
|
122
|
+
import { Octokit } from "octokit";
|
|
123
|
+
|
|
124
|
+
const octokit = new Octokit({
|
|
125
|
+
auth: process.env.GITHUB_TOKEN,
|
|
126
|
+
baseUrl: "https://github.mycompany.com/api/v3"
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 4. Core API Surfaces
|
|
131
|
+
|
|
132
|
+
### Repositories
|
|
133
|
+
|
|
134
|
+
**Minimal Example - Get Repository:**
|
|
135
|
+
```javascript
|
|
136
|
+
const { data: repo } = await octokit.rest.repos.get({
|
|
137
|
+
owner: "octokit",
|
|
138
|
+
repo: "rest.js"
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Advanced Example - Create Repository:**
|
|
143
|
+
```javascript
|
|
144
|
+
const { data: newRepo } = await octokit.rest.repos.createForAuthenticatedUser({
|
|
145
|
+
name: "my-new-repo",
|
|
146
|
+
description: "Created via Octokit",
|
|
147
|
+
private: false,
|
|
148
|
+
auto_init: true,
|
|
149
|
+
gitignore_template: "Node",
|
|
150
|
+
license_template: "mit",
|
|
151
|
+
homepage: "https://example.com",
|
|
152
|
+
has_issues: true,
|
|
153
|
+
has_projects: true,
|
|
154
|
+
has_wiki: true
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**List User Repositories:**
|
|
159
|
+
|
|
160
|
+
To view available methods and parameters:
|
|
161
|
+
```bash
|
|
162
|
+
npm view @octokit/plugin-rest-endpoint-methods
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
const { data: repos } = await octokit.rest.repos.listForAuthenticatedUser({
|
|
167
|
+
sort: "updated",
|
|
168
|
+
direction: "desc",
|
|
169
|
+
per_page: 100
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Update Repository:**
|
|
174
|
+
```javascript
|
|
175
|
+
const { data: updated } = await octokit.rest.repos.update({
|
|
176
|
+
owner: "username",
|
|
177
|
+
repo: "repo-name",
|
|
178
|
+
description: "New description",
|
|
179
|
+
homepage: "https://newsite.com",
|
|
180
|
+
has_issues: false
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Issues
|
|
185
|
+
|
|
186
|
+
**Minimal Example - List Issues:**
|
|
187
|
+
|
|
188
|
+
For CLI alternative: `gh issue list --repo owner/repo`
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
const { data: issues } = await octokit.rest.issues.listForRepo({
|
|
192
|
+
owner: "facebook",
|
|
193
|
+
repo: "react"
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Advanced Example - Create Issue with Labels:**
|
|
198
|
+
```javascript
|
|
199
|
+
const { data: issue } = await octokit.rest.issues.create({
|
|
200
|
+
owner: "owner",
|
|
201
|
+
repo: "repo",
|
|
202
|
+
title: "Bug: Application crashes on startup",
|
|
203
|
+
body: `## Description
|
|
204
|
+
Detailed description of the issue...
|
|
205
|
+
|
|
206
|
+
## Steps to Reproduce
|
|
207
|
+
1. Step 1
|
|
208
|
+
2. Step 2
|
|
209
|
+
|
|
210
|
+
## Expected Behavior
|
|
211
|
+
What should happen
|
|
212
|
+
|
|
213
|
+
## Actual Behavior
|
|
214
|
+
What actually happens`,
|
|
215
|
+
labels: ["bug", "high-priority"],
|
|
216
|
+
assignees: ["username1", "username2"],
|
|
217
|
+
milestone: 1
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Update Issue:**
|
|
222
|
+
```javascript
|
|
223
|
+
const { data: updated } = await octokit.rest.issues.update({
|
|
224
|
+
owner: "owner",
|
|
225
|
+
repo: "repo",
|
|
226
|
+
issue_number: 123,
|
|
227
|
+
state: "closed",
|
|
228
|
+
state_reason: "completed",
|
|
229
|
+
labels: ["resolved"]
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Add Comment:**
|
|
234
|
+
```javascript
|
|
235
|
+
const { data: comment } = await octokit.rest.issues.createComment({
|
|
236
|
+
owner: "owner",
|
|
237
|
+
repo: "repo",
|
|
238
|
+
issue_number: 123,
|
|
239
|
+
body: "Thanks for reporting! This has been fixed in v2.0.0"
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Pull Requests
|
|
244
|
+
|
|
245
|
+
**Minimal Example - List Pull Requests:**
|
|
246
|
+
|
|
247
|
+
For CLI alternative: `gh pr list --repo owner/repo --state open`
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
const { data: pulls } = await octokit.rest.pulls.list({
|
|
251
|
+
owner: "microsoft",
|
|
252
|
+
repo: "vscode",
|
|
253
|
+
state: "open"
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Advanced Example - Create Pull Request:**
|
|
258
|
+
```javascript
|
|
259
|
+
const { data: pr } = await octokit.rest.pulls.create({
|
|
260
|
+
owner: "owner",
|
|
261
|
+
repo: "repo",
|
|
262
|
+
title: "Add new feature",
|
|
263
|
+
head: "feature-branch",
|
|
264
|
+
base: "main",
|
|
265
|
+
body: "Detailed description of changes in this pull request.",
|
|
266
|
+
maintainer_can_modify: true,
|
|
267
|
+
draft: false
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Get Pull Request Files:**
|
|
272
|
+
```javascript
|
|
273
|
+
const { data: files } = await octokit.rest.pulls.listFiles({
|
|
274
|
+
owner: "owner",
|
|
275
|
+
repo: "repo",
|
|
276
|
+
pull_number: 123
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Merge Pull Request:**
|
|
281
|
+
```javascript
|
|
282
|
+
const { data: merge } = await octokit.rest.pulls.merge({
|
|
283
|
+
owner: "owner",
|
|
284
|
+
repo: "repo",
|
|
285
|
+
pull_number: 123,
|
|
286
|
+
commit_title: "Merge PR #123: Add new feature",
|
|
287
|
+
commit_message: "Additional details about the merge",
|
|
288
|
+
merge_method: "squash" // or "merge" or "rebase"
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Request Reviewers:**
|
|
293
|
+
```javascript
|
|
294
|
+
await octokit.rest.pulls.requestReviewers({
|
|
295
|
+
owner: "owner",
|
|
296
|
+
repo: "repo",
|
|
297
|
+
pull_number: 123,
|
|
298
|
+
reviewers: ["reviewer1", "reviewer2"],
|
|
299
|
+
team_reviewers: ["team-slug"]
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Commits
|
|
304
|
+
|
|
305
|
+
**Minimal Example - Get Commit:**
|
|
306
|
+
```javascript
|
|
307
|
+
const { data: commit } = await octokit.rest.repos.getCommit({
|
|
308
|
+
owner: "owner",
|
|
309
|
+
repo: "repo",
|
|
310
|
+
ref: "abc123"
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Advanced Example - List Commits with Filtering:**
|
|
315
|
+
```javascript
|
|
316
|
+
const { data: commits } = await octokit.rest.repos.listCommits({
|
|
317
|
+
owner: "owner",
|
|
318
|
+
repo: "repo",
|
|
319
|
+
sha: "main",
|
|
320
|
+
path: "src/index.js",
|
|
321
|
+
author: "username",
|
|
322
|
+
since: "2025-01-01T00:00:00Z",
|
|
323
|
+
until: "2025-12-31T23:59:59Z",
|
|
324
|
+
per_page: 100
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Compare Commits:**
|
|
329
|
+
```javascript
|
|
330
|
+
const { data: comparison } = await octokit.rest.repos.compareCommits({
|
|
331
|
+
owner: "owner",
|
|
332
|
+
repo: "repo",
|
|
333
|
+
base: "main",
|
|
334
|
+
head: "feature-branch"
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Branches
|
|
339
|
+
|
|
340
|
+
**List Branches:**
|
|
341
|
+
|
|
342
|
+
For CLI alternative: `gh api repos/owner/repo/branches` or `git branch -r`
|
|
343
|
+
|
|
344
|
+
```javascript
|
|
345
|
+
const { data: branches } = await octokit.rest.repos.listBranches({
|
|
346
|
+
owner: "owner",
|
|
347
|
+
repo: "repo"
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Get Branch:**
|
|
352
|
+
```javascript
|
|
353
|
+
const { data: branch } = await octokit.rest.repos.getBranch({
|
|
354
|
+
owner: "owner",
|
|
355
|
+
repo: "repo",
|
|
356
|
+
branch: "main"
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Create Branch (via Git References):**
|
|
361
|
+
```javascript
|
|
362
|
+
// First, get the SHA of the source branch
|
|
363
|
+
const { data: refData } = await octokit.rest.git.getRef({
|
|
364
|
+
owner: "owner",
|
|
365
|
+
repo: "repo",
|
|
366
|
+
ref: "heads/main"
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Create new branch from that SHA
|
|
370
|
+
await octokit.rest.git.createRef({
|
|
371
|
+
owner: "owner",
|
|
372
|
+
repo: "repo",
|
|
373
|
+
ref: "refs/heads/new-feature",
|
|
374
|
+
sha: refData.object.sha
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Files and Content
|
|
379
|
+
|
|
380
|
+
**Minimal Example - Get File Content:**
|
|
381
|
+
```javascript
|
|
382
|
+
const { data: file } = await octokit.rest.repos.getContent({
|
|
383
|
+
owner: "owner",
|
|
384
|
+
repo: "repo",
|
|
385
|
+
path: "README.md"
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Decode base64 content
|
|
389
|
+
const content = Buffer.from(file.content, "base64").toString("utf8");
|
|
390
|
+
console.log(content);
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Advanced Example - Create or Update File:**
|
|
394
|
+
```javascript
|
|
395
|
+
// Get current file to retrieve SHA (required for updates)
|
|
396
|
+
let sha;
|
|
397
|
+
try {
|
|
398
|
+
const { data: existing } = await octokit.rest.repos.getContent({
|
|
399
|
+
owner: "owner",
|
|
400
|
+
repo: "repo",
|
|
401
|
+
path: "config.json"
|
|
402
|
+
});
|
|
403
|
+
sha = existing.sha;
|
|
404
|
+
} catch (error) {
|
|
405
|
+
// File doesn't exist, will create new
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const content = JSON.stringify({ version: "2.0.0" }, null, 2);
|
|
409
|
+
const { data: result } = await octokit.rest.repos.createOrUpdateFileContents({
|
|
410
|
+
owner: "owner",
|
|
411
|
+
repo: "repo",
|
|
412
|
+
path: "config.json",
|
|
413
|
+
message: "Update config version to 2.0.0",
|
|
414
|
+
content: Buffer.from(content).toString("base64"),
|
|
415
|
+
sha: sha, // Required for updates, omit for new files
|
|
416
|
+
branch: "main",
|
|
417
|
+
committer: {
|
|
418
|
+
name: "Bot Name",
|
|
419
|
+
email: "bot@example.com"
|
|
420
|
+
},
|
|
421
|
+
author: {
|
|
422
|
+
name: "Author Name",
|
|
423
|
+
email: "author@example.com"
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
console.log(`File updated: ${result.content.html_url}`);
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**Delete File:**
|
|
431
|
+
```javascript
|
|
432
|
+
// Get file SHA first
|
|
433
|
+
const { data: file } = await octokit.rest.repos.getContent({
|
|
434
|
+
owner: "owner",
|
|
435
|
+
repo: "repo",
|
|
436
|
+
path: "file-to-delete.txt"
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
await octokit.rest.repos.deleteFile({
|
|
440
|
+
owner: "owner",
|
|
441
|
+
repo: "repo",
|
|
442
|
+
path: "file-to-delete.txt",
|
|
443
|
+
message: "Remove obsolete file",
|
|
444
|
+
sha: file.sha,
|
|
445
|
+
branch: "main"
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Releases
|
|
450
|
+
|
|
451
|
+
**Minimal Example - List Releases:**
|
|
452
|
+
|
|
453
|
+
For CLI alternative: `gh release list --repo owner/repo`
|
|
454
|
+
|
|
455
|
+
```javascript
|
|
456
|
+
const { data: releases } = await octokit.rest.repos.listReleases({
|
|
457
|
+
owner: "owner",
|
|
458
|
+
repo: "repo"
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
**Advanced Example - Create Release:**
|
|
463
|
+
```javascript
|
|
464
|
+
const { data: release } = await octokit.rest.repos.createRelease({
|
|
465
|
+
owner: "owner",
|
|
466
|
+
repo: "repo",
|
|
467
|
+
tag_name: "v2.0.0",
|
|
468
|
+
name: "Version 2.0.0",
|
|
469
|
+
body: "Release notes and changelog content here.",
|
|
470
|
+
draft: false,
|
|
471
|
+
prerelease: false,
|
|
472
|
+
generate_release_notes: false,
|
|
473
|
+
target_commitish: "main"
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Get Latest Release:**
|
|
479
|
+
|
|
480
|
+
For CLI alternative: `gh release view --repo owner/repo`
|
|
481
|
+
|
|
482
|
+
```javascript
|
|
483
|
+
const { data: latest } = await octokit.rest.repos.getLatestRelease({
|
|
484
|
+
owner: "owner",
|
|
485
|
+
repo: "repo"
|
|
486
|
+
});
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Gists
|
|
490
|
+
|
|
491
|
+
**Minimal Example - Create Gist:**
|
|
492
|
+
```javascript
|
|
493
|
+
const { data: gist } = await octokit.rest.gists.create({
|
|
494
|
+
files: {
|
|
495
|
+
"hello.js": {
|
|
496
|
+
content: "console.log('Hello World');"
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
description: "Hello World example",
|
|
500
|
+
public: true
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**Advanced Example - Multi-file Gist:**
|
|
506
|
+
```javascript
|
|
507
|
+
const { data: gist } = await octokit.rest.gists.create({
|
|
508
|
+
files: {
|
|
509
|
+
"package.json": {
|
|
510
|
+
content: JSON.stringify({
|
|
511
|
+
name: "example",
|
|
512
|
+
version: "1.0.0"
|
|
513
|
+
}, null, 2)
|
|
514
|
+
},
|
|
515
|
+
"index.js": {
|
|
516
|
+
content: "const express = require('express');\n// App code here"
|
|
517
|
+
},
|
|
518
|
+
"README.md": {
|
|
519
|
+
content: "# Example Project\n\nDescription here"
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
description: "Full project example",
|
|
523
|
+
public: false
|
|
524
|
+
});
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Search
|
|
528
|
+
|
|
529
|
+
**Search Repositories:**
|
|
530
|
+
```javascript
|
|
531
|
+
const { data: results } = await octokit.rest.search.repos({
|
|
532
|
+
q: "language:javascript stars:>1000 created:>2024-01-01",
|
|
533
|
+
sort: "stars",
|
|
534
|
+
order: "desc",
|
|
535
|
+
per_page: 30
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Search Issues and Pull Requests:**
|
|
540
|
+
```javascript
|
|
541
|
+
const { data: results } = await octokit.rest.search.issuesAndPullRequests({
|
|
542
|
+
q: "type:pr repo:facebook/react is:open label:bug",
|
|
543
|
+
sort: "created",
|
|
544
|
+
order: "desc"
|
|
545
|
+
});
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
**Search Code:**
|
|
549
|
+
```javascript
|
|
550
|
+
const { data: results } = await octokit.rest.search.code({
|
|
551
|
+
q: "import Octokit from octokit language:javascript",
|
|
552
|
+
per_page: 50
|
|
553
|
+
});
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
**Search Users:**
|
|
557
|
+
```javascript
|
|
558
|
+
const { data: results } = await octokit.rest.search.users({
|
|
559
|
+
q: "followers:>1000 location:London",
|
|
560
|
+
per_page: 20
|
|
561
|
+
});
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Users and Organizations
|
|
565
|
+
|
|
566
|
+
**Get Authenticated User:**
|
|
567
|
+
|
|
568
|
+
For CLI alternative: `gh api user`
|
|
569
|
+
|
|
570
|
+
```javascript
|
|
571
|
+
const { data: user } = await octokit.rest.users.getAuthenticated();
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**Get User by Username:**
|
|
575
|
+
|
|
576
|
+
For CLI alternative: `gh api users/username`
|
|
577
|
+
|
|
578
|
+
```javascript
|
|
579
|
+
const { data: user } = await octokit.rest.users.getByUsername({
|
|
580
|
+
username: "torvalds"
|
|
581
|
+
});
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**List Organization Repositories:**
|
|
585
|
+
|
|
586
|
+
For CLI alternative: `gh repo list org-name`
|
|
587
|
+
|
|
588
|
+
```javascript
|
|
589
|
+
const { data: repos } = await octokit.rest.repos.listForOrg({
|
|
590
|
+
org: "github",
|
|
591
|
+
type: "public",
|
|
592
|
+
sort: "updated",
|
|
593
|
+
per_page: 100
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**List Organization Members:**
|
|
598
|
+
|
|
599
|
+
For CLI alternative: `gh api orgs/org-name/members`
|
|
600
|
+
|
|
601
|
+
```javascript
|
|
602
|
+
const { data: members } = await octokit.rest.orgs.listMembers({
|
|
603
|
+
org: "github",
|
|
604
|
+
per_page: 100
|
|
605
|
+
});
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Webhooks
|
|
609
|
+
|
|
610
|
+
**List Repository Webhooks:**
|
|
611
|
+
|
|
612
|
+
For CLI alternative: `gh api repos/owner/repo/hooks`
|
|
613
|
+
|
|
614
|
+
```javascript
|
|
615
|
+
const { data: hooks } = await octokit.rest.repos.listWebhooks({
|
|
616
|
+
owner: "owner",
|
|
617
|
+
repo: "repo"
|
|
618
|
+
});
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
**Create Webhook:**
|
|
622
|
+
```javascript
|
|
623
|
+
const { data: hook } = await octokit.rest.repos.createWebhook({
|
|
624
|
+
owner: "owner",
|
|
625
|
+
repo: "repo",
|
|
626
|
+
name: "web",
|
|
627
|
+
active: true,
|
|
628
|
+
events: ["push", "pull_request", "issues"],
|
|
629
|
+
config: {
|
|
630
|
+
url: "https://example.com/webhook",
|
|
631
|
+
content_type: "json",
|
|
632
|
+
secret: process.env.WEBHOOK_SECRET,
|
|
633
|
+
insecure_ssl: "0"
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### GraphQL API
|
|
639
|
+
|
|
640
|
+
**Minimal Example - Simple Query:**
|
|
641
|
+
```javascript
|
|
642
|
+
const { repository } = await octokit.graphql(`
|
|
643
|
+
query {
|
|
644
|
+
repository(owner: "octokit", name: "graphql.js") {
|
|
645
|
+
name
|
|
646
|
+
description
|
|
647
|
+
stargazerCount
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
`);
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
**Advanced Example - Query with Variables:**
|
|
654
|
+
```javascript
|
|
655
|
+
const query = `
|
|
656
|
+
query($owner: String!, $repo: String!, $issueCount: Int!) {
|
|
657
|
+
repository(owner: $owner, name: $repo) {
|
|
658
|
+
name
|
|
659
|
+
issues(last: $issueCount, states: OPEN) {
|
|
660
|
+
edges {
|
|
661
|
+
node {
|
|
662
|
+
number
|
|
663
|
+
title
|
|
664
|
+
author {
|
|
665
|
+
login
|
|
666
|
+
}
|
|
667
|
+
createdAt
|
|
668
|
+
labels(first: 5) {
|
|
669
|
+
nodes {
|
|
670
|
+
name
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
`;
|
|
679
|
+
|
|
680
|
+
const { repository } = await octokit.graphql(query, {
|
|
681
|
+
owner: "facebook",
|
|
682
|
+
repo: "react",
|
|
683
|
+
issueCount: 10
|
|
684
|
+
});
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
**GraphQL Mutation Example:**
|
|
688
|
+
```javascript
|
|
689
|
+
const mutation = `
|
|
690
|
+
mutation($repositoryId: ID!, $issueTitle: String!, $issueBody: String!) {
|
|
691
|
+
createIssue(input: {
|
|
692
|
+
repositoryId: $repositoryId,
|
|
693
|
+
title: $issueTitle,
|
|
694
|
+
body: $issueBody
|
|
695
|
+
}) {
|
|
696
|
+
issue {
|
|
697
|
+
number
|
|
698
|
+
url
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
`;
|
|
703
|
+
|
|
704
|
+
// First get repository ID
|
|
705
|
+
const { repository } = await octokit.graphql(`
|
|
706
|
+
query($owner: String!, $name: String!) {
|
|
707
|
+
repository(owner: $owner, name: $name) {
|
|
708
|
+
id
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
`, {
|
|
712
|
+
owner: "owner",
|
|
713
|
+
name: "repo"
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Create issue
|
|
717
|
+
const result = await octokit.graphql(mutation, {
|
|
718
|
+
repositoryId: repository.id,
|
|
719
|
+
issueTitle: "New issue via GraphQL",
|
|
720
|
+
issueBody: "Issue body content"
|
|
721
|
+
});
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
## 5. Advanced Features
|
|
725
|
+
|
|
726
|
+
### Pagination
|
|
727
|
+
|
|
728
|
+
**Automatic Pagination - Get All Results:**
|
|
729
|
+
```javascript
|
|
730
|
+
// Get all issues (auto-handles pagination)
|
|
731
|
+
const allIssues = await octokit.paginate(
|
|
732
|
+
octokit.rest.issues.listForRepo,
|
|
733
|
+
{
|
|
734
|
+
owner: "facebook",
|
|
735
|
+
repo: "react",
|
|
736
|
+
state: "all",
|
|
737
|
+
per_page: 100
|
|
738
|
+
}
|
|
739
|
+
);
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
**Iterator-based Pagination:**
|
|
743
|
+
```javascript
|
|
744
|
+
// Process results as they come
|
|
745
|
+
for await (const response of octokit.paginate.iterator(
|
|
746
|
+
octokit.rest.repos.listForOrg,
|
|
747
|
+
{
|
|
748
|
+
org: "github",
|
|
749
|
+
per_page: 100
|
|
750
|
+
}
|
|
751
|
+
)) {
|
|
752
|
+
// response.data contains up to 100 items
|
|
753
|
+
// Process each batch here
|
|
754
|
+
}
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
**Custom Page Limit:**
|
|
758
|
+
```javascript
|
|
759
|
+
// Get only first 500 items across pages
|
|
760
|
+
const limitedResults = await octokit.paginate(
|
|
761
|
+
octokit.rest.issues.listForRepo,
|
|
762
|
+
{
|
|
763
|
+
owner: "owner",
|
|
764
|
+
repo: "repo",
|
|
765
|
+
per_page: 100
|
|
766
|
+
},
|
|
767
|
+
(response, done) => {
|
|
768
|
+
if (response.data.length >= 500) {
|
|
769
|
+
done();
|
|
770
|
+
}
|
|
771
|
+
return response.data;
|
|
772
|
+
}
|
|
773
|
+
);
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### Error Handling
|
|
777
|
+
|
|
778
|
+
**Comprehensive Error Handling:**
|
|
779
|
+
```javascript
|
|
780
|
+
import { RequestError } from "@octokit/request-error";
|
|
781
|
+
|
|
782
|
+
try {
|
|
783
|
+
const { data } = await octokit.rest.repos.get({
|
|
784
|
+
owner: "owner",
|
|
785
|
+
repo: "nonexistent"
|
|
786
|
+
});
|
|
787
|
+
} catch (error) {
|
|
788
|
+
if (error instanceof RequestError) {
|
|
789
|
+
console.error(`Error ${error.status}: ${error.message}`);
|
|
790
|
+
|
|
791
|
+
// Check specific error codes
|
|
792
|
+
if (error.status === 404) {
|
|
793
|
+
console.error("Repository not found");
|
|
794
|
+
} else if (error.status === 403) {
|
|
795
|
+
if (error.response.headers["x-ratelimit-remaining"] === "0") {
|
|
796
|
+
console.error("Rate limit exceeded");
|
|
797
|
+
const resetTime = new Date(
|
|
798
|
+
error.response.headers["x-ratelimit-reset"] * 1000
|
|
799
|
+
);
|
|
800
|
+
console.error(`Rate limit resets at ${resetTime}`);
|
|
801
|
+
} else {
|
|
802
|
+
console.error("Forbidden - check permissions");
|
|
803
|
+
}
|
|
804
|
+
} else if (error.status === 401) {
|
|
805
|
+
console.error("Unauthorized - check authentication token");
|
|
806
|
+
} else if (error.status >= 500) {
|
|
807
|
+
console.error("GitHub server error - retry later");
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Log additional error details
|
|
811
|
+
console.error("Request ID:", error.response.headers["x-github-request-id"]);
|
|
812
|
+
} else {
|
|
813
|
+
console.error("Unexpected error:", error);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
### Rate Limiting with Throttle Plugin
|
|
819
|
+
|
|
820
|
+
**Setup:**
|
|
821
|
+
```bash
|
|
822
|
+
npm install @octokit/plugin-throttling
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
**Implementation:**
|
|
826
|
+
```javascript
|
|
827
|
+
import { Octokit } from "@octokit/core";
|
|
828
|
+
import { throttling } from "@octokit/plugin-throttling";
|
|
829
|
+
|
|
830
|
+
const MyOctokit = Octokit.plugin(throttling);
|
|
831
|
+
|
|
832
|
+
const octokit = new MyOctokit({
|
|
833
|
+
auth: process.env.GITHUB_TOKEN,
|
|
834
|
+
throttle: {
|
|
835
|
+
onRateLimit: (retryAfter, options, octokit, retryCount) => {
|
|
836
|
+
octokit.log.warn(
|
|
837
|
+
`Request quota exhausted for request ${options.method} ${options.url}`
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
// Retry first 3 times
|
|
841
|
+
if (retryCount < 3) {
|
|
842
|
+
octokit.log.info(`Retrying after ${retryAfter} seconds!`);
|
|
843
|
+
return true;
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
onSecondaryRateLimit: (retryAfter, options, octokit, retryCount) => {
|
|
847
|
+
octokit.log.warn(
|
|
848
|
+
`SecondaryRateLimit detected for request ${options.method} ${options.url}`
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
// Always retry on secondary rate limit
|
|
852
|
+
if (retryCount < 5) {
|
|
853
|
+
octokit.log.info(`Retrying after ${retryAfter} seconds!`);
|
|
854
|
+
return true;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
### Retry Plugin
|
|
862
|
+
|
|
863
|
+
**Setup:**
|
|
864
|
+
```bash
|
|
865
|
+
npm install @octokit/plugin-retry
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
**Implementation:**
|
|
869
|
+
```javascript
|
|
870
|
+
import { Octokit } from "@octokit/core";
|
|
871
|
+
import { retry } from "@octokit/plugin-retry";
|
|
872
|
+
|
|
873
|
+
const MyOctokit = Octokit.plugin(retry);
|
|
874
|
+
|
|
875
|
+
const octokit = new MyOctokit({
|
|
876
|
+
auth: process.env.GITHUB_TOKEN
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// Automatic retries on 500 errors (up to 3 times)
|
|
880
|
+
const { data } = await octokit.rest.repos.get({
|
|
881
|
+
owner: "owner",
|
|
882
|
+
repo: "repo"
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
// Manual retry configuration
|
|
886
|
+
const { data: issues } = await octokit.rest.issues.listForRepo({
|
|
887
|
+
owner: "owner",
|
|
888
|
+
repo: "repo",
|
|
889
|
+
request: {
|
|
890
|
+
retries: 5,
|
|
891
|
+
retryAfter: 3 // seconds
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
### Custom Request Options
|
|
897
|
+
|
|
898
|
+
**Timeouts:**
|
|
899
|
+
```javascript
|
|
900
|
+
const { data } = await octokit.rest.repos.get({
|
|
901
|
+
owner: "owner",
|
|
902
|
+
repo: "repo",
|
|
903
|
+
request: {
|
|
904
|
+
timeout: 10000 // 10 seconds
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
**Custom Headers:**
|
|
910
|
+
```javascript
|
|
911
|
+
const { data } = await octokit.rest.repos.get({
|
|
912
|
+
owner: "owner",
|
|
913
|
+
repo: "repo",
|
|
914
|
+
headers: {
|
|
915
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
**Signal for Abort:**
|
|
921
|
+
```javascript
|
|
922
|
+
const controller = new AbortController();
|
|
923
|
+
|
|
924
|
+
setTimeout(() => controller.abort(), 5000); // Abort after 5s
|
|
925
|
+
|
|
926
|
+
try {
|
|
927
|
+
const { data } = await octokit.rest.repos.get({
|
|
928
|
+
owner: "owner",
|
|
929
|
+
repo: "repo",
|
|
930
|
+
request: {
|
|
931
|
+
signal: controller.signal
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
} catch (error) {
|
|
935
|
+
if (error.name === "AbortError") {
|
|
936
|
+
console.log("Request was aborted");
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
## 6. TypeScript Usage
|
|
942
|
+
|
|
943
|
+
### Basic TypeScript Setup
|
|
944
|
+
|
|
945
|
+
**tsconfig.json Configuration:**
|
|
946
|
+
```json
|
|
947
|
+
{
|
|
948
|
+
"compilerOptions": {
|
|
949
|
+
"moduleResolution": "node16",
|
|
950
|
+
"module": "node16",
|
|
951
|
+
"target": "ES2022",
|
|
952
|
+
"lib": ["ES2022"]
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
### Type-Safe API Calls
|
|
958
|
+
|
|
959
|
+
**Import Types:**
|
|
960
|
+
```typescript
|
|
961
|
+
import { Octokit } from "octokit";
|
|
962
|
+
import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods";
|
|
963
|
+
|
|
964
|
+
// Type for a specific endpoint
|
|
965
|
+
type GetRepoResponse = RestEndpointMethodTypes["repos"]["get"]["response"];
|
|
966
|
+
type GetRepoParams = RestEndpointMethodTypes["repos"]["get"]["parameters"];
|
|
967
|
+
|
|
968
|
+
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
|
969
|
+
|
|
970
|
+
const params: GetRepoParams = {
|
|
971
|
+
owner: "octokit",
|
|
972
|
+
repo: "rest.js"
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
const response: GetRepoResponse = await octokit.rest.repos.get(params);
|
|
976
|
+
const repo = response.data;
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
**Generic Response Types:**
|
|
980
|
+
```typescript
|
|
981
|
+
import type { Endpoints } from "@octokit/types";
|
|
982
|
+
|
|
983
|
+
type IssuesListResponse = Endpoints["GET /repos/{owner}/{repo}/issues"]["response"];
|
|
984
|
+
type Issue = Endpoints["GET /repos/{owner}/{repo}/issues"]["response"]["data"][number];
|
|
985
|
+
|
|
986
|
+
const issues: IssuesListResponse = await octokit.rest.issues.listForRepo({
|
|
987
|
+
owner: "facebook",
|
|
988
|
+
repo: "react"
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
const firstIssue: Issue = issues.data[0];
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
**Custom Type-Safe Wrapper:**
|
|
995
|
+
```typescript
|
|
996
|
+
interface GitHubService {
|
|
997
|
+
getRepository(owner: string, repo: string): Promise<Repository>;
|
|
998
|
+
createIssue(params: CreateIssueParams): Promise<Issue>;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
interface Repository {
|
|
1002
|
+
name: string;
|
|
1003
|
+
description: string;
|
|
1004
|
+
stars: number;
|
|
1005
|
+
url: string;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
interface CreateIssueParams {
|
|
1009
|
+
owner: string;
|
|
1010
|
+
repo: string;
|
|
1011
|
+
title: string;
|
|
1012
|
+
body: string;
|
|
1013
|
+
labels?: string[];
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
interface Issue {
|
|
1017
|
+
number: number;
|
|
1018
|
+
title: string;
|
|
1019
|
+
url: string;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
class GitHubClient implements GitHubService {
|
|
1023
|
+
constructor(private octokit: Octokit) {}
|
|
1024
|
+
|
|
1025
|
+
async getRepository(owner: string, repo: string): Promise<Repository> {
|
|
1026
|
+
const { data } = await this.octokit.rest.repos.get({ owner, repo });
|
|
1027
|
+
|
|
1028
|
+
return {
|
|
1029
|
+
name: data.name,
|
|
1030
|
+
description: data.description || "",
|
|
1031
|
+
stars: data.stargazers_count,
|
|
1032
|
+
url: data.html_url
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
async createIssue(params: CreateIssueParams): Promise<Issue> {
|
|
1037
|
+
const { data } = await this.octokit.rest.issues.create({
|
|
1038
|
+
owner: params.owner,
|
|
1039
|
+
repo: params.repo,
|
|
1040
|
+
title: params.title,
|
|
1041
|
+
body: params.body,
|
|
1042
|
+
labels: params.labels
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
return {
|
|
1046
|
+
number: data.number,
|
|
1047
|
+
title: data.title,
|
|
1048
|
+
url: data.html_url
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Usage
|
|
1054
|
+
const client = new GitHubClient(octokit);
|
|
1055
|
+
const repo = await client.getRepository("facebook", "react");
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
### GraphQL TypeScript
|
|
1059
|
+
|
|
1060
|
+
```typescript
|
|
1061
|
+
import { Octokit } from "octokit";
|
|
1062
|
+
|
|
1063
|
+
interface RepositoryQuery {
|
|
1064
|
+
repository: {
|
|
1065
|
+
name: string;
|
|
1066
|
+
stargazerCount: number;
|
|
1067
|
+
issues: {
|
|
1068
|
+
totalCount: number;
|
|
1069
|
+
nodes: Array<{
|
|
1070
|
+
number: number;
|
|
1071
|
+
title: string;
|
|
1072
|
+
author: {
|
|
1073
|
+
login: string;
|
|
1074
|
+
} | null;
|
|
1075
|
+
}>;
|
|
1076
|
+
};
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
|
1081
|
+
|
|
1082
|
+
const result: RepositoryQuery = await octokit.graphql(`
|
|
1083
|
+
query($owner: String!, $repo: String!) {
|
|
1084
|
+
repository(owner: $owner, name: $repo) {
|
|
1085
|
+
name
|
|
1086
|
+
stargazerCount
|
|
1087
|
+
issues(last: 5, states: OPEN) {
|
|
1088
|
+
totalCount
|
|
1089
|
+
nodes {
|
|
1090
|
+
number
|
|
1091
|
+
title
|
|
1092
|
+
author {
|
|
1093
|
+
login
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
`, {
|
|
1100
|
+
owner: "facebook",
|
|
1101
|
+
repo: "react"
|
|
1102
|
+
});
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
## 7. Best Practices
|
|
1106
|
+
|
|
1107
|
+
### Authentication and Security
|
|
1108
|
+
|
|
1109
|
+
**Never Hardcode Tokens:**
|
|
1110
|
+
```javascript
|
|
1111
|
+
// BAD - Never do this
|
|
1112
|
+
const octokit = new Octokit({ auth: "ghp_actualtoken123" });
|
|
1113
|
+
|
|
1114
|
+
// GOOD - Use environment variables
|
|
1115
|
+
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
**Use Fine-Grained Tokens:**
|
|
1119
|
+
Fine-grained personal access tokens provide more granular permissions and are repository-scoped. Use them instead of classic tokens when possible.
|
|
1120
|
+
|
|
1121
|
+
**Rotate Tokens Regularly:**
|
|
1122
|
+
Implement a token rotation strategy, especially for long-running applications.
|
|
1123
|
+
|
|
1124
|
+
**Use GitHub Apps for Production:**
|
|
1125
|
+
GitHub Apps provide better security, higher rate limits, and better audit trails than personal access tokens.
|
|
1126
|
+
|
|
1127
|
+
### Rate Limiting Strategy
|
|
1128
|
+
|
|
1129
|
+
**Check Rate Limit Status:**
|
|
1130
|
+
```javascript
|
|
1131
|
+
const { data: rateLimit } = await octokit.rest.rateLimit.get();
|
|
1132
|
+
|
|
1133
|
+
console.log(`Remaining: ${rateLimit.rate.remaining}/${rateLimit.rate.limit}`);
|
|
1134
|
+
console.log(`Resets at: ${new Date(rateLimit.rate.reset * 1000)}`);
|
|
1135
|
+
|
|
1136
|
+
// Check specific resource limits
|
|
1137
|
+
console.log(`Search limit: ${rateLimit.resources.search.remaining}`);
|
|
1138
|
+
console.log(`GraphQL limit: ${rateLimit.resources.graphql.remaining}`);
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
**Implement Backoff Strategy:**
|
|
1142
|
+
```javascript
|
|
1143
|
+
async function makeRequestWithBackoff(fn, maxRetries = 3) {
|
|
1144
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
1145
|
+
try {
|
|
1146
|
+
return await fn();
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
if (error.status === 403 && error.response.headers["x-ratelimit-remaining"] === "0") {
|
|
1149
|
+
const resetTime = parseInt(error.response.headers["x-ratelimit-reset"]) * 1000;
|
|
1150
|
+
const waitTime = resetTime - Date.now();
|
|
1151
|
+
|
|
1152
|
+
if (i < maxRetries - 1) {
|
|
1153
|
+
console.log(`Rate limited. Waiting ${waitTime}ms...`);
|
|
1154
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
throw error;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
**Use Conditional Requests:**
|
|
1165
|
+
```javascript
|
|
1166
|
+
// First request
|
|
1167
|
+
const { data, headers } = await octokit.rest.repos.get({
|
|
1168
|
+
owner: "owner",
|
|
1169
|
+
repo: "repo"
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
const etag = headers.etag;
|
|
1173
|
+
|
|
1174
|
+
// Later request - only downloads if changed
|
|
1175
|
+
const { status, data: newData } = await octokit.rest.repos.get({
|
|
1176
|
+
owner: "owner",
|
|
1177
|
+
repo: "repo",
|
|
1178
|
+
headers: {
|
|
1179
|
+
"If-None-Match": etag
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
if (status === 304) {
|
|
1184
|
+
console.log("No changes - use cached data");
|
|
1185
|
+
} else {
|
|
1186
|
+
console.log("Data changed - use new data");
|
|
1187
|
+
}
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
### Error Recovery
|
|
1191
|
+
|
|
1192
|
+
**Implement Retry Logic:**
|
|
1193
|
+
```javascript
|
|
1194
|
+
async function retryRequest(fn, maxRetries = 3, delay = 1000) {
|
|
1195
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
1196
|
+
try {
|
|
1197
|
+
return await fn();
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
const isLastRetry = i === maxRetries - 1;
|
|
1200
|
+
const shouldRetry = error.status >= 500 || error.status === 429;
|
|
1201
|
+
|
|
1202
|
+
if (!shouldRetry || isLastRetry) {
|
|
1203
|
+
throw error;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const backoffDelay = delay * Math.pow(2, i);
|
|
1207
|
+
console.log(`Retry ${i + 1}/${maxRetries} after ${backoffDelay}ms`);
|
|
1208
|
+
await new Promise(resolve => setTimeout(resolve, backoffDelay));
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// Usage
|
|
1214
|
+
const data = await retryRequest(
|
|
1215
|
+
() => octokit.rest.repos.get({ owner: "owner", repo: "repo" })
|
|
1216
|
+
);
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
### Performance Optimization
|
|
1220
|
+
|
|
1221
|
+
**Use GraphQL for Complex Queries:**
|
|
1222
|
+
```javascript
|
|
1223
|
+
// BAD - Multiple REST requests
|
|
1224
|
+
const { data: repo } = await octokit.rest.repos.get({ owner, repo });
|
|
1225
|
+
const { data: issues } = await octokit.rest.issues.listForRepo({ owner, repo });
|
|
1226
|
+
const { data: pulls } = await octokit.rest.pulls.list({ owner, repo });
|
|
1227
|
+
|
|
1228
|
+
// GOOD - Single GraphQL request
|
|
1229
|
+
const result = await octokit.graphql(`
|
|
1230
|
+
query($owner: String!, $repo: String!) {
|
|
1231
|
+
repository(owner: $owner, name: $repo) {
|
|
1232
|
+
name
|
|
1233
|
+
description
|
|
1234
|
+
issues(last: 10, states: OPEN) {
|
|
1235
|
+
totalCount
|
|
1236
|
+
nodes { number title }
|
|
1237
|
+
}
|
|
1238
|
+
pullRequests(last: 10, states: OPEN) {
|
|
1239
|
+
totalCount
|
|
1240
|
+
nodes { number title }
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
`, { owner, repo });
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1247
|
+
**Batch Operations:**
|
|
1248
|
+
```javascript
|
|
1249
|
+
// Process items in batches to avoid overwhelming the API
|
|
1250
|
+
async function processBatch(items, batchSize, handler) {
|
|
1251
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
1252
|
+
const batch = items.slice(i, i + batchSize);
|
|
1253
|
+
await Promise.all(batch.map(handler));
|
|
1254
|
+
|
|
1255
|
+
// Brief pause between batches
|
|
1256
|
+
if (i + batchSize < items.length) {
|
|
1257
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// Usage
|
|
1263
|
+
await processBatch(
|
|
1264
|
+
repositories,
|
|
1265
|
+
10,
|
|
1266
|
+
async (repo) => {
|
|
1267
|
+
const { data } = await octokit.rest.repos.get({
|
|
1268
|
+
owner: repo.owner,
|
|
1269
|
+
repo: repo.name
|
|
1270
|
+
});
|
|
1271
|
+
// Process repo data
|
|
1272
|
+
}
|
|
1273
|
+
);
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
### Data Validation
|
|
1277
|
+
|
|
1278
|
+
**Validate Input:**
|
|
1279
|
+
```javascript
|
|
1280
|
+
function validateRepoParams(owner, repo) {
|
|
1281
|
+
if (!owner || typeof owner !== "string" || owner.trim() === "") {
|
|
1282
|
+
throw new Error("Invalid owner parameter");
|
|
1283
|
+
}
|
|
1284
|
+
if (!repo || typeof repo !== "string" || repo.trim() === "") {
|
|
1285
|
+
throw new Error("Invalid repo parameter");
|
|
1286
|
+
}
|
|
1287
|
+
if (!/^[a-zA-Z0-9-_.]+$/.test(owner)) {
|
|
1288
|
+
throw new Error("Owner contains invalid characters");
|
|
1289
|
+
}
|
|
1290
|
+
if (!/^[a-zA-Z0-9-_.]+$/.test(repo)) {
|
|
1291
|
+
throw new Error("Repo contains invalid characters");
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// Usage
|
|
1296
|
+
validateRepoParams(userInput.owner, userInput.repo);
|
|
1297
|
+
const { data } = await octokit.rest.repos.get({
|
|
1298
|
+
owner: userInput.owner,
|
|
1299
|
+
repo: userInput.repo
|
|
1300
|
+
});
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
**Sanitize Output:**
|
|
1304
|
+
```javascript
|
|
1305
|
+
function sanitizeIssue(issue) {
|
|
1306
|
+
return {
|
|
1307
|
+
number: issue.number,
|
|
1308
|
+
title: issue.title.trim(),
|
|
1309
|
+
body: issue.body?.trim() || "",
|
|
1310
|
+
state: issue.state,
|
|
1311
|
+
createdAt: new Date(issue.created_at),
|
|
1312
|
+
author: issue.user?.login || "unknown"
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
```
|
|
1316
|
+
|
|
1317
|
+
## 8. Production Checklist
|
|
1318
|
+
|
|
1319
|
+
### Version Pinning
|
|
1320
|
+
```json
|
|
1321
|
+
{
|
|
1322
|
+
"dependencies": {
|
|
1323
|
+
"octokit": "3.1.2"
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
Pin exact versions (no `^` or `~`) to prevent unexpected breaking changes.
|
|
1329
|
+
|
|
1330
|
+
### Robust Error Handling
|
|
1331
|
+
```javascript
|
|
1332
|
+
// Production-ready request wrapper
|
|
1333
|
+
async function safeRequest(requestFn) {
|
|
1334
|
+
try {
|
|
1335
|
+
const { data } = await requestFn();
|
|
1336
|
+
return { success: true, data, error: null };
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
console.error("GitHub API Error:", {
|
|
1339
|
+
status: error.status,
|
|
1340
|
+
message: error.message,
|
|
1341
|
+
requestId: error.response?.headers?.["x-github-request-id"],
|
|
1342
|
+
timestamp: new Date().toISOString()
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
return {
|
|
1346
|
+
success: false,
|
|
1347
|
+
data: null,
|
|
1348
|
+
error: {
|
|
1349
|
+
code: error.status,
|
|
1350
|
+
message: error.message,
|
|
1351
|
+
retryable: error.status >= 500 || error.status === 429
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// Usage
|
|
1358
|
+
const result = await safeRequest(
|
|
1359
|
+
() => octokit.rest.repos.get({ owner: "owner", repo: "repo" })
|
|
1360
|
+
);
|
|
1361
|
+
|
|
1362
|
+
if (result.success) {
|
|
1363
|
+
console.log(result.data);
|
|
1364
|
+
} else {
|
|
1365
|
+
console.error("Request failed:", result.error);
|
|
1366
|
+
if (result.error.retryable) {
|
|
1367
|
+
// Implement retry logic
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
### Environment Configuration
|
|
1373
|
+
```javascript
|
|
1374
|
+
// config.js
|
|
1375
|
+
export const config = {
|
|
1376
|
+
github: {
|
|
1377
|
+
token: process.env.GITHUB_TOKEN,
|
|
1378
|
+
baseUrl: process.env.GITHUB_API_URL || "https://api.github.com",
|
|
1379
|
+
timeout: parseInt(process.env.GITHUB_TIMEOUT || "30000"),
|
|
1380
|
+
userAgent: `${process.env.APP_NAME}/${process.env.APP_VERSION}`
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
|
|
1384
|
+
// Validate configuration on startup
|
|
1385
|
+
function validateConfig() {
|
|
1386
|
+
if (!config.github.token) {
|
|
1387
|
+
throw new Error("GITHUB_TOKEN environment variable is required");
|
|
1388
|
+
}
|
|
1389
|
+
if (config.github.timeout < 1000 || config.github.timeout > 60000) {
|
|
1390
|
+
throw new Error("GITHUB_TIMEOUT must be between 1000 and 60000");
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
validateConfig();
|
|
1395
|
+
|
|
1396
|
+
export const octokit = new Octokit({
|
|
1397
|
+
auth: config.github.token,
|
|
1398
|
+
baseUrl: config.github.baseUrl,
|
|
1399
|
+
userAgent: config.github.userAgent,
|
|
1400
|
+
request: {
|
|
1401
|
+
timeout: config.github.timeout
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
```
|
|
1405
|
+
|
|
1406
|
+
### Logging and Monitoring
|
|
1407
|
+
```javascript
|
|
1408
|
+
import { Octokit } from "@octokit/core";
|
|
1409
|
+
|
|
1410
|
+
class LoggingOctokit extends Octokit {
|
|
1411
|
+
constructor(options) {
|
|
1412
|
+
super(options);
|
|
1413
|
+
|
|
1414
|
+
this.hook.before("request", async (options) => {
|
|
1415
|
+
console.log(`[GitHub API] ${options.method} ${options.url}`, {
|
|
1416
|
+
timestamp: new Date().toISOString()
|
|
1417
|
+
});
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
this.hook.after("request", async (response, options) => {
|
|
1421
|
+
console.log(`[GitHub API] ${options.method} ${options.url} - ${response.status}`, {
|
|
1422
|
+
rateLimit: response.headers["x-ratelimit-remaining"],
|
|
1423
|
+
timestamp: new Date().toISOString()
|
|
1424
|
+
});
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
this.hook.error("request", async (error, options) => {
|
|
1428
|
+
console.error(`[GitHub API] ${options.method} ${options.url} - ERROR`, {
|
|
1429
|
+
status: error.status,
|
|
1430
|
+
message: error.message,
|
|
1431
|
+
requestId: error.response?.headers?.["x-github-request-id"],
|
|
1432
|
+
timestamp: new Date().toISOString()
|
|
1433
|
+
});
|
|
1434
|
+
throw error;
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
const octokit = new LoggingOctokit({ auth: process.env.GITHUB_TOKEN });
|
|
1440
|
+
```
|
|
1441
|
+
|
|
1442
|
+
### Validate Structured Output
|
|
1443
|
+
```javascript
|
|
1444
|
+
function validateRepository(data) {
|
|
1445
|
+
const required = ["id", "name", "full_name", "owner", "html_url"];
|
|
1446
|
+
for (const field of required) {
|
|
1447
|
+
if (!(field in data)) {
|
|
1448
|
+
throw new Error(`Missing required field: ${field}`);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
return data;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
const { data } = await octokit.rest.repos.get({ owner, repo });
|
|
1455
|
+
const validatedRepo = validateRepository(data);
|
|
1456
|
+
```
|
|
1457
|
+
|
|
1458
|
+
### Avoid Preview/Unstable APIs
|
|
1459
|
+
```javascript
|
|
1460
|
+
// BAD - Using preview API
|
|
1461
|
+
const { data } = await octokit.rest.repos.get({
|
|
1462
|
+
owner: "owner",
|
|
1463
|
+
repo: "repo",
|
|
1464
|
+
mediaType: {
|
|
1465
|
+
previews: ["mercy"] // Preview API
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
// GOOD - Use stable APIs only
|
|
1470
|
+
const { data } = await octokit.rest.repos.get({
|
|
1471
|
+
owner: "owner",
|
|
1472
|
+
repo: "repo"
|
|
1473
|
+
});
|
|
1474
|
+
```
|
|
1475
|
+
|
|
1476
|
+
### Health Checks
|
|
1477
|
+
```javascript
|
|
1478
|
+
async function healthCheck() {
|
|
1479
|
+
try {
|
|
1480
|
+
const { data: rateLimit } = await octokit.rest.rateLimit.get();
|
|
1481
|
+
const remaining = rateLimit.rate.remaining;
|
|
1482
|
+
|
|
1483
|
+
return {
|
|
1484
|
+
healthy: remaining > 100,
|
|
1485
|
+
rateLimit: {
|
|
1486
|
+
remaining,
|
|
1487
|
+
limit: rateLimit.rate.limit,
|
|
1488
|
+
reset: new Date(rateLimit.rate.reset * 1000)
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
return {
|
|
1493
|
+
healthy: false,
|
|
1494
|
+
error: error.message
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// Run periodically
|
|
1500
|
+
setInterval(async () => {
|
|
1501
|
+
const health = await healthCheck();
|
|
1502
|
+
if (!health.healthy) {
|
|
1503
|
+
console.warn("GitHub API health check failed", health);
|
|
1504
|
+
}
|
|
1505
|
+
}, 60000); // Every minute
|
|
1506
|
+
```
|
|
1507
|
+
|
|
1508
|
+
### Testing Strategy
|
|
1509
|
+
```javascript
|
|
1510
|
+
// Mock Octokit for tests
|
|
1511
|
+
import { jest } from "@jest/globals";
|
|
1512
|
+
|
|
1513
|
+
const mockOctokit = {
|
|
1514
|
+
rest: {
|
|
1515
|
+
repos: {
|
|
1516
|
+
get: jest.fn().mockResolvedValue({
|
|
1517
|
+
data: {
|
|
1518
|
+
id: 1,
|
|
1519
|
+
name: "test-repo",
|
|
1520
|
+
full_name: "owner/test-repo",
|
|
1521
|
+
owner: { login: "owner" },
|
|
1522
|
+
html_url: "https://github.com/owner/test-repo"
|
|
1523
|
+
}
|
|
1524
|
+
})
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
|
|
1529
|
+
// Test
|
|
1530
|
+
test("getRepository returns formatted data", async () => {
|
|
1531
|
+
const client = new GitHubClient(mockOctokit);
|
|
1532
|
+
const repo = await client.getRepository("owner", "test-repo");
|
|
1533
|
+
|
|
1534
|
+
expect(repo.name).toBe("test-repo");
|
|
1535
|
+
expect(mockOctokit.rest.repos.get).toHaveBeenCalledWith({
|
|
1536
|
+
owner: "owner",
|
|
1537
|
+
repo: "test-repo"
|
|
1538
|
+
});
|
|
1539
|
+
});
|
|
1540
|
+
```
|
|
1541
|
+
|
|
1542
|
+
### Graceful Degradation
|
|
1543
|
+
```javascript
|
|
1544
|
+
async function getRepoWithFallback(owner, repo) {
|
|
1545
|
+
try {
|
|
1546
|
+
const { data } = await octokit.rest.repos.get({ owner, repo });
|
|
1547
|
+
return data;
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
if (error.status === 404) {
|
|
1550
|
+
console.warn(`Repository ${owner}/${repo} not found`);
|
|
1551
|
+
return null;
|
|
1552
|
+
}
|
|
1553
|
+
if (error.status === 403) {
|
|
1554
|
+
console.warn("Rate limited - using cached data");
|
|
1555
|
+
return getCachedRepo(owner, repo);
|
|
1556
|
+
}
|
|
1557
|
+
throw error; // Re-throw unexpected errors
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
```
|