ttmg-pack 0.2.8-unity.1 → 0.2.9-beta.1

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.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * ==========================================
3
3
  * @Description: ttmg pack
4
- * @Version: 0.2.8-unity.1
4
+ * @Version: 0.2.9-beta.1
5
5
  * @Author: zhanghongyang.mocha
6
- * @Date: 2025-12-10 13:44:38
6
+ * @Date: 2026-01-22 14:34:50
7
7
  * ==========================================
8
8
  */
9
9
  'use strict';
@@ -146,32 +146,93 @@ const PACKAGE_TYPE = {
146
146
  IndependentPackage: 'independent_package',
147
147
  MainPackage: 'main_package',
148
148
  };
149
+ const TTMG_TEMP_DIR = '__TTMG_TEMP__';
150
+ const USELESS_DIRS = ['node_modules', '__MACOSX', TTMG_TEMP_DIR];
151
+ const USELESS_FILES = ['.DS_Store', 'Thumbs.db'];
152
+ const GAME_PKG_SIZE_LIMIT = {
153
+ unity: {
154
+ project: 60 * 1024 * 1024,
155
+ main: null,
156
+ independent: null,
157
+ },
158
+ };
149
159
 
150
- // 递归统计目录下所有文件的大小,支持我过滤某些文件夹
151
- function getDirSizeSync({ rootDir, entryDir, filterDirs, }) {
160
+ /**
161
+ * 递归统计目录下所有文件的大小,支持过滤文件夹和文件
162
+ */
163
+ function getDirSizeSync(options) {
164
+ const { rootDir, entryDir, filterDirs = USELESS_DIRS, filterFiles = USELESS_FILES, filterPatterns = [], filterRegex = [], } = options;
152
165
  let totalSize = 0;
153
- if (!fs.existsSync(entryDir))
166
+ // 检查目录是否存在
167
+ if (!fs.existsSync(entryDir)) {
154
168
  return 0;
155
- const entries = fs.readdirSync(entryDir, { withFileTypes: true });
156
- for (const entry of entries) {
157
- const fullPath = path.join(entryDir, entry.name);
158
- if (entry.isDirectory()) {
159
- /**
160
- * 基于 rootDir 算出 fullPath 的相对路径
161
- */
162
- const relativePath = path.relative(rootDir, fullPath).replace(/\\/g, '/');
163
- if (!filterDirs.includes(relativePath)) {
164
- totalSize += getDirSizeSync({
165
- entryDir: fullPath,
166
- filterDirs,
167
- rootDir,
168
- });
169
+ }
170
+ // 将通配符模式转换为正则表达式
171
+ const patternRegexes = filterPatterns.map(pattern => {
172
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
173
+ const regexStr = escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
174
+ return new RegExp(`^${regexStr}$`);
175
+ });
176
+ // 合并所有正则表达式
177
+ const allRegexes = [...patternRegexes, ...filterRegex];
178
+ // 检查是否应该忽略当前条目
179
+ const shouldIgnore = (name, isDirectory) => {
180
+ // 1. 检查精确匹配
181
+ if (isDirectory && filterDirs.includes(name)) {
182
+ return true;
183
+ }
184
+ if (!isDirectory && filterFiles.includes(name)) {
185
+ return true;
186
+ }
187
+ // 2. 检查相对路径匹配(针对目录)
188
+ if (isDirectory) {
189
+ const relativePath = path
190
+ .relative(rootDir, path.join(entryDir, name))
191
+ .replace(/\\/g, '/');
192
+ if (filterDirs.includes(relativePath)) {
193
+ return true;
169
194
  }
170
195
  }
171
- else if (entry.isFile()) {
172
- totalSize += fs.statSync(fullPath).size;
196
+ // 3. 检查正则表达式匹配
197
+ if (allRegexes.some(regex => regex.test(name))) {
198
+ return true;
199
+ }
200
+ return false;
201
+ };
202
+ try {
203
+ const entries = fs.readdirSync(entryDir, { withFileTypes: true });
204
+ for (const entry of entries) {
205
+ const fullPath = path.join(entryDir, entry.name);
206
+ try {
207
+ if (entry.isDirectory()) {
208
+ // 检查是否应该忽略此目录
209
+ if (!shouldIgnore(entry.name, true)) {
210
+ totalSize += getDirSizeSync({
211
+ rootDir,
212
+ entryDir: fullPath,
213
+ filterDirs,
214
+ filterFiles,
215
+ filterPatterns,
216
+ filterRegex,
217
+ });
218
+ }
219
+ }
220
+ else if (entry.isFile()) {
221
+ // 检查是否应该忽略此文件
222
+ if (!shouldIgnore(entry.name, false)) {
223
+ totalSize += fs.statSync(fullPath).size;
224
+ }
225
+ }
226
+ }
227
+ catch (err) {
228
+ // 跳过无法访问的文件/目录
229
+ console.warn(`无法访问: ${fullPath}`, err.message);
230
+ }
173
231
  }
174
232
  }
233
+ catch (err) {
234
+ console.warn(`无法读取目录: ${entryDir}`, err.message);
235
+ }
175
236
  return totalSize;
176
237
  }
177
238
 
@@ -216,52 +277,69 @@ function deleteNoJsFilesSync(dir) {
216
277
  }
217
278
  }
218
279
 
219
- const uselessDirs = ['node_modules', '__MACOSX'];
220
280
  const uselessFiles = ['.DS_Store', 'Thumbs.db'];
221
- function removeSystemUseless(gameEntry) {
222
- /**
223
- * 递归删除目录下的无用文件夹和无用文件
224
- */
225
- function removeUselessDirs(dir) {
226
- let files;
281
+ function removeSystemUseless(gameEntry, uselessDirs) {
282
+ const progress = {
283
+ total: 0,
284
+ processed: 0,
285
+ deletedDirs: 0,
286
+ deletedFiles: 0,
287
+ errors: 0,
288
+ };
289
+ // 1. 读取目录
290
+ let files;
291
+ try {
292
+ files = fs.readdirSync(gameEntry);
293
+ progress.total = files.length;
294
+ logger.info(`开始清理无用文件,共 ${progress.total} 个条目`);
295
+ }
296
+ catch (err) {
297
+ logger.warn(`无法访问目录: ${gameEntry}, ${err.message}`);
298
+ progress.errors++;
299
+ return progress;
300
+ }
301
+ // 2. 处理每个条目
302
+ files.forEach((file, index) => {
303
+ const filePath = path.join(gameEntry, file);
227
304
  try {
228
- files = fs.readdirSync(dir);
305
+ const stat = fs.statSync(filePath);
306
+ if (stat.isDirectory() && uselessDirs.includes(file)) {
307
+ // 删除无用目录
308
+ logger.debug(`正在删除目录: ${filePath}`);
309
+ fs.rmSync(filePath, { recursive: true, force: true });
310
+ progress.deletedDirs++;
311
+ logger.info(`✅ 已删除无用目录: ${filePath}`);
312
+ }
313
+ else if (stat.isFile() && uselessFiles.includes(file)) {
314
+ // 删除无用文件
315
+ logger.debug(`正在删除文件: ${filePath}`);
316
+ fs.rmSync(filePath, { force: true });
317
+ progress.deletedFiles++;
318
+ logger.info(`✅ 已删除无用文件: ${filePath}`);
319
+ }
320
+ else {
321
+ logger.debug(`跳过: ${filePath} (非目标文件/目录)`);
322
+ }
229
323
  }
230
324
  catch (err) {
231
- // 目录不可访问,跳过
232
- logger.warn(`无法访问目录: ${dir}, ${err.message}`);
233
- return;
325
+ logger.warn(`❌ 无法处理: ${filePath}, ${err.message}`);
326
+ progress.errors++;
234
327
  }
235
- files.forEach(file => {
236
- const filePath = path.join(dir, file);
237
- try {
238
- const stat = fs.statSync(filePath);
239
- if (stat.isDirectory()) {
240
- if (uselessDirs.includes(file)) {
241
- // 删除无用目录
242
- fs.rmSync(filePath, { recursive: true, force: true });
243
- logger.info(`已删除无用目录: ${filePath}`);
244
- }
245
- else {
246
- // 递归处理子目录
247
- removeUselessDirs(filePath);
248
- }
249
- }
250
- else if (stat.isFile()) {
251
- if (uselessFiles.includes(file)) {
252
- // 删除无用文件
253
- fs.rmSync(filePath, { force: true });
254
- logger.info(`已删除无用文件: ${filePath}`);
255
- }
256
- }
257
- }
258
- catch (err) {
259
- // 某个文件/目录不可访问,跳过
260
- logger.warn(`无法处理: ${filePath}, ${err.message}`);
261
- }
262
- });
263
- }
264
- removeUselessDirs(gameEntry);
328
+ // 更新进度
329
+ progress.processed = index + 1;
330
+ // 可选:每处理10个条目输出一次进度
331
+ if ((index + 1) % 10 === 0 || index + 1 === progress.total) {
332
+ const percentage = Math.round(((index + 1) / progress.total) * 100);
333
+ logger.info(`进度: ${index + 1}/${progress.total} (${percentage}%)`);
334
+ }
335
+ });
336
+ // 3. 输出总结
337
+ logger.info(`清理完成!`);
338
+ logger.info(`- 总条目: ${progress.total}`);
339
+ logger.info(`- 已删除目录: ${progress.deletedDirs}`);
340
+ logger.info(`- 已删除文件: ${progress.deletedFiles}`);
341
+ logger.info(`- 错误数: ${progress.errors}`);
342
+ return progress;
265
343
  }
266
344
 
267
345
  async function asyncPool$1(poolLimit, array, iteratorFn) {
@@ -315,7 +393,7 @@ function getMainPkgSize({ entryDir }) {
315
393
  return getDirSizeSync({
316
394
  rootDir: entryDir,
317
395
  entryDir,
318
- filterDirs: getSubpackagesRoots(entryDir),
396
+ filterDirs: [...getSubpackagesRoots(entryDir), ...USELESS_DIRS],
319
397
  });
320
398
  }
321
399
  function getSubpackagesRoots(entryDir) {
@@ -339,7 +417,6 @@ function getProjectSize({ entryDir }) {
339
417
  return getDirSizeSync({
340
418
  rootDir: entryDir,
341
419
  entryDir,
342
- filterDirs: [],
343
420
  });
344
421
  }
345
422
 
@@ -362,58 +439,6 @@ function getGameEngine(gameEntry) {
362
439
  }
363
440
  }
364
441
 
365
- function isUnityEngine(gameEntry) {
366
- return getGameEngine(gameEntry) === 'unity';
367
- }
368
-
369
- function getWasmBrCodePath({ entryDir, }) {
370
- if (!isUnityEngine(entryDir)) {
371
- return '';
372
- }
373
- else {
374
- /**
375
- * 检查项目下存在一个名为 wasmcode 的文件夹,且文件夹下存在一个文件后缀为 .webgl.wasm.code.unityweb.wasm.br 的文件。,如果满足条件,就展示 true,否则 返回 false
376
- */
377
- const wasmCodeDir = path.join(entryDir, 'wasmcode');
378
- if (!fs.existsSync(wasmCodeDir)) {
379
- return '';
380
- }
381
- const wasmBrFiles = fs.readdirSync(wasmCodeDir).filter(file => file.endsWith('.webgl.wasm.code.unityweb.wasm.br'));
382
- if (wasmBrFiles.length === 0) {
383
- return '';
384
- }
385
- else {
386
- return path.relative(entryDir, path.join(wasmCodeDir, wasmBrFiles[0]));
387
- }
388
- }
389
- }
390
-
391
- function getWasmBrCodeMd5({ entryDir }) {
392
- // originalWasmMd5
393
- try {
394
- const gameConfig = JSON.parse(fs.readFileSync(path.join(entryDir, 'game.json'), 'utf-8'));
395
- return gameConfig.originalWasmMd5 || '';
396
- }
397
- catch (_a) {
398
- return '';
399
- }
400
- }
401
-
402
- // 面向高鹏编程 😢
403
- /**
404
- * 覆盖配置,根据游戏引擎类型,设置主包和独立子包的大小限制
405
- * @param entryDir 游戏项目入口目录
406
- * @param config 原始构建配置
407
- * @returns 覆盖后的构建配置
408
- */
409
- function overrideConfig(entryDir, config) {
410
- var _a, _b;
411
- const isUnity = isUnityEngine(entryDir);
412
- return Object.assign(Object.assign({}, config), { build: Object.assign(Object.assign({}, config.build), { mainPkgSizeLimit: !isUnity ? (_a = config.build) === null || _a === void 0 ? void 0 : _a.mainPkgSizeLimit : null, independentSubPkgSizeLimit: !isUnity
413
- ? (_b = config.build) === null || _b === void 0 ? void 0 : _b.independentSubPkgSizeLimit
414
- : null }) });
415
- }
416
-
417
442
  let initializePromise = null;
418
443
  /**
419
444
  * 确保一个异步函数在整个应用生命周期内只被执行一次。
@@ -534,6 +559,98 @@ async function genOpenDataContext(entryPath) {
534
559
  return jsCode;
535
560
  }
536
561
 
562
+ function isUnityEngine(gameEntry) {
563
+ return getGameEngine(gameEntry) === 'unity';
564
+ }
565
+
566
+ function getWasmBrCodePath({ entryDir, }) {
567
+ if (!isUnityEngine(entryDir)) {
568
+ return '';
569
+ }
570
+ else {
571
+ /**
572
+ * 检查项目下存在一个名为 wasmcode 的文件夹,且文件夹下存在一个文件后缀为 .webgl.wasm.code.unityweb.wasm.br 的文件。,如果满足条件,就展示 true,否则 返回 false
573
+ */
574
+ const wasmCodeDir = path.join(entryDir, 'wasmcode');
575
+ if (!fs.existsSync(wasmCodeDir)) {
576
+ return '';
577
+ }
578
+ const wasmBrFiles = fs.readdirSync(wasmCodeDir).filter(file => file.endsWith('.webgl.wasm.code.unityweb.wasm.br'));
579
+ if (wasmBrFiles.length === 0) {
580
+ return '';
581
+ }
582
+ else {
583
+ return path.relative(entryDir, path.join(wasmCodeDir, wasmBrFiles[0]));
584
+ }
585
+ }
586
+ }
587
+
588
+ function getWasmBrCodeMd5({ entryDir }) {
589
+ // originalWasmMd5
590
+ try {
591
+ const gameConfig = JSON.parse(fs.readFileSync(path.join(entryDir, 'game.json'), 'utf-8'));
592
+ return gameConfig.originalWasmMd5 || '';
593
+ }
594
+ catch (_a) {
595
+ return '';
596
+ }
597
+ }
598
+
599
+ function getUnityBuildConfig() {
600
+ return {
601
+ pkgSizeLimit: GAME_PKG_SIZE_LIMIT.unity.project,
602
+ mainPkgSizeLimit: GAME_PKG_SIZE_LIMIT.unity.main,
603
+ independentSubPkgSizeLimit: GAME_PKG_SIZE_LIMIT.unity.independent,
604
+ };
605
+ }
606
+
607
+ function getCheckConfig(config) {
608
+ if (isUnityEngine(config.entry)) {
609
+ return getUnityBuildConfig();
610
+ }
611
+ else {
612
+ return {
613
+ pkgSizeLimit: 30 * 1024 * 1024,
614
+ mainPkgSizeLimit: 4 * 1024 * 1024,
615
+ independentSubPkgSizeLimit: 4 * 1024 * 1024,
616
+ };
617
+ }
618
+ }
619
+
620
+ function copyDirectory(src, dest) {
621
+ try {
622
+ if (!fs.existsSync(dest)) {
623
+ fs.mkdirSync(dest, { recursive: true });
624
+ }
625
+ const items = fs.readdirSync(src);
626
+ const normalizedDest = path.resolve(dest) + path.sep;
627
+ items.forEach(item => {
628
+ const srcPath = path.join(src, item);
629
+ const destPath = path.join(dest, item);
630
+ // 检查当前要处理的源路径是否就是目标路径
631
+ // 这可以防止将目标目录本身复制到自身中
632
+ if (path.resolve(srcPath) === path.resolve(dest)) {
633
+ return; // 跳过目标目录本身
634
+ }
635
+ // 获取文件状态
636
+ const stat = fs.statSync(srcPath);
637
+ if (stat.isFile()) {
638
+ // 复制文件
639
+ fs.copyFileSync(srcPath, destPath);
640
+ }
641
+ else if (stat.isDirectory()) {
642
+ const childNormalizedSrc = path.resolve(srcPath) + path.sep;
643
+ if (!normalizedDest.startsWith(childNormalizedSrc)) {
644
+ copyDirectory(srcPath, destPath);
645
+ }
646
+ }
647
+ });
648
+ }
649
+ catch (error) {
650
+ logger.error(`copyDirectory error: ${error}`);
651
+ }
652
+ }
653
+
537
654
  /**
538
655
  * 将 packageConfig packages 中 分拆的包 配置迁移到 odr_packages 中
539
656
  */
@@ -553,7 +670,7 @@ async function writeOdrConfig(outputDir) {
553
670
  if (!odrPackages[relatedPkgName]) {
554
671
  odrPackages[relatedPkgName] = {};
555
672
  }
556
- odrPackages[relatedPkgName] = Object.assign(Object.assign({}, odrPackages[relatedPkgName]), { root: pkgConfig.root, code_output: pkgConfig.output, code_md5: pkgConfig.md5, main: pkgConfig.main });
673
+ odrPackages[relatedPkgName] = Object.assign(Object.assign({}, odrPackages[relatedPkgName]), { root: pkgConfig.root, code_output: pkgConfig.output, code_md5: pkgConfig.md5, main: pkgConfig.main, dependencies: pkgConfig.dependencies, type: pkgConfig.type });
557
674
  delete packages[pkgName];
558
675
  }
559
676
  if (pkgName.endsWith(ODR_ASSET_DIR_SUFFIX)) {
@@ -582,12 +699,14 @@ async function makeOdrPkgs(outputDir) {
582
699
  const pkgOutput = path.join(outputDir, pkgName);
583
700
  const codeOutput = path.join(outputDir, `${pkgName}${ODR_CODE_DIR_SUFFIX}`);
584
701
  const assetOutput = path.join(outputDir, `${pkgName}${ODR_ASSET_DIR_SUFFIX}`);
585
- ensureDirSync(codeOutput);
586
- ensureDirSync(assetOutput);
587
- fs.cpSync(pkgOutput, codeOutput, { recursive: true });
588
- fs.cpSync(pkgOutput, assetOutput, { recursive: true });
589
- deleteNoJsFilesSync(codeOutput);
590
- deleteJsFilesSync(assetOutput);
702
+ if (packages[pkgName].type === 'game') {
703
+ ensureDirSync(codeOutput);
704
+ ensureDirSync(assetOutput);
705
+ fs.cpSync(pkgOutput, codeOutput, { recursive: true });
706
+ fs.cpSync(pkgOutput, assetOutput, { recursive: true });
707
+ deleteNoJsFilesSync(codeOutput);
708
+ deleteJsFilesSync(assetOutput);
709
+ }
591
710
  /**
592
711
  * 写入配置
593
712
  */
@@ -830,7 +949,8 @@ function checkGameJson(entryDir, config) {
830
949
  */
831
950
  function checkProjectSize(entryDir, config) {
832
951
  logger.info('start check project size');
833
- const { build: { pkgSizeLimit }, dev, } = config;
952
+ const { dev } = config;
953
+ const { pkgSizeLimit } = getCheckConfig(config);
834
954
  const enableDev = dev === null || dev === void 0 ? void 0 : dev.enable;
835
955
  const gameSize = getProjectSize({ entryDir });
836
956
  const gameSizeMB = (gameSize / (1024 * 1024)).toFixed(2);
@@ -867,11 +987,12 @@ const defaultCheckConfig$2 = {
867
987
  */
868
988
  function checkMainPackageSize({ entryDir, config, }) {
869
989
  logger.info('start check main package size');
870
- const { build, dev } = config;
990
+ const { dev } = config;
991
+ const { mainPkgSizeLimit } = getCheckConfig(config);
871
992
  const mainPkgSize = getMainPkgSize({ entryDir });
872
993
  const mainPkgSizeMB = (mainPkgSize / (1024 * 1024)).toFixed(2);
873
- const limitMB = (build.mainPkgSizeLimit / (1024 * 1024)).toFixed(2);
874
- if (mainPkgSize > build.mainPkgSizeLimit) {
994
+ const limitMB = (mainPkgSizeLimit / (1024 * 1024)).toFixed(2);
995
+ if (mainPkgSize > mainPkgSizeLimit) {
875
996
  const errMsg = `Check main package size failed, main package size ${mainPkgSizeMB}MB, must not exceed ${limitMB}MB`;
876
997
  logger.error(errMsg);
877
998
  if (dev === null || dev === void 0 ? void 0 : dev.enable) {
@@ -912,10 +1033,7 @@ function checkMainPackage(entryDir, config) {
912
1033
  entryDir,
913
1034
  config,
914
1035
  }));
915
- /**
916
- * 检查主包大小是否超出限制, 仅对非unity工程生效
917
- */
918
- if (!isUnityEngine(entryDir) && ((_a = config.build) === null || _a === void 0 ? void 0 : _a.mainPkgSizeLimit)) {
1036
+ if ((_a = getCheckConfig(config)) === null || _a === void 0 ? void 0 : _a.mainPkgSizeLimit) {
919
1037
  checkResults.push(checkMainPackageSize({
920
1038
  entryDir,
921
1039
  config,
@@ -1014,7 +1132,6 @@ function checkIndependentPackages(entryDir, config) {
1014
1132
  logger.info('start check independent subpackages in game.json');
1015
1133
  const independentSubpackages = getIndependentPackagesConfig(entryDir);
1016
1134
  independentSubpackages.forEach(sub => {
1017
- var _a;
1018
1135
  /**
1019
1136
  * 校验 independent subpackage 配置是否为空
1020
1137
  */
@@ -1061,8 +1178,8 @@ function checkIndependentPackages(entryDir, config) {
1061
1178
  /**
1062
1179
  * 校验 independent subpackage 包大小,不做校验
1063
1180
  */
1064
- const independentSubPkgSizeLimit = (_a = config === null || config === void 0 ? void 0 : config.build) === null || _a === void 0 ? void 0 : _a.independentSubPkgSizeLimit;
1065
- if (!isUnityEngine(entryDir) && independentSubPkgSizeLimit) {
1181
+ const { independentSubPkgSizeLimit } = getCheckConfig(config);
1182
+ if (independentSubPkgSizeLimit) {
1066
1183
  const checkSizeResult = checkPkgSize({
1067
1184
  entryDir: subpackageEntryDir,
1068
1185
  pkgName: sub.name,
@@ -1090,7 +1207,6 @@ function checkPkgSize({ entryDir, limit, pkgName, dimension, }) {
1090
1207
  const size = getDirSizeSync({
1091
1208
  rootDir: entryDir,
1092
1209
  entryDir,
1093
- filterDirs: [],
1094
1210
  });
1095
1211
  const passed = size <= limit;
1096
1212
  const sizeMB = size / 1024 / 1024;
@@ -1274,6 +1390,8 @@ async function setup(entryDir, outputDir) {
1274
1390
  root: '',
1275
1391
  main: 'game.js',
1276
1392
  output: `${GAME_MAIN_PACKAGE_NAME}${STTPKG_EXT}`,
1393
+ type: 'game',
1394
+ dependencies: []
1277
1395
  };
1278
1396
  transformSubPackages(gameJson.subpackages).forEach(({ name, root, main, independent }) => {
1279
1397
  // root 是目录
@@ -1284,6 +1402,8 @@ async function setup(entryDir, outputDir) {
1284
1402
  main,
1285
1403
  output: `${name}${STTPKG_EXT}`,
1286
1404
  independent,
1405
+ type: 'game',
1406
+ dependencies: []
1287
1407
  };
1288
1408
  if (independent) {
1289
1409
  fyfPackages.push({
@@ -1431,6 +1551,27 @@ function collectMaps(entryDir, packages) {
1431
1551
  logger.info('基于游戏源代码收集分包中的 JS 文件完成');
1432
1552
  return result;
1433
1553
  }
1554
+ function collectPluginMaps(gameDir, entryDir, name) {
1555
+ logger.info('开始基于插件源代码收集分包中的 JS 文件');
1556
+ const result = {};
1557
+ let jsFiles;
1558
+ jsFiles = collectPkgJsFiles({
1559
+ entry: entryDir,
1560
+ root: entryDir,
1561
+ exts: ['.js', '.ts', '.jsx', '.tsx'],
1562
+ excludeDirs: [],
1563
+ });
1564
+ const list = [];
1565
+ jsFiles.forEach(absPath => {
1566
+ list.push(path.relative(gameDir, absPath).replace(/\\/g, '/'));
1567
+ });
1568
+ const packNameRoot = name;
1569
+ result[name] = {
1570
+ [`${packNameRoot}/game.pack.js`]: list,
1571
+ };
1572
+ logger.info('基于插件源代码收集分包中的 JS 文件完成');
1573
+ return result;
1574
+ }
1434
1575
 
1435
1576
  function collectDeps(gameEntry, packages) {
1436
1577
  // 自动扫描 game 目录下的一级文件夹作为根前缀
@@ -1483,6 +1624,7 @@ function collectDeps(gameEntry, packages) {
1483
1624
  }
1484
1625
  requireCalls.push({
1485
1626
  file: path.relative(gameEntry, fullPath),
1627
+ type: 'game',
1486
1628
  callee: node.callee.name,
1487
1629
  requirePath,
1488
1630
  resolvedPath: resolvedPath
@@ -1490,6 +1632,20 @@ function collectDeps(gameEntry, packages) {
1490
1632
  : '',
1491
1633
  });
1492
1634
  }
1635
+ else if (node.type === 'CallExpression' &&
1636
+ node.callee.type === 'Identifier' &&
1637
+ node.callee.name === 'requirePlugin' &&
1638
+ node.arguments.length > 0 &&
1639
+ node.arguments[0].type === 'Literal') {
1640
+ const requirePath = node.arguments[0].value;
1641
+ requireCalls.push({
1642
+ file: path.relative(gameEntry, fullPath),
1643
+ type: 'plugin',
1644
+ callee: node.callee.name,
1645
+ requirePath,
1646
+ resolvedPath: requirePath,
1647
+ });
1648
+ }
1493
1649
  });
1494
1650
  });
1495
1651
  const newRequireCalls = requireCalls.map(i => {
@@ -1497,11 +1653,16 @@ function collectDeps(gameEntry, packages) {
1497
1653
  * 基于 packages 来判断
1498
1654
  */
1499
1655
  let depModule = '';
1500
- Object.keys(packages).forEach(pkg => {
1501
- if (i.resolvedPath && i.resolvedPath.startsWith(packages[pkg].root)) {
1502
- depModule = pkg;
1503
- }
1504
- });
1656
+ if (i.type === 'plugin') {
1657
+ depModule = i.requirePath.split('/')[0];
1658
+ }
1659
+ else if (i.type === 'game') {
1660
+ Object.keys(packages).forEach(pkg => {
1661
+ if (i.resolvedPath && i.resolvedPath.startsWith(packages[pkg].root)) {
1662
+ depModule = pkg;
1663
+ }
1664
+ });
1665
+ }
1505
1666
  let curModule = '';
1506
1667
  Object.keys(packages).forEach(pkg => {
1507
1668
  if (i.file.startsWith(packages[pkg].root)) {
@@ -1515,17 +1676,26 @@ function collectDeps(gameEntry, packages) {
1515
1676
  const exts = ['.js', '.ts', '.jsx', '.tsx'];
1516
1677
  const isJsLike = f => exts.some(ext => f.endsWith(ext));
1517
1678
  const result = newRequireCalls.reduce((acc, cur) => {
1518
- if (!acc[cur.curModule]) {
1519
- acc[cur.curModule] = {};
1679
+ if (!acc.packageDeps[cur.curModule]) {
1680
+ acc.packageDeps[cur.curModule] = {};
1681
+ }
1682
+ if (!acc.pluginDeps[cur.curModule]) {
1683
+ acc.pluginDeps[cur.curModule] = [];
1520
1684
  }
1521
1685
  if (cur.curModule === cur.depModule) {
1522
1686
  return acc;
1523
1687
  }
1524
- if (cur.resolvedPath && isJsLike(cur.resolvedPath)) {
1525
- acc[cur.curModule] = Object.assign(Object.assign({}, acc[cur.curModule]), { [cur.resolvedPath]: cur.depModule });
1688
+ if (cur.resolvedPath && isJsLike(cur.resolvedPath) && cur.type === 'game') {
1689
+ acc.packageDeps[cur.curModule][cur.resolvedPath] = cur.depModule;
1690
+ }
1691
+ else if (cur.type === 'plugin') {
1692
+ acc.pluginDeps[cur.curModule].push(cur.depModule);
1526
1693
  }
1527
1694
  return acc;
1528
- }, {});
1695
+ }, {
1696
+ packageDeps: {},
1697
+ pluginDeps: {},
1698
+ });
1529
1699
  return result;
1530
1700
  }
1531
1701
  function getRootPrefixes(gameEntry) {
@@ -1571,7 +1741,7 @@ async function pack({ gameEntry, pkgName, allDeps, allMaps, pkgConfig, destRoot,
1571
1741
  const packMap = allMaps[pkgName];
1572
1742
  const moduleConfig = {
1573
1743
  main: pkgConfig.main,
1574
- deps: allDeps[pkgName] || {},
1744
+ deps: allDeps.packageDeps[pkgName] || {},
1575
1745
  map: allMaps[pkgName],
1576
1746
  };
1577
1747
  ensureDirSync(destRoot);
@@ -1622,7 +1792,8 @@ async function pack({ gameEntry, pkgName, allDeps, allMaps, pkgConfig, destRoot,
1622
1792
  // });
1623
1793
  // const code = fs.readFileSync(absPath, 'utf-8');
1624
1794
  const fileId = relPath.replace(/\\/g, '/');
1625
- const defineHeader = `define("game:${fileId}", ["require", "requireAsync", "module", "exports", "sandboxGlobal"], function(require, requireAsync, module, exports, sandboxGlobal){\nwith(sandboxGlobal){\n`;
1795
+ const schema = config.build.type === 'plugin' ? 'plugin' : 'game';
1796
+ const defineHeader = `define("${schema}:${fileId}", ["require", "requireAsync", "module", "exports", "sandboxGlobal"], function(require, requireAsync, module, exports, sandboxGlobal){\nwith(sandboxGlobal){\n`;
1626
1797
  const defineFooter = `\n}\n});\n`;
1627
1798
  const fileMagic = new MagicString(code, { filename: fileId });
1628
1799
  fileMagic.prepend(defineHeader);
@@ -1709,23 +1880,88 @@ async function partition({ pkgRoot, destRoot, packedFiles, gameEntry, gameOutput
1709
1880
  }
1710
1881
  }
1711
1882
 
1883
+ /**
1884
+ * 根据 TikTok DevPortal 接口获取插件包信息(url + md5)
1885
+ *
1886
+ * 对应 curl:
1887
+ * POST https://developers.tiktok.com/tiktok/v4/devportal/minigame/plugin_meta/get
1888
+ * body: { plugin_name, plugin_version }
1889
+ */
1890
+ async function getPluginInfo(params) {
1891
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1892
+ const baseUrl = (_a = params.baseUrl) !== null && _a !== void 0 ? _a : 'https://developers.tiktok.com';
1893
+ const url = `${baseUrl}/tiktok/v4/devportal/minigame/plugin_meta/get`;
1894
+ const res = await fetch(url, {
1895
+ method: 'POST',
1896
+ headers: Object.assign({ 'Content-Type': 'application/json' }, ((_b = params.headers) !== null && _b !== void 0 ? _b : {})),
1897
+ body: JSON.stringify({
1898
+ plugin_name: params.plugin_name,
1899
+ plugin_version: params.plugin_version,
1900
+ }),
1901
+ });
1902
+ const json = await res.json();
1903
+ const meta = (_g = (_e = (_c = json === null || json === void 0 ? void 0 : json.PluginMeta) !== null && _c !== void 0 ? _c : (_d = json === null || json === void 0 ? void 0 : json.data) === null || _d === void 0 ? void 0 : _d.PluginMeta) !== null && _e !== void 0 ? _e : (_f = json === null || json === void 0 ? void 0 : json.result) === null || _f === void 0 ? void 0 : _f.PluginMeta) !== null && _g !== void 0 ? _g : (_j = (_h = json === null || json === void 0 ? void 0 : json.data) === null || _h === void 0 ? void 0 : _h.result) === null || _j === void 0 ? void 0 : _j.PluginMeta;
1904
+ if (!(meta === null || meta === void 0 ? void 0 : meta.PackageCDNURL) || !(meta === null || meta === void 0 ? void 0 : meta.PackageMD5)) {
1905
+ throw new Error(`[ttmg-pack] plugin get error: ${JSON.stringify(json)}`);
1906
+ }
1907
+ return { url: meta.PackageCDNURL, md5: meta.PackageMD5 };
1908
+ }
1909
+ async function addDepsToPackages(gameEntry, outputDir, allDeps) {
1910
+ const gamePackConfigPath = path.join(outputDir, GAME_PACK_CONFIG_FILE_NAME);
1911
+ const gameJsonPath = path.join(gameEntry, GAME_ORIGIN_CONFIG_FILE_NAME);
1912
+ const gamePackConfig = JSON.parse(fs.readFileSync(gamePackConfigPath, 'utf-8'));
1913
+ const gameJson = JSON.parse(fs.readFileSync(gameJsonPath, 'utf-8'));
1914
+ const pluginConfig = gameJson.plugins;
1915
+ for (const [pkgName, plugins] of Object.entries(allDeps)) {
1916
+ for (const pluginName of plugins) {
1917
+ if (!(pluginConfig === null || pluginConfig === void 0 ? void 0 : pluginConfig[pluginName]))
1918
+ continue;
1919
+ const version = pluginConfig[pluginName].version;
1920
+ const pluginPkgName = `${pluginName}_${version}`;
1921
+ const { url, md5 } = await getPluginInfo({
1922
+ plugin_name: pluginName.toLowerCase(),
1923
+ plugin_version: version,
1924
+ headers: {
1925
+ 'x-tt-env': 'ppe_unity_plugin_meta',
1926
+ 'x-use-ppe': '1',
1927
+ },
1928
+ });
1929
+ gamePackConfig.packages[pkgName].dependencies.push(pluginPkgName);
1930
+ if (!gamePackConfig.packages[pluginPkgName]) {
1931
+ gamePackConfig.packages[pluginPkgName] = {
1932
+ url,
1933
+ md5,
1934
+ root: '',
1935
+ main: '',
1936
+ output: `${pluginPkgName}${STTPKG_EXT}`,
1937
+ independent: false,
1938
+ type: 'plugin',
1939
+ dependencies: [],
1940
+ };
1941
+ }
1942
+ else {
1943
+ // 如果已存在,也覆盖 url/md5,保证最新
1944
+ gamePackConfig.packages[pluginPkgName].url = url;
1945
+ gamePackConfig.packages[pluginPkgName].md5 = md5;
1946
+ }
1947
+ }
1948
+ }
1949
+ fs.writeFileSync(gamePackConfigPath, JSON.stringify(gamePackConfig, null, 2));
1950
+ }
1951
+
1712
1952
  function getSkipDirs({ packages, entryDir, }) {
1713
1953
  const skipDirs = [];
1714
1954
  if (packages) {
1715
1955
  skipDirs.push(...Object.values(packages).map(item => item.root));
1716
1956
  }
1717
- // const { entry: openDataContextEntry } = getOpenDataContextEntry({ entryDir });
1718
- // if (openDataContextEntry) {
1719
- // skipDirs.push(openDataContextEntry);
1720
- // }
1721
1957
  return skipDirs;
1722
1958
  }
1723
1959
  async function makePkgs({ gameEntry, gameOutput, config, }) {
1724
1960
  let startTime = Date.now();
1725
- removeSystemUseless(gameEntry);
1726
1961
  logger.info(`pack start,startTime:${startTime}`);
1727
1962
  const { packages } = await setup(gameEntry, gameOutput);
1728
1963
  const allDeps = collectDeps(gameEntry, packages);
1964
+ await addDepsToPackages(gameEntry, gameOutput, allDeps.pluginDeps);
1729
1965
  const allMaps = collectMaps(gameEntry, packages);
1730
1966
  const pkgOutput = {};
1731
1967
  /**
@@ -1775,6 +2011,46 @@ async function makePkgs({ gameEntry, gameOutput, config, }) {
1775
2011
  logger.info(`pack end,duration:${Date.now() - startTime}ms`);
1776
2012
  return pkgOutput;
1777
2013
  }
2014
+ async function makePlugin({ gameEntry, gameOutput, config, }) {
2015
+ try {
2016
+ const pluginConfig = fs.readFileSync(path.join(gameEntry, 'plugin.json'), 'utf-8');
2017
+ const { name, main, version } = JSON.parse(pluginConfig);
2018
+ const pluginName = `${name}_${version}`;
2019
+ // 把 entryDir 下的所有文件复制到 entryDir/plugin
2020
+ const pluginDir = path.join(gameEntry, pluginName);
2021
+ copyDirectory(gameEntry, pluginDir);
2022
+ const packages = {
2023
+ [pluginName]: {
2024
+ url: '',
2025
+ md5: '',
2026
+ root: pluginName,
2027
+ main,
2028
+ output: `${pluginName}${STTPKG_EXT}`,
2029
+ type: 'game',
2030
+ dependencies: []
2031
+ }
2032
+ };
2033
+ fs.writeFileSync(path.join(gameOutput, GAME_PACK_CONFIG_FILE_NAME), JSON.stringify({ packages }, null, 2));
2034
+ const pluginEntry = path.join(gameEntry, pluginName);
2035
+ const allMaps = collectPluginMaps(gameEntry, pluginEntry, pluginName);
2036
+ await pack({
2037
+ gameEntry,
2038
+ allDeps: {
2039
+ packageDeps: {},
2040
+ pluginDeps: {},
2041
+ },
2042
+ allMaps,
2043
+ pkgName: pluginName,
2044
+ pkgConfig: packages[pluginName],
2045
+ destRoot: path.join(gameOutput, pluginName, packages[pluginName].root),
2046
+ config,
2047
+ });
2048
+ }
2049
+ catch (error) {
2050
+ logger.error('plugin.json 不存在');
2051
+ return;
2052
+ }
2053
+ }
1778
2054
 
1779
2055
  /**
1780
2056
  * 将 packages 中的每个 package 下的文件内容合并到 outputDir 下,不保留 package name
@@ -1846,10 +2122,18 @@ async function mergePkgs(outputDir) {
1846
2122
  logger.info('mergePkgs 完成');
1847
2123
  }
1848
2124
 
2125
+ async function clearPkgs(entryDir, uselessDirs = ['node_modules', '__MACOSX']) {
2126
+ removeSystemUseless(entryDir, uselessDirs);
2127
+ }
2128
+
1849
2129
  async function debugPkgs(config) {
1850
2130
  const { entry: entryDir, output: outputDir, dev: { enableLog }, } = config;
1851
2131
  logger.init(outputDir, enableLog);
1852
2132
  try {
2133
+ /**
2134
+ * 清理
2135
+ */
2136
+ clearPkgs(entryDir);
1853
2137
  /**
1854
2138
  * 校验
1855
2139
  */
@@ -1888,13 +2172,21 @@ async function debugPkgs(config) {
1888
2172
  }
1889
2173
  }
1890
2174
 
1891
- async function buildPkgs(originConfig) {
1892
- const { entry: entryDir, output: outputDir, build } = originConfig;
2175
+ async function buildPkgs(config) {
2176
+ var _a;
2177
+ if (((_a = config === null || config === void 0 ? void 0 : config.build) === null || _a === void 0 ? void 0 : _a.type) === 'plugin') {
2178
+ await buildPlugin(config);
2179
+ return;
2180
+ }
2181
+ const { entry: entryDir, output: outputDir, build } = config;
1893
2182
  let startTime = Date.now();
1894
2183
  logger.init(outputDir, true);
1895
- logger.info(`TTMG_PACK_VERSION: ${"0.2.8-unity.1"}`);
2184
+ logger.info(`TTMG_PACK_VERSION: ${"0.2.9-beta.1"}`);
1896
2185
  logger.info(`pack start, startTime:${startTime}`);
1897
- const config = overrideConfig(entryDir, originConfig);
2186
+ /**
2187
+ * 清理
2188
+ */
2189
+ clearPkgs(entryDir, ['node_modules', '__MACOSX', TTMG_TEMP_DIR]);
1898
2190
  /**
1899
2191
  * 校验
1900
2192
  */
@@ -1921,6 +2213,31 @@ async function buildPkgs(originConfig) {
1921
2213
  }
1922
2214
  logger.info(`pack end:${Date.now() - startTime}ms`);
1923
2215
  }
2216
+ async function buildPlugin(originConfig) {
2217
+ const { entry: entryDir, output: outputDir, build } = originConfig;
2218
+ let startTime = Date.now();
2219
+ logger.init(outputDir, true);
2220
+ logger.info(`TTMG_PACK_VERSION: ${"0.2.9-beta.1"}`);
2221
+ logger.info(`pack start, startTime:${startTime}`);
2222
+ /**
2223
+ * 打包
2224
+ */
2225
+ await makePlugin({
2226
+ config: originConfig,
2227
+ gameEntry: entryDir,
2228
+ gameOutput: outputDir,
2229
+ });
2230
+ if (build === null || build === void 0 ? void 0 : build.enableOdr) {
2231
+ /**
2232
+ * 等待文件全部读写完成后
2233
+ */
2234
+ await makeOdrPkgs(outputDir);
2235
+ /**
2236
+ * 分拆 odr 包
2237
+ */
2238
+ }
2239
+ logger.info(`pack end:${Date.now() - startTime}ms`);
2240
+ }
1924
2241
 
1925
2242
  async function getPkgs({ entryDir, }) {
1926
2243
  const independentPackages = getIndependentPackagesConfig(entryDir);
@@ -1939,7 +2256,6 @@ async function getPkgs({ entryDir, }) {
1939
2256
  size: getDirSizeSync({
1940
2257
  rootDir: entryDir,
1941
2258
  entryDir: path.join(entryDir, sub.root),
1942
- filterDirs: [],
1943
2259
  }),
1944
2260
  name: sub.name,
1945
2261
  root: sub.root,
@@ -1964,6 +2280,7 @@ async function getPkgs({ entryDir, }) {
1964
2280
  };
1965
2281
  }
1966
2282
 
2283
+ exports.TTMG_TEMP_DIR = TTMG_TEMP_DIR;
1967
2284
  exports.buildOdrPkgs = buildOdrPkgs;
1968
2285
  exports.buildPkgs = buildPkgs;
1969
2286
  exports.checkPkgs = checkPkgs;