@ttmg/cli 0.0.3-beta.5 → 0.0.3-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18,7 +18,6 @@ var semver = require('semver');
18
18
  var handlebars = require('handlebars');
19
19
  var esbuild = require('esbuild');
20
20
  var archiver = require('archiver');
21
- var multer = require('multer');
22
21
  var WebSocket = require('ws');
23
22
  var glob = require('glob');
24
23
  var got = require('got');
@@ -204,6 +203,7 @@ async function openUrl(url) {
204
203
  '--remote-allow-origins=*',
205
204
  '--user-data-dir=/tmp/chrome-debug-profile',
206
205
  '--disable-popup-blocking',
206
+ '--force-dark-mode=off',
207
207
  ],
208
208
  });
209
209
  await new Promise(() => { });
@@ -639,73 +639,27 @@ function getOutputDir() {
639
639
  return path.join(os.homedir(), '__TTMG__', clientKey);
640
640
  }
641
641
 
642
- class Store {
643
- constructor(initialState) {
644
- this.listeners = [];
645
- this.state = initialState;
646
- }
647
- // 获取单例实例
648
- static getInstance(initialState) {
649
- if (!Store.instance) {
650
- Store.instance = new Store(initialState);
651
- }
652
- return Store.instance;
653
- }
654
- // 获取状态
655
- getState() {
656
- return this.state;
657
- }
658
- // 设置状态
659
- setState(newState) {
660
- this.state = { ...this.state, ...newState };
661
- this.listeners.forEach(listener => listener(this.state));
662
- }
663
- // 订阅状态变化
664
- subscribe(listener) {
665
- this.listeners.push(listener);
666
- return () => {
667
- this.listeners = this.listeners.filter(l => l !== listener);
668
- };
669
- }
670
- // 重置状态
671
- reset(newState) {
672
- this.state = newState;
673
- this.listeners.forEach(listener => listener(this.state));
674
- }
675
- }
676
- const store = Store.getInstance({
677
- clientServerPort: '',
678
- clientServerHost: '',
679
- clientKey: '',
680
- packages: {},
681
- });
682
-
683
642
  const DEV_PORT = 9528;
684
643
  const DEV_WS_PORT = 9529;
685
644
  const OUTPUT_DIR = path.join(os.homedir(), '__TTMG__');
686
645
 
687
- function getSessionId({ clientWsPort }) {
688
- const config = {
689
- ws_port: DEV_WS_PORT,
690
- nodeWsPort: DEV_WS_PORT,
691
- // http_port: DEV_PORT,
692
- clientWsPort,
693
- };
694
- return btoa(JSON.stringify(config));
695
- }
696
-
697
- /**
698
- * 把我的调试相关的数据,生成 base64 字符串
699
- */
700
646
  let server;
