From 030bbf23e2cfdc09623ff644b953de7ea95e7690 Mon Sep 17 00:00:00 2001 From: Gradyn Wursten Date: Fri, 5 Dec 2025 12:40:27 -0700 Subject: [PATCH 1/3] add support for info.plist variables and ios build number check --- index.js | 5 +-- lib/helpers.js | 89 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 716cbf9..7b0cf28 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,7 @@ const pathToPackage = argv.pathToPackage || `${pathToRoot}/package.json`; const info = helpers.getPackageInfo(pathToPackage); const pathToPlist = argv.pathToPlist || `${pathToRoot}/ios/${info.name}/Info.plist`; +const pathToPbxproj = argv.pathToPbxproj || `${pathToRoot}/ios/${info.name}.xcodeproj/project.pbxproj`; const pathToGradle = argv.pathToGradle || `${pathToRoot}/android/app/build.gradle`; // handle case of several plist files const pathsToPlists = Array.isArray(pathToPlist) ? pathToPlist : [pathToPlist]; @@ -27,7 +28,7 @@ let patch = helpers.version(versions[2], argv.patch, argv.major || argv.minor); const version = `${major}.${minor}.${patch}`; // getting next build number -const buildCurrent = helpers.getBuildNumberFromPlist(pathsToPlists[0]); +const buildCurrent = helpers.getMaximumBuildNumber(pathsToPlists[0], pathToPbxproj); const build = buildCurrent + 1; // getting commit message @@ -73,7 +74,7 @@ const update = chain.then(() => { log.info('Updating version in xcode project...', 1); pathsToPlists.forEach(pathToPlist => { - helpers.changeVersionAndBuildInPlist(pathToPlist, version, build); + helpers.changeVersionAndBuildInPlist(pathToPlist, version, build, pathToPbxproj); }); log.success(`Version and build number in ios project (plist file) changed.`, 2); }).then(() => { diff --git a/lib/helpers.js b/lib/helpers.js index c411c25..8352981 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -19,31 +19,102 @@ module.exports = { return flag ? value + 1 : value; }, + replacePlaceholdersFromPbxproj(string, pathToPbxproj) { + const pbxProjContent = fs.readFileSync(pathToPbxproj, 'utf8'); + + // Convert to set and back to remove duplicates, since replaceAll will replace all instances of a placeholder + const placeholders = [...new Set(string.match(/\$\([^)]*\)/))]; + for (let placeholder of placeholders) { + // Slice to remove $( and ) + const regex = new RegExp(`${placeholder.slice(2,-1)}\\s*=\\s*([^;]+);`); + const match = pbxProjContent.match(regex); + if (match) string = string.replaceAll(placeholder, match[1]); + } + + return string; + }, + getPackageInfo(pathToFile) { return JSON.parse(fs.readFileSync(pathToFile, 'utf8')); }, - getBuildNumberFromPlist(pathToPlist) { + getMaximumBuildNumber(pathToPlist, pathToPbxproj = null) { + const buildNumbers = []; + buildNumbers.push(this.getBuildNumberFromPlist(pathToPlist, pathToPbxproj)); + + return Math.max(...buildNumbers); + }, + + matchPlistBuildNumber(content) { + return content.match(/(CFBundleVersion<\/key>\s+)(.*)(<\/string>)/)[2]; + }, + + matchPlistVersionNumber(content) { + return content.match(/(CFBundleShortVersionString<\/key>\s+)(.*)(<\/string>)/)[2]; + }, + + getBuildNumberFromPlist(pathToPlist, pathToPbxproj = null) { const content = fs.readFileSync(pathToPlist, 'utf8'); - const match = content.match(/(CFBundleVersion<\/key>\s+)([\d\.]+)(<\/string>)/); - if (match && match[2]) { - return parseInt(match[2]); + const match = this.matchPlistBuildNumber(content); + if (match) { + let result = match; + if (pathToPbxproj) result = this.replacePlaceholdersFromPbxproj(match, pathToPbxproj); + return parseInt(result); } return 1; }, + setBuildSetting(content, key, newValue) { + // Escape key in case it ever contains regex characters + const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + + const regex = new RegExp(`(${escapedKey}\\s*=\\s*)([^;]*)(;)`, 'g'); + + return content.replaceAll(regex, (_, prefix, _oldValue, suffix) => { + return `${prefix}${newValue}${suffix}`; + }); + }, + changeVersionInPackage(pathToFile, version) { let packageContent = fs.readFileSync(pathToFile, 'utf8'); packageContent = packageContent.replace(/("version":\s*")([\d\.]+)(")/g, `$1${version}$3`); fs.writeFileSync(pathToFile, packageContent, 'utf8'); }, - changeVersionAndBuildInPlist(pathToFile, version, build) { - let content = fs.readFileSync(pathToFile, 'utf8'); - content = content.replace(/(CFBundleShortVersionString<\/key>\s*)([\d\.]+)(<\/string>)/g, `$1${version}$3`); - content = content.replace(/(CFBundleVersion<\/key>\s+)([\d\.]+)(<\/string>)/g, `$1${build}$3`); - fs.writeFileSync(pathToFile, content, 'utf8'); + isSingleVariableRef(str) { + return /^\$\([^)]+\)$/.test(str); + }, + + changeVersionAndBuildInPlist(pathToPlist, version, build, pathToPbxproj = null) { + let plistContent = fs.readFileSync(pathToPlist, 'utf8'); + let pbxProjContent = null; + const rawBuildNumber = this.matchPlistBuildNumber(plistContent); + const rawVersion = this.matchPlistVersionNumber(plistContent); + + if (pathToPbxproj) { + pbxProjContent = fs.readFileSync(pathToPbxproj, 'utf8'); + } + + if (!isNaN(parseInt(rawBuildNumber))) { + plistContent = plistContent.replace(/(CFBundleVersion<\/key>\s+)([\d\.]+)(<\/string>)/g, `$1${build}$3`); + } + else if (this.isSingleVariableRef(rawBuildNumber) && pathToPbxproj) { + pbxProjContent = this.setBuildSetting(pbxProjContent, rawBuildNumber.slice(2, -1), build); + } else { + throw new Error('Build number in plist is not a single variable nor a number. Unsure how to proceed, bailing out'); + } + + if (!rawVersion.split('.').some(part => !isNaN(parseInt(part))) && rawVersion.split('.').length === 3) { + plistContent = plistContent.replace(/(CFBundleShortVersionString<\/key>\s*)([\d\.]+)(<\/string>)/g, `$1${version}$3`); + } else if (this.isSingleVariableRef(rawVersion) && pathToPbxproj) { + pbxProjContent = this.setBuildSetting(pbxProjContent, rawVersion.slice(2, -1), version); + } else { + throw new Error('Version number in plist is not a single variable nor a number. Unsure how to proceed, bailing out'); + } + + fs.writeFileSync(pathToPlist, plistContent, 'utf8'); + if (pathToPbxproj) fs.writeFileSync(pathToPbxproj, pbxProjContent, 'utf8'); }, changeVersionAndBuildInGradle(pathToFile, version, build) { From e382a688c2f36a0d2d5461622a2a012d277faef7 Mon Sep 17 00:00:00 2001 From: Gradyn Wursten Date: Mon, 8 Dec 2025 13:02:35 -0700 Subject: [PATCH 2/3] fix git tagging on windows --- lib/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/helpers.js b/lib/helpers.js index 8352981..4450dcf 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -126,7 +126,7 @@ module.exports = { commitVersionIncrease(version, message, pathsToAdd = []) { return new Promise((resolve, reject) => { - exec(`git add ${pathsToAdd.join(' ')} && git commit -m '${message}' && git tag -a v${version} -m '${message}'`, error => { + exec(`git add ${pathsToAdd.join(' ')} && git commit -m "${message}" && git tag -a v${version} -m "${message}"`, error => { if (error) { reject(error); return; From 1e6bd2735f1835958e3f642c51eaf971eeacc7eb Mon Sep 17 00:00:00 2001 From: Gradyn Wursten Date: Mon, 8 Dec 2025 13:10:05 -0700 Subject: [PATCH 3/3] Include pathToPbxproj in commit files Added pathToPbxproj to the commit files array. --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 7b0cf28..5a91cb1 100644 --- a/index.js +++ b/index.js @@ -97,7 +97,8 @@ const commit = update.then(() => { return helpers.commitVersionIncrease(version, message, [ pathToPackage, ...pathsToPlists, - pathToGradle + pathToGradle, + pathToPbxproj ]).then(() => { log.success(`Commit with files added. Run "git push".`, 1); });