Skip to main content

REST API と JavaScript を使用したスクリプト

JavaScript を使用して の REST API と対話するスクリプトを記述する場合、 では、Octokit.js SDK を使用することをお勧めします。 Octokit.js は によって管理されます。 SDK によってベスト プラクティスが実装されており、JavaScript を使用して REST API を簡単に操作できます。 Octokit.js は、最新のあらゆるブラウザー、Node.js、Deno で動作します。 Octokit.js について詳しくは、Octokit.js の README を参照してください。

このガイドでは、JavaScript と REST API について理解していることを前提としています。 REST API の詳細については、「REST API を使用した作業の開始」を参照してください。

Octokit.js ライブラリを使うには、octokit をインストールしてインポートする必要があります。 このガイドでは、ES6 に従って import ステートメントを使用します。 さまざまなインストールとインポートの方法について詳しくは、Octokit.js の README の「使用法」セクションを参照してください

警告

認証の資格情報はパスワードと同じように扱ってください。

資格情報を安全な状態に保つには、ご利用の資格情報をシークレットとして格納し、 Actions を介してスクリプトを実行します。 詳しくは、「 Actions でのシークレットの使用」をご覧ください。

これを使用できない場合、別の CLI サービスを使用して資格情報を安全に格納することを検討してください。

個人用に REST API を使用する場合は、personal access tokenを作成できます。 personal access tokenの作成の詳細については、「個人用アクセス トークンを管理する」を参照してください。

まず、Octokitoctokit からインポートします。 次に、Octokit のインスタンスを作成するときに、personal access tokenを渡します。 次の例では、YOUR-TOKEN をpersonal access tokenへの参照に置き換えます。 HOSTNAME を お使いの Enterprise Server インスタンスの名前に置き換えます。

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
  auth: 'YOUR-TOKEN',
});

Organization または他のユーザーの代わりに API を使用する場合、 では、 App の使用が推奨されます。 エンドポイントが Apps で使用できる場合、そのエンドポイントの REST リファレンス ドキュメントには、どの種類の App トークンが必要かがと示されます。 詳細については、「 App の登録」および「 アプリでの認証について」を参照してください。

Octokitoctokit からインポートする代わりに、App をインポートします。 次の例では、APP_ID をアプリの ID への参照に置き換えます。 PRIVATE_KEY をアプリの秘密キーへの参照に置き換えます。 INSTALLATION_ID を、代わりに認証するアプリのインストールの ID に置き換えます。 アプリの ID を見つけて、アプリの設定ページで秘密キーを生成できます。 詳しくは、「 Apps の秘密キーの管理」をご覧ください。 インストール ID は GET /users/{username}/installationGET /repos/{owner}/{repo}/installation、または GET /orgs/{org}/installation のエンドポイントで取得できます。 詳細については、「 Apps用 REST API エンドポイント」を参照してください。HOSTNAME は お使いの Enterprise Server インスタンス の名前に置き換えてください。

JavaScript
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);

Actions ワークフローで API を使用する場合、 では、トークンを作成するのではなく、組み込み _TOKEN で認証することが推奨されます。 permissions キーを使用して、_TOKEN へのアクセス許可を付与できます。 _TOKEN の詳細については、「自動トークン認証」を参照してください。

ワークフローがワークフローのリポジトリの外部にあるリソースにアクセスする必要がある場合は、_TOKEN を使用できません。 その場合は、資格情報をシークレットとして格納し、次の例で _TOKEN を実際のシークレットの名前に置き換えます。 シークレットの詳細については、「 Actions でのシークレットの使用」を参照してください。

run キーワードを使用して Actions ワークフローで JavaScript スクリプトを実行する場合は、_TOKEN の値を環境変数として格納できます。 スクリプトは、process.env.VARIABLE_NAME として環境変数にアクセスできます。

たとえば、このワークフロー ステップでは、TOKEN という環境変数に _TOKEN が格納されます。

