diff --git a/scripts/postlink/android/postlink.js b/scripts/postlink/android/postlink.js index def6bfd..1c24147 100644 --- a/scripts/postlink/android/postlink.js +++ b/scripts/postlink/android/postlink.js @@ -2,88 +2,94 @@ var fs = require("fs"); var glob = require("glob"); var path = require("path"); -var ignoreFolders = { ignore: ["node_modules/**", "**/build/**"] }; -var buildGradlePath = path.join("android", "app", "build.gradle"); -var manifestPath = glob.sync("**/AndroidManifest.xml", ignoreFolders)[0]; +module.exports = () => { -function findMainApplication() { - if (!manifestPath) { - return null; + console.log("Running android postlink script"); + + var ignoreFolders = { ignore: ["node_modules/**", "**/build/**"] }; + var buildGradlePath = path.join("android", "app", "build.gradle"); + var manifestPath = glob.sync("**/AndroidManifest.xml", ignoreFolders)[0]; + + function findMainApplication() { + if (!manifestPath) { + return null; + } + + var manifest = fs.readFileSync(manifestPath, "utf8"); + + // Android manifest must include single 'application' element + var matchResult = manifest.match(/application\s+android:name\s*=\s*"(.*?)"/); + if (matchResult) { + var appName = matchResult[1]; + } else { + return null; + } + + var nameParts = appName.split('.'); + var searchPath = glob.sync("**/" + nameParts[nameParts.length - 1] + ".java", ignoreFolders)[0]; + return searchPath; } - var manifest = fs.readFileSync(manifestPath, "utf8"); + var mainApplicationPath = findMainApplication() || glob.sync("**/MainApplication.java", ignoreFolders)[0]; - // Android manifest must include single 'application' element - var matchResult = manifest.match(/application\s+android:name\s*=\s*"(.*?)"/); - if (matchResult) { - var appName = matchResult[1]; - } else { - return null; + // 1. Add the getJSBundleFile override + var getJSBundleFileOverride = ` + @Override + protected String getJSBundleFile() { + return CodePush.getJSBundleFile(); + } + `; + + function isAlreadyOverridden(codeContents) { + return /@Override\s*\n\s*protected String getJSBundleFile\(\)\s*\{[\s\S]*?\}/.test(codeContents); } - - var nameParts = appName.split('.'); - var searchPath = glob.sync("**/" + nameParts[nameParts.length - 1] + ".java", ignoreFolders)[0]; - return searchPath; -} -var mainApplicationPath = findMainApplication() || glob.sync("**/MainApplication.java", ignoreFolders)[0]; - -// 1. Add the getJSBundleFile override -var getJSBundleFileOverride = ` - @Override - protected String getJSBundleFile() { - return CodePush.getJSBundleFile(); - } -`; - -function isAlreadyOverridden(codeContents) { - return /@Override\s*\n\s*protected String getJSBundleFile\(\)\s*\{[\s\S]*?\}/.test(codeContents); -} - -if (mainApplicationPath) { - var mainApplicationContents = fs.readFileSync(mainApplicationPath, "utf8"); - if (isAlreadyOverridden(mainApplicationContents)) { - console.log(`"getJSBundleFile" is already overridden`); - } else { - var reactNativeHostInstantiation = "new ReactNativeHost(this) {"; - mainApplicationContents = mainApplicationContents.replace(reactNativeHostInstantiation, - `${reactNativeHostInstantiation}\n${getJSBundleFileOverride}`); - fs.writeFileSync(mainApplicationPath, mainApplicationContents); - } -} else { - var mainActivityPath = glob.sync("**/MainActivity.java", ignoreFolders)[0]; - if (mainActivityPath) { - var mainActivityContents = fs.readFileSync(mainActivityPath, "utf8"); - if (isAlreadyOverridden(mainActivityContents)) { + if (mainApplicationPath) { + var mainApplicationContents = fs.readFileSync(mainApplicationPath, "utf8"); + if (isAlreadyOverridden(mainApplicationContents)) { console.log(`"getJSBundleFile" is already overridden`); } else { - var mainActivityClassDeclaration = "public class MainActivity extends ReactActivity {"; - mainActivityContents = mainActivityContents.replace(mainActivityClassDeclaration, - `${mainActivityClassDeclaration}\n${getJSBundleFileOverride}`); - fs.writeFileSync(mainActivityPath, mainActivityContents); + var reactNativeHostInstantiation = "new ReactNativeHost(this) {"; + mainApplicationContents = mainApplicationContents.replace(reactNativeHostInstantiation, + `${reactNativeHostInstantiation}\n${getJSBundleFileOverride}`); + fs.writeFileSync(mainApplicationPath, mainApplicationContents); } } else { - console.error(`Couldn't find Android application entry point. You might need to update it manually. \ -Please refer to plugin configuration section for Android at \ -https://github.com/microsoft/react-native-code-push#plugin-configuration-android for more details`); + var mainActivityPath = glob.sync("**/MainActivity.java", ignoreFolders)[0]; + if (mainActivityPath) { + var mainActivityContents = fs.readFileSync(mainActivityPath, "utf8"); + if (isAlreadyOverridden(mainActivityContents)) { + console.log(`"getJSBundleFile" is already overridden`); + } else { + var mainActivityClassDeclaration = "public class MainActivity extends ReactActivity {"; + mainActivityContents = mainActivityContents.replace(mainActivityClassDeclaration, + `${mainActivityClassDeclaration}\n${getJSBundleFileOverride}`); + fs.writeFileSync(mainActivityPath, mainActivityContents); + } + } else { + return Promise.reject(`Couldn't find Android application entry point. You might need to update it manually. \ + Please refer to plugin configuration section for Android at \ + https://github.com/microsoft/react-native-code-push#plugin-configuration-android for more details`); + } } -} -if (!fs.existsSync(buildGradlePath)) { - console.error(`Couldn't find build.gradle file. You might need to update it manually. \ -Please refer to plugin installation section for Android at \ -https://github.com/microsoft/react-native-code-push#plugin-installation-android---manual`); - return; -} + if (!fs.existsSync(buildGradlePath)) { + return Promise.reject(`Couldn't find build.gradle file. You might need to update it manually. \ + Please refer to plugin installation section for Android at \ + https://github.com/microsoft/react-native-code-push#plugin-installation-android---manual`); + } -// 2. Add the codepush.gradle build task definitions -var buildGradleContents = fs.readFileSync(buildGradlePath, "utf8"); -var reactGradleLink = buildGradleContents.match(/\napply from: ["'].*?react\.gradle["']/)[0]; -var codePushGradleLink = `apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"`; -if (~buildGradleContents.indexOf(codePushGradleLink)) { - console.log(`"codepush.gradle" is already linked in the build definition`); -} else { - buildGradleContents = buildGradleContents.replace(reactGradleLink, - `${reactGradleLink}\n${codePushGradleLink}`); - fs.writeFileSync(buildGradlePath, buildGradleContents); + // 2. Add the codepush.gradle build task definitions + var buildGradleContents = fs.readFileSync(buildGradlePath, "utf8"); + var reactGradleLink = buildGradleContents.match(/\napply from: ["'].*?react\.gradle["']/)[0]; + var codePushGradleLink = `apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"`; + if (~buildGradleContents.indexOf(codePushGradleLink)) { + console.log(`"codepush.gradle" is already linked in the build definition`); + } else { + buildGradleContents = buildGradleContents.replace(reactGradleLink, + `${reactGradleLink}\n${codePushGradleLink}`); + fs.writeFileSync(buildGradlePath, buildGradleContents); + } + + return Promise.resolve(); } \ No newline at end of file diff --git a/scripts/postlink/ios/postlink.js b/scripts/postlink/ios/postlink.js index 68c38bb..24f16d9 100644 --- a/scripts/postlink/ios/postlink.js +++ b/scripts/postlink/ios/postlink.js @@ -7,179 +7,184 @@ var xcode = require("xcode"); var package = require('../../../../../package.json'); -var ignoreNodeModules = { ignore: "node_modules/**" }; -var appDelegatePaths = glob.sync("**/AppDelegate.+(mm|m)", ignoreNodeModules); +module.exports = () => { -// Fix for https://github.com/Microsoft/react-native-code-push/issues/477 -// Typical location of AppDelegate.m for newer RN versions: $PROJECT_ROOT/ios//AppDelegate.m -// Let's try to find that path by filtering the whole array for any path containing -// If we can't find it there, play dumb and pray it is the first path we find. -var appDelegatePath = findFileByAppName(appDelegatePaths, package ? package.name : null) || appDelegatePaths[0]; + console.log("Running ios postlink script"); -if (!appDelegatePath) { - console.log(`Couldn't find AppDelegate. You might need to update it manually \ -Please refer to plugin configuration section for iOS at \ -https://github.com/microsoft/react-native-code-push#plugin-configuration-ios`); - return; -} + var ignoreNodeModules = { ignore: "node_modules/**" }; + var appDelegatePaths = glob.sync("**/AppDelegate.+(mm|m)", ignoreNodeModules); -var appDelegateContents = fs.readFileSync(appDelegatePath, "utf8"); + // Fix for https://github.com/Microsoft/react-native-code-push/issues/477 + // Typical location of AppDelegate.m for newer RN versions: $PROJECT_ROOT/ios//AppDelegate.m + // Let's try to find that path by filtering the whole array for any path containing + // If we can't find it there, play dumb and pray it is the first path we find. + var appDelegatePath = findFileByAppName(appDelegatePaths, package ? package.name : null) || appDelegatePaths[0]; -// 1. Add the header import statement -var codePushHeaderImportStatement = `#import `; -if (~appDelegateContents.indexOf(codePushHeaderImportStatement)) { - console.log(`"CodePush.h" header already imported.`); -} else { - var appDelegateHeaderImportStatement = `#import "AppDelegate.h"`; - appDelegateContents = appDelegateContents.replace(appDelegateHeaderImportStatement, - `${appDelegateHeaderImportStatement}\n${codePushHeaderImportStatement}`); -} - -// 2. Modify jsCodeLocation value assignment -var oldJsCodeLocationAssignmentStatement = appDelegateContents.match(/(jsCodeLocation = .*)/)[1]; -var newJsCodeLocationAssignmentStatement = "jsCodeLocation = [CodePush bundleURL];"; -if (~appDelegateContents.indexOf(newJsCodeLocationAssignmentStatement)) { - console.log(`"jsCodeLocation" already pointing to "[CodePush bundleURL]".`); -} else { - var jsCodeLocationPatch = ` -#ifdef DEBUG - ${oldJsCodeLocationAssignmentStatement} -#else - ${newJsCodeLocationAssignmentStatement} -#endif`; - appDelegateContents = appDelegateContents.replace(oldJsCodeLocationAssignmentStatement, - jsCodeLocationPatch); -} - -var plistPath = getPlistPath(); - -if (!plistPath) { - console.log(`Couldn't find .plist file. You might need to update it manually \ -Please refer to plugin configuration section for iOS at \ -https://github.com/microsoft/react-native-code-push#plugin-configuration-ios`); - return; -} - -var plistContents = fs.readFileSync(plistPath, "utf8"); - -// 3. Add CodePushDeploymentKey to plist file -var parsedInfoPlist = plist.parse(plistContents); -if (parsedInfoPlist.CodePushDeploymentKey) { - console.log(`"CodePushDeploymentKey" already specified in the plist file.`); - writePatches(); -} else { - inquirer.prompt({ - "type": "input", - "name": "iosDeploymentKey", - "message": "What is your CodePush deployment key for iOS (hit to ignore)" - }).then(function(answer) { - parsedInfoPlist.CodePushDeploymentKey = answer.iosDeploymentKey || "deployment-key-here"; - plistContents = plist.build(parsedInfoPlist); - - writePatches(); - }); -} - -function writePatches() { - fs.writeFileSync(appDelegatePath, appDelegateContents); - fs.writeFileSync(plistPath, plistContents); -} - -// Helper that filters an array with AppDelegate.m paths for a path with the app name inside it -// Should cover nearly all cases -function findFileByAppName(array, appName) { - if (array.length === 0 || !appName) return null; - - for (var i = 0; i < array.length; i++) { - var path = array[i]; - if (path && path.indexOf(appName) !== -1) { - return path; - } + if (!appDelegatePath) { + return Promise.reject(`Couldn't find AppDelegate. You might need to update it manually \ + Please refer to plugin configuration section for iOS at \ + https://github.com/microsoft/react-native-code-push#plugin-configuration-ios`); } - return null; -} + var appDelegateContents = fs.readFileSync(appDelegatePath, "utf8"); -function getDefaultPlistPath() { - //this is old logic in case we are unable to find PLIST from xcode/pbxproj - at least we can fallback to default solution - return glob.sync(`**/${package.name}/*Info.plist`, ignoreNodeModules)[0]; -} + // 1. Add the header import statement + var codePushHeaderImportStatement = `#import `; + if (~appDelegateContents.indexOf(codePushHeaderImportStatement)) { + console.log(`"CodePush.h" header already imported.`); + } else { + var appDelegateHeaderImportStatement = `#import "AppDelegate.h"`; + appDelegateContents = appDelegateContents.replace(appDelegateHeaderImportStatement, + `${appDelegateHeaderImportStatement}\n${codePushHeaderImportStatement}`); + } -// This is enhanced version of standard implementation of xcode 'getBuildProperty' function -// but allows us to narrow results by PRODUCT_NAME property also. -// So we suppose that proj name should be the same as package name, otherwise fallback to default plist path searching logic -function getBuildSettingsPropertyMatchingTargetProductName(parsedXCodeProj, prop, targetProductName, build){ - var target; - var COMMENT_KEY = /_comment$/; - var PRODUCT_NAME_PROJECT_KEY = 'PRODUCT_NAME'; - var TV_OS_DEPLOYMENT_TARGET_PROPERTY_NAME = 'TVOS_DEPLOYMENT_TARGET'; - var TEST_HOST_PROPERTY_NAME = 'TEST_HOST'; + // 2. Modify jsCodeLocation value assignment + var oldJsCodeLocationAssignmentStatement = appDelegateContents.match(/(jsCodeLocation = .*)/)[1]; + var newJsCodeLocationAssignmentStatement = "jsCodeLocation = [CodePush bundleURL];"; + if (~appDelegateContents.indexOf(newJsCodeLocationAssignmentStatement)) { + console.log(`"jsCodeLocation" already pointing to "[CodePush bundleURL]".`); + } else { + var jsCodeLocationPatch = ` + #ifdef DEBUG + ${oldJsCodeLocationAssignmentStatement} + #else + ${newJsCodeLocationAssignmentStatement} + #endif`; + appDelegateContents = appDelegateContents.replace(oldJsCodeLocationAssignmentStatement, + jsCodeLocationPatch); + } - var configs = parsedXCodeProj.pbxXCBuildConfigurationSection(); - for (var configName in configs) { - if (!COMMENT_KEY.test(configName)) { - var config = configs[configName]; - if ( (build && config.name === build) || (build === undefined) ) { - if (targetProductName) { - if (config.buildSettings[prop] !== undefined && config.buildSettings[PRODUCT_NAME_PROJECT_KEY] == targetProductName) { - target = config.buildSettings[prop]; - } - } else { - if (config.buildSettings[prop] !== undefined && - //exclude tvOS projects - config.buildSettings[TV_OS_DEPLOYMENT_TARGET_PROPERTY_NAME] == undefined && - //exclude test app - config.buildSettings[TEST_HOST_PROPERTY_NAME] == undefined) { - target = config.buildSettings[prop]; - } - } + var plistPath = getPlistPath(); + + if (!plistPath) { + return Promise.reject(`Couldn't find .plist file. You might need to update it manually \ + Please refer to plugin configuration section for iOS at \ + https://github.com/microsoft/react-native-code-push#plugin-configuration-ios`); + } + + var plistContents = fs.readFileSync(plistPath, "utf8"); + + // 3. Add CodePushDeploymentKey to plist file + var parsedInfoPlist = plist.parse(plistContents); + if (parsedInfoPlist.CodePushDeploymentKey) { + console.log(`"CodePushDeploymentKey" already specified in the plist file.`); + writePatches(); + return Promise.resolve(); + } else { + return inquirer.prompt({ + "type": "input", + "name": "iosDeploymentKey", + "message": "What is your CodePush deployment key for iOS (hit to ignore)" + }).then(function(answer) { + parsedInfoPlist.CodePushDeploymentKey = answer.iosDeploymentKey || "deployment-key-here"; + plistContents = plist.build(parsedInfoPlist); + + writePatches(); + return Promise.resolve(); + }); + } + + function writePatches() { + fs.writeFileSync(appDelegatePath, appDelegateContents); + fs.writeFileSync(plistPath, plistContents); + } + + // Helper that filters an array with AppDelegate.m paths for a path with the app name inside it + // Should cover nearly all cases + function findFileByAppName(array, appName) { + if (array.length === 0 || !appName) return null; + + for (var i = 0; i < array.length; i++) { + var path = array[i]; + if (path && path.indexOf(appName) !== -1) { + return path; } } - } - return target; -} -function getPlistPath(){ - var xcodeProjectPaths = glob.sync(`**/*.xcodeproj/project.pbxproj`, ignoreNodeModules); - if (!xcodeProjectPaths){ - return getDefaultPlistPath(); + return null; } - if (xcodeProjectPaths.length !== 1) { - console.log('Could not determine correct xcode proj path to retrieve related plist file, there are multiple xcodeproj under the solution.'); - return getDefaultPlistPath(); + function getDefaultPlistPath() { + //this is old logic in case we are unable to find PLIST from xcode/pbxproj - at least we can fallback to default solution + return glob.sync(`**/${package.name}/*Info.plist`, ignoreNodeModules)[0]; } - var xcodeProjectPath = xcodeProjectPaths[0]; - var parsedXCodeProj; + // This is enhanced version of standard implementation of xcode 'getBuildProperty' function + // but allows us to narrow results by PRODUCT_NAME property also. + // So we suppose that proj name should be the same as package name, otherwise fallback to default plist path searching logic + function getBuildSettingsPropertyMatchingTargetProductName(parsedXCodeProj, prop, targetProductName, build){ + var target; + var COMMENT_KEY = /_comment$/; + var PRODUCT_NAME_PROJECT_KEY = 'PRODUCT_NAME'; + var TV_OS_DEPLOYMENT_TARGET_PROPERTY_NAME = 'TVOS_DEPLOYMENT_TARGET'; + var TEST_HOST_PROPERTY_NAME = 'TEST_HOST'; - try { - var proj = xcode.project(xcodeProjectPath); - //use sync version because there are some problems with async version of xcode lib as of current version - parsedXCodeProj = proj.parseSync(); - } - catch(e) { - console.log('Couldn\'t read info.plist path from xcode project - error: ' + e.message); - return getDefaultPlistPath(); + var configs = parsedXCodeProj.pbxXCBuildConfigurationSection(); + for (var configName in configs) { + if (!COMMENT_KEY.test(configName)) { + var config = configs[configName]; + if ( (build && config.name === build) || (build === undefined) ) { + if (targetProductName) { + if (config.buildSettings[prop] !== undefined && config.buildSettings[PRODUCT_NAME_PROJECT_KEY] == targetProductName) { + target = config.buildSettings[prop]; + } + } else { + if (config.buildSettings[prop] !== undefined && + //exclude tvOS projects + config.buildSettings[TV_OS_DEPLOYMENT_TARGET_PROPERTY_NAME] == undefined && + //exclude test app + config.buildSettings[TEST_HOST_PROPERTY_NAME] == undefined) { + target = config.buildSettings[prop]; + } + } + } + } + } + return target; } - var INFO_PLIST_PROJECT_KEY = 'INFOPLIST_FILE'; - var RELEASE_BUILD_PROPERTY_NAME = "Release"; - var targetProductName = package ? package.name : null; + function getPlistPath(){ + var xcodeProjectPaths = glob.sync(`**/*.xcodeproj/project.pbxproj`, ignoreNodeModules); + if (!xcodeProjectPaths){ + return getDefaultPlistPath(); + } - //Try to get 'Release' build of ProductName matching the package name first and if it doesn't exist then try to get any other if existing - var plistPathValue = getBuildSettingsPropertyMatchingTargetProductName(parsedXCodeProj, INFO_PLIST_PROJECT_KEY, targetProductName, RELEASE_BUILD_PROPERTY_NAME) || - getBuildSettingsPropertyMatchingTargetProductName(parsedXCodeProj, INFO_PLIST_PROJECT_KEY, targetProductName) || - getBuildSettingsPropertyMatchingTargetProductName(parsedXCodeProj, INFO_PLIST_PROJECT_KEY, null, RELEASE_BUILD_PROPERTY_NAME) || - getBuildSettingsPropertyMatchingTargetProductName(parsedXCodeProj, INFO_PLIST_PROJECT_KEY) || - parsedXCodeProj.getBuildProperty(INFO_PLIST_PROJECT_KEY, RELEASE_BUILD_PROPERTY_NAME) || - parsedXCodeProj.getBuildProperty(INFO_PLIST_PROJECT_KEY); + if (xcodeProjectPaths.length !== 1) { + console.log('Could not determine correct xcode proj path to retrieve related plist file, there are multiple xcodeproj under the solution.'); + return getDefaultPlistPath(); + } - if (!plistPathValue){ - return getDefaultPlistPath(); + var xcodeProjectPath = xcodeProjectPaths[0]; + var parsedXCodeProj; + + try { + var proj = xcode.project(xcodeProjectPath); + //use sync version because there are some problems with async version of xcode lib as of current version + parsedXCodeProj = proj.parseSync(); + } + catch(e) { + console.log('Couldn\'t read info.plist path from xcode project - error: ' + e.message); + return getDefaultPlistPath(); + } + + var INFO_PLIST_PROJECT_KEY = 'INFOPLIST_FILE'; + var RELEASE_BUILD_PROPERTY_NAME = "Release"; + var targetProductName = package ? package.name : null; + + //Try to get 'Release' build of ProductName matching the package name first and if it doesn't exist then try to get any other if existing + var plistPathValue = getBuildSettingsPropertyMatchingTargetProductName(parsedXCodeProj, INFO_PLIST_PROJECT_KEY, targetProductName, RELEASE_BUILD_PROPERTY_NAME) || + getBuildSettingsPropertyMatchingTargetProductName(parsedXCodeProj, INFO_PLIST_PROJECT_KEY, targetProductName) || + getBuildSettingsPropertyMatchingTargetProductName(parsedXCodeProj, INFO_PLIST_PROJECT_KEY, null, RELEASE_BUILD_PROPERTY_NAME) || + getBuildSettingsPropertyMatchingTargetProductName(parsedXCodeProj, INFO_PLIST_PROJECT_KEY) || + parsedXCodeProj.getBuildProperty(INFO_PLIST_PROJECT_KEY, RELEASE_BUILD_PROPERTY_NAME) || + parsedXCodeProj.getBuildProperty(INFO_PLIST_PROJECT_KEY); + + if (!plistPathValue){ + return getDefaultPlistPath(); + } + + //also remove surrounding quotes from plistPathValue to get correct path resolved + //(see https://github.com/Microsoft/react-native-code-push/issues/534#issuecomment-302069326 for details) + return path.resolve(path.dirname(xcodeProjectPath), '..', plistPathValue.replace(/^"(.*)"$/, '$1')); } - - //also remove surrounding quotes from plistPathValue to get correct path resolved - //(see https://github.com/Microsoft/react-native-code-push/issues/534#issuecomment-302069326 for details) - return path.resolve(path.dirname(xcodeProjectPath), '..', plistPathValue.replace(/^"(.*)"$/, '$1')); -} +} \ No newline at end of file diff --git a/scripts/postlink/run.js b/scripts/postlink/run.js index 6910f7f..967ed2e 100644 --- a/scripts/postlink/run.js +++ b/scripts/postlink/run.js @@ -1,2 +1,11 @@ -require("./ios/postlink"); -require("./android/postlink"); \ No newline at end of file +var postlinks = [ + require("./ios/postlink"), + require("./android/postlink") +]; + +//run them sequentially +postlinks + .reduce((p, fn) => p.then(fn), Promise.resolve()) + .catch((err) => { + console.error(err.message); + }); \ No newline at end of file