Skip to main content

Scripting con la API de REST y JavaScript

Escribe un script mediante el SDK de Octokit.js para interactuar con la API de REST.

Acerca de Octokit.js

Si quieres escribir un script mediante JavaScript para interactuar con la API de REST de GitHub, GitHub recomienda usar el SDK de Octokit.js. GitHub mantiene Octokit.js. El SDK implementa los procedimientos recomendados y facilita la interacción con la API de REST a través de JavaScript. Octokit.js funciona con todos los exploradores modernos, Node.js y Deno. Para más información, consulta el archivo README de Octokit.js.

Prerrequisitos

En esta guía se supone que sabes usar JavaScript y la API de REST GitHub. Para obtener más información sobre la API de REST, consulta "Introducción a la API REST".

Debes instalar e importar octokit para usar la biblioteca de Octokit.js. En esta guía se usan instrucciones de importación de acuerdo con ES6. Para obtener más información sobre los diferentes métodos de instalación e importación, consulta la sección Uso del archivo README de Octokit.js.

Creación de instancias y autenticación

Advertencia: Trata tus credenciales de autenticación como una contraseña.

Para proteger tus credenciales, puedes almacenarlas como secreto y ejecutar el script a través de GitHub Actions. Para obtener más información, vea «Uso de secretos en Acciones de GitHub».

También puedes almacenar tus credenciales como un secreto de Codespaces y ejecutar el script en Codespaces. Para más información, consulta "Administración de secretos específicos de la cuenta para GitHub Codespaces".

Si estas opciones no son posibles, considera el uso de otro servicio de CLI para almacenar tus credenciales de forma segura.

Autenticación con un personal access token

Si deseas usar la API de REST de GitHub para uso personal, puedes crear un personal access token. Para obtener más información sobre la creación de un personal access token, consulte "Administración de tokens de acceso personal".

En primer lugar, importa Octokit desde octokit. A continuación, pasa los datos de personal access token al crear una instancia de Octokit. En el ejemplo siguiente, reemplaza YOUR-TOKEN por una referencia a los datos de personal access token.

JavaScript
import { Octokit } from "octokit";

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

Autenticación con una GitHub App

Si deseas usar la API en nombre de una organización u otro usuario, GitHub recomienda usar un GitHub App. Si un punto de conexión está disponible para GitHub Apps, en la documentación de referencia de REST para ese punto de conexión se indicará que tipo de token de GitHub App se requiere. Para obtener más información, vea «Registro de una instancia de GitHub App» y «Acerca de la autenticación con una aplicación de GitHub».

En lugar de importar Octokit desde octokit, importa App. En el ejemplo siguiente, reemplaza APP_ID por una referencia al identificador de la aplicación. Reemplaza PRIVATE_KEY por una referencia a la clave privada de la aplicación. Reemplaza INSTALLATION_ID por el identificador de la instalación de la aplicación en nombre de la cual quieras autenticarte. Puedes encontrar el identificador de la aplicación y generar una clave privada en la página de configuración de la aplicación. Para obtener más información, vea «Administración de claves privadas para aplicaciones de GitHub». Puedes obtener un identificador de instalación con GET /users/{username}/installation, GET /repos/{owner}/{repo}/installation o los puntos de conexión de GET /orgs/{org}/installation. Para más información, consulta "Puntos de conexión de la API de REST para GitHub Apps."

JavaScript
import { App } from "octokit";

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

const octokit = await app.getInstallationOctokit(INSTALLATION_ID);

Autenticación en GitHub Actions

Si deseas usar la API en un flujo de trabajo de GitHub Actions, GitHub recomienda autenticarse con el GITHUB_TOKEN integrado en lugar de crear un token. Puedes conceder permisos a GITHUB_TOKEN con la clave permissions. Para obtener más información sobre GITHUB_TOKEN, consulta "Autenticación automática de tokens".

Si el flujo de trabajo necesita acceder a los recursos fuera del repositorio del flujo de trabajo, no podrás usar GITHUB_TOKEN. En ese caso, almacena tus credenciales como secreto y reemplaza GITHUB_TOKEN en el ejemplo siguiente por el nombre del secreto. Para obtener más información sobre secretos, consulte "Uso de secretos en Acciones de GitHub".