- name: Run script
  env:
    TOKEN: ${{ secrets._TOKEN }}
  run: |
    node ./actions-scripts/use-the-api.mjs

ワークフローが実行するスクリプトは、認証に process.env.TOKEN を使用します。

JavaScript
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] を お使いの Enterprise Server インスタンスの名前に置き換えます。

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
});

Octokit では、要求を行う複数の方法がサポートされています。 エンドポイントの HTTP 動詞とパスがわかっている場合は、request メソッドを使用して要求を行うことができます。 rest メソッドを使用すると、IDE と入力でのオートコンプリートを利用できます。 ページ分割されたエンドポイントの場合は、paginate メソッドを使用して複数のページのデータを要求できます。

request メソッドを使用して要求を行うには、HTTP メソッドとパスを最初の引数として渡します。 オブジェクト内の本文、クエリ、またはパスのパラメーターを 2 番目の引数として渡します。 たとえば、GET 要求を /repos/{owner}/{repo}/issues に行い、ownerrepoper_page パラメーターを渡すには、次のようにします。

JavaScript
await octokit.request("GET /repos/{owner}/{repo}/issues", {
  owner: "",
  repo: "docs",
  per_page: 2
});

request メソッドでは Accept: application/vnd.+json ヘッダーが自動的に渡されます。 追加のヘッダーまたは別の Accept ヘッダーを渡すには、2 番目の引数として渡されるオブジェクトに headers プロパティを追加します。 headers プロパティの値は、キーがヘッダー名で値がヘッダー値のオブジェクトです。 たとえば、text/plain の値を持つ content-type ヘッダーと、2022-11-28 の値を持つ x--api-version ヘッダーを送信するには、次のようにします。

JavaScript
await octokit.request("POST /markdown/raw", {
  text: "Hello **world**",
  headers: {
    "content-type": "text/plain",
    "x--api-version": "2022-11-28",
  },
});

すべての REST API エンドポイントには、Octokit に関連付けられた rest エンドポイント メソッドがあります。 これらのメソッドは、通常、便宜上 IDE でオートコンプリートされます。 任意のパラメーターをオブジェクトとしてメソッドに渡すことができます。

JavaScript
await octokit.rest.issues.listForRepo({
  owner: "",
  repo: "docs",
  per_page: 2
});

さらに、TypeScript などの型指定された言語を使用している場合は、これらのメソッドで使用する型をインポートできます。 詳しくは、plugin-rest-endpoint-methods.js の README の「TypeScript」セクションを参照してください。

エンドポイントがページ分割されていて、複数のページの結果をフェッチする場合は、paginate メソッドを使用できます。 paginate は、最後のページに達するまで結果の次のページをフェッチし、すべての結果を 1 つの配列として返します。 いくつかのエンドポイントは、ページ分割された結果を配列として返すのではなく、ページ分割された結果をオブジェクト内の配列として返します。 生の結果がオブジェクトであっても、paginate は常にアイテムの配列を返します。

たとえば、上記の例では /docs リポジトリからすべての issue が取得されます。 一度に 100 の issue が要求されますが、データの最後のページに達するまで関数は返されません。

JavaScript
const issueData = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "",
  repo: "docs",
  per_page: 100,
  headers: {
    "x--api-version": "2022-11-28",
  },
});

paginate メソッドは省略可能な map 関数を受け入れます。これを使用して、応答から必要なデータのみを収集できます。 これにより、スクリプトによるメモリ使用量が削減されます。 map 関数は、最後のページに到達する前に改ページ位置の自動修正の終了を呼び出すことができる 2 番目の引数 done を受け取ることができます。 これにより、ページのサブセットをフェッチできます。 たとえば、次の例では、タイトルに "test" を含む issue が返されるまで、結果がフェッチされます。 返されたデータのページには、issue のタイトルと作成者のみが格納されます。

