react-native-update-cli 2.4.2 → 2.6.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/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/cli.json +40 -0
- package/lib/api.js +1 -1
- package/lib/locales/en.js +16 -1
- package/lib/locales/zh.js +16 -1
- package/lib/package.js +60 -6
- package/lib/provider.js +3 -0
- package/lib/utils/app-info-parser/aab.js +230 -0
- package/lib/utils/app-info-parser/apk.js +25 -27
- package/lib/utils/app-info-parser/app.js +10 -11
- package/lib/utils/app-info-parser/index.js +13 -8
- package/lib/utils/app-info-parser/ipa.js +16 -21
- package/lib/utils/app-info-parser/resource-finder.js +365 -305
- package/lib/utils/app-info-parser/utils.js +78 -63
- package/lib/utils/app-info-parser/xml-parser/binary.js +57 -51
- package/lib/utils/app-info-parser/xml-parser/manifest.js +47 -39
- package/lib/utils/app-info-parser/zip.js +21 -11
- package/lib/utils/http-helper.js +1 -1
- package/lib/utils/index.js +137 -0
- package/lib/versions.js +22 -0
- package/package.json +3 -2
- package/proto/Configuration.proto +183 -0
- package/proto/Resources.proto +569 -0
- package/src/api.ts +2 -6
- package/src/locales/en.ts +20 -0
- package/src/locales/zh.ts +18 -0
- package/src/modules/version-module.ts +1 -1
- package/src/package.ts +112 -12
- package/src/provider.ts +3 -0
- package/src/utils/app-info-parser/aab.ts +240 -0
- package/src/utils/app-info-parser/{apk.js → apk.ts} +30 -41
- package/src/utils/app-info-parser/app.ts +3 -0
- package/src/utils/app-info-parser/index.ts +9 -5
- package/src/utils/app-info-parser/{ipa.js → ipa.ts} +17 -31
- package/src/utils/app-info-parser/resource-finder.ts +508 -0
- package/src/utils/app-info-parser/utils.ts +162 -0
- package/src/utils/app-info-parser/xml-parser/{binary.js → binary.ts} +69 -61
- package/src/utils/app-info-parser/xml-parser/{manifest.js → manifest.ts} +50 -51
- package/src/utils/app-info-parser/zip.ts +86 -0
- package/src/utils/dep-versions.ts +7 -4
- package/src/utils/http-helper.ts +1 -1
- package/src/utils/index.ts +154 -0
- package/src/utils/latest-version/index.ts +2 -1
- package/src/versions.ts +27 -2
- package/src/utils/app-info-parser/app.js +0 -16
- package/src/utils/app-info-parser/resource-finder.js +0 -495
- package/src/utils/app-info-parser/utils.js +0 -172
- package/src/utils/app-info-parser/zip.js +0 -66
package/README.md
CHANGED
|
@@ -223,10 +223,12 @@ Each workflow step contains:
|
|
|
223
223
|
|
|
224
224
|
- `uploadIpa`: Upload IPA files (supports `--version` to override extracted version)
|
|
225
225
|
- `uploadApk`: Upload APK files (supports `--version` to override extracted version)
|
|
226
|
+
- `uploadAab`: Upload AAB files (converted to APK, supports `--version`, `--includeAllSplits`, `--splits`)
|
|
226
227
|
- `uploadApp`: Upload APP files (supports `--version` to override extracted version)
|
|
227
228
|
- `parseApp`: Parse APP file information
|
|
228
229
|
- `parseIpa`: Parse IPA file information
|
|
229
230
|
- `parseApk`: Parse APK file information
|
|
231
|
+
- `extractApk`: Extract a universal APK from an AAB (supports `--output`, `--includeAllSplits`, `--splits`)
|
|
230
232
|
- `packages`: List packages
|
|
231
233
|
|
|
232
234
|
### User Module (`user`)
|
package/README.zh-CN.md
CHANGED
|
@@ -221,10 +221,12 @@ const workflowResult = await moduleManager.executeWorkflow('my-workflow', {
|
|
|
221
221
|
|
|
222
222
|
- `uploadIpa`: 上传 IPA 文件(支持 `--version` 参数覆盖提取的版本)
|
|
223
223
|
- `uploadApk`: 上传 APK 文件(支持 `--version` 参数覆盖提取的版本)
|
|
224
|
+
- `uploadAab`: 上传 AAB 文件(转换为 APK,支持 `--version`、`--includeAllSplits`、`--splits`)
|
|
224
225
|
- `uploadApp`: 上传 APP 文件(支持 `--version` 参数覆盖提取的版本)
|
|
225
226
|
- `parseApp`: 解析 APP 文件信息
|
|
226
227
|
- `parseIpa`: 解析 IPA 文件信息
|
|
227
228
|
- `parseApk`: 解析 APK 文件信息
|
|
229
|
+
- `extractApk`: 从 AAB 提取通用 APK(支持 `--output`、`--includeAllSplits`、`--splits`)
|
|
228
230
|
- `packages`: 列出包
|
|
229
231
|
|
|
230
232
|
### User 模块 (`user`)
|
package/cli.json
CHANGED
|
@@ -45,6 +45,19 @@
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
|
+
"uploadAab": {
|
|
49
|
+
"options": {
|
|
50
|
+
"version": {
|
|
51
|
+
"hasValue": true
|
|
52
|
+
},
|
|
53
|
+
"includeAllSplits": {
|
|
54
|
+
"default": false
|
|
55
|
+
},
|
|
56
|
+
"splits": {
|
|
57
|
+
"hasValue": true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
48
61
|
"uploadApp": {
|
|
49
62
|
"options": {
|
|
50
63
|
"version": {
|
|
@@ -55,6 +68,20 @@
|
|
|
55
68
|
"parseApp": {},
|
|
56
69
|
"parseIpa": {},
|
|
57
70
|
"parseApk": {},
|
|
71
|
+
"parseAab": {},
|
|
72
|
+
"extractApk": {
|
|
73
|
+
"options": {
|
|
74
|
+
"output": {
|
|
75
|
+
"hasValue": true
|
|
76
|
+
},
|
|
77
|
+
"includeAllSplits": {
|
|
78
|
+
"default": false
|
|
79
|
+
},
|
|
80
|
+
"splits": {
|
|
81
|
+
"hasValue": true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
58
85
|
"packages": {
|
|
59
86
|
"options": {
|
|
60
87
|
"platform": {
|
|
@@ -172,6 +199,19 @@
|
|
|
172
199
|
}
|
|
173
200
|
}
|
|
174
201
|
},
|
|
202
|
+
"deleteVersion": {
|
|
203
|
+
"options": {
|
|
204
|
+
"platform": {
|
|
205
|
+
"hasValue": true
|
|
206
|
+
},
|
|
207
|
+
"versionId": {
|
|
208
|
+
"hasValue": true
|
|
209
|
+
},
|
|
210
|
+
"appId": {
|
|
211
|
+
"hasValue": true
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
},
|
|
175
215
|
"build": {
|
|
176
216
|
"description": "Bundle javascript and copy assets."
|
|
177
217
|
},
|
package/lib/api.js
CHANGED
|
@@ -51,10 +51,10 @@ const _formdata = /*#__PURE__*/ _interop_require_default(require("form-data"));
|
|
|
51
51
|
const _nodefetch = /*#__PURE__*/ _interop_require_default(require("node-fetch"));
|
|
52
52
|
const _progress = /*#__PURE__*/ _interop_require_default(require("progress"));
|
|
53
53
|
const _tcpping = /*#__PURE__*/ _interop_require_default(require("tcp-ping"));
|
|
54
|
+
const _httphelper = require("./utils/http-helper");
|
|
54
55
|
const _packagejson = /*#__PURE__*/ _interop_require_default(require("../package.json"));
|
|
55
56
|
const _constants = require("./utils/constants");
|
|
56
57
|
const _i18n = require("./utils/i18n");
|
|
57
|
-
const _httphelper = require("./utils/http-helper");
|
|
58
58
|
function _interop_require_default(obj) {
|
|
59
59
|
return obj && obj.__esModule ? obj : {
|
|
60
60
|
default: obj
|
package/lib/locales/en.js
CHANGED
|
@@ -11,6 +11,15 @@ Object.defineProperty(exports, "default", {
|
|
|
11
11
|
const _default = {
|
|
12
12
|
addedToGitignore: 'Added {{line}} to .gitignore',
|
|
13
13
|
androidCrunchPngsWarning: 'The crunchPngs option of android seems not disabled (Please ignore this warning if already disabled), which may cause abnormal consumption of mobile network traffic. Please refer to https://cresc.dev/docs/getting-started#disable-crunchpngs-on-android \n',
|
|
14
|
+
aabOpenApksFailed: 'Failed to open generated .apks file',
|
|
15
|
+
aabReadUniversalApkFailed: 'Failed to read universal.apk',
|
|
16
|
+
aabUniversalApkNotFound: 'universal.apk not found in generated .apks',
|
|
17
|
+
aabBundletoolDownloadHint: 'bundletool not found. Downloading node-bundletool via npx (first run may take a while).',
|
|
18
|
+
aabManifestNotFound: "AndroidManifest.xml can't be found in AAB base/manifest/",
|
|
19
|
+
aabParseResourcesWarning: '[Warning] Failed to parse resources.arsc: {{error}}',
|
|
20
|
+
aabParseFailed: 'Failed to parse AAB: {{error}}',
|
|
21
|
+
aabParseManifestError: 'Parse AndroidManifest.xml error: {{error}}',
|
|
22
|
+
aabParseResourcesError: 'Parser resources.arsc error: {{error}}',
|
|
14
23
|
appId: 'App ID',
|
|
15
24
|
appIdMismatchApk: 'App ID mismatch! Current APK: {{appIdInPkg}}, current update.json: {{appId}}',
|
|
16
25
|
appIdMismatchApp: 'App ID mismatch! Current APP: {{appIdInPkg}}, current update.json: {{appId}}',
|
|
@@ -100,10 +109,13 @@ This can reduce the risk of inconsistent dependencies and supply chain attacks.
|
|
|
100
109
|
uploadingSourcemap: 'Uploading sourcemap',
|
|
101
110
|
usageDiff: 'Usage: cresc {{command}} <origin> <next>',
|
|
102
111
|
usageParseApk: 'Usage: cresc parseApk <apk file>',
|
|
112
|
+
usageParseAab: 'Usage: cresc parseAab <aab file>',
|
|
113
|
+
usageExtractApk: 'Usage: cresc extractApk <aab file> [--output <apk file>] [--includeAllSplits] [--splits <split names>]',
|
|
103
114
|
usageParseApp: 'Usage: cresc parseApp <app file>',
|
|
104
115
|
usageParseIpa: 'Usage: cresc parseIpa <ipa file>',
|
|
105
116
|
usageUnderDevelopment: 'Usage is under development now.',
|
|
106
117
|
usageUploadApk: 'Usage: cresc uploadApk <apk file>',
|
|
118
|
+
usageUploadAab: 'Usage: cresc uploadAab <aab file> [--includeAllSplits] [--splits <split names>]',
|
|
107
119
|
usageUploadApp: 'Usage: cresc uploadApp <app file>',
|
|
108
120
|
usageUploadIpa: 'Usage: cresc uploadIpa <ipa file>',
|
|
109
121
|
versionBind: 'Bound hot update {{version}} to native version {{nativeVersion}} (id: {{id}})',
|
|
@@ -119,8 +131,11 @@ This can reduce the risk of inconsistent dependencies and supply chain attacks.
|
|
|
119
131
|
deletePackageSuccess: 'Native package {{packageId}} deleted successfully',
|
|
120
132
|
deletePackageError: 'Failed to delete native package {{packageId}}: {{error}}',
|
|
121
133
|
usageDeletePackage: 'Usage: cresc deletePackage [packageId] --appId [appId]',
|
|
134
|
+
deleteVersionSuccess: 'Version {{versionId}} deleted successfully',
|
|
135
|
+
deleteVersionError: 'Failed to delete version {{versionId}}: {{error}}',
|
|
122
136
|
bundleFileNotFound: 'Bundle file not found! Please use default bundle file name and path.',
|
|
123
137
|
diffPackageGenerated: '{{- output}} generated.',
|
|
124
138
|
nodeBsdiffRequired: 'This function needs "node-bsdiff". Please run "{{scriptName}} install node-bsdiff" to install',
|
|
125
|
-
nodeHdiffpatchRequired: 'This function needs "node-hdiffpatch". Please run "{{scriptName}} install node-hdiffpatch" to install'
|
|
139
|
+
nodeHdiffpatchRequired: 'This function needs "node-hdiffpatch". Please run "{{scriptName}} install node-hdiffpatch" to install',
|
|
140
|
+
apkExtracted: 'APK extracted to {{output}}'
|
|
126
141
|
};
|
package/lib/locales/zh.js
CHANGED
|
@@ -11,6 +11,15 @@ Object.defineProperty(exports, "default", {
|
|
|
11
11
|
const _default = {
|
|
12
12
|
addedToGitignore: '已将 {{line}} 添加到 .gitignore',
|
|
13
13
|
androidCrunchPngsWarning: 'android 的 crunchPngs 选项似乎尚未禁用(如已禁用则请忽略此提示),这可能导致热更包体积异常增大,具体请参考 https://pushy.reactnative.cn/docs/getting-started.html#%E7%A6%81%E7%94%A8-android-%E7%9A%84-crunch-%E4%BC%98%E5%8C%96 \n',
|
|
14
|
+
aabOpenApksFailed: '无法打开生成的 .apks 文件',
|
|
15
|
+
aabReadUniversalApkFailed: '无法读取 universal.apk',
|
|
16
|
+
aabUniversalApkNotFound: '在生成的 .apks 中未找到 universal.apk',
|
|
17
|
+
aabBundletoolDownloadHint: '未找到 bundletool,正在通过 npx 下载 node-bundletool(首次下载可能需要一些时间)。',
|
|
18
|
+
aabManifestNotFound: '在 AAB 的 base/manifest/ 中找不到 AndroidManifest.xml',
|
|
19
|
+
aabParseResourcesWarning: '[警告] 解析 resources.arsc 失败:{{error}}',
|
|
20
|
+
aabParseFailed: '解析 AAB 失败:{{error}}',
|
|
21
|
+
aabParseManifestError: '解析 AndroidManifest.xml 出错:{{error}}',
|
|
22
|
+
aabParseResourcesError: '解析 resources.arsc 出错:{{error}}',
|
|
14
23
|
appId: '应用 id',
|
|
15
24
|
appIdMismatchApk: 'appId不匹配!当前apk: {{appIdInPkg}}, 当前update.json: {{appId}}',
|
|
16
25
|
appIdMismatchApp: 'appId不匹配!当前app: {{appIdInPkg}}, 当前update.json: {{appId}}',
|
|
@@ -100,9 +109,12 @@ const _default = {
|
|
|
100
109
|
uploadingSourcemap: '正在上传 sourcemap',
|
|
101
110
|
usageDiff: '用法:pushy {{command}} <origin> <next>',
|
|
102
111
|
usageParseApk: '使用方法: pushy parseApk apk后缀文件',
|
|
112
|
+
usageParseAab: '使用方法: pushy parseAab aab后缀文件',
|
|
113
|
+
usageExtractApk: '使用方法: pushy extractApk aab后缀文件 [--output apk文件] [--includeAllSplits] [--splits 分包名列表]',
|
|
103
114
|
usageParseApp: '使用方法: pushy parseApp app后缀文件',
|
|
104
115
|
usageParseIpa: '使用方法: pushy parseIpa ipa后缀文件',
|
|
105
116
|
usageUploadApk: '使用方法: pushy uploadApk apk后缀文件',
|
|
117
|
+
usageUploadAab: '使用方法: pushy uploadAab aab后缀文件 [--includeAllSplits] [--splits 分包名列表]',
|
|
106
118
|
usageUploadApp: '使用方法: pushy uploadApp app后缀文件',
|
|
107
119
|
usageUploadIpa: '使用方法: pushy uploadIpa ipa后缀文件',
|
|
108
120
|
versionBind: '已将热更包 {{version}} 绑定到原生版本 {{nativeVersion}} (id: {{id}})',
|
|
@@ -118,8 +130,11 @@ const _default = {
|
|
|
118
130
|
deletePackageSuccess: '原生包 {{packageId}} 删除成功',
|
|
119
131
|
deletePackageError: '删除原生包 {{packageId}} 失败: {{error}}',
|
|
120
132
|
usageDeletePackage: '使用方法: pushy deletePackage [packageId] --appId [appId]',
|
|
133
|
+
deleteVersionSuccess: '热更包 {{versionId}} 删除成功',
|
|
134
|
+
deleteVersionError: '删除热更包 {{versionId}} 失败: {{error}}',
|
|
121
135
|
bundleFileNotFound: '未找到 bundle 文件!请使用默认的 bundle 文件名和路径。',
|
|
122
136
|
diffPackageGenerated: '{{- output}} 已生成。',
|
|
123
137
|
nodeBsdiffRequired: '此功能需要 "node-bsdiff"。请运行 "{{scriptName}} install node-bsdiff" 来安装',
|
|
124
|
-
nodeHdiffpatchRequired: '此功能需要 "node-hdiffpatch"。请运行 "{{scriptName}} install node-hdiffpatch" 来安装'
|
|
138
|
+
nodeHdiffpatchRequired: '此功能需要 "node-hdiffpatch"。请运行 "{{scriptName}} install node-hdiffpatch" 来安装',
|
|
139
|
+
apkExtracted: 'APK 已提取到 {{output}}'
|
|
125
140
|
};
|
package/lib/package.js
CHANGED
|
@@ -19,20 +19,24 @@ _export(exports, {
|
|
|
19
19
|
return packageCommands;
|
|
20
20
|
}
|
|
21
21
|
});
|
|
22
|
+
const _os = /*#__PURE__*/ _interop_require_default(require("os"));
|
|
23
|
+
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
|
|
24
|
+
const _fsextra = /*#__PURE__*/ _interop_require_default(require("fs-extra"));
|
|
25
|
+
const _ttytable = /*#__PURE__*/ _interop_require_default(require("tty-table"));
|
|
22
26
|
const _api = require("./api");
|
|
23
|
-
const _utils = require("./utils");
|
|
24
|
-
const _i18n = require("./utils/i18n");
|
|
25
27
|
const _app = require("./app");
|
|
26
|
-
const
|
|
28
|
+
const _utils = require("./utils");
|
|
29
|
+
const _aab = require("./utils/app-info-parser/aab");
|
|
27
30
|
const _depversions = require("./utils/dep-versions");
|
|
28
31
|
const _git = require("./utils/git");
|
|
32
|
+
const _i18n = require("./utils/i18n");
|
|
29
33
|
function _interop_require_default(obj) {
|
|
30
34
|
return obj && obj.__esModule ? obj : {
|
|
31
35
|
default: obj
|
|
32
36
|
};
|
|
33
37
|
}
|
|
34
38
|
async function listPackage(appId) {
|
|
35
|
-
const allPkgs = await (0, _api.getAllPackages)(appId);
|
|
39
|
+
const allPkgs = await (0, _api.getAllPackages)(appId) || [];
|
|
36
40
|
const header = [
|
|
37
41
|
{
|
|
38
42
|
value: (0, _i18n.t)('nativePackageId')
|
|
@@ -75,7 +79,7 @@ async function choosePackage(appId) {
|
|
|
75
79
|
const list = await listPackage(appId);
|
|
76
80
|
while(true){
|
|
77
81
|
const id = await (0, _utils.question)((0, _i18n.t)('enterNativePackageId'));
|
|
78
|
-
const app = list.find((v)=>v.id.toString() === id);
|
|
82
|
+
const app = list == null ? void 0 : list.find((v)=>v.id.toString() === id);
|
|
79
83
|
if (app) {
|
|
80
84
|
return app;
|
|
81
85
|
}
|
|
@@ -170,6 +174,32 @@ const packageCommands = {
|
|
|
170
174
|
buildTime
|
|
171
175
|
}));
|
|
172
176
|
},
|
|
177
|
+
uploadAab: async ({ args, options })=>{
|
|
178
|
+
const source = args[0];
|
|
179
|
+
if (!source || !source.endsWith('.aab')) {
|
|
180
|
+
throw new Error((0, _i18n.t)('usageUploadAab'));
|
|
181
|
+
}
|
|
182
|
+
const output = _path.default.join(_os.default.tmpdir(), `${_path.default.basename(source, _path.default.extname(source))}-${Date.now()}.apk`);
|
|
183
|
+
const includeAllSplits = options.includeAllSplits === true || options.includeAllSplits === 'true';
|
|
184
|
+
const splits = options.splits ? String(options.splits).split(',').map((item)=>item.trim()).filter(Boolean) : null;
|
|
185
|
+
const parser = new _aab.AabParser(source);
|
|
186
|
+
try {
|
|
187
|
+
await parser.extractApk(output, {
|
|
188
|
+
includeAllSplits,
|
|
189
|
+
splits
|
|
190
|
+
});
|
|
191
|
+
await packageCommands.uploadApk({
|
|
192
|
+
args: [
|
|
193
|
+
output
|
|
194
|
+
],
|
|
195
|
+
options
|
|
196
|
+
});
|
|
197
|
+
} finally{
|
|
198
|
+
if (await _fsextra.default.pathExists(output)) {
|
|
199
|
+
await _fsextra.default.remove(output);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
173
203
|
uploadApp: async ({ args, options })=>{
|
|
174
204
|
const fn = args[0];
|
|
175
205
|
if (!fn || !fn.endsWith('.app')) {
|
|
@@ -235,6 +265,30 @@ const packageCommands = {
|
|
|
235
265
|
}
|
|
236
266
|
console.log(await (0, _utils.getApkInfo)(fn));
|
|
237
267
|
},
|
|
268
|
+
parseAab: async ({ args })=>{
|
|
269
|
+
const fn = args[0];
|
|
270
|
+
if (!fn || !fn.endsWith('.aab')) {
|
|
271
|
+
throw new Error((0, _i18n.t)('usageParseAab'));
|
|
272
|
+
}
|
|
273
|
+
console.log(await (0, _utils.getAabInfo)(fn));
|
|
274
|
+
},
|
|
275
|
+
extractApk: async ({ args, options })=>{
|
|
276
|
+
const source = args[0];
|
|
277
|
+
if (!source || !source.endsWith('.aab')) {
|
|
278
|
+
throw new Error((0, _i18n.t)('usageExtractApk'));
|
|
279
|
+
}
|
|
280
|
+
const output = options.output || _path.default.join(_path.default.dirname(source), `${_path.default.basename(source, _path.default.extname(source))}.apk`);
|
|
281
|
+
const includeAllSplits = options.includeAllSplits === true || options.includeAllSplits === 'true';
|
|
282
|
+
const splits = options.splits ? String(options.splits).split(',').map((item)=>item.trim()).filter(Boolean) : null;
|
|
283
|
+
const parser = new _aab.AabParser(source);
|
|
284
|
+
await parser.extractApk(output, {
|
|
285
|
+
includeAllSplits,
|
|
286
|
+
splits
|
|
287
|
+
});
|
|
288
|
+
console.log((0, _i18n.t)('apkExtracted', {
|
|
289
|
+
output
|
|
290
|
+
}));
|
|
291
|
+
},
|
|
238
292
|
packages: async ({ options })=>{
|
|
239
293
|
const platform = await (0, _app.getPlatform)(options.platform);
|
|
240
294
|
const { appId } = await (0, _app.getSelectedApp)(platform);
|
|
@@ -243,7 +297,7 @@ const packageCommands = {
|
|
|
243
297
|
deletePackage: async ({ args, options })=>{
|
|
244
298
|
let { appId, packageId, packageVersion } = options;
|
|
245
299
|
if (!appId) {
|
|
246
|
-
const platform = await (0, _app.getPlatform)();
|
|
300
|
+
const platform = await (0, _app.getPlatform)(options.platform);
|
|
247
301
|
appId = (await (0, _app.getSelectedApp)(platform)).appId;
|
|
248
302
|
}
|
|
249
303
|
// If no packageId provided as argument, let user choose from list
|
package/lib/provider.js
CHANGED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "AabParser", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return AabParser;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _child_process = require("child_process");
|
|
12
|
+
const _os = /*#__PURE__*/ _interop_require_default(require("os"));
|
|
13
|
+
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
|
|
14
|
+
const _fsextra = /*#__PURE__*/ _interop_require_default(require("fs-extra"));
|
|
15
|
+
const _yauzl = require("yauzl");
|
|
16
|
+
const _i18n = require("../i18n");
|
|
17
|
+
const _resourcefinder = require("./resource-finder");
|
|
18
|
+
const _utils = require("./utils");
|
|
19
|
+
const _manifest = require("./xml-parser/manifest");
|
|
20
|
+
const _zip = require("./zip");
|
|
21
|
+
function _interop_require_default(obj) {
|
|
22
|
+
return obj && obj.__esModule ? obj : {
|
|
23
|
+
default: obj
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
class AabParser extends _zip.Zip {
|
|
27
|
+
async extractApk(outputPath, { includeAllSplits, splits }) {
|
|
28
|
+
const normalizedSplits = Array.isArray(splits) ? splits.map((item)=>item.trim()).filter(Boolean) : [];
|
|
29
|
+
const modules = includeAllSplits ? null : Array.from(new Set([
|
|
30
|
+
'base',
|
|
31
|
+
...normalizedSplits
|
|
32
|
+
]));
|
|
33
|
+
const modulesArgs = modules ? [
|
|
34
|
+
`--modules=${modules.join(',')}`
|
|
35
|
+
] : [];
|
|
36
|
+
const runCommand = (command, args, options = {})=>new Promise((resolve, reject)=>{
|
|
37
|
+
const inheritStdio = options.stdio === 'inherit';
|
|
38
|
+
const child = (0, _child_process.spawn)(command, args, {
|
|
39
|
+
stdio: inheritStdio ? 'inherit' : [
|
|
40
|
+
'ignore',
|
|
41
|
+
'pipe',
|
|
42
|
+
'pipe'
|
|
43
|
+
],
|
|
44
|
+
env: options.env
|
|
45
|
+
});
|
|
46
|
+
let stderr = '';
|
|
47
|
+
if (!inheritStdio) {
|
|
48
|
+
var _child_stderr;
|
|
49
|
+
(_child_stderr = child.stderr) == null ? void 0 : _child_stderr.on('data', (chunk)=>{
|
|
50
|
+
stderr += chunk.toString();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
child.on('error', reject);
|
|
54
|
+
child.on('close', (code)=>{
|
|
55
|
+
if (code === 0) {
|
|
56
|
+
resolve();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
reject(new Error(stderr.trim() || `Command failed: ${command} (code ${code})`));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
// Create a temp file for the .apks output
|
|
63
|
+
const tempDir = _os.default.tmpdir();
|
|
64
|
+
const tempApksPath = _path.default.join(tempDir, `temp-${Date.now()}.apks`);
|
|
65
|
+
const needsNpxDownload = async ()=>{
|
|
66
|
+
try {
|
|
67
|
+
await runCommand('npx', [
|
|
68
|
+
'--no-install',
|
|
69
|
+
'node-bundletool',
|
|
70
|
+
'--version'
|
|
71
|
+
]);
|
|
72
|
+
return false;
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
// 1. Build APKS (universal mode)
|
|
79
|
+
// We assume bundletool is in the path.
|
|
80
|
+
// User might need keystore to sign it properly but for simple extraction we stick to default debug key if possible or unsigned?
|
|
81
|
+
// actually bundletool build-apks signs with debug key by default if no keystore provided.
|
|
82
|
+
try {
|
|
83
|
+
await runCommand('bundletool', [
|
|
84
|
+
'build-apks',
|
|
85
|
+
'--mode=universal',
|
|
86
|
+
`--bundle=${this.file}`,
|
|
87
|
+
`--output=${tempApksPath}`,
|
|
88
|
+
'--overwrite',
|
|
89
|
+
...modulesArgs
|
|
90
|
+
]);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
// Fallback to npx node-bundletool if bundletool is not in PATH
|
|
93
|
+
// We use -y to avoid interactive prompt for installation
|
|
94
|
+
if (await needsNpxDownload()) {
|
|
95
|
+
console.log((0, _i18n.t)('aabBundletoolDownloadHint'));
|
|
96
|
+
}
|
|
97
|
+
await runCommand('npx', [
|
|
98
|
+
'-y',
|
|
99
|
+
'node-bundletool',
|
|
100
|
+
'build-apks',
|
|
101
|
+
'--mode=universal',
|
|
102
|
+
`--bundle=${this.file}`,
|
|
103
|
+
`--output=${tempApksPath}`,
|
|
104
|
+
'--overwrite',
|
|
105
|
+
...modulesArgs
|
|
106
|
+
], {
|
|
107
|
+
stdio: 'inherit',
|
|
108
|
+
env: {
|
|
109
|
+
...process.env,
|
|
110
|
+
npm_config_progress: 'true'
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// 2. Extract universal.apk from the .apks (zip) file
|
|
115
|
+
await new Promise((resolve, reject)=>{
|
|
116
|
+
(0, _yauzl.open)(tempApksPath, {
|
|
117
|
+
lazyEntries: true
|
|
118
|
+
}, (err, zipfile)=>{
|
|
119
|
+
if (err || !zipfile) {
|
|
120
|
+
reject(err || new Error((0, _i18n.t)('aabOpenApksFailed')));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
let found = false;
|
|
124
|
+
zipfile.readEntry();
|
|
125
|
+
zipfile.on('entry', (entry)=>{
|
|
126
|
+
if (entry.fileName === 'universal.apk') {
|
|
127
|
+
found = true;
|
|
128
|
+
zipfile.openReadStream(entry, (err, readStream)=>{
|
|
129
|
+
if (err || !readStream) {
|
|
130
|
+
reject(err || new Error((0, _i18n.t)('aabReadUniversalApkFailed')));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const writeStream = _fsextra.default.createWriteStream(outputPath);
|
|
134
|
+
readStream.pipe(writeStream);
|
|
135
|
+
writeStream.on('close', ()=>{
|
|
136
|
+
zipfile.close();
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
139
|
+
writeStream.on('error', reject);
|
|
140
|
+
});
|
|
141
|
+
} else {
|
|
142
|
+
zipfile.readEntry();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
zipfile.on('end', ()=>{
|
|
146
|
+
if (!found) reject(new Error((0, _i18n.t)('aabUniversalApkNotFound')));
|
|
147
|
+
});
|
|
148
|
+
zipfile.on('error', reject);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
} finally{
|
|
152
|
+
// Cleanup
|
|
153
|
+
if (await _fsextra.default.pathExists(tempApksPath)) {
|
|
154
|
+
await _fsextra.default.remove(tempApksPath);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 解析 AAB 文件信息(类似 APK parser 的 parse 方法)
|
|
160
|
+
* 注意:AAB 中的 AndroidManifest.xml 在 base/manifest/AndroidManifest.xml
|
|
161
|
+
*/ async parse() {
|
|
162
|
+
const manifestPath = 'base/manifest/AndroidManifest.xml';
|
|
163
|
+
const ResourceName = /^base\/resources\.arsc$/;
|
|
164
|
+
try {
|
|
165
|
+
const manifestBuffer = await this.getEntry(new RegExp(`^${escapeRegExp(manifestPath)}$`));
|
|
166
|
+
if (!manifestBuffer) {
|
|
167
|
+
throw new Error((0, _i18n.t)('aabManifestNotFound'));
|
|
168
|
+
}
|
|
169
|
+
let apkInfo = this._parseManifest(manifestBuffer);
|
|
170
|
+
try {
|
|
171
|
+
const resourceBuffer = await this.getEntry(ResourceName);
|
|
172
|
+
if (resourceBuffer) {
|
|
173
|
+
const resourceMap = this._parseResourceMap(resourceBuffer);
|
|
174
|
+
apkInfo = (0, _utils.mapInfoResource)(apkInfo, resourceMap);
|
|
175
|
+
}
|
|
176
|
+
} catch (e) {
|
|
177
|
+
var _e_message;
|
|
178
|
+
console.warn((0, _i18n.t)('aabParseResourcesWarning', {
|
|
179
|
+
error: (_e_message = e == null ? void 0 : e.message) != null ? _e_message : e
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
return apkInfo;
|
|
183
|
+
} catch (error) {
|
|
184
|
+
var _error_message;
|
|
185
|
+
throw new Error((0, _i18n.t)('aabParseFailed', {
|
|
186
|
+
error: (_error_message = error.message) != null ? _error_message : error
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Parse manifest
|
|
192
|
+
* @param {Buffer} buffer // manifest file's buffer
|
|
193
|
+
*/ _parseManifest(buffer) {
|
|
194
|
+
try {
|
|
195
|
+
const parser = new _manifest.ManifestParser(buffer, {
|
|
196
|
+
ignore: [
|
|
197
|
+
'application.activity',
|
|
198
|
+
'application.service',
|
|
199
|
+
'application.receiver',
|
|
200
|
+
'application.provider',
|
|
201
|
+
'permission-group'
|
|
202
|
+
]
|
|
203
|
+
});
|
|
204
|
+
return parser.parse();
|
|
205
|
+
} catch (e) {
|
|
206
|
+
var _e_message;
|
|
207
|
+
throw new Error((0, _i18n.t)('aabParseManifestError', {
|
|
208
|
+
error: (_e_message = e == null ? void 0 : e.message) != null ? _e_message : e
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Parse resourceMap
|
|
214
|
+
* @param {Buffer} buffer // resourceMap file's buffer
|
|
215
|
+
*/ _parseResourceMap(buffer) {
|
|
216
|
+
try {
|
|
217
|
+
return new _resourcefinder.ResourceFinder().processResourceTable(buffer);
|
|
218
|
+
} catch (e) {
|
|
219
|
+
var _e_message;
|
|
220
|
+
throw new Error((0, _i18n.t)('aabParseResourcesError', {
|
|
221
|
+
error: (_e_message = e == null ? void 0 : e.message) != null ? _e_message : e
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
constructor(file){
|
|
226
|
+
super(file);
|
|
227
|
+
this.file = file;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const escapeRegExp = (value)=>value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
@@ -1,34 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "ApkParser", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return ApkParser;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _resourcefinder = require("./resource-finder");
|
|
12
|
+
const _utils = require("./utils");
|
|
13
|
+
const _manifest = require("./xml-parser/manifest");
|
|
14
|
+
const _zip = require("./zip");
|
|
4
15
|
const ManifestName = /^androidmanifest\.xml$/;
|
|
5
16
|
const ResourceName = /^resources\.arsc$/;
|
|
6
|
-
|
|
7
|
-
const ResourceFinder = require('./resource-finder');
|
|
8
|
-
class ApkParser extends Zip {
|
|
17
|
+
class ApkParser extends _zip.Zip {
|
|
9
18
|
parse() {
|
|
10
19
|
return new Promise((resolve, reject)=>{
|
|
11
20
|
this.getEntries([
|
|
12
21
|
ManifestName,
|
|
13
22
|
ResourceName
|
|
14
23
|
]).then((buffers)=>{
|
|
15
|
-
|
|
24
|
+
const manifestBuffer = buffers[ManifestName];
|
|
25
|
+
if (!manifestBuffer) {
|
|
16
26
|
throw new Error("AndroidManifest.xml can't be found.");
|
|
17
27
|
}
|
|
18
|
-
let apkInfo
|
|
28
|
+
let apkInfo;
|
|
19
29
|
let resourceMap;
|
|
30
|
+
apkInfo = this._parseManifest(manifestBuffer);
|
|
20
31
|
if (!buffers[ResourceName]) {
|
|
21
32
|
resolve(apkInfo);
|
|
22
33
|
} else {
|
|
23
|
-
// parse resourceMap
|
|
24
34
|
resourceMap = this._parseResourceMap(buffers[ResourceName]);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// find icon path and parse icon
|
|
28
|
-
const iconPath = findApkIconPath(apkInfo);
|
|
35
|
+
apkInfo = (0, _utils.mapInfoResource)(apkInfo, resourceMap);
|
|
36
|
+
const iconPath = (0, _utils.findApkIconPath)(apkInfo);
|
|
29
37
|
if (iconPath) {
|
|
30
38
|
this.getEntry(iconPath).then((iconBuffer)=>{
|
|
31
|
-
apkInfo.icon = iconBuffer ? getBase64FromBuffer(iconBuffer) : null;
|
|
39
|
+
apkInfo.icon = iconBuffer ? (0, _utils.getBase64FromBuffer)(iconBuffer) : null;
|
|
32
40
|
resolve(apkInfo);
|
|
33
41
|
}).catch((e)=>{
|
|
34
42
|
apkInfo.icon = null;
|
|
@@ -50,7 +58,7 @@ class ApkParser extends Zip {
|
|
|
50
58
|
* @param {Buffer} buffer // manifest file's buffer
|
|
51
59
|
*/ _parseManifest(buffer) {
|
|
52
60
|
try {
|
|
53
|
-
const parser = new
|
|
61
|
+
const parser = new _manifest.ManifestParser(buffer, {
|
|
54
62
|
ignore: [
|
|
55
63
|
'application.activity',
|
|
56
64
|
'application.service',
|
|
@@ -61,7 +69,7 @@ class ApkParser extends Zip {
|
|
|
61
69
|
});
|
|
62
70
|
return parser.parse();
|
|
63
71
|
} catch (e) {
|
|
64
|
-
throw new Error(
|
|
72
|
+
throw new Error(`Parse AndroidManifest.xml error: ${e.message || e}`);
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
75
|
/**
|
|
@@ -69,19 +77,9 @@ class ApkParser extends Zip {
|
|
|
69
77
|
* @param {Buffer} buffer // resourceMap file's buffer
|
|
70
78
|
*/ _parseResourceMap(buffer) {
|
|
71
79
|
try {
|
|
72
|
-
return new ResourceFinder().processResourceTable(buffer);
|
|
80
|
+
return new _resourcefinder.ResourceFinder().processResourceTable(buffer);
|
|
73
81
|
} catch (e) {
|
|
74
|
-
throw new Error(
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* parser for parsing .apk file
|
|
79
|
-
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
|
|
80
|
-
*/ constructor(file){
|
|
81
|
-
super(file);
|
|
82
|
-
if (!(this instanceof ApkParser)) {
|
|
83
|
-
return new ApkParser(file);
|
|
82
|
+
throw new Error(`Parser resources.arsc error: ${e}`);
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
85
|
}
|
|
87
|
-
module.exports = ApkParser;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (!(this instanceof AppParser)) {
|
|
10
|
-
return new AppParser(file);
|
|
11
|
-
}
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "AppParser", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return AppParser;
|
|
12
9
|
}
|
|
10
|
+
});
|
|
11
|
+
const _zip = require("./zip");
|
|
12
|
+
class AppParser extends _zip.Zip {
|
|
13
13
|
}
|
|
14
|
-
module.exports = AppParser;
|