@playcraft/build 0.0.11 → 0.0.14

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 (102) hide show
  1. package/dist/analyzers/__tests__/optimization-analyzer.test.d.ts +1 -0
  2. package/dist/analyzers/__tests__/optimization-analyzer.test.js +169 -0
  3. package/dist/analyzers/playable-analyzer.js +3 -2
  4. package/dist/analyzers/scene-asset-collector.js +99 -9
  5. package/dist/base-builder.d.ts +15 -78
  6. package/dist/base-builder.js +34 -735
  7. package/dist/engines/engine-detector.d.ts +38 -0
  8. package/dist/engines/engine-detector.js +201 -0
  9. package/dist/engines/generic-adapter.d.ts +71 -0
  10. package/dist/engines/generic-adapter.js +378 -0
  11. package/dist/engines/index.d.ts +7 -0
  12. package/dist/engines/index.js +7 -0
  13. package/dist/engines/playcanvas-adapter.d.ts +85 -0
  14. package/dist/engines/playcanvas-adapter.js +813 -0
  15. package/dist/generators/config-generator.js +59 -1
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.js +4 -0
  18. package/dist/loaders/playcraft-loader.js +240 -5
  19. package/dist/platforms/adikteev.d.ts +1 -1
  20. package/dist/platforms/adikteev.js +30 -34
  21. package/dist/platforms/applovin.d.ts +1 -1
  22. package/dist/platforms/applovin.js +34 -33
  23. package/dist/platforms/base.d.ts +27 -5
  24. package/dist/platforms/base.js +79 -181
  25. package/dist/platforms/bigo.d.ts +1 -1
  26. package/dist/platforms/bigo.js +28 -28
  27. package/dist/platforms/facebook.d.ts +1 -1
  28. package/dist/platforms/facebook.js +21 -10
  29. package/dist/platforms/google.d.ts +1 -1
  30. package/dist/platforms/google.js +28 -21
  31. package/dist/platforms/index.d.ts +1 -0
  32. package/dist/platforms/index.js +4 -0
  33. package/dist/platforms/inmobi.d.ts +1 -1
  34. package/dist/platforms/inmobi.js +27 -32
  35. package/dist/platforms/ironsource.d.ts +1 -1
  36. package/dist/platforms/ironsource.js +37 -37
  37. package/dist/platforms/liftoff.d.ts +1 -1
  38. package/dist/platforms/liftoff.js +24 -27
  39. package/dist/platforms/mintegral.d.ts +10 -0
  40. package/dist/platforms/mintegral.js +65 -0
  41. package/dist/platforms/moloco.d.ts +1 -1
  42. package/dist/platforms/moloco.js +18 -20
  43. package/dist/platforms/playcraft.d.ts +1 -1
  44. package/dist/platforms/playcraft.js +2 -2
  45. package/dist/platforms/remerge.d.ts +1 -1
  46. package/dist/platforms/remerge.js +19 -20
  47. package/dist/platforms/snapchat.d.ts +1 -1
  48. package/dist/platforms/snapchat.js +35 -23
  49. package/dist/platforms/tiktok.d.ts +1 -1
  50. package/dist/platforms/tiktok.js +28 -24
  51. package/dist/platforms/unity.d.ts +1 -1
  52. package/dist/platforms/unity.js +32 -32
  53. package/dist/playable-builder.d.ts +1 -0
  54. package/dist/playable-builder.js +19 -3
  55. package/dist/templates/__loading__.js +100 -0
  56. package/dist/templates/__modules__.js +47 -0
  57. package/dist/templates/__settings__.template.js +20 -0
  58. package/dist/templates/__start__.js +332 -0
  59. package/dist/templates/index.html +18 -0
  60. package/dist/templates/logo.png +0 -0
  61. package/dist/templates/manifest.json +1 -0
  62. package/dist/templates/patches/cannon.min.js +28 -0
  63. package/dist/templates/patches/lz4.js +10 -0
  64. package/dist/templates/patches/one-page-http-get.js +20 -0
  65. package/dist/templates/patches/one-page-inline-game-scripts.js +52 -0
  66. package/dist/templates/patches/one-page-mraid-resize-canvas.js +46 -0
  67. package/dist/templates/patches/p2.min.js +27 -0
  68. package/dist/templates/patches/playcraft-no-xhr.js +76 -0
  69. package/dist/templates/playcanvas-stable.min.js +16363 -0
  70. package/dist/templates/styles.css +43 -0
  71. package/dist/types.d.ts +114 -1
  72. package/dist/types.js +77 -1
  73. package/dist/utils/ammo-detector.d.ts +9 -0
  74. package/dist/utils/ammo-detector.js +76 -0
  75. package/dist/utils/build-mode-detector.js +2 -0
  76. package/dist/utils/minify.d.ts +32 -0
  77. package/dist/utils/minify.js +82 -0
  78. package/dist/vite/config-builder-generic.d.ts +70 -0
  79. package/dist/vite/config-builder-generic.js +251 -0
  80. package/dist/vite/config-builder.d.ts +8 -0
  81. package/dist/vite/config-builder.js +56 -16
  82. package/dist/vite/platform-configs.d.ts +1 -0
  83. package/dist/vite/platform-configs.js +30 -1
  84. package/dist/vite/plugin-build-state.d.ts +2 -0
  85. package/dist/vite/plugin-build-state.js +5 -3
  86. package/dist/vite/plugin-compress-js.d.ts +21 -0
  87. package/dist/vite/plugin-compress-js.js +213 -0
  88. package/dist/vite/plugin-esm-html-generator.js +15 -2
  89. package/dist/vite/plugin-platform.d.ts +5 -0
  90. package/dist/vite/plugin-platform.js +502 -36
  91. package/dist/vite/plugin-playcanvas.d.ts +1 -0
  92. package/dist/vite/plugin-playcanvas.js +181 -88
  93. package/dist/vite/plugin-source-builder.js +102 -21
  94. package/dist/vite-builder.d.ts +25 -7
  95. package/dist/vite-builder.js +141 -52
  96. package/package.json +4 -2
  97. package/physics/cannon-rigidbody-adapter.js +243 -22
  98. package/templates/__loading__.js +0 -12
  99. package/templates/index.esm.mjs +0 -11
  100. package/templates/patches/one-page-mraid-resize-canvas.js +18 -4
  101. package/templates/patches/playcraft-cta-adapter.js +129 -31
  102. package/templates/patches/scene-physics-defaults.js +49 -0
