@ttmg/cli 0.1.5 → 0.1.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/CHANGELOG.md +7 -1
- package/dist/index.js +564 -272
- package/dist/index.js.map +1 -1
- package/dist/public/assets/index-3dSJcSdI.js +67 -0
- package/dist/public/assets/index-3dSJcSdI.js.br +0 -0
- package/dist/public/assets/index-B9XS0oB-.css +1 -0
- package/dist/public/assets/index-B9XS0oB-.css.br +0 -0
- package/dist/public/assets/index-BBms5OYm.css +1 -0
- package/dist/public/assets/index-BBms5OYm.css.br +0 -0
- package/dist/public/assets/index-BLeuZX1W.css +1 -0
- package/dist/public/assets/index-BLeuZX1W.css.br +0 -0
- package/dist/public/assets/index-BQeetFTy.css +1 -0
- package/dist/public/assets/index-BQeetFTy.css.br +0 -0
- package/dist/public/assets/index-B_Qh1IFA.js +67 -0
- package/dist/public/assets/index-B_Qh1IFA.js.br +0 -0
- package/dist/public/assets/index-BcvpKJpv.js +12 -0
- package/dist/public/assets/index-BcvpKJpv.js.br +0 -0
- package/dist/public/assets/index-BjfplLoJ.js +67 -0
- package/dist/public/assets/index-BjfplLoJ.js.br +0 -0
- package/dist/public/assets/index-Bw6Q4Jbr.js +67 -0
- package/dist/public/assets/index-Bw6Q4Jbr.js.br +0 -0
- package/dist/public/assets/index-BzM7ZRWT.js +67 -0
- package/dist/public/assets/index-CEfsFr9q.js +67 -0
- package/dist/public/assets/index-CEfsFr9q.js.br +0 -0
- package/dist/public/assets/index-CNGNW4V0.css +1 -0
- package/dist/public/assets/index-CNGNW4V0.css.br +0 -0
- package/dist/public/assets/index-CPKWZ0lk.js +67 -0
- package/dist/public/assets/index-CPKWZ0lk.js.br +0 -0
- package/dist/public/assets/index-CYjeGeW3.js +67 -0
- package/dist/public/assets/index-CYjeGeW3.js.br +0 -0
- package/dist/public/assets/index-CieLbLV3.js +67 -0
- package/dist/public/assets/index-CieLbLV3.js.br +0 -0
- package/dist/public/assets/index-CkfGcO9X.js +67 -0
- package/dist/public/assets/index-CkfGcO9X.js.br +0 -0
- package/dist/public/assets/index-CvRRmiYm.js +67 -0
- package/dist/public/assets/index-CvRRmiYm.js.br +0 -0
- package/dist/public/assets/index-D92wLRBC.css +1 -0
- package/dist/public/assets/index-D92wLRBC.css.br +0 -0
- package/dist/public/assets/index-DF19Ik_I.js +67 -0
- package/dist/public/assets/index-DF19Ik_I.js.br +0 -0
- package/dist/public/assets/index-DL6K0uau.css +1 -0
- package/dist/public/assets/index-DL6K0uau.css.br +0 -0
- package/dist/public/assets/index-DOZ9HWK0.js +67 -0
- package/dist/public/assets/index-DOZ9HWK0.js.br +0 -0
- package/dist/public/assets/index-DPSI2SAJ.js +67 -0
- package/dist/public/assets/index-DPSI2SAJ.js.br +0 -0
- package/dist/public/assets/index-DTOwOuUY.js +67 -0
- package/dist/public/assets/index-DTOwOuUY.js.br +0 -0
- package/dist/public/assets/index-DewGL4pl.js +12 -0
- package/dist/public/assets/index-DiaqC69h.js +67 -0
- package/dist/public/assets/index-DiaqC69h.js.br +0 -0
- package/dist/public/assets/index-Dsnf7n5I.css +1 -0
- package/dist/public/assets/index-DxEOIplv.js +67 -0
- package/dist/public/assets/index-DxEOIplv.js.br +0 -0
- package/dist/public/assets/index-VKaSrbK1.js +67 -0
- package/dist/public/assets/index-Wp-ZSjKf.css +1 -0
- package/dist/public/assets/index-a4bDRD38.js +67 -0
- package/dist/public/assets/index-a4bDRD38.js.br +0 -0
- package/dist/public/assets/index-nhTk4P_h.js +67 -0
- package/dist/public/assets/index-wkPXet9W.js +67 -0
- package/dist/public/assets/index-wkPXet9W.js.br +0 -0
- package/dist/public/assets/react-dom-B7KT1F-k.js +32 -0
- package/dist/public/assets/react-l0sNRNKZ.js +1 -0
- package/dist/public/assets/semi-ui-3SIY-ze1.js +25 -0
- package/dist/public/assets/semi-ui-BgfPE7Mt.css +1 -0
- package/dist/public/assets/vendor-BgfPE7Mt.css +1 -0
- package/dist/public/assets/vendor-BgfPE7Mt.css.br +0 -0
- package/dist/public/assets/vendor-CP79f9j0.js +56 -0
- package/dist/public/assets/vendor-CP79f9j0.js.br +0 -0
- package/dist/public/index.html +2 -2
- package/dist/template/open_context.html.hbs +0 -1
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -15,14 +15,17 @@ var os = require('os');
|
|
|
15
15
|
var child_process = require('child_process');
|
|
16
16
|
var https = require('https');
|
|
17
17
|
var semver = require('semver');
|
|
18
|
+
var axios = require('axios');
|
|
18
19
|
var handlebars = require('handlebars');
|
|
19
20
|
var esbuild = require('esbuild');
|
|
20
21
|
var archiver = require('archiver');
|
|
22
|
+
var expressStaticGzip = require('express-static-gzip');
|
|
23
|
+
var chokidar = require('chokidar');
|
|
21
24
|
var WebSocket = require('ws');
|
|
25
|
+
var ttmgPack = require('ttmg-pack');
|
|
22
26
|
var glob = require('glob');
|
|
23
27
|
var got = require('got');
|
|
24
28
|
var FormData = require('form-data');
|
|
25
|
-
var ttmgPack = require('ttmg-pack');
|
|
26
29
|
|
|
27
30
|
function _interopNamespaceDefault(e) {
|
|
28
31
|
var n = Object.create(null);
|
|
@@ -41,6 +44,8 @@ function _interopNamespaceDefault(e) {
|
|
|
41
44
|
return Object.freeze(n);
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
48
|
+
var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
|
|
44
49
|
var glob__namespace = /*#__PURE__*/_interopNamespaceDefault(glob);
|
|
45
50
|
|
|
46
51
|
const CONFIG_FILE_NAME = 'minigame.config.json';
|
|
@@ -192,6 +197,7 @@ function init() {
|
|
|
192
197
|
|
|
193
198
|
async function openUrl(url) {
|
|
194
199
|
try {
|
|
200
|
+
const userDataDir = path__namespace.join(os__namespace.homedir(), '.my-chrome-dev-profile');
|
|
195
201
|
await chromeLauncher.launch({
|
|
196
202
|
startingUrl: url,
|
|
197
203
|
chromeFlags: [
|
|
@@ -200,11 +206,11 @@ async function openUrl(url) {
|
|
|
200
206
|
'--allow-insecure-localhost',
|
|
201
207
|
'--allow-running-insecure-content',
|
|
202
208
|
'--remote-allow-origins=*',
|
|
203
|
-
|
|
204
|
-
'--disable-popup-blocking'
|
|
209
|
+
`--user-data-dir=${userDataDir}`,
|
|
205
210
|
],
|
|
206
211
|
});
|
|
207
212
|
await new Promise(() => { });
|
|
213
|
+
return true;
|
|
208
214
|
}
|
|
209
215
|
catch (e) {
|
|
210
216
|
// const open = await import('open');
|
|
@@ -291,48 +297,143 @@ To update, run: ${chalk.magenta(`npm i -g ${pkgName}`)}
|
|
|
291
297
|
}
|
|
292
298
|
}
|
|
293
299
|
|
|
300
|
+
let config$1 = null;
|
|
301
|
+
const CONFIG_PATH = path.join(os.homedir(), '.ttmgrc');
|
|
302
|
+
const getTTmgrcConfig = () => {
|
|
303
|
+
if (config$1) {
|
|
304
|
+
return config$1;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
// only check one time
|
|
308
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
309
|
+
fs.writeFileSync(CONFIG_PATH, '{}');
|
|
310
|
+
return {};
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
// safe parse
|
|
314
|
+
let res = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
315
|
+
config$1 = res;
|
|
316
|
+
return res;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
const setTTmgrcConfig = (config) => {
|
|
321
|
+
// updata cache config
|
|
322
|
+
config = config;
|
|
323
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config));
|
|
324
|
+
};
|
|
325
|
+
|
|
294
326
|
/*
|
|
295
327
|
*
|
|
296
328
|
* 基于开发者输入的邮箱账号和密码进行登录,登录成功后存储账号和返回的 cookie 至本地,类似 npm
|
|
297
329
|
*/
|
|
330
|
+
const LOGIN_TT4D = 'https://developers.tiktok.com/passport/web/email/login';
|
|
331
|
+
const params = {
|
|
332
|
+
aid: '2471',
|
|
333
|
+
account_sdk_source: 'web',
|
|
334
|
+
sdk_version: '2.1.1',
|
|
335
|
+
};
|
|
298
336
|
const prompt = inquirer.createPromptModule();
|
|
299
|
-
const CONFIG_PATH = path.join(os.homedir(), '.ttmgrc');
|
|
300
337
|
async function login() {
|
|
301
|
-
//
|
|
338
|
+
// 增加对邮箱密码的校验
|
|
302
339
|
const { email, password } = await prompt([
|
|
303
|
-
{
|
|
304
|
-
|
|
340
|
+
{
|
|
341
|
+
type: 'input',
|
|
342
|
+
name: 'email',
|
|
343
|
+
message: '请输入邮箱账号:',
|
|
344
|
+
validate: input => {
|
|
345
|
+
if (!input) {
|
|
346
|
+
return 'email is required, please input email';
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
/**
|
|
350
|
+
* 邮箱格式校验
|
|
351
|
+
*/
|
|
352
|
+
if (!/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/.test(input)) {
|
|
353
|
+
return 'email format is invalid';
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return true;
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
type: 'password',
|
|
361
|
+
name: 'password',
|
|
362
|
+
message: '请输入密码:',
|
|
363
|
+
mask: '*',
|
|
364
|
+
validate: input => {
|
|
365
|
+
if (!input) {
|
|
366
|
+
return 'password is required, please input password';
|
|
367
|
+
}
|
|
368
|
+
return true;
|
|
369
|
+
},
|
|
370
|
+
},
|
|
305
371
|
]);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
// if (!setCookie) {
|
|
321
|
-
// console.log('登录失败,未返回 cookie');
|
|
322
|
-
// return;
|
|
323
|
-
// }
|
|
324
|
-
// 4. 存储账号和 cookie 到本地 ~/.ttmgrc
|
|
372
|
+
// const email = 'zhanghongyang.mocha@bytedance.com';
|
|
373
|
+
// const password = 'BOKEboke1314?';
|
|
374
|
+
const url = LOGIN_TT4D + '?' + new URLSearchParams(params);
|
|
375
|
+
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
|
376
|
+
console.log(chalk.yellow('⏳ Please wait, logging in...'));
|
|
377
|
+
const response = await axios.post(url, {
|
|
378
|
+
email,
|
|
379
|
+
password,
|
|
380
|
+
}, { headers });
|
|
381
|
+
if (response?.data?.data?.error_code) {
|
|
382
|
+
console.error(chalk.red(`❌ login failed: ${response.data?.data?.description || response.data?.data?.error_code}`));
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
325
386
|
const data = {
|
|
326
387
|
email,
|
|
327
|
-
|
|
388
|
+
user_id: response?.data?.data?.user_id,
|
|
389
|
+
cookie: response?.headers['set-cookie'].join('; '),
|
|
328
390
|
};
|
|
329
|
-
|
|
330
|
-
console.log(chalk.green
|
|
391
|
+
setTTmgrcConfig(data);
|
|
392
|
+
console.log(chalk.bold.green('✅ login successfully!'));
|
|
331
393
|
process.exit(0);
|
|
332
394
|
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* 获取配置文件中的 cookie
|
|
399
|
+
*/
|
|
400
|
+
const config = getTTmgrcConfig();
|
|
401
|
+
const cookie = config.cookie;
|
|
402
|
+
axios.defaults.headers.common['Cookie'] = cookie;
|
|
403
|
+
// ppe_dev_tool
|
|
404
|
+
async function request({ url, method, data, headers, params, }) {
|
|
405
|
+
try {
|
|
406
|
+
const res = await axios({
|
|
407
|
+
url,
|
|
408
|
+
method,
|
|
409
|
+
data,
|
|
410
|
+
params,
|
|
411
|
+
headers,
|
|
412
|
+
});
|
|
413
|
+
return {
|
|
414
|
+
data: res.data,
|
|
415
|
+
error: null,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
333
418
|
catch (err) {
|
|
334
|
-
console.error(
|
|
335
|
-
|
|
419
|
+
console.error('request failed', err.response?.data);
|
|
420
|
+
return {
|
|
421
|
+
data: null,
|
|
422
|
+
error: err.response?.data,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function getCurrentUser() {
|
|
428
|
+
try {
|
|
429
|
+
const config = getTTmgrcConfig();
|
|
430
|
+
return {
|
|
431
|
+
email: config?.email || '',
|
|
432
|
+
user_id: config?.user_id || '',
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
catch (err) {
|
|
436
|
+
return {};
|
|
336
437
|
}
|
|
337
438
|
}
|
|
338
439
|
|
|
@@ -655,15 +756,53 @@ function getOutputDir() {
|
|
|
655
756
|
|
|
656
757
|
const DEV_PORT = 9528;
|
|
657
758
|
const DEV_WS_PORT = 9529;
|
|
658
|
-
|
|
759
|
+
path.join(os.homedir(), '__TTMG__');
|
|
760
|
+
|
|
761
|
+
function openDevTool() {
|
|
762
|
+
try {
|
|
763
|
+
const qrCodeUrl = `http://localhost:${DEV_PORT}`;
|
|
764
|
+
console.log('');
|
|
765
|
+
console.log(chalk.green.bold('💡 Tips for Local Debugging'));
|
|
766
|
+
console.log(chalk.gray('─────────────────────────────────────────────'));
|
|
767
|
+
console.log(chalk.yellow.bold('1.') +
|
|
768
|
+
' The QR code page will be opened automatically in Chrome.');
|
|
769
|
+
console.log(' If it fails, please open the following link manually:');
|
|
770
|
+
console.log(' ' + chalk.cyan.underline('http://localhost:9528'));
|
|
771
|
+
console.log('');
|
|
772
|
+
console.log(chalk.yellow.bold('2.') +
|
|
773
|
+
' The debugging service will automatically compile your game assets.');
|
|
774
|
+
console.log(' Any changes in your game directory will trigger recompilation.');
|
|
775
|
+
console.log(' You can debug the updated content when upload is completed.');
|
|
776
|
+
console.log('');
|
|
777
|
+
console.log(chalk.yellow.bold('3.') +
|
|
778
|
+
' After scanning the QR code with your phone for Test User authentication,');
|
|
779
|
+
console.log(' the compiled code package will be uploaded to the client automatically.');
|
|
780
|
+
console.log(' Game debugging will start right away.');
|
|
781
|
+
console.log(chalk.gray('─────────────────────────────────────────────'));
|
|
782
|
+
console.log('');
|
|
783
|
+
/**
|
|
784
|
+
* 自动打开浏览器
|
|
785
|
+
*/
|
|
786
|
+
openUrl(qrCodeUrl);
|
|
787
|
+
}
|
|
788
|
+
catch (err) {
|
|
789
|
+
console.error(chalk.red('Failed to generate QR code image.'), err);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
659
792
|
|
|
793
|
+
const successCode = 0;
|
|
794
|
+
const publicPath = path.join(__dirname, 'public');
|
|
795
|
+
// 自定义静态文件服务中间件
|
|
660
796
|
let server;
|
|
661
|
-
async function
|
|
797
|
+
async function start() {
|
|
662
798
|
if (server) {
|
|
663
799
|
closeServer();
|
|
664
800
|
}
|
|
665
|
-
const publicPath = path.join(__dirname, 'public');
|
|
666
801
|
const app = express();
|
|
802
|
+
app.use(expressStaticGzip(publicPath, {
|
|
803
|
+
enableBrotli: true,
|
|
804
|
+
orderPreference: ['br'],
|
|
805
|
+
}));
|
|
667
806
|
/**
|
|
668
807
|
* 支持跨域请求
|
|
669
808
|
*/
|
|
@@ -681,20 +820,45 @@ async function createServer() {
|
|
|
681
820
|
* 支持文件访问
|
|
682
821
|
*/
|
|
683
822
|
const outputDir = getOutputDir();
|
|
684
|
-
/**
|
|
685
|
-
*
|
|
686
|
-
*/
|
|
687
|
-
app.use(express.static(publicPath));
|
|
688
823
|
/**
|
|
689
824
|
* 支持静态资源
|
|
690
825
|
*/
|
|
691
826
|
app.use('/game/files', express.static(outputDir));
|
|
692
827
|
app.get('/game/config', async (req, res) => {
|
|
693
|
-
|
|
828
|
+
const { clientKey } = getClientKey();
|
|
829
|
+
const { email, user_id } = getCurrentUser();
|
|
830
|
+
res.send({
|
|
831
|
+
email,
|
|
832
|
+
user_id,
|
|
833
|
+
code: successCode,
|
|
834
|
+
nodeWsPort: DEV_WS_PORT,
|
|
835
|
+
clientKey: clientKey,
|
|
836
|
+
schema: `https://www.tiktok.com/ttmg/dev/${clientKey}?host=${getLocalIP()}&port=${DEV_WS_PORT}`,
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
app.get('/game/info', async (req, res) => {
|
|
840
|
+
const { clientKey } = getClientKey();
|
|
841
|
+
const data = await request({
|
|
842
|
+
url: 'https://developers.tiktok.com/tiktok/v3/devportal/minigame/info',
|
|
843
|
+
method: 'GET',
|
|
844
|
+
params: {
|
|
845
|
+
client_key: 'mg7oas80sthix6xy',
|
|
846
|
+
version_type: 1,
|
|
847
|
+
},
|
|
848
|
+
headers: {
|
|
849
|
+
'x-tt-env': 'ppe_dev_tool',
|
|
850
|
+
'x-use-ppe': '1',
|
|
851
|
+
},
|
|
852
|
+
});
|
|
853
|
+
res.send({
|
|
854
|
+
code: successCode,
|
|
855
|
+
data,
|
|
856
|
+
});
|
|
694
857
|
});
|
|
695
858
|
app.get('/game/schema', async (req, res) => {
|
|
696
859
|
const { clientKey } = getClientKey();
|
|
697
860
|
res.send({
|
|
861
|
+
code: successCode,
|
|
698
862
|
schema: `https://www.tiktok.com/ttmg/dev/${clientKey}?host=${getLocalIP()}&port=${DEV_WS_PORT}`,
|
|
699
863
|
nodeWsPort: DEV_WS_PORT,
|
|
700
864
|
clientKey: clientKey,
|
|
@@ -705,7 +869,8 @@ async function createServer() {
|
|
|
705
869
|
});
|
|
706
870
|
// 启动服务
|
|
707
871
|
server = app.listen(port, () => {
|
|
708
|
-
console.log(chalk.
|
|
872
|
+
console.log(chalk.green.bold(`✅ Local dev server started successfully! \n`));
|
|
873
|
+
openDevTool();
|
|
709
874
|
});
|
|
710
875
|
return {
|
|
711
876
|
port,
|
|
@@ -720,6 +885,81 @@ function closeServer() {
|
|
|
720
885
|
});
|
|
721
886
|
}
|
|
722
887
|
|
|
888
|
+
/**
|
|
889
|
+
* 一个类型安全的、通用的事件发射器类。
|
|
890
|
+
* 它允许你注册、注销和触发具有严格类型检查的事件。
|
|
891
|
+
*/
|
|
892
|
+
class TypedEventEmitter {
|
|
893
|
+
constructor() {
|
|
894
|
+
// 使用 Map 存储事件监听器。
|
|
895
|
+
// Key 是事件名,Value 是一个 Set,其中包含该事件的所有监听器函数。
|
|
896
|
+
// 使用 Set 可以自动防止同一个监听器被重复注册。
|
|
897
|
+
this.listeners = new Map();
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* 注册一个事件监听器。
|
|
901
|
+
* @param eventName 要监听的事件名称。
|
|
902
|
+
* @param listener 事件触发时执行的回调函数。
|
|
903
|
+
* @returns 返回一个函数,调用该函数即可注销此监听器,方便使用。
|
|
904
|
+
*/
|
|
905
|
+
on(eventName, listener) {
|
|
906
|
+
if (!this.listeners.has(eventName)) {
|
|
907
|
+
this.listeners.set(eventName, new Set());
|
|
908
|
+
}
|
|
909
|
+
this.listeners.get(eventName).add(listener);
|
|
910
|
+
// 返回一个便捷的取消订阅函数
|
|
911
|
+
return () => this.off(eventName, listener);
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* 注销一个事件监听器。
|
|
915
|
+
* @param eventName 要注销的事件名称。
|
|
916
|
+
* @param listener 之前通过 on() 方法注册的回调函数实例。
|
|
917
|
+
*/
|
|
918
|
+
off(eventName, listener) {
|
|
919
|
+
const eventListeners = this.listeners.get(eventName);
|
|
920
|
+
if (eventListeners) {
|
|
921
|
+
eventListeners.delete(listener);
|
|
922
|
+
if (eventListeners.size === 0) {
|
|
923
|
+
this.listeners.delete(eventName);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* 触发一个事件,并同步调用所有相关的监听器。
|
|
929
|
+
* @param eventName 要触发的事件名称。
|
|
930
|
+
* @param payload 传递给所有监听器的数据。类型必须与事件定义匹配。
|
|
931
|
+
*/
|
|
932
|
+
emit(eventName, payload) {
|
|
933
|
+
const eventListeners = this.listeners.get(eventName);
|
|
934
|
+
if (eventListeners) {
|
|
935
|
+
// 遍历 Set 并执行每一个监听器
|
|
936
|
+
eventListeners.forEach(listener => {
|
|
937
|
+
try {
|
|
938
|
+
// 在独立的 try...catch 中调用,防止一个监听器的错误影响其他监听器
|
|
939
|
+
listener(payload);
|
|
940
|
+
}
|
|
941
|
+
catch (error) {
|
|
942
|
+
console.error(`Error in listener for event "${String(eventName)}":`, error);
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* 注销指定事件的所有监听器。
|
|
949
|
+
* @param eventName 要清除监听器的事件名称。
|
|
950
|
+
*/
|
|
951
|
+
removeAllListeners(eventName) {
|
|
952
|
+
this.listeners.delete(eventName);
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* 清空所有事件的所有监听器。
|
|
956
|
+
*/
|
|
957
|
+
clearAll() {
|
|
958
|
+
this.listeners.clear();
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
const eventEmitter = new TypedEventEmitter();
|
|
962
|
+
|
|
723
963
|
class Store {
|
|
724
964
|
constructor(initialState) {
|
|
725
965
|
this.listeners = [];
|
|
@@ -761,149 +1001,18 @@ const store = Store.getInstance({
|
|
|
761
1001
|
clientWsHost: '',
|
|
762
1002
|
clientKey: '',
|
|
763
1003
|
packages: {},
|
|
1004
|
+
isUnderCompiling: false,
|
|
1005
|
+
isWaitingForUpload: false,
|
|
1006
|
+
projectInfo: {
|
|
1007
|
+
projectSize: 0,
|
|
1008
|
+
mainPkg: {
|
|
1009
|
+
pkgSize: 0,
|
|
1010
|
+
isMatchSizeLimit: false,
|
|
1011
|
+
},
|
|
1012
|
+
independentPkgs: {},
|
|
1013
|
+
},
|
|
764
1014
|
});
|
|
765
1015
|
|
|
766
|
-
async function uploadGame(callback) {
|
|
767
|
-
const outputDir = getOutputDir();
|
|
768
|
-
callback({
|
|
769
|
-
status: 'start',
|
|
770
|
-
percent: 0,
|
|
771
|
-
});
|
|
772
|
-
console.log(chalk.yellow.bold('Start compress game resource'));
|
|
773
|
-
const zipPath = path.join(os.homedir(), '__TTMG__', 'upload.zip');
|
|
774
|
-
await zipDirectory(outputDir, zipPath);
|
|
775
|
-
console.log(chalk.green.bold('Compress game package resource success \n'));
|
|
776
|
-
await uploadZip(zipPath, callback);
|
|
777
|
-
}
|
|
778
|
-
/**
|
|
779
|
-
* 复制源目录内容到临时目录,然后根据 glob 模式过滤并压缩文件。
|
|
780
|
-
* 原始目录保持不变。
|
|
781
|
-
*
|
|
782
|
-
* @param sourceDir - 要压缩的源文件夹路径。
|
|
783
|
-
* @param outPath - 输出的 zip 文件路径。
|
|
784
|
-
*/
|
|
785
|
-
async function zipDirectory(sourceDir, outPath) {
|
|
786
|
-
// 1. 创建一个唯一的临时目录
|
|
787
|
-
// fsp 现在是 fs.promises 的别名
|
|
788
|
-
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'zip-temp-'));
|
|
789
|
-
try {
|
|
790
|
-
// 2. 将源目录的所有内容复制到临时目录
|
|
791
|
-
await fs.promises.cp(sourceDir, tempDir, { recursive: true });
|
|
792
|
-
// 3. 对临时目录进行压缩
|
|
793
|
-
const output = fs.createWriteStream(outPath);
|
|
794
|
-
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
795
|
-
// 使用 Promise 包装流操作
|
|
796
|
-
const archivePromise = new Promise((resolve, reject) => {
|
|
797
|
-
output.on('close', () => {
|
|
798
|
-
resolve();
|
|
799
|
-
});
|
|
800
|
-
archive.on('warning', err => console.warn('Archiver warning:', err));
|
|
801
|
-
output.on('error', err => reject(err));
|
|
802
|
-
archive.on('error', err => reject(err));
|
|
803
|
-
});
|
|
804
|
-
archive.pipe(output);
|
|
805
|
-
// 4. 使用 glob 在临时目录中查找文件,并应用过滤规则
|
|
806
|
-
const files = await glob__namespace.glob('**/*', {
|
|
807
|
-
cwd: tempDir,
|
|
808
|
-
nodir: true,
|
|
809
|
-
ignore: '**/*.js.map', // 过滤规则
|
|
810
|
-
});
|
|
811
|
-
// 5. 将过滤后的文件逐个添加到压缩包
|
|
812
|
-
for (const file of files) {
|
|
813
|
-
const filePath = path.join(tempDir, file);
|
|
814
|
-
archive.file(filePath, { name: file });
|
|
815
|
-
}
|
|
816
|
-
// 6. 完成压缩
|
|
817
|
-
await archive.finalize();
|
|
818
|
-
// 等待文件流关闭
|
|
819
|
-
await archivePromise;
|
|
820
|
-
}
|
|
821
|
-
catch (err) {
|
|
822
|
-
console.error('压缩过程中发生错误:', err);
|
|
823
|
-
throw err;
|
|
824
|
-
}
|
|
825
|
-
finally {
|
|
826
|
-
// 7. 无论成功还是失败,都清理临时目录
|
|
827
|
-
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
async function uploadZip(zipPath, callback) {
|
|
831
|
-
const form = new FormData();
|
|
832
|
-
form.append('file', fs.createReadStream(zipPath), {
|
|
833
|
-
filename: 'upload.zip',
|
|
834
|
-
contentType: 'application/zip',
|
|
835
|
-
});
|
|
836
|
-
// 帮我计算下文件大小,变成 MB 为单位
|
|
837
|
-
const fileSize = fs.statSync(zipPath).size / 1024 / 1024;
|
|
838
|
-
console.log(chalk.yellow.bold(`Start upload resource to client, size: ${fileSize.toFixed(2)} MB`));
|
|
839
|
-
const { clientHttpPort, clientHost } = store.getState();
|
|
840
|
-
const url = `http://${clientHost}:${clientHttpPort}/game/upload`;
|
|
841
|
-
try {
|
|
842
|
-
// 1. 创建请求流
|
|
843
|
-
const stream = got.stream.post(url, {
|
|
844
|
-
body: form,
|
|
845
|
-
});
|
|
846
|
-
// 2. 监听上传进度 (这个回调是并行的,不影响封装)
|
|
847
|
-
stream.on('uploadProgress', progress => {
|
|
848
|
-
const percent = progress.percent;
|
|
849
|
-
// const transferred = progress.transferred;
|
|
850
|
-
// const total = progress.total;
|
|
851
|
-
process.stdout.write(`\r${chalk.cyan('Uploading progress: ')}${chalk.green((percent * 100).toFixed(0) + '%')}`);
|
|
852
|
-
callback({
|
|
853
|
-
status: 'process',
|
|
854
|
-
percent,
|
|
855
|
-
});
|
|
856
|
-
});
|
|
857
|
-
// 3. 【核心封装】将流的处理过程包装在 Promise 中
|
|
858
|
-
const response = await new Promise((resolve, reject) => {
|
|
859
|
-
// 用于拼接响应数据
|
|
860
|
-
const chunks = [];
|
|
861
|
-
// 当流传输数据时,收集数据块
|
|
862
|
-
stream.on('data', chunk => {
|
|
863
|
-
chunks.push(chunk);
|
|
864
|
-
});
|
|
865
|
-
// 当流成功结束时
|
|
866
|
-
stream.on('end', () => {
|
|
867
|
-
// 拼接所有数据块,并转换为字符串
|
|
868
|
-
// const responseBody = Buffer.concat(chunks).toString('utf-8');
|
|
869
|
-
// stream.response 在流结束后才可用
|
|
870
|
-
// 将完整的响应对象 resolve 出去
|
|
871
|
-
callback({
|
|
872
|
-
status: 'success',
|
|
873
|
-
percent: 1,
|
|
874
|
-
});
|
|
875
|
-
resolve({
|
|
876
|
-
statusCode: 200,
|
|
877
|
-
});
|
|
878
|
-
});
|
|
879
|
-
// 当流发生错误时
|
|
880
|
-
stream.on('error', err => {
|
|
881
|
-
// 将错误 reject 出去,这样外层的 try...catch 就能捕获到
|
|
882
|
-
reject(err);
|
|
883
|
-
callback({
|
|
884
|
-
status: 'error',
|
|
885
|
-
percent: 0,
|
|
886
|
-
msg: err.message,
|
|
887
|
-
});
|
|
888
|
-
});
|
|
889
|
-
});
|
|
890
|
-
// 4. 当 await 完成后,说明流已成功结束,可以安全地执行后续操作
|
|
891
|
-
process.stdout.write('\n'); // 换行,保持终端整洁
|
|
892
|
-
console.log(chalk.green.bold('✔ Upload completed successfully!'));
|
|
893
|
-
return response;
|
|
894
|
-
}
|
|
895
|
-
catch (err) {
|
|
896
|
-
callback({
|
|
897
|
-
status: 'error',
|
|
898
|
-
percent: 0,
|
|
899
|
-
msg: err?.message,
|
|
900
|
-
});
|
|
901
|
-
process.stdout.write('\n');
|
|
902
|
-
console.log('\n');
|
|
903
|
-
console.error(chalk.red.bold('✖ Upload failed with server error, please scan qrcode to reupload'));
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
|
|
907
1016
|
class WsServer {
|
|
908
1017
|
constructor() {
|
|
909
1018
|
this.ws = new WebSocket.Server({ port: DEV_WS_PORT });
|
|
@@ -928,30 +1037,42 @@ class WsServer {
|
|
|
928
1037
|
const method = clientMessage.method;
|
|
929
1038
|
switch (method) {
|
|
930
1039
|
case 'startUpload':
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1040
|
+
if (store.getState().isUnderCompiling) {
|
|
1041
|
+
store.setState({
|
|
1042
|
+
isWaitingForUpload: true,
|
|
1043
|
+
});
|
|
1044
|
+
this.sendResourceCompile();
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
eventEmitter.emit('compileSuccess', {});
|
|
1048
|
+
}
|
|
1049
|
+
// /**
|
|
1050
|
+
// * 如果是编译中,需要等待编译好之后再上传,关键如何实现呢?
|
|
1051
|
+
// * 1. 编译完成后,需要通知客户端上传
|
|
1052
|
+
// * 2. 客户端上传完成后,需要通知服务端上传完成
|
|
1053
|
+
// */
|
|
1054
|
+
// this.sendUploadStatus('start');
|
|
1055
|
+
// uploadGame(({ status, percent, msg }) => {
|
|
1056
|
+
// if (status === 'process') {
|
|
1057
|
+
// this.sendUploadStatus('process', {
|
|
1058
|
+
// status: 'process',
|
|
1059
|
+
// progress: percent,
|
|
1060
|
+
// });
|
|
1061
|
+
// } else if (status === 'error') {
|
|
1062
|
+
// this.sendUploadStatus('error', {
|
|
1063
|
+
// status: 'error',
|
|
1064
|
+
// errMsg: msg,
|
|
1065
|
+
// isSuccess: false,
|
|
1066
|
+
// });
|
|
1067
|
+
// } else if (status === 'success') {
|
|
1068
|
+
// this.sendUploadStatus('success', {
|
|
1069
|
+
// status: 'success',
|
|
1070
|
+
// packages: store.getState().packages,
|
|
1071
|
+
// clientKey: getClientKey().clientKey,
|
|
1072
|
+
// isSuccess: true,
|
|
1073
|
+
// });
|
|
1074
|
+
// }
|
|
1075
|
+
// });
|
|
955
1076
|
break;
|
|
956
1077
|
case 'closeLocalDebug':
|
|
957
1078
|
console.log('closeLocalDebug');
|
|
@@ -1048,6 +1169,11 @@ class WsServer {
|
|
|
1048
1169
|
method: 'resourceChange',
|
|
1049
1170
|
});
|
|
1050
1171
|
}
|
|
1172
|
+
sendResourceCompile() {
|
|
1173
|
+
this.send({
|
|
1174
|
+
method: 'resourceCompile',
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1051
1177
|
close() {
|
|
1052
1178
|
this.ws.close();
|
|
1053
1179
|
}
|
|
@@ -1061,7 +1187,7 @@ class WsServer {
|
|
|
1061
1187
|
}
|
|
1062
1188
|
const wsServer = new WsServer();
|
|
1063
1189
|
|
|
1064
|
-
async function
|
|
1190
|
+
async function compile(context) {
|
|
1065
1191
|
const entryDir = process.cwd();
|
|
1066
1192
|
const outputDir = getOutputDir();
|
|
1067
1193
|
const { clientKey, msg } = getClientKey();
|
|
@@ -1075,7 +1201,12 @@ async function prepareResource(context) {
|
|
|
1075
1201
|
if (!fs.existsSync(outputDir)) {
|
|
1076
1202
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
1077
1203
|
}
|
|
1078
|
-
|
|
1204
|
+
store.setState({
|
|
1205
|
+
isUnderCompiling: true,
|
|
1206
|
+
});
|
|
1207
|
+
const tip = context?.mode === 'watch'
|
|
1208
|
+
? chalk.yellow('⏳ Local game assets updated. Starting build process...\n')
|
|
1209
|
+
: chalk.yellow.bold('⏳ Start compile game assets for local debugging...\n');
|
|
1079
1210
|
console.log(tip);
|
|
1080
1211
|
const { isSuccess, errorMsg, packages } = await ttmgPack.debugPkgs({
|
|
1081
1212
|
entry: entryDir,
|
|
@@ -1094,15 +1225,25 @@ async function prepareResource(context) {
|
|
|
1094
1225
|
},
|
|
1095
1226
|
});
|
|
1096
1227
|
if (!isSuccess) {
|
|
1228
|
+
store.setState({
|
|
1229
|
+
isUnderCompiling: false,
|
|
1230
|
+
});
|
|
1097
1231
|
console.log(chalk.redBright('Build game package failed, Please check the error message below:'));
|
|
1098
1232
|
console.log(chalk.redBright(errorMsg));
|
|
1099
1233
|
process.exit(1);
|
|
1100
1234
|
}
|
|
1101
1235
|
else {
|
|
1102
1236
|
store.setState({
|
|
1237
|
+
isUnderCompiling: false,
|
|
1103
1238
|
packages,
|
|
1104
1239
|
});
|
|
1105
|
-
|
|
1240
|
+
if (store.getState().isWaitingForUpload) {
|
|
1241
|
+
eventEmitter.emit('compileSuccess', {});
|
|
1242
|
+
}
|
|
1243
|
+
console.log(chalk.green.bold('✅ Compile game assets success! \n'));
|
|
1244
|
+
/**
|
|
1245
|
+
* 开始上传客户端
|
|
1246
|
+
*/
|
|
1106
1247
|
return {
|
|
1107
1248
|
isSuccess,
|
|
1108
1249
|
errorMsg,
|
|
@@ -1112,94 +1253,240 @@ async function prepareResource(context) {
|
|
|
1112
1253
|
}
|
|
1113
1254
|
|
|
1114
1255
|
// import { uploadGame } from './uploadGame';
|
|
1115
|
-
async function
|
|
1256
|
+
async function watch() {
|
|
1116
1257
|
let debounceTimer = null;
|
|
1117
|
-
|
|
1258
|
+
// 监听当前工作目录,排除 node_modules 和 .git
|
|
1259
|
+
const watcher = chokidar.watch(process.cwd(), {
|
|
1260
|
+
// ignored: /(^|[\/\\])(\.git|node_modules)/, // 忽略 .git 和 node_modules
|
|
1261
|
+
ignoreInitial: true, // 忽略初始添加事件
|
|
1262
|
+
persistent: true,
|
|
1263
|
+
awaitWriteFinish: {
|
|
1264
|
+
stabilityThreshold: 1000,
|
|
1265
|
+
},
|
|
1266
|
+
});
|
|
1267
|
+
// 任意文件变化都触发
|
|
1268
|
+
watcher.on('all', (event, path) => {
|
|
1118
1269
|
// 清除之前的定时器
|
|
1119
1270
|
if (debounceTimer)
|
|
1120
1271
|
clearTimeout(debounceTimer);
|
|
1121
1272
|
// 重新设置定时器
|
|
1122
1273
|
debounceTimer = setTimeout(async () => {
|
|
1123
|
-
await
|
|
1274
|
+
await compile({
|
|
1124
1275
|
mode: 'watch',
|
|
1125
1276
|
});
|
|
1126
1277
|
wsServer.sendResourceChange();
|
|
1127
|
-
// TODO:只做文件预准备,但不主动上传
|
|
1128
|
-
// uploadGame()
|
|
1129
|
-
// .then((res) => {
|
|
1130
|
-
// if (res.isSuccess) {
|
|
1131
|
-
// wsServer.sendUploadStatus('success');
|
|
1132
|
-
// } else {
|
|
1133
|
-
// wsServer.sendUploadStatus('error', {
|
|
1134
|
-
// errMsg: res.errorMsg,
|
|
1135
|
-
// });
|
|
1136
|
-
// }
|
|
1137
|
-
// })
|
|
1138
|
-
// .catch(() => {
|
|
1139
|
-
// wsServer.sendUploadStatus('error');
|
|
1140
|
-
// });
|
|
1141
1278
|
debounceTimer = null;
|
|
1142
|
-
},
|
|
1279
|
+
}, 3000); // 1秒内只执行一次
|
|
1280
|
+
});
|
|
1281
|
+
watcher.on('error', error => {
|
|
1282
|
+
// console.error(chalk.red('[watch] 监听发生错误:'), error);
|
|
1143
1283
|
});
|
|
1144
1284
|
}
|
|
1145
1285
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1286
|
+
async function uploadGame(callback) {
|
|
1287
|
+
const outputDir = getOutputDir();
|
|
1288
|
+
callback({
|
|
1289
|
+
status: 'start',
|
|
1290
|
+
percent: 0,
|
|
1291
|
+
});
|
|
1292
|
+
console.log(chalk.yellow.bold('Start compress game resource'));
|
|
1293
|
+
const zipPath = path.join(os.homedir(), '__TTMG__', 'upload.zip');
|
|
1294
|
+
await zipDirectory(outputDir, zipPath);
|
|
1295
|
+
console.log(chalk.green.bold('Compress game package resource success \n'));
|
|
1296
|
+
await uploadZip(zipPath, callback);
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* 复制源目录内容到临时目录,然后根据 glob 模式过滤并压缩文件。
|
|
1300
|
+
* 原始目录保持不变。
|
|
1301
|
+
*
|
|
1302
|
+
* @param sourceDir - 要压缩的源文件夹路径。
|
|
1303
|
+
* @param outPath - 输出的 zip 文件路径。
|
|
1304
|
+
*/
|
|
1305
|
+
async function zipDirectory(sourceDir, outPath) {
|
|
1306
|
+
// 1. 创建一个唯一的临时目录
|
|
1307
|
+
// fsp 现在是 fs.promises 的别名
|
|
1308
|
+
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'zip-temp-'));
|
|
1309
|
+
try {
|
|
1310
|
+
// 2. 将源目录的所有内容复制到临时目录
|
|
1311
|
+
await fs.promises.cp(sourceDir, tempDir, { recursive: true });
|
|
1312
|
+
// 3. 对临时目录进行压缩
|
|
1313
|
+
const output = fs.createWriteStream(outPath);
|
|
1314
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
1315
|
+
// 使用 Promise 包装流操作
|
|
1316
|
+
const archivePromise = new Promise((resolve, reject) => {
|
|
1317
|
+
output.on('close', () => {
|
|
1318
|
+
resolve();
|
|
1319
|
+
});
|
|
1320
|
+
archive.on('warning', err => console.warn('Archiver warning:', err));
|
|
1321
|
+
output.on('error', err => reject(err));
|
|
1322
|
+
archive.on('error', err => reject(err));
|
|
1323
|
+
});
|
|
1324
|
+
archive.pipe(output);
|
|
1325
|
+
// 4. 使用 glob 在临时目录中查找文件,并应用过滤规则
|
|
1326
|
+
const files = await glob__namespace.glob('**/*', {
|
|
1327
|
+
cwd: tempDir,
|
|
1328
|
+
nodir: true,
|
|
1329
|
+
ignore: '**/*.js.map', // 过滤规则
|
|
1330
|
+
});
|
|
1331
|
+
// 5. 将过滤后的文件逐个添加到压缩包
|
|
1332
|
+
for (const file of files) {
|
|
1333
|
+
const filePath = path.join(tempDir, file);
|
|
1334
|
+
archive.file(filePath, { name: file });
|
|
1335
|
+
}
|
|
1336
|
+
// 6. 完成压缩
|
|
1337
|
+
await archive.finalize();
|
|
1338
|
+
// 等待文件流关闭
|
|
1339
|
+
await archivePromise;
|
|
1151
1340
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1341
|
+
catch (err) {
|
|
1342
|
+
console.error('压缩过程中发生错误:', err);
|
|
1343
|
+
throw err;
|
|
1344
|
+
}
|
|
1345
|
+
finally {
|
|
1346
|
+
// 7. 无论成功还是失败,都清理临时目录
|
|
1347
|
+
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
async function uploadZip(zipPath, callback) {
|
|
1351
|
+
const form = new FormData();
|
|
1352
|
+
form.append('file', fs.createReadStream(zipPath), {
|
|
1353
|
+
filename: 'upload.zip',
|
|
1354
|
+
contentType: 'application/zip',
|
|
1355
|
+
});
|
|
1356
|
+
// 帮我计算下文件大小,变成 MB 为单位
|
|
1357
|
+
const fileSize = fs.statSync(zipPath).size / 1024 / 1024;
|
|
1358
|
+
console.log(chalk.yellow.bold(`Start upload resource to client, size: ${fileSize.toFixed(2)} MB`));
|
|
1359
|
+
const { clientHttpPort, clientHost } = store.getState();
|
|
1360
|
+
const url = `http://${clientHost}:${clientHttpPort}/game/upload`;
|
|
1154
1361
|
try {
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1362
|
+
// 1. 创建请求流
|
|
1363
|
+
const stream = got.stream.post(url, {
|
|
1364
|
+
body: form,
|
|
1365
|
+
});
|
|
1366
|
+
// 2. 监听上传进度 (这个回调是并行的,不影响封装)
|
|
1367
|
+
stream.on('uploadProgress', progress => {
|
|
1368
|
+
const percent = progress.percent;
|
|
1369
|
+
// const transferred = progress.transferred;
|
|
1370
|
+
// const total = progress.total;
|
|
1371
|
+
process.stdout.write(`\r${chalk.cyan('Uploading progress: ')}${chalk.green((percent * 100).toFixed(0) + '%')}`);
|
|
1372
|
+
callback({
|
|
1373
|
+
status: 'process',
|
|
1374
|
+
percent,
|
|
1375
|
+
});
|
|
1376
|
+
});
|
|
1377
|
+
// 3. 【核心封装】将流的处理过程包装在 Promise 中
|
|
1378
|
+
const response = await new Promise((resolve, reject) => {
|
|
1379
|
+
// 用于拼接响应数据
|
|
1380
|
+
const chunks = [];
|
|
1381
|
+
// 当流传输数据时,收集数据块
|
|
1382
|
+
stream.on('data', chunk => {
|
|
1383
|
+
chunks.push(chunk);
|
|
1384
|
+
});
|
|
1385
|
+
// 当流成功结束时
|
|
1386
|
+
stream.on('end', () => {
|
|
1387
|
+
// 拼接所有数据块,并转换为字符串
|
|
1388
|
+
// const responseBody = Buffer.concat(chunks).toString('utf-8');
|
|
1389
|
+
// stream.response 在流结束后才可用
|
|
1390
|
+
// 将完整的响应对象 resolve 出去
|
|
1391
|
+
callback({
|
|
1392
|
+
status: 'success',
|
|
1393
|
+
percent: 1,
|
|
1394
|
+
});
|
|
1395
|
+
resolve({
|
|
1396
|
+
statusCode: 200,
|
|
1397
|
+
});
|
|
1398
|
+
});
|
|
1399
|
+
// 当流发生错误时
|
|
1400
|
+
stream.on('error', err => {
|
|
1401
|
+
// 将错误 reject 出去,这样外层的 try...catch 就能捕获到
|
|
1402
|
+
reject(err);
|
|
1403
|
+
callback({
|
|
1404
|
+
status: 'error',
|
|
1405
|
+
percent: 0,
|
|
1406
|
+
msg: err.message,
|
|
1407
|
+
});
|
|
1408
|
+
});
|
|
1409
|
+
});
|
|
1410
|
+
// 4. 当 await 完成后,说明流已成功结束,可以安全地执行后续操作
|
|
1411
|
+
process.stdout.write('\n'); // 换行,保持终端整洁
|
|
1412
|
+
console.log(chalk.green.bold('✔ Upload completed successfully!'));
|
|
1413
|
+
return response;
|
|
1166
1414
|
}
|
|
1167
1415
|
catch (err) {
|
|
1168
|
-
|
|
1416
|
+
callback({
|
|
1417
|
+
status: 'error',
|
|
1418
|
+
percent: 0,
|
|
1419
|
+
msg: err?.message,
|
|
1420
|
+
});
|
|
1421
|
+
process.stdout.write('\n');
|
|
1422
|
+
console.log('\n');
|
|
1423
|
+
console.error(chalk.red.bold('✖ Upload failed with server error, please scan qrcode to reupload'));
|
|
1169
1424
|
}
|
|
1170
1425
|
}
|
|
1171
1426
|
|
|
1427
|
+
function listen() {
|
|
1428
|
+
eventEmitter.on('compileSuccess', () => {
|
|
1429
|
+
wsServer.sendUploadStatus('start');
|
|
1430
|
+
store.setState({
|
|
1431
|
+
isWaitingForUpload: false,
|
|
1432
|
+
});
|
|
1433
|
+
uploadGame(({ status, percent, msg }) => {
|
|
1434
|
+
if (status === 'process') {
|
|
1435
|
+
wsServer.sendUploadStatus('process', {
|
|
1436
|
+
status: 'process',
|
|
1437
|
+
progress: percent,
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
else if (status === 'error') {
|
|
1441
|
+
wsServer.sendUploadStatus('error', {
|
|
1442
|
+
status: 'error',
|
|
1443
|
+
errMsg: msg,
|
|
1444
|
+
isSuccess: false,
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
else if (status === 'success') {
|
|
1448
|
+
wsServer.sendUploadStatus('success', {
|
|
1449
|
+
status: 'success',
|
|
1450
|
+
packages: store.getState().packages,
|
|
1451
|
+
clientKey: getClientKey().clientKey,
|
|
1452
|
+
isSuccess: true,
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1172
1459
|
async function dev() {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
/**
|
|
1178
|
-
* 2. 创建本地调试服务
|
|
1179
|
-
*/
|
|
1180
|
-
await createServer();
|
|
1181
|
-
/**
|
|
1182
|
-
* 3. 显示调试二维码信息,扫码启动客户端调试服务
|
|
1183
|
-
*/
|
|
1184
|
-
await showSchema();
|
|
1185
|
-
/**
|
|
1186
|
-
* 4. 监听游戏资源变化
|
|
1187
|
-
*/
|
|
1188
|
-
await watchChange();
|
|
1460
|
+
await start();
|
|
1461
|
+
compile();
|
|
1462
|
+
watch();
|
|
1463
|
+
listen();
|
|
1189
1464
|
}
|
|
1190
1465
|
|
|
1191
|
-
|
|
1466
|
+
/**
|
|
1467
|
+
* 获取配置文件中的 cookie
|
|
1468
|
+
*/
|
|
1469
|
+
async function upload() {
|
|
1470
|
+
const res = await request({
|
|
1471
|
+
url: 'https://developers.tiktok.com/tiktok/v3/devportal/minigame/info',
|
|
1472
|
+
method: 'GET',
|
|
1473
|
+
params: {
|
|
1474
|
+
client_key: 'mg7oas80sthix6xy',
|
|
1475
|
+
version_type: 1,
|
|
1476
|
+
},
|
|
1477
|
+
headers: {
|
|
1478
|
+
'x-tt-env': 'ppe_dev_tool',
|
|
1479
|
+
'x-use-ppe': '1',
|
|
1480
|
+
},
|
|
1481
|
+
});
|
|
1482
|
+
console.log('app info', res?.data);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
var version = "0.1.6";
|
|
1192
1486
|
var pkg = {
|
|
1193
1487
|
version: version};
|
|
1194
1488
|
|
|
1195
1489
|
const program = new commander.Command();
|
|
1196
|
-
(async () => {
|
|
1197
|
-
try {
|
|
1198
|
-
await checkUpdate();
|
|
1199
|
-
// eslint-disable-next-line no-empty
|
|
1200
|
-
}
|
|
1201
|
-
catch (err) { }
|
|
1202
|
-
})();
|
|
1203
1490
|
program
|
|
1204
1491
|
.name('ttmg')
|
|
1205
1492
|
.description('TikTok Mini Games Command Line Tool')
|
|
@@ -1239,6 +1526,11 @@ program
|
|
|
1239
1526
|
}
|
|
1240
1527
|
else {
|
|
1241
1528
|
dev();
|
|
1529
|
+
try {
|
|
1530
|
+
checkUpdate();
|
|
1531
|
+
// eslint-disable-next-line no-empty
|
|
1532
|
+
}
|
|
1533
|
+
catch (err) { }
|
|
1242
1534
|
}
|
|
1243
1535
|
});
|
|
1244
1536
|
/**
|
|
@@ -1258,10 +1550,10 @@ program
|
|
|
1258
1550
|
}
|
|
1259
1551
|
});
|
|
1260
1552
|
program
|
|
1261
|
-
.command('
|
|
1262
|
-
.description('
|
|
1553
|
+
.command('upload')
|
|
1554
|
+
.description('Upload Native Mini Game')
|
|
1263
1555
|
.action(async () => {
|
|
1264
|
-
|
|
1556
|
+
await upload();
|
|
1265
1557
|
});
|
|
1266
1558
|
program.parse(process.argv);
|
|
1267
1559
|
//# sourceMappingURL=index.js.map
|