Skip to content

Commit

Permalink
Handle errors in postlink scripts (#6267)
Browse files Browse the repository at this point in the history
This PR aims to provide some basic error handling logic to the postlink scripts, which run after `react-native link`, by identifying potential drawbacks and managing the execution flow via try/catch statements and the use of throw under specific conditions. It was developed with the support of the @underscopeio team!

I added a few messages I thought are informative, which provide some basic feedback and direct the user to the specific section in the documentation whenever it's possible. The script was mostly optimistic, so I changed this a little bit to throw a few errors when unexpected things occur. On the previous version, errors would either be silent or be incorrectly informed as the library being already linked, as this could not be asserted from the information available during execution.

Also, I replaced a few single quote/double quote discrepancies on the files I worked on (went for double quotes since that was predominant among the scripts). But adding linting/prettier to these scripts would be nice too, this could be done in a future PR if there's interest.

I'd also like to address the issue of the deprecation warning for `react-native link` on a separate PR, I have a candidate solution that should require minimum changes on the docs and the process. I will send this shortly.
  • Loading branch information
Eduardo Pelitti authored Jun 8, 2020
1 parent ab63850 commit daa48ca
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 133 deletions.
61 changes: 49 additions & 12 deletions autolink/postlink/activityLinker.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
// @ts-check
var path = require('./path');
var path = require("./path");
var fs = require("fs");
var { warnn, logn, infon, debugn } = require("./log");
var { errorn, warnn, logn, infon, debugn } = require("./log");

class ActivityLinker {
constructor() {
this.activityPath = path.mainActivityJava;
this.extendNavigationActivitySuccess = false;
this.removeGetMainComponentNameSuccess = false;
}

link() {
if (!this.activityPath) {
errorn(
" MainActivity not found! Does the file exist in the correct folder?\n Please check the manual installation docs:\n https://wix.github.io/react-native-navigation/docs/installing#2-update-mainactivityjava"
);
return;
}

logn("Linking MainActivity...");
if (this.activityPath) {
var activityContent = fs.readFileSync(this.activityPath, "utf8");

var activityContent = fs.readFileSync(this.activityPath, "utf8");

try {
activityContent = this._extendNavigationActivity(activityContent);
this.extendNavigationActivitySuccess = true;
} catch (e) {
errorn(" " + e.message);
}

try {
activityContent = this._removeGetMainComponentName(activityContent);
fs.writeFileSync(this.activityPath, activityContent);
this.removeGetMainComponentNameSuccess = true;
} catch (e) {
errorn(" " + e.message);
}

fs.writeFileSync(this.activityPath, activityContent);
if (this.extendNavigationActivitySuccess && this.removeGetMainComponentNameSuccess) {
infon("MainActivity linked successfully!\n");
} else if (!this.extendNavigationActivitySuccess && !this.removeGetMainComponentNameSuccess) {
errorn(
"MainActivity was not linked. Please check the logs above for more information and proceed with manual linking of the MainActivity file in Android:\nhttps://wix.github.io/react-native-navigation/docs/installing#2-update-mainactivityjava"
);
} else {
warnn(" MainActivity not found!");
warnn(
"MainActivity was only partially linked. Please check the logs above for more information and proceed with manual linking for the failed steps:\nhttps://wix.github.io/react-native-navigation/docs/installing#2-update-mainactivityjava"
);
}
}

Expand All @@ -27,24 +56,32 @@ class ActivityLinker {
debugn(" Removing getMainComponentName function");
return contents.replace(/\/\*\*\s*\n([^\*]|(\*(?!\/)))*\*\/\s*@Override\s*protected\s*String\s*getMainComponentName\s*\(\)\s*{\s*return.+\s*\}/, "");
}
warnn(" getMainComponentName function was not found.");
return contents;
}

_extendNavigationActivity(activityContent) {
if (this._doesActivityExtendReactActivity(activityContent)) {
debugn(" Extending NavigationActivity")
debugn(" Extending NavigationActivity");
return activityContent
.replace(/extends\s+ReactActivity\s*/, "extends NavigationActivity ")
.replace("import com.facebook.react.ReactActivity;", "import com.reactnativenavigation.NavigationActivity;")
} else {
warnn(" MainActivity already extends NavigationActivity")
.replace("import com.facebook.react.ReactActivity;", "import com.reactnativenavigation.NavigationActivity;");
}
return activityContent;
if (this._hasAlreadyExtendReactActivity) {
warnn(" MainActivity already extends NavigationActivity");
return activityContent;
}

throw new Error("MainActivity was not successfully replaced. Please check the documentation and proceed manually.");
}

_doesActivityExtendReactActivity(activityContent) {
return /public\s+class\s+MainActivity\s+extends\s+ReactActivity\s*/.test(activityContent);
}

_hasAlreadyExtendReactActivity(activityContent) {
return /public\s+class\s+MainActivity\s+extends\s+ReactActivity\s*/.test(activityContent);
}
}

module.exports = ActivityLinker;
module.exports = ActivityLinker;
155 changes: 111 additions & 44 deletions autolink/postlink/appDelegateLinker.js
Original file line number Diff line number Diff line change
@@ -1,78 +1,145 @@
// @ts-check
var path = require('./path');
var fs = require("fs");
var {warnn, logn, infon, debugn} = require("./log");
var path = require("./path");
var { warnn, logn, infon, debugn, errorn } = require("./log");

class ApplicationLinker {
class AppDelegateLinker {
constructor() {
this.appDelegatePath = path.appDelegate;
this.removeUnneededImportsSuccess = false;
this.removeApplicationLaunchContentSuccess = false;
}

link() {
if (this.appDelegatePath) {
logn("Linking AppDelegate...");
var appDelegateContents = fs.readFileSync(this.appDelegatePath, "utf8");
if (this._doesBootstrapNavigation(appDelegateContents)) {
infon("AppDelegate already linked\n");
return;
}
if (!this.appDelegatePath) {
errorn(
" AppDelegate not found! Does the file exist in the correct folder?\n Please check the manual installation docs:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation"
);
return;
}

logn("Linking AppDelegate...");

var appDelegateContents = fs.readFileSync(this.appDelegatePath, "utf8");

try {
appDelegateContents = this._removeUnneededImports(appDelegateContents);
appDelegateContents = this._importNavigation(appDelegateContents);
appDelegateContents = this._bootstrapNavigation(appDelegateContents);
this.removeUnneededImportsSuccess = true;
} catch (e) {
errorn(" " + e.message);
}

appDelegateContents = this._importNavigation(appDelegateContents);

appDelegateContents = this._bootstrapNavigation(appDelegateContents);

try {
appDelegateContents = this._removeApplicationLaunchContent(appDelegateContents);
fs.writeFileSync(this.appDelegatePath, appDelegateContents);
this.removeApplicationLaunchContentSuccess = true;
} catch (e) {
errorn(" " + e.message);
}

fs.writeFileSync(this.appDelegatePath, appDelegateContents);

if (this.removeUnneededImportsSuccess && this.removeApplicationLaunchContentSuccess) {
infon("AppDelegate linked successfully!\n");
} else {
warnn("AppDelegate was partially linked, please check the details above and proceed with the manual installation documentation to complete the linking process.!\n");
}
}

_doesBootstrapNavigation(applicationContent) {
return /ReactNativeNavigation\s+bootstrap/.test(applicationContent);
}
_removeUnneededImports(content) {
debugn(" Removing Unneeded imports");

_removeUnneededImports(applicationContent) {
const unneededImports = [/\#import\s+\<React\/RCTBridge.h>\s/, /#import\s+\<React\/RCTRootView.h>\s/];
debugn(" Removing Unneeded imports");
unneededImports.forEach(unneededImport => {
if (unneededImport.test(applicationContent)) {
applicationContent = applicationContent.replace(unneededImport, "");
let elementsRemovedCount = 0;

unneededImports.forEach((unneededImport) => {
if (unneededImport.test(content)) {
content = content.replace(unneededImport, "");
elementsRemovedCount++;
}
});

return applicationContent;
if (unneededImports.length === elementsRemovedCount) {
debugn(" All imports have been removed");
} else if (elementsRemovedCount === 0) {
warnn(
" No imports could be removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation"
);
} else {
throw new Error(
"Some imports were removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation"
);
}

return content;
}

_importNavigation(content) {
if (!this._doesImportNavigation(content)) {
debugn(" Importing ReactNativeNavigation.h");
return content.replace(/#import\s+"AppDelegate.h"/, '#import "AppDelegate.h"\n#import <ReactNativeNavigation/ReactNativeNavigation.h>');
}

warnn(" AppDelegate already imports ReactNativeNavigation.h");
return content;
}

_importNavigation(applicationContent) {
if (!this._doesImportNavigation(applicationContent)) {
debugn(" ");
return applicationContent
.replace(/#import\s+"AppDelegate.h"/, "#import \"AppDelegate.h\"\n#import <ReactNativeNavigation/ReactNativeNavigation.h>")
_bootstrapNavigation(content) {
if (this._doesBootstrapNavigation(content)) {
warnn(" Navigation Bootstrap already present.");
return content;
}
warnn(" AppDelegate already imports Navigation");
return applicationContent;

debugn(" Bootstrapping Navigation");
return content.replace(/RCTBridge.*];/, "[ReactNativeNavigation bootstrapWithDelegate:self launchOptions:launchOptions];");
}

_doesBootstrapNavigation(content) {
return /ReactNativeNavigation\s+bootstrap/.test(content);
}

_removeApplicationLaunchContent(applicationContent) {
const toRemove = [/RCTRootView\s+\*rootView((.|\r|\s)*?)];\s+/, /rootView.backgroundColor((.|\r)*)];\s+/,
/self.window((.|\r)*)];\s+/, /UIViewController\s\*rootViewController((.|\r)*)];\s+/, /rootViewController\.view\s+=\s+rootView;\s+/,
/self.window.rootViewController\s+=\s+rootViewController;\s+/, /\[self.window\s+makeKeyAndVisible];\s+/
]
_removeApplicationLaunchContent(content) {
debugn(" Removing Application launch content");

toRemove.forEach(element => {
if (element.test(applicationContent)) {
applicationContent = applicationContent.replace(element, "");
const toRemove = [
/RCTRootView\s+\*rootView((.|\r|\s)*?)];\s+/,
/rootView.backgroundColor((.|\r)*)];\s+/,
/self.window((.|\r)*)];\s+/,
/UIViewController\s\*rootViewController((.|\r)*)];\s+/,
/rootViewController\.view\s+=\s+rootView;\s+/,
/self.window.rootViewController\s+=\s+rootViewController;\s+/,
/\[self.window\s+makeKeyAndVisible];\s+/,
];
let elementsRemovedCount = 0;

toRemove.forEach((element) => {
if (element.test(content)) {
content = content.replace(element, "");
elementsRemovedCount++;
}
});

return applicationContent;
}
if (toRemove.length === elementsRemovedCount) {
debugn(" Application Launch content has been removed");
} else if (elementsRemovedCount === 0) {
warnn(
" No elements could be removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation"
);
} else {
throw new Error(
"Some elements were removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation"
);
}

_bootstrapNavigation(applicationContent) {
return applicationContent.replace(/RCTBridge.*];/, "[ReactNativeNavigation bootstrapWithDelegate:self launchOptions:launchOptions];");
return content;
}

_doesImportNavigation(applicationContent) {
return /#import\s+\<ReactNativeNavigation\/ReactNativeNavigation.h>/.test(applicationContent);
_doesImportNavigation(content) {
return /#import\s+\<ReactNativeNavigation\/ReactNativeNavigation.h>/.test(content);
}
}

module.exports = ApplicationLinker;
module.exports = AppDelegateLinker;
Loading

0 comments on commit daa48ca

Please sign in to comment.