@maoyugames/phaser-framework 1.0.1 → 1.0.3

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
@@ -236,7 +237,15 @@ function createPlatformConfig(platform, opts) {
236
237
  __FRAMEWORK_VERSION__: JSON.stringify(frameworkVersion)
237
238
  },
238
239
  resolve: {
239
- alias: createAlias(projectRoot)
240
+ alias: createAlias(projectRoot),
241
+ // 去重 phaser:避免框架包与业务解析到不同 phaser 实例
242
+ dedupe: ["phaser"]
243
+ },
244
+ // dev 模式必须预打包 phaser(及框架包)为带 default 导出的 ESM,
245
+ // 否则 vite dev 下 `import Phaser from 'phaser'` 报
246
+ // "phaser does not provide an export named 'default'"(build 模式 rollup 自带 CJS 互操作,不受影响)。
247
+ optimizeDeps: {
248
+ include: ["phaser", "@maoyugames/phaser-framework"]
240
249
  },
241
250
  esbuild: {
242
251
  // 兼容低端 webview / 小游戏 JS 引擎
@@ -530,7 +539,7 @@ function writeShellFiles(distDir, projectRoot, files) {
530
539
  const dst = join(distDir, f.relPath);
531
540
  fse4.ensureDirSync(resolve(dst, ".."));
532
541
  fse4.writeFileSync(dst, f.content, "utf-8");
533
- console.log(pc4.green(` \u2713 ${f.relPath} \u2192 ${relative(projectRoot, dst)}`));
542
+ console.log(pc6.green(` \u2713 ${f.relPath} \u2192 ${relative(projectRoot, dst)}`));
534
543
  }
535
544
  }
536
545
  function injectShell(opts) {
@@ -540,19 +549,19 @@ function injectShell(opts) {
540
549
  case "tiktok":
541
550
  return true;
542
551
  case "facebook": {
543
- 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`));
544
553
  writeShellFiles(distDir, projectRoot, [renderFacebookAppConfig(config.facebook)]);
545
554
  return true;
546
555
  }
547
556
  case "capacitor": {
548
- 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)`));
549
558
  const capCfgPath = resolve(projectRoot, "capacitor.config.ts");
550
559
  fse4.writeFileSync(capCfgPath, renderCapacitorConfig(config.capacitor), "utf-8");
551
- 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)}`));
552
561
  return true;
553
562
  }
554
563
  case "wechat": {
555
- 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`));
556
565
  writeShellFiles(distDir, projectRoot, renderWeChatShells(config.wechat));
557
566
  copyPublicAssetsToWechat(projectRoot, distDir);
558
567
  copyWechatVendor(projectRoot, distDir);
@@ -565,37 +574,37 @@ function injectShell(opts) {
565
574
  function copyPublicAssetsToWechat(projectRoot, distDir) {
566
575
  const srcPublic = resolve(projectRoot, "src/game/public");
567
576
  if (!fse4.existsSync(srcPublic)) return;
568
- 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)"));
569
578
  fse4.copySync(srcPublic, distDir);
570
- console.log(pc4.green(" \u2713 src/game/public/* \u2192 dist/wechat/"));
579
+ console.log(pc6.green(" \u2713 src/game/public/* \u2192 dist/wechat/"));
571
580
  }
572
581
  function copyWechatVendor(projectRoot, distDir) {
573
- 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)"));
574
583
  const dirs = wechatVendorDirs(projectRoot);
575
584
  const adapter = dirs.map((d) => join(d, "weapp-adapter.js")).find((p) => fse4.existsSync(p));
576
585
  if (adapter) {
577
586
  fse4.copyFileSync(adapter, join(distDir, "weapp-adapter.js"));
578
- 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"));
579
588
  const vendorDir = resolve(adapter, "..");
580
589
  const symbol = join(vendorDir, "symbol.js");
581
590
  if (fse4.existsSync(symbol)) {
582
591
  fse4.copyFileSync(symbol, join(distDir, "symbol.js"));
583
- 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"));
584
593
  }
585
594
  return true;
586
595
  }
