| #!/usr/bin/env tsx |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import * as url from "url"; |
| import yargs from "yargs"; |
| import { hideBin } from "yargs/helpers"; |
| import { GitHubRestApi, type GitHubApi } from "./api.js"; |
| import { computePrPatch, linkedIssueNumbers } from "./rules.js"; |
| import { applyPatch, printPlan, resolveToken } from "./sync-issue.js"; |
| import type { Patch } from "./types.js"; |
|
|
| export interface PlanPrInput { |
| prNumber: number; |
| api: GitHubApi; |
| } |
|
|
| |
| |
| export async function planPr({ prNumber, api }: PlanPrInput): Promise<Patch> { |
| const pr = await api.getPullRequest(prNumber); |
| const currentLabels = new Set(pr.labels.map((l) => l.name)); |
| const issueNumbers = linkedIssueNumbers(pr.body); |
|
|
| const linkedIssues = await Promise.all( |
| issueNumbers.map((n) => |
| api.getIssue(n).catch((err) => { |
| console.warn(`Could not fetch linked issue #${n}: ${String(err)}, skipping.`); |
| return null; |
| }) |
| ) |
| ).then((results) => results.filter((i): i is NonNullable<typeof i> => i !== null)); |
|
|
| return computePrPatch({ pr, currentLabels, linkedIssues }); |
| } |
|
|
| |
| |
| export async function syncPr({ prNumber, api }: PlanPrInput): Promise<Patch> { |
| const patch = await planPr({ prNumber, api }); |
| await applyPatch(patch, api); |
| return patch; |
| } |
|
|
| |
| |
| |
|
|
| if (process.argv[1] === url.fileURLToPath(import.meta.url)) { |
| const argv = await yargs(hideBin(process.argv)) |
| .option("pr", { type: "number", demandOption: true, description: "PR number" }) |
| .option("repo", { type: "string", demandOption: true, description: "owner/repo" }) |
| .option("token", { type: "string", description: "GitHub token (falls back to GITHUB_TOKEN env var or `gh auth token`)" }) |
| .option("execute", { |
| type: "boolean", |
| default: false, |
| description: "Apply the patch. Without this flag only the plan is printed.", |
| }) |
| .strict() |
| .parseAsync(); |
|
|
| const [owner, repo] = argv.repo.split("/") as [string, string]; |
| const token = resolveToken(argv.token); |
| const api = new GitHubRestApi(owner, repo, token); |
|
|
| if (argv.execute) { |
| const patch = await syncPr({ prNumber: argv.pr, api }); |
| if (patch.ops.length === 0) { |
| console.log(`PR #${argv.pr}: already in sync, no changes needed.`); |
| } |
| } else { |
| const patch = await planPr({ prNumber: argv.pr, api }); |
| printPlan(patch, `PR #${argv.pr}`); |
| } |
| } |
|
|