Si usas la palabra clave run para ejecutar el script de JavaScript en tus flujos de trabajo de GitHub Actions, puedes almacenar el valor de GITHUB_TOKEN como una variable de entorno. El script puede acceder a la variable de entorno como process.env.VARIABLE_NAME.

Por ejemplo, este paso de flujo de trabajo almacena GITHUB_TOKEN en una variable de entorno denominada TOKEN:

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

El script que el flujo de trabajo ejecuta usa process.env.TOKEN para autenticarse:

JavaScript
import { Octokit } from "octokit";

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

Creación de instancias sin autenticación

Puedes usar la API de REST sin autenticación, aunque tendrás un límite de velocidad inferior y no podrás usar algunos puntos de conexión. Para crear una instancia de Octokit sin autenticación, no pases el argumento auth.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ });

Realización de solicitudes

Octokit admite varias formas de realizar solicitudes. Puedes usar el método request para realizar solicitudes si conoces el verbo HTTP y la ruta de acceso del punto de conexión. Puedes usar el método rest si quieres aprovechar la función de autocompletar en el IDE y escribir. En el caso de los puntos de conexión paginados, puedes usar el método paginate para solicitar varias páginas de datos.

Uso del método request para realizar solicitudes

Para usar el método request para realizar solicitudes, pasa el método HTTP y la ruta de acceso como primer argumento. Pasa cualquier parámetro de cuerpo, consulta o ruta en un objeto como segundo argumento. Por ejemplo, para realizar una solicitud GET a /repos/{owner}/{repo}/issues y pasar los parámetros owner, repo y per_page:

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

El método request pasa automáticamente el encabezado Accept: application/vnd.github+json. Para pasar encabezados adicionales o un encabezado Accept diferente, agrega una propiedad headers al objeto que se pasa como segundo argumento. El valor de la propiedad headers es un objeto con los nombres de encabezado como claves y valores de encabezado como valores. Por ejemplo, para enviar un encabezado content-type con un valor de text/plain y un encabezado x-github-api-version con un valor de 2022-11-28:

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

Uso de métodos de punto de conexión rest para realizar solicitudes

Cada punto de conexión de la API de REST tiene un método de punto de conexión asociado rest en Octokit. Por lo general, estos métodos se autocompletan en el IDE para mayor comodidad. Puedes pasar cualquier parámetro como un objeto al método.

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

Además, si usas un lenguaje con tipo como TypeScript, puedes importar tipos para usarlos con estos métodos. Para obtener más información, consulta la sección TypeScript en el archivo README de plugin-rest-endpoint-methods.js.

Realización de solicitudes paginadas

Si el punto de conexión está paginado y quieres capturar más de una página de resultados, puedes usar el método paginate. paginate capturará la siguiente página de resultados hasta llegar a la última página y, a continuación, devolverá todos los resultados como una sola matriz. Algunos puntos de conexión devuelven resultados paginados como matriz en un objeto, en lugar de devolverlos como una matriz. paginate siempre devuelve una matriz de elementos, aun cuando el resultado sin procesar sea un objeto.

Por ejemplo, el siguiente ejemplo obtiene todas las incidencias del repositorio github/docs. Aunque se solicitan 100 incidencias a la vez, la función no regresará hasta que se alcance la última página de datos.

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

El método paginate acepta una función de asignación opcional, que puedes usar para recopilar solo los datos que quieras de la respuesta. Esto reduce la cantidad de memoria que usa el script. La función de asignación puede tomar un segundo argumento, done, al que puedes llamar para finalizar la paginación antes de alcanzar la última página. Esto te permite capturar un subconjunto de páginas. Por ejemplo, el ejemplo siguiente continúa capturando resultados hasta que se devuelve un problema que incluye "test" en el título. Para las páginas de datos que se han devuelto, solo se almacenan el título del problema y el autor.

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

