Files
ci/src/core/gitea.ts
T
Jon Chery e8c6c5c917 feat(P05): ship infrastructure — Gitea API client, release notes, npm publishConfig, ciagent projects cmd, --project flag
---ci---
phase: 5
milestone: v1.0
plan: 05
task: SHIP-01-04 MULTI-01 MULTI-02
status: execute
---/ci---
2026-05-29 18:15:58 +00:00

170 lines
4.2 KiB
TypeScript

import { execSync } from "node:child_process";
export interface GiteaReleaseConfig {
baseUrl: string;
token: string;
owner: string;
repo: string;
}
export interface GiteaRelease {
id: number;
tag_name: string;
name: string;
body: string;
url: string;
html_url: string;
draft: boolean;
prerelease: boolean;
}
export class GiteaClient {
private config: GiteaReleaseConfig;
constructor(config: GiteaReleaseConfig) {
this.config = config;
}
async createRelease(params: {
tag_name: string;
name: string;
body: string;
draft?: boolean;
prerelease?: boolean;
}): Promise<GiteaRelease> {
const url = `${this.config.baseUrl}/api/v1/repos/${this.config.owner}/${this.config.repo}/releases`;
const response = await fetch(url, {
method: "POST",
headers: {
"Authorization": `token ${this.config.token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
tag_name: params.tag_name,
name: params.name,
body: params.body,
draft: params.draft ?? false,
prerelease: params.prerelease ?? false,
}),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`Gitea API error: ${response.status} ${text}`);
}
return response.json() as Promise<GiteaRelease>;
}
async listReleases(): Promise<GiteaRelease[]> {
const url = `${this.config.baseUrl}/api/v1/repos/${this.config.owner}/${this.config.repo}/releases`;
const response = await fetch(url, {
method: "GET",
headers: {
"Authorization": `token ${this.config.token}`,
},
});
if (!response.ok) {
throw new Error(`Gitea API error: ${response.status}`);
}
return response.json() as Promise<GiteaRelease[]>;
}
async getReleaseByTag(tag: string): Promise<GiteaRelease | null> {
const url = `${this.config.baseUrl}/api/v1/repos/${this.config.owner}/${this.config.repo}/releases/tags/${tag}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Authorization": `token ${this.config.token}`,
},
});
if (response.status === 404) {
return null;
}
if (!response.ok) {
throw new Error(`Gitea API error: ${response.status}`);
}
return response.json() as Promise<GiteaRelease>;
}
}
export function generateReleaseNotes(projectPath: string, fromTag: string | null, toTag: string): string {
let gitLogCmd: string;
if (fromTag) {
gitLogCmd = `git log ${fromTag}..${toTag} --oneline`;
} else {
gitLogCmd = `git log ${toTag} --oneline`;
}
let logOutput: string;
try {
logOutput = execSync(gitLogCmd, { cwd: projectPath, encoding: "utf-8" }).trim();
} catch {
return `## What's Changed\n\nNo commits found between ${fromTag || "beginning"} and ${toTag}.\n`;
}
if (!logOutput) {
return `## What's Changed\n\nNo commits found between ${fromTag || "beginning"} and ${toTag}.\n`;
}
const lines = logOutput.split("\n").filter(Boolean);
const featCommits: string[] = [];
const fixCommits: string[] = [];
const testCommits: string[] = [];
const otherCommits: string[] = [];
for (const line of lines) {
const subject = line.replace(/^[a-f0-9]+\s+/, "");
if (/^feat/i.test(subject)) {
featCommits.push(subject);
} else if (/^fix/i.test(subject)) {
fixCommits.push(subject);
} else if (/^test/i.test(subject)) {
testCommits.push(subject);
} else {
otherCommits.push(subject);
}
}
const sections: string[] = [];
if (featCommits.length > 0) {
sections.push("### New Features\n");
for (const c of featCommits) {
sections.push(`- ${c}`);
}
sections.push("");
}
if (fixCommits.length > 0) {
sections.push("### Bug Fixes\n");
for (const c of fixCommits) {
sections.push(`- ${c}`);
}
sections.push("");
}
if (testCommits.length > 0) {
sections.push("### Tests\n");
for (const c of testCommits) {
sections.push(`- ${c}`);
}
sections.push("");
}
if (otherCommits.length > 0) {
sections.push("### Other Changes\n");
for (const c of otherCommits) {
sections.push(`- ${c}`);
}
sections.push("");
}
return `## What's Changed\n\n${sections.join("\n")}`;
}