Files
ctms/frontend/scripts/client-version.mjs
T
Cheng Zhou 7c721d4e5c
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled
refactor(client): unify web and desktop release workflow
2026-06-30 20:25:07 +08:00

123 lines
4.8 KiB
JavaScript

import { readFile, writeFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
const frontendDir = fileURLToPath(new URL("../", import.meta.url));
const paths = {
packageJson: new URL("../package.json", import.meta.url),
packageLock: new URL("../package-lock.json", import.meta.url),
tauriConfig: new URL("../src-tauri/tauri.conf.json", import.meta.url),
cargoToml: new URL("../src-tauri/Cargo.toml", import.meta.url),
cargoLock: new URL("../src-tauri/Cargo.lock", import.meta.url),
};
const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
const readJson = async (path) => JSON.parse(await readFile(path, "utf8"));
const writeJson = async (path, value) => writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
const readCargoPackageVersion = async () => {
const cargo = await readFile(paths.cargoToml, "utf8");
const packageSection = cargo.match(/\[package\]([\s\S]*?)(?=\n\[|$)/)?.[1];
const version = packageSection?.match(/^version\s*=\s*"([^"]+)"/m)?.[1];
if (!version) throw new Error("Cannot find [package].version in src-tauri/Cargo.toml");
return version;
};
const readCargoLockPackageVersion = async () => {
const cargoLock = await readFile(paths.cargoLock, "utf8");
const packageSection = cargoLock
.match(/\[\[package\]\]([\s\S]*?)(?=\n\[\[package\]\]|$)/g)
?.find((section) => /^name\s*=\s*"ctms-desktop"$/m.test(section));
const version = packageSection?.match(/^version\s*=\s*"([^"]+)"/m)?.[1];
if (!version) throw new Error("Cannot find ctms-desktop version in src-tauri/Cargo.lock");
return version;
};
const readVersions = async () => {
const [packageJson, packageLock, tauriConfig, cargoVersion, cargoLockVersion] = await Promise.all([
readJson(paths.packageJson),
readJson(paths.packageLock),
readJson(paths.tauriConfig),
readCargoPackageVersion(),
readCargoLockPackageVersion(),
]);
return {
"package.json": packageJson.version,
"package-lock.json": packageLock.version,
"package-lock.json root": packageLock.packages?.[""]?.version,
"tauri.conf.json": tauriConfig.version,
"Cargo.toml": cargoVersion,
"Cargo.lock": cargoLockVersion,
};
};
const assertVersionsMatch = async () => {
const versions = await readVersions();
const uniqueVersions = new Set(Object.values(versions));
if (uniqueVersions.size !== 1 || uniqueVersions.has(undefined)) {
const details = Object.entries(versions)
.map(([file, version]) => ` ${file}: ${version ?? "<missing>"}`)
.join("\n");
throw new Error(`Client versions are not synchronized:\n${details}`);
}
const [version] = uniqueVersions;
console.log(`Client version ${version} is synchronized.`);
};
const setVersion = async (version) => {
if (!SEMVER_PATTERN.test(version)) {
throw new Error(`Invalid semantic version: ${version}`);
}
const [packageJson, packageLock, tauriConfig, tauriConfigSource, cargo, cargoLock] = await Promise.all([
readJson(paths.packageJson),
readJson(paths.packageLock),
readJson(paths.tauriConfig),
readFile(paths.tauriConfig, "utf8"),
readFile(paths.cargoToml, "utf8"),
readFile(paths.cargoLock, "utf8"),
]);
packageJson.version = version;
packageLock.version = version;
packageLock.packages[""].version = version;
const tauriVersionPattern = /("version"\s*:\s*")[^"]+(")/;
const packageSectionPattern = /(\[package\][\s\S]*?^version\s*=\s*")[^"]+(")/m;
const lockPackagePattern =
/(\[\[package\]\]\nname\s*=\s*"ctms-desktop"\nversion\s*=\s*")[^"]+(")/m;
if (typeof tauriConfig.version !== "string" || !tauriVersionPattern.test(tauriConfigSource)) {
throw new Error("Cannot update version in src-tauri/tauri.conf.json");
}
if (!packageSectionPattern.test(cargo)) {
throw new Error("Cannot update [package].version in src-tauri/Cargo.toml");
}
if (!lockPackagePattern.test(cargoLock)) {
throw new Error("Cannot update ctms-desktop version in src-tauri/Cargo.lock");
}
await Promise.all([
writeJson(paths.packageJson, packageJson),
writeJson(paths.packageLock, packageLock),
writeFile(paths.tauriConfig, tauriConfigSource.replace(tauriVersionPattern, `$1${version}$2`)),
writeFile(paths.cargoToml, cargo.replace(packageSectionPattern, `$1${version}$2`)),
writeFile(paths.cargoLock, cargoLock.replace(lockPackagePattern, `$1${version}$2`)),
]);
console.log(`Updated CTMS Web and Desktop client version to ${version} in ${frontendDir}`);
await assertVersionsMatch();
};
const [, , command = "--check", value] = process.argv;
try {
if (command === "--check") {
await assertVersionsMatch();
} else if (command === "--set" && value) {
await setVersion(value);
} else {
throw new Error("Usage: node scripts/client-version.mjs [--check | --set <semver>]");
}
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
}