Skip to main content

Scripts com a API REST e o JavaScript

Se você quiser escrever um script usando JavaScript para interagir com a API REST da , a recomenda que você use o SDK do Octokit.js. O Octokit.js é mantido pela . 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.

Este guia pressupõe que você esteja familiarizado com o JavaScript e a API REST da . Para obter informações sobre a API REST, confira Introdução à API REST.

Você precisa instalar e importar octokit 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 Uso do arquivo LEIAME do Octokit.js.

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 Actions. Para saber mais, confira Usar segredos em ações do .

Você também pode armazenar suas credenciais como um segredo do Codespaces e executar seu script no Codespaces. Para saber mais, confira Gerenciando segredos específicos da sua conta para o Codespaces.

Se essas opções não forem possíveis, considere usar outro serviço CLI para armazenar suas credenciais com segurança.

Se quiser usar a API REST do para uso pessoal, crie um personal access token. Para obter mais informações sobre como criar um personal access token, confira Gerenciar seus tokens de acesso pessoal.

Primeiro, importe Octokit de octokit. Em seguida, passe seu personal access token ao criar uma instância de Octokit. No exemplo a seguir, substitua YOUR-TOKEN por uma referência ao seu personal access token.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  auth: 'YOUR-TOKEN',
});

Se você quiser usar a API em nome de uma organização ou de outro usuário, a recomenda que você use um App. Se um ponto de extremidade estiver disponível para Apps, a documentação de referência da API REST para esse ponto de extremidade indicará que tipo de token do App é necessário. Para saber mais, confira Registrar um Aplicativo e Sobre a autenticação com um App.

Em vez de importar Octokit de octokit, importe App. No exemplo a seguir, substitua APP_ID por uma referência à ID do seu aplicativo. Substitua PRIVATE_KEY por uma referência à chave privada do seu aplicativo. Substitua INSTALLATION_ID 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 Como gerenciar chaves privadas para Aplicativos . Você pode obter uma ID de instalação com os pontos de extremidade GET /users/{username}/installation, GET /repos/{owner}/{repo}/installation ou GET /orgs/{org}/installation. Para obter mais informações, confira Pontos de extremidade da API REST para o Apps.

JavaScript
import { App } from "octokit";

const app = new App({
  appId: APP_ID,
  privateKey: PRIVATE_KEY,
});

const octokit = await app.getInstallationOctokit(INSTALLATION_ID);

Se você quiser usar a API em um fluxo de trabalho de Actions, a recomenda que você se autentique com o _TOKEN interno, em vez de criar um token. Você pode conceder permissões à _TOKEN com a chave permissions. Para obter mais informações sobre _TOKEN, confira Autenticação automática de token.

Se o fluxo de trabalho precisar acessar recursos fora do repositório dele, então você não poderá usar _TOKEN. Nesse caso, armazene suas credenciais como um segredo e substitua _TOKEN nos exemplos abaixo pelo nome do segredo. Para saber mais sobre segredos, confira Usar segredos em ações do .

Se usar a palavra-chave run para executar o script do JavaScript em seus fluxos de trabalho de Actions, você poderá armazenar o valor de _TOKEN como uma variável de ambiente. Seu script pode acessar a variável de ambiente como process.env.VARIABLE_NAME.

Por exemplo, essa etapa do fluxo de trabalho armazena _TOKEN em uma variável de ambiente chamada TOKEN:

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

O script que o fluxo de trabalho executa usa process.env.TOKEN para se autenticar:

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  auth: process.env.TOKEN,
});

Você pode usar a API REST sem autenticação, embora isso forneça um limite de taxa mais baixo e não permita o uso de alguns pontos de extremidade. Para criar uma instância de Octokit sem autenticação, não passe o argumento auth.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ });

O Octokit dá suporte a várias maneiras de fazer solicitações. Você pode usar o método request para fazer solicitações se souber o verbo HTTP e o caminho para o ponto de extremidade. Você pode usar o método rest se quiser aproveitar o preenchimento automático em seu IDE e digitar. Para pontos de extremidade paginados, você pode usar o método paginate para solicitar várias páginas de dados.

