@ttmg/cli 0.1.0-alpha.3 → 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.
Files changed (2) hide show
  1. package/dist/index.js +284 -17
  2. 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 chromeLauncher = require('chrome-launcher');
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
- async function openUrl(url) {
291
- try {
292
- await chromeLauncher.launch({
293
- startingUrl: url,
294
- chromeFlags: [
295
- '--auto-open-devtools-for-tabs', // 自动打开 DevTools
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
- '--user-data-dir=/tmp/chrome-debug-profile',
301
- '--disable-popup-blocking',
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 (e) {
307
- // const open = await import('open');
308
- // await open.default(url);
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-alpha.2";
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",
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": {