UNPKG

keep-a-changelog

Version:

Parse and generate changelogs following the [keepachangelog](https://keepachangelog.com/) format.

259 lines (253 loc) 8.02 kB
#!/usr/bin/env node import { join } from "node:path"; import { parseArgs } from "node:util"; import { cwd, exit } from "node:process"; import { readFileSync, writeFileSync } from "node:fs"; import { parse as parseIni } from "ini"; import { Changelog, parser, Release } from "./mod.js"; import getSettingsForURL from "./src/settings.js"; const { values } = parseArgs({ options: { help: { type: "boolean", short: "h", }, file: { type: "string", default: "CHANGELOG.md", }, format: { type: "string", default: "compact", }, release: { type: "boolean", }, init: { type: "boolean", }, create: { type: "string", }, url: { type: "string", }, https: { type: "boolean", default: true, }, quiet: { type: "boolean", }, head: { type: "string", }, combine: { type: "boolean", }, "bullet-style": { type: "string", default: "-", }, "latest-release": { type: "boolean", }, "latest-release-full": { type: "boolean", }, "no-v-prefix": { type: "boolean", }, "no-sort-releases": { type: "boolean", }, }, }); const file = join(cwd(), values.file); try { if (values.help) { showHelp(); exit(0); } if (values.init) { const changelog = new Changelog("Changelog").addRelease(new Release("0.1.0", new Date(), "First version")); changelog.format = values.format; changelog.bulletStyle = values["bullet-style"]; save(file, changelog, true); exit(0); } const changelog = parser(readFileSync(file, { encoding: "utf8" }), { autoSortReleases: !values["no-sort-releases"], }); changelog.format = values.format; changelog.bulletStyle = values["bullet-style"]; if (values["no-v-prefix"]) { changelog.tagNameBuilder = (release) => String(release.version); } if (values["latest-release"]) { const release = changelog.releases.find((release) => release.date && release.version); if (release) { console.log(release.version?.toString()); } exit(0); } if (values["latest-release-full"]) { const release = changelog.releases.find((release) => release.date && release.version); if (release) { console.log(release.toString()); } exit(0); } if (values.release) { const release = changelog.releases.find((release) => { if (release.date) { return false; } if (typeof values.release === "string") { return !release.version || values.release === release.version.toString(); } return !!release.version; }); if (release) { release.date = new Date(); if (typeof values.release === "string") { release.setVersion(values.release); } } else { console.error("Not found any valid unreleased version"); exit(1); } } if (values.combine) { const combinedReleases = changelog.releases.reduce((acc, release) => { if (release.version) { if (acc[release.version]) { acc[release.version].combineChanges(release.changes); } else { acc[release.version] = release; } } return acc; }, {}); changelog.releases = Object.values(combinedReleases); } if (values.create) { let version = values.create || undefined; if (version === "major" || version === "minor" || version === "patch") { const latestRelease = changelog.releases.find((release) => release.parsedVersion); if (!latestRelease) { console.error("No releases found to bump version from."); exit(1); } let { major, minor, patch } = latestRelease.parsedVersion; if (version === "major") { major += 1; minor = 0; patch = 0; } else if (version === "minor") { minor += 1; patch = 0; } else if (version === "patch") { patch += 1; } version = `${major}.${minor}.${patch}`; } const release = changelog.releases.find((release) => release.version === version); if (release) { console.warn("Release already exists."); } else { changelog.addRelease(new Release(version)); } } save(file, changelog); } catch (err) { console.error(red(err.message)); if (!values.quiet) { exit(1); } } function save(file, changelog, isNew = false) { changelog.url = values.url || changelog.url || getRemoteUrl(values.https); if (!changelog.url) { console.error(red('Please, set the repository url with --url="https://github.com/username/repository"')); changelog.url = "https://example.com"; } if (changelog.url) { const settings = getSettingsForURL(changelog.url); if (settings) { changelog.head = settings.head; changelog.tagLinkBuilder = settings.tagLink; } } if (values.head) { changelog.head = values.head; } writeFileSync(file, changelog.toString()); if (isNew) { console.log(green("Generated new file"), file); } else { console.log(green("Updated file"), file); } } function red(message) { return "\u001b[" + 31 + "m" + message + "\u001b[" + 39 + "m"; } function green(message) { return "\u001b[" + 32 + "m" + message + "\u001b[" + 39 + "m"; } function normalizeUrl(url, https) { // remove .git suffix url = url.replace(/\.git$/, ""); // normalize git@host urls if (url.startsWith("git@")) { url = url.replace(/^git@([^:]+):(.*)$/, (https ? "https" : "http") + "://$1/$2"); } // remove trailing slashes url = url.replace(/\/+$/, ""); return new URL(url); } function getRemoteUrl(https = true) { try { const file = join(cwd(), ".git", "config"); const content = readFileSync(file, { encoding: "utf8" }); const data = parseIni(content); const origin = data['remote "origin"']; if (!origin?.url) { return; } return normalizeUrl(origin.url, https).href; } catch (err) { console.error(red(err.message)); // Ignore } } function showHelp() { console.log(`keep-a-changelog Usage: keep-a-changelog [options] Options: --file, -f Changelog file (default: CHANGELOG.md) --format Output format (default: compact) --bullet-style Bullet point style (default: -) --url Repository URL --init Initialize a new changelog file --latest-release Print the latest release version --latest-release-full Print the latest release --release Set the date of the specified release --combine Combine changes from releases with the same version --create Create a new release. Optionally accepts a version number or 'patch', 'minor' or 'major' --no-v-prefix Do not add a "v" prefix to the version --no-sort-releases Do not sort releases --head Set the HEAD link --quiet Do not print errors --help, -h Show this help message `); }