@playcraft/devkit 1.0.31-beta.2 → 1.0.31-beta.4

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,3 +1,3 @@
1
- "use strict";const fs=require("fs");const http=require("http");const path=require("path");const{spawnCocos,needsXvfb}=require("./cli.js");const{syncGeneratedTheme}=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");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")}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"}/**
1
+ "use strict";const fs=require("fs");const http=require("http");const path=require("path");const{spawnCocos,needsXvfb}=require("./cli.js");const{syncGeneratedTheme}=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")}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"}/**
2
2
  * 常驻官方 cocos CLI 预览:start-mcp-server(同一端口既有 / 也有 /mcp)。
3
- */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)));async function isPreviewUp(){try{const res=await request(previewUrl,{timeout:1500});return res.status>0&&res.status<500}catch{return false}}async function waitForPreview(){const start=Date.now();let lastLog=0;while(Date.now()-start<timeoutMs){if(await isPreviewUp())return;if(Date.now()-lastLog>=5000){console.log(`[dev] waiting for ${previewUrl}`);lastLog=Date.now()}await sleep(1000)}throw new Error(`preview not ready after ${timeoutMs}ms: ${previewUrl}`)}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}={}){if(!(await isPreviewUp())){console.error(`[dev] preview is down: ${previewUrl}`);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(port,"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()}async function bundlePreviewAssets(){bundlePreviewScripts();try{const result=await packPreviewJson(projectRoot,{previewOrigin:`http://127.0.0.1:${port}`});if(!result)return;const kb=Math.max(1,Math.round(result.bytes/1024));console.log(`[dev] preview json ${result.files} → 1 pack (${kb} kB)`)}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")){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)})}}function holdUntilExit(child){const stop=()=>{if(child&&child.exitCode==null){child.kill("SIGTERM")}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})`);process.exit(code==null?1:code)});return}console.log("[dev] CLI already running; Ctrl+C leaves it open");setInterval(()=>{},60*1000)}async function start(){if(await isPreviewUp()){printBanner(true);const synced=syncThemeOnce();if(synced)await compile({fatal:false});else await bundlePreviewAssets();watchAndCompile();holdUntilExit(null);return}if(needsXvfb()){console.log("[dev] linux, wrapping xvfb-run -a")}const args=["--no-interactive","start-mcp-server","--project",projectRoot,"--port",String(port)];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)});try{await waitForPreview()}catch(err){console.error(`[dev] ${err.message}`);child.kill("SIGTERM");process.exit(1)}printBanner(false);const synced=syncThemeOnce();if(synced)await compile({fatal:false});else await bundlePreviewAssets();watchAndCompile();holdUntilExit(child)}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};
3
+ */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)));async function isPreviewUp(){try{const res=await request(previewUrl,{timeout:1500});return res.status>0&&res.status<500}catch{return false}}async function waitForPreview(){const start=Date.now();let lastLog=0;while(Date.now()-start<timeoutMs){if(await isPreviewUp())return;if(Date.now()-lastLog>=5000){console.log(`[dev] waiting for ${previewUrl}`);lastLog=Date.now()}await sleep(1000)}throw new Error(`preview not ready after ${timeoutMs}ms: ${previewUrl}`)}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}={}){if(!(await isPreviewUp())){console.error(`[dev] preview is down: ${previewUrl}`);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(port,"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()}async function bundlePreviewAssets(){bundlePreviewScripts();try{const result=await packPreviewJson(projectRoot,{previewOrigin:`http://127.0.0.1:${port}`});if(!result)return;const kb=Math.max(1,Math.round(result.bytes/1024));console.log(`[dev] preview json ${result.files} → 1 pack (${kb} kB)`)}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")){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)})}}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 start(){if(await isPreviewUp()){console.warn("[dev] reusing CLI without cache proxy; restart npm run dev to enable long-cache");printBanner(true);const synced=syncThemeOnce();if(synced)await compile({fatal:false});else await bundlePreviewAssets();watchAndCompile();holdUntilExit(null);return}if(needsXvfb()){console.log("[dev] linux, wrapping xvfb-run -a")}const cliPort=await findFreePort(port);let proxy;try{proxy=await startPreviewProxy({listenPort:port,targetPort:cliPort})}catch(err){console.error(`[dev] cache proxy failed: ${err.message}`);process.exit(1)}console.log(`[dev] cache proxy ${port} → CLI ${cliPort}`);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)});try{await waitForPreview()}catch(err){console.error(`[dev] ${err.message}`);child.kill("SIGTERM");if(proxy&&proxy.close)await proxy.close();process.exit(1)}printBanner(false);const synced=syncThemeOnce();if(synced)await compile({fatal:false});else await bundlePreviewAssets();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};
@@ -0,0 +1,5 @@
1
+ "use strict";const IMMUTABLE="public, max-age=31536000, immutable";const REVALIDATE="public, max-age=0, must-revalidate";const NO_STORE="no-store";function pathOnly(url){return String(url||"").split("?")[0]}function hasContentHash(url){return /[?&]h=[a-f0-9]{6,}/i.test(String(url||""))}/**
2
+ * Cache-Control for CLI preview. Engine / static never change during a remix
3
+ * session; hashed playcraft artifacts are immutable until the next compile.
4
+ * HTML / settings / MCP stay no-store.
5
+ */function cacheControlFor(url){const pathname=pathOnly(url);if(!pathname||pathname==="/")return NO_STORE;if(pathname.endsWith(".html"))return NO_STORE;if(pathname==="/preview/settings.js")return NO_STORE;if(pathname.startsWith("/mcp"))return NO_STORE;if(pathname.startsWith("/console-log"))return NO_STORE;if(pathname.startsWith("/socket.io/")&&!pathname.endsWith(".js"))return null;if(/^\/scene\/.+\.json$/.test(pathname))return NO_STORE;if(pathname.startsWith("/scripting/engine-dist/"))return IMMUTABLE;if(pathname.startsWith("/static/web/"))return IMMUTABLE;if(pathname.startsWith("/scripting/engine/"))return IMMUTABLE;if(pathname==="/socket.io/socket.io.js")return IMMUTABLE;if(/\/chunks\/[0-9a-f]{2}\/[0-9a-f]{40}\.js$/.test(pathname))return IMMUTABLE;if(hasContentHash(url)&&(pathname.endsWith("/playcraft-bundle.js")||pathname.endsWith("/playcraft-json-pack.json"))){return IMMUTABLE}return REVALIDATE}module.exports={cacheControlFor,IMMUTABLE,REVALIDATE,NO_STORE};
@@ -0,0 +1 @@
1
+ "use strict";const{describe,it}=require("node:test");const assert=require("node:assert/strict");const{cacheControlFor,IMMUTABLE,REVALIDATE,NO_STORE}=require("./preview-cache.js");describe("cacheControlFor",()=>{it("does not cache html, settings, or mcp",()=>{assert.equal(cacheControlFor("/"),NO_STORE);assert.equal(cacheControlFor("/preview/settings.js"),NO_STORE);assert.equal(cacheControlFor("/mcp"),NO_STORE);assert.equal(cacheControlFor("/scene/0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d.json"),NO_STORE)});it("long-caches engine and static files that do not change",()=>{assert.equal(cacheControlFor("/scripting/engine-dist/bundled/index.js"),IMMUTABLE);assert.equal(cacheControlFor("/static/web/polyfills.bundle.js"),IMMUTABLE);assert.equal(cacheControlFor("/scripting/engine/effect-settings"),IMMUTABLE)});it("long-caches hashed playcraft artifacts and revalidates unhashed ones",()=>{assert.equal(cacheControlFor("/scripting/x/chunks/playcraft-bundle.js?h=abcdef12"),IMMUTABLE);assert.equal(cacheControlFor("/scripting/x/chunks/playcraft-bundle.js"),REVALIDATE);assert.equal(cacheControlFor("/scripting/systemjs/playcraft-json-pack.json?h=abcdef12"),IMMUTABLE)})});
@@ -1,8 +1,8 @@
1
- "use strict";const fs=require("fs");const path=require("path");const ENTRY="cce:/internal/x/prerequisite-imports";const SKIP=new Set(["cce:/internal/x/cc"]);const GLOBAL_DEPS=new Set(["cc","cc/env","cc/userland/macro"]);const BUNDLE_FILE="playcraft-bundle.js";const BUNDLE_URL=`./chunks/${BUNDLE_FILE}`;const REGISTER_RE=/^System\.register\(\s*(\[[\s\S]*?\])\s*,\s*function/;function previewDir(projectRoot){return path.join(projectRoot,"temp/programming/packer-driver/targets/preview")}function normalizeChunkUrl(url){const raw=String(url||"").replace(/\\/g,"/");if(raw.startsWith("./chunks/"))return raw;if(raw.startsWith("chunks/"))return`./${raw}`;return raw}function isChunkUrl(url){return normalizeChunkUrl(url).startsWith("./chunks/")&&normalizeChunkUrl(url).endsWith(".js")}function packChunkName(chunkUrl){return`pack-chunk:${path.basename(normalizeChunkUrl(chunkUrl),".js")}`}function isBundledImportMap(map){const imports=map&&map.imports||{};return imports[ENTRY]===BUNDLE_URL&&Object.keys(imports).length===1}function loadSourceImportMap(dir){const livePath=path.join(dir,"import-map.json");const backupPath=path.join(dir,"import-map.quickpack.json");if(!fs.existsSync(livePath))return null;const live=JSON.parse(fs.readFileSync(livePath,"utf8"));if(isBundledImportMap(live)){if(!fs.existsSync(backupPath))return null;return JSON.parse(fs.readFileSync(backupPath,"utf8"))}fs.writeFileSync(backupPath,`${JSON.stringify(live,null,2)}\n`);return live}function skipChunkUrls(map){const skip=new Set;const imports=map.imports||{};for(const spec of SKIP){if(isChunkUrl(imports[spec]))skip.add(normalizeChunkUrl(imports[spec]))}return skip}function collectChunkUrls(map){const skipUrls=skipChunkUrls(map);const urls=new Set;const queue=[];const add=url=>{if(!isChunkUrl(url))return;const normalized=normalizeChunkUrl(url);if(skipUrls.has(normalized)||urls.has(normalized))return;urls.add(normalized);queue.push(normalized)};const imports=map.imports||{};for(const[spec,url]of Object.entries(imports)){if(SKIP.has(spec))continue;add(url)}while(queue.length){const scopeUrl=queue.shift();for(const target of Object.values((map.scopes||{})[scopeUrl]||{})){add(target)}}return urls}function rewriteDep(dep,chunkUrl,map,nameOf){if(GLOBAL_DEPS.has(dep))return dep;const mapped=((map.scopes||{})[chunkUrl]||{})[dep]||(map.imports||{})[dep];if(!mapped)return dep;if(typeof mapped==="string"&&mapped.startsWith("cce:/internal/x/cc-fu"))return"cc";if(SKIP.has(mapped)||mapped===(map.imports||{})["cce:/internal/x/cc"])return"cc";if(isChunkUrl(mapped))return nameOf(normalizeChunkUrl(mapped));return mapped}function rewriteChunk(source,name,chunkUrl,map,nameOf,anonymous){const stripped=source.replace(/\n\/\/# sourceMappingURL=.*$/m,"");const match=stripped.match(REGISTER_RE);if(!match){throw new Error(`no System.register in ${chunkUrl}`)}const deps=JSON.parse(match[1]).map(dep=>rewriteDep(dep,chunkUrl,map,nameOf));const head=anonymous?`System.register(${JSON.stringify(deps)}, function`:`System.register(${JSON.stringify(name)}, ${JSON.stringify(deps)}, function`;return`${head}${stripped.slice(match[0].length)}`}/**
1
+ "use strict";const crypto=require("crypto");const fs=require("fs");const path=require("path");const ENTRY="cce:/internal/x/prerequisite-imports";const SKIP=new Set(["cce:/internal/x/cc"]);const GLOBAL_DEPS=new Set(["cc","cc/env","cc/userland/macro"]);const BUNDLE_FILE="playcraft-bundle.js";const BUNDLE_URL=`./chunks/${BUNDLE_FILE}`;const REGISTER_RE=/^System\.register\(\s*(\[[\s\S]*?\])\s*,\s*function/;function previewDir(projectRoot){return path.join(projectRoot,"temp/programming/packer-driver/targets/preview")}function normalizeChunkUrl(url){const raw=String(url||"").replace(/\\/g,"/");if(raw.startsWith("./chunks/"))return raw;if(raw.startsWith("chunks/"))return`./${raw}`;return raw}function isChunkUrl(url){return normalizeChunkUrl(url).startsWith("./chunks/")&&normalizeChunkUrl(url).endsWith(".js")}function packChunkName(chunkUrl){return`pack-chunk:${path.basename(normalizeChunkUrl(chunkUrl),".js")}`}function shortHash(buf){return crypto.createHash("sha1").update(buf).digest("hex").slice(0,10)}function isBundledImportMap(map){const imports=map&&map.imports||{};const entry=String(imports[ENTRY]||"").split("?")[0];return entry===BUNDLE_URL&&Object.keys(imports).length===1}function loadSourceImportMap(dir){const livePath=path.join(dir,"import-map.json");const backupPath=path.join(dir,"import-map.quickpack.json");if(!fs.existsSync(livePath))return null;const live=JSON.parse(fs.readFileSync(livePath,"utf8"));if(isBundledImportMap(live)){if(!fs.existsSync(backupPath))return null;return JSON.parse(fs.readFileSync(backupPath,"utf8"))}fs.writeFileSync(backupPath,`${JSON.stringify(live,null,2)}\n`);return live}function skipChunkUrls(map){const skip=new Set;const imports=map.imports||{};for(const spec of SKIP){if(isChunkUrl(imports[spec]))skip.add(normalizeChunkUrl(imports[spec]))}return skip}function collectChunkUrls(map){const skipUrls=skipChunkUrls(map);const urls=new Set;const queue=[];const add=url=>{if(!isChunkUrl(url))return;const normalized=normalizeChunkUrl(url);if(skipUrls.has(normalized)||urls.has(normalized))return;urls.add(normalized);queue.push(normalized)};const imports=map.imports||{};for(const[spec,url]of Object.entries(imports)){if(SKIP.has(spec))continue;add(url)}while(queue.length){const scopeUrl=queue.shift();for(const target of Object.values((map.scopes||{})[scopeUrl]||{})){add(target)}}return urls}function rewriteDep(dep,chunkUrl,map,nameOf){if(GLOBAL_DEPS.has(dep))return dep;const mapped=((map.scopes||{})[chunkUrl]||{})[dep]||(map.imports||{})[dep];if(!mapped)return dep;if(typeof mapped==="string"&&mapped.startsWith("cce:/internal/x/cc-fu"))return"cc";if(SKIP.has(mapped)||mapped===(map.imports||{})["cce:/internal/x/cc"])return"cc";if(isChunkUrl(mapped))return nameOf(normalizeChunkUrl(mapped));return mapped}function rewriteChunk(source,name,chunkUrl,map,nameOf,anonymous){const stripped=source.replace(/\n\/\/# sourceMappingURL=.*$/m,"");const match=stripped.match(REGISTER_RE);if(!match){throw new Error(`no System.register in ${chunkUrl}`)}const deps=JSON.parse(match[1]).map(dep=>rewriteDep(dep,chunkUrl,map,nameOf));const head=anonymous?`System.register(${JSON.stringify(deps)}, function`:`System.register(${JSON.stringify(name)}, ${JSON.stringify(deps)}, function`;return`${head}${stripped.slice(match[0].length)}`}/**
2
2
  * Fold QuickPack's per-file chunks into one named-register bundle and
3
3
  * point prerequisite-imports at it. Original chunks stay on disk for
4
4
  * incremental compile; import-map.quickpack.json keeps the pre-rewrite map.
5
5
  */function concatPreviewScripts(projectRoot){const dir=previewDir(projectRoot);const map=loadSourceImportMap(dir);if(!map)return null;const imports=map.imports||{};const chunkToSpecifier=new Map;for(const[spec,url]of Object.entries(imports)){if(SKIP.has(spec)||!isChunkUrl(url))continue;chunkToSpecifier.set(normalizeChunkUrl(url),spec)}const entryChunk=imports[ENTRY]&&isChunkUrl(imports[ENTRY])?normalizeChunkUrl(imports[ENTRY]):null;const nameOf=chunkUrl=>{if(entryChunk&&chunkUrl===entryChunk)return ENTRY;return chunkToSpecifier.get(chunkUrl)||packChunkName(chunkUrl)};const chunkUrls=[...collectChunkUrls(map)];if(!chunkUrls.length)return null;// Named modules first; anonymous entry last so script-tag onload getRegister()
6
6
  // binds prerequisite-imports (named-register's firstNamedDefine is cleared
7
7
  // before the load event).
8
- const ordered=[];for(const url of chunkUrls){if(url!==entryChunk)ordered.push(url)}if(entryChunk&&chunkUrls.includes(entryChunk))ordered.push(entryChunk);const parts=[];for(const chunkUrl of ordered){const abs=path.join(dir,chunkUrl.replace(/^\.\//,""));if(!fs.existsSync(abs)){throw new Error(`missing chunk ${chunkUrl}`)}parts.push(rewriteChunk(fs.readFileSync(abs,"utf8"),nameOf(chunkUrl),chunkUrl,map,nameOf,chunkUrl===entryChunk))}const bundle=`${parts.join("\n")}\n`;const bundleAbs=path.join(dir,"chunks",BUNDLE_FILE);fs.mkdirSync(path.dirname(bundleAbs),{recursive:true});fs.writeFileSync(bundleAbs,bundle);const bundledMap={imports:{[ENTRY]:BUNDLE_URL}};const tmp=path.join(dir,"import-map.json.tmp");fs.writeFileSync(tmp,`${JSON.stringify(bundledMap)}\n`);fs.renameSync(tmp,path.join(dir,"import-map.json"));return{modules:ordered.length,bytes:Buffer.byteLength(bundle),bundle:BUNDLE_URL}}module.exports={concatPreviewScripts,ENTRY,BUNDLE_URL,BUNDLE_FILE,isBundledImportMap};
8
+ const ordered=[];for(const url of chunkUrls){if(url!==entryChunk)ordered.push(url)}if(entryChunk&&chunkUrls.includes(entryChunk))ordered.push(entryChunk);const parts=[];for(const chunkUrl of ordered){const abs=path.join(dir,chunkUrl.replace(/^\.\//,""));if(!fs.existsSync(abs)){throw new Error(`missing chunk ${chunkUrl}`)}parts.push(rewriteChunk(fs.readFileSync(abs,"utf8"),nameOf(chunkUrl),chunkUrl,map,nameOf,chunkUrl===entryChunk))}const bundle=`${parts.join("\n")}\n`;const bundleAbs=path.join(dir,"chunks",BUNDLE_FILE);fs.mkdirSync(path.dirname(bundleAbs),{recursive:true});fs.writeFileSync(bundleAbs,bundle);const hashedUrl=`${BUNDLE_URL}?h=${shortHash(bundle)}`;const bundledMap={imports:{[ENTRY]:hashedUrl}};const tmp=path.join(dir,"import-map.json.tmp");fs.writeFileSync(tmp,`${JSON.stringify(bundledMap)}\n`);fs.renameSync(tmp,path.join(dir,"import-map.json"));return{modules:ordered.length,bytes:Buffer.byteLength(bundle),bundle:hashedUrl}}module.exports={concatPreviewScripts,ENTRY,BUNDLE_URL,BUNDLE_FILE,isBundledImportMap};
@@ -1 +1 @@
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{concatPreviewScripts,BUNDLE_URL,ENTRY}=require("./preview-concat.js");describe("concatPreviewScripts",()=>{let root;let preview;before(()=>{root=fs.mkdtempSync(path.join(os.tmpdir(),"preview-concat-"));preview=path.join(root,"temp/programming/packer-driver/targets/preview");fs.mkdirSync(path.join(preview,"chunks/aa"),{recursive:true});fs.mkdirSync(path.join(preview,"chunks/bb"),{recursive:true});fs.writeFileSync(path.join(preview,"chunks/aa/aaa111.js"),"System.register([\"__unresolved_0\", \"cc\", \"@playcraft/adsdk/cjs\"], function (_export, _context) {\n return { setters: [], execute() {} };\n});\n//# sourceMappingURL=aaa111.js.map\n");fs.writeFileSync(path.join(preview,"chunks/bb/bbb222.js"),"System.register([], function (_export, _context) {\n return { setters: [], execute() {} };\n});\n");fs.writeFileSync(path.join(preview,"import-map.json"),JSON.stringify({imports:{[ENTRY]:"./chunks/aa/aaa111.js","cce:/internal/x/cc":"./chunks/cc/skip-me.js"},scopes:{"./chunks/aa/aaa111.js":{__unresolved_0:"./chunks/bb/bbb222.js","@playcraft/adsdk/cjs":"./chunks/bb/bbb222.js",cc:"./chunks/cc/skip-me.js"}}}))});after(()=>{fs.rmSync(root,{recursive:true,force:true})});it("writes one named-register bundle and a single import-map entry",()=>{const result=concatPreviewScripts(root);assert.equal(result.modules,2);assert.equal(result.bundle,BUNDLE_URL);const map=JSON.parse(fs.readFileSync(path.join(preview,"import-map.json"),"utf8"));assert.deepEqual(map,{imports:{[ENTRY]:BUNDLE_URL}});const bundle=fs.readFileSync(path.join(preview,"chunks/playcraft-bundle.js"),"utf8");assert.equal(bundle.includes("sourceMappingURL"),false);assert.equal(bundle.includes(`System.register("${ENTRY}"`),false);assert.match(bundle,/System\.register\("pack-chunk:bbb222"/);assert.match(bundle,/System\.register\(\["pack-chunk:bbb222","cc","pack-chunk:bbb222"\], function \(_export/);assert.equal(bundle.includes("\"@playcraft/adsdk/cjs\""),false);const last=bundle.trim().split("System.register").pop();assert.match(last,/^\(\[/);assert.equal(bundle.includes("functionfunction"),false);assert.equal(bundle.includes("cce:/internal/x/cc"),false)});it("is idempotent using the quickpack backup",()=>{const again=concatPreviewScripts(root);assert.equal(again.modules,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{concatPreviewScripts,ENTRY}=require("./preview-concat.js");describe("concatPreviewScripts",()=>{let root;let preview;before(()=>{root=fs.mkdtempSync(path.join(os.tmpdir(),"preview-concat-"));preview=path.join(root,"temp/programming/packer-driver/targets/preview");fs.mkdirSync(path.join(preview,"chunks/aa"),{recursive:true});fs.mkdirSync(path.join(preview,"chunks/bb"),{recursive:true});fs.writeFileSync(path.join(preview,"chunks/aa/aaa111.js"),"System.register([\"__unresolved_0\", \"cc\", \"@playcraft/adsdk/cjs\"], function (_export, _context) {\n return { setters: [], execute() {} };\n});\n//# sourceMappingURL=aaa111.js.map\n");fs.writeFileSync(path.join(preview,"chunks/bb/bbb222.js"),"System.register([], function (_export, _context) {\n return { setters: [], execute() {} };\n});\n");fs.writeFileSync(path.join(preview,"import-map.json"),JSON.stringify({imports:{[ENTRY]:"./chunks/aa/aaa111.js","cce:/internal/x/cc":"./chunks/cc/skip-me.js"},scopes:{"./chunks/aa/aaa111.js":{__unresolved_0:"./chunks/bb/bbb222.js","@playcraft/adsdk/cjs":"./chunks/bb/bbb222.js",cc:"./chunks/cc/skip-me.js"}}}))});after(()=>{fs.rmSync(root,{recursive:true,force:true})});it("writes one named-register bundle and a single import-map entry",()=>{const result=concatPreviewScripts(root);assert.equal(result.modules,2);const map=JSON.parse(fs.readFileSync(path.join(preview,"import-map.json"),"utf8"));assert.match(map.imports[ENTRY],/^\.\/chunks\/playcraft-bundle\.js\?h=[a-f0-9]+$/);assert.equal(result.bundle,map.imports[ENTRY]);const bundle=fs.readFileSync(path.join(preview,"chunks/playcraft-bundle.js"),"utf8");assert.equal(bundle.includes("sourceMappingURL"),false);assert.equal(bundle.includes(`System.register("${ENTRY}"`),false);assert.match(bundle,/System\.register\("pack-chunk:bbb222"/);assert.match(bundle,/System\.register\(\["pack-chunk:bbb222","cc","pack-chunk:bbb222"\], function \(_export/);assert.equal(bundle.includes("\"@playcraft/adsdk/cjs\""),false);const last=bundle.trim().split("System.register").pop();assert.match(last,/^\(\[/);assert.equal(bundle.includes("functionfunction"),false);assert.equal(bundle.includes("cce:/internal/x/cc"),false)});it("is idempotent using the quickpack backup",()=>{const again=concatPreviewScripts(root);assert.equal(again.modules,2)})});
@@ -1 +1 @@
1
- "use strict";/* playcraft-json-pack:start */(function(){var pack=null;var pending=[];var packUrl="/scripting/systemjs/playcraft-json-pack.json";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){packUrl=name.replace(/system\.js(\?.*)?$/,"playcraft-json-pack.json");break}}}catch(err){}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 configNameFromUrl(url){var match=String(url||"").match(/\/(?:assets|remote)\/([^/]+)\/(?:config|cc\.config)\.json(?:\?|$)/i);return match?match[1]:""}function lookup(url){if(!pack)return null;var uuid=uuidFromUrl(url);if(uuid&&pack.assets&&Object.prototype.hasOwnProperty.call(pack.assets,uuid)){return pack.assets[uuid]}var configName=configNameFromUrl(url);if(configName&&pack.configs&&Object.prototype.hasOwnProperty.call(pack.configs,configName)){return pack.configs[configName]}return null}function serve(xhr,data){var text=typeof data==="string"?data:JSON.stringify(data);var json=typeof data==="string"?JSON.parse(data):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"?json: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;XMLHttpRequest.prototype.open=function(method,url){this.__pcUrl=url;return xhrOpen.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){serve(xhr,data);return}xhrSend.call(xhr,body)};if(pack){run();return}pending.push(run)};var req=new XMLHttpRequest;req.open("GET",packUrl,true);req.responseType="json";req.onload=function(){pack=req.response&&typeof req.response==="object"?req.response:{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 packUrl="/scripting/systemjs/playcraft-json-pack.json";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){packUrl=name.replace(/system\.js(\?.*)?$/,"playcraft-json-pack.json");break}}}catch(err){}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 lookup(url){if(!pack)return null;var extUuid=extnameUuidFromUrl(url);if(extUuid){if(pack.assets&&Object.prototype.hasOwnProperty.call(pack.assets,extUuid))return"";return null}var uuid=uuidFromUrl(url);if(uuid&&pack.assets&&Object.prototype.hasOwnProperty.call(pack.assets,uuid)){return pack.assets[uuid]}var configName=configNameFromUrl(url);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;XMLHttpRequest.prototype.open=function(method,url){this.__pcUrl=url;return xhrOpen.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){serve(xhr,data);return}xhrSend.call(xhr,body)};if(pack){run();return}pending.push(run)};var req=new XMLHttpRequest;req.open("GET",packUrl,true);req.responseType="json";req.onload=function(){pack=req.response&&typeof req.response==="object"?req.response:{assets:{},configs:{}};flush()};req.onerror=function(){pack={assets:{},configs:{}};flush()};req.send(null)})();/* playcraft-json-pack:end */
@@ -1,5 +1,5 @@
1
- "use strict";const fs=require("fs");const http=require("http");const https=require("https");const path=require("path");const{resolveCocosBin}=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(){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"))}const bin=resolveCocosBin();const starts=[];if(bin&&path.isAbsolute(bin))starts.push(bin);try{const{execSync}=require("child_process");const which=execSync("command -v cocos",{encoding:"utf8"}).trim();if(which)starts.push(which)}catch{}for(const start of starts){try{let current=fs.realpathSync(start);for(let i=0;i<8;i++){add(path.join(current,"packages/engine/editor/library"));add(path.join(current,"engine/editor/library"));add(path.join(current,"editor/library"));current=path.dirname(current)}}catch{}}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()){if(dir&&fs.existsSync(dir)&&!dirs.includes(dir))dirs.push(dir)}return dirs}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){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/,"")}fs.writeFileSync(systemJsPath,`${hookSource()}${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}}/**
1
+ "use strict";const crypto=require("crypto");const fs=require("fs");const http=require("http");const https=require("https");const path=require("path");const{resolveCocosBin}=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(){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"))}const bin=resolveCocosBin();const starts=[];if(bin&&path.isAbsolute(bin))starts.push(bin);try{const{execSync}=require("child_process");const which=execSync("command -v cocos",{encoding:"utf8"}).trim();if(which)starts.push(which)}catch{}for(const start of starts){try{let current=fs.realpathSync(start);for(let i=0;i<8;i++){add(path.join(current,"packages/engine/editor/library"));add(path.join(current,"engine/editor/library"));add(path.join(current,"editor/library"));current=path.dirname(current)}}catch{}}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()){if(dir&&fs.existsSync(dir)&&!dirs.includes(dir))dirs.push(dir)}return dirs}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){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(packHash){hook=hook.replace(/\/scripting\/systemjs\/playcraft-json-pack\.json/g,`/scripting/systemjs/playcraft-json-pack.json?h=${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
2
  * Pack preview UUID JSON + bundle configs into one file and intercept XHR
3
3
  * from system.js (before builtin assets load). Official merge_all_json is
4
4
  * 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 hooked=injectJsonPackHook(projectRoot);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};
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};
@@ -1,2 +1,2 @@
1
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(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.XMLHttpRequest=FakeXHR;// eslint-disable-next-line no-eval
2
- eval(hook.replace(HOOK_START,"").replace("/* playcraft-json-pack:end */",""));const packXhr=instances[0];packXhr.response=pack;packXhr.onload();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");delete global.XMLHttpRequest;delete global.performance})});
2
+ eval(hook.replace(HOOK_START,"").replace("/* playcraft-json-pack:end */",""));const packXhr=instances[0];packXhr.response=pack;packXhr.onload();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,"");delete global.XMLHttpRequest;delete global.performance})});
@@ -0,0 +1 @@
1
+ "use strict";const http=require("http");const net=require("net");const{cacheControlFor}=require("./preview-cache.js");const HOP=new Set(["connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]);function findFreePort(exclude=0){return new Promise((resolve,reject)=>{const tryListen=()=>{const server=net.createServer();server.unref();server.once("error",()=>tryListen());server.listen(0,"127.0.0.1",()=>{const addr=server.address();const port=addr&&addr.port;server.close(err=>{if(err){reject(err);return}if(!port||port===exclude)tryListen();else resolve(port)})})};tryListen()})}function copyHeaders(src){const out={};for(const[key,value]of Object.entries(src||{})){if(HOP.has(key.toLowerCase()))continue;if(value==null)continue;out[key]=value}return out}function applyCacheControl(url,headers){const next=cacheControlFor(url);if(!next)return headers;const out={...headers};out["cache-control"]=next;return out}function startPreviewProxy({listenPort,targetPort}){const server=http.createServer((req,res)=>{const headers=copyHeaders(req.headers);headers.host=`127.0.0.1:${targetPort}`;const proxyReq=http.request({hostname:"127.0.0.1",port:targetPort,path:req.url,method:req.method,headers},proxyRes=>{const outHeaders=applyCacheControl(req.url,copyHeaders(proxyRes.headers));res.writeHead(proxyRes.statusCode||502,outHeaders);proxyRes.pipe(res)});proxyReq.on("error",()=>{if(!res.headersSent){res.writeHead(502)}res.end("preview upstream down")});req.pipe(proxyReq)});server.on("upgrade",(req,clientSocket,head)=>{const headers=copyHeaders(req.headers);headers.host=`127.0.0.1:${targetPort}`;headers.connection="upgrade";headers.upgrade=req.headers.upgrade||"websocket";const proxyReq=http.request({hostname:"127.0.0.1",port:targetPort,path:req.url,method:req.method,headers});proxyReq.on("upgrade",(proxyRes,proxySocket,proxyHead)=>{const lines=["HTTP/1.1 101 Switching Protocols"];for(const[key,value]of Object.entries(proxyRes.headers)){if(value==null)continue;const text=Array.isArray(value)?value.join(", "):String(value);lines.push(`${key}: ${text}`)}clientSocket.write(`${lines.join("\r\n")}\r\n\r\n`);if(head&&head.length)proxySocket.write(head);if(proxyHead&&proxyHead.length)clientSocket.write(proxyHead);proxySocket.pipe(clientSocket);clientSocket.pipe(proxySocket)});proxyReq.on("error",()=>clientSocket.destroy());proxyReq.end()});return new Promise((resolve,reject)=>{server.once("error",reject);server.listen(listenPort,"127.0.0.1",()=>{resolve({close:()=>new Promise((done,fail)=>{server.close(err=>err?fail(err):done())})})})})}module.exports={findFreePort,startPreviewProxy,applyCacheControl};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcraft/devkit",
3
- "version": "1.0.31-beta.2",
3
+ "version": "1.0.31-beta.4",
4
4
  "description": "HTML5 Playable Ads 构建工具,支持多广告渠道打包",
5
5
  "main": "core/index.js",
6
6
  "module": "core/index.js",