Skip to main content

REST API ๋ฐ JavaScript๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํฌ๋ฆฝํŒ…

Octokit.js SDK๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ REST API์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

Octokit.js ์ •๋ณด

JavaScript๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ GitHub์˜ REST API์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋ ค๋ฉด GitHub์—์„œ Octokit.js SDK๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Octokit.js๋Š” GitHub์— ์˜ํ•ด ์œ ์ง€๋จ๋‹ˆ๋‹ค. SDK๋Š” ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  JavaScript๋ฅผ ํ†ตํ•ด REST API์™€ ๋ณด๋‹ค ์‰ฝ๊ฒŒ ์ƒํ˜ธ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. Octokit.js๋Š” ๋ชจ๋“  ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €, Node.js ๋ฐ Deno์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. Octokit.js์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ •๋ณด๋Š” Octokit.js ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

ํ•„์ˆ˜ ์กฐ๊ฑด

์ด ๊ฐ€์ด๋“œ์—์„œ๋Š” JavaScript ๋ฐ GitHub REST API์— ์ต์ˆ™ํ•˜๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. REST API์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ REST API ์‹œ์ž‘์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

์ฐธ๊ณ : Octokit.js ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด octokit์„(๋ฅผ) ์„ค์น˜ํ•˜๊ณ  ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ€์ด๋“œ์—์„œ๋Š” ES6์— ๋”ฐ๋ผ import ๋ฌธ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์–‘ํ•œ ์„ค์น˜ ๋ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ •๋ณด๋Š” Octokit.js ์ถ”๊ฐ€ ์ •๋ณด ์‚ฌ์šฉ ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”.

์ธ์Šคํ„ด์Šคํ™” ๋ฐ ์ธ์ฆ

๊ฒฝ๊ณ 

์ธ์ฆ ์ž๊ฒฉ ์ฆ๋ช…์„ ์•”ํ˜ธ์ฒ˜๋Ÿผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ž๊ฒฉ ์ฆ๋ช…์„ ์•ˆ์ „ํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋น„๋ฐ€๋กœ ์ €์žฅํ•˜๊ณ  GitHub Actions๋ฅผ ํ†ตํ•ด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub Actions์—์„œ ๋น„๋ฐ€ ์‚ฌ์šฉ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

๋˜ํ•œ ์ž๊ฒฉ ์ฆ๋ช…์„ Codespaces ๋น„๋ฐ€๋กœ ์ €์žฅํ•˜๊ณ  Codespaces์—์„œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub Codespaces์— ๋Œ€ํ•œ ๊ณ„์ •๋ณ„ ๋น„๋ฐ€ ๊ด€๋ฆฌ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

์ด(๊ฐ€) ์˜ต์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ, ๋‹ค๋ฅธ CLI ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž๊ฒฉ ์ฆ๋ช…์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

personal access token์„(๋ฅผ) ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ

๊ฐœ์ธ์šฉ์œผ๋กœ GitHub REST API๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋Š” ๊ฒฝ์šฐ personal access token๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. personal access token์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๊ฐœ์ธ์šฉ ์•ก์„ธ์Šค ํ† ํฐ ๊ด€๋ฆฌ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

๋จผ์ € octokit์—์„œ Octokit๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ Octokit ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค ๋•Œ personal access token์„(๋ฅผ) ํŒจ์Šคํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” YOUR-TOKEN์„ personal access token์— ๋Œ€ํ•œ ์ฐธ์กฐ๋กœ ์น˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

JavaScript
import { Octokit } from "octokit";

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

GitHub App(์œผ)๋กœ ์ธ์ฆ

์กฐ์ง ๋˜๋Š” ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋ฅผ ๋Œ€์‹ ํ•˜์—ฌ API๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋Š” ๊ฒฝ์šฐ GitHub์—์„œ GitHub App์„(๋ฅผ) ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. GitHub Apps์— ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ REST ์ฐธ์กฐ ์„ค๋ช…์„œ์— ํ•„์š”ํ•œ GitHub App ํ† ํฐ ์œ ํ˜•์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub ์•ฑ ๋“ฑ๋ก ๋ฐ GitHub ์•ฑ์„ ์‚ฌ์šฉํ•œ ์ธ์ฆ ์ •๋ณด์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

