Spaces:
Configuration error
Configuration error
| 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<string, string>([ | |
| ["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<string, RepoLabel> { | |
| 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])); | |
| } | |