import { execFileSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; type RepoLabel = { name: string; color?: string; }; const COLOR_BY_PREFIX = new Map([ ["channel", "1d76db"], ["app", "6f42c1"], ["extensions", "0e8a16"], ["docs", "0075ca"], ["cli", "f9d0c4"], ["gateway", "d4c5f9"], ]); const configPath = resolve(".github/labeler.yml"); const labelNames = extractLabelNames(readFileSync(configPath, "utf8")); if (!labelNames.length) { throw new Error("labeler.yml must declare at least one label."); } const repo = resolveRepo(); const existing = fetchExistingLabels(repo); const missing = labelNames.filter((label) => !existing.has(label)); if (!missing.length) { console.log("All labeler labels already exist."); process.exit(0); } for (const label of missing) { const color = pickColor(label); execFileSync( "gh", [ "api", "-X", "POST", `repos/${repo}/labels`, "-f", `name=${label}`, "-f", `color=${color}`, ], { stdio: "inherit" }, ); console.log(`Created label: ${label}`); } function extractLabelNames(contents: string): string[] { const labels: string[] = []; for (const line of contents.split("\n")) { if (!line.trim() || line.trimStart().startsWith("#")) { continue; } if (/^\s/.test(line)) { continue; } const match = line.match(/^(["'])(.+)\1\s*:/) ?? line.match(/^([^:]+):/); if (match) { const name = (match[2] ?? match[1] ?? "").trim(); if (name) { labels.push(name); } } } return labels; } function pickColor(label: string): string { const prefix = label.includes(":") ? label.split(":", 1)[0].trim() : label.trim(); return COLOR_BY_PREFIX.get(prefix) ?? "ededed"; } function resolveRepo(): string { const remote = execFileSync("git", ["config", "--get", "remote.origin.url"], { encoding: "utf8", }).trim(); if (!remote) { throw new Error("Unable to determine repository from git remote."); } if (remote.startsWith("git@github.com:")) { return remote.replace("git@github.com:", "").replace(/\.git$/, ""); } if (remote.startsWith("https://github.com/")) { return remote.replace("https://github.com/", "").replace(/\.git$/, ""); } throw new Error(`Unsupported GitHub remote: ${remote}`); } function fetchExistingLabels(repo: string): Map { const raw = execFileSync( "gh", ["api", `repos/${repo}/labels?per_page=100`, "--paginate"], { encoding: "utf8" }, ); const labels = JSON.parse(raw) as RepoLabel[]; return new Map(labels.map((label) => [label.name, label])); }