e8c6c5c917
---ci--- phase: 5 milestone: v1.0 plan: 05 task: SHIP-01-04 MULTI-01 MULTI-02 status: execute ---/ci---
170 lines
4.2 KiB
TypeScript
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")}`;
|
|
} |