@playcraft/build 0.0.2

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 (74) hide show
  1. package/README.md +96 -0
  2. package/dist/base-builder.d.ts +66 -0
  3. package/dist/base-builder.js +415 -0
  4. package/dist/converter.d.ts +35 -0
  5. package/dist/converter.js +148 -0
  6. package/dist/generators/config-generator.d.ts +7 -0
  7. package/dist/generators/config-generator.js +122 -0
  8. package/dist/generators/settings-generator.d.ts +14 -0
  9. package/dist/generators/settings-generator.js +100 -0
  10. package/dist/index.d.ts +14 -0
  11. package/dist/index.js +14 -0
  12. package/dist/loaders/playcanvas-loader.d.ts +10 -0
  13. package/dist/loaders/playcanvas-loader.js +18 -0
  14. package/dist/loaders/playcraft-loader.d.ts +10 -0
  15. package/dist/loaders/playcraft-loader.js +51 -0
  16. package/dist/platforms/applovin.d.ts +10 -0
  17. package/dist/platforms/applovin.js +67 -0
  18. package/dist/platforms/base.d.ts +29 -0
  19. package/dist/platforms/base.js +11 -0
  20. package/dist/platforms/bigo.d.ts +15 -0
  21. package/dist/platforms/bigo.js +77 -0
  22. package/dist/platforms/facebook.d.ts +9 -0
  23. package/dist/platforms/facebook.js +37 -0
  24. package/dist/platforms/google.d.ts +10 -0
  25. package/dist/platforms/google.js +53 -0
  26. package/dist/platforms/index.d.ts +14 -0
  27. package/dist/platforms/index.js +47 -0
  28. package/dist/platforms/ironsource.d.ts +10 -0
  29. package/dist/platforms/ironsource.js +71 -0
  30. package/dist/platforms/liftoff.d.ts +10 -0
  31. package/dist/platforms/liftoff.js +56 -0
  32. package/dist/platforms/moloco.d.ts +10 -0
  33. package/dist/platforms/moloco.js +53 -0
  34. package/dist/platforms/snapchat.d.ts +10 -0
  35. package/dist/platforms/snapchat.js +59 -0
  36. package/dist/platforms/tiktok.d.ts +15 -0
  37. package/dist/platforms/tiktok.js +65 -0
  38. package/dist/platforms/unity.d.ts +10 -0
  39. package/dist/platforms/unity.js +69 -0
  40. package/dist/playable-builder.d.ts +97 -0
  41. package/dist/playable-builder.js +590 -0
  42. package/dist/types.d.ts +90 -0
  43. package/dist/types.js +1 -0
  44. package/dist/vite/config-builder.d.ts +15 -0
  45. package/dist/vite/config-builder.js +212 -0
  46. package/dist/vite/platform-configs.d.ts +38 -0
  47. package/dist/vite/platform-configs.js +257 -0
  48. package/dist/vite/plugin-model-compression.d.ts +11 -0
  49. package/dist/vite/plugin-model-compression.js +63 -0
  50. package/dist/vite/plugin-platform.d.ts +17 -0
  51. package/dist/vite/plugin-platform.js +241 -0
  52. package/dist/vite/plugin-playcanvas.d.ts +18 -0
  53. package/dist/vite/plugin-playcanvas.js +711 -0
  54. package/dist/vite/plugin-source-builder.d.ts +15 -0
  55. package/dist/vite/plugin-source-builder.js +344 -0
  56. package/dist/vite-builder.d.ts +51 -0
  57. package/dist/vite-builder.js +122 -0
  58. package/package.json +51 -0
  59. package/templates/__loading__.js +100 -0
  60. package/templates/__modules__.js +47 -0
  61. package/templates/__settings__.template.js +20 -0
  62. package/templates/__start__.js +332 -0
  63. package/templates/index.html +18 -0
  64. package/templates/logo.png +0 -0
  65. package/templates/manifest.json +1 -0
  66. package/templates/patches/cannon.min.js +28 -0
  67. package/templates/patches/lz4.js +10 -0
  68. package/templates/patches/one-page-http-get.js +20 -0
  69. package/templates/patches/one-page-inline-game-scripts.js +20 -0
  70. package/templates/patches/one-page-mraid-resize-canvas.js +46 -0
  71. package/templates/patches/p2.min.js +27 -0
  72. package/templates/patches/playcraft-no-xhr.js +52 -0
  73. package/templates/playcanvas-stable.min.js +16363 -0
  74. package/templates/styles.css +43 -0