JavaScript
const issueData = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "",
  repo: "docs",
  per_page: 100,
  headers: {
    "x--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 作成者を返します。 反復子は、ページ分割されたデータをフェッチするための最もメモリ効率の高いメソッドです。

JavaScript
const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/issues", {
  owner: "",
  repo: "docs",
  per_page: 100,
  headers: {
    "x--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 番目の引数として渡します。

JavaScript
const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
  owner: "",
  repo: "docs",
  per_page: 100,
  headers: {
    "x--api-version": "2022-11-28",
  },
});

改ページ位置の自動修正の詳細については、「REST API 内での改ページ位置の自動修正の使用」を参照してください。

場合によって、 REST API からエラーが返されることがあります。 たとえば、アクセス トークンの有効期限が切れている場合や、必要なパラメーターを省略した場合にエラーが発生します。 Octokit.js は 400 Bad Request401 Unauthorized403 Forbidden404 Not Found422 Unprocessable Entity 以外のエラーが発生すると、要求を自動的に再試行します。 再試行後も API エラーが発生した場合、Octokit.js は、応答の HTTP 状態コード (response.status) と応答ヘッダー (response.headers) を含むエラーをスローします。 これらのエラーはコードで対処する必要があります。 たとえば、try/catch ブロックを使用してエラーをキャッチできます。

JavaScript
let filesChanged = []

try {
  const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", {
    owner: "",
    repo: "docs",
    pull_number: 22809,
    per_page: 100,
    headers: {
      "x--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)
}

場合によっては、 は 4xx 状態コードを使用してエラー以外の応答を示します。 使用しているエンドポイントでこれを行う場合は、特定のエラーに対する処理を追加できます。 たとえば、リポジトリに星が付いていない場合、GET /user/starred/{owner}/{repo} エンドポイントは 404 を返します。 次の例では、404 応答を使用して、リポジトリに星が付いていないことを示しています。その他のすべてのエラー コードはエラーとして扱われます。

JavaScript
try {
  await octokit.request("GET /user/starred/{owner}/{repo}", {
    owner: "",
    repo: "docs",
    headers: {
      "x--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}`);
  }
}

レート制限エラーが発生した場合は、待機後に要求を再試行できます。 レート制限がある場合、 は 403 Forbidden エラーで応答し、x-ratelimit-remaining 応答ヘッダーの値は "0" になります。 応答ヘッダーには、現在のレート制限ウィンドウがリセットされる時刻を UTC エポック秒数で示す x-ratelimit-reset ヘッダーが含まれます。 要求は、x-ratelimit-reset で指定された時刻より後に再試行できます。

JavaScript
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: "",
    repo: "docs",
    per_page: 2
  })

request メソッドは、要求が成功した場合にオブジェクトに解決される Promise を返します。 オブジェクトのプロパティは data、(エンドポイントによって返される応答本文)、status (HTTP 応答コード)、url (要求の URL)、および headers (応答ヘッダーを含むオブジェクト) です。 特に指定しない限り、応答本文は JSON 形式となります。 一部のエンドポイントは応答本文を返しません。このような場合、data プロパティは省略されます。

JavaScript
const response = await octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}", {
  owner: "",
  repo: "docs",
  issue_number: 11901,
  headers: {
    "x--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、またはヘッダーを返しません。

JavaScript
const data = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "",
  repo: "docs",
  per_page: 100,
  headers: {
    "x--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の代わりに、 App で認証する場合は、Octokit の代わりに App をインポートしてインスタンス化します。 詳細については、「 App による認証」を参照してください。

getChangedFiles 関数は、pull request で変更されたすべてのファイルを取得します。 commentIfDataFilesChanged 関数は getChangedFiles 関数を呼び出します。 pull request で変更されたいずれかのファイルのファイル パスに /data/ が含まれている場合、この関数が pull request にコメントを付けます。

JavaScript
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--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--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: "", repo: "docs", pullNumber: 191});