@@ -0,0 +1,38 @@
1
+ import type { EngineType } from '../types.js';
2
+ /**
3
+ * 引擎检测器
4
+ * 根据项目文件结构自动检测引擎类型
5
+ */
6
+ export declare class EngineDetector {
7
+ /**
8
+ * 检测项目的引擎类型
9
+ * @param projectDir 项目目录
10
+ * @returns 引擎类型
11
+ * @throws 如果无法识别引擎,抛出错误
12
+ */
13
+ static detect(projectDir: string): Promise<EngineType>;
14
+ /**
15
+ * 检测 PlayCanvas 特有文件
16
+ */
17
+ private static detectPlayCanvasFiles;
18
+ /**
19
+ * 读取 package.json
20
+ */
21
+ private static readPackageJson;
22
+ /**
23
+ * 从 package.json 检测引擎类型
24
+ */
25
+ private static detectFromPackageJson;
26
+ /**
27
+ * 强制指定引擎类型(用于手动指定时验证)
28
+ */
29
+ static validateEngine(engine: string): EngineType | null;
30
+ /**
31
+ * 获取项目构建命令(从 package.json 的 scripts)
32
+ */
33
+ static getBuildCommand(projectDir: string): Promise<string | null>;
34
+ /**
35
+ * 检测包管理器(npm/pnpm/yarn)
36
+ */
37
+ static detectPackageManager(projectDir: string): Promise<'npm' | 'pnpm' | 'yarn'>;
38
+ }
@@ -0,0 +1,201 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ /**
4
+ * 引擎检测映射表 - 从 package.json 的 dependencies 检测引擎类型
5
+ * 复用后端 ExternalImportService 的逻辑
6
+ */
7
+ const ENGINE_DETECTION_MAP = [
8
+ // PlayCanvas - 优先检测(通过文件名而非 package.json)
9
+ { match: (n) => n === 'phaser', engine: 'phaser', priority: 100 },
10
+ { match: (n) => n === 'pixi.js' || n.startsWith('@pixi/'), engine: 'pixijs', priority: 100 },
11
+ { match: (n) => n === 'three', engine: 'threejs', priority: 100 },
12
+ { match: (n) => n === 'cocos-creator' || n === 'cc', engine: 'cocos', priority: 100 },
13
+ { match: (n) => n === 'playcanvas', engine: 'playcanvas', priority: 100 },
14
+ { match: (n) => n === 'babylonjs' || n.startsWith('@babylonjs/'), engine: 'babylonjs', priority: 100 },
15
+ { match: (n) => n.startsWith('laya-') || n.startsWith('@layabox/'), engine: 'layaair', priority: 100 },
16
+ { match: (n) => n === 'egret', engine: 'egret', priority: 100 },
17
+ ];
18
+ /**
19
+ * 引擎检测器
20
+ * 根据项目文件结构自动检测引擎类型
21
+ */
22
+ export class EngineDetector {
23
+ /**
24
+ * 检测项目的引擎类型
25
+ * @param projectDir 项目目录
26
+ * @returns 引擎类型
27
+ * @throws 如果无法识别引擎,抛出错误
28
+ */
29
+ static async detect(projectDir) {
30
+ // 1. 优先检测 PlayCanvas / PlayCraft 特有文件(最快路径)
31
+ const isPlayCanvas = await this.detectPlayCanvasFiles(projectDir);
32
+ if (isPlayCanvas) {
33
+ return 'playcanvas';
34
+ }
35
+ // 2. 检测 package.json
36
+ const pkg = await this.readPackageJson(projectDir);
37
+ if (!pkg) {
38
+ throw new Error(`[EngineDetector] 无法识别项目类型:找不到 package.json 或 PlayCanvas 文件结构\n` +
39
+ `项目目录: ${projectDir}\n` +
40
+ `请确保这是一个有效的项目目录。`);
41
+ }
42
+ // 3. 从 package.json 检测引擎
43
+ const engine = this.detectFromPackageJson(pkg);
44
+ if (engine) {
45
+ console.log(`[EngineDetector] 检测到引擎: ${engine}(通过 package.json)`);
46
+ return engine;
47
+ }
48
+ // 4. 兜底:generic(任何有 npm build 的项目)
49
+ console.log('[EngineDetector] 无法识别具体引擎,使用 generic 类型');
50
+ return 'generic';
51
+ }
52
+ /**
53
+ * 检测 PlayCanvas 特有文件
54
+ */
55
+ static async detectPlayCanvasFiles(projectDir) {
56
+ // 1. 先检查是否是 PlayCraft 项目(manifest.json + format === 'playcraft')
57
+ // PlayCraft 项目也有 manifest.json,但不应被识别为 PlayCanvas Web Build
58
+ try {
59
+ const manifestPath = path.join(projectDir, 'manifest.json');
60
+ await fs.access(manifestPath);
61
+ const manifestContent = await fs.readFile(manifestPath, 'utf-8');
62
+ const manifest = JSON.parse(manifestContent);
63
+ if (manifest.format === 'playcraft' || Array.isArray(manifest.assets)) {
64
+ // 这是 PlayCraft 格式项目,仍然属于 PlayCanvas 引擎体系
65
+ console.log('[EngineDetector] 检测到 PlayCraft 项目格式(manifest.json format=playcraft)');
66
+ return true;
67
+ }
68
+ }
69
+ catch {
70
+ // manifest.json 不存在或解析失败,继续其他检测
71
+ }
72
+ // 2. PlayCanvas Web Build 输出文件(构建产物)
73
+ // 这些文件是 PlayCanvas 独有的,不会与其他引擎混淆
74
+ const webBuildIndicators = [
75
+ 'config.json',
76
+ '__start__.js',
77
+ '__settings__.js',
78
+ ];
79
+ for (const file of webBuildIndicators) {
80
+ try {
81
+ await fs.access(path.join(projectDir, file));
82
+ console.log(`[EngineDetector] 检测到 PlayCanvas Web Build 文件: ${file}`);
83
+ return true;
84
+ }
85
+ catch {
86
+ // 继续检查下一个
87
+ }
88
+ }
89
+ // 3. PlayCanvas 编辑器导出格式(原始项目数据)
90
+ // 必须同时存在这些文件才算是 PlayCanvas 编辑器项目
91
+ const editorExportIndicators = [
92
+ 'assets.json',
93
+ 'scenes.json',
94
+ 'project.json',
95
+ ];
96
+ let editorExportCount = 0;
97
+ for (const file of editorExportIndicators) {
98
+ try {
99
+ await fs.access(path.join(projectDir, file));
100
+ editorExportCount++;
101
+ }
102
+ catch {
103
+ // 继续检查下一个
104
+ }
105
+ }
106
+ // 如果至少有 2 个编辑器导出文件存在,认为是 PlayCanvas 项目
107
+ if (editorExportCount >= 2) {
108
+ return true;
109
+ }
110
+ return false;
111
+ }
112
+ /**
113
+ * 读取 package.json
114
+ */
115
+ static async readPackageJson(projectDir) {
116
+ try {
117
+ const pkgPath = path.join(projectDir, 'package.json');
118
+ const content = await fs.readFile(pkgPath, 'utf-8');
119
+ return JSON.parse(content);
120
+ }
121
+ catch {
122
+ return null;
123
+ }
124
+ }
125
+ /**
126
+ * 从 package.json 检测引擎类型
127
+ */
128
+ static detectFromPackageJson(pkg) {
129
+ const allDeps = {
130
+ ...pkg.dependencies,
131
+ ...pkg.devDependencies,
132
+ };
133
+ // 收集所有匹配的引擎
134
+ const matches = [];
135
+ for (const [depName] of Object.entries(allDeps)) {
136
+ for (const { match, engine, priority } of ENGINE_DETECTION_MAP) {
137
+ if (match(depName)) {
138
+ matches.push({ engine, priority });
139
+ }
140
+ }
141
+ }
142
+ if (matches.length === 0) {
143
+ return null;
144
+ }
145
+ // 按优先级排序,返回最高优先级的引擎
146
+ matches.sort((a, b) => b.priority - a.priority);
147
+ return matches[0].engine;
148
+ }
149
+ /**
150
+ * 强制指定引擎类型(用于手动指定时验证)
151
+ */
152
+ static validateEngine(engine) {
153
+ const validEngines = [
154
+ 'playcanvas', 'phaser', 'pixijs', 'threejs',
155
+ 'cocos', 'babylonjs', 'layaair', 'egret', 'generic'
156
+ ];
157
+ if (validEngines.includes(engine)) {
158
+ return engine;
159
+ }
160
+ return null;
161
+ }
162
+ /**
163
+ * 获取项目构建命令(从 package.json 的 scripts)
164
+ */
165
+ static async getBuildCommand(projectDir) {
166
+ const pkg = await this.readPackageJson(projectDir);
167
+ if (!pkg || !pkg.scripts) {
168
+ return null;
169
+ }
170
+ // 优先查找常见的 build 命令
171
+ const buildScripts = ['build', 'build:prod', 'build:production', 'dist', 'compile'];
172
+ for (const script of buildScripts) {
173
+ if (pkg.scripts[script]) {
174
+ return script;
175
+ }
176
+ }
177
+ return null;
178
+ }
179
+ /**
180
+ * 检测包管理器(npm/pnpm/yarn)
181
+ */
182
+ static async detectPackageManager(projectDir) {
183
+ // 按优先级检查锁文件
184
+ const lockFiles = [
185
+ { file: 'pnpm-lock.yaml', manager: 'pnpm' },
186
+ { file: 'yarn.lock', manager: 'yarn' },
187
+ { file: 'package-lock.json', manager: 'npm' },
188
+ ];
189
+ for (const { file, manager } of lockFiles) {
190
+ try {
191
+ await fs.access(path.join(projectDir, file));
192
+ return manager;
193
+ }
194
+ catch {
195
+ // 继续检查下一个
196
+ }
197
+ }
198
+ // 默认使用 npm
199
+ return 'npm';
200
+ }
201
+ }
@@ -0,0 +1,71 @@
1
+ import type { BaseBuildOptions, BaseBuildOutput, EngineType } from '../types.js';
2
+ /**
3
+ * 通用引擎适配器
4
+ * 负责外部引擎(Phaser、Three.js、PixiJS 等)的 Base Build
5
+ * 核心逻辑:npm install + npm run build
6
+ */
7
+ export declare class GenericAdapter {
8
+ private projectDir;
9
+ private options;
10
+ private engine;
11
+ constructor(projectDir: string, options: BaseBuildOptions, engine: EngineType);
12
+ /**
13
+ * 执行通用基础构建
14
+ * 流程:
15
+ * 1. 检测包管理器
16
+ * 2. 执行 npm install(超时 3 分钟)
17
+ * 3. 检测 build script
18
+ * 4. 执行 npm run build(超时 5 分钟)
19
+ * 5. 查找构建产物目录
20
+ * 6. 复制构建产物到 outputDir
21
+ * 7. 写入 .build-metadata.json
22
+ */
23
+ baseBuild(): Promise<BaseBuildOutput>;
24
+ /**
25
+ * 检测包管理器
26
+ */
27
+ private detectPackageManager;
28
+ /**
29
+ * 执行 npm install
30
+ * 超时:3 分钟
31
+ */
32
+ private runNpmInstall;
33
+ /**
34
+ * 检测 build script
35
+ */
36
+ private detectBuildScript;
37
+ /**
38
+ * 执行 npm run build
39
+ * 超时:5 分钟
40
+ */
41
+ private runNpmBuild;
42
+ /**
43
+ * 查找构建输出目录
44
+ * 按优先级搜索:dist/、build/、out/、public/
45
+ */
46
+ private findBuildOutputDir;
47
+ /**
48
+ * 获取嵌套对象值
49
+ */
50
+ private getNestedValue;
51
+ /**
52
+ * 复制构建产物到输出目录
53
+ */
54
+ private copyBuildOutput;
55
+ /**
56
+ * 递归复制目录
57
+ */
58
+ private copyDirectory;
59
+ /**
60
+ * 扫描输出文件
61
+ */
62
+ private scanOutputFiles;
63
+ /**
64
+ * 保存构建元数据
65
+ */
66
+ private saveBuildMetadata;
67
+ /**
68
+ * 执行命令并设置超时
69
+ */
70
+ private runCommandWithTimeout;
71
+ }
@@ -0,0 +1,378 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { spawn } from 'child_process';
4
+ /**
5
+ * 通用引擎适配器
6
+ * 负责外部引擎(Phaser、Three.js、PixiJS 等)的 Base Build
7
+ * 核心逻辑:npm install + npm run build
8
+ */
9
+ export class GenericAdapter {
10
+ constructor(projectDir, options, engine) {
11
+ this.projectDir = projectDir;
12
+ this.options = options;
13
+ this.engine = engine;
14
+ console.log(`[GenericAdapter] 初始化: projectDir=${projectDir}, engine=${engine}`);
15
+ }
16
+ /**
17
+ * 执行通用基础构建
18
+ * 流程:
19
+ * 1. 检测包管理器
20
+ * 2. 执行 npm install(超时 3 分钟)
21
+ * 3. 检测 build script
22
+ * 4. 执行 npm run build(超时 5 分钟)
23
+ * 5. 查找构建产物目录
24
+ * 6. 复制构建产物到 outputDir
25
+ * 7. 写入 .build-metadata.json
26
+ */
27
+ async baseBuild() {
28
+ console.log(`[GenericAdapter] 开始构建引擎: ${this.engine}`);
29
+ // 1. 检测包管理器
30
+ const packageManager = await this.detectPackageManager();
31
+ console.log(`[GenericAdapter] 检测到包管理器: ${packageManager}`);
32
+ // 2. 执行安装
33
+ console.log('[GenericAdapter] 执行依赖安装...');
34
+ await this.runNpmInstall(packageManager);
35
+ // 3. 检测构建命令
36
+ const buildScript = await this.detectBuildScript();
37
+ if (!buildScript) {
38
+ throw new Error(`[GenericAdapter] 未找到 build script\n` +
39
+ `请确保 package.json 中包含 scripts.build 字段。`);
40
+ }
41
+ console.log(`[GenericAdapter] 检测到构建命令: ${buildScript}`);
42
+ // 4. 执行构建
43
+ console.log('[GenericAdapter] 执行项目构建...');
44
+ const originalBuildDir = await this.runNpmBuild(packageManager, buildScript);
45
+ // 5. 复制构建产物
46
+ console.log('[GenericAdapter] 复制构建产物...');
47
+ const files = await this.copyBuildOutput(originalBuildDir);
48
+ // 6. 构建元数据
49
+ const metadata = {
50
+ mode: 'classic', // 外部引擎统一使用 classic 模式
51
+ engine: this.engine,
52
+ buildOutputDir: originalBuildDir,
53
+ };
54
+ // 7. 保存元数据
55
+ await this.saveBuildMetadata(metadata);
56
+ return {
57
+ outputDir: this.options.outputDir,
58
+ metadata,
59
+ files,
60
+ };
61
+ }
62
+ /**
63
+ * 检测包管理器
64
+ */
65
+ async detectPackageManager() {
66
+ const lockFiles = [
67
+ { file: 'pnpm-lock.yaml', manager: 'pnpm' },
68
+ { file: 'yarn.lock', manager: 'yarn' },
69
+ { file: 'package-lock.json', manager: 'npm' },
70
+ ];
71
+ for (const { file, manager } of lockFiles) {
72
+ try {
73
+ await fs.access(path.join(this.projectDir, file));
74
+ return manager;
75
+ }
76
+ catch {
77
+ // 继续检查下一个
78
+ }
79
+ }
80
+ // 默认使用 npm
81
+ return 'npm';
82
+ }
83
+ /**
84
+ * 执行 npm install
85
+ * 超时:3 分钟
86
+ */
87
+ async runNpmInstall(packageManager) {
88
+ const cmd = packageManager === 'npm' ? 'npm' : packageManager;
89
+ const args = ['install'];
90
+ // npm 使用 --legacy-peer-deps 避免冲突
91
+ if (packageManager === 'npm') {
92
+ args.push('--legacy-peer-deps');
93
+ }
94
+ console.log(`[GenericAdapter] 运行: ${cmd} ${args.join(' ')}`);
95
+ await this.runCommandWithTimeout(cmd, args, {
96
+ cwd: this.projectDir,
97
+ timeout: 3 * 60 * 1000, // 3 分钟
98
+ env: {
99
+ ...process.env,
100
+ NODE_ENV: 'production',
101
+ CI: 'true', // 避免交互式提示
102
+ },
103
+ });
104
+ console.log('[GenericAdapter] 依赖安装完成');
105
+ }
106
+ /**
107
+ * 检测 build script
108
+ */
109
+ async detectBuildScript() {
110
+ try {
111
+ const pkgPath = path.join(this.projectDir, 'package.json');
112
+ const content = await fs.readFile(pkgPath, 'utf-8');
113
+ const pkg = JSON.parse(content);
114
+ if (!pkg.scripts) {
115
+ return null;
116
+ }
117
+ // 优先查找常见的 build 命令
118
+ const buildScripts = ['build', 'build:prod', 'build:production', 'dist', 'compile'];
119
+ for (const script of buildScripts) {
120
+ if (pkg.scripts[script]) {
121
+ return script;
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ }
130
+ /**
131
+ * 执行 npm run build
132
+ * 超时:5 分钟
133
+ */
134
+ async runNpmBuild(packageManager, script) {
135
+ const cmd = packageManager === 'npm' ? 'npm' : packageManager;
136
+ const args = packageManager === 'npm' ? ['run', script] : [script];
137
+ console.log(`[GenericAdapter] 运行: ${cmd} ${args.join(' ')}`);
138
+ await this.runCommandWithTimeout(cmd, args, {
139
+ cwd: this.projectDir,
140
+ timeout: 5 * 60 * 1000, // 5 分钟
141
+ env: {
142
+ ...process.env,
143
+ NODE_ENV: 'production',
144
+ CI: 'true',
145
+ },
146
+ });
147
+ console.log('[GenericAdapter] 项目构建完成');
148
+ // 查找构建输出目录
149
+ return this.findBuildOutputDir();
150
+ }
151
+ /**
152
+ * 查找构建输出目录
153
+ * 按优先级搜索:dist/、build/、out/、public/
154
+ */
155
+ async findBuildOutputDir() {
156
+ const possibleDirs = ['dist', 'build', 'out', 'public'];
157
+ for (const dir of possibleDirs) {
158
+ const fullPath = path.join(this.projectDir, dir);
159
+ try {
160
+ const stat = await fs.stat(fullPath);
161
+ if (stat.isDirectory()) {
162
+ // 检查目录中是否有 index.html
163
+ const files = await fs.readdir(fullPath);
164
+ if (files.includes('index.html')) {
165
+ console.log(`[GenericAdapter] 找到构建输出目录: ${dir}`);
166
+ return fullPath;
167
+ }
168
+ }
169
+ }
170
+ catch {
171
+ // 目录不存在,继续检查下一个
172
+ }
173
+ }
174
+ // 如果没找到,尝试从 package.json 读取
175
+ try {
176
+ const pkgPath = path.join(this.projectDir, 'package.json');
177
+ const content = await fs.readFile(pkgPath, 'utf-8');
178
+ const pkg = JSON.parse(content);
179
+ // 常见配置字段
180
+ const possibleFields = ['build.outDir', 'build.outdir', 'outDir', 'output', 'dist', 'buildDir'];
181
+ for (const field of possibleFields) {
182
+ const value = this.getNestedValue(pkg, field);
183
+ if (value && typeof value === 'string') {
184
+ const fullPath = path.join(this.projectDir, value);
185
+ try {
186
+ const stat = await fs.stat(fullPath);
187
+ if (stat.isDirectory()) {
188
+ console.log(`[GenericAdapter] 从 package.json 找到构建目录: ${value}`);
189
+ return fullPath;
190
+ }
191
+ }
192
+ catch {
193
+ // 目录不存在
194
+ }
195
+ }
196
+ }
197
+ }
198
+ catch {
199
+ // 无法读取 package.json
200
+ }
201
+ throw new Error(`[GenericAdapter] 无法找到构建输出目录\n` +
202
+ `请确保构建输出包含 index.html,且位于以下目录之一:\n` +
203
+ possibleDirs.join(', '));
204
+ }
205
+ /**
206
+ * 获取嵌套对象值
207
+ */
208
+ getNestedValue(obj, path) {
209
+ return path.split('.').reduce((current, key) => {
210
+ return current && current[key] !== undefined ? current[key] : undefined;
211
+ }, obj);
212
+ }
213
+ /**
214
+ * 复制构建产物到输出目录
215
+ */
216
+ async copyBuildOutput(buildDir) {
217
+ const files = {
218
+ html: '',
219
+ engine: null,
220
+ config: '',
221
+ settings: null,
222
+ modules: null,
223
+ start: '',
224
+ scenes: [],
225
+ assets: [],
226
+ };
227
+ // 验证构建目录
228
+ const indexPath = path.join(buildDir, 'index.html');
229
+ try {
230
+ await fs.access(indexPath);
231
+ }
232
+ catch {
233
+ throw new Error(`[GenericAdapter] 构建输出缺少 index.html\n` +
234
+ `构建目录: ${buildDir}\n` +
235
+ `请确保构建脚本生成有效的 HTML 入口文件。`);
236
+ }
237
+ // 复制整个构建目录到输出目录
238
+ await this.copyDirectory(buildDir, this.options.outputDir);
239
+ // 设置文件路径
240
+ files.html = path.join(this.options.outputDir, 'index.html');
241
+ // 扫描其他文件
242
+ await this.scanOutputFiles(files);
243
+ console.log(`[GenericAdapter] 构建产物已复制到: ${this.options.outputDir}`);
244
+ return files;
245
+ }
246
+ /**
247
+ * 递归复制目录
248
+ */
249
+ async copyDirectory(src, dest) {
250
+ // 确保目标目录存在
251
+ await fs.mkdir(dest, { recursive: true });
252
+ const entries = await fs.readdir(src, { withFileTypes: true });
253
+ for (const entry of entries) {
254
+ const srcPath = path.join(src, entry.name);
255
+ const destPath = path.join(dest, entry.name);
256
+ // 安全检查:确保目标路径在输出目录内
257
+ const resolvedDest = path.resolve(destPath);
258
+ const resolvedOutput = path.resolve(this.options.outputDir);
259
+ if (!resolvedDest.startsWith(resolvedOutput)) {
260
+ console.warn(`[GenericAdapter] 跳过不安全的文件路径: ${srcPath}`);
261
+ continue;
262
+ }
263
+ if (entry.isDirectory()) {
264
+ await this.copyDirectory(srcPath, destPath);
265
+ }
266
+ else {
267
+ await fs.copyFile(srcPath, destPath);
268
+ }
269
+ }
270
+ }
271
+ /**
272
+ * 扫描输出文件
273
+ */
274
+ async scanOutputFiles(files) {
275
+ const scanDir = async (dir, basePath) => {
276
+ const entries = await fs.readdir(dir, { withFileTypes: true });
277
+ for (const entry of entries) {
278
+ const fullPath = path.join(dir, entry.name);
279
+ const relativePath = path.relative(basePath, fullPath);
280
+ if (entry.isDirectory()) {
281
+ await scanDir(fullPath, basePath);
282
+ }
283
+ else {
284
+ // 分类文件
285
+ const ext = path.extname(entry.name).toLowerCase();
286
+ if (['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.bmp'].includes(ext)) {
287
+ files.assets.push(fullPath);
288
+ }
289
+ else if (['.mp3', '.wav', '.ogg', '.mp4', '.webm'].includes(ext)) {
290
+ files.assets.push(fullPath);
291
+ }
292
+ else if (['.json', '.yaml', '.yml', '.xml'].includes(ext)) {
293
+ if (entry.name === 'config.json') {
294
+ files.config = fullPath;
295
+ }
296
+ else {
297
+ files.assets.push(fullPath);
298
+ }
299
+ }
300
+ }
301
+ }
302
+ };
303
+ try {
304
+ await scanDir(this.options.outputDir, this.options.outputDir);
305
+ }
306
+ catch (error) {
307
+ console.warn('[GenericAdapter] 扫描输出文件失败:', error);
308
+ }
309
+ }
310
+ /**
311
+ * 保存构建元数据
312
+ */
313
+ async saveBuildMetadata(metadata) {
314
+ const metadataPath = path.join(this.options.outputDir, '.build-metadata.json');
315
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
316
+ console.log(`[GenericAdapter] 构建元数据已保存(引擎: ${this.engine})`);
317
+ }
318
+ /**
319
+ * 执行命令并设置超时
320
+ */
321
+ runCommandWithTimeout(cmd, args, options) {
322
+ return new Promise((resolve, reject) => {
323
+ const controller = new AbortController();
324
+ const { signal } = controller;
325
+ const timeoutId = setTimeout(() => {
326
+ controller.abort();
327
+ reject(new Error(`[GenericAdapter] 命令超时(${options.timeout / 1000}秒)\n` +
328
+ `命令: ${cmd} ${args.join(' ')}\n` +
329
+ `请检查项目依赖是否过多,或构建脚本是否有死循环。`));
330
+ }, options.timeout);
331
+ const child = spawn(cmd, args, {
332
+ cwd: options.cwd,
333
+ env: options.env,
334
+ stdio: 'pipe',
335
+ signal,
336
+ });
337
+ let stdout = '';
338
+ let stderr = '';
339
+ child.stdout?.on('data', (data) => {
340
+ stdout += data.toString();
341
+ // 限制输出长度
342
+ if (stdout.length > 10000) {
343
+ stdout = stdout.slice(-5000);
344
+ }
345
+ });
346
+ child.stderr?.on('data', (data) => {
347
+ stderr += data.toString();
348
+ // 限制输出长度
349
+ if (stderr.length > 10000) {
350
+ stderr = stderr.slice(-5000);
351
+ }
352
+ });
353
+ child.on('error', (error) => {
354
+ clearTimeout(timeoutId);
355
+ if (error.name === 'AbortError') {
356
+ reject(new Error(`命令被中止: ${cmd} ${args.join(' ')}`));
357
+ }
358
+ else {
359
+ reject(new Error(`[GenericAdapter] 命令执行失败\n` +
360
+ `命令: ${cmd} ${args.join(' ')}\n` +
361
+ `错误: ${error.message}`));
362
+ }
363
+ });
364
+ child.on('close', (code) => {
365
+ clearTimeout(timeoutId);
366
+ if (code === 0) {
367
+ resolve();
368
+ }
369
+ else {
370
+ reject(new Error(`[GenericAdapter] 命令退出码非零: ${code}\n` +
371
+ `命令: ${cmd} ${args.join(' ')}\n` +
372
+ `stdout: ${stdout.slice(-1000)}\n` +
373
+ `stderr: ${stderr.slice(-1000)}`));
374
+ }
375
+ });
376
+ });
377
+ }
378
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 引擎适配器模块
3
+ * 支持多引擎构建的基础架构
4
+ */
5
+ export { EngineDetector } from './engine-detector.js';
6
+ export { PlayCanvasAdapter } from './playcanvas-adapter.js';
7
+ export { GenericAdapter } from './generic-adapter.js';