@ttmg/cli 0.0.12-beta.8 → 0.0.12-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.
Files changed (2) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var e,t,o,n,s,i,r=require("commander"),c=require("inquirer"),l=require("fs"),a=require("jsdom"),u=require("prettier"),d=require("chalk"),p=require("express"),m=require("path"),f=require("cheerio"),g=require("crypto"),h=require("os"),w=require("archiver"),y=require("multer"),S=require("ws"),b=require("axios"),v=require("form-data"),k=require("ttmg-pack"),j=require("qrcode-terminal");function _(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function M(){return t?e:(t=1,e={CONFIG_FILE_NAME:"minigame.config.json",SDK_URL:"https://connect.tiktok-minis.com/game/sdk.js",VCONSOLE_URL:"https://connect.tiktok-minis.com/libs/vConsole.js",VCONSOLE_INIT:"\n if(typeof VConsole === 'function') {\n window.vConsole = new VConsole();\n }\n ",MINIS_MANIFEST_FILE_NAME:"minis.manifest.json",MINIS_RUNTIME_URL:"https://www.tiktok.com/minigames/runtime"})}function $(){if(n)return o;n=1;const e=l,t=a,s=u,i=d,{JSDOM:r}=t,{CONFIG_FILE_NAME:c,SDK_URL:p,VCONSOLE_URL:m,VCONSOLE_INIT:f}=M(),g=`${process.cwd()}/${c}`,h=`${process.cwd()}/index.html`;return o=async function({config:t,clientKey:o}){if(e.writeFileSync(g,JSON.stringify(t,null,2)),!e.existsSync(h))return void console.error("index.html does not exist");const n=e.readFileSync(h,"utf8"),c=new r(n),{document:l}=c.window;let a=l.querySelector("head");a||(a=l.createElement("head"),l.documentElement.insertBefore(a,l.body));const u=Array.from(l.querySelectorAll("script")),d=u.some(e=>e.src&&e.src.includes(p)),w=u.some(e=>e.src&&e.src.includes("vConsole.js"));let y=null;if(d){const e=Array.from(a.querySelectorAll("script"));if(y=e.find(e=>function(e,t){return!!e.innerHTML&&e.innerHTML.includes("TTMinis.game.init")&&e.innerHTML.includes(t)}(e,o)),y)console.log("JS SDK 已接入,跳过 SDK 相关脚本注入");else{const t=e.filter(e=>e.src&&e.src.includes(p));t.length>0&&(y=t[t.length-1])}}else{const e=l.createElement("script");e.src=p,a.insertBefore(e,a.firstChild);const t=l.createElement("script");t.innerHTML=`\n window.TTMinis = TTMinis;\n TTMinis.game.init({\n clientKey: "${o}",\n });\n `,a.insertBefore(t,e.nextSibling),y=t}if(function(e){return e.startsWith("sb")}(o)&&(console.log("Sandbox 环境,跳过 vConsole 相关脚本注入"),!w)){let e=y;e=e?e.nextSibling:a.firstChild;const t=l.createElement("script");t.src=m;const o=l.createElement("script");o.innerHTML=f,a.insertBefore(t,e),a.insertBefore(o,e)}const S=await s.format(c.serialize(),{parser:"html"});e.writeFileSync(h,S),console.log(i.green.bold("TikTok H5 Mini Game initialization has been completed..."))},o}var T,E,N=function(){if(i)return s;i=1;const e=c,t=$();return s=function(){e.createPromptModule()([{type:"input",name:"clientKey",message:"Please input client key"},{type:"input",name:"devPort",message:"Please input dev port",default:9527}]).then(async e=>{const{clientKey:o,devPort:n}=e;t({clientKey:o,config:{_comment:"orientation is the orientation of the game. It can be either 'VERTICAL' or 'HORIZONTAL'.our game default is VERTICAL; minigame.config.json dev is a configuration file for minigame development. You can use it to configure the minigame.",orientation:"VERTICAL",dev:{port:n}}})})}}(),I=_(N);var O,z,C,L,x=_(function(){if(E)return T;E=1;const e=p,t=m,o=l,n=d,s=f,i=e(),r=g,{CONFIG_FILE_NAME:c,MINIS_RUNTIME_URL:a}=M();return T=function(){const l=t.join(process.cwd(),c);if(!o.existsSync(l))return void console.log(n.red.bold(`${c} is not exist, please run minis game init first`));const u=JSON.parse(o.readFileSync(l,"utf8")),d=u.dev?.port||9527;console.log(n.yellow.bold("⚠️ Before dev, please ensure:\n 1. The account used to login www.tiktok.com is in the sandbox target user range of Minis developer platform, otherwise login authorization will throw an error.\n 2. The browser allows www.tiktok.com <popup and redirect>, because the authorization login linkage needs to open a new tab popup for operation, otherwise the authorization login linkage will not be able to debug normally.")),console.log(n.bold.blue("\n \n============== start dev your game, it will take a few seconds ============ \n \n")),i.use((e,t,o)=>{e.url.endsWith(".br")?t.setHeader("Content-Encoding","br"):e.url.endsWith(".gz")&&t.setHeader("Content-Encoding","gzip"),o()}),i.use((e,i,c)=>{try{const e=t.join(process.cwd(),"index.html"),n=o.readFileSync(e,"utf8"),c=s.load(n),l=[];c("script:not([src])").each((e,t)=>{const o=c(t).html();o&&o.trim()&&l.push(o)});l.map(e=>`'sha256-${r.createHash("sha256").update(e,"utf8").digest("base64")}'`);const a="*";i.setHeader("Content-Security-Policy",`default-src 'self';script-src 'self' data: blob: 'unsafe-eval' 'unsafe-inline' connect.tiktok-minis.com sf-connect.tiktokminis.us;img-src 'self' ${a} data: blob: *; connect-src 'self' ${a} data: blob: ; style-src 'self' ${a} 'unsafe-inline' fonts.googleapis.com data: blob: *; font-src 'self' fonts.gstatic.com blob: data: *; media-src 'self' ${a} data: blob: *; frame-src 'none'; base-uri 'self'; worker-src 'self' blob: data: ;`)}catch(e){console.warn(n.red("Failed to set CSP header:"),e.message)}c()}),i.use(e.static(t.join(process.cwd()))),i.listen(d,()=>{const e=`${a}?minis_url=${`http://localhost:${d}`}&enable_log=1`;console.log(`you can access ${n.green.underline.bold(e)} to debug your game in browser...`);try{open(e)}catch(e){console.warn(n.red("Failed to open browser, you can access it manually"),e.message)}})}}());function q(){if(z)return O;z=1;const e=d,t=l,o=m,{MINIS_MANIFEST_FILE_NAME:n}=M();function s(e,t){let o=t.find(t=>"folder"===t.type&&t.name===e[0]);return o||(o={type:"folder",name:e[0],children:[]},t.push(o)),e.length>1?s(e.slice(1),o.children):o}function i(e,n=e){const s={};return t.readdirSync(e).forEach(r=>{const c=o.join(e,r);if(t.statSync(c).isDirectory()){const e=i(c,n);Object.assign(s,e)}else if(""!==o.extname(r)&&r){const e="/"+o.relative(n,c).replace(/\\/g,"/");s[c]=e}}),s}return O={buildMinisManifest:async function(){try{const e=o.join(process.cwd()),r=[],c=i(e);Object.keys(c).filter(e=>!e.endsWith(".map")).forEach(e=>{const t=c[e].split("/").filter(Boolean),o=t.pop()||"";if(0===t.length)r.push({type:"file",name:o});else{s(t,r).children.push({type:"file",name:o})}}),t.writeFileSync(o.join(e,n),JSON.stringify({name:n,resource_list:r},null,2))}catch(t){console.error(e.red(`Error during debug process: ${t.message}`)),t instanceof Error&&t.stack&&console.error(e.red(`Stack trace: ${t.stack}`)),process.exit(1)}}}}var P=_(function(){if(L)return C;L=1;const e=l,t=h,o=m,n=d,s=w,i=c.createPromptModule(),{buildMinisManifest:r}=q();return C=async function(){try{const{zipName:c}=await i({type:"input",name:"zipName",default:"game",message:"Please input zip name"}),l=Date.now();console.log(n.bold.blue("start build your game, it will take a few minutes..."));e.readdirSync(process.cwd()).forEach(t=>{t.endsWith(".zip")&&e.unlinkSync(o.join(process.cwd(),t))}),await r();const a=s("zip",{zlib:{level:9}});a.on("error",function(e){throw e});const u=function(){const n=t.homedir(),s=["Desktop","桌面"];for(const t of s){const s=o.join(n,t);if(e.existsSync(s))return s}return o.join(n,"Desktop")}(),d=o.join(u,`${c}.zip`);return await a.pipe(e.createWriteStream(d)),await a.directory(o.resolve(process.cwd()),!1),await a.finalize(),console.log(n.yellow.bold(`build ${c}.zip success, you can find it in desktop, use time ${Date.now()-l} ms`)),new Promise((e,t)=>{a.on("end",e),a.on("error",t)})}catch(e){console.log(n.red(`auto build ${zipNameInput}.zip failed: ${e.message}, you should zip it manually`))}}}());const{exec:F}=require("child_process");function H(e){const t=process.platform;"win32"===t?F(`start ${e}`):"darwin"===t?F(`open ${e}`):"linux"===t?F(`xdg-open ${e}`):console.error("Unsupported OS, please open url manually")}function U(){const e=h.networkInterfaces();for(const t in e){const o=e[t];if(o)for(const e of o)if("IPv4"===e.family&&!e.internal)return e.address}}class D{constructor(e){this.listeners=[],this.state=e}static getInstance(e){return D.instance||(D.instance=new D(e)),D.instance}getState(){return this.state}setState(e){this.state=Object.assign(Object.assign({},this.state),e),this.listeners.forEach(e=>e(this.state))}subscribe(e){return this.listeners.push(e),()=>{this.listeners=this.listeners.filter(t=>t!==e)}}reset(e){this.state=e,this.listeners.forEach(e=>e(this.state))}}const A=D.getInstance({clientServerPort:"",clientServerHost:"",clientKey:""}),G=9528,R=9529,B=m.join(h.homedir(),"__TTMG__");let V;async function J(){console.log("Start local dev server...");const e=p(),t=G,o=y({dest:h.tmpdir()});return e.use(p.json()),e.use(p.urlencoded({extended:!0})),e.post("/game/env",async(e,t)=>{const o=e.body,{host:n,port:s}=o;A.setState({clientServerPort:s,clientServerHost:n});const i=`http://${n}:${s}?session=${function(){const e={ws_port:R,http_port:G};return btoa(JSON.stringify(e))}()}`;H(i),console.log(d.bold.yellow(`Game debug is ready! Visit ${i} in your browser.`)),t.json({code:0,msg:"ok",data:{devUrl:i}})}),e.post("/game/upload",o.single("file"),async(e,t)=>{t.json({code:0,msg:"ok",filename:e.file.filename,originalname:e.file.originalname,size:e.file.size,path:e.file.path})}),V=e.listen(t,()=>{console.log("Dev server is running on port 9528")}),{port:t,host:U()}}const K=m.join(h.homedir(),".ttmg-cli");function W(){const e=m.join(process.cwd(),"project.config.json");let t;try{const o=JSON.parse(l.readFileSync(e,"utf-8"));t=o.appid||o.appId}catch(e){t=""}if(t)return t;console.log(d.red.bold("No appid found in project.config.json, you should provide it in project.config.json")),process.exit(1)}async function Q(){console.log(d.yellow("Start upload game to client"));const e=process.cwd(),t=W(),o=m.join(h.homedir(),"__TTMG__",t);l.existsSync(o)&&(console.log(d.yellow("Output dir exists, remove it")),l.rmSync(o,{recursive:!0,force:!0}),console.log(d.yellow("Create new output dir"))),console.log(d.yellow("Start compile game package"));const n=await k.debugPkgs({entry:e,output:o,rules:{mainPackageSizeLimit:209715200,projectSizeLimit:209715200}});(null==n?void 0:n.isSuccess)?console.log(d.yellow("Compile game package success")):(console.log(d.redBright("Build game package failed, Please check the error message below:")),console.log(d.redBright(null==n?void 0:n.errorMsg)),process.exit(1)),console.log(d.yellow("Start compress will upload game resource"));const s=m.join(h.homedir(),"__TTMG__","upload.zip");var i,r;await(i=o,r=s,new Promise((e,t)=>{const o=l.createWriteStream(r),n=w("zip",{zlib:{level:9}});o.on("close",()=>{e()}),o.on("error",e=>{t(e)}),n.on("error",e=>{t(e)}),n.pipe(o),n.directory(i,!1),n.finalize()})),console.log(d.yellow("Compress game package resource success \n")),console.log(d.yellow("Start upload game package resource to client")),await async function(e){const t=new v;t.append("file",l.createReadStream(e),"upload.zip");try{console.log(d.yellow.bold("Start upload resource to client"));const{clientServerHost:o,clientServerPort:n}=A.getState();await b.post(`http://${o}:${n}/game/upload`,t,{headers:t.getHeaders()}),l.unlinkSync(e)}catch(e){console.error("Upload resource to client failed:",e.message)}}(s),console.log(d.yellow("Upload game package resource success"))}m.join(K,"config.json");const Y=new class{constructor(){this.ws=new S.Server({port:R}),this.ws.on("connection",e=>{e.on("message",e=>{const t=JSON.parse(e.toString());if("browser"===t.from){switch(t.event){case"connected":this.sendUploadStatus("start"),Q().then(()=>{this.sendUploadStatus("success")}).catch(()=>{this.sendUploadStatus("failed")});break;case"closeBrowserDebug":console.log("close upload"),this.ws.close(),V.close(()=>{console.log("Dev server closed"),V=null,process.exit(0)}),console.log("close server")}}else{if("shareDevParams"===t.method){console.log("shareDevParams",t);const e=t.payload;console.log("shareDevParams",e);const{host:o,port:n}=e;A.setState({clientServerPort:n,clientServerHost:o});const s=`http://${o}:${n}?session=${function(){const e={ws_port:R,http_port:G};return btoa(JSON.stringify(e))}()}`;H(s),console.log(d.bold.yellow(`Game debug is ready! Visit ${s} in your browser.`))}}})})}send(e){this.ws.clients.forEach(t=>{if(t.readyState===S.OPEN){const o=Object.assign(Object.assign({},e),{from:"nodeServer"});t.send(JSON.stringify(o))}})}close(){this.ws.close()}sendUploadStatus(e){this.send({event:"uploadStatus",status:e})}};async function Z(){const e=W(),t=m.join(B,e),o=U();console.log(d.yellow("Tips:")),console.log(` 1. ${d.blue("Scan the QR code to start the client devServer.")}`),console.log(` 2. ${d.blue("Will auto upload compiled resource to client.")} ${d.bold(t)}`),console.log(` 3. ${d.blue("Debug your game in the browser.")}\n`);const n=`https://www.tiktok.com/ttmg/dev/${e}?host=${o}&port=9529`;j.generate(n,{small:!0},e=>{console.log(function(e){const t=process.stdout.columns||80,o=e.split("\n"),n=o.reduce((e,t)=>Math.max(e,t.length),0),s=Math.floor((t-n)/3),i=" ".repeat(s>0?s:0);return o.map(e=>i+e).join("\n")}(e))})}async function X(){await J(),await Z(),await async function(){let e=null;l.watch(process.cwd(),(t,o)=>{console.log(d.yellow("game resource change, restart to upload")),e&&clearTimeout(e),e=setTimeout(async()=>{Y.sendUploadStatus("start"),Q().then(()=>{Y.sendUploadStatus("success")}).catch(()=>{Y.sendUploadStatus("failed")}),e=null},500)})}()}const ee=new r.Command;ee.name("ttmg").description("TikTok Mini Game Command Line Tool").version("0.0.1"),ee.command("init").option("--h5","H5 Mini Game").description("Initialize project").action(e=>{e.h5?I():console.log("Native Mini Game initialize")}),ee.command("dev").description("Open browser dev environment").option("--h5","H5 Mini Game").action(e=>{e.h5?x():X()}),ee.command("build").option("--h5","H5 Mini Game").description("Bundle project").action(async e=>{e.h5?P():console.log("Native Mini Game bundle")}),ee.parse(process.argv);
2
+ "use strict";var e,t,o,n,s,i,r=require("commander"),c=require("inquirer"),a=require("fs"),l=require("jsdom"),u=require("prettier"),d=require("chalk"),p=require("express"),m=require("path"),g=require("cheerio"),f=require("crypto"),h=require("os"),w=require("archiver"),y=require("multer"),S=require("ws"),b=require("axios"),v=require("form-data"),k=require("ttmg-pack"),j=require("qrcode-terminal");function M(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function _(){return t?e:(t=1,e={CONFIG_FILE_NAME:"minigame.config.json",SDK_URL:"https://connect.tiktok-minis.com/game/sdk.js",VCONSOLE_URL:"https://connect.tiktok-minis.com/libs/vConsole.js",VCONSOLE_INIT:"\n if(typeof VConsole === 'function') {\n window.vConsole = new VConsole();\n }\n ",MINIS_MANIFEST_FILE_NAME:"minis.manifest.json",MINIS_RUNTIME_URL:"https://www.tiktok.com/minigames/runtime"})}function $(){if(n)return o;n=1;const e=a,t=l,s=u,i=d,{JSDOM:r}=t,{CONFIG_FILE_NAME:c,SDK_URL:p,VCONSOLE_URL:m,VCONSOLE_INIT:g}=_(),f=`${process.cwd()}/${c}`,h=`${process.cwd()}/index.html`;return o=async function({config:t,clientKey:o}){if(e.writeFileSync(f,JSON.stringify(t,null,2)),!e.existsSync(h))return void console.error("index.html does not exist");const n=e.readFileSync(h,"utf8"),c=new r(n),{document:a}=c.window;let l=a.querySelector("head");l||(l=a.createElement("head"),a.documentElement.insertBefore(l,a.body));const u=Array.from(a.querySelectorAll("script")),d=u.some(e=>e.src&&e.src.includes(p)),w=u.some(e=>e.src&&e.src.includes("vConsole.js"));let y=null;if(d){const e=Array.from(l.querySelectorAll("script"));if(y=e.find(e=>function(e,t){return!!e.innerHTML&&e.innerHTML.includes("TTMinis.game.init")&&e.innerHTML.includes(t)}(e,o)),y)console.log("JS SDK 已接入,跳过 SDK 相关脚本注入");else{const t=e.filter(e=>e.src&&e.src.includes(p));t.length>0&&(y=t[t.length-1])}}else{const e=a.createElement("script");e.src=p,l.insertBefore(e,l.firstChild);const t=a.createElement("script");t.innerHTML=`\n window.TTMinis = TTMinis;\n TTMinis.game.init({\n clientKey: "${o}",\n });\n `,l.insertBefore(t,e.nextSibling),y=t}if(function(e){return e.startsWith("sb")}(o)&&(console.log("Sandbox 环境,跳过 vConsole 相关脚本注入"),!w)){let e=y;e=e?e.nextSibling:l.firstChild;const t=a.createElement("script");t.src=m;const o=a.createElement("script");o.innerHTML=g,l.insertBefore(t,e),l.insertBefore(o,e)}const S=await s.format(c.serialize(),{parser:"html"});e.writeFileSync(h,S),console.log(i.green.bold("TikTok H5 Mini Game initialization has been completed..."))},o}var T,E,N=function(){if(i)return s;i=1;const e=c,t=$();return s=function(){e.createPromptModule()([{type:"input",name:"clientKey",message:"Please input client key"},{type:"input",name:"devPort",message:"Please input dev port",default:9527}]).then(async e=>{const{clientKey:o,devPort:n}=e;t({clientKey:o,config:{_comment:"orientation is the orientation of the game. It can be either 'VERTICAL' or 'HORIZONTAL'.our game default is VERTICAL; minigame.config.json dev is a configuration file for minigame development. You can use it to configure the minigame.",orientation:"VERTICAL",dev:{port:n}}})})}}(),I=M(N);var O,z,C,L,x=M(function(){if(E)return T;E=1;const e=p,t=m,o=a,n=d,s=g,i=e(),r=f,{CONFIG_FILE_NAME:c,MINIS_RUNTIME_URL:l}=_();return T=function(){const a=t.join(process.cwd(),c);if(!o.existsSync(a))return void console.log(n.red.bold(`${c} is not exist, please run minis game init first`));const u=JSON.parse(o.readFileSync(a,"utf8")),d=u.dev?.port||9527;console.log(n.yellow.bold("⚠️ Before dev, please ensure:\n 1. The account used to login www.tiktok.com is in the sandbox target user range of Minis developer platform, otherwise login authorization will throw an error.\n 2. The browser allows www.tiktok.com <popup and redirect>, because the authorization login linkage needs to open a new tab popup for operation, otherwise the authorization login linkage will not be able to debug normally.")),console.log(n.bold.blue("\n \n============== start dev your game, it will take a few seconds ============ \n \n")),i.use((e,t,o)=>{e.url.endsWith(".br")?t.setHeader("Content-Encoding","br"):e.url.endsWith(".gz")&&t.setHeader("Content-Encoding","gzip"),o()}),i.use((e,i,c)=>{try{const e=t.join(process.cwd(),"index.html"),n=o.readFileSync(e,"utf8"),c=s.load(n),a=[];c("script:not([src])").each((e,t)=>{const o=c(t).html();o&&o.trim()&&a.push(o)});a.map(e=>`'sha256-${r.createHash("sha256").update(e,"utf8").digest("base64")}'`);const l="*";i.setHeader("Content-Security-Policy",`default-src 'self';script-src 'self' data: blob: 'unsafe-eval' 'unsafe-inline' connect.tiktok-minis.com sf-connect.tiktokminis.us;img-src 'self' ${l} data: blob: *; connect-src 'self' ${l} data: blob: ; style-src 'self' ${l} 'unsafe-inline' fonts.googleapis.com data: blob: *; font-src 'self' fonts.gstatic.com blob: data: *; media-src 'self' ${l} data: blob: *; frame-src 'none'; base-uri 'self'; worker-src 'self' blob: data: ;`)}catch(e){console.warn(n.red("Failed to set CSP header:"),e.message)}c()}),i.use(e.static(t.join(process.cwd()))),i.listen(d,()=>{const e=`${l}?minis_url=${`http://localhost:${d}`}&enable_log=1`;console.log(`you can access ${n.green.underline.bold(e)} to debug your game in browser...`);try{open(e)}catch(e){console.warn(n.red("Failed to open browser, you can access it manually"),e.message)}})}}());function q(){if(z)return O;z=1;const e=d,t=a,o=m,{MINIS_MANIFEST_FILE_NAME:n}=_();function s(e,t){let o=t.find(t=>"folder"===t.type&&t.name===e[0]);return o||(o={type:"folder",name:e[0],children:[]},t.push(o)),e.length>1?s(e.slice(1),o.children):o}function i(e,n=e){const s={};return t.readdirSync(e).forEach(r=>{const c=o.join(e,r);if(t.statSync(c).isDirectory()){const e=i(c,n);Object.assign(s,e)}else if(""!==o.extname(r)&&r){const e="/"+o.relative(n,c).replace(/\\/g,"/");s[c]=e}}),s}return O={buildMinisManifest:async function(){try{const e=o.join(process.cwd()),r=[],c=i(e);Object.keys(c).filter(e=>!e.endsWith(".map")).forEach(e=>{const t=c[e].split("/").filter(Boolean),o=t.pop()||"";if(0===t.length)r.push({type:"file",name:o});else{s(t,r).children.push({type:"file",name:o})}}),t.writeFileSync(o.join(e,n),JSON.stringify({name:n,resource_list:r},null,2))}catch(t){console.error(e.red(`Error during debug process: ${t.message}`)),t instanceof Error&&t.stack&&console.error(e.red(`Stack trace: ${t.stack}`)),process.exit(1)}}}}var P=M(function(){if(L)return C;L=1;const e=a,t=h,o=m,n=d,s=w,i=c.createPromptModule(),{buildMinisManifest:r}=q();return C=async function(){try{const{zipName:c}=await i({type:"input",name:"zipName",default:"game",message:"Please input zip name"}),a=Date.now();console.log(n.bold.blue("start build your game, it will take a few minutes..."));e.readdirSync(process.cwd()).forEach(t=>{t.endsWith(".zip")&&e.unlinkSync(o.join(process.cwd(),t))}),await r();const l=s("zip",{zlib:{level:9}});l.on("error",function(e){throw e});const u=function(){const n=t.homedir(),s=["Desktop","桌面"];for(const t of s){const s=o.join(n,t);if(e.existsSync(s))return s}return o.join(n,"Desktop")}(),d=o.join(u,`${c}.zip`);return await l.pipe(e.createWriteStream(d)),await l.directory(o.resolve(process.cwd()),!1),await l.finalize(),console.log(n.yellow.bold(`build ${c}.zip success, you can find it in desktop, use time ${Date.now()-a} ms`)),new Promise((e,t)=>{l.on("end",e),l.on("error",t)})}catch(e){console.log(n.red(`auto build ${zipNameInput}.zip failed: ${e.message}, you should zip it manually`))}}}());const{exec:F}=require("child_process");function H(e){const t=process.platform;"win32"===t?F(`start ${e}`):"darwin"===t?F(`open ${e}`):"linux"===t?F(`xdg-open ${e}`):console.error("Unsupported OS, please open url manually")}function U(){const e=h.networkInterfaces();for(const t in e){const o=e[t];if(o)for(const e of o)if("IPv4"===e.family&&!e.internal)return e.address}}class D{constructor(e){this.listeners=[],this.state=e}static getInstance(e){return D.instance||(D.instance=new D(e)),D.instance}getState(){return this.state}setState(e){this.state=Object.assign(Object.assign({},this.state),e),this.listeners.forEach(e=>e(this.state))}subscribe(e){return this.listeners.push(e),()=>{this.listeners=this.listeners.filter(t=>t!==e)}}reset(e){this.state=e,this.listeners.forEach(e=>e(this.state))}}const A=D.getInstance({clientServerPort:"",clientServerHost:"",clientKey:""}),G=9528,R=9529,B=m.join(h.homedir(),"__TTMG__");let V;async function J(){console.log("Start local dev server...");const e=p(),t=G,o=y({dest:h.tmpdir()});return e.use(p.json()),e.use(p.urlencoded({extended:!0})),e.post("/game/env",async(e,t)=>{const o=e.body,{host:n,port:s}=o;A.setState({clientServerPort:s,clientServerHost:n});const i=`http://${n}:${s}?session=${function(){const e={ws_port:R,http_port:G};return btoa(JSON.stringify(e))}()}`;H(i),console.log(d.bold.yellow(`Game debug is ready! Visit ${i} in your browser.`)),t.json({code:0,msg:"ok",data:{devUrl:i}})}),e.post("/game/upload",o.single("file"),async(e,t)=>{t.json({code:0,msg:"ok",filename:e.file.filename,originalname:e.file.originalname,size:e.file.size,path:e.file.path})}),V=e.listen(t,()=>{console.log("Dev server is running on port 9528")}),{port:t,host:U()}}const K=m.join(h.homedir(),".ttmg-cli");function W(){const e=m.join(process.cwd(),"project.config.json");let t;try{const o=JSON.parse(a.readFileSync(e,"utf-8"));t=o.appid||o.appId}catch(e){t=""}if(t)return t;console.log(d.red.bold("No appid found in project.config.json, you should provide it in project.config.json")),process.exit(1)}async function Q(){console.log(d.yellow("Start upload game to client"));const e=process.cwd(),t=W(),o=m.join(h.homedir(),"__TTMG__",t);a.existsSync(o)&&(console.log(d.yellow("Output dir exists, remove it")),a.rmSync(o,{recursive:!0,force:!0}),console.log(d.yellow("Create new output dir"))),console.log(d.yellow("Start compile game package"));const{isSuccess:n,errorMsg:s,packages:i}=await k.debugPkgs({entry:e,output:o,rules:{mainPackageSizeLimit:209715200,projectSizeLimit:209715200}});n?console.log(d.yellow("Compile game package success")):(console.log(d.redBright("Build game package failed, Please check the error message below:")),console.log(d.redBright(s)),process.exit(1)),console.log(d.yellow("Start compress will upload game resource"));const r=m.join(h.homedir(),"__TTMG__","upload.zip");var c,l;return await(c=o,l=r,new Promise((e,t)=>{const o=a.createWriteStream(l),n=w("zip",{zlib:{level:9}});o.on("close",()=>{e()}),o.on("error",e=>{t(e)}),n.on("error",e=>{t(e)}),n.pipe(o),n.directory(c,!1),n.finalize()})),console.log(d.yellow("Compress game package resource success \n")),console.log(d.yellow("Start upload game package resource to client")),await async function(e){const t=new v;t.append("file",a.createReadStream(e),"upload.zip");try{console.log(d.yellow.bold("Start upload resource to client"));const{clientServerHost:o,clientServerPort:n}=A.getState();await b.post(`http://${o}:${n}/game/upload`,t,{headers:t.getHeaders()}),a.unlinkSync(e)}catch(e){console.error("Upload resource to client failed:",e.message)}}(r),{isSuccess:n,errorMsg:s,packages:i}}m.join(K,"config.json");const Y=new class{constructor(){this.ws=new S.Server({port:R}),this.ws.on("connection",e=>{e.on("message",e=>{const t=JSON.parse(e.toString());if("browser"===t.from){switch(t.event){case"connected":this.sendUploadStatus("start"),Q().then(e=>{this.sendUploadStatus("success",{packages:e.packages,isSuccess:e.isSuccess})}).catch(()=>{this.sendUploadStatus("failed",{isSuccess:!1})});break;case"closeBrowserDebug":console.log("close upload"),this.ws.close(),V.close(()=>{console.log("Dev server closed"),V=null,process.exit(0)}),console.log("close server")}}else{if("shareDevParams"===t.method){console.log("shareDevParams",t);const e=t.payload;console.log("shareDevParams",e);const{host:o,port:n}=e;A.setState({clientServerPort:n,clientServerHost:o});const s=`http://${o}:${n}?session=${function(){const e={ws_port:R,http_port:G};return btoa(JSON.stringify(e))}()}`;H(s),console.log(d.bold.yellow(`Game debug is ready! Visit ${s} in your browser.`))}}})})}send(e){this.ws.clients.forEach(t=>{if(t.readyState===S.OPEN){const o=Object.assign(Object.assign({},e),{from:"nodeServer"});t.send(JSON.stringify(o))}})}close(){this.ws.close()}sendUploadStatus(e,t){this.send({event:"uploadStatus",status:e,payload:t})}};async function Z(){const e=W(),t=m.join(B,e),o=U();console.log(d.yellow("Tips:")),console.log(` 1. ${d.blue("Scan the QR code to start the client devServer.")}`),console.log(` 2. ${d.blue("Will auto upload compiled resource to client.")} ${d.bold(t)}`),console.log(` 3. ${d.blue("Debug your game in the browser.")}\n`);const n=`https://www.tiktok.com/ttmg/dev/${e}?host=${o}&port=9529`;console.log(n),j.generate(n,{small:!0},e=>{console.log(function(e){const t=process.stdout.columns||80,o=e.split("\n"),n=o.reduce((e,t)=>Math.max(e,t.length),0),s=Math.floor((t-n)/3),i=" ".repeat(s>0?s:0);return o.map(e=>i+e).join("\n")}(e))})}async function X(){await J(),await Z(),await async function(){let e=null;a.watch(process.cwd(),(t,o)=>{console.log(d.yellow("game resource change, restart to upload")),e&&clearTimeout(e),e=setTimeout(async()=>{Y.sendUploadStatus("start"),Q().then(()=>{Y.sendUploadStatus("success")}).catch(()=>{Y.sendUploadStatus("failed")}),e=null},500)})}()}const ee=new r.Command;ee.name("ttmg").description("TikTok Mini Game Command Line Tool").version("0.0.1"),ee.command("init").option("--h5","H5 Mini Game").description("Initialize project").action(e=>{e.h5?I():console.log("Native Mini Game initialize")}),ee.command("dev").description("Open browser dev environment").option("--h5","H5 Mini Game").action(e=>{e.h5?x():X()}),ee.command("build").option("--h5","H5 Mini Game").description("Bundle project").action(async e=>{e.h5?P():console.log("Native Mini Game bundle")}),ee.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttmg/cli",
3
- "version": "0.0.12-beta.8",
3
+ "version": "0.0.12-beta.9",
4
4
  "description": "TikTok Mini Game Command Line Tool",
5
5
  "license": "ISC",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "multer": "^2.0.2",
38
38
  "prettier": "^3.6.2",
39
39
  "qrcode-terminal": "^0.12.0",
40
- "ttmg-pack": "^0.0.13",
40
+ "ttmg-pack": "^0.0.14",
41
41
  "ws": "^8.18.3"
42
42
  },
43
43
  "devDependencies": {