mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-03-23 23:57:23 +01:00
Auto versioning (#1344)
* feat: implement version bumping script and pre-push hook * chore: bump version to 1.113.5 * chore: bump version to 1.113.6 * chore: bump version to 1.113.7 * chore: bump version to 1.113.8 * chore: bump version to 1.113.9 * chore: bump version to 1.113.10 * chore: bump version to 1.113.3 and update versioning process * chore: enhance version bump process with base version check * Update .github/workflows/bump-version.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update .github/workflows/bump-version.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: sync package-lock.json version fields during automated version bump (#1345) * Initial plan * fix: update package-lock.json version fields during version bump Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> * Fix branch name in versioning.js comment: 'main' → 'master' (#1346) * Initial plan * fix: update branch name in versioning.js comment from 'main' to 'master' Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> * Extend bump-version.js to update ?v= cache-busting hashes in public/**/*.js dynamic imports (#1347) * Initial plan * feat: extend bump-version.js to update ?v= hashes in public/**/*.js dynamic imports Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> * chore: merge base branch changes (package-lock.json sync, RELEASE_BOT_TOKEN, node 24.x, comment fix) Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> * Update scripts/bump-version.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update scripts/bump-version.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> Co-authored-by: Azgaar <maxganiev@yandex.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * refactor: streamline dynamic import hash updates for public JS files * refactor: enhance version bump detection using AI analysis of PR diffs * Auto versioning (#1348) * Initial plan * fix: add ref to checkout step and stage public/**/*.js in bump workflow Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Azgaar <26469650+Azgaar@users.noreply.github.com>
This commit is contained in:
parent
a66b60b5a7
commit
d7555ff1b2
6 changed files with 601 additions and 19 deletions
326
scripts/bump-version.js
Normal file
326
scripts/bump-version.js
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Bump the project version (patch / minor / major).
|
||||
*
|
||||
* Updates:
|
||||
* - public/versioning.js — VERSION constant
|
||||
* - package.json — "version" field
|
||||
* - package-lock.json — top-level "version" and packages[""].version fields
|
||||
* - src/index.html — ?v= cache-busting hashes for changed public/*.js files
|
||||
* - public/**\/*.js — ?v= cache-busting hashes in dynamic import() calls
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/bump-version.js # interactive prompt
|
||||
* node scripts/bump-version.js patch # non-interactive
|
||||
* node scripts/bump-version.js minor # non-interactive
|
||||
* node scripts/bump-version.js major # non-interactive
|
||||
* node scripts/bump-version.js --dry-run # preview only, no writes
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const readline = require("readline");
|
||||
const {execSync} = require("child_process");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Paths
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
const packageJsonPath = path.join(repoRoot, "package.json");
|
||||
const packageLockJsonPath = path.join(repoRoot, "package-lock.json");
|
||||
const versioningPath = path.join(repoRoot, "public", "versioning.js");
|
||||
const indexHtmlPath = path.join(repoRoot, "src", "index.html");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function readFile(filePath) {
|
||||
return fs.readFileSync(filePath, "utf8");
|
||||
}
|
||||
|
||||
function writeFile(filePath, content) {
|
||||
fs.writeFileSync(filePath, content, "utf8");
|
||||
}
|
||||
|
||||
function parseCurrentVersion() {
|
||||
const content = readFile(versioningPath);
|
||||
const match = content.match(/const VERSION = "(\d+\.\d+\.\d+)";/);
|
||||
if (!match) throw new Error("Could not find VERSION constant in public/versioning.js");
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function bumpVersion(version, type) {
|
||||
const [major, minor, patch] = version.split(".").map(Number);
|
||||
if (type === "major") return `${major + 1}.0.0`;
|
||||
if (type === "minor") return `${major}.${minor + 1}.0`;
|
||||
return `${major}.${minor}.${patch + 1}`;
|
||||
}
|
||||
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
/** Returns true if versionA is strictly greater than versionB (semver). */
|
||||
function isVersionGreater(versionA, versionB) {
|
||||
const a = versionA.split(".").map(Number);
|
||||
const b = versionB.split(".").map(Number);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (a[i] > b[i]) return true;
|
||||
if (a[i] < b[i]) return false;
|
||||
}
|
||||
return false; // equal
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns public/*.js paths (relative to repo root) that have changed.
|
||||
* Checks (in order, deduplicating):
|
||||
* 1. Upstream branch diff — catches everything on a feature/PR branch
|
||||
* 2. Staged (index) diff — catches files staged but not yet committed
|
||||
* 3. Last-commit diff — fallback for main / detached HEAD
|
||||
*/
|
||||
function getChangedPublicJsFiles() {
|
||||
const run = cmd => execSync(cmd, {encoding: "utf8", cwd: repoRoot});
|
||||
const parseFiles = output =>
|
||||
output
|
||||
.split("\n")
|
||||
.map(f => f.trim())
|
||||
.filter(f => f.startsWith("public/") && f.endsWith(".js"));
|
||||
|
||||
const seen = new Set();
|
||||
const collect = files => files.forEach(f => seen.add(f));
|
||||
|
||||
// 1. Upstream branch diff
|
||||
try {
|
||||
const upstream = run("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}").trim();
|
||||
collect(parseFiles(run(`git diff --name-only ${upstream}...HEAD`)));
|
||||
} catch {
|
||||
/* no upstream */
|
||||
}
|
||||
|
||||
// 2. Staged changes (useful when building before committing)
|
||||
try {
|
||||
collect(parseFiles(run("git diff --name-only --cached")));
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
|
||||
if (seen.size > 0) return [...seen];
|
||||
|
||||
// 3. Fallback: last commit diff
|
||||
try {
|
||||
return parseFiles(run("git diff --name-only HEAD~1 HEAD"));
|
||||
} catch {
|
||||
/* shallow / single-commit repo */
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// File updaters
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function updateVersioningJs(newVersion, dry) {
|
||||
const original = readFile(versioningPath);
|
||||
const updated = original.replace(/const VERSION = "\d+\.\d+\.\d+";/, `const VERSION = "${newVersion}";`);
|
||||
if (original === updated) throw new Error("Failed to update VERSION in public/versioning.js");
|
||||
if (!dry) writeFile(versioningPath, updated);
|
||||
console.log(` public/versioning.js → ${newVersion}`);
|
||||
}
|
||||
|
||||
function updatePackageJson(newVersion, dry) {
|
||||
const original = readFile(packageJsonPath);
|
||||
const pkg = JSON.parse(original);
|
||||
const oldVersion = pkg.version;
|
||||
pkg.version = newVersion;
|
||||
if (!dry) writeFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
||||
console.log(` package.json ${oldVersion} → ${newVersion}`);
|
||||
}
|
||||
|
||||
function updatePackageLockJson(newVersion, dry) {
|
||||
if (!fs.existsSync(packageLockJsonPath)) {
|
||||
console.log(" package-lock.json (not found, skipping)");
|
||||
return;
|
||||
}
|
||||
const original = readFile(packageLockJsonPath);
|
||||
const lock = JSON.parse(original);
|
||||
const oldVersion = lock.version;
|
||||
lock.version = newVersion;
|
||||
if (lock.packages && lock.packages[""]) {
|
||||
lock.packages[""].version = newVersion;
|
||||
}
|
||||
if (!dry) writeFile(packageLockJsonPath, `${JSON.stringify(lock, null, 2)}\n`);
|
||||
console.log(` package-lock.json ${oldVersion} → ${newVersion}`);
|
||||
}
|
||||
|
||||
function updateIndexHtmlHashes(changedFiles, newVersion, dry) {
|
||||
if (changedFiles.length === 0) {
|
||||
console.log(" src/index.html (no changed public/*.js files detected)");
|
||||
return;
|
||||
}
|
||||
|
||||
let html = readFile(indexHtmlPath);
|
||||
const updated = [];
|
||||
|
||||
for (const publicPath of changedFiles) {
|
||||
const htmlPath = publicPath.replace(/^public\//, "");
|
||||
const pattern = new RegExp(`${escapeRegExp(htmlPath)}\\?v=[0-9.]+`, "g");
|
||||
if (pattern.test(html)) {
|
||||
html = html.replace(pattern, `${htmlPath}?v=${newVersion}`);
|
||||
updated.push(htmlPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (updated.length > 0) {
|
||||
if (!dry) writeFile(indexHtmlPath, html);
|
||||
console.log(` src/index.html hashes updated for:\n - ${updated.join("\n - ")}`);
|
||||
} else {
|
||||
console.log(
|
||||
` src/index.html (changed files have no ?v= entry: ${changedFiles.map(f => f.replace("public/", "")).join(", ")}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For each changed public JS file, scans ALL other public JS files for
|
||||
* dynamic import() calls that reference it via a relative ?v= path, and
|
||||
* updates the hash to newVersion.
|
||||
*
|
||||
* Example: public/modules/dynamic/installation.js changed →
|
||||
* main.js: import("./modules/dynamic/installation.js?v=1.89.19")
|
||||
* → import("./modules/dynamic/installation.js?v=1.113.4")
|
||||
*/
|
||||
function updatePublicJsDynamicImportHashes(changedFiles, newVersion, dry) {
|
||||
if (changedFiles.length === 0) {
|
||||
console.log(" public/**/*.js (no changed files, skipping dynamic import hashes)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Absolute paths of every changed file for O(1) lookup
|
||||
const changedAbsPaths = new Set(changedFiles.map(f => path.join(repoRoot, f)));
|
||||
|
||||
// Collect all public JS files, skipping public/libs (third-party)
|
||||
const publicRoot = path.join(repoRoot, "public");
|
||||
const allJsFiles = [];
|
||||
(function walk(dir) {
|
||||
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (path.relative(publicRoot, full).replace(/\\/g, "/") === "libs") continue;
|
||||
walk(full);
|
||||
} else if (entry.isFile() && entry.name.endsWith(".js")) {
|
||||
allJsFiles.push(full);
|
||||
}
|
||||
}
|
||||
})(publicRoot);
|
||||
|
||||
const updatedMap = {};
|
||||
|
||||
for (const absJsFile of allJsFiles) {
|
||||
const content = readFile(absJsFile);
|
||||
// Matches: import("../path/file.js?v=1.2.3") or import('../path/file.js?v=1.2.3')
|
||||
const pattern = /(['"])(\.{1,2}\/[^'"?]+)\?v=[0-9.]+\1/g;
|
||||
let anyChanged = false;
|
||||
const newContent = content.replace(pattern, (match, quote, relImportPath) => {
|
||||
const absImport = path.resolve(path.dirname(absJsFile), relImportPath);
|
||||
if (!changedAbsPaths.has(absImport)) return match;
|
||||
const repoRelFile = path.relative(repoRoot, absJsFile).replace(/\\/g, "/");
|
||||
if (!updatedMap[repoRelFile]) updatedMap[repoRelFile] = [];
|
||||
updatedMap[repoRelFile].push(relImportPath);
|
||||
anyChanged = true;
|
||||
return `${quote}${relImportPath}?v=${newVersion}${quote}`;
|
||||
});
|
||||
if (anyChanged && !dry) writeFile(absJsFile, newContent);
|
||||
}
|
||||
|
||||
if (Object.keys(updatedMap).length > 0) {
|
||||
const lines = Object.entries(updatedMap)
|
||||
.map(([file, refs]) => ` ${file}:\n - ${refs.join("\n - ")}`)
|
||||
.join("\n");
|
||||
console.log(` public/**/*.js dynamic import hashes updated:\n${lines}`);
|
||||
} else {
|
||||
console.log(" public/**/*.js (no dynamic import ?v= hashes to update)");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Prompt
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function promptBumpType(currentVersion) {
|
||||
return new Promise(resolve => {
|
||||
const rl = readline.createInterface({input: process.stdin, output: process.stdout});
|
||||
process.stdout.write(`\nCurrent version: ${currentVersion}\nBump type (patch / minor / major) [patch]: `);
|
||||
rl.once("line", answer => {
|
||||
rl.close();
|
||||
const input = answer.trim().toLowerCase();
|
||||
if (input === "minor" || input === "mi") return resolve("minor");
|
||||
if (input === "major" || input === "maj") return resolve("major");
|
||||
resolve("patch");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function main() {
|
||||
const argv = process.argv.slice(2);
|
||||
const args = argv.map(a => a.toLowerCase());
|
||||
const dry = args.includes("--dry-run");
|
||||
|
||||
// --base-version X.Y.Z — version on master before this PR was merged.
|
||||
// When provided, the script checks whether the developer already bumped
|
||||
// the version manually in their branch. If so, the increment is skipped
|
||||
// and only the ?v= hashes in index.html are refreshed.
|
||||
const baseVersionFlagIdx = argv.findIndex(a => a === "--base-version");
|
||||
const baseVersion = baseVersionFlagIdx !== -1 ? argv[baseVersionFlagIdx + 1] : null;
|
||||
|
||||
if (dry) console.log("\n[bump-version] DRY RUN — no files will be changed\n");
|
||||
|
||||
const currentVersion = parseCurrentVersion();
|
||||
|
||||
if (baseVersion && isVersionGreater(currentVersion, baseVersion)) {
|
||||
// Developer already bumped the version manually in their branch.
|
||||
console.log(
|
||||
`\n[bump-version] Version already updated manually: ${baseVersion} → ${currentVersion} (base was ${baseVersion})\n`
|
||||
);
|
||||
console.log(" Skipping version increment — updating ?v= hashes only.\n");
|
||||
const changedFiles = getChangedPublicJsFiles();
|
||||
updateIndexHtmlHashes(changedFiles, currentVersion, dry);
|
||||
updatePublicJsDynamicImportHashes(changedFiles, currentVersion, dry);
|
||||
console.log(`\n[bump-version] ${dry ? "(dry run) " : ""}done.\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine bump type: CLI arg → stdin prompt → default patch
|
||||
let bumpType;
|
||||
if (args.includes("major")) bumpType = "major";
|
||||
else if (args.includes("minor")) bumpType = "minor";
|
||||
else if (args.includes("patch")) bumpType = "patch";
|
||||
else if (process.stdin.isTTY) bumpType = await promptBumpType(currentVersion);
|
||||
else bumpType = "patch"; // non-interactive (CI / pipe)
|
||||
|
||||
const newVersion = bumpVersion(currentVersion, bumpType);
|
||||
|
||||
console.log(`\n[bump-version] ${bumpType}: ${currentVersion} → ${newVersion}\n`);
|
||||
|
||||
const changedFiles = getChangedPublicJsFiles();
|
||||
updateVersioningJs(newVersion, dry);
|
||||
updatePackageJson(newVersion, dry);
|
||||
updatePackageLockJson(newVersion, dry);
|
||||
updateIndexHtmlHashes(changedFiles, newVersion, dry);
|
||||
updatePublicJsDynamicImportHashes(changedFiles, newVersion, dry);
|
||||
|
||||
console.log(`\n[bump-version] ${dry ? "(dry run) " : ""}done.\n`);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error("\n[bump-version] Error:", err.message || err);
|
||||
process.exit(1);
|
||||
});
|
||||
158
scripts/detect-bump-type.js
Normal file
158
scripts/detect-bump-type.js
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Uses the GitHub Models API (gpt-4o-mini, no extra secrets required —
|
||||
* GITHUB_TOKEN is enough when running inside GitHub Actions) to analyse
|
||||
* a PR diff and decide whether the release is a patch, minor, or major bump.
|
||||
*
|
||||
* Versioning rules (from public/versioning.js):
|
||||
* MAJOR — incompatible changes that break existing .map files
|
||||
* MINOR — backward-compatible additions or changes that may require
|
||||
* old .map files to be updated / migrated
|
||||
* PATCH — backward-compatible bug fixes and small features that do
|
||||
* NOT affect the .map file format at all
|
||||
*
|
||||
* Usage (called by bump-version.yml):
|
||||
* node scripts/detect-bump-type.js --diff-file <path>
|
||||
*
|
||||
* Output: prints exactly one of: patch | minor | major
|
||||
* Exit 0 always (falls back to "patch" on any error).
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const https = require("https");
|
||||
const path = require("path");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const MODEL = "gpt-4o-mini";
|
||||
const ENDPOINT_HOST = "models.inference.ai.azure.com";
|
||||
const ENDPOINT_PATH = "/chat/completions";
|
||||
// Keep the diff under ~20 000 chars to stay within the model's context window.
|
||||
const MAX_DIFF_CHARS = 20_000;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// System prompt — contains the project-specific versioning rules
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const SYSTEM_PROMPT = `\
|
||||
You are a semantic-version expert for Azgaar's Fantasy Map Generator.
|
||||
|
||||
The project uses semantic versioning where the PUBLIC API is the .map save-file format.
|
||||
|
||||
Rules:
|
||||
• MAJOR — any change that makes existing .map files incompatible or unreadable
|
||||
(e.g. removing or renaming top-level save-data fields, changing data types of
|
||||
stored values, restructuring the save format)
|
||||
• MINOR — backward-compatible additions or changes that may require old .map
|
||||
files to be silently migrated on load (e.g. adding new optional fields to the
|
||||
save format, changing default values that affect saved maps, adding new
|
||||
generators that store new data)
|
||||
• PATCH — everything else: UI improvements, bug fixes, refactors, new features
|
||||
that do not touch the .map file format at all, dependency updates, docs
|
||||
|
||||
Respond with EXACTLY one lowercase word: patch | minor | major
|
||||
No explanation, no punctuation, no extra words.`;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function httpsPost(host, pathStr, headers, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = JSON.stringify(body);
|
||||
const req = https.request(
|
||||
{host, path: pathStr, method: "POST", headers: {...headers, "Content-Length": Buffer.byteLength(data)}},
|
||||
res => {
|
||||
let raw = "";
|
||||
res.on("data", c => (raw += c));
|
||||
res.on("end", () => {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) resolve(raw);
|
||||
else reject(new Error(`HTTP ${res.statusCode}: ${raw.slice(0, 300)}`));
|
||||
});
|
||||
}
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.write(data);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const diffFileIdx = args.indexOf("--diff-file");
|
||||
const diffFile = diffFileIdx !== -1 ? args[diffFileIdx + 1] : null;
|
||||
|
||||
if (!diffFile || !fs.existsSync(diffFile)) {
|
||||
console.error("[detect-bump-type] --diff-file <path> is required and must exist.");
|
||||
process.stdout.write("patch\n");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (!token) {
|
||||
console.error("[detect-bump-type] GITHUB_TOKEN not set — falling back to patch.");
|
||||
process.stdout.write("patch\n");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let diff = fs.readFileSync(diffFile, "utf8").trim();
|
||||
if (!diff) {
|
||||
console.error("[detect-bump-type] Diff is empty — falling back to patch.");
|
||||
process.stdout.write("patch\n");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Trim diff to avoid exceeding context window
|
||||
if (diff.length > MAX_DIFF_CHARS) {
|
||||
diff = diff.slice(0, MAX_DIFF_CHARS) + "\n\n[diff truncated]";
|
||||
console.error(`[detect-bump-type] Diff truncated to ${MAX_DIFF_CHARS} chars.`);
|
||||
}
|
||||
|
||||
const userMessage = `Analyse this git diff and respond with exactly one word (patch, minor, or major):\n\n${diff}`;
|
||||
|
||||
try {
|
||||
const raw = await httpsPost(
|
||||
ENDPOINT_HOST,
|
||||
ENDPOINT_PATH,
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
{
|
||||
model: MODEL,
|
||||
messages: [
|
||||
{role: "system", content: SYSTEM_PROMPT},
|
||||
{role: "user", content: userMessage}
|
||||
],
|
||||
temperature: 0,
|
||||
max_tokens: 5
|
||||
}
|
||||
);
|
||||
|
||||
const json = JSON.parse(raw);
|
||||
const answer = json.choices?.[0]?.message?.content?.trim().toLowerCase() ?? "patch";
|
||||
|
||||
if (answer === "major" || answer === "minor" || answer === "patch") {
|
||||
console.error(`[detect-bump-type] AI decision: ${answer}`);
|
||||
process.stdout.write(`${answer}\n`);
|
||||
} else {
|
||||
console.error(`[detect-bump-type] Unexpected AI response "${answer}" — defaulting to patch.`);
|
||||
process.stdout.write("patch\n");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[detect-bump-type] API error: ${err.message} — falling back to patch.`);
|
||||
process.stdout.write("patch\n");
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main();
|
||||
Loading…
Add table
Add a link
Reference in a new issue