diff --git a/package.json b/package.json index c855518..ab0dd1d 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "fs-extra": "^11.1.1", "markdown-utils": "^1.0.0", "remeda": "^1.14.0", + "semver": "^7.5.0", + "simple-git": "^3.18.0", "yaml": "^2.2.2", "yargs": "^17.7.2" }, diff --git a/src/changelord.js b/src/changelord.js index e1c4b80..5ee4af7 100755 --- a/src/changelord.js +++ b/src/changelord.js @@ -11,6 +11,7 @@ import print from "./command/print.js"; import init from "./command/init.js"; import schema from "./command/schema.js"; import add from "./command/add.js"; +import cut from "./command/cut.js"; consola.raw = (...args) => console.log(...args); @@ -20,13 +21,11 @@ yargs(hideBin(process.argv)) }) .default("source", join(process.cwd(), "CHANGELOG.yml")) .describe("source", "changelog source") - .command({ - ...print, - command: "$0", - }) + .command(print) .command(init) .command(add) .command(schema) + .command(cut) .command(print) .help() .parse(); diff --git a/src/command/cut.js b/src/command/cut.js new file mode 100644 index 0000000..75dafa9 --- /dev/null +++ b/src/command/cut.js @@ -0,0 +1,68 @@ +import yaml from "yaml"; +import fs from "fs-extra"; +import u from "@yanick/updeep-remeda"; +import semverInc from "semver/functions/inc.js"; +import { simpleGit } from "simple-git"; + +const code_churn = async (previous_version) => { + previous_version = previous_version + ? "v" + previous_version + : "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; // empty tree sha1 + + return simpleGit().diff(["--shortstat", previous_version, "HEAD"]); +}; + +const handler = async (config) => { + config.consola.start("cutting the next version..."); + const changelog = yaml.parse(await fs.readFile(config.source, "utf-8")); + + const next = changelog.releases.find(u.matches({ version: "NEXT" })); + + if (!next) throw new Error("no changes since last version, aborting"); + + const previous_version = changelog.releases.find( + ({ version }) => version !== "NEXT" + ); + + next.date = new Date().toISOString().replace(/T.*/, ""); + + const type_to_level = (type = "") => + changelog.change_types.find((t) => t.keywords.includes(type)).level; + + const bump_level = (types) => + types.includes("major") + ? "major" + : types.includes("minor") + ? "minor" + : "patch"; + + const bumper = bump_level( + next.changes.map(({ type }) => type_to_level(type)) + ); + + if (changelog.project.with_stats) { + next.changes.push({ + type: "stats", + desc: "code churn:" + (await code_churn(previous_version)), + }); + } + + next.version = semverInc(previous_version ?? "0.0.0", bumper); + + if (config.dry) { + config.consola.info("running in dry mode, not saving\n", next); + } else { + await fs.writeFile(config.source, yaml.stringify(changelog)); + } + + config.consola.success(`version ${next.version} is cut!`); +}; + +export default { + command: "cut", + desc: "cut the next version", + builder: (yargs) => { + yargs.boolean("dry"); + }, + handler, +};