701
647
  async function createServer() {
702
648
  if (server) {
703
649
  closeServer();
704
650
  }
651
+ const publicPath = path.join(__dirname, 'public');
705
652
  const app = express();
706
- const port = DEV_PORT; // 你可以自定义端口
707
- const upload = multer({ dest: os.tmpdir() }); // 上传到 uploads 目录
708
- // 解析 JSON body
653
+ /**
654
+ * 支持跨域请求
655
+ */
656
+ app.use((req, res, next) => {
657
+ res.header('Access-Control-Allow-Origin', '*');
658
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
659
+ res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
660
+ next();
661
+ });
662
+ const port = DEV_PORT;
709
663
  app.use(express.json());
710
664
  // 解析 FormData body
711
665
  app.use(express.urlencoded({ extended: true }));
@@ -713,6 +667,10 @@ async function createServer() {
713
667
  * 支持文件访问
714
668
  */
715
669
  const outputDir = getOutputDir();
670
+ /**
671
+ *
672
+ */
673
+ app.use(express.static(publicPath));
716
674
  /**
717
675
  * 支持静态资源
718
676
  */
@@ -720,42 +678,11 @@ async function createServer() {
720
678
  app.get('/game/qrcode', async (req, res) => {
721
679
  res.sendFile(path.join(OUTPUT_DIR, `dev-qrcode.png`));
722
680
  });
723
- /**
724
- * 支持文件访问
725
- */
726
- /**
727
- * TODO: 提供的接口供客户端进行调用,告诉 NodeServer 客户端的 host 和 port
728
- */
729
- app.post('/game/env', async (req, res) => {
730
- // 获取请求体
731
- const body = req.body;
732
- const { host, port, wsPort } = body;
733
- store.setState({
734
- clientServerPort: port,
735
- clientServerHost: host,
736
- });
737
- // 响应内容(可以根据实际业务返回需要的数据)
738
- const devUrl = `http://${host}:${port}?session=${getSessionId({ clientWsPort: wsPort })}`;
739
- openUrl(devUrl);
740
- console.log(chalk.bold.yellow(`Game debug is ready! Visit ${devUrl} in your browser.`));
741
- res.json({
742
- code: 0,
743
- msg: 'ok',
744
- data: {
745
- devUrl,
746
- },
747
- });
681
+ app.get('/game/config', async (req, res) => {
682
+ res.send({ nodeWsPort: DEV_WS_PORT });
748
683
  });
749
- app.post('/game/upload', upload.single('file'), async (req, res) => {
750
- // 文件信息在 req.file
751
- res.json({
752
- code: 0,
753
- msg: 'ok',
754
- filename: req.file.filename, // multer生成的临时文件名
755
- originalname: req.file.originalname, // 上传时的原始文件名
756
- size: req.file.size,
757
- path: req.file.path,
758
- });
684
+ app.get('*', (req, res) => {
685
+ res.sendFile(path.join(publicPath, 'index.html'));
759
686
  });
760
687
  // 启动服务
761
688
  server = app.listen(port, () => {
@@ -774,11 +701,54 @@ function closeServer() {
774
701
  });
775
702
  }
776
703
 
704
+ class Store {
705
+ constructor(initialState) {
706
+ this.listeners = [];
707
+ this.state = initialState;
708
+ }
709
+ // 获取单例实例
710
+ static getInstance(initialState) {
711
+ if (!Store.instance) {
712
+ Store.instance = new Store(initialState);
713
+ }
714
+ return Store.instance;
715
+ }
716
+ // 获取状态
717
+ getState() {
718
+ return this.state;
719
+ }
720
+ // 设置状态
721
+ setState(newState) {
722
+ this.state = { ...this.state, ...newState };
723
+ this.listeners.forEach(listener => listener(this.state));
724
+ }
725
+ // 订阅状态变化
726
+ subscribe(listener) {
727
+ this.listeners.push(listener);
728
+ return () => {
729
+ this.listeners = this.listeners.filter(l => l !== listener);
730
+ };
731
+ }
732
+ // 重置状态
733
+ reset(newState) {
734
+ this.state = newState;
735
+ this.listeners.forEach(listener => listener(this.state));
736
+ }
737
+ }
738
+ const store = Store.getInstance({
739
+ clientHost: '',
740
+ clientHttpPort: '',
741
+ clientWsPort: '',
742
+ clientWsHost: '',
743
+ clientKey: '',
744
+ packages: {},
745
+ });
746
+
777
747
  async function uploadGame(callback) {
778
748
  const outputDir = getOutputDir();
779
749
  callback({
780
750
  status: 'start',
781
- percent: '0%',
751
+ percent: 0,
782
752
  });
783
753
  console.log(chalk.yellow.bold('Start compress game resource'));
784
754
  const zipPath = path.join(os.homedir(), '__TTMG__', 'upload.zip');
@@ -786,31 +756,59 @@ async function uploadGame(callback) {
786
756
  console.log(chalk.green.bold('Compress game package resource success \n'));
787
757
  await uploadZip(zipPath, callback);
788
758
  }
759
+ /**
760
+ * 复制源目录内容到临时目录,然后根据 glob 模式过滤并压缩文件。
761
+ * 原始目录保持不变。
762
+ *
763
+ * @param sourceDir - 要压缩的源文件夹路径。
764
+ * @param outPath - 输出的 zip 文件路径。
765
+ */
789
766
  async function zipDirectory(sourceDir, outPath) {
790
- const output = fs.createWriteStream(outPath);
791
- const archive = archiver('zip', { zlib: { level: 9 } });
792
- // eslint-disable-next-line no-async-promise-executor
793
- return new Promise(async (resolve, reject) => {
794
- output.on('close', () => resolve());
795
- output.on('error', err => reject(err));
796
- archive.on('error', err => reject(err));
797
- archive.pipe(output);
798
- try {
799
- // 注意这里用 await glob.glob
800
- const files = await glob__namespace.glob('**/*', {
801
- cwd: sourceDir,
802
- nodir: true,
803
- ignore: '**/*.js.map',
804
- });
805
- files.forEach(file => {
806
- archive.file(path.join(sourceDir, file), { name: file });
767
+ // 1. 创建一个唯一的临时目录
768
+ // fsp 现在是 fs.promises 的别名
769
+ const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'zip-temp-'));
770
+ try {
771
+ // 2. 将源目录的所有内容复制到临时目录
772
+ await fs.promises.cp(sourceDir, tempDir, { recursive: true });
773
+ // 3. 对临时目录进行压缩
774
+ const output = fs.createWriteStream(outPath);
775
+ const archive = archiver('zip', { zlib: { level: 9 } });
776
+ // 使用 Promise 包装流操作
777
+ const archivePromise = new Promise((resolve, reject) => {
778
+ output.on('close', () => {
779
+ console.log(`压缩完成,总大小: ${archive.pointer()} bytes`);
780
+ resolve();
807
781
  });
808
- archive.finalize();
809
- }
810
- catch (err) {
811
- reject(err);
782
+ archive.on('warning', err => console.warn('Archiver warning:', err));
783
+ output.on('error', err => reject(err));
784
+ archive.on('error', err => reject(err));
785
+ });
786
+ archive.pipe(output);
787
+ // 4. 使用 glob 在临时目录中查找文件,并应用过滤规则
788
+ const files = await glob__namespace.glob('**/*', {
789
+ cwd: tempDir,
790
+ nodir: true,
791
+ ignore: '**/*.js.map', // 过滤规则
792
+ });
793
+ // 5. 将过滤后的文件逐个添加到压缩包
794
+ for (const file of files) {
795
+ const filePath = path.join(tempDir, file);
796
+ archive.file(filePath, { name: file });
812
797
  }
813
- });
798
+ // 6. 完成压缩
799
+ await archive.finalize();
800
+ // 等待文件流关闭
801
+ await archivePromise;
802
+ }
803
+ catch (err) {
804
+ console.error('压缩过程中发生错误:', err);
805
+ throw err;
806
+ }
807
+ finally {
808
+ // 7. 无论成功还是失败,都清理临时目录
809
+ await fs.promises.rm(tempDir, { recursive: true, force: true });
810
+ console.log(`临时目录 ${tempDir} 已清理。`);
811
+ }
814
812
  }
815
813
  async function uploadZip(zipPath, callback) {
816
814
  const form = new FormData();
@@ -821,8 +819,8 @@ async function uploadZip(zipPath, callback) {
821
819
  // 帮我计算下文件大小,变成 MB 为单位
822
820
  const fileSize = fs.statSync(zipPath).size / 1024 / 1024;
823
821
  console.log(chalk.yellow.bold(`Start upload resource to client, size: ${fileSize.toFixed(2)} MB`));
824
- const { clientServerHost, clientServerPort } = store.getState();
825
- const url = `http://${clientServerHost}:${clientServerPort}/game/upload`;
822
+ const { clientHttpPort, clientHost } = store.getState();
823
+ const url = `http://${clientHost}:${clientHttpPort}/game/upload`;
826
824
  try {
827
825
  // 1. 创建请求流
828
826
  const stream = got.stream.post(url, {
@@ -830,13 +828,13 @@ async function uploadZip(zipPath, callback) {
830
828
  });
831
829
  // 2. 监听上传进度 (这个回调是并行的,不影响封装)
832
830
  stream.on('uploadProgress', progress => {
833
- const percent = (progress.percent * 100).toFixed(1);
831
+ const percent = progress.percent;
834
832
  // const transferred = progress.transferred;
835
833
  // const total = progress.total;
836
834
  process.stdout.write(`\r${chalk.cyan('Uploading progress: ')}${chalk.green(percent + '%')}`);
837
835
  callback({
838
836
  status: 'process',
839
- percent: `${percent}%`,
837
+ percent,
840
838
  });
841
839
  });
842
840
  // 3. 【核心封装】将流的处理过程包装在 Promise 中
@@ -855,7 +853,7 @@ async function uploadZip(zipPath, callback) {
855
853
  // 将完整的响应对象 resolve 出去
856
854
  callback({
857
855
  status: 'success',
858
- percent: '100%',
856
+ percent: 1,
859
857
  });
860
858
  resolve({
861
859
  statusCode: 200,
@@ -867,7 +865,7 @@ async function uploadZip(zipPath, callback) {
867
865
  reject(err);
868
866
  callback({
869
867
  status: 'error',
870
- percent: '',
868
+ percent: 0,
871
869
  msg: err.message,
872
870
  });
873
871
  });
@@ -875,13 +873,12 @@ async function uploadZip(zipPath, callback) {
875
873
  // 4. 当 await 完成后,说明流已成功结束,可以安全地执行后续操作
876
874
  process.stdout.write('\n'); // 换行,保持终端整洁
877
875
  console.log(chalk.green.bold('✔ Upload completed successfully!'));
878
- fs.unlinkSync(zipPath);
879
876
  return response;
880
877
  }
881
878
  catch (err) {
882
879
  callback({
883
880
  status: 'error',
884
- percent: '',
881
+ percent: 0,
885
882
  msg: err?.message,
886
883
  });
887
884
  process.stdout.write('\n');
@@ -901,16 +898,26 @@ class WsServer {
901
898
  constructor() {
902
899
  this.ws = new WebSocket.Server({ port: DEV_WS_PORT });
903
900
  this.ws.on('connection', ws => {
901
+ const { clientHttpPort, clientHost, clientWsPort } = store.getState();
902
+ if (clientHost) {
903
+ this.send({
904
+ method: 'clientDebugInfo',
905
+ payload: {
906
+ clientHttpPort,
907
+ clientHost,
908
+ clientWsPort,
909
+ },
910
+ });
911
+ }
904
912
  ws.on('message', message => {
905
913
  /** 客户端发送的消息 */
906
914
  const clientMessage = JSON.parse(message.toString());
907
- console.log('Client Message', clientMessage);
908
915
  const from = clientMessage.from;
909
916
  if (from === 'browser') {
917
+ console.log(chalk.yellow.bold('Browser message'), clientMessage);
910
918
  const method = clientMessage.method;
911
919
  switch (method) {
912
920
  case 'startUpload':
913
- console.log('startUpload');
914
921
  this.sendUploadStatus('start');
915
922
  uploadGame(({ status, percent, msg }) => {
916
923
  if (status === 'process') {
@@ -949,26 +956,67 @@ class WsServer {
949
956
  }
950
957
  }
951
958
  else {
959
+ console.log(chalk.green.bold('Client message'), clientMessage);
952
960
  const method = clientMessage.method;
953
961
  switch (method) {
962
+ /**
963
+ * 客户端完成扫码成功,返回客户端的 host 和 port
964
+ */
965
+ case 'startScanQRcode': {
966
+ const payload = clientMessage.payload;
967
+ console.log('startQRcode', payload);
968
+ this.send({
969
+ method: 'startScanQRcode',
970
+ });
971
+ break;
972
+ }
973
+ case 'scanQRCodeResult': {
974
+ const payload = clientMessage.payload || {};
975
+ const { host, port, wsPort, errMsg, isSuccess } = payload;
976
+ if (isSuccess) {
977
+ store.setState({
978
+ clientHttpPort: port,
979
+ clientHost: host,
980
+ clientWsPort: wsPort,
981
+ });
982
+ this.send({
983
+ method: 'scanQRCodeSuccess',
984
+ payload: {
985
+ clientHttpPort: port,
986
+ clientHost: host,
987
+ clientWsPort: wsPort,
988
+ },
989
+ });
990
+ console.log('scanQRcodeSuccess');
991
+ }
992
+ else {
993
+ this.send({
994
+ method: 'scanQRCodeFailed',
995
+ payload: {
996
+ errMsg,
997
+ },
998
+ });
999
+ }
1000
+ break;
1001
+ }
1002
+ // 待废弃
954
1003
  case 'shareDevParams':
955
- console.log('shareDevParams', clientMessage);
956
1004
  const payload = clientMessage.payload;
957
- console.log('shareDevParams', payload);
958
1005
  const { host, port, wsPort } = payload;
959
1006
  store.setState({
960
- clientServerPort: port,
961
- clientServerHost: host,
1007
+ clientHttpPort: port,
1008
+ clientHost: host,
1009
+ clientWsPort: wsPort,
1010
+ clientWsHost: host,
1011
+ });
1012
+ this.send({
1013
+ method: 'scanQRCodeSuccess',
1014
+ payload: {
1015
+ clientHttpPort: port,
1016
+ clientHost: host,
1017
+ clientWsPort: wsPort,
1018
+ },
962
1019
  });
963
- // 响应内容(可以根据实际业务返回需要的数据)
964
- const devUrl = `http://${host}:${port}?session=${getSessionId({ clientWsPort: wsPort })}`;
965
- openUrl(devUrl);
966
- console.log(chalk.bold.yellow(`Game debug is ready! Visit ${devUrl} in your browser.`));
967
- break;
968
- // 鉴权失败
969
- case 'checkPermissionFailed':
970
- // 提醒使用客户端先完成测试用户授权,再重新扫描二维码开启调试服务
971
- console.log(chalk.red.bold('Check permission failed! Please authorize in client first.'));
972
1020
  break;
973
1021
  }
974
1022
  }
@@ -986,6 +1034,11 @@ class WsServer {
986
1034
  }
987
1035
  });
988
1036
  }
1037
+ sendResourceChange() {
1038
+ this.send({
1039
+ method: 'resourceChange',
1040
+ });
1041
+ }
989
1042
  close() {
990
1043
  this.ws.close();
991
1044
  }
@@ -1026,6 +1079,7 @@ async function prepareResource(context) {
1026
1079
  enableLog: false,
1027
1080
  },
1028
1081
  build: {
1082
+ enableOdr: false,
1029
1083
  pkgSizeLimit: 30 * 1024 * 1024,
1030
1084
  mainPkgSizeLimit: 4 * 1024 * 1024,
1031
1085
  },
@@ -1040,6 +1094,11 @@ async function prepareResource(context) {
1040
1094
  packages,
1041
1095
  });
1042
1096
  console.log(chalk.green.bold('Compile game package success \n'));
1097
+ return {
1098
+ isSuccess,
1099
+ errorMsg,
1100
+ packages,
1101
+ };
1043
1102
  }
1044
1103
  }
1045
1104
 
@@ -1055,9 +1114,7 @@ async function watchChange() {
1055
1114
  await prepareResource({
1056
1115
  mode: 'watch',
1057
1116
  });
1058
- wsServer.send({
1059
- method: 'resourceChange',
1060
- });
1117
+ wsServer.sendResourceChange();
1061
1118
  // TODO:只做文件预准备,但不主动上传
1062
1119
  // uploadGame()
1063
1120
  // .then((res) => {
@@ -1097,7 +1154,7 @@ async function showSchema() {
1097
1154
  margin: 1,
1098
1155
  });
1099
1156
  // 4. 构建可通过静态服务器访问的 URL
1100
- const qrCodeUrl = `http://localhost:${DEV_PORT}/game/qrcode`;
1157
+ const qrCodeUrl = `http://localhost:${DEV_PORT}?enableLog=1`;
1101
1158
  // 5. 打印更新后的提示信息
1102
1159
  console.log(chalk.green.bold('Tips:'));
1103
1160
  console.log(` 1. ${chalk.yellow.bold('Open the link below in your browser to see the QR code, then scan it.')}`);
@@ -1133,7 +1190,7 @@ async function dev() {
1133
1190
  await watchChange();
1134
1191
  }
1135
1192
 
1136
- var version = "0.0.3-beta.5";
1193
+ var version = "0.0.3-beta.6";
1137
1194
  var pkg = {
1138
1195
  version: version};
1139
1196