217 lines
5.6 KiB
TypeScript
217 lines
5.6 KiB
TypeScript
export type Platform = "github" | "gitea";
|
|
|
|
export async function getDefaultBranch(): Promise<string> {
|
|
try {
|
|
const result =
|
|
await Bun.$`git symbolic-ref refs/remotes/origin/HEAD`.quiet().text();
|
|
return result.trim().replace("refs/remotes/origin/", "");
|
|
} catch {
|
|
try {
|
|
const branches = await Bun.$`git branch -r`.quiet().text();
|
|
for (const line of branches.split("\n")) {
|
|
const trimmed = line.trim();
|
|
if (trimmed === "origin/main" || trimmed === "origin/master") {
|
|
return trimmed.replace("origin/", "");
|
|
}
|
|
}
|
|
} catch {}
|
|
return "main";
|
|
}
|
|
}
|
|
|
|
export async function getBranchName(): Promise<string> {
|
|
const result =
|
|
await Bun.$`git rev-parse --abbrev-ref HEAD`.quiet().text();
|
|
return result.trim();
|
|
}
|
|
|
|
export async function getBranchPushStatus(): Promise<{
|
|
pushed: boolean;
|
|
upstream: string | null;
|
|
}> {
|
|
try {
|
|
const upstream = (await Bun.$`git rev-parse --abbrev-ref --symbolic-full-name @{u}`.quiet().text()).trim();
|
|
const localHead = (await Bun.$`git rev-parse HEAD`.quiet().text()).trim();
|
|
const remoteHead = (await Bun.$`git rev-parse @{u}`.quiet().text()).trim();
|
|
return { pushed: localHead === remoteHead, upstream };
|
|
} catch {
|
|
return { pushed: false, upstream: null };
|
|
}
|
|
}
|
|
|
|
export async function pushCurrentBranch(branch: string, upstream: string | null): Promise<void> {
|
|
const args = upstream
|
|
? ["push"]
|
|
: ["push", "-u", "origin", branch];
|
|
|
|
const proc = Bun.spawn(["git", ...args], {
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
const exitCode = await proc.exited;
|
|
const stdout = await new Response(proc.stdout).text();
|
|
const stderr = await new Response(proc.stderr).text();
|
|
|
|
if (exitCode !== 0) {
|
|
throw new Error(stderr.trim() || stdout.trim() || `git push failed (exit code ${exitCode})`);
|
|
}
|
|
}
|
|
|
|
export async function getBranchCommits(base: string): Promise<string[]> {
|
|
try {
|
|
const result =
|
|
await Bun.$`git log --oneline origin/${base}..HEAD`.quiet().text();
|
|
return result.trim().split("\n").filter(Boolean);
|
|
} catch {
|
|
try {
|
|
const result =
|
|
await Bun.$`git log --oneline ${base}..HEAD`.quiet().text();
|
|
return result.trim().split("\n").filter(Boolean);
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function getBranchDiff(base: string): Promise<string> {
|
|
try {
|
|
const result =
|
|
await Bun.$`git diff ${base}...HEAD`.quiet().text();
|
|
return result.trim();
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function parseRemoteHostname(url: string): string | null {
|
|
const hostname = url
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/^(https?:\/\/|ssh:\/\/|git:\/\/)/, "")
|
|
.replace(/^[^@]+@/, "")
|
|
.split(/[:/]/)[0];
|
|
return hostname || null;
|
|
}
|
|
|
|
export async function detectPlatform(): Promise<Platform | null> {
|
|
try {
|
|
const url = await Bun.$`git remote get-url origin`.quiet().text();
|
|
const hostname = parseRemoteHostname(url);
|
|
|
|
if (!hostname) return null;
|
|
|
|
if (hostname === "github.com") return "github";
|
|
if (hostname.includes("gitea")) return "gitea";
|
|
return null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function getRemoteHostname(): Promise<string | null> {
|
|
try {
|
|
const url = await Bun.$`git remote get-url origin`.quiet().text();
|
|
return parseRemoteHostname(url);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function checkCLI(platform: Platform): string | null {
|
|
const bin = platform === "github" ? "gh" : "tea";
|
|
const path = Bun.which(bin);
|
|
if (!path) {
|
|
if (platform === "github") {
|
|
return "GitHub CLI (gh) not found. Install: brew install gh";
|
|
}
|
|
return "Gitea CLI (tea) not found. Install from: https://gitea.com/gitea/tea";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export async function checkAuth(platform: Platform): Promise<string | null> {
|
|
if (platform === "github") {
|
|
try {
|
|
await Bun.$`gh auth status`.quiet();
|
|
return null;
|
|
} catch {
|
|
return "Not authenticated with GitHub CLI. Run: gh auth login";
|
|
}
|
|
}
|
|
|
|
try {
|
|
const result = await Bun.$`tea logins list`.quiet().text();
|
|
if (result.trim()) return null;
|
|
return "Not authenticated with Gitea CLI. Run: tea login add";
|
|
} catch {
|
|
return "Not authenticated with Gitea CLI. Run: tea login add";
|
|
}
|
|
}
|
|
|
|
export async function createPR(
|
|
platform: Platform,
|
|
title: string,
|
|
body: string,
|
|
base: string,
|
|
draft: boolean,
|
|
): Promise<string> {
|
|
if (platform === "github") {
|
|
const args = [
|
|
"pr",
|
|
"create",
|
|
"--title",
|
|
title,
|
|
"--body",
|
|
body,
|
|
"--base",
|
|
base,
|
|
];
|
|
if (draft) args.push("--draft");
|
|
|
|
const proc = Bun.spawn(["gh", ...args], {
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
const exitCode = await proc.exited;
|
|
const stdout = await new Response(proc.stdout).text();
|
|
const stderr = await new Response(proc.stderr).text();
|
|
|
|
if (exitCode !== 0) {
|
|
throw new Error(
|
|
stderr.trim() || `gh pr create failed (exit code ${exitCode})`,
|
|
);
|
|
}
|
|
|
|
const match = stdout.match(/(https?:\/\/[^\s]+)/);
|
|
return match?.[1] ?? stdout.trim();
|
|
}
|
|
|
|
const args = [
|
|
"pulls",
|
|
"create",
|
|
"--title",
|
|
title,
|
|
"--description",
|
|
body,
|
|
"--base",
|
|
base,
|
|
];
|
|
|
|
const proc = Bun.spawn(["tea", ...args], {
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
const exitCode = await proc.exited;
|
|
const stdout = await new Response(proc.stdout).text();
|
|
const stderr = await new Response(proc.stderr).text();
|
|
|
|
if (exitCode !== 0) {
|
|
throw new Error(
|
|
stderr.trim() || `tea pulls create failed (exit code ${exitCode})`,
|
|
);
|
|
}
|
|
|
|
const match = stdout.match(/(https?:\/\/[^\s]+)/);
|
|
return match?.[1] ?? stdout.trim();
|
|
}
|