@maoyugames/phaser-framework 1.0.2 → 1.0.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.
@@ -7,6 +7,7 @@
7
7
  * pf build <platform|all> 构建某平台/全部平台(临时入口 + 编程式 vite + 外壳注入 + 校验)
8
8
  * pf size-check [platform] 产物体积检查(扫 <root>/dist/<p>)
9
9
  * pf verify-isolation [platform] SDK 隔离校验(扫 <root>/dist/<p>)
10
+ * pf verify [platform] [--no-build] 实跑冒烟验证(Playwright 无头:确认渲染 + 无运行期错误)
10
11
  * pf cap <sync|open> [android|ios] Capacitor 同步/打开原生工程
11
12
  * pf help 查看帮助
12
13
  *
package/dist/cli/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import '../chunk-PKBMQBKP.js';
3
- import pc4 from 'picocolors';
4
- import { resolve, join, relative, basename, dirname } from 'path';
3
+ import pc6 from 'picocolors';
4
+ import { resolve, join, extname, relative, basename, dirname } from 'path';
5
5
  import { randomBytes } from 'crypto';
6
6
  import fse4 from 'fs-extra';
7
7
  import { createRequire } from 'module';
8
8
  import { fileURLToPath } from 'url';
9
- import { existsSync, readFileSync } from 'fs';
9
+ import { existsSync, mkdirSync, writeFileSync, readFile, readFileSync } from 'fs';
10
+ import http from 'http';
10
11
  import { spawnSync } from 'child_process';
11
12
 
12
13
  // src/cli/platforms-meta.ts
@@ -538,7 +539,7 @@ function writeShellFiles(distDir, projectRoot, files) {
538
539
  const dst = join(distDir, f.relPath);
539
540
  fse4.ensureDirSync(resolve(dst, ".."));
540
541
  fse4.writeFileSync(dst, f.content, "utf-8");
541
- console.log(pc4.green(` \u2713 ${f.relPath} \u2192 ${relative(projectRoot, dst)}`));
542
+ console.log(pc6.green(` \u2713 ${f.relPath} \u2192 ${relative(projectRoot, dst)}`));
542
543
  }
543
544
  }
