@playcraft/devkit 1.0.31-beta.8 → 1.0.31-beta.9
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,4 +1,4 @@
|
|
|
1
|
-
"use strict";const fs=require("fs");const path=require("path");/** Overlay of /opt/cocos-cli. Not a script cache; recreating it on Linux is expensive. */const KEEP_TEMP_DIRS=new Set(["cocos-cli"]);/** Cocos packer-driver / asset-db recreate these immediately after a clean. */const WRITABLE_WORKDIR_RELS=["library","temp","temp/programming","temp/programming/packer-driver","temp/programming/packer-driver/logs","temp/builder","build"];function exists(p){try{return Boolean(p)&&fs.existsSync(p)}catch{return false}}function chmodTreeWritable(dir){try{const st=fs.lstatSync(dir);if(st.isSymbolicLink())return;fs.chmodSync(dir,st.mode|128);if(!st.isDirectory())return;for(const name of fs.readdirSync(dir)){chmodTreeWritable(path.join(dir,name))}}catch{// best-effort; rmSync may still fail and we want that error
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");/** Overlay of /opt/cocos-cli. Not a script cache; recreating it on Linux is expensive. */const KEEP_TEMP_DIRS=new Set(["cocos-cli"]);/** Preview merge lives here; wiping it breaks npm run dev until re-bundle (many UUID .json requests). */const PREVIEW_CACHE_RELS=["temp/programming/preview","temp/programming/packer-driver/targets/preview"];/** Other temp/* dirs safe to drop before engine build. */const TEMP_CACHE_DIRS=new Set(["asset-db","builder","declarations","logs","writablePath"]);/** Cocos packer-driver / asset-db recreate these immediately after a clean. */const WRITABLE_WORKDIR_RELS=["library","temp","temp/programming","temp/programming/packer-driver","temp/programming/packer-driver/logs","temp/builder","build"];function exists(p){try{return Boolean(p)&&fs.existsSync(p)}catch{return false}}function chmodTreeWritable(dir){try{const st=fs.lstatSync(dir);if(st.isSymbolicLink())return;fs.chmodSync(dir,st.mode|128);if(!st.isDirectory())return;for(const name of fs.readdirSync(dir)){chmodTreeWritable(path.join(dir,name))}}catch{// best-effort; rmSync may still fail and we want that error
|
|
2
2
|
}}function rmTree(dir){if(!exists(dir))return false;chmodTreeWritable(dir);fs.rmSync(dir,{recursive:true,force:true});return true}/**
|
|
3
3
|
* Drop Creator / cocos-cli project caches so packer-driver does not reuse
|
|
4
4
|
* import-map + chunks that still point at another machine's paths.
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* Keeps `temp/cocos-cli` (writable CLI overlay) unless keepCliOverlay is false.
|
|
7
7
|
*
|
|
8
8
|
* @returns {string[]} relative paths that were removed
|
|
9
|
-
*/function cleanCocosProjectCaches(projectRoot,options={}){const keepOverlay=options.keepCliOverlay!==false;const removed=[];const library=path.join(projectRoot,"library");if(rmTree(library))removed.push("library");const tempRoot=path.join(projectRoot,"temp");if(!exists(tempRoot))return removed;if(!keepOverlay){if(rmTree(tempRoot))removed.push("temp");return removed}for(const name of fs.readdirSync(tempRoot)){if(KEEP_TEMP_DIRS.has(name))continue;if(rmTree(path.join(tempRoot,name)))removed.push(`temp/${name}`)}return removed}function ensureDir(dir){try{const st=fs.lstatSync(dir);if(st.isSymbolicLink()||!st.isDirectory()){fs.rmSync(dir,{recursive:true,force:true})}}catch(err){if(err.code!=="ENOENT")throw err}fs.mkdirSync(dir,{recursive:true,mode:511});try{fs.chmodSync(dir,511)}catch{// chmod may fail on some mounts; mkdir succeeding is enough if we own the dir.
|
|
9
|
+
*/function shouldWipePreviewCaches(options={},env=process.env){if(options.cleanPreviewCache===true)return true;if(options.cleanPreviewCache===false)return false;return env.PLAYABLE_CLEAN_PREVIEW_CACHE==="1"}function cleanProgrammingBuildCaches(projectRoot,removed,wipePreview){const programming=path.join(projectRoot,"temp","programming");if(!exists(programming))return;if(wipePreview){for(const rel of PREVIEW_CACHE_RELS){if(rmTree(path.join(projectRoot,rel)))removed.push(rel)}if(rmTree(programming)){removed.push("temp/programming");return}}const editor=path.join(programming,"packer-driver","targets","editor");if(rmTree(editor))removed.push("temp/programming/packer-driver/targets/editor");const logs=path.join(programming,"packer-driver","logs");if(rmTree(logs))removed.push("temp/programming/packer-driver/logs")}function cleanCocosProjectCaches(projectRoot,options={}){const keepOverlay=options.keepCliOverlay!==false;const wipePreview=shouldWipePreviewCaches(options,process.env);const removed=[];const library=path.join(projectRoot,"library");if(rmTree(library))removed.push("library");const tempRoot=path.join(projectRoot,"temp");if(!exists(tempRoot))return removed;if(!keepOverlay){if(rmTree(tempRoot))removed.push("temp");return removed}for(const name of fs.readdirSync(tempRoot)){if(KEEP_TEMP_DIRS.has(name))continue;if(name==="programming"){cleanProgrammingBuildCaches(projectRoot,removed,wipePreview);continue}if(TEMP_CACHE_DIRS.has(name)&&rmTree(path.join(tempRoot,name))){removed.push(`temp/${name}`)}}return removed}function ensureDir(dir){try{const st=fs.lstatSync(dir);if(st.isSymbolicLink()||!st.isDirectory()){fs.rmSync(dir,{recursive:true,force:true})}}catch(err){if(err.code!=="ENOENT")throw err}fs.mkdirSync(dir,{recursive:true,mode:511});try{fs.chmodSync(dir,511)}catch{// chmod may fail on some mounts; mkdir succeeding is enough if we own the dir.
|
|
10
10
|
}}/**
|
|
11
11
|
* Recreate the dirs cocos-cli writes on startup (winston logs, library).
|
|
12
12
|
* A leftover read-only `temp/programming` from git / a previous root-owned
|
|
13
13
|
* overlay makes `mkdir logs` throw EACCES.
|
|
14
|
-
*/function ensureWritableCocosWorkdirs(projectRoot){const created=[];for(const rel of WRITABLE_WORKDIR_RELS){const dir=path.join(projectRoot,rel);ensureDir(dir);created.push(rel)}const probe=path.join(projectRoot,"temp","programming","packer-driver","logs",`.write-probe-${process.pid}`);try{fs.writeFileSync(probe,"ok");fs.unlinkSync(probe)}catch(err){throw new Error(`[cocos] ${path.join("temp","programming","packer-driver","logs")} is not writable (${err.code||err.message}). `+"In the sandbox image, chown the remix project to the build user and chmod u+rwX temp library build. "+"Do not chmod /opt/cocos-cli.")}return created}function shouldCleanCocosCache(buildOptions={},env=process.env){if(env.PLAYABLE_KEEP_COCOS_CACHE==="1")return false;if(env.PLAYABLE_CLEAN_COCOS_CACHE==="1")return true;const cocos=buildOptions.cocos||{};if(cocos.cleanCache===false)return false;if(cocos.cleanCache===true)return true;return Boolean(buildOptions.cleanCocosCache)}module.exports={KEEP_TEMP_DIRS,WRITABLE_WORKDIR_RELS,cleanCocosProjectCaches,ensureWritableCocosWorkdirs,shouldCleanCocosCache};
|
|
14
|
+
*/function ensureWritableCocosWorkdirs(projectRoot){const created=[];for(const rel of WRITABLE_WORKDIR_RELS){const dir=path.join(projectRoot,rel);ensureDir(dir);created.push(rel)}const probe=path.join(projectRoot,"temp","programming","packer-driver","logs",`.write-probe-${process.pid}`);try{fs.writeFileSync(probe,"ok");fs.unlinkSync(probe)}catch(err){throw new Error(`[cocos] ${path.join("temp","programming","packer-driver","logs")} is not writable (${err.code||err.message}). `+"In the sandbox image, chown the remix project to the build user and chmod u+rwX temp library build. "+"Do not chmod /opt/cocos-cli.")}return created}function shouldCleanCocosCache(buildOptions={},env=process.env){if(env.PLAYABLE_KEEP_COCOS_CACHE==="1")return false;if(env.PLAYABLE_CLEAN_COCOS_CACHE==="1")return true;const cocos=buildOptions.cocos||{};if(cocos.cleanCache===false)return false;if(cocos.cleanCache===true)return true;return Boolean(buildOptions.cleanCocosCache)}module.exports={KEEP_TEMP_DIRS,PREVIEW_CACHE_RELS,WRITABLE_WORKDIR_RELS,cleanCocosProjectCaches,ensureWritableCocosWorkdirs,shouldCleanCocosCache,shouldWipePreviewCaches};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const{describe,it,afterEach}=require("node:test");const assert=require("node:assert/strict");const fs=require("fs");const os=require("os");const path=require("path");const{cleanCocosProjectCaches,ensureWritableCocosWorkdirs,shouldCleanCocosCache}=require("./clean-cache.js");function writeFile(file,body){fs.mkdirSync(path.dirname(file),{recursive:true});fs.writeFileSync(file,body)}describe("cleanCocosProjectCaches",()=>{let root;afterEach(()=>{if(root&&fs.existsSync(root))fs.rmSync(root,{recursive:true,force:true})});it("removes
|
|
1
|
+
"use strict";const{describe,it,afterEach}=require("node:test");const assert=require("node:assert/strict");const fs=require("fs");const os=require("os");const path=require("path");const{cleanCocosProjectCaches,ensureWritableCocosWorkdirs,shouldCleanCocosCache}=require("./clean-cache.js");function writeFile(file,body){fs.mkdirSync(path.dirname(file),{recursive:true});fs.writeFileSync(file,body)}describe("cleanCocosProjectCaches",()=>{let root;afterEach(()=>{if(root&&fs.existsSync(root))fs.rmSync(root,{recursive:true,force:true})});it("removes editor build cache but keeps preview merge + cli overlay",()=>{root=fs.mkdtempSync(path.join(os.tmpdir(),"cocos-clean-cache-"));writeFile(path.join(root,"library","uuid-to-mtime.json"),"{}");writeFile(path.join(root,"temp","programming","packer-driver","targets","editor","import-map.json"),"{}");writeFile(path.join(root,"temp","programming","packer-driver","targets","preview","chunks","playcraft-bundle.js"),"bundle");writeFile(path.join(root,"temp","programming","preview","systemjs","playcraft-json-pack.json"),"{}");writeFile(path.join(root,"temp","builder","log.txt"),"x");writeFile(path.join(root,"temp","cocos-cli","bin","cocos"),"#!/usr/bin/env node\n");const removed=cleanCocosProjectCaches(root);assert.deepEqual(new Set(removed),new Set(["library","temp/programming/packer-driver/targets/editor","temp/builder"]));assert.equal(fs.existsSync(path.join(root,"library")),false);assert.equal(fs.existsSync(path.join(root,"temp","programming","packer-driver","targets","editor")),false);assert.equal(fs.existsSync(path.join(root,"temp","programming","packer-driver","targets","preview","chunks","playcraft-bundle.js")),true);assert.equal(fs.existsSync(path.join(root,"temp","cocos-cli","bin","cocos")),true)});it("replaces a leftover programming dir with writable packer logs",()=>{root=fs.mkdtempSync(path.join(os.tmpdir(),"cocos-clean-cache-"));writeFile(path.join(root,"temp","programming","packer-driver","stale.txt"),"old");fs.chmodSync(path.join(root,"temp","programming","packer-driver"),365);cleanCocosProjectCaches(root);ensureWritableCocosWorkdirs(root);const logs=path.join(root,"temp","programming","packer-driver","logs");const probe=path.join(logs,"ok.txt");fs.writeFileSync(probe,"1");assert.equal(fs.readFileSync(probe,"utf8"),"1")});it("can wipe the overlay when keepCliOverlay is false",()=>{root=fs.mkdtempSync(path.join(os.tmpdir(),"cocos-clean-cache-"));writeFile(path.join(root,"temp","cocos-cli","bin","cocos"),"ok");const removed=cleanCocosProjectCaches(root,{keepCliOverlay:false});assert.deepEqual(removed,["temp"]);assert.equal(fs.existsSync(path.join(root,"temp")),false)})});describe("shouldCleanCocosCache",()=>{it("defaults off, env and cocos.cleanCache win",()=>{assert.equal(shouldCleanCocosCache({},{}),false);assert.equal(shouldCleanCocosCache({cocos:{cleanCache:true}},{}),true);assert.equal(shouldCleanCocosCache({cocos:{cleanCache:true}},{PLAYABLE_KEEP_COCOS_CACHE:"1"}),false);assert.equal(shouldCleanCocosCache({cocos:{cleanCache:false}},{PLAYABLE_CLEAN_COCOS_CACHE:"1"}),true)})});
|
|
@@ -1,3 +1,5 @@
|
|
|
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")}
|
|
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
|
+
}}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"}/**
|
|
2
3
|
* 常驻官方 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
|
|
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.
|
|
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";const fs=require("fs");const path=require("path");const{options}=require("../../options.js");const{mergeOptions}=require("../../utils/mergeOptions.js");const{logOptions}=require("../../utils/logOptions.js");const{resolveCreatorOutputDir}=require("./creator-cli");const{runCocosEngineBuild}=require("./cli-build");const{mergeWebMobile}=require("./merge");const{detectCurrentTheme,loadThemePayload,writeGeneratedThemeModule}=require("./theme-assets");const{emitChannel}=require("../../artifacts/emit");const{isSocialMediaNetwork,stripSocialShareFromDir}=require("./strip-social-share");function resolveWebMobileDir(root,buildOptions){const cocos=buildOptions.cocos||{};const fromCreator=resolveCreatorOutputDir(root,cocos.buildConfig||"build-configs/web-mobile.json");const explicit=cocos.webMobileDir?path.resolve(root,cocos.webMobileDir):null;if(cocos.skipEngineBuild){return explicit||fromCreator||path.resolve(root,"dist/web-mobile")}if(fromCreator){if(explicit&&explicit!==fromCreator){console.warn(`[cocos] webMobileDir (${path.relative(root,explicit)}) != Creator output (${path.relative(root,fromCreator)}); using Creator buildPath/outputName`)}return fromCreator}return explicit||path.resolve(root,"dist/web-mobile")}async function mergeTemplate(root,buildOptions){const webMobileDir=resolveWebMobileDir(root,buildOptions);const singleFilePath=path.join(root,"build","single-file-3x.html");await mergeWebMobile(webMobileDir,singleFilePath,{enableSplash:!!(buildOptions.cocos&&buildOptions.cocos.enableSplash)});return fs.readFileSync(singleFilePath,"utf8")}function resolveThemeName(root,buildOptions,themeConfig){return buildOptions.themeName||detectCurrentTheme(root,themeConfig)||"default"}function loadBuildsThemeConfig(root){const configPath=path.join(root,"builds.config.js");if(!fs.existsSync(configPath))return{sourceDir:"src/theme",entryFile:"src/theme/index.ts"};try{delete require.cache[configPath];const config=require(configPath);return{sourceDir:"src/theme",entryFile:"src/theme/index.ts",...(config.themes||{})}}catch{return{sourceDir:"src/theme",entryFile:"src/theme/index.ts"}}}function stripPackedChannel(outDir,network){if(isSocialMediaNetwork(network)){console.log("[cocos] social_media: keep AiX hooks (inject IIFE later)");return}const packed=stripSocialShareFromDir(outDir);console.log(`[cocos] stripped social-share from ${packed.changed}/${packed.files} files in ${path.relative(process.cwd(),outDir)||"."}`)}async function prepareCocosTemplate(customOptions){const buildOptions=mergeOptions(options,customOptions);const root=process.cwd();await runCocosEngineBuild(root,buildOptions);return mergeTemplate(root,buildOptions)}async function emitCocosChannel(templateHtml,customOptions){const buildOptions=mergeOptions(options,customOptions);const root=process.cwd();const themeConfig=loadBuildsThemeConfig(root);const themeName=resolveThemeName(root,buildOptions,themeConfig);let themePayload={themeName,mergedData:{},i18nOverride:{},assetRecordByPath:{}};try{themePayload=loadThemePayload(root,themeName,themeConfig)}catch(err){console.warn(`[cocos] theme payload skipped: ${err.message}`)}const generated=buildOptions.cocos&&buildOptions.cocos.generatedThemeFile||"assets/scripts/platform/theme.generated.ts";writeGeneratedThemeModule(path.join(root,generated),themePayload);const outDir=path.resolve(root,buildOptions.outDir);await emitChannel({templateHtml,buildOptions,themePayload,outDir});stripPackedChannel(outDir,buildOptions.network)}async function runCocosBuild(customOptions){const buildOptions=mergeOptions(options,customOptions);logOptions(buildOptions);const templateHtml=await prepareCocosTemplate(buildOptions);await emitCocosChannel(templateHtml,customOptions)}async function packCocosBatch({config,themes,tasks,outputDir,projectRoot,isChannelFold2Zip}){const buildOptionsBase=mergeOptions(options,{engine:"cocos",googlePlayUrl:config.googlePlayUrl,appStoreUrl:config.appStoreUrl,isChannelFold2Zip,obfuscateLevel:config.obfuscateLevel,fflateCompression:config.fflateCompression});if(config.cocos)Object.assign(buildOptionsBase.cocos,config.cocos);if(buildOptionsBase.cocos.cleanCache!==false&&process.env.PLAYABLE_KEEP_COCOS_CACHE!=="1"){buildOptionsBase.cocos.cleanCache=true}await runCocosEngineBuild(projectRoot,buildOptionsBase);const templateHtml=await mergeTemplate(projectRoot,buildOptionsBase);const themeConfig=config.themes||loadBuildsThemeConfig(projectRoot);const results=[];for(const theme of themes){let themePayload;try{themePayload=loadThemePayload(projectRoot,theme,themeConfig)}catch(err){console.error(`[cocos] theme ${theme}: ${err.message}`);for(const task of tasks){results.push({success:false,theme,channel:task.channel,orientation:task.orientation,error:err.message})}continue}for(const task of tasks){const orientationValue=task.orientation||"both";const fileName=config.naming&&typeof config.naming.generator==="function"?config.naming.generator(theme,task.channel):`${theme}-${task.channel}`;const tempDir=path.join(projectRoot,`${config.build.tempDirPrefix||"dist-temp"}-cocos-${theme}-${task.channel}-${orientationValue}`);try{const channelOptions=mergeOptions(buildOptionsBase,{network:task.channel,playcraftOrientationValue:orientationValue,filename:fileName||options.filename,outDir:tempDir,isChannelFold2Zip});await emitChannel({templateHtml,buildOptions:channelOptions,themePayload,outDir:tempDir});stripPackedChannel(tempDir,task.channel);const themeDir=path.join(outputDir,theme);fs.mkdirSync(themeDir,{recursive:true});for(const file of fs.readdirSync(tempDir)){const src=path.join(tempDir,file);if(fs.statSync(src).isDirectory())continue;fs.copyFileSync(src,path.join(themeDir,file))}results.push({success:true,theme,channel:task.channel,orientation:orientationValue})}catch(error){results.push({success:false,theme,channel:task.channel,orientation:orientationValue,error:error.message})}finally{if(config.build.cleanTemp!==false&&fs.existsSync(tempDir)){fs.rmSync(tempDir,{recursive:true,force:true})}}}}return results}async function syncThemeForPreview(projectRoot,themeName,themeConfig,generatedRel){const payload=loadThemePayload(projectRoot,themeName,themeConfig);writeGeneratedThemeModule(path.join(projectRoot,generatedRel),payload);return payload}module.exports={runCocosBuild,prepareCocosTemplate,emitCocosChannel,packCocosBatch,mergeTemplate,syncThemeForPreview};
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");const{options}=require("../../options.js");const{mergeOptions}=require("../../utils/mergeOptions.js");const{logOptions}=require("../../utils/logOptions.js");const{resolveCreatorOutputDir}=require("./creator-cli");const{runCocosEngineBuild}=require("./cli-build");const{rebundlePreviewMerge}=require("./rebundle-preview.js");const{mergeWebMobile}=require("./merge");const{detectCurrentTheme,loadThemePayload,writeGeneratedThemeModule}=require("./theme-assets");const{emitChannel}=require("../../artifacts/emit");const{isSocialMediaNetwork,stripSocialShareFromDir}=require("./strip-social-share");function resolveWebMobileDir(root,buildOptions){const cocos=buildOptions.cocos||{};const fromCreator=resolveCreatorOutputDir(root,cocos.buildConfig||"build-configs/web-mobile.json");const explicit=cocos.webMobileDir?path.resolve(root,cocos.webMobileDir):null;if(cocos.skipEngineBuild){return explicit||fromCreator||path.resolve(root,"dist/web-mobile")}if(fromCreator){if(explicit&&explicit!==fromCreator){console.warn(`[cocos] webMobileDir (${path.relative(root,explicit)}) != Creator output (${path.relative(root,fromCreator)}); using Creator buildPath/outputName`)}return fromCreator}return explicit||path.resolve(root,"dist/web-mobile")}async function mergeTemplate(root,buildOptions){const webMobileDir=resolveWebMobileDir(root,buildOptions);const singleFilePath=path.join(root,"build","single-file-3x.html");await mergeWebMobile(webMobileDir,singleFilePath,{enableSplash:!!(buildOptions.cocos&&buildOptions.cocos.enableSplash)});return fs.readFileSync(singleFilePath,"utf8")}function resolveThemeName(root,buildOptions,themeConfig){return buildOptions.themeName||detectCurrentTheme(root,themeConfig)||"default"}function loadBuildsThemeConfig(root){const configPath=path.join(root,"builds.config.js");if(!fs.existsSync(configPath))return{sourceDir:"src/theme",entryFile:"src/theme/index.ts"};try{delete require.cache[configPath];const config=require(configPath);return{sourceDir:"src/theme",entryFile:"src/theme/index.ts",...(config.themes||{})}}catch{return{sourceDir:"src/theme",entryFile:"src/theme/index.ts"}}}function stripPackedChannel(outDir,network){if(isSocialMediaNetwork(network)){console.log("[cocos] social_media: keep AiX hooks (inject IIFE later)");return}const packed=stripSocialShareFromDir(outDir);console.log(`[cocos] stripped social-share from ${packed.changed}/${packed.files} files in ${path.relative(process.cwd(),outDir)||"."}`)}async function prepareCocosTemplate(customOptions){const buildOptions=mergeOptions(options,customOptions);const root=process.cwd();await runCocosEngineBuild(root,buildOptions);await rebundlePreviewMerge(root);return mergeTemplate(root,buildOptions)}async function emitCocosChannel(templateHtml,customOptions){const buildOptions=mergeOptions(options,customOptions);const root=process.cwd();const themeConfig=loadBuildsThemeConfig(root);const themeName=resolveThemeName(root,buildOptions,themeConfig);let themePayload={themeName,mergedData:{},i18nOverride:{},assetRecordByPath:{}};try{themePayload=loadThemePayload(root,themeName,themeConfig)}catch(err){console.warn(`[cocos] theme payload skipped: ${err.message}`)}const generated=buildOptions.cocos&&buildOptions.cocos.generatedThemeFile||"assets/scripts/platform/theme.generated.ts";writeGeneratedThemeModule(path.join(root,generated),themePayload);const outDir=path.resolve(root,buildOptions.outDir);await emitChannel({templateHtml,buildOptions,themePayload,outDir});stripPackedChannel(outDir,buildOptions.network)}async function runCocosBuild(customOptions){const buildOptions=mergeOptions(options,customOptions);logOptions(buildOptions);const templateHtml=await prepareCocosTemplate(buildOptions);await emitCocosChannel(templateHtml,customOptions)}async function packCocosBatch({config,themes,tasks,outputDir,projectRoot,isChannelFold2Zip}){const buildOptionsBase=mergeOptions(options,{engine:"cocos",googlePlayUrl:config.googlePlayUrl,appStoreUrl:config.appStoreUrl,isChannelFold2Zip,obfuscateLevel:config.obfuscateLevel,fflateCompression:config.fflateCompression});if(config.cocos)Object.assign(buildOptionsBase.cocos,config.cocos);if(buildOptionsBase.cocos.cleanCache!==false&&process.env.PLAYABLE_KEEP_COCOS_CACHE!=="1"){buildOptionsBase.cocos.cleanCache=true}await runCocosEngineBuild(projectRoot,buildOptionsBase);await rebundlePreviewMerge(projectRoot);const templateHtml=await mergeTemplate(projectRoot,buildOptionsBase);const themeConfig=config.themes||loadBuildsThemeConfig(projectRoot);const results=[];for(const theme of themes){let themePayload;try{themePayload=loadThemePayload(projectRoot,theme,themeConfig)}catch(err){console.error(`[cocos] theme ${theme}: ${err.message}`);for(const task of tasks){results.push({success:false,theme,channel:task.channel,orientation:task.orientation,error:err.message})}continue}for(const task of tasks){const orientationValue=task.orientation||"both";const fileName=config.naming&&typeof config.naming.generator==="function"?config.naming.generator(theme,task.channel):`${theme}-${task.channel}`;const tempDir=path.join(projectRoot,`${config.build.tempDirPrefix||"dist-temp"}-cocos-${theme}-${task.channel}-${orientationValue}`);try{const channelOptions=mergeOptions(buildOptionsBase,{network:task.channel,playcraftOrientationValue:orientationValue,filename:fileName||options.filename,outDir:tempDir,isChannelFold2Zip});await emitChannel({templateHtml,buildOptions:channelOptions,themePayload,outDir:tempDir});stripPackedChannel(tempDir,task.channel);const themeDir=path.join(outputDir,theme);fs.mkdirSync(themeDir,{recursive:true});for(const file of fs.readdirSync(tempDir)){const src=path.join(tempDir,file);if(fs.statSync(src).isDirectory())continue;fs.copyFileSync(src,path.join(themeDir,file))}results.push({success:true,theme,channel:task.channel,orientation:orientationValue})}catch(error){results.push({success:false,theme,channel:task.channel,orientation:orientationValue,error:error.message})}finally{if(config.build.cleanTemp!==false&&fs.existsSync(tempDir)){fs.rmSync(tempDir,{recursive:true,force:true})}}}}return results}async function syncThemeForPreview(projectRoot,themeName,themeConfig,generatedRel){const payload=loadThemePayload(projectRoot,themeName,themeConfig);writeGeneratedThemeModule(path.join(projectRoot,generatedRel),payload);return payload}module.exports={runCocosBuild,prepareCocosTemplate,emitCocosChannel,packCocosBatch,mergeTemplate,syncThemeForPreview};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");const{concatPreviewScripts}=require("./preview-concat.js");const{packPreviewJson}=require("./preview-json-pack.js");function previewTargetDir(projectRoot){return path.join(projectRoot,"temp/programming/packer-driver/targets/preview")}/**
|
|
2
|
+
* Re-apply script concat + JSON pack after a build cleared editor caches.
|
|
3
|
+
* Safe no-op if preview was never compiled.
|
|
4
|
+
*/async function rebundlePreviewMerge(projectRoot,options={}){const previewDir=previewTargetDir(projectRoot);if(!fs.existsSync(path.join(previewDir,"import-map.json"))){return null}let scriptResult=null;try{scriptResult=concatPreviewScripts(projectRoot);if(scriptResult){const kb=Math.max(1,Math.round(scriptResult.bytes/1024));console.log(`[cocos] preview scripts ${scriptResult.modules} → 1 bundle (${kb} kB)`)}}catch(err){console.warn(`[cocos] preview script concat skipped: ${err.message}`)}let jsonResult=null;try{const port=options.previewPort||process.env.PREVIEW_PORT;const previewOrigin=port?`http://127.0.0.1:${port}`:options.previewOrigin;jsonResult=await packPreviewJson(projectRoot,{previewOrigin,fetchText:options.fetchText,engineLibraryDirs:options.engineLibraryDirs});if(jsonResult){const kb=Math.max(1,Math.round(jsonResult.bytes/1024));console.log(`[cocos] preview json ${jsonResult.files} → 1 pack (${kb} kB)`)}}catch(err){console.warn(`[cocos] preview json pack skipped: ${err.message}`)}return{scriptResult,jsonResult}}module.exports={previewTargetDir,rebundlePreviewMerge};
|