Octokit.js について
JavaScript を使用して GitHub の REST API と対話するスクリプトを記述する場合、GitHub では、Octokit.js SDK を使用することをお勧めします。 Octokit.js は GitHub によって管理されます。 SDK によってベスト プラクティスが実装されており、JavaScript を使用して REST API を簡単に操作できます。 Octokit.js は、最新のあらゆるブラウザー、Node.js、Deno で動作します。 Octokit.js について詳しくは、Octokit.js の README を参照してください。
前提条件
このガイドでは、JavaScript と GitHub REST API について理解していることを前提としています。 REST API について詳しくは、「REST API を使用した作業の開始」をご覧ください。
Octokit.js ライブラリを使うには、octokit
をインストールしてインポートする必要があります。 このガイドでは、ES6 に従って import ステートメントを使用します。 さまざまなインストールとインポートの方法について詳しくは、Octokit.js の README の「使用法」セクションを参照してください。
インスタンス化と認証
Warning
認証の資格情報はパスワードと同じように扱ってください。
資格情報を安全な状態に保つには、ご利用の資格情報をシークレットとして格納し、GitHub Actions を介してスクリプトを実行します。 詳しくは、「GitHub Actions でのシークレットの使用」を参照してください。
これを使用できない場合、別の CLI サービスを使用して資格情報を安全に格納することを検討してください。
personal access token で認証を行う
個人用に GitHub REST API を使用する場合は、personal access tokenを作成できます。 personal access tokenの作成について詳しくは、「個人用アクセス トークンを管理する」をご覧ください。
まず、Octokit
を octokit
からインポートします。 次に、Octokit
のインスタンスを作成するときに、personal access tokenを渡します。 次の例では、YOUR-TOKEN
をpersonal access tokenへの参照に置き換えます。 HOSTNAME
を お使いの GitHub Enterprise Server インスタンスの名前に置き換えます。
import { Octokit } from "octokit"; const octokit = new Octokit({ baseUrl: "http(s)://HOSTNAME/api/v3", auth: 'YOUR-TOKEN', });
import { Octokit } from "octokit";
const octokit = new Octokit({
baseUrl: "http(s)://HOSTNAME/api/v3",
auth: 'YOUR-TOKEN',
});
GitHub App による認証
Organization または他のユーザーの代わりに API を使用する場合、GitHub では、GitHub App の使用が推奨されます。 エンドポイントが GitHub Apps で使用できる場合、そのエンドポイントの REST リファレンス ドキュメントには、どの種類の GitHub App トークンが必要かがと示されます。 詳細については、「GitHub App の登録」および「GitHub アプリでの認証について」を参照してください。
Octokit
を octokit
からインポートする代わりに、App
をインポートします。 次の例では、APP_ID
をアプリの ID への参照に置き換えます。 PRIVATE_KEY
をアプリの秘密キーへの参照に置き換えます。 INSTALLATION_ID
を、代わりに認証するアプリのインストールの ID に置き換えます。 アプリの ID を見つけて、アプリの設定ページで秘密キーを生成できます。 詳しくは、「GitHub Apps の秘密キーの管理」を参照してください。 インストール ID は GET /users/{username}/installation
、GET /repos/{owner}/{repo}/installation
、または GET /orgs/{org}/installation
のエンドポイントで取得できます。 詳細については、「GitHub Apps用 REST API エンドポイント」を参照してください。HOSTNAME
をお使いの GitHub Enterprise Server インスタンスの名前に置き換えます。
import { App } from "octokit"; const app = new App({ appId: APP_ID, privateKey: PRIVATE_KEY, Octokit: Octokit.defaults({ baseUrl: "http(s)://HOSTNAME/api/v3", }), }); const octokit = await app.getInstallationOctokit(INSTALLATION_ID);
import { App } from "octokit";
const app = new App({
appId: APP_ID,
privateKey: PRIVATE_KEY,
Octokit: Octokit.defaults({
baseUrl: "http(s)://HOSTNAME/api/v3",
}),
});
const octokit = await app.getInstallationOctokit(INSTALLATION_ID);
GitHub Actions における認証
GitHub Actions ワークフローで API を使用する場合、GitHub では、トークンを作成するのではなく、組み込み GITHUB_TOKEN
で認証することが推奨されます。 permissions
キーを使用して、GITHUB_TOKEN
へのアクセス許可を付与できます。 GITHUB_TOKEN
について詳しくは、「自動トークン認証」をご覧ください。
ワークフローがワークフローのリポジトリの外部にあるリソースにアクセスする必要がある場合は、GITHUB_TOKEN
を使用できません。 その場合は、資格情報をシークレットとして格納し、次の例で GITHUB_TOKEN
を実際のシークレットの名前に置き換えます。 シークレットについて詳しくは、「GitHub Actions でのシークレットの使用」をご覧ください。
run
キーワードを使用してGitHub Actions ワークフローで JavaScript スクリプトを実行する場合は、GITHUB_TOKEN
の値を環境変数として格納できます。 スクリプトは、process.env.VARIABLE_NAME
として環境変数にアクセスできます。
たとえば、このワークフロー ステップでは、TOKEN
という環境変数に GITHUB_TOKEN
が格納されます。
- name: Run script
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node .github/actions-scripts/use-the-api.mjs
ワークフローが実行するスクリプトは、認証に process.env.TOKEN
を使用します。
import { Octokit } from "octokit"; const octokit = new Octokit({ baseUrl: "http(s)://HOSTNAME/api/v3", auth: process.env.TOKEN, });
import { Octokit } from "octokit";
const octokit = new Octokit({
baseUrl: "http(s)://HOSTNAME/api/v3",
auth: process.env.TOKEN,
});
認証なしでのインスタンス化
REST API は認証なしで使用できますが、レート制限が低く、一部のエンドポイントを使用することができません。 認証を行わずに Octokit
のインスタンスを作成する場合、auth
引数を渡さないでください。 ベース URL は http(s)://HOSTNAME/api/v3
に設定します。 [hostname]
を お使いの GitHub Enterprise Server インスタンスの名前に置き換えます。
import { Octokit } from "octokit"; const octokit = new Octokit({ baseUrl: "http(s)://HOSTNAME/api/v3", });
import { Octokit } from "octokit";
const octokit = new Octokit({
baseUrl: "http(s)://HOSTNAME/api/v3",
});
要求の作成
Octokit では、要求を行う複数の方法がサポートされています。 エンドポイントの HTTP 動詞とパスがわかっている場合は、request
メソッドを使用して要求を行うことができます。 rest
メソッドを使用すると、IDE と入力でのオートコンプリートを利用できます。 ページ分割されたエンドポイントの場合は、paginate
メソッドを使用して複数のページのデータを要求できます。
request
メソッドを使用して要求を行う
request
メソッドを使用して要求を行うには、HTTP メソッドとパスを最初の引数として渡します。 オブジェクト内の本文、クエリ、またはパスのパラメーターを 2 番目の引数として渡します。 たとえば、GET
要求を /repos/{owner}/{repo}/issues
に行い、owner
、repo
、per_page
パラメーターを渡すには、次のようにします。
await octokit.request("GET /repos/{owner}/{repo}/issues", { owner: "github", repo: "docs", per_page: 2 });
await octokit.request("GET /repos/{owner}/{repo}/issues", {
owner: "github",
repo: "docs",
per_page: 2
});
request
メソッドでは Accept: application/vnd.github+json
ヘッダーが自動的に渡されます。 追加のヘッダーまたは別の Accept
ヘッダーを渡すには、2 番目の引数として渡されるオブジェクトに headers
プロパティを追加します。 headers
プロパティの値は、キーがヘッダー名で値がヘッダー値のオブジェクトです。 たとえば、text/plain
の値を持つ content-type
ヘッダーと、2022-11-28
の値を持つ x-github-api-version
ヘッダーを送信するには、次のようにします。
await octokit.request("POST /markdown/raw", { text: "Hello **world**", headers: { "content-type": "text/plain", "x-github-api-version": "2022-11-28", }, });
await octokit.request("POST /markdown/raw", {
text: "Hello **world**",
headers: {
"content-type": "text/plain",
"x-github-api-version": "2022-11-28",
},
});
rest
エンドポイント メソッドを使用して要求を行う
すべての REST API エンドポイントには、Octokit に関連付けられた rest
エンドポイント メソッドがあります。 これらのメソッドは、通常、便宜上 IDE でオートコンプリートされます。 任意のパラメーターをオブジェクトとしてメソッドに渡すことができます。
await octokit.rest.issues.listForRepo({ owner: "github", repo: "docs", per_page: 2 });
await octokit.rest.issues.listForRepo({
owner: "github",
repo: "docs",
per_page: 2
});
さらに、TypeScript などの型指定された言語を使用している場合は、これらのメソッドで使用する型をインポートできます。 詳しくは、plugin-rest-endpoint-methods.js の README の「TypeScript」セクションを参照してください。
ページ分割された要求の作成
エンドポイントがページ分割されていて、複数のページの結果をフェッチする場合は、paginate
メソッドを使用できます。 paginate
は、最後のページに達するまで結果の次のページをフェッチし、すべての結果を 1 つの配列として返します。 いくつかのエンドポイントは、ページ分割された結果を配列として返すのではなく、ページ分割された結果をオブジェクト内の配列として返します。 生の結果がオブジェクトであっても、paginate
は常にアイテムの配列を返します。
たとえば、上記の例では github/docs
リポジトリからすべての issue が取得されます。 一度に 100 の issue が要求されますが、データの最後のページに達するまで関数は返されません。
const issueData = await octokit.paginate("GET /repos/{owner}/{repo}/issues", { owner: "github", repo: "docs", per_page: 100, headers: { "x-github-api-version": "2022-11-28", }, });
const issueData = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
owner: "github",
repo: "docs",
per_page: 100,
headers: {
"x-github-api-version": "2022-11-28",
},
});
paginate
メソッドは省略可能な map 関数を受け入れます。これを使用して、応答から必要なデータのみを収集できます。 これにより、スクリプトによるメモリ使用量が削減されます。 map 関数は、最後のページに到達する前に改ページ位置の自動修正の終了を呼び出すことができる 2 番目の引数 done
を受け取ることができます。 これにより、ページのサブセットをフェッチできます。 たとえば、次の例では、タイトルに "test" を含む issue が返されるまで、結果がフェッチされます。 返されたデータのページには、issue のタイトルと作成者のみが格納されます。
const issueData = await octokit.paginate("GET /repos/{owner}/{repo}/issues", { owner: "github", repo: "docs", per_page: 100, headers: { "x-github-api-version": "2022-11-28", }, }, (response, done) => response.data.map((issue) => { if (issue.title.includes("test")) { done() } return ({title: issue.title, author: issue.user.login}) }) );
const issueData = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
owner: "github",
repo: "docs",
per_page: 100,
headers: {
"x-github-api-version": "2022-11-28",
},
},
(response, done) => response.data.map((issue) => {
if (issue.title.includes("test")) {
done()
}
return ({title: issue.title, author: issue.user.login})
})
);
すべての結果を一度にフェッチする代わりに、octokit.paginate.iterator()
を使用して一度に 1 つのページを反復処理できます。 たとえば、次の例では、一度に 1 ページの結果をフェッチし、次のページをフェッチする前に、ページの各オブジェクトを処理します。 タイトルに "test" を含む issue に達すると、スクリプトは反復を停止し、処理された各オブジェクトの issue タイトルと issue 作成者を返します。 反復子は、ページ分割されたデータをフェッチするための最もメモリ効率の高いメソッドです。
const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/issues", { owner: "github", repo: "docs", per_page: 100, headers: { "x-github-api-version": "2022-11-28", }, }); let issueData = [] let breakLoop = false for await (const {data} of iterator) { if (breakLoop) break for (const issue of data) { if (issue.title.includes("test")) { breakLoop = true break } else { issueData = [...issueData, {title: issue.title, author: issue.user.login}]; } } }
const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/issues", {
owner: "github",
repo: "docs",
per_page: 100,
headers: {
"x-github-api-version": "2022-11-28",
},
});
let issueData = []
let breakLoop = false
for await (const {data} of iterator) {
if (breakLoop) break
for (const issue of data) {
if (issue.title.includes("test")) {
breakLoop = true
break
} else {
issueData = [...issueData, {title: issue.title, author: issue.user.login}];
}
}
}
rest
エンドポイント メソッドでも、paginate
メソッドを使用できます。 rest
エンドポイント メソッドを最初の引数として渡します。 任意のパラメーターを 2 番目の引数として渡します。
const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, { owner: "github", repo: "docs", per_page: 100, headers: { "x-github-api-version": "2022-11-28", }, });
const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
owner: "github",
repo: "docs",
per_page: 100,
headers: {
"x-github-api-version": "2022-11-28",
},
});
改ページ位置の自動修正について詳しくは、「REST API 内での改ページ位置の自動修正の使用」をご覧ください。
エラーのキャッチ
すべてのエラーのキャッチ
場合によって、GitHub REST API からエラーが返されることがあります。 たとえば、アクセス トークンの有効期限が切れている場合や、必要なパラメーターを省略した場合にエラーが発生します。 Octokit.js は 400 Bad Request
、401 Unauthorized
、403 Forbidden
、404 Not Found
、422 Unprocessable Entity
以外のエラーが発生すると、要求を自動的に再試行します。 再試行後も API エラーが発生した場合、Octokit.js は、応答の HTTP 状態コード (response.status
) と応答ヘッダー (response.headers
) を含むエラーをスローします。 これらのエラーはコードで対処する必要があります。 たとえば、try/catch ブロックを使用してエラーをキャッチできます。
let filesChanged = [] try { const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", { owner: "github", repo: "docs", pull_number: 22809, per_page: 100, headers: { "x-github-api-version": "2022-11-28", }, }); for await (const {data} of iterator) { filesChanged = [...filesChanged, ...data.map(fileData => fileData.filename)]; } } catch (error) { if (error.response) { console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`) } console.error(error) }
let filesChanged = []
try {
const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", {
owner: "github",
repo: "docs",
pull_number: 22809,
per_page: 100,
headers: {
"x-github-api-version": "2022-11-28",
},
});
for await (const {data} of iterator) {
filesChanged = [...filesChanged, ...data.map(fileData => fileData.filename)];
}
} catch (error) {
if (error.response) {
console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
}
console.error(error)
}
目的のエラー コードの処理
場合によっては、GitHub は 4xx 状態コードを使用してエラー以外の応答を示します。 使用しているエンドポイントでこれを行う場合は、特定のエラーに対する処理を追加できます。 たとえば、リポジトリに星が付いていない場合、GET /user/starred/{owner}/{repo}
エンドポイントは 404
を返します。 次の例では、404
応答を使用して、リポジトリに星が付いていないことを示しています。その他のすべてのエラー コードはエラーとして扱われます。
try { await octokit.request("GET /user/starred/{owner}/{repo}", { owner: "github", repo: "docs", headers: { "x-github-api-version": "2022-11-28", }, }); console.log(`The repository is starred by me`); } catch (error) { if (error.status === 404) { console.log(`The repository is not starred by me`); } else { console.error(`An error occurred while checking if the repository is starred: ${error?.response?.data?.message}`); } }
try {
await octokit.request("GET /user/starred/{owner}/{repo}", {
owner: "github",
repo: "docs",
headers: {
"x-github-api-version": "2022-11-28",
},
});
console.log(`The repository is starred by me`);
} catch (error) {
if (error.status === 404) {
console.log(`The repository is not starred by me`);
} else {
console.error(`An error occurred while checking if the repository is starred: ${error?.response?.data?.message}`);
}
}
レート制限エラーの処理
レート制限エラーが発生した場合は、待機後に要求を再試行できます。 レート制限がある場合、GitHub は 403 Forbidden
エラーで応答し、x-ratelimit-remaining
応答ヘッダーの値は "0"
になります。 応答ヘッダーには、現在のレート制限ウィンドウがリセットされる時刻を UTC エポック秒数で示す x-ratelimit-reset
ヘッダーが含まれます。 要求は、x-ratelimit-reset
で指定された時刻より後に再試行できます。
async function requestRetry(route, parameters) { try { const response = await octokit.request(route, parameters); return response } catch (error) { if (error.response && error.status === 403 && error.response.headers['x-ratelimit-remaining'] === '0') { const resetTimeEpochSeconds = error.response.headers['x-ratelimit-reset']; const currentTimeEpochSeconds = Math.floor(Date.now() / 1000); const secondsToWait = resetTimeEpochSeconds - currentTimeEpochSeconds; console.log(`You have exceeded your rate limit. Retrying in ${secondsToWait} seconds.`); setTimeout(requestRetry, secondsToWait * 1000, route, parameters); } else { console.error(error); } } } const response = await requestRetry("GET /repos/{owner}/{repo}/issues", { owner: "github", repo: "docs", per_page: 2 })
async function requestRetry(route, parameters) {
try {
const response = await octokit.request(route, parameters);
return response
} catch (error) {
if (error.response && error.status === 403 && error.response.headers['x-ratelimit-remaining'] === '0') {
const resetTimeEpochSeconds = error.response.headers['x-ratelimit-reset'];
const currentTimeEpochSeconds = Math.floor(Date.now() / 1000);
const secondsToWait = resetTimeEpochSeconds - currentTimeEpochSeconds;
console.log(`You have exceeded your rate limit. Retrying in ${secondsToWait} seconds.`);
setTimeout(requestRetry, secondsToWait * 1000, route, parameters);
} else {
console.error(error);
}
}
}
const response = await requestRetry("GET /repos/{owner}/{repo}/issues", {
owner: "github",
repo: "docs",
per_page: 2
})
応答の使用
request
メソッドは、要求が成功した場合にオブジェクトに解決される Promise を返します。 オブジェクトのプロパティは data
、(エンドポイントによって返される応答本文)、status
(HTTP 応答コード)、url
(要求の URL)、および headers
(応答ヘッダーを含むオブジェクト) です。 特に指定しない限り、応答本文は JSON 形式となります。 一部のエンドポイントは応答本文を返しません。このような場合、data
プロパティは省略されます。
const response = await octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}", { owner: "github", repo: "docs", issue_number: 11901, headers: { "x-github-api-version": "2022-11-28", }, }); console.log(`The status of the response is: ${response.status}`) console.log(`The request URL was: ${response.url}`) console.log(`The x-ratelimit-remaining response header is: ${response.headers["x-ratelimit-remaining"]}`) console.log(`The issue title is: ${response.data.title}`)
const response = await octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}", {
owner: "github",
repo: "docs",
issue_number: 11901,
headers: {
"x-github-api-version": "2022-11-28",
},
});
console.log(`The status of the response is: ${response.status}`)
console.log(`The request URL was: ${response.url}`)
console.log(`The x-ratelimit-remaining response header is: ${response.headers["x-ratelimit-remaining"]}`)
console.log(`The issue title is: ${response.data.title}`)
同様に、paginate
メソッドは Promise を返します。 要求が成功した場合、Promise はエンドポイントによって返されるデータの配列に解決されます。 request
メソッドとは異なり、paginate
メソッドは状態コード、URL、またはヘッダーを返しません。
const data = await octokit.paginate("GET /repos/{owner}/{repo}/issues", { owner: "github", repo: "docs", per_page: 100, headers: { "x-github-api-version": "2022-11-28", }, }); console.log(`${data.length} issues were returned`) console.log(`The title of the first issue is: ${data[0].title}`)
const data = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
owner: "github",
repo: "docs",
per_page: 100,
headers: {
"x-github-api-version": "2022-11-28",
},
});
console.log(`${data.length} issues were returned`)
console.log(`The title of the first issue is: ${data[0].title}`)
サンプル スクリプト
Octokit.js を使用するスクリプトの完全な例を次に示します。 このスクリプトは Octokit
をインポートし、Octokit
の新しいインスタンスを作成します。 personal access tokenの代わりに、GitHub App で認証する場合は、Octokit
の代わりに App
をインポートしてインスタンス化します。 詳しくは、「GitHub App による認証」を参照してください。
getChangedFiles
関数は、pull request で変更されたすべてのファイルを取得します。 commentIfDataFilesChanged
関数は getChangedFiles
関数を呼び出します。 pull request で変更されたいずれかのファイルのファイル パスに /data/
が含まれている場合、この関数が pull request にコメントを付けます。
import { Octokit } from "octokit"; const octokit = new Octokit({ baseUrl: "http(s)://HOSTNAME/api/v3", auth: 'YOUR-TOKEN', }); async function getChangedFiles({owner, repo, pullNumber}) { let filesChanged = [] try { const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", { owner: owner, repo: repo, pull_number: pullNumber, per_page: 100, headers: { "x-github-api-version": "2022-11-28", }, }); for await (const {data} of iterator) { filesChanged = [...filesChanged, ...data.map(fileData => fileData.filename)]; } } catch (error) { if (error.response) { console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`) } console.error(error) } return filesChanged } async function commentIfDataFilesChanged({owner, repo, pullNumber}) { const changedFiles = await getChangedFiles({owner, repo, pullNumber}); const filePathRegex = new RegExp(/\/data\//, "i"); if (!changedFiles.some(fileName => filePathRegex.test(fileName))) { return; } try { const {data: comment} = await octokit.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", { owner: owner, repo: repo, issue_number: pullNumber, body: `It looks like you changed a data file. These files are auto-generated. \n\nYou must revert any changes to data files before your pull request will be reviewed.`, headers: { "x-github-api-version": "2022-11-28", }, }); return comment.html_url; } catch (error) { if (error.response) { console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`) } console.error(error) } } await commentIfDataFilesChanged({owner: "github", repo: "docs", pullNumber: 191});
import { Octokit } from "octokit";
const octokit = new Octokit({
baseUrl: "http(s)://HOSTNAME/api/v3",
auth: 'YOUR-TOKEN',
});
async function getChangedFiles({owner, repo, pullNumber}) {
let filesChanged = []
try {
const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", {
owner: owner,
repo: repo,
pull_number: pullNumber,
per_page: 100,
headers: {
"x-github-api-version": "2022-11-28",
},
});
for await (const {data} of iterator) {
filesChanged = [...filesChanged, ...data.map(fileData => fileData.filename)];
}
} catch (error) {
if (error.response) {
console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
}
console.error(error)
}
return filesChanged
}
async function commentIfDataFilesChanged({owner, repo, pullNumber}) {
const changedFiles = await getChangedFiles({owner, repo, pullNumber});
const filePathRegex = new RegExp(/\/data\//, "i");
if (!changedFiles.some(fileName => filePathRegex.test(fileName))) {
return;
}
try {
const {data: comment} = await octokit.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", {
owner: owner,
repo: repo,
issue_number: pullNumber,
body: `It looks like you changed a data file. These files are auto-generated. \n\nYou must revert any changes to data files before your pull request will be reviewed.`,
headers: {
"x-github-api-version": "2022-11-28",
},
});
return comment.html_url;
} catch (error) {
if (error.response) {
console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
}
console.error(error)
}
}
await commentIfDataFilesChanged({owner: "github", repo: "docs", pullNumber: 191});
次の手順
- Octokit.js について詳しくは、Octokit.js のドキュメントを参照してください。
- 実際の例については、GitHub Docs リポジトリを検索して、GitHub Docs でどのように Octokit.js が使用されているかをご確認ください。