react-native-ota-hot-update 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@ package com.rnhotupdate;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.content.Intent;
5
+ import android.content.pm.PackageInfo;
5
6
  import android.util.Log;
6
7
 
7
8
  import com.facebook.react.bridge.Promise;
@@ -101,6 +102,12 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
101
102
  file.delete();
102
103
  SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
103
104
  sharedPrefs.putString(Common.INSTANCE.getPATH(), fileUnzip);
105
+ PackageInfo info = OtaHotUpdate.packageInfo(getReactApplicationContext());
106
+ String latestVer = null;
107
+ if (info != null) {
108
+ latestVer = info.versionName;
109
+ }
110
+ sharedPrefs.putString(Common.INSTANCE.getCURRENT_VERSION_NAME(), latestVer);
104
111
  promise.resolve(true);
105
112
  } else {
106
113
  file.delete();
@@ -157,6 +164,19 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
157
164
  promise.resolve(true);
158
165
  }
159
166
 
167
+ @ReactMethod
168
+ public void setExactBundlePath(String path, Promise promise) {
169
+ SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
170
+ sharedPrefs.putString(Common.INSTANCE.getPATH(), path);
171
+ PackageInfo info = OtaHotUpdate.packageInfo(getReactApplicationContext());
172
+ String latestVer = null;
173
+ if (info != null) {
174
+ latestVer = info.versionName;
175
+ }
176
+ sharedPrefs.putString(Common.INSTANCE.getCURRENT_VERSION_NAME(), latestVer);
177
+ promise.resolve(true);
178
+ }
179
+
160
180
  @NonNull
161
181
  @Override