587
- 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")));
588
597
  console.log(
589
- pc4.yellow(
598
+ pc6.yellow(
590
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"
591
600
  )
592
601
  );
593
- 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:`));
594
603
  for (const d of wechatVendorDirs(projectRoot)) {
595
- console.log(pc4.yellow(` - ${relative(projectRoot, d)}/weapp-adapter.js`));
604
+ console.log(pc6.yellow(` - ${relative(projectRoot, d)}/weapp-adapter.js`));
596
605
  }
597
606
  console.log(
598
- 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")
599
608
  );
600
609
  return false;
601
610
  }
@@ -619,43 +628,43 @@ function findMainBundle(files) {
619
628
  }
620
629
  function checkPlatform(platform, distDir) {
621
630
  if (!fse4.existsSync(distDir)) {
622
- console.log(pc4.gray(` [${platform}] \u672A\u6784\u5EFA,\u8DF3\u8FC7`));
631
+ console.log(pc6.gray(` [${platform}] \u672A\u6784\u5EFA,\u8DF3\u8FC7`));
623
632
  return true;
624
633
  }
625
634
  const files = listFiles(distDir);
626
635
  const total = files.reduce((s, f) => s + f.size, 0);
627
636
  const main2 = findMainBundle(files);
628
637
  const limits = SIZE_LIMITS[platform];
629
- console.log(pc4.cyan(` [${platform}]`));
630
- 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)`);
631
640
  if (main2) {
632
- 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))}`);
633
642
  }
634
643
  let ok = true;
635
644
  if (limits?.totalHardLimit) {
636
645
  if (total > limits.totalHardLimit) {
637
646
  console.log(
638
- pc4.red(
647
+ pc6.red(
639
648
  ` \u2717 \u8D85\u51FA TikTok \u6574\u5305\u786C\u4E0A\u9650 ${formatBytes(limits.totalHardLimit)}(\u8D85 ${formatBytes(total - limits.totalHardLimit)})`
640
649
  )
641
650
  );
642
651
  ok = false;
643
652
  } else {
644
653
  console.log(
645
- 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)})`)
646
655
  );
647
656
  }
648
657
  }
649
658
  if (limits?.mainBundleSoftLimit && main2) {
650
659
  if (main2.size > limits.mainBundleSoftLimit) {
651
660
  console.log(
652
- pc4.yellow(
661
+ pc6.yellow(
653
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`
654
663
  )
655
664
  );
656
665
  } else {
657
666
  console.log(
658
- 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)})`)
659
668
  );
660
669
  }
661
670
  }
@@ -664,29 +673,29 @@ function checkPlatform(platform, distDir) {
664
673
  function runSizeCheck(target) {
665
674
  const ctx = resolveProject();
666
675
  const targets = target ? isPlatform(target) ? [target] : invalidTarget(target) : ALL_PLATFORMS;
667
- 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")));
668
677
  let allOk = true;
669
678
  for (const p of targets) {
670
679
  const distDir = join(ctx.root, "dist", p);
671
680
  if (!checkPlatform(p, distDir)) allOk = false;
672
681
  }
673
682
  if (!allOk) {
674
- 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)")));
675
684
  return false;
676
685
  }
677
- 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")));
678
687
  return true;
679
688
  }
680
689
  function sizeCheckPlatform(platform, distDir) {
681
- 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")));
682
691
  const ok = checkPlatform(platform, distDir);
683
- if (ok) console.log(pc4.green(pc4.bold("\u2713 \u4F53\u79EF\u68C0\u67E5\u5B8C\u6210")));
684
- 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")));
685
694
  return ok;
686
695
  }
687
696
  function invalidTarget(target) {
688
- console.log(pc4.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
689
- 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(" / ")}`));
690
699
  process.exit(1);
691
700
  }
692
701
  function collectJsFiles(dir) {
@@ -708,12 +717,12 @@ function snippetAround(text, index, span = 60) {
708
717
  function verifyPlatform(platform, distDir, projectRoot) {
709
718
  const rules = ISOLATION_RULES[platform];
710
719
  if (!fse4.existsSync(distDir)) {
711
- 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`));
712
721
  return 0;
713
722
  }
714
723
  const files = collectJsFiles(distDir);
715
724
  if (files.length === 0) {
716
- 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`));
717
726
  return 0;
718
727
  }
719
728
  let hits = 0;
@@ -727,13 +736,13 @@ function verifyPlatform(platform, distDir, projectRoot) {
727
736
  hits++;
728
737
  perSig++;
729
738
  const rel = relative(projectRoot, file);
730
- console.log(pc4.red(` \u2717 [${platform}] \u547D\u4E2D\u7981\u7528 SDK \u7B7E\u540D ${pc4.bold(sig)}`));
731
- console.log(pc4.gray(` \u6587\u4EF6:${rel}`));
732
- 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`));
733
742
  from = idx + sig.length;
