À propos d’Octokit.js
Si vous souhaitez écrire un script avec JavaScript pour interagir avec l’API REST de GitHub, GitHub vous recommande d’utiliser le SDK Octokit.js. Octokit.js est géré par GitHub. Le SDK implémente les bonnes pratiques et vous permet d’interagir plus facilement avec l’API REST via JavaScript. Octokit.js fonctionne avec tous les navigateurs modernes, Node.js et Deno. Pour plus d’informations sur Octokit.js, consultez le fichier README d’Octokit.js.
Prérequis
Ce guide suppose que vous êtes familiarisé avec JavaScript et l’API REST GitHub. Pour plus d’informations sur l’API REST, consultez AUTOTITLE.
Vous devez installer et importer pour utiliser la bibliothèque Octokit.js. Ce guide utilise des déclarations d'importation selon ES6. Pour plus d’informations sur les différentes méthodes d’installation et d’importation, consultez la section Usage du fichier README d’Octokit.js.
Instanciation et authentification
Avertissement
Gérez vos informations d’authentification comme un mot de passe.
Pour que vos informations d’identification restent sécurisées, vous pouvez les stocker sous forme de secret et exécuter votre script par le biais de GitHub Actions. Pour plus d’informations, consultez « AUTOTITLE ».
Si ce n’est pas possible, envisagez d’utiliser un autre service CLI pour stocker vos informations d’identification de façon sécurisée.
Authentification avec un personal access token
Si vous voulez utiliser l’API REST GitHub à des fins personnelles, vous pouvez créer un personal access token. Pour plus d’informations sur la création d’un personal access token, consultez AUTOTITLE.
Tout d’abord, importez depuis . Ensuite, transmettez votre personal access token lorsque vous créez une instance . Dans l’exemple suivant, remplacez par une référence à votre personal access token. Remplacez par le nom de votre instance 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',
});
Authentification avec un(e) GitHub App
Si vous voulez utiliser l’API au nom d’une organisation ou d’un autre utilisateur, GitHub vous recommande d’utiliser une GitHub App. Si un point de terminaison est disponible pour GitHub Apps, la documentation de référence de REST pour ce point de terminaison indique quel type de jeton GitHub App est nécessaire. Pour plus d’informations, consultez « AUTOTITLE » et « AUTOTITLE ».
Au lieu d’importer depuis , importez . Dans l’exemple suivant, remplacez par une référence à l’ID de votre application. Remplacez par une référence à la clé privée de votre application. Remplacez par l’ID de l’installation de votre application que vous souhaitez authentifier. Vous pouvez trouver l’ID de votre application et générer une clé privée dans la page des paramètres de votre application. Pour plus d’informations, consultez « AUTOTITLE ». Vous pouvez obtenir un ID d’installation avec les points de terminaison , ou . Pour plus d’informations, consultez AUTOTITLE. Remplacez par le nom de votre instance 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);
Authentification dans GitHub Actions
Si vous voulez utiliser l’API dans un workflow GitHub Actions, GitHub vous recommande d’être authentifié avec le jeton intégré au lieu de créer un jeton. Vous pouvez accorder des autorisations au avec la clé . Pour plus d’informations sur , consultez AUTOTITLE.
Si votre workflow doit accéder à des ressources en dehors du dépôt du workflow, vous ne pourrez pas utiliser . Dans ce cas, stockez vos informations d’identification sous forme de secret et remplacez dans les exemples ci-dessous par le nom de votre secret. Pour plus d’informations sur les secrets, consultez « AUTOTITLE ».
Si vous utilisez le mot clé pour exécuter votre script JavaScript dans vos workflows GitHub Actions, vous pouvez stocker la valeur de en tant que variable d’environnement. Votre script peut accéder à la variable d’environnement en tant que .
Par exemple, cette étape de workflow stocke dans une variable d’environnement appelée :
- name: Run script
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node .github/actions-scripts/use-the-api.mjs
Le script qu’exécute le workflow utilise pour l’authentification :
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,
});
Instanciation sans authentification
Vous pouvez utiliser l’API REST sans authentification, mais vous aurez une limite de débit inférieure et vous ne pourrez pas utiliser certains points de terminaison. Pour créer une instance sans authentification, ne passez pas l’argument . Définissez l’URL de base sur . Remplacez par le nom de votre instance 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",
});
Faire des requêtes
Octokit prend en charge plusieurs façons de créer des requêtes. Vous pouvez utiliser la méthode pour créer des requêtes si vous connaissez le verbe HTTP et le chemin du point de terminaison. Vous pouvez utiliser la méthode si vous souhaitez tirer parti de l’autocomplétion dans votre IDE et de la saisie. Pour les points de terminaison paginés, vous pouvez utiliser la méthode pour demander plusieurs pages de données.
Utilisation de la méthode pour créer des requêtes
Pour utiliser la méthode pour créer des requêtes, passez la méthode HTTP et le chemin comme premier argument. Passez tous les paramètres de corps, de requête ou de chemin dans un objet en tant que deuxième argument. Par exemple, pour créer une requête dans et passer les paramètres , et :
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
});
La méthode passe automatiquement l’en-tête . Pour transmettre des en-têtes supplémentaires ou un en-tête différent, ajoutez une propriété à l'objet qui est passé en tant que deuxième argument. La valeur de la propriété est un objet avec les noms d’en-tête en tant que clés et les valeurs d’en-tête en tant que valeurs. Par exemple, pour envoyer un en-tête avec la valeur et un en-tête avec la valeur :
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",
},
});
Utilisation des méthodes d'endpoint pour effectuer des requêtes
Chaque point de terminaison d’API REST a une méthode de point de terminaison associée dans Octokit. Ces méthodes appliquent généralement l’autocomplétion dans votre IDE pour des raisons pratiques. Vous pouvez passer n’importe quel paramètre en tant qu’objet à la méthode.
await octokit.rest.issues.listForRepo({
owner: "github",
repo: "docs",
per_page: 2
});
await octokit.rest.issues.listForRepo({
owner: "github",
repo: "docs",
per_page: 2
});
De plus, si vous utilisez un langage typé tel que TypeScript, vous pouvez importer des types à utiliser avec ces méthodes. Pour plus d’informations, consultez la section TypeScript dans le README plugin-rest-endpoint-methods.js.
Création de requêtes paginées
Si le point de terminaison est paginé et que vous souhaitez extraire plusieurs pages de résultats, vous pouvez utiliser la méthode . récupère la page de résultats suivante jusqu’à atteindre la dernière page, puis retourne tous les résultats sous la forme d’un tableau. Quelques points de terminaison retournent les résultats paginés sous forme de tableau dans un objet, au lieu de les retourner sous forme de tableau. retourne toujours un tableau d’éléments même si le résultat brut était un objet.
Par exemple, l’exemple suivant récupère tous les problèmes du dépôt . Bien qu’elle demande 100 incidents à la fois, la fonction ne renvoie pas de résultat tant que la dernière page de données n’est pas atteinte.
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",
},
});
La méthode accepte une fonction map facultative, que vous pouvez utiliser pour collecter uniquement les données souhaitées à partir de la réponse. Cela réduit l’utilisation de la mémoire par votre script. La fonction map peut prendre un deuxième argument, , que vous pouvez appeler pour mettre fin à la pagination avant que la dernière page ne soit atteinte. Cela vous permet d’extraire un sous-ensemble de pages. Par exemple, l’exemple suivant continue d’extraire les résultats jusqu’à ce qu’un problème incluant « test » dans le titre soit retourné. Pour les pages de données retournées, seuls le titre et l'auteur de l'issue sont stockés.
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})
})
);
Au lieu d’extraire tous les résultats à la fois, vous pouvez utiliser pour itérer au sein d’une seule page à la fois. Par exemple, l’exemple suivant extrait une page de résultats à la fois et traite chaque objet de la page avant d’extraire la page suivante. Une fois qu’un problème qui inclut « test » dans le titre est atteint, le script arrête l’itération et retourne le titre du problème et l’auteur du problème de chaque objet qui a été traité. L’itérateur est la méthode la plus efficace en mémoire pour extraire des données paginées.
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}];
}
}
}
Vous pouvez également utiliser la méthode avec les méthodes de point de terminaison . Passez la méthode de point de terminaison en tant que premier argument. Passez tous les paramètres en tant que deuxième argument.
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",
},
});
Pour plus d’informations sur la pagination, consultez AUTOTITLE.
Interception des erreurs
Capture de toutes les erreurs
Parfois, l’API REST GitHub retourne une erreur. Par exemple, vous obtenez une erreur si votre jeton d’accès a expiré ou si vous avez omis un paramètre obligatoire. Octokit.js retente automatiquement la demande lorsqu’il obtient une erreur autre que , , , et . Si une erreur d’API se produit même après plusieurs tentatives, Octokit.js lève une erreur qui inclut le code d’état HTTP de la réponse () et les en-têtes de réponse (). Vous devez gérer ces erreurs dans votre code. Par exemple, vous pouvez utiliser un bloc try/catch pour intercepter les erreurs :
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)
}
Gestion des codes d’erreur prévus
Parfois, GitHub utilise un code d’état 4xx pour indiquer une réponse de non-erreur. Si le point de terminaison que vous utilisez le fait, vous pouvez ajouter une gestion supplémentaire pour des erreurs spécifiques. Par exemple, le point de terminaison retourne une erreur si le dépôt n’a pas d’étoile. L’exemple suivant utilise la réponse pour indiquer que le dépôt n'est pas étoilé ; tous les autres codes d’erreur sont traités comme des erreurs.
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}`);
}
}
Gestion des erreurs de limite de débit
Si vous recevez une erreur de limite de débit, vous pouvez retenter votre demande après avoir attendu. Lorsque votre taux est limité, GitHub répond avec une erreur et la valeur d’en-tête de réponse est . Les en-têtes de réponse incluent un en-tête , qui vous indique l’heure à laquelle la fenêtre de limite de débit actuelle est réinitialisée, en secondes d’époque UTC. Vous pouvez retenter votre demande après l’heure spécifiée par .
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
})
Utilisation de la réponse
La méthode retourne une promesse qui se résout en un objet si la demande a réussi. Les propriétés de l’objet sont (le corps de la réponse retourné par le point de terminaison), (le code de réponse HTTP), (l’URL de la requête) et (un objet contenant les en-têtes de réponse). Sauf indication contraire, le corps de réponse est au format JSON. Certains points de terminaison ne retournent pas de corps de réponse, auquel cas, la propriété est omise.
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}`)
De même, la méthode retourne une promesse. Si la demande a réussi, la promesse est résolue en tableau de données retourné par le point de terminaison. Contrairement à la méthode , la méthode ne retourne pas le code d’état, l’URL ou les en-têtes.
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}`)
Exemple de script
Voici un exemple de script complet qui utilise Octokit.js. Le script importe et crée une nouvelle instance . Si vous souhaitez vous authentifier avec une GitHub App au lieu d’un personal access token, vous devez importer et instancier à la place de . Pour plus d’informations, consultez Authentification avec une GitHub App.
La fonction obtient tous les fichiers modifiés pour une pull request. La fonction appelle la fonction . Si l’un des fichiers modifiés par la demande de tirage inclut dans le chemin, la fonction commente la demande de tirage.
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});
Étapes suivantes
- Pour en savoir plus sur Octokit.js, consultez la documentation Octokit.js.
- Pour obtenir des exemples concrets, regardez comment GitHub Docs utilise Octokit.js en recherchant dans le dépôt GitHub Docs.