162
182
  public String getName() {
@@ -1,6 +1,9 @@
1
1
  package com.rnhotupdate;
2
2
 
3
3
  import android.content.Context;
4
+ import android.content.pm.PackageInfo;
5
+ import android.content.pm.PackageManager;
6
+ import android.os.Build;
4
7
 
5
8
  import com.facebook.react.ReactPackage;
6
9
  import com.facebook.react.bridge.NativeModule;
@@ -31,13 +34,30 @@ public class OtaHotUpdate implements ReactPackage {
31
34
  public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactApplicationContext) {
32
35
  return Collections.emptyList();
33
36
  }
37
+ public static PackageInfo packageInfo (Context context) {
38
+ try {
39
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
40
+ return context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.PackageInfoFlags.of(0));
41
+ } else {
42
+ return context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
43
+ }
44
+ } catch (Exception e) {
45
+ return null;
46
+ }
47
+ }
34
48
  public static String getBundleJS() {
35
49
  if (mContext == null) {
36
50
  return Common.INSTANCE.getDEFAULT_BUNDLE();
37
51
  }
38
52
  SharedPrefs sharedPrefs = new SharedPrefs(mContext);
53
+ PackageInfo info = OtaHotUpdate.packageInfo(mContext);
54
+ String latestVer = null;
55
+ if (info != null) {
56
+ latestVer = info.versionName;
57
+ }
39
58
  String pathBundle = sharedPrefs.getString(Common.INSTANCE.getPATH());
40
- if (pathBundle.equals("")) {
59
+ String currentVer = sharedPrefs.getString(Common.INSTANCE.getCURRENT_VERSION_NAME());
60
+ if (pathBundle.equals("") || (info != null && !currentVer.equals("") && !latestVer.equals(currentVer))) {
41
61
  return Common.INSTANCE.getDEFAULT_BUNDLE();
42
62
  }
43
63
  return pathBundle;
@@ -27,5 +27,6 @@ object Common {
27
27
  val PATH = "PATH"
28
28
  val VERSION = "VERSION"
29
29
  val SHARED_PREFERENCE_NAME = "HOT-UPDATE-REACT_NATIVE"
30
+ val CURRENT_VERSION_NAME = "CURRENT_VERSION_NAME"
30
31
  val DEFAULT_BUNDLE = "assets://index.android.bundle"
31
32
  }
package/ios/RNhotupdate.m CHANGED
@@ -90,7 +90,10 @@ RCT_EXPORT_MODULE()
90
90
  + (NSURL *)getBundle {
91
91
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
92
92
  NSString *retrievedString = [defaults stringForKey:@"PATH"];
93
- if (retrievedString && [self isFilePathExist:retrievedString]) {
93
+ NSString *currentVersionName = [defaults stringForKey:@"VERSION_NAME"];
94
+ NSString *versionName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
95
+
96
+ if (retrievedString && [self isFilePathExist:retrievedString] && [currentVersionName isEqualToString:versionName]) {
94
97
  NSURL *fileURL = [NSURL fileURLWithPath:retrievedString];
95
98
  return fileURL;
96
99
  } else {
@@ -174,6 +177,7 @@ RCT_EXPORT_METHOD(setupBundlePath:(NSString *)path extension:(NSString *)extensi
174
177
  NSLog(@"file extraction----- %@", extractedFilePath);
175
178
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
176
179
  [defaults setObject:extractedFilePath forKey:@"PATH"];
180
+ [defaults setObject:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] forKey:@"VERSION_NAME"];
177
181
  [defaults synchronize];
178
182
  resolve(@(YES));
179
183
  } else {
@@ -211,6 +215,20 @@ RCT_EXPORT_METHOD(setCurrentVersion:(NSString *)version withResolver:(RCTPromise
211
215
  }
212
216
  }
213
217
 
218
+ RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
219
+ resolve:(RCTPromiseResolveBlock)resolve
220
+ reject:(RCTPromiseRejectBlock)reject) {
221
+ if (path) {
222
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
223
+ [defaults setObject:path forKey:@"PATH"];
224
+ [defaults setObject:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] forKey:@"VERSION_NAME"];
225
+ [defaults synchronize];
226
+ resolve(@(YES));
227
+ } else {
228
+ resolve(@(NO));
229
+ }
230
+ }
231
+
214
232
  - (void)loadBundle
215
233
  {
216
234
  RCTTriggerReloadCommandListeners(@"rn-hotupdate: Restart");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-ota-hot-update",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Hot update for react native",
5
5
  "main": "src/index",
6
6
  "repository": "https://github.com/vantuan88291/react-native-ota-hot-update",
@@ -10,8 +10,13 @@
10
10
  "url": "https://github.com/vantuan88291/react-native-ota-hot-update/issues"
11
11
  },
12
12
  "homepage": "https://github.com/vantuan88291/react-native-ota-hot-update",
13
+ "dependencies": {
14
+ "buffer": "^6.0.3",
15
+ "isomorphic-git": "git+https://github.com/vantuan88291/isomorphic-git.git"
16
+ },
13
17
  "peerDependencies": {
14
- "react-native": ">=0.63.4"
18
+ "react-native": ">=0.63.4",
19
+ "react-native-fs": "*"
15
20
  },
16
21
  "create-react-native-library": {
17
22
  "type": "module-legacy",
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @format
3
+ */
4
+ FileReader.prototype.readAsArrayBuffer = function (blob) {
5
+ if (this.readyState === this.LOADING) throw new Error('InvalidStateError');
6
+ this._setReadyState(this.LOADING);
7
+ this._result = null;
8
+ this._error = null;
9
+ const fr = new FileReader();
10
+ fr.onloadend = () => {
11
+ const content = atob(fr.result.replace(/data:[^;]+;base64,/, ''));
12
+ const buffer = new ArrayBuffer(content.length);
13
+ const view = new Uint8Array(buffer);
14
+ view.set(Array.from(content).map((c) => c.charCodeAt(0)));
15
+ this._result = buffer;
16
+ this._setReadyState(this.DONE);
17
+ };
18
+ fr.readAsDataURL(blob);
19
+ };
20
+
21
+ // from: https://stackoverflow.com/questions/42829838/react-native-atob-btoa-not-working-without-remote-js-debugging
22
+ const chars =
23
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
24
+ const atob = (input = '') => {
25
+ let str = input.replace(/[=]+$/, '');
26
+ let output = '';
27
+
28
+ if (str.length % 4 == 1) {
29
+ throw new Error(
30
+ "'atob' failed: The string to be decoded is not correctly encoded."
31
+ );
32
+ }
33
+ for (
34
+ let bc = 0, bs = 0, buffer, i = 0;
35
+ (buffer = str.charAt(i++));
36
+ ~buffer && ((bs = bc % 4 ? bs * 64 + buffer : buffer), bc++ % 4)
37
+ ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))
38
+ : 0
39
+ ) {
40
+ buffer = chars.indexOf(buffer);
41
+ }
42
+
43
+ return output;
44
+ };
@@ -0,0 +1,154 @@
1
+ import { Buffer } from 'buffer';
2
+
3
+ let RNFS = {
4
+ unlink: console.log,
5
+ readdir: console.log,
6
+ mkdir: console.log,
7
+ readFile: console.log,
8
+ writeFile: console.log,
9
+ stat: console.log,
10
+ };
11
+ try {
12
+ RNFS = require('react-native-fs');
13
+ } catch {}
14
+
15
+ function Err(name: string) {
16
+ return class extends Error {
17
+ public code = name;
18
+ constructor(...args: any) {
19
+ super(...args);
20
+ if (this.message) {
21
+ this.message = name + ': ' + this.message;
22
+ } else {
23
+ this.message = name;
24
+ }
25
+ }
26
+ };
27
+ }
28
+
29
+ // const EEXIST = Err('EEXIST'); // <-- Unused because RNFS's mkdir never throws
30
+ const ENOENT = Err('ENOENT');
31
+ const ENOTDIR = Err('ENOTDIR');
32
+ // const ENOTEMPTY = Err('ENOTEMPTY'); // <-- Unused because RNFS's unlink is recursive by default
33
+
34
+ export const readdir = async (path: string) => {
35
+ try {
36
+ return await RNFS.readdir(path);
37
+ } catch (err: any) {
38
+ switch (err.message) {
39
+ case 'Attempt to get length of null array': {
40
+ throw new ENOTDIR(path);
41
+ }
42
+ case 'Folder does not exist': {
43
+ throw new ENOENT(path);
44
+ }
45
+ default:
46
+ throw err;
47
+ }
48
+ }
49
+ };
50
+
51
+ export const mkdir = async (path: string) => {
52
+ return RNFS.mkdir(path);
53
+ };
54
+
55
+ export const readFile = async (
56
+ path: string,
57
+ opts?: string | { [key: string]: string }
58
+ ) => {
59
+ let encoding;
60
+
61
+ if (typeof opts === 'string') {
62
+ encoding = opts;
63
+ } else if (typeof opts === 'object') {
64
+ encoding = opts.encoding;
65
+ }
66
+
67
+ // @ts-ignore
68
+ let result: string | Uint8Array = await RNFS.readFile(
69
+ path,
70
+ encoding || 'base64'
71
+ );
72
+
73
+ if (!encoding) {
74
+ // @ts-ignore
75
+ result = Buffer.from(result, 'base64');
76
+ }
77
+
78
+ return result;
79
+ };
80
+ export const writeFile = async (
81
+ path: string,
82
+ content: string | Uint8Array,
83
+ opts?: string | { [key: string]: string }
84
+ ) => {
85
+ let encoding;
86
+
87
+ if (typeof opts === 'string') {
88
+ encoding = opts;
89
+ } else if (typeof opts === 'object') {
90
+ encoding = opts.encoding;
91
+ }
92
+
93
+ if (typeof content === 'string') {
94
+ encoding = encoding || 'utf8';
95
+ } else {
96
+ encoding = 'base64';
97
+ content = Buffer.from(content).toString('base64');
98
+ }
99
+
100
+ await RNFS.writeFile(path, content as string, encoding);
101
+ };
102
+
103
+ export const stat = async (path: string) => {
104
+ try {
105
+ const r = await RNFS.stat(path);
106
+ // we monkeypatch the result with a `isSymbolicLink` method because isomorphic-git needs it.
107
+ // Since RNFS doesn't appear to support symlinks at all, we'll just always return false.
108
+ // @ts-ignore
109
+ r.isSymbolicLink = () => false;
110
+ return r;
111
+ } catch (err: any) {
112
+ switch (err.message) {
113
+ case 'File does not exist': {
114
+ throw new ENOENT(path);
115
+ }
116
+ default:
117
+ throw err;
118
+ }
119
+ }
120
+ };
121
+
122
+ // Since there are no symbolic links, lstat and stat are equivalent
123
+ export const lstat = stat;
124
+
125
+ export const unlink = async (path: string) => {
126
+ try {
127
+ await RNFS.unlink(path);
128
+ } catch (err: any) {
129
+ switch (err.message) {
130
+ case 'File does not exist': {
131
+ throw new ENOENT(path);
132
+ }
133
+ default:
134
+ throw err;
135
+ }
136
+ }
137
+ };
138
+
139
+ // RNFS doesn't have a separate rmdir method, so we can use unlink for deleting directories too
140
+ export const rmdir = unlink;
141
+
142
+ // These are optional, which is good because there is no equivalent in RNFS
143
+ export const readlink = async () => {
144
+ throw new Error('not implemented');
145
+ };
146
+ export const symlink = async () => {
147
+ throw new Error('not implemented');
148
+ };
149
+
150
+ // Technically we could pull this off by using `readFile` + `writeFile` with the `mode` option
151
+ // However, it's optional, because isomorphic-git will do exactly that (a readFile and a writeFile with the new mode)
152
+ export const chmod = async () => {
153
+ throw new Error('not implemented');
154
+ };
@@ -0,0 +1,118 @@
1
+ import './helper/fileReader.js';
2
+
3
+ // @ts-ignore
4
+ import git, { PromiseFsClient } from 'isomorphic-git/index.umd.min.js';
5
+ import http from 'isomorphic-git/http/web/index.js';
6
+ import * as promises from './helper/fs';
7
+ import type { CloneOption, PullOption } from '../type';
8
+
9
+ const fs: PromiseFsClient = { promises };
10
+ const getFolder = (folderName?: string) => {
11
+ try {
12
+ const { DocumentDirectoryPath } = require('react-native-fs');
13
+ return DocumentDirectoryPath + (folderName || '/git_hot_update');
14
+ } catch (e) {}
15
+ return '';
16
+ };
17
+ /**
18
+ * Should set config after clone success, otherwise cannot pull
19
+ */
20
+ const setConfig = async (
21
+ folderName?: string,
22
+ options?: {
23
+ userName?: string;
24
+ email?: string;
25
+ }
26
+ ) => {
27
+ await git.setConfig({
28
+ fs,
29
+ dir: getFolder(folderName),
30
+ path: options?.userName || 'user.name',
31
+ value: options?.email || 'hotupdate',
32
+ });
33
+ };
34
+ const cloneRepo = async (options: CloneOption) => {
35
+ try {
36
+ await git.clone({
37
+ fs,
38
+ http,
39
+ dir: getFolder(options?.folderName),
40
+ url: options?.url,
41
+ singleBranch: true,
42
+ depth: 1,
43
+ ref: options?.branch,
44
+ onProgress({ loaded, total }: { loaded: number; total: number }) {
45
+ if (options?.onProgress && total > 0) {
46
+ options?.onProgress(loaded, total);
47
+ }
48
+ },
49
+ });
50
+ await setConfig(options?.folderName, {
51
+ email: options?.email,
52
+ userName: options?.userName,
53
+ });
54
+ return {
55
+ success: true,
56
+ msg: null,
57
+ bundle: `${getFolder(options?.folderName)}/${options.bundlePath}`,
58
+ };
59
+ } catch (e: any) {
60
+ return {
61
+ success: false,
62
+ msg: e.toString(),
63
+ bundle: null,
64
+ };
65
+ }
66
+ };
67
+ const pullUpdate = async (options: PullOption) => {
68
+ try {
69
+ let count = 0;
70
+ await git.pull({
71
+ fs,
72
+ http,
73
+ dir: getFolder(options?.folderName),
74
+ ref: options?.branch,
75
+ singleBranch: true,
76
+ onProgress({ loaded, total }: { loaded: number; total: number }) {
77
+ if (total > 0) {
78
+ count = total;
79
+ if (options?.onProgress) {
80
+ options?.onProgress(loaded, total);
81
+ }
82
+ }
83
+ },
84
+ });
85
+ return {
86
+ success: count > 0,
87
+ msg: count > 0 ? 'Pull success' : 'No updated',
88
+ };
89
+ } catch (e: any) {
90
+ console.log(e.toString());
91
+ return {
92
+ success: false,
93
+ msg: e.toString(),
94
+ };
95
+ }
96
+ };
97
+ const getBranchName = async (folderName?: string) => {
98
+ try {
99
+ return await git.currentBranch({
100
+ fs,
101
+ dir: getFolder(folderName),
102
+ fullname: false,
103
+ });
104
+ } catch (e: any) {
105
+ console.log(e.toString());
106
+ return null;
107
+ }
108
+ };
109
+ const removeGitUpdate = (folderName?: string) => {
110
+ fs.promises.unlink(getFolder(folderName));
111
+ };
112
+ export default {
113
+ cloneRepo,
114
+ pullUpdate,
115
+ getBranchName,
116
+ setConfig,
117
+ removeGitUpdate,
118
+ };
package/src/index.tsx CHANGED
@@ -1,19 +1,14 @@
1
1
  import { NativeModules, Platform } from 'react-native';
2
2
  import {DownloadManager} from './download';
3
+ import { UpdateGitOption, UpdateOption } from './type';
4
+ import git from './gits';
5
+
3
6
  const LINKING_ERROR =
4
7
  'The package \'rn-hotupdate\' doesn\'t seem to be linked. Make sure: \n\n' +
5
8
  Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
6
9
  '- You rebuilt the app after installing the package\n' +
7
10
  '- You are not using Expo Go\n';
8
11
 
9
- export interface UpdateOption {
10
- headers?: object
11
- progress?(received: string, total: string): void
12
- updateSuccess?(): void
13
- updateFail?(message?: string): void
14
- restartAfterInstall?: boolean
15
- extensionBundle?: string,
16
- }
17
12
  const RNhotupdate = NativeModules.RNhotupdate
18
13
  ? NativeModules.RNhotupdate
19
14
  : new Proxy(
@@ -42,6 +37,9 @@ const downloadBundleFile = async (downloadManager: DownloadManager, uri: string,
42
37
  function setupBundlePath(path: string, extension?: string): Promise<boolean> {
43
38
  return RNhotupdate.setupBundlePath(path, extension);
44
39
  }
40
+ function setupExactBundlePath(path: string): Promise<boolean> {
41
+ return RNhotupdate.setExactBundlePath(path);
42
+ }
45
43
  function deleteBundlePath(): Promise<boolean> {
46
44
  return RNhotupdate.deleteBundle();
47
45
  }
@@ -111,12 +109,71 @@ async function downloadBundleUri(downloadManager: DownloadManager, uri: string,
111
109
  installFail(option, e);
112
110
  }
113
111
  }
114
-
112
+ const checkForGitUpdate = async (options: UpdateGitOption) => {
113
+ try {
114
+ if (!options.url || !options.bundlePath) {
115
+ throw new Error(`url or bundlePath should not be null`);
116
+ }
117
+ const branch = await git.getBranchName();
118
+ if (branch) {
119
+ const pull = await git.pullUpdate({
120
+ branch,
121
+ onProgress: options?.onProgress,
122
+ folderName: options?.folderName,
123
+ });
124
+ if (pull.success) {
125
+ options?.onPullSuccess?.();
126
+ if (options?.restartAfterInstall) {
127
+ setTimeout(() => {
128
+ resetApp();
129
+ }, 300);
130
+ }
131
+ } else {
132
+ options?.onPullFailed?.(pull.msg);
133
+ }
134
+ } else {
135
+ const clone = await git.cloneRepo({
136
+ onProgress: options?.onProgress,
137
+ folderName: options?.folderName,
138
+ url: options.url,
139
+ branch: options?.branch,
140
+ bundlePath: options.bundlePath,
141
+ });
142
+ if (clone.success) {
143
+ await git.setConfig();
144
+ if (clone.bundle) {
145
+ await setupExactBundlePath(clone.bundle);
146
+ options?.onCloneSuccess?.();
147
+ if (options?.restartAfterInstall) {
148
+ setTimeout(() => {
149
+ resetApp();
150
+ }, 300);
151
+ }
152
+ }
153
+ } else {
154
+ options?.onCloneFailed?.(clone.msg);
155
+ }
156
+ }
157
+ } catch (e: any) {
158
+ options?.onCloneFailed?.(e.toString());
159
+ } finally {
160
+ options?.onFinishProgress?.();
161
+ }
162
+ };
115
163
  export default {
116
164
  setupBundlePath,
165
+ setupExactBundlePath,
117
166
  removeUpdate: removeBundle,
118
167
  downloadBundleUri,
119
168
  resetApp,
120
169
  getCurrentVersion: getVersionAsNumber,
121
170
  setCurrentVersion,
171
+ git: {
172
+ checkForGitUpdate,
173
+ ...git,
174
+ removeGitUpdate: (folder?: string) => {
175
+ RNhotupdate.setExactBundlePath('');
176
+ git.removeGitUpdate(folder);
177
+ },
178
+ },
122
179
  };
package/src/type.ts ADDED
@@ -0,0 +1,82 @@
1
+ export interface UpdateOption {
2
+ headers?: object;
3
+ progress?(received: string, total: string): void;
4
+ updateSuccess?(): void;
5
+ updateFail?(message?: string): void;
6
+ restartAfterInstall?: boolean;
7
+ extensionBundle?: string;
8
+ }
9
+
10
+ /**
11
+ * Options for updating a Git repository.
12
+ */
13
+ export interface UpdateGitOption {
14
+ /**
15
+ * The URL of the Git repository to check update.
16
+ */
17
+ url: string;
18
+
19
+ /**
20
+ * Optional callback to monitor the progress of the update.
21
+ * @param received - The number of bytes received so far.
22
+ * @param total - The total number of bytes to be received.
23
+ */
24
+ onProgress?(received: number, total: number): void;
25
+
26
+ /**
27
+ * Optional branch name to update or switch to.
28
+ * If not specified, the default branch will be main.
29
+ */
30
+ branch?: string;
31
+
32
+ /**
33
+ * Optional name of the folder where the repository will be cloned or updated.
34
+ * If not specified, a default folder name will be git_hot_update.
35
+ */
36
+ folderName?: string;
37
+ /**
38
+ * Optional callback when pull success, should handle for case update.
39
+ */
40
+ onPullSuccess?(): void;
41
+ /**
42
+ * Optional callback when pull failed.
43
+ */
44
+ onPullFailed?(msg: string): void;
45
+ /**
46
+ * Optional callback when clone success, handle it in the first time clone.
47
+ */
48
+ onCloneSuccess?(): void;
49
+ /**
50
+ * Optional callback when clone failed.
51
+ */
52
+ onCloneFailed?(msg: string): void;
53
+ /**
54
+ * The bundle path of the Git repository, it should place at root.
55
+ * Eg: the folder name is git_hot_update, bundle file place at git_hot_update/output/main.jsbundle, so bundlePath should be: "output/main.jsbundle".
56
+ */
57
+ bundlePath: string;
58
+ /**
59
+ * Optional restart app after clone / pull success for apply the new bundle.
60
+ */
61
+ restartAfterInstall?: boolean;
62
+ /**
63
+ * Optional when all process success, use for set loading false.
64
+ */
65
+ onFinishProgress?(): void;
66
+ }
67
+
68
+ export interface CloneOption {
69
+ url: string;
70
+ folderName?: string;
71
+ onProgress?(received: number, total: number): void;
72
+ branch?: string;
73
+ bundlePath: string;
74
+ userName?: string;
75
+ email?: string;
76
+ }
77
+
78
+ export interface PullOption {
79
+ folderName?: string;
80
+ onProgress?(received: number, total: number): void;
81
+ branch: string;
82
+ }