@playcraft/devkit 1.0.31-beta.11 → 1.0.31-beta.12

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 (95) hide show
  1. package/cli/bin/playable-scripts.js +6 -53
  2. package/cli/commands/build.js +32 -241
  3. package/cli/commands/builds.js +2 -13
  4. package/cli/commands/dev.js +1 -22
  5. package/cli/commands/pack.js +40 -320
  6. package/cli/commands/vite-dev.js +1 -7
  7. package/core/artifacts/emit-fonts.test.js +1 -0
  8. package/core/artifacts/emit.js +4 -98
  9. package/core/artifacts/fflate.js +1 -31
  10. package/core/artifacts/zip.js +1 -39
  11. package/core/batch-build.js +147 -1261
  12. package/core/constants.js +2 -20
  13. package/core/cos-uploader.js +23 -289
  14. package/core/engines/cocos/clean-cache.js +5 -179
  15. package/core/engines/cocos/clean-cache.test.js +1 -75
  16. package/core/engines/cocos/cli-build.js +2 -116
  17. package/core/engines/cocos/cli.js +11 -38
  18. package/core/engines/cocos/constants.js +1 -21
  19. package/core/engines/cocos/creator-cli.js +1 -108
  20. package/core/engines/cocos/dev-server.js +4 -412
  21. package/core/engines/cocos/fs.js +1 -105
  22. package/core/engines/cocos/injects/3x/init.js +19 -438
  23. package/core/engines/cocos/injects/3x/main.js +2 -52
  24. package/core/engines/cocos/injects/libs/pako.js +1 -2
  25. package/core/engines/cocos/mcp-refresh.js +2 -76
  26. package/core/engines/cocos/merge.js +3 -148
  27. package/core/engines/cocos/pack.js +1 -187
  28. package/core/engines/cocos/preview-cache.js +2 -48
  29. package/core/engines/cocos/preview-cache.test.js +1 -32
  30. package/core/engines/cocos/preview-concat.js +5 -189
  31. package/core/engines/cocos/preview-concat.test.js +1 -73
  32. package/core/engines/cocos/preview-json-hook.js +1 -200
  33. package/core/engines/cocos/preview-json-pack.js +3 -376
  34. package/core/engines/cocos/preview-json-pack.test.js +2 -226
  35. package/core/engines/cocos/preview-proxy.js +1 -132
  36. package/core/engines/cocos/rebundle-preview.js +2 -54
  37. package/core/engines/cocos/strip-social-share.js +3 -115
  38. package/core/engines/cocos/theme-assets.js +2 -161
  39. package/core/engines/cocos/writable-cli.js +2 -248
  40. package/core/engines/cocos/writable-cli.test.js +1 -112
  41. package/core/engines/web/index.js +1 -20
  42. package/core/engines/web/vite.dev.js +17 -322
  43. package/core/engines/web/webpack.build.js +37 -522
  44. package/core/engines/web/webpack.common.js +11 -177
  45. package/core/engines/web/webpack.dev.js +6 -88
  46. package/core/index.js +7 -101
  47. package/core/injectors/apply.js +1 -94
  48. package/core/injectors/check-channel-matrix.js +2 -130
  49. package/core/injectors/spec.js +9 -217
  50. package/core/loaders/gltf-loader.js +28 -74
  51. package/core/options.js +16 -353
  52. package/core/plugins/AdikteevInjectorPlugin.js +4 -26
  53. package/core/plugins/BigoAdsInjectorPlugin.js +4 -21
  54. package/core/plugins/DAPIInjectorPlugin.js +2 -25
  55. package/core/plugins/DebuggerInjectionPlugin.js +3 -31
  56. package/core/plugins/ExitAPIInjectorPlugin.js +3 -56
  57. package/core/plugins/FflateCompressionPlugin.js +40 -231
  58. package/core/plugins/FontInjectionWebpackPlugin.js +4 -46
  59. package/core/plugins/LiftoffInjectorPlugin.js +4 -26
  60. package/core/plugins/MRAIDInjectorPlugin.js +2 -24
  61. package/core/plugins/MintegralInjectorPlugin.js +2 -25
  62. package/core/plugins/PangleInjectorPlugin.js +2 -24
  63. package/core/plugins/PlaycraftOrientationInjectorPlugin.js +3 -29
  64. package/core/plugins/SnapchatInjectorPlugin.js +4 -26
  65. package/core/plugins/SocialMediaInjectorPlugin.js +7 -119
  66. package/core/plugins/TikTokInjectorPlugin.js +2 -24
  67. package/core/plugins/UnityInjectorPlugin.js +8 -37
  68. package/core/plugins/ZipPlugin.js +20 -154
  69. package/core/plugins/socialMediaVitePlugin.js +2 -108
  70. package/core/utils/buildDefines.js +2 -30
  71. package/core/utils/buildTemplateString.js +5 -39
  72. package/core/utils/date.js +1 -16
  73. package/core/utils/fontRegistry.js +13 -81
  74. package/core/utils/fontResolver.js +37 -335
  75. package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +7 -42
  76. package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +3 -30
  77. package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +7 -43
  78. package/core/utils/getPlaycraftOrientationSuffix.js +2 -9
  79. package/core/utils/injectSDKPlugins.js +13 -68
  80. package/core/utils/logOptions.js +5 -46
  81. package/core/utils/mergeOptions.js +2 -21
  82. package/core/utils/obfuscateLevels.js +10 -0
  83. package/core/utils/obfuscateLevels.test.js +10 -0
  84. package/core/utils/parseArgvOptions.js +5 -125
  85. package/core/utils/resolveChannelFold2Zip.js +3 -15
  86. package/core/utils/validateThemeData.js +26 -227
  87. package/core/validators/pre-build-checker.js +25 -287
  88. package/core/validators/tracking-validator.js +40 -441
  89. package/core/validators/tracking-validator.test.js +2 -84
  90. package/core/vite.dev.js +1 -1
  91. package/core/webpack.build.js +1 -1
  92. package/core/webpack.common.js +1 -1
  93. package/core/webpack.dev.js +1 -1
  94. package/package.json +2 -3
  95. package/README.md +0 -262
