1
0
mirror of https://gitcode.com/github-mirrors/react-native-update-cli.git synced 2025-09-16 09:41:38 +08:00
Code Issues Packages Projects Releases Wiki Activity GitHub Gitee

Merge pull request #6 from bozaigao/master

implement getAppInfo and uploadApp methods
This commit is contained in:
Sunny Luo
2025-01-07 14:19:42 +08:00
committed by GitHub
8 changed files with 3280 additions and 44 deletions

View File

@@ -33,6 +33,8 @@
}, },
"uploadIpa": {}, "uploadIpa": {},
"uploadApk": {}, "uploadApk": {},
"uploadApp": {},
"parseApp": {},
"parseIpa": {}, "parseIpa": {},
"parseApk": {}, "parseApk": {},
"packages": { "packages": {

View File

@@ -184,16 +184,21 @@ async function runReactNativeBundleCommand(
async function copyHarmonyBundle(outputFolder) { async function copyHarmonyBundle(outputFolder) {
const harmonyRawPath = 'harmony/entry/src/main/resources/rawfile'; const harmonyRawPath = 'harmony/entry/src/main/resources/rawfile';
try { try {
await fs.ensureDir(harmonyRawPath);
try {
await fs.access(harmonyRawPath, fs.constants.W_OK);
} catch (error) {
await fs.chmod(harmonyRawPath, 0o755);
}
await fs.remove(path.join(harmonyRawPath, 'update.json'));
await fs.copy('update.json', path.join(harmonyRawPath, 'update.json'));
await fs.ensureDir(outputFolder); await fs.ensureDir(outputFolder);
await fs.copy(harmonyRawPath, outputFolder); await fs.copy(harmonyRawPath, outputFolder);
console.log(
`Successfully copied from ${harmonyRawPath} to ${outputFolder}`,
);
} catch (error) { } catch (error) {
console.error('Error in copyHarmonyBundle:', error); console.error('copyHarmonyBundle 错误:', error);
throw new Error(`复制文件失败: ${error.message}`);
} }
} }
@@ -333,7 +338,7 @@ async function pack(dir, output) {
console.log('ppk热更包已生成并保存到: ' + output); console.log('ppk热更包已生成并保存到: ' + output);
} }
function readEntire(entry, zipFile) { export function readEntire(entry, zipFile) {
const buffers = []; const buffers = [];
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
zipFile.openReadStream(entry, (err, stream) => { zipFile.openReadStream(entry, (err, stream) => {
@@ -608,7 +613,7 @@ async function diffFromPackage(
await writePromise; await writePromise;
} }
async function enumZipEntries(zipFn, callback, nestedPath = '') { export async function enumZipEntries(zipFn, callback, nestedPath = '') {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
openZipFile(zipFn, { lazyEntries: true }, async (err, zipfile) => { openZipFile(zipFn, { lazyEntries: true }, async (err, zipfile) => {
if (err) { if (err) {

View File

@@ -3,7 +3,7 @@ import { question, saveToLocal } from './utils';
import { checkPlatform, getSelectedApp } from './app'; import { checkPlatform, getSelectedApp } from './app';
import { getApkInfo, getIpaInfo } from './utils'; import { getApkInfo, getIpaInfo, getAppInfo } from './utils';
import Table from 'tty-table'; import Table from 'tty-table';
export async function listPackage(appId) { export async function listPackage(appId) {
@@ -122,6 +122,51 @@ export const commands = {
`已成功上传apk原生包id: ${id}, version: ${versionName}, buildTime: ${buildTime}`, `已成功上传apk原生包id: ${id}, version: ${versionName}, buildTime: ${buildTime}`,
); );
}, },
uploadApp: async function ({ args }) {
const fn = args[0];
if (!fn || !fn.endsWith('.app')) {
throw new Error('使用方法: pushy uploadApp app后缀文件');
}
const {
versionName,
buildTime,
appId: appIdInPkg,
appKey: appKeyInPkg,
} = await getAppInfo(fn);
const { appId, appKey } = await getSelectedApp('harmony');
if (appIdInPkg && appIdInPkg != appId) {
throw new Error(
`appId不匹配当前app: ${appIdInPkg}, 当前update.json: ${appId}`,
);
}
if (appKeyInPkg && appKeyInPkg !== appKey) {
throw new Error(
`appKey不匹配当前app: ${appKeyInPkg}, 当前update.json: ${appKey}`,
);
}
const { hash } = await uploadFile(fn);
const { id } = await post(`/app/${appId}/package/create`, {
name: versionName,
hash,
buildTime,
});
saveToLocal(fn, `${appId}/package/${id}.app`);
console.log(
`已成功上传app原生包id: ${id}, version: ${versionName}, buildTime: ${buildTime}`,
);
},
parseApp: async function ({ args }) {
const fn = args[0];
if (!fn || !fn.endsWith('.app')) {
throw new Error('使用方法: pushy parseApp app后缀文件');
}
console.log(await getAppInfo(fn));
},
parseIpa: async function ({ args }) { parseIpa: async function ({ args }) {
const fn = args[0]; const fn = args[0];
if (!fn || !fn.endsWith('.ipa')) { if (!fn || !fn.endsWith('.ipa')) {

View File

@@ -0,0 +1,16 @@
const Zip = require('./zip')
class AppParser extends Zip {
/**
* parser for parsing .apk file
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
*/
constructor (file) {
super(file)
if (!(this instanceof AppParser)) {
return new AppParser(file)
}
}
}
module.exports = AppParser

View File

@@ -1,35 +1,43 @@
const ApkParser = require('./apk') const ApkParser = require('./apk');
const IpaParser = require('./ipa') const IpaParser = require('./ipa');
const supportFileTypes = ['ipa', 'apk'] const AppParser = require('./app');
const supportFileTypes = ['ipa', 'apk', 'app'];
class AppInfoParser { class AppInfoParser {
/** /**
* parser for parsing .ipa or .apk file * parser for parsing .ipa or .apk file
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser * @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
*/ */
constructor (file) { constructor(file) {
if (!file) { if (!file) {
throw new Error('Param miss: file(file\'s path in Node, instance of File or Blob in browser).') throw new Error(
"Param miss: file(file's path in Node, instance of File or Blob in browser).",
);
} }
const splits = (file.name || file).split('.') const splits = (file.name || file).split('.');
const fileType = splits[splits.length - 1].toLowerCase() const fileType = splits[splits.length - 1].toLowerCase();
if (!supportFileTypes.includes(fileType)) { if (!supportFileTypes.includes(fileType)) {
throw new Error('Unsupported file type, only support .ipa or .apk file.') throw new Error(
'Unsupported file type, only support .ipa or .apk or .app file.',
);
} }
this.file = file this.file = file;
switch (fileType) { switch (fileType) {
case 'ipa': case 'ipa':
this.parser = new IpaParser(this.file) this.parser = new IpaParser(this.file);
break break;
case 'apk': case 'apk':
this.parser = new ApkParser(this.file) this.parser = new ApkParser(this.file);
break break;
case 'app':
this.parser = new AppParser(this.file);
break;
} }
} }
parse () { parse() {
return this.parser.parse() return this.parser.parse();
} }
} }
module.exports = AppInfoParser module.exports = AppInfoParser;

View File

@@ -1,20 +1,23 @@
const Unzip = require('isomorphic-unzip') const Unzip = require('isomorphic-unzip');
const { isBrowser, decodeNullUnicode } = require('./utils') const { isBrowser, decodeNullUnicode } = require('./utils');
import { enumZipEntries, readEntire } from '../../bundle';
class Zip { class Zip {
constructor (file) { constructor(file) {
if (isBrowser()) { if (isBrowser()) {
if (!(file instanceof window.Blob || typeof file.size !== 'undefined')) { if (!(file instanceof window.Blob || typeof file.size !== 'undefined')) {
throw new Error('Param error: [file] must be an instance of Blob or File in browser.') throw new Error(
'Param error: [file] must be an instance of Blob or File in browser.',
);
} }
this.file = file this.file = file;
} else { } else {
if (typeof file !== 'string') { if (typeof file !== 'string') {
throw new Error('Param error: [file] must be file path in Node.') throw new Error('Param error: [file] must be file path in Node.');
} }
this.file = require('path').resolve(file) this.file = require('path').resolve(file);
} }
this.unzip = new Unzip(this.file) this.unzip = new Unzip(this.file);
} }
/** /**
@@ -22,27 +25,42 @@ class Zip {
* @param {Array} regexps // regexps for matching files * @param {Array} regexps // regexps for matching files
* @param {String} type // return type, can be buffer or blob, default buffer * @param {String} type // return type, can be buffer or blob, default buffer
*/ */
getEntries (regexps, type = 'buffer') { getEntries(regexps, type = 'buffer') {
regexps = regexps.map(regex => decodeNullUnicode(regex)) regexps = regexps.map((regex) => decodeNullUnicode(regex));
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.unzip.getBuffer(regexps, { type }, (err, buffers) => { this.unzip.getBuffer(regexps, { type }, (err, buffers) => {
err ? reject(err) : resolve(buffers) err ? reject(err) : resolve(buffers);
}) });
}) });
} }
/** /**
* get entry by regex, return an instance of Buffer or Blob * get entry by regex, return an instance of Buffer or Blob
* @param {Regex} regex // regex for matching file * @param {Regex} regex // regex for matching file
* @param {String} type // return type, can be buffer or blob, default buffer * @param {String} type // return type, can be buffer or blob, default buffer
*/ */
getEntry (regex, type = 'buffer') { getEntry(regex, type = 'buffer') {
regex = decodeNullUnicode(regex) regex = decodeNullUnicode(regex);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.unzip.getBuffer([regex], { type }, (err, buffers) => { this.unzip.getBuffer([regex], { type }, (err, buffers) => {
err ? reject(err) : resolve(buffers[regex]) console.log(buffers);
}) err ? reject(err) : resolve(buffers[regex]);
}) });
});
}
async getEntryFromHarmonyApp(regex) {
try {
let originSource;
await enumZipEntries(this.file, (entry, zipFile) => {
if (regex.test(entry.fileName)) {
return readEntire(entry, zipFile).then((v) => (originSource = v));
}
});
return originSource;
} catch (error) {
console.error('Error in getEntryFromHarmonyApp:', error);
}
} }
} }
module.exports = Zip module.exports = Zip;

View File

@@ -87,6 +87,43 @@ export async function getApkInfo(fn) {
return { versionName, buildTime, ...appCredential }; return { versionName, buildTime, ...appCredential };
} }
export async function getAppInfo(fn) {
const appInfoParser = new AppInfoParser(fn);
const bundleFile = await appInfoParser.parser.getEntryFromHarmonyApp(
/rawfile\/bundle.harmony.js/,
);
if (!bundleFile) {
throw new Error(
'找不到bundle文件。请确保此app为release版本且bundle文件名为默认的bundle.harmony.js',
);
}
const updateJsonFile = await appInfoParser.parser.getEntryFromHarmonyApp(
/rawfile\/update.json/,
);
let appCredential = {};
if (updateJsonFile) {
appCredential = JSON.parse(updateJsonFile.toString()).harmony;
}
const metaJsonFile = await appInfoParser.parser.getEntryFromHarmonyApp(
/rawfile\/meta.json/,
);
let metaData = {};
if (metaJsonFile) {
metaData = JSON.parse(metaJsonFile.toString());
}
const { versionName, pushy_build_time } = metaData;
let buildTime = 0;
if (pushy_build_time) {
buildTime = pushy_build_time;
}
if (buildTime == 0) {
throw new Error(
'无法获取此包的编译时间戳。请更新 react-native-update 到最新版本后重新打包上传。',
);
}
return { versionName, buildTime, ...appCredential };
}
export async function getIpaInfo(fn) { export async function getIpaInfo(fn) {
const appInfoParser = new AppInfoParser(fn); const appInfoParser = new AppInfoParser(fn);
const bundleFile = await appInfoParser.parser.getEntry( const bundleFile = await appInfoParser.parser.getEntry(

3105
yarn.lock Normal file

File diff suppressed because it is too large Load Diff