@ttmg/cli 0.1.0-alpha.2 → 0.1.0-alpha.4
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 +284 -17
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -10,7 +10,8 @@ var chalk = require('chalk');
|
|
|
10
10
|
var express = require('express');
|
|
11
11
|
var path = require('path');
|
|
12
12
|
var require$$4 = require('cheerio');
|
|
13
|
-
var
|
|
13
|
+
var puppeteer = require('puppeteer');
|
|
14
|
+
var child_process = require('child_process');
|
|
14
15
|
var os = require('os');
|
|
15
16
|
var require$$6 = require('crypto');
|
|
16
17
|
var require$$5$1 = require('archiver');
|
|
@@ -287,25 +288,286 @@ function requireInit () {
|
|
|
287
288
|
var initExports = requireInit();
|
|
288
289
|
var index$2 = /*@__PURE__*/getDefaultExportFromCjs(initExports);
|
|
289
290
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
291
|
+
const iPhone12 = puppeteer.KnownDevices['iPhone 12 Pro'];
|
|
292
|
+
const DEBUGGING_PORT = 9222;
|
|
293
|
+
async function waitForEndpoint(url, timeout = 30000) {
|
|
294
|
+
const startTime = Date.now();
|
|
295
|
+
while (Date.now() - startTime < timeout) {
|
|
296
|
+
try {
|
|
297
|
+
const response = await fetch(url);
|
|
298
|
+
if (response.ok) {
|
|
299
|
+
console.log(`端点 ${url} 已可用!`);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (error) { }
|
|
304
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
305
|
+
}
|
|
306
|
+
throw new Error(`等待端点 ${url} 可用超时 (${timeout}ms)`);
|
|
307
|
+
}
|
|
308
|
+
class BrowserManager {
|
|
309
|
+
constructor() {
|
|
310
|
+
this.browser = null;
|
|
311
|
+
this.page = null;
|
|
312
|
+
this.isClosing = false;
|
|
313
|
+
this.monitorInterval = null;
|
|
314
|
+
this.chromeProcess = null;
|
|
315
|
+
this.isReusedInstance = false;
|
|
316
|
+
}
|
|
317
|
+
getSystemChromePath() {
|
|
318
|
+
try {
|
|
319
|
+
const platform = os.platform();
|
|
320
|
+
if (platform === 'win32') {
|
|
321
|
+
const paths = [
|
|
322
|
+
process.env.PROGRAMFILES +
|
|
323
|
+
'\\Google\\Chrome\\Application\\chrome.exe',
|
|
324
|
+
process.env['PROGRAMFILES(X86)'] +
|
|
325
|
+
'\\Google\\Chrome\\Application\\chrome.exe',
|
|
326
|
+
process.env.LOCALAPPDATA +
|
|
327
|
+
'\\Google\\Chrome\\Application\\chrome.exe',
|
|
328
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
329
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
330
|
+
];
|
|
331
|
+
for (const path of paths) {
|
|
332
|
+
if (path && fs.existsSync(path))
|
|
333
|
+
return path;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else if (platform === 'darwin') {
|
|
337
|
+
const macPaths = [
|
|
338
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
339
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
340
|
+
];
|
|
341
|
+
for (const path of macPaths) {
|
|
342
|
+
if (fs.existsSync(path))
|
|
343
|
+
return path;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
const linuxPaths = [
|
|
348
|
+
'/usr/bin/google-chrome',
|
|
349
|
+
'/usr/bin/google-chrome-stable',
|
|
350
|
+
'/usr/bin/chromium-browser',
|
|
351
|
+
'/usr/bin/chromium',
|
|
352
|
+
'/snap/bin/chromium',
|
|
353
|
+
'/snap/bin/google-chrome',
|
|
354
|
+
];
|
|
355
|
+
for (const path of linuxPaths) {
|
|
356
|
+
if (fs.existsSync(path))
|
|
357
|
+
return path;
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
const chromePath = child_process.execSync('which google-chrome || which google-chrome-stable || which chromium-browser || which chromium', { encoding: 'utf8' }).trim();
|
|
361
|
+
if (chromePath && fs.existsSync(chromePath))
|
|
362
|
+
return chromePath;
|
|
363
|
+
}
|
|
364
|
+
catch (e) { }
|
|
365
|
+
}
|
|
366
|
+
throw new Error('未在常见位置找到系统 Chrome 浏览器');
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
return '';
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async initializeBrowser() {
|
|
373
|
+
const browserURL = `http://127.0.0.1:${DEBUGGING_PORT}`;
|
|
374
|
+
const versionURL = `${browserURL}/json/version`;
|
|
375
|
+
try {
|
|
376
|
+
this.browser = await puppeteer.connect({
|
|
377
|
+
browserURL,
|
|
378
|
+
defaultViewport: null,
|
|
379
|
+
});
|
|
380
|
+
this.isReusedInstance = true;
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
this.isReusedInstance = false;
|
|
384
|
+
const chromePath = this.getSystemChromePath();
|
|
385
|
+
if (!chromePath) {
|
|
386
|
+
console.error('错误:未找到可用的 Chrome 或 Chromium 浏览器!\n请前往 https://www.google.com/chrome/ 下载并安装。\n');
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
const launchArgs = [
|
|
390
|
+
`--remote-debugging-port=${DEBUGGING_PORT}`,
|
|
391
|
+
'--auto-open-devtools-for-tabs',
|
|
296
392
|
'--no-default-browser-check',
|
|
297
|
-
'--allow-insecure-localhost',
|
|
298
|
-
'--allow-running-insecure-content',
|
|
299
393
|
'--remote-allow-origins=*',
|
|
300
|
-
|
|
301
|
-
'--
|
|
302
|
-
|
|
394
|
+
`--user-data-dir=/tmp/chrome-persistent-profile`,
|
|
395
|
+
'--no-first-run',
|
|
396
|
+
'--homepage=about:blank',
|
|
397
|
+
];
|
|
398
|
+
this.chromeProcess = child_process.spawn(chromePath, launchArgs, {
|
|
399
|
+
detached: true,
|
|
400
|
+
stdio: 'ignore',
|
|
401
|
+
});
|
|
402
|
+
this.chromeProcess.unref();
|
|
403
|
+
try {
|
|
404
|
+
await waitForEndpoint(versionURL);
|
|
405
|
+
}
|
|
406
|
+
catch (waitError) {
|
|
407
|
+
console.error(waitError);
|
|
408
|
+
if (this.chromeProcess && this.chromeProcess.pid) {
|
|
409
|
+
process.kill(this.chromeProcess.pid);
|
|
410
|
+
}
|
|
411
|
+
throw waitError;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
this.browser = await puppeteer.connect({ browserURL });
|
|
415
|
+
console.log(`✅ 新浏览器实例已启动并连接成功 (PID: ${this.chromeProcess.pid})`);
|
|
416
|
+
}
|
|
417
|
+
catch (connectError) {
|
|
418
|
+
throw connectError;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const allPages = await this.browser.pages();
|
|
422
|
+
this.page =
|
|
423
|
+
allPages.find(p => p.url() !== 'about:blank') || allPages[0] || null;
|
|
424
|
+
if (!this.page) {
|
|
425
|
+
this.page = await this.browser.newPage();
|
|
426
|
+
}
|
|
427
|
+
this.setupBrowserListeners();
|
|
428
|
+
}
|
|
429
|
+
async openUrlWithIntelligence(url) {
|
|
430
|
+
try {
|
|
431
|
+
if (!this.browser || !this.browser.isConnected()) {
|
|
432
|
+
await this.initializeBrowser();
|
|
433
|
+
}
|
|
434
|
+
const existingPage = await this.findPageByUrl(url);
|
|
435
|
+
if (existingPage) {
|
|
436
|
+
await this.switchToPageAndRefresh(existingPage, url);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
await this.openNewPage(url);
|
|
440
|
+
}
|
|
441
|
+
this.startNonBlockingMonitor();
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
await this.cleanup();
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
startNonBlockingMonitor() {
|
|
449
|
+
if (this.monitorInterval)
|
|
450
|
+
clearInterval(this.monitorInterval);
|
|
451
|
+
this.monitorInterval = setInterval(() => {
|
|
452
|
+
if (this.isClosing || !this.browser || !this.browser.isConnected()) {
|
|
453
|
+
if (this.monitorInterval)
|
|
454
|
+
clearInterval(this.monitorInterval);
|
|
455
|
+
this.monitorInterval = null;
|
|
456
|
+
}
|
|
457
|
+
}, 5000);
|
|
458
|
+
}
|
|
459
|
+
async findPageByUrl(targetUrl) {
|
|
460
|
+
if (!this.browser)
|
|
461
|
+
return null;
|
|
462
|
+
const pages = await this.browser.pages();
|
|
463
|
+
for (const page of pages) {
|
|
464
|
+
if (this.isSamePage(page.url(), targetUrl)) {
|
|
465
|
+
return page;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
isSamePage(url1, url2) {
|
|
471
|
+
try {
|
|
472
|
+
const parsedUrl1 = new URL(url1);
|
|
473
|
+
const parsedUrl2 = new URL(url2);
|
|
474
|
+
return (parsedUrl1.hostname === parsedUrl2.hostname &&
|
|
475
|
+
parsedUrl1.pathname === parsedUrl2.pathname);
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async switchToPageAndRefresh(page, url) {
|
|
482
|
+
await page.bringToFront();
|
|
483
|
+
try {
|
|
484
|
+
await page.emulate(iPhone12);
|
|
485
|
+
await page.evaluate(() => {
|
|
486
|
+
window.location.href = url;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
catch (error) { }
|
|
490
|
+
this.page = page;
|
|
491
|
+
}
|
|
492
|
+
async openNewPage(url) {
|
|
493
|
+
if (!this.browser)
|
|
494
|
+
throw new Error('Browser not initialized.');
|
|
495
|
+
if (this.page) {
|
|
496
|
+
try {
|
|
497
|
+
await this.page.emulate(iPhone12);
|
|
498
|
+
await this.page.goto(url, { waitUntil: 'load', timeout: 60000 });
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
console.error(`在现有页面导航失败,将尝试创建新页面。错误: ${error}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
await this.page.emulate(iPhone12);
|
|
506
|
+
this.page = await this.browser.newPage();
|
|
507
|
+
this.setupPageListeners(this.page);
|
|
508
|
+
try {
|
|
509
|
+
await this.page.goto(url, { waitUntil: 'load', timeout: 60000 });
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
throw error;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
setupBrowserListeners() {
|
|
516
|
+
if (!this.browser)
|
|
517
|
+
return;
|
|
518
|
+
this.browser.on('disconnected', () => {
|
|
519
|
+
this.cleanup();
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
setupPageListeners(page) {
|
|
523
|
+
page.on('close', () => {
|
|
524
|
+
if (this.page === page) {
|
|
525
|
+
this.page = null;
|
|
526
|
+
}
|
|
303
527
|
});
|
|
528
|
+
}
|
|
529
|
+
async close() {
|
|
530
|
+
if (this.isClosing)
|
|
531
|
+
return;
|
|
532
|
+
this.isClosing = true;
|
|
533
|
+
if (this.monitorInterval)
|
|
534
|
+
clearInterval(this.monitorInterval);
|
|
535
|
+
try {
|
|
536
|
+
if (this.browser && this.browser.isConnected()) {
|
|
537
|
+
await this.browser.disconnect();
|
|
538
|
+
}
|
|
539
|
+
if (!this.isReusedInstance &&
|
|
540
|
+
this.chromeProcess &&
|
|
541
|
+
this.chromeProcess.pid) {
|
|
542
|
+
process.kill(this.chromeProcess.pid);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
}
|
|
547
|
+
finally {
|
|
548
|
+
this.cleanup();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
cleanup() {
|
|
552
|
+
if (this.monitorInterval)
|
|
553
|
+
clearInterval(this.monitorInterval);
|
|
554
|
+
this.monitorInterval = null;
|
|
555
|
+
this.browser = null;
|
|
556
|
+
this.page = null;
|
|
557
|
+
this.chromeProcess = null;
|
|
558
|
+
this.isClosing = false;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async function openUrl(url) {
|
|
562
|
+
const browserManager = new BrowserManager();
|
|
563
|
+
try {
|
|
564
|
+
await browserManager.openUrlWithIntelligence(url);
|
|
304
565
|
await new Promise(() => { });
|
|
566
|
+
return browserManager;
|
|
305
567
|
}
|
|
306
|
-
catch (
|
|
307
|
-
|
|
308
|
-
|
|
568
|
+
catch (error) {
|
|
569
|
+
await browserManager.close();
|
|
570
|
+
throw error;
|
|
309
571
|
}
|
|
310
572
|
}
|
|
311
573
|
|
|
@@ -968,6 +1230,7 @@ async function uploadZip(zipPath) {
|
|
|
968
1230
|
process.stdout.write(`\r${chalk.cyan('Uploading progress: ')}${chalk.green(percent + '%')}`);
|
|
969
1231
|
// 生成标准的 百分比
|
|
970
1232
|
wsServer?.sendUploadStatus('process', {
|
|
1233
|
+
status: 'process',
|
|
971
1234
|
progress: `${percent}%`,
|
|
972
1235
|
});
|
|
973
1236
|
});
|
|
@@ -1035,18 +1298,22 @@ class WsServer {
|
|
|
1035
1298
|
.then(res => {
|
|
1036
1299
|
if (res.isSuccess) {
|
|
1037
1300
|
this.sendUploadStatus('success', {
|
|
1301
|
+
status: 'success',
|
|
1038
1302
|
packages: store.getState().packages,
|
|
1303
|
+
clientKey: getClientKey(),
|
|
1039
1304
|
isSuccess: res.isSuccess,
|
|
1040
1305
|
});
|
|
1041
1306
|
}
|
|
1042
1307
|
else {
|
|
1043
1308
|
this.sendUploadStatus('error', {
|
|
1309
|
+
status: 'error',
|
|
1044
1310
|
errMsg: res.errorMsg,
|
|
1045
1311
|
});
|
|
1046
1312
|
}
|
|
1047
1313
|
})
|
|
1048
1314
|
.catch(() => {
|
|
1049
1315
|
this.sendUploadStatus('error', {
|
|
1316
|
+
status: 'error',
|
|
1050
1317
|
isSuccess: false,
|
|
1051
1318
|
});
|
|
1052
1319
|
console.log(chalk.red.bold('Start upload resource to client failed!'));
|
|
@@ -1153,12 +1420,12 @@ async function prepareResource() {
|
|
|
1153
1420
|
async function watchChange() {
|
|
1154
1421
|
let debounceTimer = null;
|
|
1155
1422
|
fs.watch(process.cwd(), (eventType, filename) => {
|
|
1156
|
-
console.log(chalk.yellow('game resource change, restart to upload'));
|
|
1157
1423
|
// 清除之前的定时器
|
|
1158
1424
|
if (debounceTimer)
|
|
1159
1425
|
clearTimeout(debounceTimer);
|
|
1160
1426
|
// 重新设置定时器
|
|
1161
1427
|
debounceTimer = setTimeout(async () => {
|
|
1428
|
+
console.log(chalk.yellow('game resource change, restart to upload'));
|
|
1162
1429
|
await prepareResource();
|
|
1163
1430
|
wsServer.send({
|
|
1164
1431
|
method: 'resourceChange',
|
|
@@ -1217,7 +1484,7 @@ async function dev() {
|
|
|
1217
1484
|
await watchChange();
|
|
1218
1485
|
}
|
|
1219
1486
|
|
|
1220
|
-
var version = "0.1.0-
|
|
1487
|
+
var version = "0.1.0-beta.14";
|
|
1221
1488
|
var pkg = {
|
|
1222
1489
|
version: version};
|
|
1223
1490
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttmg/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "TikTok Mini Game Command Line Tool",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"prepublish": "npm run build",
|
|
14
14
|
"build": "rollup -c",
|
|
15
15
|
"watch": "rollup -c -w",
|
|
16
|
-
"perf": "rollup -c --perf"
|
|
16
|
+
"perf": "rollup -c --perf",
|
|
17
|
+
"dev": "ts-node -r tsconfig-paths/register src/index.ts"
|
|
17
18
|
},
|
|
18
19
|
"keywords": [
|
|
19
20
|
"TTMG",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"multer": "^2.0.2",
|
|
42
43
|
"open": "^10.2.0",
|
|
43
44
|
"prettier": "^3.6.2",
|
|
45
|
+
"puppeteer": "^24.17.1",
|
|
44
46
|
"qrcode-terminal": "^0.12.0",
|
|
45
47
|
"ttmg-pack": "^0.0.20-alpha.1",
|
|
46
48
|
"ws": "^8.18.3"
|
|
@@ -60,6 +62,8 @@
|
|
|
60
62
|
"eslint": "^9.31.0",
|
|
61
63
|
"rollup": "^4.46.4",
|
|
62
64
|
"rollup-plugin-visualizer": "^6.0.3",
|
|
65
|
+
"ts-node": "^10.9.2",
|
|
66
|
+
"tsconfig-paths": "^4.2.0",
|
|
63
67
|
"typescript": "^5.9.2"
|
|
64
68
|
},
|
|
65
69
|
"engines": {
|