octokit์—์„œ Octokit๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋Œ€์‹  App์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” APP_ID์„(๋ฅผ) ์•ฑ ID์— ๋Œ€ํ•œ ์ฐธ์กฐ๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. ์•ฑ์˜ ํ”„๋ผ์ด๋น— ํ‚ค์— ๋Œ€ํ•œ ์ฐธ์กฐ๋กœ PRIVATE_KEY๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. INSTALLATION_ID๋ฅผ ๋Œ€์‹ ํ•˜์—ฌ ์ธ์ฆํ•˜๋ ค๋Š” ์•ฑ์˜ ์„ค์น˜์˜ ID๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. ์•ฑ ID๋ฅผ ์ฐพ๊ณ  ์•ฑ์˜ ์„ค์ • ํŽ˜์ด์ง€ ์„ค์ •์—์„œ ํ”„๋ผ์ด๋น— ํ‚ค๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub ์•ฑ์— ๋Œ€ํ•œ ํ”„๋ผ์ด๋น— ํ‚ค ๊ด€๋ฆฌ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”. GET /users/{username}/installation, GET /repos/{owner}/{repo}/installation, GET /orgs/{org}/installation ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ค์น˜ ID๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub Apps์— ๋Œ€ํ•œ REST API ์—”๋“œํฌ์ธํŠธ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

JavaScript
import { App } from "octokit";

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

const octokit = await app.getInstallationOctokit(INSTALLATION_ID);

GitHub Actions์œผ๋กœ ์ธ์ฆ

GitHub Actions ์›Œํฌํ”Œ๋กœ์—์„œ API๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด GitHub์—์„œ ํ† ํฐ์„ ๋งŒ๋“œ๋Š” ๋Œ€์‹  ๊ธฐ๋ณธ ์ œ๊ณต GITHUB_TOKEN์œผ๋กœ ์ธ์ฆํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. permissions ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ GITHUB_TOKEN์— ๋Œ€ํ•œ ์‚ฌ์šฉ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. GITHUB_TOKEN์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์›Œํฌํ”Œ๋กœ์—์„œ ์ธ์ฆ์— GITHUB_TOKEN ์‚ฌ์šฉ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

์›Œํฌํ”Œ๋กœ๊ฐ€ ์›Œํฌํ”Œ๋กœ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ์™ธ๋ถ€์˜ ๋ฆฌ์†Œ์Šค์— ์•ก์„ธ์Šคํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ GITHUB_TOKEN์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์ž๊ฒฉ ์ฆ๋ช…์„ ๋น„๋ฐ€๋กœ ์ €์žฅํ•˜๊ณ  ์•„๋ž˜ ์˜ˆ์ œ์˜ GITHUB_TOKEN์„ ๋น„๋ฐ€์˜ ์ด๋ฆ„์œผ๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. ๋น„๋ฐ€์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub Actions์—์„œ ๋น„๋ฐ€ ์‚ฌ์šฉ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

๋˜ํ•œ run ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌGitHub Actions ์›Œํฌํ”Œ๋กœ์—์„œ JavaScript ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ GITHUB_TOKEN์˜ ๊ฐ’์„ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— process.env.VARIABLE_NAME๋กœ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์ด ์›Œํฌํ”Œ๋กœ ๋‹จ๊ณ„์—์„œ TOKEN์ด๋ผ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— GITHUB_TOKEN์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

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

์›Œํฌํ”Œ๋กœ๊ฐ€ ์‹คํ–‰ํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋Š” process.env.TOKEN์„ ์ธ์ฆํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

JavaScript
import { Octokit } from "octokit";

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

์ธ์ฆ ์—†๋Š” ์ธ์Šคํ„ด์Šคํ™”

