Skip to content

Instantly share code, notes, and snippets.

@imkarimkarim
Created June 10, 2025 05:17
Show Gist options
  • Save imkarimkarim/c1d08cf7ddac2709bc12d802cefc9bf7 to your computer and use it in GitHub Desktop.
Save imkarimkarim/c1d08cf7ddac2709bc12d802cefc9bf7 to your computer and use it in GitHub Desktop.
React Native Expo Version Manager
node set-version.mjs patch
node set-version.mjs minor
node set-version.mjs major
node set-version.mjs 0.0.1
#!/usr/bin/env node
import { execSync } from 'child_process';
import fs from 'fs';
import plist from 'plist';
import semver from 'semver';
import { Builder, parseStringPromise } from 'xml2js';
// Check for clean working tree
const hasChanges = execSync('git status --porcelain').toString().trim().length > 0;
if (hasChanges) {
console.error('❌ Working tree is not clean. Please commit or stash changes first.');
process.exit(1);
}
const arg = process.argv[2];
if (!arg) {
console.error('❌ Provide version or bump type (patch, minor, major)');
process.exit(1);
}
// --- 1. Read current version from package.json ---
const pkgPath = './package.json';
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
const currentVersion = pkg.version;
// --- 2. Resolve next version ---
let version;
if (['patch', 'minor', 'major'].includes(arg)) {
version = semver.inc(currentVersion, arg);
} else if (semver.valid(arg)) {
version = arg;
} else {
console.error(`❌ Invalid version or bump type: ${arg}`);
process.exit(1);
}
console.log(`πŸ”§ Updating version: ${currentVersion} β†’ ${version}`);
// --- Helper function ---
function updateJSONFile(filePath, updater) {
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
const updated = updater(data);
fs.writeFileSync(filePath, JSON.stringify(updated, null, 2));
console.log(`βœ… Updated ${filePath}`);
}
// --- Update app.json ---
updateJSONFile('./app.json', data => {
if (data.expo) data.expo.version = version;
return data;
});
// --- Update package.json ---
updateJSONFile(pkgPath, data => {
data.version = version;
return data;
});
// --- Update AndroidManifest.xml ---
const manifestPath = './android/app/src/main/AndroidManifest.xml';
if (fs.existsSync(manifestPath)) {
const manifestXml = fs.readFileSync(manifestPath, 'utf8');
const manifestObj = await parseStringPromise(manifestXml);
fs.writeFileSync(manifestPath, new Builder().buildObject(manifestObj));
console.log(`βœ… Verified ${manifestPath}`);
}
// --- Update build.gradle ---
const gradlePath = './android/app/build.gradle';
if (fs.existsSync(gradlePath)) {
let gradleText = fs.readFileSync(gradlePath, 'utf8');
gradleText = gradleText
.replace(/versionName\s+"[^"]+"/, `versionName "${version}"`)
.replace(
/versionCode\s+\d+/,
`versionCode ${semver.major(version)}${semver.minor(version)}${semver.patch(version)}`
);
fs.writeFileSync(gradlePath, gradleText);
console.log(`βœ… Updated ${gradlePath}`);
}
// --- Update Info.plist ---
const plistPath = './ios/Rumis/Info.plist';
if (fs.existsSync(plistPath)) {
const rawPlist = fs.readFileSync(plistPath, 'utf8');
const plistData = plist.parse(rawPlist);
plistData.CFBundleShortVersionString = version;
plistData.CFBundleVersion = version;
fs.writeFileSync(plistPath, plist.build(plistData));
console.log(`βœ… Updated ${plistPath}`);
}
// --- Create git tag ---
try {
// Format code
execSync('yarn format');
console.log('βœ… Formatted code');
// Check if there are changes
const hasChanges = execSync('git status --porcelain').toString().trim().length > 0;
if (hasChanges) {
// Commit version changes
execSync('git add .');
execSync(`git commit -m "chore: bump version to ${version}"`);
console.log(`βœ… Committed version changes`);
} else {
console.log('ℹ️ No changes to commit');
}
// Create and push tag
execSync(`git tag -a v${version} -m "Release v${version}"`);
execSync('git push --follow-tags');
console.log(`βœ… Created and pushed git tag v${version}`);
} catch (error) {
console.error('❌ Git operation failed:', error.message);
}
console.log('πŸŽ‰ Done!');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment