UNPKG

@oppo-minigame/cli

Version:

Command line interface for rapid OPPO minigame development

1 lines 8.82 kB
const fs=require("fs-extra"),path=require("path"),jszip=require("jszip"),signer=require("../sign/bundle"),{MANIFEST_FILE_NAME:MANIFEST_FILE_NAME,DEST_DIR:DEST_DIR,SIGN_DIR_NAME:SIGN_DIR_NAME,SIGN_DEBUG_DIR:SIGN_DEBUG_DIR,PRIVATE_FILE_NAME:PRIVATE_FILE_NAME,CERTIFICATE_FILE_NAME:CERTIFICATE_FILE_NAME,VSCODE_DIR:VSCODE_DIR,DS_STORE_FILE_NAME:DS_STORE_FILE_NAME,MAIN_PACK_NAME:MAIN_PACK_NAME,SIGN_RELEASE_DIR:SIGN_RELEASE_DIR}=require("../config/config"),{shellExit:shellExit,packSpinner:packSpinner,TPL_PATH:TPL_PATH,traverseDir:traverseDir,execInstallRpkProcess:execInstallRpkProcess,packLog:packLog}=require("../utils"),RPK_COMPRESS_OPTION={type:"nodebuffer",compression:"DEFLATE",compressionOptions:{level:9}},RPK_FILE_EXT=".rpk",MAIN_RPK_FILE=MAIN_PACK_NAME+".rpk",PLUGIN_CACHE_DIR_NAME=".plugincache",RPK_FILE_SIGNED_FLAG=".signed",UNITY_SYMBOL_FILE_NAME="webgl.wasm.symbols.unityweb",PLAYABLE_PACKAGE_SUFFIX=".interactive",PLAYABLE_SIZE_LIMIT_MB=15,PLAYABLE_SIZE_LIMIT=15728640,PLAYABLE_SIZE_WARNING_MB=4,PLAYABLE_SIZE_WARNING=4194304;class PackBase{static DEFAULT_ENTRY_FILE="main.js";static GAME_JS_FILE="game.js";static SUBPACKAGE_ZIP_PATH_STRATEGY={RELATIVE_TO_GAME:0,RELATIVE_TO_PACKAGE:1};distRpkName="";distRpkPath="";isDebugEnv=!1;isPlayablePack=!1;isPlayablePackageOriginally=!1;cwd="";manifest=null;privateKeyBuffer=null;certificateBuffer=null;mainPackageDatas={};subpackageDatas={};failCallback;constructor(){this.cwd=process.cwd()}async start(t){this.isDebugEnv=t.isDebugEnv,this.isPlayablePack=!!t.playable,this.cwd=t.pub_dir||this.cwd,this.manifest=await fs.readJson(path.join(this.cwd,MANIFEST_FILE_NAME)),this.isPlayablePackageOriginally=this.manifest.package.endsWith(".interactive"),this.distRpkName=`${this.manifest.package}${this.isDebugEnv?"":".signed"}${this.isPlayablePack&&!this.isPlayablePackageOriginally?".interactive":""}.rpk`;const a=path.join(this.cwd,DEST_DIR);return this.distRpkPath=path.join(a,this.distRpkName),this.failCallback=t.fail,packLog(`开始构建 [${this.getIdentifierName()}] 小游戏${this.isPlayablePack?"试玩包":""}`),await this.checkProjectStructure(),await this.checkSubpackages(),await this.adjustConfig(),await fs.emptyDir(a),await this.loadPrivateKeyCertificate(),await this.pack(),await this.checkPackResult(),t.autoInstall&&await execInstallRpkProcess(this.distRpkPath,{fail:t.fail}),this.distRpkPath}async checkProjectStructure(){const t="检查工程结构",a=packSpinner(t);let e="";this.manifest||(e=`工程缺少配置文件 [${MANIFEST_FILE_NAME}]`),this.isPlayablePack&&this.manifest.subpackages?e="试玩包不允许包含分包配置":this.checkEntryFiles(this.cwd)||(e=`工程缺少入口文件 [${this.getEntryFiles()}]`),e?await this.failProcess(e,{spinner:a,title:t,msg:e}):a.succeed()}async checkSubpackages(){if(!this.manifest.subpackages)return;const t="检查分包",a=packSpinner(t);let e;for(let i=0;i<this.manifest.subpackages.length;i++){const s=this.manifest.subpackages[i];if(e="",s.name)if("string"!=typeof s.name)e=`subpackages[${i}] "name" 字段必须为字符串`;else if(s.root)if("string"!=typeof s.root)e=`subpackages[${i}] "root" 字段必须为字符串`;else{const t=path.basename(s.root),a=!path.extname(t),i=path.join(this.cwd,s.root);a?(await fs.exists(i)||(e=`分包 [${s.name}] 指定的 "root" 目录不存在`),(await fs.readdir(i)).length||(e=`分包 [${s.name}] 指定的 "root" 目录为空`),await this.checkEntryFiles(i)||(e=`分包 [${s.name}] 指定的 "root" 目录中不存在入口文件 "${this.getEntryFiles()}"`)):(t.endsWith(".js")||(e=`分包 [${s.name}] "root" 文件类型只支持 ".js"`),await fs.exists(i)||(e=`分包 [${s.name}] 指定的 "root" 文件 "${s.root}" 不存在`))}else e=`subpackages[${i}] 缺少 "root" 字段`;else e=`subpackages[${i}] 缺少 "name" 字段`;e&&await this.failProcess(e,{spinner:a,title:t,msg:e})}a.succeed()}async checkEntryFiles(t){const a=this.getEntryFiles();for(const e of a)if(await fs.exists(path.join(t,e)))return!0;return!1}async adjustConfig(){const t=packSpinner("调整配置");this.isPlayablePack&&!this.manifest.package.endsWith(".interactive")&&(this.manifest.package+=".interactive",await fs.writeFile(path.join(this.cwd,MANIFEST_FILE_NAME),JSON.stringify(this.manifest))),t.succeed()}async loadPrivateKeyCertificate(){const t="加载证书和私钥",a=packSpinner(t);if(this.isDebugEnv?(this.privateKeyBuffer=await fs.readFile(path.resolve(TPL_PATH,`../${SIGN_DIR_NAME}/${SIGN_DEBUG_DIR}/${PRIVATE_FILE_NAME}`)),this.certificateBuffer=await fs.readFile(path.resolve(TPL_PATH,`../${SIGN_DIR_NAME}/${SIGN_DEBUG_DIR}/${CERTIFICATE_FILE_NAME}`))):(this.privateKeyBuffer=await fs.readFile(path.join(this.cwd,SIGN_DIR_NAME,SIGN_RELEASE_DIR,PRIVATE_FILE_NAME)),this.certificateBuffer=await fs.readFile(path.join(this.cwd,SIGN_DIR_NAME,SIGN_RELEASE_DIR,CERTIFICATE_FILE_NAME))),!this.privateKeyBuffer||!this.certificateBuffer){const e="证书或私钥文件不存在";this.failProcess(e,{spinner:a,title:t,msg:e})}a.succeed()}async pack(){const t=await this.packSub(),a=await this.packMain();if(t.length){const e=await this.packWhole(),i=await this.zipSign([{name:this.distRpkName,bufferGetter:e},{name:MAIN_RPK_FILE,bufferGetter:a},...t],"总包","构建总包");await fs.writeFile(this.distRpkPath,i)}else{const t=packSpinner("生成整包",this.distRpkName);await fs.writeFile(this.distRpkPath,a),t.succeed()}}async packSub(){const t=[];if(!this.manifest.subpackages)return t;for(const a of this.manifest.subpackages){const e=[],i=path.join(this.cwd,a.root);let s="";path.extname(path.basename(a.root))?(e.push(i),s=path.dirname(i)):(await traverseDir(i,e,[path.join(this.cwd,VSCODE_DIR),path.join(this.cwd,DS_STORE_FILE_NAME)]),s=i);const n=[];for(let t of e){t=t.replaceBackslash();const a=this.getSubpackageZipPathStrategy();let e="";a===PackBase.SUBPACKAGE_ZIP_PATH_STRATEGY.RELATIVE_TO_GAME?e=path.relative(this.cwd,t):a===PackBase.SUBPACKAGE_ZIP_PATH_STRATEGY.RELATIVE_TO_PACKAGE&&(e=path.relative(s,t)),n.push({name:e,bufferGetter:async function(){const a=await fs.readFile(t);return this.subpackageDatas[t]=a,a}})}const c=await this.zipSign(n,`分包 [${a.name}] `,`构建分包 [${a.name}]`);t.push({name:a.name+".rpk",bufferGetter:c})}return t}async packMain(){const t=[];await traverseDir(this.cwd,t,[path.join(this.cwd,UNITY_SYMBOL_FILE_NAME),path.join(this.cwd,".plugincache"),path.join(this.cwd,DEST_DIR),path.join(this.cwd,SIGN_DIR_NAME),path.join(this.cwd,VSCODE_DIR),path.join(this.cwd,DS_STORE_FILE_NAME)]);const a=[];for(let e of t)e=e.replaceBackslash(),Object.keys(this.subpackageDatas).includes(e)||a.push({name:path.relative(this.cwd,e),bufferGetter:async function(){const t=await fs.readFile(e);return this.mainPackageDatas[e]=t,t}});return await this.zipSign(a,"主包","构建主包")}async packWhole(){const t=[];for(const a in this.mainPackageDatas)t.push({name:path.relative(this.cwd,a),bufferGetter:this.mainPackageDatas[a]});for(const a in this.subpackageDatas)t.push({name:path.relative(this.cwd,a),bufferGetter:this.subpackageDatas[a]});return await this.zipSign(t,"整包","构建整包")}async zipSign(t,a,e){const i=packSpinner(e),s=new jszip,n=[];for(let a of t){i.setText(e+": 归档",a.name);const t=a.name.replaceBackslash();let c;c="function"==typeof a.bufferGetter?await a.bufferGetter.apply(this):a.bufferGetter,s.file(t,c),n.push({name:Buffer.from(t),hash:signer.hashFileBuffer(c)})}const c=await s.generateAsync(RPK_COMPRESS_OPTION,t=>{t.currentFile&&i.setText(e+": 压缩",t.currentFile)}),r=signer.signZipBuffer({buffer:c,files:n},this.privateKeyBuffer,this.certificateBuffer);if(r.errMsg){const t=`${a}${r.errMsg}`;await this.failProcess(t,{spinner:i,title:"签名错误",msg:t})}return i.succeed(e+": 完成"),r.signedBuffer}async failProcess(t,a){a.spinner.fail(a.title,a.msg),this.failCallback&&await this.failCallback(t),shellExit()}async checkPackResult(){const t="检查包体",a=packSpinner(t);let e="";if(this.isPlayablePack){const i=await fs.stat(this.distRpkPath);if(i.size>15728640){await this.revertPlayableManifestPackage(),await fs.remove(this.distRpkPath);const e="试玩包体大小超过 15MB 限制";await this.failProcess(e,{spinner:a,title:t,msg:e})}else i.size>4194304&&(e="试玩包体较大,超过 4MB,可能影响用户体验");await this.revertPlayableManifestPackage()}a.succeed(t,e)}async revertPlayableManifestPackage(){this.isPlayablePack&&!this.isPlayablePackageOriginally&&(this.manifest.package=this.manifest.package.substring(0,this.manifest.package.lastIndexOf(".interactive")),await fs.writeFile(path.join(this.cwd,MANIFEST_FILE_NAME),JSON.stringify(this.manifest)))}getIdentifierName(){}getEntryFiles(){return[PackBase.DEFAULT_ENTRY_FILE]}getSubpackageZipPathStrategy(){return PackBase.SUBPACKAGE_ZIP_PATH_STRATEGY.RELATIVE_TO_GAME}}module.exports=PackBase;