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/src/package.ts
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import { getPlatform, getSelectedApp } from './app';
|
|
6
|
-
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs-extra';
|
|
7
4
|
import Table from 'tty-table';
|
|
5
|
+
import { doDelete, getAllPackages, post, uploadFile } from './api';
|
|
6
|
+
import { getPlatform, getSelectedApp } from './app';
|
|
8
7
|
import type { Platform } from './types';
|
|
9
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getAabInfo,
|
|
10
|
+
getApkInfo,
|
|
11
|
+
getAppInfo,
|
|
12
|
+
getIpaInfo,
|
|
13
|
+
question,
|
|
14
|
+
saveToLocal,
|
|
15
|
+
} from './utils';
|
|
16
|
+
import { AabParser } from './utils/app-info-parser/aab';
|
|
10
17
|
import { depVersions } from './utils/dep-versions';
|
|
11
18
|
import { getCommitInfo } from './utils/git';
|
|
19
|
+
import { t } from './utils/i18n';
|
|
12
20
|
|
|
13
21
|
export async function listPackage(appId: string) {
|
|
14
|
-
const allPkgs = await getAllPackages(appId);
|
|
22
|
+
const allPkgs = (await getAllPackages(appId)) || [];
|
|
15
23
|
|
|
16
24
|
const header = [
|
|
17
25
|
{ value: t('nativePackageId') },
|
|
@@ -49,7 +57,7 @@ export async function choosePackage(appId: string) {
|
|
|
49
57
|
|
|
50
58
|
while (true) {
|
|
51
59
|
const id = await question(t('enterNativePackageId'));
|
|
52
|
-
const app = list
|
|
60
|
+
const app = list?.find((v) => v.id.toString() === id);
|
|
53
61
|
if (app) {
|
|
54
62
|
return app;
|
|
55
63
|
}
|
|
@@ -143,6 +151,48 @@ export const packageCommands = {
|
|
|
143
151
|
saveToLocal(fn, `${appId}/package/${id}.apk`);
|
|
144
152
|
console.log(t('apkUploadSuccess', { id, version: versionName, buildTime }));
|
|
145
153
|
},
|
|
154
|
+
uploadAab: async ({
|
|
155
|
+
args,
|
|
156
|
+
options,
|
|
157
|
+
}: {
|
|
158
|
+
args: string[];
|
|
159
|
+
options: Record<string, any>;
|
|
160
|
+
}) => {
|
|
161
|
+
const source = args[0];
|
|
162
|
+
if (!source || !source.endsWith('.aab')) {
|
|
163
|
+
throw new Error(t('usageUploadAab'));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const output = path.join(
|
|
167
|
+
os.tmpdir(),
|
|
168
|
+
`${path.basename(source, path.extname(source))}-${Date.now()}.apk`,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const includeAllSplits =
|
|
172
|
+
options.includeAllSplits === true || options.includeAllSplits === 'true';
|
|
173
|
+
const splits = options.splits
|
|
174
|
+
? String(options.splits)
|
|
175
|
+
.split(',')
|
|
176
|
+
.map((item) => item.trim())
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
: null;
|
|
179
|
+
|
|
180
|
+
const parser = new AabParser(source);
|
|
181
|
+
try {
|
|
182
|
+
await parser.extractApk(output, {
|
|
183
|
+
includeAllSplits,
|
|
184
|
+
splits,
|
|
185
|
+
});
|
|
186
|
+
await packageCommands.uploadApk({
|
|
187
|
+
args: [output],
|
|
188
|
+
options,
|
|
189
|
+
});
|
|
190
|
+
} finally {
|
|
191
|
+
if (await fs.pathExists(output)) {
|
|
192
|
+
await fs.remove(output);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
},
|
|
146
196
|
uploadApp: async ({
|
|
147
197
|
args,
|
|
148
198
|
options,
|
|
@@ -207,6 +257,49 @@ export const packageCommands = {
|
|
|
207
257
|
}
|
|
208
258
|
console.log(await getApkInfo(fn));
|
|
209
259
|
},
|
|
260
|
+
parseAab: async ({ args }: { args: string[] }) => {
|
|
261
|
+
const fn = args[0];
|
|
262
|
+
if (!fn || !fn.endsWith('.aab')) {
|
|
263
|
+
throw new Error(t('usageParseAab'));
|
|
264
|
+
}
|
|
265
|
+
console.log(await getAabInfo(fn));
|
|
266
|
+
},
|
|
267
|
+
extractApk: async ({
|
|
268
|
+
args,
|
|
269
|
+
options,
|
|
270
|
+
}: {
|
|
271
|
+
args: string[];
|
|
272
|
+
options: Record<string, any>;
|
|
273
|
+
}) => {
|
|
274
|
+
const source = args[0];
|
|
275
|
+
if (!source || !source.endsWith('.aab')) {
|
|
276
|
+
throw new Error(t('usageExtractApk'));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const output =
|
|
280
|
+
options.output ||
|
|
281
|
+
path.join(
|
|
282
|
+
path.dirname(source),
|
|
283
|
+
`${path.basename(source, path.extname(source))}.apk`,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const includeAllSplits =
|
|
287
|
+
options.includeAllSplits === true || options.includeAllSplits === 'true';
|
|
288
|
+
const splits = options.splits
|
|
289
|
+
? String(options.splits)
|
|
290
|
+
.split(',')
|
|
291
|
+
.map((item) => item.trim())
|
|
292
|
+
.filter(Boolean)
|
|
293
|
+
: null;
|
|
294
|
+
|
|
295
|
+
const parser = new AabParser(source);
|
|
296
|
+
await parser.extractApk(output, {
|
|
297
|
+
includeAllSplits,
|
|
298
|
+
splits,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
console.log(t('apkExtracted', { output }));
|
|
302
|
+
},
|
|
210
303
|
packages: async ({ options }: { options: { platform: Platform } }) => {
|
|
211
304
|
const platform = await getPlatform(options.platform);
|
|
212
305
|
const { appId } = await getSelectedApp(platform);
|
|
@@ -217,12 +310,17 @@ export const packageCommands = {
|
|
|
217
310
|
options,
|
|
218
311
|
}: {
|
|
219
312
|
args: string[];
|
|
220
|
-
options: {
|
|
313
|
+
options: {
|
|
314
|
+
appId?: string;
|
|
315
|
+
packageId?: string;
|
|
316
|
+
packageVersion?: string;
|
|
317
|
+
platform?: Platform;
|
|
318
|
+
};
|
|
221
319
|
}) => {
|
|
222
320
|
let { appId, packageId, packageVersion } = options;
|
|
223
321
|
|
|
224
322
|
if (!appId) {
|
|
225
|
-
const platform = await getPlatform();
|
|
323
|
+
const platform = await getPlatform(options.platform);
|
|
226
324
|
appId = (await getSelectedApp(platform)).appId as string;
|
|
227
325
|
}
|
|
228
326
|
|
|
@@ -232,7 +330,9 @@ export const packageCommands = {
|
|
|
232
330
|
if (!allPkgs) {
|
|
233
331
|
throw new Error(t('noPackagesFound', { appId }));
|
|
234
332
|
}
|
|
235
|
-
const selectedPackage = allPkgs.find(
|
|
333
|
+
const selectedPackage = allPkgs.find(
|
|
334
|
+
(pkg) => pkg.name === packageVersion,
|
|
335
|
+
);
|
|
236
336
|
if (!selectedPackage) {
|
|
237
337
|
throw new Error(t('packageNotFound', { packageVersion }));
|
|
238
338
|
}
|
package/src/provider.ts
CHANGED
|
@@ -122,6 +122,9 @@ export class CLIProviderImpl implements CLIProvider {
|
|
|
122
122
|
case 'apk':
|
|
123
123
|
await packageCommands.uploadApk(context);
|
|
124
124
|
break;
|
|
125
|
+
case 'aab':
|
|
126
|
+
await packageCommands.uploadAab(context);
|
|
127
|
+
break;
|
|
125
128
|
case 'app':
|
|
126
129
|
await packageCommands.uploadApp(context);
|
|
127
130
|
break;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import { open as openZipFile } from 'yauzl';
|
|
6
|
+
import { t } from '../i18n';
|
|
7
|
+
import { ResourceFinder } from './resource-finder';
|
|
8
|
+
import { mapInfoResource } from './utils';
|
|
9
|
+
import { ManifestParser } from './xml-parser/manifest';
|
|
10
|
+
import { Zip } from './zip';
|
|
11
|
+
|
|
12
|
+
export class AabParser extends Zip {
|
|
13
|
+
file: string | File;
|
|
14
|
+
|
|
15
|
+
constructor(file: string | File) {
|
|
16
|
+
super(file);
|
|
17
|
+
this.file = file;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async extractApk(
|
|
21
|
+
outputPath: string,
|
|
22
|
+
{
|
|
23
|
+
includeAllSplits,
|
|
24
|
+
splits,
|
|
25
|
+
}: { includeAllSplits?: boolean; splits?: string[] | null },
|
|
26
|
+
) {
|
|
27
|
+
const normalizedSplits = Array.isArray(splits)
|
|
28
|
+
? splits.map((item) => item.trim()).filter(Boolean)
|
|
29
|
+
: [];
|
|
30
|
+
const modules = includeAllSplits
|
|
31
|
+
? null
|
|
32
|
+
: Array.from(new Set(['base', ...normalizedSplits]));
|
|
33
|
+
const modulesArgs = modules ? [`--modules=${modules.join(',')}`] : [];
|
|
34
|
+
|
|
35
|
+
const runCommand = (
|
|
36
|
+
command: string,
|
|
37
|
+
args: string[],
|
|
38
|
+
options: { stdio?: 'inherit'; env?: NodeJS.ProcessEnv } = {},
|
|
39
|
+
) =>
|
|
40
|
+
new Promise<void>((resolve, reject) => {
|
|
41
|
+
const inheritStdio = options.stdio === 'inherit';
|
|
42
|
+
const child = spawn(command, args, {
|
|
43
|
+
stdio: inheritStdio ? 'inherit' : ['ignore', 'pipe', 'pipe'],
|
|
44
|
+
env: options.env,
|
|
45
|
+
});
|
|
46
|
+
let stderr = '';
|
|
47
|
+
if (!inheritStdio) {
|
|
48
|
+
child.stderr?.on('data', (chunk) => {
|
|
49
|
+
stderr += chunk.toString();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
child.on('error', reject);
|
|
53
|
+
child.on('close', (code) => {
|
|
54
|
+
if (code === 0) {
|
|
55
|
+
resolve();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
reject(
|
|
59
|
+
new Error(
|
|
60
|
+
stderr.trim() || `Command failed: ${command} (code ${code})`,
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Create a temp file for the .apks output
|
|
67
|
+
const tempDir = os.tmpdir();
|
|
68
|
+
const tempApksPath = path.join(tempDir, `temp-${Date.now()}.apks`);
|
|
69
|
+
|
|
70
|
+
const needsNpxDownload = async () => {
|
|
71
|
+
try {
|
|
72
|
+
await runCommand('npx', [
|
|
73
|
+
'--no-install',
|
|
74
|
+
'node-bundletool',
|
|
75
|
+
'--version',
|
|
76
|
+
]);
|
|
77
|
+
return false;
|
|
78
|
+
} catch {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// 1. Build APKS (universal mode)
|
|
85
|
+
// We assume bundletool is in the path.
|
|
86
|
+
// User might need keystore to sign it properly but for simple extraction we stick to default debug key if possible or unsigned?
|
|
87
|
+
// actually bundletool build-apks signs with debug key by default if no keystore provided.
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await runCommand('bundletool', [
|
|
91
|
+
'build-apks',
|
|
92
|
+
'--mode=universal',
|
|
93
|
+
`--bundle=${this.file}`,
|
|
94
|
+
`--output=${tempApksPath}`,
|
|
95
|
+
'--overwrite',
|
|
96
|
+
...modulesArgs,
|
|
97
|
+
]);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
// Fallback to npx node-bundletool if bundletool is not in PATH
|
|
100
|
+
// We use -y to avoid interactive prompt for installation
|
|
101
|
+
if (await needsNpxDownload()) {
|
|
102
|
+
console.log(t('aabBundletoolDownloadHint'));
|
|
103
|
+
}
|
|
104
|
+
await runCommand(
|
|
105
|
+
'npx',
|
|
106
|
+
[
|
|
107
|
+
'-y',
|
|
108
|
+
'node-bundletool',
|
|
109
|
+
'build-apks',
|
|
110
|
+
'--mode=universal',
|
|
111
|
+
`--bundle=${this.file}`,
|
|
112
|
+
`--output=${tempApksPath}`,
|
|
113
|
+
'--overwrite',
|
|
114
|
+
...modulesArgs,
|
|
115
|
+
],
|
|
116
|
+
{
|
|
117
|
+
stdio: 'inherit',
|
|
118
|
+
env: {
|
|
119
|
+
...process.env,
|
|
120
|
+
npm_config_progress: 'true',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 2. Extract universal.apk from the .apks (zip) file
|
|
127
|
+
await new Promise<void>((resolve, reject) => {
|
|
128
|
+
openZipFile(tempApksPath, { lazyEntries: true }, (err, zipfile) => {
|
|
129
|
+
if (err || !zipfile) {
|
|
130
|
+
reject(err || new Error(t('aabOpenApksFailed')));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let found = false;
|
|
135
|
+
zipfile.readEntry();
|
|
136
|
+
zipfile.on('entry', (entry) => {
|
|
137
|
+
if (entry.fileName === 'universal.apk') {
|
|
138
|
+
found = true;
|
|
139
|
+
zipfile.openReadStream(entry, (err, readStream) => {
|
|
140
|
+
if (err || !readStream) {
|
|
141
|
+
reject(err || new Error(t('aabReadUniversalApkFailed')));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const writeStream = fs.createWriteStream(outputPath);
|
|
145
|
+
readStream.pipe(writeStream);
|
|
146
|
+
writeStream.on('close', () => {
|
|
147
|
+
zipfile.close();
|
|
148
|
+
resolve();
|
|
149
|
+
});
|
|
150
|
+
writeStream.on('error', reject);
|
|
151
|
+
});
|
|
152
|
+
} else {
|
|
153
|
+
zipfile.readEntry();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
zipfile.on('end', () => {
|
|
158
|
+
if (!found) reject(new Error(t('aabUniversalApkNotFound')));
|
|
159
|
+
});
|
|
160
|
+
zipfile.on('error', reject);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
} finally {
|
|
164
|
+
// Cleanup
|
|
165
|
+
if (await fs.pathExists(tempApksPath)) {
|
|
166
|
+
await fs.remove(tempApksPath);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 解析 AAB 文件信息(类似 APK parser 的 parse 方法)
|
|
173
|
+
* 注意:AAB 中的 AndroidManifest.xml 在 base/manifest/AndroidManifest.xml
|
|
174
|
+
*/
|
|
175
|
+
async parse() {
|
|
176
|
+
const manifestPath = 'base/manifest/AndroidManifest.xml';
|
|
177
|
+
const ResourceName = /^base\/resources\.arsc$/;
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const manifestBuffer = await this.getEntry(
|
|
181
|
+
new RegExp(`^${escapeRegExp(manifestPath)}$`),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
if (!manifestBuffer) {
|
|
185
|
+
throw new Error(t('aabManifestNotFound'));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let apkInfo = this._parseManifest(manifestBuffer as Buffer);
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const resourceBuffer = await this.getEntry(ResourceName);
|
|
192
|
+
if (resourceBuffer) {
|
|
193
|
+
const resourceMap = this._parseResourceMap(resourceBuffer as Buffer);
|
|
194
|
+
apkInfo = mapInfoResource(apkInfo, resourceMap);
|
|
195
|
+
}
|
|
196
|
+
} catch (e: any) {
|
|
197
|
+
console.warn(t('aabParseResourcesWarning', { error: e?.message ?? e }));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return apkInfo;
|
|
201
|
+
} catch (error: any) {
|
|
202
|
+
throw new Error(t('aabParseFailed', { error: error.message ?? error }));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Parse manifest
|
|
207
|
+
* @param {Buffer} buffer // manifest file's buffer
|
|
208
|
+
*/
|
|
209
|
+
private _parseManifest(buffer: Buffer) {
|
|
210
|
+
try {
|
|
211
|
+
const parser = new ManifestParser(buffer, {
|
|
212
|
+
ignore: [
|
|
213
|
+
'application.activity',
|
|
214
|
+
'application.service',
|
|
215
|
+
'application.receiver',
|
|
216
|
+
'application.provider',
|
|
217
|
+
'permission-group',
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
return parser.parse();
|
|
221
|
+
} catch (e: any) {
|
|
222
|
+
throw new Error(t('aabParseManifestError', { error: e?.message ?? e }));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Parse resourceMap
|
|
228
|
+
* @param {Buffer} buffer // resourceMap file's buffer
|
|
229
|
+
*/
|
|
230
|
+
private _parseResourceMap(buffer: Buffer) {
|
|
231
|
+
try {
|
|
232
|
+
return new ResourceFinder().processResourceTable(buffer);
|
|
233
|
+
} catch (e: any) {
|
|
234
|
+
throw new Error(t('aabParseResourcesError', { error: e?.message ?? e }));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const escapeRegExp = (value: string) =>
|
|
240
|
+
value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
@@ -1,54 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} = require('./utils');
|
|
1
|
+
import { ResourceFinder } from './resource-finder';
|
|
2
|
+
import { findApkIconPath, getBase64FromBuffer, mapInfoResource } from './utils';
|
|
3
|
+
import { ManifestParser } from './xml-parser/manifest';
|
|
4
|
+
import { Zip } from './zip';
|
|
5
|
+
|
|
7
6
|
const ManifestName = /^androidmanifest\.xml$/;
|
|
8
7
|
const ResourceName = /^resources\.arsc$/;
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class ApkParser extends Zip {
|
|
14
|
-
/**
|
|
15
|
-
* parser for parsing .apk file
|
|
16
|
-
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
|
|
17
|
-
*/
|
|
18
|
-
constructor(file) {
|
|
19
|
-
super(file);
|
|
20
|
-
if (!(this instanceof ApkParser)) {
|
|
21
|
-
return new ApkParser(file);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
parse() {
|
|
9
|
+
export class ApkParser extends Zip {
|
|
10
|
+
parse(): Promise<any> {
|
|
25
11
|
return new Promise((resolve, reject) => {
|
|
26
12
|
this.getEntries([ManifestName, ResourceName])
|
|
27
|
-
.then((buffers) => {
|
|
28
|
-
|
|
13
|
+
.then((buffers: any) => {
|
|
14
|
+
const manifestBuffer = buffers[ManifestName];
|
|
15
|
+
if (!manifestBuffer) {
|
|
29
16
|
throw new Error("AndroidManifest.xml can't be found.");
|
|
30
17
|
}
|
|
31
|
-
let apkInfo
|
|
32
|
-
let resourceMap;
|
|
18
|
+
let apkInfo: any;
|
|
19
|
+
let resourceMap: any;
|
|
20
|
+
|
|
21
|
+
apkInfo = this._parseManifest(manifestBuffer as Buffer);
|
|
22
|
+
|
|
33
23
|
if (!buffers[ResourceName]) {
|
|
34
24
|
resolve(apkInfo);
|
|
35
25
|
} else {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
resourceMap = this._parseResourceMap(
|
|
27
|
+
buffers[ResourceName] as Buffer,
|
|
28
|
+
);
|
|
39
29
|
apkInfo = mapInfoResource(apkInfo, resourceMap);
|
|
40
30
|
|
|
41
|
-
// find icon path and parse icon
|
|
42
31
|
const iconPath = findApkIconPath(apkInfo);
|
|
43
32
|
if (iconPath) {
|
|
44
33
|
this.getEntry(iconPath)
|
|
45
|
-
.then((iconBuffer) => {
|
|
34
|
+
.then((iconBuffer: Buffer | null) => {
|
|
46
35
|
apkInfo.icon = iconBuffer
|
|
47
36
|
? getBase64FromBuffer(iconBuffer)
|
|
48
37
|
: null;
|
|
49
38
|
resolve(apkInfo);
|
|
50
39
|
})
|
|
51
|
-
.catch((e) => {
|
|
40
|
+
.catch((e: any) => {
|
|
52
41
|
apkInfo.icon = null;
|
|
53
42
|
resolve(apkInfo);
|
|
54
43
|
console.warn('[Warning] failed to parse icon: ', e);
|
|
@@ -59,18 +48,19 @@ class ApkParser extends Zip {
|
|
|
59
48
|
}
|
|
60
49
|
}
|
|
61
50
|
})
|
|
62
|
-
.catch((e) => {
|
|
51
|
+
.catch((e: any) => {
|
|
63
52
|
reject(e);
|
|
64
53
|
});
|
|
65
54
|
});
|
|
66
55
|
}
|
|
56
|
+
|
|
67
57
|
/**
|
|
68
58
|
* Parse manifest
|
|
69
59
|
* @param {Buffer} buffer // manifest file's buffer
|
|
70
60
|
*/
|
|
71
|
-
_parseManifest(buffer) {
|
|
61
|
+
private _parseManifest(buffer: Buffer) {
|
|
72
62
|
try {
|
|
73
|
-
const parser = new
|
|
63
|
+
const parser = new ManifestParser(buffer, {
|
|
74
64
|
ignore: [
|
|
75
65
|
'application.activity',
|
|
76
66
|
'application.service',
|
|
@@ -80,21 +70,20 @@ class ApkParser extends Zip {
|
|
|
80
70
|
],
|
|
81
71
|
});
|
|
82
72
|
return parser.parse();
|
|
83
|
-
} catch (e) {
|
|
84
|
-
throw new Error(
|
|
73
|
+
} catch (e: any) {
|
|
74
|
+
throw new Error(`Parse AndroidManifest.xml error: ${e.message || e}`);
|
|
85
75
|
}
|
|
86
76
|
}
|
|
77
|
+
|
|
87
78
|
/**
|
|
88
79
|
* Parse resourceMap
|
|
89
80
|
* @param {Buffer} buffer // resourceMap file's buffer
|
|
90
81
|
*/
|
|
91
|
-
_parseResourceMap(buffer) {
|
|
82
|
+
private _parseResourceMap(buffer: Buffer) {
|
|
92
83
|
try {
|
|
93
84
|
return new ResourceFinder().processResourceTable(buffer);
|
|
94
|
-
} catch (e) {
|
|
95
|
-
throw new Error(
|
|
85
|
+
} catch (e: any) {
|
|
86
|
+
throw new Error(`Parser resources.arsc error: ${e}`);
|
|
96
87
|
}
|
|
97
88
|
}
|
|
98
89
|
}
|
|
99
|
-
|
|
100
|
-
module.exports = ApkParser;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { AabParser } from './aab';
|
|
2
|
+
import { ApkParser } from './apk';
|
|
3
|
+
import { AppParser } from './app';
|
|
4
|
+
import { IpaParser } from './ipa';
|
|
5
|
+
const supportFileTypes = ['ipa', 'apk', 'app', 'aab'];
|
|
5
6
|
|
|
6
7
|
class AppInfoParser {
|
|
7
8
|
file: string | File;
|
|
@@ -20,7 +21,7 @@ class AppInfoParser {
|
|
|
20
21
|
const fileType = splits[splits.length - 1].toLowerCase();
|
|
21
22
|
if (!supportFileTypes.includes(fileType)) {
|
|
22
23
|
throw new Error(
|
|
23
|
-
'Unsupported file type, only support .ipa
|
|
24
|
+
'Unsupported file type, only support .ipa, .apk, .app, or .aab file.',
|
|
24
25
|
);
|
|
25
26
|
}
|
|
26
27
|
this.file = file;
|
|
@@ -35,6 +36,9 @@ class AppInfoParser {
|
|
|
35
36
|
case 'app':
|
|
36
37
|
this.parser = new AppParser(this.file);
|
|
37
38
|
break;
|
|
39
|
+
case 'aab':
|
|
40
|
+
this.parser = new AabParser(this.file);
|
|
41
|
+
break;
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
parse() {
|
|
@@ -2,49 +2,37 @@ const parsePlist = require('plist').parse;
|
|
|
2
2
|
const parseBplist = require('bplist-parser').parseBuffer;
|
|
3
3
|
const cgbiToPng = require('cgbi-to-png');
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { findIpaIconPath, getBase64FromBuffer, isBrowser } from './utils';
|
|
6
|
+
import { Zip } from './zip';
|
|
7
7
|
|
|
8
8
|
const PlistName = /payload\/[^\/]+?.app\/info.plist$/i;
|
|
9
9
|
const ProvisionName = /payload\/.+?\.app\/embedded.mobileprovision/;
|
|
10
10
|
|
|
11
|
-
class IpaParser extends Zip {
|
|
12
|
-
|
|
13
|
-
* parser for parsing .ipa file
|
|
14
|
-
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
|
|
15
|
-
*/
|
|
16
|
-
constructor(file) {
|
|
17
|
-
super(file);
|
|
18
|
-
if (!(this instanceof IpaParser)) {
|
|
19
|
-
return new IpaParser(file);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
parse() {
|
|
11
|
+
export class IpaParser extends Zip {
|
|
12
|
+
parse(): Promise<any> {
|
|
23
13
|
return new Promise((resolve, reject) => {
|
|
24
14
|
this.getEntries([PlistName, ProvisionName])
|
|
25
|
-
.then((buffers) => {
|
|
26
|
-
if (!buffers[PlistName]) {
|
|
15
|
+
.then((buffers: any) => {
|
|
16
|
+
if (!buffers[PlistName as any]) {
|
|
27
17
|
throw new Error("Info.plist can't be found.");
|
|
28
18
|
}
|
|
29
|
-
const plistInfo = this._parsePlist(buffers[PlistName]);
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
const plistInfo = this._parsePlist(buffers[PlistName as any]);
|
|
20
|
+
const provisionInfo = this._parseProvision(
|
|
21
|
+
buffers[ProvisionName as any],
|
|
22
|
+
);
|
|
32
23
|
plistInfo.mobileProvision = provisionInfo;
|
|
33
24
|
|
|
34
|
-
// find icon path and parse icon
|
|
35
25
|
const iconRegex = new RegExp(
|
|
36
26
|
findIpaIconPath(plistInfo).toLowerCase(),
|
|
37
27
|
);
|
|
38
28
|
this.getEntry(iconRegex)
|
|
39
|
-
.then((iconBuffer) => {
|
|
29
|
+
.then((iconBuffer: any) => {
|
|
40
30
|
try {
|
|
41
|
-
// In general, the ipa file's icon has been specially processed, should be converted
|
|
42
31
|
plistInfo.icon = iconBuffer
|
|
43
32
|
? getBase64FromBuffer(cgbiToPng.revert(iconBuffer))
|
|
44
33
|
: null;
|
|
45
34
|
} catch (err) {
|
|
46
35
|
if (isBrowser()) {
|
|
47
|
-
// Normal conversion in other cases
|
|
48
36
|
plistInfo.icon = iconBuffer
|
|
49
37
|
? getBase64FromBuffer(
|
|
50
38
|
window.btoa(String.fromCharCode(...iconBuffer)),
|
|
@@ -57,11 +45,11 @@ class IpaParser extends Zip {
|
|
|
57
45
|
}
|
|
58
46
|
resolve(plistInfo);
|
|
59
47
|
})
|
|
60
|
-
.catch((e) => {
|
|
48
|
+
.catch((e: any) => {
|
|
61
49
|
reject(e);
|
|
62
50
|
});
|
|
63
51
|
})
|
|
64
|
-
.catch((e) => {
|
|
52
|
+
.catch((e: any) => {
|
|
65
53
|
reject(e);
|
|
66
54
|
});
|
|
67
55
|
});
|
|
@@ -70,8 +58,8 @@ class IpaParser extends Zip {
|
|
|
70
58
|
* Parse plist
|
|
71
59
|
* @param {Buffer} buffer // plist file's buffer
|
|
72
60
|
*/
|
|
73
|
-
_parsePlist(buffer) {
|
|
74
|
-
let result;
|
|
61
|
+
private _parsePlist(buffer: Buffer) {
|
|
62
|
+
let result: any;
|
|
75
63
|
const bufferType = buffer[0];
|
|
76
64
|
if (bufferType === 60 || bufferType === '<' || bufferType === 239) {
|
|
77
65
|
result = parsePlist(buffer.toString());
|
|
@@ -86,8 +74,8 @@ class IpaParser extends Zip {
|
|
|
86
74
|
* parse provision
|
|
87
75
|
* @param {Buffer} buffer // provision file's buffer
|
|
88
76
|
*/
|
|
89
|
-
_parseProvision(buffer) {
|
|
90
|
-
let info = {};
|
|
77
|
+
private _parseProvision(buffer: Buffer | undefined) {
|
|
78
|
+
let info: Record<string, any> = {};
|
|
91
79
|
if (buffer) {
|
|
92
80
|
let content = buffer.toString('utf-8');
|
|
93
81
|
const firstIndex = content.indexOf('<?xml');
|
|
@@ -100,5 +88,3 @@ class IpaParser extends Zip {
|
|
|
100
88
|
return info;
|
|
101
89
|
}
|
|
102
90
|
}
|
|
103
|
-
|
|
104
|
-
module.exports = IpaParser;
|