ktx/scripts/semantic-release-version-policy.cjs

115 lines
3.1 KiB
JavaScript
Raw Permalink Normal View History

const { readFileSync } = require('node:fs');
const { join } = require('node:path');
const SEMVER_PATTERN =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
const FIRST_STABLE_RELEASE_FLOOR_VERSION = '0.0.0';
function parseSemver(version) {
const match = SEMVER_PATTERN.exec(version);
if (!match) {
throw new Error(`Invalid public npm package version: ${version}`);
}
return {
major: Number(match[1]),
minor: Number(match[2]),
patch: Number(match[3]),
prerelease: match[4] ?? null,
};
}
function readReleasePolicy(cwd) {
return JSON.parse(readFileSync(join(cwd, 'release-policy.json'), 'utf8'));
}
function releaseKind(env) {
return env.KTX_RELEASE_KIND || env.INPUT_RELEASE_KIND || 'rc';
}
function stableBaseVersion(version) {
const parsed = parseSemver(version);
return `${parsed.major}.${parsed.minor}.${parsed.patch}`;
}
function isFirstStableReleaseFloor(context) {
return (
releaseKind(context.env) === 'stable' &&
context.env.KTX_STABLE_RELEASE_FLOOR_TAG &&
context.lastRelease.version === FIRST_STABLE_RELEASE_FLOOR_VERSION &&
context.lastRelease.gitTag === context.env.KTX_STABLE_RELEASE_FLOOR_TAG
);
}
function analyzeCommits(config, context) {
if (!isFirstStableReleaseFloor(context)) {
return undefined;
}
context.logger.log('Using temporary stable release floor to publish 0.1.0');
return 'minor';
}
function assertNoAutomaticMajorRelease(context, policyVersion) {
const policy = parseSemver(policyVersion);
const next = parseSemver(context.nextRelease.version);
if (next.major <= policy.major) {
return;
}
throw new Error(
[
`Refusing automatic major release ${context.nextRelease.version}.`,
`release-policy.json is still on major ${policy.major}.`,
'Update release-policy.json manually before publishing a new major version.',
].join(' '),
);
}
function assertStableReleaseFloorTarget(context, policyVersion) {
if (!isFirstStableReleaseFloor(context)) {
return;
}
const expectedVersion = stableBaseVersion(policyVersion);
if (context.nextRelease.version !== expectedVersion) {
throw new Error(
`Stable release floor expected ${expectedVersion}, got ${context.nextRelease.version}.`,
);
}
}
function verifyRelease(config, context) {
const policy = readReleasePolicy(context.cwd);
const policyVersion = policy.publicNpmPackageVersion;
assertNoAutomaticMajorRelease(context, policyVersion);
assertStableReleaseFloorTarget(context, policyVersion);
}
function prepare(config, context) {
const floorTag = context.env.KTX_STABLE_RELEASE_FLOOR_TAG;
if (!floorTag) {
return;
}
const { execFileSync } = require('node:child_process');
execFileSync('git', ['tag', '-d', floorTag], {
cwd: context.cwd,
stdio: 'ignore',
});
context.logger.log(`Deleted temporary stable release floor tag ${floorTag}`);
}
module.exports = {
FIRST_STABLE_RELEASE_FLOOR_VERSION,
analyzeCommits,
assertNoAutomaticMajorRelease,
assertStableReleaseFloorTarget,
isFirstStableReleaseFloor,
parseSemver,
prepare,
stableBaseVersion,
verifyRelease,
};