react-native-update-cli 1.36.0 → 1.38.0
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/cli.json +2 -0
- package/lib/api.js +13 -25
- package/lib/app.js +14 -13
- package/lib/bundle.js +25 -6
- package/lib/package.js +30 -1
- package/lib/user.js +5 -5
- package/lib/utils/app-info-parser/app.js +14 -0
- package/lib/utils/app-info-parser/index.js +8 -3
- package/lib/utils/app-info-parser/zip.js +18 -0
- package/lib/utils/index.js +33 -0
- package/lib/versions.js +5 -5
- package/package.json +6 -5
- package/src/api.ts +18 -25
- package/src/app.js +10 -7
- package/src/bundle.js +14 -9
- package/src/package.js +47 -2
- package/src/user.js +4 -4
- package/src/utils/app-info-parser/app.js +16 -0
- package/src/utils/app-info-parser/index.js +24 -16
- package/src/utils/app-info-parser/zip.js +37 -19
- package/src/utils/index.js +37 -0
- package/src/versions.js +5 -5
package/cli.json
CHANGED
package/lib/api.js
CHANGED
|
@@ -15,9 +15,6 @@ _export(exports, {
|
|
|
15
15
|
doDelete: function() {
|
|
16
16
|
return doDelete;
|
|
17
17
|
},
|
|
18
|
-
get: function() {
|
|
19
|
-
return get;
|
|
20
|
-
},
|
|
21
18
|
getSession: function() {
|
|
22
19
|
return getSession;
|
|
23
20
|
},
|
|
@@ -61,13 +58,11 @@ let savedSession;
|
|
|
61
58
|
const defaultEndpoint = 'https://update.reactnative.cn/api';
|
|
62
59
|
let host = process.env.PUSHY_REGISTRY || defaultEndpoint;
|
|
63
60
|
const userAgent = `react-native-update-cli/${_packagejson.default.version}`;
|
|
64
|
-
const getSession =
|
|
65
|
-
|
|
66
|
-
};
|
|
67
|
-
const replaceSession = function(newSession) {
|
|
61
|
+
const getSession = ()=>session;
|
|
62
|
+
const replaceSession = (newSession)=>{
|
|
68
63
|
session = newSession;
|
|
69
64
|
};
|
|
70
|
-
const loadSession = async
|
|
65
|
+
const loadSession = async ()=>{
|
|
71
66
|
if (_nodefs.default.existsSync('.update')) {
|
|
72
67
|
try {
|
|
73
68
|
replaceSession(JSON.parse(_nodefs.default.readFileSync('.update', 'utf8')));
|
|
@@ -78,7 +73,7 @@ const loadSession = async function() {
|
|
|
78
73
|
}
|
|
79
74
|
}
|
|
80
75
|
};
|
|
81
|
-
const saveSession =
|
|
76
|
+
const saveSession = ()=>{
|
|
82
77
|
// Only save on change.
|
|
83
78
|
if (session !== savedSession) {
|
|
84
79
|
const current = session;
|
|
@@ -87,7 +82,7 @@ const saveSession = function() {
|
|
|
87
82
|
savedSession = current;
|
|
88
83
|
}
|
|
89
84
|
};
|
|
90
|
-
const closeSession =
|
|
85
|
+
const closeSession = ()=>{
|
|
91
86
|
if (_nodefs.default.existsSync('.update')) {
|
|
92
87
|
_nodefs.default.unlinkSync('.update');
|
|
93
88
|
savedSession = undefined;
|
|
@@ -101,32 +96,27 @@ async function query(url, options) {
|
|
|
101
96
|
let json;
|
|
102
97
|
try {
|
|
103
98
|
json = JSON.parse(text);
|
|
104
|
-
} catch (e) {
|
|
105
|
-
|
|
99
|
+
} catch (e) {}
|
|
100
|
+
if (resp.status !== 200) {
|
|
101
|
+
const message = (json == null ? void 0 : json.message) || resp.statusText;
|
|
102
|
+
if (resp.status === 401) {
|
|
106
103
|
throw new Error('登录信息已过期,请使用 pushy login 命令重新登录');
|
|
107
|
-
} else {
|
|
108
|
-
throw new Error(`Server error: ${resp.statusText}`);
|
|
109
104
|
}
|
|
110
|
-
|
|
111
|
-
if (resp.status !== 200) {
|
|
112
|
-
throw new Error(`${resp.status}: ${resp.statusText}`);
|
|
105
|
+
throw new Error(message);
|
|
113
106
|
}
|
|
114
107
|
return json;
|
|
115
108
|
}
|
|
116
109
|
function queryWithoutBody(method) {
|
|
117
|
-
return
|
|
118
|
-
return query(host + api, {
|
|
110
|
+
return (api)=>query(host + api, {
|
|
119
111
|
method,
|
|
120
112
|
headers: {
|
|
121
113
|
'User-Agent': userAgent,
|
|
122
114
|
'X-AccessToken': session ? session.token : ''
|
|
123
115
|
}
|
|
124
116
|
});
|
|
125
|
-
};
|
|
126
117
|
}
|
|
127
118
|
function queryWithBody(method) {
|
|
128
|
-
return
|
|
129
|
-
return query(host + api, {
|
|
119
|
+
return (api, body)=>query(host + api, {
|
|
130
120
|
method,
|
|
131
121
|
headers: {
|
|
132
122
|
'User-Agent': userAgent,
|
|
@@ -135,9 +125,7 @@ function queryWithBody(method) {
|
|
|
135
125
|
},
|
|
136
126
|
body: JSON.stringify(body)
|
|
137
127
|
});
|
|
138
|
-
};
|
|
139
128
|
}
|
|
140
|
-
const get = queryWithoutBody('GET');
|
|
141
129
|
const post = queryWithBody('POST');
|
|
142
130
|
const put = queryWithBody('PUT');
|
|
143
131
|
const doDelete = queryWithBody('DELETE');
|
|
@@ -176,7 +164,7 @@ async function uploadFile(fn, key) {
|
|
|
176
164
|
form.append(k, v);
|
|
177
165
|
});
|
|
178
166
|
const fileStream = _nodefs.default.createReadStream(fn);
|
|
179
|
-
fileStream.on('data',
|
|
167
|
+
fileStream.on('data', (data)=>{
|
|
180
168
|
bar.tick(data.length);
|
|
181
169
|
});
|
|
182
170
|
if (key) {
|
package/lib/app.js
CHANGED
|
@@ -26,7 +26,7 @@ _export(exports, {
|
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
28
|
const _utils = require("./utils");
|
|
29
|
-
const
|
|
29
|
+
const _nodefs = /*#__PURE__*/ _interop_require_default(require("node:fs"));
|
|
30
30
|
const _ttytable = /*#__PURE__*/ _interop_require_default(require("tty-table"));
|
|
31
31
|
const _api = require("./api");
|
|
32
32
|
function _interop_require_default(obj) {
|
|
@@ -36,7 +36,8 @@ function _interop_require_default(obj) {
|
|
|
36
36
|
}
|
|
37
37
|
const validPlatforms = {
|
|
38
38
|
ios: 1,
|
|
39
|
-
android: 1
|
|
39
|
+
android: 1,
|
|
40
|
+
harmony: 1
|
|
40
41
|
};
|
|
41
42
|
function checkPlatform(platform) {
|
|
42
43
|
if (!validPlatforms[platform]) {
|
|
@@ -46,10 +47,10 @@ function checkPlatform(platform) {
|
|
|
46
47
|
}
|
|
47
48
|
function getSelectedApp(platform) {
|
|
48
49
|
checkPlatform(platform);
|
|
49
|
-
if (!
|
|
50
|
+
if (!_nodefs.default.existsSync('update.json')) {
|
|
50
51
|
throw new Error(`App not selected. run 'pushy selectApp --platform ${platform}' first!`);
|
|
51
52
|
}
|
|
52
|
-
const updateInfo = JSON.parse(
|
|
53
|
+
const updateInfo = JSON.parse(_nodefs.default.readFileSync('update.json', 'utf8'));
|
|
53
54
|
if (!updateInfo[platform]) {
|
|
54
55
|
throw new Error(`App not selected. run 'pushy selectApp --platform ${platform}' first!`);
|
|
55
56
|
}
|
|
@@ -99,7 +100,7 @@ const commands = {
|
|
|
99
100
|
createApp: async function({ options }) {
|
|
100
101
|
const name = options.name || await (0, _utils.question)('应用名称:');
|
|
101
102
|
const { downloadUrl } = options;
|
|
102
|
-
const platform = checkPlatform(options.platform || await (0, _utils.question)('平台(ios/android):'));
|
|
103
|
+
const platform = checkPlatform(options.platform || await (0, _utils.question)('平台(ios/android/harmony):'));
|
|
103
104
|
const { id } = await (0, _api.post)('/app/create', {
|
|
104
105
|
name,
|
|
105
106
|
platform
|
|
@@ -115,7 +116,7 @@ const commands = {
|
|
|
115
116
|
}
|
|
116
117
|
});
|
|
117
118
|
},
|
|
118
|
-
deleteApp: async
|
|
119
|
+
deleteApp: async ({ args, options })=>{
|
|
119
120
|
const { platform } = options;
|
|
120
121
|
const id = args[0] || chooseApp(platform);
|
|
121
122
|
if (!id) {
|
|
@@ -124,17 +125,17 @@ const commands = {
|
|
|
124
125
|
await (0, _api.doDelete)(`/app/${id}`);
|
|
125
126
|
console.log('操作成功');
|
|
126
127
|
},
|
|
127
|
-
apps: async
|
|
128
|
+
apps: async ({ options })=>{
|
|
128
129
|
const { platform } = options;
|
|
129
130
|
listApp(platform);
|
|
130
131
|
},
|
|
131
|
-
selectApp: async
|
|
132
|
-
const platform = checkPlatform(options.platform || await (0, _utils.question)('平台(ios/android):'));
|
|
133
|
-
const id = args[0] ? parseInt(args[0]) : (await chooseApp(platform)).id;
|
|
132
|
+
selectApp: async ({ args, options })=>{
|
|
133
|
+
const platform = checkPlatform(options.platform || await (0, _utils.question)('平台(ios/android/harmony):'));
|
|
134
|
+
const id = args[0] ? Number.parseInt(args[0]) : (await chooseApp(platform)).id;
|
|
134
135
|
let updateInfo = {};
|
|
135
|
-
if (
|
|
136
|
+
if (_nodefs.default.existsSync('update.json')) {
|
|
136
137
|
try {
|
|
137
|
-
updateInfo = JSON.parse(
|
|
138
|
+
updateInfo = JSON.parse(_nodefs.default.readFileSync('update.json', 'utf8'));
|
|
138
139
|
} catch (e) {
|
|
139
140
|
console.error('Failed to parse file `update.json`. Try to remove it manually.');
|
|
140
141
|
throw e;
|
|
@@ -145,6 +146,6 @@ const commands = {
|
|
|
145
146
|
appId: id,
|
|
146
147
|
appKey
|
|
147
148
|
};
|
|
148
|
-
|
|
149
|
+
_nodefs.default.writeFileSync('update.json', JSON.stringify(updateInfo, null, 4), 'utf8');
|
|
149
150
|
}
|
|
150
151
|
};
|
package/lib/bundle.js
CHANGED
|
@@ -2,10 +2,21 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: all[name]
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
commands: function() {
|
|
8
13
|
return commands;
|
|
14
|
+
},
|
|
15
|
+
enumZipEntries: function() {
|
|
16
|
+
return enumZipEntries;
|
|
17
|
+
},
|
|
18
|
+
readEntire: function() {
|
|
19
|
+
return readEntire;
|
|
9
20
|
}
|
|
10
21
|
});
|
|
11
22
|
const _utils = require("./utils");
|
|
@@ -194,11 +205,19 @@ async function runReactNativeBundleCommand(bundleName, development, entryFile, o
|
|
|
194
205
|
async function copyHarmonyBundle(outputFolder) {
|
|
195
206
|
const harmonyRawPath = 'harmony/entry/src/main/resources/rawfile';
|
|
196
207
|
try {
|
|
208
|
+
await _fsextra.ensureDir(harmonyRawPath);
|
|
209
|
+
try {
|
|
210
|
+
await _fsextra.access(harmonyRawPath, _fsextra.constants.W_OK);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
await _fsextra.chmod(harmonyRawPath, 0o755);
|
|
213
|
+
}
|
|
214
|
+
await _fsextra.remove(path.join(harmonyRawPath, 'update.json'));
|
|
215
|
+
await _fsextra.copy('update.json', path.join(harmonyRawPath, 'update.json'));
|
|
197
216
|
await _fsextra.ensureDir(outputFolder);
|
|
198
217
|
await _fsextra.copy(harmonyRawPath, outputFolder);
|
|
199
|
-
console.log(`Successfully copied from ${harmonyRawPath} to ${outputFolder}`);
|
|
200
218
|
} catch (error) {
|
|
201
|
-
console.error('
|
|
219
|
+
console.error('copyHarmonyBundle 错误:', error);
|
|
220
|
+
throw new Error(`复制文件失败: ${error.message}`);
|
|
202
221
|
}
|
|
203
222
|
}
|
|
204
223
|
function getHermesOSBin() {
|
|
@@ -596,7 +615,7 @@ function diffArgsCheck(args, options, diffFn) {
|
|
|
596
615
|
}
|
|
597
616
|
const commands = {
|
|
598
617
|
bundle: async function({ options }) {
|
|
599
|
-
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android):'));
|
|
618
|
+
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android/harmony):'));
|
|
600
619
|
let { bundleName, entryFile, intermediaDir, output, dev, sourcemap } = (0, _utils.translateOptions)({
|
|
601
620
|
...options,
|
|
602
621
|
platform
|
package/lib/package.js
CHANGED
|
@@ -119,6 +119,35 @@ const commands = {
|
|
|
119
119
|
(0, _utils.saveToLocal)(fn, `${appId}/package/${id}.apk`);
|
|
120
120
|
console.log(`已成功上传apk原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`);
|
|
121
121
|
},
|
|
122
|
+
uploadApp: async function({ args }) {
|
|
123
|
+
const fn = args[0];
|
|
124
|
+
if (!fn || !fn.endsWith('.app')) {
|
|
125
|
+
throw new Error('使用方法: pushy uploadApp app后缀文件');
|
|
126
|
+
}
|
|
127
|
+
const { versionName, buildTime, appId: appIdInPkg, appKey: appKeyInPkg } = await (0, _utils.getAppInfo)(fn);
|
|
128
|
+
const { appId, appKey } = await (0, _app.getSelectedApp)('harmony');
|
|
129
|
+
if (appIdInPkg && appIdInPkg != appId) {
|
|
130
|
+
throw new Error(`appId不匹配!当前app: ${appIdInPkg}, 当前update.json: ${appId}`);
|
|
131
|
+
}
|
|
132
|
+
if (appKeyInPkg && appKeyInPkg !== appKey) {
|
|
133
|
+
throw new Error(`appKey不匹配!当前app: ${appKeyInPkg}, 当前update.json: ${appKey}`);
|
|
134
|
+
}
|
|
135
|
+
const { hash } = await (0, _api.uploadFile)(fn);
|
|
136
|
+
const { id } = await (0, _api.post)(`/app/${appId}/package/create`, {
|
|
137
|
+
name: versionName,
|
|
138
|
+
hash,
|
|
139
|
+
buildTime
|
|
140
|
+
});
|
|
141
|
+
(0, _utils.saveToLocal)(fn, `${appId}/package/${id}.app`);
|
|
142
|
+
console.log(`已成功上传app原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`);
|
|
143
|
+
},
|
|
144
|
+
parseApp: async function({ args }) {
|
|
145
|
+
const fn = args[0];
|
|
146
|
+
if (!fn || !fn.endsWith('.app')) {
|
|
147
|
+
throw new Error('使用方法: pushy parseApp app后缀文件');
|
|
148
|
+
}
|
|
149
|
+
console.log(await (0, _utils.getAppInfo)(fn));
|
|
150
|
+
},
|
|
122
151
|
parseIpa: async function({ args }) {
|
|
123
152
|
const fn = args[0];
|
|
124
153
|
if (!fn || !fn.endsWith('.ipa')) {
|
|
@@ -134,7 +163,7 @@ const commands = {
|
|
|
134
163
|
console.log(await (0, _utils.getApkInfo)(fn));
|
|
135
164
|
},
|
|
136
165
|
packages: async function({ options }) {
|
|
137
|
-
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android):'));
|
|
166
|
+
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android/harmony):'));
|
|
138
167
|
const { appId } = await (0, _app.getSelectedApp)(platform);
|
|
139
168
|
await listPackage(appId);
|
|
140
169
|
}
|
package/lib/user.js
CHANGED
|
@@ -10,17 +10,17 @@ Object.defineProperty(exports, "commands", {
|
|
|
10
10
|
});
|
|
11
11
|
const _utils = require("./utils");
|
|
12
12
|
const _api = require("./api");
|
|
13
|
-
const
|
|
13
|
+
const _nodecrypto = /*#__PURE__*/ _interop_require_default(require("node:crypto"));
|
|
14
14
|
function _interop_require_default(obj) {
|
|
15
15
|
return obj && obj.__esModule ? obj : {
|
|
16
16
|
default: obj
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
function md5(str) {
|
|
20
|
-
return
|
|
20
|
+
return _nodecrypto.default.createHash('md5').update(str).digest('hex');
|
|
21
21
|
}
|
|
22
22
|
const commands = {
|
|
23
|
-
login: async
|
|
23
|
+
login: async ({ args })=>{
|
|
24
24
|
const email = args[0] || await (0, _utils.question)('email:');
|
|
25
25
|
const pwd = args[1] || await (0, _utils.question)('password:', true);
|
|
26
26
|
const { token, info } = await (0, _api.post)('/user/login', {
|
|
@@ -33,11 +33,11 @@ const commands = {
|
|
|
33
33
|
await (0, _api.saveSession)();
|
|
34
34
|
console.log(`欢迎使用 pushy 热更新服务, ${info.name}.`);
|
|
35
35
|
},
|
|
36
|
-
logout: async
|
|
36
|
+
logout: async ()=>{
|
|
37
37
|
await (0, _api.closeSession)();
|
|
38
38
|
console.log('已退出登录');
|
|
39
39
|
},
|
|
40
|
-
me: async
|
|
40
|
+
me: async ()=>{
|
|
41
41
|
const me = await (0, _api.get)('/user/me');
|
|
42
42
|
for(const k in me){
|
|
43
43
|
if (k !== 'ok') {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const Zip = require('./zip');
|
|
3
|
+
class AppParser extends Zip {
|
|
4
|
+
/**
|
|
5
|
+
* parser for parsing .apk file
|
|
6
|
+
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
|
|
7
|
+
*/ constructor(file){
|
|
8
|
+
super(file);
|
|
9
|
+
if (!(this instanceof AppParser)) {
|
|
10
|
+
return new AppParser(file);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
module.exports = AppParser;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const ApkParser = require('./apk');
|
|
3
3
|
const IpaParser = require('./ipa');
|
|
4
|
+
const AppParser = require('./app');
|
|
4
5
|
const supportFileTypes = [
|
|
5
6
|
'ipa',
|
|
6
|
-
'apk'
|
|
7
|
+
'apk',
|
|
8
|
+
'app'
|
|
7
9
|
];
|
|
8
10
|
class AppInfoParser {
|
|
9
11
|
parse() {
|
|
@@ -14,12 +16,12 @@ class AppInfoParser {
|
|
|
14
16
|
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
|
|
15
17
|
*/ constructor(file){
|
|
16
18
|
if (!file) {
|
|
17
|
-
throw new Error(
|
|
19
|
+
throw new Error("Param miss: file(file's path in Node, instance of File or Blob in browser).");
|
|
18
20
|
}
|
|
19
21
|
const splits = (file.name || file).split('.');
|
|
20
22
|
const fileType = splits[splits.length - 1].toLowerCase();
|
|
21
23
|
if (!supportFileTypes.includes(fileType)) {
|
|
22
|
-
throw new Error('Unsupported file type, only support .ipa or .apk file.');
|
|
24
|
+
throw new Error('Unsupported file type, only support .ipa or .apk or .app file.');
|
|
23
25
|
}
|
|
24
26
|
this.file = file;
|
|
25
27
|
switch(fileType){
|
|
@@ -29,6 +31,9 @@ class AppInfoParser {
|
|
|
29
31
|
case 'apk':
|
|
30
32
|
this.parser = new ApkParser(this.file);
|
|
31
33
|
break;
|
|
34
|
+
case 'app':
|
|
35
|
+
this.parser = new AppParser(this.file);
|
|
36
|
+
break;
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _bundle = require("../../bundle");
|
|
2
6
|
const Unzip = require('isomorphic-unzip');
|
|
3
7
|
const { isBrowser, decodeNullUnicode } = require('./utils');
|
|
4
8
|
class Zip {
|
|
@@ -28,10 +32,24 @@ class Zip {
|
|
|
28
32
|
], {
|
|
29
33
|
type
|
|
30
34
|
}, (err, buffers)=>{
|
|
35
|
+
console.log(buffers);
|
|
31
36
|
err ? reject(err) : resolve(buffers[regex]);
|
|
32
37
|
});
|
|
33
38
|
});
|
|
34
39
|
}
|
|
40
|
+
async getEntryFromHarmonyApp(regex) {
|
|
41
|
+
try {
|
|
42
|
+
let originSource;
|
|
43
|
+
await (0, _bundle.enumZipEntries)(this.file, (entry, zipFile)=>{
|
|
44
|
+
if (regex.test(entry.fileName)) {
|
|
45
|
+
return (0, _bundle.readEntire)(entry, zipFile).then((v)=>originSource = v);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return originSource;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Error in getEntryFromHarmonyApp:', error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
35
53
|
constructor(file){
|
|
36
54
|
if (isBrowser()) {
|
|
37
55
|
if (!(file instanceof window.Blob || typeof file.size !== 'undefined')) {
|
package/lib/utils/index.js
CHANGED
|
@@ -12,6 +12,9 @@ _export(exports, {
|
|
|
12
12
|
getApkInfo: function() {
|
|
13
13
|
return getApkInfo;
|
|
14
14
|
},
|
|
15
|
+
getAppInfo: function() {
|
|
16
|
+
return getAppInfo;
|
|
17
|
+
},
|
|
15
18
|
getIpaInfo: function() {
|
|
16
19
|
return getIpaInfo;
|
|
17
20
|
},
|
|
@@ -115,6 +118,36 @@ async function getApkInfo(fn) {
|
|
|
115
118
|
...appCredential
|
|
116
119
|
};
|
|
117
120
|
}
|
|
121
|
+
async function getAppInfo(fn) {
|
|
122
|
+
const appInfoParser = new _appinfoparser.default(fn);
|
|
123
|
+
const bundleFile = await appInfoParser.parser.getEntryFromHarmonyApp(/rawfile\/bundle.harmony.js/);
|
|
124
|
+
if (!bundleFile) {
|
|
125
|
+
throw new Error('找不到bundle文件。请确保此app为release版本,且bundle文件名为默认的bundle.harmony.js');
|
|
126
|
+
}
|
|
127
|
+
const updateJsonFile = await appInfoParser.parser.getEntryFromHarmonyApp(/rawfile\/update.json/);
|
|
128
|
+
let appCredential = {};
|
|
129
|
+
if (updateJsonFile) {
|
|
130
|
+
appCredential = JSON.parse(updateJsonFile.toString()).harmony;
|
|
131
|
+
}
|
|
132
|
+
const metaJsonFile = await appInfoParser.parser.getEntryFromHarmonyApp(/rawfile\/meta.json/);
|
|
133
|
+
let metaData = {};
|
|
134
|
+
if (metaJsonFile) {
|
|
135
|
+
metaData = JSON.parse(metaJsonFile.toString());
|
|
136
|
+
}
|
|
137
|
+
const { versionName, pushy_build_time } = metaData;
|
|
138
|
+
let buildTime = 0;
|
|
139
|
+
if (pushy_build_time) {
|
|
140
|
+
buildTime = pushy_build_time;
|
|
141
|
+
}
|
|
142
|
+
if (buildTime == 0) {
|
|
143
|
+
throw new Error('无法获取此包的编译时间戳。请更新 react-native-update 到最新版本后重新打包上传。');
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
versionName,
|
|
147
|
+
buildTime,
|
|
148
|
+
...appCredential
|
|
149
|
+
};
|
|
150
|
+
}
|
|
118
151
|
async function getIpaInfo(fn) {
|
|
119
152
|
const appInfoParser = new _appinfoparser.default(fn);
|
|
120
153
|
const bundleFile = await appInfoParser.parser.getEntry(/payload\/.+?\.app\/main.jsbundle/);
|
package/lib/versions.js
CHANGED
|
@@ -81,9 +81,9 @@ const commands = {
|
|
|
81
81
|
const fn = args[0];
|
|
82
82
|
const { name, description, metaInfo } = options;
|
|
83
83
|
if (!fn || !fn.endsWith('.ppk')) {
|
|
84
|
-
throw new Error('使用方法: pushy publish ppk后缀文件 --platform ios|android');
|
|
84
|
+
throw new Error('使用方法: pushy publish ppk后缀文件 --platform ios|android|harmony');
|
|
85
85
|
}
|
|
86
|
-
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android):'));
|
|
86
|
+
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android/harmony):'));
|
|
87
87
|
const { appId } = await (0, _app.getSelectedApp)(platform);
|
|
88
88
|
const { hash } = await (0, _api.uploadFile)(fn);
|
|
89
89
|
const { id } = await (0, _api.post)(`/app/${appId}/version/create`, {
|
|
@@ -107,12 +107,12 @@ const commands = {
|
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
109
|
versions: async function({ options }) {
|
|
110
|
-
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android):'));
|
|
110
|
+
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android/harmony):'));
|
|
111
111
|
const { appId } = await (0, _app.getSelectedApp)(platform);
|
|
112
112
|
await listVersions(appId);
|
|
113
113
|
},
|
|
114
114
|
update: async function({ args, options }) {
|
|
115
|
-
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android):'));
|
|
115
|
+
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android/harmony):'));
|
|
116
116
|
const { appId } = await (0, _app.getSelectedApp)(platform);
|
|
117
117
|
let versionId = options.versionId || (await chooseVersion(appId)).id;
|
|
118
118
|
if (versionId === 'null') {
|
|
@@ -229,7 +229,7 @@ const commands = {
|
|
|
229
229
|
console.log(`已将热更版本 ${versionId} 绑定到原生版本 ${pkgVersion} (id: ${pkgId})`);
|
|
230
230
|
},
|
|
231
231
|
updateVersionInfo: async function({ args, options }) {
|
|
232
|
-
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android):'));
|
|
232
|
+
const platform = (0, _app.checkPlatform)(options.platform || await (0, _utils.question)('平台(ios/android/harmony):'));
|
|
233
233
|
const { appId } = await (0, _app.getSelectedApp)(platform);
|
|
234
234
|
const versionId = options.versionId || (await chooseVersion(appId)).id;
|
|
235
235
|
const updateParams = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-update-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.0",
|
|
4
4
|
"description": "Command tools for javaScript updater with `pushy` service for react native apps.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "swc src -d lib --strip-leading-paths",
|
|
16
|
-
"prepare": "npm run build && chmod +x lib/index.js"
|
|
16
|
+
"prepare": "npm run build && chmod +x lib/index.js",
|
|
17
|
+
"lint": "tsc --noEmit & biome check --write ."
|
|
17
18
|
},
|
|
18
19
|
"repository": {
|
|
19
20
|
"type": "git",
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
"react-native",
|
|
24
25
|
"ios",
|
|
25
26
|
"android",
|
|
27
|
+
"harmony",
|
|
26
28
|
"update"
|
|
27
29
|
],
|
|
28
30
|
"author": "reactnativecn",
|
|
@@ -61,11 +63,10 @@
|
|
|
61
63
|
"node": ">= 10"
|
|
62
64
|
},
|
|
63
65
|
"devDependencies": {
|
|
66
|
+
"@biomejs/biome": "^1.9.4",
|
|
64
67
|
"@swc/cli": "^0.5.1",
|
|
65
68
|
"@swc/core": "^1.9.3",
|
|
66
69
|
"@types/node": "^22.9.3",
|
|
67
|
-
"oxlint": "^0.13.1",
|
|
68
70
|
"typescript": "^5.7.2"
|
|
69
|
-
}
|
|
70
|
-
"packageManager": "yarn@1.22.22"
|
|
71
|
+
}
|
|
71
72
|
}
|
package/src/api.ts
CHANGED
|
@@ -7,7 +7,7 @@ import packageJson from '../package.json';
|
|
|
7
7
|
import tcpp from 'tcp-ping';
|
|
8
8
|
import filesizeParser from 'filesize-parser';
|
|
9
9
|
import { pricingPageUrl } from './utils';
|
|
10
|
-
import { Session } from 'types';
|
|
10
|
+
import type { Session } from 'types';
|
|
11
11
|
import FormData from 'form-data';
|
|
12
12
|
|
|
13
13
|
const tcpPing = util.promisify(tcpp.ping);
|
|
@@ -20,15 +20,13 @@ let host = process.env.PUSHY_REGISTRY || defaultEndpoint;
|
|
|
20
20
|
|
|
21
21
|
const userAgent = `react-native-update-cli/${packageJson.version}`;
|
|
22
22
|
|
|
23
|
-
export const getSession =
|
|
24
|
-
return session;
|
|
25
|
-
};
|
|
23
|
+
export const getSession = () => session;
|
|
26
24
|
|
|
27
|
-
export const replaceSession =
|
|
25
|
+
export const replaceSession = (newSession: { token: string }) => {
|
|
28
26
|
session = newSession;
|
|
29
27
|
};
|
|
30
28
|
|
|
31
|
-
export const loadSession = async
|
|
29
|
+
export const loadSession = async () => {
|
|
32
30
|
if (fs.existsSync('.update')) {
|
|
33
31
|
try {
|
|
34
32
|
replaceSession(JSON.parse(fs.readFileSync('.update', 'utf8')));
|
|
@@ -42,7 +40,7 @@ export const loadSession = async function () {
|
|
|
42
40
|
}
|
|
43
41
|
};
|
|
44
42
|
|
|
45
|
-
export const saveSession =
|
|
43
|
+
export const saveSession = () => {
|
|
46
44
|
// Only save on change.
|
|
47
45
|
if (session !== savedSession) {
|
|
48
46
|
const current = session;
|
|
@@ -52,7 +50,7 @@ export const saveSession = function () {
|
|
|
52
50
|
}
|
|
53
51
|
};
|
|
54
52
|
|
|
55
|
-
export const closeSession =
|
|
53
|
+
export const closeSession = () => {
|
|
56
54
|
if (fs.existsSync('.update')) {
|
|
57
55
|
fs.unlinkSync('.update');
|
|
58
56
|
savedSession = undefined;
|
|
@@ -64,38 +62,35 @@ export const closeSession = function () {
|
|
|
64
62
|
async function query(url: string, options: fetch.RequestInit) {
|
|
65
63
|
const resp = await fetch(url, options);
|
|
66
64
|
const text = await resp.text();
|
|
67
|
-
let json;
|
|
65
|
+
let json: any;
|
|
68
66
|
try {
|
|
69
67
|
json = JSON.parse(text);
|
|
70
|
-
} catch (e) {
|
|
71
|
-
if (resp.statusText.includes('Unauthorized')) {
|
|
72
|
-
throw new Error('登录信息已过期,请使用 pushy login 命令重新登录');
|
|
73
|
-
} else {
|
|
74
|
-
throw new Error(`Server error: ${resp.statusText}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
68
|
+
} catch (e) {}
|
|
77
69
|
|
|
78
70
|
if (resp.status !== 200) {
|
|
79
|
-
|
|
71
|
+
const message = json?.message || resp.statusText;
|
|
72
|
+
if (resp.status === 401) {
|
|
73
|
+
throw new Error('登录信息已过期,请使用 pushy login 命令重新登录');
|
|
74
|
+
}
|
|
75
|
+
throw new Error(message);
|
|
80
76
|
}
|
|
81
77
|
return json;
|
|
82
78
|
}
|
|
83
79
|
|
|
84
80
|
function queryWithoutBody(method: string) {
|
|
85
|
-
return
|
|
86
|
-
|
|
81
|
+
return (api: string) =>
|
|
82
|
+
query(host + api, {
|
|
87
83
|
method,
|
|
88
84
|
headers: {
|
|
89
85
|
'User-Agent': userAgent,
|
|
90
86
|
'X-AccessToken': session ? session.token : '',
|
|
91
87
|
},
|
|
92
88
|
});
|
|
93
|
-
};
|
|
94
89
|
}
|
|
95
90
|
|
|
96
91
|
function queryWithBody(method: string) {
|
|
97
|
-
return
|
|
98
|
-
|
|
92
|
+
return (api: string, body: Record<string, any>) =>
|
|
93
|
+
query(host + api, {
|
|
99
94
|
method,
|
|
100
95
|
headers: {
|
|
101
96
|
'User-Agent': userAgent,
|
|
@@ -104,10 +99,8 @@ function queryWithBody(method: string) {
|
|
|
104
99
|
},
|
|
105
100
|
body: JSON.stringify(body),
|
|
106
101
|
});
|
|
107
|
-
};
|
|
108
102
|
}
|
|
109
103
|
|
|
110
|
-
export const get = queryWithoutBody('GET');
|
|
111
104
|
export const post = queryWithBody('POST');
|
|
112
105
|
export const put = queryWithBody('PUT');
|
|
113
106
|
export const doDelete = queryWithBody('DELETE');
|
|
@@ -155,7 +148,7 @@ export async function uploadFile(fn: string, key?: string) {
|
|
|
155
148
|
form.append(k, v);
|
|
156
149
|
});
|
|
157
150
|
const fileStream = fs.createReadStream(fn);
|
|
158
|
-
fileStream.on('data',
|
|
151
|
+
fileStream.on('data', (data) => {
|
|
159
152
|
bar.tick(data.length);
|
|
160
153
|
});
|
|
161
154
|
|
package/src/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { question } from './utils';
|
|
2
|
-
import fs from 'fs';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
3
|
import Table from 'tty-table';
|
|
4
4
|
|
|
5
5
|
import { post, get, doDelete } from './api';
|
|
@@ -7,6 +7,7 @@ import { post, get, doDelete } from './api';
|
|
|
7
7
|
const validPlatforms = {
|
|
8
8
|
ios: 1,
|
|
9
9
|
android: 1,
|
|
10
|
+
harmony: 1,
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
export function checkPlatform(platform) {
|
|
@@ -74,7 +75,7 @@ export const commands = {
|
|
|
74
75
|
const name = options.name || (await question('应用名称:'));
|
|
75
76
|
const { downloadUrl } = options;
|
|
76
77
|
const platform = checkPlatform(
|
|
77
|
-
options.platform || (await question('平台(ios/android):')),
|
|
78
|
+
options.platform || (await question('平台(ios/android/harmony):')),
|
|
78
79
|
);
|
|
79
80
|
const { id } = await post('/app/create', { name, platform });
|
|
80
81
|
console.log(`已成功创建应用(id: ${id})`);
|
|
@@ -83,7 +84,7 @@ export const commands = {
|
|
|
83
84
|
options: { platform, downloadUrl },
|
|
84
85
|
});
|
|
85
86
|
},
|
|
86
|
-
deleteApp: async
|
|
87
|
+
deleteApp: async ({ args, options }) => {
|
|
87
88
|
const { platform } = options;
|
|
88
89
|
const id = args[0] || chooseApp(platform);
|
|
89
90
|
if (!id) {
|
|
@@ -92,15 +93,17 @@ export const commands = {
|
|
|
92
93
|
await doDelete(`/app/${id}`);
|
|
93
94
|
console.log('操作成功');
|
|
94
95
|
},
|
|
95
|
-
apps: async
|
|
96
|
+
apps: async ({ options }) => {
|
|
96
97
|
const { platform } = options;
|
|
97
98
|
listApp(platform);
|
|
98
99
|
},
|
|
99
|
-
selectApp: async
|
|
100
|
+
selectApp: async ({ args, options }) => {
|
|
100
101
|
const platform = checkPlatform(
|
|
101
|
-
options.platform || (await question('平台(ios/android):')),
|
|
102
|
+
options.platform || (await question('平台(ios/android/harmony):')),
|
|
102
103
|
);
|
|
103
|
-
const id = args[0]
|
|
104
|
+
const id = args[0]
|
|
105
|
+
? Number.parseInt(args[0])
|
|
106
|
+
: (await chooseApp(platform)).id;
|
|
104
107
|
|
|
105
108
|
let updateInfo = {};
|
|
106
109
|
if (fs.existsSync('update.json')) {
|
package/src/bundle.js
CHANGED
|
@@ -184,16 +184,21 @@ async function runReactNativeBundleCommand(
|
|
|
184
184
|
|
|
185
185
|
async function copyHarmonyBundle(outputFolder) {
|
|
186
186
|
const harmonyRawPath = 'harmony/entry/src/main/resources/rawfile';
|
|
187
|
-
|
|
188
187
|
try {
|
|
188
|
+
await fs.ensureDir(harmonyRawPath);
|
|
189
|
+
try {
|
|
190
|
+
await fs.access(harmonyRawPath, fs.constants.W_OK);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
await fs.chmod(harmonyRawPath, 0o755);
|
|
193
|
+
}
|
|
194
|
+
await fs.remove(path.join(harmonyRawPath, 'update.json'));
|
|
195
|
+
await fs.copy('update.json', path.join(harmonyRawPath, 'update.json'));
|
|
196
|
+
|
|
189
197
|
await fs.ensureDir(outputFolder);
|
|
190
198
|
await fs.copy(harmonyRawPath, outputFolder);
|
|
191
|
-
|
|
192
|
-
console.log(
|
|
193
|
-
`Successfully copied from ${harmonyRawPath} to ${outputFolder}`,
|
|
194
|
-
);
|
|
195
199
|
} catch (error) {
|
|
196
|
-
console.error('
|
|
200
|
+
console.error('copyHarmonyBundle 错误:', error);
|
|
201
|
+
throw new Error(`复制文件失败: ${error.message}`);
|
|
197
202
|
}
|
|
198
203
|
}
|
|
199
204
|
|
|
@@ -333,7 +338,7 @@ async function pack(dir, output) {
|
|
|
333
338
|
console.log('ppk热更包已生成并保存到: ' + output);
|
|
334
339
|
}
|
|
335
340
|
|
|
336
|
-
function readEntire(entry, zipFile) {
|
|
341
|
+
export function readEntire(entry, zipFile) {
|
|
337
342
|
const buffers = [];
|
|
338
343
|
return new Promise((resolve, reject) => {
|
|
339
344
|
zipFile.openReadStream(entry, (err, stream) => {
|
|
@@ -608,7 +613,7 @@ async function diffFromPackage(
|
|
|
608
613
|
await writePromise;
|
|
609
614
|
}
|
|
610
615
|
|
|
611
|
-
async function enumZipEntries(zipFn, callback, nestedPath = '') {
|
|
616
|
+
export async function enumZipEntries(zipFn, callback, nestedPath = '') {
|
|
612
617
|
return new Promise((resolve, reject) => {
|
|
613
618
|
openZipFile(zipFn, { lazyEntries: true }, async (err, zipfile) => {
|
|
614
619
|
if (err) {
|
|
@@ -699,7 +704,7 @@ function diffArgsCheck(args, options, diffFn) {
|
|
|
699
704
|
export const commands = {
|
|
700
705
|
bundle: async function ({ options }) {
|
|
701
706
|
const platform = checkPlatform(
|
|
702
|
-
options.platform || (await question('平台(ios/android):')),
|
|
707
|
+
options.platform || (await question('平台(ios/android/harmony):')),
|
|
703
708
|
);
|
|
704
709
|
|
|
705
710
|
let { bundleName, entryFile, intermediaDir, output, dev, sourcemap } =
|
package/src/package.js
CHANGED
|
@@ -3,7 +3,7 @@ import { question, saveToLocal } from './utils';
|
|
|
3
3
|
|
|
4
4
|
import { checkPlatform, getSelectedApp } from './app';
|
|
5
5
|
|
|
6
|
-
import { getApkInfo, getIpaInfo } from './utils';
|
|
6
|
+
import { getApkInfo, getIpaInfo, getAppInfo } from './utils';
|
|
7
7
|
import Table from 'tty-table';
|
|
8
8
|
|
|
9
9
|
export async function listPackage(appId) {
|
|
@@ -122,6 +122,51 @@ export const commands = {
|
|
|
122
122
|
`已成功上传apk原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
|
|
123
123
|
);
|
|
124
124
|
},
|
|
125
|
+
uploadApp: async function ({ args }) {
|
|
126
|
+
const fn = args[0];
|
|
127
|
+
if (!fn || !fn.endsWith('.app')) {
|
|
128
|
+
throw new Error('使用方法: pushy uploadApp app后缀文件');
|
|
129
|
+
}
|
|
130
|
+
const {
|
|
131
|
+
versionName,
|
|
132
|
+
buildTime,
|
|
133
|
+
appId: appIdInPkg,
|
|
134
|
+
appKey: appKeyInPkg,
|
|
135
|
+
} = await getAppInfo(fn);
|
|
136
|
+
const { appId, appKey } = await getSelectedApp('harmony');
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if (appIdInPkg && appIdInPkg != appId) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`appId不匹配!当前app: ${appIdInPkg}, 当前update.json: ${appId}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (appKeyInPkg && appKeyInPkg !== appKey) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`appKey不匹配!当前app: ${appKeyInPkg}, 当前update.json: ${appKey}`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const { hash } = await uploadFile(fn);
|
|
152
|
+
|
|
153
|
+
const { id } = await post(`/app/${appId}/package/create`, {
|
|
154
|
+
name: versionName,
|
|
155
|
+
hash,
|
|
156
|
+
buildTime,
|
|
157
|
+
});
|
|
158
|
+
saveToLocal(fn, `${appId}/package/${id}.app`);
|
|
159
|
+
console.log(
|
|
160
|
+
`已成功上传app原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
|
|
161
|
+
);
|
|
162
|
+
},
|
|
163
|
+
parseApp: async function ({ args }) {
|
|
164
|
+
const fn = args[0];
|
|
165
|
+
if (!fn || !fn.endsWith('.app')) {
|
|
166
|
+
throw new Error('使用方法: pushy parseApp app后缀文件');
|
|
167
|
+
}
|
|
168
|
+
console.log(await getAppInfo(fn));
|
|
169
|
+
},
|
|
125
170
|
parseIpa: async function ({ args }) {
|
|
126
171
|
const fn = args[0];
|
|
127
172
|
if (!fn || !fn.endsWith('.ipa')) {
|
|
@@ -138,7 +183,7 @@ export const commands = {
|
|
|
138
183
|
},
|
|
139
184
|
packages: async function ({ options }) {
|
|
140
185
|
const platform = checkPlatform(
|
|
141
|
-
options.platform || (await question('平台(ios/android):')),
|
|
186
|
+
options.platform || (await question('平台(ios/android/harmony):')),
|
|
142
187
|
);
|
|
143
188
|
const { appId } = await getSelectedApp(platform);
|
|
144
189
|
await listPackage(appId);
|
package/src/user.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { question } from './utils';
|
|
2
2
|
import { post, get, replaceSession, saveSession, closeSession } from './api';
|
|
3
|
-
import crypto from 'crypto';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
function md5(str) {
|
|
6
6
|
return crypto.createHash('md5').update(str).digest('hex');
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export const commands = {
|
|
10
|
-
login: async
|
|
10
|
+
login: async ({ args }) => {
|
|
11
11
|
const email = args[0] || (await question('email:'));
|
|
12
12
|
const pwd = args[1] || (await question('password:', true));
|
|
13
13
|
const { token, info } = await post('/user/login', {
|
|
@@ -18,11 +18,11 @@ export const commands = {
|
|
|
18
18
|
await saveSession();
|
|
19
19
|
console.log(`欢迎使用 pushy 热更新服务, ${info.name}.`);
|
|
20
20
|
},
|
|
21
|
-
logout: async
|
|
21
|
+
logout: async () => {
|
|
22
22
|
await closeSession();
|
|
23
23
|
console.log('已退出登录');
|
|
24
24
|
},
|
|
25
|
-
me: async
|
|
25
|
+
me: async () => {
|
|
26
26
|
const me = await get('/user/me');
|
|
27
27
|
for (const k in me) {
|
|
28
28
|
if (k !== 'ok') {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const Zip = require('./zip')
|
|
2
|
+
|
|
3
|
+
class AppParser extends Zip {
|
|
4
|
+
/**
|
|
5
|
+
* parser for parsing .apk file
|
|
6
|
+
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
|
|
7
|
+
*/
|
|
8
|
+
constructor (file) {
|
|
9
|
+
super(file)
|
|
10
|
+
if (!(this instanceof AppParser)) {
|
|
11
|
+
return new AppParser(file)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = AppParser
|
|
@@ -1,35 +1,43 @@
|
|
|
1
|
-
const ApkParser = require('./apk')
|
|
2
|
-
const IpaParser = require('./ipa')
|
|
3
|
-
const
|
|
1
|
+
const ApkParser = require('./apk');
|
|
2
|
+
const IpaParser = require('./ipa');
|
|
3
|
+
const AppParser = require('./app');
|
|
4
|
+
const supportFileTypes = ['ipa', 'apk', 'app'];
|
|
4
5
|
|
|
5
6
|
class AppInfoParser {
|
|
6
7
|
/**
|
|
7
8
|
* parser for parsing .ipa or .apk file
|
|
8
9
|
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
|
|
9
10
|
*/
|
|
10
|
-
constructor
|
|
11
|
+
constructor(file) {
|
|
11
12
|
if (!file) {
|
|
12
|
-
throw new Error(
|
|
13
|
+
throw new Error(
|
|
14
|
+
"Param miss: file(file's path in Node, instance of File or Blob in browser).",
|
|
15
|
+
);
|
|
13
16
|
}
|
|
14
|
-
const splits = (file.name || file).split('.')
|
|
15
|
-
const fileType = splits[splits.length - 1].toLowerCase()
|
|
17
|
+
const splits = (file.name || file).split('.');
|
|
18
|
+
const fileType = splits[splits.length - 1].toLowerCase();
|
|
16
19
|
if (!supportFileTypes.includes(fileType)) {
|
|
17
|
-
throw new Error(
|
|
20
|
+
throw new Error(
|
|
21
|
+
'Unsupported file type, only support .ipa or .apk or .app file.',
|
|
22
|
+
);
|
|
18
23
|
}
|
|
19
|
-
this.file = file
|
|
24
|
+
this.file = file;
|
|
20
25
|
|
|
21
26
|
switch (fileType) {
|
|
22
27
|
case 'ipa':
|
|
23
|
-
this.parser = new IpaParser(this.file)
|
|
24
|
-
break
|
|
28
|
+
this.parser = new IpaParser(this.file);
|
|
29
|
+
break;
|
|
25
30
|
case 'apk':
|
|
26
|
-
this.parser = new ApkParser(this.file)
|
|
27
|
-
break
|
|
31
|
+
this.parser = new ApkParser(this.file);
|
|
32
|
+
break;
|
|
33
|
+
case 'app':
|
|
34
|
+
this.parser = new AppParser(this.file);
|
|
35
|
+
break;
|
|
28
36
|
}
|
|
29
37
|
}
|
|
30
|
-
parse
|
|
31
|
-
return this.parser.parse()
|
|
38
|
+
parse() {
|
|
39
|
+
return this.parser.parse();
|
|
32
40
|
}
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
module.exports = AppInfoParser
|
|
43
|
+
module.exports = AppInfoParser;
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
const Unzip = require('isomorphic-unzip')
|
|
2
|
-
const { isBrowser, decodeNullUnicode } = require('./utils')
|
|
1
|
+
const Unzip = require('isomorphic-unzip');
|
|
2
|
+
const { isBrowser, decodeNullUnicode } = require('./utils');
|
|
3
|
+
import { enumZipEntries, readEntire } from '../../bundle';
|
|
3
4
|
|
|
4
5
|
class Zip {
|
|
5
|
-
constructor
|
|
6
|
+
constructor(file) {
|
|
6
7
|
if (isBrowser()) {
|
|
7
8
|
if (!(file instanceof window.Blob || typeof file.size !== 'undefined')) {
|
|
8
|
-
throw new Error(
|
|
9
|
+
throw new Error(
|
|
10
|
+
'Param error: [file] must be an instance of Blob or File in browser.',
|
|
11
|
+
);
|
|
9
12
|
}
|
|
10
|
-
this.file = file
|
|
13
|
+
this.file = file;
|
|
11
14
|
} else {
|
|
12
15
|
if (typeof file !== 'string') {
|
|
13
|
-
throw new Error('Param error: [file] must be file path in Node.')
|
|
16
|
+
throw new Error('Param error: [file] must be file path in Node.');
|
|
14
17
|
}
|
|
15
|
-
this.file = require('path').resolve(file)
|
|
18
|
+
this.file = require('path').resolve(file);
|
|
16
19
|
}
|
|
17
|
-
this.unzip = new Unzip(this.file)
|
|
20
|
+
this.unzip = new Unzip(this.file);
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
/**
|
|
@@ -22,27 +25,42 @@ class Zip {
|
|
|
22
25
|
* @param {Array} regexps // regexps for matching files
|
|
23
26
|
* @param {String} type // return type, can be buffer or blob, default buffer
|
|
24
27
|
*/
|
|
25
|
-
getEntries
|
|
26
|
-
regexps = regexps.map(regex => decodeNullUnicode(regex))
|
|
28
|
+
getEntries(regexps, type = 'buffer') {
|
|
29
|
+
regexps = regexps.map((regex) => decodeNullUnicode(regex));
|
|
27
30
|
return new Promise((resolve, reject) => {
|
|
28
31
|
this.unzip.getBuffer(regexps, { type }, (err, buffers) => {
|
|
29
|
-
err ? reject(err) : resolve(buffers)
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
+
err ? reject(err) : resolve(buffers);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
32
35
|
}
|
|
33
36
|
/**
|
|
34
37
|
* get entry by regex, return an instance of Buffer or Blob
|
|
35
38
|
* @param {Regex} regex // regex for matching file
|
|
36
39
|
* @param {String} type // return type, can be buffer or blob, default buffer
|
|
37
40
|
*/
|
|
38
|
-
getEntry
|
|
39
|
-
regex = decodeNullUnicode(regex)
|
|
41
|
+
getEntry(regex, type = 'buffer') {
|
|
42
|
+
regex = decodeNullUnicode(regex);
|
|
40
43
|
return new Promise((resolve, reject) => {
|
|
41
44
|
this.unzip.getBuffer([regex], { type }, (err, buffers) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
console.log(buffers);
|
|
46
|
+
err ? reject(err) : resolve(buffers[regex]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getEntryFromHarmonyApp(regex) {
|
|
52
|
+
try {
|
|
53
|
+
let originSource;
|
|
54
|
+
await enumZipEntries(this.file, (entry, zipFile) => {
|
|
55
|
+
if (regex.test(entry.fileName)) {
|
|
56
|
+
return readEntire(entry, zipFile).then((v) => (originSource = v));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return originSource;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('Error in getEntryFromHarmonyApp:', error);
|
|
62
|
+
}
|
|
45
63
|
}
|
|
46
64
|
}
|
|
47
65
|
|
|
48
|
-
module.exports = Zip
|
|
66
|
+
module.exports = Zip;
|
package/src/utils/index.js
CHANGED
|
@@ -87,6 +87,43 @@ export async function getApkInfo(fn) {
|
|
|
87
87
|
return { versionName, buildTime, ...appCredential };
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
export async function getAppInfo(fn) {
|
|
91
|
+
const appInfoParser = new AppInfoParser(fn);
|
|
92
|
+
const bundleFile = await appInfoParser.parser.getEntryFromHarmonyApp(
|
|
93
|
+
/rawfile\/bundle.harmony.js/,
|
|
94
|
+
);
|
|
95
|
+
if (!bundleFile) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'找不到bundle文件。请确保此app为release版本,且bundle文件名为默认的bundle.harmony.js',
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const updateJsonFile = await appInfoParser.parser.getEntryFromHarmonyApp(
|
|
101
|
+
/rawfile\/update.json/,
|
|
102
|
+
);
|
|
103
|
+
let appCredential = {};
|
|
104
|
+
if (updateJsonFile) {
|
|
105
|
+
appCredential = JSON.parse(updateJsonFile.toString()).harmony;
|
|
106
|
+
}
|
|
107
|
+
const metaJsonFile = await appInfoParser.parser.getEntryFromHarmonyApp(
|
|
108
|
+
/rawfile\/meta.json/,
|
|
109
|
+
);
|
|
110
|
+
let metaData = {};
|
|
111
|
+
if (metaJsonFile) {
|
|
112
|
+
metaData = JSON.parse(metaJsonFile.toString());
|
|
113
|
+
}
|
|
114
|
+
const { versionName, pushy_build_time } = metaData;
|
|
115
|
+
let buildTime = 0;
|
|
116
|
+
if (pushy_build_time) {
|
|
117
|
+
buildTime = pushy_build_time;
|
|
118
|
+
}
|
|
119
|
+
if (buildTime == 0) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
'无法获取此包的编译时间戳。请更新 react-native-update 到最新版本后重新打包上传。',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return { versionName, buildTime, ...appCredential };
|
|
125
|
+
}
|
|
126
|
+
|
|
90
127
|
export async function getIpaInfo(fn) {
|
|
91
128
|
const appInfoParser = new AppInfoParser(fn);
|
|
92
129
|
const bundleFile = await appInfoParser.parser.getEntry(
|
package/src/versions.js
CHANGED
|
@@ -86,12 +86,12 @@ export const commands = {
|
|
|
86
86
|
|
|
87
87
|
if (!fn || !fn.endsWith('.ppk')) {
|
|
88
88
|
throw new Error(
|
|
89
|
-
'使用方法: pushy publish ppk后缀文件 --platform ios|android',
|
|
89
|
+
'使用方法: pushy publish ppk后缀文件 --platform ios|android|harmony',
|
|
90
90
|
);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
const platform = checkPlatform(
|
|
94
|
-
options.platform || (await question('平台(ios/android):')),
|
|
94
|
+
options.platform || (await question('平台(ios/android/harmony):')),
|
|
95
95
|
);
|
|
96
96
|
const { appId } = await getSelectedApp(platform);
|
|
97
97
|
|
|
@@ -114,14 +114,14 @@ export const commands = {
|
|
|
114
114
|
},
|
|
115
115
|
versions: async function ({ options }) {
|
|
116
116
|
const platform = checkPlatform(
|
|
117
|
-
options.platform || (await question('平台(ios/android):')),
|
|
117
|
+
options.platform || (await question('平台(ios/android/harmony):')),
|
|
118
118
|
);
|
|
119
119
|
const { appId } = await getSelectedApp(platform);
|
|
120
120
|
await listVersions(appId);
|
|
121
121
|
},
|
|
122
122
|
update: async function ({ args, options }) {
|
|
123
123
|
const platform = checkPlatform(
|
|
124
|
-
options.platform || (await question('平台(ios/android):')),
|
|
124
|
+
options.platform || (await question('平台(ios/android/harmony):')),
|
|
125
125
|
);
|
|
126
126
|
const { appId } = await getSelectedApp(platform);
|
|
127
127
|
let versionId = options.versionId || (await chooseVersion(appId)).id;
|
|
@@ -255,7 +255,7 @@ export const commands = {
|
|
|
255
255
|
},
|
|
256
256
|
updateVersionInfo: async function ({ args, options }) {
|
|
257
257
|
const platform = checkPlatform(
|
|
258
|
-
options.platform || (await question('平台(ios/android):')),
|
|
258
|
+
options.platform || (await question('平台(ios/android/harmony):')),
|
|
259
259
|
);
|
|
260
260
|
const { appId } = await getSelectedApp(platform);
|
|
261
261
|
const versionId = options.versionId || (await chooseVersion(appId)).id;
|