@playcraft/cli 0.0.52 → 0.0.55

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 (79) hide show
  1. package/dist/commands/create.js +164 -98
  2. package/dist/index.js +4 -1
  3. package/dist/scaffold/templates.js +35 -0
  4. package/package.json +3 -3
  5. package/project-templates/playable-template-phaser/.playcraftignore +10 -0
  6. package/project-templates/playable-template-phaser/CLAUDE.md +178 -0
  7. package/project-templates/playable-template-phaser/PLAYCRAFT-MIGRATION.md +328 -0
  8. package/project-templates/playable-template-phaser/README.md +381 -0
  9. package/project-templates/playable-template-phaser/assets/brain.png +0 -0
  10. package/project-templates/playable-template-phaser/assets/button.png +0 -0
  11. package/project-templates/playable-template-phaser/assets/interact.png +0 -0
  12. package/project-templates/playable-template-phaser/assets/logo.webp +0 -0
  13. package/project-templates/playable-template-phaser/assets/warn_brain.png +0 -0
  14. package/project-templates/playable-template-phaser/builds.config.js +12 -0
  15. package/project-templates/playable-template-phaser/globals.d.ts +2 -0
  16. package/project-templates/playable-template-phaser/index.html +12 -0
  17. package/project-templates/playable-template-phaser/package.json +45 -0
  18. package/project-templates/playable-template-phaser/scripts/base64-to-mp3.js +340 -0
  19. package/project-templates/playable-template-phaser/scripts/convert-to-webp.js +370 -0
  20. package/project-templates/playable-template-phaser/src/Game.ts +154 -0
  21. package/project-templates/playable-template-phaser/src/config/LoadingConfig.ts +40 -0
  22. package/project-templates/playable-template-phaser/src/config/ThemeConfig.ts +81 -0
  23. package/project-templates/playable-template-phaser/src/index.css +18 -0
  24. package/project-templates/playable-template-phaser/src/index.html +9 -0
  25. package/project-templates/playable-template-phaser/src/index.ts +30 -0
  26. package/project-templates/playable-template-phaser/src/json5.d.ts +4 -0
  27. package/project-templates/playable-template-phaser/src/managers/AudioManager.ts +559 -0
  28. package/project-templates/playable-template-phaser/src/scenes/MainScene.ts +301 -0
  29. package/project-templates/playable-template-phaser/src/scenes/PreloadScene.ts +256 -0
  30. package/project-templates/playable-template-phaser/src/theme/README.md +194 -0
  31. package/project-templates/playable-template-phaser/src/theme/blue-theme/assets/.gitkeep +24 -0
  32. package/project-templates/playable-template-phaser/src/theme/blue-theme/index.ts +27 -0
  33. package/project-templates/playable-template-phaser/src/theme/blue-theme/theme.data.json5 +19 -0
  34. package/project-templates/playable-template-phaser/src/theme/green-theme/assets/.gitkeep +3 -0
  35. package/project-templates/playable-template-phaser/src/theme/green-theme/index.ts +20 -0
  36. package/project-templates/playable-template-phaser/src/theme/green-theme/theme.data.json5 +19 -0
  37. package/project-templates/playable-template-phaser/src/theme/index.ts +1 -0
  38. package/project-templates/playable-template-phaser/src/theme/red-theme/assets/.gitkeep +3 -0
  39. package/project-templates/playable-template-phaser/src/theme/red-theme/index.ts +20 -0
  40. package/project-templates/playable-template-phaser/src/theme/red-theme/theme.data.json5 +19 -0
  41. package/project-templates/playable-template-phaser/src/theme/theme.metadata.md +60 -0
  42. package/project-templates/playable-template-phaser/src/theme/theme.schema.json5 +149 -0
  43. package/project-templates/playable-template-phaser/src/ui/BrainAgeUI.ts +681 -0
  44. package/project-templates/playable-template-phaser/src/ui/CountdownUI.ts +565 -0
  45. package/project-templates/playable-template-phaser/src/ui/PlayButton.ts +51 -0
  46. package/project-templates/playable-template-phaser/src/ui/ScoreUI.ts +207 -0
  47. package/project-templates/playable-template-phaser/src/ui/SuccessUI.ts +43 -0
  48. package/project-templates/playable-template-phaser/src/ui/index.ts +15 -0
  49. package/project-templates/playable-template-phaser/tsconfig.json +26 -0
  50. package/project-templates/playable-template-phaser/vite.config.ts +64 -0
  51. package/project-templates/playable-template-phaser//346/226/260/346/211/213/345/274/200/345/217/221/346/214/207/345/215/227.md +209 -0
  52. package/project-templates/playable-template-three/README.md +795 -0
  53. package/project-templates/playable-template-three/assets/Stork.glb +0 -0
  54. package/project-templates/playable-template-three/assets/button.png +0 -0
  55. package/project-templates/playable-template-three/assets/logo.webp +0 -0
  56. package/project-templates/playable-template-three/build.json +7 -0
  57. package/project-templates/playable-template-three/builds.config.example.js +151 -0
  58. package/project-templates/playable-template-three/builds.config.js +158 -0
  59. package/project-templates/playable-template-three/package.json +36 -0
  60. package/project-templates/playable-template-three/scripts/convert-to-webp.js +370 -0
  61. package/project-templates/playable-template-three/scripts/theme-builds.js +514 -0
  62. package/project-templates/playable-template-three/src/Game.ts +215 -0
  63. package/project-templates/playable-template-three/src/config/LoadingConfig.ts +27 -0
  64. package/project-templates/playable-template-three/src/config/ThemeConfig.ts +36 -0
  65. package/project-templates/playable-template-three/src/global.d.ts +108 -0
  66. package/project-templates/playable-template-three/src/index.css +18 -0
  67. package/project-templates/playable-template-three/src/index.html +9 -0
  68. package/project-templates/playable-template-three/src/index.ts +20 -0
  69. package/project-templates/playable-template-three/src/managers/AudioManager.ts +538 -0
  70. package/project-templates/playable-template-three/src/scenes/PreloadScene.ts +305 -0
  71. package/project-templates/playable-template-three/src/theme/README.md +225 -0
  72. package/project-templates/playable-template-three/src/theme/blue-theme/assets/.gitkeep +28 -0
  73. package/project-templates/playable-template-three/src/theme/blue-theme/index.ts +45 -0
  74. package/project-templates/playable-template-three/src/theme/current.ts +12 -0
  75. package/project-templates/playable-template-three/src/theme/green-theme/assets/.gitkeep +3 -0
  76. package/project-templates/playable-template-three/src/theme/green-theme/index.ts +34 -0
  77. package/project-templates/playable-template-three/src/theme/red-theme/assets/.gitkeep +3 -0
  78. package/project-templates/playable-template-three/src/theme/red-theme/index.ts +34 -0
  79. package/project-templates/playable-template-three/tsconfig.json +24 -0
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Phaser Template 批量构建配置
3
+ *
4
+ * 支持多主题批量构建,适合 A/B 测试和版本迭代
5
+ */
6
+
7
+ module.exports = {
8
+ // 项目名称(用于 html title生成)
9
+ title: 'Phaser-Template',
10
+ // 可玩广告对应的 App 名称
11
+ appName: 'Sola Mahjong',
12
+ };
@@ -0,0 +1,2 @@
1
+ /// <reference types="@playcraft/adsdk/defines" />
2
+ /// <reference types="@playcraft/devkit/defines" />
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
6
+ <title>Phaser Template - Playable Ads</title>
7
+ </head>
8
+ <body>
9
+ <div id="game-container"></div>
10
+ <script type="module" src="/src/index.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "playable-template-phaser",
3
+ "version": "1.0.0",
4
+ "description": "A starter template for creating playable ads using Phaser and @playcraft/adsdk with TypeScript support",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "dev:wp": "playable-scripts dev",
8
+ "build": "playable-scripts build",
9
+ "builds": "playable-scripts builds",
10
+ "pack-to-playcraft-template-zip": "playable-scripts pack",
11
+ "webp": "node scripts/convert-to-webp.js",
12
+ "webp:80": "node scripts/convert-to-webp.js 80"
13
+ },
14
+ "keywords": [
15
+ "playable",
16
+ "ads",
17
+ "template",
18
+ "phaser",
19
+ "webgl",
20
+ "2d",
21
+ "typescript",
22
+ "game"
23
+ ],
24
+ "author": "Smoud",
25
+ "dependencies": {
26
+ "@playcraft/adsdk": "*",
27
+ "phaser": "^3.88.2",
28
+ "json5": "^2.2.3"
29
+ },
30
+ "devDependencies": {
31
+ "@playcraft/devkit": "*",
32
+ "@types/node": "^22.10.5",
33
+ "vite": "^5.4.0",
34
+ "typescript": "~5.7.2",
35
+ "prettier": "^2.7.1",
36
+ "babel-loader": "^10.0.0",
37
+ "css-loader": "^7.1.2",
38
+ "esbuild-loader": "^4.4.1",
39
+ "json5-loader": "^4.0.1",
40
+ "style-loader": "^4.0.0"
41
+ },
42
+ "engines": {
43
+ "node": ">=16.0.0"
44
+ }
45
+ }
@@ -0,0 +1,340 @@
1
+ /**
2
+ * Base64 音频转 MP3 文件脚本
3
+ *
4
+ * 支持两种输入源:
5
+ * 1. 直接传入 base64 字符串
6
+ * 2. 从 JS/TS 文件中提取 base64 常量
7
+ *
8
+ * 使用方法:
9
+ * node scripts/base64-to-mp3.js --input=<base64字符串或文件路径> --output=<输出mp3路径>
10
+ * node scripts/base64-to-mp3.js --file=./src/config/const.ts --var=Btn_Click_Audio --output=./output.mp3
11
+ * node scripts/base64-to-mp3.js --help
12
+ *
13
+ * 示例:
14
+ * # 从 JS 文件提取变量
15
+ * node scripts/base64-to-mp3.js --file=./src/game/config/const.ts --var=Btn_Click_Audio -o click.mp3
16
+ *
17
+ * # 直接传入 base64 字符串(适合短字符串)
18
+ * node scripts/base64-to-mp3.js --input="SGVsbG8..." -o output.mp3
19
+ *
20
+ * # 从文本文件读取 base64(适合长字符串)
21
+ * node scripts/base64-to-mp3.js --input=./base64.txt -o output.mp3
22
+ *
23
+ * # 支持 brotli 压缩的 base64(先解压再写入)
24
+ * node scripts/base64-to-mp3.js --file=./src/game/config/const.ts --var=Btn_Click_Audio -o click.mp3 --brotli
25
+ */
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+
30
+ /**
31
+ * 解析命令行参数
32
+ */
33
+ function parseArgs() {
34
+ const args = process.argv.slice(2);
35
+ const config = {
36
+ input: null, // base64 字符串或包含 base64 的文件路径
37
+ file: null, // JS/TS 源文件路径(从中提取变量)
38
+ varName: null, // 要提取的变量名
39
+ output: null, // 输出 mp3 文件路径
40
+ brotli: false, // 是否先进行 brotli 解压
41
+ };
42
+
43
+ for (let i = 0; i < args.length; i++) {
44
+ const arg = args[i];
45
+ if (arg === '--help' || arg === '-h') {
46
+ showHelp();
47
+ process.exit(0);
48
+ } else if (arg.startsWith('--input=')) {
49
+ config.input = arg.split('=').slice(1).join('=');
50
+ } else if (arg === '--input' || arg === '-i') {
51
+ config.input = args[++i];
52
+ } else if (arg.startsWith('--file=')) {
53
+ config.file = arg.split('=').slice(1).join('=');
54
+ } else if (arg.startsWith('--var=')) {
55
+ config.varName = arg.split('=').slice(1).join('=');
56
+ } else if (arg.startsWith('--output=') || arg.startsWith('-o=')) {
57
+ config.output = arg.split('=').slice(1).join('=');
58
+ } else if (arg === '--output' || arg === '-o') {
59
+ config.output = args[++i];
60
+ } else if (arg === '--brotli') {
61
+ config.brotli = true;
62
+ }
63
+ }
64
+
65
+ return config;
66
+ }
67
+
68
+ /**
69
+ * 从 JS/TS 文件中提取指定变量的字符串值
70
+ */
71
+ function extractVarFromFile(filePath, varName) {
72
+ const content = fs.readFileSync(filePath, 'utf-8');
73
+
74
+ // 匹配多种导出/赋值格式:
75
+ // export const Xxx = '...'
76
+ // export const Xxx = "..."
77
+ // const Xxx = '...'
78
+ // const Xxx = "..."
79
+ // export const Xxx = `...`
80
+ const patterns = [
81
+ // 单引号
82
+ new RegExp(`(?:export\\s+)?(?:const|let|var)\\s+${varName}\\s*=\\s*'([^']*)'`, 's'),
83
+ // 双引号
84
+ new RegExp(`(?:export\\s+)?(?:const|let|var)\\s+${varName}\\s*=\\s*"([^"]*)"`, 's'),
85
+ // 模板字符串
86
+ new RegExp(`(?:export\\s+)?(?:const|let|var)\\s+${varName}\\s*=\\s*\`([^\`]*)\``, 's'),
87
+ // 带类型注解: const Xxx: string = '...'
88
+ new RegExp(`(?:export\\s+)?(?:const|let|var)\\s+${varName}\\s*:\\s*\\w+\\s*=\\s*'([^']*)'`, 's'),
89
+ new RegExp(`(?:export\\s+)?(?:const|let|var)\\s+${varName}\\s*:\\s*\\w+\\s*=\\s*"([^"]*)"`, 's'),
90
+ ];
91
+
92
+ for (const pattern of patterns) {
93
+ const match = content.match(pattern);
94
+ if (match) {
95
+ return match[1];
96
+ }
97
+ }
98
+
99
+ return null;
100
+ }
101
+
102
+ /**
103
+ * 获取 base64 字符串
104
+ */
105
+ function getBase64String(config) {
106
+ // 方式 1:从 JS/TS 文件提取变量
107
+ if (config.file && config.varName) {
108
+ const absPath = path.resolve(config.file);
109
+ if (!fs.existsSync(absPath)) {
110
+ console.error(`❌ File not found: ${absPath}`);
111
+ process.exit(1);
112
+ }
113
+ console.log(`📄 Reading variable "${config.varName}" from: ${absPath}`);
114
+ const value = extractVarFromFile(absPath, config.varName);
115
+ if (!value) {
116
+ console.error(`❌ Variable "${config.varName}" not found in ${config.file}`);
117
+ process.exit(1);
118
+ }
119
+ console.log(`✅ Found variable, length: ${value.length} chars`);
120
+ return value;
121
+ }
122
+
123
+ // 方式 2:直接输入或从文件读取
124
+ if (config.input) {
125
+ // 判断是文件路径还是 base64 字符串
126
+ if (fs.existsSync(config.input)) {
127
+ console.log(`📄 Reading base64 from file: ${config.input}`);
128
+ return fs.readFileSync(config.input, 'utf-8').trim();
129
+ }
130
+ // 直接当作 base64 字符串
131
+ return config.input;
132
+ }
133
+
134
+ console.error('❌ No input specified. Use --input or --file + --var');
135
+ process.exit(1);
136
+ }
137
+
138
+ /**
139
+ * 格式化文件大小
140
+ */
141
+ function formatSize(bytes) {
142
+ if (bytes === 0) return '0 B';
143
+ const k = 1024;
144
+ const sizes = ['B', 'KB', 'MB', 'GB'];
145
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
146
+ return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
147
+ }
148
+
149
+ /**
150
+ * 显示帮助
151
+ */
152
+ function showHelp() {
153
+ console.log(`
154
+ 🎵 Base64 音频转 MP3 文件工具
155
+
156
+ 使用方法:
157
+ node scripts/base64-to-mp3.js [选项]
158
+
159
+ 选项:
160
+ --file=<路径> JS/TS 源文件路径
161
+ --var=<变量名> 要从文件中提取的变量名
162
+ --input=<base64> 直接传入 base64 字符串,或包含 base64 的文本文件路径
163
+ --output=<路径>, -o 输出 MP3 文件路径(默认: ./output.mp3)
164
+ --brotli 先进行 brotli 解压(适用于压缩后的 base64)
165
+ --help, -h 显示帮助信息
166
+
167
+ 示例:
168
+ # 从 const.ts 提取 Btn_Click_Audio 变量,转为 mp3
169
+ node scripts/base64-to-mp3.js --file=./src/game/config/const.ts --var=Btn_Click_Audio -o click.mp3
170
+
171
+ # brotli 压缩的 base64(先解压再写入)
172
+ node scripts/base64-to-mp3.js --file=./src/game/config/const.ts --var=Btn_Click_Audio -o click.mp3 --brotli
173
+
174
+ # base64 编码的 URL(自动识别并下载,如爱给网等素材站)
175
+ node scripts/base64-to-mp3.js --input="aHR0cHM6Ly9zMi5haWdlaS5jb20v..." -o bgm.mp3
176
+
177
+ # 从文本文件读取 base64
178
+ node scripts/base64-to-mp3.js --input=./audio_base64.txt -o output.mp3
179
+
180
+ # 直接传入短 base64 音频数据
181
+ node scripts/base64-to-mp3.js --input="AAAAIGZ0eXBpc29..." -o output.mp3
182
+ `);
183
+ }
184
+
185
+ /**
186
+ * 主函数
187
+ */
188
+ async function main() {
189
+ const config = parseArgs();
190
+
191
+ if (!config.output) {
192
+ config.output = './output.mp3';
193
+ }
194
+
195
+ console.log('='.repeat(50));
196
+ console.log('🎵 Base64 → MP3 转换工具');
197
+ console.log('='.repeat(50));
198
+
199
+ // 获取 base64 字符串
200
+ const base64String = getBase64String(config);
201
+
202
+ // 去除可能的 data URI 前缀
203
+ let cleanBase64 = base64String;
204
+ const dataUriMatch = base64String.match(/^data:[^;]+;base64,(.+)/);
205
+ if (dataUriMatch) {
206
+ cleanBase64 = dataUriMatch[1];
207
+ console.log('📎 Stripped data URI prefix');
208
+ }
209
+
210
+ // 解码 base64
211
+ console.log(`🔄 Decoding base64 (${cleanBase64.length} chars)...`);
212
+ const buffer = Buffer.from(cleanBase64, 'base64');
213
+ const decodedStr = buffer.toString('utf-8');
214
+
215
+ // 自动识别:解码后是 URL 还是二进制音频数据
216
+ if (decodedStr.startsWith('http://') || decodedStr.startsWith('https://')) {
217
+ // base64 编码的是 URL,自动下载
218
+ const url = decodedStr.trim();
219
+ console.log(`🔗 Detected URL inside base64:`);
220
+ console.log(` ${url}`);
221
+ console.log(`🌐 Downloading MP3...`);
222
+
223
+ await downloadFile(url, config.output);
224
+ return;
225
+ }
226
+
227
+ // 二进制音频数据
228
+ console.log(` Decoded size: ${formatSize(buffer.length)}`);
229
+ let outputBuffer = buffer;
230
+
231
+ // brotli 解压(可选)
232
+ if (config.brotli) {
233
+ console.log('🗜️ Decompressing with brotli...');
234
+ try {
235
+ const brotli = require('brotli/decompress');
236
+ const decompressed = brotli(new Uint8Array(buffer));
237
+ if (!decompressed) {
238
+ console.error('❌ Brotli decompression returned null');
239
+ process.exit(1);
240
+ }
241
+ outputBuffer = Buffer.from(decompressed);
242
+ console.log(` Decompressed size: ${formatSize(outputBuffer.length)}`);
243
+ } catch (e) {
244
+ console.error(`❌ Brotli decompression failed: ${e.message}`);
245
+ console.log(' Tip: npm install brotli');
246
+ process.exit(1);
247
+ }
248
+ }
249
+
250
+ // 写入文件
251
+ saveFile(config.output, outputBuffer);
252
+
253
+ /**
254
+ * 下载文件
255
+ */
256
+ function downloadFile(url, outputFilePath) {
257
+ return new Promise((resolve, reject) => {
258
+ const outputPath = path.resolve(outputFilePath);
259
+ const outputDir = path.dirname(outputPath);
260
+ if (!fs.existsSync(outputDir)) {
261
+ fs.mkdirSync(outputDir, { recursive: true });
262
+ }
263
+
264
+ // 根据协议选择 http 或 https
265
+ const httpModule = url.startsWith('https') ? require('https') : require('http');
266
+
267
+ const request = httpModule.get(url, (response) => {
268
+ // 处理重定向
269
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
270
+ console.log(` ↪ Redirecting to: ${response.headers.location}`);
271
+ return downloadFile(response.headers.location, outputFilePath).then(resolve).catch(reject);
272
+ }
273
+
274
+ if (response.statusCode !== 200) {
275
+ reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
276
+ return;
277
+ }
278
+
279
+ const contentLength = response.headers['content-length'];
280
+ if (contentLength) {
281
+ console.log(` Content-Length: ${formatSize(parseInt(contentLength))}`);
282
+ }
283
+
284
+ const fileStream = fs.createWriteStream(outputPath);
285
+ let downloadedBytes = 0;
286
+
287
+ response.on('data', (chunk) => {
288
+ downloadedBytes += chunk.length;
289
+ });
290
+
291
+ response.pipe(fileStream);
292
+
293
+ fileStream.on('finish', () => {
294
+ fileStream.close();
295
+ console.log('');
296
+ console.log('='.repeat(50));
297
+ console.log(`✅ Downloaded to: ${outputPath}`);
298
+ console.log(`📦 File size: ${formatSize(downloadedBytes)}`);
299
+ console.log('='.repeat(50));
300
+ resolve();
301
+ });
302
+
303
+ fileStream.on('error', (err) => {
304
+ fs.unlinkSync(outputPath);
305
+ reject(err);
306
+ });
307
+ });
308
+
309
+ request.on('error', reject);
310
+ request.setTimeout(30000, () => {
311
+ request.destroy();
312
+ reject(new Error('Download timeout (30s)'));
313
+ });
314
+ });
315
+ }
316
+
317
+ /**
318
+ * 保存 buffer 到文件
319
+ */
320
+ function saveFile(outputFilePath, buffer) {
321
+ const outputPath = path.resolve(outputFilePath);
322
+ const outputDir = path.dirname(outputPath);
323
+ if (!fs.existsSync(outputDir)) {
324
+ fs.mkdirSync(outputDir, { recursive: true });
325
+ }
326
+
327
+ fs.writeFileSync(outputPath, buffer);
328
+
329
+ console.log('');
330
+ console.log('='.repeat(50));
331
+ console.log(`✅ Saved to: ${outputPath}`);
332
+ console.log(`📦 File size: ${formatSize(buffer.length)}`);
333
+ console.log('='.repeat(50));
334
+ }
335
+
336
+ // 运行
337
+ main().catch(error => {
338
+ console.error('❌ Error:', error.message);
339
+ process.exit(1);
340
+ });