mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-08-07 10:04:59 +00:00
chore: Fix full auto release process (#7663)
This commit is contained in:
@@ -84,7 +84,7 @@ jobs:
|
|||||||
RELEASE_BETA_VERSION: ${{ inputs.version }}
|
RELEASE_BETA_VERSION: ${{ inputs.version }}
|
||||||
RELEASE_PREVIOUS_VERSION: ${{ inputs.previous_version }}
|
RELEASE_PREVIOUS_VERSION: ${{ inputs.previous_version }}
|
||||||
DRY_RUN: ${{ inputs.dry_run }}
|
DRY_RUN: ${{ inputs.dry_run }}
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
run: npm run release-beta
|
run: npm run release-beta
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
RELEASE_VERSION: ${{ inputs.version }}
|
RELEASE_VERSION: ${{ inputs.version }}
|
||||||
RELEASE_PREVIOUS_VERSION: ${{ inputs.previous_version }}
|
RELEASE_PREVIOUS_VERSION: ${{ inputs.previous_version }}
|
||||||
DRY_RUN: ${{ inputs.dry_run }}
|
DRY_RUN: ${{ inputs.dry_run }}
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
run: npm run release-final
|
run: npm run release-final
|
||||||
|
|||||||
@@ -27,11 +27,7 @@ const outputFormat = JSON.stringify({
|
|||||||
others: [192, 21],
|
others: [192, 21],
|
||||||
});
|
});
|
||||||
|
|
||||||
const prompt = `Input Data:
|
const prompt = `Input Data: {{ input }}
|
||||||
\`\`\`json
|
|
||||||
{{ input }}
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
LLM Task:
|
LLM Task:
|
||||||
- Output a one-line JSON object in the following format:
|
- Output a one-line JSON object in the following format:
|
||||||
{{ outputFormat }}
|
{{ outputFormat }}
|
||||||
@@ -190,6 +186,75 @@ export async function generateChangelog(previousVersion, categorizedMap) {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Changelog using AI
|
||||||
|
* @param {string} previousVersion Previous Version Tag
|
||||||
|
* @returns {Promise<string>} Changelog Content
|
||||||
|
*/
|
||||||
|
export async function generateChangelogAI(previousVersion) {
|
||||||
|
// 1. Generate changelog
|
||||||
|
let categorizedMap = null;
|
||||||
|
|
||||||
|
console.log("Running opencode to categorize PRs...");
|
||||||
|
const llmPrompt = (await getPrompt(previousVersion)).replaceAll("\n", " ");
|
||||||
|
|
||||||
|
console.log(llmPrompt);
|
||||||
|
|
||||||
|
console.log("Running opencode with the above prompt...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = childProcess.spawnSync(
|
||||||
|
"opencode",
|
||||||
|
["run", "-m", "opencode/big-pickle", "--format", "json", llmPrompt],
|
||||||
|
{
|
||||||
|
encoding: "utf-8",
|
||||||
|
timeout: 120000,
|
||||||
|
shell: true,
|
||||||
|
cwd: process.cwd(),
|
||||||
|
env: process.env,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.status === 0 && result.stdout) {
|
||||||
|
// Parse NDJSON output: find "type":"text" line
|
||||||
|
for (const line of result.stdout.trim().split("\n")) {
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(line);
|
||||||
|
if (obj.type === "text" && obj.part?.text) {
|
||||||
|
const jsonMatch = obj.part.text.match(/\{[\s\S]*\}/);
|
||||||
|
if (jsonMatch) {
|
||||||
|
categorizedMap = JSON.parse(jsonMatch[0]);
|
||||||
|
console.log("LLM categorization applied.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// skip unparseable lines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!categorizedMap) {
|
||||||
|
console.warn("No JSON found in opencode response.");
|
||||||
|
console.warn(result.stdout);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("opencode failed or returned no output (status:", result.status, ")");
|
||||||
|
if (result.stderr) {
|
||||||
|
console.warn("stderr:", result.stderr.slice(0, 500));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Failed to run opencode:", e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!categorizedMap) {
|
||||||
|
categorizedMap = {};
|
||||||
|
console.log("OpenCode unavailable, using uncategorized fallback.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await generateChangelog(previousVersion, categorizedMap);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} previousVersion Previous Version Tag
|
* @param {string} previousVersion Previous Version Tag
|
||||||
* @param {boolean} removeAuthor Whether to strip the author field from the returned PR list
|
* @param {boolean} removeAuthor Whether to strip the author field from the returned PR list
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { createRelease } from "./lib.mjs";
|
||||||
|
import { generateChangelogAI } from "../generate-changelog.mjs";
|
||||||
|
|
||||||
|
const version = process.env.RELEASE_VERSION || process.env.RELEASE_BETA_VERSION;
|
||||||
|
const previousVersion = process.env.RELEASE_PREVIOUS_VERSION;
|
||||||
|
|
||||||
|
if (!version) {
|
||||||
|
console.error("RELEASE_VERSION is required");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!previousVersion) {
|
||||||
|
console.error("RELEASE_PREVIOUS_VERSION is required");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBeta = !!process.env.RELEASE_BETA_VERSION;
|
||||||
|
const changelog = await generateChangelogAI(previousVersion);
|
||||||
|
await createRelease(version, changelog, isBeta, "");
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import * as childProcess from "child_process";
|
import * as childProcess from "child_process";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { generateChangelog, getPrompt } from "../generate-changelog.mjs";
|
import { generateChangelogAI } from "../generate-changelog.mjs";
|
||||||
|
import { createRelease } from "./lib.mjs";
|
||||||
|
|
||||||
const version = process.env.RELEASE_VERSION || process.env.RELEASE_BETA_VERSION;
|
const version = process.env.RELEASE_VERSION || process.env.RELEASE_BETA_VERSION;
|
||||||
const previousVersion = process.env.RELEASE_PREVIOUS_VERSION;
|
const previousVersion = process.env.RELEASE_PREVIOUS_VERSION;
|
||||||
@@ -41,96 +42,12 @@ if (!fs.existsSync(prNumberFile)) {
|
|||||||
}
|
}
|
||||||
const prNumber = fs.readFileSync(prNumberFile, "utf-8").trim();
|
const prNumber = fs.readFileSync(prNumberFile, "utf-8").trim();
|
||||||
|
|
||||||
// 1. Generate changelog
|
|
||||||
let categorizedMap = null;
|
|
||||||
|
|
||||||
console.log("Running opencode to categorize PRs...");
|
|
||||||
const llmPrompt = await getPrompt(previousVersion);
|
|
||||||
try {
|
|
||||||
const result = childProcess.spawnSync(
|
|
||||||
"opencode",
|
|
||||||
["run", "-m", "opencode/big-pickle", "--format", "json", llmPrompt],
|
|
||||||
{
|
|
||||||
encoding: "utf-8",
|
|
||||||
timeout: 120000,
|
|
||||||
cwd: process.cwd(),
|
|
||||||
env: process.env,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.status === 0 && result.stdout) {
|
|
||||||
// Parse NDJSON output: find "type":"text" line
|
|
||||||
for (const line of result.stdout.trim().split("\n")) {
|
|
||||||
try {
|
|
||||||
const obj = JSON.parse(line);
|
|
||||||
if (obj.type === "text" && obj.part?.text) {
|
|
||||||
const jsonMatch = obj.part.text.match(/\{[\s\S]*\}/);
|
|
||||||
if (jsonMatch) {
|
|
||||||
categorizedMap = JSON.parse(jsonMatch[0]);
|
|
||||||
console.log("LLM categorization applied.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// skip unparseable lines
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!categorizedMap) {
|
|
||||||
console.warn("No JSON found in opencode response.");
|
|
||||||
console.warn("Last 500 chars:", result.stdout.slice(-500));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn("opencode failed or returned no output (status:", result.status, ")");
|
|
||||||
if (result.stderr) {
|
|
||||||
console.warn("stderr:", result.stderr.slice(0, 500));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Failed to run opencode:", e.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!categorizedMap) {
|
|
||||||
categorizedMap = {};
|
|
||||||
console.log("OpenCode unavailable, using uncategorized fallback.");
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Generating changelog...");
|
console.log("Generating changelog...");
|
||||||
const changelog = await generateChangelog(previousVersion, categorizedMap);
|
const changelog = await generateChangelogAI(previousVersion);
|
||||||
console.log("Changelog generated.");
|
console.log("Changelog generated.");
|
||||||
|
|
||||||
// 2. Squash merge the PR
|
// 2. Squash merge the PR
|
||||||
console.log(`Squash merging PR #${prNumber}...`);
|
console.log(`Squash merging PR #${prNumber}...`);
|
||||||
execSync(`gh pr merge ${prNumber} --squash --delete-branch --subject "Update to ${version}" --admin`);
|
execSync(`gh pr merge ${prNumber} --squash --delete-branch --subject "Update to ${version}" --admin`);
|
||||||
|
|
||||||
// 3. Create draft release with changelog and dist.tar.gz
|
await createRelease(version, changelog, isBeta, distTarGz);
|
||||||
console.log(`Creating draft release ${version}...`);
|
|
||||||
if (!fs.existsSync(distTarGz)) {
|
|
||||||
console.error(`dist.tar.gz not found: ${distTarGz}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const releaseBody = `## ${version}
|
|
||||||
|
|
||||||
${changelog}`;
|
|
||||||
|
|
||||||
const releaseArgs = ["release", "create", version, distTarGz, "--draft", "--title", version, "--notes", releaseBody];
|
|
||||||
|
|
||||||
if (isBeta) {
|
|
||||||
releaseArgs.push("--prerelease");
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = childProcess.spawnSync("gh", releaseArgs, {
|
|
||||||
encoding: "utf-8",
|
|
||||||
stdio: "inherit",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.status !== 0) {
|
|
||||||
console.error("Failed to create release");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Release ${version} is ready (draft).`);
|
|
||||||
console.log("Next steps:");
|
|
||||||
console.log(` 1. Review the draft release: https://github.com/louislam/uptime-kuma/releases/tag/${version}`);
|
|
||||||
console.log(` 2. Edit if needed and publish.`);
|
|
||||||
|
|||||||
@@ -373,3 +373,51 @@ The \`dist.tar.gz\` archive will be available as an artifact in the [workflow ru
|
|||||||
console.log("Successfully created draft pull request");
|
console.log("Successfully created draft pull request");
|
||||||
return prNumber;
|
return prNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} version
|
||||||
|
* @param {string} distTarGz If empty, it will not be uploaded to the release
|
||||||
|
* @param {string} changelog
|
||||||
|
* @param {boolean} isBeta
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function createRelease(version, changelog, isBeta = false, distTarGz = undefined) {
|
||||||
|
// 3. Create draft release with changelog and dist.tar.gz
|
||||||
|
console.log(`Creating draft release ${version}...`);
|
||||||
|
|
||||||
|
const releaseBody = `## ${version}
|
||||||
|
|
||||||
|
${changelog}`;
|
||||||
|
|
||||||
|
let releaseArgs = ["release", "create", version];
|
||||||
|
|
||||||
|
if (distTarGz) {
|
||||||
|
if (!fs.existsSync(distTarGz)) {
|
||||||
|
console.error(`dist.tar.gz not found: ${distTarGz}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseArgs.push(distTarGz);
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseArgs = releaseArgs.concat(["--draft", "--title", version, "--notes", releaseBody]);
|
||||||
|
|
||||||
|
if (isBeta) {
|
||||||
|
releaseArgs.push("--prerelease");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = childProcess.spawnSync("gh", releaseArgs, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.status !== 0) {
|
||||||
|
console.error("Failed to create release");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Release ${version} is ready (draft).`);
|
||||||
|
console.log("Next steps:");
|
||||||
|
console.log(` 1. Review the draft release: https://github.com/louislam/uptime-kuma/releases/tag/${version}`);
|
||||||
|
console.log(` 2. Edit if needed and publish.`);
|
||||||
|
}
|
||||||
|
|||||||
+2
-1
@@ -66,7 +66,8 @@
|
|||||||
"start-dev-container": "cd docker && docker-compose -f docker-compose-dev.yml up --force-recreate",
|
"start-dev-container": "cd docker && docker-compose -f docker-compose-dev.yml up --force-recreate",
|
||||||
"rebase-pr-to-1.23.X": "node extra/rebase-pr.js 1.23.X",
|
"rebase-pr-to-1.23.X": "node extra/rebase-pr.js 1.23.X",
|
||||||
"reset-migrate-aggregate-table-state": "node extra/reset-migrate-aggregate-table-state.js",
|
"reset-migrate-aggregate-table-state": "node extra/reset-migrate-aggregate-table-state.js",
|
||||||
"generate-changelog": "node ./extra/generate-changelog.mjs"
|
"generate-changelog": "node ./extra/generate-changelog.mjs",
|
||||||
|
"create-release": "node ./extra/release/create-release.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "~1.8.22",
|
"@grpc/grpc-js": "~1.8.22",
|
||||||
|
|||||||
Reference in New Issue
Block a user