@playcraft/devkit 1.0.31-beta.17 → 1.0.31-beta.18
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";const fs=require("fs");const http=require("http");const path=require("path");const{spawnCocos,needsXvfb}=require("./cli.js");const{syncGeneratedTheme,detectCurrentTheme,themeAssetStamp}=require("./theme-assets.js");const{refreshAssets}=require("./mcp-refresh.js");const{stripAdsdkCjsImport}=require("./strip-social-share.js");const{concatPreviewScripts}=require("./preview-concat.js");const{packPreviewJson}=require("./preview-json-pack.js");const{findFreePort,startPreviewProxy}=require("./preview-proxy.js");function sleep(ms){return new Promise(resolve=>setTimeout(resolve,ms))}function request(url,{method="GET",timeout=8000}={}){return new Promise((resolve,reject)=>{const req=http.request(url,{method,timeout},res=>{const chunks=[];res.on("data",c=>chunks.push(c));res.on("end",()=>{resolve({status:res.statusCode||0,body:Buffer.concat(chunks).toString("utf8")})})});req.on("timeout",()=>{req.destroy();reject(new Error(`timeout ${url}`))});req.on("error",reject);req.end()})}function resolvePreviewScene(projectRoot,explicit){if(explicit)return explicit;if(process.env.PREVIEW_SCENE)return process.env.PREVIEW_SCENE;try{const cfgPath=path.join(projectRoot,"settings/cocos.config.json");if(fs.existsSync(cfgPath)){const cfg=JSON.parse(fs.readFileSync(cfgPath,"utf8"));const fromConfig=cfg.builder&&cfg.builder.common&&cfg.builder.common.startScene||cfg.builder&&cfg.builder.platforms&&cfg.builder.platforms["web-desktop"]&&cfg.builder.platforms["web-desktop"].startScene||cfg.builder&&cfg.builder.platforms&&cfg.builder.platforms["web-mobile"]&&cfg.builder.platforms["web-mobile"].startScene;if(fromConfig)return fromConfig}}catch(_){}const scenesDir=path.join(projectRoot,"assets/scenes");if(fs.existsSync(scenesDir)){const first=fs.readdirSync(scenesDir).find(name=>name.endsWith(".scene"));if(first)return`db://assets/scenes/${first}`}return""}function killSocialShareForDev(projectRoot){const result=stripAdsdkCjsImport(projectRoot);if(result.skipped&&result.reason==="missing"){console.warn("[dev] adsdk CJS missing, skip social-share strip");return}if(result.skipped&&result.reason==="already"){console.log("[dev] social-share import() already stripped");return}if(result.skipped){console.warn("[dev] social-share import() not found in CJS");return}console.log("[dev] stripped social-share import() from adsdk CJS")}const PREVIEW_MERGE_MARKER=path.join("temp",".playcraft-preview-merge-ready");function previewMergeMarkerPath(projectRoot){return path.join(projectRoot,PREVIEW_MERGE_MARKER)}function clearPreviewMergeMarker(projectRoot){try{fs.unlinkSync(previewMergeMarkerPath(projectRoot))}catch{// ignore
|
|
2
2
|
}}function markPreviewMergeReady(projectRoot){const marker=previewMergeMarkerPath(projectRoot);fs.mkdirSync(path.dirname(marker),{recursive:true});fs.writeFileSync(marker,`${Date.now()}\n`)}function shouldIgnoreWatch(file){if(!file)return false;const base=path.basename(file);if(base===".DS_Store"||base==="theme.generated.ts")return true;if(base.startsWith(".")&&base!==".json5")return true;const ext=path.extname(file).toLowerCase();return ext===".meta"}/**
|
|
3
3
|
* 常驻官方 cocos CLI 预览:start-mcp-server(同一端口既有 / 也有 /mcp)。
|
|
4
|
-
*/async function runCocosDev(opts={}){const projectRoot=opts.projectRoot||process.cwd();const port=Number(opts.port||process.env.PREVIEW_PORT||3000);const timeoutMs=Number(opts.timeoutMs||process.env.DEV_TIMEOUT_MS||180000);const debounceMs=Number(opts.debounceMs||process.env.DEV_DEBOUNCE_MS||400);const cmd=opts.cmd||process.argv[2]||"start";const scene=resolvePreviewScene(projectRoot,opts.scene);const previewUrl=scene?`http://127.0.0.1:${port}/?scene=${encodeURIComponent(scene)}`:`http://127.0.0.1:${port}/`;const watchDirs=["assets","src/theme"].filter(dir=>fs.existsSync(path.join(projectRoot,dir)));function previewUrlFor(previewPort){return scene?`http://127.0.0.1:${previewPort}/?scene=${encodeURIComponent(scene)}`:`http://127.0.0.1:${previewPort}/`}async function isPreviewUpOn(previewPort){try{const res=await request(previewUrlFor(previewPort),{timeout:1500});return res.status>0&&res.status<500}catch{return false}}async function isPreviewUp(){return isPreviewUpOn(port)}async function waitForPreviewOn(previewPort){const url=previewUrlFor(previewPort);const start=Date.now();let lastLog=0;while(Date.now()-start<timeoutMs){if(await isPreviewUpOn(previewPort))return;if(Date.now()-lastLog>=5000){console.log(`[dev] waiting for ${url}`);lastLog=Date.now()}await sleep(1000)}throw new Error(`preview not ready after ${timeoutMs}ms: ${url}`)}function printBanner(reused){console.log(`[dev] ${reused?"reusing cocos CLI":"cocos CLI ready"}`);console.log(`[dev] iframe → ${previewUrl}`);console.log(`[dev] mcp → http://127.0.0.1:${port}/mcp`);console.log(`[dev] watch ${watchDirs.map(d=>`${d}/`).join(" ")}`)}function syncThemeOnce(){try{const result=syncGeneratedTheme(projectRoot);if(!result){console.log("[dev] no theme entry; skip generated module");return false}console.log(`[dev] synced theme ${result.themeName}${result.changed===false?" (unchanged)":""}`);return result.changed!==false}catch(syncErr){console.warn(`[dev] theme sync skipped: ${syncErr.message}`);return false}}async function compile({fatal=true,previewPort=port}={}){const origin=previewUrlFor(previewPort);if(!(await isPreviewUpOn(previewPort))){console.error(`[dev] preview is down: ${origin}`);if(fatal){console.error("[dev] Run npm run dev first.");process.exit(1)}return}console.log(`[dev] assets-refresh db://assets`);try{const res=await refreshAssets(previewPort,"db://assets");const err=res.parsed&&(res.parsed.error||res.parsed.result&&res.parsed.result.isError);console.log(`[dev] ${res.status}${err?" error":""}`);if(res.status===404){console.error("[dev] /mcp \u4E0D\u5B58\u5728\u3002\u8BF7\u505C\u6389\u5F53\u524D\u9884\u89C8\uFF0C\u91CD\u65B0 npm run dev\uFF08\u4F1A\u8D77 start-mcp-server\uFF09")}if((res.status>=400||err)&&fatal)process.exit(1)}catch(err){console.error(`[dev] compile failed: ${err.message}`);if(fatal)process.exit(1)}await bundlePreviewAssets({previewPort})}async function bundlePreviewAssets({previewPort=port}={}){bundlePreviewScripts();try{const result=await packPreviewJson(projectRoot,{previewOrigin:`http://127.0.0.1:${previewPort}`});if(!result)return;const kb=Math.max(1,Math.round(result.bytes/1024));console.log(`[dev] preview json ${result.files} → 1 pack (${kb} kB)`);markPreviewMergeReady(projectRoot)}catch(err){console.warn(`[dev] preview json pack skipped: ${err.message}`)}}function bundlePreviewScripts(){try{const result=concatPreviewScripts(projectRoot);if(!result)return;const kb=Math.max(1,Math.round(result.bytes/1024));console.log(`[dev] preview scripts ${result.modules} → 1 bundle (${kb} kB)`)}catch(err){console.warn(`[dev] preview script concat skipped: ${err.message}`)}}function watchAndCompile(){let timer=null;let compiling=false;let pending=null;const queue=rel=>{pending=rel;if(timer)clearTimeout(timer);timer=setTimeout(()=>{timer=null;run()},debounceMs)};const run=async()=>{if(compiling)return;const rel=pending;pending=null;compiling=true;if(rel)console.log(`[dev] change ${rel}`);try{if(!rel||rel.startsWith("src/theme")||/resources\/textures\//.test(rel)){syncThemeOnce()}await compile({fatal:false})}finally{compiling=false;if(pending)queue(pending)}};for(const dir of watchDirs){const abs=path.join(projectRoot,dir);fs.watch(abs,{recursive:true},(_event,file)=>{if(shouldIgnoreWatch(file))return;const rel=path.join(dir,file||"").replace(/\\/g,"/");queue(rel)})}const themeName=detectCurrentTheme(projectRoot,{sourceDir:"src/theme",entryFile:"src/theme/index.ts"});const themeAssetsDir=themeName?path.join(projectRoot,"src/theme",themeName,"assets"):"";if(themeAssetsDir&&fs.existsSync(themeAssetsDir)){fs.watch(themeAssetsDir,()=>{queue(path.join("src/theme",themeName,"assets"))});let stamp=themeAssetStamp(themeAssetsDir);setInterval(()=>{const next=themeAssetStamp(themeAssetsDir);if(next!==stamp){stamp=next;queue(path.join("src/theme",themeName,"assets"))}},1000)}}function holdUntilExit(child,proxy){const stop=()=>{if(child&&child.exitCode==null){child.kill("SIGTERM")}if(proxy&&proxy.close){Promise.resolve(proxy.close()).finally(()=>process.exit(0));return}process.exit(0)};process.on("SIGINT",stop);process.on("SIGTERM",stop);if(child){child.on("exit",(code,signal)=>{console.log(`[dev] cocos CLI exited (${signal||code})`);if(proxy&&proxy.close){Promise.resolve(proxy.close()).finally(()=>process.exit(code==null?1:code));return}process.exit(code==null?1:code)});return}console.log("[dev] CLI already running; Ctrl+C leaves it open");setInterval(()=>{},60*1000)}async function warmPreviewAfterReady(previewPort){// Cold sandbox: library/ is empty until assets-refresh; skipping compile leaves JSON unpack hooked but empty → 100+ UUID requests.
|
|
4
|
+
*/async function runCocosDev(opts={}){const projectRoot=opts.projectRoot||process.cwd();const port=Number(opts.port||process.env.PREVIEW_PORT||3000);const timeoutMs=Number(opts.timeoutMs||process.env.DEV_TIMEOUT_MS||180000);const debounceMs=Number(opts.debounceMs||process.env.DEV_DEBOUNCE_MS||400);const cmd=opts.cmd||process.argv[2]||"start";const scene=resolvePreviewScene(projectRoot,opts.scene);const previewUrl=scene?`http://127.0.0.1:${port}/?scene=${encodeURIComponent(scene)}`:`http://127.0.0.1:${port}/`;const watchDirs=["assets","src/theme"].filter(dir=>fs.existsSync(path.join(projectRoot,dir)));function previewUrlFor(previewPort){return scene?`http://127.0.0.1:${previewPort}/?scene=${encodeURIComponent(scene)}`:`http://127.0.0.1:${previewPort}/`}async function isPreviewUpOn(previewPort){try{const res=await request(previewUrlFor(previewPort),{timeout:1500});return res.status>0&&res.status<500}catch{return false}}async function isPreviewUp(){return isPreviewUpOn(port)}async function waitForPreviewOn(previewPort){const url=previewUrlFor(previewPort);const start=Date.now();let lastLog=0;while(Date.now()-start<timeoutMs){if(await isPreviewUpOn(previewPort))return;if(Date.now()-lastLog>=5000){console.log(`[dev] waiting for ${url}`);lastLog=Date.now()}await sleep(1000)}throw new Error(`preview not ready after ${timeoutMs}ms: ${url}`)}function printBanner(reused){console.log(`[dev] ${reused?"reusing cocos CLI":"cocos CLI ready"}`);console.log(`[dev] iframe → ${previewUrl}`);console.log(`[dev] mcp → http://127.0.0.1:${port}/mcp`);console.log(`[dev] watch ${watchDirs.map(d=>`${d}/`).join(" ")}`)}function syncThemeOnce(){try{const result=syncGeneratedTheme(projectRoot);if(!result){console.log("[dev] no theme entry; skip generated module");return false}console.log(`[dev] synced theme ${result.themeName}${result.changed===false?" (unchanged)":""}`);return result.changed!==false}catch(syncErr){console.warn(`[dev] theme sync skipped: ${syncErr.message}`);return false}}async function compile({fatal=true,previewPort=port}={}){const origin=previewUrlFor(previewPort);if(!(await isPreviewUpOn(previewPort))){console.error(`[dev] preview is down: ${origin}`);if(fatal){console.error("[dev] Run npm run dev first.");process.exit(1)}return}console.log(`[dev] assets-refresh db://assets`);try{const res=await refreshAssets(previewPort,"db://assets");const err=res.parsed&&(res.parsed.error||res.parsed.result&&res.parsed.result.isError);console.log(`[dev] ${res.status}${err?" error":""}`);if(res.status===404){console.error("[dev] /mcp \u4E0D\u5B58\u5728\u3002\u8BF7\u505C\u6389\u5F53\u524D\u9884\u89C8\uFF0C\u91CD\u65B0 npm run dev\uFF08\u4F1A\u8D77 start-mcp-server\uFF09")}if((res.status>=400||err)&&fatal)process.exit(1)}catch(err){console.error(`[dev] compile failed: ${err.message}`);if(fatal)process.exit(1)}await bundlePreviewAssets({previewPort})}async function bundlePreviewAssets({previewPort=port}={}){bundlePreviewScripts();try{const result=await packPreviewJson(projectRoot,{previewOrigin:`http://127.0.0.1:${previewPort}`});if(!result)return;const kb=Math.max(1,Math.round(result.bytes/1024));console.log(`[dev] preview json ${result.files} + ${result.bins||0} bin + ${result.audios||0} audio → 1 pack (${kb} kB)`);markPreviewMergeReady(projectRoot)}catch(err){console.warn(`[dev] preview json pack skipped: ${err.message}`)}}function bundlePreviewScripts(){try{const result=concatPreviewScripts(projectRoot);if(!result)return;const kb=Math.max(1,Math.round(result.bytes/1024));console.log(`[dev] preview scripts ${result.modules} → 1 bundle (${kb} kB)`)}catch(err){console.warn(`[dev] preview script concat skipped: ${err.message}`)}}function watchAndCompile(){let timer=null;let compiling=false;let pending=null;const queue=rel=>{pending=rel;if(timer)clearTimeout(timer);timer=setTimeout(()=>{timer=null;run()},debounceMs)};const run=async()=>{if(compiling)return;const rel=pending;pending=null;compiling=true;if(rel)console.log(`[dev] change ${rel}`);try{if(!rel||rel.startsWith("src/theme")||/resources\/textures\//.test(rel)){syncThemeOnce()}await compile({fatal:false})}finally{compiling=false;if(pending)queue(pending)}};for(const dir of watchDirs){const abs=path.join(projectRoot,dir);fs.watch(abs,{recursive:true},(_event,file)=>{if(shouldIgnoreWatch(file))return;const rel=path.join(dir,file||"").replace(/\\/g,"/");queue(rel)})}const themeName=detectCurrentTheme(projectRoot,{sourceDir:"src/theme",entryFile:"src/theme/index.ts"});const themeAssetsDir=themeName?path.join(projectRoot,"src/theme",themeName,"assets"):"";if(themeAssetsDir&&fs.existsSync(themeAssetsDir)){fs.watch(themeAssetsDir,()=>{queue(path.join("src/theme",themeName,"assets"))});let stamp=themeAssetStamp(themeAssetsDir);setInterval(()=>{const next=themeAssetStamp(themeAssetsDir);if(next!==stamp){stamp=next;queue(path.join("src/theme",themeName,"assets"))}},1000)}}function holdUntilExit(child,proxy){const stop=()=>{if(child&&child.exitCode==null){child.kill("SIGTERM")}if(proxy&&proxy.close){Promise.resolve(proxy.close()).finally(()=>process.exit(0));return}process.exit(0)};process.on("SIGINT",stop);process.on("SIGTERM",stop);if(child){child.on("exit",(code,signal)=>{console.log(`[dev] cocos CLI exited (${signal||code})`);if(proxy&&proxy.close){Promise.resolve(proxy.close()).finally(()=>process.exit(code==null?1:code));return}process.exit(code==null?1:code)});return}console.log("[dev] CLI already running; Ctrl+C leaves it open");setInterval(()=>{},60*1000)}async function warmPreviewAfterReady(previewPort){// Cold sandbox: library/ is empty until assets-refresh; skipping compile leaves JSON unpack hooked but empty → 100+ UUID requests.
|
|
5
5
|
syncThemeOnce();await compile({fatal:false,previewPort})}async function start(){clearPreviewMergeMarker(projectRoot);if(await isPreviewUp()){console.warn("[dev] reusing CLI without cache proxy; restart npm run dev to enable long-cache");printBanner(true);await warmPreviewAfterReady(port);watchAndCompile();holdUntilExit(null);return}if(needsXvfb()){console.log("[dev] linux, wrapping xvfb-run -a")}const cliPort=await findFreePort(port);const args=["--no-interactive","start-mcp-server","--project",projectRoot,"--port",String(cliPort)];if(scene){console.log(`[dev] start scene ${scene}`)}console.log(`[dev] cocos ${args.join(" ")}`);const child=spawnCocos(args,{cwd:projectRoot,env:process.env,stdio:"inherit"});child.on("error",err=>{console.error(err);process.exit(1)});let proxy;try{await waitForPreviewOn(cliPort);console.log(`[dev] cocos CLI listening on ${cliPort} (public ${port} still closed)`);await warmPreviewAfterReady(cliPort);proxy=await startPreviewProxy({listenPort:port,targetPort:cliPort});console.log(`[dev] cache proxy ${port} → CLI ${cliPort} (preview merge ready)`)}catch(err){console.error(`[dev] ${err.message}`);child.kill("SIGTERM");if(proxy&&proxy.close)await proxy.close();process.exit(1)}printBanner(false);watchAndCompile();holdUntilExit(child,proxy)}killSocialShareForDev(projectRoot);if(cmd==="compile"||cmd==="refresh"){await compile();return}if(cmd==="start"||cmd==="dev"){await start();return}console.error(`[dev] unknown command: ${cmd}`);console.error("[dev] usage: playable-scripts dev [start|compile]");process.exit(1)}module.exports={runCocosDev,previewMergeMarkerPath,clearPreviewMergeMarker,markPreviewMergeReady,PREVIEW_MERGE_MARKER};
|
|
@@ -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}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
|
+
"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 hasBin(uuid){return!!(uuid&&pack&&pack.bins&&Object.prototype.hasOwnProperty.call(pack.bins,uuid))}function hasAudio(uuid){return!!(uuid&&pack&&pack.audios&&Object.prototype.hasOwnProperty.call(pack.audios,uuid))}function rawFromUrl(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})(?:@[^.\\/?]+)?\.(bin|cconb|mp3)(?:\?|$)/i);return match?{uuid:match[1].toLowerCase(),ext:match[2].toLowerCase()}:null}function lookup(url){if(!pack)return null;var href=String(url||"");var extUuid=extnameUuidFromUrl(href);if(extUuid){if(hasBin(extUuid))return".cconb";if(hasAsset(extUuid))return".json";return null}var raw=rawFromUrl(href);if(raw&&(raw.ext==="bin"||raw.ext==="cconb")&&hasBin(raw.uuid)){return{__pcRaw:pack.bins[raw.uuid]}}if(raw&&raw.ext==="mp3"&&hasAudio(raw.uuid)){return{__pcRaw:pack.audios[raw.uuid]}}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 decodeRaw(b64){var binary=atob(b64);var bytes=new Uint8Array(binary.length);for(var i=0;i<binary.length;i++)bytes[i]=binary.charCodeAt(i);return bytes}function serve(xhr,data){if(data&&typeof data==="object"&&data.__pcRaw){var bytes=decodeRaw(data.__pcRaw);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(){if(xhr.responseType==="blob"&&typeof Blob==="function")return new Blob([bytes]);return bytes.buffer}});Object.defineProperty(xhr,"responseText",{configurable:true,get:function(){return""}});if(typeof xhr.onreadystatechange==="function")xhr.onreadystatechange();if(typeof xhr.onload==="function")xhr.onload();return}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){if(data&&typeof data==="object"&&data.__pcRaw){return Promise.resolve(new Response(decodeRaw(data.__pcRaw),{status:200,headers:{"Content-Type":"application/octet-stream"}}))}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:{},bins:{},audios:{},configs:{}};if(!pack.bins)pack.bins={};if(!pack.audios)pack.audios={};flush()};req.onerror=function(){pack={assets:{},bins:{},audios:{},configs:{}};flush()};req.send(null)})();/* playcraft-json-pack:end */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
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
|
|
3
|
-
* Pack preview UUID JSON
|
|
4
|
-
* from system.js (before builtin assets load).
|
|
5
|
-
* builder-only and uses a compiled format preview cannot feed.
|
|
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
|
|
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 findLibraryFile(uuid,dirs,ext){const id=String(uuid).split("@")[0];const name=ext===".json"?`${uuid}${ext}`:`${id}${ext}`;const prefix=(ext===".json"?String(uuid):id).slice(0,2);for(const dir of dirs){const abs=path.join(dir,prefix,name);if(fs.existsSync(abs))return abs}return""}function readBase64(abs){try{if(fs.statSync(abs).size>FILE_MAX_BYTES)return"";return fs.readFileSync(abs).toString("base64")}catch{return""}}function storeRaw(map,uuid,body){const key=String(uuid).toLowerCase();map[key]=body;const base=key.split("@")[0];if(base!==key)map[base]=body}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 bins={};const audios={};const queue=[...seeds];const seen=new Set(seeds);while(queue.length){const uuid=queue.pop();const abs=findLibraryFile(uuid,dirs,".json");const audioPath=findLibraryFile(uuid,dirs,".mp3");const audio=audioPath?readBase64(audioPath):"";if(audio)storeRaw(audios,uuid,audio);if(!abs){const binPath=findLibraryFile(uuid,dirs,".bin");const bin=binPath?readBase64(binPath):"";if(bin)storeRaw(bins,uuid,bin);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,bins,audios}}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}}/**
|
|
3
|
+
* Pack preview UUID JSON, CCON .bin clips, referenced mp3, and bundle configs
|
|
4
|
+
* into one file and intercept XHR from system.js (before builtin assets load).
|
|
5
|
+
* Official merge_all_json is builder-only and uses a compiled format preview cannot feed.
|
|
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 closed=closeUuidJson(seeds,dirs);const assets=closed.assets;await fillAssetsFromOrigin(assets,seeds,{...options,bundleConfigs:configs});const pack={assets,bins:closed.bins,audios:closed.audios,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,bins:new Set(Object.keys(closed.bins).map(key=>key.split("@")[0])).size,audios:new Set(Object.keys(closed.audios).map(key=>key.split("@")[0])).size,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");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,
|
|
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
|
+
"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";const anim="aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee";const audio="bbbbbbbb-cccc-4ddd-8eee-ffffffffffff";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},clip:{__uuid__:anim},audio:{__uuid__:audio}});writeJson(path.join(root,"library"),extra,{name:"extra"});const binAbs=path.join(root,"library",anim.slice(0,2),`${anim}.bin`);fs.mkdirSync(path.dirname(binAbs),{recursive:true});fs.writeFileSync(binAbs,Buffer.from([1,2,3,4,5]));const audioAbs=path.join(root,"library",audio.slice(0,2),`${audio}.mp3`);fs.mkdirSync(path.dirname(audioAbs),{recursive:true});fs.writeFileSync(audioAbs,Buffer.from("mp3-bytes"));writeJson(path.join(root,"library"),audio,{name:"clip"});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,4);assert.equal(result.bins,1);assert.equal(result.audios,1);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");assert.equal(Buffer.from(pack.bins[anim],"base64").toString("hex"),"0102030405");assert.equal(Buffer.from(pack.audios[audio],"base64").toString(),"mp3-bytes");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");const clipExt=new FakeXHR;clipExt.responseType="";let clipExtLoaded=null;clipExt.onload=function(){clipExtLoaded=clipExt.response};XMLHttpRequest.prototype.open.call(clipExt,"GET",`/query-extname/${anim}`);XMLHttpRequest.prototype.send.call(clipExt,null);assert.equal(clipExt.__network,false);assert.equal(clipExtLoaded,".cconb");const clip=new FakeXHR;clip.responseType="arraybuffer";let clipLoaded=null;clip.onload=function(){clipLoaded=clip.response};XMLHttpRequest.prototype.open.call(clip,"GET",`https://host/api/sandbox-preview/assets/sid/slot/assets/main/import/aa/${anim}.bin`);XMLHttpRequest.prototype.send.call(clip,null);assert.equal(clip.__network,false);assert.equal(Buffer.from(clipLoaded).toString("hex"),"0102030405");const sound=new FakeXHR;sound.responseType="arraybuffer";let soundLoaded=null;sound.onload=function(){soundLoaded=sound.response};XMLHttpRequest.prototype.open.call(sound,"GET",`https://host/api/sandbox-preview/assets/sid/slot/assets/main/native/bb/${audio}.mp3`);XMLHttpRequest.prototype.send.call(sound,null);assert.equal(sound.__network,false);assert.equal(Buffer.from(soundLoaded).toString(),"mp3-bytes");delete global.XMLHttpRequest;delete global.performance;delete global.document;if(originalFetch)globalThis.fetch=originalFetch;else delete globalThis.fetch})});
|