Files
przeprogramowani__10x-cli/scripts/auto-version.mjs
T
“mkczarkowski” e77b26ca8d fix(smoke): Windows CI — cross-platform tmp path and relaxed startup budget
- auto-version.mjs: use os.tmpdir() instead of hardcoded /tmp/ for
  release-notes.md (Windows has no /tmp)
- binary.test.ts: raise startup budget to 150ms on Windows (CI runners
  have slower process spawn overhead)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 15:41:09 +02:00

135 lines
4.2 KiB
JavaScript

#!/usr/bin/env node
/**
* Auto-version: uses conventional-recommended-bump to determine the bump level
* (major/minor/patch) from conventional commits since the last release tag,
* then writes the new version to package.json.
*
* Two gates before a release triggers:
* 1. Conventional commits exist (excluding chore(release) auto-commits)
* 2. Files that ship in the package actually changed (src/, package.json)
* Gate 2 is the ground truth — commit types can be wrong, git diff cannot.
*
* Release notes are generated by GitHub's --generate-notes on gh release create.
*
* Exits 0 on success (prints NEW_VERSION).
* Exits 1 if no release needed.
*/
import { readFileSync, writeFileSync } from "node:fs";
import { execSync } from "node:child_process";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Bumper } from "conventional-recommended-bump";
const PKG_PATH = "package.json";
async function main() {
const pkg = JSON.parse(readFileSync(PKG_PATH, "utf8"));
const currentVersion = pkg.version;
const lastTag = `v${currentVersion}`;
const tagExists = tagExistsInRepo(lastTag);
const bumper = new Bumper();
bumper.loadPreset("angular");
bumper.commits({ from: tagExists ? lastTag : "" }, {});
if (tagExists) bumper.tag({ prefix: "v" });
const recommendation = await bumper.bump();
const releasableCommits = (recommendation.commits || []).filter(
(c) => c.scope !== "release",
);
if (releasableCommits.length === 0) {
console.error("No version bump needed.");
process.exit(1);
}
// Ground truth: did any file that ships in the package actually change?
// Commit message types can be wrong — git diff cannot.
if (tagExists && !packageFilesChanged(lastTag)) {
console.error("No version bump needed — no package-affecting files changed since " + lastTag);
process.exit(1);
}
const [major, minor, patch] = currentVersion.split(".").map(Number);
let newVersion;
switch (recommendation.releaseType) {
case "major":
newVersion = `${major + 1}.0.0`;
break;
case "minor":
newVersion = `${major}.${minor + 1}.0`;
break;
case "patch":
newVersion = `${major}.${minor}.${patch + 1}`;
break;
default:
console.error(`Unexpected releaseType: ${recommendation.releaseType}`);
process.exit(1);
}
pkg.version = newVersion;
writeFileSync(PKG_PATH, `${JSON.stringify(pkg, null, 2)}\n`);
const notes = formatReleaseNotes(newVersion, releasableCommits);
const notesPath = join(tmpdir(), "release-notes.md");
writeFileSync(notesPath, notes);
console.log(
`${recommendation.releaseType}: ${currentVersion}${newVersion}`,
);
console.log(`NEW_VERSION=v${newVersion}`);
console.log(`RELEASE_NOTES_PATH=${notesPath}`);
}
function formatReleaseNotes(version, commits) {
const sections = {
feat: { title: "Features", items: [] },
fix: { title: "Fixes", items: [] },
perf: { title: "Performance", items: [] },
refactor: { title: "Refactoring", items: [] },
chore: { title: "Maintenance", items: [] },
docs: { title: "Documentation", items: [] },
};
for (const c of commits) {
const hash = c.hash ? c.hash.slice(0, 7) : "";
const entry = `- ${c.subject || c.header} (${hash})`;
(sections[c.type] || sections.chore).items.push(entry);
}
let notes = "";
for (const s of Object.values(sections)) {
if (s.items.length) notes += `### ${s.title}\n\n${s.items.join("\n")}\n\n`;
}
return notes || `Release ${version}\n`;
}
/**
* Check if any files that affect the published package changed since the tag.
* src/ and package.json are the ground truth — not commit message types.
*/
function packageFilesChanged(sinceTag) {
const RELEASE_PATHS = ["src/", "package.json"];
const diff = execSync(
`git diff --name-only ${sinceTag}..HEAD -- ${RELEASE_PATHS.join(" ")}`,
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] },
).trim();
return diff.length > 0;
}
function tagExistsInRepo(tag) {
try {
execSync(`git rev-parse --verify refs/tags/${tag}`, { stdio: "pipe" });
return true;
} catch {
return false;
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});