@playcraft/devkit 1.0.31-beta.10 → 1.0.31-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/artifacts/emit-fonts.test.js +1 -0
- package/core/artifacts/emit.js +4 -1
- package/core/engines/cocos/cli.js +11 -1
- package/core/engines/cocos/fs.js +1 -1
- package/core/engines/cocos/merge.js +3 -3
- package/core/engines/cocos/pack.js +1 -1
- package/core/engines/cocos/preview-json-hook.js +1 -1
- package/core/engines/cocos/preview-json-pack.js +3 -2
- package/core/engines/cocos/preview-json-pack.test.js +2 -2
- package/core/engines/web/webpack.build.js +3 -31
- package/core/utils/obfuscateLevels.js +10 -0
- package/core/utils/obfuscateLevels.test.js +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const{describe,it}=require("node:test");const assert=require("node:assert/strict");const{insertPlayableFontStyle}=require("./emit.js");const CSS="<style data-playable-fonts>\n@font-face{font-family:'Poppins';}\n</style>";describe("insertPlayableFontStyle",()=>{it("inserts the font style before </head>",()=>{const html="<html><head><title>x</title></head><body></body></html>";const out=insertPlayableFontStyle(html,CSS);assert.match(out,/data-playable-fonts[\s\S]*<\/head>/);assert.equal(out.indexOf("data-playable-fonts")<out.indexOf("</head>"),true)});it("does not insert twice",()=>{const html=`<html><head>${CSS}</head><body></body></html>`;const out=insertPlayableFontStyle(html,CSS);assert.equal(out.split("data-playable-fonts").length-1,1)});it("leaves html unchanged when there is no font css",()=>{const html="<html><head></head><body></body></html>";assert.equal(insertPlayableFontStyle(html,""),html)})});
|
package/core/artifacts/emit.js
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
-
"use strict";const fs=require("fs");const path=require("path");const{buildTemplateString}=require("../utils/buildTemplateString.js");const{getPlaycraftOrientationSuffix}=require("../utils/getPlaycraftOrientationSuffix.js");const{resolveChannelFold2Zip}=require("../utils/resolveChannelFold2Zip.js");const{applyInjections,extractInlineScripts}=require("../injectors/apply");const{getChannelPackPlan,ADIKTEEV_INDEX,BIGABID_INDEX}=require("../injectors/spec");const{zipDirectory}=require("./zip");const{compressHtml}=require("./fflate");const{mraidPartners}=require("../utils/parseArgvOptions");
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");const{buildTemplateString}=require("../utils/buildTemplateString.js");const{getPlaycraftOrientationSuffix}=require("../utils/getPlaycraftOrientationSuffix.js");const{resolveChannelFold2Zip}=require("../utils/resolveChannelFold2Zip.js");const{applyInjections,extractInlineScripts}=require("../injectors/apply");const{getChannelPackPlan,ADIKTEEV_INDEX,BIGABID_INDEX}=require("../injectors/spec");const{zipDirectory}=require("./zip");const{compressHtml}=require("./fflate");const{mraidPartners}=require("../utils/parseArgvOptions");const{getOrGenerateFontCSS}=require("../utils/fontResolver.js");/**
|
|
2
|
+
* Insert the same base64 @font-face block the Vite / webpack font plugins emit.
|
|
3
|
+
* Cocos pack has no bundler plugin, so this runs on the single-file HTML.
|
|
4
|
+
*/function insertPlayableFontStyle(html,css){if(!css)return html;if(/data-playable-fonts/i.test(html))return html;if(/<\/head>/i.test(html))return html.replace(/<\/head>/i,`${css}\n</head>`);return`${css}\n${html}`}function fontCssForTheme(buildOptions,themePayload,themeConfig){const themeName=themePayload&&themePayload.themeName||buildOptions.themeName;if(!themeName)return"";try{return getOrGenerateFontCSS({projectRoot:process.cwd(),themeName,themeConfig,mergedData:themePayload&&themePayload.mergedData,force:true})||""}catch(err){console.warn(`[font] inject skipped: ${err.message}`);return""}}function getFileName(buildOptions){let filename=buildTemplateString(buildOptions.filename,buildOptions);if(buildOptions.network==="mintegral"){return filename.replace(/\s+/g,"").replace(/[^a-zA-Z0-9]/g,"_").replace("_fullhash_6_",Date.now().toString(36).slice(-6))}return filename}function writeStagingFiles(staging,layout,html){if(layout==="adikteev"){const extracted=extractInlineScripts(html);fs.writeFileSync(path.join(staging,"index.html"),ADIKTEEV_INDEX);fs.writeFileSync(path.join(staging,"creative.js"),extracted.scripts.join("\n"));fs.writeFileSync(path.join(staging,"style.css"),"");return}if(layout==="bigabid"){const extracted=extractInlineScripts(html);fs.writeFileSync(path.join(staging,"index.html"),BIGABID_INDEX);fs.writeFileSync(path.join(staging,"main.js"),extracted.scripts.join("\n"));return}fs.writeFileSync(path.join(staging,"index.html"),html)}async function emitChannel({templateHtml,buildOptions,themePayload,themeConfig,outDir}){if(mraidPartners.includes(buildOptions.network)&&(buildOptions.protocol==="none"||!buildOptions.protocol)){buildOptions={...buildOptions,protocol:"mraid"}}const adNetwork=buildOptions.network;const orientationValue=buildOptions.playcraftOrientationValue||"both";const orientationSuffix=getPlaycraftOrientationSuffix(orientationValue);const outputId=`${adNetwork}${orientationSuffix}`;const plan=getChannelPackPlan(buildOptions,themePayload);const{isChannelFold2Zip,isZipOutputFinal}=resolveChannelFold2Zip({isChannelFold2ZipOption:buildOptions.isChannelFold2Zip,adNetwork,isZipOutput:plan.isZip});let html=insertPlayableFontStyle(templateHtml,fontCssForTheme(buildOptions,themePayload,themeConfig));html=applyInjections(html,plan.spec);if(buildOptions.fflateCompression){html=await compressHtml(html,{googlePlayUrl:buildOptions.googlePlayUrl,appStoreUrl:buildOptions.appStoreUrl})}fs.mkdirSync(outDir,{recursive:true});const fileName=getFileName(buildOptions);if(!isZipOutputFinal){const htmlName=`${fileName}.html`;fs.writeFileSync(path.join(outDir,htmlName),html);console.log(`[cocos] wrote ${htmlName}`);return path.join(outDir,htmlName)}const staging=path.join(outDir,outputId);if(fs.existsSync(staging))fs.rmSync(staging,{recursive:true,force:true});fs.mkdirSync(staging,{recursive:true});writeStagingFiles(staging,plan.layout,html);for(const extra of plan.extraFiles){if(fs.existsSync(extra.from)){fs.copyFileSync(extra.from,path.join(staging,extra.to))}else{console.warn(`[cocos] missing extra file: ${extra.from}`)}}const zipName=isChannelFold2Zip?outputId:fileName;const zipPath=path.join(outDir,`${zipName}.zip`);await zipDirectory(staging,zipPath);fs.rmSync(staging,{recursive:true,force:true});console.log(`[cocos] wrote ${path.basename(zipPath)}`);return zipPath}module.exports={emitChannel,getFileName,insertPlayableFontStyle,fontCssForTheme};
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
"use strict";const{spawn,spawnSync}=require("child_process");function resolveCocosBin(env=process.env){return env.COCOS_CLI||"cocos"}function needsXvfb(){return process.platform==="linux"}
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");const{spawn,spawnSync}=require("child_process");function resolveCocosBin(env=process.env){return env.COCOS_CLI||"cocos"}function needsXvfb(){return process.platform==="linux"}/**
|
|
2
|
+
* Windows 上 `cocos` 是 npm 生成的 cmd shim(cocos / cocos.cmd / cocos.ps1),不是 exe:
|
|
3
|
+
* spawn('cocos') → CreateProcess 只补 .exe,找不到 → ENOENT
|
|
4
|
+
* spawn('cocos.cmd') → Node 拒绝无 shell 的 .cmd/.bat → EINVAL
|
|
5
|
+
* 套 cmd.exe 能跑,但 kill 只会杀掉 cmd.exe,cocos 进程会变成孤儿占着端口,
|
|
6
|
+
* 所以这里把 shim 里那条 `node <cli.js>` 解出来,用当前 node 直接跑。
|
|
7
|
+
*/function findWindowsShim(bin){if(/[\\/]/.test(bin))return fs.existsSync(bin)?bin:"";const result=spawnSync("where",[bin],{encoding:"utf8",windowsHide:true});if(result.error||result.status!==0)return"";const found=String(result.stdout||"").split(/\r?\n/).map(line=>line.trim()).filter(Boolean);return found.find(file=>/\.cmd$/i.test(file))||found.find(file=>/\.exe$/i.test(file))||found[0]||""}/** npm 的 cmd shim 结尾形如: ... "%_prog%" "%dp0%\node_modules\cocos-cli\dist\cli.js" %* */function readShimScript(shimPath){try{const source=fs.readFileSync(shimPath,"utf8");const match=source.match(/%dp0%[\\/]([^"%\r\n]+\.(?:[cm]?js))/i)||source.match(/"([^"\r\n]+\.(?:[cm]?js))"/i);if(!match)return"";const script=path.resolve(path.dirname(shimPath),match[1]);return fs.existsSync(script)?script:""}catch(_){return""}}/**
|
|
8
|
+
* cocos 命令实际落到的文件路径:Windows 上会穿透 npm 的 .cmd shim 拿到 cli.js。
|
|
9
|
+
* 除了 spawn,preview-json-pack 也要靠它往上找 engine/library。
|
|
10
|
+
*/function resolveCocosEntryPoint(env=process.env){const bin=resolveCocosBin(env);const isWin=process.platform==="win32";if(path.isAbsolute(bin)){if(!isWin||/\.(?:com|exe|[cm]?js)$/i.test(bin))return bin;return readShimScript(bin)||bin}if(isWin){if(/\.(?:[cm]?js)$/i.test(bin)&&fs.existsSync(bin))return bin;const shim=findWindowsShim(bin);if(!shim)return"";if(/\.exe$/i.test(shim))return shim;return readShimScript(shim)||shim}try{const{execSync}=require("child_process");const found=execSync(`command -v ${JSON.stringify(bin)}`,{encoding:"utf8"}).trim();return found.split(/\r?\n/)[0]||""}catch(_){return""}}function windowsCocosCommand(bin,args,env){if(/\.(?:com|exe)$/i.test(bin))return{cmd:bin,args};const entry=resolveCocosEntryPoint(env);if(entry&&/\.(?:[cm]?js)$/i.test(entry))return{cmd:process.execPath,args:[entry,...args]};if(entry&&/\.exe$/i.test(entry))return{cmd:entry,args};// shim 解不出来时退回 shell,交给 cmd.exe 按 PATHEXT 找(kill 语义会差一些)
|
|
11
|
+
return{cmd:entry||bin,args,shell:true}}function cocosCommand(args,env=process.env){const bin=resolveCocosBin(env);if(needsXvfb()){return{cmd:"xvfb-run",args:["-a",bin,...args]}}if(process.platform==="win32"){return windowsCocosCommand(bin,args,env)}return{cmd:bin,args}}function spawnCocos(args,options={}){const env=options.env||process.env;const{cmd,args:fullArgs,shell}=cocosCommand(args,env);return spawn(cmd,fullArgs,shell?{...options,shell}:options)}function spawnCocosSync(args,options={}){const env=options.env||process.env;const{cmd,args:fullArgs,shell}=cocosCommand(args,env);return spawnSync(cmd,fullArgs,shell?{...options,shell}:options)}module.exports={resolveCocosBin,resolveCocosEntryPoint,needsXvfb,cocosCommand,spawnCocos,spawnCocosSync};
|
package/core/engines/cocos/fs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const fs=require("fs");const path=require("path");const{lookup}=require("mime-types");const{ADAPTER_FETCH,TO_STRING_EXTNAME,TO_SKIP_EXTNAME}=require("./constants");function getRealPath(pathStr){return pathStr.indexOf("\\")!==-1?pathStr.replace(/\\/g,"/"):pathStr}function getAllFilesFromDir(dirPath){let files=[];if(!fs.existsSync(dirPath))return files;for(const fileName of fs.readdirSync(dirPath)){const filePath=path.join(dirPath,fileName);const stat=fs.statSync(filePath);if(stat.isDirectory())files=files.concat(getAllFilesFromDir(filePath));else files.push(filePath)}return files}function sniffImageMime(buf){if(!buf||buf.length<12){if(buf&&buf.length>=8&&buf[0]===137&&buf[1]===80&&buf[2]===78&&buf[3]===71){return"image/png"}return""}if(buf[0]===137&&buf[1]===80&&buf[2]===78&&buf[3]===71)return"image/png";if(buf[0]===255&&buf[1]===216&&buf[2]===255)return"image/jpeg";if(buf[0]===82&&buf[1]===73&&buf[2]===70&&buf[3]===70&&buf[8]===87&&buf[9]===69&&buf[10]===66&&buf[11]===80){return"image/webp"}return""}function sniffAudioMime(buf){if(!buf||buf.length<4)return"";if(buf[0]===73&&buf[1]===68&&buf[2]===51)return"audio/mpeg";if(buf[0]===255&&(buf[1]&224)===224)return"audio/mpeg";if(buf[0]===79&&buf[1]===103&&buf[2]===103&&buf[3]===83)return"audio/ogg";if(buf[0]===82&&buf[1]===73&&buf[2]===70&&buf[3]===70){if(buf.length>=12&&buf[8]===87&&buf[9]===69&&buf[10]===66&&buf[11]===80){return""}return"audio/wav"}if(buf.length>=8&&buf[4]===102&&buf[5]===116&&buf[6]===121&&buf[7]===112)return"audio/mp4";return""}function getBase64FromFile(filePath){const buf=fs.readFileSync(filePath);const lookedUp=lookup(filePath)||"";const imageMime=lookedUp.startsWith("image/")?lookedUp:sniffImageMime(buf);const mime=imageMime||(lookedUp.startsWith("audio/")?lookedUp:sniffAudioMime(buf)||lookedUp)||"application/octet-stream";return`data:${mime};base64,${buf.toString("base64")}`}function getTargetResData(filePath){const fileExtname=path.extname(filePath);if(!fileExtname)return"";if(TO_STRING_EXTNAME.includes(fileExtname)){return fs.readFileSync(filePath,"utf-8")}return getBase64FromFile(filePath)}function removeXMLHttpRequest(codeStr){return codeStr.replace(/XMLHttpRequest/g,ADAPTER_FETCH)}async function getResourceMapper({dirPath,rmHttp=false,mountCbFn,skipFiles=[]}){const resMapper={};const readPkgPath=getRealPath(`${dirPath}/`);for(const filePath of getAllFilesFromDir(dirPath)){const fileExtname=path.extname(filePath);if(TO_SKIP_EXTNAME.includes(fileExtname))continue;if(skipFiles.includes(filePath))continue;const objKey=getRealPath(filePath).replace(readPkgPath,"");let data=getTargetResData(filePath);if(mountCbFn)data=mountCbFn(objKey,data);if(rmHttp&&fileExtname===".js")data=removeXMLHttpRequest(data);resMapper[objKey]=data}return{resMapper}}module.exports={getRealPath,getAllFilesFromDir,getBase64FromFile,getTargetResData,getResourceMapper,removeXMLHttpRequest,sniffAudioMime};
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");const{lookup}=require("mime-types");const{ADAPTER_FETCH,TO_STRING_EXTNAME,TO_SKIP_EXTNAME}=require("./constants");function getRealPath(pathStr){return pathStr.indexOf("\\")!==-1?pathStr.replace(/\\/g,"/"):pathStr}function getAllFilesFromDir(dirPath){let files=[];if(!fs.existsSync(dirPath))return files;for(const fileName of fs.readdirSync(dirPath)){const filePath=path.join(dirPath,fileName);const stat=fs.statSync(filePath);if(stat.isDirectory())files=files.concat(getAllFilesFromDir(filePath));else files.push(filePath)}return files}function sniffImageMime(buf){if(!buf||buf.length<12){if(buf&&buf.length>=8&&buf[0]===137&&buf[1]===80&&buf[2]===78&&buf[3]===71){return"image/png"}return""}if(buf[0]===137&&buf[1]===80&&buf[2]===78&&buf[3]===71)return"image/png";if(buf[0]===255&&buf[1]===216&&buf[2]===255)return"image/jpeg";if(buf[0]===82&&buf[1]===73&&buf[2]===70&&buf[3]===70&&buf[8]===87&&buf[9]===69&&buf[10]===66&&buf[11]===80){return"image/webp"}return""}function sniffAudioMime(buf){if(!buf||buf.length<4)return"";if(buf[0]===73&&buf[1]===68&&buf[2]===51)return"audio/mpeg";if(buf[0]===255&&(buf[1]&224)===224)return"audio/mpeg";if(buf[0]===79&&buf[1]===103&&buf[2]===103&&buf[3]===83)return"audio/ogg";if(buf[0]===82&&buf[1]===73&&buf[2]===70&&buf[3]===70){if(buf.length>=12&&buf[8]===87&&buf[9]===69&&buf[10]===66&&buf[11]===80){return""}return"audio/wav"}if(buf.length>=8&&buf[4]===102&&buf[5]===116&&buf[6]===121&&buf[7]===112)return"audio/mp4";return""}function getBase64FromFile(filePath){const buf=fs.readFileSync(filePath);const lookedUp=lookup(filePath)||"";const imageMime=lookedUp.startsWith("image/")?lookedUp:sniffImageMime(buf);const mime=imageMime||(lookedUp.startsWith("audio/")?lookedUp:sniffAudioMime(buf)||lookedUp)||"application/octet-stream";return`data:${mime};base64,${buf.toString("base64")}`}function getTargetResData(filePath){const fileExtname=path.extname(filePath);if(!fileExtname)return"";if(TO_STRING_EXTNAME.includes(fileExtname)){return fs.readFileSync(filePath,"utf-8")}return getBase64FromFile(filePath)}function removeXMLHttpRequest(codeStr){return codeStr.replace(/XMLHttpRequest/g,ADAPTER_FETCH)}async function getResourceMapper({dirPath,rmHttp=false,mountCbFn,skipFiles=[]}){const resMapper={};const readPkgPath=getRealPath(`${dirPath}/`);for(const filePath of getAllFilesFromDir(dirPath)){const fileExtname=path.extname(filePath);if(TO_SKIP_EXTNAME.includes(fileExtname))continue;if(skipFiles.includes(filePath))continue;const objKey=getRealPath(filePath).replace(readPkgPath,"");let data=getTargetResData(filePath);if(mountCbFn)data=await mountCbFn(objKey,data);if(rmHttp&&fileExtname===".js")data=removeXMLHttpRequest(data);resMapper[objKey]=data}return{resMapper}}module.exports={getRealPath,getAllFilesFromDir,getBase64FromFile,getTargetResData,getResourceMapper,removeXMLHttpRequest,sniffAudioMime};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";const fs=require("fs");const path=require("path");const{load}=require("cheerio");const{deflate}=require("pako");const{getResourceMapper,getBase64FromFile}=require("./fs");const{TRANSPARENT_GIF}=require("./constants");function readInject(rel){return fs.readFileSync(path.join(__dirname,rel),"utf8")}function paddingStyleTags($,originPkgPath,enableSplash){$("link[type=\"text/css\"]").toArray().forEach(item=>{const href=$(item).attr("href");if(!href)return;const cssPath=path.join(originPkgPath,href);if(!fs.existsSync(cssPath))return;const cssStr=fs.readFileSync(cssPath,"utf-8");$(`<style>${cssStr}</style>`).appendTo("head")});if(!enableSplash){$("<style>#splash,#splash .progress-bar{display:none!important;opacity:0;visibility:hidden}</style>").appendTo("head")}$("link[type=\"text/css\"]").remove();$("head").find("style").each((_index,elem)=>{const cssUrlReg=/url\("?'?.*"?'?\)/g;let styleTagStr=$(elem).html()||"";const matchStrList=styleTagStr.match(cssUrlReg);if(!matchStrList)return;matchStrList.forEach(str=>{const imgUrl=str.replace(/"|'|url|\(|\)/g,"");const imgPath=path.join(originPkgPath,imgUrl);const imgBase64=enableSplash&&fs.existsSync(imgPath)?getBase64FromFile(imgPath):TRANSPARENT_GIF;styleTagStr=styleTagStr.replace(str,`url(${imgBase64})`)});$(elem).html(styleTagStr)})}function paddingScriptTags($,originPkgPath){let scriptTags="";$("script[type=\"systemjs-importmap\"]").toArray().forEach(item=>{const href=$(item).attr("src");if(!href)return;const srcPath=path.join(originPkgPath,href);if(!fs.existsSync(srcPath))return;scriptTags+=`<script type="systemjs-importmap">${fs.readFileSync(srcPath,"utf-8")}</script>`});$("head link").remove();$("body script").remove();if(scriptTags)$(scriptTags).appendTo("body")}function getJsListFromSettingsJson(data){const jsonData=JSON.parse(data);jsonData.plugins={jsList:[],...jsonData.plugins};const jsList=[...jsonData.plugins.jsList];jsonData.plugins.jsList=[];return{jsList,settingsData:jsonData}}function stripSplashScreen(settingsData){if(!settingsData||!settingsData.splashScreen)return settingsData;settingsData.splashScreen.totalTime=0;settingsData.splashScreen.logo={type:"none",base64:""};return settingsData}function getJsListFromSettingsJs(data){const originData={jsList:[],settingsData:data};const settingsStrList=data.split("jsList:");if(settingsStrList.length<2)return originData;const settingsStr=settingsStrList.pop()||"";const jsListStrRegExp=/\[[^\]]*\]/.exec(settingsStr);if(!jsListStrRegExp)return originData;const jsListStr=jsListStrRegExp[0];return{jsList:JSON.parse(jsListStr),settingsData:data.replace(jsListStr,"[]")}}/**
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");const{load}=require("cheerio");const{deflate}=require("pako");const{getResourceMapper,getBase64FromFile}=require("./fs");const{TRANSPARENT_GIF}=require("./constants");const{applyObfuscation,isCocosBusinessScript,obfuscateLevelName}=require("../../utils/obfuscateLevels.js");function readInject(rel){return fs.readFileSync(path.join(__dirname,rel),"utf8")}function paddingStyleTags($,originPkgPath,enableSplash){$("link[type=\"text/css\"]").toArray().forEach(item=>{const href=$(item).attr("href");if(!href)return;const cssPath=path.join(originPkgPath,href);if(!fs.existsSync(cssPath))return;const cssStr=fs.readFileSync(cssPath,"utf-8");$(`<style>${cssStr}</style>`).appendTo("head")});if(!enableSplash){$("<style>#splash,#splash .progress-bar{display:none!important;opacity:0;visibility:hidden}</style>").appendTo("head")}$("link[type=\"text/css\"]").remove();$("head").find("style").each((_index,elem)=>{const cssUrlReg=/url\("?'?.*"?'?\)/g;let styleTagStr=$(elem).html()||"";const matchStrList=styleTagStr.match(cssUrlReg);if(!matchStrList)return;matchStrList.forEach(str=>{const imgUrl=str.replace(/"|'|url|\(|\)/g,"");const imgPath=path.join(originPkgPath,imgUrl);const imgBase64=enableSplash&&fs.existsSync(imgPath)?getBase64FromFile(imgPath):TRANSPARENT_GIF;styleTagStr=styleTagStr.replace(str,`url(${imgBase64})`)});$(elem).html(styleTagStr)})}function paddingScriptTags($,originPkgPath){let scriptTags="";$("script[type=\"systemjs-importmap\"]").toArray().forEach(item=>{const href=$(item).attr("src");if(!href)return;const srcPath=path.join(originPkgPath,href);if(!fs.existsSync(srcPath))return;scriptTags+=`<script type="systemjs-importmap">${fs.readFileSync(srcPath,"utf-8")}</script>`});$("head link").remove();$("body script").remove();if(scriptTags)$(scriptTags).appendTo("body")}function getJsListFromSettingsJson(data){const jsonData=JSON.parse(data);jsonData.plugins={jsList:[],...jsonData.plugins};const jsList=[...jsonData.plugins.jsList];jsonData.plugins.jsList=[];return{jsList,settingsData:jsonData}}function stripSplashScreen(settingsData){if(!settingsData||!settingsData.splashScreen)return settingsData;settingsData.splashScreen.totalTime=0;settingsData.splashScreen.logo={type:"none",base64:""};return settingsData}function getJsListFromSettingsJs(data){const originData={jsList:[],settingsData:data};const settingsStrList=data.split("jsList:");if(settingsStrList.length<2)return originData;const settingsStr=settingsStrList.pop()||"";const jsListStrRegExp=/\[[^\]]*\]/.exec(settingsStr);if(!jsListStrRegExp)return originData;const jsListStr=jsListStrRegExp[0];return{jsList:JSON.parse(jsListStr),settingsData:data.replace(jsListStr,"[]")}}/**
|
|
2
2
|
* Inline a Creator 3.x web-mobile folder into a single HTML file.
|
|
3
3
|
* @param {string} originPkgPath - Absolute path to build/web-mobile
|
|
4
4
|
* @param {string} singleFilePath - Output html path
|
|
5
|
-
* @param {{ enableSplash?: boolean, isZip?: boolean }} [opts]
|
|
6
|
-
*/async function mergeWebMobile(originPkgPath,singleFilePath,opts={}){const enableSplash=opts.enableSplash===true;const isZip=opts.isZip!==false;const htmlPath=path.join(originPkgPath,"index.html");if(!fs.existsSync(htmlPath)){throw new Error(`[cocos] web-mobile index.html not found: ${htmlPath}`)}const $=load(fs.readFileSync(htmlPath,"utf-8"));paddingStyleTags($,originPkgPath,enableSplash);paddingScriptTags($,originPkgPath);let pluginJsList=[];const{resMapper}=await getResourceMapper({dirPath:originPkgPath,rmHttp:true,mountCbFn:(objKey,data)=>{if(objKey.indexOf("src/settings.json")!==-1){const{jsList,settingsData}=getJsListFromSettingsJson(data);pluginJsList=jsList;if(!enableSplash)stripSplashScreen(settingsData);return JSON.stringify(settingsData)}if(objKey.indexOf("src/settings.js")!==-1){const{jsList,settingsData}=getJsListFromSettingsJs(data);pluginJsList=jsList;return settingsData}return data}});let resStr=JSON.stringify(resMapper);let compDiff=0;if(isZip){const zip=deflate(resStr);const zipStr=Buffer.from(zip).toString("base64");console.log("[cocos] origin pkg",resStr.length,"compressed",zipStr.length);if(zipStr.length<resStr.length){compDiff=resStr.length-zipStr.length;resStr=zipStr}}if(compDiff>0){$(`<script data-id="jszip">${readInject("injects/libs/pako.js")}</script>`).appendTo("body");$(`<script data-id="adapter-zip-0">window.__adapter_zip__="${resStr}";</script>`).appendTo("body")}else{$(`<script data-id="adapter-resource-0">window.__adapter_resource__=${resStr}</script>`).appendTo("body")}$(`<script data-id="adapter-plugins">window.__adapter_plugins__=${JSON.stringify(pluginJsList)}</script>`).appendTo("body");$(`<script data-id="adapter-init">${readInject("injects/3x/init.js")}</script>`).appendTo("body");$(`<script data-id="adapter-main">${readInject("injects/3x/main.js")}</script>`).appendTo("body");fs.mkdirSync(path.dirname(singleFilePath),{recursive:true});fs.writeFileSync(singleFilePath,$.html());const kb=(fs.statSync(singleFilePath).size/1024).toFixed(1);console.log(`[cocos] single-file template ${kb}kb → ${singleFilePath}`);return{resMapper,compDiff,singleFilePath}}module.exports={mergeWebMobile};
|
|
5
|
+
* @param {{ enableSplash?: boolean, isZip?: boolean, obfuscateLevel?: number }} [opts]
|
|
6
|
+
*/async function mergeWebMobile(originPkgPath,singleFilePath,opts={}){const enableSplash=opts.enableSplash===true;const isZip=opts.isZip!==false;const obfuscateLevel=Number(opts.obfuscateLevel)||0;if(obfuscateLevel>0){console.log(`[cocos] obfuscate business scripts level ${obfuscateLevel} (${obfuscateLevelName(obfuscateLevel)})`)}const htmlPath=path.join(originPkgPath,"index.html");if(!fs.existsSync(htmlPath)){throw new Error(`[cocos] web-mobile index.html not found: ${htmlPath}`)}const $=load(fs.readFileSync(htmlPath,"utf-8"));paddingStyleTags($,originPkgPath,enableSplash);paddingScriptTags($,originPkgPath);let pluginJsList=[];const{resMapper}=await getResourceMapper({dirPath:originPkgPath,rmHttp:true,mountCbFn:async(objKey,data)=>{if(objKey.indexOf("src/settings.json")!==-1){const{jsList,settingsData}=getJsListFromSettingsJson(data);pluginJsList=jsList;if(!enableSplash)stripSplashScreen(settingsData);return JSON.stringify(settingsData)}if(objKey.indexOf("src/settings.js")!==-1){const{jsList,settingsData}=getJsListFromSettingsJs(data);pluginJsList=jsList;return settingsData}if(obfuscateLevel>0&&isCocosBusinessScript(objKey)&&typeof data==="string"){const next=await applyObfuscation(data,obfuscateLevel);console.log(`[cocos] ${objKey} ${data.length} -> ${next.length} bytes`);return next}return data}});let resStr=JSON.stringify(resMapper);let compDiff=0;if(isZip){const zip=deflate(resStr);const zipStr=Buffer.from(zip).toString("base64");console.log("[cocos] origin pkg",resStr.length,"compressed",zipStr.length);if(zipStr.length<resStr.length){compDiff=resStr.length-zipStr.length;resStr=zipStr}}if(compDiff>0){$(`<script data-id="jszip">${readInject("injects/libs/pako.js")}</script>`).appendTo("body");$(`<script data-id="adapter-zip-0">window.__adapter_zip__="${resStr}";</script>`).appendTo("body")}else{$(`<script data-id="adapter-resource-0">window.__adapter_resource__=${resStr}</script>`).appendTo("body")}$(`<script data-id="adapter-plugins">window.__adapter_plugins__=${JSON.stringify(pluginJsList)}</script>`).appendTo("body");$(`<script data-id="adapter-init">${readInject("injects/3x/init.js")}</script>`).appendTo("body");$(`<script data-id="adapter-main">${readInject("injects/3x/main.js")}</script>`).appendTo("body");fs.mkdirSync(path.dirname(singleFilePath),{recursive:true});fs.writeFileSync(singleFilePath,$.html());const kb=(fs.statSync(singleFilePath).size/1024).toFixed(1);console.log(`[cocos] single-file template ${kb}kb → ${singleFilePath}`);return{resMapper,compDiff,singleFilePath}}module.exports={mergeWebMobile};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const fs=require("fs");const path=require("path");const{options}=require("../../options.js");const{mergeOptions}=require("../../utils/mergeOptions.js");const{logOptions}=require("../../utils/logOptions.js");const{resolveCreatorOutputDir}=require("./creator-cli");const{runCocosEngineBuild}=require("./cli-build");const{rebundlePreviewMerge}=require("./rebundle-preview.js");const{mergeWebMobile}=require("./merge");const{detectCurrentTheme,loadThemePayload,writeGeneratedThemeModule}=require("./theme-assets");const{emitChannel}=require("../../artifacts/emit");const{isSocialMediaNetwork,stripSocialShareFromDir}=require("./strip-social-share");function resolveWebMobileDir(root,buildOptions){const cocos=buildOptions.cocos||{};const fromCreator=resolveCreatorOutputDir(root,cocos.buildConfig||"build-configs/web-mobile.json");const explicit=cocos.webMobileDir?path.resolve(root,cocos.webMobileDir):null;if(cocos.skipEngineBuild){return explicit||fromCreator||path.resolve(root,"dist/web-mobile")}if(fromCreator){if(explicit&&explicit!==fromCreator){console.warn(`[cocos] webMobileDir (${path.relative(root,explicit)}) != Creator output (${path.relative(root,fromCreator)}); using Creator buildPath/outputName`)}return fromCreator}return explicit||path.resolve(root,"dist/web-mobile")}async function mergeTemplate(root,buildOptions){const webMobileDir=resolveWebMobileDir(root,buildOptions);const singleFilePath=path.join(root,"build","single-file-3x.html");await mergeWebMobile(webMobileDir,singleFilePath,{enableSplash:!!(buildOptions.cocos&&buildOptions.cocos.enableSplash)});return fs.readFileSync(singleFilePath,"utf8")}function resolveThemeName(root,buildOptions,themeConfig){return buildOptions.themeName||detectCurrentTheme(root,themeConfig)||"default"}function loadBuildsThemeConfig(root){const configPath=path.join(root,"builds.config.js");if(!fs.existsSync(configPath))return{sourceDir:"src/theme",entryFile:"src/theme/index.ts"};try{delete require.cache[configPath];const config=require(configPath);return{sourceDir:"src/theme",entryFile:"src/theme/index.ts",...(config.themes||{})}}catch{return{sourceDir:"src/theme",entryFile:"src/theme/index.ts"}}}function stripPackedChannel(outDir,network){if(isSocialMediaNetwork(network)){console.log("[cocos] social_media: keep AiX hooks (inject IIFE later)");return}const packed=stripSocialShareFromDir(outDir);console.log(`[cocos] stripped social-share from ${packed.changed}/${packed.files} files in ${path.relative(process.cwd(),outDir)||"."}`)}async function prepareCocosTemplate(customOptions){const buildOptions=mergeOptions(options,customOptions);const root=process.cwd();await runCocosEngineBuild(root,buildOptions);await rebundlePreviewMerge(root);return mergeTemplate(root,buildOptions)}async function emitCocosChannel(templateHtml,customOptions){const buildOptions=mergeOptions(options,customOptions);const root=process.cwd();const themeConfig=loadBuildsThemeConfig(root);const themeName=resolveThemeName(root,buildOptions,themeConfig);let themePayload={themeName,mergedData:{},i18nOverride:{},assetRecordByPath:{}};try{themePayload=loadThemePayload(root,themeName,themeConfig)}catch(err){console.warn(`[cocos] theme payload skipped: ${err.message}`)}const generated=buildOptions.cocos&&buildOptions.cocos.generatedThemeFile||"assets/scripts/platform/theme.generated.ts";writeGeneratedThemeModule(path.join(root,generated),themePayload);const outDir=path.resolve(root,buildOptions.outDir);await emitChannel({templateHtml,buildOptions,themePayload,outDir});stripPackedChannel(outDir,buildOptions.network)}async function runCocosBuild(customOptions){const buildOptions=mergeOptions(options,customOptions);logOptions(buildOptions);const templateHtml=await prepareCocosTemplate(buildOptions);await emitCocosChannel(templateHtml,customOptions)}async function packCocosBatch({config,themes,tasks,outputDir,projectRoot,isChannelFold2Zip}){const buildOptionsBase=mergeOptions(options,{engine:"cocos",googlePlayUrl:config.googlePlayUrl,appStoreUrl:config.appStoreUrl,isChannelFold2Zip,obfuscateLevel:config.obfuscateLevel,fflateCompression:config.fflateCompression});if(config.cocos)Object.assign(buildOptionsBase.cocos,config.cocos);if(buildOptionsBase.cocos.cleanCache!==false&&process.env.PLAYABLE_KEEP_COCOS_CACHE!=="1"){buildOptionsBase.cocos.cleanCache=true}await runCocosEngineBuild(projectRoot,buildOptionsBase);await rebundlePreviewMerge(projectRoot);const templateHtml=await mergeTemplate(projectRoot,buildOptionsBase);const themeConfig=config.themes||loadBuildsThemeConfig(projectRoot);const results=[];for(const theme of themes){let themePayload;try{themePayload=loadThemePayload(projectRoot,theme,themeConfig)}catch(err){console.error(`[cocos] theme ${theme}: ${err.message}`);for(const task of tasks){results.push({success:false,theme,channel:task.channel,orientation:task.orientation,error:err.message})}continue}for(const task of tasks){const orientationValue=task.orientation||"both";const fileName=config.naming&&typeof config.naming.generator==="function"?config.naming.generator(theme,task.channel):`${theme}-${task.channel}`;const tempDir=path.join(projectRoot,`${config.build.tempDirPrefix||"dist-temp"}-cocos-${theme}-${task.channel}-${orientationValue}`);try{const channelOptions=mergeOptions(buildOptionsBase,{network:task.channel,playcraftOrientationValue:orientationValue,filename:fileName||options.filename,outDir:tempDir,isChannelFold2Zip});await emitChannel({templateHtml,buildOptions:channelOptions,themePayload,outDir:tempDir});stripPackedChannel(tempDir,task.channel);const themeDir=path.join(outputDir,theme);fs.mkdirSync(themeDir,{recursive:true});for(const file of fs.readdirSync(tempDir)){const src=path.join(tempDir,file);if(fs.statSync(src).isDirectory())continue;fs.copyFileSync(src,path.join(themeDir,file))}results.push({success:true,theme,channel:task.channel,orientation:orientationValue})}catch(error){results.push({success:false,theme,channel:task.channel,orientation:orientationValue,error:error.message})}finally{if(config.build.cleanTemp!==false&&fs.existsSync(tempDir)){fs.rmSync(tempDir,{recursive:true,force:true})}}}}return results}async function syncThemeForPreview(projectRoot,themeName,themeConfig,generatedRel){const payload=loadThemePayload(projectRoot,themeName,themeConfig);writeGeneratedThemeModule(path.join(projectRoot,generatedRel),payload);return payload}module.exports={runCocosBuild,prepareCocosTemplate,emitCocosChannel,packCocosBatch,mergeTemplate,syncThemeForPreview};
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");const{options}=require("../../options.js");const{mergeOptions}=require("../../utils/mergeOptions.js");const{logOptions}=require("../../utils/logOptions.js");const{resolveCreatorOutputDir}=require("./creator-cli");const{runCocosEngineBuild}=require("./cli-build");const{rebundlePreviewMerge}=require("./rebundle-preview.js");const{mergeWebMobile}=require("./merge");const{detectCurrentTheme,loadThemePayload,writeGeneratedThemeModule}=require("./theme-assets");const{emitChannel}=require("../../artifacts/emit");const{isSocialMediaNetwork,stripSocialShareFromDir}=require("./strip-social-share");function resolveWebMobileDir(root,buildOptions){const cocos=buildOptions.cocos||{};const fromCreator=resolveCreatorOutputDir(root,cocos.buildConfig||"build-configs/web-mobile.json");const explicit=cocos.webMobileDir?path.resolve(root,cocos.webMobileDir):null;if(cocos.skipEngineBuild){return explicit||fromCreator||path.resolve(root,"dist/web-mobile")}if(fromCreator){if(explicit&&explicit!==fromCreator){console.warn(`[cocos] webMobileDir (${path.relative(root,explicit)}) != Creator output (${path.relative(root,fromCreator)}); using Creator buildPath/outputName`)}return fromCreator}return explicit||path.resolve(root,"dist/web-mobile")}async function mergeTemplate(root,buildOptions){const webMobileDir=resolveWebMobileDir(root,buildOptions);const singleFilePath=path.join(root,"build","single-file-3x.html");await mergeWebMobile(webMobileDir,singleFilePath,{enableSplash:!!(buildOptions.cocos&&buildOptions.cocos.enableSplash),obfuscateLevel:buildOptions.obfuscateLevel});return fs.readFileSync(singleFilePath,"utf8")}function resolveThemeName(root,buildOptions,themeConfig){return buildOptions.themeName||detectCurrentTheme(root,themeConfig)||"default"}function loadBuildsThemeConfig(root){const configPath=path.join(root,"builds.config.js");if(!fs.existsSync(configPath))return{sourceDir:"src/theme",entryFile:"src/theme/index.ts"};try{delete require.cache[configPath];const config=require(configPath);return{sourceDir:"src/theme",entryFile:"src/theme/index.ts",...(config.themes||{})}}catch{return{sourceDir:"src/theme",entryFile:"src/theme/index.ts"}}}function stripPackedChannel(outDir,network){if(isSocialMediaNetwork(network)){console.log("[cocos] social_media: keep AiX hooks (inject IIFE later)");return}const packed=stripSocialShareFromDir(outDir);console.log(`[cocos] stripped social-share from ${packed.changed}/${packed.files} files in ${path.relative(process.cwd(),outDir)||"."}`)}async function prepareCocosTemplate(customOptions){const buildOptions=mergeOptions(options,customOptions);const root=process.cwd();await runCocosEngineBuild(root,buildOptions);await rebundlePreviewMerge(root);return mergeTemplate(root,buildOptions)}async function emitCocosChannel(templateHtml,customOptions){const buildOptions=mergeOptions(options,customOptions);const root=process.cwd();const themeConfig=loadBuildsThemeConfig(root);const themeName=resolveThemeName(root,buildOptions,themeConfig);let themePayload={themeName,mergedData:{},i18nOverride:{},assetRecordByPath:{}};try{themePayload=loadThemePayload(root,themeName,themeConfig)}catch(err){console.warn(`[cocos] theme payload skipped: ${err.message}`)}const generated=buildOptions.cocos&&buildOptions.cocos.generatedThemeFile||"assets/scripts/platform/theme.generated.ts";writeGeneratedThemeModule(path.join(root,generated),themePayload);const outDir=path.resolve(root,buildOptions.outDir);await emitChannel({templateHtml,buildOptions,themePayload,themeConfig,outDir});stripPackedChannel(outDir,buildOptions.network)}async function runCocosBuild(customOptions){const buildOptions=mergeOptions(options,customOptions);logOptions(buildOptions);const templateHtml=await prepareCocosTemplate(buildOptions);await emitCocosChannel(templateHtml,customOptions)}async function packCocosBatch({config,themes,tasks,outputDir,projectRoot,isChannelFold2Zip}){const buildOptionsBase=mergeOptions(options,{engine:"cocos",googlePlayUrl:config.googlePlayUrl,appStoreUrl:config.appStoreUrl,isChannelFold2Zip,obfuscateLevel:config.obfuscateLevel,fflateCompression:config.fflateCompression});if(config.cocos)Object.assign(buildOptionsBase.cocos,config.cocos);if(buildOptionsBase.cocos.cleanCache!==false&&process.env.PLAYABLE_KEEP_COCOS_CACHE!=="1"){buildOptionsBase.cocos.cleanCache=true}await runCocosEngineBuild(projectRoot,buildOptionsBase);await rebundlePreviewMerge(projectRoot);const templateHtml=await mergeTemplate(projectRoot,buildOptionsBase);const themeConfig=config.themes||loadBuildsThemeConfig(projectRoot);const results=[];for(const theme of themes){let themePayload;try{themePayload=loadThemePayload(projectRoot,theme,themeConfig)}catch(err){console.error(`[cocos] theme ${theme}: ${err.message}`);for(const task of tasks){results.push({success:false,theme,channel:task.channel,orientation:task.orientation,error:err.message})}continue}for(const task of tasks){const orientationValue=task.orientation||"both";const fileName=config.naming&&typeof config.naming.generator==="function"?config.naming.generator(theme,task.channel):`${theme}-${task.channel}`;const tempDir=path.join(projectRoot,`${config.build.tempDirPrefix||"dist-temp"}-cocos-${theme}-${task.channel}-${orientationValue}`);try{const channelOptions=mergeOptions(buildOptionsBase,{network:task.channel,playcraftOrientationValue:orientationValue,filename:fileName||options.filename,outDir:tempDir,isChannelFold2Zip});await emitChannel({templateHtml,buildOptions:channelOptions,themePayload,themeConfig,outDir:tempDir});stripPackedChannel(tempDir,task.channel);const themeDir=path.join(outputDir,theme);fs.mkdirSync(themeDir,{recursive:true});for(const file of fs.readdirSync(tempDir)){const src=path.join(tempDir,file);if(fs.statSync(src).isDirectory())continue;fs.copyFileSync(src,path.join(themeDir,file))}results.push({success:true,theme,channel:task.channel,orientation:orientationValue})}catch(error){results.push({success:false,theme,channel:task.channel,orientation:orientationValue,error:error.message})}finally{if(config.build.cleanTemp!==false&&fs.existsSync(tempDir)){fs.rmSync(tempDir,{recursive:true,force:true})}}}}return results}async function syncThemeForPreview(projectRoot,themeName,themeConfig,generatedRel){const payload=loadThemePayload(projectRoot,themeName,themeConfig);writeGeneratedThemeModule(path.join(projectRoot,generatedRel),payload);return payload}module.exports={runCocosBuild,prepareCocosTemplate,emitCocosChannel,packCocosBatch,mergeTemplate,syncThemeForPreview};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";/* playcraft-json-pack:start */(function(){var pack=null;var pending=[];var packHash="";function resolvePackUrl(){var src="";try{if(document.currentScript&&document.currentScript.src)src=document.currentScript.src}catch(err){}if(!src){try{var scripts=document.getElementsByTagName("script");for(var i=scripts.length-1;i>=0;--i){var candidate=scripts[i].src||"";if(candidate.indexOf("system.js")!==-1){src=candidate;break}}}catch(err){}}if(!src){try{var entries=performance.getEntriesByType("resource");for(var i=entries.length-1;i>=0;--i){var name=entries[i].name;if(name.indexOf("/systemjs/system.js")!==-1){src=name;break}}}catch(err){}}var url=src?src.replace(/system\.js(\?.*)?$/,"playcraft-json-pack.json"):"scripting/systemjs/playcraft-json-pack.json";if(packHash&&url.indexOf("?")===-1)url+="?h="+packHash;return url}
|
|
1
|
+
"use strict";/* playcraft-json-pack:start */(function(){var pack=null;var pending=[];var packHash="";function resolvePackUrl(){var src="";try{if(document.currentScript&&document.currentScript.src)src=document.currentScript.src}catch(err){}if(!src){try{var scripts=document.getElementsByTagName("script");for(var i=scripts.length-1;i>=0;--i){var candidate=scripts[i].src||"";if(candidate.indexOf("system.js")!==-1){src=candidate;break}}}catch(err){}}if(!src){try{var entries=performance.getEntriesByType("resource");for(var i=entries.length-1;i>=0;--i){var name=entries[i].name;if(name.indexOf("/systemjs/system.js")!==-1){src=name;break}}}catch(err){}}var url=src?src.replace(/system\.js(\?.*)?$/,"playcraft-json-pack.json"):"scripting/systemjs/playcraft-json-pack.json";if(packHash&&url.indexOf("?")===-1)url+="?h="+packHash;return url}function uuidFromUrl(url){var match=String(url||"").match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:@[^.\\/?]+)?)(?:\.json)?(?:\?|$)/i);return match?match[1].toLowerCase():""}function extnameUuidFromUrl(url){var match=String(url||"").match(/query-extname\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:@[^.\\/?]+)?)(?:\?|$)/i);return match?match[1].toLowerCase():""}function configNameFromUrl(url){var match=String(url||"").match(/\/(?:assets|remote)\/([^/]+)\/(?:config|cc\.config)\.json(?:\?|$)/i);return match?match[1]:""}function hasAsset(uuid){return!!(uuid&&pack&&pack.assets&&Object.prototype.hasOwnProperty.call(pack.assets,uuid))}function lookup(url){if(!pack)return null;var href=String(url||"");var extUuid=extnameUuidFromUrl(href);if(extUuid){if(hasAsset(extUuid))return".json";return null}var uuid=uuidFromUrl(href);if(hasAsset(uuid))return pack.assets[uuid];var configName=configNameFromUrl(href);if(configName&&pack.configs&&Object.prototype.hasOwnProperty.call(pack.configs,configName)){return pack.configs[configName]}return null}function serve(xhr,data){var isString=typeof data==="string";var text=isString?data:JSON.stringify(data);Object.defineProperty(xhr,"readyState",{configurable:true,get:function(){return 4}});Object.defineProperty(xhr,"status",{configurable:true,get:function(){return 200}});Object.defineProperty(xhr,"response",{configurable:true,get:function(){return xhr.responseType==="json"&&!isString?data:text}});Object.defineProperty(xhr,"responseText",{configurable:true,get:function(){return text}});if(typeof xhr.onreadystatechange==="function")xhr.onreadystatechange();if(typeof xhr.onload==="function")xhr.onload()}function flush(){var queue=pending.splice(0);for(var i=0;i<queue.length;i++)queue[i]()}var xhrOpen=XMLHttpRequest.prototype.open;var xhrSend=XMLHttpRequest.prototype.send;var xhrHeader=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.open=function(method,url){this.__pcUrl=String(url==null?"":url);this.__pcOpenArgs=arguments;this.__pcHit=false;this.__pcWait=false;if(this.__pcUrl.indexOf("playcraft-json-pack.json")!==-1){return xhrOpen.apply(this,arguments)}if(pack){if(lookup(this.__pcUrl)!=null){this.__pcHit=true;return}return xhrOpen.apply(this,arguments)}this.__pcWait=true};XMLHttpRequest.prototype.setRequestHeader=function(){if(this.__pcHit||this.__pcWait)return;return xhrHeader.apply(this,arguments)};XMLHttpRequest.prototype.send=function(body){var xhr=this;var url=xhr.__pcUrl;if(url&&String(url).indexOf("playcraft-json-pack.json")!==-1){return xhrSend.apply(xhr,arguments)}var run=function(){var data=lookup(url);if(data!=null){xhr.__pcHit=true;serve(xhr,data);return}if(xhr.__pcWait)xhrOpen.apply(xhr,xhr.__pcOpenArgs||["GET",url]);xhrSend.call(xhr,body)};if(pack){run();return}pending.push(run)};if(typeof globalThis!=="undefined"&&typeof globalThis.fetch==="function"){var nativeFetch=globalThis.fetch.bind(globalThis);globalThis.fetch=function(input,init){var url=typeof input==="string"?input:input&&input.url;if(url&&String(url).indexOf("playcraft-json-pack.json")!==-1){return nativeFetch.apply(this,arguments)}var run=function(){var data=lookup(url);if(data!=null){var isString=typeof data==="string";var text=isString?data:JSON.stringify(data);return Promise.resolve(new Response(text,{status:200,headers:{"Content-Type":isString?"text/plain":"application/json"}}))}return nativeFetch.call(this,input,init)};if(pack)return run();return new Promise(function(resolve,reject){pending.push(function(){Promise.resolve(run()).then(resolve,reject)})})}}if(pack)return;var req=new XMLHttpRequest;req.open("GET",resolvePackUrl(),true);req.onload=function(){var text=req.responseText||req.response;try{pack=typeof text==="string"?JSON.parse(text):text}catch(err){pack=null}if(!pack||typeof pack!=="object")pack={assets:{},configs:{}};flush()};req.onerror=function(){pack={assets:{},configs:{}};flush()};req.send(null)})();/* playcraft-json-pack:end */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
"use strict";const crypto=require("crypto");const fs=require("fs");const http=require("http");const https=require("https");const path=require("path");const{
|
|
1
|
+
"use strict";const crypto=require("crypto");const fs=require("fs");const http=require("http");const https=require("https");const path=require("path");const{resolveCocosEntryPoint}=require("./cli.js");const HOOK_START="/* playcraft-json-pack:start */";const HOOK_END="/* playcraft-json-pack:end */";const PACK_FILE="playcraft-json-pack.json";const UUID_RE=/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:@[0-9a-z]+)?/gi;const UUID_FILE_RE=/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:@.+)?)\.json$/i;const FALLBACK_MAX_BYTES=200*1024;const FILE_MAX_BYTES=5*1024*1024;function systemJsDir(projectRoot){return path.join(projectRoot,"temp/programming/preview/systemjs")}function hookSource(){return fs.readFileSync(path.join(__dirname,"preview-json-hook.js"),"utf8").trim()+"\n"}function uuidFromUrl(url){const match=String(url||"").match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:@[^.\\/?]+)?)(?:\.json)?(?:\?|$)/i);return match?match[1].toLowerCase():""}function configNameFromUrl(url){const match=String(url||"").match(/\/(?:assets|remote)\/([^/]+)\/(?:config|cc\.config)\.json(?:\?|$)/i);return match?match[1]:""}function addUuid(set,value){if(typeof value!=="string"||!value)return;const inner=value.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:@[0-9a-z]+)?/i);if(inner)set.add(inner[0].toLowerCase())}function collectFromSettings(settings,seeds){if(!settings||typeof settings!=="object")return;const builtins=settings.engine&&settings.engine.builtinAssets;if(Array.isArray(builtins)){for(const uuid of builtins)addUuid(seeds,uuid)}addUuid(seeds,settings.launch&&settings.launch.launchScene);addUuid(seeds,settings.launchScene);const scenes=settings.scenes||settings.launch&&settings.launch.scenes;if(Array.isArray(scenes)){for(const scene of scenes){if(scene&&typeof scene==="object")addUuid(seeds,scene.uuid);else addUuid(seeds,scene)}}}function collectFromConfig(config,seeds){if(!config||typeof config!=="object")return;for(const uuid of config.uuids||[])addUuid(seeds,String(uuid));for(const uuid of Object.keys(config.paths||{}))addUuid(seeds,uuid);const scenes=config.scenes||{};if(typeof scenes==="object"){for(const value of Object.values(scenes)){if(Array.isArray(value)){for(const item of value)addUuid(seeds,String(item))}else{addUuid(seeds,String(value))}}}}function parseSettings(text){const match=String(text||"").match(/window\._CCSettings\s*=\s*(\{[\s\S]*\});\s*$/);if(!match)return null;return JSON.parse(match[1])}function fetchText(url,timeout=8000){return new Promise((resolve,reject)=>{const client=String(url).startsWith("https:")?https:http;const req=client.get(url,{timeout},res=>{const chunks=[];res.on("data",chunk=>chunks.push(chunk));res.on("end",()=>{const body=Buffer.concat(chunks).toString("utf8");if((res.statusCode||0)>=400){reject(new Error(`${res.statusCode} ${url}`));return}resolve(body)})});req.on("timeout",()=>{req.destroy();reject(new Error(`timeout ${url}`))});req.on("error",reject)})}function startSceneUuid(projectRoot){try{const cfg=JSON.parse(fs.readFileSync(path.join(projectRoot,"settings/cocos.config.json"),"utf8"));return cfg.builder&&cfg.builder.common&&cfg.builder.common.startScene||cfg.builder&&cfg.builder.platforms&&cfg.builder.platforms["web-mobile"]&&cfg.builder.platforms["web-mobile"].startScene||cfg.builder&&cfg.builder.platforms&&cfg.builder.platforms["web-desktop"]&&cfg.builder.platforms["web-desktop"].startScene||""}catch{return""}}function resolveEngineLibraryDirs(projectRoot){const dirs=[];const add=dir=>{if(dir&&fs.existsSync(dir)&&!dirs.includes(dir))dirs.push(dir)};if(process.env.COCOS_ENGINE){add(path.join(process.env.COCOS_ENGINE,"editor/library"))}if(projectRoot){add(path.join(projectRoot,"temp/cocos-cli/packages/engine/editor/library"))}// Windows 上要穿透 npm 的 cocos.cmd shim 才能往上走到 engine/library
|
|
2
|
+
try{const entry=resolveCocosEntryPoint();let current=entry?fs.realpathSync(entry):"";for(let i=0;current&&i<8;i++){add(path.join(current,"packages/engine/editor/library"));add(path.join(current,"engine/editor/library"));add(path.join(current,"editor/library"));const parent=path.dirname(current);current=parent===current?"":parent}}catch{}add("/opt/cocos-cli/packages/engine/editor/library");add("/usr/local/lib/node_modules/cocos-cli/packages/engine/editor/library");return dirs}function libraryDirs(projectRoot,extraDirs){const dirs=[];const projectLib=path.join(projectRoot,"library");if(fs.existsSync(projectLib))dirs.push(projectLib);for(const dir of extraDirs||resolveEngineLibraryDirs(projectRoot)){if(dir&&fs.existsSync(dir)&&!dirs.includes(dir))dirs.push(dir)}return dirs}function importJsonUrl(origin,bundle,uuid){return`${String(origin).replace(/\/$/,"")}/assets/${bundle}/import/${uuid.slice(0,2)}/${uuid}.json`}async function fetchAssetJson(origin,uuid,bundles,fetch){for(const bundle of bundles){try{return JSON.parse(await fetch(importJsonUrl(origin,bundle,uuid)))}catch{}}try{return JSON.parse(await fetch(`${String(origin).replace(/\/$/,"")}/scene/${uuid}.json`))}catch{}return null}async function fillAssetsFromOrigin(assets,seeds,options){const origin=options.previewOrigin;if(!origin)return;const fetch=options.fetchText||fetchText;const bundles=Object.keys(options.bundleConfigs||{}).filter(Boolean);if(!bundles.length)bundles.push("internal","main","resources");const queue=[];const seen=new Set(Object.keys(assets));for(const uuid of seeds){seen.add(uuid);if(!assets[uuid])queue.push(uuid)}const runOne=async uuid=>{if(assets[uuid])return;const json=await fetchAssetJson(origin,uuid,bundles,fetch);if(!json)return;assets[uuid]=json;for(const match of JSON.stringify(json).matchAll(UUID_RE)){const next=match[0].toLowerCase();if(seen.has(next))continue;seen.add(next);queue.push(next)}};while(queue.length){const batch=queue.splice(0,10);await Promise.all(batch.map(runOne))}}function findLibraryJson(uuid,dirs){const name=`${uuid}.json`;const prefix=uuid.slice(0,2);for(const dir of dirs){const abs=path.join(dir,prefix,name);if(fs.existsSync(abs))return abs}return""}function scanFallbackUuids(dirs){const seeds=new Set;for(const dir of dirs){if(!fs.existsSync(dir))continue;for(const bucket of fs.readdirSync(dir)){const absBucket=path.join(dir,bucket);let stat;try{stat=fs.statSync(absBucket)}catch{continue}if(!stat.isDirectory())continue;for(const name of fs.readdirSync(absBucket)){const match=name.match(UUID_FILE_RE);if(!match)continue;const abs=path.join(absBucket,name);try{if(fs.statSync(abs).size>FALLBACK_MAX_BYTES)continue}catch{continue}seeds.add(match[1].toLowerCase())}}}return seeds}function closeUuidJson(seeds,dirs){const assets={};const queue=[...seeds];const seen=new Set(seeds);while(queue.length){const uuid=queue.pop();const abs=findLibraryJson(uuid,dirs);if(!abs)continue;let stat;try{stat=fs.statSync(abs)}catch{continue}if(stat.size>FILE_MAX_BYTES)continue;let json;try{json=JSON.parse(fs.readFileSync(abs,"utf8"))}catch{continue}assets[uuid]=json;const text=JSON.stringify(json);UUID_RE.lastIndex=0;let match;while(match=UUID_RE.exec(text)){const next=match[0].toLowerCase();if(seen.has(next))continue;seen.add(next);queue.push(next)}}return assets}function injectJsonPackHook(projectRoot,packHash,packObject){const dir=systemJsDir(projectRoot);const systemJsPath=path.join(dir,"system.js");if(!fs.existsSync(systemJsPath))return false;let source=fs.readFileSync(systemJsPath,"utf8");const start=source.indexOf(HOOK_START);const end=source.indexOf(HOOK_END);if(start!==-1&&end!==-1&&end>start){source=`${source.slice(0,start)}${source.slice(end+HOOK_END.length)}`.replace(/^\n/,"")}let hook=hookSource();if(packObject){hook=hook.replace("var pack = null;",`var pack = ${JSON.stringify(packObject)};`)}if(packHash){hook=hook.replace(/var packHash = '';/,`var packHash = ${JSON.stringify(packHash)};`)}fs.writeFileSync(systemJsPath,`${hook}${source}`);return true}async function loadPreviewMeta(options){if(options.settings||options.bundleConfigs){return{settings:options.settings||null,configs:options.bundleConfigs||{}}}const origin=options.previewOrigin;if(!origin)return{settings:null,configs:{}};const fetch=options.fetchText||fetchText;const settings=parseSettings(await fetch(`${origin.replace(/\/$/,"")}/preview/settings.js`));const bundles=settings&&settings.assets&&settings.assets.projectBundles||["internal","main","resources"];const configs={};for(const name of bundles){try{configs[name]=JSON.parse(await fetch(`${origin.replace(/\/$/,"")}/assets/${name}/config.json`))}catch{}}return{settings,configs}}/**
|
|
2
3
|
* Pack preview UUID JSON + bundle configs into one file and intercept XHR
|
|
3
4
|
* from system.js (before builtin assets load). Official merge_all_json is
|
|
4
5
|
* builder-only and uses a compiled format preview cannot feed.
|
|
5
|
-
*/async function packPreviewJson(projectRoot,options={}){const dirs=libraryDirs(projectRoot,options.engineLibraryDirs);const{settings,configs}=await loadPreviewMeta(options);const seeds=new Set;collectFromSettings(settings,seeds);for(const config of Object.values(configs||{}))collectFromConfig(config,seeds);addUuid(seeds,startSceneUuid(projectRoot));if(!seeds.size){for(const uuid of scanFallbackUuids(dirs))seeds.add(uuid)}const assets=closeUuidJson(seeds,dirs);const pack={assets,configs:configs||{}};const json=`${JSON.stringify(pack)}\n`;const dir=systemJsDir(projectRoot);fs.mkdirSync(dir,{recursive:true});const abs=path.join(dir,PACK_FILE);const tmp=`${abs}.tmp`;fs.writeFileSync(tmp,json);fs.renameSync(tmp,abs);const packHash=crypto.createHash("sha1").update(json).digest("hex").slice(0,10);const hooked=injectJsonPackHook(projectRoot,packHash);return{files:Object.keys(assets).length,configs:Object.keys(pack.configs).length,bytes:Buffer.byteLength(json),hooked,pack:`./${PACK_FILE}`}}module.exports={packPreviewJson,injectJsonPackHook,uuidFromUrl,configNameFromUrl,parseSettings,collectFromSettings,collectFromConfig,PACK_FILE,HOOK_START};
|
|
6
|
+
*/async function packPreviewJson(projectRoot,options={}){const dirs=libraryDirs(projectRoot,options.engineLibraryDirs);const{settings,configs}=await loadPreviewMeta(options);const seeds=new Set;collectFromSettings(settings,seeds);for(const config of Object.values(configs||{}))collectFromConfig(config,seeds);addUuid(seeds,startSceneUuid(projectRoot));const projectLib=path.join(projectRoot,"library");for(const uuid of scanFallbackUuids([projectLib]))seeds.add(uuid);if(!seeds.size){for(const uuid of scanFallbackUuids(dirs))seeds.add(uuid)}const assets=closeUuidJson(seeds,dirs);await fillAssetsFromOrigin(assets,seeds,{...options,bundleConfigs:configs});const pack={assets,configs:configs||{}};const json=`${JSON.stringify(pack)}\n`;const dir=systemJsDir(projectRoot);fs.mkdirSync(dir,{recursive:true});const abs=path.join(dir,PACK_FILE);const tmp=`${abs}.tmp`;fs.writeFileSync(tmp,json);fs.renameSync(tmp,abs);const packHash=crypto.createHash("sha1").update(json).digest("hex").slice(0,10);const hooked=injectJsonPackHook(projectRoot,packHash,pack);return{files:Object.keys(assets).length,configs:Object.keys(pack.configs).length,bytes:Buffer.byteLength(json),hooked,pack:`./${PACK_FILE}`}}module.exports={packPreviewJson,injectJsonPackHook,uuidFromUrl,configNameFromUrl,parseSettings,collectFromSettings,collectFromConfig,PACK_FILE,HOOK_START};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const{describe,it,before,after}=require("node:test");const assert=require("node:assert/strict");const fs=require("fs");const os=require("os");const path=require("path");const{packPreviewJson,uuidFromUrl,configNameFromUrl,HOOK_START}=require("./preview-json-pack.js");describe("packPreviewJson",()=>{let root;const scene="0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d";const builtin="970b0598-bcb0-4714-91fb-2e81440dccd8";const extra="11111111-2222-4333-8444-555555555555";before(()=>{root=fs.mkdtempSync(path.join(os.tmpdir(),"preview-json-"));const writeJson=(dir,uuid,json)=>{const abs=path.join(dir,uuid.slice(0,2),`${uuid}.json`);fs.mkdirSync(path.dirname(abs),{recursive:true});fs.writeFileSync(abs,`${JSON.stringify(json)}\n`)};writeJson(path.join(root,"library"),scene,{name:"scene",ref:{__uuid__:extra}});writeJson(path.join(root,"library"),extra,{name:"extra"});writeJson(path.join(root,"engine-lib"),builtin,{name:"effect"});fs.mkdirSync(path.join(root,"temp/programming/preview/systemjs"),{recursive:true});fs.writeFileSync(path.join(root,"temp/programming/preview/systemjs/system.js"),"(function () { 'use strict'; window.System = {}; })();\n");fs.mkdirSync(path.join(root,"settings"),{recursive:true});fs.writeFileSync(path.join(root,"settings/cocos.config.json"),JSON.stringify({builder:{common:{startScene:scene}}}))});after(()=>{fs.rmSync(root,{recursive:true,force:true})});it("parses preview json urls",()=>{assert.equal(uuidFromUrl("/assets/internal/import/97/970b0598-bcb0-4714-91fb-2e81440dccd8.json?t=1"),builtin);assert.equal(uuidFromUrl(`/scene/${scene}.json`),scene);assert.equal(uuidFromUrl(`https://host/api/sandbox-preview/assets/sid/slot/import/97/${builtin}`),builtin);assert.equal(configNameFromUrl("/assets/internal/config.json"),"internal")});it("packs uuid closure + configs and injects the xhr hook",async()=>{const result=await packPreviewJson(root,{engineLibraryDirs:[path.join(root,"engine-lib")],settings:{engine:{builtinAssets:[builtin]},launch:{launchScene:scene},assets:{projectBundles:["internal"]}},bundleConfigs:{internal:{name:"internal",uuids:[builtin],paths:{[builtin]:["db://internal/effect","cc.EffectAsset"]}}}});assert.equal(result.files,3);assert.equal(result.configs,1);assert.equal(result.hooked,true);const pack=JSON.parse(fs.readFileSync(path.join(root,"temp/programming/preview/systemjs/playcraft-json-pack.json"),"utf8"));assert.equal(pack.assets[scene].name,"scene");assert.equal(pack.assets[extra].name,"extra");assert.equal(pack.assets[builtin].name,"effect");assert.equal(pack.configs.internal.name,"internal");const systemJs=fs.readFileSync(path.join(root,"temp/programming/preview/systemjs/system.js"),"utf8");assert.equal(systemJs.includes(HOOK_START),true);assert.equal(systemJs.includes("XMLHttpRequest.prototype.send"),true);assert.match(systemJs,/window\.System = \{\}/);const again=await packPreviewJson(root,{engineLibraryDirs:[path.join(root,"engine-lib")],settings:{engine:{builtinAssets:[builtin]},launch:{launchScene:scene}},bundleConfigs:{internal:{name:"internal",uuids:[builtin],paths:{}}}});assert.equal(again.hooked,true);const injected=fs.readFileSync(path.join(root,"temp/programming/preview/systemjs/system.js"),"utf8");assert.equal(injected.split(HOOK_START).length-1,1)});it("serves packed uuid json from the xhr hook without network",async()=>{await packPreviewJson(root,{engineLibraryDirs:[path.join(root,"engine-lib")],settings:{engine:{builtinAssets:[builtin]},launch:{launchScene:scene}},bundleConfigs:{internal:{name:"internal",uuids:[builtin],paths:{}}}});const hook=fs.readFileSync(path.join(root,"temp/programming/preview/systemjs/system.js"),"utf8").split("(function () { 'use strict'; window.System")[0];const pack=JSON.parse(fs.readFileSync(path.join(root,"temp/programming/preview/systemjs/playcraft-json-pack.json"),"utf8"));const instances=[];function FakeXHR(){instances.push(this);this.readyState=1;this.status=0;this.response=null;this.responseType="json";this.onload=null;this.onreadystatechange=null;this.__network=false}FakeXHR.prototype.open=function(method,url){this.__pcUrl=url};FakeXHR.prototype.send=function(){this.__network=true};global.performance={getEntriesByType:()=>[]};global.document={currentScript:null,getElementsByTagName:()=>[]};global.XMLHttpRequest=FakeXHR;// eslint-disable-next-line no-eval
|
|
2
|
-
eval(hook.replace(HOOK_START,"").replace("/* playcraft-json-pack:end */",""));const packXhr=instances[0];
|
|
1
|
+
"use strict";const{describe,it,before,after}=require("node:test");const assert=require("node:assert/strict");const fs=require("fs");const os=require("os");const path=require("path");const{packPreviewJson,uuidFromUrl,configNameFromUrl,HOOK_START}=require("./preview-json-pack.js");describe("packPreviewJson",()=>{let root;const scene="0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d";const builtin="970b0598-bcb0-4714-91fb-2e81440dccd8";const extra="11111111-2222-4333-8444-555555555555";before(()=>{root=fs.mkdtempSync(path.join(os.tmpdir(),"preview-json-"));const writeJson=(dir,uuid,json)=>{const abs=path.join(dir,uuid.slice(0,2),`${uuid}.json`);fs.mkdirSync(path.dirname(abs),{recursive:true});fs.writeFileSync(abs,`${JSON.stringify(json)}\n`)};writeJson(path.join(root,"library"),scene,{name:"scene",ref:{__uuid__:extra}});writeJson(path.join(root,"library"),extra,{name:"extra"});writeJson(path.join(root,"engine-lib"),builtin,{name:"effect"});fs.mkdirSync(path.join(root,"temp/programming/preview/systemjs"),{recursive:true});fs.writeFileSync(path.join(root,"temp/programming/preview/systemjs/system.js"),"(function () { 'use strict'; window.System = {}; })();\n");fs.mkdirSync(path.join(root,"settings"),{recursive:true});fs.writeFileSync(path.join(root,"settings/cocos.config.json"),JSON.stringify({builder:{common:{startScene:scene}}}))});after(()=>{fs.rmSync(root,{recursive:true,force:true})});it("parses preview json urls",()=>{assert.equal(uuidFromUrl("/assets/internal/import/97/970b0598-bcb0-4714-91fb-2e81440dccd8.json?t=1"),builtin);assert.equal(uuidFromUrl(`/scene/${scene}.json`),scene);assert.equal(uuidFromUrl(`https://host/api/sandbox-preview/assets/sid/slot/import/97/${builtin}`),builtin);assert.equal(configNameFromUrl("/assets/internal/config.json"),"internal");assert.equal(uuidFromUrl("https://host/api/sandbox-preview/assets/sid/slot/assets/internal/import/a3/a3cd009f-0ab0-420d-9278-b9fdab939bbc.json"),"a3cd009f-0ab0-420d-9278-b9fdab939bbc")});it("packs uuid closure + configs and injects the xhr hook",async()=>{const result=await packPreviewJson(root,{engineLibraryDirs:[path.join(root,"engine-lib")],settings:{engine:{builtinAssets:[builtin]},launch:{launchScene:scene},assets:{projectBundles:["internal"]}},bundleConfigs:{internal:{name:"internal",uuids:[builtin],paths:{[builtin]:["db://internal/effect","cc.EffectAsset"]}}}});assert.equal(result.files,3);assert.equal(result.configs,1);assert.equal(result.hooked,true);const pack=JSON.parse(fs.readFileSync(path.join(root,"temp/programming/preview/systemjs/playcraft-json-pack.json"),"utf8"));assert.equal(pack.assets[scene].name,"scene");assert.equal(pack.assets[extra].name,"extra");assert.equal(pack.assets[builtin].name,"effect");assert.equal(pack.configs.internal.name,"internal");const systemJs=fs.readFileSync(path.join(root,"temp/programming/preview/systemjs/system.js"),"utf8");assert.equal(systemJs.includes(HOOK_START),true);assert.equal(systemJs.includes("XMLHttpRequest.prototype.send"),true);assert.match(systemJs,/window\.System = \{\}/);const again=await packPreviewJson(root,{engineLibraryDirs:[path.join(root,"engine-lib")],settings:{engine:{builtinAssets:[builtin]},launch:{launchScene:scene}},bundleConfigs:{internal:{name:"internal",uuids:[builtin],paths:{}}}});assert.equal(again.hooked,true);const injected=fs.readFileSync(path.join(root,"temp/programming/preview/systemjs/system.js"),"utf8");assert.equal(injected.split(HOOK_START).length-1,1)});it("serves packed uuid json from the xhr hook without network",async()=>{await packPreviewJson(root,{engineLibraryDirs:[path.join(root,"engine-lib")],settings:{engine:{builtinAssets:[builtin]},launch:{launchScene:scene}},bundleConfigs:{internal:{name:"internal",uuids:[builtin],paths:{}}}});const hook=fs.readFileSync(path.join(root,"temp/programming/preview/systemjs/system.js"),"utf8").split("(function () { 'use strict'; window.System")[0];const pack=JSON.parse(fs.readFileSync(path.join(root,"temp/programming/preview/systemjs/playcraft-json-pack.json"),"utf8"));const instances=[];function FakeXHR(){instances.push(this);this.readyState=1;this.status=0;this.response=null;this.responseType="json";this.onload=null;this.onreadystatechange=null;this.__network=false}FakeXHR.prototype.open=function(method,url){this.__pcUrl=url};FakeXHR.prototype.setRequestHeader=function(){};FakeXHR.prototype.send=function(){this.__network=true};global.performance={getEntriesByType:()=>[]};global.document={currentScript:null,getElementsByTagName:()=>[]};global.XMLHttpRequest=FakeXHR;const originalFetch=globalThis.fetch;// eslint-disable-next-line no-eval
|
|
2
|
+
eval(hook.replace(HOOK_START,"").replace("/* playcraft-json-pack:end */",""));const packXhr=instances[0];if(packXhr&&String(packXhr.__pcUrl||"").includes("playcraft-json-pack.json")){packXhr.responseText=JSON.stringify(pack);packXhr.onload()}else{assert.equal(hook.includes("var pack = {"),true)}const asset=new FakeXHR;let loaded=null;asset.onload=function(){loaded=asset.response};XMLHttpRequest.prototype.open.call(asset,"GET",`/scene/${scene}.json`);XMLHttpRequest.prototype.send.call(asset,null);assert.equal(asset.__network,false);assert.equal(asset.status,200);assert.equal(loaded.name,"scene");const config=new FakeXHR;let configLoaded=null;config.onload=function(){configLoaded=config.response};XMLHttpRequest.prototype.open.call(config,"GET","/assets/internal/config.json");XMLHttpRequest.prototype.send.call(config,null);assert.equal(config.__network,false);assert.equal(configLoaded.name,"internal");const ext=new FakeXHR;ext.responseType="";let extLoaded=null;ext.onload=function(){extLoaded=ext.response};XMLHttpRequest.prototype.open.call(ext,"GET",`/query-extname/${scene}`);XMLHttpRequest.prototype.send.call(ext,null);assert.equal(ext.__network,false);assert.equal(extLoaded,".json");const sandboxJson=new FakeXHR;let sandboxLoaded=null;sandboxJson.onload=function(){sandboxLoaded=sandboxJson.response};XMLHttpRequest.prototype.open.call(sandboxJson,"GET",`https://host/api/sandbox-preview/assets/sid/slot/assets/internal/import/97/${builtin}.json`);XMLHttpRequest.prototype.send.call(sandboxJson,null);assert.equal(sandboxJson.__network,false);assert.equal(sandboxLoaded.name,"effect");const sandboxExt=new FakeXHR;sandboxExt.responseType="";let sandboxExtLoaded=null;sandboxExt.onload=function(){sandboxExtLoaded=sandboxExt.response};XMLHttpRequest.prototype.open.call(sandboxExt,"GET",`https://host/api/sandbox-preview/assets/sid/slot/query-extname/${scene}`);XMLHttpRequest.prototype.send.call(sandboxExt,null);assert.equal(sandboxExt.__network,false);assert.equal(sandboxExtLoaded,".json");delete global.XMLHttpRequest;delete global.performance;delete global.document;if(originalFetch)globalThis.fetch=originalFetch;else delete globalThis.fetch})});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";const{merge}=require("webpack-merge");const{webpackCommonConfig}=require("./webpack.common.js");const webpack=require("webpack");const TerserPlugin=require("terser-webpack-plugin");const CopyWebpackPlugin=require("copy-webpack-plugin");const HtmlInlineScriptPlugin=require("html-inline-script-webpack-plugin");const HtmlWebpackPlugin=require("html-webpack-plugin");const MiniCssExtractPlugin=require("mini-css-extract-plugin");const{ZipPlugin}=require("../../plugins/ZipPlugin.js");const{FflateCompressionPlugin}=require("../../plugins/FflateCompressionPlugin.js");const{generateAdikteevHtmlWebpackPluginConfig}=require("../../utils/generateAdikteevHtmlWebpackPluginConfig.js");const{generateBigabidHtmlWebpackPluginConfig}=require("../../utils/generateBigabidHtmlWebpackPluginConfig.js");const{generateInMobiHtmlWebpackPluginConfig}=require("../../utils/generateInMobiHtmlWebpackPluginConfig.js");const path=require("path");const{mergeOptions}=require("../../utils/mergeOptions.js");const{options}=require("../../options.js");const{buildDefines}=require("../../utils/buildDefines.js");const{buildTemplateString}=require("../../utils/buildTemplateString.js");const{logOptions}=require("../../utils/logOptions.js");const{injectSDKPlugins}=require("../../utils/injectSDKPlugins.js");const{resolveChannelFold2Zip}=require("../../utils/resolveChannelFold2Zip.js");const{getPlaycraftOrientationSuffix}=require("../../utils/getPlaycraftOrientationSuffix.js");const{PlaycraftOrientationInjectorPlugin}=require("../../plugins/PlaycraftOrientationInjectorPlugin.js");const META={COMMON_CHARSET:"utf-8",COMMON_VIEWPORT:"width=device-width,initial-scale=1.0,viewport-fit=cover,maximum-scale=1.0,user-scalable=no",MINTEGRAL_VIEWPORT:"width=device-width,user-scalable=no,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0"};const FontFamily={LIFTOFF:"<style>body{font-family:Roboto,\"Helvetica Neue\",-apple-system,BlinkMacSystemFont,sans-serif}</style>"};/**
|
|
1
|
+
"use strict";const{merge}=require("webpack-merge");const{webpackCommonConfig}=require("./webpack.common.js");const webpack=require("webpack");const TerserPlugin=require("terser-webpack-plugin");const CopyWebpackPlugin=require("copy-webpack-plugin");const HtmlInlineScriptPlugin=require("html-inline-script-webpack-plugin");const HtmlWebpackPlugin=require("html-webpack-plugin");const MiniCssExtractPlugin=require("mini-css-extract-plugin");const{ZipPlugin}=require("../../plugins/ZipPlugin.js");const{FflateCompressionPlugin}=require("../../plugins/FflateCompressionPlugin.js");const{generateAdikteevHtmlWebpackPluginConfig}=require("../../utils/generateAdikteevHtmlWebpackPluginConfig.js");const{generateBigabidHtmlWebpackPluginConfig}=require("../../utils/generateBigabidHtmlWebpackPluginConfig.js");const{generateInMobiHtmlWebpackPluginConfig}=require("../../utils/generateInMobiHtmlWebpackPluginConfig.js");const path=require("path");const{mergeOptions}=require("../../utils/mergeOptions.js");const{options}=require("../../options.js");const{buildDefines}=require("../../utils/buildDefines.js");const{buildTemplateString}=require("../../utils/buildTemplateString.js");const{logOptions}=require("../../utils/logOptions.js");const{injectSDKPlugins}=require("../../utils/injectSDKPlugins.js");const{resolveChannelFold2Zip}=require("../../utils/resolveChannelFold2Zip.js");const{getPlaycraftOrientationSuffix}=require("../../utils/getPlaycraftOrientationSuffix.js");const{PlaycraftOrientationInjectorPlugin}=require("../../plugins/PlaycraftOrientationInjectorPlugin.js");const{obfuscatorOptionsForLevel,obfuscateLevelName,terserOptions}=require("../../utils/obfuscateLevels.js");const META={COMMON_CHARSET:"utf-8",COMMON_VIEWPORT:"width=device-width,initial-scale=1.0,viewport-fit=cover,maximum-scale=1.0,user-scalable=no",MINTEGRAL_VIEWPORT:"width=device-width,user-scalable=no,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0"};const FontFamily={LIFTOFF:"<style>body{font-family:Roboto,\"Helvetica Neue\",-apple-system,BlinkMacSystemFont,sans-serif}</style>"};/**
|
|
2
2
|
* HtmlWebpackPlugin beforeEmit 钩子。
|
|
3
3
|
* 在最终 HTML 输出前统一修正 meta 标签:
|
|
4
4
|
* - charset → 统一小写 utf-8
|
|
@@ -17,39 +17,11 @@ if(this.adNetwork==="liftoff"){const fontStyle=FontFamily.LIFTOFF;data.html=data
|
|
|
17
17
|
* @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
|
|
18
18
|
* @returns {import('webpack').Configuration} Final webpack production configuration
|
|
19
19
|
*/function makeWebpackBuildConfig(customOptions,customDefines,webpackCustomConfig){const buildOptions=mergeOptions(options,customOptions);logOptions(buildOptions);customDefines=customDefines||{};webpackCustomConfig=webpackCustomConfig||{};/** @type {AD_NETWORK} */const adNetwork=buildOptions["network"];/** @type {AD_PROTOCOL} */const adProtocol=buildOptions["protocol"];/** @type {'both'|'landscape'|'portrait'} 当前单次构建实际使用的横竖屏方向版本,默认 'both' */const playcraftOrientationValue=buildOptions.playcraftOrientationValue||"both";/** @type {string} 方向版本对应的命名后缀:landscape→'_L',portrait→'_P',both→'' */const orientationSuffix=getPlaycraftOrientationSuffix(playcraftOrientationValue);/** @type {string} 渠道 + 方向后缀组成的产物标识,如 'google_L' */const outputId=`${adNetwork}${orientationSuffix}`;let outDir=buildTemplateString(buildOptions.outDir,buildOptions);function getFileName(){let filename=buildTemplateString(buildOptions.filename,buildOptions);if(adNetwork==="mintegral")return filename.replace(/\s+/g,"").replace(/[^a-zA-Z0-9]/g,"_").replace("_fullhash_6_","[fullhash:6]");return filename}const isZipOutput=zipOutputNetworks.includes(adNetwork)||buildOptions.zip&&zipOutputAllowedNetworks.includes(adNetwork);const{isChannelFold2Zip,isZipOutputFinal}=resolveChannelFold2Zip({isChannelFold2ZipOption:buildOptions.isChannelFold2Zip,adNetwork,isZipOutput});let htmlFileName="";if(adNetwork==="mintegral")htmlFileName="index.html";// 修改为 index.html
|
|
20
|
-
else if(isZipOutputFinal)htmlFileName="index.html";else htmlFileName=`${getFileName()}.html`;const metaTags={viewport:META.COMMON_VIEWPORT};if(adNetwork==="mintegral"){metaTags["viewport"]=META.MINTEGRAL_VIEWPORT}if(!buildOptions.skipRecommendedMeta){metaTags["HandheldFriendly"]="True";metaTags["cleartype"]={"http-equiv":"cleartype",content:"on"};metaTags["apple-mobile-web-app-capable"]="yes";metaTags["mobile-web-app-capable"]="yes";metaTags["X-UA-Compatible"]={"http-equiv":"X-UA-Compatible",content:"IE=10"}}let htmlWebpackPluginConfig={template:path.resolve("src/index.html"),filename:htmlFileName,title:`${buildOptions.name} - ${buildOptions.app}`,inlineSource:".(js|css|png|jpg|webp|svg|xml|atlas|mp3|gif|glb|fbx|obj)$",meta:metaTags};if(adNetwork==="adikteev")htmlWebpackPluginConfig=generateAdikteevHtmlWebpackPluginConfig("src/index.html");else if(adNetwork==="bigabid")htmlWebpackPluginConfig=generateBigabidHtmlWebpackPluginConfig("src/index.html");else if(adNetwork==="inmobi")htmlWebpackPluginConfig=generateInMobiHtmlWebpackPluginConfig("src/index.html",buildOptions);const webpackConfig=merge(webpackCommonConfig,{mode:"production",stats:"errors-only",optimization:{usedExports:true,minimize:(buildOptions.obfuscateLevel!==undefined?buildOptions.obfuscateLevel:2)>=1,minimizer:[new TerserPlugin({extractComments:false,terserOptions:
|
|
21
|
-
// properties: true,
|
|
22
|
-
toplevel:true},compress:{drop_console:true,arrows:false,drop_debugger:true,unused:true,dead_code:true,// 🔥 增加优化轮数
|
|
23
|
-
passes:3,// 🔥 激进压缩选项
|
|
24
|
-
inline:3,// 最大内联
|
|
25
|
-
collapse_vars:true,// 折叠变量
|
|
26
|
-
reduce_vars:true,// 减少变量
|
|
27
|
-
hoist_vars:true,// 提升变量
|
|
28
|
-
hoist_funs:true,// 提升函数
|
|
29
|
-
sequences:20,// 更长的序列化
|
|
30
|
-
properties:true,// 优化属性访问
|
|
31
|
-
conditionals:true,// 优化条件
|
|
32
|
-
comparisons:true,// 优化比较
|
|
33
|
-
booleans:true,// 优化布尔
|
|
34
|
-
loops:true,// 优化循环
|
|
35
|
-
if_return:true,// 优化 if-return
|
|
36
|
-
join_vars:true,// 合并变量
|
|
37
|
-
negate_iife:true,// 否定 IIFE
|
|
38
|
-
side_effects:true,// 移除副作用
|
|
39
|
-
switches:true,// 优化 switch
|
|
40
|
-
typeofs:true,// 优化 typeof
|
|
41
|
-
unsafe:false,// 不使用 unsafe(避免破坏框架)
|
|
42
|
-
keep_fargs:false,// 移除未使用参数
|
|
43
|
-
drop_console:true},output:{comments:false,quote_style:3,beautify:false,wrap_iife:true,ecma:2015},toplevel:true,keep_classnames:false,keep_fnames:false}})]},plugins:[new HtmlWebpackPlugin(htmlWebpackPluginConfig),new MetaTagOverridePlugin(adNetwork),// 注入 window.playcraftOrientation 全局变量,标识当前构建产物对应的横竖屏方向版本
|
|
20
|
+
else if(isZipOutputFinal)htmlFileName="index.html";else htmlFileName=`${getFileName()}.html`;const metaTags={viewport:META.COMMON_VIEWPORT};if(adNetwork==="mintegral"){metaTags["viewport"]=META.MINTEGRAL_VIEWPORT}if(!buildOptions.skipRecommendedMeta){metaTags["HandheldFriendly"]="True";metaTags["cleartype"]={"http-equiv":"cleartype",content:"on"};metaTags["apple-mobile-web-app-capable"]="yes";metaTags["mobile-web-app-capable"]="yes";metaTags["X-UA-Compatible"]={"http-equiv":"X-UA-Compatible",content:"IE=10"}}let htmlWebpackPluginConfig={template:path.resolve("src/index.html"),filename:htmlFileName,title:`${buildOptions.name} - ${buildOptions.app}`,inlineSource:".(js|css|png|jpg|webp|svg|xml|atlas|mp3|gif|glb|fbx|obj)$",meta:metaTags};if(adNetwork==="adikteev")htmlWebpackPluginConfig=generateAdikteevHtmlWebpackPluginConfig("src/index.html");else if(adNetwork==="bigabid")htmlWebpackPluginConfig=generateBigabidHtmlWebpackPluginConfig("src/index.html");else if(adNetwork==="inmobi")htmlWebpackPluginConfig=generateInMobiHtmlWebpackPluginConfig("src/index.html",buildOptions);const webpackConfig=merge(webpackCommonConfig,{mode:"production",stats:"errors-only",optimization:{usedExports:true,minimize:(buildOptions.obfuscateLevel!==undefined?buildOptions.obfuscateLevel:2)>=1,minimizer:[new TerserPlugin({extractComments:false,terserOptions:terserOptions()})]},plugins:[new HtmlWebpackPlugin(htmlWebpackPluginConfig),new MetaTagOverridePlugin(adNetwork),// 注入 window.playcraftOrientation 全局变量,标识当前构建产物对应的横竖屏方向版本
|
|
44
21
|
new PlaycraftOrientationInjectorPlugin(playcraftOrientationValue),new webpack.DefinePlugin({...buildDefines(buildOptions),...buildOptions.defines,...customDefines}),// For some reason needed for PIXI v8
|
|
45
22
|
new webpack.optimize.LimitChunkCountPlugin({maxChunks:1})]},webpackCustomConfig);webpackConfig.output.path=path.resolve(outDir);// 根据混淆等级注入 obfuscator loader
|
|
46
23
|
const obfuscateLevel=buildOptions.obfuscateLevel!==undefined?buildOptions.obfuscateLevel:2;// 获取需要排除压缩的目录列表
|
|
47
|
-
const excludeCompressDirs=buildOptions.excludeCompressDirs||[];if(obfuscateLevel>=2){
|
|
48
|
-
const obfuscatorOptions={// 通用基础配置
|
|
49
|
-
target:"browser",sourceMap:false,selfDefending:false,renameProperties:false,disableConsoleOutput:true,splitStrings:false,unicodeEscapeSequence:false};if(obfuscateLevel===2){// 等级2:基础混淆(控制流平坦化 + 字符串数组)
|
|
50
|
-
Object.assign(obfuscatorOptions,{controlFlowFlattening:true,controlFlowFlatteningThreshold:0.75,deadCodeInjection:false,stringArray:true,stringArrayThreshold:0.75,stringArrayEncoding:["base64"],stringArrayIndexShift:true,stringArrayRotate:true,stringArrayShuffle:true,stringArrayWrappersCount:2,stringArrayWrappersChainedCalls:true,stringArrayWrappersType:"function",stringArrayCallsTransform:true,stringArrayCallsTransformThreshold:0.75,transformObjectKeys:true,identifierNamesGenerator:"hexadecimal"})}else if(obfuscateLevel===3){// 等级3:在等级2基础上增加死代码注入
|
|
51
|
-
Object.assign(obfuscatorOptions,{controlFlowFlattening:true,controlFlowFlatteningThreshold:0.75,deadCodeInjection:true,deadCodeInjectionThreshold:0.4,stringArray:true,stringArrayThreshold:0.75,stringArrayEncoding:["base64"],stringArrayIndexShift:true,stringArrayRotate:true,stringArrayShuffle:true,stringArrayWrappersCount:2,stringArrayWrappersChainedCalls:true,stringArrayWrappersType:"function",stringArrayCallsTransform:true,stringArrayCallsTransformThreshold:0.75,transformObjectKeys:true,identifierNamesGenerator:"hexadecimal"})}else if(obfuscateLevel===4){// 等级4:最高强度混淆
|
|
52
|
-
Object.assign(obfuscatorOptions,{controlFlowFlattening:true,controlFlowFlatteningThreshold:1,deadCodeInjection:true,deadCodeInjectionThreshold:0.5,stringArray:true,stringArrayThreshold:1,stringArrayEncoding:["rc4"],stringArrayIndexShift:true,stringArrayRotate:true,stringArrayShuffle:true,stringArrayWrappersCount:3,stringArrayWrappersChainedCalls:true,stringArrayWrappersType:"function",stringArrayCallsTransform:true,stringArrayCallsTransformThreshold:1,transformObjectKeys:true,identifierNamesGenerator:"hexadecimal",splitStrings:true,splitStringsChunkLength:5,unicodeEscapeSequence:true})}console.log(`Obfuscation level: ${obfuscateLevel} (${["","terser-only","basic","dead-code","maximum"][obfuscateLevel]})`);// 构建exclude数组,包含node_modules和用户指定的排除目录
|
|
24
|
+
const excludeCompressDirs=buildOptions.excludeCompressDirs||[];if(obfuscateLevel>=2){const obfuscatorOptions=obfuscatorOptionsForLevel(obfuscateLevel);console.log(`Obfuscation level: ${obfuscateLevel} (${obfuscateLevelName(obfuscateLevel)})`);// 构建exclude数组,包含node_modules和用户指定的排除目录
|
|
53
25
|
const excludePatterns=[];if(excludeCompressDirs.length>0){excludeCompressDirs.forEach(dir=>{excludePatterns.push(path.resolve(dir))});console.log(`ℹ️ Excluding directories from compression: ${excludeCompressDirs.join(", ")}`)}webpackConfig.module.rules.push({test:/\.(js|ts)$/,include:[path.resolve("src")],exclude:excludePatterns,enforce:"post",use:{loader:require.resolve("webpack-obfuscator/dist/loader"),options:obfuscatorOptions}})}else if(obfuscateLevel===1){console.log("Obfuscation level: 1 (terser-only)")}else{console.log("Obfuscation level: 0 (no minimize/obfuscate)")}// Adikteev 需要单独提取 CSS 为 style.css,替换默认的 style-loader
|
|
54
26
|
if(adNetwork==="adikteev"){const cssRuleIndex=webpackConfig.module.rules.findIndex(rule=>rule.test&&rule.test.toString().includes("\\.css"));if(cssRuleIndex!==-1){webpackConfig.module.rules[cssRuleIndex]={test:/\.css$/,use:[MiniCssExtractPlugin.loader,"css-loader"]};console.log("\u2705 Adikteev: CSS will be extracted to style.css")}}// 决定是否内联 JS 到 HTML
|
|
55
27
|
// 以下渠道需要分离 JS 文件,不使用 HtmlInlineScriptPlugin:
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";/**
|
|
2
|
+
* Same ladder as the Phaser webpack build:
|
|
3
|
+
* 0 none, 1 terser, 2 basic obfuscator, 3 + dead code, 4 maximum.
|
|
4
|
+
* Cocos applies this only to business bundles (assets/main/index.js).
|
|
5
|
+
* Order matches webpack: obfuscator loader, then Terser minimizer.
|
|
6
|
+
*/const LEVEL_NAMES=["none","terser-only","basic","dead-code","maximum"];function obfuscateLevelName(level){const n=Number(level)||0;return LEVEL_NAMES[n]||LEVEL_NAMES[0]}function obfuscatorOptionsForLevel(level){const n=Number(level)||0;if(n<2)return null;const options={target:"browser",sourceMap:false,selfDefending:false,renameProperties:false,disableConsoleOutput:true,splitStrings:false,unicodeEscapeSequence:false};if(n===2){Object.assign(options,{controlFlowFlattening:true,controlFlowFlatteningThreshold:0.75,deadCodeInjection:false,stringArray:true,stringArrayThreshold:0.75,stringArrayEncoding:["base64"],stringArrayIndexShift:true,stringArrayRotate:true,stringArrayShuffle:true,stringArrayWrappersCount:2,stringArrayWrappersChainedCalls:true,stringArrayWrappersType:"function",stringArrayCallsTransform:true,stringArrayCallsTransformThreshold:0.75,transformObjectKeys:true,identifierNamesGenerator:"hexadecimal"})}else if(n===3){Object.assign(options,{controlFlowFlattening:true,controlFlowFlatteningThreshold:0.75,deadCodeInjection:true,deadCodeInjectionThreshold:0.4,stringArray:true,stringArrayThreshold:0.75,stringArrayEncoding:["base64"],stringArrayIndexShift:true,stringArrayRotate:true,stringArrayShuffle:true,stringArrayWrappersCount:2,stringArrayWrappersChainedCalls:true,stringArrayWrappersType:"function",stringArrayCallsTransform:true,stringArrayCallsTransformThreshold:0.75,transformObjectKeys:true,identifierNamesGenerator:"hexadecimal"})}else{Object.assign(options,{controlFlowFlattening:true,controlFlowFlatteningThreshold:1,deadCodeInjection:true,deadCodeInjectionThreshold:0.5,stringArray:true,stringArrayThreshold:1,stringArrayEncoding:["rc4"],stringArrayIndexShift:true,stringArrayRotate:true,stringArrayShuffle:true,stringArrayWrappersCount:3,stringArrayWrappersChainedCalls:true,stringArrayWrappersType:"function",stringArrayCallsTransform:true,stringArrayCallsTransformThreshold:1,transformObjectKeys:true,identifierNamesGenerator:"hexadecimal",splitStrings:true,splitStringsChunkLength:5,unicodeEscapeSequence:true})}return options}function terserOptions(){return{safari10:true,mangle:{reserved:["$"],toplevel:true},compress:{drop_console:true,arrows:false,drop_debugger:true,unused:true,dead_code:true,passes:3,inline:3,collapse_vars:true,reduce_vars:true,hoist_vars:true,hoist_funs:true,sequences:20,properties:true,conditionals:true,comparisons:true,booleans:true,loops:true,if_return:true,join_vars:true,negate_iife:true,side_effects:true,switches:true,typeofs:true,unsafe:false,keep_fargs:false},output:{comments:false,quote_style:3,beautify:false,wrap_iife:true,ecma:2015},toplevel:true,keep_classnames:false,keep_fnames:false}}/**
|
|
7
|
+
* Creator web-mobile gameplay bundle. Engine (`cocos-js`, `assets/internal`)
|
|
8
|
+
* and the resources asset table stay as Creator emitted them.
|
|
9
|
+
* @param {string} objKey path inside the web-mobile folder
|
|
10
|
+
*/function isCocosBusinessScript(objKey){const key=String(objKey||"").replace(/\\/g,"/").replace(/^\//,"");return /^assets\/main\/index(?:\.[^/]+)?\.js$/.test(key)}async function applyObfuscation(code,level){const n=Number(level)||0;if(n<=0||typeof code!=="string"||!code)return code;let out=code;const obf=obfuscatorOptionsForLevel(n);if(obf){const JavaScriptObfuscator=require("javascript-obfuscator");out=JavaScriptObfuscator.obfuscate(out,obf).getObfuscatedCode()}if(n>=1){const{minify}=require("terser");const result=await minify(out,terserOptions());if(!result||!result.code){throw new Error(`terser produced empty output at obfuscate level ${n}`)}out=result.code}return out}module.exports={LEVEL_NAMES,obfuscateLevelName,obfuscatorOptionsForLevel,terserOptions,isCocosBusinessScript,applyObfuscation};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";const{describe,it}=require("node:test");const assert=require("node:assert/strict");const vm=require("vm");const{isCocosBusinessScript,applyObfuscation,obfuscatorOptionsForLevel}=require("./obfuscateLevels.js");const SAMPLE=`
|
|
2
|
+
System.register("chunks:///_virtual/Demo.js", ["./rollupPluginModLoBabelHelpers.js", "cc"], function (exports) {
|
|
3
|
+
return {
|
|
4
|
+
setters: [function () {}, function () {}],
|
|
5
|
+
execute: function () {
|
|
6
|
+
exports("findPath", function (start, goal) { return start + goal; });
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
});
|
|
10
|
+
`;function loadRegisters(code){const registered=[];const context=vm.createContext({System:{register(name,deps,factory){const ns=factory(function(){});registered.push({name:String(name),deps:Array.from(deps,dep=>String(dep)),keys:ns&&typeof ns==="object"?Object.keys(ns).map(String).sort():[]})}},console});vm.runInContext(code,context,{timeout:5000});return registered}describe("isCocosBusinessScript",()=>{it("matches the main bundle only",()=>{assert.equal(isCocosBusinessScript("assets/main/index.js"),true);assert.equal(isCocosBusinessScript("assets/main/index.abc123.js"),true);assert.equal(isCocosBusinessScript("assets/internal/index.js"),false);assert.equal(isCocosBusinessScript("assets/resources/index.js"),false);assert.equal(isCocosBusinessScript("cocos-js/cc.js"),false)})});describe("applyObfuscation",()=>{it("keeps System.register setters and execute at every level",async()=>{const original=loadRegisters(SAMPLE);assert.deepEqual(original[0].keys,["execute","setters"]);for(const level of[1,2,3,4]){const out=await applyObfuscation(SAMPLE,level);assert.equal(typeof out,"string");assert.ok(out.length>0,`level ${level} empty`);const registered=loadRegisters(out);assert.equal(registered.length,1,`level ${level} register count`);assert.deepEqual(registered[0].keys,["execute","setters"],`level ${level} keys`);assert.deepEqual(registered[0].deps,original[0].deps,`level ${level} deps`)}});it("level 0 and 1 do not enable the obfuscator",()=>{assert.equal(obfuscatorOptionsForLevel(0),null);assert.equal(obfuscatorOptionsForLevel(1),null);assert.equal(obfuscatorOptionsForLevel(2).stringArray,true);assert.equal(obfuscatorOptionsForLevel(3).deadCodeInjection,true);assert.equal(obfuscatorOptionsForLevel(4).stringArrayEncoding[0],"rc4")})});
|