544
545
  function injectShell(opts) {
@@ -548,19 +549,19 @@ function injectShell(opts) {
548
549
  case "tiktok":
549
550
  return true;
550
551
  case "facebook": {
551
- console.log(pc4.cyan(` \u25B6 [facebook] \u6CE8\u5165\u5916\u58F3\u6587\u4EF6`));
552
+ console.log(pc6.cyan(` \u25B6 [facebook] \u6CE8\u5165\u5916\u58F3\u6587\u4EF6`));
552
553
  writeShellFiles(distDir, projectRoot, [renderFacebookAppConfig(config.facebook)]);
553
554
  return true;
554
555
  }
555
556
  case "capacitor": {
556
- console.log(pc4.cyan(` \u25B6 [capacitor] \u751F\u6210 capacitor.config.ts(\u9879\u76EE\u6839)`));
557
+ console.log(pc6.cyan(` \u25B6 [capacitor] \u751F\u6210 capacitor.config.ts(\u9879\u76EE\u6839)`));
557
558
  const capCfgPath = resolve(projectRoot, "capacitor.config.ts");
558
559
  fse4.writeFileSync(capCfgPath, renderCapacitorConfig(config.capacitor), "utf-8");
559
- console.log(pc4.green(` \u2713 capacitor.config.ts \u2192 ${relative(projectRoot, capCfgPath)}`));
560
+ console.log(pc6.green(` \u2713 capacitor.config.ts \u2192 ${relative(projectRoot, capCfgPath)}`));
560
561
  return true;
561
562
  }
562
563
  case "wechat": {
563
- console.log(pc4.cyan(` \u25B6 [wechat] \u6CE8\u5165\u5916\u58F3\u6587\u4EF6`));
564
+ console.log(pc6.cyan(` \u25B6 [wechat] \u6CE8\u5165\u5916\u58F3\u6587\u4EF6`));
564
565
  writeShellFiles(distDir, projectRoot, renderWeChatShells(config.wechat));
565
566
  copyPublicAssetsToWechat(projectRoot, distDir);
566
567
  copyWechatVendor(projectRoot, distDir);
@@ -573,37 +574,37 @@ function injectShell(opts) {
573
574
  function copyPublicAssetsToWechat(projectRoot, distDir) {
574
575
  const srcPublic = resolve(projectRoot, "src/game/public");
575
576
  if (!fse4.existsSync(srcPublic)) return;
576
- console.log(pc4.cyan(" \u25B6 [wechat] \u62F7\u8D1D\u4E1A\u52A1\u9759\u6001\u8D44\u6E90(src/game/public)"));
577
+ console.log(pc6.cyan(" \u25B6 [wechat] \u62F7\u8D1D\u4E1A\u52A1\u9759\u6001\u8D44\u6E90(src/game/public)"));
577
578
  fse4.copySync(srcPublic, distDir);
578
- console.log(pc4.green(" \u2713 src/game/public/* \u2192 dist/wechat/"));
579
+ console.log(pc6.green(" \u2713 src/game/public/* \u2192 dist/wechat/"));
579
580
  }
580
581
  function copyWechatVendor(projectRoot, distDir) {
581
- console.log(pc4.cyan(" \u25B6 [wechat] \u68C0\u6D4B\u5FAE\u4FE1\u9002\u914D\u5668(vendor)"));
582
+ console.log(pc6.cyan(" \u25B6 [wechat] \u68C0\u6D4B\u5FAE\u4FE1\u9002\u914D\u5668(vendor)"));
582
583
  const dirs = wechatVendorDirs(projectRoot);
583
584
  const adapter = dirs.map((d) => join(d, "weapp-adapter.js")).find((p) => fse4.existsSync(p));
584
585
  if (adapter) {
585
586
  fse4.copyFileSync(adapter, join(distDir, "weapp-adapter.js"));
586
- console.log(pc4.green(" \u2713 weapp-adapter.js \u2192 dist/wechat/weapp-adapter.js"));
587
+ console.log(pc6.green(" \u2713 weapp-adapter.js \u2192 dist/wechat/weapp-adapter.js"));
587
588
  const vendorDir = resolve(adapter, "..");
588
589
  const symbol = join(vendorDir, "symbol.js");
589
590
  if (fse4.existsSync(symbol)) {
590
591
  fse4.copyFileSync(symbol, join(distDir, "symbol.js"));
591
- console.log(pc4.green(" \u2713 symbol.js \u2192 dist/wechat/symbol.js"));
592
+ console.log(pc6.green(" \u2713 symbol.js \u2192 dist/wechat/symbol.js"));
592
593
  }
593
594
  return true;
594
595
  }
595
- console.log(pc4.yellow(pc4.bold(" \u26A0 \u7F3A\u5C11\u5FAE\u4FE1\u9002\u914D\u5668 weapp-adapter.js")));
596
+ console.log(pc6.yellow(pc6.bold(" \u26A0 \u7F3A\u5C11\u5FAE\u4FE1\u9002\u914D\u5668 weapp-adapter.js")));
596
597
  console.log(
597
- pc4.yellow(
598
+ pc6.yellow(
598
599
  " \u5FAE\u4FE1\u5C0F\u6E38\u620F\u65E0 DOM,\u9700\u6B64\u5B98\u65B9\u9002\u914D\u5668\u6CE8\u5165 canvas/document shim,\u5426\u5219 game.js \u542F\u52A8\u5373\u62A5\u9519\u3002"
599
600
  )
600
601
  );
601
- console.log(pc4.yellow(` \u8BF7\u628A\u5B98\u65B9 weapp-adapter.js(\u53CA\u53EF\u9009 symbol.js)\u653E\u5230\u4EE5\u4E0B\u4EFB\u4E00\u76EE\u5F55\u540E\u91CD\u65B0\u6784\u5EFA:`));
602
+ console.log(pc6.yellow(` \u8BF7\u628A\u5B98\u65B9 weapp-adapter.js(\u53CA\u53EF\u9009 symbol.js)\u653E\u5230\u4EE5\u4E0B\u4EFB\u4E00\u76EE\u5F55\u540E\u91CD\u65B0\u6784\u5EFA:`));
602
603
  for (const d of wechatVendorDirs(projectRoot)) {
603
- console.log(pc4.yellow(` - ${relative(projectRoot, d)}/weapp-adapter.js`));
604
+ console.log(pc6.yellow(` - ${relative(projectRoot, d)}/weapp-adapter.js`));
604
605
  }
605
606
  console.log(
606
- pc4.yellow(" \u83B7\u53D6\u65B9\u5F0F:\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u65B0\u5EFA\u300C\u5C0F\u6E38\u620F\u300D\u9879\u76EE\u6A21\u677F\u91CC\u9644\u5E26 weapp-adapter.js\u3002")
607
+ pc6.yellow(" \u83B7\u53D6\u65B9\u5F0F:\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u65B0\u5EFA\u300C\u5C0F\u6E38\u620F\u300D\u9879\u76EE\u6A21\u677F\u91CC\u9644\u5E26 weapp-adapter.js\u3002")
607
608
  );
608
609
  return false;
609
610
  }
@@ -627,43 +628,43 @@ function findMainBundle(files) {
627
628
  }
628
629
  function checkPlatform(platform, distDir) {
629
630
  if (!fse4.existsSync(distDir)) {
630
- console.log(pc4.gray(` [${platform}] \u672A\u6784\u5EFA,\u8DF3\u8FC7`));
631
+ console.log(pc6.gray(` [${platform}] \u672A\u6784\u5EFA,\u8DF3\u8FC7`));
631
632
  return true;
632
633
  }
633
634
  const files = listFiles(distDir);
634
635
  const total = files.reduce((s, f) => s + f.size, 0);
635
636
  const main2 = findMainBundle(files);
636
637
  const limits = SIZE_LIMITS[platform];
637
- console.log(pc4.cyan(` [${platform}]`));
638
- console.log(` \u603B\u4F53\u79EF:${pc4.bold(formatBytes(total))}(${files.length} \u4E2A\u6587\u4EF6)`);
638
+ console.log(pc6.cyan(` [${platform}]`));
639
+ console.log(` \u603B\u4F53\u79EF:${pc6.bold(formatBytes(total))}(${files.length} \u4E2A\u6587\u4EF6)`);
639
640
  if (main2) {
640
- console.log(` \u4E3B bundle:${basename(main2.path)} = ${pc4.bold(formatBytes(main2.size))}`);
641
+ console.log(` \u4E3B bundle:${basename(main2.path)} = ${pc6.bold(formatBytes(main2.size))}`);
641
642
  }
642
643
  let ok = true;
643
644
  if (limits?.totalHardLimit) {
644
645
  if (total > limits.totalHardLimit) {
645
646
  console.log(
646
- pc4.red(
647
+ pc6.red(
647
648
  ` \u2717 \u8D85\u51FA TikTok \u6574\u5305\u786C\u4E0A\u9650 ${formatBytes(limits.totalHardLimit)}(\u8D85 ${formatBytes(total - limits.totalHardLimit)})`
648
649
  )
649
650
  );
650
651
  ok = false;
651
652
  } else {
652
653
  console.log(
653
- pc4.green(` \u2713 \u5728 TikTok 50MB \u6574\u5305\u4E0A\u9650\u5185(\u4F59 ${formatBytes(limits.totalHardLimit - total)})`)
654
+ pc6.green(` \u2713 \u5728 TikTok 50MB \u6574\u5305\u4E0A\u9650\u5185(\u4F59 ${formatBytes(limits.totalHardLimit - total)})`)
654
655
  );
655
656
  }
656
657
  }
657
658
  if (limits?.mainBundleSoftLimit && main2) {
658
659
  if (main2.size > limits.mainBundleSoftLimit) {
659
660
  console.log(
660
- pc4.yellow(
661
+ pc6.yellow(
661
662
  ` \u26A0 \u5FAE\u4FE1\u4E3B\u5305\u8D85\u8FC7\u5EFA\u8BAE ${formatBytes(limits.mainBundleSoftLimit)}(\u5F53\u524D ${formatBytes(main2.size)});\u5EFA\u8BAE\u62C6\u5206\u5305\u6216\u88C1\u526A\u8D44\u6E90,\u4F46\u4E0D\u963B\u65AD\u6784\u5EFA`
662
663
  )
663
664
  );
664
665
  } else {
665
666
  console.log(
666
- pc4.green(` \u2713 \u5FAE\u4FE1\u4E3B\u5305\u5728 4MB \u5EFA\u8BAE\u503C\u5185(\u4F59 ${formatBytes(limits.mainBundleSoftLimit - main2.size)})`)
667
+ pc6.green(` \u2713 \u5FAE\u4FE1\u4E3B\u5305\u5728 4MB \u5EFA\u8BAE\u503C\u5185(\u4F59 ${formatBytes(limits.mainBundleSoftLimit - main2.size)})`)
667
668
  );
668
669
  }
669
670
  }
@@ -672,29 +673,29 @@ function checkPlatform(platform, distDir) {
672
673
  function runSizeCheck(target) {
673
674
  const ctx = resolveProject();
674
675
  const targets = target ? isPlatform(target) ? [target] : invalidTarget(target) : ALL_PLATFORMS;
675
- console.log(pc4.cyan(pc4.bold("\u25B6 \u4EA7\u7269\u4F53\u79EF\u68C0\u67E5")));
676
+ console.log(pc6.cyan(pc6.bold("\u25B6 \u4EA7\u7269\u4F53\u79EF\u68C0\u67E5")));
676
677
  let allOk = true;
677
678
  for (const p of targets) {
678
679
  const distDir = join(ctx.root, "dist", p);
679
680
  if (!checkPlatform(p, distDir)) allOk = false;
680
681
  }
681
682
  if (!allOk) {
682
- console.log(pc4.red(pc4.bold("\n\u2717 \u4F53\u79EF\u68C0\u67E5\u672A\u901A\u8FC7(\u5B58\u5728\u8D85\u786C\u4E0A\u9650\u7684\u5E73\u53F0)")));
683
+ console.log(pc6.red(pc6.bold("\n\u2717 \u4F53\u79EF\u68C0\u67E5\u672A\u901A\u8FC7(\u5B58\u5728\u8D85\u786C\u4E0A\u9650\u7684\u5E73\u53F0)")));
683
684
  return false;
684
685
  }
685
- console.log(pc4.green(pc4.bold("\n\u2713 \u4F53\u79EF\u68C0\u67E5\u5B8C\u6210")));
686
+ console.log(pc6.green(pc6.bold("\n\u2713 \u4F53\u79EF\u68C0\u67E5\u5B8C\u6210")));
686
687
  return true;
687
688
  }
688
689
  function sizeCheckPlatform(platform, distDir) {
689
- console.log(pc4.cyan(pc4.bold("\u25B6 \u4EA7\u7269\u4F53\u79EF\u68C0\u67E5")));
690
+ console.log(pc6.cyan(pc6.bold("\u25B6 \u4EA7\u7269\u4F53\u79EF\u68C0\u67E5")));
690
691
  const ok = checkPlatform(platform, distDir);
691
- if (ok) console.log(pc4.green(pc4.bold("\u2713 \u4F53\u79EF\u68C0\u67E5\u5B8C\u6210")));
692
- else console.log(pc4.red(pc4.bold("\u2717 \u4F53\u79EF\u68C0\u67E5\u672A\u901A\u8FC7")));
692
+ if (ok) console.log(pc6.green(pc6.bold("\u2713 \u4F53\u79EF\u68C0\u67E5\u5B8C\u6210")));
693
+ else console.log(pc6.red(pc6.bold("\u2717 \u4F53\u79EF\u68C0\u67E5\u672A\u901A\u8FC7")));
693
694
  return ok;
694
695
  }
695
696
  function invalidTarget(target) {
696
- console.log(pc4.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
697
- console.log(pc4.gray(`\u53EF\u9009:${ALL_PLATFORMS.join(" / ")}`));
697
+ console.log(pc6.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
698
+ console.log(pc6.gray(`\u53EF\u9009:${ALL_PLATFORMS.join(" / ")}`));
698
699
  process.exit(1);
699
700
  }
700
701
  function collectJsFiles(dir) {
@@ -716,12 +717,12 @@ function snippetAround(text, index, span = 60) {
716
717
  function verifyPlatform(platform, distDir, projectRoot) {
717
718
  const rules = ISOLATION_RULES[platform];
718
719
  if (!fse4.existsSync(distDir)) {
719
- console.log(pc4.gray(` [${platform}] \u672A\u6784\u5EFA(${relative(projectRoot, distDir)} \u4E0D\u5B58\u5728),\u8DF3\u8FC7`));
720
+ console.log(pc6.gray(` [${platform}] \u672A\u6784\u5EFA(${relative(projectRoot, distDir)} \u4E0D\u5B58\u5728),\u8DF3\u8FC7`));
720
721
  return 0;
721
722
  }
722
723
  const files = collectJsFiles(distDir);
723
724
  if (files.length === 0) {
724
- console.log(pc4.yellow(` [${platform}] \u672A\u53D1\u73B0 JS \u4EA7\u7269,\u8DF3\u8FC7`));
725
+ console.log(pc6.yellow(` [${platform}] \u672A\u53D1\u73B0 JS \u4EA7\u7269,\u8DF3\u8FC7`));
725
726
  return 0;
726
727
  }
727
728
  let hits = 0;
@@ -735,13 +736,13 @@ function verifyPlatform(platform, distDir, projectRoot) {
735
736
  hits++;
736
737
  perSig++;
737
738
  const rel = relative(projectRoot, file);
738
- console.log(pc4.red(` \u2717 [${platform}] \u547D\u4E2D\u7981\u7528 SDK \u7B7E\u540D ${pc4.bold(sig)}`));
739
- console.log(pc4.gray(` \u6587\u4EF6:${rel}`));
740
- console.log(pc4.gray(` \u7247\u6BB5:\u2026${snippetAround(text, idx)}\u2026`));
739
+ console.log(pc6.red(` \u2717 [${platform}] \u547D\u4E2D\u7981\u7528 SDK \u7B7E\u540D ${pc6.bold(sig)}`));
740
+ console.log(pc6.gray(` \u6587\u4EF6:${rel}`));
741
+ console.log(pc6.gray(` \u7247\u6BB5:\u2026${snippetAround(text, idx)}\u2026`));
741
742
  from = idx + sig.length;
742
743
  idx = text.indexOf(sig, from);
743
744
  if (perSig >= 3 && idx !== -1) {
744
- console.log(pc4.gray(` (\u8BE5\u7B7E\u540D\u5728\u672C\u6587\u4EF6\u8FD8\u6709\u66F4\u591A\u547D\u4E2D,\u5DF2\u6298\u53E0)`));
745
+ console.log(pc6.gray(` (\u8BE5\u7B7E\u540D\u5728\u672C\u6587\u4EF6\u8FD8\u6709\u66F4\u591A\u547D\u4E2D,\u5DF2\u6298\u53E0)`));
745
746
  break;
746
747
  }
747
748
  }
@@ -749,7 +750,7 @@ function verifyPlatform(platform, distDir, projectRoot) {
749
750
  }
750
751
  if (hits === 0) {
751
752
  console.log(
752
- pc4.green(` \u2713 [${platform}] \u9694\u79BB\u901A\u8FC7(\u626B\u63CF ${files.length} \u4E2A JS \u6587\u4EF6,\u65E0\u5176\u5B83\u5E73\u53F0 SDK \u7B7E\u540D)`)
753
+ pc6.green(` \u2713 [${platform}] \u9694\u79BB\u901A\u8FC7(\u626B\u63CF ${files.length} \u4E2A JS \u6587\u4EF6,\u65E0\u5176\u5B83\u5E73\u53F0 SDK \u7B7E\u540D)`)
753
754
  );
754
755
  }
755
756
  return hits;
@@ -757,32 +758,32 @@ function verifyPlatform(platform, distDir, projectRoot) {
757
758
  function runVerifyIsolation(target) {
758
759
  const ctx = resolveProject();
759
760
  const targets = target ? isPlatform(target) ? [target] : invalidTarget2(target) : ALL_PLATFORMS;
760
- console.log(pc4.cyan(pc4.bold("\u25B6 SDK \u9694\u79BB\u6821\u9A8C")));
761
+ console.log(pc6.cyan(pc6.bold("\u25B6 SDK \u9694\u79BB\u6821\u9A8C")));
761
762
  let totalHits = 0;
762
763
  for (const p of targets) {
763
764
  totalHits += verifyPlatform(p, join(ctx.root, "dist", p), ctx.root);
764
765
  }
765
766
  if (totalHits > 0) {
766
- console.log(pc4.red(pc4.bold(`
767
+ console.log(pc6.red(pc6.bold(`
767
768
  \u2717 \u9694\u79BB\u6821\u9A8C\u5931\u8D25:\u5171\u53D1\u73B0 ${totalHits} \u5904\u8DE8\u5E73\u53F0 SDK \u7B7E\u540D\u6CC4\u6F0F`)));
768
769
  return false;
769
770
  }
770
- console.log(pc4.green(pc4.bold("\n\u2713 \u9694\u79BB\u6821\u9A8C\u5168\u90E8\u901A\u8FC7")));
771
+ console.log(pc6.green(pc6.bold("\n\u2713 \u9694\u79BB\u6821\u9A8C\u5168\u90E8\u901A\u8FC7")));
771
772
  return true;
772
773
  }
773
774
  function verifyIsolationPlatform(platform, distDir, projectRoot) {
774
- console.log(pc4.cyan(pc4.bold("\u25B6 SDK \u9694\u79BB\u6821\u9A8C")));
775
+ console.log(pc6.cyan(pc6.bold("\u25B6 SDK \u9694\u79BB\u6821\u9A8C")));
775
776
  const hits = verifyPlatform(platform, distDir, projectRoot);
776
777
  if (hits > 0) {
777
- console.log(pc4.red(pc4.bold(`\u2717 \u9694\u79BB\u6821\u9A8C\u5931\u8D25:${hits} \u5904\u8DE8\u5E73\u53F0 SDK \u7B7E\u540D\u6CC4\u6F0F`)));
778
+ console.log(pc6.red(pc6.bold(`\u2717 \u9694\u79BB\u6821\u9A8C\u5931\u8D25:${hits} \u5904\u8DE8\u5E73\u53F0 SDK \u7B7E\u540D\u6CC4\u6F0F`)));
778
779
  return false;
779
780
  }
780
- console.log(pc4.green(pc4.bold("\u2713 \u9694\u79BB\u6821\u9A8C\u901A\u8FC7")));
781
+ console.log(pc6.green(pc6.bold("\u2713 \u9694\u79BB\u6821\u9A8C\u901A\u8FC7")));
781
782
  return true;
782
783
  }
783
784
  function invalidTarget2(target) {
784
- console.log(pc4.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
785
- console.log(pc4.gray(`\u53EF\u9009:${ALL_PLATFORMS.join(" / ")}`));
785
+ console.log(pc6.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
786
+ console.log(pc6.gray(`\u53EF\u9009:${ALL_PLATFORMS.join(" / ")}`));
786
787
  process.exit(1);
787
788
  }
788
789
 
@@ -928,7 +929,7 @@ function findFirstHtml(dir) {
928
929
  return void 0;
929
930
  }
930
931
  async function buildPlatform(platform, ctx, config, vite) {
931
- console.log(pc4.bgCyan(pc4.black(` \u6784\u5EFA\u5E73\u53F0:${platform} `)));
932
+ console.log(pc6.bgCyan(pc6.black(` \u6784\u5EFA\u5E73\u53F0:${platform} `)));
932
933
  const cache = cacheDir(ctx.root);
933
934
  fse4.ensureDirSync(cache);
934
935
  const hash = randomBytes(4).toString("hex");
@@ -948,7 +949,7 @@ async function buildPlatform(platform, ctx, config, vite) {
948
949
  tempFiles.push(htmlPath);
949
950
  inputFile = htmlPath;
950
951
  }
951
- console.log(pc4.cyan(` \u25B6 [${platform}] vite build`));
952
+ console.log(pc6.cyan(` \u25B6 [${platform}] vite build`));
952
953
  const inlineConfig = createPlatformConfig(platform, {
953
954
  projectRoot: ctx.root,
954
955
  viteRoot: platform === "wechat" ? ctx.root : cache,
@@ -961,22 +962,22 @@ async function buildPlatform(platform, ctx, config, vite) {
961
962
  if (platform !== "wechat") {
962
963
  normalizeHtmlOutput(distDir);
963
964
  }
964
- console.log(pc4.green(` \u2713 [${platform}] vite build \u5B8C\u6210 \u2192 ${relative(ctx.root, distDir)}`));
965
+ console.log(pc6.green(` \u2713 [${platform}] vite build \u5B8C\u6210 \u2192 ${relative(ctx.root, distDir)}`));
965
966
  const injectOk = injectShell({ platform, projectRoot: ctx.root, distDir, config });
966
967
  const sizeOk = sizeCheckPlatform(platform, distDir);
967
968
  const isoOk = verifyIsolationPlatform(platform, distDir, ctx.root);
968
969
  const allOk = injectOk && sizeOk && isoOk;
969
970
  if (allOk) {
970
- console.log(pc4.green(pc4.bold(`\u2713 [${platform}] \u6784\u5EFA + \u6821\u9A8C\u5B8C\u6210 \u2192 ${relative(ctx.root, distDir)}
971
+ console.log(pc6.green(pc6.bold(`\u2713 [${platform}] \u6784\u5EFA + \u6821\u9A8C\u5B8C\u6210 \u2192 ${relative(ctx.root, distDir)}
971
972
  `)));
972
973
  } else {
973
- console.log(pc4.red(pc4.bold(`\u2717 [${platform}] \u6821\u9A8C/\u6CE8\u5165\u672A\u5168\u90E8\u901A\u8FC7
974
+ console.log(pc6.red(pc6.bold(`\u2717 [${platform}] \u6821\u9A8C/\u6CE8\u5165\u672A\u5168\u90E8\u901A\u8FC7
974
975
  `)));
975
976
  }
976
977
  return allOk;
977
978
  } catch (e) {
978
- console.log(pc4.red(` \u2717 [${platform}] vite build \u5931\u8D25:${e.message}`));
979
- if (e.stack) console.log(pc4.gray(e.stack ?? ""));
979
+ console.log(pc6.red(` \u2717 [${platform}] vite build \u5931\u8D25:${e.message}`));
980
+ if (e.stack) console.log(pc6.gray(e.stack ?? ""));
980
981
  return false;
981
982
  } finally {
982
983
  for (const f of tempFiles) {
@@ -989,37 +990,37 @@ async function buildPlatform(platform, ctx, config, vite) {
989
990
  }
990
991
  async function runBuild(target) {
991
992
  if (!target) {
992
- console.log(pc4.red("\u7528\u6CD5:pf build <web|tiktok|wechat|facebook|capacitor|all>"));
993
+ console.log(pc6.red("\u7528\u6CD5:pf build <web|tiktok|wechat|facebook|capacitor|all>"));
993
994
  return false;
994
995
  }
995
996
  const platforms = target === "all" ? ALL_PLATFORMS : isPlatform(target) ? [target] : null;
996
997
  if (!platforms) {
997
- console.log(pc4.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
998
- console.log(pc4.gray(`\u53EF\u9009:${ALL_PLATFORMS.join(" / ")} / all`));
998
+ console.log(pc6.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
999
+ console.log(pc6.gray(`\u53EF\u9009:${ALL_PLATFORMS.join(" / ")} / all`));
999
1000
  return false;
1000
1001
  }
1001
1002
  const ctx = resolveProject();
1002
1003
  const config = await loadPlatformConfig(ctx);
1003
1004
  const vite = await loadViteFromRoot(ctx.root);
1004
- console.log(pc4.cyan(pc4.bold(`\u25B6 \u5F00\u59CB\u6784\u5EFA:${platforms.join(", ")}(\u9879\u76EE:${ctx.root})
1005
+ console.log(pc6.cyan(pc6.bold(`\u25B6 \u5F00\u59CB\u6784\u5EFA:${platforms.join(", ")}(\u9879\u76EE:${ctx.root})
1005
1006
  `)));
1006
1007
  const results = [];
1007
1008
  for (const p of platforms) {
1008
1009
  results.push({ platform: p, ok: await buildPlatform(p, ctx, config, vite) });
1009
1010
  }
1010
- console.log(pc4.cyan(pc4.bold("\u25B6 \u6784\u5EFA\u6C47\u603B")));
1011
+ console.log(pc6.cyan(pc6.bold("\u25B6 \u6784\u5EFA\u6C47\u603B")));
1011
1012
  let allOk = true;
1012
1013
  for (const r of results) {
1013
- if (r.ok) console.log(pc4.green(` \u2713 ${r.platform}`));
1014
+ if (r.ok) console.log(pc6.green(` \u2713 ${r.platform}`));
1014
1015
  else {
1015
- console.log(pc4.red(` \u2717 ${r.platform}`));
1016
+ console.log(pc6.red(` \u2717 ${r.platform}`));
1016
1017
  allOk = false;
1017
1018
  }
1018
1019
  }
1019
1020
  if (!allOk) {
1020
- console.log(pc4.red(pc4.bold("\n\u2717 \u5B58\u5728\u6784\u5EFA/\u6821\u9A8C\u5931\u8D25\u7684\u5E73\u53F0")));
1021
+ console.log(pc6.red(pc6.bold("\n\u2717 \u5B58\u5728\u6784\u5EFA/\u6821\u9A8C\u5931\u8D25\u7684\u5E73\u53F0")));
1021
1022
  } else {
1022
- console.log(pc4.green(pc4.bold("\n\u2713 \u5168\u90E8\u5B8C\u6210")));
1023
+ console.log(pc6.green(pc6.bold("\n\u2713 \u5168\u90E8\u5B8C\u6210")));
1023
1024
  }
1024
1025
  return allOk;
1025
1026
  }
@@ -1043,8 +1044,8 @@ function renderHtmlShell2(platform, config, scriptSrc) {
1043
1044
  async function runDev(target) {
1044
1045
  const platform = target ? isPlatform(target) ? target : invalidTarget3(target) : "web";
1045
1046
  if (platform === "wechat") {
1046
- console.log(pc4.red("\u2717 wechat \u65E0 HTML dev server \u5F62\u6001\u3002"));
1047
- console.log(pc4.yellow(" \u8BF7\u7528 `pf build wechat` \u4EA7\u51FA dist/wechat,\u518D\u7528\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u6253\u5F00\u9884\u89C8\u3002"));
1047
+ console.log(pc6.red("\u2717 wechat \u65E0 HTML dev server \u5F62\u6001\u3002"));
1048
+ console.log(pc6.yellow(" \u8BF7\u7528 `pf build wechat` \u4EA7\u51FA dist/wechat,\u518D\u7528\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u6253\u5F00\u9884\u89C8\u3002"));
1048
1049
  return false;
1049
1050
  }
1050
1051
  const ctx = resolveProject();
@@ -1096,9 +1097,9 @@ async function runDev(target) {
1096
1097
  await server.listen();
1097
1098
  const info = server.resolvedUrls;
1098
1099
  const url = info?.local?.[0] ?? "http://localhost:5173/";
1099
- console.log(pc4.green(pc4.bold(`\u25B6 [${platform}] dev server \u5C31\u7EEA`)));
1100
- console.log(` \u6253\u5F00:${pc4.cyan(url)}`);
1101
- console.log(pc4.gray(" (Ctrl+C \u9000\u51FA,\u4E34\u65F6\u5165\u53E3\u4F1A\u81EA\u52A8\u6E05\u7406)"));
1100
+ console.log(pc6.green(pc6.bold(`\u25B6 [${platform}] dev server \u5C31\u7EEA`)));
1101
+ console.log(` \u6253\u5F00:${pc6.cyan(url)}`);
1102
+ console.log(pc6.gray(" (Ctrl+C \u9000\u51FA,\u4E34\u65F6\u5165\u53E3\u4F1A\u81EA\u52A8\u6E05\u7406)"));
1102
1103
  const onExit = async () => {
1103
1104
  cleanup();
1104
1105
  await server.close();
@@ -1109,17 +1110,227 @@ async function runDev(target) {
1109
1110
  return true;
1110
1111
  } catch (e) {
1111
1112
  cleanup();
1112
- console.log(pc4.red(`\u2717 dev server \u542F\u52A8\u5931\u8D25:${e.message}`));
1113
+ console.log(pc6.red(`\u2717 dev server \u542F\u52A8\u5931\u8D25:${e.message}`));
1113
1114
  return false;
1114
1115
  }
1115
1116
  }
1116
1117
  function invalidTarget3(target) {
1117
- console.log(pc4.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
1118
+ console.log(pc6.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
1119
+ process.exit(1);
1120
+ }
1121
+ var CONTENT_TYPES = {
1122
+ ".html": "text/html; charset=utf-8",
1123
+ ".js": "text/javascript; charset=utf-8",
1124
+ ".mjs": "text/javascript; charset=utf-8",
1125
+ ".json": "application/json; charset=utf-8",
1126
+ ".css": "text/css; charset=utf-8",
1127
+ ".png": "image/png",
1128
+ ".jpg": "image/jpeg",
1129
+ ".jpeg": "image/jpeg",
1130
+ ".webp": "image/webp",
1131
+ ".svg": "image/svg+xml",
1132
+ ".wasm": "application/wasm"
1133
+ };
1134
+ function serveDist(distDir) {
1135
+ const server = http.createServer((req, res) => {
1136
+ let p = decodeURIComponent((req.url ?? "/").split("?")[0]);
1137
+ if (p === "/") p = "/index.html";
1138
+ if (p === "/favicon.ico") {
1139
+ res.statusCode = 204;
1140
+ res.end();
1141
+ return;
1142
+ }
1143
+ const fp = join(distDir, p);
1144
+ if (!resolve(fp).startsWith(resolve(distDir))) {
1145
+ res.statusCode = 403;
1146
+ res.end("forbidden");
1147
+ return;
1148
+ }
1149
+ readFile(fp, (err, data) => {
1150
+ if (err) {
1151
+ res.statusCode = 404;
1152
+ res.end("not found");
1153
+ return;
1154
+ }
1155
+ res.setHeader("Content-Type", CONTENT_TYPES[extname(fp).toLowerCase()] ?? "application/octet-stream");
1156
+ res.end(data);
1157
+ });
1158
+ });
1159
+ return new Promise((r) => {
1160
+ server.listen(0, "127.0.0.1", () => {
1161
+ const addr = server.address();
1162
+ const port = typeof addr === "object" && addr ? addr.port : 0;
1163
+ r({ server, port });
1164
+ });
1165
+ });
1166
+ }
1167
+ function resolvePlaywright(root) {
1168
+ const req = createRequire(join(root, "package.json"));
1169
+ for (const name of ["playwright", "playwright-core", "@playwright/test"]) {
1170
+ try {
1171
+ const mod = req(name);
1172
+ if (mod && mod.chromium) return { chromium: mod.chromium };
1173
+ } catch {
1174
+ }
1175
+ }
1176
+ return null;
1177
+ }
1178
+ async function readViewport(ctx) {
1179
+ try {
1180
+ const mod = await loadConfigModule(ctx.root, ctx.gameConfigPath);
1181
+ const cfg = mod.gameConfig ?? mod.default ?? {};
1182
+ const width = Number(cfg.designWidth) || 720;
1183
+ const height = Number(cfg.designHeight) || 1280;
1184
+ return { width, height };
1185
+ } catch {
1186
+ return { width: 720, height: 1280 };
1187
+ }
1188
+ }
1189
+ async function runVerify(target, opts = {}) {
1190
+ const platform = target ? isPlatform(target) ? target : invalid(target) : "web";
1191
+ if (platform === "wechat") {
1192
+ console.log(pc6.yellow("\u26A0 wechat \u65E0 DOM\u3001\u9700\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177,\u65E0\u6CD5\u7528\u6D4F\u89C8\u5668 verify\u3002"));
1193
+ console.log(pc6.gray(" \u8BF7 `pf build wechat` \u540E\u7528\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u6253\u5F00 dist/wechat \u9884\u89C8\u9A8C\u8BC1\u3002"));
1194
+ return false;
1195
+ }
1196
+ const ctx = resolveProject();
1197
+ const pw = resolvePlaywright(ctx.root);
1198
+ if (!pw) {
1199
+ console.log(pc6.red("\u2717 pf verify \u9700\u8981 Playwright(\u53EF\u9009\u4F9D\u8D56,\u672A\u5B89\u88C5)\u3002"));
1200
+ console.log(pc6.yellow(" \u5B89\u88C5:") + pc6.cyan(" npm i -D playwright"));
1201
+ console.log(pc6.gray(" \u6D4F\u89C8\u5668\u4F18\u5148\u7528\u7CFB\u7EDF Chrome(channel:chrome),\u901A\u5E38\u65E0\u9700 `npx playwright install`\u3002"));
1202
+ return false;
1203
+ }
1204
+ if (!opts.noBuild) {
1205
+ console.log(pc6.cyan(pc6.bold(`\u25B6 [verify] \u5148\u6784\u5EFA ${platform}`)));
1206
+ const built = await runBuild(platform);
1207
+ if (!built) {
1208
+ console.log(pc6.red("\u2717 [verify] \u6784\u5EFA\u5931\u8D25,\u7EC8\u6B62\u9A8C\u8BC1"));
1209
+ return false;
1210
+ }
1211
+ }
1212
+ const distDir = resolve(ctx.root, "dist", platform);
1213
+ if (!existsSync(join(distDir, "index.html"))) {
1214
+ console.log(pc6.red(`\u2717 [verify] \u627E\u4E0D\u5230 ${join(distDir, "index.html")}(\u5148 build \u6216\u53BB\u6389 --no-build)`));
1215
+ return false;
1216
+ }
1217
+ const viewport = await readViewport(ctx);
1218
+ const { server, port } = await serveDist(distDir);
1219
+ const url = `http://127.0.0.1:${port}/`;
1220
+ console.log(pc6.cyan(pc6.bold(`\u25B6 [verify] \u65E0\u5934\u6D4F\u89C8\u5668\u6253\u5F00 ${platform} \u4EA7\u7269`)));
1221
+ const launchArgs = [
1222
+ "--no-sandbox",
1223
+ "--use-gl=angle",
1224
+ "--use-angle=swiftshader",
1225
+ "--enable-unsafe-swiftshader",
1226
+ "--ignore-gpu-blocklist"
1227
+ ];
1228
+ let browser;
1229
+ try {
1230
+ browser = await pw.chromium.launch({ channel: "chrome", headless: true, args: launchArgs });
1231
+ } catch {
1232
+ try {
1233
+ browser = await pw.chromium.launch({ headless: true, args: launchArgs });
1234
+ } catch (e) {
1235
+ server.close();
1236
+ console.log(pc6.red(`\u2717 [verify] \u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25:${e.message}`));
1237
+ console.log(pc6.yellow(" \u88C5\u7CFB\u7EDF Chrome,\u6216\u8FD0\u884C `npx playwright install chromium`\u3002"));
1238
+ return false;
1239
+ }
1240
+ }
1241
+ const errors = [];
1242
+ let rendered = false;
1243
+ let distinctColors = 0;
1244
+ let canvasInView = false;
1245
+ let canvasInfo = null;
1246
+ const outDir = join(ctx.root, ".pf-verify");
1247
+ mkdirSync(outDir, { recursive: true });
1248
+ const shot = join(outDir, `${platform}.png`);
1249
+ try {
1250
+ const page = await browser.newPage({ viewport });
1251
+ page.on("pageerror", (e) => errors.push("PAGEERROR: " + e.message));
1252
+ page.on("console", (m) => {
1253
+ if (m.type() === "error" && !/favicon/i.test(m.text())) errors.push("CONSOLE: " + m.text());
1254
+ });
1255
+ await page.goto(url, { waitUntil: "load", timeout: 3e4 });
1256
+ for (let i = 0; i < 20; i++) {
1257
+ await page.waitForTimeout(1e3);
1258
+ const buf = await page.screenshot({ type: "png" }).catch(() => null);
1259
+ if (buf) writeFileSync(shot, buf);
1260
+ if (buf) {
1261
+ const dataUrl = "data:image/png;base64," + buf.toString("base64");
1262
+ distinctColors = await page.evaluate(async (u) => {
1263
+ const img = new Image();
1264
+ img.src = u;
1265
+ await img.decode();
1266
+ const cv = document.createElement("canvas");
1267
+ cv.width = img.width;
1268
+ cv.height = img.height;
1269
+ const cx = cv.getContext("2d");
1270
+ if (!cx) return 0;
1271
+ cx.drawImage(img, 0, 0);
1272
+ const data = cx.getImageData(0, 0, cv.width, cv.height).data;
1273
+ const colors = /* @__PURE__ */ new Set();
1274
+ const total = cv.width * cv.height;
1275
+ const step = Math.max(1, Math.floor(total / 5e3));
1276
+ for (let k = 0; k < total && colors.size < 40; k += step) {
1277
+ const o = k * 4;
1278
+ colors.add(`${data[o]},${data[o + 1]},${data[o + 2]}`);
1279
+ }
1280
+ return colors.size;
1281
+ }, dataUrl).catch(() => 0);
1282
+ }
1283
+ const vis = await page.evaluate(() => {
1284
+ const c = document.querySelector("canvas");
1285
+ if (!c) return { has: false, inView: false, w: 0, h: 0 };
1286
+ const r = c.getBoundingClientRect();
1287
+ const st = getComputedStyle(c);
1288
+ const inView = r.bottom > 0 && r.right > 0 && r.top < innerHeight && r.left < innerWidth && r.width > 1 && r.height > 1 && st.display !== "none" && st.visibility !== "hidden" && Number(st.opacity) > 0;
1289
+ return { has: true, inView, w: Math.round(r.width), h: Math.round(r.height) };
1290
+ }).catch(() => ({ has: false, inView: false, w: 0, h: 0 }));
1291
+ canvasInfo = vis.has ? { w: vis.w, h: vis.h } : canvasInfo;
1292
+ canvasInView = vis.inView;
1293
+ if (distinctColors > 3 && canvasInView) {
1294
+ rendered = true;
1295
+ break;
1296
+ }
1297
+ }
1298
+ console.log(pc6.gray(` \u622A\u56FE(\u5C4F\u5E55\u5B9E\u62CD):${shot}`));
1299
+ } catch (e) {
1300
+ errors.push("VERIFY: " + e.message);
1301
+ } finally {
1302
+ await browser.close();
1303
+ server.close();
1304
+ }
1305
+ const ok = errors.length === 0 && rendered;
1306
+ console.log("");
1307
+ console.log(pc6.bold("\u25B6 [verify] \u7ED3\u679C"));
1308
+ console.log(
1309
+ ` canvas:${canvasInfo ? `${canvasInfo.w}\xD7${canvasInfo.h}` : "\u65E0"};\u5728\u89C6\u53E3\u5185:${canvasInView ? "\u662F" : "\u5426"};\u5C4F\u5E55\u989C\u8272\u6570:${distinctColors}(>3 \u89C6\u4E3A\u5DF2\u6E32\u67D3)`
1310
+ );
1311
+ if (errors.length) {
1312
+ console.log(pc6.red(` \u63A7\u5236\u53F0/\u9875\u9762\u9519\u8BEF(${errors.length}):`));
1313
+ errors.slice(0, 12).forEach((e) => console.log(pc6.red(" " + e)));
1314
+ } else {
1315
+ console.log(pc6.green(" \u65E0\u63A7\u5236\u53F0/\u9875\u9762\u9519\u8BEF"));
1316
+ }
1317
+ if (ok) {
1318
+ console.log(pc6.green(pc6.bold(`
1319
+ \u2713 [verify] ${platform} \u5B9E\u8DD1\u901A\u8FC7(\u5C4F\u5E55\u6E32\u67D3\u6B63\u5E38\u3001\u65E0\u8FD0\u884C\u671F\u9519\u8BEF)`)));
1320
+ } else {
1321
+ const reason = errors.length ? "\u5B58\u5728\u8FD0\u884C\u671F\u9519\u8BEF" : !canvasInView ? "canvas \u4E0D\u5728\u89C6\u53E3\u5185(\u88AB\u5E03\u5C40\u63A8\u51FA\u5C4F\u5E55/\u9690\u85CF \u2192 \u7528\u6237\u770B\u5230\u9ED1\u5C4F)" : "\u5C4F\u5E55\u753B\u9762\u672A\u6E32\u67D3\u51FA\u5185\u5BB9(\u7591\u4F3C\u5D29\u6E83/\u9ED1\u5C4F)";
1322
+ console.log(pc6.red(pc6.bold(`
1323
+ \u2717 [verify] ${platform} \u672A\u901A\u8FC7:${reason}`)));
1324
+ }
1325
+ return ok;
1326
+ }
1327
+ function invalid(target) {
1328
+ console.log(pc6.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
1118
1329
  process.exit(1);
1119
1330
  }
1120
1331
  function spawnCap(projectRoot, args) {
1121
1332
  const npx = process.platform === "win32" ? "npx.cmd" : "npx";
1122
- console.log(pc4.gray(` $ ${npx} cap ${args.join(" ")}`));
1333
+ console.log(pc6.gray(` $ ${npx} cap ${args.join(" ")}`));
1123
1334
  const res = spawnSync(npx, ["cap", ...args], {
1124
1335
  cwd: projectRoot,
1125
1336
  stdio: "inherit",
@@ -1127,9 +1338,9 @@ function spawnCap(projectRoot, args) {
1127
1338
  shell: process.platform === "win32"
1128
1339
  });
1129
1340
  if (res.error) {
1130
- console.log(pc4.red(` \u2717 \u6267\u884C\u5931\u8D25:${res.error.message}`));
1341
+ console.log(pc6.red(` \u2717 \u6267\u884C\u5931\u8D25:${res.error.message}`));
1131
1342
  console.log(
1132
- pc4.yellow(
1343
+ pc6.yellow(
1133
1344
  " \u8BF7\u786E\u8BA4\u5DF2\u5B89\u88C5 Capacitor CLI:npm i -D @capacitor/cli @capacitor/android @capacitor/ios"
1134
1345
  )
1135
1346
  );
@@ -1140,11 +1351,11 @@ function spawnCap(projectRoot, args) {
1140
1351
  function runCap(action, platform) {
1141
1352
  const ctx = resolveProject();
1142
1353
  if (action === "sync") {
1143
- console.log(pc4.cyan(pc4.bold("\u25B6 Capacitor sync")));
1354
+ console.log(pc6.cyan(pc6.bold("\u25B6 Capacitor sync")));
1144
1355
  const distDir = resolve(ctx.root, "dist", "capacitor");
1145
1356
  if (!fse4.existsSync(distDir)) {
1146
- console.log(pc4.red("\u2717 dist/capacitor \u4E0D\u5B58\u5728"));
1147
- console.log(pc4.yellow(" \u8BF7\u5148\u6784\u5EFA:pf build capacitor"));
1357
+ console.log(pc6.red("\u2717 dist/capacitor \u4E0D\u5B58\u5728"));
1358
+ console.log(pc6.yellow(" \u8BF7\u5148\u6784\u5EFA:pf build capacitor"));
1148
1359
  return false;
1149
1360
  }
1150
1361
  const args = platform ? ["sync", platform] : ["sync"];
@@ -1152,13 +1363,13 @@ function runCap(action, platform) {
1152
1363
  }
1153
1364
  if (action === "open") {
1154
1365
  if (platform !== "android" && platform !== "ios") {
1155
- console.log(pc4.red("\u2717 open \u9700\u6307\u5B9A\u5E73\u53F0:pf cap open <android|ios>"));
1366
+ console.log(pc6.red("\u2717 open \u9700\u6307\u5B9A\u5E73\u53F0:pf cap open <android|ios>"));
1156
1367
  return false;
1157
1368
  }
1158
- console.log(pc4.cyan(pc4.bold(`\u25B6 Capacitor open ${platform}`)));
1369
+ console.log(pc6.cyan(pc6.bold(`\u25B6 Capacitor open ${platform}`)));
1159
1370
  return capExec(ctx.root, ["open", platform]);
1160
1371
  }
1161
- console.log(pc4.red("\u7528\u6CD5:"));
1372
+ console.log(pc6.red("\u7528\u6CD5:"));
1162
1373
  console.log(" pf cap sync [android|ios]");
1163
1374
  console.log(" pf cap open <android|ios>");
1164
1375
  return false;
@@ -1197,35 +1408,43 @@ async function main(argv = process.argv.slice(2)) {
1197
1408
  process.exitCode = ok ? 0 : 1;
1198
1409
  return;
1199
1410
  }
1411
+ case "verify": {
1412
+ const noBuild = rest.includes("--no-build");
1413
+ const platform = rest.find((a) => !a.startsWith("-"));
1414
+ const ok = await runVerify(platform, { noBuild });
1415
+ process.exitCode = ok ? 0 : 1;
1416
+ return;
1417
+ }
1200
1418
  case "cap": {
1201
1419
  const ok = runCap(rest[0], rest[1]);
1202
1420
  process.exitCode = ok ? 0 : 1;
1203
1421
  return;
1204
1422
  }
1205
1423
  default: {
1206
- console.log(pc4.yellow(`[pf] \u672A\u77E5\u547D\u4EE4 "${command}"\u3002`));
1207
- console.log(pc4.dim("\u8FD0\u884C `pf help` \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002"));
1424
+ console.log(pc6.yellow(`[pf] \u672A\u77E5\u547D\u4EE4 "${command}"\u3002`));
1425
+ console.log(pc6.dim("\u8FD0\u884C `pf help` \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002"));
1208
1426
  process.exitCode = 1;
1209
1427
  }
1210
1428
  }
1211
1429
  } catch (e) {
1212
- console.log(pc4.red(`[pf] \u6267\u884C "${command}" \u51FA\u9519:${e.message}`));
1213
- if (e.stack) console.log(pc4.gray(e.stack ?? ""));
1430
+ console.log(pc6.red(`[pf] \u6267\u884C "${command}" \u51FA\u9519:${e.message}`));
1431
+ if (e.stack) console.log(pc6.gray(e.stack ?? ""));
1214
1432
  process.exitCode = 1;
1215
1433
  }
1216
1434
  }
1217
1435
  function printHelp() {
1218
- console.log(pc4.bold("@maoyugames/phaser-framework CLI (pf)"));
1219
- console.log(pc4.dim("\u591A\u5E73\u53F0 Phaser \u6846\u67B6\u6784\u5EFA\u5DE5\u5177\u3002"));
1436
+ console.log(pc6.bold("@maoyugames/phaser-framework CLI (pf)"));
1437
+ console.log(pc6.dim("\u591A\u5E73\u53F0 Phaser \u6846\u67B6\u6784\u5EFA\u5DE5\u5177\u3002"));
1220
1438
  console.log("");
1221
1439
  console.log("\u547D\u4EE4:");
1222
- console.log(` ${pc4.cyan("pf dev [platform]")} \u542F\u52A8\u67D0\u5E73\u53F0 dev server(\u9ED8\u8BA4 web)`);
1223
- console.log(` ${pc4.cyan("pf build <platform|all>")} \u6784\u5EFA\u67D0\u5E73\u53F0/\u5168\u90E8\u5E73\u53F0`);
1224
- console.log(` ${pc4.cyan("pf size-check [platform]")} \u4EA7\u7269\u4F53\u79EF\u68C0\u67E5`);
1225
- console.log(` ${pc4.cyan("pf verify-isolation [platform]")} SDK \u9694\u79BB\u6821\u9A8C`);
1226
- console.log(` ${pc4.cyan("pf cap <sync|open> [android|ios]")} Capacitor \u539F\u751F\u5DE5\u7A0B\u64CD\u4F5C`);
1440
+ console.log(` ${pc6.cyan("pf dev [platform]")} \u542F\u52A8\u67D0\u5E73\u53F0 dev server(\u9ED8\u8BA4 web)`);
1441
+ console.log(` ${pc6.cyan("pf build <platform|all>")} \u6784\u5EFA\u67D0\u5E73\u53F0/\u5168\u90E8\u5E73\u53F0`);
1442
+ console.log(` ${pc6.cyan("pf size-check [platform]")} \u4EA7\u7269\u4F53\u79EF\u68C0\u67E5`);
1443
+ console.log(` ${pc6.cyan("pf verify-isolation [platform]")} SDK \u9694\u79BB\u6821\u9A8C`);
1444
+ console.log(` ${pc6.cyan("pf verify [platform] [--no-build]")} \u5B9E\u8DD1\u5192\u70DF\u9A8C\u8BC1(Playwright \u65E0\u5934:\u6E32\u67D3+\u65E0\u8FD0\u884C\u671F\u9519\u8BEF)`);
1445
+ console.log(` ${pc6.cyan("pf cap <sync|open> [android|ios]")} Capacitor \u539F\u751F\u5DE5\u7A0B\u64CD\u4F5C`);
1227
1446
  console.log("");
1228
- console.log(pc4.dim("\u5E73\u53F0:web / tiktok / wechat / facebook / capacitor"));
1447
+ console.log(pc6.dim("\u5E73\u53F0:web / tiktok / wechat / facebook / capacitor"));
1229
1448
  }
1230
1449
  void main();
1231
1450
 
package/dist/index.js CHANGED
@@ -2091,6 +2091,15 @@ var AccountService = class {
2091
2091
  }
2092
2092
  };
2093
2093
 
2094
+ // src/scene/active-scene.ts
2095
+ var _active = null;
2096
+ function setActiveScene(scene) {
2097
+ _active = scene;
2098
+ }
2099
+ function getActiveScene() {
2100
+ return _active;
2101
+ }
2102
+
2094
2103
  // src/services/SceneManager.ts
2095
2104
  var SceneManager = class {
2096
2105
  constructor() {
@@ -2108,19 +2117,18 @@ var SceneManager = class {
2108
2117
  return;
2109
2118
  }
2110
2119
  const sceneData = asSceneData(data);
2111
- const all = manager.getScenes(false);
2112
- const vehicle = all.find((s) => s.scene.key !== key);
2113
- for (const scene of all) {
2114
- const sceneKey = scene.scene.key;
2115
- if (sceneKey !== key && scene !== vehicle) {
2116
- manager.stop(sceneKey);
2120
+ const active = getActiveScene();
2121
+ if (active && active.scene && typeof active.scene.start === "function") {
2122
+ for (const scene of manager.getScenes(true)) {
2123
+ const sceneKey = scene.scene.key;
2124
+ if (sceneKey !== key && scene !== active) {
2125
+ manager.stop(sceneKey);
2126
+ }
2117
2127
  }
2128
+ active.scene.start(key, sceneData);
2129
+ return;
2118
2130
  }
2119
- if (vehicle) {
2120
- vehicle.scene.start(key, sceneData);
2121
- } else {
2122
- manager.start(key, sceneData);
2123
- }
2131
+ manager.start(key, sceneData);
2124
2132
  }
2125
2133
  push(key, data) {
2126
2134
  const manager = this.game?.scene;
@@ -2868,9 +2876,6 @@ var PanelManager = class {
2868
2876
  return actives[actives.length - 1];
2869
2877
  }
2870
2878
  };
2871
-
2872
- // src/bootstrap.ts
2873
- var _game = null;
2874
2879
  async function startGame(opts) {
2875
2880
  const { platform, config, scenes } = opts;
2876
2881
  PlatformContext.set(platform);
@@ -2932,31 +2937,40 @@ async function startGame(opts) {
2932
2937
  await opts.setup(App);
2933
2938
  }
2934
2939
  log.info(`[bootstrap] \u5E73\u53F0 ${platform.info.name} \u521D\u59CB\u5316\u5B8C\u6210,\u542F\u52A8 Phaser`);
2935
- _game = await createPhaserGame(config, scenes);
2936
- audio.bindGame(_game);
2937
- sceneManager.bindGame(_game);
2938
- panelManager.bindGame(_game);
2940
+ const game = createPhaserGameInstance(config, scenes);
2941
+ audio.bindGame(game);
2942
+ sceneManager.bindGame(game);
2943
+ panelManager.bindGame(game);
2944
+ await waitForGameReady(game);
2939
2945
  await App.modules.initAll();
2940
2946
  log.info("[bootstrap] \u6E38\u620F\u542F\u52A8\u5B8C\u6210");
2941
2947
  }
2942
- function createPhaserGame(config, scenes) {
2948
+ function createPhaserGameInstance(config, scenes) {
2949
+ const gameConfig = {
2950
+ type: Phaser4.AUTO,
2951
+ // 关键:把 canvas 挂进各平台外壳的 #game-root(外壳已为它写好布局/居中 CSS)。
2952
+ // 不设 parent 时 Phaser 把 canvas 追加到 document.body —— 而外壳里 #game-root 占满
2953
+ // 100% 高度,canvas 作为其后的块级兄弟会被挤到视口外,再被 body 的 overflow:hidden
2954
+ // 裁掉 → 屏幕全黑(但 canvas 后备缓冲其实已渲染,故 toDataURL/getImageData 读不出问题)。
2955
+ // wechat 无 DOM(weapp-adapter),'game-root' 不存在时 Phaser 自动回退,安全。
2956
+ parent: "game-root",
2957
+ // 透明背景占位;业务场景自行绘制背景
2958
+ backgroundColor: "#1f2937",
2959
+ scale: {
2960
+ // FIT:按设计分辨率等比缩放铺满容器(保持比例,留黑边);CENTER_BOTH:居中
2961
+ mode: Phaser4.Scale.FIT,
2962
+ autoCenter: Phaser4.Scale.CENTER_BOTH,
2963
+ width: config.designWidth,
2964
+ height: config.designHeight
2965
+ },
2966
+ scene: scenes
2967
+ // 微信等无 DOM 平台由 weapp-adapter 提供 canvas;有 DOM 平台 Phaser 自动建 canvas
2968
+ };
2969
+ return new Phaser4.Game(gameConfig);
2970
+ }
2971
+ function waitForGameReady(game) {
2943
2972
  return new Promise((resolve) => {
2944
- const gameConfig = {
2945
- type: Phaser4.AUTO,
2946
- // 透明背景占位;业务场景自行绘制背景
2947
- backgroundColor: "#1f2937",
2948
- scale: {
2949
- // FIT:按设计分辨率等比缩放铺满容器(保持比例,留黑边);CENTER_BOTH:居中
2950
- mode: Phaser4.Scale.FIT,
2951
- autoCenter: Phaser4.Scale.CENTER_BOTH,
2952
- width: config.designWidth,
2953
- height: config.designHeight
2954
- },
2955
- scene: scenes
2956
- // 微信等无 DOM 平台由 weapp-adapter 提供 canvas;有 DOM 平台 Phaser 自动建 canvas
2957
- };
2958
- const game = new Phaser4.Game(gameConfig);
2959
- game.events.once(Phaser4.Core.Events.READY, () => resolve(game));
2973
+ game.events.once(Phaser4.Core.Events.READY, () => resolve());
2960
2974
  });
2961
2975
  }
2962
2976
 
@@ -3094,6 +3108,7 @@ var BaseScene = class extends Phaser4.Scene {
3094
3108
  * 注意:业务场景若覆写 init(data),请调用 super.init(data) 以保留自动清理(this.bind / this.scheduler)。
3095
3109
  */
3096
3110
  init(_data) {
3111
+ setActiveScene(this);
3097
3112
  this.armCleanupHooks();
3098
3113
  }
3099
3114
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maoyugames/phaser-framework",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "多平台 Phaser 游戏框架:业务/底层分离,HTTP/WebSocket/KCP,Web/TikTok/微信/Facebook/App 隔离打包",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -53,11 +53,15 @@
53
53
  "phaser": "^3.60.0 || ^3.80.0 || ^3.90.0",
54
54
  "vite": "^5.0.0",
55
55
  "typescript": "^5.0.0",
56
- "terser": "^5.0.0"
56
+ "terser": "^5.0.0",
57
+ "playwright": "^1.40.0"
57
58
  },
58
59
  "peerDependenciesMeta": {
59
60
  "terser": {
60
61
  "optional": true
62
+ },
63
+ "playwright": {
64
+ "optional": true
61
65
  }
62
66
  },
63
67
  "dependencies": {