Files

103 lines
3.9 KiB
JavaScript

// 读取并校验 workspace 根版本
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
const workspaceDir = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
const packageFileName = "package.json";
const packageVersionPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
const MAX_CHROME_VERSION_COMPONENT = 65535;
const releaseTypes = new Set(["major", "minor", "patch"]);
const readJsonFile = (filePath) => {
let source;
try {
source = readFileSync(filePath, "utf8");
} catch (error) {
throw new Error(`Unable to read ${filePath}`, { cause: error });
}
try {
return JSON.parse(source);
} catch (error) {
throw new Error(`Invalid JSON in ${filePath}`, { cause: error });
}
};
const workspacePackageDirectories = ["apps", "packages"];
/** 按 workspace 根目录顺序枚举可参与版本镜像的 package。 */
export const listWorkspacePackagePaths = (rootDir = workspaceDir) =>
workspacePackageDirectories.flatMap((directory) => {
const directoryPath = resolve(rootDir, directory);
if (!existsSync(directoryPath)) return [];
return readdirSync(directoryPath, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => resolve(directoryPath, entry.name, "package.json"))
.filter((packagePath) => existsSync(packagePath))
.sort();
});
/** 校验同时满足 npm 与 Chrome Manifest 的三段版本。 */
export const validatePackageVersion = (version, source = "package.json") => {
if (typeof version !== "string" || version.length === 0) {
throw new Error(`${source} version must be a non-empty string`);
}
if (!packageVersionPattern.test(version)) {
throw new Error(
`${source} version must be a three-component numeric SemVer without prerelease or build metadata`,
);
}
const components = version.split(".").map(Number);
if (components.some((component) => component > MAX_CHROME_VERSION_COMPONENT)) {
throw new Error(`${source} version components must be between 0 and 65535`);
}
if (components.every((component) => component === 0)) {
throw new Error(`${source} version must not be 0.0.0`);
}
return version;
};
/** 递增符合 Chrome 限制的 workspace 发布版本。 */
export const bumpPackageVersion = (version, releaseType = "patch") => {
validatePackageVersion(version);
if (!releaseTypes.has(releaseType)) {
throw new Error("Release type must be major, minor, or patch");
}
const [major, minor, patch] = version.split(".").map(Number);
const nextVersion =
releaseType === "major"
? `${major + 1}.0.0`
: releaseType === "minor"
? `${major}.${minor + 1}.0`
: `${major}.${minor}.${patch + 1}`;
return validatePackageVersion(nextVersion, "Next package");
};
/** 读取唯一的 workspace 根版本事实源。 */
export const readRootPackageVersion = (rootDir = workspaceDir) => {
const packagePath = resolve(rootDir, packageFileName);
const manifest = readJsonFile(packagePath);
return validatePackageVersion(manifest?.version, `${packagePath}`);
};
/** 只更新 workspace 根版本这一唯一事实源。 */
export const writeRootPackageVersion = (version, rootDir = workspaceDir) => {
const packagePath = resolve(rootDir, packageFileName);
const manifest = readJsonFile(packagePath);
manifest.version = validatePackageVersion(version, `${packagePath}`);
writeFileSync(packagePath, `${JSON.stringify(manifest, null, 4)}\n`);
};
/** 输出供 CI 和 shell 命令消费的根版本。 */
const printPackageVersion = () => {
console.log(readRootPackageVersion());
};
if (process.argv[1] === fileURLToPath(import.meta.url)) printPackageVersion();