734
743
  idx = text.indexOf(sig, from);
735
744
  if (perSig >= 3 && idx !== -1) {
736
- 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)`));
737
746
  break;
738
747
  }
739
748
  }
@@ -741,7 +750,7 @@ function verifyPlatform(platform, distDir, projectRoot) {
741
750
  }
742
751
  if (hits === 0) {
743
752
  console.log(
744
- 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)`)
745
754
  );
746
755
  }
747
756
  return hits;
@@ -749,32 +758,32 @@ function verifyPlatform(platform, distDir, projectRoot) {
749
758
  function runVerifyIsolation(target) {
750
759
  const ctx = resolveProject();
751
760
  const targets = target ? isPlatform(target) ? [target] : invalidTarget2(target) : ALL_PLATFORMS;
752
- 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")));
753
762
  let totalHits = 0;
754
763
  for (const p of targets) {
755
764
  totalHits += verifyPlatform(p, join(ctx.root, "dist", p), ctx.root);
756
765
  }
757
766
  if (totalHits > 0) {
758
- console.log(pc4.red(pc4.bold(`
767
+ console.log(pc6.red(pc6.bold(`
759
768
  \u2717 \u9694\u79BB\u6821\u9A8C\u5931\u8D25:\u5171\u53D1\u73B0 ${totalHits} \u5904\u8DE8\u5E73\u53F0 SDK \u7B7E\u540D\u6CC4\u6F0F`)));
760
769
  return false;
761
770
  }
762
- 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")));
763
772
  return true;
764
773
  }
765
774
  function verifyIsolationPlatform(platform, distDir, projectRoot) {
766
- 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")));
767
776
  const hits = verifyPlatform(platform, distDir, projectRoot);
768
777
  if (hits > 0) {
769
- 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`)));
770
779
  return false;
771
780
  }
772
- 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")));
773
782
  return true;
774
783
  }
775
784
  function invalidTarget2(target) {
776
- console.log(pc4.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
777
- 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(" / ")}`));
778
787
  process.exit(1);
779
788
  }
780
789
 
@@ -920,7 +929,7 @@ function findFirstHtml(dir) {
920
929
  return void 0;
921
930
  }
922
931
  async function buildPlatform(platform, ctx, config, vite) {
923
- console.log(pc4.bgCyan(pc4.black(` \u6784\u5EFA\u5E73\u53F0:${platform} `)));
932
+ console.log(pc6.bgCyan(pc6.black(` \u6784\u5EFA\u5E73\u53F0:${platform} `)));
924
933
  const cache = cacheDir(ctx.root);
925
934
  fse4.ensureDirSync(cache);
926
935
  const hash = randomBytes(4).toString("hex");
@@ -940,7 +949,7 @@ async function buildPlatform(platform, ctx, config, vite) {
940
949
  tempFiles.push(htmlPath);
941
950
  inputFile = htmlPath;
942
951
  }
943
- console.log(pc4.cyan(` \u25B6 [${platform}] vite build`));
952
+ console.log(pc6.cyan(` \u25B6 [${platform}] vite build`));
944
953
  const inlineConfig = createPlatformConfig(platform, {
945
954
  projectRoot: ctx.root,
946
955
  viteRoot: platform === "wechat" ? ctx.root : cache,
@@ -953,22 +962,22 @@ async function buildPlatform(platform, ctx, config, vite) {
953
962
  if (platform !== "wechat") {
954
963
  normalizeHtmlOutput(distDir);
955
964
  }
956
- 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)}`));
957
966
  const injectOk = injectShell({ platform, projectRoot: ctx.root, distDir, config });
958
967
  const sizeOk = sizeCheckPlatform(platform, distDir);
959
968
  const isoOk = verifyIsolationPlatform(platform, distDir, ctx.root);
960
969
  const allOk = injectOk && sizeOk && isoOk;
961
970
  if (allOk) {
962
- 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)}
963
972
  `)));
964
973
  } else {
965
- 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
966
975
  `)));
967
976
  }
968
977
  return allOk;
969
978
  } catch (e) {
970
- console.log(pc4.red(` \u2717 [${platform}] vite build \u5931\u8D25:${e.message}`));
971
- 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 ?? ""));
972
981
  return false;
