@ttmg/cli 0.3.2-beta.5 → 0.3.2-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 +1616 -989
- package/dist/index.js.map +1 -1
- package/dist/openDataContext/template/open_context.html.hbs +29 -0
- package/dist/openDataContext/template/open_context_sdk.js.hbs +16 -0
- package/dist/package.json +4 -3
- package/dist/public/assets/index-0QsVohJs.js +1 -0
- package/dist/public/assets/index-1oO8mc2L.js +1 -0
- package/dist/public/assets/index-2LCyHbgv.js +1 -0
- package/dist/public/assets/index-5oEydzr8.js +1 -0
- package/dist/public/assets/index-7DkNQbVL.js +1 -0
- package/dist/public/assets/index-7DkNQbVL.js.br +0 -0
- package/dist/public/assets/index-7gXKDEMH.js +1 -0
- package/dist/public/assets/index-A5pSGmoW.js +1 -0
- package/dist/public/assets/index-A5pSGmoW.js.br +0 -0
- package/dist/public/assets/index-B-vfB9Ya.js +1 -0
- package/dist/public/assets/index-B0Cw26f2.js +1 -0
- package/dist/public/assets/index-B0lMLCEN.js +1 -0
- package/dist/public/assets/index-B0lMLCEN.js.br +0 -0
- package/dist/public/assets/index-B6HUBYPb.js +1 -0
- package/dist/public/assets/index-B7JMUpIM.js +23 -0
- package/dist/public/assets/index-B7JMUpIM.js.br +0 -0
- package/dist/public/assets/index-B7j-geVc.js +1 -0
- package/dist/public/assets/index-B8N8nU5k.js +1 -0
- package/dist/public/assets/index-B90t3m2Y.js +1 -0
- package/dist/public/assets/index-BA1vKWLl.js +1 -0
- package/dist/public/assets/index-BB5kLk47.css +1 -0
- package/dist/public/assets/index-BBiD3ZY0.js +1 -0
- package/dist/public/assets/index-BEkRt3ox.js +1 -0
- package/dist/public/assets/index-BH5OVuUr.js +1 -0
- package/dist/public/assets/index-BH5OVuUr.js.br +0 -0
- package/dist/public/assets/index-BIJQ2jN-.js +1 -0
- package/dist/public/assets/index-BK45hlql.js +1 -0
- package/dist/public/assets/index-BK45hlql.js.br +0 -0
- package/dist/public/assets/index-BNnfKu4_.js +1 -0
- package/dist/public/assets/index-BPf4cmgA.css +1 -0
- package/dist/public/assets/index-BQQoGoko.js +1 -0
- package/dist/public/assets/index-BR-m6RXi.js +1 -0
- package/dist/public/assets/index-BRZmkpre.js +1 -0
- package/dist/public/assets/index-BSH4idOV.js +1 -0
- package/dist/public/assets/index-BSH4idOV.js.br +0 -0
- package/dist/public/assets/index-BSb2BUk_.js +1 -0
- package/dist/public/assets/index-BTnlaKEm.js +1 -0
- package/dist/public/assets/index-BUT4SrUj.js +1 -0
- package/dist/public/assets/index-BUT4SrUj.js.br +0 -0
- package/dist/public/assets/index-BWO_zr5E.js +1 -0
- package/dist/public/assets/index-BWO_zr5E.js.br +0 -0
- package/dist/public/assets/index-Bb28RxEr.js +1 -0
- package/dist/public/assets/index-Bb28RxEr.js.br +0 -0
- package/dist/public/assets/index-BdGK9RSJ.js +1 -0
- package/dist/public/assets/index-Bf6aJOeV.css +1 -0
- package/dist/public/assets/index-BfX_lbWX.css +1 -0
- package/dist/public/assets/index-BfX_lbWX.css.br +0 -0
- package/dist/public/assets/index-BfnzurfX.js +1 -0
- package/dist/public/assets/index-BkiB8M4Y.css +1 -0
- package/dist/public/assets/index-BkiB8M4Y.css.br +0 -0
- package/dist/public/assets/index-BmT98gGj.js +1 -0
- package/dist/public/assets/index-BmT98gGj.js.br +0 -0
- package/dist/public/assets/index-Bpv7pLX2.js +1 -0
- package/dist/public/assets/index-Bpv7pLX2.js.br +0 -0
- package/dist/public/assets/index-Bq6YxKLX.css +1 -0
- package/dist/public/assets/index-BreEkwI6.js +1 -0
- package/dist/public/assets/index-BreEkwI6.js.br +0 -0
- package/dist/public/assets/index-Bs8SawoL.js +1 -0
- package/dist/public/assets/index-Bs8SawoL.js.br +0 -0
- package/dist/public/assets/index-Bue_fL5q.css +1 -0
- package/dist/public/assets/index-Bue_fL5q.css.br +0 -0
- package/dist/public/assets/index-BujwLBmz.js +1 -0
- package/dist/public/assets/index-BvC2EkcY.js +1 -0
- package/dist/public/assets/index-Bw3iU4wV.js +1 -0
- package/dist/public/assets/index-BwbPFgZF.css +1 -0
- package/dist/public/assets/index-ByAY7l0v.js +1 -0
- package/dist/public/assets/index-Byxv2SPa.js +1 -0
- package/dist/public/assets/index-C250u6X0.css +1 -0
- package/dist/public/assets/index-C250u6X0.css.br +0 -0
- package/dist/public/assets/index-C2qYQNvg.js +1 -0
- package/dist/public/assets/index-C2qYQNvg.js.br +0 -0
- package/dist/public/assets/index-C46fvTvh.js +23 -0
- package/dist/public/assets/index-C46fvTvh.js.br +0 -0
- package/dist/public/assets/index-C4FRfjag.css +1 -0
- package/dist/public/assets/index-C4cINkXW.js +1 -0
- package/dist/public/assets/index-C4cINkXW.js.br +0 -0
- package/dist/public/assets/index-C8f539tK.css +1 -0
- package/dist/public/assets/index-C9KcyJn2.js +1 -0
- package/dist/public/assets/index-C9_SXd2H.css +1 -0
- package/dist/public/assets/index-CAwhcizP.css +1 -0
- package/dist/public/assets/index-CAwhcizP.css.br +0 -0
- package/dist/public/assets/index-CFwZ2h-R.js +1 -0
- package/dist/public/assets/index-CHTG85h6.js +1 -0
- package/dist/public/assets/index-CL1kuXlO.js +1 -0
- package/dist/public/assets/index-CPibVFVC.js +1 -0
- package/dist/public/assets/index-CSEt7Wc0.js +25 -0
- package/dist/public/assets/index-CSEt7Wc0.js.br +0 -0
- package/dist/public/assets/index-CUlfC1eK.js +1 -0
- package/dist/public/assets/index-CUlfC1eK.js.br +0 -0
- package/dist/public/assets/index-CV0599Ro.js +1 -0
- package/dist/public/assets/index-CV0599Ro.js.br +0 -0
- package/dist/public/assets/index-CWMGWfEt.js +1 -0
- package/dist/public/assets/index-CWS-rk-J.js +23 -0
- package/dist/public/assets/index-CWS-rk-J.js.br +0 -0
- package/dist/public/assets/index-CXQMSlbc.js +1 -0
- package/dist/public/assets/index-CXQMSlbc.js.br +0 -0
- package/dist/public/assets/index-CYyj9uYU.js +1 -0
- package/dist/public/assets/index-CYyj9uYU.js.br +0 -0
- package/dist/public/assets/index-CZtKeBZm.js +1 -0
- package/dist/public/assets/index-Ca67110A.js +1 -0
- package/dist/public/assets/index-CdrbgBJX.js +1 -0
- package/dist/public/assets/index-CfmWGzK2.js +1 -0
- package/dist/public/assets/index-Cfp2WBke.js +1 -0
- package/dist/public/assets/index-Cfp2WBke.js.br +0 -0
- package/dist/public/assets/index-CgVq-50w.css +1 -0
- package/dist/public/assets/index-CgVq-50w.css.br +0 -0
- package/dist/public/assets/index-ChblnyYX.js +1 -0
- package/dist/public/assets/index-ChceQzYz.js +1 -0
- package/dist/public/assets/index-CjzSEyzv.js +1 -0
- package/dist/public/assets/index-CluLESuq.js +1 -0
- package/dist/public/assets/index-CluLESuq.js.br +0 -0
- package/dist/public/assets/index-CnBegIDJ.js +1 -0
- package/dist/public/assets/index-Cnuj31jy.js +1 -0
- package/dist/public/assets/index-CzYlYBW_.js +1 -0
- package/dist/public/assets/index-D-_HSgCe.js +1 -0
- package/dist/public/assets/index-D1_vcunJ.js +23 -0
- package/dist/public/assets/index-D1_vcunJ.js.br +0 -0
- package/dist/public/assets/index-D4s2xpeA.js +1 -0
- package/dist/public/assets/index-D5ydsq9J.js +24 -0
- package/dist/public/assets/index-D5ydsq9J.js.br +0 -0
- package/dist/public/assets/index-D67sKNrw.css +1 -0
- package/dist/public/assets/index-D67sKNrw.css.br +0 -0
- package/dist/public/assets/index-D6RKE8qm.js +1 -0
- package/dist/public/assets/index-D6rLeEiy.js +1 -0
- package/dist/public/assets/index-DDC2R7_m.js +1 -0
- package/dist/public/assets/index-DDqw8mHL.js +1 -0
- package/dist/public/assets/index-DE1GZvdF.js +1 -0
- package/dist/public/assets/index-DFD_qEAi.js +1 -0
- package/dist/public/assets/index-DFEMmQoM.js +23 -0
- package/dist/public/assets/index-DFEMmQoM.js.br +0 -0
- package/dist/public/assets/index-DGHL1pSq.js +1 -0
- package/dist/public/assets/index-DJ-xo_2v.js +1 -0
- package/dist/public/assets/index-DJIK_Un5.js +23 -0
- package/dist/public/assets/index-DJIK_Un5.js.br +0 -0
- package/dist/public/assets/index-DKK8dOYS.js +1 -0
- package/dist/public/assets/index-DNmLdBiH.js +1 -0
- package/dist/public/assets/index-DNmLdBiH.js.br +0 -0
- package/dist/public/assets/index-DP2DtK77.js +1 -0
- package/dist/public/assets/index-DPSts5Re.css +1 -0
- package/dist/public/assets/index-DPSts5Re.css.br +0 -0
- package/dist/public/assets/index-DT6IV9TH.js +1 -0
- package/dist/public/assets/index-DU0eX6vn.js +1 -0
- package/dist/public/assets/index-DU0eX6vn.js.br +0 -0
- package/dist/public/assets/index-DWVXwBku.js +1 -0
- package/dist/public/assets/index-DXFg6tGR.css +1 -0
- package/dist/public/assets/index-DaS-23NX.js +1 -0
- package/dist/public/assets/index-Db0ve94g.js +1 -0
- package/dist/public/assets/index-Db0ve94g.js.br +0 -0
- package/dist/public/assets/index-DcTZlirs.js +1 -0
- package/dist/public/assets/index-De1gwiJs.js +1 -0
- package/dist/public/assets/index-De1gwiJs.js.br +0 -0
- package/dist/public/assets/index-DfGgXJTb.js +1 -0
- package/dist/public/assets/index-DfGgXJTb.js.br +0 -0
- package/dist/public/assets/index-DfZFMeOV.js +1 -0
- package/dist/public/assets/index-DfZFMeOV.js.br +0 -0
- package/dist/public/assets/index-DjHdTW8H.js +1 -0
- package/dist/public/assets/index-DlnMPCtX.js +1 -0
- package/dist/public/assets/index-Do_Bcsn1.js +1 -0
- package/dist/public/assets/index-DpFldGAl.js +1 -0
- package/dist/public/assets/index-DpgkUWkv.css +1 -0
- package/dist/public/assets/index-DpgkUWkv.css.br +0 -0
- package/dist/public/assets/index-DrFLOJ8g.js +23 -0
- package/dist/public/assets/index-DrFLOJ8g.js.br +0 -0
- package/dist/public/assets/index-DuBOXL7A.js +23 -0
- package/dist/public/assets/index-DuBOXL7A.js.br +0 -0
- package/dist/public/assets/index-DxQ0R3Pw.js +1 -0
- package/dist/public/assets/index-DyMZ_Lkc.js +1 -0
- package/dist/public/assets/index-DzLOWSx9.js +1 -0
- package/dist/public/assets/index-DzLOWSx9.js.br +0 -0
- package/dist/public/assets/index-FEOLSRkl.js +1 -0
- package/dist/public/assets/index-Fi4Rrt8I.js +1 -0
- package/dist/public/assets/index-HY89kziJ.js +1 -0
- package/dist/public/assets/index-Ifzp5fEn.js +1 -0
- package/dist/public/assets/index-J11P4g5S.js +1 -0
- package/dist/public/assets/index-J11P4g5S.js.br +0 -0
- package/dist/public/assets/index-JSX8aCBJ.js +1 -0
- package/dist/public/assets/index-JSX8aCBJ.js.br +0 -0
- package/dist/public/assets/index-JvC1YVUI.js +1 -0
- package/dist/public/assets/index-MT2NggoZ.js +1 -0
- package/dist/public/assets/index-MT2NggoZ.js.br +0 -0
- package/dist/public/assets/index-NdyUV0z0.js +1 -0
- package/dist/public/assets/index-OVJGZ7cU.js +1 -0
- package/dist/public/assets/index-T0nyAjcT.js +24 -0
- package/dist/public/assets/index-T0nyAjcT.js.br +0 -0
- package/dist/public/assets/index-UfX9eOrO.js +1 -0
- package/dist/public/assets/index-UujKgP-l.js +1 -0
- package/dist/public/assets/index-V67-RbBt.js +1 -0
- package/dist/public/assets/index-V67-RbBt.js.br +0 -0
- package/dist/public/assets/index-VzuG_wgX.js +1 -0
- package/dist/public/assets/index-XZ5vQlJ4.js +1 -0
- package/dist/public/assets/index-XdW2ZDCl.js +1 -0
- package/dist/public/assets/index-_ZVXaqDY.js +1 -0
- package/dist/public/assets/index-aPvChUsm.js +1 -0
- package/dist/public/assets/index-b5aKh2UT.js +1 -0
- package/dist/public/assets/index-bgdF_Zeb.js +23 -0
- package/dist/public/assets/index-bgdF_Zeb.js.br +0 -0
- package/dist/public/assets/index-d9sXoj67.js +1 -0
- package/dist/public/assets/index-d9sXoj67.js.br +0 -0
- package/dist/public/assets/index-ea8VcG1l.js +1 -0
- package/dist/public/assets/index-eoaE1BZf.css +1 -0
- package/dist/public/assets/index-eoaE1BZf.css.br +0 -0
- package/dist/public/assets/index-gZxXPtMU.js +1 -0
- package/dist/public/assets/index-hGMU1JI_.js +1 -0
- package/dist/public/assets/index-khqRg3Tb.js +1 -0
- package/dist/public/assets/index-nCPCU1qS.js +1 -0
- package/dist/public/assets/index-umN-hrVX.js +1 -0
- package/dist/public/assets/index-yh4tKekV.css +1 -0
- package/dist/public/assets/index-z6YlZek6.js +1 -0
- package/dist/public/index.html +2 -2
- package/dist/scripts/resetLocalState.js +48 -0
- package/dist/scripts/worker.js +62 -12
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -3,205 +3,59 @@
|
|
|
3
3
|
|
|
4
4
|
var commander = require('commander');
|
|
5
5
|
var inquirer = require('inquirer');
|
|
6
|
-
var fs = require('fs');
|
|
7
|
-
var jsdom = require('jsdom');
|
|
8
|
-
var prettier = require('prettier');
|
|
9
|
-
var chalk = require('chalk');
|
|
10
|
-
var express = require('express');
|
|
11
6
|
var path = require('path');
|
|
12
|
-
var cheerio = require('cheerio');
|
|
13
7
|
var os = require('os');
|
|
14
|
-
var worker_threads = require('worker_threads');
|
|
15
8
|
var axios = require('axios');
|
|
16
|
-
var
|
|
9
|
+
var fs = require('fs');
|
|
17
10
|
var handlebars = require('handlebars');
|
|
18
11
|
var esbuild = require('esbuild');
|
|
12
|
+
var chalk = require('chalk');
|
|
13
|
+
var boxen = require('boxen');
|
|
14
|
+
var semver = require('semver');
|
|
15
|
+
var jsdom = require('jsdom');
|
|
16
|
+
var prettier = require('prettier');
|
|
17
|
+
var express = require('express');
|
|
18
|
+
var cheerio = require('cheerio');
|
|
19
19
|
var archiver = require('archiver');
|
|
20
20
|
var chokidar = require('chokidar');
|
|
21
21
|
var WebSocket = require('ws');
|
|
22
|
+
var worker_threads = require('worker_threads');
|
|
22
23
|
var glob = require('glob');
|
|
23
24
|
var got = require('got');
|
|
24
25
|
var FormData$1 = require('form-data');
|
|
25
26
|
var stream = require('stream');
|
|
26
27
|
var ttmgPack = require('ttmg-pack');
|
|
27
|
-
var boxen = require('boxen');
|
|
28
28
|
var http = require('http');
|
|
29
|
+
var expressStaticGzip = require('express-static-gzip');
|
|
30
|
+
var fileUpload = require('express-fileupload');
|
|
29
31
|
var fs$1 = require('node:fs');
|
|
30
32
|
var path$1 = require('node:path');
|
|
31
33
|
var zlib = require('zlib');
|
|
32
34
|
var promises = require('fs/promises');
|
|
33
|
-
var
|
|
34
|
-
var fileUpload = require('express-fileupload');
|
|
35
|
+
var qs = require('qs');
|
|
35
36
|
|
|
36
37
|
function _interopNamespaceDefault(e) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
var n = Object.create(null);
|
|
39
|
+
if (e) {
|
|
40
|
+
Object.keys(e).forEach(function (k) {
|
|
41
|
+
if (k !== 'default') {
|
|
42
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
43
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
44
|
+
enumerable: true,
|
|
45
|
+
get: function () { return e[k]; }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
45
48
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
n.default = e;
|
|
50
|
-
return Object.freeze(n);
|
|
49
|
+
}
|
|
50
|
+
n.default = e;
|
|
51
|
+
return Object.freeze(n);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
54
54
|
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
55
55
|
var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
|
|
56
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
56
57
|
var glob__namespace = /*#__PURE__*/_interopNamespaceDefault(glob);
|
|
57
58
|
|
|
58
|
-
const CONFIG_FILE_NAME = 'minigame.config.json';
|
|
59
|
-
const SDK_URL = 'https://connect.tiktok-minis.com/game/sdk.js';
|
|
60
|
-
const VCONSOLE_URL = 'https://connect.tiktok-minis.com/libs/vConsole.js';
|
|
61
|
-
const VCONSOLE_INIT = `
|
|
62
|
-
if(typeof VConsole === 'function') {
|
|
63
|
-
window.vConsole = new VConsole();
|
|
64
|
-
}
|
|
65
|
-
`;
|
|
66
|
-
const MINIS_MANIFEST_FILE_NAME = 'minis.manifest.json';
|
|
67
|
-
const MINIS_RUNTIME_URL = 'https://www.tiktok.com/minigames/runtime';
|
|
68
|
-
|
|
69
|
-
const { JSDOM } = jsdom;
|
|
70
|
-
const CONFIG_PATH$1 = `${process.cwd()}/${CONFIG_FILE_NAME}`;
|
|
71
|
-
const INDEX_HTML_PATH = `${process.cwd()}/index.html`;
|
|
72
|
-
function isSandbox(clientKey) {
|
|
73
|
-
/**
|
|
74
|
-
* sb 开头的 clientKey 都是 sandbox 环境
|
|
75
|
-
*/
|
|
76
|
-
return clientKey.startsWith('sb');
|
|
77
|
-
}
|
|
78
|
-
// 判断是否是 TTMinis.game.init 的初始化脚本
|
|
79
|
-
function isTTMinisInitScript(script, clientKey) {
|
|
80
|
-
if (!script.innerHTML) {
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
return (script.innerHTML.includes('TTMinis.game.init') &&
|
|
84
|
-
script.innerHTML.includes(clientKey));
|
|
85
|
-
}
|
|
86
|
-
async function injectScripts({ config, clientKey }) {
|
|
87
|
-
// 1. 检查 config 文件是否已存在
|
|
88
|
-
fs.writeFileSync(CONFIG_PATH$1, JSON.stringify(config, null, 2));
|
|
89
|
-
// 2. 读取 index.html
|
|
90
|
-
if (!fs.existsSync(INDEX_HTML_PATH)) {
|
|
91
|
-
console.error('index.html does not exist');
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const indexHtml = fs.readFileSync(INDEX_HTML_PATH, 'utf8');
|
|
95
|
-
const dom = new JSDOM(indexHtml);
|
|
96
|
-
const { document } = dom.window;
|
|
97
|
-
// 3. head 标签
|
|
98
|
-
let head = document.querySelector('head');
|
|
99
|
-
if (!head) {
|
|
100
|
-
head = document.createElement('head');
|
|
101
|
-
document.documentElement.insertBefore(head, document.body);
|
|
102
|
-
}
|
|
103
|
-
// 4. 检查是否已注入 SDK
|
|
104
|
-
const scriptList = Array.from(document.querySelectorAll('script'));
|
|
105
|
-
const hasSDK = scriptList.some(script => script.src && script.src.includes(SDK_URL));
|
|
106
|
-
// 5. 检查是否已注入 vConsole
|
|
107
|
-
const hasVConsole = scriptList.some(script => script.src && script.src.includes('vConsole.js'));
|
|
108
|
-
let lastInitScript = null;
|
|
109
|
-
if (!hasSDK) {
|
|
110
|
-
// 插入 SDK 脚本
|
|
111
|
-
const sdkScript = document.createElement('script');
|
|
112
|
-
sdkScript.src = SDK_URL;
|
|
113
|
-
head.insertBefore(sdkScript, head.firstChild);
|
|
114
|
-
// 插入 SDK init 脚本
|
|
115
|
-
const initScript = document.createElement('script');
|
|
116
|
-
initScript.innerHTML = `
|
|
117
|
-
window.TTMinis = TTMinis;
|
|
118
|
-
TTMinis.game.init({
|
|
119
|
-
clientKey: "${clientKey}",
|
|
120
|
-
});
|
|
121
|
-
`;
|
|
122
|
-
head.insertBefore(initScript, sdkScript.nextSibling);
|
|
123
|
-
lastInitScript = initScript;
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
// 已经有 SDK,查找 TTMinis.game.init 脚本
|
|
127
|
-
const headScripts = Array.from(head.querySelectorAll('script'));
|
|
128
|
-
lastInitScript = headScripts.find(script => isTTMinisInitScript(script, clientKey));
|
|
129
|
-
if (lastInitScript) {
|
|
130
|
-
console.log('JS SDK 已接入,跳过 SDK 相关脚本注入');
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
// 没有 TTMinis.game.init,则查找最后一个 SDK 脚本
|
|
134
|
-
const sdkScripts = headScripts.filter(script => script.src && script.src.includes(SDK_URL));
|
|
135
|
-
if (sdkScripts.length > 0) {
|
|
136
|
-
lastInitScript = sdkScripts[sdkScripts.length - 1];
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* 只有 Sandbox 环境才需要注入 vConsole
|
|
142
|
-
*/
|
|
143
|
-
if (isSandbox(clientKey)) {
|
|
144
|
-
console.log('Sandbox 环境,跳过 vConsole 相关脚本注入');
|
|
145
|
-
// 8. 插入 vConsole 相关脚本(如果需要)
|
|
146
|
-
if (!hasVConsole) {
|
|
147
|
-
// vConsole 相关脚本的插入点
|
|
148
|
-
let insertAfterNode = lastInitScript;
|
|
149
|
-
if (insertAfterNode) {
|
|
150
|
-
insertAfterNode = insertAfterNode.nextSibling;
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
insertAfterNode = head.firstChild;
|
|
154
|
-
}
|
|
155
|
-
// vConsole 源码
|
|
156
|
-
const vconsoleSourceScript = document.createElement('script');
|
|
157
|
-
vconsoleSourceScript.src = VCONSOLE_URL;
|
|
158
|
-
// vConsole 初始化
|
|
159
|
-
const vconsoleInitScript = document.createElement('script');
|
|
160
|
-
vconsoleInitScript.innerHTML = VCONSOLE_INIT;
|
|
161
|
-
head.insertBefore(vconsoleSourceScript, insertAfterNode);
|
|
162
|
-
head.insertBefore(vconsoleInitScript, insertAfterNode);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
// 9. 格式化并写回 index.html
|
|
166
|
-
const formattedHtml = await prettier.format(dom.serialize(), {
|
|
167
|
-
parser: 'html',
|
|
168
|
-
});
|
|
169
|
-
fs.writeFileSync(INDEX_HTML_PATH, formattedHtml);
|
|
170
|
-
console.log(chalk.green.bold('TikTok H5 Mini Game initialization has been completed...'));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function init$1() {
|
|
174
|
-
const promptModule = inquirer.createPromptModule();
|
|
175
|
-
promptModule([
|
|
176
|
-
{
|
|
177
|
-
type: 'input',
|
|
178
|
-
name: 'clientKey',
|
|
179
|
-
message: 'Please input client key',
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
type: 'input',
|
|
183
|
-
name: 'devPort',
|
|
184
|
-
message: 'Please input dev port',
|
|
185
|
-
default: 9527,
|
|
186
|
-
},
|
|
187
|
-
])
|
|
188
|
-
.then(async (answers) => {
|
|
189
|
-
const { clientKey, devPort } = answers;
|
|
190
|
-
const config = {
|
|
191
|
-
_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.',
|
|
192
|
-
orientation: 'VERTICAL',
|
|
193
|
-
dev: {
|
|
194
|
-
port: devPort,
|
|
195
|
-
},
|
|
196
|
-
};
|
|
197
|
-
await injectScripts({ clientKey, config });
|
|
198
|
-
process.exit(0);
|
|
199
|
-
})
|
|
200
|
-
.catch(() => {
|
|
201
|
-
process.exit(1);
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
59
|
async function openUrl(url) {
|
|
206
60
|
const { launch } = await import('chrome-launcher');
|
|
207
61
|
try {
|
|
@@ -209,7 +63,7 @@ async function openUrl(url) {
|
|
|
209
63
|
await launch({
|
|
210
64
|
startingUrl: url,
|
|
211
65
|
chromeFlags: [
|
|
212
|
-
'--auto-open-devtools-for-tabs',
|
|
66
|
+
'--auto-open-devtools-for-tabs',
|
|
213
67
|
'--no-default-browser-check',
|
|
214
68
|
'--allow-insecure-localhost',
|
|
215
69
|
'--allow-running-insecure-content',
|
|
@@ -217,6 +71,7 @@ async function openUrl(url) {
|
|
|
217
71
|
`--user-data-dir=${userDataDir}`,
|
|
218
72
|
'--disk-cache-size=0',
|
|
219
73
|
'--media-cache-size=0',
|
|
74
|
+
'--disable-web-security',
|
|
220
75
|
],
|
|
221
76
|
});
|
|
222
77
|
await new Promise(resolve => { });
|
|
@@ -224,46 +79,18 @@ async function openUrl(url) {
|
|
|
224
79
|
}
|
|
225
80
|
catch (e) {
|
|
226
81
|
return false;
|
|
227
|
-
// return Promise.reject(e);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function getLocalIP() {
|
|
232
|
-
const networkInterfaces = os.networkInterfaces();
|
|
233
|
-
for (const interfaceName in networkInterfaces) {
|
|
234
|
-
const interfaceInfo = networkInterfaces[interfaceName];
|
|
235
|
-
if (!interfaceInfo)
|
|
236
|
-
continue;
|
|
237
|
-
for (const addressInfo of interfaceInfo) {
|
|
238
|
-
if (addressInfo.family === 'IPv4' && !addressInfo.internal) {
|
|
239
|
-
return addressInfo.address;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
82
|
}
|
|
243
83
|
}
|
|
244
84
|
|
|
245
|
-
|
|
246
|
-
const worker = new worker_threads.Worker(path.resolve(__dirname, './scripts/worker.js'));
|
|
247
|
-
worker.on('message', msg => {
|
|
248
|
-
// console.log(msg);
|
|
249
|
-
});
|
|
250
|
-
worker.on('error', err => {
|
|
251
|
-
console.error(err);
|
|
252
|
-
});
|
|
253
|
-
worker.postMessage({
|
|
254
|
-
type: 'checkUpdate',
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const CONFIG_PATH = process.env.TTMGRC_PATH || path.join(os.homedir(), '.ttmgrc');
|
|
85
|
+
const CONFIG_PATH$1 = process.env.TTMGRC_PATH || path.join(os.homedir(), '.ttmgrc');
|
|
259
86
|
const getTTMGRC = () => {
|
|
260
87
|
// only check one time
|
|
261
|
-
if (!fs.existsSync(CONFIG_PATH)) {
|
|
88
|
+
if (!fs.existsSync(CONFIG_PATH$1)) {
|
|
262
89
|
return null;
|
|
263
90
|
}
|
|
264
91
|
else {
|
|
265
92
|
// safe parse
|
|
266
|
-
let res = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
93
|
+
let res = JSON.parse(fs.readFileSync(CONFIG_PATH$1, 'utf8'));
|
|
267
94
|
return res;
|
|
268
95
|
}
|
|
269
96
|
};
|
|
@@ -271,198 +98,36 @@ const setTTMGRC = (config) => {
|
|
|
271
98
|
// updata cache config
|
|
272
99
|
config = config;
|
|
273
100
|
const originConfig = getTTMGRC() || {};
|
|
274
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify({ ...originConfig, ...config }));
|
|
101
|
+
fs.writeFileSync(CONFIG_PATH$1, JSON.stringify({ ...originConfig, ...config }));
|
|
275
102
|
};
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const prefix = type === 'Error' ? chalk.red('Error: ') : chalk.yellow('Warning: ');
|
|
279
|
-
const log = type === 'Error' ? console.error : console.warn;
|
|
280
|
-
log(prefix + message);
|
|
281
|
-
}
|
|
282
|
-
let spinner$1;
|
|
283
|
-
const LOGIN_TT4D = 'https://developers.tiktok.com/passport/web/email/login';
|
|
284
|
-
const params = {
|
|
285
|
-
aid: '2471',
|
|
286
|
-
account_sdk_source: 'web',
|
|
287
|
-
sdk_version: '2.1.6-tiktok',
|
|
103
|
+
const resetTTMGRC = (config = {}) => {
|
|
104
|
+
fs.writeFileSync(CONFIG_PATH$1, JSON.stringify(config));
|
|
288
105
|
};
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (!/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/.test(input)) {
|
|
315
|
-
return 'email format is invalid';
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
return true;
|
|
319
|
-
},
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
type: 'password',
|
|
323
|
-
name: 'password',
|
|
324
|
-
message: 'Password:',
|
|
325
|
-
mask: '*',
|
|
326
|
-
validate: input => {
|
|
327
|
-
if (!input) {
|
|
328
|
-
return 'password is required, please input password';
|
|
329
|
-
}
|
|
330
|
-
return true;
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
]);
|
|
334
|
-
const url = LOGIN_TT4D + '?' + new URLSearchParams(params);
|
|
335
|
-
log('Request URL', url);
|
|
336
|
-
log('Request params', { ...params, email: email.replace(/(.{2}).*(@.*)/, '$1***$2') });
|
|
337
|
-
const headers = {
|
|
338
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
339
|
-
Accept: '*/*',
|
|
340
|
-
'Accept-Encoding': 'gzip, deflate, br',
|
|
341
|
-
Origin: 'https://developers.tiktok.com',
|
|
342
|
-
Referer: 'https://developers.tiktok.com/passport/web/email/login',
|
|
343
|
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36',
|
|
344
|
-
};
|
|
345
|
-
const ora = await import('ora');
|
|
346
|
-
spinner$1 = ora.default({
|
|
347
|
-
text: chalk.bold.cyan('Logging in...'),
|
|
348
|
-
spinner: 'dots',
|
|
349
|
-
});
|
|
350
|
-
spinner$1.start();
|
|
351
|
-
const data = qs.stringify({
|
|
352
|
-
email,
|
|
353
|
-
password,
|
|
354
|
-
mix_mode: '1',
|
|
355
|
-
fixed_mix_mode: '1',
|
|
356
|
-
});
|
|
357
|
-
try {
|
|
358
|
-
log('Sending POST request...');
|
|
359
|
-
const response = await axios.post(url, data, {
|
|
360
|
-
headers,
|
|
361
|
-
maxRedirects: 20,
|
|
362
|
-
timeout: 30000,
|
|
363
|
-
});
|
|
364
|
-
log('Response status', response.status);
|
|
365
|
-
log('Response data', response?.data);
|
|
366
|
-
if (!response?.data?.data?.user_id) {
|
|
367
|
-
const errCode = response.data?.data?.error_code;
|
|
368
|
-
const errMsg = response.data?.data?.description;
|
|
369
|
-
const statusText = response?.statusText ?? '';
|
|
370
|
-
spinner$1.fail(chalk.red('login failed'));
|
|
371
|
-
if (verbose) {
|
|
372
|
-
console.log(chalk.gray('Response status:'), response?.status);
|
|
373
|
-
console.log(chalk.gray('Response statusText:'), statusText || '(empty)');
|
|
374
|
-
console.log(chalk.gray('Response data:'), response?.data ?? '(empty)');
|
|
375
|
-
console.log('');
|
|
376
|
-
}
|
|
377
|
-
if (errCode || errMsg) {
|
|
378
|
-
log('Login failed (api)', { errCode, errMsg, fullData: response?.data });
|
|
379
|
-
printMessage('Error', `Login failed: ${errMsg}${errCode ? `, error_code: ${errCode}` : ''}`);
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
log('Login failed (no user_id)', { responseBody: response?.data });
|
|
383
|
-
if (verbose)
|
|
384
|
-
log('Full response (for debugging)', response);
|
|
385
|
-
const looksLikeProxyResponse = statusText === 'Connection established' ||
|
|
386
|
-
(response?.status === 200 &&
|
|
387
|
-
(response?.data == null ||
|
|
388
|
-
typeof response.data !== 'object' ||
|
|
389
|
-
!('data' in response.data)));
|
|
390
|
-
if (looksLikeProxyResponse) {
|
|
391
|
-
printMessage('Warning', [
|
|
392
|
-
'The response does not look like the login API (e.g. proxy returned "Connection established" instead of forwarding the real response).',
|
|
393
|
-
'',
|
|
394
|
-
'The API is not reachable from mainland without proxy. Please ensure your proxy correctly forwards HTTPS to developers.tiktok.com: try another proxy node, or check that the proxy is in global/tunnel mode and not intercepting HTTPS.',
|
|
395
|
-
'',
|
|
396
|
-
'Proxy troubleshooting doc:',
|
|
397
|
-
'https://bytedance.larkoffice.com/wiki/ZblJwT0ZNil9jJkS8EgcFlcQnFc',
|
|
398
|
-
].join('\n'));
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
printMessage('Error', 'Login failed. No user_id in response.');
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
spinner$1.stop();
|
|
405
|
-
process.exit(1);
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
const data = {
|
|
409
|
-
email,
|
|
410
|
-
user_id: response?.data?.data?.user_id,
|
|
411
|
-
cookie: response?.headers['set-cookie'].join('; '),
|
|
412
|
-
};
|
|
413
|
-
log('Login success', { user_id: data.user_id, email: data.email });
|
|
414
|
-
setTTMGRC(data);
|
|
415
|
-
spinner$1.succeed(chalk.bold.green('login successfully!'));
|
|
416
|
-
process.exit(0);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
catch (error) {
|
|
420
|
-
log('Request error', error instanceof Error ? { message: error.message, name: error.name, stack: error.stack } : error);
|
|
421
|
-
spinner$1.fail(chalk.red.bold('Failed to connect to login service'));
|
|
422
|
-
printMessage('Error', [
|
|
423
|
-
"Detected that the current terminal's network proxy settings are preventing external network access.",
|
|
424
|
-
'',
|
|
425
|
-
'Please check your local terminal proxy configuration. You can follow this doc to modify your terminal network settings:',
|
|
426
|
-
'https://bytedance.larkoffice.com/wiki/ZblJwT0ZNil9jJkS8EgcFlcQnFc',
|
|
427
|
-
].join('\n'));
|
|
428
|
-
process.exit(1);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
async function setup(options) {
|
|
433
|
-
const inputLang = options?.lang;
|
|
434
|
-
const normalizedLang = inputLang === 'en-US' || inputLang === 'zh-CN' ? inputLang : null;
|
|
435
|
-
const lang = normalizedLang ??
|
|
436
|
-
(await inquirer.createPromptModule()([
|
|
437
|
-
{
|
|
438
|
-
type: 'list',
|
|
439
|
-
name: 'lang',
|
|
440
|
-
message: 'Select language your prefer',
|
|
441
|
-
choices: [
|
|
442
|
-
{ name: 'English (en-US)', value: 'en-US' },
|
|
443
|
-
{ name: '简体中文 (zh-CN)', value: 'zh-CN' },
|
|
444
|
-
],
|
|
445
|
-
default: 'en-US',
|
|
446
|
-
},
|
|
447
|
-
])).lang;
|
|
448
|
-
setTTMGRC({ lang });
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// ppe_dev_tool
|
|
452
|
-
async function request({ url, method, data, headers, params, }) {
|
|
453
|
-
const config = getTTMGRC();
|
|
454
|
-
const cookie = config?.cookie;
|
|
455
|
-
try {
|
|
456
|
-
const res = await axios({
|
|
457
|
-
url,
|
|
458
|
-
method,
|
|
459
|
-
data,
|
|
460
|
-
params,
|
|
461
|
-
headers: {
|
|
462
|
-
Cookie: cookie,
|
|
463
|
-
'x-use-ppe': '1',
|
|
464
|
-
'x-tt-env': 'ppe_upgrade_script',
|
|
465
|
-
...(headers || {}),
|
|
106
|
+
const getCurrentUser = () => {
|
|
107
|
+
try {
|
|
108
|
+
const config = getTTMGRC();
|
|
109
|
+
return config;
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// ppe_dev_tool
|
|
117
|
+
async function request({ url, method, data, headers, params, }) {
|
|
118
|
+
const config = getTTMGRC();
|
|
119
|
+
const cookie = config?.cookie;
|
|
120
|
+
try {
|
|
121
|
+
const res = await axios({
|
|
122
|
+
url,
|
|
123
|
+
method,
|
|
124
|
+
data,
|
|
125
|
+
params,
|
|
126
|
+
headers: {
|
|
127
|
+
Cookie: cookie,
|
|
128
|
+
// 'x-use-ppe': '1',
|
|
129
|
+
// 'x-tt-env': 'ppe_upgrade_script',
|
|
130
|
+
...(headers || {}),
|
|
466
131
|
},
|
|
467
132
|
});
|
|
468
133
|
// @ts-ignore
|
|
@@ -635,22 +300,6 @@ async function uploadGameToPlatform({ data, name, clientKey, note = '--', appId,
|
|
|
635
300
|
}
|
|
636
301
|
}
|
|
637
302
|
|
|
638
|
-
function getCurrentUser() {
|
|
639
|
-
try {
|
|
640
|
-
const config = getTTMGRC();
|
|
641
|
-
return config;
|
|
642
|
-
}
|
|
643
|
-
catch (err) {
|
|
644
|
-
return null;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
function ensureDirSync(dirPath) {
|
|
649
|
-
if (!fs.existsSync(dirPath)) {
|
|
650
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
|
|
654
303
|
const bareToRelativePlugin = {
|
|
655
304
|
name: 'bare-to-relative',
|
|
656
305
|
setup(build) {
|
|
@@ -685,8 +334,8 @@ function buildOpenContext(sourcePath) {
|
|
|
685
334
|
}
|
|
686
335
|
|
|
687
336
|
function generateOpenContextHtml(openContextPath, appId) {
|
|
688
|
-
const templatePath =
|
|
689
|
-
const sdkPath =
|
|
337
|
+
const templatePath = resolveTemplatePath('open_context.html.hbs');
|
|
338
|
+
const sdkPath = resolveTemplatePath('open_context_sdk.js.hbs');
|
|
690
339
|
const templateSource = fs.readFileSync(templatePath).toString();
|
|
691
340
|
const sdkSource = fs.readFileSync(sdkPath).toString();
|
|
692
341
|
const template = handlebars.compile(templateSource);
|
|
@@ -699,127 +348,649 @@ function generateOpenContextHtml(openContextPath, appId) {
|
|
|
699
348
|
});
|
|
700
349
|
fs.writeFileSync('./open_context.html', openContextRes);
|
|
701
350
|
}
|
|
351
|
+
function resolveTemplatePath(fileName) {
|
|
352
|
+
const runtimePath = path.join(__dirname, 'openDataContext/template', fileName);
|
|
353
|
+
if (fs.existsSync(runtimePath)) {
|
|
354
|
+
return runtimePath;
|
|
355
|
+
}
|
|
356
|
+
return path.join(process.cwd(), 'statics/openDataContext/template', fileName);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const messages = {
|
|
360
|
+
'en-US': {
|
|
361
|
+
'cli.description': 'TikTok Mini Games Command Line Tool',
|
|
362
|
+
'cli.version.desc': 'Show version',
|
|
363
|
+
'cli.option.dev.client': 'Debug TikTok Mini Games for Client',
|
|
364
|
+
'cli.option.dev.h5': 'Debug TikTok Mini Games for Web',
|
|
365
|
+
'cli.command.login.desc': 'Login with developer account',
|
|
366
|
+
'cli.command.login.verbose': 'Print verbose logs for debugging',
|
|
367
|
+
'cli.command.setup.desc': 'Initialize ttmg environment',
|
|
368
|
+
'cli.command.setup.lang': 'Language (only supports): en-US | zh-CN',
|
|
369
|
+
'cli.command.reset.desc': 'Reset local CLI state',
|
|
370
|
+
'cli.command.init.desc': 'Initialize project',
|
|
371
|
+
'cli.command.dev.desc': 'Open browser dev environment',
|
|
372
|
+
'cli.command.build.desc': 'Bundle project',
|
|
373
|
+
'cli.option.h5': 'H5 Mini Game',
|
|
374
|
+
'cli.native.init.placeholder': 'Native Mini Game initialize',
|
|
375
|
+
'cli.native.build.placeholder': 'Native Mini Game bundle',
|
|
376
|
+
'setup.prompt.selectLanguage': 'Select your preferred language',
|
|
377
|
+
'setup.choice.en': 'English (en-US)',
|
|
378
|
+
'setup.choice.zh': '简体中文 (zh-CN)',
|
|
379
|
+
'notice.versionUpdated': 'TTMG has been updated to v{version}.',
|
|
380
|
+
'notice.setup.recommend': 'Tip: run setup once to configure your preferred CLI language.',
|
|
381
|
+
'notice.setup.alreadyConfigured': 'Current language is {lang}. You can update it anytime.',
|
|
382
|
+
'notice.setup.howTo': 'How to configure language:',
|
|
383
|
+
'notice.setup.commandInteractive': '• Interactive: ttmg setup',
|
|
384
|
+
'notice.setup.commandExplicit': '• Explicit: ttmg setup --lang en-US | ttmg setup --lang zh-CN',
|
|
385
|
+
'notice.setup.commandHint': 'Run: ttmg setup (interactive) or ttmg setup --lang en-US | ttmg setup --lang zh-CN',
|
|
386
|
+
'setup.error.unsupportedLang': 'Unsupported language: {lang}.',
|
|
387
|
+
'setup.error.availableLangs': 'Supported languages: en-US, zh-CN.',
|
|
388
|
+
'setup.error.chooseHint': 'Run `ttmg setup` to choose language interactively.',
|
|
389
|
+
'setup.success': 'Setup completed. CLI language is now {lang}.',
|
|
390
|
+
'reset.warning.title': 'Reset will perform these changes:',
|
|
391
|
+
'reset.warning.lang': 'Language setting will be cleared.',
|
|
392
|
+
'reset.warning.login': 'Login state will be cleared.',
|
|
393
|
+
'reset.warning.clientKey': 'Historical debug client keys will be removed.',
|
|
394
|
+
'reset.confirm.prompt': 'Do you want to continue reset?',
|
|
395
|
+
'reset.confirm.yes': 'Yes, reset now',
|
|
396
|
+
'reset.confirm.no': 'No, keep current local state',
|
|
397
|
+
'reset.cancelled': 'Reset cancelled.',
|
|
398
|
+
'reset.success': 'Local state has been reset.',
|
|
399
|
+
'login.error.prefix': 'Error: ',
|
|
400
|
+
'login.warning.prefix': 'Warning: ',
|
|
401
|
+
'login.verbose.enabled': 'Verbose logging enabled',
|
|
402
|
+
'login.note': '⚠️ Note: Please login with your TikTok Developer Platform account.',
|
|
403
|
+
'login.email.prompt': 'Email:',
|
|
404
|
+
'login.email.required': 'email is required, please input email',
|
|
405
|
+
'login.email.invalid': 'email format is invalid',
|
|
406
|
+
'login.password.prompt': 'Password:',
|
|
407
|
+
'login.password.required': 'password is required, please input password',
|
|
408
|
+
'login.spinner.loggingIn': 'Logging in...',
|
|
409
|
+
'login.failed': 'login failed',
|
|
410
|
+
'login.success': 'login successfully!',
|
|
411
|
+
'login.error.withCode': 'Login failed: {message}, error_code: {code}',
|
|
412
|
+
'login.error.withMessage': 'Login failed: {message}',
|
|
413
|
+
'login.error.noUserId': 'Login failed. No user_id in response.',
|
|
414
|
+
'login.warning.proxyIssue': 'The response does not look like the login API (e.g. proxy returned "Connection established" instead of forwarding the real response).\n\nThe API is not reachable from mainland without proxy. Please ensure your proxy correctly forwards HTTPS to developers.tiktok.com: try another proxy node, or check that the proxy is in global/tunnel mode and not intercepting HTTPS.\n\nProxy troubleshooting doc:\nhttps://bytedance.larkoffice.com/wiki/ZblJwT0ZNil9jJkS8EgcFlcQnFc',
|
|
415
|
+
'login.error.connectService': 'Failed to connect to login service',
|
|
416
|
+
'login.error.networkBlocked': "Detected that the current terminal's network proxy settings are preventing external network access.\n\nPlease check your local terminal proxy configuration. You can follow this doc to modify your terminal network settings:\nhttps://bytedance.larkoffice.com/wiki/ZblJwT0ZNil9jJkS8EgcFlcQnFc",
|
|
417
|
+
'h5.dev.configMissing': '{file} is not exist, please run minis game init first',
|
|
418
|
+
'h5.dev.precheckTips': '⚠️ 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.',
|
|
419
|
+
'h5.dev.startingBanner': '\n \n============== start dev your game, it will take a few seconds ============ \n \n',
|
|
420
|
+
'h5.dev.cspFailed': 'Failed to set CSP header:',
|
|
421
|
+
'h5.dev.accessUrl': 'you can access {url} to debug your game in browser...',
|
|
422
|
+
'h5.dev.openBrowserFailed': 'Failed to open browser, you can access it manually',
|
|
423
|
+
'h5.bundle.configMissing': '{file} is not exist, please run minis game init first',
|
|
424
|
+
'h5.bundle.prompt.zipName': 'Please input zip name',
|
|
425
|
+
'h5.bundle.start': 'start build your game, it will take a few minutes...',
|
|
426
|
+
'h5.bundle.success': 'build {zipName}.zip success, you can find it in desktop, use time {cost} ms',
|
|
427
|
+
'h5.bundle.fail': 'auto build {zipName}.zip failed: {error}, you should zip it manually',
|
|
428
|
+
'h5.bundle.desktopFallback': 'Desktop folder not found, using default path: {path}',
|
|
429
|
+
'h5.init.prompt.clientKey': 'Please input client key',
|
|
430
|
+
'h5.init.prompt.devPort': 'Please input dev port',
|
|
431
|
+
'h5.init.indexMissing': 'index.html does not exist',
|
|
432
|
+
'h5.init.sdkAlreadyInjected': 'JS SDK is already injected, skip SDK script injection',
|
|
433
|
+
'h5.init.sandboxSkipVconsole': 'Sandbox environment detected, skip vConsole script injection',
|
|
434
|
+
'h5.init.done': 'TikTok H5 Mini Game initialization has been completed...',
|
|
435
|
+
'native.server.readyIn': 'ready in',
|
|
436
|
+
'native.server.portInUse': 'Port {port} is already in use, trying {next}...',
|
|
437
|
+
'native.server.failedAfterRetries': 'Failed to start server after trying {retries} ports.',
|
|
438
|
+
'native.check.notWorkspace': 'Current directory is not a Mini Game project entry directory, please enter the game project root directory for debugging',
|
|
439
|
+
'native.check.subpackagesCaseError': "Error: 'subPackages' (camelCase) is found in {file}. Please use 'subpackages' (all lowercase) instead.",
|
|
440
|
+
'native.compile.watching': '🔔 Watching game assets for local debugging...',
|
|
441
|
+
'native.compile.compiling': '🚀 Compiling game assets for local debugging...',
|
|
442
|
+
'native.compile.success': '✔ Game resources compiled successfully!',
|
|
443
|
+
'native.upload.spinner.start': 'Uploading game assets to client...',
|
|
444
|
+
'native.upload.spinner.size': 'Start upload Game assets to client, size: {size} MB',
|
|
445
|
+
'native.upload.spinner.progress': 'Uploading game assets to client... {percent}%',
|
|
446
|
+
'native.upload.success': 'Upload game assets to client success! Cost: {cost}ms',
|
|
447
|
+
'native.upload.fail': 'Upload failed with error: {error}, please check current debug env and try to scan qrcode to reupload again.',
|
|
448
|
+
'native.upload.serverFail': '✖ Upload failed with server error, please scan qrcode to reupload',
|
|
449
|
+
'native.upload.compressError': 'Error during compression:',
|
|
450
|
+
'native.server.upload.packingDir': 'Packing current directory: {cwd} ...',
|
|
451
|
+
'native.server.upload.packed': 'Pack completed, size: {size} MB',
|
|
452
|
+
'native.server.upload.failed': 'Upload failed: {error}',
|
|
453
|
+
'common.unknownError': 'An unknown error occurred.',
|
|
454
|
+
'native.init.selectClientKey': 'Select game client key for debugging:',
|
|
455
|
+
'native.init.lastUsed': '{clientKey} (last used)',
|
|
456
|
+
'native.init.addNew': 'Add a new client key',
|
|
457
|
+
'native.init.inputNew': 'Input new client key:',
|
|
458
|
+
'native.init.inputYour': 'Input your Client Key:',
|
|
459
|
+
'native.init.clientKeyRequired': 'Client key is required, please input client key',
|
|
460
|
+
'native.tips.1': ' The QR code page will be opened automatically in Chrome.',
|
|
461
|
+
'native.tips.1.sub': ' If it fails, please open the following link manually:',
|
|
462
|
+
'native.tips.2': ' The debugging service will automatically compile your game assets.',
|
|
463
|
+
'native.tips.2.sub1': ' Any changes in your game directory will trigger recompilation.',
|
|
464
|
+
'native.tips.2.sub2': ' You can debug the updated content when upload is completed.',
|
|
465
|
+
'native.tips.3': ' After scanning the QR code with your phone for Test User authentication,',
|
|
466
|
+
'native.tips.3.sub1': ' the compiled code package will be uploaded to the client automatically.',
|
|
467
|
+
'native.tips.3.sub2': ' Game debugging will start right away.',
|
|
468
|
+
'native.tips.4': ' Before scanning with TikTok, make sure your phone and computer are on the same Wi-Fi.',
|
|
469
|
+
'native.tips.4.sub1': ' This is required for stable local connection and debugging.',
|
|
470
|
+
},
|
|
471
|
+
'zh-CN': {
|
|
472
|
+
'cli.description': 'TikTok 小游戏命令行工具',
|
|
473
|
+
'cli.version.desc': '显示版本号',
|
|
474
|
+
'cli.option.dev.client': '客户端调试 TikTok 小游戏',
|
|
475
|
+
'cli.option.dev.h5': 'Web 调试 TikTok 小游戏',
|
|
476
|
+
'cli.command.login.desc': '使用开发者账号登录',
|
|
477
|
+
'cli.command.login.verbose': '输出调试用详细日志',
|
|
478
|
+
'cli.command.setup.desc': '初始化 ttmg 环境',
|
|
479
|
+
'cli.command.setup.lang': '语言(仅支持):en-US | zh-CN',
|
|
480
|
+
'cli.command.reset.desc': '重置本地 CLI 状态',
|
|
481
|
+
'cli.command.init.desc': '初始化项目',
|
|
482
|
+
'cli.command.dev.desc': '打开浏览器调试环境',
|
|
483
|
+
'cli.command.build.desc': '打包项目',
|
|
484
|
+
'cli.option.h5': 'H5 小游戏',
|
|
485
|
+
'cli.native.init.placeholder': 'Native 小游戏初始化',
|
|
486
|
+
'cli.native.build.placeholder': 'Native 小游戏打包',
|
|
487
|
+
'setup.prompt.selectLanguage': '请选择偏好语言',
|
|
488
|
+
'setup.choice.en': '英语 (en-US)',
|
|
489
|
+
'setup.choice.zh': '简体中文 (zh-CN)',
|
|
490
|
+
'notice.versionUpdated': 'TTMG 已升级到 v{version}。',
|
|
491
|
+
'notice.setup.recommend': '建议执行一次 setup,配置 CLI 输出语言。',
|
|
492
|
+
'notice.setup.alreadyConfigured': '当前语言为 {lang},你可以随时调整。',
|
|
493
|
+
'notice.setup.howTo': '语言配置方式:',
|
|
494
|
+
'notice.setup.commandInteractive': '• 交互式:ttmg setup',
|
|
495
|
+
'notice.setup.commandExplicit': '• 显式指定:ttmg setup --lang en-US | ttmg setup --lang zh-CN',
|
|
496
|
+
'notice.setup.commandHint': '执行:ttmg setup(交互选择)或 ttmg setup --lang en-US | ttmg setup --lang zh-CN',
|
|
497
|
+
'setup.error.unsupportedLang': '不支持的语言:{lang}。',
|
|
498
|
+
'setup.error.availableLangs': '可选语言:en-US、zh-CN。',
|
|
499
|
+
'setup.error.chooseHint': '可执行 `ttmg setup` 进行交互选择。',
|
|
500
|
+
'setup.success': '设置完成,CLI 语言已切换为 {lang}。',
|
|
501
|
+
'reset.warning.title': '重置将执行以下操作:',
|
|
502
|
+
'reset.warning.lang': '语言设置会被清空。',
|
|
503
|
+
'reset.warning.login': '登录状态会被清空。',
|
|
504
|
+
'reset.warning.clientKey': '历史调试的 client key 会被清理。',
|
|
505
|
+
'reset.confirm.prompt': '是否继续重置?',
|
|
506
|
+
'reset.confirm.yes': '是,立即重置',
|
|
507
|
+
'reset.confirm.no': '否,保留当前本地状态',
|
|
508
|
+
'reset.cancelled': '已取消重置。',
|
|
509
|
+
'reset.success': '本地状态已重置。',
|
|
510
|
+
'login.error.prefix': '错误:',
|
|
511
|
+
'login.warning.prefix': '警告:',
|
|
512
|
+
'login.verbose.enabled': '已开启详细日志',
|
|
513
|
+
'login.note': '⚠️ 提示:请使用 TikTok 开发者平台账号登录。',
|
|
514
|
+
'login.email.prompt': '邮箱:',
|
|
515
|
+
'login.email.required': '邮箱必填,请输入邮箱',
|
|
516
|
+
'login.email.invalid': '邮箱格式不正确',
|
|
517
|
+
'login.password.prompt': '密码:',
|
|
518
|
+
'login.password.required': '密码必填,请输入密码',
|
|
519
|
+
'login.spinner.loggingIn': '登录中...',
|
|
520
|
+
'login.failed': '登录失败',
|
|
521
|
+
'login.success': '登录成功!',
|
|
522
|
+
'login.error.withCode': '登录失败:{message},错误码:{code}',
|
|
523
|
+
'login.error.withMessage': '登录失败:{message}',
|
|
524
|
+
'login.error.noUserId': '登录失败,响应中未返回 user_id。',
|
|
525
|
+
'login.warning.proxyIssue': '当前响应看起来不是登录接口返回(例如代理返回了 "Connection established",而不是转发真实响应)。\n\n该接口在大陆网络通常需要代理。请确认代理可正确转发 developers.tiktok.com 的 HTTPS 请求:尝试切换节点,或检查代理是否为全局/隧道模式且未拦截 HTTPS。\n\n代理排查文档:\nhttps://bytedance.larkoffice.com/wiki/ZblJwT0ZNil9jJkS8EgcFlcQnFc',
|
|
526
|
+
'login.error.connectService': '连接登录服务失败',
|
|
527
|
+
'login.error.networkBlocked': '检测到当前终端代理设置导致无法访问外网。\n\n请检查本地终端代理配置,可参考以下文档修改:\nhttps://bytedance.larkoffice.com/wiki/ZblJwT0ZNil9jJkS8EgcFlcQnFc',
|
|
528
|
+
'h5.dev.configMissing': '{file} 不存在,请先执行 minis game init',
|
|
529
|
+
'h5.dev.precheckTips': '⚠️ 开始调试前请确认:\n 1. 当前登录 www.tiktok.com 的账号在小程序开发者平台沙盒目标用户范围内,否则登录授权会报错。\n 2. 浏览器允许 www.tiktok.com 弹窗与重定向,授权登录链路需要新开标签页进行操作,否则无法正常调试。',
|
|
530
|
+
'h5.dev.startingBanner': '\n \n============== 已开始调试你的游戏,通常需要几秒钟 ============ \n \n',
|
|
531
|
+
'h5.dev.cspFailed': '设置 CSP 头失败:',
|
|
532
|
+
'h5.dev.accessUrl': '可访问 {url} 在浏览器中调试游戏...',
|
|
533
|
+
'h5.dev.openBrowserFailed': '自动打开浏览器失败,请手动访问链接',
|
|
534
|
+
'h5.bundle.configMissing': '{file} 不存在,请先执行 minis game init',
|
|
535
|
+
'h5.bundle.prompt.zipName': '请输入压缩包名称',
|
|
536
|
+
'h5.bundle.start': '开始打包游戏,预计需要几分钟...',
|
|
537
|
+
'h5.bundle.success': '打包 {zipName}.zip 成功,可在桌面找到,耗时 {cost} ms',
|
|
538
|
+
'h5.bundle.fail': '自动打包 {zipName}.zip 失败:{error},请手动压缩',
|
|
539
|
+
'h5.bundle.desktopFallback': '未找到桌面目录,使用默认路径:{path}',
|
|
540
|
+
'h5.init.prompt.clientKey': '请输入 client key',
|
|
541
|
+
'h5.init.prompt.devPort': '请输入调试端口',
|
|
542
|
+
'h5.init.indexMissing': 'index.html 不存在',
|
|
543
|
+
'h5.init.sdkAlreadyInjected': '检测到 JS SDK 已接入,跳过 SDK 相关脚本注入',
|
|
544
|
+
'h5.init.sandboxSkipVconsole': '检测到 Sandbox 环境,跳过 vConsole 相关脚本注入',
|
|
545
|
+
'h5.init.done': 'TikTok H5 小游戏初始化已完成...',
|
|
546
|
+
'native.server.readyIn': '启动耗时',
|
|
547
|
+
'native.server.portInUse': '端口 {port} 已被占用,尝试 {next}...',
|
|
548
|
+
'native.server.failedAfterRetries': '连续尝试 {retries} 个端口后,服务启动失败。',
|
|
549
|
+
'native.check.notWorkspace': '当前目录不是小游戏工程入口目录,请进入游戏项目根目录后再进行调试',
|
|
550
|
+
'native.check.subpackagesCaseError': "错误:在 {file} 中发现 'subPackages'(驼峰),请改为全小写 'subpackages'。",
|
|
551
|
+
'native.compile.watching': '🔔 正在监听游戏资源变更(本地调试)...',
|
|
552
|
+
'native.compile.compiling': '🚀 正在编译游戏资源(本地调试)...',
|
|
553
|
+
'native.compile.success': '✔ 游戏资源编译成功!',
|
|
554
|
+
'native.upload.spinner.start': '正在上传游戏资源到客户端...',
|
|
555
|
+
'native.upload.spinner.size': '开始上传游戏资源到客户端,大小:{size} MB',
|
|
556
|
+
'native.upload.spinner.progress': '正在上传游戏资源到客户端... {percent}%',
|
|
557
|
+
'native.upload.success': '上传游戏资源到客户端成功!耗时:{cost}ms',
|
|
558
|
+
'native.upload.fail': '上传失败:{error},请检查当前调试环境并重新扫码上传。',
|
|
559
|
+
'native.upload.serverFail': '✖ 上传失败(服务端错误),请重新扫码上传',
|
|
560
|
+
'native.upload.compressError': '压缩过程中发生错误:',
|
|
561
|
+
'native.server.upload.packingDir': '正在打包当前目录:{cwd} ...',
|
|
562
|
+
'native.server.upload.packed': '打包完成,大小:{size} MB',
|
|
563
|
+
'native.server.upload.failed': '上传失败:{error}',
|
|
564
|
+
'common.unknownError': '发生未知错误。',
|
|
565
|
+
'native.init.selectClientKey': '请选择用于调试的游戏 client key:',
|
|
566
|
+
'native.init.lastUsed': '{clientKey}(上次使用)',
|
|
567
|
+
'native.init.addNew': '新增一个 client key',
|
|
568
|
+
'native.init.inputNew': '输入新的 client key:',
|
|
569
|
+
'native.init.inputYour': '输入你的 Client Key:',
|
|
570
|
+
'native.init.clientKeyRequired': 'client key 必填,请输入 client key',
|
|
571
|
+
'native.tips.1': ' 将自动在 Chrome 打开二维码页面。',
|
|
572
|
+
'native.tips.1.sub': ' 若失败,请手动打开以下链接:',
|
|
573
|
+
'native.tips.2': ' 调试服务会自动编译你的游戏资源。',
|
|
574
|
+
'native.tips.2.sub1': ' 游戏目录内的任何改动都会触发重新编译。',
|
|
575
|
+
'native.tips.2.sub2': ' 上传完成后即可调试最新内容。',
|
|
576
|
+
'native.tips.3': ' 手机扫码通过 Test User 认证后,',
|
|
577
|
+
'native.tips.3.sub1': ' 编译后的代码包会自动上传到客户端。',
|
|
578
|
+
'native.tips.3.sub2': ' 随即开始游戏调试。',
|
|
579
|
+
'native.tips.4': ' 在使用 TikTok 扫码前,请确保手机和电脑处于同一个 Wi-Fi。',
|
|
580
|
+
'native.tips.4.sub1': ' 这是本地连接与正常调试的前提条件。',
|
|
581
|
+
},
|
|
582
|
+
};
|
|
702
583
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const configPath = path.join(process.cwd(), CONFIG_FILE_NAME);
|
|
707
|
-
if (!fs.existsSync(configPath)) {
|
|
708
|
-
console.log(chalk.red.bold(`${CONFIG_FILE_NAME} is not exist, please run minis game init first`));
|
|
709
|
-
return;
|
|
584
|
+
function resolveSupportedLanguage(lang) {
|
|
585
|
+
if (!lang) {
|
|
586
|
+
return undefined;
|
|
710
587
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const devPort = gameConfig.dev?.port || 9527;
|
|
714
|
-
const hasOpenContext = !!gameConfig.openDataContext;
|
|
715
|
-
if (hasOpenContext) {
|
|
716
|
-
generateOpenContextHtml(gameConfig.openDataContext, gameConfig.app_id);
|
|
588
|
+
if (lang in messages) {
|
|
589
|
+
return lang;
|
|
717
590
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
591
|
+
const mainLang = lang.split('-')[0];
|
|
592
|
+
if (mainLang === 'zh') {
|
|
593
|
+
return 'zh-CN';
|
|
594
|
+
}
|
|
595
|
+
if (mainLang === 'en') {
|
|
596
|
+
return 'en-US';
|
|
597
|
+
}
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
600
|
+
function getConfiguredLanguage() {
|
|
601
|
+
return resolveSupportedLanguage(getTTMGRC()?.lang);
|
|
602
|
+
}
|
|
603
|
+
function getLanguage() {
|
|
604
|
+
return getConfiguredLanguage() ?? 'en-US';
|
|
605
|
+
}
|
|
606
|
+
function format(template, params) {
|
|
607
|
+
if (!params)
|
|
608
|
+
return template;
|
|
609
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
610
|
+
const value = params[key];
|
|
611
|
+
return value === undefined ? `{${key}}` : String(value);
|
|
732
612
|
});
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
613
|
+
}
|
|
614
|
+
function t(key, params, langOverride) {
|
|
615
|
+
const lang = langOverride ?? getLanguage();
|
|
616
|
+
const template = messages[lang][key] ?? messages['en-US'][key] ?? key;
|
|
617
|
+
return format(template, params);
|
|
618
|
+
}
|
|
619
|
+
function getCurrentLanguage() {
|
|
620
|
+
return getLanguage();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const SETUP_NOTICE_ID = 'setup-language-onboarding';
|
|
624
|
+
const versionNoticeConfigs = [
|
|
625
|
+
{
|
|
626
|
+
id: SETUP_NOTICE_ID,
|
|
627
|
+
sinceVersion: '0.3.2-beta.5',
|
|
628
|
+
headlineKey: 'notice.versionUpdated',
|
|
629
|
+
bodyConfiguredKey: 'notice.setup.alreadyConfigured',
|
|
630
|
+
bodyUnconfiguredKey: 'notice.setup.recommend',
|
|
631
|
+
guideTitleKey: 'notice.setup.howTo',
|
|
632
|
+
guideItemKeys: ['notice.setup.commandInteractive', 'notice.setup.commandExplicit'],
|
|
633
|
+
dismissOnCommands: ['setup', 'reset'],
|
|
634
|
+
},
|
|
635
|
+
];
|
|
636
|
+
|
|
637
|
+
const boxenFn = boxen.default ??
|
|
638
|
+
boxen;
|
|
639
|
+
function maybeShowPostInstallNotice(currentVersion) {
|
|
640
|
+
if (!process.stdout.isTTY)
|
|
641
|
+
return;
|
|
642
|
+
const parsedCurrentVersion = semver.valid(currentVersion);
|
|
643
|
+
if (!parsedCurrentVersion)
|
|
644
|
+
return;
|
|
645
|
+
const argv = process.argv.slice(2);
|
|
646
|
+
const isHelpOrVersion = argv.includes('-h') ||
|
|
647
|
+
argv.includes('--help') ||
|
|
648
|
+
argv.includes('-v') ||
|
|
649
|
+
argv.includes('--version');
|
|
650
|
+
if (isHelpOrVersion)
|
|
651
|
+
return;
|
|
652
|
+
const config = getTTMGRC() || {};
|
|
653
|
+
const seenNoticeIds = new Set(config.seenNoticeIds || []);
|
|
654
|
+
let shouldPersistState = false;
|
|
655
|
+
// Backward compatibility for old single-key notice status.
|
|
656
|
+
const legacyVersion = config.lastNotifiedCliVersion;
|
|
657
|
+
if (legacyVersion &&
|
|
658
|
+
semver.valid(legacyVersion) &&
|
|
659
|
+
semver.gte(legacyVersion, '0.3.2-beta.5') &&
|
|
660
|
+
!seenNoticeIds.has(SETUP_NOTICE_ID)) {
|
|
661
|
+
seenNoticeIds.add(SETUP_NOTICE_ID);
|
|
662
|
+
shouldPersistState = true;
|
|
663
|
+
}
|
|
664
|
+
const commandName = argv.find(arg => !arg.startsWith('-'));
|
|
665
|
+
const currentLang = config.lang;
|
|
666
|
+
const hasConfiguredLang = currentLang === 'en-US' || currentLang === 'zh-CN';
|
|
667
|
+
const pendingNotices = versionNoticeConfigs.filter(notice => {
|
|
668
|
+
if (seenNoticeIds.has(notice.id))
|
|
669
|
+
return false;
|
|
670
|
+
return semver.gte(parsedCurrentVersion, notice.sinceVersion);
|
|
671
|
+
});
|
|
672
|
+
for (const notice of pendingNotices) {
|
|
673
|
+
if (commandName && notice.dismissOnCommands?.includes(commandName)) {
|
|
674
|
+
seenNoticeIds.add(notice.id);
|
|
675
|
+
shouldPersistState = true;
|
|
676
|
+
continue;
|
|
765
677
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
678
|
+
const lines = [
|
|
679
|
+
chalk.green.bold(t(notice.headlineKey, { version: currentVersion })),
|
|
680
|
+
];
|
|
681
|
+
if (hasConfiguredLang && notice.bodyConfiguredKey) {
|
|
682
|
+
lines.push(t(notice.bodyConfiguredKey, { lang: currentLang }));
|
|
769
683
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
// 4. 静态资源服务
|
|
773
|
-
app.use(express.static(path.join(process.cwd())));
|
|
774
|
-
// 5. 启动服务并自动打开浏览器
|
|
775
|
-
app.listen(devPort, () => {
|
|
776
|
-
const gameUrl = `http://localhost:${devPort}`;
|
|
777
|
-
const openContextUrl = `http://localhost:${devPort}/open_context.html`;
|
|
778
|
-
let devUrl = `${MINIS_RUNTIME_URL}?minis_url=${gameUrl}&enable_log=1`;
|
|
779
|
-
if (hasOpenContext) {
|
|
780
|
-
devUrl += `&open_context_url=${openContextUrl}`;
|
|
684
|
+
else if (!hasConfiguredLang && notice.bodyUnconfiguredKey) {
|
|
685
|
+
lines.push(chalk.yellow(t(notice.bodyUnconfiguredKey)));
|
|
781
686
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
openUrl(devUrl);
|
|
687
|
+
if (notice.guideTitleKey) {
|
|
688
|
+
lines.push('');
|
|
689
|
+
lines.push(chalk.bold(t(notice.guideTitleKey)));
|
|
786
690
|
}
|
|
787
|
-
|
|
788
|
-
|
|
691
|
+
for (const itemKey of notice.guideItemKeys || []) {
|
|
692
|
+
lines.push(chalk.cyan(t(itemKey)));
|
|
789
693
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
694
|
+
const content = lines.join('\n');
|
|
695
|
+
console.log(boxenFn(content, {
|
|
696
|
+
title: 'Notice',
|
|
697
|
+
titleAlignment: 'left',
|
|
698
|
+
borderStyle: 'round',
|
|
699
|
+
borderColor: 'cyan',
|
|
700
|
+
padding: {
|
|
701
|
+
top: 0,
|
|
702
|
+
right: 1,
|
|
703
|
+
bottom: 0,
|
|
704
|
+
left: 1,
|
|
705
|
+
},
|
|
706
|
+
margin: 1,
|
|
707
|
+
}));
|
|
708
|
+
seenNoticeIds.add(notice.id);
|
|
709
|
+
shouldPersistState = true;
|
|
710
|
+
}
|
|
711
|
+
if (shouldPersistState || legacyVersion) {
|
|
712
|
+
setTTMGRC({
|
|
713
|
+
seenNoticeIds: Array.from(seenNoticeIds),
|
|
714
|
+
lastNotifiedCliVersion: undefined,
|
|
811
715
|
});
|
|
812
|
-
fs.writeFileSync(path.join(buildPath, MINIS_MANIFEST_FILE_NAME), JSON.stringify({ name: MINIS_MANIFEST_FILE_NAME, resource_list: resourceList }, null, 2));
|
|
813
716
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}
|
|
819
|
-
process.exit(1);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function ensureDirSync(dirPath) {
|
|
720
|
+
if (!fs.existsSync(dirPath)) {
|
|
721
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
820
722
|
}
|
|
821
723
|
}
|
|
822
|
-
|
|
724
|
+
|
|
725
|
+
const CONFIG_FILE_NAME = 'minigame.config.json';
|
|
726
|
+
const SDK_URL = 'https://connect.tiktok-minis.com/game/sdk.js';
|
|
727
|
+
const VCONSOLE_URL = 'https://connect.tiktok-minis.com/libs/vConsole.js';
|
|
728
|
+
const VCONSOLE_INIT = `
|
|
729
|
+
if(typeof VConsole === 'function') {
|
|
730
|
+
window.vConsole = new VConsole();
|
|
731
|
+
}
|
|
732
|
+
`;
|
|
733
|
+
const MINIS_MANIFEST_FILE_NAME = 'minis.manifest.json';
|
|
734
|
+
const MINIS_RUNTIME_URL = 'https://www.tiktok.com/minigames/runtime';
|
|
735
|
+
|
|
736
|
+
const { JSDOM } = jsdom;
|
|
737
|
+
const CONFIG_PATH = `${process.cwd()}/${CONFIG_FILE_NAME}`;
|
|
738
|
+
const INDEX_HTML_PATH = `${process.cwd()}/index.html`;
|
|
739
|
+
function isSandbox(clientKey) {
|
|
740
|
+
/**
|
|
741
|
+
* sb 开头的 clientKey 都是 sandbox 环境
|
|
742
|
+
*/
|
|
743
|
+
return clientKey.startsWith('sb');
|
|
744
|
+
}
|
|
745
|
+
// 判断是否是 TTMinis.game.init 的初始化脚本
|
|
746
|
+
function isTTMinisInitScript(script, clientKey) {
|
|
747
|
+
if (!script.innerHTML) {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
return (script.innerHTML.includes('TTMinis.game.init') &&
|
|
751
|
+
script.innerHTML.includes(clientKey));
|
|
752
|
+
}
|
|
753
|
+
async function injectScripts({ config, clientKey }) {
|
|
754
|
+
// 1. 检查 config 文件是否已存在
|
|
755
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
756
|
+
// 2. 读取 index.html
|
|
757
|
+
if (!fs.existsSync(INDEX_HTML_PATH)) {
|
|
758
|
+
console.error(t('h5.init.indexMissing'));
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const indexHtml = fs.readFileSync(INDEX_HTML_PATH, 'utf8');
|
|
762
|
+
const dom = new JSDOM(indexHtml);
|
|
763
|
+
const { document } = dom.window;
|
|
764
|
+
// 3. head 标签
|
|
765
|
+
let head = document.querySelector('head');
|
|
766
|
+
if (!head) {
|
|
767
|
+
head = document.createElement('head');
|
|
768
|
+
document.documentElement.insertBefore(head, document.body);
|
|
769
|
+
}
|
|
770
|
+
// 4. 检查是否已注入 SDK
|
|
771
|
+
const scriptList = Array.from(document.querySelectorAll('script'));
|
|
772
|
+
const hasSDK = scriptList.some(script => script.src && script.src.includes(SDK_URL));
|
|
773
|
+
// 5. 检查是否已注入 vConsole
|
|
774
|
+
const hasVConsole = scriptList.some(script => script.src && script.src.includes('vConsole.js'));
|
|
775
|
+
let lastInitScript = null;
|
|
776
|
+
if (!hasSDK) {
|
|
777
|
+
// 插入 SDK 脚本
|
|
778
|
+
const sdkScript = document.createElement('script');
|
|
779
|
+
sdkScript.src = SDK_URL;
|
|
780
|
+
head.insertBefore(sdkScript, head.firstChild);
|
|
781
|
+
// 插入 SDK init 脚本
|
|
782
|
+
const initScript = document.createElement('script');
|
|
783
|
+
initScript.innerHTML = `
|
|
784
|
+
window.TTMinis = TTMinis;
|
|
785
|
+
TTMinis.game.init({
|
|
786
|
+
clientKey: "${clientKey}",
|
|
787
|
+
});
|
|
788
|
+
`;
|
|
789
|
+
head.insertBefore(initScript, sdkScript.nextSibling);
|
|
790
|
+
lastInitScript = initScript;
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
// 已经有 SDK,查找 TTMinis.game.init 脚本
|
|
794
|
+
const headScripts = Array.from(head.querySelectorAll('script'));
|
|
795
|
+
lastInitScript = headScripts.find(script => isTTMinisInitScript(script, clientKey));
|
|
796
|
+
if (lastInitScript) {
|
|
797
|
+
console.log(t('h5.init.sdkAlreadyInjected'));
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
// 没有 TTMinis.game.init,则查找最后一个 SDK 脚本
|
|
801
|
+
const sdkScripts = headScripts.filter(script => script.src && script.src.includes(SDK_URL));
|
|
802
|
+
if (sdkScripts.length > 0) {
|
|
803
|
+
lastInitScript = sdkScripts[sdkScripts.length - 1];
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* 只有 Sandbox 环境才需要注入 vConsole
|
|
809
|
+
*/
|
|
810
|
+
if (isSandbox(clientKey)) {
|
|
811
|
+
console.log(t('h5.init.sandboxSkipVconsole'));
|
|
812
|
+
// 8. 插入 vConsole 相关脚本(如果需要)
|
|
813
|
+
if (!hasVConsole) {
|
|
814
|
+
// vConsole 相关脚本的插入点
|
|
815
|
+
let insertAfterNode = lastInitScript;
|
|
816
|
+
if (insertAfterNode) {
|
|
817
|
+
insertAfterNode = insertAfterNode.nextSibling;
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
insertAfterNode = head.firstChild;
|
|
821
|
+
}
|
|
822
|
+
// vConsole 源码
|
|
823
|
+
const vconsoleSourceScript = document.createElement('script');
|
|
824
|
+
vconsoleSourceScript.src = VCONSOLE_URL;
|
|
825
|
+
// vConsole 初始化
|
|
826
|
+
const vconsoleInitScript = document.createElement('script');
|
|
827
|
+
vconsoleInitScript.innerHTML = VCONSOLE_INIT;
|
|
828
|
+
head.insertBefore(vconsoleSourceScript, insertAfterNode);
|
|
829
|
+
head.insertBefore(vconsoleInitScript, insertAfterNode);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
// 9. 格式化并写回 index.html
|
|
833
|
+
const formattedHtml = await prettier.format(dom.serialize(), {
|
|
834
|
+
parser: 'html',
|
|
835
|
+
});
|
|
836
|
+
fs.writeFileSync(INDEX_HTML_PATH, formattedHtml);
|
|
837
|
+
console.log(chalk.green.bold(t('h5.init.done')));
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function init$1() {
|
|
841
|
+
const promptModule = inquirer.createPromptModule();
|
|
842
|
+
promptModule([
|
|
843
|
+
{
|
|
844
|
+
type: 'input',
|
|
845
|
+
name: 'clientKey',
|
|
846
|
+
message: t('h5.init.prompt.clientKey'),
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
type: 'input',
|
|
850
|
+
name: 'devPort',
|
|
851
|
+
message: t('h5.init.prompt.devPort'),
|
|
852
|
+
default: 9527,
|
|
853
|
+
},
|
|
854
|
+
])
|
|
855
|
+
.then(async (answers) => {
|
|
856
|
+
const { clientKey, devPort } = answers;
|
|
857
|
+
const config = {
|
|
858
|
+
_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.',
|
|
859
|
+
orientation: 'VERTICAL',
|
|
860
|
+
dev: {
|
|
861
|
+
port: devPort,
|
|
862
|
+
},
|
|
863
|
+
};
|
|
864
|
+
await injectScripts({ clientKey, config });
|
|
865
|
+
process.exit(0);
|
|
866
|
+
})
|
|
867
|
+
.catch(() => {
|
|
868
|
+
process.exit(1);
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const app = express();
|
|
873
|
+
// 1. 检查配置文件是否存在
|
|
874
|
+
function dev$1() {
|
|
875
|
+
const configPath = path.join(process.cwd(), CONFIG_FILE_NAME);
|
|
876
|
+
if (!fs.existsSync(configPath)) {
|
|
877
|
+
console.log(chalk.red.bold(t('h5.dev.configMissing', { file: CONFIG_FILE_NAME })));
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
// 2. 读取配置
|
|
881
|
+
const gameConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
882
|
+
const devPort = gameConfig.dev?.port || 9527;
|
|
883
|
+
const hasOpenContext = !!gameConfig.openDataContext;
|
|
884
|
+
if (hasOpenContext) {
|
|
885
|
+
generateOpenContextHtml(gameConfig.openDataContext, gameConfig.app_id);
|
|
886
|
+
}
|
|
887
|
+
// 3. 打印开发前提示
|
|
888
|
+
console.log(chalk.yellow.bold(t('h5.dev.precheckTips')));
|
|
889
|
+
console.log(chalk.bold.blue(t('h5.dev.startingBanner')));
|
|
890
|
+
/**
|
|
891
|
+
* 支持 .br 文件, 支持 gzip
|
|
892
|
+
*/
|
|
893
|
+
app.use((req, res, next) => {
|
|
894
|
+
if (req.url.endsWith('.br')) {
|
|
895
|
+
res.setHeader('Content-Encoding', 'br');
|
|
896
|
+
}
|
|
897
|
+
else if (req.url.endsWith('.gz')) {
|
|
898
|
+
res.setHeader('Content-Encoding', 'gzip');
|
|
899
|
+
}
|
|
900
|
+
next();
|
|
901
|
+
});
|
|
902
|
+
/**
|
|
903
|
+
* 给所有的请求返回设置 CSP
|
|
904
|
+
*/
|
|
905
|
+
app.use((req, res, next) => {
|
|
906
|
+
/**
|
|
907
|
+
* 计算 HTML 中的内联脚本生成 hash 插入 CSP 中
|
|
908
|
+
*/
|
|
909
|
+
try {
|
|
910
|
+
// 1. 读取 HTML 文件内容
|
|
911
|
+
const htmlPath = path.join(process.cwd(), 'index.html');
|
|
912
|
+
const html = fs.readFileSync(htmlPath, 'utf8');
|
|
913
|
+
// 2. 用 cheerio 解析 HTML
|
|
914
|
+
const $ = cheerio.load(html);
|
|
915
|
+
// 3. 提取所有无 src 属性的内联 <script> 内容
|
|
916
|
+
const scripts = [];
|
|
917
|
+
$('script:not([src])').each((i, elem) => {
|
|
918
|
+
const content = $(elem).html();
|
|
919
|
+
if (content && content.trim()) {
|
|
920
|
+
scripts.push(content);
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
// 4. 计算每段脚本的 SHA-256 hash 并 base64 编码
|
|
924
|
+
// const hashes = scripts.map(script => {
|
|
925
|
+
// const hash = crypto
|
|
926
|
+
// .createHash('sha256')
|
|
927
|
+
// .update(script, 'utf8')
|
|
928
|
+
// .digest('base64');
|
|
929
|
+
// return `'sha256-${hash}'`;
|
|
930
|
+
// });
|
|
931
|
+
// 开发者本地调试,信任的域名默认为 * 便于调试
|
|
932
|
+
const devTrustedDomain = '*';
|
|
933
|
+
res.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' ${devTrustedDomain} data: blob: *; connect-src 'self' ${devTrustedDomain} data: blob: ; style-src 'self' ${devTrustedDomain} 'unsafe-inline' fonts.googleapis.com data: blob: *; font-src 'self' fonts.gstatic.com blob: data: *; media-src 'self' ${devTrustedDomain} data: blob: *; frame-src 'none'; base-uri 'self'; worker-src 'self' blob: data: ;`);
|
|
934
|
+
}
|
|
935
|
+
catch (e) {
|
|
936
|
+
// 如果 index.html 不存在或有异常,CSP 头就不设置
|
|
937
|
+
console.warn(chalk.red(t('h5.dev.cspFailed')), e.message);
|
|
938
|
+
}
|
|
939
|
+
next();
|
|
940
|
+
});
|
|
941
|
+
// 4. 静态资源服务
|
|
942
|
+
app.use(express.static(path.join(process.cwd())));
|
|
943
|
+
// 5. 启动服务并自动打开浏览器
|
|
944
|
+
app.listen(devPort, () => {
|
|
945
|
+
const gameUrl = `http://localhost:${devPort}`;
|
|
946
|
+
const openContextUrl = `http://localhost:${devPort}/open_context.html`;
|
|
947
|
+
let devUrl = `${MINIS_RUNTIME_URL}?minis_url=${gameUrl}&enable_log=1`;
|
|
948
|
+
if (hasOpenContext) {
|
|
949
|
+
devUrl += `&open_context_url=${openContextUrl}`;
|
|
950
|
+
}
|
|
951
|
+
console.log(t('h5.dev.accessUrl', {
|
|
952
|
+
url: String(chalk.green.underline.bold(devUrl)),
|
|
953
|
+
}));
|
|
954
|
+
try {
|
|
955
|
+
// 自动打开浏览器,跨平台
|
|
956
|
+
openUrl(devUrl);
|
|
957
|
+
}
|
|
958
|
+
catch (e) {
|
|
959
|
+
console.warn(chalk.red(t('h5.dev.openBrowserFailed')), e.message);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
async function buildMinisManifest() {
|
|
965
|
+
try {
|
|
966
|
+
const buildPath = path.join(process.cwd());
|
|
967
|
+
const resourceList = [];
|
|
968
|
+
const allFiles = collectAllFiles(buildPath);
|
|
969
|
+
Object.keys(allFiles)
|
|
970
|
+
.filter(file => !file.endsWith('.map'))
|
|
971
|
+
.forEach(file => {
|
|
972
|
+
const relativeFilePath = allFiles[file];
|
|
973
|
+
const filePathArr = relativeFilePath.split('/').filter(Boolean); // Split filename
|
|
974
|
+
const fileName = filePathArr.pop() || ''; // Get filename
|
|
975
|
+
if (filePathArr.length === 0) {
|
|
976
|
+
resourceList.push({ type: 'file', name: fileName });
|
|
977
|
+
}
|
|
978
|
+
else {
|
|
979
|
+
const folder = findOrCreateFolder(filePathArr, resourceList);
|
|
980
|
+
folder.children.push({ type: 'file', name: fileName });
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
fs.writeFileSync(path.join(buildPath, MINIS_MANIFEST_FILE_NAME), JSON.stringify({ name: MINIS_MANIFEST_FILE_NAME, resource_list: resourceList }, null, 2));
|
|
984
|
+
}
|
|
985
|
+
catch (error) {
|
|
986
|
+
console.error(chalk.red(`Error during debug process: ${error.message}`));
|
|
987
|
+
if (error instanceof Error && error.stack) {
|
|
988
|
+
console.error(chalk.red(`Stack trace: ${error.stack}`));
|
|
989
|
+
}
|
|
990
|
+
process.exit(1);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
function findOrCreateFolder(pathArray, currentFolder) {
|
|
823
994
|
let folder = currentFolder.find(item => item.type === 'folder' && item.name === pathArray[0]);
|
|
824
995
|
if (!folder) {
|
|
825
996
|
folder = { type: 'folder', name: pathArray[0], children: [] };
|
|
@@ -854,7 +1025,7 @@ async function build() {
|
|
|
854
1025
|
try {
|
|
855
1026
|
const configPath = path.join(process.cwd(), CONFIG_FILE_NAME);
|
|
856
1027
|
if (!fs.existsSync(configPath)) {
|
|
857
|
-
console.log(chalk.red.bold(
|
|
1028
|
+
console.log(chalk.red.bold(t('h5.bundle.configMissing', { file: CONFIG_FILE_NAME })));
|
|
858
1029
|
return;
|
|
859
1030
|
}
|
|
860
1031
|
const gameConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
@@ -869,11 +1040,11 @@ async function build() {
|
|
|
869
1040
|
type: 'input',
|
|
870
1041
|
name: 'zipName',
|
|
871
1042
|
default: 'game',
|
|
872
|
-
message: '
|
|
1043
|
+
message: t('h5.bundle.prompt.zipName'),
|
|
873
1044
|
});
|
|
874
1045
|
zName = zipNameInput;
|
|
875
1046
|
const startTime = Date.now();
|
|
876
|
-
console.log(chalk.bold.blue('start
|
|
1047
|
+
console.log(chalk.bold.blue(t('h5.bundle.start')));
|
|
877
1048
|
/**
|
|
878
1049
|
* 移除掉当前根目录下的文件包
|
|
879
1050
|
*/
|
|
@@ -898,11 +1069,17 @@ async function build() {
|
|
|
898
1069
|
await archive.pipe(fs.createWriteStream(zipPath));
|
|
899
1070
|
await archive.directory(path.resolve(process.cwd()), false);
|
|
900
1071
|
await archive.finalize();
|
|
901
|
-
console.log(chalk.yellow.bold(
|
|
1072
|
+
console.log(chalk.yellow.bold(t('h5.bundle.success', {
|
|
1073
|
+
zipName: zipNameInput,
|
|
1074
|
+
cost: Date.now() - startTime,
|
|
1075
|
+
})));
|
|
902
1076
|
process.exit(0);
|
|
903
1077
|
}
|
|
904
1078
|
catch (error) {
|
|
905
|
-
console.log(chalk.red(
|
|
1079
|
+
console.log(chalk.red(t('h5.bundle.fail', {
|
|
1080
|
+
zipName: zName,
|
|
1081
|
+
error: error.message,
|
|
1082
|
+
})));
|
|
906
1083
|
process.exit(1);
|
|
907
1084
|
}
|
|
908
1085
|
}
|
|
@@ -935,7 +1112,7 @@ function getDesktopPath() {
|
|
|
935
1112
|
// 这是一种安全的默认行为,因为即使文件夹不存在,程序后续创建文件时
|
|
936
1113
|
// 也可以选择自动创建这个目录。
|
|
937
1114
|
const defaultPath = path.join(homeDir, 'Desktop');
|
|
938
|
-
console.log(
|
|
1115
|
+
console.log(t('h5.bundle.desktopFallback', { path: defaultPath }));
|
|
939
1116
|
return defaultPath;
|
|
940
1117
|
}
|
|
941
1118
|
|
|
@@ -1076,6 +1253,7 @@ const store = Store.getInstance({
|
|
|
1076
1253
|
nodeWsPort: DEV_WS_PORT,
|
|
1077
1254
|
packages: {},
|
|
1078
1255
|
isUnderCompiling: false,
|
|
1256
|
+
isUnderUploading: false,
|
|
1079
1257
|
isWaitingForUpload: false,
|
|
1080
1258
|
projectInfo: {
|
|
1081
1259
|
projectSize: 0,
|
|
@@ -1276,10 +1454,6 @@ function getOutputDir() {
|
|
|
1276
1454
|
}
|
|
1277
1455
|
|
|
1278
1456
|
async function compile(context) {
|
|
1279
|
-
// const { openDataContext } = getOpenContextConfig();
|
|
1280
|
-
// if (!!openDataContext) {
|
|
1281
|
-
// buildOpenContextToFile(openDataContext);
|
|
1282
|
-
// }
|
|
1283
1457
|
const entryDir = process.cwd();
|
|
1284
1458
|
const outputDir = getOutputDir();
|
|
1285
1459
|
const { clientKey, msg } = getClientKey();
|
|
@@ -1291,8 +1465,8 @@ async function compile(context) {
|
|
|
1291
1465
|
console.log(chalk.red.bold(msg));
|
|
1292
1466
|
}
|
|
1293
1467
|
const startTip = context?.mode === 'watch'
|
|
1294
|
-
? '
|
|
1295
|
-
: '
|
|
1468
|
+
? t('native.compile.watching')
|
|
1469
|
+
: t('native.compile.compiling');
|
|
1296
1470
|
console.log(chalk.bold.cyan(startTip));
|
|
1297
1471
|
wsServer.sendCompilationStatus('start');
|
|
1298
1472
|
store.setState({
|
|
@@ -1329,7 +1503,7 @@ async function compile(context) {
|
|
|
1329
1503
|
wsServer.sendCompilationStatus('end', {
|
|
1330
1504
|
isSuccess: true,
|
|
1331
1505
|
});
|
|
1332
|
-
console.log(chalk.green.bold('
|
|
1506
|
+
console.log(chalk.green.bold(t('native.compile.success')));
|
|
1333
1507
|
resolve(msg); // 编译结束,返回结果
|
|
1334
1508
|
}
|
|
1335
1509
|
worker.terminate();
|
|
@@ -1351,6 +1525,7 @@ async function compile(context) {
|
|
|
1351
1525
|
outputDir,
|
|
1352
1526
|
devPort: store.getState().nodeServerPort,
|
|
1353
1527
|
entryDir,
|
|
1528
|
+
lang: getCurrentLanguage(),
|
|
1354
1529
|
},
|
|
1355
1530
|
});
|
|
1356
1531
|
});
|
|
@@ -1390,22 +1565,35 @@ async function watch() {
|
|
|
1390
1565
|
});
|
|
1391
1566
|
}
|
|
1392
1567
|
|
|
1393
|
-
let spinner;
|
|
1568
|
+
let spinner$1;
|
|
1394
1569
|
async function uploadGame(callback) {
|
|
1395
1570
|
const ora = await import('ora');
|
|
1396
|
-
spinner = ora.default({
|
|
1397
|
-
text: chalk.cyan.bold('
|
|
1571
|
+
spinner$1 = ora.default({
|
|
1572
|
+
text: chalk.cyan.bold(t('native.upload.spinner.start')),
|
|
1398
1573
|
spinner: 'dots',
|
|
1399
1574
|
});
|
|
1400
|
-
spinner.start();
|
|
1575
|
+
spinner$1.start();
|
|
1401
1576
|
const outputDir = getOutputDir();
|
|
1402
1577
|
callback({
|
|
1403
1578
|
status: 'start',
|
|
1404
1579
|
percent: 0,
|
|
1405
1580
|
});
|
|
1406
1581
|
const zipPath = path.join(os.homedir(), '__TTMG__', 'upload.zip');
|
|
1407
|
-
|
|
1408
|
-
|
|
1582
|
+
try {
|
|
1583
|
+
await zipDirectory(outputDir, zipPath);
|
|
1584
|
+
await uploadZip(zipPath, callback);
|
|
1585
|
+
}
|
|
1586
|
+
catch (err) {
|
|
1587
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1588
|
+
if (spinner$1?.isSpinning) {
|
|
1589
|
+
spinner$1.fail(chalk.red.bold(t('native.upload.fail', { error: errorMsg })));
|
|
1590
|
+
}
|
|
1591
|
+
callback({
|
|
1592
|
+
status: 'error',
|
|
1593
|
+
percent: 0,
|
|
1594
|
+
msg: errorMsg,
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1409
1597
|
}
|
|
1410
1598
|
/**
|
|
1411
1599
|
* 复制源目录内容到临时目录,然后根据 glob 模式过滤并压缩文件。
|
|
@@ -1449,7 +1637,7 @@ async function zipDirectory(sourceDir, outPath) {
|
|
|
1449
1637
|
await archivePromise;
|
|
1450
1638
|
}
|
|
1451
1639
|
catch (err) {
|
|
1452
|
-
console.error('
|
|
1640
|
+
console.error(t('native.upload.compressError'), err);
|
|
1453
1641
|
throw err;
|
|
1454
1642
|
}
|
|
1455
1643
|
finally {
|
|
@@ -1466,63 +1654,65 @@ async function uploadZip(zipPath, callback) {
|
|
|
1466
1654
|
});
|
|
1467
1655
|
// 帮我计算下文件大小,变成 MB 为单位
|
|
1468
1656
|
const fileSize = fs.statSync(zipPath).size / 1024 / 1024;
|
|
1469
|
-
spinner.text = chalk.cyan.bold(
|
|
1657
|
+
spinner$1.text = chalk.cyan.bold(t('native.upload.spinner.size', { size: fileSize.toFixed(2) }));
|
|
1470
1658
|
const { clientHttpPort, clientHost } = store.getState();
|
|
1471
1659
|
const url = `http://${clientHost}:${clientHttpPort}/game/upload`;
|
|
1472
1660
|
try {
|
|
1473
1661
|
// 1. 创建请求流
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
const handleProgress = progress => {
|
|
1478
|
-
const percent = progress.percent;
|
|
1479
|
-
// const transferred = progress.transferred;
|
|
1480
|
-
// const total = progress.total;
|
|
1481
|
-
spinner.text = chalk.cyan.bold(`Uploading game assets to client... ${(percent * 100).toFixed(0)}%`);
|
|
1482
|
-
callback({
|
|
1483
|
-
status: 'process',
|
|
1484
|
-
percent,
|
|
1662
|
+
await new Promise(resolve => {
|
|
1663
|
+
const stream = got.stream.post(url, {
|
|
1664
|
+
body: form,
|
|
1485
1665
|
});
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1666
|
+
const handleProgress = progress => {
|
|
1667
|
+
const percent = progress.percent;
|
|
1668
|
+
spinner$1.text = chalk.cyan.bold(t('native.upload.spinner.progress', {
|
|
1669
|
+
percent: (percent * 100).toFixed(0),
|
|
1670
|
+
}));
|
|
1671
|
+
callback({
|
|
1672
|
+
status: 'process',
|
|
1673
|
+
percent,
|
|
1674
|
+
});
|
|
1675
|
+
};
|
|
1676
|
+
const cleanup = () => {
|
|
1677
|
+
stream.off('uploadProgress', handleProgress);
|
|
1678
|
+
};
|
|
1679
|
+
stream.on('uploadProgress', handleProgress);
|
|
1680
|
+
// Consume response body to ensure 'end' is emitted.
|
|
1681
|
+
stream.on('data', () => { });
|
|
1682
|
+
// 当流成功结束时
|
|
1683
|
+
stream.on('end', () => {
|
|
1684
|
+
cleanup();
|
|
1685
|
+
spinner$1.succeed(chalk.green.bold(t('native.upload.success', { cost: Date.now() - startTime })));
|
|
1686
|
+
callback({
|
|
1687
|
+
status: 'success',
|
|
1688
|
+
percent: 1,
|
|
1689
|
+
});
|
|
1690
|
+
resolve();
|
|
1499
1691
|
});
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1692
|
+
// 当流发生错误时
|
|
1693
|
+
stream.on('error', err => {
|
|
1694
|
+
cleanup();
|
|
1695
|
+
spinner$1.fail(chalk.red.bold(t('native.upload.fail', { error: err.message })));
|
|
1696
|
+
callback({
|
|
1697
|
+
status: 'error',
|
|
1698
|
+
percent: 0,
|
|
1699
|
+
msg: err.message,
|
|
1700
|
+
});
|
|
1701
|
+
resolve();
|
|
1509
1702
|
});
|
|
1510
1703
|
});
|
|
1511
1704
|
}
|
|
1512
1705
|
catch (err) {
|
|
1513
|
-
|
|
1514
|
-
status: 'error',
|
|
1515
|
-
percent: 0,
|
|
1516
|
-
msg: err?.message,
|
|
1517
|
-
});
|
|
1518
|
-
process.stdout.write('\n');
|
|
1519
|
-
console.log('\n');
|
|
1520
|
-
console.error(chalk.red.bold('✖ Upload failed with server error, please scan qrcode to reupload'));
|
|
1706
|
+
throw err;
|
|
1521
1707
|
}
|
|
1522
1708
|
}
|
|
1523
1709
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1710
|
+
let hasBoundUploadListeners = false;
|
|
1711
|
+
function listen$1() {
|
|
1712
|
+
if (hasBoundUploadListeners)
|
|
1713
|
+
return;
|
|
1714
|
+
hasBoundUploadListeners = true;
|
|
1715
|
+
eventEmitter.on('startUpload', async () => {
|
|
1526
1716
|
/**
|
|
1527
1717
|
* 如果还在编译中,需要等到编译结束再上传
|
|
1528
1718
|
*/
|
|
@@ -1532,30 +1722,53 @@ function listen() {
|
|
|
1532
1722
|
});
|
|
1533
1723
|
return;
|
|
1534
1724
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
else if (status === 'error') {
|
|
1544
|
-
wsServer.sendUploadStatus('error', {
|
|
1545
|
-
status: 'error',
|
|
1546
|
-
errMsg: msg,
|
|
1547
|
-
isSuccess: false,
|
|
1548
|
-
});
|
|
1549
|
-
}
|
|
1550
|
-
else if (status === 'success') {
|
|
1551
|
-
wsServer.sendUploadStatus('success', {
|
|
1552
|
-
status: 'success',
|
|
1553
|
-
packages: store.getState().packages,
|
|
1554
|
-
clientKey: getClientKey().clientKey,
|
|
1555
|
-
isSuccess: true,
|
|
1556
|
-
});
|
|
1557
|
-
}
|
|
1725
|
+
/**
|
|
1726
|
+
* 避免重复触发导致并发上传(会触发 ora 多 spinner 警告)
|
|
1727
|
+
*/
|
|
1728
|
+
if (store.getState().isUnderUploading) {
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
store.setState({
|
|
1732
|
+
isUnderUploading: true,
|
|
1558
1733
|
});
|
|
1734
|
+
wsServer.sendUploadStatus('start');
|
|
1735
|
+
try {
|
|
1736
|
+
await uploadGame(({ status, percent, msg }) => {
|
|
1737
|
+
if (status === 'process') {
|
|
1738
|
+
wsServer.sendUploadStatus('process', {
|
|
1739
|
+
status: 'process',
|
|
1740
|
+
progress: percent,
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
else if (status === 'error') {
|
|
1744
|
+
wsServer.sendUploadStatus('error', {
|
|
1745
|
+
status: 'error',
|
|
1746
|
+
errMsg: msg,
|
|
1747
|
+
isSuccess: false,
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
else if (status === 'success') {
|
|
1751
|
+
wsServer.sendUploadStatus('success', {
|
|
1752
|
+
status: 'success',
|
|
1753
|
+
packages: store.getState().packages,
|
|
1754
|
+
clientKey: getClientKey().clientKey,
|
|
1755
|
+
isSuccess: true,
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
catch (error) {
|
|
1761
|
+
wsServer.sendUploadStatus('error', {
|
|
1762
|
+
status: 'error',
|
|
1763
|
+
errMsg: error instanceof Error ? error.message : String(error),
|
|
1764
|
+
isSuccess: false,
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
finally {
|
|
1768
|
+
store.setState({
|
|
1769
|
+
isUnderUploading: false,
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1559
1772
|
});
|
|
1560
1773
|
eventEmitter.on('compileSuccess', () => {
|
|
1561
1774
|
/**
|
|
@@ -1712,6 +1925,20 @@ function getLocalIPs() {
|
|
|
1712
1925
|
}
|
|
1713
1926
|
}
|
|
1714
1927
|
|
|
1928
|
+
function getLocalIP() {
|
|
1929
|
+
const networkInterfaces = os.networkInterfaces();
|
|
1930
|
+
for (const interfaceName in networkInterfaces) {
|
|
1931
|
+
const interfaceInfo = networkInterfaces[interfaceName];
|
|
1932
|
+
if (!interfaceInfo)
|
|
1933
|
+
continue;
|
|
1934
|
+
for (const addressInfo of interfaceInfo) {
|
|
1935
|
+
if (addressInfo.family === 'IPv4' && !addressInfo.internal) {
|
|
1936
|
+
return addressInfo.address;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1715
1942
|
async function init() {
|
|
1716
1943
|
const promptModule = inquirer.createPromptModule();
|
|
1717
1944
|
const { clientKey: lastUsedClientKey } = getTTMGRC() || {};
|
|
@@ -1720,12 +1947,12 @@ async function init() {
|
|
|
1720
1947
|
{
|
|
1721
1948
|
type: 'list',
|
|
1722
1949
|
name: 'selectedClientKey',
|
|
1723
|
-
message: '
|
|
1950
|
+
message: t('native.init.selectClientKey'),
|
|
1724
1951
|
choices: [{
|
|
1725
|
-
name:
|
|
1952
|
+
name: t('native.init.lastUsed', { clientKey: lastUsedClientKey }),
|
|
1726
1953
|
value: lastUsedClientKey,
|
|
1727
1954
|
}, {
|
|
1728
|
-
name: '
|
|
1955
|
+
name: t('native.init.addNew'),
|
|
1729
1956
|
value: 'new',
|
|
1730
1957
|
}],
|
|
1731
1958
|
},
|
|
@@ -1738,10 +1965,10 @@ async function init() {
|
|
|
1738
1965
|
{
|
|
1739
1966
|
type: 'input',
|
|
1740
1967
|
name: 'clientKey',
|
|
1741
|
-
message: '
|
|
1968
|
+
message: t('native.init.inputNew'),
|
|
1742
1969
|
validate: input => {
|
|
1743
1970
|
if (!input) {
|
|
1744
|
-
return '
|
|
1971
|
+
return t('native.init.clientKeyRequired');
|
|
1745
1972
|
}
|
|
1746
1973
|
return true;
|
|
1747
1974
|
},
|
|
@@ -1764,10 +1991,10 @@ async function init() {
|
|
|
1764
1991
|
{
|
|
1765
1992
|
type: 'input',
|
|
1766
1993
|
name: 'clientKey',
|
|
1767
|
-
message: '
|
|
1994
|
+
message: t('native.init.inputYour'),
|
|
1768
1995
|
validate: input => {
|
|
1769
1996
|
if (!input) {
|
|
1770
|
-
return '
|
|
1997
|
+
return t('native.init.clientKeyRequired');
|
|
1771
1998
|
}
|
|
1772
1999
|
return true;
|
|
1773
2000
|
},
|
|
@@ -1791,19 +2018,23 @@ function getDevToolVersion() {
|
|
|
1791
2018
|
function showTips(context) {
|
|
1792
2019
|
console.log(chalk.gray('─────────────────────────────────────────────'));
|
|
1793
2020
|
console.log(chalk.yellow.bold('1.') +
|
|
1794
|
-
'
|
|
1795
|
-
console.log('
|
|
2021
|
+
t('native.tips.1'));
|
|
2022
|
+
console.log(t('native.tips.1.sub'));
|
|
1796
2023
|
console.log(' ' + chalk.cyan.underline(context.server));
|
|
1797
2024
|
console.log('');
|
|
1798
2025
|
console.log(chalk.yellow.bold('2.') +
|
|
1799
|
-
'
|
|
1800
|
-
console.log('
|
|
1801
|
-
console.log('
|
|
2026
|
+
t('native.tips.2'));
|
|
2027
|
+
console.log(t('native.tips.2.sub1'));
|
|
2028
|
+
console.log(t('native.tips.2.sub2'));
|
|
1802
2029
|
console.log('');
|
|
1803
2030
|
console.log(chalk.yellow.bold('3.') +
|
|
1804
|
-
'
|
|
1805
|
-
console.log('
|
|
1806
|
-
console.log('
|
|
2031
|
+
t('native.tips.3'));
|
|
2032
|
+
console.log(t('native.tips.3.sub1'));
|
|
2033
|
+
console.log(t('native.tips.3.sub2'));
|
|
2034
|
+
console.log('');
|
|
2035
|
+
console.log(chalk.red.bold('4. ⚠️') +
|
|
2036
|
+
t('native.tips.4'));
|
|
2037
|
+
console.log(t('native.tips.4.sub1'));
|
|
1807
2038
|
console.log(chalk.gray('─────────────────────────────────────────────'));
|
|
1808
2039
|
}
|
|
1809
2040
|
|
|
@@ -1887,7 +2118,7 @@ async function check() {
|
|
|
1887
2118
|
// process.exit(1);
|
|
1888
2119
|
// }
|
|
1889
2120
|
if (!isWorkspace()) {
|
|
1890
|
-
printBox('Error',
|
|
2121
|
+
printBox('Error', t('native.check.notWorkspace'));
|
|
1891
2122
|
process.exit(1);
|
|
1892
2123
|
}
|
|
1893
2124
|
// const checkResult = await checkPkgs({
|
|
@@ -1937,6 +2168,236 @@ async function check() {
|
|
|
1937
2168
|
// }
|
|
1938
2169
|
}
|
|
1939
2170
|
|
|
2171
|
+
const devToolVersion$1 = getDevToolVersion();
|
|
2172
|
+
async function listen(app, options) {
|
|
2173
|
+
const maxRetries = options?.maxRetries;
|
|
2174
|
+
const version = devToolVersion$1;
|
|
2175
|
+
const server = http.createServer(app);
|
|
2176
|
+
function tryListen(port) {
|
|
2177
|
+
return new Promise(resolve => {
|
|
2178
|
+
const onError = err => {
|
|
2179
|
+
server.removeListener('listening', onListening);
|
|
2180
|
+
if (err.code === 'EADDRINUSE') {
|
|
2181
|
+
console.log(chalk(t('native.server.portInUse', {
|
|
2182
|
+
port,
|
|
2183
|
+
next: port + 1,
|
|
2184
|
+
})));
|
|
2185
|
+
resolve(false);
|
|
2186
|
+
}
|
|
2187
|
+
else {
|
|
2188
|
+
console.log(chalk.red.bold(err.message));
|
|
2189
|
+
process.exit(1);
|
|
2190
|
+
}
|
|
2191
|
+
};
|
|
2192
|
+
const onListening = () => {
|
|
2193
|
+
server.removeListener('error', onError);
|
|
2194
|
+
resolve(true);
|
|
2195
|
+
};
|
|
2196
|
+
server.once('error', onError);
|
|
2197
|
+
server.once('listening', onListening);
|
|
2198
|
+
server.listen(port);
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
let isListening = false;
|
|
2202
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
2203
|
+
const currentPort = store.getState().nodeServerPort;
|
|
2204
|
+
isListening = await tryListen(currentPort);
|
|
2205
|
+
if (isListening) {
|
|
2206
|
+
break;
|
|
2207
|
+
}
|
|
2208
|
+
store.setState({ nodeServerPort: currentPort + 1 });
|
|
2209
|
+
}
|
|
2210
|
+
if (!isListening) {
|
|
2211
|
+
console.log(chalk.red.bold(t('native.server.failedAfterRetries', { retries: maxRetries })));
|
|
2212
|
+
process.exit(1);
|
|
2213
|
+
}
|
|
2214
|
+
// @ts-ignore
|
|
2215
|
+
const port = server.address().port;
|
|
2216
|
+
const url = `http://localhost:${port}?v=${encodeURIComponent(version)}`;
|
|
2217
|
+
return { server, port, url, version };
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
function setupMiddlewares(app, options) {
|
|
2221
|
+
const { publicPath, outputDir } = options;
|
|
2222
|
+
app.use(fileUpload());
|
|
2223
|
+
app.use(expressStaticGzip(publicPath, {
|
|
2224
|
+
enableBrotli: true,
|
|
2225
|
+
orderPreference: ['br'],
|
|
2226
|
+
}));
|
|
2227
|
+
app.use((req, res, next) => {
|
|
2228
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
2229
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
2230
|
+
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
2231
|
+
res.header('Pragma', 'no-cache');
|
|
2232
|
+
res.header('Expires', '0');
|
|
2233
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
2234
|
+
next();
|
|
2235
|
+
});
|
|
2236
|
+
app.use(express.json());
|
|
2237
|
+
app.use(express.urlencoded({ extended: true }));
|
|
2238
|
+
app.use('/game/files', express.static(outputDir));
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
const successCode = 0;
|
|
2242
|
+
const errorCode = -1;
|
|
2243
|
+
|
|
2244
|
+
function buildCheckPkgsOptions(outputDir) {
|
|
2245
|
+
return {
|
|
2246
|
+
entry: process.cwd(),
|
|
2247
|
+
output: outputDir,
|
|
2248
|
+
dev: {
|
|
2249
|
+
enable: true,
|
|
2250
|
+
port: store.getState().nodeServerPort,
|
|
2251
|
+
host: 'localhost',
|
|
2252
|
+
enableSourcemap: false,
|
|
2253
|
+
enableLog: false,
|
|
2254
|
+
},
|
|
2255
|
+
build: {
|
|
2256
|
+
enableOdr: false,
|
|
2257
|
+
enableAPICheck: true,
|
|
2258
|
+
...PKG_SIZE_LIMIT,
|
|
2259
|
+
},
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
const outputDir$2 = getOutputDir();
|
|
2264
|
+
const devToolVersion = getDevToolVersion();
|
|
2265
|
+
const gameConfigRoute = {
|
|
2266
|
+
method: 'get',
|
|
2267
|
+
path: '/game/config',
|
|
2268
|
+
handler: async (req, res) => {
|
|
2269
|
+
const [basic, checkResult] = await Promise.all([
|
|
2270
|
+
ttmgPack.getPkgs({ entryDir: process.cwd() }),
|
|
2271
|
+
ttmgPack.checkPkgs(buildCheckPkgsOptions(outputDir$2), {
|
|
2272
|
+
lang: getCurrentLanguage(),
|
|
2273
|
+
}),
|
|
2274
|
+
]);
|
|
2275
|
+
const user = getCurrentUser();
|
|
2276
|
+
const { clientKey } = getClientKey();
|
|
2277
|
+
const localLang = getConfiguredLanguage();
|
|
2278
|
+
res.send({
|
|
2279
|
+
error: null,
|
|
2280
|
+
data: {
|
|
2281
|
+
user,
|
|
2282
|
+
code: successCode,
|
|
2283
|
+
nodeWsPort: store.getState().nodeWsPort,
|
|
2284
|
+
clientKey,
|
|
2285
|
+
schema: `https://www.tiktok.com/ttmg/dev/${clientKey}?host=${getLocalIP()}&port=${store.getState().nodeWsPort}&host_list=${encodeURIComponent(JSON.stringify(getLocalIPs()))}`,
|
|
2286
|
+
...basic,
|
|
2287
|
+
devToolVersion,
|
|
2288
|
+
checkResult,
|
|
2289
|
+
lang: localLang,
|
|
2290
|
+
},
|
|
2291
|
+
});
|
|
2292
|
+
},
|
|
2293
|
+
};
|
|
2294
|
+
|
|
2295
|
+
const gameConfigFillbackRoute = {
|
|
2296
|
+
method: 'post',
|
|
2297
|
+
path: '/game/config-fillback',
|
|
2298
|
+
handler: async (req, res) => {
|
|
2299
|
+
const configuredLang = getConfiguredLanguage();
|
|
2300
|
+
if (configuredLang) {
|
|
2301
|
+
res.send({
|
|
2302
|
+
code: successCode,
|
|
2303
|
+
data: {
|
|
2304
|
+
lang: configuredLang,
|
|
2305
|
+
updated: false,
|
|
2306
|
+
},
|
|
2307
|
+
});
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
2310
|
+
const incomingLang = req.body?.lang;
|
|
2311
|
+
const fallbackLang = resolveSupportedLanguage(incomingLang);
|
|
2312
|
+
if (!fallbackLang) {
|
|
2313
|
+
res.send({
|
|
2314
|
+
code: successCode,
|
|
2315
|
+
data: {
|
|
2316
|
+
lang: null,
|
|
2317
|
+
updated: false,
|
|
2318
|
+
},
|
|
2319
|
+
});
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2322
|
+
setTTMGRC({ lang: fallbackLang });
|
|
2323
|
+
res.send({
|
|
2324
|
+
code: successCode,
|
|
2325
|
+
data: {
|
|
2326
|
+
lang: fallbackLang,
|
|
2327
|
+
updated: true,
|
|
2328
|
+
},
|
|
2329
|
+
});
|
|
2330
|
+
},
|
|
2331
|
+
};
|
|
2332
|
+
|
|
2333
|
+
const outputDir$1 = getOutputDir();
|
|
2334
|
+
const gameCheckRoute = {
|
|
2335
|
+
method: 'get',
|
|
2336
|
+
path: '/game/check',
|
|
2337
|
+
handler: async (req, res) => {
|
|
2338
|
+
const checkResult = await ttmgPack.checkPkgs(buildCheckPkgsOptions(outputDir$1), {
|
|
2339
|
+
lang: getCurrentLanguage(),
|
|
2340
|
+
});
|
|
2341
|
+
res.send({ code: successCode, data: checkResult });
|
|
2342
|
+
},
|
|
2343
|
+
};
|
|
2344
|
+
|
|
2345
|
+
const gameDetailRoute = {
|
|
2346
|
+
method: 'get',
|
|
2347
|
+
path: '/game/detail',
|
|
2348
|
+
handler: async (req, res) => {
|
|
2349
|
+
const basic = await ttmgPack.getPkgs({ entryDir: process.cwd() });
|
|
2350
|
+
const { clientKey } = getClientKey();
|
|
2351
|
+
const { error, data: gameInfo } = await fetchGameInfo(clientKey);
|
|
2352
|
+
store.setState({
|
|
2353
|
+
appId: gameInfo?.app_id,
|
|
2354
|
+
});
|
|
2355
|
+
if (error) {
|
|
2356
|
+
res.send({ error, data: null });
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
res.send({ error: null, data: { ...basic, ...gameInfo } });
|
|
2360
|
+
},
|
|
2361
|
+
};
|
|
2362
|
+
|
|
2363
|
+
const gameUploadRoute = {
|
|
2364
|
+
method: 'post',
|
|
2365
|
+
path: '/game/upload',
|
|
2366
|
+
handler: async (req, res) => {
|
|
2367
|
+
try {
|
|
2368
|
+
console.log(t('native.server.upload.packingDir', { cwd: process.cwd() }));
|
|
2369
|
+
const gameZipBuffer = await zipCwdToBuffer();
|
|
2370
|
+
console.log(t('native.server.upload.packed', {
|
|
2371
|
+
size: (gameZipBuffer.length / 1024 / 1024).toFixed(2),
|
|
2372
|
+
}));
|
|
2373
|
+
const desc = req.headers['ttmg-game-desc'];
|
|
2374
|
+
const decodedDesc = decodeURIComponent(desc || '--');
|
|
2375
|
+
const { data, error } = await uploadGameToPlatform({
|
|
2376
|
+
// @ts-ignore
|
|
2377
|
+
data: gameZipBuffer,
|
|
2378
|
+
name: 'game.zip',
|
|
2379
|
+
clientKey: getClientKey().clientKey,
|
|
2380
|
+
note: decodedDesc,
|
|
2381
|
+
appId: store.getState().appId,
|
|
2382
|
+
});
|
|
2383
|
+
if (error) {
|
|
2384
|
+
res.send({ code: errorCode, error });
|
|
2385
|
+
}
|
|
2386
|
+
else {
|
|
2387
|
+
res.send({ code: successCode, data });
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
catch (error) {
|
|
2391
|
+
let errorMessage = t('common.unknownError');
|
|
2392
|
+
if (error instanceof Error) {
|
|
2393
|
+
errorMessage = error.message;
|
|
2394
|
+
}
|
|
2395
|
+
console.error(t('native.server.upload.failed', { error: errorMessage }));
|
|
2396
|
+
res.status(500).send({ code: errorCode, data: errorMessage });
|
|
2397
|
+
}
|
|
2398
|
+
},
|
|
2399
|
+
};
|
|
2400
|
+
|
|
1940
2401
|
const BASE_URL = 'https://developers.tiktok.com';
|
|
1941
2402
|
const DEV_HEADERS = {
|
|
1942
2403
|
// 'x-use-ppe': '1',
|
|
@@ -2825,212 +3286,56 @@ const getTaskInfo = async (params) => {
|
|
|
2825
3286
|
});
|
|
2826
3287
|
};
|
|
2827
3288
|
|
|
2828
|
-
const
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
const
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
app.use(fileUpload()); // 启用 express-fileupload 中间件
|
|
2837
|
-
// --- 中间件和路由设置 ---
|
|
2838
|
-
app.use(expressStaticGzip(publicPath, {
|
|
2839
|
-
enableBrotli: true,
|
|
2840
|
-
orderPreference: ['br'],
|
|
2841
|
-
}));
|
|
2842
|
-
app.use((req, res, next) => {
|
|
2843
|
-
res.header('Access-Control-Allow-Origin', '*');
|
|
2844
|
-
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
2845
|
-
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
2846
|
-
res.header('Pragma', 'no-cache');
|
|
2847
|
-
res.header('Expires', '0');
|
|
2848
|
-
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
2849
|
-
next();
|
|
2850
|
-
});
|
|
2851
|
-
app.use(express.json());
|
|
2852
|
-
app.use(express.urlencoded({ extended: true }));
|
|
2853
|
-
app.use('/game/files', express.static(outputDir));
|
|
2854
|
-
app.get('/game/config', async (req, res) => {
|
|
2855
|
-
const [basic, checkResult] = await Promise.all([
|
|
2856
|
-
ttmgPack.getPkgs({ entryDir: process.cwd() }),
|
|
2857
|
-
ttmgPack.checkPkgs({
|
|
2858
|
-
entry: process.cwd(),
|
|
2859
|
-
output: outputDir,
|
|
2860
|
-
dev: {
|
|
2861
|
-
enable: true,
|
|
2862
|
-
port: store.getState().nodeServerPort,
|
|
2863
|
-
host: 'localhost',
|
|
2864
|
-
enableSourcemap: false,
|
|
2865
|
-
enableLog: false,
|
|
2866
|
-
},
|
|
2867
|
-
build: {
|
|
2868
|
-
enableOdr: false,
|
|
2869
|
-
enableAPICheck: true,
|
|
2870
|
-
...PKG_SIZE_LIMIT,
|
|
2871
|
-
},
|
|
2872
|
-
}),
|
|
2873
|
-
]);
|
|
2874
|
-
const user = getCurrentUser();
|
|
2875
|
-
const { clientKey } = getClientKey();
|
|
2876
|
-
res.send({
|
|
2877
|
-
error: null,
|
|
2878
|
-
data: {
|
|
2879
|
-
user,
|
|
2880
|
-
code: successCode,
|
|
2881
|
-
nodeWsPort: store.getState().nodeWsPort,
|
|
2882
|
-
clientKey: clientKey,
|
|
2883
|
-
schema: `https://www.tiktok.com/ttmg/dev/${clientKey}?host=${getLocalIP()}&port=${store.getState().nodeWsPort}&host_list=${encodeURIComponent(JSON.stringify(getLocalIPs()))}`,
|
|
2884
|
-
...basic,
|
|
2885
|
-
devToolVersion,
|
|
2886
|
-
checkResult,
|
|
2887
|
-
},
|
|
2888
|
-
});
|
|
2889
|
-
});
|
|
2890
|
-
app.get('/game/detail', async (req, res) => {
|
|
2891
|
-
const basic = await ttmgPack.getPkgs({ entryDir: process.cwd() });
|
|
2892
|
-
const { clientKey } = getClientKey();
|
|
2893
|
-
const { error, data: gameInfo } = await fetchGameInfo(clientKey);
|
|
2894
|
-
store.setState({
|
|
2895
|
-
appId: gameInfo?.app_id,
|
|
2896
|
-
});
|
|
2897
|
-
if (error) {
|
|
2898
|
-
res.send({ error, data: null });
|
|
2899
|
-
return;
|
|
2900
|
-
}
|
|
2901
|
-
else {
|
|
2902
|
-
res.send({ error: null, data: { ...basic, ...gameInfo } });
|
|
2903
|
-
}
|
|
2904
|
-
});
|
|
2905
|
-
app.get('/game/check', async (req, res) => {
|
|
2906
|
-
const checkResult = await ttmgPack.checkPkgs({
|
|
2907
|
-
entry: process.cwd(),
|
|
2908
|
-
output: outputDir,
|
|
2909
|
-
dev: {
|
|
2910
|
-
enable: true,
|
|
2911
|
-
port: store.getState().nodeServerPort,
|
|
2912
|
-
host: 'localhost',
|
|
2913
|
-
enableSourcemap: false,
|
|
2914
|
-
enableLog: false,
|
|
2915
|
-
},
|
|
2916
|
-
build: {
|
|
2917
|
-
enableOdr: false,
|
|
2918
|
-
enableAPICheck: true,
|
|
2919
|
-
...PKG_SIZE_LIMIT,
|
|
2920
|
-
},
|
|
3289
|
+
const gameWasmCancelRoute = {
|
|
3290
|
+
method: 'post',
|
|
3291
|
+
path: '/game/wasm-cancel',
|
|
3292
|
+
handler: async (req, res) => {
|
|
3293
|
+
const { codePath } = req.body;
|
|
3294
|
+
console.log('wasm-cancel', req.body);
|
|
3295
|
+
await cancelSplit({
|
|
3296
|
+
wasmCodePath: codePath,
|
|
2921
3297
|
});
|
|
2922
|
-
res.send({ code: successCode, data: checkResult });
|
|
2923
|
-
});
|
|
2924
|
-
app.post('/game/upload', async (req, res) => {
|
|
2925
|
-
try {
|
|
2926
|
-
console.log(`正在打包当前目录: ${process.cwd()} ...`);
|
|
2927
|
-
const gameZipBuffer = await zipCwdToBuffer();
|
|
2928
|
-
// 变成 MB
|
|
2929
|
-
console.log(`打包完成,大小: ${(gameZipBuffer.length / 1024 / 1024).toFixed(2)} MB`);
|
|
2930
|
-
const desc = req.headers['ttmg-game-desc'];
|
|
2931
|
-
const decodedDesc = decodeURIComponent(desc || '--');
|
|
2932
|
-
const { data, error } = await uploadGameToPlatform({
|
|
2933
|
-
// @ts-ignore
|
|
2934
|
-
data: gameZipBuffer,
|
|
2935
|
-
name: 'game.zip',
|
|
2936
|
-
clientKey: getClientKey().clientKey,
|
|
2937
|
-
note: decodedDesc,
|
|
2938
|
-
appId: store.getState().appId,
|
|
2939
|
-
});
|
|
2940
|
-
if (error) {
|
|
2941
|
-
res.send({ code: errorCode, error });
|
|
2942
|
-
}
|
|
2943
|
-
else {
|
|
2944
|
-
res.send({ code: successCode, data });
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
catch (error) {
|
|
2948
|
-
let errorMessage = 'An unknown error occurred.';
|
|
2949
|
-
if (error instanceof Error) {
|
|
2950
|
-
errorMessage = error.message;
|
|
2951
|
-
}
|
|
2952
|
-
console.error('Upload failed:', errorMessage);
|
|
2953
|
-
res.status(500).send({ code: errorCode, data: errorMessage });
|
|
2954
|
-
}
|
|
2955
|
-
});
|
|
2956
|
-
app.get('/game/wasm-split-config', (req, res) => {
|
|
2957
|
-
const config = getSplitConfig();
|
|
2958
|
-
if (!config) {
|
|
2959
|
-
res.send({ code: errorCode, data: 'Failed to parse split config' });
|
|
2960
|
-
}
|
|
2961
|
-
else {
|
|
2962
|
-
res.send({ code: successCode, data: config });
|
|
2963
|
-
}
|
|
2964
|
-
});
|
|
2965
|
-
app.get('/game/wasm-split-options', (req, res) => {
|
|
2966
3298
|
res.send({
|
|
2967
3299
|
code: successCode,
|
|
2968
|
-
|
|
2969
|
-
options: [
|
|
2970
|
-
{
|
|
2971
|
-
md5: '123',
|
|
2972
|
-
desc: 'test',
|
|
2973
|
-
version: '1.0.0',
|
|
2974
|
-
time: '2023-01-01',
|
|
2975
|
-
funcCounts: 100,
|
|
2976
|
-
},
|
|
2977
|
-
{
|
|
2978
|
-
md5: '456',
|
|
2979
|
-
desc: 'test2',
|
|
2980
|
-
version: '1.0.1',
|
|
2981
|
-
time: '2023-01-02',
|
|
2982
|
-
funcCounts: 200,
|
|
2983
|
-
},
|
|
2984
|
-
],
|
|
2985
|
-
},
|
|
3300
|
+
msg: 'cancel success',
|
|
2986
3301
|
});
|
|
2987
|
-
}
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
const
|
|
3302
|
+
},
|
|
3303
|
+
};
|
|
3304
|
+
|
|
3305
|
+
const gameWasmCollectFuncidsRoute = {
|
|
3306
|
+
method: 'get',
|
|
3307
|
+
path: '/game/wasm-collect-funcids',
|
|
3308
|
+
handler: async (req, res) => {
|
|
3309
|
+
const { clientKey, codeMd5 } = req.query;
|
|
3310
|
+
console.log('wasm-collect-funcids', req.query);
|
|
3311
|
+
const response = await getCollectedFuncIds({
|
|
2995
3312
|
client_key: clientKey,
|
|
2996
|
-
desc,
|
|
2997
3313
|
wasm_md5: codeMd5,
|
|
2998
|
-
wasm_file_path: codePath,
|
|
2999
3314
|
});
|
|
3000
|
-
|
|
3001
|
-
if (result.error) {
|
|
3315
|
+
if (response.error) {
|
|
3002
3316
|
res.send({
|
|
3003
3317
|
code: errorCode,
|
|
3004
|
-
error:
|
|
3005
|
-
ctx:
|
|
3318
|
+
error: response.error,
|
|
3319
|
+
ctx: response.ctx,
|
|
3006
3320
|
});
|
|
3007
3321
|
}
|
|
3008
3322
|
else {
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
ctx: result.ctx,
|
|
3015
|
-
});
|
|
3016
|
-
}
|
|
3017
|
-
else {
|
|
3018
|
-
res.send({
|
|
3019
|
-
code: successCode,
|
|
3020
|
-
data: result.data?.result || {},
|
|
3021
|
-
ctx: result.ctx,
|
|
3022
|
-
});
|
|
3023
|
-
}
|
|
3323
|
+
res.send({
|
|
3324
|
+
code: successCode,
|
|
3325
|
+
data: response.data?.result || {},
|
|
3326
|
+
ctx: response.ctx,
|
|
3327
|
+
});
|
|
3024
3328
|
}
|
|
3025
|
-
}
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
const { clientKey, codeMd5 } = req.
|
|
3033
|
-
|
|
3329
|
+
},
|
|
3330
|
+
};
|
|
3331
|
+
|
|
3332
|
+
const gameWasmCollectInfoRoute = {
|
|
3333
|
+
method: 'get',
|
|
3334
|
+
path: '/game/wasm-collect-info',
|
|
3335
|
+
handler: async (req, res) => {
|
|
3336
|
+
const { clientKey, codeMd5 } = req.query;
|
|
3337
|
+
console.log('wasm-collect-info', req.query);
|
|
3338
|
+
const response = await getCollecttingInfo({
|
|
3034
3339
|
client_key: clientKey,
|
|
3035
3340
|
wasm_md5: codeMd5,
|
|
3036
3341
|
});
|
|
@@ -3048,14 +3353,13 @@ async function start() {
|
|
|
3048
3353
|
ctx: response.ctx,
|
|
3049
3354
|
});
|
|
3050
3355
|
}
|
|
3051
|
-
}
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
*/
|
|
3356
|
+
},
|
|
3357
|
+
};
|
|
3358
|
+
|
|
3359
|
+
const gameWasmPrepareDownloadRoute = {
|
|
3360
|
+
method: 'post',
|
|
3361
|
+
path: '/game/wasm-prepare-download',
|
|
3362
|
+
handler: async (req, res) => {
|
|
3059
3363
|
const { clientKey, codeMd5, codePath } = req.body;
|
|
3060
3364
|
console.log('wasm-prepare-download-start', req.body);
|
|
3061
3365
|
const response = await downloadPrepared({
|
|
@@ -3077,14 +3381,16 @@ async function start() {
|
|
|
3077
3381
|
ctx: response.ctx,
|
|
3078
3382
|
});
|
|
3079
3383
|
}
|
|
3080
|
-
}
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3384
|
+
},
|
|
3385
|
+
};
|
|
3386
|
+
|
|
3387
|
+
const gameWasmPrepareResultRoute = {
|
|
3388
|
+
method: 'post',
|
|
3389
|
+
path: '/game/wasm-prepare-result',
|
|
3390
|
+
handler: async (req, res) => {
|
|
3391
|
+
console.log('wasm-prepare-result-request', req.body);
|
|
3085
3392
|
const { clientKey, codeMd5 } = req.body;
|
|
3086
|
-
|
|
3087
|
-
const response = await startSplit({
|
|
3393
|
+
const response = await getTaskStatus({
|
|
3088
3394
|
client_key: clientKey,
|
|
3089
3395
|
wasm_md5: codeMd5,
|
|
3090
3396
|
});
|
|
@@ -3098,34 +3404,57 @@ async function start() {
|
|
|
3098
3404
|
else {
|
|
3099
3405
|
res.send({
|
|
3100
3406
|
code: successCode,
|
|
3101
|
-
data: response.data,
|
|
3407
|
+
data: response.data?.result || {},
|
|
3102
3408
|
ctx: response.ctx,
|
|
3103
3409
|
});
|
|
3104
3410
|
}
|
|
3105
|
-
}
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3411
|
+
},
|
|
3412
|
+
};
|
|
3413
|
+
|
|
3414
|
+
const gameWasmPrepareRoute = {
|
|
3415
|
+
method: 'post',
|
|
3416
|
+
path: '/game/wasm-prepare',
|
|
3417
|
+
handler: async (req, res) => {
|
|
3418
|
+
const { codePath, desc, codeMd5, clientKey } = req.body;
|
|
3419
|
+
console.log('wasm-prepare-start', req.body);
|
|
3420
|
+
const result = await startPrepare({
|
|
3110
3421
|
client_key: clientKey,
|
|
3422
|
+
desc,
|
|
3111
3423
|
wasm_md5: codeMd5,
|
|
3424
|
+
wasm_file_path: codePath,
|
|
3112
3425
|
});
|
|
3113
|
-
|
|
3426
|
+
console.log('wasm-prepare-end', result);
|
|
3427
|
+
if (result.error) {
|
|
3114
3428
|
res.send({
|
|
3115
3429
|
code: errorCode,
|
|
3116
|
-
error:
|
|
3117
|
-
ctx:
|
|
3430
|
+
error: result.error,
|
|
3431
|
+
ctx: result.ctx,
|
|
3118
3432
|
});
|
|
3119
3433
|
}
|
|
3120
3434
|
else {
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3435
|
+
const { md5 } = result.data?.result || {};
|
|
3436
|
+
if (!md5) {
|
|
3437
|
+
res.send({
|
|
3438
|
+
code: errorCode,
|
|
3439
|
+
error: result.data,
|
|
3440
|
+
ctx: result.ctx,
|
|
3441
|
+
});
|
|
3442
|
+
}
|
|
3443
|
+
else {
|
|
3444
|
+
res.send({
|
|
3445
|
+
code: successCode,
|
|
3446
|
+
data: result.data?.result || {},
|
|
3447
|
+
ctx: result.ctx,
|
|
3448
|
+
});
|
|
3449
|
+
}
|
|
3126
3450
|
}
|
|
3127
|
-
}
|
|
3128
|
-
|
|
3451
|
+
},
|
|
3452
|
+
};
|
|
3453
|
+
|
|
3454
|
+
const gameWasmSetCollectRoute = {
|
|
3455
|
+
method: 'post',
|
|
3456
|
+
path: '/game/wasm-set-collect',
|
|
3457
|
+
handler: async (req, res) => {
|
|
3129
3458
|
const { clientKey, codeMd5 } = req.body;
|
|
3130
3459
|
console.log('wasm-set-collect', req.body);
|
|
3131
3460
|
const response = await setCollect({
|
|
@@ -3146,13 +3475,19 @@ async function start() {
|
|
|
3146
3475
|
ctx: response.ctx,
|
|
3147
3476
|
});
|
|
3148
3477
|
}
|
|
3149
|
-
}
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3478
|
+
},
|
|
3479
|
+
};
|
|
3480
|
+
|
|
3481
|
+
const gameWasmSplitDownloadResultRoute = {
|
|
3482
|
+
method: 'post',
|
|
3483
|
+
path: '/game/wasm-split-download-result',
|
|
3484
|
+
handler: async (req, res) => {
|
|
3485
|
+
const { clientKey, codeMd5, codePath } = req.body;
|
|
3486
|
+
console.log('game/wasm-split-download-result-start', req.body);
|
|
3487
|
+
const response = await getSplitResult({
|
|
3154
3488
|
client_key: clientKey,
|
|
3155
3489
|
wasm_md5: codeMd5,
|
|
3490
|
+
wasm_path: codePath,
|
|
3156
3491
|
});
|
|
3157
3492
|
if (response.error) {
|
|
3158
3493
|
res.send({
|
|
@@ -3162,20 +3497,47 @@ async function start() {
|
|
|
3162
3497
|
});
|
|
3163
3498
|
}
|
|
3164
3499
|
else {
|
|
3500
|
+
const splitResult = (response.data?.result || {});
|
|
3501
|
+
const requiredDownloadFields = [
|
|
3502
|
+
'main_wasm_download_url',
|
|
3503
|
+
'main_wasm_h5_download_url',
|
|
3504
|
+
// 'sub_wasm_download_url',
|
|
3505
|
+
// 'sub_js_download_url',
|
|
3506
|
+
// 'sub_js_data_download_url',
|
|
3507
|
+
// 'sub_js_range_download_url',
|
|
3508
|
+
];
|
|
3509
|
+
const missingFields = requiredDownloadFields.filter(field => {
|
|
3510
|
+
const value = splitResult[field];
|
|
3511
|
+
return typeof value !== 'string' || value.trim() === '';
|
|
3512
|
+
});
|
|
3513
|
+
if (missingFields.length > 0) {
|
|
3514
|
+
res.send({
|
|
3515
|
+
code: errorCode,
|
|
3516
|
+
error: {
|
|
3517
|
+
message: `Missing required wasm split fields: ${missingFields.join(', ')}`,
|
|
3518
|
+
},
|
|
3519
|
+
data: response.data || {},
|
|
3520
|
+
ctx: response.ctx,
|
|
3521
|
+
});
|
|
3522
|
+
return;
|
|
3523
|
+
}
|
|
3165
3524
|
res.send({
|
|
3166
3525
|
code: successCode,
|
|
3167
|
-
data: response.data
|
|
3526
|
+
data: response.data || {},
|
|
3527
|
+
msg: 'download success',
|
|
3168
3528
|
ctx: response.ctx,
|
|
3169
3529
|
});
|
|
3170
3530
|
}
|
|
3171
|
-
}
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3531
|
+
},
|
|
3532
|
+
};
|
|
3533
|
+
|
|
3534
|
+
const gameWasmSplitDownloadRoute = {
|
|
3535
|
+
method: 'post',
|
|
3536
|
+
path: '/game/wasm-split-download',
|
|
3537
|
+
handler: async (req, res) => {
|
|
3538
|
+
console.log('game/wasm-split-download-start', req.body);
|
|
3539
|
+
const response = await downloadSplited(req.body);
|
|
3540
|
+
console.log('game/wasm-split-download-end', response);
|
|
3179
3541
|
if (response.error) {
|
|
3180
3542
|
res.send({
|
|
3181
3543
|
code: errorCode,
|
|
@@ -3186,21 +3548,51 @@ async function start() {
|
|
|
3186
3548
|
else {
|
|
3187
3549
|
res.send({
|
|
3188
3550
|
code: successCode,
|
|
3189
|
-
data: response.data
|
|
3551
|
+
data: response.data || {},
|
|
3552
|
+
msg: 'download success',
|
|
3190
3553
|
ctx: response.ctx,
|
|
3191
3554
|
});
|
|
3192
3555
|
}
|
|
3193
|
-
}
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3556
|
+
},
|
|
3557
|
+
};
|
|
3558
|
+
|
|
3559
|
+
const gameWasmSplitOptionsRoute = {
|
|
3560
|
+
method: 'get',
|
|
3561
|
+
path: '/game/wasm-split-options',
|
|
3562
|
+
handler: (req, res) => {
|
|
3563
|
+
res.send({
|
|
3564
|
+
code: successCode,
|
|
3565
|
+
data: {
|
|
3566
|
+
options: [
|
|
3567
|
+
{
|
|
3568
|
+
md5: '123',
|
|
3569
|
+
desc: 'test',
|
|
3570
|
+
version: '1.0.0',
|
|
3571
|
+
time: '2023-01-01',
|
|
3572
|
+
funcCounts: 100,
|
|
3573
|
+
},
|
|
3574
|
+
{
|
|
3575
|
+
md5: '456',
|
|
3576
|
+
desc: 'test2',
|
|
3577
|
+
version: '1.0.1',
|
|
3578
|
+
time: '2023-01-02',
|
|
3579
|
+
funcCounts: 200,
|
|
3580
|
+
},
|
|
3581
|
+
],
|
|
3582
|
+
},
|
|
3203
3583
|
});
|
|
3584
|
+
},
|
|
3585
|
+
};
|
|
3586
|
+
|
|
3587
|
+
const gameWasmSplitResetRoute = {
|
|
3588
|
+
method: 'post',
|
|
3589
|
+
path: '/game/wasm-split-reset',
|
|
3590
|
+
handler: async (req, res) => {
|
|
3591
|
+
const { clientKey, codeMd5, codePath } = req.body;
|
|
3592
|
+
console.log('wasm-split-reset', req.body);
|
|
3593
|
+
const response = await resetWasmSplit({
|
|
3594
|
+
clientkey: clientKey,
|
|
3595
|
+
wasmMd5: codeMd5});
|
|
3204
3596
|
if (response.error) {
|
|
3205
3597
|
res.send({
|
|
3206
3598
|
code: errorCode,
|
|
@@ -3211,21 +3603,22 @@ async function start() {
|
|
|
3211
3603
|
else {
|
|
3212
3604
|
res.send({
|
|
3213
3605
|
code: successCode,
|
|
3214
|
-
data: response.data
|
|
3606
|
+
data: response.data || {},
|
|
3215
3607
|
ctx: response.ctx,
|
|
3216
3608
|
});
|
|
3217
3609
|
}
|
|
3218
|
-
}
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
const
|
|
3610
|
+
},
|
|
3611
|
+
};
|
|
3612
|
+
|
|
3613
|
+
const gameWasmSplitResultRoute = {
|
|
3614
|
+
method: 'post',
|
|
3615
|
+
path: '/game/wasm-split-result',
|
|
3616
|
+
handler: async (req, res) => {
|
|
3617
|
+
const { codeMd5, clientKey } = req.body;
|
|
3618
|
+
console.log('wasm-split-result', req.body);
|
|
3619
|
+
const response = await getTaskStatus({
|
|
3226
3620
|
client_key: clientKey,
|
|
3227
3621
|
wasm_md5: codeMd5,
|
|
3228
|
-
wasm_path: codePath,
|
|
3229
3622
|
});
|
|
3230
3623
|
if (response.error) {
|
|
3231
3624
|
res.send({
|
|
@@ -3237,16 +3630,37 @@ async function start() {
|
|
|
3237
3630
|
else {
|
|
3238
3631
|
res.send({
|
|
3239
3632
|
code: successCode,
|
|
3240
|
-
data: response.data || {},
|
|
3241
|
-
msg: 'download success',
|
|
3633
|
+
data: response.data?.result || {},
|
|
3242
3634
|
ctx: response.ctx,
|
|
3243
3635
|
});
|
|
3244
3636
|
}
|
|
3245
|
-
}
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3637
|
+
},
|
|
3638
|
+
};
|
|
3639
|
+
|
|
3640
|
+
const gameWasmSplitConfigRoute = {
|
|
3641
|
+
method: 'get',
|
|
3642
|
+
path: '/game/wasm-split-config',
|
|
3643
|
+
handler: (req, res) => {
|
|
3644
|
+
const config = getSplitConfig();
|
|
3645
|
+
if (!config) {
|
|
3646
|
+
res.send({ code: errorCode, data: 'Failed to parse split config' });
|
|
3647
|
+
}
|
|
3648
|
+
else {
|
|
3649
|
+
res.send({ code: successCode, data: config });
|
|
3650
|
+
}
|
|
3651
|
+
},
|
|
3652
|
+
};
|
|
3653
|
+
|
|
3654
|
+
const gameWasmSplitRoute = {
|
|
3655
|
+
method: 'post',
|
|
3656
|
+
path: '/game/wasm-split',
|
|
3657
|
+
handler: async (req, res) => {
|
|
3658
|
+
const { clientKey, codeMd5 } = req.body;
|
|
3659
|
+
console.log('wasm-split-start', req.body);
|
|
3660
|
+
const response = await startSplit({
|
|
3661
|
+
client_key: clientKey,
|
|
3662
|
+
wasm_md5: codeMd5,
|
|
3663
|
+
});
|
|
3250
3664
|
if (response.error) {
|
|
3251
3665
|
res.send({
|
|
3252
3666
|
code: errorCode,
|
|
@@ -3257,29 +3671,23 @@ async function start() {
|
|
|
3257
3671
|
else {
|
|
3258
3672
|
res.send({
|
|
3259
3673
|
code: successCode,
|
|
3260
|
-
data: response.data
|
|
3261
|
-
msg: 'download success',
|
|
3674
|
+
data: response.data,
|
|
3262
3675
|
ctx: response.ctx,
|
|
3263
3676
|
});
|
|
3264
3677
|
}
|
|
3265
|
-
}
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3678
|
+
},
|
|
3679
|
+
};
|
|
3680
|
+
|
|
3681
|
+
const gameWasmTaskinfoRoute = {
|
|
3682
|
+
method: 'get',
|
|
3683
|
+
path: '/game/wasm-taskinfo',
|
|
3684
|
+
handler: async (req, res) => {
|
|
3685
|
+
const { clientKey, codeMd5 } = req.query;
|
|
3686
|
+
console.log('wasm-taskinfo', req.query);
|
|
3687
|
+
const response = await getTaskInfo({
|
|
3688
|
+
client_key: clientKey,
|
|
3689
|
+
wasm_md5: codeMd5,
|
|
3275
3690
|
});
|
|
3276
|
-
});
|
|
3277
|
-
app.post('/game/wasm-split-reset', async (req, res) => {
|
|
3278
|
-
const { clientKey, codeMd5, codePath } = req.body;
|
|
3279
|
-
console.log('wasm-split-reset', req.body);
|
|
3280
|
-
const response = await resetWasmSplit({
|
|
3281
|
-
clientkey: clientKey,
|
|
3282
|
-
wasmMd5: codeMd5});
|
|
3283
3691
|
if (response.error) {
|
|
3284
3692
|
res.send({
|
|
3285
3693
|
code: errorCode,
|
|
@@ -3290,133 +3698,352 @@ async function start() {
|
|
|
3290
3698
|
else {
|
|
3291
3699
|
res.send({
|
|
3292
3700
|
code: successCode,
|
|
3293
|
-
data: response.data || {},
|
|
3701
|
+
data: response.data?.result || {},
|
|
3294
3702
|
ctx: response.ctx,
|
|
3295
3703
|
});
|
|
3296
3704
|
}
|
|
3705
|
+
},
|
|
3706
|
+
};
|
|
3707
|
+
|
|
3708
|
+
function getGameFallbackRoute(publicPath) {
|
|
3709
|
+
return {
|
|
3710
|
+
method: 'get',
|
|
3711
|
+
path: '*',
|
|
3712
|
+
handler: (req, res) => {
|
|
3713
|
+
res.sendFile(path.join(publicPath, 'index.html'));
|
|
3714
|
+
},
|
|
3715
|
+
};
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
const routes = [
|
|
3719
|
+
gameConfigRoute,
|
|
3720
|
+
gameConfigFillbackRoute,
|
|
3721
|
+
gameDetailRoute,
|
|
3722
|
+
gameCheckRoute,
|
|
3723
|
+
gameUploadRoute,
|
|
3724
|
+
gameWasmSplitConfigRoute,
|
|
3725
|
+
gameWasmSplitOptionsRoute,
|
|
3726
|
+
gameWasmPrepareRoute,
|
|
3727
|
+
gameWasmPrepareResultRoute,
|
|
3728
|
+
gameWasmPrepareDownloadRoute,
|
|
3729
|
+
gameWasmSplitRoute,
|
|
3730
|
+
gameWasmTaskinfoRoute,
|
|
3731
|
+
gameWasmSetCollectRoute,
|
|
3732
|
+
gameWasmCollectFuncidsRoute,
|
|
3733
|
+
gameWasmCollectInfoRoute,
|
|
3734
|
+
gameWasmSplitResultRoute,
|
|
3735
|
+
gameWasmSplitDownloadResultRoute,
|
|
3736
|
+
gameWasmSplitDownloadRoute,
|
|
3737
|
+
gameWasmCancelRoute,
|
|
3738
|
+
gameWasmSplitResetRoute,
|
|
3739
|
+
];
|
|
3740
|
+
function registerRoutes(app, options) {
|
|
3741
|
+
const allRoutes = [...routes, getGameFallbackRoute(options.publicPath)];
|
|
3742
|
+
for (const route of allRoutes) {
|
|
3743
|
+
if (route.method === 'get') {
|
|
3744
|
+
app.get(route.path, route.handler);
|
|
3745
|
+
}
|
|
3746
|
+
else if (route.method === 'post') {
|
|
3747
|
+
app.post(route.path, route.handler);
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
const outputDir = getOutputDir();
|
|
3753
|
+
const publicPath = path.join(__dirname, 'public');
|
|
3754
|
+
async function start() {
|
|
3755
|
+
const startTime = Date.now();
|
|
3756
|
+
const app = express();
|
|
3757
|
+
setupMiddlewares(app, { publicPath, outputDir });
|
|
3758
|
+
registerRoutes(app, { publicPath });
|
|
3759
|
+
const { url, version } = await listen(app, { maxRetries: 20 });
|
|
3760
|
+
console.log(chalk.green.bold(`TTMG`), chalk.green(`v${version}`), chalk.gray(t('native.server.readyIn')), chalk.bold(`${Date.now() - startTime}ms`));
|
|
3761
|
+
showTips({ server: url });
|
|
3762
|
+
openUrl(url);
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
async function dev() {
|
|
3766
|
+
await check();
|
|
3767
|
+
await init();
|
|
3768
|
+
await start();
|
|
3769
|
+
await compile();
|
|
3770
|
+
listen$1();
|
|
3771
|
+
watch();
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
function printMessage(type, message) {
|
|
3775
|
+
const prefix = type === 'Error'
|
|
3776
|
+
? chalk.red(t('login.error.prefix'))
|
|
3777
|
+
: chalk.yellow(t('login.warning.prefix'));
|
|
3778
|
+
const log = type === 'Error' ? console.error : console.warn;
|
|
3779
|
+
log(prefix + message);
|
|
3780
|
+
}
|
|
3781
|
+
let spinner;
|
|
3782
|
+
const LOGIN_TT4D = 'https://developers.tiktok.com/passport/web/email/login';
|
|
3783
|
+
const params = {
|
|
3784
|
+
aid: '2471',
|
|
3785
|
+
account_sdk_source: 'web',
|
|
3786
|
+
sdk_version: '2.1.6-tiktok',
|
|
3787
|
+
};
|
|
3788
|
+
const prompt = inquirer.createPromptModule();
|
|
3789
|
+
async function login(options) {
|
|
3790
|
+
const verbose = options?.verbose === true;
|
|
3791
|
+
const log = (msg, data) => {
|
|
3792
|
+
if (!verbose)
|
|
3793
|
+
return;
|
|
3794
|
+
console.log(chalk.gray('[ttmg login]'), msg, data !== undefined ? data : '');
|
|
3795
|
+
};
|
|
3796
|
+
if (verbose)
|
|
3797
|
+
log(t('login.verbose.enabled'));
|
|
3798
|
+
console.log(chalk.yellowBright(t('login.note')));
|
|
3799
|
+
const { email, password } = await prompt([
|
|
3800
|
+
{
|
|
3801
|
+
type: 'input',
|
|
3802
|
+
name: 'email',
|
|
3803
|
+
message: t('login.email.prompt'),
|
|
3804
|
+
validate: input => {
|
|
3805
|
+
if (!input) {
|
|
3806
|
+
return t('login.email.required');
|
|
3807
|
+
}
|
|
3808
|
+
else {
|
|
3809
|
+
if (!/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/.test(input)) {
|
|
3810
|
+
return t('login.email.invalid');
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
return true;
|
|
3814
|
+
},
|
|
3815
|
+
},
|
|
3816
|
+
{
|
|
3817
|
+
type: 'password',
|
|
3818
|
+
name: 'password',
|
|
3819
|
+
message: t('login.password.prompt'),
|
|
3820
|
+
mask: '*',
|
|
3821
|
+
validate: input => {
|
|
3822
|
+
if (!input) {
|
|
3823
|
+
return t('login.password.required');
|
|
3824
|
+
}
|
|
3825
|
+
return true;
|
|
3826
|
+
},
|
|
3827
|
+
},
|
|
3828
|
+
]);
|
|
3829
|
+
const url = LOGIN_TT4D + '?' + new URLSearchParams(params);
|
|
3830
|
+
log('Request URL', url);
|
|
3831
|
+
log('Request params', { ...params, email: email.replace(/(.{2}).*(@.*)/, '$1***$2') });
|
|
3832
|
+
const headers = {
|
|
3833
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
3834
|
+
Accept: '*/*',
|
|
3835
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
3836
|
+
Origin: 'https://developers.tiktok.com',
|
|
3837
|
+
Referer: 'https://developers.tiktok.com/passport/web/email/login',
|
|
3838
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36',
|
|
3839
|
+
};
|
|
3840
|
+
const ora = await import('ora');
|
|
3841
|
+
spinner = ora.default({
|
|
3842
|
+
text: chalk.bold.cyan(t('login.spinner.loggingIn')),
|
|
3843
|
+
spinner: 'dots',
|
|
3297
3844
|
});
|
|
3298
|
-
|
|
3299
|
-
|
|
3845
|
+
spinner.start();
|
|
3846
|
+
const data = qs.stringify({
|
|
3847
|
+
email,
|
|
3848
|
+
password,
|
|
3849
|
+
mix_mode: '1',
|
|
3850
|
+
fixed_mix_mode: '1',
|
|
3300
3851
|
});
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
const
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3852
|
+
try {
|
|
3853
|
+
log('Sending POST request...');
|
|
3854
|
+
const response = await axios.post(url, data, {
|
|
3855
|
+
headers,
|
|
3856
|
+
maxRedirects: 20,
|
|
3857
|
+
timeout: 30000,
|
|
3858
|
+
});
|
|
3859
|
+
log('Response status', response.status);
|
|
3860
|
+
log('Response data', response?.data);
|
|
3861
|
+
if (!response?.data?.data?.user_id) {
|
|
3862
|
+
const errCode = response.data?.data?.error_code;
|
|
3863
|
+
const errMsg = response.data?.data?.description;
|
|
3864
|
+
const statusText = response?.statusText ?? '';
|
|
3865
|
+
spinner.fail(chalk.red(t('login.failed')));
|
|
3866
|
+
if (verbose) {
|
|
3867
|
+
console.log(chalk.gray('Response status:'), response?.status);
|
|
3868
|
+
console.log(chalk.gray('Response statusText:'), statusText || '(empty)');
|
|
3869
|
+
console.log(chalk.gray('Response data:'), response?.data ?? '(empty)');
|
|
3870
|
+
console.log('');
|
|
3871
|
+
}
|
|
3872
|
+
if (errCode || errMsg) {
|
|
3873
|
+
log('Login failed (api)', { errCode, errMsg, fullData: response?.data });
|
|
3874
|
+
printMessage('Error', errCode
|
|
3875
|
+
? t('login.error.withCode', {
|
|
3876
|
+
message: String(errMsg ?? ''),
|
|
3877
|
+
code: String(errCode),
|
|
3878
|
+
})
|
|
3879
|
+
: errMsg
|
|
3880
|
+
? t('login.error.withMessage', { message: String(errMsg) })
|
|
3881
|
+
: t('login.error.noUserId'));
|
|
3882
|
+
}
|
|
3883
|
+
else {
|
|
3884
|
+
log('Login failed (no user_id)', { responseBody: response?.data });
|
|
3885
|
+
if (verbose)
|
|
3886
|
+
log('Full response (for debugging)', response);
|
|
3887
|
+
const looksLikeProxyResponse = statusText === 'Connection established' ||
|
|
3888
|
+
(response?.status === 200 &&
|
|
3889
|
+
(response?.data == null ||
|
|
3890
|
+
typeof response.data !== 'object' ||
|
|
3891
|
+
!('data' in response.data)));
|
|
3892
|
+
if (looksLikeProxyResponse) {
|
|
3893
|
+
printMessage('Warning', t('login.warning.proxyIssue'));
|
|
3318
3894
|
}
|
|
3319
3895
|
else {
|
|
3320
|
-
|
|
3321
|
-
console.log(chalk.red.bold(err.message));
|
|
3322
|
-
process.exit(1);
|
|
3896
|
+
printMessage('Error', t('login.error.noUserId'));
|
|
3323
3897
|
}
|
|
3324
|
-
}
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
// 清理掉另一个事件的监听器
|
|
3328
|
-
server.removeListener('error', onError);
|
|
3329
|
-
resolve(true); // 明确表示成功
|
|
3330
|
-
};
|
|
3331
|
-
// 使用 .once() 来确保监听器只执行一次然后自动移除
|
|
3332
|
-
server.once('error', onError);
|
|
3333
|
-
server.once('listening', onListening);
|
|
3334
|
-
// 执行监听动作
|
|
3335
|
-
server.listen(port);
|
|
3336
|
-
});
|
|
3337
|
-
}
|
|
3338
|
-
// 步骤 3: 使用循环来线性、串行地尝试启动服务
|
|
3339
|
-
let isListening = false;
|
|
3340
|
-
const maxRetries = 20; // 设置一个最大重试次数,以防万一
|
|
3341
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
3342
|
-
const currentPort = store.getState().nodeServerPort;
|
|
3343
|
-
isListening = await tryListen(currentPort);
|
|
3344
|
-
if (isListening) {
|
|
3345
|
-
break; // 成功,跳出循环
|
|
3898
|
+
}
|
|
3899
|
+
spinner.stop();
|
|
3900
|
+
process.exit(1);
|
|
3346
3901
|
}
|
|
3347
3902
|
else {
|
|
3348
|
-
|
|
3349
|
-
|
|
3903
|
+
const loginData = {
|
|
3904
|
+
email,
|
|
3905
|
+
user_id: response?.data?.data?.user_id,
|
|
3906
|
+
cookie: response?.headers['set-cookie'].join('; '),
|
|
3907
|
+
};
|
|
3908
|
+
log('Login success', { user_id: loginData.user_id, email: loginData.email });
|
|
3909
|
+
setTTMGRC(loginData);
|
|
3910
|
+
spinner.succeed(chalk.bold.green(t('login.success')));
|
|
3911
|
+
process.exit(0);
|
|
3350
3912
|
}
|
|
3351
3913
|
}
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3914
|
+
catch (error) {
|
|
3915
|
+
log('Request error', error instanceof Error ? { message: error.message, name: error.name, stack: error.stack } : error);
|
|
3916
|
+
spinner.fail(chalk.red.bold(t('login.error.connectService')));
|
|
3917
|
+
printMessage('Error', t('login.error.networkBlocked'));
|
|
3355
3918
|
process.exit(1);
|
|
3356
3919
|
}
|
|
3357
|
-
// --- 服务启动成功后的逻辑 ---
|
|
3358
|
-
// @ts-ignore
|
|
3359
|
-
const finalPort = server.address().port; // 从成功的 server 实例安全地获取最终端口
|
|
3360
|
-
console.log(chalk.green.bold(`TTMG`), chalk.green(`v${devToolVersion}`), chalk.gray(`ready in`), chalk.bold(`${Date.now() - startTime}ms`));
|
|
3361
|
-
const baseUrl = `http://localhost:${finalPort}?v=${devToolVersion}`;
|
|
3362
|
-
showTips({ server: baseUrl });
|
|
3363
|
-
openUrl(baseUrl);
|
|
3364
3920
|
}
|
|
3365
3921
|
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3922
|
+
const supportedLangs = ['en-US', 'zh-CN'];
|
|
3923
|
+
function isSupportedLang(lang) {
|
|
3924
|
+
return supportedLangs.includes(lang);
|
|
3925
|
+
}
|
|
3926
|
+
async function setup(options) {
|
|
3927
|
+
const inputLang = options?.lang;
|
|
3928
|
+
if (inputLang !== undefined && !isSupportedLang(inputLang)) {
|
|
3929
|
+
console.error(chalk.red(t('setup.error.unsupportedLang', { lang: inputLang })));
|
|
3930
|
+
console.error(chalk.yellow(t('setup.error.availableLangs')));
|
|
3931
|
+
console.error(chalk.cyan(t('setup.error.chooseHint')));
|
|
3932
|
+
process.exit(1);
|
|
3933
|
+
}
|
|
3934
|
+
const lang = inputLang ??
|
|
3935
|
+
(await inquirer.createPromptModule()([
|
|
3936
|
+
{
|
|
3937
|
+
type: 'list',
|
|
3938
|
+
name: 'lang',
|
|
3939
|
+
message: t('setup.prompt.selectLanguage'),
|
|
3940
|
+
choices: [
|
|
3941
|
+
{ name: t('setup.choice.en'), value: 'en-US' },
|
|
3942
|
+
{ name: t('setup.choice.zh'), value: 'zh-CN' },
|
|
3943
|
+
],
|
|
3944
|
+
default: 'en-US',
|
|
3945
|
+
},
|
|
3946
|
+
])).lang;
|
|
3947
|
+
setTTMGRC({ lang });
|
|
3948
|
+
console.log(chalk.green.bold(t('setup.success', { lang }, lang)));
|
|
3949
|
+
process.exit(0);
|
|
3373
3950
|
}
|
|
3374
3951
|
|
|
3375
|
-
|
|
3952
|
+
async function checkUpdate() {
|
|
3953
|
+
const worker = new worker_threads.Worker(path.resolve(__dirname, './scripts/worker.js'));
|
|
3954
|
+
worker.on('message', msg => {
|
|
3955
|
+
// console.log(msg);
|
|
3956
|
+
});
|
|
3957
|
+
worker.on('error', err => {
|
|
3958
|
+
console.error(err);
|
|
3959
|
+
});
|
|
3960
|
+
worker.postMessage({
|
|
3961
|
+
type: 'checkUpdate',
|
|
3962
|
+
lang: getCurrentLanguage(),
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3966
|
+
async function reset() {
|
|
3967
|
+
console.log(chalk.yellow.bold(t('reset.warning.title')));
|
|
3968
|
+
console.log(chalk.yellow(`1. ${t('reset.warning.lang')}`));
|
|
3969
|
+
console.log(chalk.yellow(`2. ${t('reset.warning.login')}`));
|
|
3970
|
+
console.log(chalk.yellow(`3. ${t('reset.warning.clientKey')}`));
|
|
3971
|
+
console.log('');
|
|
3972
|
+
const prompt = inquirer.createPromptModule();
|
|
3973
|
+
const { confirmed } = await prompt([
|
|
3974
|
+
{
|
|
3975
|
+
type: 'list',
|
|
3976
|
+
name: 'confirmed',
|
|
3977
|
+
message: t('reset.confirm.prompt'),
|
|
3978
|
+
choices: [
|
|
3979
|
+
{ name: t('reset.confirm.yes'), value: true },
|
|
3980
|
+
{ name: t('reset.confirm.no'), value: false },
|
|
3981
|
+
],
|
|
3982
|
+
default: false,
|
|
3983
|
+
},
|
|
3984
|
+
]);
|
|
3985
|
+
if (!confirmed) {
|
|
3986
|
+
console.log(chalk.gray(t('reset.cancelled')));
|
|
3987
|
+
process.exit(0);
|
|
3988
|
+
}
|
|
3989
|
+
// Reset to an empty local state.
|
|
3990
|
+
resetTTMGRC({});
|
|
3991
|
+
console.log(chalk.green.bold(t('reset.success')));
|
|
3992
|
+
process.exit(0);
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3995
|
+
var version = "0.3.2-beta.6";
|
|
3376
3996
|
var pkg = {
|
|
3377
3997
|
version: version};
|
|
3378
3998
|
|
|
3379
3999
|
const program = new commander.Command();
|
|
4000
|
+
maybeShowPostInstallNotice(pkg.version);
|
|
3380
4001
|
program
|
|
3381
4002
|
.name('ttmg')
|
|
3382
|
-
.description('
|
|
3383
|
-
.version(pkg.version, '-v, --version', '
|
|
3384
|
-
.option('dev', '
|
|
3385
|
-
.option('dev --h5', '
|
|
4003
|
+
.description(t('cli.description'))
|
|
4004
|
+
.version(pkg.version, '-v, --version', t('cli.version.desc'))
|
|
4005
|
+
.option('dev', t('cli.option.dev.client'))
|
|
4006
|
+
.option('dev --h5', t('cli.option.dev.h5'));
|
|
3386
4007
|
program
|
|
3387
4008
|
.command('login')
|
|
3388
|
-
.description('
|
|
3389
|
-
.option('--verbose', '
|
|
4009
|
+
.description(t('cli.command.login.desc'))
|
|
4010
|
+
.option('--verbose', t('cli.command.login.verbose'))
|
|
3390
4011
|
.action(async (cmd) => {
|
|
3391
4012
|
await login({ verbose: cmd.verbose });
|
|
3392
4013
|
});
|
|
3393
4014
|
program
|
|
3394
4015
|
.command('setup')
|
|
3395
|
-
.description('
|
|
3396
|
-
.option('--lang <lang>', '
|
|
4016
|
+
.description(t('cli.command.setup.desc'))
|
|
4017
|
+
.option('--lang <lang>', t('cli.command.setup.lang'))
|
|
3397
4018
|
.action(async (cmd) => {
|
|
3398
4019
|
await setup({ lang: cmd.lang });
|
|
3399
4020
|
});
|
|
3400
4021
|
program
|
|
3401
|
-
.
|
|
4022
|
+
.command('reset')
|
|
4023
|
+
.description(t('cli.command.reset.desc'))
|
|
4024
|
+
.action(async () => {
|
|
4025
|
+
await reset();
|
|
4026
|
+
});
|
|
4027
|
+
program
|
|
4028
|
+
.option('--h5', t('cli.option.h5'))
|
|
3402
4029
|
.command('init')
|
|
3403
|
-
.description('
|
|
4030
|
+
.description(t('cli.command.init.desc'))
|
|
3404
4031
|
.action(() => {
|
|
3405
4032
|
const options = program.opts(); // 获取 options
|
|
3406
4033
|
if (options.h5) {
|
|
3407
4034
|
init$1();
|
|
3408
4035
|
}
|
|
3409
4036
|
else {
|
|
3410
|
-
console.log('
|
|
4037
|
+
console.log(t('cli.native.init.placeholder'));
|
|
3411
4038
|
}
|
|
3412
4039
|
});
|
|
3413
4040
|
/**
|
|
3414
4041
|
* ttmg dev 命令
|
|
3415
4042
|
*/
|
|
3416
4043
|
program
|
|
3417
|
-
.option('--h5', '
|
|
4044
|
+
.option('--h5', t('cli.option.h5'))
|
|
3418
4045
|
.command('dev')
|
|
3419
|
-
.description('
|
|
4046
|
+
.description(t('cli.command.dev.desc'))
|
|
3420
4047
|
.action(async () => {
|
|
3421
4048
|
const options = program.opts();
|
|
3422
4049
|
if (options.h5) {
|
|
@@ -3431,16 +4058,16 @@ program
|
|
|
3431
4058
|
* ttmg build 命令
|
|
3432
4059
|
*/
|
|
3433
4060
|
program
|
|
3434
|
-
.option('--h5', '
|
|
4061
|
+
.option('--h5', t('cli.option.h5'))
|
|
3435
4062
|
.command('build')
|
|
3436
|
-
.description('
|
|
4063
|
+
.description(t('cli.command.build.desc'))
|
|
3437
4064
|
.action(() => {
|
|
3438
4065
|
const options = program.opts(); // 获取 options
|
|
3439
4066
|
if (options.h5) {
|
|
3440
4067
|
build();
|
|
3441
4068
|
}
|
|
3442
4069
|
else {
|
|
3443
|
-
console.log('
|
|
4070
|
+
console.log(t('cli.native.build.placeholder'));
|
|
3444
4071
|
}
|
|
3445
4072
|
});
|
|
3446
4073
|
program.parse(process.argv);
|