blob: ac3e868ccfb8a2cea81082b4603359ff05a3b7f6 [file]
#!/usr/bin/env node
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import console from "node:console";
import { appendFileSync, readFileSync } from "node:fs";
import process from "node:process";
import { URL, pathToFileURL } from "node:url";
const PACKAGE_NAME = JSON.parse(
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
).name;
const SEMVER_PATTERN =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?$/;
const NPM_TAG_PATTERN = /^[a-z][a-z0-9._-]*$/;
function parseVersion(version) {
const match = SEMVER_PATTERN.exec(version);
if (match === null) {
throw new Error(`Release version ${JSON.stringify(version)} is not valid SemVer`);
}
return {
version,
major: BigInt(match[1]),
minor: BigInt(match[2]),
patch: BigInt(match[3]),
prerelease: match[4]?.split(".") ?? [],
};
}
function compareIdentifiers(left, right) {
if (left === right) {
return 0;
}
const leftIsNumeric = /^\d+$/.test(left);
const rightIsNumeric = /^\d+$/.test(right);
if (leftIsNumeric && rightIsNumeric) {
return BigInt(left) < BigInt(right) ? -1 : 1;
}
if (leftIsNumeric) {
return -1;
}
if (rightIsNumeric) {
return 1;
}
return left < right ? -1 : 1;
}
function compareVersions(left, right) {
for (const field of ["major", "minor", "patch"]) {
if (left[field] !== right[field]) {
return left[field] < right[field] ? -1 : 1;
}
}
if (left.prerelease.length === 0 || right.prerelease.length === 0) {
return right.prerelease.length - left.prerelease.length;
}
const length = Math.max(left.prerelease.length, right.prerelease.length);
for (let index = 0; index < length; index += 1) {
const leftIdentifier = left.prerelease[index];
const rightIdentifier = right.prerelease[index];
if (leftIdentifier === undefined) {
return -1;
}
if (rightIdentifier === undefined) {
return 1;
}
const difference = compareIdentifiers(leftIdentifier, rightIdentifier);
if (difference !== 0) {
return difference;
}
}
return 0;
}
function getPrereleaseChannel(prerelease) {
return prerelease[0]?.match(/^[A-Za-z]+/)?.[0].toLowerCase();
}
export function validateReleaseInputs({ releaseTag, npmTag, currentDistTagVersion = "" }) {
if (!releaseTag.startsWith("ts-sdk/")) {
throw new Error("Release tag must have the form ts-sdk/<semver>");
}
const version = releaseTag.slice("ts-sdk/".length);
const parsedVersion = parseVersion(version);
if (!NPM_TAG_PATTERN.test(npmTag)) {
throw new Error(`npm tag ${JSON.stringify(npmTag)} is invalid`);
}
if (parsedVersion.prerelease.length === 0 && npmTag !== "latest") {
throw new Error("Stable releases must use the latest npm dist-tag");
}
if (parsedVersion.prerelease.length > 0) {
const channel = getPrereleaseChannel(parsedVersion.prerelease);
if (npmTag !== "next" && npmTag !== channel) {
const allowedTags =
channel === undefined || channel === "next" ? "next" : `${channel} or next`;
throw new Error(`Prerelease ${version} must use the ${allowedTags} npm dist-tag`);
}
}
if (currentDistTagVersion !== "") {
const currentVersion = parseVersion(currentDistTagVersion);
if (compareVersions(parsedVersion, currentVersion) <= 0) {
throw new Error(
`Release ${version} must be newer than ${npmTag}'s current version ${currentDistTagVersion}`,
);
}
}
return {
version,
packageName: PACKAGE_NAME,
packageFile: `${PACKAGE_NAME}-${version}.tgz`,
};
}
function run() {
const result = validateReleaseInputs({
releaseTag: process.env.RELEASE_TAG ?? "",
npmTag: process.env.NPM_TAG ?? "",
currentDistTagVersion: process.env.CURRENT_DIST_TAG_VERSION ?? "",
});
const outputFile = process.env.GITHUB_OUTPUT;
if (outputFile === undefined) {
throw new Error("GITHUB_OUTPUT is required");
}
appendFileSync(
outputFile,
`version=${result.version}\npackage_name=${result.packageName}\n` +
`package_file=${result.packageFile}\n`,
);
}
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
try {
run();
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
}
}