973
982
  } finally {
974
983
  for (const f of tempFiles) {
@@ -981,37 +990,37 @@ async function buildPlatform(platform, ctx, config, vite) {
981
990
  }
982
991
  async function runBuild(target) {
983
992
  if (!target) {
984
- 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>"));
985
994
  return false;
986
995
  }
987
996
  const platforms = target === "all" ? ALL_PLATFORMS : isPlatform(target) ? [target] : null;
988
997
  if (!platforms) {
989
- console.log(pc4.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
990
- 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`));
991
1000
  return false;
992
1001
  }
993
1002
  const ctx = resolveProject();
994
1003
  const config = await loadPlatformConfig(ctx);
995
1004
  const vite = await loadViteFromRoot(ctx.root);
996
- 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})
997
1006
  `)));
998
1007
  const results = [];
999
1008
  for (const p of platforms) {
1000
1009
  results.push({ platform: p, ok: await buildPlatform(p, ctx, config, vite) });
1001
1010
  }
1002
- console.log(pc4.cyan(pc4.bold("\u25B6 \u6784\u5EFA\u6C47\u603B")));
1011
+ console.log(pc6.cyan(pc6.bold("\u25B6 \u6784\u5EFA\u6C47\u603B")));
1003
1012
  let allOk = true;
1004
1013
  for (const r of results) {
1005
- if (r.ok) console.log(pc4.green(` \u2713 ${r.platform}`));
1014
+ if (r.ok) console.log(pc6.green(` \u2713 ${r.platform}`));
1006
1015
  else {
1007
- console.log(pc4.red(` \u2717 ${r.platform}`));
1016
+ console.log(pc6.red(` \u2717 ${r.platform}`));
1008
1017
  allOk = false;
1009
1018
  }
1010
1019
  }
1011
1020
  if (!allOk) {
1012
- 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")));
1013
1022
  } else {
1014
- 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")));
1015
1024
  }
1016
1025
  return allOk;
1017
1026
  }
@@ -1035,35 +1044,33 @@ function renderHtmlShell2(platform, config, scriptSrc) {
1035
1044
  async function runDev(target) {
1036
1045
  const platform = target ? isPlatform(target) ? target : invalidTarget3(target) : "web";
1037
1046
  if (platform === "wechat") {
1038
- console.log(pc4.red("\u2717 wechat \u65E0 HTML dev server \u5F62\u6001\u3002"));
1039
- 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"));
1040
1049
  return false;
1041
1050
  }
1042
1051
  const ctx = resolveProject();
1043
1052
  const config = await loadPlatformConfig(ctx);
1044
1053
  const vite = await loadViteFromRoot(ctx.root);
1045
1054
  const cache = cacheDir2(ctx.root);
1046
- fse4.ensureDirSync(cache);
1047
1055
  const hash = randomBytes(4).toString("hex");
1048
- const entryFileName = `main.${platform}.${hash}.ts`;
1049
- const entryPath = join(cache, entryFileName);
1050
- const htmlName = `index.${platform}.${hash}.html`;
1051
- const htmlPath = join(cache, htmlName);
1056
+ const runDir = join(cache, `dev-${platform}-${hash}`);
1057
+ fse4.ensureDirSync(runDir);
1058
+ const entryFileName = `main.${platform}.ts`;
1059
+ const entryPath = join(runDir, entryFileName);
1060
+ const htmlPath = join(runDir, "index.html");
1052
1061
  fse4.writeFileSync(entryPath, renderEntry({ platform, projectRoot: ctx.root }), "utf-8");
1053
1062
  fse4.writeFileSync(htmlPath, renderHtmlShell2(platform, config, `./${entryFileName}`), "utf-8");
1054
1063
  const cleanup = () => {
1055
- for (const f of [entryPath, htmlPath]) {
1056
- try {
1057
- fse4.removeSync(f);
1058
- } catch {
1059
- }
1064
+ try {
1065
+ fse4.removeSync(runDir);
1066
+ } catch {
1060
1067
  }
1061
1068
  };
1062
1069
  try {
1063
1070
  const server = await vite.createServer({
1064
- // root 设为缓存目录(临时 HTML 所在),入口页直接是 /index.<p>.<hash>.html;
1065
- // alias/publicDir 用绝对路径不受影响
1066
- root: cache,
1071
+ // root 设为本次 dev 的独立子目录(其根有 index.html),浏览器打开 / 即入口页;
1072
+ // alias/publicDir 用绝对路径不受 root 影响
1073
+ root: runDir,
1067
1074
  base: "/",
1068
1075
  configFile: false,
1069
1076
  define: {
@@ -1075,7 +1082,14 @@ async function runDev(target) {
1075
1082
  alias: [
1076
1083
  { find: /^@game$/, replacement: resolve(ctx.root, "src/game") },
1077
1084
  { find: /^@game\//, replacement: resolve(ctx.root, "src/game") + "/" }
1078
- ]
1085
+ ],
1086
+ // 去重 phaser,避免框架包与业务解析到不同实例
1087
+ dedupe: ["phaser"]
1088
+ },
1089
+ // 必须预打包 phaser(及框架包)为带 default 导出的 ESM,
1090
+ // 否则 dev 下 `import Phaser from 'phaser'` 报 "does not provide an export named 'default'"。
1091
+ optimizeDeps: {
1092
+ include: ["phaser", "@maoyugames/phaser-framework"]
1079
1093
  },
1080
1094
  publicDir: resolve(ctx.root, "src/game/public"),
1081
1095
  server: { open: false }
@@ -1083,9 +1097,9 @@ async function runDev(target) {
1083
1097
  await server.listen();
1084
1098
  const info = server.resolvedUrls;
1085
1099
  const url = info?.local?.[0] ?? "http://localhost:5173/";
1086
- console.log(pc4.green(pc4.bold(`\u25B6 [${platform}] dev server \u5C31\u7EEA`)));
1087
- console.log(` \u6253\u5F00:${pc4.cyan(url + htmlName)}`);
1088
- 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)"));
1089
1103
  const onExit = async () => {
1090
1104
  cleanup();
1091
1105
  await server.close();
@@ -1096,17 +1110,208 @@ async function runDev(target) {
1096
1110
  return true;
1097
1111
  } catch (e) {
1098
1112
  cleanup();
1099
- 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}`));
1100
1114
  return false;
1101
1115
  }
1102
1116
  }
1103
1117
  function invalidTarget3(target) {
1104
- 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 = ["--no-sandbox", "--disable-webgl", "--disable-webgl2"];
1222
+ let browser;
1223
+ try {
1224
+ browser = await pw.chromium.launch({ channel: "chrome", headless: true, args: launchArgs });
1225
+ } catch {
1226
+ try {
1227
+ browser = await pw.chromium.launch({ headless: true, args: launchArgs });
1228
+ } catch (e) {
1229
+ server.close();
1230
+ console.log(pc6.red(`\u2717 [verify] \u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25:${e.message}`));
1231
+ console.log(pc6.yellow(" \u88C5\u7CFB\u7EDF Chrome,\u6216\u8FD0\u884C `npx playwright install chromium`\u3002"));
1232
+ return false;
1233
+ }
1234
+ }
1235
+ const errors = [];
1236
+ let rendered = false;
1237
+ let distinctColors = 0;
1238
+ let canvasInfo = null;
1239
+ try {
1240
+ const page = await browser.newPage({ viewport });
1241
+ page.on("pageerror", (e) => errors.push("PAGEERROR: " + e.message));
1242
+ page.on("console", (m) => {
1243
+ if (m.type() === "error" && !/favicon/i.test(m.text())) errors.push("CONSOLE: " + m.text());
1244
+ });
1245
+ await page.goto(url, { waitUntil: "load", timeout: 3e4 });
1246
+ for (let i = 0; i < 20; i++) {
1247
+ await page.waitForTimeout(1e3);
1248
+ const stat = await page.evaluate(() => {
1249
+ const c = document.querySelector("canvas");
1250
+ if (!c) return { ok: false, distinct: 0, w: 0, h: 0 };
1251
+ const cx = c.getContext("2d");
1252
+ if (!cx) return { ok: false, distinct: 0, w: c.width, h: c.height };
1253
+ const w = c.width;
1254
+ const h = c.height;
1255
+ const data = cx.getImageData(0, 0, w, h).data;
1256
+ const colors = /* @__PURE__ */ new Set();
1257
+ const total = w * h;
1258
+ const step = Math.max(1, Math.floor(total / 3e3));
1259
+ for (let k = 0; k < total && colors.size < 40; k += step) {
1260
+ const o = k * 4;
1261
+ colors.add(`${data[o]},${data[o + 1]},${data[o + 2]}`);
1262
+ }
1263
+ return { ok: true, distinct: colors.size, w, h };
1264
+ }).catch(() => ({ ok: false, distinct: 0, w: 0, h: 0 }));
1265
+ canvasInfo = stat.w ? { w: stat.w, h: stat.h } : canvasInfo;
1266
+ distinctColors = stat.distinct;
1267
+ if (stat.ok && stat.distinct > 3) {
1268
+ rendered = true;
1269
+ break;
1270
+ }
1271
+ }
1272
+ const durl = await page.evaluate(() => {
1273
+ const c = document.querySelector("canvas");
1274
+ return c ? c.toDataURL("image/png") : "";
1275
+ }).catch(() => "");
1276
+ if (durl) {
1277
+ const outDir = join(ctx.root, ".pf-verify");
1278
+ mkdirSync(outDir, { recursive: true });
1279
+ const shot = join(outDir, `${platform}.png`);
1280
+ writeFileSync(shot, Buffer.from(durl.split(",")[1], "base64"));
1281
+ console.log(pc6.gray(` \u622A\u56FE:${shot}`));
1282
+ }
1283
+ } catch (e) {
1284
+ errors.push("VERIFY: " + e.message);
1285
+ } finally {
1286
+ await browser.close();
1287
+ server.close();
1288
+ }
1289
+ const ok = errors.length === 0 && rendered;
1290
+ console.log("");
1291
+ console.log(pc6.bold("\u25B6 [verify] \u7ED3\u679C"));
1292
+ console.log(` canvas:${canvasInfo ? `${canvasInfo.w}\xD7${canvasInfo.h}` : "\u65E0"};\u753B\u9762\u989C\u8272\u6570:${distinctColors}(>3 \u89C6\u4E3A\u5DF2\u6E32\u67D3)`);
1293
+ if (errors.length) {
1294
+ console.log(pc6.red(` \u63A7\u5236\u53F0/\u9875\u9762\u9519\u8BEF(${errors.length}):`));
1295
+ errors.slice(0, 12).forEach((e) => console.log(pc6.red(" " + e)));
1296
+ } else {
1297
+ console.log(pc6.green(" \u65E0\u63A7\u5236\u53F0/\u9875\u9762\u9519\u8BEF"));
1298
+ }
1299
+ if (ok) {
1300
+ console.log(pc6.green(pc6.bold(`
1301
+ \u2713 [verify] ${platform} \u5B9E\u8DD1\u901A\u8FC7(\u6E32\u67D3\u6B63\u5E38\u3001\u65E0\u8FD0\u884C\u671F\u9519\u8BEF)`)));
1302
+ } else {
1303
+ console.log(pc6.red(pc6.bold(`
1304
+ \u2717 [verify] ${platform} \u672A\u901A\u8FC7:${!rendered ? "\u753B\u9762\u672A\u6E32\u67D3\u51FA\u5185\u5BB9(\u7591\u4F3C\u5D29\u6E83/\u9ED1\u5C4F)" : "\u5B58\u5728\u8FD0\u884C\u671F\u9519\u8BEF"}`)));
1305
+ }
1306
+ return ok;
1307
+ }
1308
+ function invalid(target) {
1309
+ console.log(pc6.red(`\u672A\u77E5\u5E73\u53F0:${target}`));
1105
1310
  process.exit(1);
1106
1311
  }
1107
1312
  function spawnCap(projectRoot, args) {
1108
1313
  const npx = process.platform === "win32" ? "npx.cmd" : "npx";
1109
- console.log(pc4.gray(` $ ${npx} cap ${args.join(" ")}`));
1314
+ console.log(pc6.gray(` $ ${npx} cap ${args.join(" ")}`));
1110
1315
  const res = spawnSync(npx, ["cap", ...args], {
1111
1316
  cwd: projectRoot,
1112
1317
  stdio: "inherit",
@@ -1114,9 +1319,9 @@ function spawnCap(projectRoot, args) {
1114
1319
  shell: process.platform === "win32"
1115
1320
  });
1116
1321
  if (res.error) {
1117
- console.log(pc4.red(` \u2717 \u6267\u884C\u5931\u8D25:${res.error.message}`));
1322
+ console.log(pc6.red(` \u2717 \u6267\u884C\u5931\u8D25:${res.error.message}`));
1118
1323
  console.log(
1119
- pc4.yellow(
1324
+ pc6.yellow(
1120
1325
  " \u8BF7\u786E\u8BA4\u5DF2\u5B89\u88C5 Capacitor CLI:npm i -D @capacitor/cli @capacitor/android @capacitor/ios"
1121
1326
  )
1122
1327
  );
@@ -1127,11 +1332,11 @@ function spawnCap(projectRoot, args) {
1127
1332
  function runCap(action, platform) {
1128
1333
  const ctx = resolveProject();
1129
1334
  if (action === "sync") {
1130
- console.log(pc4.cyan(pc4.bold("\u25B6 Capacitor sync")));
1335
+ console.log(pc6.cyan(pc6.bold("\u25B6 Capacitor sync")));
1131
1336
  const distDir = resolve(ctx.root, "dist", "capacitor");
1132
1337
  if (!fse4.existsSync(distDir)) {
1133
- console.log(pc4.red("\u2717 dist/capacitor \u4E0D\u5B58\u5728"));
1134
- console.log(pc4.yellow(" \u8BF7\u5148\u6784\u5EFA:pf build capacitor"));
1338
+ console.log(pc6.red("\u2717 dist/capacitor \u4E0D\u5B58\u5728"));
1339
+ console.log(pc6.yellow(" \u8BF7\u5148\u6784\u5EFA:pf build capacitor"));
1135
1340
  return false;
1136
1341
  }
1137
1342
  const args = platform ? ["sync", platform] : ["sync"];
@@ -1139,13 +1344,13 @@ function runCap(action, platform) {
1139
1344
  }
1140
1345
  if (action === "open") {
1141
1346
  if (platform !== "android" && platform !== "ios") {
1142
- console.log(pc4.red("\u2717 open \u9700\u6307\u5B9A\u5E73\u53F0:pf cap open <android|ios>"));
1347
+ console.log(pc6.red("\u2717 open \u9700\u6307\u5B9A\u5E73\u53F0:pf cap open <android|ios>"));
1143
1348
  return false;
1144
1349
  }
1145
- console.log(pc4.cyan(pc4.bold(`\u25B6 Capacitor open ${platform}`)));
1350
+ console.log(pc6.cyan(pc6.bold(`\u25B6 Capacitor open ${platform}`)));
1146
1351
  return capExec(ctx.root, ["open", platform]);
1147
1352
  }
1148
- console.log(pc4.red("\u7528\u6CD5:"));
1353
+ console.log(pc6.red("\u7528\u6CD5:"));
1149
1354
  console.log(" pf cap sync [android|ios]");
1150
1355
  console.log(" pf cap open <android|ios>");
1151
1356
  return false;
@@ -1184,35 +1389,43 @@ async function main(argv = process.argv.slice(2)) {
1184
1389
  process.exitCode = ok ? 0 : 1;
1185
1390
  return;
1186
1391
  }
1392
+ case "verify": {
1393
+ const noBuild = rest.includes("--no-build");
1394
+ const platform = rest.find((a) => !a.startsWith("-"));
1395
+ const ok = await runVerify(platform, { noBuild });
1396
+ process.exitCode = ok ? 0 : 1;
1397
+ return;
1398
+ }
1187
1399
  case "cap": {
1188
1400
  const ok = runCap(rest[0], rest[1]);
1189
1401
  process.exitCode = ok ? 0 : 1;
1190
1402
  return;
1191
1403
  }
1192
1404
  default: {
1193
- console.log(pc4.yellow(`[pf] \u672A\u77E5\u547D\u4EE4 "${command}"\u3002`));
1194
- console.log(pc4.dim("\u8FD0\u884C `pf help` \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002"));
1405
+ console.log(pc6.yellow(`[pf] \u672A\u77E5\u547D\u4EE4 "${command}"\u3002`));
1406
+ console.log(pc6.dim("\u8FD0\u884C `pf help` \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002"));
1195
1407
  process.exitCode = 1;
1196
1408
  }
1197
1409
  }
1198
1410
  } catch (e) {
1199
- console.log(pc4.red(`[pf] \u6267\u884C "${command}" \u51FA\u9519:${e.message}`));
1200
- if (e.stack) console.log(pc4.gray(e.stack ?? ""));
1411
+ console.log(pc6.red(`[pf] \u6267\u884C "${command}" \u51FA\u9519:${e.message}`));
1412
+ if (e.stack) console.log(pc6.gray(e.stack ?? ""));
1201
1413
  process.exitCode = 1;
1202
1414
  }
1203
1415
  }
1204
1416
  function printHelp() {
1205
- console.log(pc4.bold("@maoyugames/phaser-framework CLI (pf)"));
1206
- console.log(pc4.dim("\u591A\u5E73\u53F0 Phaser \u6846\u67B6\u6784\u5EFA\u5DE5\u5177\u3002"));
1417
+ console.log(pc6.bold("@maoyugames/phaser-framework CLI (pf)"));
1418
+ console.log(pc6.dim("\u591A\u5E73\u53F0 Phaser \u6846\u67B6\u6784\u5EFA\u5DE5\u5177\u3002"));
1207
1419
  console.log("");
1208
1420
  console.log("\u547D\u4EE4:");
1209
- console.log(` ${pc4.cyan("pf dev [platform]")} \u542F\u52A8\u67D0\u5E73\u53F0 dev server(\u9ED8\u8BA4 web)`);
1210
- console.log(` ${pc4.cyan("pf build <platform|all>")} \u6784\u5EFA\u67D0\u5E73\u53F0/\u5168\u90E8\u5E73\u53F0`);
1211
- console.log(` ${pc4.cyan("pf size-check [platform]")} \u4EA7\u7269\u4F53\u79EF\u68C0\u67E5`);
1212
- console.log(` ${pc4.cyan("pf verify-isolation [platform]")} SDK \u9694\u79BB\u6821\u9A8C`);
1213
- console.log(` ${pc4.cyan("pf cap <sync|open> [android|ios]")} Capacitor \u539F\u751F\u5DE5\u7A0B\u64CD\u4F5C`);
1421
+ console.log(` ${pc6.cyan("pf dev [platform]")} \u542F\u52A8\u67D0\u5E73\u53F0 dev server(\u9ED8\u8BA4 web)`);
1422
+ console.log(` ${pc6.cyan("pf build <platform|all>")} \u6784\u5EFA\u67D0\u5E73\u53F0/\u5168\u90E8\u5E73\u53F0`);
1423
+ console.log(` ${pc6.cyan("pf size-check [platform]")} \u4EA7\u7269\u4F53\u79EF\u68C0\u67E5`);
1424
+ console.log(` ${pc6.cyan("pf verify-isolation [platform]")} SDK \u9694\u79BB\u6821\u9A8C`);
1425
+ 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)`);
1426
+ console.log(` ${pc6.cyan("pf cap <sync|open> [android|ios]")} Capacitor \u539F\u751F\u5DE5\u7A0B\u64CD\u4F5C`);
1214
1427
  console.log("");
1215
- console.log(pc4.dim("\u5E73\u53F0:web / tiktok / wechat / facebook / capacitor"));
1428
+ console.log(pc6.dim("\u5E73\u53F0:web / tiktok / wechat / facebook / capacitor"));
1216
1429
  }
1217
1430
  void main();
1218
1431
 
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,34 @@ 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
+ // 透明背景占位;业务场景自行绘制背景
2952
+ backgroundColor: "#1f2937",
2953
+ scale: {
2954
+ // FIT:按设计分辨率等比缩放铺满容器(保持比例,留黑边);CENTER_BOTH:居中
2955
+ mode: Phaser4.Scale.FIT,
2956
+ autoCenter: Phaser4.Scale.CENTER_BOTH,
2957
+ width: config.designWidth,
2958
+ height: config.designHeight
2959
+ },
2960
+ scene: scenes
2961
+ // 微信等无 DOM 平台由 weapp-adapter 提供 canvas;有 DOM 平台 Phaser 自动建 canvas
2962
+ };
2963
+ return new Phaser4.Game(gameConfig);
2964
+ }
2965
+ function waitForGameReady(game) {
2943
2966
  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));
2967
+ game.events.once(Phaser4.Core.Events.READY, () => resolve());
2960
2968
  });
2961
2969
  }
2962
2970
 
@@ -3094,6 +3102,7 @@ var BaseScene = class extends Phaser4.Scene {
3094
3102
  * 注意:业务场景若覆写 init(data),请调用 super.init(data) 以保留自动清理(this.bind / this.scheduler)。
3095
3103
  */
3096
3104
  init(_data) {
3105
+ setActiveScene(this);
3097
3106
  this.armCleanupHooks();
3098
3107
  }
3099
3108
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maoyugames/phaser-framework",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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": {