ํŠธ๋ž˜ํ”ฝ๋ฅ  ์ œํ•œ์ด ๋‚ฎ๊ณ  ์ผ๋ถ€ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์–ด๋„ ์ธ์ฆ ์—†์ด REST API๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ธ์ฆํ•˜์ง€ ์•Š๊ณ  Octokit ์ธ์Šคํ„ด์Šค ๋ฅผ ๋งŒ๋“ค๋ ค๋ฉด auth ์ธ์ˆ˜๋ฅผ ํŒจ์Šคํ•˜์ง€ ๋งˆ์„ธ์š”.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ });

์š”์ฒญ ์ˆ˜ํ–‰

Octokit์€ ์š”์ฒญ์„ ๋งŒ๋“œ๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ HTTP ๋™์‚ฌ์™€ ๊ฒฝ๋กœ๋ฅผ ์•Œ๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ request ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. IDE์—์„œ ์ž๋™ ์™„์„ฑ ๋ฐ ์ž…๋ ฅ์„ ์ด์šฉํ•˜๋ ค๋Š” ๊ฒฝ์šฐ rest ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ์—”๋“œํฌ์ธํŠธ์˜ ๊ฒฝ์šฐ paginate ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

request ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

request ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ๋งŒ๋“ค๋ ค๋ฉด HTTP ๋ฉ”์„œ๋“œ์™€ ๊ฒฝ๋กœ๋ฅผ ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ์ฒด์˜ ๋ณธ๋ฌธ, ์ฟผ๋ฆฌ, ๊ฒฝ๋กœ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ํŒจ์Šคํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด /repos/{owner}/{repo}/issues์— GET์„ ์š”์ฒญํ•˜๊ณ  owner, repo, per_page ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋ ค๋ฉด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”.

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

request ๋ฉ”์„œ๋“œ๋Š” ์ž๋™์œผ๋กœ Accept: application/vnd.github+json ๋จธ๋ฆฌ๊ธ€์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ถ”๊ฐ€ ํ—ค๋” ๋˜๋Š” ๋‹ค๋ฅธ Accept ๋จธ๋ฆฌ๊ธ€์„ ์ „๋‹ฌํ•˜๋ ค๋ฉด ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌ๋˜๋Š” ๊ฐœ์ฒด์— headers ์†์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. headers ์†์„ฑ์˜ ๊ฐ’์€ ํ—ค๋” ์ด๋ฆ„์„ ํ‚ค๋กœ, ํ—ค๋” ๊ฐ’์„ ๊ฐ’์œผ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฐœ์ฒด์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๊ฐ’์ด text/plain์ธ content-type ๋จธ๋ฆฌ๊ธ€๊ณผ ๊ฐ’์ด 2022-11-28์ธ x-github-api-version ๋จธ๋ฆฌ๊ธ€์„ ๋ณด๋‚ด๋ ค๋ฉด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

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

rest ์—”๋“œํฌ์ธํŠธ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ

๋ชจ๋“  REST API ์—”๋“œํฌ์ธํŠธ์—๋Š” Octokit์— ์—ฐ๊ฒฐ๋œ rest ์—”๋“œํฌ์ธํŠธ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฉ”์„œ๋“œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ํŽธ์˜๋ฅผ ์œ„ํ•ด IDE์—์„œ ์ž๋™ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ๊ฐœ์ฒด๋กœ ๋ฉ”์„œ๋“œ์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

๋˜ํ•œ TypeScript์™€ ๊ฐ™์€ ํ˜•์‹ํ™”๋œ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์ด๋Ÿฌํ•œ ๋ฉ”์„œ๋“œ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ํ˜•์‹์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ์ •๋ณด๋Š” plugin-rest-endpoint-methods.js ์ถ”๊ฐ€ ์ •๋ณด์—์„œ TypeScript ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”.

ํŽ˜์ด์ง€ ๋งค๊ธด ์š”์ฒญ ๋งŒ๋“ค๊ธฐ

