Sobre o Octokit.js
Se você quiser escrever um script usando JavaScript para interagir com a API REST da GitHub, a GitHub recomenda que você use o SDK do Octokit.js. O Octokit.js é mantido pela GitHub. O SDK implementa as melhores práticas e facilita a interação com a API REST por meio do JavaScript. O Octokit.js funciona com todos os navegadores modernos, Node.js e Deno. Para obter mais informações sobre o Octokit.js, confira o arquivo LEIAME do Octokit.js.
Pré-requisitos
Este guia pressupõe que você esteja familiarizado com o JavaScript e a API REST da GitHub. Para obter informações sobre a API REST, confira AUTOTITLE.
Você precisa instalar e importar para usar a biblioteca do Octokit.js. Este guia usa instruções de importação de acordo com o ES6. Para obter mais informações sobre diferentes métodos de instalação e importação, confira a seção de uso no README do Octokit.js.
Instanciação e autenticação
Aviso
Trate suas credenciais de autenticação como uma senha.
Para manter suas credenciais seguras, você pode armazená-las como um segredo e executar seu script por meio de GitHub Actions. Para saber mais, confira AUTOTITLE.
Se isso não for possível, considere usar outro serviço CLI para armazenar suas credenciais com segurança.
Autenticar com um personal access token
Se quiser usar a API REST do GitHub para uso pessoal, crie um personal access token. Para obter mais informações sobre como criar um personal access token, confira AUTOTITLE.
Primeiro, importe de . Em seguida, passe seu personal access token ao criar uma instância de . No exemplo a seguir, substitua por uma referência ao seu personal access token. Substitua pelo nome de sua instância do 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',
});
Autenticação com um GitHub App
Se você quiser usar a API em nome de uma organização ou de outro usuário, a GitHub recomenda que você use um GitHub App. Se um ponto de extremidade estiver disponível para GitHub Apps, a documentação de referência da API REST para esse ponto de extremidade indicará que tipo de token do GitHub App é necessário. Para saber mais, confira AUTOTITLE e AUTOTITLE.
Em vez de importar de , importe . No exemplo a seguir, substitua por uma referência à ID do seu aplicativo. Substitua por uma referência à chave privada do seu aplicativo. Substitua pela ID de instalação do seu aplicativo em nome do qual deseja se autenticar. Você pode encontrar a ID do seu aplicativo e gerar uma chave privada na página de configurações do aplicativo. Para saber mais, confira AUTOTITLE. Você pode obter uma ID de instalação com os pontos de extremidade , ou . Para obter mais informações, confira AUTOTITLE. Substitua pelo nome de sua instância do 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);
Autenticação em GitHub Actions
Se você quiser usar a API em um fluxo de trabalho de GitHub Actions, GitHub recomenda que você faça a autenticação com o interno, em vez de criar um token. Você pode conceder permissões à com a chave . Para obter mais informações sobre , confira AUTOTITLE.
Se o fluxo de trabalho precisar acessar recursos fora do repositório dele, então você não poderá usar . Nesse caso, armazene suas credenciais como um segredo e substitua nos exemplos abaixo pelo nome do segredo. Para saber mais sobre segredos, confira AUTOTITLE.
Se usar a palavra-chave para executar o script do JavaScript em seus fluxos de trabalho de GitHub Actions, você poderá armazenar o valor de como uma variável de ambiente. Seu script pode acessar a variável de ambiente como .
Por exemplo, essa etapa do fluxo de trabalho armazena em uma variável de ambiente chamada :
- name: Run script
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node .github/actions-scripts/use-the-api.mjs
O script que o fluxo de trabalho executa usa para se autenticar:
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,
});
Instanciação sem autenticação
Você pode usar a API REST sem autenticação, embora isso resulte em uma limitação de taxa mais baixa e não permita o uso de alguns endpoints. Para criar uma instância de sem autenticação, não passe o argumento .Defina a URL base como . Substitua pelo nome de sua instância do 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",
});
Como fazer solicitações
O Octokit dá suporte a várias maneiras de fazer solicitações. Você pode usar o método para fazer solicitações se souber o verbo HTTP e o caminho para o ponto de extremidade. Você pode usar o método se quiser aproveitar o preenchimento automático em seu IDE e digitar. Para pontos de extremidade paginados, você pode usar o método para solicitar várias páginas de dados.
Como usar o método para fazer solicitações
Para usar o método a fim de fazer solicitações, passe o método HTTP e o caminho como o primeiro argumento. Passe quaisquer parâmetros de corpo, consulta ou caminho em um objeto como o segundo argumento. Por exemplo, para fazer uma solicitação para e passar os parâmetros , e :
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
});
O método passa automaticamente o cabeçalho . Para passar cabeçalhos adicionais ou um cabeçalho diferente, adicione uma propriedade ao objeto que é passado como o segundo argumento. O valor da propriedade é um objeto onde os nomes dos cabeçalhos são as chaves e os valores correspondentes são os valores. Por exemplo, para enviar um cabeçalho com o valor de e um cabeçalho com o valor de :
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",
},
});
Como usar métodos endpoint para fazer solicitações
Cada ponto de extremidade da API REST tem um método de ponto de extremidade associado no Octokit. Esses métodos geralmente são preenchidos automaticamente em seu IDE para conveniência. Você pode passar qualquer parâmetro como um objeto para o método.
await octokit.rest.issues.listForRepo({
owner: "github",
repo: "docs",
per_page: 2
});
await octokit.rest.issues.listForRepo({
owner: "github",
repo: "docs",
per_page: 2
});
Além disso, se estiver usando uma linguagem tipada, como TypeScript, você poderá importar tipos para usar com esses métodos. Para obter mais informações, confira a seção de TypeScript no README do plugin-rest-endpoint-methods.js.
Como fazer solicitações paginadas
Se o endpoint for paginado e você quiser recuperar mais de uma página de resultados, poderá usar o método . buscará a próxima página de resultados até chegar à última página e retornará todos os resultados como uma única matriz. Alguns pontos de extremidade retornam resultados paginados como matriz em um objeto, em vez de retornar os resultados paginados como uma matriz. sempre retorna uma matriz de itens, mesmo que o resultado bruto tenha sido um objeto .
Por exemplo, o exemplo a seguir obtém todos os problemas do repositório . Embora solicite 100 solicitações por vez, a função não retornará até que a última página de dados seja atingida.
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",
},
});
O método aceita uma função de mapa opcional, que pode ser usada para coletar apenas os dados desejados da resposta. Isso reduz o uso de memória pelo seu script. A função de mapa pode usar um segundo argumento, , que pode ser chamado para encerrar a paginação antes que a última página seja alcançada. Isso permite que você busque um subconjunto de páginas. Por exemplo, o exemplo a seguir continua buscando resultados até que seja retornada uma questão que inclua "teste" no título. Para as páginas de dados que foram retornadas, apenas o título e o autor da questão são armazenados.
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})
})
);
Em vez de buscar todos os resultados de uma só vez, você pode usar para percorrer uma só página de cada vez. Por exemplo, o caso a seguir busca uma página de resultados por vez e processa cada objeto da página atual antes de buscar a próxima. Uma vez encontrado um problema com "teste" no título, o script interrompe a iteração e retorna o título e o autor do problema de cada objeto que foi processado. O iterador é o método mais eficiente em termos de memória para buscar dados paginados.
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}];
}
}
}
Você também pode usar o método com os métodos de endpoint . Passe o método de ponto de extremidade como o primeiro argumento. Passe todos os demais parâmetros como o segundo argumento.
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",
},
});
Para saber mais sobre a paginação, confira AUTOTITLE.
Captura de erros
Capturando todos os erros
Às vezes, a API REST da GitHub retorna um erro. Por exemplo, um erro será exibido se o token de acesso tiver expirado ou se um parâmetro obrigatório for omitido. O Octokit.js faz automaticamente novas tentativas de executar a solicitação quando obtém um erro diferente de , , , ou . Se ocorrer um erro de API mesmo após novas tentativas, o Octokit.js gera um erro que inclui o código de status HTTP da resposta () e os cabeçalhos da resposta (). Você deve tratar esses erros em seu código. Por exemplo, você pode usar um bloco try/catch para capturar erros:
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)
}
Tratamento de códigos de erro previstos
Às vezes, a GitHub usa um código de status 4xx para indicar uma resposta sem erro. Se o endpoint que você está usando fizer isso, você poderá adicionar tratamento adicional para erros específicos. Por exemplo, o endpoint retornará se o repositório não estiver marcado com estrela. O exemplo a seguir usa a resposta para indicar que o repositório não foi estrelado; todos os demais códigos de erros são tratados como erros.
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}`);
}
}
Tratamento de erros de limite de taxa
Se você receber um erro de limite de taxa, talvez seja necessário repetir a solicitação após aguardar um tempo. Quando você tem taxa limitada, a GitHub responde com um erro e o valor do cabeçalho de resposta será . Os cabeçalhos de resposta incluirão um cabeçalho , que informa a hora em que a janela de limite de taxa atual é redefinida, em segundos UTC. Você pode repetir a solicitação após aguardar o tempo especificado por .
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
})
Como usar a resposta
O método retorna uma promessa que será resolvida para um objeto se a solicitação for bem-sucedida. As propriedades do objeto são (o corpo da resposta retornado pelo ponto de extremidade), (o código da resposta HTTP), (a URL da solicitação) e (um objeto que contém os cabeçalhos da resposta). A menos que especificado de outra forma, o corpo da resposta está no formato JSON. Alguns endpoints não retornam um corpo de resposta; nesses casos, a propriedade é omitida.
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}`)
Da mesma forma, o método retorna uma promessa. Se a solicitação tiver sido bem-sucedida, a promessa será resolvida para uma matriz de dados retornada pelo ponto de extremidade. Ao contrário do método , o método não retorna o código de status, a URL ou os cabeçalhos.
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}`)
Script de exemplo
Aqui está um script de exemplo completo que usa o Octokit.js. O script importa e cria uma instância de . Se você quisesse se autenticar com um GitHub App em vez de um personal access token, você importaria e instanciaria em vez de . Para obter mais informações, confira Como se autenticar com um GitHub App.
A função obtém todos os arquivos alterados para uma solicitação de pull. A função chama a função . Se qualquer um dos arquivos que a solicitação de pull alterou incluir no caminho, a função comentará sobre a solicitação de pull.
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});
Próximas etapas
- Para saber mais sobre o Octokit.js, confira a documentação do Octokit.js.
- Para obter alguns exemplos da vida real, veja como o GitHub Docs usa o Octokit.js pesquisando o repositório do GitHub Docs.