@@ -1,157 +1,35 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const yazl = require('yazl');
6
-
7
- // ==================== 配置 ====================
8
-
9
- const projectRoot = process.cwd();
10
-
11
- /**
1
+ "use strict";const fs=require("fs");const path=require("path");const yazl=require("yazl");// ==================== 配置 ====================
2
+ const projectRoot=process.cwd();/**
12
3
  * 内置忽略:即使已提交且未写进 .gitignore / .playcraftignore,打包时也排除。
13
- */
14
- const BUILTIN_IGNORE_PATTERNS = [
15
- 'package-lock.json',
16
- 'npm-shrinkwrap.json',
17
- 'yarn.lock',
18
- 'pnpm-lock.yaml',
19
- 'bun.lock',
20
- 'bun.lockb',
21
- ];
22
-
23
- /**
4
+ */const BUILTIN_IGNORE_PATTERNS=["package-lock.json","npm-shrinkwrap.json","yarn.lock","pnpm-lock.yaml","bun.lock","bun.lockb"];/**
24
5
  * 解析 appName,依次从 builds.config.js、build.json 或 CLI 参数中获取。
25
6
  * 优先级: CLI --app-name > builds.config.js > build.json > 文件夹名称
26
7
  * @returns {string}
27
- */
28
- function resolveAppName() {
29
- // 1. CLI 参数: --app-name=xxx
30
- const args = process.argv.slice(2);
31
- for (const arg of args) {
32
- if (arg.startsWith('--app-name=')) {
33
- return arg.split('=')[1].trim();
34
- }
35
- }
36
-
37
- // 2. builds.config.js → appName 或 title
38
- const buildsConfigPath = path.join(projectRoot, 'builds.config.js');
39
- if (fs.existsSync(buildsConfigPath)) {
40
- try {
41
- delete require.cache[buildsConfigPath];
42
- const config = require(buildsConfigPath);
43
- if (config.appName) return config.appName;
44
- if (config.title) return config.title;
45
- } catch (e) {
46
- // 忽略错误
47
- }
48
- }
49
-
50
- // 3. build.json → app
51
- const buildJsonPath = path.join(projectRoot, 'build.json');
52
- if (fs.existsSync(buildJsonPath)) {
53
- try {
54
- const data = JSON.parse(fs.readFileSync(buildJsonPath, 'utf8'));
55
- if (data.app) return data.app;
56
- } catch (e) {
57
- // 忽略错误
58
- }
59
- }
60
-
61
- // 4. 兜底: 使用项目文件夹名称
62
- return path.basename(projectRoot);
63
- }
64
-
65
- /**
8
+ */function resolveAppName(){// 1. CLI 参数: --app-name=xxx
9
+ const args=process.argv.slice(2);for(const arg of args){if(arg.startsWith("--app-name=")){return arg.split("=")[1].trim()}}// 2. builds.config.js → appName 或 title
10
+ const buildsConfigPath=path.join(projectRoot,"builds.config.js");if(fs.existsSync(buildsConfigPath)){try{delete require.cache[buildsConfigPath];const config=require(buildsConfigPath);if(config.appName)return config.appName;if(config.title)return config.title}catch(e){// 忽略错误
11
+ }}// 3. build.json → app
12
+ const buildJsonPath=path.join(projectRoot,"build.json");if(fs.existsSync(buildJsonPath)){try{const data=JSON.parse(fs.readFileSync(buildJsonPath,"utf8"));if(data.app)return data.app}catch(e){// 忽略错误
13
+ }}// 4. 兜底: 使用项目文件夹名称
14
+ return path.basename(projectRoot)}/**
66
15
  * 从 CLI 参数中解析输出目录。
67
16
  * @returns {string}
68
- */
69
- function resolveOutputDir() {
70
- const args = process.argv.slice(2);
71
- for (const arg of args) {
72
- if (arg.startsWith('--out-dir=')) {
73
- return arg.split('=')[1].trim();
74
- }
75
- }
76
- return 'dist';
77
- }
78
-
79
- // ==================== 忽略规则解析 ====================
80
-
17
+ */function resolveOutputDir(){const args=process.argv.slice(2);for(const arg of args){if(arg.startsWith("--out-dir=")){return arg.split("=")[1].trim()}}return"dist"}// ==================== 忽略规则解析 ====================
81
18
  /**
82
19
  * 解析单个忽略文件,返回规范化后的模式数组。
83
20
  * @param {string} filePath - 忽略文件的绝对路径
84
21
  * @returns {string[]} 规范化后的忽略模式数组
85
- */
86
- function parseSingleIgnoreFile(filePath) {
87
- if (!fs.existsSync(filePath)) {
88
- return [];
89
- }
90
-
91
- const content = fs.readFileSync(filePath, 'utf8');
92
- const patterns = [];
93
-
94
- for (const rawLine of content.split(/\r?\n/)) {
95
- const line = rawLine.trim();
96
-
97
- // 跳过空行和注释
98
- if (!line || line.startsWith('#')) continue;
99
-
100
- // 不支持取反模式,给出警告并跳过
101
- if (line.startsWith('!')) {
102
- console.log(`\x1b[33m⚠️ 不支持取反模式,已跳过: ${line}\x1b[0m`);
103
- continue;
104
- }
105
-
106
- // 规范化:
107
- // - 移除尾部斜杠(用于目录匹配)
108
- // - 移除前导 ./(gitignore 中 ./foo 等同于 foo)
109
- let normalized = line.replace(/\/+$/, '');
110
- normalized = normalized.replace(/^\.\//g, '');
111
- patterns.push(normalized);
112
- }
113
-
114
- return patterns;
115
- }
116
-
117
- /**
22
+ */function parseSingleIgnoreFile(filePath){if(!fs.existsSync(filePath)){return[]}const content=fs.readFileSync(filePath,"utf8");const patterns=[];for(const rawLine of content.split(/\r?\n/)){const line=rawLine.trim();// 跳过空行和注释
23
+ if(!line||line.startsWith("#"))continue;// 不支持取反模式,给出警告并跳过
24
+ if(line.startsWith("!")){console.log(`\x1b[33m⚠️ 不支持取反模式,已跳过: ${line}\x1b[0m`);continue}// 规范化:
25
+ // - 移除尾部斜杠(用于目录匹配)
26
+ // - 移除前导 ./(gitignore 中 ./foo 等同于 foo)
27
+ let normalized=line.replace(/\/+$/,"");normalized=normalized.replace(/^\.\//g,"");patterns.push(normalized)}return patterns}/**
118
28
  * 解析 .gitignore 和 .playcraftignore 文件,合并并去重。
119
29
  * 内置规则始终生效;.gitignore 提供基础忽略,.playcraftignore 再补充。
120
30
  * @returns {{ patterns: string[], sources: { builtin: number, gitignore: number, playcraftignore: number } }}
121
- */
122
- function parseIgnoreFiles() {
123
- const gitignorePath = path.join(projectRoot, '.gitignore');
124
- const playcraftignorePath = path.join(projectRoot, '.playcraftignore');
125
-
126
- const gitignorePatterns = parseSingleIgnoreFile(gitignorePath);
127
- const playcraftPatterns = parseSingleIgnoreFile(playcraftignorePath);
128
-
129
- if (gitignorePatterns.length === 0 && playcraftPatterns.length === 0) {
130
- console.log('\x1b[33m⚠️ 未找到 .gitignore 或 .playcraftignore 文件,将仅应用内置忽略规则。\x1b[0m');
131
- }
132
-
133
- // 合并并去重(内置 → .gitignore → .playcraftignore)
134
- const seen = new Set();
135
- const merged = [];
136
-
137
- for (const p of [...BUILTIN_IGNORE_PATTERNS, ...gitignorePatterns, ...playcraftPatterns]) {
138
- if (!seen.has(p)) {
139
- seen.add(p);
140
- merged.push(p);
141
- }
142
- }
143
-
144
- return {
145
- patterns: merged,
146
- sources: {
147
- builtin: BUILTIN_IGNORE_PATTERNS.length,
148
- gitignore: gitignorePatterns.length,
149
- playcraftignore: playcraftPatterns.length,
150
- },
151
- };
152
- }
153
-
154
- /**
31
+ */function parseIgnoreFiles(){const gitignorePath=path.join(projectRoot,".gitignore");const playcraftignorePath=path.join(projectRoot,".playcraftignore");const gitignorePatterns=parseSingleIgnoreFile(gitignorePath);const playcraftPatterns=parseSingleIgnoreFile(playcraftignorePath);if(gitignorePatterns.length===0&&playcraftPatterns.length===0){console.log("\x1B[33m\u26A0\uFE0F \u672A\u627E\u5230 .gitignore \u6216 .playcraftignore \u6587\u4EF6\uFF0C\u5C06\u4EC5\u5E94\u7528\u5185\u7F6E\u5FFD\u7565\u89C4\u5219\u3002\x1B[0m")}// 合并并去重(内置 → .gitignore → .playcraftignore)
32
+ const seen=new Set;const merged=[];for(const p of[...BUILTIN_IGNORE_PATTERNS,...gitignorePatterns,...playcraftPatterns]){if(!seen.has(p)){seen.add(p);merged.push(p)}}return{patterns:merged,sources:{builtin:BUILTIN_IGNORE_PATTERNS.length,gitignore:gitignorePatterns.length,playcraftignore:playcraftPatterns.length}}}/**
155
33
  * 将 gitignore 风格的模式转换为正则表达式。
156
34
  *
157
35
  * gitignore 语义:
@@ -162,202 +40,44 @@ function parseIgnoreFiles() {
162
40
  *
163
41
  * @param {string} pattern
164
42
  * @returns {RegExp}
165
- */
166
- function patternToRegex(pattern) {
167
- let p = pattern;
168
- let anchored = false;
169
-
170
- // 前导 / 表示锚定到根目录;构建正则前剥离
171
- if (p.startsWith('/')) {
172
- p = p.slice(1);
173
- anchored = true;
174
- }
175
-
176
- // 如果模式中包含 /,同样视为锚定到根目录
177
- if (p.includes('/')) {
178
- anchored = true;
179
- }
180
-
181
- // 转义正则特殊字符(* 和 ? 除外)
182
- let regexStr = p.replace(/[.+^${}()|[\]\\]/g, '\\$&');
183
-
184
- // 处理 **(匹配任意路径层级,包括嵌套目录)
185
- regexStr = regexStr.replace(/\*\*/g, '{{GLOBSTAR}}');
186
- // 处理 *(匹配除路径分隔符外的任意字符)
187
- regexStr = regexStr.replace(/\*/g, '[^/]*');
188
- // 处理 ?(匹配除路径分隔符外的单个字符)
189
- regexStr = regexStr.replace(/\?/g, '[^/]');
190
- // 还原 globstar 占位符
191
- regexStr = regexStr.replace(/\{\{GLOBSTAR\}\}/g, '.*');
192
-
193
- if (anchored) {
194
- // 锚定模式:从相对路径的起始位置匹配
195
- return new RegExp(`^${regexStr}(/|$)`);
196
- }
197
-
198
- // 非锚定模式:在目录树中任意位置匹配路径段
199
- return new RegExp(`(^|/)${regexStr}(/|$)`);
200
- }
201
-
202
- /**
43
+ */function patternToRegex(pattern){let p=pattern;let anchored=false;// 前导 / 表示锚定到根目录;构建正则前剥离
44
+ if(p.startsWith("/")){p=p.slice(1);anchored=true}// 如果模式中包含 /,同样视为锚定到根目录
45
+ if(p.includes("/")){anchored=true}// 转义正则特殊字符(* 和 ? 除外)
46
+ let regexStr=p.replace(/[.+^${}()|[\]\\]/g,"\\$&");// 处理 **(匹配任意路径层级,包括嵌套目录)
47
+ regexStr=regexStr.replace(/\*\*/g,"{{GLOBSTAR}}");// 处理 *(匹配除路径分隔符外的任意字符)
48
+ regexStr=regexStr.replace(/\*/g,"[^/]*");// 处理 ?(匹配除路径分隔符外的单个字符)
49
+ regexStr=regexStr.replace(/\?/g,"[^/]");// 还原 globstar 占位符
50
+ regexStr=regexStr.replace(/\{\{GLOBSTAR\}\}/g,".*");if(anchored){// 锚定模式:从相对路径的起始位置匹配
51
+ return new RegExp(`^${regexStr}(/|$)`)}// 非锚定模式:在目录树中任意位置匹配路径段
52
+ return new RegExp(`(^|/)${regexStr}(/|$)`)}/**
203
53
  * 检查相对路径是否应被忽略。
204
54
  * @param {string} relativePath - 使用正斜杠规范化后的相对路径
205
55
  * @param {RegExp[]} regexPatterns
206
56
  * @returns {boolean}
207
- */
208
- function shouldIgnore(relativePath, regexPatterns) {
209
- for (const regex of regexPatterns) {
210
- if (regex.test(relativePath)) return true;
211
- }
212
- return false;
213
- }
214
-
215
- // ==================== 文件收集 ====================
216
-
57
+ */function shouldIgnore(relativePath,regexPatterns){for(const regex of regexPatterns){if(regex.test(relativePath))return true}return false}// ==================== 文件收集 ====================
217
58
  /**
218
59
  * 递归收集目录下的所有文件,遵循忽略规则。
219
60
  * @param {string} dir - 目录的绝对路径
220
61
  * @param {string} baseDir - 项目根目录,用于计算相对路径
221
62
  * @param {RegExp[]} ignoreRegexes
222
63
  * @returns {string[]} 文件绝对路径数组
223
- */
224
- function collectFiles(dir, baseDir, ignoreRegexes) {
225
- const results = [];
226
- let entries;
227
-
228
- try {
229
- entries = fs.readdirSync(dir, { withFileTypes: true });
230
- } catch (e) {
231
- console.warn(`\x1b[33m⚠️ 无法读取目录: ${dir}\x1b[0m`);
232
- return results;
233
- }
234
-
235
- for (const entry of entries) {
236
- const fullPath = path.join(dir, entry.name);
237
- // 使用正斜杠以保持匹配一致性
238
- const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
239
-
240
- if (shouldIgnore(relativePath, ignoreRegexes)) {
241
- continue;
242
- }
243
-
244
- if (entry.isDirectory()) {
245
- results.push(...collectFiles(fullPath, baseDir, ignoreRegexes));
246
- } else if (entry.isFile() || entry.isSymbolicLink()) {
247
- results.push(fullPath);
248
- }
249
- }
250
-
251
- return results;
252
- }
253
-
254
- // ==================== Zip 创建 ====================
255
-
64
+ */function collectFiles(dir,baseDir,ignoreRegexes){const results=[];let entries;try{entries=fs.readdirSync(dir,{withFileTypes:true})}catch(e){console.warn(`\x1b[33m⚠️ 无法读取目录: ${dir}\x1b[0m`);return results}for(const entry of entries){const fullPath=path.join(dir,entry.name);// 使用正斜杠以保持匹配一致性
65
+ const relativePath=path.relative(baseDir,fullPath).replace(/\\/g,"/");if(shouldIgnore(relativePath,ignoreRegexes)){continue}if(entry.isDirectory()){results.push(...collectFiles(fullPath,baseDir,ignoreRegexes))}else if(entry.isFile()||entry.isSymbolicLink()){results.push(fullPath)}}return results}// ==================== Zip 创建 ====================
256
66
  /**
257
67
  * 根据收集到的文件创建 zip 压缩包。
258
68
  * @param {string[]} files - 文件绝对路径数组
259
69
  * @param {string} rootFolderName - zip 内的根文件夹名称
260
70
  * @param {string} outputPath - 输出 zip 文件的绝对路径
261
71
  * @returns {Promise<{fileCount: number, zipSize: number}>}
262
- */
263
- function createZip(files, rootFolderName, outputPath) {
264
- return new Promise((resolve, reject) => {
265
- const zipFile = new yazl.ZipFile();
266
- let fileCount = 0;
267
-
268
- for (const filePath of files) {
269
- const relativePath = path.relative(projectRoot, filePath).replace(/\\/g, '/');
270
- const zipEntryPath = `${rootFolderName}/${relativePath}`;
271
-
272
- zipFile.addFile(filePath, zipEntryPath);
273
- fileCount++;
274
- }
275
-
276
- zipFile.end();
277
-
278
- // 确保输出目录存在
279
- const outputDir = path.dirname(outputPath);
280
- if (!fs.existsSync(outputDir)) {
281
- fs.mkdirSync(outputDir, { recursive: true });
282
- }
283
-
284
- const writeStream = fs.createWriteStream(outputPath);
285
-
286
- writeStream.on('error', (err) => {
287
- reject(err);
288
- });
289
-
290
- writeStream.on('close', () => {
291
- const stats = fs.statSync(outputPath);
292
- resolve({ fileCount, zipSize: stats.size });
293
- });
294
-
295
- zipFile.outputStream.pipe(writeStream);
296
- });
297
- }
298
-
299
- // ==================== 格式化工具 ====================
300
-
72
+ */function createZip(files,rootFolderName,outputPath){return new Promise((resolve,reject)=>{const zipFile=new yazl.ZipFile;let fileCount=0;for(const filePath of files){const relativePath=path.relative(projectRoot,filePath).replace(/\\/g,"/");const zipEntryPath=`${rootFolderName}/${relativePath}`;zipFile.addFile(filePath,zipEntryPath);fileCount++}zipFile.end();// 确保输出目录存在
73
+ const outputDir=path.dirname(outputPath);if(!fs.existsSync(outputDir)){fs.mkdirSync(outputDir,{recursive:true})}const writeStream=fs.createWriteStream(outputPath);writeStream.on("error",err=>{reject(err)});writeStream.on("close",()=>{const stats=fs.statSync(outputPath);resolve({fileCount,zipSize:stats.size})});zipFile.outputStream.pipe(writeStream)})}// ==================== 格式化工具 ====================
301
74
  /**
302
75
  * 将字节数格式化为可读字符串。
303
76
  * @param {number} bytes
304
77
  * @returns {string}
305
- */
306
- function formatSize(bytes) {
307
- if (bytes < 1024) return `${bytes} B`;
308
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
309
- return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
310
- }
311
-
312
- // ==================== 主流程 ====================
313
-
314
- async function main() {
315
- console.log('\x1b[36m📦 playable-scripts 打包\x1b[0m\n');
316
-
317
- // 1. 解析 appName
318
- const appName = resolveAppName();
319
- console.log(` 应用名称: \x1b[1m${appName}\x1b[0m`);
320
-
321
- // 2. 解析忽略规则(内置 lock 文件 + .gitignore + .playcraftignore)
322
- const { patterns, sources } = parseIgnoreFiles();
323
- const ignoreRegexes = patterns.map(patternToRegex);
324
-
325
- if (patterns.length > 0) {
326
- const parts = [];
327
- if (sources.builtin > 0) parts.push(`${sources.builtin} 条内置`);
328
- if (sources.gitignore > 0) parts.push(`${sources.gitignore} 条来自 .gitignore`);
329
- if (sources.playcraftignore > 0) parts.push(`${sources.playcraftignore} 条来自 .playcraftignore`);
330
- console.log(` 忽略规则: \x1b[33m${patterns.length}\x1b[0m 条模式 (${parts.join(', ')})`);
331
- }
332
-
333
- // 3. 收集文件
334
- console.log(` 正在扫描项目文件...`);
335
- const files = collectFiles(projectRoot, projectRoot, ignoreRegexes);
336
-
337
- if (files.length === 0) {
338
- console.error('\x1b[31m❌ 没有可打包的文件!请检查 .playcraftignore 规则。\x1b[0m');
339
- process.exit(1);
340
- }
341
-
342
- console.log(` 文件数量: \x1b[32m${files.length}\x1b[0m 个待打包文件`);
343
-
344
- // 4. 创建 zip 压缩包
345
- const outDir = resolveOutputDir();
346
- const outputPath = path.resolve(projectRoot, outDir, `${appName}.zip`);
347
-
348
- console.log(` 输出路径: ${outputPath}\n`);
349
- console.log(' 正在打包...');
350
-
351
- try {
352
- const { fileCount, zipSize } = await createZip(files, appName, outputPath);
353
- console.log(`\n\x1b[32m✅ 打包完成!\x1b[0m`);
354
- console.log(` 文件数: ${fileCount}`);
355
- console.log(` 大小: ${formatSize(zipSize)}`);
356
- console.log(` 输出: ${outputPath}`);
357
- } catch (err) {
358
- console.error(`\n\x1b[31m❌ 打包失败: ${err.message}\x1b[0m`);
359
- process.exit(1);
360
- }
361
- }
362
-
363
- main();
78
+ */function formatSize(bytes){if(bytes<1024)return`${bytes} B`;if(bytes<1024*1024)return`${(bytes/1024).toFixed(2)} KB`;return`${(bytes/(1024*1024)).toFixed(2)} MB`}// ==================== 主流程 ====================
79
+ async function main(){console.log("\x1B[36m\uD83D\uDCE6 playable-scripts \u6253\u5305\x1B[0m\n");// 1. 解析 appName
80
+ const appName=resolveAppName();console.log(` 应用名称: \x1b[1m${appName}\x1b[0m`);// 2. 解析忽略规则(内置 lock 文件 + .gitignore + .playcraftignore)
81
+ const{patterns,sources}=parseIgnoreFiles();const ignoreRegexes=patterns.map(patternToRegex);if(patterns.length>0){const parts=[];if(sources.builtin>0)parts.push(`${sources.builtin} 条内置`);if(sources.gitignore>0)parts.push(`${sources.gitignore} 条来自 .gitignore`);if(sources.playcraftignore>0)parts.push(`${sources.playcraftignore} 条来自 .playcraftignore`);console.log(` 忽略规则: \x1b[33m${patterns.length}\x1b[0m 条模式 (${parts.join(", ")})`)}// 3. 收集文件
82
+ console.log(` 正在扫描项目文件...`);const files=collectFiles(projectRoot,projectRoot,ignoreRegexes);if(files.length===0){console.error("\x1B[31m\u274C \u6CA1\u6709\u53EF\u6253\u5305\u7684\u6587\u4EF6\uFF01\u8BF7\u68C0\u67E5 .playcraftignore \u89C4\u5219\u3002\x1B[0m");process.exit(1)}console.log(` 文件数量: \x1b[32m${files.length}\x1b[0m 个待打包文件`);// 4. 创建 zip 压缩包
83
+ const outDir=resolveOutputDir();const outputPath=path.resolve(projectRoot,outDir,`${appName}.zip`);console.log(` 输出路径: ${outputPath}\n`);console.log(" \u6B63\u5728\u6253\u5305...");try{const{fileCount,zipSize}=await createZip(files,appName,outputPath);console.log(`\n\x1b[32m✅ 打包完成!\x1b[0m`);console.log(` 文件数: ${fileCount}`);console.log(` 大小: ${formatSize(zipSize)}`);console.log(` 输出: ${outputPath}`)}catch(err){console.error(`\n\x1b[31m❌ 打包失败: ${err.message}\x1b[0m`);process.exit(1)}}main();
@@ -1,7 +1 @@
1
- 'use strict';
2
-
3
- process.env.BABEL_ENV = 'development';
4
- process.env.NODE_ENV = 'development';
5
-
6
- const { runViteDev } = require('../../core/vite.dev.js');
7
- runViteDev();
1
+ "use strict";process.env.BABEL_ENV="development";process.env.NODE_ENV="development";const{runViteDev}=require("../../core/vite.dev.js");runViteDev();
@@ -0,0 +1 @@
1
+ "use strict";const{describe,it}=require("node:test");const assert=require("node:assert/strict");const{insertPlayableFontStyle}=require("./emit.js");const CSS="<style data-playable-fonts>\n@font-face{font-family:'Poppins';}\n</style>";describe("insertPlayableFontStyle",()=>{it("inserts the font style before </head>",()=>{const html="<html><head><title>x</title></head><body></body></html>";const out=insertPlayableFontStyle(html,CSS);assert.match(out,/data-playable-fonts[\s\S]*<\/head>/);assert.equal(out.indexOf("data-playable-fonts")<out.indexOf("</head>"),true)});it("does not insert twice",()=>{const html=`<html><head>${CSS}</head><body></body></html>`;const out=insertPlayableFontStyle(html,CSS);assert.equal(out.split("data-playable-fonts").length-1,1)});it("leaves html unchanged when there is no font css",()=>{const html="<html><head></head><body></body></html>";assert.equal(insertPlayableFontStyle(html,""),html)})});
@@ -1,98 +1,4 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { buildTemplateString } = require('../utils/buildTemplateString.js');
6
- const { getPlaycraftOrientationSuffix } = require('../utils/getPlaycraftOrientationSuffix.js');
7
- const { resolveChannelFold2Zip } = require('../utils/resolveChannelFold2Zip.js');
8
- const { applyInjections, extractInlineScripts } = require('../injectors/apply');
9
- const {
10
- getChannelPackPlan,
11
- ADIKTEEV_INDEX,
12
- BIGABID_INDEX,
13
- } = require('../injectors/spec');
14
- const { zipDirectory } = require('./zip');
15
- const { compressHtml } = require('./fflate');
16
- const { mraidPartners } = require('../utils/parseArgvOptions');
17
-
18
- function getFileName(buildOptions) {
19
- let filename = buildTemplateString(buildOptions.filename, buildOptions);
20
- if (buildOptions.network === 'mintegral') {
21
- return filename.replace(/\s+/g, '').replace(/[^a-zA-Z0-9]/g, '_').replace('_fullhash_6_', Date.now().toString(36).slice(-6));
22
- }
23
- return filename;
24
- }
25
-
26
- function writeStagingFiles(staging, layout, html) {
27
- if (layout === 'adikteev') {
28
- const extracted = extractInlineScripts(html);
29
- fs.writeFileSync(path.join(staging, 'index.html'), ADIKTEEV_INDEX);
30
- fs.writeFileSync(path.join(staging, 'creative.js'), extracted.scripts.join('\n'));
31
- fs.writeFileSync(path.join(staging, 'style.css'), '');
32
- return;
33
- }
34
- if (layout === 'bigabid') {
35
- const extracted = extractInlineScripts(html);
36
- fs.writeFileSync(path.join(staging, 'index.html'), BIGABID_INDEX);
37
- fs.writeFileSync(path.join(staging, 'main.js'), extracted.scripts.join('\n'));
38
- return;
39
- }
40
- fs.writeFileSync(path.join(staging, 'index.html'), html);
41
- }
42
-
43
- async function emitChannel({ templateHtml, buildOptions, themePayload, outDir }) {
44
- if (mraidPartners.includes(buildOptions.network) && (buildOptions.protocol === 'none' || !buildOptions.protocol)) {
45
- buildOptions = { ...buildOptions, protocol: 'mraid' };
46
- }
47
- const adNetwork = buildOptions.network;
48
- const orientationValue = buildOptions.playcraftOrientationValue || 'both';
49
- const orientationSuffix = getPlaycraftOrientationSuffix(orientationValue);
50
- const outputId = `${adNetwork}${orientationSuffix}`;
51
- const plan = getChannelPackPlan(buildOptions, themePayload);
52
- const { isChannelFold2Zip, isZipOutputFinal } = resolveChannelFold2Zip({
53
- isChannelFold2ZipOption: buildOptions.isChannelFold2Zip,
54
- adNetwork,
55
- isZipOutput: plan.isZip
56
- });
57
-
58
- let html = applyInjections(templateHtml, plan.spec);
59
-
60
- if (buildOptions.fflateCompression) {
61
- html = await compressHtml(html, {
62
- googlePlayUrl: buildOptions.googlePlayUrl,
63
- appStoreUrl: buildOptions.appStoreUrl,
64
- });
65
- }
66
-
67
- fs.mkdirSync(outDir, { recursive: true });
68
- const fileName = getFileName(buildOptions);
69
-
70
- if (!isZipOutputFinal) {
71
- const htmlName = `${fileName}.html`;
72
- fs.writeFileSync(path.join(outDir, htmlName), html);
73
- console.log(`[cocos] wrote ${htmlName}`);
74
- return path.join(outDir, htmlName);
75
- }
76
-
77
- const staging = path.join(outDir, outputId);
78
- if (fs.existsSync(staging)) fs.rmSync(staging, { recursive: true, force: true });
79
- fs.mkdirSync(staging, { recursive: true });
80
- writeStagingFiles(staging, plan.layout, html);
81
-
82
- for (const extra of plan.extraFiles) {
83
- if (fs.existsSync(extra.from)) {
84
- fs.copyFileSync(extra.from, path.join(staging, extra.to));
85
- } else {
86
- console.warn(`[cocos] missing extra file: ${extra.from}`);
87
- }
88
- }
89
-
90
- const zipName = isChannelFold2Zip ? outputId : fileName;
91
- const zipPath = path.join(outDir, `${zipName}.zip`);
92
- await zipDirectory(staging, zipPath);
93
- fs.rmSync(staging, { recursive: true, force: true });
94
- console.log(`[cocos] wrote ${path.basename(zipPath)}`);
95
- return zipPath;
96
- }
97
-
98
- module.exports = { emitChannel, getFileName };
1
+ "use strict";const fs=require("fs");const path=require("path");const{buildTemplateString}=require("../utils/buildTemplateString.js");const{getPlaycraftOrientationSuffix}=require("../utils/getPlaycraftOrientationSuffix.js");const{resolveChannelFold2Zip}=require("../utils/resolveChannelFold2Zip.js");const{applyInjections,extractInlineScripts}=require("../injectors/apply");const{getChannelPackPlan,ADIKTEEV_INDEX,BIGABID_INDEX}=require("../injectors/spec");const{zipDirectory}=require("./zip");const{compressHtml}=require("./fflate");const{mraidPartners}=require("../utils/parseArgvOptions");const{getOrGenerateFontCSS}=require("../utils/fontResolver.js");/**
2
+ * Insert the same base64 @font-face block the Vite / webpack font plugins emit.
3
+ * Cocos pack has no bundler plugin, so this runs on the single-file HTML.
4
+ */function insertPlayableFontStyle(html,css){if(!css)return html;if(/data-playable-fonts/i.test(html))return html;if(/<\/head>/i.test(html))return html.replace(/<\/head>/i,`${css}\n</head>`);return`${css}\n${html}`}function fontCssForTheme(buildOptions,themePayload,themeConfig){const themeName=themePayload&&themePayload.themeName||buildOptions.themeName;if(!themeName)return"";try{return getOrGenerateFontCSS({projectRoot:process.cwd(),themeName,themeConfig,mergedData:themePayload&&themePayload.mergedData,force:true})||""}catch(err){console.warn(`[font] inject skipped: ${err.message}`);return""}}function getFileName(buildOptions){let filename=buildTemplateString(buildOptions.filename,buildOptions);if(buildOptions.network==="mintegral"){return filename.replace(/\s+/g,"").replace(/[^a-zA-Z0-9]/g,"_").replace("_fullhash_6_",Date.now().toString(36).slice(-6))}return filename}function writeStagingFiles(staging,layout,html){if(layout==="adikteev"){const extracted=extractInlineScripts(html);fs.writeFileSync(path.join(staging,"index.html"),ADIKTEEV_INDEX);fs.writeFileSync(path.join(staging,"creative.js"),extracted.scripts.join("\n"));fs.writeFileSync(path.join(staging,"style.css"),"");return}if(layout==="bigabid"){const extracted=extractInlineScripts(html);fs.writeFileSync(path.join(staging,"index.html"),BIGABID_INDEX);fs.writeFileSync(path.join(staging,"main.js"),extracted.scripts.join("\n"));return}fs.writeFileSync(path.join(staging,"index.html"),html)}async function emitChannel({templateHtml,buildOptions,themePayload,themeConfig,outDir}){if(mraidPartners.includes(buildOptions.network)&&(buildOptions.protocol==="none"||!buildOptions.protocol)){buildOptions={...buildOptions,protocol:"mraid"}}const adNetwork=buildOptions.network;const orientationValue=buildOptions.playcraftOrientationValue||"both";const orientationSuffix=getPlaycraftOrientationSuffix(orientationValue);const outputId=`${adNetwork}${orientationSuffix}`;const plan=getChannelPackPlan(buildOptions,themePayload);const{isChannelFold2Zip,isZipOutputFinal}=resolveChannelFold2Zip({isChannelFold2ZipOption:buildOptions.isChannelFold2Zip,adNetwork,isZipOutput:plan.isZip});let html=insertPlayableFontStyle(templateHtml,fontCssForTheme(buildOptions,themePayload,themeConfig));html=applyInjections(html,plan.spec);if(buildOptions.fflateCompression){html=await compressHtml(html,{googlePlayUrl:buildOptions.googlePlayUrl,appStoreUrl:buildOptions.appStoreUrl})}fs.mkdirSync(outDir,{recursive:true});const fileName=getFileName(buildOptions);if(!isZipOutputFinal){const htmlName=`${fileName}.html`;fs.writeFileSync(path.join(outDir,htmlName),html);console.log(`[cocos] wrote ${htmlName}`);return path.join(outDir,htmlName)}const staging=path.join(outDir,outputId);if(fs.existsSync(staging))fs.rmSync(staging,{recursive:true,force:true});fs.mkdirSync(staging,{recursive:true});writeStagingFiles(staging,plan.layout,html);for(const extra of plan.extraFiles){if(fs.existsSync(extra.from)){fs.copyFileSync(extra.from,path.join(staging,extra.to))}else{console.warn(`[cocos] missing extra file: ${extra.from}`)}}const zipName=isChannelFold2Zip?outputId:fileName;const zipPath=path.join(outDir,`${zipName}.zip`);await zipDirectory(staging,zipPath);fs.rmSync(staging,{recursive:true,force:true});console.log(`[cocos] wrote ${path.basename(zipPath)}`);return zipPath}module.exports={emitChannel,getFileName,insertPlayableFontStyle,fontCssForTheme};
@@ -1,31 +1 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const fflate = require('fflate');
6
- const { minify } = require('terser');
7
- const { FflateCompressionPlugin } = require('../plugins/FflateCompressionPlugin.js');
8
-
9
- async function compressHtml(html, options = {}) {
10
- const plugin = new FflateCompressionPlugin(options);
11
- const originalSize = Buffer.byteLength(html, 'utf8');
12
- const compressedUint8Array = fflate.zlibSync(new TextEncoder().encode(html), { level: 9 });
13
- const base64String = Buffer.from(compressedUint8Array).toString('base64');
14
-
15
- const fflatePackageRoot = path.join(path.dirname(require.resolve('fflate')), '..');
16
- const fflateLibPath = path.join(fflatePackageRoot, 'umd', 'index.js');
17
- let fflateLibContent = fs.readFileSync(fflateLibPath, 'utf8');
18
- const minifiedFflate = await minify(fflateLibContent, {
19
- compress: { passes: 2, dead_code: true, drop_console: true, drop_debugger: true },
20
- mangle: true,
21
- format: { comments: false }
22
- });
23
- fflateLibContent = minifiedFflate.code;
24
-
25
- const headContent = plugin.extractHeadContent(html);
26
- const out = await plugin.generateDecompressHtml(base64String, fflateLibContent, headContent);
27
- console.log(`[fflate] ${(originalSize / 1024).toFixed(1)}kb → ${(Buffer.byteLength(out, 'utf8') / 1024).toFixed(1)}kb`);
28
- return out;
29
- }
30
-
31
- module.exports = { compressHtml };
1
+ "use strict";const fs=require("fs");const path=require("path");const fflate=require("fflate");const{minify}=require("terser");const{FflateCompressionPlugin}=require("../plugins/FflateCompressionPlugin.js");async function compressHtml(html,options={}){const plugin=new FflateCompressionPlugin(options);const originalSize=Buffer.byteLength(html,"utf8");const compressedUint8Array=fflate.zlibSync(new TextEncoder().encode(html),{level:9});const base64String=Buffer.from(compressedUint8Array).toString("base64");const fflatePackageRoot=path.join(path.dirname(require.resolve("fflate")),"..");const fflateLibPath=path.join(fflatePackageRoot,"umd","index.js");let fflateLibContent=fs.readFileSync(fflateLibPath,"utf8");const minifiedFflate=await minify(fflateLibContent,{compress:{passes:2,dead_code:true,drop_console:true,drop_debugger:true},mangle:true,format:{comments:false}});fflateLibContent=minifiedFflate.code;const headContent=plugin.extractHeadContent(html);const out=await plugin.generateDecompressHtml(base64String,fflateLibContent,headContent);console.log(`[fflate] ${(originalSize/1024).toFixed(1)}kb → ${(Buffer.byteLength(out,"utf8")/1024).toFixed(1)}kb`);return out}module.exports={compressHtml};
@@ -1,39 +1 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const yazl = require('yazl');
6
- const { MAX_PLAYABLE_PACKAGE_BYTES } = require('../constants.js');
7
-
8
- function zipDirectory(dirPath, zipPath) {
9
- return new Promise((resolve, reject) => {
10
- const zipFile = new yazl.ZipFile();
11
- const addDir = (dir, prefix) => {
12
- for (const name of fs.readdirSync(dir)) {
13
- const abs = path.join(dir, name);
14
- const entry = prefix ? `${prefix}/${name}` : name;
15
- if (fs.statSync(abs).isDirectory()) addDir(abs, entry);
16
- else zipFile.addFile(abs, entry.replace(/\\/g, '/'));
17
- }
18
- };
19
- addDir(dirPath, '');
20
- zipFile.end();
21
- fs.mkdirSync(path.dirname(zipPath), { recursive: true });
22
- const out = fs.createWriteStream(zipPath);
23
- zipFile.outputStream.pipe(out);
24
- out.on('close', () => {
25
- const size = fs.statSync(zipPath).size;
26
- const sizeMb = (size / (1024 * 1024)).toFixed(2);
27
- const maxMb = (MAX_PLAYABLE_PACKAGE_BYTES / (1024 * 1024)).toFixed(0);
28
- console.log(`📦 ZIP size: ${sizeMb}MB (limit: ${maxMb}MB)`);
29
- if (size > MAX_PLAYABLE_PACKAGE_BYTES) {
30
- reject(new Error(`ZIP 产物超过 ${maxMb}MB 上限: ${sizeMb}MB (${path.basename(zipPath)})`));
31
- return;
32
- }
33
- resolve(zipPath);
34
- });
35
- out.on('error', reject);
36
- });
37
- }
38
-
39
- module.exports = { zipDirectory };
1
+ "use strict";const fs=require("fs");const path=require("path");const yazl=require("yazl");const{MAX_PLAYABLE_PACKAGE_BYTES}=require("../constants.js");function zipDirectory(dirPath,zipPath){return new Promise((resolve,reject)=>{const zipFile=new yazl.ZipFile;const addDir=(dir,prefix)=>{for(const name of fs.readdirSync(dir)){const abs=path.join(dir,name);const entry=prefix?`${prefix}/${name}`:name;if(fs.statSync(abs).isDirectory())addDir(abs,entry);else zipFile.addFile(abs,entry.replace(/\\/g,"/"))}};addDir(dirPath,"");zipFile.end();fs.mkdirSync(path.dirname(zipPath),{recursive:true});const out=fs.createWriteStream(zipPath);zipFile.outputStream.pipe(out);out.on("close",()=>{const size=fs.statSync(zipPath).size;const sizeMb=(size/(1024*1024)).toFixed(2);const maxMb=(MAX_PLAYABLE_PACKAGE_BYTES/(1024*1024)).toFixed(0);console.log(`📦 ZIP size: ${sizeMb}MB (limit: ${maxMb}MB)`);if(size>MAX_PLAYABLE_PACKAGE_BYTES){reject(new Error(`ZIP 产物超过 ${maxMb}MB 上限: ${sizeMb}MB (${path.basename(zipPath)})`));return}resolve(zipPath)});out.on("error",reject)})}module.exports={zipDirectory};