react-native-ota-hot-update 1.1.2 → 1.1.4
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.
- package/android/src/main/java/com/rnhotupdate/HotUpdateModule.java +13 -2
- package/ios/RNhotupdate.m +14 -1
- package/package.json +7 -2
- package/src/gits/helper/fileReader.js +44 -0
- package/src/gits/helper/fs.ts +154 -0
- package/src/gits/index.ts +118 -0
- package/src/index.tsx +66 -9
- package/src/type.ts +82 -0
|
@@ -92,17 +92,21 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
92
92
|
@ReactMethod
|
|
93
93
|
public void setupBundlePath(String path, String extension, Promise promise) {
|
|
94
94
|
if (path != null) {
|
|
95
|
-
deleteOldBundleIfneeded();
|
|
96
95
|
File file = new File(path);
|
|
97
96
|
if (file.exists() && file.isFile()) {
|
|
97
|
+
deleteOldBundleIfneeded();
|
|
98
98
|
String fileUnzip = unzip(file, extension != null ? extension : ".bundle");
|
|
99
|
-
Log.d("setupBundlePath: ", fileUnzip);
|
|
100
99
|
if (fileUnzip != null) {
|
|
100
|
+
Log.d("setupBundlePath: ", fileUnzip);
|
|
101
101
|
file.delete();
|
|
102
102
|
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
103
103
|
sharedPrefs.putString(Common.INSTANCE.getPATH(), fileUnzip);
|
|
104
104
|
promise.resolve(true);
|
|
105
105
|
} else {
|
|
106
|
+
file.delete();
|
|
107
|
+
deleteDirectory(file.getParentFile());
|
|
108
|
+
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
109
|
+
sharedPrefs.putString(Common.INSTANCE.getPATH(), "");
|
|
106
110
|
promise.resolve(false);
|
|
107
111
|
}
|
|
108
112
|
} else {
|
|
@@ -153,6 +157,13 @@ public class HotUpdateModule extends ReactContextBaseJavaModule {
|
|
|
153
157
|
promise.resolve(true);
|
|
154
158
|
}
|
|
155
159
|
|
|
160
|
+
@ReactMethod
|
|
161
|
+
public void setExactBundlePath(String path, Promise promise) {
|
|
162
|
+
SharedPrefs sharedPrefs = new SharedPrefs(getReactApplicationContext());
|
|
163
|
+
sharedPrefs.putString(Common.INSTANCE.getPATH(), path);
|
|
164
|
+
promise.resolve(true);
|
|
165
|
+
}
|
|
166
|
+
|
|
156
167
|
@NonNull
|
|
157
168
|
@Override
|
|
158
169
|
public String getName() {
|
package/ios/RNhotupdate.m
CHANGED
|
@@ -170,8 +170,8 @@ RCT_EXPORT_METHOD(setupBundlePath:(NSString *)path extension:(NSString *)extensi
|
|
|
170
170
|
[self removeBundleIfNeeded];
|
|
171
171
|
//Unzip file
|
|
172
172
|
NSString *extractedFilePath = [self unzipFileAtPath:path extension:(extension != nil) ? extension : @".jsbundle"];
|
|
173
|
-
NSLog(@"file extraction----- %@", extractedFilePath);
|
|
174
173
|
if (extractedFilePath) {
|
|
174
|
+
NSLog(@"file extraction----- %@", extractedFilePath);
|
|
175
175
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
176
176
|
[defaults setObject:extractedFilePath forKey:@"PATH"];
|
|
177
177
|
[defaults synchronize];
|
|
@@ -211,6 +211,19 @@ RCT_EXPORT_METHOD(setCurrentVersion:(NSString *)version withResolver:(RCTPromise
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
RCT_EXPORT_METHOD(setExactBundlePath:(NSString *)path
|
|
215
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
216
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
217
|
+
if (path) {
|
|
218
|
+
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
219
|
+
[defaults setObject:path forKey:@"PATH"];
|
|
220
|
+
[defaults synchronize];
|
|
221
|
+
resolve(@(YES));
|
|
222
|
+
} else {
|
|
223
|
+
resolve(@(NO));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
214
227
|
- (void)loadBundle
|
|
215
228
|
{
|
|
216
229
|
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
|
+
"version": "1.1.4",
|
|
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
|
+
}
|