📦 deps(thirdparty): update snapshots
This commit is contained in:
Executable
+282
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* GitHub gatherer for status
|
||||
*
|
||||
* Collects data via `gh` CLI:
|
||||
* - Open PRs with CI status and review decisions
|
||||
* - Recent workflow runs
|
||||
*/
|
||||
|
||||
import { parseArgs } from "node:util";
|
||||
import { parseTimeConstraint, toCutoffDate } from "../lib/time";
|
||||
import type {
|
||||
GathererResult,
|
||||
GitHubData,
|
||||
GitHubPR,
|
||||
GitHubWorkflowRun,
|
||||
} from "../lib/types";
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv.slice(2),
|
||||
options: {
|
||||
time: { type: "string", short: "t", default: "24h" },
|
||||
repo: { type: "string", short: "r" },
|
||||
help: { type: "boolean", short: "h" },
|
||||
},
|
||||
});
|
||||
|
||||
if (values.help) {
|
||||
console.log(`
|
||||
github-gatherer.ts - Gather GitHub PR and CI data
|
||||
|
||||
Usage:
|
||||
./github-gatherer.ts [options]
|
||||
|
||||
Options:
|
||||
-t, --time <constraint> Time constraint (24h, 7d, 2w) [default: 24h]
|
||||
-r, --repo <owner/repo> Repository [default: current repo]
|
||||
-h, --help Show this help
|
||||
|
||||
Output:
|
||||
JSON GathererResult with GitHubData
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of running a gh CLI command.
|
||||
*/
|
||||
interface GhOutput<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a gh CLI command and parses JSON output.
|
||||
* @param args - Arguments to pass to gh
|
||||
* @returns Parsed output or error
|
||||
*/
|
||||
async function runGh<T>(args: string[]): Promise<GhOutput<T>> {
|
||||
const repoArgs = values.repo ? ["-R", values.repo] : [];
|
||||
|
||||
const proc = Bun.spawn(["gh", ...repoArgs, ...args], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const stdout = await new Response(proc.stdout).text();
|
||||
const stderr = await new Response(proc.stderr).text();
|
||||
const exitCode = await proc.exited;
|
||||
|
||||
if (exitCode !== 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: stderr || `gh exited with code ${exitCode}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(stdout);
|
||||
return { success: true, data };
|
||||
} catch {
|
||||
return { success: false, error: `Failed to parse gh output: ${stdout}` };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if gh CLI is installed.
|
||||
* @returns True if gh is available
|
||||
*/
|
||||
async function checkGhAvailable(): Promise<boolean> {
|
||||
const proc = Bun.spawn(["which", "gh"], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
const exitCode = await proc.exited;
|
||||
return exitCode === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if gh CLI is authenticated.
|
||||
* @returns True if authenticated
|
||||
*/
|
||||
async function checkGhAuth(): Promise<boolean> {
|
||||
const proc = Bun.spawn(["gh", "auth", "status"], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
const exitCode = await proc.exited;
|
||||
return exitCode === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current repository name (owner/repo format).
|
||||
* @returns Repository name or null if not in a repo
|
||||
*/
|
||||
async function getRepoName(): Promise<string | null> {
|
||||
if (values.repo) return values.repo;
|
||||
|
||||
const proc = Bun.spawn(
|
||||
["gh", "repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"],
|
||||
{
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
},
|
||||
);
|
||||
|
||||
const stdout = await new Response(proc.stdout).text();
|
||||
const exitCode = await proc.exited;
|
||||
|
||||
if (exitCode !== 0) return null;
|
||||
return stdout.trim();
|
||||
}
|
||||
|
||||
// GraphQL query for PRs with status checks
|
||||
const PR_QUERY_FIELDS = [
|
||||
"number",
|
||||
"title",
|
||||
"state",
|
||||
"isDraft",
|
||||
"author",
|
||||
"updatedAt",
|
||||
"url",
|
||||
"headRefName",
|
||||
"statusCheckRollup",
|
||||
"reviewDecision",
|
||||
].join(",");
|
||||
|
||||
/**
|
||||
* Gathers GitHub data including PRs and workflow runs.
|
||||
* @returns Gatherer result with GitHub data
|
||||
*/
|
||||
async function gatherGitHubData(): Promise<GathererResult<GitHubData>> {
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
// Check gh CLI availability
|
||||
const ghAvailable = await checkGhAvailable();
|
||||
if (!ghAvailable) {
|
||||
return {
|
||||
source: "github",
|
||||
status: "unavailable",
|
||||
reason: "gh CLI not installed",
|
||||
timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
// Check authentication
|
||||
const ghAuth = await checkGhAuth();
|
||||
if (!ghAuth) {
|
||||
return {
|
||||
source: "github",
|
||||
status: "unavailable",
|
||||
reason: "gh CLI not authenticated (run: gh auth login)",
|
||||
timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
// Get repo name
|
||||
const repo = await getRepoName();
|
||||
if (!repo) {
|
||||
return {
|
||||
source: "github",
|
||||
status: "unavailable",
|
||||
reason: "Not in a GitHub repository",
|
||||
timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
// Parse time constraint
|
||||
const timeValue = values.time ?? "24h";
|
||||
let timeMs: number;
|
||||
try {
|
||||
timeMs = parseTimeConstraint(timeValue);
|
||||
} catch (e) {
|
||||
return {
|
||||
source: "github",
|
||||
status: "error",
|
||||
error: e instanceof Error ? e.message : "Invalid time constraint",
|
||||
timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
const cutoff = toCutoffDate(timeMs);
|
||||
const _cutoffDate = cutoff.toISOString().split("T")[0]; // YYYY-MM-DD for search
|
||||
|
||||
// Gather data in parallel
|
||||
const [prsResult, runsResult] = await Promise.all([
|
||||
// Get open PRs (no date filter needed - we want all open)
|
||||
runGh<GitHubPR[]>([
|
||||
"pr",
|
||||
"list",
|
||||
"--state=open",
|
||||
"--json",
|
||||
PR_QUERY_FIELDS,
|
||||
"--limit=20",
|
||||
]),
|
||||
// Get recent workflow runs
|
||||
runGh<GitHubWorkflowRun[]>([
|
||||
"run",
|
||||
"list",
|
||||
"--json",
|
||||
"name,status,conclusion,createdAt,url",
|
||||
"--limit=20",
|
||||
]),
|
||||
]);
|
||||
|
||||
if (!prsResult.success && !runsResult.success) {
|
||||
return {
|
||||
source: "github",
|
||||
status: "error",
|
||||
error:
|
||||
prsResult.error || runsResult.error || "Failed to fetch GitHub data",
|
||||
timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
// Transform PR data to match our types
|
||||
const openPRs: GitHubPR[] = (prsResult.data || []).map(
|
||||
(pr: Record<string, unknown>) => ({
|
||||
number: pr.number as number,
|
||||
title: pr.title as string,
|
||||
state: pr.state as "OPEN" | "CLOSED" | "MERGED",
|
||||
isDraft: pr.isDraft as boolean,
|
||||
author: pr.author as { login: string },
|
||||
updatedAt: pr.updatedAt as string,
|
||||
url: pr.url as string,
|
||||
headRefName: pr.headRefName as string,
|
||||
statusCheckRollup: pr.statusCheckRollup as GitHubPR["statusCheckRollup"],
|
||||
reviewDecision: pr.reviewDecision as GitHubPR["reviewDecision"],
|
||||
}),
|
||||
);
|
||||
|
||||
// Filter workflow runs by time
|
||||
const allRuns = runsResult.data || [];
|
||||
const recentRuns: GitHubWorkflowRun[] = allRuns
|
||||
.filter(
|
||||
(run: Record<string, unknown>) =>
|
||||
new Date(run.createdAt as string) >= cutoff,
|
||||
)
|
||||
.map((run: Record<string, unknown>) => ({
|
||||
name: run.name as string,
|
||||
status: run.status as string,
|
||||
conclusion: run.conclusion as string | null,
|
||||
createdAt: run.createdAt as string,
|
||||
url: run.url as string,
|
||||
}));
|
||||
|
||||
return {
|
||||
source: "github",
|
||||
status: "success",
|
||||
data: {
|
||||
repo,
|
||||
openPRs,
|
||||
recentRuns,
|
||||
},
|
||||
timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
// Main execution
|
||||
const result = await gatherGitHubData();
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
Reference in New Issue
Block a user