О Octokit.js
Если вы хотите написать скрипт с помощью JavaScript для взаимодействия с REST API GitHub, GitHub рекомендует использовать пакет SDK Octokit.js. Octokit.js поддерживается GitHub. Пакет SDK реализует рекомендации и упрощает взаимодействие с REST API с помощью JavaScript. Octokit.js работает со всеми современными браузерами, Node.js и Deno. Дополнительные сведения о Octokit.js см . в Octokit.js README.
Необходимые компоненты
В этом руководстве предполагается, что вы знакомы с JavaScript и REST API GitHub REST API. Дополнительные сведения о REST API см. в разделе AUTOTITLE.
Чтобы использовать библиотеку Octokit.js, необходимо установить и импортировать ее. В этом руководстве используются инструкции импорта в соответствии с ES6. Дополнительные сведения о различных методах установки и импорта см . в разделе об использовании Octokit.js README.
Создание экземпляров и проверка подлинности
Предупреждение
Обработайте учетные данные проверки подлинности как пароль.
Чтобы обеспечить безопасность учетных данных, вы можете хранить свои учетные данные в виде секрета и запускать скрипт с помощью GitHub Actions. Дополнительные сведения см. в разделе AUTOTITLE.
Вы также можете хранить свои учетные данные в виде секрета Codespaces и запустить скрипт в Codespaces. Дополнительные сведения см. в разделе AUTOTITLE.
Если эти параметры недоступны, попробуйте использовать другую службу CLI для безопасного хранения учетных данных.
Проверка подлинности с помощью personal access token
Если вы хотите использовать REST API GitHub для личного использования, можно создать personal access token. Дополнительные сведения о создании personal access tokenсм. в разделе AUTOTITLE.
Сначала импортируйте из . Затем передайте personal access token при создании экземпляра . В следующем примере замените ссылку на данные personal access token.{ %ifversion ghes %} Замените именем GitHub.com.{ % endif %}
import { Octokit } from "octokit";
const octokit = new Octokit({
auth: 'YOUR-TOKEN',
});
import { Octokit } from "octokit";
const octokit = new Octokit({
auth: 'YOUR-TOKEN',
});
Проверка подлинности с помощью GitHub App
Если вы хотите использовать API от имени организации или другого пользователя, GitHub рекомендует использовать GitHub App. Если конечная точка доступна для GitHub Apps, справочная документация REST для этой конечной точки будет указывать тип маркера GitHub App. Дополнительные сведения см. в разделе AUTOTITLE и AUTOTITLE.
Вместо импорта из , импорта . В следующем примере замените ссылку на идентификатор приложения. Замените ссылкой на закрытый ключ приложения. Замените идентификатором установки приложения, от имени которого требуется выполнить проверку подлинности. Идентификатор приложения можно найти и создать закрытый ключ на странице параметров приложения. Дополнительные сведения см. в разделе AUTOTITLE. Идентификатор установки можно получить с помощью конечных точек или конечных точек. Дополнительные сведения см. в разделе AUTOTITLE.{ %ifversion ghes %} Замените именем GitHub.com.{ % endif %}
import { App } from "octokit";
const app = new App({
appId: APP_ID,
privateKey: PRIVATE_KEY,
});
const octokit = await app.getInstallationOctokit(INSTALLATION_ID);
import { App } from "octokit";
const app = new App({
appId: APP_ID,
privateKey: PRIVATE_KEY,
});
const octokit = await app.getInstallationOctokit(INSTALLATION_ID);
Проверка подлинности в GitHub Actions
Если вы хотите использовать API в рабочем процессе GitHub Actions, GitHub рекомендует выполнять проверку подлинности с помощью встроенного вместо создания маркера. Вы можете предоставить разрешения для с помощью ключа . Дополнительные сведения см. в разделе AUTOTITLE.
Если рабочий процесс должен получить доступ к ресурсам за пределами репозитория рабочего процесса, вы не сможете использовать . В этом случае сохраните свои учетные данные в виде секрета и замените в приведенных ниже примерах именем секрета. Дополнительные сведения о секретах см. в разделе AUTOTITLE.
Если вы используете ключевое слово для выполнения скрипта JavaScript в рабочих процессах GitHub Actions, можно сохранить значение в качестве переменной среды. Скрипт может получить доступ к переменной среды как .
Например, этот шаг рабочего процесса сохраняется в переменной среды:
- name: Run script
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node .github/actions-scripts/use-the-api.mjs
Скрипт, который выполняется рабочим процессом для проверки подлинности:
import { Octokit } from "octokit";
const octokit = new Octokit({
auth: process.env.TOKEN,
});
import { Octokit } from "octokit";
const octokit = new Octokit({
auth: process.env.TOKEN,
});
Создание экземпляров без проверки подлинности
REST API можно использовать без проверки подлинности, хотя у вас будет более низкий предел скорости и не удастся использовать некоторые конечные точки. Чтобы создать экземпляр без проверки подлинности, не передайте аргумент.{ %ifversion ghes %} Задайте базовый URL-адрес . Замените именем GitHub.com.{ % endif %}
import { Octokit } from "octokit";
const octokit = new Octokit({ });
import { Octokit } from "octokit";
const octokit = new Octokit({ });
Выполнение запросов
Octokit поддерживает несколько способов выполнения запросов. Метод можно использовать для выполнения запросов, если вы знаете HTTP-команду и путь к конечной точке. Этот метод можно использовать, если вы хотите воспользоваться преимуществами автозаполнения в интегрированной среде разработки и вводе. Для конечных точек с разбивкой на страницы можно использовать метод для запроса нескольких страниц данных.
Использование метода для выполнения запросов
Чтобы использовать метод для выполнения запросов, передайте метод HTTP и путь в качестве первого аргумента. Передайте все параметры текста, запроса или пути в объект в качестве второго аргумента. Например, чтобы выполнить запрос к и передать параметры, и выполнить следующие параметры:
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
});
Метод автоматически передает заголовок. Чтобы передать дополнительные заголовки или другой заголовок, добавьте свойство в объект, передаваемый в качестве второго аргумента. Значение свойства — это объект с именами заголовков в качестве ключей и значениями заголовков в качестве значений. Например, чтобы отправить заголовок со значением и заголовком со значением :
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 API имеет связанный метод конечной точки в Octokit. Эти методы обычно автоматически заполняются в интегрированной среде разработки для удобства. В метод можно передать любые параметры в качестве объекта.
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, можно импортировать типы для использования с этими методами. Дополнительные сведения см . в разделе TypeScript в plugin-rest-endpoint-methods.js README.
Выполнение запросов с разбивкой на страницы
Если конечная точка разбина на страницы и вы хотите получить несколько страниц результатов, можно использовать этот метод. Возвращает следующую страницу результатов, пока не достигнет последней страницы, а затем возвращает все результаты в виде одного массива. Несколько конечных точек возвращают результаты с разбивкой на страницы в виде массива в объекте, а не возвращать результаты с разбивкой на страницы в виде массива. всегда возвращает массив элементов, даже если необработанный результат был объектом.
Например, следующий пример получает все проблемы из репозитория. Хотя она запрашивает 100 проблем за раз, функция не возвращается до достижения последней страницы данных.
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",
},
});
Метод принимает необязательную функцию карты, которую можно использовать для сбора только нужных данных из ответа. Это сокращает использование памяти скриптом. Функция карты может принимать второй аргумент, который можно вызвать для завершения разбиения на страницы до достижения последней страницы. Это позволяет получить подмножество страниц. Например, следующий пример продолжает получение результатов до тех пор, пока не будет возвращена проблема, содержащая "test" в заголовке. Для страниц возвращаемых данных хранятся только название проблемы и автор.
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})
})
);
Вместо одновременного получения всех результатов можно использовать для итерации по одной странице за раз. Например, следующий пример извлекает одну страницу результатов за раз и обрабатывает каждый объект из страницы перед получением следующей страницы. После достижения проблемы, включающей "test" в заголовок, скрипт останавливает итерацию и возвращает заголовок проблемы и автор проблемы каждого обработанного объекта. Итератор — это наиболее эффективный метод памяти для получения данных с разбивкой на страницы.
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}];
}
}
}
Этот метод также можно использовать с методами конечной точки. Передайте метод конечной точки в качестве первого аргумента. Передайте все параметры в качестве второго аргумента.
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",
},
});
Дополнительные сведения о разбиении на страницы см. в разделе AUTOTITLE.
выявления ошибок;
Перехват всех ошибок
Иногда REST API GitHub возвращает ошибку. Например, вы получите ошибку, если срок действия маркера доступа истек или если не указан обязательный параметр. Octokit.js автоматически повторяет запрос при получении ошибки, отличной от , , и . Если ошибка API возникает даже после повторных попыток, Octokit.js выдает ошибку, содержащую код состояния HTTP ответа () и заголовки ответа (). Эти ошибки следует обрабатывать в коде. Например, для перехвата ошибок можно использовать блок 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 для указания ответа без ошибок. Если используется эта конечная точка, можно добавить дополнительную обработку для определенных ошибок. Например, конечная точка вернет объект, если репозиторий не указан. В следующем примере используется ответ, указывающий, что репозиторий не был в главной роли; все остальные коды ошибок рассматриваются как ошибки.
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 отвечает с ошибкой, а значение заголовка ответа будет. Заголовки ответа будут содержать заголовок, который указывает время сброса текущего ограничения скорости в секундах эпохи UTC. После указанного времени можно повторить запрос.
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
})
Использование ответа
Метод возвращает обещание, разрешающее объекту, если запрос выполнен успешно. Свойства объекта : (текст ответа, возвращаемый конечной точкой), (код ОТВЕТА HTTP), (URL-адрес запроса) и (объект, содержащий заголовки ответа). Если не указано иное, текст ответа имеет формат JSON. Некоторые конечные точки не возвращают текст ответа; В этих случаях свойство опущено.
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}`)
Аналогичным образом метод возвращает обещание. Если запрос выполнен успешно, обещание разрешает массив данных, возвращаемых конечной точкой. В отличие от метода, метод не возвращает код состояния, 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. Скрипт импортирует и создает новый экземпляр . Если вы хотите выполнить проверку подлинности с помощью GitHub App, а не personal access token, вы будете импортировать и создать экземпляр вместо него. Дополнительные сведения см. в статье "Проверка подлинности с помощью GitHub App".
Функция получает все файлы, измененные для запроса на вытягивание. Функция вызывает функцию . Если любой из файлов, измененных запросом на вытягивание, включен в путь к файлу, функция будет комментировать запрос на вытягивание.
import { Octokit } from "octokit";
const octokit = new Octokit({
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({
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 используют Octokit.js путем поиска в репозитории документов GitHub.