Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

pi/agent/skillz/github/github-cli/scripts/gh_code_search.mjs

Raw
#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";

function values(name, short) {
	const out = [];
	const a = process.argv;
	for (let i = 2; i < a.length; i++)
		if (a[i] === name || (short && a[i] === short)) out.push(a[++i]);
	return out;
}
function arg(name, short, def) {
	const v = values(name, short);
	return v.length ? v.at(-1) : def;
}
function has(name) {
	return process.argv.includes(name);
}
function positional() {
	return process.argv
		.slice(2)
		.find(
			(a, i, arr) =>
				!a.startsWith("-") &&
				![
					"--limit",
					"-L",
					"--language",
					"--filename",
					"--extension",
					"--repo",
					"-R",
					"--owner",
					"--match",
					"--size",
					"--min-matches",
					"--output",
					"-o",
					"--sort-by",
					"--output-file",
					"--timeout",
				].includes(arr[i - 1]),
		);
}
function checkGh() {
	const r = spawnSync("gh", ["auth", "status"], { stdio: "ignore" });
	return !r.error && r.status === 0;
}
function build(query) {
	const cmd = ["gh", "search", "code", query];
	const add = (flag, value) => {
		if (value) cmd.push(flag, String(value));
	};
	add("--limit", arg("--limit", "-L", "30"));
	add("--language", arg("--language"));
	add("--filename", arg("--filename"));
	add("--extension", arg("--extension"));
	for (const r of values("--repo", "-R")) cmd.push("--repo", r);
	for (const o of values("--owner")) cmd.push("--owner", o);
	add("--match", arg("--match"));
	add("--size", arg("--size"));
	cmd.push("--json", "path,repository,sha,textMatches,url");
	return cmd;
}
function execute(cmd, timeout) {
	const r = spawnSync(cmd[0], cmd.slice(1), {
		encoding: "utf8",
		timeout: timeout * 1000,
		maxBuffer: 64 * 1024 * 1024,
	});
	if (r.error?.code === "ETIMEDOUT")
		throw new Error(
			`Search timed out after ${timeout} seconds. Try a simpler query or increase --timeout.`,
		);
	if (r.status !== 0) {
		const e = (r.stderr ?? "").trim();
		if (e.includes("HTTP 403") && e.toLowerCase().includes("rate limit"))
			throw new Error(
				`GitHub API rate limit exceeded. Please wait and try again later.\nDetails: ${e}`,
			);
		if (e.includes("HTTP 408"))
			throw new Error(
				`Search query timed out. Try a simpler query or try again later.\nDetails: ${e}`,
			);
		throw new Error(`GitHub search failed: ${e}`);
	}
	return r.stdout.trim() ? JSON.parse(r.stdout) : [];
}
function filter(results) {
	let out = results;
	if (has("--exclude-forks")) out = out.filter((r) => !r.repository?.isFork);
	if (has("--exclude-private"))
		out = out.filter((r) => !r.repository?.isPrivate);
	const min = Number(arg("--min-matches", undefined, "0"));
	if (min) out = out.filter((r) => (r.textMatches ?? []).length >= min);
	return out;
}
function sort(results, by) {
	if (by === "matches")
		return results.toSorted(
			(a, b) => (b.textMatches ?? []).length - (a.textMatches ?? []).length,
		);
	if (by === "repo")
		return results.toSorted((a, b) =>
			(a.repository?.nameWithOwner ?? "").localeCompare(
				b.repository?.nameWithOwner ?? "",
			),
		);
	if (by === "path")
		return results.toSorted((a, b) =>
			(a.path ?? "").localeCompare(b.path ?? ""),
		);
	return results;
}
function pretty(results) {
	if (!results.length) return "No results found.";
	const out = [`Found ${results.length} result(s)\n`, "=".repeat(80)];
	results.forEach((r, i) => {
		let frag = r.textMatches?.[0]?.fragment ?? "";
		if (frag.length > 100) frag = `${frag.slice(0, 97)}...`;
		out.push(
			`\n${i + 1}. ${r.repository?.nameWithOwner ?? "Unknown"}:${r.path ?? "Unknown"}`,
			`   URL: ${r.url ?? ""}`,
			`   Matches: ${(r.textMatches ?? []).length}`,
		);
		if (frag) out.push(`   Preview: ${frag}`);
		out.push("-".repeat(80));
	});
	return out.join("\n");
}
function summary(results) {
	if (!results.length) return "No results found.";
	const repos = {},
		exts = {};
	let total = 0;
	for (const r of results) {
		const repo = r.repository?.nameWithOwner ?? "Unknown";
		repos[repo] = (repos[repo] ?? 0) + 1;
		total += (r.textMatches ?? []).length;
		const p = r.path ?? "";
		if (p.includes(".")) {
			const e = p.split(".").at(-1);
			exts[e] = (exts[e] ?? 0) + 1;
		}
	}
	const out = [
		"SEARCH SUMMARY",
		"=".repeat(80),
		`Total files found: ${results.length}`,
		`Total text matches: ${total}`,
		`Unique repositories: ${Object.keys(repos).length}`,
		"\nTop Repositories:",
	];
	for (const [k, v] of Object.entries(repos)
		.sort((a, b) => b[1] - a[1])
		.slice(0, 10))
		out.push(`  ${k}: ${v} file(s)`);
	out.push("\nFile Extensions:");
	for (const [k, v] of Object.entries(exts).sort((a, b) => b[1] - a[1]))
		out.push(`  .${k}: ${v} file(s)`);
	return out.join("\n");
}
function main() {
	if (!checkGh()) {
		console.error(
			"Error: GitHub CLI (gh) is not installed or not authenticated.",
		);
		console.error("Install from: https://cli.github.com/");
		console.error("Then run: gh auth login");
		return 1;
	}
	const q = positional();
	if (!q) {
		console.error("Error: query required");
		return 1;
	}
	const results = sort(
		filter(execute(build(q), Number(arg("--timeout", undefined, "30")))),
		arg("--sort-by"),
	);
	const fmt = arg("--output", "-o", "pretty");
	const output =
		fmt === "json"
			? JSON.stringify(results, null, 2)
			: fmt === "summary"
				? summary(results)
				: pretty(results);
	const file = arg("--output-file");
	if (file) {
		mkdirSync(dirname(file), { recursive: true });
		writeFileSync(file, output, "utf8");
		console.error(`Results written to ${file}`);
	} else console.log(output);
	return 0;
}
export const __test = {
	build,
	filter,
	positional,
	pretty,
	sort,
	summary,
};

if (process.argv[1] === fileURLToPath(import.meta.url)) {
	try {
		process.exit(main());
	} catch (e) {
		console.error(`Error: ${e.message}`);
		process.exit(1);
	}
}