@playcraft/devkit 1.0.15-beta.1 → 1.0.17

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 (45) hide show
  1. package/README.md +259 -0
  2. package/cli/bin/playable-scripts.js +44 -1
  3. package/cli/commands/build.js +122 -17
  4. package/cli/commands/builds.js +13 -2
  5. package/cli/commands/dev.js +7 -1
  6. package/cli/commands/pack.js +308 -39
  7. package/cli/commands/vite-dev.js +7 -1
  8. package/core/batch-build.js +1091 -124
  9. package/core/cos-uploader.js +264 -20
  10. package/core/index.js +58 -4
  11. package/core/loaders/gltf-loader.js +74 -28
  12. package/core/options.js +310 -15
  13. package/core/plugins/AdikteevInjectorPlugin.js +26 -4
  14. package/core/plugins/BigoAdsInjectorPlugin.js +21 -4
  15. package/core/plugins/DAPIInjectorPlugin.js +25 -2
  16. package/core/plugins/DebuggerInjectionPlugin.js +31 -3
  17. package/core/plugins/ExitAPIInjectorPlugin.js +57 -3
  18. package/core/plugins/FflateCompressionPlugin.js +225 -37
  19. package/core/plugins/LiftoffInjectorPlugin.js +26 -4
  20. package/core/plugins/MRAIDInjectorPlugin.js +24 -2
  21. package/core/plugins/MintegralInjectorPlugin.js +25 -2
  22. package/core/plugins/PangleInjectorPlugin.js +24 -2
  23. package/core/plugins/SnapchatInjectorPlugin.js +26 -4
  24. package/core/plugins/TikTokInjectorPlugin.js +24 -2
  25. package/core/plugins/UnityInjectorPlugin.js +37 -8
  26. package/core/plugins/ZipPlugin.js +138 -19
  27. package/core/utils/buildDefines.js +29 -2
  28. package/core/utils/buildTemplateString.js +40 -5
  29. package/core/utils/date.js +16 -1
  30. package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +42 -7
  31. package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +30 -3
  32. package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +43 -7
  33. package/core/utils/injectSDKPlugins.js +58 -11
  34. package/core/utils/logOptions.js +37 -4
  35. package/core/utils/mergeOptions.js +19 -2
  36. package/core/utils/parseArgvOptions.js +110 -5
  37. package/core/utils/resolveChannelFold2Zip.js +15 -3
  38. package/core/utils/validateThemeData.js +220 -25
  39. package/core/validators/pre-build-checker.js +201 -21
  40. package/core/validators/tracking-validator.js +358 -40
  41. package/core/vite.dev.js +181 -15
  42. package/core/webpack.build.js +446 -52
  43. package/core/webpack.common.js +177 -10
  44. package/core/webpack.dev.js +82 -5
  45. package/package.json +3 -2