์—”๋“œํฌ์ธํŠธ์˜ ํŽ˜์ด์ง€๊ฐ€ ๋งค๊ฒจ์ง€๊ณ  ๋‘˜ ์ด์ƒ์˜ ๊ฒฐ๊ณผ ํŽ˜์ด์ง€๋ฅผ ํŽ˜์น˜ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ paginate ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. paginate๋Š” ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์— ๋„๋‹ฌํ•  ๋•Œ๊นŒ์ง€ ๊ฒฐ๊ณผ์˜ ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜จ ๋‹ค์Œ ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ๋‹จ์ผ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๊ฐ€ ๋งค๊ฒจ์ง„ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹ , ๋ช‡ ๊ฐœ์˜ ์—”๋“œํฌ์ธํŠธ๋Š” ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ๊ฒฐ๊ณผ๋ฅผ ๊ฐœ์ฒด์˜ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์›์‹œ ๊ฒฐ๊ณผ๊ฐ€ ๊ฐœ์ฒด์ธ ๊ฒฝ์šฐ์—๋„ paginate๋Š” ํ•ญ์ƒ ํ•ญ๋ชฉ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” github/docs ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์—์„œ ๋ชจ๋“  ์ด์Šˆ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ํ•œ ๋ฒˆ์— 100๊ฐœ์˜ ์ด์Šˆ๋ฅผ ์š”์ฒญํ•˜์ง€๋งŒ ํ•จ์ˆ˜๋Š” ๋ฐ์ดํ„ฐ์˜ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์— ๋„๋‹ฌํ•  ๋•Œ๊นŒ์ง€ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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

paginate ๋ฉ”์„œ๋“œ๋Š” ์‘๋‹ต์—์„œ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ์ˆ˜์ง‘ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์„ ํƒ์  ๋งต ํ•จ์ˆ˜๋ฅผ ์ˆ˜๋ฝํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์Šคํฌ๋ฆฝํŠธ์˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์ค„์–ด๋“ญ๋‹ˆ๋‹ค. map ํ•จ์ˆ˜๋Š” ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์— ๋„๋‹ฌํ•˜๊ธฐ ์ „์— ํŽ˜์ด์ง€ ๋งค๊น€์„ ์ข…๋ฃŒํ•˜๊ธฐ ์œ„ํ•ด ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜ done์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํŽ˜์ด์ง€์˜ ํ•˜์œ„ ์ง‘ํ•ฉ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” ์ œ๋ชฉ์— "test"๊ฐ€ ํฌํ•จ๋œ ์ด์Šˆ๊ฐ€ ๋ฐ˜ํ™˜๋  ๋•Œ๊นŒ์ง€ ๊ฒฐ๊ณผ๋ฅผ ๊ณ„์† ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜๋œ ๋ฐ์ดํ„ฐ ํŽ˜์ด์ง€์˜ ๊ฒฝ์šฐ ์ด์Šˆ ์ œ๋ชฉ๊ณผ ์ž‘์„ฑ์ž๋งŒ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

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

๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ค๋Š” ๋Œ€์‹  ํ•œ ๋ฒˆ์— ๋‹จ์ผ ํŽ˜์ด์ง€๋ฅผ ๋ฐ˜๋ณตํ•˜๋Š” ๋ฐ octokit.paginate.iterator()๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” ๊ฒฐ๊ณผ์˜ ํ•œ ํŽ˜์ด์ง€๋ฅผ ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ค๊ณ  ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์ „์— ํŽ˜์ด์ง€์—์„œ ๊ฐ ๊ฐœ์ฒด๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ œ๋ชฉ์— "test"๊ฐ€ ํฌํ•จ๋œ ์ด์Šˆ์— ๋„๋‹ฌํ•˜๋ฉด ์Šคํฌ๋ฆฝํŠธ๋Š” ๋ฐ˜๋ณต์„ ์ค‘์ง€ํ•˜๊ณ  ์ฒ˜๋ฆฌ๋œ ๊ฐ ๊ฐœ์ฒด์˜ ์ด์Šˆ ์ œ๋ชฉ ๋ฐ ์ด์Šˆ ์ž‘์„ฑ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ณต๊ธฐ๋Š” ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ๊ฐ€์žฅ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์ธ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.

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

