feat(app): automate iOS beta distribution

Beta tags now reach external testers without per-release dashboard work. Reserve distinct native build slots so successive betas and the promoted stable build can coexist under one app version.
This commit is contained in:
Mohamed Boudra
2026-08-04 19:21:22 +02:00
parent adf508a1c8
commit d191fa6685
6 changed files with 105 additions and 33 deletions
+41
View File
@@ -0,0 +1,41 @@
const versionPattern = /^(\d+)\.(\d+)\.(\d+)(?:-beta\.(\d+))?$/;
const stableIosBuildSlot = 999;
function getNativeReleaseVersion(version) {
const match = versionPattern.exec(version);
if (!match) {
throw new Error(`Cannot derive native release version from unsupported version: ${version}`);
}
const [, majorText, minorText, patchText, betaText] = match;
const major = Number(majorText);
const minor = Number(minorText);
const patch = Number(patchText);
const betaNumber = betaText === undefined ? null : Number(betaText);
if (minor > 999 || patch > 999) {
throw new Error(`Cannot derive collision-free native version from: ${version}`);
}
if (betaNumber !== null && (betaNumber < 1 || betaNumber >= stableIosBuildSlot)) {
throw new Error(`iOS beta number must be between 1 and 998: ${version}`);
}
const versionCode = major * 1_000_000 + minor * 1_000 + patch;
if (!Number.isSafeInteger(versionCode) || versionCode <= 0 || versionCode > 2_100_000_000) {
throw new Error(`Derived Android versionCode is out of range: ${versionCode}`);
}
const iosBuildSlot = betaNumber ?? stableIosBuildSlot;
const iosBuildNumber = versionCode * 1_000 + iosBuildSlot;
if (!Number.isSafeInteger(iosBuildNumber)) {
throw new Error(`Derived iOS buildNumber is out of range: ${iosBuildNumber}`);
}
return {
appVersion: `${major}.${minor}.${patch}`,
androidVersionCode: versionCode,
iosBuildNumber: String(iosBuildNumber),
};
}
module.exports = { getNativeReleaseVersion };