From bbda8eaca13bc3f5d2ef6bb3c4cc76410b82d10b Mon Sep 17 00:00:00 2001 From: dyzulk <66510723+dyzulk@users.noreply.github.com> Date: Mon, 19 Jan 2026 06:56:44 +0700 Subject: [PATCH] ci: add ai release notes generator to workflow --- .dockerignore | 1 + .github/scripts/generate-release-notes.js | 97 +++++++++++++++++++++++ .github/workflows/release.yml | 14 +++- .gitignore | 1 + package-lock.json | 15 +++- package.json | 3 +- 6 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 .github/scripts/generate-release-notes.js diff --git a/.dockerignore b/.dockerignore index 2e68746..cae8e7b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ .git .gitignore +.github .env node_modules deploy_package.tar.gz diff --git a/.github/scripts/generate-release-notes.js b/.github/scripts/generate-release-notes.js new file mode 100644 index 0000000..90ff82d --- /dev/null +++ b/.github/scripts/generate-release-notes.js @@ -0,0 +1,97 @@ +const { GoogleGenerativeAI } = require("@google/generative-ai"); +const { execSync } = require("child_process"); +const fs = require("fs"); +const path = require("path"); + +// Configuration +const API_KEY = process.env.GEMINI_API_KEY; +const MODEL_NAME = process.env.GEMINI_MODEL || "gemini-2.5-flash"; +const VERSION_TAG = process.argv[2]; // e.g., v1.2.0 +// Fix for Windows: Avoid 2>/dev/null, handle error in try-catch block instead +const PREVIOUS_TAG_CMD = `git describe --abbrev=0 --tags ${VERSION_TAG}~1`; + +if (!API_KEY) { + console.error("Error: GEMINI_API_KEY is not set."); + process.exit(1); +} + +if (!VERSION_TAG) { + console.error("Error: Version tag must be provided as the first argument."); + process.exit(1); +} + +async function run() { + try { + console.log(`Generating release notes for ${VERSION_TAG} using ${MODEL_NAME}...`); + + // 1. Get Previous Tag + let previousTag; + try { + previousTag = execSync(PREVIOUS_TAG_CMD).toString().trim(); + } catch (e) { + console.log("No previous tag found, assuming first release."); + previousTag = execSync("git rev-list --max-parents=0 HEAD").toString().trim(); + } + console.log(`Comparing from ${previousTag} to ${VERSION_TAG}`); + + // 2. Get Commit Messages + const commits = execSync(`git log ${previousTag}..${VERSION_TAG} --pretty=format:"- %s (%h)" --no-merges`).toString(); + + if (!commits) { + console.log("No commits found between tags."); + return; + } + + // 3. Generate Content with Gemini + const genAI = new GoogleGenerativeAI(API_KEY); + const model = genAI.getGenerativeModel({ model: MODEL_NAME }); + + const prompt = ` + You are a release note generator for a software project named 'Mivo'. + + Here are the commits for the new version ${VERSION_TAG}: + ${commits} + + Please generate a clean, professional release note in Markdown format. + + Strict Rules: + 1. **NO EMOJIS**: Do not use any emojis in headers, bullet points, or text. + 2. **Structure**: Group changes strictly into these headers (if applicable): + - ### Features + - ### Bug Fixes + - ### Improvements + - ### Maintenance + 3. **Format**: Use simple bullet points (-) for each item. + 4. **Content**: Keep it concise but descriptive. Do not mention 'Merge pull request' commits. + 5. **Header**: Start with a simple header: "# Release Notes ${VERSION_TAG}" + 6. **Output**: Output ONLY the markdown content. + `; + + const result = await model.generateContent(prompt); + const response = await result.response; + const text = response.text(); + + // 4. Read Template (Optional) and Merge + // For now, we just output the AI text. You can append this to a template if needed. + + // Write to file + const outputPath = path.join(process.cwd(), ".github", "release_notes.md"); + fs.writeFileSync(outputPath, text); + + console.log(`Release notes generated at ${outputPath}`); + console.log(text); + + // Export for GitHub Actions + const githubOutput = process.env.GITHUB_OUTPUT; + if (githubOutput) { + // Multiline string for GitHub Output + fs.appendFileSync(githubOutput, `RELEASE_NOTES<=18" } }, + "node_modules/@google/generative-ai": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.2.1.tgz", + "integrity": "sha512-gNmMFadfwi7qf/6M9gImgyGJXY1jKQ/de8vGOqgJ0PPYgQ7WwzZDavbKrIuXS2zdqZZaYtxW3EFN6aG9x5wtFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", diff --git a/package.json b/package.json index 2fd49f1..89de478 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "codemirror": "^6.0.2", "esbuild": "^0.27.2", "postcss": "^8.5.6", - "tailwindcss": "^3.4.17" + "tailwindcss": "^3.4.17", + "@google/generative-ai": "^0.2.0" } } \ No newline at end of file