rest ์—”๋“œํฌ์ธํŠธ ๋ฉ”์„œ๋“œ์—์„œ๋„ paginate ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. rest ์—”๋“œํฌ์ธํŠธ ๋ฉ”์„œ๋“œ๋ฅผ ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

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

ํŽ˜์ด์ง€ ๋งค๊น€์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ REST API์—์„œ ํŽ˜์ด์ง€ ๋งค๊น€ ์‚ฌ์šฉ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

์˜ค๋ฅ˜ ํฌ์ฐฉํ•˜๊ธฐ

๋ชจ๋“  ์˜ค๋ฅ˜ ํฌ์ฐฉํ•˜๊ธฐ

๊ฒฝ์šฐ์— ๋”ฐ๋ผ GitHub REST API์—์„œ ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์•ก์„ธ์Šค ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ๊ฑฐ๋‚˜ ํ•„์ˆ˜ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ์ƒ๋žตํ•˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. Octokit.js๋Š” 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found๋ฐ 422 Unprocessable Entity ์ด์™ธ์˜ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์š”์ฒญ์„ ์ž๋™์œผ๋กœ ๋‹ค์‹œ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. ์žฌ์‹œ๋„ ํ›„์—๋„ API ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด Octokit.js๋Š” ์‘๋‹ต(response.status) ๋ฐ ์‘๋‹ต ํ—ค๋”(response.headers)์˜ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ throwํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ์—์„œ ์ด๋Ÿฌํ•œ ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด try/catch ๋ธ”๋ก์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ค๋ฅ˜๋ฅผ ํฌ์ฐฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

์˜๋„ํ•œ ์˜ค๋ฅ˜ ์ฝ”๋“œ ์ฒ˜๋ฆฌํ•˜๊ธฐ

๊ฒฝ์šฐ์— ๋”ฐ๋ผ GitHub์€(๋Š”) 4xx ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ค๋ฅ˜๊ฐ€ ์•„๋‹Œ ์‘๋‹ต์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์‚ฌ์šฉ ์ค‘์ธ ์—”๋“œํฌ์ธํŠธ์—์„œ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ, ํŠน์ • ์˜ค๋ฅ˜์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด GET /user/starred/{owner}/{repo} ์—”๋“œํฌ์ธํŠธ๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ๋ณ„ํ‘œ๋ฅผ ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ 404๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” 404 ์‘๋‹ต์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๊ฐ€ ๋ณ„ํ‘œ ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜์Œ์„ ๋‚˜ํƒ€๋‚ด๊ณ  ๋‹ค๋ฅธ ๋ชจ๋“  ์˜ค๋ฅ˜ ์ฝ”๋“œ๋Š” ์˜ค๋ฅ˜๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

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

ํŠธ๋ž˜ํ”ฝ๋ฅ  ์ œํ•œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ

ํŠธ๋ž˜ํ”ฝ๋ฅ  ์ œํ•œ ์˜ค๋ฅ˜๊ฐ€ ํ‘œ์‹œ๋˜๋Š” ๊ฒฝ์šฐ ๋Œ€๊ธฐ ํ›„ ์š”์ฒญ์„ ๋‹ค์‹œ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠธ๋ž˜ํ”ฝ๋ฅ ์ด ์ œํ•œ๋˜๋ฉด GitHub์ด(๊ฐ€) 403 Forbidden ์˜ค๋ฅ˜๋กœ ์‘๋‹ตํ•˜๊ณ  x-ratelimit-remaining ์‘๋‹ต ํ—ค๋” ๊ฐ’์ด "0"์ด ๋ฉ๋‹ˆ๋‹ค. ์‘๋‹ต ํ—ค๋”์—๋Š” ํ˜„์žฌ ํŠธ๋ž˜ํ”ฝ๋ฅ  ์ œํ•œ ์ฐฝ์ด ์žฌ์„ค์ •๋˜๋Š” ์‹œ๊ฐ„์„ UTC Epoch ์ดˆ ๋‹จ์œ„๋กœ ์•Œ๋ ค์ฃผ๋Š” x-ratelimit-reset ํ—ค๋”๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. 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
  })

