// This script sets up the version numbers for a release build. // // Run it as `node -r sucrase/register ./scripts/gitVersionFile.ts []` // // Each production build has a version number and a build number. // The version number is a human-readable string like, "1.2.3-d", // while the build number is a machine-readable integer like 18010203, // based on the current date and an incrementing counter. // // The version number comes from package.json, plus an optional suffix // based on the current branch name, which this script takes as a parameter. // // Once this script determines the version information, // it returns a JSON string with all version information. // /****************************************************************************/ // WARNING: This script is run from the Jenkinsfile without npm install of // the package.json dependencies. It should require only default nodejs // packages. It may use typescript as sucrase will be installed by the // Jenkinsfile /****************************************************************************/ import childProcess from 'child_process' import fs from 'fs' import { join } from 'path' const specialBranches: Record = { develop: '-d', master: '', beta: '', coinhub: '', staging: '-rc', test: '-t', yolo: '-yolo', 'test-cheddar': '-cheddar', 'test-feta': '-feta', 'test-gouda': '-gouda', 'test-halloumi': '-halloumi', 'test-paneer': '-paneer' } let _currentPath = __dirname const baseDir = join(_currentPath, '..') const versionFileName = 'release-version.json' async function main(): Promise { const cwd = join(__dirname, '..') const branch = process.argv[2] ?? 'master' // Determine the current version: const packageJson = JSON.parse( fs.readFileSync(join(cwd, 'package.json'), { encoding: 'utf8' }) ) const version = `${packageJson.version}${pickVersionSuffix(branch)}` updateVersionFile(branch, version) } function updateVersionFile(branch: string, version: string): void { const buildRepoUrl = process.env.BUILD_REPO_URL ?? 'git@github.com:EdgeApp/edge-build-server.git' const githubSshKey = process.env.GITHUB_SSH_KEY ?? join(baseDir, 'id_github') // Determine the current build number: const pathTemp = buildRepoUrl.split('/') const repo = pathTemp[pathTemp.length - 1].replace('.git', '') const repoPath = join(baseDir, repo) let retries = 5 while (--retries > 0) { if (fs.existsSync(repoPath)) { call(`rm -rf ${repoPath}`) } // Clone repo chdir(baseDir) call( `GIT_SSH_COMMAND="ssh -i ${githubSshKey}" git clone --depth 1 ${buildRepoUrl}` ) const newBuildNum = pickBuildNumber() let build // Rm edge-build-server const versionFileDir = join(repoPath, 'versionFiles', branch) const versionFilePath = join(versionFileDir, versionFileName) if (fs.existsSync(versionFilePath)) { const result = fs.readFileSync(versionFilePath, { encoding: 'utf8' }) const { build: previousBuild } = JSON.parse(result) if (typeof previousBuild !== 'number') throw new Error(`Invalid previous buildNum ${previousBuild}`) // Advance by 3, not 1, so each build owns a block of three // versionCodes: the Android split APKs add per-ABI offsets to the // build number (universal +0, armeabi-v7a +1, arm64-v8a +2, see // app/build.gradle), and Google Play rejects any versionCode it // has ever seen, so consecutive builds must never overlap blocks. // The stride must stay >= the number of APK flavors per build, // which is pinned by the gradle splits include list. Widening the // gap the other way, by giving the splits their own numeric range // (build * 10 + offset) and leaving this at +1, does not work: the // next build's universal APK would then carry a lower code than // the previous build's splits, which Play treats as a downgrade, // and the universal APK has to keep reporting the plain build // number because getBuildNumber() returns the versionCode and the // info server string-compares it against minBuildNum/maxBuildNum/ // exactBuildNum rules. // // Costs, both accepted: iOS build numbers share this counter and // simply skip by 3, and same-day capacity drops from 99 builds to // 33 before the date-shaped number bleeds into the next day's // range. That bleed already existed at 99, and it only misreads // the date -- build numbers stay unique and increasing either way: build = Math.max(previousBuild + 3, newBuildNum) } else { build = newBuildNum } const tryVersionFile = { build, version, branch } call(`mkdir -p ${versionFileDir}`) const versionFileString = JSON.stringify(tryVersionFile) fs.writeFileSync(versionFilePath, versionFileString, { encoding: 'utf8' }) fs.writeFileSync(join(baseDir, versionFileName), versionFileString, { encoding: 'utf8' }) chdir(repoPath) call(`git add ${versionFilePath}`) call(`git commit -m "Update ${branch} to build ${build}"`) try { call(`GIT_SSH_COMMAND="ssh -i ${githubSshKey}" git push`) fs.writeFileSync(join(baseDir, versionFileName), versionFileString, { encoding: 'utf8' }) process.exit(0) } catch (e: any) { // Error pushing file. Retry a few times } } console.error(`Unable to get new version`) process.exit(-1) } /** * Pick a build number based on the current date. */ function pickBuildNumber(now: Date = new Date()): number { const year = now.getFullYear() - 2000 const month = now.getMonth() + 1 const day = now.getDate() const counter = 1 return (year % 100) * 1000000 + month * 10000 + day * 100 + counter } /** * Pick a suffix to add to the package.json version. */ function pickVersionSuffix(branch?: string): string { if (branch == null || branch === '') return '' const specialSuffix = specialBranches[branch] if (specialSuffix != null) return specialSuffix return '-' + branch.replace(/[^0-9a-zA-Z]+/g, '-') } function chdir(path: string): void { _currentPath = path } function call(cmdstring: string): void { childProcess.execSync(cmdstring, { encoding: 'utf8', timeout: 3600000, stdio: 'inherit', cwd: _currentPath, killSignal: 'SIGKILL' }) } main().catch((error: unknown) => { console.error(error) process.exit(-1) })