@@ -0,0 +1,148 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import mimeTypes from 'mime-types';
4
+ export class OnePageConverter {
5
+ constructor(projectDir, options) {
6
+ this.assets = [];
7
+ this.projectDir = projectDir;
8
+ this.options = options;
9
+ }
10
+ /**
11
+ * 添加资产到转换列表
12
+ */
13
+ addAsset(assetPath, type) {
14
+ this.assets.push({
15
+ path: assetPath,
16
+ type,
17
+ size: 0,
18
+ });
19
+ }
20
+ /**
21
+ * 编码所有资产为 Base64
22
+ */
23
+ async encodeAssets() {
24
+ for (const asset of this.assets) {
25
+ const fullPath = path.join(this.projectDir, asset.path);
26
+ try {
27
+ const fileBuffer = await fs.readFile(fullPath);
28
+ asset.size = fileBuffer.length;
29
+ asset.base64 = fileBuffer.toString('base64');
30
+ asset.mimeType = mimeTypes.lookup(asset.path) || 'application/octet-stream';
31
+ }
32
+ catch (error) {
33
+ console.warn(`Warning: Failed to encode asset ${asset.path}: ${error.message}`);
34
+ }
35
+ }
36
+ }
37
+ /**
38
+ * 获取资产的 Base64 数据 URL
39
+ */
40
+ getDataUrl(assetPath) {
41
+ if (!assetPath)
42
+ return null;
43
+ // 移除查询参数
44
+ let cleanPath = assetPath;
45
+ if (cleanPath.includes('?')) {
46
+ cleanPath = cleanPath.split('?')[0];
47
+ }
48
+ // 解码 URL 编码的路径
49
+ try {
50
+ cleanPath = decodeURIComponent(cleanPath);
51
+ }
52
+ catch (e) {
53
+ // 如果解码失败,使用原始路径
54
+ }
55
+ // 1. 精确匹配
56
+ let asset = this.assets.find(a => a.path === cleanPath);
57
+ // 2. 尝试移除前导斜杠
58
+ if (!asset && cleanPath.startsWith('/')) {
59
+ asset = this.assets.find(a => a.path === cleanPath.substring(1));
60
+ }
61
+ // 3. 尝试模糊匹配(文件名匹配)
62
+ if (!asset) {
63
+ const filename = path.basename(cleanPath);
64
+ asset = this.assets.find(a => {
65
+ const assetFilename = path.basename(a.path);
66
+ return assetFilename === filename;
67
+ });
68
+ }
69
+ // 4. 尝试部分路径匹配(处理 files/assets/... 格式)
70
+ if (!asset) {
71
+ asset = this.assets.find(a => a.path.endsWith(cleanPath) || cleanPath.endsWith(a.path));
72
+ }
73
+ if (!asset || !asset.base64) {
74
+ return null;
75
+ }
76
+ return `data:${asset.mimeType};base64,${asset.base64}`;
77
+ }
78
+ /**
79
+ * 替换 HTML 中的 URL 为 Base64 数据 URL
80
+ */
81
+ replaceUrlsWithBase64(html, urlMap) {
82
+ let result = html;
83
+ for (const [url, dataUrl] of urlMap.entries()) {
84
+ // 替换各种可能的 URL 格式
85
+ result = result.replace(new RegExp(url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), dataUrl);
86
+ // 也替换相对路径
87
+ const relativeUrl = url.startsWith('/') ? url.substring(1) : url;
88
+ result = result.replace(new RegExp(relativeUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), dataUrl);
89
+ }
90
+ return result;
91
+ }
92
+ /**
93
+ * 内联脚本到 HTML
94
+ */
95
+ async inlineScripts(html) {
96
+ // 查找所有 <script src="..."> 标签
97
+ const scriptRegex = /<script\s+src=["']([^"']+)["'][^>]*><\/script>/gi;
98
+ let result = html;
99
+ const matches = [];
100
+ let match;
101
+ // 先收集所有匹配项
102
+ while ((match = scriptRegex.exec(html)) !== null) {
103
+ matches.push({
104
+ fullMatch: match[0],
105
+ scriptPath: match[1],
106
+ });
107
+ }
108
+ // 处理每个脚本
109
+ for (const { fullMatch, scriptPath } of matches) {
110
+ // 尝试多个可能的路径
111
+ const possiblePaths = [
112
+ path.join(this.projectDir, scriptPath),
113
+ path.join(this.projectDir, scriptPath.replace(/^\//, '')),
114
+ scriptPath, // 绝对路径
115
+ ];
116
+ let scriptContent = null;
117
+ for (const fullPath of possiblePaths) {
118
+ try {
119
+ scriptContent = await fs.readFile(fullPath, 'utf-8');
120
+ break;
121
+ }
122
+ catch (error) {
123
+ // 继续尝试下一个路径
124
+ }
125
+ }
126
+ if (scriptContent) {
127
+ // 替换为内联脚本
128
+ result = result.replace(fullMatch, `<script>${scriptContent}</script>`);
129
+ }
130
+ else {
131
+ console.warn(`Warning: Failed to inline script ${scriptPath}`);
132
+ }
133
+ }
134
+ return result;
135
+ }
136
+ /**
137
+ * 获取所有资产信息
138
+ */
139
+ getAssets() {
140
+ return this.assets;
141
+ }
142
+ /**
143
+ * 获取总大小
144
+ */
145
+ getTotalSize() {
146
+ return this.assets.reduce((sum, asset) => sum + asset.size, 0);
147
+ }
148
+ }
@@ -0,0 +1,7 @@
1
+ import { PlayCanvasProject } from '../loaders/playcanvas-loader.js';
2
+ import { PlayCraftProject } from '../loaders/playcraft-loader.js';
3
+ export type ProjectConfig = PlayCanvasProject | PlayCraftProject;
4
+ /**
5
+ * 生成 PlayCanvas 构建产物格式的 config.json
6
+ */
7
+ export declare function generateConfig(projectConfig: ProjectConfig): any;
@@ -0,0 +1,122 @@
1
+ function shouldIncludeAsset(asset, scriptIds, requiredScriptIds) {
2
+ if (!asset) {
3
+ return false;
4
+ }
5
+ if (asset.type === 'folder') {
6
+ return false;
7
+ }
8
+ // PlayCanvas 源资产(source=true)不进入构建产物
9
+ if (asset.source === true) {
10
+ return false;
11
+ }
12
+ // 字体源文件(ttf/otf)不进入构建产物
13
+ const filename = asset.file?.filename?.toLowerCase?.();
14
+ if (filename && (filename.endsWith('.ttf') || filename.endsWith('.otf'))) {
15
+ return false;
16
+ }
17
+ // 仅保留在 settings.scripts 中声明的脚本资产
18
+ if (asset.type === 'script' && asset.id != null) {
19
+ const id = String(asset.id);
20
+ return scriptIds.has(id) || requiredScriptIds.has(id);
21
+ }
22
+ return true;
23
+ }
24
+ function collectRequiredScriptIds(assets) {
25
+ const required = new Set();
26
+ for (const asset of Object.values(assets)) {
27
+ const assetData = asset;
28
+ if (assetData?.type === 'wasm' && assetData?.data) {
29
+ const glueId = assetData.data.glueScriptId;
30
+ const fallbackId = assetData.data.fallbackScriptId;
31
+ if (glueId != null) {
32
+ required.add(String(glueId));
33
+ }
34
+ if (fallbackId != null) {
35
+ required.add(String(fallbackId));
36
+ }
37
+ }
38
+ }
39
+ return required;
40
+ }
41
+ /**
42
+ * 生成 PlayCanvas 构建产物格式的 config.json
43
+ */
44
+ export function generateConfig(projectConfig) {
45
+ if (projectConfig.format === 'playcanvas') {
46
+ const pcProject = projectConfig;
47
+ // 从 PlayCanvas 项目格式生成 config.json
48
+ const config = {
49
+ application_properties: pcProject.project.settings || {},
50
+ };
51
+ // 兼容官方构建:确保 libraries/batchGroups 为数组
52
+ if (!Array.isArray(config.application_properties.libraries)) {
53
+ config.application_properties.libraries = [];
54
+ }
55
+ if (!Array.isArray(config.application_properties.batchGroups)) {
56
+ config.application_properties.batchGroups = [];
57
+ }
58
+ // 处理场景
59
+ if (pcProject.scenes) {
60
+ if (Array.isArray(pcProject.scenes)) {
61
+ config.scenes = pcProject.scenes.map((scene) => ({
62
+ name: scene.name || scene.id,
63
+ url: scene.url || scene.file || `${scene.id}.json`,
64
+ }));
65
+ }
66
+ else {
67
+ // scenes.json 是对象格式
68
+ config.scenes = Object.entries(pcProject.scenes).map(([id, scene]) => ({
69
+ name: scene.name || id,
70
+ url: scene.url || scene.file || `${id}.json`,
71
+ }));
72
+ }
73
+ }
74
+ // 处理资产(过滤非运行时资产)
75
+ if (pcProject.assets) {
76
+ const scriptIds = new Set((pcProject.project.settings?.scripts || []).map((id) => String(id)));
77
+ const requiredScriptIds = collectRequiredScriptIds(pcProject.assets);
78
+ const filtered = {};
79
+ for (const [assetId, asset] of Object.entries(pcProject.assets)) {
80
+ if (shouldIncludeAsset(asset, scriptIds, requiredScriptIds)) {
81
+ filtered[assetId] = asset;
82
+ }
83
+ }
84
+ config.assets = filtered;
85
+ }
86
+ return config;
87
+ }
88
+ else {
89
+ // PlayCraft 格式
90
+ const pcProject = projectConfig;
91
+ const config = {
92
+ application_properties: pcProject.manifest.settings || {},
93
+ };
94
+ // 兼容官方构建:确保 libraries/batchGroups 为数组
95
+ if (!Array.isArray(config.application_properties.libraries)) {
96
+ config.application_properties.libraries = [];
97
+ }
98
+ if (!Array.isArray(config.application_properties.batchGroups)) {
99
+ config.application_properties.batchGroups = [];
100
+ }
101
+ // 处理场景
102
+ if (pcProject.scenes && pcProject.scenes.length > 0) {
103
+ config.scenes = pcProject.scenes.map((scene) => ({
104
+ name: scene.name || scene.id,
105
+ url: scene.url || `${scene.id || 'scene'}.json`,
106
+ }));
107
+ }
108
+ // 处理资产(过滤非运行时资产)
109
+ if (pcProject.assets) {
110
+ const scriptIds = new Set((pcProject.manifest.settings?.scripts || []).map((id) => String(id)));
111
+ const requiredScriptIds = collectRequiredScriptIds(pcProject.assets);
112
+ const filtered = {};
113
+ for (const [assetId, asset] of Object.entries(pcProject.assets)) {
114
+ if (shouldIncludeAsset(asset, scriptIds, requiredScriptIds)) {
115
+ filtered[assetId] = asset;
116
+ }
117
+ }
118
+ config.assets = filtered;
119
+ }
120
+ return config;
121
+ }
122
+ }
@@ -0,0 +1,14 @@
1
+ import { PlayCanvasProject } from '../loaders/playcanvas-loader.js';
2
+ import { PlayCraftProject } from '../loaders/playcraft-loader.js';
3
+ export type ProjectConfig = PlayCanvasProject | PlayCraftProject;
4
+ export interface PreloadModule {
5
+ moduleName: string;
6
+ glueUrl: string;
7
+ wasmUrl: string;
8
+ fallbackUrl?: string;
9
+ preload?: boolean;
10
+ }
11
+ /**
12
+ * 生成 __settings__.js 文件内容
13
+ */
14
+ export declare function generateSettings(projectConfig: ProjectConfig, scenePath?: string): string;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * 提取预加载模块(WASM 等)
3
+ */
4
+ function extractPreloadModules(projectConfig) {
5
+ const modules = [];
6
+ const assets = projectConfig.format === 'playcanvas'
7
+ ? projectConfig.assets
8
+ : projectConfig.assets;
9
+ if (!assets) {
10
+ return modules;
11
+ }
12
+ for (const asset of Object.values(assets)) {
13
+ const assetData = asset;
14
+ if (assetData.type !== 'wasm') {
15
+ continue;
16
+ }
17
+ const wasmUrl = assetData.file?.url;
18
+ const moduleName = assetData.data?.moduleName;
19
+ const glueId = assetData.data?.glueScriptId;
20
+ const fallbackId = assetData.data?.fallbackScriptId;
21
+ if (!wasmUrl || !moduleName || !glueId) {
22
+ continue;
23
+ }
24
+ const glueAsset = assets[String(glueId)];
25
+ const fallbackAsset = fallbackId ? assets[String(fallbackId)] : null;
26
+ const glueUrl = glueAsset?.file?.url;
27
+ const fallbackUrl = fallbackAsset?.file?.url;
28
+ if (!glueUrl) {
29
+ continue;
30
+ }
31
+ modules.push({
32
+ moduleName,
33
+ glueUrl,
34
+ wasmUrl,
35
+ fallbackUrl,
36
+ preload: true,
37
+ });
38
+ }
39
+ return modules;
40
+ }
41
+ /**
42
+ * 生成 __settings__.js 文件内容
43
+ */
44
+ export function generateSettings(projectConfig, scenePath) {
45
+ let settings;
46
+ let scripts = [];
47
+ if (projectConfig.format === 'playcanvas') {
48
+ const pcProject = projectConfig;
49
+ settings = pcProject.project.settings || {};
50
+ scripts = settings.scripts || [];
51
+ }
52
+ else {
53
+ const pcProject = projectConfig;
54
+ settings = pcProject.manifest.settings || {};
55
+ scripts = settings.scripts || [];
56
+ }
57
+ // 确定场景路径
58
+ let finalScenePath = scenePath || '';
59
+ if (!finalScenePath && projectConfig.format === 'playcanvas') {
60
+ const pcProject = projectConfig;
61
+ if (pcProject.scenes) {
62
+ const scenes = Array.isArray(pcProject.scenes)
63
+ ? pcProject.scenes
64
+ : Object.values(pcProject.scenes);
65
+ if (scenes.length > 0) {
66
+ const firstScene = scenes[0];
67
+ finalScenePath = firstScene.url || firstScene.file || `${firstScene.id || 'scene'}.json`;
68
+ }
69
+ }
70
+ }
71
+ else if (!finalScenePath) {
72
+ const pcProject = projectConfig;
73
+ if (pcProject.scenes && pcProject.scenes.length > 0) {
74
+ const firstScene = pcProject.scenes[0];
75
+ finalScenePath = firstScene.url || `${firstScene.id || 'scene'}.json`;
76
+ }
77
+ }
78
+ const preloadModules = extractPreloadModules(projectConfig);
79
+ return `window.ASSET_PREFIX = "";
80
+ window.SCRIPT_PREFIX = "";
81
+ window.SCENE_PATH = "${finalScenePath}";
82
+ window.CONTEXT_OPTIONS = {
83
+ antialias: ${settings.antiAlias ?? true},
84
+ alpha: false,
85
+ preserveDrawingBuffer: ${settings.preserveDrawingBuffer ?? false},
86
+ deviceTypes: ['webgl2', 'webgl1'],
87
+ powerPreference: "${settings.powerPreference || 'default'}"
88
+ };
89
+ window.SCRIPTS = ${JSON.stringify(scripts)};
90
+ window.CONFIG_FILENAME = "config.json";
91
+ window.INPUT_SETTINGS = {
92
+ useKeyboard: ${settings.useKeyboard ?? true},
93
+ useMouse: ${settings.useMouse ?? true},
94
+ useGamepads: ${settings.useGamepads ?? false},
95
+ useTouch: ${settings.useTouch ?? true}
96
+ };
97
+ pc.script.legacy = ${settings.useLegacyScripts ?? false};
98
+ window.PRELOAD_MODULES = ${JSON.stringify(preloadModules)};
99
+ `;
100
+ }
@@ -0,0 +1,14 @@
1
+ export { BaseBuilder } from './base-builder.js';
2
+ export type { BaseBuildOptions, BaseBuildOutput } from './base-builder.js';
3
+ export { ViteBuilder } from './vite-builder.js';
4
+ export type { ViteBuildOutput } from './vite-builder.js';
5
+ export { PlayableBuilder } from './playable-builder.js';
6
+ export type { PlayableBuildOutput } from './playable-builder.js';
7
+ export { OnePageConverter } from './converter.js';
8
+ export { createPlatformAdapter, PlatformAdapter } from './platforms/index.js';
9
+ export { FacebookAdapter } from './platforms/facebook.js';
10
+ export { SnapchatAdapter } from './platforms/snapchat.js';
11
+ export { ViteConfigBuilder } from './vite/config-builder.js';
12
+ export { PLATFORM_CONFIGS } from './vite/platform-configs.js';
13
+ export type { PlatformViteConfig } from './vite/platform-configs.js';
14
+ export * from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ // 导出构建器
2
+ export { BaseBuilder } from './base-builder.js';
3
+ export { ViteBuilder } from './vite-builder.js';
4
+ export { PlayableBuilder } from './playable-builder.js';
5
+ export { OnePageConverter } from './converter.js';
6
+ // 导出平台适配器
7
+ export { createPlatformAdapter, PlatformAdapter } from './platforms/index.js';
8
+ export { FacebookAdapter } from './platforms/facebook.js';
9
+ export { SnapchatAdapter } from './platforms/snapchat.js';
10
+ // 导出 Vite 配置
11
+ export { ViteConfigBuilder } from './vite/config-builder.js';
12
+ export { PLATFORM_CONFIGS } from './vite/platform-configs.js';
13
+ // 导出类型
14
+ export * from './types.js';
@@ -0,0 +1,10 @@
1
+ export interface PlayCanvasProject {
2
+ project: any;
3
+ scenes: any;
4
+ assets: any;
5
+ format: 'playcanvas';
6
+ }
7
+ /**
8
+ * 加载 PlayCanvas 源代码项目
9
+ */
10
+ export declare function loadPlayCanvasProject(projectDir: string): Promise<PlayCanvasProject>;
@@ -0,0 +1,18 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ /**
4
+ * 加载 PlayCanvas 源代码项目
5
+ */
6
+ export async function loadPlayCanvasProject(projectDir) {
7
+ const [projectJson, scenesJson, assetsJson] = await Promise.all([
8
+ fs.readFile(path.join(projectDir, 'project.json'), 'utf-8'),
9
+ fs.readFile(path.join(projectDir, 'scenes.json'), 'utf-8'),
10
+ fs.readFile(path.join(projectDir, 'assets.json'), 'utf-8'),
11
+ ]);
12
+ return {
13
+ project: JSON.parse(projectJson),
14
+ scenes: JSON.parse(scenesJson),
15
+ assets: JSON.parse(assetsJson),
16
+ format: 'playcanvas',
17
+ };
18
+ }
@@ -0,0 +1,10 @@
1
+ export interface PlayCraftProject {
2
+ manifest: any;
3
+ scenes: any[];
4
+ assets: any;
5
+ format: 'playcraft';
6
+ }
7
+ /**
8
+ * 加载 PlayCraft 源代码项目
9
+ */
10
+ export declare function loadPlayCraftProject(projectDir: string): Promise<PlayCraftProject>;
@@ -0,0 +1,51 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ /**
4
+ * 加载 PlayCraft 源代码项目
5
+ */
6
+ export async function loadPlayCraftProject(projectDir) {
7
+ // 1. 读取 manifest.json
8
+ const manifestContent = await fs.readFile(path.join(projectDir, 'manifest.json'), 'utf-8');
9
+ const manifest = JSON.parse(manifestContent);
10
+ // 2. 读取场景文件
11
+ const scenesDir = path.join(projectDir, 'scenes');
12
+ let scenes = [];
13
+ try {
14
+ const sceneFiles = await fs.readdir(scenesDir);
15
+ scenes = await Promise.all(sceneFiles
16
+ .filter(f => f.endsWith('.scene.json'))
17
+ .map(async (f) => {
18
+ const content = await fs.readFile(path.join(scenesDir, f), 'utf-8');
19
+ return JSON.parse(content);
20
+ }));
21
+ }
22
+ catch (error) {
23
+ // scenes 目录可能不存在
24
+ console.warn('警告: scenes 目录不存在,跳过场景加载');
25
+ }
26
+ // 3. 读取 assets.json(PlayCraft 保留的 PlayCanvas 兼容层)
27
+ let assets = {};
28
+ const assetsJsonPath = path.join(projectDir, 'assets', 'assets.json');
29
+ try {
30
+ const assetsContent = await fs.readFile(assetsJsonPath, 'utf-8');
31
+ assets = JSON.parse(assetsContent);
32
+ }
33
+ catch (error) {
34
+ // assets.json 可能不存在,尝试从 manifest 中读取
35
+ if (manifest.assets && Array.isArray(manifest.assets)) {
36
+ // 转换为 PlayCanvas 格式(key-value 对象)
37
+ assets = {};
38
+ for (const asset of manifest.assets) {
39
+ if (asset.id) {
40
+ assets[asset.id] = asset;
41
+ }
42
+ }
43
+ }
44
+ }
45
+ return {
46
+ manifest,
47
+ scenes,
48
+ assets,
49
+ format: 'playcraft',
50
+ };
51
+ }
@@ -0,0 +1,10 @@
1
+ import { PlatformAdapter } from './base.js';
2
+ import type { AssetInfo } from '../types.js';
3
+ export declare class AppLovinAdapter extends PlatformAdapter {
4
+ getName(): string;
5
+ getSizeLimit(): number;
6
+ getDefaultFormat(): 'html' | 'zip';
7
+ modifyHTML(html: string, assets: AssetInfo[]): string;
8
+ getPlatformScript(): string;
9
+ validateOptions(): void;
10
+ }
@@ -0,0 +1,67 @@
1
+ import { PlatformAdapter } from './base.js';
2
+ export class AppLovinAdapter extends PlatformAdapter {
3
+ getName() {
4
+ return 'AppLovin';
5
+ }
6
+ getSizeLimit() {
7
+ // AppLovin: 5MB
8
+ return 5 * 1024 * 1024;
9
+ }
10
+ getDefaultFormat() {
11
+ return 'html';
12
+ }
13
+ modifyHTML(html, assets) {
14
+ // AppLovin 需要 MRAID v2.0 支持
15
+ const appLovinScript = `
16
+ <script>
17
+ // MRAID 2.0 API
18
+ window.mraid = window.mraid || {
19
+ getVersion: function() { return '2.0'; },
20
+ isReady: function() { return true; },
21
+ open: function(url) {
22
+ console.log('AppLovin CTA: opening store');
23
+ window.open(url);
24
+ },
25
+ close: function() {
26
+ console.warn('AppLovin: close() button is disabled, handled by platform');
27
+ },
28
+ addEventListener: function(event, listener) {
29
+ if (event === 'ready') {
30
+ setTimeout(listener, 0);
31
+ }
32
+ },
33
+ removeEventListener: function(event, listener) {},
34
+ getState: function() { return 'default'; },
35
+ getPlacementType: function() { return 'interstitial'; },
36
+ isViewable: function() { return true; }
37
+ };
38
+ </script>
39
+ <!--
40
+ 注意:
41
+ - AppLovin 要求必须同时支持横屏和竖屏
42
+ - 禁止添加退出按钮(平台统一处理)
43
+ - CTA 使用 mraid.open()
44
+ -->
45
+ `;
46
+ // 在 </head> 之前插入
47
+ return html.replace('</head>', `${appLovinScript}</head>`);
48
+ }
49
+ getPlatformScript() {
50
+ return `
51
+ // AppLovin Playable Ad
52
+ // 使用 mraid.open() 跳转应用商店
53
+ // 禁止自定义退出按钮
54
+
55
+ if (typeof mraid !== 'undefined') {
56
+ mraid.addEventListener('ready', function() {
57
+ console.log('MRAID ready');
58
+ });
59
+ }
60
+ `;
61
+ }
62
+ validateOptions() {
63
+ if (this.options.format && this.options.format !== 'html') {
64
+ console.warn('警告: AppLovin 使用单文件 HTML 格式');
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,29 @@
1
+ import type { BuildOptions, AssetInfo } from '../types.js';
2
+ export declare abstract class PlatformAdapter {
3
+ protected options: BuildOptions;
4
+ constructor(options: BuildOptions);
5
+ /**
6
+ * 获取平台名称
7
+ */
8
+ abstract getName(): string;
9
+ /**
10
+ * 获取大小限制(字节)
11
+ */
12
+ abstract getSizeLimit(): number;
13
+ /**
14
+ * 获取默认格式
15
+ */
16
+ abstract getDefaultFormat(): 'html' | 'zip';
17
+ /**
18
+ * 应用平台特定的 HTML 修改
19
+ */
20
+ abstract modifyHTML(html: string, assets: AssetInfo[]): string;
21
+ /**
22
+ * 获取平台特定的 JavaScript 代码
23
+ */
24
+ abstract getPlatformScript(): string;
25
+ /**
26
+ * 验证配置
27
+ */
28
+ validateOptions(): void;
29
+ }
@@ -0,0 +1,11 @@
1
+ export class PlatformAdapter {
2
+ constructor(options) {
3
+ this.options = options;
4
+ }
5
+ /**
6
+ * 验证配置
7
+ */
8
+ validateOptions() {
9
+ // 默认实现,子类可以覆盖
10
+ }
11
+ }
@@ -0,0 +1,15 @@
1
+ import { PlatformAdapter } from './base.js';
2
+ import type { AssetInfo } from '../types.js';
3
+ export declare class BigoAdapter extends PlatformAdapter {
4
+ getName(): string;
5
+ getSizeLimit(): number;
6
+ getDefaultFormat(): 'html' | 'zip';
7
+ modifyHTML(html: string, assets: AssetInfo[]): string;
8
+ getPlatformScript(): string;
9
+ /**
10
+ * 生成 config.json(BIGO 要求)
11
+ * @param orientation 0=横或竖, 1=仅竖屏, 2=仅横屏
12
+ */
13
+ generateConfigJson(orientation?: 0 | 1 | 2): object;
14
+ validateOptions(): void;
15
+ }