์‘๋‹ต ์‚ฌ์šฉ

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด request ๋ฉ”์„œ๋“œ๋Š” ๊ฐœ์ฒด๋กœ ํ™•์ธ๋˜๋Š” Promise๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ์ฒด ์†์„ฑ์€ data(์—”๋“œํฌ์ธํŠธ์—์„œ ๋ฐ˜ํ™˜๋œ ์‘๋‹ต ๋ณธ๋ฌธ), status(HTTP ์‘๋‹ต ์ฝ”๋“œ), url(์š”์ฒญ์˜ URL) ๋ฐ headers(์‘๋‹ต ๋จธ๋ฆฌ๊ธ€์„ ํฌํ•จํ•˜๋Š” ๊ฐœ์ฒด)์ž…๋‹ˆ๋‹ค. ๋‹ฌ๋ฆฌ ์ง€์ •ํ•˜์ง€ ์•Š๋Š” ํ•œ ์‘๋‹ต ๋ณธ๋ฌธ์€ JSON ํ˜•์‹์ž…๋‹ˆ๋‹ค. ์ผ๋ถ€ ์—”๋“œํฌ์ธํŠธ๋Š” ์‘๋‹ต ๋ณธ๋ฌธ์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ 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}`)

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ paginate ๋ฉ”์„œ๋“œ๋Š” ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ํ”„๋ผ๋ฏธ์Šค๋Š” ์—”๋“œํฌ์ธํŠธ์—์„œ ๋ฐ˜ํ™˜๋œ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด๋กœ ํ™•์ธ๋ฉ๋‹ˆ๋‹ค. request ๋ฉ”์„œ๋“œ์™€ ๋‹ฌ๋ฆฌ paginate ๋ฉ”์„œ๋“œ๋Š” ์ƒํƒœ ์ฝ”๋“œ, URL ๋˜๋Š” ๋จธ๋ฆฌ๊ธ€์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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

์˜ˆ์ œ ์Šคํฌ๋ฆฝํŠธ

๋‹ค์Œ์€ Octokit.js๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ „์ฒด ์˜ˆ์ œ ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋Š” Octokit๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์ƒˆ ์ธ์Šคํ„ด์Šค Octokit๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. personal access token ๋Œ€์‹  GitHub App์„(๋ฅผ) ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆํ•˜๋ ค๋Š” ๊ฒฝ์šฐ Octokit ๋Œ€์‹  App์„ ๊ฐ€์ ธ์˜ค๊ณ  ์ธ์Šคํ„ด์Šคํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ์ •๋ณด๋Š” GitHub App์—์„œ ์ธ์ฆ์„ ์ฐธ์กฐํ•˜์„ธ์š”.

getChangedFiles ํ•จ์ˆ˜๋Š” ๋Œ์–ด์˜ค๊ธฐ ์š”์ฒญ์— ๋Œ€ํ•ด ๋ณ€๊ฒฝ๋œ ๋ชจ๋“  ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. commentIfDataFilesChanged ํ•จ์ˆ˜๋Š” getChangedFiles ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋Œ์–ด์˜ค๊ธฐ ์š”์ฒญ์ด ๋ณ€๊ฒฝ๋œ ํŒŒ์ผ์ด ํŒŒ์ผ ๊ฒฝ๋กœ์— /data/๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ ํ•จ์ˆ˜๋Š” ๋Œ์–ด์˜ค๊ธฐ ์š”์ฒญ์— ๋Œ€ํ•ด ์„ค๋ช…์„ ๋‹ฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

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

๋‹ค์Œ ๋‹จ๊ณ„