diff --git a/README.md b/README.md index b9fce37a6..1399e087f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # React Native Module for CodePush +*Note: This README is only relevant to the latest version of our plugin. If you are using an older version, please switch to the relevant tag on [our GitHub repo](https://github.com/Microsoft/react-native-code-push) to view the docs for that particular version.* + +![Switching tags](https://cloud.githubusercontent.com/assets/8598682/17350832/ce0dec40-58de-11e6-9c8c-906bb114c34f.png) + This plugin provides client-side integration for the [CodePush service](http://codepush.tools), allowing you to easily add a dynamic update experience to your React Native app(s). * [How does it work?](#how-does-it-work) @@ -110,6 +114,8 @@ In order to accommodate as many developer preferences as possible, the CodePush *Note: If you don't already have RNPM installed, you can do so by simply running `npm i -g rnpm` and then executing the above command. If you already have RNPM installed, make sure you have v1.9.0+ in order to benefit from this one step install.* +2. You will be prompted for the deployment key you'd like to use. If you don't already have it, you can retrieve this value by running `code-push deployment ls -k`, or you can choose to ignore it (by simply hitting ``) and add it in later. To get started, we would recommend just using your `Staging` deployment key, so that you can test out the CodePush end-to-end. + And that's it! Isn't RNPM awesome? :) #### Plugin Installation (iOS - CocoaPods) @@ -160,6 +166,8 @@ Add a new value, `$(SRCROOT)/../node_modules/react-native-code-push` and select ### Plugin Configuration (iOS) +*NOTE: If you used RNPM or `react-native link` to automatically link the plugin, these steps have already been done for you so you may skip this section.* + Once your Xcode project has been setup to build/link the CodePush plugin, you need to configure your app to consult CodePush for the location of your JS bundle, since it is responsible for synchronizing it with updates that are released to the CodePush server. To do this, perform the following steps: 1. Open up the `AppDelegate.m` file, and add an import statement for the CodePush headers: @@ -230,15 +238,6 @@ In order to accommodate as many developer preferences as possible, the CodePush 2. If you're using RNPM >=1.6.0, you will be prompted for the deployment key you'd like to use. If you don't already have it, you can retreive this value by running `code-push deployment ls -k`, or you can choose to ignore it (by simply hitting ``) and add it in later. To get started, we would recommend just using your `Staging` deployment key, so that you can test out the CodePush end-to-end. -3. (Only needed in v1.8.0+ of the plugin) In your `android/app/build.gradle` file, add the `codepush.gradle` file as an additional build task definition underneath `react.gradle`: - - ```gradle - ... - apply from: "../../node_modules/react-native/react.gradle" - apply from: "../../node_modules/react-native-code-push/android/codepush.gradle" - ... - ``` - And that's it for installation using RNPM! Continue below to the [Plugin Configuration](#plugin-configuration-android) section to complete the setup. #### Plugin Installation (Android - Manual) @@ -260,7 +259,7 @@ And that's it for installation using RNPM! Continue below to the [Plugin Configu } ``` -3. (Only needed in v1.8.0+ of the plugin) In your `android/app/build.gradle` file, add the `codepush.gradle` file as an additional build task definition underneath `react.gradle`: +3. In your `android/app/build.gradle` file, add the `codepush.gradle` file as an additional build task definition underneath `react.gradle`: ```gradle ... @@ -271,7 +270,7 @@ And that's it for installation using RNPM! Continue below to the [Plugin Configu ### Plugin Configuration (Android) -*Note: If you are using an older version (<=1.9.0-beta) of the CodePush plugin, please refer to [these docs](https://github.com/Microsoft/react-native-code-push/tree/e717eb024fe9d1810ac21c40c097f7bc165ea5f1#plugin-configuration-android---react-native--v0180) instead.* +*NOTE: If you used RNPM or `react-native link` to automatically link the plugin, these steps have already been done for you so you may skip this section.* After installing the plugin and syncing your Android Studio project with Gradle, you need to configure your app to consult CodePush for the location of your JS bundle, since it will "take control" of managing the current and all future versions. To do this: diff --git a/package.json b/package.json index 3df570585..fbe4437a1 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,9 @@ "url": "https://github.com/Microsoft/react-native-code-push" }, "dependencies": { - "code-push": "1.8.0-beta" + "code-push": "1.8.0-beta", + "inquirer": "1.1.2", + "plist": "1.2.0" }, "devDependencies": { "archiver": "latest", @@ -33,16 +35,23 @@ "run-sequence": "latest" }, "rnpm": { - "android": { - "packageInstance": "new CodePush(${androidDeploymentKey}, this, BuildConfig.DEBUG)" - }, - "ios": { - "sharedLibraries": ["libz"] - }, - "params": [{ + "android": { + "packageInstance": "new CodePush(${androidDeploymentKey}, this, BuildConfig.DEBUG)" + }, + "ios": { + "sharedLibraries": [ + "libz" + ] + }, + "params": [ + { "type": "input", "name": "androidDeploymentKey", "message": "What is your CodePush deployment key for Android (hit to ignore)" - }] + } + ], + "commands": { + "postlink": "node node_modules/react-native-code-push/scripts/postlink/run" + } } } diff --git a/scripts/postlink/android/postlink.js b/scripts/postlink/android/postlink.js new file mode 100644 index 000000000..611c3a4c2 --- /dev/null +++ b/scripts/postlink/android/postlink.js @@ -0,0 +1,54 @@ +var fs = require("fs"); +var glob = require("glob"); +var path = require("path"); + +var ignoreNodeModules = { ignore: "node_modules/**" }; +var mainApplicationPath = glob.sync("**/MainApplication.java", ignoreNodeModules)[0]; +var mainActivityPath = glob.sync("**/MainActivity.java", ignoreNodeModules)[0]; +var buildGradlePath = path.join("android", "app", "build.gradle"); + +// 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 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); + } +} + +// 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); +} \ No newline at end of file diff --git a/scripts/postlink/ios/postlink.js b/scripts/postlink/ios/postlink.js new file mode 100644 index 000000000..01db1e4e9 --- /dev/null +++ b/scripts/postlink/ios/postlink.js @@ -0,0 +1,62 @@ +var fs = require("fs"); +var glob = require("glob"); +var inquirer = require('inquirer'); +var path = require("path"); +var plist = require("plist"); + +var ignoreNodeModules = { ignore: "node_modules/**" }; +var appDelegatePath = glob.sync("**/AppDelegate.m", ignoreNodeModules)[0]; +// Glob only allows foward slashes in patterns: https://www.npmjs.com/package/glob#windows +var plistPath = glob.sync(path.join(path.dirname(appDelegatePath), "*Info.plist").replace(/\\/g, "/"), ignoreNodeModules)[0]; + +var appDelegateContents = fs.readFileSync(appDelegatePath, "utf8"); +var plistContents = fs.readFileSync(plistPath, "utf8"); + +// 1. Add the header import statement +var codePushHeaderImportStatement = `#import "CodePush.h"`; +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 = .*)\n/)[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); +} + +// 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); +} \ No newline at end of file diff --git a/scripts/postlink/run.js b/scripts/postlink/run.js new file mode 100644 index 000000000..6910f7f5a --- /dev/null +++ b/scripts/postlink/run.js @@ -0,0 +1,2 @@ +require("./ios/postlink"); +require("./android/postlink"); \ No newline at end of file