mirror of
				https://gitcode.com/github-mirrors/react-native-update-cli.git
				synced 2025-10-31 23:03:11 +08:00 
			
		
		
		
	implement getAppInfo and uploadApp methods
This commit is contained in:
		
							
								
								
									
										2
									
								
								cli.json
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								cli.json
									
									
									
									
									
								
							| @@ -33,6 +33,8 @@ | ||||
|     }, | ||||
|     "uploadIpa": {}, | ||||
|     "uploadApk": {}, | ||||
|     "uploadApp": {}, | ||||
|     "parseApp": {}, | ||||
|     "parseIpa": {}, | ||||
|     "parseApk": {}, | ||||
|     "packages": { | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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')) { | ||||
|   | ||||
							
								
								
									
										16
									
								
								src/utils/app-info-parser/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/utils/app-info-parser/app.js
									
									
									
									
									
										Normal 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 | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 HeYanbo
					HeYanbo