En lugar de capturar todos los resultados a la vez, puedes usar octokit.paginate.iterator() para recorrer en iteración una sola página a la vez. Por ejemplo, en el ejemplo siguiente se captura una página de resultados a la vez y se procesa cada objeto de la página antes de capturar la página siguiente. Una vez que se alcanza un problema que incluye "prueba" en el título, el script detiene la iteración y devuelve el título del problema y el autor del problema de cada objeto que se procesó. El iterador es el método más eficiente en memoria para obtener datos paginados.

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

También puedes usar el método paginate con los métodos de punto de conexión rest. Pasa el método de punto de conexión rest como primer argumento. Pasa los parámetros como segundo argumento.

JavaScript
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 obtener más información sobre la paginación, consulta "Uso de la paginación en la API de REST".

Almacenamiento en caché de los errores

Detección de todos los errores

A veces, la API de REST de GitHub devolverá un error. Por ejemplo, recibirás un error si el token de acceso ha expirado o si has omitido un parámetro necesario. Octokit.js reintenta automáticamente la solicitud cuando obtiene un error distinto de 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Foundy 422 Unprocessable Entity. Si se produce un error de API incluso después de varios reintentos, Octokit.js genera un error que incluye el código de estado HTTP de la respuesta (response.status) y los encabezados de respuesta (response.headers). Debes controlar estos errores en el código. Por ejemplo, puedes usar un bloque try/catch para detectar errores:

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

Control de los códigos de error previstos

A veces, los datos GitHub usan un código de estado 4xx para indicar una respuesta que no es de error. Si el punto de conexión que usa lo hace, puedes agregar control adicional para errores específicos. Por ejemplo, el punto de conexión GET /user/starred/{owner}/{repo} devolverá un valor 404 si el repositorio no está destacado. En el ejemplo siguiente se usa la respuesta 404 para indicar que el repositorio no se ha destacado; todos los demás códigos de error se tratan como errores.

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

Control de errores de límite de frecuencia

Si recibes un error de límite de frecuencia, es posible que quieras volver a realizar la solicitud después de esperar. Cuando se limita la velocidad, GitHub responde con un error 403 Forbidden y el valor del encabezado de respuesta x-ratelimit-remaining será "0". Los encabezados de respuesta incluirán un encabezado x-ratelimit-reset, que indica la hora a la que se restablece la ventana de límite de velocidad actual, en segundos de época UTC. Puedes volver a intentar realizar tu solicitud después del tiempo 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: "github",
    repo: "docs",
    per_page: 2
  })

Análisis de la respuesta

El método request devuelve una promesa que se resuelve en un objeto si la solicitud se ha realizado correctamente. Las propiedades del objeto son data (el cuerpo de la respuesta devuelto por el punto de conexión), status (el código de respuesta HTTP), url (la dirección URL de la solicitud) y headers (un objeto que contiene los encabezados de respuesta). A menos que se especifique lo contrario, el cuerpo de la respuesta está en formato JSON. Algunos puntos de conexión no devuelven un cuerpo de respuesta; en esos casos, se omite la propiedad data.

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

Del mismo modo, el método paginate devuelve una promesa. Si la solicitud se ha realizado correctamente, la promesa se resuelve en una matriz de datos devueltos por el punto de conexión. A diferencia del método request, el método paginate no devuelve el código de estado, la dirección URL ni los encabezados.

JavaScript
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 ejemplo

Este es un script de ejemplo completo que usa Octokit.js. El script importa Octokit y crea una nueva instancia de Octokit. Si quieres autenticarte con un valor GitHub App en lugar de un personal access token, tendrás que crear e importar instancias de App en lugar de Octokit. Para más información, consulta "Autenticación con una GitHub App".

La función getChangedFiles obtiene todos los archivos modificados para una solicitud de incorporación de cambios. La función commentIfDataFilesChanged llama a la función getChangedFiles. Si alguno de los archivos que cambió la solicitud de incorporación de cambios incluye /data/ en la ruta de acceso del archivo, la función comentará la solicitud de incorporación de cambios.

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

Pasos siguientes