@ttmg/cli 0.1.9-beta.6 → 0.1.9-beta.8
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/README.md +19 -51
- package/dist/index.js +209 -130
- package/dist/index.js.map +1 -1
- package/dist/package.json +4 -6
- package/dist/public/assets/index-B-OUKblc.js +67 -0
- package/dist/public/assets/index-B-OUKblc.js.br +0 -0
- package/dist/public/assets/index-BNwkkghM.css +1 -0
- package/dist/public/assets/index-BNwkkghM.css.br +0 -0
- package/dist/public/assets/index-Bo2GE0iI.js +67 -0
- package/dist/public/assets/index-Bo2GE0iI.js.br +0 -0
- package/dist/public/assets/index-BuMld4Sl.css +1 -0
- package/dist/public/assets/index-BuMld4Sl.css.br +0 -0
- package/dist/public/assets/index-Cq-NkIfK.js +67 -0
- package/dist/public/assets/index-Cq-NkIfK.js.br +0 -0
- package/dist/public/assets/index-D80wbca2.js +67 -0
- package/dist/public/assets/index-D80wbca2.js.br +0 -0
- package/dist/public/assets/index-DIkT1K6H.js +67 -0
- package/dist/public/assets/index-DIkT1K6H.js.br +0 -0
- package/dist/public/assets/index-Dq6XWWnQ.js +67 -0
- package/dist/public/assets/index-Dq6XWWnQ.js.br +0 -0
- package/dist/public/assets/index-OaV93pLr.js +67 -0
- package/dist/public/assets/index-OaV93pLr.js.br +0 -0
- package/dist/public/index.html +2 -2
- package/dist/scripts/worker.js +3 -2
- package/package.json +4 -6
package/README.md
CHANGED
|
@@ -1,72 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
### @ttmg/cli
|
|
2
|
+
`ttmg` is a command-line tool designed for managing and developing mini-game projects. It supports initialization, development, debugging, and packaging for both H5 and native mini-games.
|
|
3
|
+
`ttmg` 是一款专为小游戏项目管理与开发设计的命令行工具,支持 H5 小游戏和原生小游戏的初始化、开发调试及打包构建。
|
|
4
|
+
#### Installation 安装
|
|
2
5
|
|
|
3
|
-
`ttmg`
|
|
4
|
-
|
|
5
|
-
### 安装
|
|
6
|
-
|
|
7
|
-
你可以通过 npm 本地安装或全局安装:
|
|
6
|
+
You can install `ttmg` globally or as a project dependency via npm:
|
|
7
|
+
你可以通过 npm 全局或本地安装 `ttmg`:
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
npm install @ttmg/cli -g
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Or add it as a development dependency in your project:
|
|
14
|
+
或者作为项目开发依赖安装:
|
|
14
15
|
|
|
15
16
|
```
|
|
16
17
|
npm install @ttmg/cli --save-dev
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
安装后,可以在命令行中使用 `ttmg` 命令。
|
|
22
|
-
|
|
23
|
-
#### 查看帮助
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
ttmg --help
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
#### 命令说明
|
|
30
|
-
|
|
31
|
-
##### 1. 初始化项目
|
|
32
|
-
|
|
33
|
-
- 对基于游戏引擎构建好的 H5 小游戏进行初始化
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
ttmg init --h5
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
- 对基于游戏引擎构建好的 Native 小游戏进行初始化
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
ttmg init --native
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
##### 2. 开发调试
|
|
20
|
+
#### Login 登录
|
|
46
21
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- H5 小游戏调试
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
ttmg dev --h5
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
- 原生小游戏调试
|
|
22
|
+
Before running any `ttmg` commands, please log in with your TikTok account. Once logged in, the tool will automatically use your account for project initialization, development, debugging, and packaging.
|
|
23
|
+
在首次使用 `ttmg` 命令前,请先登录你的 TikTok 账号。登录后,工具会自动使用该账号进行项目初始化、开发调试和打包构建。
|
|
56
24
|
|
|
57
25
|
```
|
|
58
|
-
ttmg
|
|
26
|
+
ttmg login
|
|
59
27
|
```
|
|
60
28
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
打包项目用于发布。
|
|
29
|
+
#### Debugging 调试
|
|
64
30
|
|
|
65
|
-
-
|
|
31
|
+
- By default, debug native mini-games:
|
|
32
|
+
- 默认调试原生小游戏:
|
|
66
33
|
|
|
67
34
|
```
|
|
68
|
-
ttmg
|
|
35
|
+
ttmg dev
|
|
69
36
|
```
|
|
70
37
|
|
|
71
|
-
-
|
|
72
|
-
|
|
38
|
+
- 调试 H5 小游戏:
|
|
39
|
+
- To debug H5 mini-games:
|
|
40
|
+
ttmg dev --h5
|
package/dist/index.js
CHANGED
|
@@ -17,13 +17,15 @@ var axios = require('axios');
|
|
|
17
17
|
var handlebars = require('handlebars');
|
|
18
18
|
var esbuild = require('esbuild');
|
|
19
19
|
var archiver = require('archiver');
|
|
20
|
+
var http = require('http');
|
|
20
21
|
var ttmgPack = require('ttmg-pack');
|
|
21
22
|
var expressStaticGzip = require('express-static-gzip');
|
|
23
|
+
var fileUpload = require('express-fileupload');
|
|
22
24
|
var chokidar = require('chokidar');
|
|
23
25
|
var WebSocket = require('ws');
|
|
24
26
|
var glob = require('glob');
|
|
25
27
|
var got = require('got');
|
|
26
|
-
var FormData = require('form-data');
|
|
28
|
+
var FormData$1 = require('form-data');
|
|
27
29
|
|
|
28
30
|
function _interopNamespaceDefault(e) {
|
|
29
31
|
var n = Object.create(null);
|
|
@@ -245,7 +247,7 @@ async function checkUpdate() {
|
|
|
245
247
|
|
|
246
248
|
let config$1 = null;
|
|
247
249
|
const CONFIG_PATH = path.join(os.homedir(), '.ttmgrc');
|
|
248
|
-
const
|
|
250
|
+
const getTTMGRC = () => {
|
|
249
251
|
if (config$1) {
|
|
250
252
|
return config$1;
|
|
251
253
|
}
|
|
@@ -263,7 +265,7 @@ const getTTmgrcConfig = () => {
|
|
|
263
265
|
}
|
|
264
266
|
}
|
|
265
267
|
};
|
|
266
|
-
const
|
|
268
|
+
const setTTMGRC = (config) => {
|
|
267
269
|
// updata cache config
|
|
268
270
|
config = config;
|
|
269
271
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config));
|
|
@@ -335,16 +337,13 @@ async function login() {
|
|
|
335
337
|
user_id: response?.data?.data?.user_id,
|
|
336
338
|
cookie: response?.headers['set-cookie'].join('; '),
|
|
337
339
|
};
|
|
338
|
-
|
|
340
|
+
setTTMGRC(data);
|
|
339
341
|
spinner$2.succeed(chalk.bold.green('login successfully!'));
|
|
340
342
|
process.exit(0);
|
|
341
343
|
}
|
|
342
344
|
}
|
|
343
345
|
|
|
344
|
-
|
|
345
|
-
* 获取配置文件中的 cookie
|
|
346
|
-
*/
|
|
347
|
-
const config = getTTmgrcConfig();
|
|
346
|
+
const config = getTTMGRC();
|
|
348
347
|
const cookie = config.cookie;
|
|
349
348
|
axios.defaults.headers.common['Cookie'] = cookie;
|
|
350
349
|
// ppe_dev_tool
|
|
@@ -399,9 +398,28 @@ async function fetchGameInfo(gameId) {
|
|
|
399
398
|
}
|
|
400
399
|
}
|
|
401
400
|
|
|
401
|
+
async function uploadGameToPlatform({ data, name, gameId, desc = '--', }) {
|
|
402
|
+
const formData = new FormData();
|
|
403
|
+
formData.append('file', new Blob([data], { type: 'application/zip' }), name);
|
|
404
|
+
formData.append('client_key', gameId);
|
|
405
|
+
formData.append('desc', desc);
|
|
406
|
+
const response = await request({
|
|
407
|
+
method: 'POST',
|
|
408
|
+
url: 'https://developers.tiktok.com/tiktok/v3/devportal/minigame/devtool/upload',
|
|
409
|
+
data: formData,
|
|
410
|
+
headers: {
|
|
411
|
+
'x-use-ppe': '1',
|
|
412
|
+
'x-tt-env': 'ppe_dev_tool',
|
|
413
|
+
// @ts-ignore
|
|
414
|
+
'Content-Type': `multipart/form-data; boundary=${formData._boundary}`,
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
return response?.data;
|
|
418
|
+
}
|
|
419
|
+
|
|
402
420
|
function getCurrentUser() {
|
|
403
421
|
try {
|
|
404
|
-
const config =
|
|
422
|
+
const config = getTTMGRC();
|
|
405
423
|
return {
|
|
406
424
|
email: config?.email || '',
|
|
407
425
|
user_id: config?.user_id || '',
|
|
@@ -729,10 +747,67 @@ function getOutputDir() {
|
|
|
729
747
|
return path.join(os.homedir(), '__TTMG__', clientKey);
|
|
730
748
|
}
|
|
731
749
|
|
|
732
|
-
const DEV_PORT =
|
|
733
|
-
const DEV_WS_PORT =
|
|
750
|
+
const DEV_PORT = 3000;
|
|
751
|
+
const DEV_WS_PORT = 4000;
|
|
734
752
|
path.join(os.homedir(), '__TTMG__');
|
|
735
753
|
|
|
754
|
+
// store.ts
|
|
755
|
+
class Store {
|
|
756
|
+
constructor(initialState) {
|
|
757
|
+
this.listeners = [];
|
|
758
|
+
this.state = initialState;
|
|
759
|
+
}
|
|
760
|
+
// 获取单例实例
|
|
761
|
+
static getInstance(initialState) {
|
|
762
|
+
if (!Store.instance) {
|
|
763
|
+
Store.instance = new Store(initialState);
|
|
764
|
+
}
|
|
765
|
+
return Store.instance;
|
|
766
|
+
}
|
|
767
|
+
// 获取状态
|
|
768
|
+
getState() {
|
|
769
|
+
return this.state;
|
|
770
|
+
}
|
|
771
|
+
// 设置状态
|
|
772
|
+
setState(newState) {
|
|
773
|
+
this.state = { ...this.state, ...newState };
|
|
774
|
+
this.listeners.forEach(listener => listener(this.state));
|
|
775
|
+
}
|
|
776
|
+
// 订阅状态变化
|
|
777
|
+
subscribe(listener) {
|
|
778
|
+
this.listeners.push(listener);
|
|
779
|
+
return () => {
|
|
780
|
+
this.listeners = this.listeners.filter(l => l !== listener);
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
// 重置状态
|
|
784
|
+
reset(newState) {
|
|
785
|
+
this.state = newState;
|
|
786
|
+
this.listeners.forEach(listener => listener(this.state));
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
const store = Store.getInstance({
|
|
790
|
+
clientHost: '',
|
|
791
|
+
clientHttpPort: '',
|
|
792
|
+
clientWsPort: '',
|
|
793
|
+
clientWsHost: '',
|
|
794
|
+
clientKey: '',
|
|
795
|
+
nodeServerPort: DEV_PORT,
|
|
796
|
+
nodeWsPort: DEV_WS_PORT,
|
|
797
|
+
packages: {},
|
|
798
|
+
isUnderCompiling: false,
|
|
799
|
+
isWaitingForUpload: false,
|
|
800
|
+
projectInfo: {
|
|
801
|
+
projectSize: 0,
|
|
802
|
+
mainPkg: {
|
|
803
|
+
pkgSize: 0,
|
|
804
|
+
isMatchSizeLimit: false,
|
|
805
|
+
},
|
|
806
|
+
independentPkgs: {},
|
|
807
|
+
},
|
|
808
|
+
checkResults: [],
|
|
809
|
+
});
|
|
810
|
+
|
|
736
811
|
function showTips(context) {
|
|
737
812
|
console.log(chalk.gray('─────────────────────────────────────────────'));
|
|
738
813
|
console.log(chalk.yellow.bold('1.') +
|
|
@@ -753,40 +828,29 @@ function showTips(context) {
|
|
|
753
828
|
}
|
|
754
829
|
|
|
755
830
|
const successCode = 0;
|
|
831
|
+
const errorCode = -1;
|
|
756
832
|
const outputDir = getOutputDir();
|
|
757
833
|
const publicPath = path.join(__dirname, 'public');
|
|
758
|
-
let server;
|
|
759
834
|
async function start() {
|
|
760
835
|
const startTime = Date.now();
|
|
761
|
-
if (server) {
|
|
762
|
-
closeServer();
|
|
763
|
-
}
|
|
764
836
|
const app = express();
|
|
837
|
+
app.use(fileUpload()); // 启用 express-fileupload 中间件
|
|
838
|
+
// --- 中间件和路由设置 ---
|
|
765
839
|
app.use(expressStaticGzip(publicPath, {
|
|
766
840
|
enableBrotli: true,
|
|
767
841
|
orderPreference: ['br'],
|
|
768
842
|
}));
|
|
769
|
-
/**
|
|
770
|
-
* 支持跨域请求
|
|
771
|
-
*/
|
|
772
843
|
app.use((req, res, next) => {
|
|
773
844
|
res.header('Access-Control-Allow-Origin', '*');
|
|
774
845
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
775
846
|
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
776
847
|
next();
|
|
777
848
|
});
|
|
778
|
-
const port = DEV_PORT;
|
|
779
849
|
app.use(express.json());
|
|
780
|
-
// 解析 FormData body
|
|
781
850
|
app.use(express.urlencoded({ extended: true }));
|
|
782
|
-
/**
|
|
783
|
-
* 支持静态资源
|
|
784
|
-
*/
|
|
785
851
|
app.use('/game/files', express.static(outputDir));
|
|
786
852
|
app.get('/game/config', async (req, res) => {
|
|
787
|
-
const basic = await ttmgPack.getPkgs({
|
|
788
|
-
entryDir: process.cwd(),
|
|
789
|
-
});
|
|
853
|
+
const basic = await ttmgPack.getPkgs({ entryDir: process.cwd() });
|
|
790
854
|
const { email, user_id } = getCurrentUser();
|
|
791
855
|
const { clientKey } = getClientKey();
|
|
792
856
|
res.send({
|
|
@@ -795,34 +859,23 @@ async function start() {
|
|
|
795
859
|
email,
|
|
796
860
|
user_id,
|
|
797
861
|
code: successCode,
|
|
798
|
-
nodeWsPort:
|
|
862
|
+
nodeWsPort: store.getState().nodeWsPort,
|
|
799
863
|
clientKey: clientKey,
|
|
800
|
-
schema: `https://www.tiktok.com/ttmg/dev/${clientKey}?host=${getLocalIP()}&port=${
|
|
864
|
+
schema: `https://www.tiktok.com/ttmg/dev/${clientKey}?host=${getLocalIP()}&port=${store.getState().nodeWsPort}`,
|
|
801
865
|
...basic,
|
|
802
866
|
devToolVersion: require(path.join(__dirname, 'package.json')).version,
|
|
803
867
|
},
|
|
804
868
|
});
|
|
805
869
|
});
|
|
806
870
|
app.get('/game/detail', async (req, res) => {
|
|
807
|
-
const basic = await ttmgPack.getPkgs({
|
|
808
|
-
entryDir: process.cwd(),
|
|
809
|
-
});
|
|
871
|
+
const basic = await ttmgPack.getPkgs({ entryDir: process.cwd() });
|
|
810
872
|
const { clientKey } = getClientKey();
|
|
811
873
|
const { error, data: gameInfo } = await fetchGameInfo(clientKey);
|
|
812
874
|
if (error) {
|
|
813
|
-
res.send({
|
|
814
|
-
error,
|
|
815
|
-
data: null,
|
|
816
|
-
});
|
|
875
|
+
res.send({ error, data: null });
|
|
817
876
|
return;
|
|
818
877
|
}
|
|
819
|
-
res.send({
|
|
820
|
-
error: null,
|
|
821
|
-
data: {
|
|
822
|
-
...basic,
|
|
823
|
-
...gameInfo,
|
|
824
|
-
},
|
|
825
|
-
});
|
|
878
|
+
res.send({ error: null, data: { ...basic, ...gameInfo } });
|
|
826
879
|
});
|
|
827
880
|
app.get('/game/check', async (req, res) => {
|
|
828
881
|
const checkResult = await ttmgPack.checkPkgs({
|
|
@@ -832,7 +885,7 @@ async function start() {
|
|
|
832
885
|
output: outputDir,
|
|
833
886
|
dev: {
|
|
834
887
|
enable: true,
|
|
835
|
-
port:
|
|
888
|
+
port: store.getState().nodeServerPort,
|
|
836
889
|
host: 'localhost',
|
|
837
890
|
enableSourcemap: false,
|
|
838
891
|
enableLog: false,
|
|
@@ -845,36 +898,108 @@ async function start() {
|
|
|
845
898
|
},
|
|
846
899
|
},
|
|
847
900
|
});
|
|
848
|
-
res.send({
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
901
|
+
res.send({ code: successCode, data: checkResult });
|
|
902
|
+
});
|
|
903
|
+
/**
|
|
904
|
+
* @description 上传游戏代码到服务器
|
|
905
|
+
*/
|
|
906
|
+
app.post('/game/upload', async (req, res) => {
|
|
907
|
+
const fileKeys = Object.keys(req.files);
|
|
908
|
+
const uploadedFile = req.files[fileKeys[0]];
|
|
909
|
+
if (!uploadedFile) {
|
|
910
|
+
res.status(400).send({ code: errorCode, data: 'No file uploaded' }); // 使用正确的 HTTP 状态码
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
// 通过 header 获取 desc
|
|
915
|
+
const desc = req.headers['ttmg-game-desc'];
|
|
916
|
+
// 直接传递需要的信息
|
|
917
|
+
const uploadResult = await uploadGameToPlatform({
|
|
918
|
+
data: uploadedFile.data, // 这是 Buffer
|
|
919
|
+
name: uploadedFile.name, // 这是文件名
|
|
920
|
+
gameId: getClientKey().clientKey,
|
|
921
|
+
desc,
|
|
922
|
+
});
|
|
923
|
+
res.send({ code: successCode, data: uploadResult });
|
|
924
|
+
}
|
|
925
|
+
catch (error) {
|
|
926
|
+
// 错误处理可以更具体
|
|
927
|
+
let errorMessage = 'An unknown error occurred.';
|
|
928
|
+
if (error instanceof Error) {
|
|
929
|
+
errorMessage = error.message;
|
|
930
|
+
}
|
|
931
|
+
// 打印详细错误到服务器日志,方便排查
|
|
932
|
+
res.status(500).send({ code: errorCode, data: errorMessage }); // 使用正确的 HTTP 状态码
|
|
933
|
+
}
|
|
852
934
|
});
|
|
853
935
|
app.get('*', (req, res) => {
|
|
854
936
|
res.sendFile(path.join(publicPath, 'index.html'));
|
|
855
937
|
});
|
|
856
|
-
//
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
938
|
+
// --- 中间件和路由设置结束 ---
|
|
939
|
+
// 步骤 2: 用配置好的 app 实例创建一个 http.Server。我们只创建这一次!
|
|
940
|
+
const server = http.createServer(app);
|
|
941
|
+
/**
|
|
942
|
+
* @description 尝试在指定端口启动服务。这是个纯粹的辅助函数。
|
|
943
|
+
* @param {number} port - 要尝试的端口号。
|
|
944
|
+
* @returns {Promise<boolean>} 成功返回 true,因端口占用失败则返回 false。
|
|
945
|
+
*/
|
|
946
|
+
function tryListen(port) {
|
|
947
|
+
return new Promise(resolve => {
|
|
948
|
+
// 定义错误处理函数
|
|
949
|
+
const onError = err => {
|
|
950
|
+
// 清理掉另一个事件的监听器,防止内存泄漏
|
|
951
|
+
server.removeListener('listening', onListening);
|
|
952
|
+
if (err.code === 'EADDRINUSE') {
|
|
953
|
+
console.log(chalk(`Port ${port} is already in use, trying ${port + 1}...`));
|
|
954
|
+
resolve(false); // 明确表示因端口占用而失败
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
// 对于其他致命错误,直接退出进程
|
|
958
|
+
console.log(chalk.red.bold(err.message));
|
|
959
|
+
process.exit(1);
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
// 定义成功处理函数
|
|
963
|
+
const onListening = () => {
|
|
964
|
+
// 清理掉另一个事件的监听器
|
|
965
|
+
server.removeListener('error', onError);
|
|
966
|
+
resolve(true); // 明确表示成功
|
|
967
|
+
};
|
|
968
|
+
// 使用 .once() 来确保监听器只执行一次然后自动移除
|
|
969
|
+
server.once('error', onError);
|
|
970
|
+
server.once('listening', onListening);
|
|
971
|
+
// 执行监听动作
|
|
972
|
+
server.listen(port);
|
|
865
973
|
});
|
|
866
|
-
}
|
|
867
|
-
|
|
974
|
+
}
|
|
975
|
+
// 步骤 3: 使用循环来线性、串行地尝试启动服务
|
|
976
|
+
let isListening = false;
|
|
977
|
+
const maxRetries = 20; // 设置一个最大重试次数,以防万一
|
|
978
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
979
|
+
const currentPort = store.getState().nodeServerPort;
|
|
980
|
+
isListening = await tryListen(currentPort);
|
|
981
|
+
if (isListening) {
|
|
982
|
+
break; // 成功,跳出循环
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
// 失败(端口占用),更新端口号,准备下一次循环
|
|
986
|
+
store.setState({ nodeServerPort: currentPort + 1 });
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
// 步骤 4: 检查最终结果,如果所有尝试都失败了,则退出
|
|
990
|
+
if (!isListening) {
|
|
991
|
+
console.log(chalk.red.bold(`Failed to start server after trying ${maxRetries} ports.`));
|
|
992
|
+
process.exit(1);
|
|
993
|
+
}
|
|
994
|
+
// --- 服务启动成功后的逻辑 ---
|
|
995
|
+
// @ts-ignore
|
|
996
|
+
const finalPort = server.address().port; // 从成功的 server 实例安全地获取最终端口
|
|
997
|
+
const version = require(path.join(__dirname, 'package.json')).version;
|
|
998
|
+
console.log(chalk.green.bold(`TTMG`), chalk.green(`v${version}`), chalk.gray(`ready in`), chalk.bold(`${Date.now() - startTime}ms`), chalk.green(`on port`), chalk.green.bold(finalPort));
|
|
999
|
+
const baseUrl = `http://localhost:${finalPort}`;
|
|
868
1000
|
showTips({ server: baseUrl });
|
|
869
1001
|
openUrl(baseUrl);
|
|
870
1002
|
}
|
|
871
|
-
function closeServer() {
|
|
872
|
-
server.close(() => {
|
|
873
|
-
console.log('Dev server closed');
|
|
874
|
-
server = null;
|
|
875
|
-
process.exit(0);
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
1003
|
|
|
879
1004
|
/**
|
|
880
1005
|
* 一个类型安全的、通用的事件发射器类。
|
|
@@ -951,63 +1076,12 @@ class TypedEventEmitter {
|
|
|
951
1076
|
}
|
|
952
1077
|
const eventEmitter = new TypedEventEmitter();
|
|
953
1078
|
|
|
954
|
-
class Store {
|
|
955
|
-
constructor(initialState) {
|
|
956
|
-
this.listeners = [];
|
|
957
|
-
this.state = initialState;
|
|
958
|
-
}
|
|
959
|
-
// 获取单例实例
|
|
960
|
-
static getInstance(initialState) {
|
|
961
|
-
if (!Store.instance) {
|
|
962
|
-
Store.instance = new Store(initialState);
|
|
963
|
-
}
|
|
964
|
-
return Store.instance;
|
|
965
|
-
}
|
|
966
|
-
// 获取状态
|
|
967
|
-
getState() {
|
|
968
|
-
return this.state;
|
|
969
|
-
}
|
|
970
|
-
// 设置状态
|
|
971
|
-
setState(newState) {
|
|
972
|
-
this.state = { ...this.state, ...newState };
|
|
973
|
-
this.listeners.forEach(listener => listener(this.state));
|
|
974
|
-
}
|
|
975
|
-
// 订阅状态变化
|
|
976
|
-
subscribe(listener) {
|
|
977
|
-
this.listeners.push(listener);
|
|
978
|
-
return () => {
|
|
979
|
-
this.listeners = this.listeners.filter(l => l !== listener);
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
// 重置状态
|
|
983
|
-
reset(newState) {
|
|
984
|
-
this.state = newState;
|
|
985
|
-
this.listeners.forEach(listener => listener(this.state));
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
const store = Store.getInstance({
|
|
989
|
-
clientHost: '',
|
|
990
|
-
clientHttpPort: '',
|
|
991
|
-
clientWsPort: '',
|
|
992
|
-
clientWsHost: '',
|
|
993
|
-
clientKey: '',
|
|
994
|
-
packages: {},
|
|
995
|
-
isUnderCompiling: false,
|
|
996
|
-
isWaitingForUpload: false,
|
|
997
|
-
projectInfo: {
|
|
998
|
-
projectSize: 0,
|
|
999
|
-
mainPkg: {
|
|
1000
|
-
pkgSize: 0,
|
|
1001
|
-
isMatchSizeLimit: false,
|
|
1002
|
-
},
|
|
1003
|
-
independentPkgs: {},
|
|
1004
|
-
},
|
|
1005
|
-
checkResults: [],
|
|
1006
|
-
});
|
|
1007
|
-
|
|
1008
1079
|
class WsServer {
|
|
1009
1080
|
constructor() {
|
|
1010
|
-
this.
|
|
1081
|
+
this.startWsServer(store.getState().nodeWsPort);
|
|
1082
|
+
}
|
|
1083
|
+
startWsServer(port) {
|
|
1084
|
+
this.ws = new WebSocket.Server({ port });
|
|
1011
1085
|
this.ws.on('connection', ws => {
|
|
1012
1086
|
const { clientHttpPort, clientHost, clientWsPort } = store.getState();
|
|
1013
1087
|
if (clientHost) {
|
|
@@ -1041,8 +1115,6 @@ class WsServer {
|
|
|
1041
1115
|
* 关闭调试服务
|
|
1042
1116
|
*/
|
|
1043
1117
|
this.ws.close();
|
|
1044
|
-
closeServer();
|
|
1045
|
-
console.log('close server');
|
|
1046
1118
|
break;
|
|
1047
1119
|
}
|
|
1048
1120
|
}
|
|
@@ -1112,6 +1184,18 @@ class WsServer {
|
|
|
1112
1184
|
}
|
|
1113
1185
|
});
|
|
1114
1186
|
});
|
|
1187
|
+
this.ws.on('error', err => {
|
|
1188
|
+
if (err.code === 'EADDRINUSE') {
|
|
1189
|
+
store.setState({
|
|
1190
|
+
nodeWsPort: store.getState().nodeWsPort + 1,
|
|
1191
|
+
});
|
|
1192
|
+
this.startWsServer(store.getState().nodeWsPort);
|
|
1193
|
+
}
|
|
1194
|
+
else {
|
|
1195
|
+
console.log(chalk.red.bold(err.message));
|
|
1196
|
+
process.exit(1);
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1115
1199
|
}
|
|
1116
1200
|
send(params) {
|
|
1117
1201
|
this.ws.clients.forEach(client => {
|
|
@@ -1225,7 +1309,7 @@ function compile(context) {
|
|
|
1225
1309
|
context: {
|
|
1226
1310
|
clientKey,
|
|
1227
1311
|
outputDir,
|
|
1228
|
-
devPort:
|
|
1312
|
+
devPort: store.getState().nodeServerPort,
|
|
1229
1313
|
entryDir,
|
|
1230
1314
|
},
|
|
1231
1315
|
});
|
|
@@ -1331,7 +1415,7 @@ async function zipDirectory(sourceDir, outPath) {
|
|
|
1331
1415
|
}
|
|
1332
1416
|
async function uploadZip(zipPath, callback) {
|
|
1333
1417
|
const startTime = Date.now();
|
|
1334
|
-
const form = new FormData();
|
|
1418
|
+
const form = new FormData$1();
|
|
1335
1419
|
form.append('file', fs.createReadStream(zipPath), {
|
|
1336
1420
|
filename: 'upload.zip',
|
|
1337
1421
|
contentType: 'application/zip',
|
|
@@ -1467,7 +1551,7 @@ async function upload() {
|
|
|
1467
1551
|
});
|
|
1468
1552
|
}
|
|
1469
1553
|
|
|
1470
|
-
var version = "0.1.9-beta.
|
|
1554
|
+
var version = "0.1.9-beta.8";
|
|
1471
1555
|
var pkg = {
|
|
1472
1556
|
version: version};
|
|
1473
1557
|
|
|
@@ -1512,11 +1596,6 @@ program
|
|
|
1512
1596
|
}
|
|
1513
1597
|
else {
|
|
1514
1598
|
dev();
|
|
1515
|
-
try {
|
|
1516
|
-
checkUpdate();
|
|
1517
|
-
// eslint-disable-next-line no-empty
|
|
1518
|
-
}
|
|
1519
|
-
catch (err) { }
|
|
1520
1599
|
}
|
|
1521
1600
|
});
|
|
1522
1601
|
/**
|