@@ -1,33 +1,145 @@
1
- "use strict";const fs=require("fs");const path=require("path");const yazl=require("yazl");// ==================== 配置 ====================
2
- const projectRoot=process.cwd();/**
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
+ /**
3
12
  * 解析 appName,依次从 builds.config.js、build.json 或 CLI 参数中获取。
4
13
  * 优先级: CLI --app-name > builds.config.js > build.json > 文件夹名称
5
14
  * @returns {string}
6
- */function resolveAppName(){// 1. CLI 参数: --app-name=xxx
7
- 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
8
- 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){// 忽略错误
9
- }}// 3. build.json → app
10
- 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){// 忽略错误
11
- }}// 4. 兜底: 使用项目文件夹名称
12
- return path.basename(projectRoot)}/**
15
+ */
16
+ function resolveAppName() {
17
+ // 1. CLI 参数: --app-name=xxx
18
+ const args = process.argv.slice(2);
19
+ for (const arg of args) {
20
+ if (arg.startsWith('--app-name=')) {
21
+ return arg.split('=')[1].trim();
22
+ }
23
+ }
24
+
25
+ // 2. builds.config.js → appName 或 title
26
+ const buildsConfigPath = path.join(projectRoot, 'builds.config.js');
27
+ if (fs.existsSync(buildsConfigPath)) {
28
+ try {
29
+ delete require.cache[buildsConfigPath];
30
+ const config = require(buildsConfigPath);
31
+ if (config.appName) return config.appName;
32
+ if (config.title) return config.title;
33
+ } catch (e) {
34
+ // 忽略错误
35
+ }
36
+ }
37
+
38
+ // 3. build.json → app
39
+ const buildJsonPath = path.join(projectRoot, 'build.json');
40
+ if (fs.existsSync(buildJsonPath)) {
41
+ try {
42
+ const data = JSON.parse(fs.readFileSync(buildJsonPath, 'utf8'));
43
+ if (data.app) return data.app;
44
+ } catch (e) {
45
+ // 忽略错误
46
+ }
47
+ }
48
+
49
+ // 4. 兜底: 使用项目文件夹名称
50
+ return path.basename(projectRoot);
51
+ }
52
+
53
+ /**
13
54
  * 从 CLI 参数中解析输出目录。
14
55
  * @returns {string}
15
- */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"}// ==================== 忽略规则解析 ====================
56
+ */
57
+ function resolveOutputDir() {
58
+ const args = process.argv.slice(2);
59
+ for (const arg of args) {
60
+ if (arg.startsWith('--out-dir=')) {
61
+ return arg.split('=')[1].trim();
62
+ }
63
+ }
64
+ return 'dist';
65
+ }
66
+
67
+ // ==================== 忽略规则解析 ====================
68
+
16
69
  /**
17
70
  * 解析单个忽略文件,返回规范化后的模式数组。
18
71
  * @param {string} filePath - 忽略文件的绝对路径
19
72
  * @returns {string[]} 规范化后的忽略模式数组
20
- */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();// 跳过空行和注释
21
- if(!line||line.startsWith("#"))continue;// 不支持取反模式,给出警告并跳过
22
- if(line.startsWith("!")){console.log(`\x1b[33m⚠️ 不支持取反模式,已跳过: ${line}\x1b[0m`);continue}// 规范化:
23
- // - 移除尾部斜杠(用于目录匹配)
24
- // - 移除前导 ./(gitignore 中 ./foo 等同于 foo)
25
- let normalized=line.replace(/\/+$/,"");normalized=normalized.replace(/^\.\//g,"");patterns.push(normalized)}return patterns}/**
73
+ */
74
+ function parseSingleIgnoreFile(filePath) {
75
+ if (!fs.existsSync(filePath)) {
76
+ return [];
77
+ }
78
+
79
+ const content = fs.readFileSync(filePath, 'utf8');
80
+ const patterns = [];
81
+
82
+ for (const rawLine of content.split(/\r?\n/)) {
83
+ const line = rawLine.trim();
84
+
85
+ // 跳过空行和注释
86
+ if (!line || line.startsWith('#')) continue;
87
+
88
+ // 不支持取反模式,给出警告并跳过
89
+ if (line.startsWith('!')) {
90
+ console.log(`\x1b[33m⚠️ 不支持取反模式,已跳过: ${line}\x1b[0m`);
91
+ continue;
92
+ }
93
+
94
+ // 规范化:
95
+ // - 移除尾部斜杠(用于目录匹配)
96
+ // - 移除前导 ./(gitignore 中 ./foo 等同于 foo)
97
+ let normalized = line.replace(/\/+$/, '');
98
+ normalized = normalized.replace(/^\.\//g, '');
99
+ patterns.push(normalized);
100
+ }
101
+
102
+ return patterns;
103
+ }
104
+
105
+ /**
26
106
  * 解析 .gitignore 和 .playcraftignore 文件,合并并去重。
27
107
  * .gitignore 提供基础忽略规则,.playcraftignore 提供额外补充规则。
28
108
  * @returns {{ patterns: string[], sources: { gitignore: number, playcraftignore: number } }}
29
- */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\u6253\u5305\u6240\u6709\u6587\u4EF6\u3002\x1B[0m");return{patterns:[],sources:{gitignore:0,playcraftignore:0}}}// 合并并去重(保持顺序,.gitignore 优先)
30
- const seen=new Set;const merged=[];for(const p of[...gitignorePatterns,...playcraftPatterns]){if(!seen.has(p)){seen.add(p);merged.push(p)}}return{patterns:merged,sources:{gitignore:gitignorePatterns.length,playcraftignore:playcraftPatterns.length}}}/**
109
+ */
110
+ function parseIgnoreFiles() {
111
+ const gitignorePath = path.join(projectRoot, '.gitignore');
112
+ const playcraftignorePath = path.join(projectRoot, '.playcraftignore');
113
+
114
+ const gitignorePatterns = parseSingleIgnoreFile(gitignorePath);
115
+ const playcraftPatterns = parseSingleIgnoreFile(playcraftignorePath);
116
+
117
+ if (gitignorePatterns.length === 0 && playcraftPatterns.length === 0) {
118
+ console.log('\x1b[33m⚠️ 未找到 .gitignore 或 .playcraftignore 文件,将打包所有文件。\x1b[0m');
119
+ return { patterns: [], sources: { gitignore: 0, playcraftignore: 0 } };
120
+ }
121
+
122
+ // 合并并去重(保持顺序,.gitignore 优先)
123
+ const seen = new Set();
124
+ const merged = [];
125
+
126
+ for (const p of [...gitignorePatterns, ...playcraftPatterns]) {
127
+ if (!seen.has(p)) {
128
+ seen.add(p);
129
+ merged.push(p);
130
+ }
131
+ }
132
+
133
+ return {
134
+ patterns: merged,
135
+ sources: {
136
+ gitignore: gitignorePatterns.length,
137
+ playcraftignore: playcraftPatterns.length,
138
+ },
139
+ };
140
+ }
141
+
142
+ /**
31
143
  * 将 gitignore 风格的模式转换为正则表达式。
32
144
  *
33
145
  * gitignore 语义:
@@ -38,44 +150,201 @@ const seen=new Set;const merged=[];for(const p of[...gitignorePatterns,...playcr
38
150
  *
39
151
  * @param {string} pattern
40
152
  * @returns {RegExp}
41
- */function patternToRegex(pattern){let p=pattern;let anchored=false;// 前导 / 表示锚定到根目录;构建正则前剥离
42
- if(p.startsWith("/")){p=p.slice(1);anchored=true}// 如果模式中包含 /,同样视为锚定到根目录
43
- if(p.includes("/")){anchored=true}// 转义正则特殊字符(* 和 ? 除外)
44
- let regexStr=p.replace(/[.+^${}()|[\]\\]/g,"\\$&");// 处理 **(匹配任意路径层级,包括嵌套目录)
45
- regexStr=regexStr.replace(/\*\*/g,"{{GLOBSTAR}}");// 处理 *(匹配除路径分隔符外的任意字符)
46
- regexStr=regexStr.replace(/\*/g,"[^/]*");// 处理 ?(匹配除路径分隔符外的单个字符)
47
- regexStr=regexStr.replace(/\?/g,"[^/]");// 还原 globstar 占位符
48
- regexStr=regexStr.replace(/\{\{GLOBSTAR\}\}/g,".*");if(anchored){// 锚定模式:从相对路径的起始位置匹配
49
- return new RegExp(`^${regexStr}(/|$)`)}// 非锚定模式:在目录树中任意位置匹配路径段
50
- return new RegExp(`(^|/)${regexStr}(/|$)`)}/**
153
+ */
154
+ function patternToRegex(pattern) {
155
+ let p = pattern;
156
+ let anchored = false;
157
+
158
+ // 前导 / 表示锚定到根目录;构建正则前剥离
159
+ if (p.startsWith('/')) {
160
+ p = p.slice(1);
161
+ anchored = true;
162
+ }
163
+
164
+ // 如果模式中包含 /,同样视为锚定到根目录
165
+ if (p.includes('/')) {
166
+ anchored = true;
167
+ }
168
+
169
+ // 转义正则特殊字符(* 和 ? 除外)
170
+ let regexStr = p.replace(/[.+^${}()|[\]\\]/g, '\\$&');
171
+
172
+ // 处理 **(匹配任意路径层级,包括嵌套目录)
173
+ regexStr = regexStr.replace(/\*\*/g, '{{GLOBSTAR}}');
174
+ // 处理 *(匹配除路径分隔符外的任意字符)
175
+ regexStr = regexStr.replace(/\*/g, '[^/]*');
176
+ // 处理 ?(匹配除路径分隔符外的单个字符)
177
+ regexStr = regexStr.replace(/\?/g, '[^/]');
178
+ // 还原 globstar 占位符
179
+ regexStr = regexStr.replace(/\{\{GLOBSTAR\}\}/g, '.*');
180
+
181
+ if (anchored) {
182
+ // 锚定模式:从相对路径的起始位置匹配
183
+ return new RegExp(`^${regexStr}(/|$)`);
184
+ }
185
+
186
+ // 非锚定模式:在目录树中任意位置匹配路径段
187
+ return new RegExp(`(^|/)${regexStr}(/|$)`);
188
+ }
189
+
190
+ /**
51
191
  * 检查相对路径是否应被忽略。
52
192
  * @param {string} relativePath - 使用正斜杠规范化后的相对路径
53
193
  * @param {RegExp[]} regexPatterns
54
194
  * @returns {boolean}
55
- */function shouldIgnore(relativePath,regexPatterns){for(const regex of regexPatterns){if(regex.test(relativePath))return true}return false}// ==================== 文件收集 ====================
195
+ */
196
+ function shouldIgnore(relativePath, regexPatterns) {
197
+ for (const regex of regexPatterns) {
198
+ if (regex.test(relativePath)) return true;
199
+ }
200
+ return false;
201
+ }
202
+
203
+ // ==================== 文件收集 ====================
204
+
56
205
  /**
57
206
  * 递归收集目录下的所有文件,遵循忽略规则。
58
207
  * @param {string} dir - 目录的绝对路径
59
208
  * @param {string} baseDir - 项目根目录,用于计算相对路径
60
209
  * @param {RegExp[]} ignoreRegexes
61
210
  * @returns {string[]} 文件绝对路径数组
62
- */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);// 使用正斜杠以保持匹配一致性
63
- 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 创建 ====================
211
+ */
212
+ function collectFiles(dir, baseDir, ignoreRegexes) {
213
+ const results = [];
214
+ let entries;
215
+
216
+ try {
217
+ entries = fs.readdirSync(dir, { withFileTypes: true });
218
+ } catch (e) {
219
+ console.warn(`\x1b[33m⚠️ 无法读取目录: ${dir}\x1b[0m`);
220
+ return results;
221
+ }
222
+
223
+ for (const entry of entries) {
224
+ const fullPath = path.join(dir, entry.name);
225
+ // 使用正斜杠以保持匹配一致性
226
+ const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
227
+
228
+ if (shouldIgnore(relativePath, ignoreRegexes)) {
229
+ continue;
230
+ }
231
+
232
+ if (entry.isDirectory()) {
233
+ results.push(...collectFiles(fullPath, baseDir, ignoreRegexes));
234
+ } else if (entry.isFile() || entry.isSymbolicLink()) {
235
+ results.push(fullPath);
236
+ }
237
+ }
238
+
239
+ return results;
240
+ }
241
+
242
+ // ==================== Zip 创建 ====================
243
+
64
244
  /**
65
245
  * 根据收集到的文件创建 zip 压缩包。
66
246
  * @param {string[]} files - 文件绝对路径数组
67
247
  * @param {string} rootFolderName - zip 内的根文件夹名称
68
248
  * @param {string} outputPath - 输出 zip 文件的绝对路径
69
249
  * @returns {Promise<{fileCount: number, zipSize: number}>}
70
- */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();// 确保输出目录存在
71
- 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)})}// ==================== 格式化工具 ====================
250
+ */
251
+ function createZip(files, rootFolderName, outputPath) {
252
+ return new Promise((resolve, reject) => {
253
+ const zipFile = new yazl.ZipFile();
254
+ let fileCount = 0;
255
+
256
+ for (const filePath of files) {
257
+ const relativePath = path.relative(projectRoot, filePath).replace(/\\/g, '/');
258
+ const zipEntryPath = `${rootFolderName}/${relativePath}`;
259
+
260
+ zipFile.addFile(filePath, zipEntryPath);
261
+ fileCount++;
262
+ }
263
+
264
+ zipFile.end();
265
+
266
+ // 确保输出目录存在
267
+ const outputDir = path.dirname(outputPath);
268
+ if (!fs.existsSync(outputDir)) {
269
+ fs.mkdirSync(outputDir, { recursive: true });
270
+ }
271
+
272
+ const writeStream = fs.createWriteStream(outputPath);
273
+
274
+ writeStream.on('error', (err) => {
275
+ reject(err);
276
+ });
277
+
278
+ writeStream.on('close', () => {
279
+ const stats = fs.statSync(outputPath);
280
+ resolve({ fileCount, zipSize: stats.size });
281
+ });
282
+
283
+ zipFile.outputStream.pipe(writeStream);
284
+ });
285
+ }
286
+
287
+ // ==================== 格式化工具 ====================
288
+
72
289
  /**
73
290
  * 将字节数格式化为可读字符串。
74
291
  * @param {number} bytes
75
292
  * @returns {string}
76
- */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`}// ==================== 主流程 ====================
77
- async function main(){console.log("\x1B[36m\uD83D\uDCE6 playable-scripts \u6253\u5305\x1B[0m\n");// 1. 解析 appName
78
- const appName=resolveAppName();console.log(` 应用名称: \x1b[1m${appName}\x1b[0m`);// 2. 解析忽略规则(合并 .gitignore + .playcraftignore)
79
- const{patterns,sources}=parseIgnoreFiles();const ignoreRegexes=patterns.map(patternToRegex);if(patterns.length>0){const parts=[];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. 收集文件
80
- 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 压缩包
81
- 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();
293
+ */
294
+ function formatSize(bytes) {
295
+ if (bytes < 1024) return `${bytes} B`;
296
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
297
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
298
+ }
299
+
300
+ // ==================== 主流程 ====================
301
+
302
+ async function main() {
303
+ console.log('\x1b[36m📦 playable-scripts 打包\x1b[0m\n');
304
+
305
+ // 1. 解析 appName
306
+ const appName = resolveAppName();
307
+ console.log(` 应用名称: \x1b[1m${appName}\x1b[0m`);
308
+
309
+ // 2. 解析忽略规则(合并 .gitignore + .playcraftignore)
310
+ const { patterns, sources } = parseIgnoreFiles();
311
+ const ignoreRegexes = patterns.map(patternToRegex);
312
+
313
+ if (patterns.length > 0) {
314
+ const parts = [];
315
+ if (sources.gitignore > 0) parts.push(`${sources.gitignore} 条来自 .gitignore`);
316
+ if (sources.playcraftignore > 0) parts.push(`${sources.playcraftignore} 条来自 .playcraftignore`);
317
+ console.log(` 忽略规则: \x1b[33m${patterns.length}\x1b[0m 条模式 (${parts.join(', ')})`);
318
+ }
319
+
320
+ // 3. 收集文件
321
+ console.log(` 正在扫描项目文件...`);
322
+ const files = collectFiles(projectRoot, projectRoot, ignoreRegexes);
323
+
324
+ if (files.length === 0) {
325
+ console.error('\x1b[31m❌ 没有可打包的文件!请检查 .playcraftignore 规则。\x1b[0m');
326
+ process.exit(1);
327
+ }
328
+
329
+ console.log(` 文件数量: \x1b[32m${files.length}\x1b[0m 个待打包文件`);
330
+
331
+ // 4. 创建 zip 压缩包
332
+ const outDir = resolveOutputDir();
333
+ const outputPath = path.resolve(projectRoot, outDir, `${appName}.zip`);
334
+
335
+ console.log(` 输出路径: ${outputPath}\n`);
336
+ console.log(' 正在打包...');
337
+
338
+ try {
339
+ const { fileCount, zipSize } = await createZip(files, appName, outputPath);
340
+ console.log(`\n\x1b[32m✅ 打包完成!\x1b[0m`);
341
+ console.log(` 文件数: ${fileCount}`);
342
+ console.log(` 大小: ${formatSize(zipSize)}`);
343
+ console.log(` 输出: ${outputPath}`);
344
+ } catch (err) {
345
+ console.error(`\n\x1b[31m❌ 打包失败: ${err.message}\x1b[0m`);
346
+ process.exit(1);
347
+ }
348
+ }
349
+
350
+ main();
@@ -1 +1,7 @@
1
- "use strict";process.env.BABEL_ENV="development";process.env.NODE_ENV="development";const{runViteDev}=require("../../core/vite.dev.js");runViteDev();
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();