Files
8bitworkshop/scripts/release-lib.mjs
T

80 lines
2.7 KiB
JavaScript

/**
* Pure helpers for the release script. Kept separate from `release.mjs` so
* they can be unit tested without touching git or the filesystem.
*/
/**
* Compute the next version. `kind` is `patch`, `minor`, `major`, or an
* explicit `x.y.z`. An explicit version may equal the current one (useful for
* the first release, where `package.json` already carries the version).
*
* @param {string} current
* @param {string} kind
* @returns {string}
*/
export function bumpVersion(current, kind) {
if (/^\d+\.\d+\.\d+$/.test(kind)) return kind;
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(current);
if (!match) {
throw new Error(`current version "${current}" is not x.y.z`);
}
let [major, minor, patch] = match.slice(1).map(Number);
switch (kind) {
case 'major':
major += 1;
minor = 0;
patch = 0;
break;
case 'minor':
minor += 1;
patch = 0;
break;
case 'patch':
patch += 1;
break;
default:
throw new Error(`unknown bump "${kind}" (use patch, minor, major, or x.y.z)`);
}
return `${major}.${minor}.${patch}`;
}
/**
* Move the entries currently under `## [Unreleased]` under a new
* `## [version] - date` heading, leaving an empty Unreleased section on top.
*
* Older entries in this repo use `## <version> (unreleased)` instead. When no
* standard Unreleased heading is found, the first such heading is renamed and
* an empty Unreleased section is inserted above it.
*
* @param {string} text changelog contents
* @param {string} version
* @param {string} date `YYYY-MM-DD`
* @returns {string}
*/
export function releaseChangelog(text, version, date) {
const heading = '## [Unreleased]';
const released = `## [${version}] - ${date}`;
const index = text.indexOf(heading);
if (index !== -1) {
// Insert after the Unreleased heading and the blank line that follows it, so
// existing content ends up beneath the new version heading.
let insertAt = text.indexOf('\n', index) + 1;
if (text[insertAt] === '\n') insertAt += 1;
return `${text.slice(0, insertAt)}${released}\n\n${text.slice(insertAt)}`;
}
const unreleased = /^## [^\n]*\(unreleased\)[^\n]*$/im.exec(text);
if (unreleased) {
const start = unreleased.index;
const lineEnd = text.indexOf('\n', start);
const end = lineEnd === -1 ? text.length : lineEnd + 1;
return `${text.slice(0, start)}${heading}\n\n${released}\n${text.slice(end)}`;
}
return `${heading}\n\n${released}\n\n${text}`;
}
/** Local calendar date as `YYYY-MM-DD`. */
export function today(date = new Date()) {
const pad = (n) => String(n).padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}