Skip to content

Commit 040f723

Browse files
committed
implement getAppInfo and uploadApp methods
1 parent 12bddc4 commit 040f723

File tree

8 files changed

+3280
-44
lines changed

8 files changed

+3280
-44
lines changed

cli.json

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
},
3434
"uploadIpa": {},
3535
"uploadApk": {},
36+
"uploadApp": {},
37+
"parseApp": {},
3638
"parseIpa": {},
3739
"parseApk": {},
3840
"packages": {

src/bundle.js

+13-8
Original file line numberDiff line numberDiff line change
@@ -184,16 +184,21 @@ async function runReactNativeBundleCommand(
184184

185185
async function copyHarmonyBundle(outputFolder) {
186186
const harmonyRawPath = 'harmony/entry/src/main/resources/rawfile';
187-
188187
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+
189197
await fs.ensureDir(outputFolder);
190198
await fs.copy(harmonyRawPath, outputFolder);
191-
192-
console.log(
193-
`Successfully copied from ${harmonyRawPath} to ${outputFolder}`,
194-
);
195199
} catch (error) {
196-
console.error('Error in copyHarmonyBundle:', error);
200+
console.error('copyHarmonyBundle 错误:', error);
201+
throw new Error(`复制文件失败: ${error.message}`);
197202
}
198203
}
199204

@@ -333,7 +338,7 @@ async function pack(dir, output) {
333338
console.log('ppk热更包已生成并保存到: ' + output);
334339
}
335340

336-
function readEntire(entry, zipFile) {
341+
export function readEntire(entry, zipFile) {
337342
const buffers = [];
338343
return new Promise((resolve, reject) => {
339344
zipFile.openReadStream(entry, (err, stream) => {
@@ -608,7 +613,7 @@ async function diffFromPackage(
608613
await writePromise;
609614
}
610615

611-
async function enumZipEntries(zipFn, callback, nestedPath = '') {
616+
export async function enumZipEntries(zipFn, callback, nestedPath = '') {
612617
return new Promise((resolve, reject) => {
613618
openZipFile(zipFn, { lazyEntries: true }, async (err, zipfile) => {
614619
if (err) {

src/package.js

+46-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { question, saveToLocal } from './utils';
33

44
import { checkPlatform, getSelectedApp } from './app';
55

6-
import { getApkInfo, getIpaInfo } from './utils';
6+
import { getApkInfo, getIpaInfo, getAppInfo } from './utils';
77
import Table from 'tty-table';
88

99
export async function listPackage(appId) {
@@ -122,6 +122,51 @@ export const commands = {
122122
`已成功上传apk原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
123123
);
124124
},
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+
},
125170
parseIpa: async function ({ args }) {
126171
const fn = args[0];
127172
if (!fn || !fn.endsWith('.ipa')) {

src/utils/app-info-parser/app.js

+16
Original file line numberDiff line numberDiff line change
@@ -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

src/utils/app-info-parser/index.js

+24-16
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,43 @@
1-
const ApkParser = require('./apk')
2-
const IpaParser = require('./ipa')
3-
const supportFileTypes = ['ipa', 'apk']
1+
const ApkParser = require('./apk');
2+
const IpaParser = require('./ipa');
3+
const AppParser = require('./app');
4+
const supportFileTypes = ['ipa', 'apk', 'app'];
45

56
class AppInfoParser {
67
/**
78
* parser for parsing .ipa or .apk file
89
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
910
*/
10-
constructor (file) {
11+
constructor(file) {
1112
if (!file) {
12-
throw new Error('Param miss: file(file\'s path in Node, instance of File or Blob in browser).')
13+
throw new Error(
14+
"Param miss: file(file's path in Node, instance of File or Blob in browser).",
15+
);
1316
}
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();
1619
if (!supportFileTypes.includes(fileType)) {
17-
throw new Error('Unsupported file type, only support .ipa or .apk file.')
20+
throw new Error(
21+
'Unsupported file type, only support .ipa or .apk or .app file.',
22+
);
1823
}
19-
this.file = file
24+
this.file = file;
2025

2126
switch (fileType) {
2227
case 'ipa':
23-
this.parser = new IpaParser(this.file)
24-
break
28+
this.parser = new IpaParser(this.file);
29+
break;
2530
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;
2836
}
2937
}
30-
parse () {
31-
return this.parser.parse()
38+
parse() {
39+
return this.parser.parse();
3240
}
3341
}
3442

35-
module.exports = AppInfoParser
43+
module.exports = AppInfoParser;

src/utils/app-info-parser/zip.js

+37-19
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,66 @@
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';
34

45
class Zip {
5-
constructor (file) {
6+
constructor(file) {
67
if (isBrowser()) {
78
if (!(file instanceof window.Blob || typeof file.size !== 'undefined')) {
8-
throw new Error('Param error: [file] must be an instance of Blob or File in browser.')
9+
throw new Error(
10+
'Param error: [file] must be an instance of Blob or File in browser.',
11+
);
912
}
10-
this.file = file
13+
this.file = file;
1114
} else {
1215
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.');
1417
}
15-
this.file = require('path').resolve(file)
18+
this.file = require('path').resolve(file);
1619
}
17-
this.unzip = new Unzip(this.file)
20+
this.unzip = new Unzip(this.file);
1821
}
1922

2023
/**
2124
* get entries by regexps, the return format is: { <filename>: <Buffer|Blob> }
2225
* @param {Array} regexps // regexps for matching files
2326
* @param {String} type // return type, can be buffer or blob, default buffer
2427
*/
25-
getEntries (regexps, type = 'buffer') {
26-
regexps = regexps.map(regex => decodeNullUnicode(regex))
28+
getEntries(regexps, type = 'buffer') {
29+
regexps = regexps.map((regex) => decodeNullUnicode(regex));
2730
return new Promise((resolve, reject) => {
2831
this.unzip.getBuffer(regexps, { type }, (err, buffers) => {
29-
err ? reject(err) : resolve(buffers)
30-
})
31-
})
32+
err ? reject(err) : resolve(buffers);
33+
});
34+
});
3235
}
3336
/**
3437
* get entry by regex, return an instance of Buffer or Blob
3538
* @param {Regex} regex // regex for matching file
3639
* @param {String} type // return type, can be buffer or blob, default buffer
3740
*/
38-
getEntry (regex, type = 'buffer') {
39-
regex = decodeNullUnicode(regex)
41+
getEntry(regex, type = 'buffer') {
42+
regex = decodeNullUnicode(regex);
4043
return new Promise((resolve, reject) => {
4144
this.unzip.getBuffer([regex], { type }, (err, buffers) => {
42-
err ? reject(err) : resolve(buffers[regex])
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+
}
4563
}
4664
}
4765

48-
module.exports = Zip
66+
module.exports = Zip;

src/utils/index.js

+37
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,43 @@ export async function getApkInfo(fn) {
8787
return { versionName, buildTime, ...appCredential };
8888
}
8989

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+
90127
export async function getIpaInfo(fn) {
91128
const appInfoParser = new AppInfoParser(fn);
92129
const bundleFile = await appInfoParser.parser.getEntry(

0 commit comments

Comments
 (0)