react-native-update-cli 2.7.0 → 2.7.2
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 +1 -0
- package/lib/api.js +3 -0
- package/lib/esm/api..mjs +183 -0
- package/lib/esm/app..mjs +130 -0
- package/lib/esm/bundle..mjs +823 -0
- package/lib/esm/exports..mjs +11 -0
- package/lib/esm/index..mjs +122 -0
- package/lib/esm/install..mjs +18 -0
- package/lib/esm/locales/en..mjs +131 -0
- package/lib/esm/locales/zh..mjs +130 -0
- package/lib/esm/module-manager..mjs +109 -0
- package/lib/esm/modules/app-module..mjs +213 -0
- package/lib/esm/modules/bundle-module..mjs +178 -0
- package/lib/esm/modules/index..mjs +17 -0
- package/lib/esm/modules/package-module..mjs +6 -0
- package/lib/esm/modules/user-module..mjs +351 -0
- package/lib/esm/modules/version-module..mjs +6 -0
- package/lib/esm/package..mjs +316 -0
- package/lib/esm/provider..mjs +293 -0
- package/lib/esm/types..mjs +1 -0
- package/lib/esm/user..mjs +36 -0
- package/lib/esm/utils/add-gitignore..mjs +32 -0
- package/lib/esm/utils/app-info-parser/aab..mjs +215 -0
- package/lib/esm/utils/app-info-parser/apk..mjs +75 -0
- package/lib/esm/utils/app-info-parser/app..mjs +3 -0
- package/lib/esm/utils/app-info-parser/index..mjs +44 -0
- package/lib/esm/utils/app-info-parser/ipa..mjs +73 -0
- package/lib/esm/utils/app-info-parser/resource-finder..mjs +401 -0
- package/lib/esm/utils/app-info-parser/utils..mjs +121 -0
- package/lib/esm/utils/app-info-parser/xml-parser/binary..mjs +569 -0
- package/lib/esm/utils/app-info-parser/xml-parser/manifest..mjs +200 -0
- package/lib/esm/utils/app-info-parser/zip..mjs +65 -0
- package/lib/esm/utils/check-lockfile..mjs +78 -0
- package/lib/esm/utils/check-plugin..mjs +25 -0
- package/lib/esm/utils/constants..mjs +19 -0
- package/lib/esm/utils/dep-versions..mjs +33 -0
- package/lib/esm/utils/git..mjs +43 -0
- package/lib/esm/utils/http-helper..mjs +70 -0
- package/lib/esm/utils/i18n..mjs +23 -0
- package/lib/esm/utils/index..mjs +316 -0
- package/lib/esm/utils/latest-version/cli..mjs +294 -0
- package/lib/esm/utils/latest-version/index..mjs +238 -0
- package/lib/esm/utils/plugin-config..mjs +23 -0
- package/lib/esm/versions..mjs +290 -0
- package/lib/package.js +1 -1
- package/package.json +19 -2
- package/src/api.ts +3 -0
- package/src/package.ts +1 -1
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { getSession, loadSession } from "./api";
|
|
2
|
+
import { getPlatform, getSelectedApp } from "./app";
|
|
3
|
+
export class CLIProviderImpl {
|
|
4
|
+
async init() {
|
|
5
|
+
try {
|
|
6
|
+
await loadSession();
|
|
7
|
+
this.session = getSession();
|
|
8
|
+
} catch (error) {}
|
|
9
|
+
}
|
|
10
|
+
async bundle(options) {
|
|
11
|
+
try {
|
|
12
|
+
const context = {
|
|
13
|
+
args: [],
|
|
14
|
+
options: {
|
|
15
|
+
dev: options.dev || false,
|
|
16
|
+
platform: options.platform,
|
|
17
|
+
bundleName: options.bundleName || 'index.bundlejs',
|
|
18
|
+
entryFile: options.entryFile || 'index.js',
|
|
19
|
+
output: options.output || '${tempDir}/output/${platform}.${time}.ppk',
|
|
20
|
+
sourcemap: options.sourcemap || false,
|
|
21
|
+
taro: options.taro || false,
|
|
22
|
+
expo: options.expo || false,
|
|
23
|
+
rncli: options.rncli || false,
|
|
24
|
+
hermes: options.hermes || false
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const { bundleCommands } = await import("./bundle");
|
|
28
|
+
await bundleCommands.bundle(context);
|
|
29
|
+
return {
|
|
30
|
+
success: true,
|
|
31
|
+
data: {
|
|
32
|
+
message: 'Bundle created successfully'
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return {
|
|
37
|
+
success: false,
|
|
38
|
+
error: error instanceof Error ? error.message : 'Unknown error during bundling'
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async publish(options) {
|
|
43
|
+
try {
|
|
44
|
+
const context = {
|
|
45
|
+
args: [],
|
|
46
|
+
options: {
|
|
47
|
+
name: options.name,
|
|
48
|
+
description: options.description,
|
|
49
|
+
metaInfo: options.metaInfo,
|
|
50
|
+
packageId: options.packageId,
|
|
51
|
+
packageVersion: options.packageVersion,
|
|
52
|
+
minPackageVersion: options.minPackageVersion,
|
|
53
|
+
maxPackageVersion: options.maxPackageVersion,
|
|
54
|
+
packageVersionRange: options.packageVersionRange,
|
|
55
|
+
rollout: options.rollout,
|
|
56
|
+
dryRun: options.dryRun || false
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const { versionCommands } = await import("./versions");
|
|
60
|
+
await versionCommands.publish(context);
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
data: {
|
|
64
|
+
message: 'Version published successfully'
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: error instanceof Error ? error.message : 'Unknown error during publishing'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async upload(options) {
|
|
75
|
+
try {
|
|
76
|
+
var _filePath_split_pop;
|
|
77
|
+
const platform = await this.getPlatform(options.platform);
|
|
78
|
+
const { appId } = await this.getSelectedApp(platform);
|
|
79
|
+
const filePath = options.filePath;
|
|
80
|
+
const fileType = (_filePath_split_pop = filePath.split('.').pop()) == null ? void 0 : _filePath_split_pop.toLowerCase();
|
|
81
|
+
const context = {
|
|
82
|
+
args: [
|
|
83
|
+
filePath
|
|
84
|
+
],
|
|
85
|
+
options: {
|
|
86
|
+
platform,
|
|
87
|
+
appId,
|
|
88
|
+
version: options.version
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const { packageCommands } = await import("./package");
|
|
92
|
+
switch(fileType){
|
|
93
|
+
case 'ipa':
|
|
94
|
+
await packageCommands.uploadIpa(context);
|
|
95
|
+
break;
|
|
96
|
+
case 'apk':
|
|
97
|
+
await packageCommands.uploadApk(context);
|
|
98
|
+
break;
|
|
99
|
+
case 'aab':
|
|
100
|
+
await packageCommands.uploadAab(context);
|
|
101
|
+
break;
|
|
102
|
+
case 'app':
|
|
103
|
+
await packageCommands.uploadApp(context);
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
throw new Error(`Unsupported file type: ${fileType}`);
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
data: {
|
|
111
|
+
message: 'File uploaded successfully'
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
error: error instanceof Error ? error.message : 'Unknown error during upload'
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async getSelectedApp(platform) {
|
|
122
|
+
const resolvedPlatform = await this.getPlatform(platform);
|
|
123
|
+
return getSelectedApp(resolvedPlatform);
|
|
124
|
+
}
|
|
125
|
+
async listApps(platform) {
|
|
126
|
+
try {
|
|
127
|
+
const resolvedPlatform = await this.getPlatform(platform);
|
|
128
|
+
const { appCommands } = await import("./app");
|
|
129
|
+
await appCommands.apps({
|
|
130
|
+
options: {
|
|
131
|
+
platform: resolvedPlatform
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
data: {
|
|
137
|
+
message: 'Apps listed successfully'
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
error: error instanceof Error ? error.message : 'Unknown error listing apps'
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async createApp(name, platform) {
|
|
148
|
+
try {
|
|
149
|
+
const { appCommands } = await import("./app");
|
|
150
|
+
await appCommands.createApp({
|
|
151
|
+
options: {
|
|
152
|
+
name,
|
|
153
|
+
platform,
|
|
154
|
+
downloadUrl: ''
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
success: true,
|
|
159
|
+
data: {
|
|
160
|
+
message: 'App created successfully'
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
} catch (error) {
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
error: error instanceof Error ? error.message : 'Unknown error creating app'
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async listVersions(appId) {
|
|
171
|
+
try {
|
|
172
|
+
const context = {
|
|
173
|
+
args: [],
|
|
174
|
+
options: {
|
|
175
|
+
appId
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const { versionCommands } = await import("./versions");
|
|
179
|
+
await versionCommands.versions(context);
|
|
180
|
+
return {
|
|
181
|
+
success: true,
|
|
182
|
+
data: {
|
|
183
|
+
message: 'Versions listed successfully'
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
error: error instanceof Error ? error.message : 'Unknown error listing versions'
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async updateVersion(appId, versionId, updates) {
|
|
194
|
+
try {
|
|
195
|
+
const context = {
|
|
196
|
+
args: [
|
|
197
|
+
versionId
|
|
198
|
+
],
|
|
199
|
+
options: {
|
|
200
|
+
appId,
|
|
201
|
+
...updates
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const { versionCommands } = await import("./versions");
|
|
205
|
+
await versionCommands.update(context);
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
data: {
|
|
209
|
+
message: 'Version updated successfully'
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
} catch (error) {
|
|
213
|
+
return {
|
|
214
|
+
success: false,
|
|
215
|
+
error: error instanceof Error ? error.message : 'Unknown error updating version'
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async getPlatform(platform) {
|
|
220
|
+
return getPlatform(platform);
|
|
221
|
+
}
|
|
222
|
+
async loadSession() {
|
|
223
|
+
await loadSession();
|
|
224
|
+
this.session = getSession();
|
|
225
|
+
if (!this.session) {
|
|
226
|
+
throw new Error('Failed to load session');
|
|
227
|
+
}
|
|
228
|
+
return this.session;
|
|
229
|
+
}
|
|
230
|
+
registerWorkflow(workflow) {
|
|
231
|
+
this.workflows.set(workflow.name, workflow);
|
|
232
|
+
}
|
|
233
|
+
async executeWorkflow(workflowName, context) {
|
|
234
|
+
const workflow = this.workflows.get(workflowName);
|
|
235
|
+
if (!workflow) {
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
error: `Workflow '${workflowName}' not found`
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
let previousResult = null;
|
|
243
|
+
for (const step of workflow.steps){
|
|
244
|
+
if (step.condition && !step.condition(context)) {
|
|
245
|
+
console.log(`Skipping step '${step.name}' due to condition`);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
console.log(`Executing step '${step.name}'`);
|
|
249
|
+
previousResult = await step.execute(context, previousResult);
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
success: true,
|
|
253
|
+
data: {
|
|
254
|
+
message: `Workflow '${workflowName}' completed successfully`,
|
|
255
|
+
result: previousResult
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
} catch (error) {
|
|
259
|
+
return {
|
|
260
|
+
success: false,
|
|
261
|
+
error: error instanceof Error ? error.message : `Workflow '${workflowName}' failed`
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
getRegisteredWorkflows() {
|
|
266
|
+
return Array.from(this.workflows.keys());
|
|
267
|
+
}
|
|
268
|
+
async listPackages(appId) {
|
|
269
|
+
try {
|
|
270
|
+
const context = {
|
|
271
|
+
args: [],
|
|
272
|
+
options: appId ? {
|
|
273
|
+
appId
|
|
274
|
+
} : {}
|
|
275
|
+
};
|
|
276
|
+
const { listPackage } = await import("./package");
|
|
277
|
+
const result = await listPackage(appId || '');
|
|
278
|
+
return {
|
|
279
|
+
success: true,
|
|
280
|
+
data: result
|
|
281
|
+
};
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return {
|
|
284
|
+
success: false,
|
|
285
|
+
error: error instanceof Error ? error.message : 'Unknown error listing packages'
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
constructor(){
|
|
290
|
+
this.workflows = new Map();
|
|
291
|
+
this.init();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import { closeSession, get, post, replaceSession, saveSession } from "./api";
|
|
3
|
+
import { question } from "./utils";
|
|
4
|
+
import { t } from "./utils/i18n";
|
|
5
|
+
function md5(str) {
|
|
6
|
+
return crypto.createHash('md5').update(str).digest('hex');
|
|
7
|
+
}
|
|
8
|
+
export const userCommands = {
|
|
9
|
+
login: async ({ args })=>{
|
|
10
|
+
const email = args[0] || await question('email:');
|
|
11
|
+
const pwd = args[1] || await question('password:', true);
|
|
12
|
+
const { token, info } = await post('/user/login', {
|
|
13
|
+
email,
|
|
14
|
+
pwd: md5(pwd)
|
|
15
|
+
});
|
|
16
|
+
replaceSession({
|
|
17
|
+
token
|
|
18
|
+
});
|
|
19
|
+
await saveSession();
|
|
20
|
+
console.log(t('welcomeMessage', {
|
|
21
|
+
name: info.name
|
|
22
|
+
}));
|
|
23
|
+
},
|
|
24
|
+
logout: async (context)=>{
|
|
25
|
+
await closeSession();
|
|
26
|
+
console.log(t('loggedOut'));
|
|
27
|
+
},
|
|
28
|
+
me: async ()=>{
|
|
29
|
+
const me = await get('/user/me');
|
|
30
|
+
for(const k in me){
|
|
31
|
+
if (k !== 'ok') {
|
|
32
|
+
console.log(`${k}: ${me[k]}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
// import path from 'path';
|
|
3
|
+
import { credentialFile, tempDir } from "./constants";
|
|
4
|
+
import { t } from "./i18n";
|
|
5
|
+
export function addGitIgnore() {
|
|
6
|
+
const shouldIgnore = [
|
|
7
|
+
credentialFile,
|
|
8
|
+
tempDir
|
|
9
|
+
];
|
|
10
|
+
const gitignorePath = '.gitignore';
|
|
11
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
|
|
15
|
+
const gitignoreLines = gitignoreContent.split('\n');
|
|
16
|
+
for (const line of gitignoreLines){
|
|
17
|
+
const index = shouldIgnore.indexOf(line.trim());
|
|
18
|
+
if (index !== -1) {
|
|
19
|
+
shouldIgnore.splice(index, 1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (shouldIgnore.length > 0) {
|
|
23
|
+
gitignoreLines.push('# react-native-update');
|
|
24
|
+
for (const line of shouldIgnore){
|
|
25
|
+
gitignoreLines.push(line);
|
|
26
|
+
console.log(t('addedToGitignore', {
|
|
27
|
+
line
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
fs.writeFileSync(gitignorePath, gitignoreLines.join('\n'));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
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
|
+
export class AabParser extends Zip {
|
|
12
|
+
async extractApk(outputPath, { includeAllSplits, splits }) {
|
|
13
|
+
const normalizedSplits = Array.isArray(splits) ? splits.map((item)=>item.trim()).filter(Boolean) : [];
|
|
14
|
+
const modules = includeAllSplits ? null : Array.from(new Set([
|
|
15
|
+
'base',
|
|
16
|
+
...normalizedSplits
|
|
17
|
+
]));
|
|
18
|
+
const modulesArgs = modules ? [
|
|
19
|
+
`--modules=${modules.join(',')}`
|
|
20
|
+
] : [];
|
|
21
|
+
const runCommand = (command, args, options = {})=>new Promise((resolve, reject)=>{
|
|
22
|
+
const inheritStdio = options.stdio === 'inherit';
|
|
23
|
+
const child = spawn(command, args, {
|
|
24
|
+
stdio: inheritStdio ? 'inherit' : [
|
|
25
|
+
'ignore',
|
|
26
|
+
'pipe',
|
|
27
|
+
'pipe'
|
|
28
|
+
],
|
|
29
|
+
env: options.env
|
|
30
|
+
});
|
|
31
|
+
let stderr = '';
|
|
32
|
+
if (!inheritStdio) {
|
|
33
|
+
var _child_stderr;
|
|
34
|
+
(_child_stderr = child.stderr) == null ? void 0 : _child_stderr.on('data', (chunk)=>{
|
|
35
|
+
stderr += chunk.toString();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
child.on('error', reject);
|
|
39
|
+
child.on('close', (code)=>{
|
|
40
|
+
if (code === 0) {
|
|
41
|
+
resolve();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
reject(new Error(stderr.trim() || `Command failed: ${command} (code ${code})`));
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
// Create a temp file for the .apks output
|
|
48
|
+
const tempDir = os.tmpdir();
|
|
49
|
+
const tempApksPath = path.join(tempDir, `temp-${Date.now()}.apks`);
|
|
50
|
+
const needsNpxDownload = async ()=>{
|
|
51
|
+
try {
|
|
52
|
+
await runCommand('npx', [
|
|
53
|
+
'--no-install',
|
|
54
|
+
'node-bundletool',
|
|
55
|
+
'--version'
|
|
56
|
+
]);
|
|
57
|
+
return false;
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
// 1. Build APKS (universal mode)
|
|
64
|
+
// We assume bundletool is in the path.
|
|
65
|
+
// User might need keystore to sign it properly but for simple extraction we stick to default debug key if possible or unsigned?
|
|
66
|
+
// actually bundletool build-apks signs with debug key by default if no keystore provided.
|
|
67
|
+
try {
|
|
68
|
+
await runCommand('bundletool', [
|
|
69
|
+
'build-apks',
|
|
70
|
+
'--mode=universal',
|
|
71
|
+
`--bundle=${this.file}`,
|
|
72
|
+
`--output=${tempApksPath}`,
|
|
73
|
+
'--overwrite',
|
|
74
|
+
...modulesArgs
|
|
75
|
+
]);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
// Fallback to npx node-bundletool if bundletool is not in PATH
|
|
78
|
+
// We use -y to avoid interactive prompt for installation
|
|
79
|
+
if (await needsNpxDownload()) {
|
|
80
|
+
console.log(t('aabBundletoolDownloadHint'));
|
|
81
|
+
}
|
|
82
|
+
await runCommand('npx', [
|
|
83
|
+
'-y',
|
|
84
|
+
'node-bundletool',
|
|
85
|
+
'build-apks',
|
|
86
|
+
'--mode=universal',
|
|
87
|
+
`--bundle=${this.file}`,
|
|
88
|
+
`--output=${tempApksPath}`,
|
|
89
|
+
'--overwrite',
|
|
90
|
+
...modulesArgs
|
|
91
|
+
], {
|
|
92
|
+
stdio: 'inherit',
|
|
93
|
+
env: {
|
|
94
|
+
...process.env,
|
|
95
|
+
npm_config_progress: 'true'
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// 2. Extract universal.apk from the .apks (zip) file
|
|
100
|
+
await new Promise((resolve, reject)=>{
|
|
101
|
+
openZipFile(tempApksPath, {
|
|
102
|
+
lazyEntries: true
|
|
103
|
+
}, (err, zipfile)=>{
|
|
104
|
+
if (err || !zipfile) {
|
|
105
|
+
reject(err || new Error(t('aabOpenApksFailed')));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
let found = false;
|
|
109
|
+
zipfile.readEntry();
|
|
110
|
+
zipfile.on('entry', (entry)=>{
|
|
111
|
+
if (entry.fileName === 'universal.apk') {
|
|
112
|
+
found = true;
|
|
113
|
+
zipfile.openReadStream(entry, (err, readStream)=>{
|
|
114
|
+
if (err || !readStream) {
|
|
115
|
+
reject(err || new Error(t('aabReadUniversalApkFailed')));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const writeStream = fs.createWriteStream(outputPath);
|
|
119
|
+
readStream.pipe(writeStream);
|
|
120
|
+
writeStream.on('close', ()=>{
|
|
121
|
+
zipfile.close();
|
|
122
|
+
resolve();
|
|
123
|
+
});
|
|
124
|
+
writeStream.on('error', reject);
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
zipfile.readEntry();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
zipfile.on('end', ()=>{
|
|
131
|
+
if (!found) reject(new Error(t('aabUniversalApkNotFound')));
|
|
132
|
+
});
|
|
133
|
+
zipfile.on('error', reject);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
} finally{
|
|
137
|
+
// Cleanup
|
|
138
|
+
if (await fs.pathExists(tempApksPath)) {
|
|
139
|
+
await fs.remove(tempApksPath);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 解析 AAB 文件信息(类似 APK parser 的 parse 方法)
|
|
145
|
+
* 注意:AAB 中的 AndroidManifest.xml 在 base/manifest/AndroidManifest.xml
|
|
146
|
+
*/ async parse() {
|
|
147
|
+
const manifestPath = 'base/manifest/AndroidManifest.xml';
|
|
148
|
+
const ResourceName = /^base\/resources\.arsc$/;
|
|
149
|
+
try {
|
|
150
|
+
const manifestBuffer = await this.getEntry(new RegExp(`^${escapeRegExp(manifestPath)}$`));
|
|
151
|
+
if (!manifestBuffer) {
|
|
152
|
+
throw new Error(t('aabManifestNotFound'));
|
|
153
|
+
}
|
|
154
|
+
let apkInfo = this._parseManifest(manifestBuffer);
|
|
155
|
+
try {
|
|
156
|
+
const resourceBuffer = await this.getEntry(ResourceName);
|
|
157
|
+
if (resourceBuffer) {
|
|
158
|
+
const resourceMap = this._parseResourceMap(resourceBuffer);
|
|
159
|
+
apkInfo = mapInfoResource(apkInfo, resourceMap);
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
var _e_message;
|
|
163
|
+
console.warn(t('aabParseResourcesWarning', {
|
|
164
|
+
error: (_e_message = e == null ? void 0 : e.message) != null ? _e_message : e
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
return apkInfo;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
var _error_message;
|
|
170
|
+
throw new Error(t('aabParseFailed', {
|
|
171
|
+
error: (_error_message = error.message) != null ? _error_message : error
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Parse manifest
|
|
177
|
+
* @param {Buffer} buffer // manifest file's buffer
|
|
178
|
+
*/ _parseManifest(buffer) {
|
|
179
|
+
try {
|
|
180
|
+
const parser = new ManifestParser(buffer, {
|
|
181
|
+
ignore: [
|
|
182
|
+
'application.activity',
|
|
183
|
+
'application.service',
|
|
184
|
+
'application.receiver',
|
|
185
|
+
'application.provider',
|
|
186
|
+
'permission-group'
|
|
187
|
+
]
|
|
188
|
+
});
|
|
189
|
+
return parser.parse();
|
|
190
|
+
} catch (e) {
|
|
191
|
+
var _e_message;
|
|
192
|
+
throw new Error(t('aabParseManifestError', {
|
|
193
|
+
error: (_e_message = e == null ? void 0 : e.message) != null ? _e_message : e
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Parse resourceMap
|
|
199
|
+
* @param {Buffer} buffer // resourceMap file's buffer
|
|
200
|
+
*/ _parseResourceMap(buffer) {
|
|
201
|
+
try {
|
|
202
|
+
return new ResourceFinder().processResourceTable(buffer);
|
|
203
|
+
} catch (e) {
|
|
204
|
+
var _e_message;
|
|
205
|
+
throw new Error(t('aabParseResourcesError', {
|
|
206
|
+
error: (_e_message = e == null ? void 0 : e.message) != null ? _e_message : e
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
constructor(file){
|
|
211
|
+
super(file);
|
|
212
|
+
this.file = file;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const escapeRegExp = (value)=>value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
const ManifestName = /^androidmanifest\.xml$/;
|
|
6
|
+
const ResourceName = /^resources\.arsc$/;
|
|
7
|
+
export class ApkParser extends Zip {
|
|
8
|
+
parse() {
|
|
9
|
+
return new Promise((resolve, reject)=>{
|
|
10
|
+
this.getEntries([
|
|
11
|
+
ManifestName,
|
|
12
|
+
ResourceName
|
|
13
|
+
]).then((buffers)=>{
|
|
14
|
+
const manifestBuffer = buffers[ManifestName];
|
|
15
|
+
if (!manifestBuffer) {
|
|
16
|
+
throw new Error("AndroidManifest.xml can't be found.");
|
|
17
|
+
}
|
|
18
|
+
let apkInfo;
|
|
19
|
+
let resourceMap;
|
|
20
|
+
apkInfo = this._parseManifest(manifestBuffer);
|
|
21
|
+
if (!buffers[ResourceName]) {
|
|
22
|
+
resolve(apkInfo);
|
|
23
|
+
} else {
|
|
24
|
+
resourceMap = this._parseResourceMap(buffers[ResourceName]);
|
|
25
|
+
apkInfo = mapInfoResource(apkInfo, resourceMap);
|
|
26
|
+
const iconPath = findApkIconPath(apkInfo);
|
|
27
|
+
if (iconPath) {
|
|
28
|
+
this.getEntry(iconPath).then((iconBuffer)=>{
|
|
29
|
+
apkInfo.icon = iconBuffer ? getBase64FromBuffer(iconBuffer) : null;
|
|
30
|
+
resolve(apkInfo);
|
|
31
|
+
}).catch((e)=>{
|
|
32
|
+
apkInfo.icon = null;
|
|
33
|
+
resolve(apkInfo);
|
|
34
|
+
console.warn('[Warning] failed to parse icon: ', e);
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
apkInfo.icon = null;
|
|
38
|
+
resolve(apkInfo);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}).catch((e)=>{
|
|
42
|
+
reject(e);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse manifest
|
|
48
|
+
* @param {Buffer} buffer // manifest file's buffer
|
|
49
|
+
*/ _parseManifest(buffer) {
|
|
50
|
+
try {
|
|
51
|
+
const parser = new ManifestParser(buffer, {
|
|
52
|
+
ignore: [
|
|
53
|
+
'application.activity',
|
|
54
|
+
'application.service',
|
|
55
|
+
'application.receiver',
|
|
56
|
+
'application.provider',
|
|
57
|
+
'permission-group'
|
|
58
|
+
]
|
|
59
|
+
});
|
|
60
|
+
return parser.parse();
|
|
61
|
+
} catch (e) {
|
|
62
|
+
throw new Error(`Parse AndroidManifest.xml error: ${e.message || e}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Parse resourceMap
|
|
67
|
+
* @param {Buffer} buffer // resourceMap file's buffer
|
|
68
|
+
*/ _parseResourceMap(buffer) {
|
|
69
|
+
try {
|
|
70
|
+
return new ResourceFinder().processResourceTable(buffer);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
throw new Error(`Parser resources.arsc error: ${e}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|