Files

63 lines
2.9 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
'use strict';
// stamp-version.js — push release.config.json's numbers into the Xcode project.
//
// Android reads release.config.json directly from build.gradle, but Xcode keeps
// MARKETING_VERSION / CURRENT_PROJECT_VERSION inside project.pbxproj, so they have to be
// written in. Both stores then get the same marketing version from one edited file, which
// is the whole point: a build number that silently disagrees between platforms is how you
// end up with "1.4 on Android, 1.2 on iPhone" bug reports that nobody can reproduce.
//
// Run with --check to verify without writing (used by release.sh as a pre-flight).
const fs = require('fs');
const path = require('path');
const MOBILE = path.resolve(__dirname, '..');
const PBXPROJ = path.join(MOBILE, 'ios', 'App', 'App.xcodeproj', 'project.pbxproj');
const release = JSON.parse(fs.readFileSync(path.join(MOBILE, 'release.config.json'), 'utf8'));
const version = String(release.version || '').trim();
const iosBuild = release.iosBuild;
// Apple rejects a CFBundleShortVersionString that isn't 1-3 dot-separated integers, and it
// rejects it at upload time — after the whole archive has already been built and signed.
if (!/^\d+(\.\d+){0,2}$/.test(version)) {
console.error(`✗ version must be 1-3 dot-separated numbers (e.g. 1.2.3) — got ${JSON.stringify(release.version)}`);
process.exit(1);
}
if (!Number.isInteger(iosBuild) || iosBuild < 1) {
console.error(`✗ iosBuild must be a positive integer — got ${JSON.stringify(iosBuild)}`);
process.exit(1);
}
if (!Number.isInteger(release.androidVersionCode) || release.androidVersionCode < 1) {
console.error(`✗ androidVersionCode must be a positive integer — got ${JSON.stringify(release.androidVersionCode)}`);
process.exit(1);
}
let pbx = fs.readFileSync(PBXPROJ, 'utf8');
const before = pbx;
// Both the Debug and Release configurations carry these, hence the global replace: an
// archive built from the wrong configuration must not ship a stale build number.
pbx = pbx.replace(/MARKETING_VERSION = [^;]+;/g, `MARKETING_VERSION = ${version};`);
pbx = pbx.replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, `CURRENT_PROJECT_VERSION = ${iosBuild};`);
const marketingHits = (pbx.match(/MARKETING_VERSION = /g) || []).length;
const buildHits = (pbx.match(/CURRENT_PROJECT_VERSION = /g) || []).length;
if (marketingHits < 2 || buildHits < 2) {
console.error(`✗ expected MARKETING_VERSION and CURRENT_PROJECT_VERSION in both build configs (found ${marketingHits}/${buildHits})`);
process.exit(1);
}
if (process.argv.includes('--check')) {
console.log(pbx === before
? `✓ Xcode already at ${version} (${iosBuild})`
: `• Xcode would change to ${version} (${iosBuild})`);
process.exit(0);
}
if (pbx !== before) fs.writeFileSync(PBXPROJ, pbx);
console.log(`✓ Xcode stamped ${version} (build ${iosBuild})`);
console.log(`✓ Gradle reads ${version} (code ${release.androidVersionCode}) from release.config.json`);