Para usar o método request a fim de fazer solicitações, passe o método HTTP e o caminho como o primeiro argumento. Passe eventuais parâmetros de caminho, consulta e corpo em um objeto como o segundo argumento. Por exemplo, para fazer uma solicitação GET para /repos/{owner}/{repo}/issues e passar os parâmetros owner, repo e per_page:

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

O método request passa automaticamente o cabeçalho Accept: application/vnd.+json. Para passar cabeçalhos adicionais ou um cabeçalho Accept diferente, adicione uma propriedade headers ao objeto que é passado como o segundo argumento. O valor da propriedade headers é um objeto com os nomes de cabeçalho como chaves e valores de cabeçalho como valores. Por exemplo, para enviar um cabeçalho content-type com o valor de text/plain e um cabeçalho x--api-version com o valor de 2022-11-28:

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

Cada ponto de extremidade da API REST tem um método de ponto de extremidade rest 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.

JavaScript
await octokit.rest.issues.listForRepo({
  owner: "",
  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 TypeScript no arquivo LEIAME do plugin-rest-endpoint-methods.js.

Se o ponto de extremidade for paginado e você quiser buscar mais de uma página de resultados, poderá usar o método paginate. paginate 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. paginate sempre retorna uma matriz de itens, mesmo que o resultado bruto tenha sido um objeto .

Por exemplo, o caso a seguir obtém todos os problemas do repositório /docs. Embora solicite 100 solicitações por vez, a função não retornará até que a última página de dados seja atingida.

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

O método paginate 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, done, 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 caso a seguir segue buscando resultados até que seja retornado um problema com "teste" no título. Para as páginas de dados que foram retornadas, são armazenados apenas o título e o autor do problema.

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

Em vez de buscar todos os resultados de uma só vez, você pode usar octokit.paginate.iterator() 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.

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}];
    }
  }
}

Você também pode usar o método paginate com os métodos de ponto de extremidade rest. Passe o método de ponto de extremidade rest como o primeiro argumento. Passe todos os demais parâmetros como o segundo argumento.

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

Para saber mais sobre a paginação, confira Como usar paginação na API REST.

Às vezes, a API REST da 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 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found ou 422 Unprocessable Entity. 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 (response.status) e os cabeçalhos da resposta (response.headers). Você deve tratar esses erros em seu código. Por exemplo, você pode usar um bloco try/catch para capturar erros:

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)
}

Às vezes, a usa um código de status 4xx para indicar uma resposta sem erro. Se o ponto de extremidade que você está usando fizer isso, você poderá adicionar tratamentos adicionais para erros específicos. Por exemplo, o ponto de extremidade GET /user/starred/{owner}/{repo} retornará 404 se o repositório não for estrelado. O exemplo a seguir usa a resposta 404 para indicar que o repositório não foi estrelado; todos os demais códigos de erros são tratados como erros.

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}`);
  }
}

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 responde com um erro 403 Forbidden e o valor do cabeçalho de resposta x-ratelimit-remaining será "0". Os cabeçalhos de resposta incluirão um cabeçalho x-ratelimit-reset, 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 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
  })

O método request retorna uma promessa que será resolvida para um objeto se a solicitação for bem-sucedida. As propriedades do objeto são data (o corpo da resposta retornado pelo ponto de extremidade), status (o código da resposta HTTP), url (a URL da solicitação) e headers (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 pontos de extremidade não retornam um corpo de resposta; nesses casos, a propriedade data é omitida.

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}`)

Da mesma forma, o método paginate 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 request, o método paginate não retorna o código de status, a URL ou os cabeçalhos.

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}`)

Aqui está um script de exemplo completo que usa o Octokit.js. O script importa Octokit e cria uma instância de Octokit. Se você quisesse se autenticar com um App em vez de um personal access token, você importaria e instanciaria App em vez de Octokit. Para obter mais informações, confira Como se autenticar com um App.

A função getChangedFiles obtém todos os arquivos alterados para uma solicitação de pull. A função commentIfDataFilesChanged chama a função getChangedFiles. Se qualquer um dos arquivos que a solicitação de pull alterou incluir /data/ no caminho, a função comentará sobre a solicitação de pull.

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