mirror of
https://github.com/przeprogramowani/10x-cli.git
synced 2026-09-19 03:30:01 +08:00
e77b26ca8d
- 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>
135 lines
4.2 KiB
JavaScript
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);
|
|
});
|