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": {},
"uploadApk": {},
"uploadApp": {},
"parseApp": {},
"parseIpa": {},
"parseApk": {},
"packages": {

View File

@@ -184,16 +184,21 @@ async function runReactNativeBundleCommand(
async function copyHarmonyBundle(outputFolder) {
const harmonyRawPath = 'harmony/entry/src/main/resources/rawfile';
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.copy(harmonyRawPath, outputFolder);
console.log(
`Successfully copied from ${harmonyRawPath} to ${outputFolder}`,
);
} 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);
}
function readEntire(entry, zipFile) {
export function readEntire(entry, zipFile) {
const buffers = [];
return new Promise((resolve, reject) => {
zipFile.openReadStream(entry, (err, stream) => {
@@ -608,7 +613,7 @@ async function diffFromPackage(
await writePromise;
}
async function enumZipEntries(zipFn, callback, nestedPath = '') {
export async function enumZipEntries(zipFn, callback, nestedPath = '') {
return new Promise((resolve, reject) => {
openZipFile(zipFn, { lazyEntries: true }, async (err, zipfile) => {
if (err) {

View File

@@ -3,7 +3,7 @@ import { question, saveToLocal } from './utils';
import { checkPlatform, getSelectedApp } from './app';
import { getApkInfo, getIpaInfo } from './utils';
import { getApkInfo, getIpaInfo, getAppInfo } from './utils';
import Table from 'tty-table';
export async function listPackage(appId) {
@@ -122,6 +122,51 @@ export const commands = {
`已成功上传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 }) {
const fn = args[0];
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,6 +1,7 @@
const ApkParser = require('./apk')
const IpaParser = require('./ipa')
const supportFileTypes = ['ipa', 'apk']
const ApkParser = require('./apk');
const IpaParser = require('./ipa');
const AppParser = require('./app');
const supportFileTypes = ['ipa', 'apk', 'app'];
class AppInfoParser {
/**
@@ -9,27 +10,34 @@ class AppInfoParser {
*/
constructor(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 fileType = splits[splits.length - 1].toLowerCase()
const splits = (file.name || file).split('.');
const fileType = splits[splits.length - 1].toLowerCase();
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) {
case 'ipa':
this.parser = new IpaParser(this.file)
break
this.parser = new IpaParser(this.file);
break;
case 'apk':
this.parser = new ApkParser(this.file)
break
this.parser = new ApkParser(this.file);
break;
case 'app':
this.parser = new AppParser(this.file);
break;
}
}
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 { isBrowser, decodeNullUnicode } = require('./utils')
const Unzip = require('isomorphic-unzip');
const { isBrowser, decodeNullUnicode } = require('./utils');
import { enumZipEntries, readEntire } from '../../bundle';
class Zip {
constructor(file) {
if (isBrowser()) {
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 {
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);
}
/**
@@ -23,12 +26,12 @@ class Zip {
* @param {String} type // return type, can be buffer or blob, default buffer
*/
getEntries(regexps, type = 'buffer') {
regexps = regexps.map(regex => decodeNullUnicode(regex))
regexps = regexps.map((regex) => decodeNullUnicode(regex));
return new Promise((resolve, reject) => {
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
@@ -36,13 +39,28 @@ class Zip {
* @param {String} type // return type, can be buffer or blob, default buffer
*/
getEntry(regex, type = 'buffer') {
regex = decodeNullUnicode(regex)
regex = decodeNullUnicode(regex);
return new Promise((resolve, reject) => {
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 };
}
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) {
const appInfoParser = new AppInfoParser(fn);
const bundleFile = await appInfoParser.parser.getEntry(

3105
yarn.lock Normal file

File diff suppressed because it is too large Load Diff