ttmg-pack 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * ==========================================
3
3
  * @Description: ttmg pack
4
- * @Version: 0.3.1
4
+ * @Version: 0.3.2
5
5
  * @Author: zhanghongyang.mocha
6
- * @Date: 2026-01-26 14:54:36
6
+ * @Date: 2026-01-30 16:40:00
7
7
  * ==========================================
8
8
  */
9
9
  'use strict';
@@ -484,17 +484,6 @@ async function safeBuild(options) {
484
484
  return esbuild.build(options);
485
485
  }
486
486
 
487
- const bareToRelativePlugin = {
488
- name: 'bare-to-relative',
489
- setup(build) {
490
- // 匹配不以 . 或 / 开头的 .js 文件路径
491
- build.onResolve({ filter: /^[^./].*\.js$/ }, args => {
492
- return {
493
- path: path.resolve(args.resolveDir, args.path),
494
- };
495
- });
496
- },
497
- };
498
487
  /**
499
488
  *
500
489
  * @param entryDir 游戏项目根目录
@@ -565,7 +554,6 @@ async function genOpenDataContext(entryPath) {
565
554
  '.svg': 'dataurl',
566
555
  '.webp': 'dataurl',
567
556
  },
568
- plugins: [bareToRelativePlugin]
569
557
  });
570
558
  const jsCode = ((_b = (_a = result === null || result === void 0 ? void 0 : result.outputFiles) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.text) || '';
571
559
  return jsCode;
@@ -629,6 +617,40 @@ function getCheckConfig(config) {
629
617
  }
630
618
  }
631
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
+
632
654
  /**
633
655
  * 将 packageConfig packages 中 分拆的包 配置迁移到 odr_packages 中
634
656
  */
@@ -648,7 +670,7 @@ async function writeOdrConfig(outputDir) {
648
670
  if (!odrPackages[relatedPkgName]) {
649
671
  odrPackages[relatedPkgName] = {};
650
672
  }
651
- 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 });
652
674
  delete packages[pkgName];
653
675
  }
654
676
  if (pkgName.endsWith(ODR_ASSET_DIR_SUFFIX)) {
@@ -677,12 +699,14 @@ async function makeOdrPkgs(outputDir) {
677
699
  const pkgOutput = path.join(outputDir, pkgName);
678
700
  const codeOutput = path.join(outputDir, `${pkgName}${ODR_CODE_DIR_SUFFIX}`);
679
701
  const assetOutput = path.join(outputDir, `${pkgName}${ODR_ASSET_DIR_SUFFIX}`);
680
- ensureDirSync(codeOutput);
681
- ensureDirSync(assetOutput);
682
- fs.cpSync(pkgOutput, codeOutput, { recursive: true });
683
- fs.cpSync(pkgOutput, assetOutput, { recursive: true });
684
- deleteNoJsFilesSync(codeOutput);
685
- 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
+ }
686
710
  /**
687
711
  * 写入配置
688
712
  */
@@ -1366,6 +1390,8 @@ async function setup(entryDir, outputDir) {
1366
1390
  root: '',
1367
1391
  main: 'game.js',
1368
1392
  output: `${GAME_MAIN_PACKAGE_NAME}${STTPKG_EXT}`,
1393
+ type: 'game',
1394
+ dependencies: []
1369
1395
  };
1370
1396
  transformSubPackages(gameJson.subpackages).forEach(({ name, root, main, independent }) => {
1371
1397
  // root 是目录
@@ -1376,6 +1402,8 @@ async function setup(entryDir, outputDir) {
1376
1402
  main,
1377
1403
  output: `${name}${STTPKG_EXT}`,
1378
1404
  independent,
1405
+ type: 'game',
1406
+ dependencies: []
1379
1407
  };
1380
1408
  if (independent) {
1381
1409
  fyfPackages.push({
@@ -1523,6 +1551,27 @@ function collectMaps(entryDir, packages) {
1523
1551
  logger.info('基于游戏源代码收集分包中的 JS 文件完成');
1524
1552
  return result;
1525
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
+ }
1526
1575
 
1527
1576
  function collectDeps(gameEntry, packages) {
1528
1577
  // 自动扫描 game 目录下的一级文件夹作为根前缀
@@ -1575,6 +1624,7 @@ function collectDeps(gameEntry, packages) {
1575
1624
  }
1576
1625
  requireCalls.push({
1577
1626
  file: path.relative(gameEntry, fullPath),
1627
+ type: 'game',
1578
1628
  callee: node.callee.name,
1579
1629
  requirePath,
1580
1630
  resolvedPath: resolvedPath
@@ -1582,6 +1632,20 @@ function collectDeps(gameEntry, packages) {
1582
1632
  : '',
1583
1633
  });
1584
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
+ }
1585
1649
  });
1586
1650
  });
1587
1651
  const newRequireCalls = requireCalls.map(i => {
@@ -1589,11 +1653,16 @@ function collectDeps(gameEntry, packages) {
1589
1653
  * 基于 packages 来判断
1590
1654
  */
1591
1655
  let depModule = '';
1592
- Object.keys(packages).forEach(pkg => {
1593
- if (i.resolvedPath && i.resolvedPath.startsWith(packages[pkg].root)) {
1594
- depModule = pkg;
1595
- }
1596
- });
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
+ }
1597
1666
  let curModule = '';
1598
1667
  Object.keys(packages).forEach(pkg => {
1599
1668
  if (i.file.startsWith(packages[pkg].root)) {
@@ -1607,17 +1676,26 @@ function collectDeps(gameEntry, packages) {
1607
1676
  const exts = ['.js', '.ts', '.jsx', '.tsx'];
1608
1677
  const isJsLike = f => exts.some(ext => f.endsWith(ext));
1609
1678
  const result = newRequireCalls.reduce((acc, cur) => {
1610
- if (!acc[cur.curModule]) {
1611
- 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] = [];
1612
1684
  }
1613
1685
  if (cur.curModule === cur.depModule) {
1614
1686
  return acc;
1615
1687
  }
1616
- if (cur.resolvedPath && isJsLike(cur.resolvedPath)) {
1617
- 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);
1618
1693
  }
1619
1694
  return acc;
1620
- }, {});
1695
+ }, {
1696
+ packageDeps: {},
1697
+ pluginDeps: {},
1698
+ });
1621
1699
  return result;
1622
1700
  }
1623
1701
  function getRootPrefixes(gameEntry) {
@@ -1663,7 +1741,7 @@ async function pack({ gameEntry, pkgName, allDeps, allMaps, pkgConfig, destRoot,
1663
1741
  const packMap = allMaps[pkgName];
1664
1742
  const moduleConfig = {
1665
1743
  main: pkgConfig.main,
1666
- deps: allDeps[pkgName] || {},
1744
+ deps: allDeps.packageDeps[pkgName] || {},
1667
1745
  map: allMaps[pkgName],
1668
1746
  };
1669
1747
  ensureDirSync(destRoot);
@@ -1714,7 +1792,8 @@ async function pack({ gameEntry, pkgName, allDeps, allMaps, pkgConfig, destRoot,
1714
1792
  // });
1715
1793
  // const code = fs.readFileSync(absPath, 'utf-8');
1716
1794
  const fileId = relPath.replace(/\\/g, '/');
1717
- 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`;
1718
1797
  const defineFooter = `\n}\n});\n`;
1719
1798
  const fileMagic = new MagicString(code, { filename: fileId });
1720
1799
  fileMagic.prepend(defineHeader);
@@ -1801,6 +1880,82 @@ async function partition({ pkgRoot, destRoot, packedFiles, gameEntry, gameOutput
1801
1880
  }
1802
1881
  }
1803
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
+ const unityDeps = ['data-package', 'StreamingAssets', 'wasmcode', 'wasmcode-android', 'wasmcode-ios'];
1916
+ for (const dep of unityDeps) {
1917
+ // 如果 game.json 的 subpackages 里有这个包,则添加进 __GAME__ 的 dependencies
1918
+ if (gameJson.subpackages.some(pkg => pkg.name === dep)) {
1919
+ gamePackConfig.packages[GAME_MAIN_PACKAGE_NAME].dependencies.push(dep);
1920
+ }
1921
+ }
1922
+ for (const [pkgName, plugins] of Object.entries(allDeps)) {
1923
+ for (const pluginName of plugins) {
1924
+ if (!(pluginConfig === null || pluginConfig === void 0 ? void 0 : pluginConfig[pluginName]))
1925
+ continue;
1926
+ const version = pluginConfig[pluginName].version;
1927
+ const pluginPkgName = `${pluginName}_${version}`;
1928
+ const { url, md5 } = await getPluginInfo({
1929
+ plugin_name: pluginName.toLowerCase(),
1930
+ plugin_version: version,
1931
+ headers: {
1932
+ 'x-tt-env': 'ppe_unity_plugin_meta',
1933
+ 'x-use-ppe': '1',
1934
+ },
1935
+ });
1936
+ gamePackConfig.packages[pkgName].dependencies.push(pluginPkgName);
1937
+ if (!gamePackConfig.packages[pluginPkgName]) {
1938
+ gamePackConfig.packages[pluginPkgName] = {
1939
+ url,
1940
+ md5,
1941
+ root: pluginPkgName,
1942
+ main: `${pluginPkgName}/index.js`,
1943
+ output: `${pluginPkgName}${STTPKG_EXT}`,
1944
+ independent: false,
1945
+ type: 'plugin',
1946
+ dependencies: [],
1947
+ };
1948
+ }
1949
+ else {
1950
+ // 如果已存在,也覆盖 url/md5,保证最新
1951
+ gamePackConfig.packages[pluginPkgName].url = url;
1952
+ gamePackConfig.packages[pluginPkgName].md5 = md5;
1953
+ }
1954
+ }
1955
+ }
1956
+ fs.writeFileSync(gamePackConfigPath, JSON.stringify(gamePackConfig, null, 2));
1957
+ }
1958
+
1804
1959
  function getSkipDirs({ packages, entryDir, }) {
1805
1960
  const skipDirs = [];
1806
1961
  if (packages) {
@@ -1813,6 +1968,7 @@ async function makePkgs({ gameEntry, gameOutput, config, }) {
1813
1968
  logger.info(`pack start,startTime:${startTime}`);
1814
1969
  const { packages } = await setup(gameEntry, gameOutput);
1815
1970
  const allDeps = collectDeps(gameEntry, packages);
1971
+ await addDepsToPackages(gameEntry, gameOutput, allDeps.pluginDeps);
1816
1972
  const allMaps = collectMaps(gameEntry, packages);
1817
1973
  const pkgOutput = {};
1818
1974
  /**
@@ -1862,6 +2018,46 @@ async function makePkgs({ gameEntry, gameOutput, config, }) {
1862
2018
  logger.info(`pack end,duration:${Date.now() - startTime}ms`);
1863
2019
  return pkgOutput;
1864
2020
  }
2021
+ async function makePlugin({ gameEntry, gameOutput, config, }) {
2022
+ try {
2023
+ const pluginConfig = fs.readFileSync(path.join(gameEntry, 'plugin.json'), 'utf-8');
2024
+ const { name, main, version } = JSON.parse(pluginConfig);
2025
+ const pluginName = `${name}_${version}`;
2026
+ // 把 entryDir 下的所有文件复制到 entryDir/plugin
2027
+ const pluginDir = path.join(gameEntry, pluginName);
2028
+ copyDirectory(gameEntry, pluginDir);
2029
+ const packages = {
2030
+ [pluginName]: {
2031
+ url: '',
2032
+ md5: '',
2033
+ root: pluginName,
2034
+ main,
2035
+ output: `${pluginName}${STTPKG_EXT}`,
2036
+ type: 'game',
2037
+ dependencies: []
2038
+ }
2039
+ };
2040
+ fs.writeFileSync(path.join(gameOutput, GAME_PACK_CONFIG_FILE_NAME), JSON.stringify({ packages }, null, 2));
2041
+ const pluginEntry = path.join(gameEntry, pluginName);
2042
+ const allMaps = collectPluginMaps(gameEntry, pluginEntry, pluginName);
2043
+ await pack({
2044
+ gameEntry,
2045
+ allDeps: {
2046
+ packageDeps: {},
2047
+ pluginDeps: {},
2048
+ },
2049
+ allMaps,
2050
+ pkgName: pluginName,
2051
+ pkgConfig: packages[pluginName],
2052
+ destRoot: path.join(gameOutput, pluginName, packages[pluginName].root),
2053
+ config,
2054
+ });
2055
+ }
2056
+ catch (error) {
2057
+ logger.error('plugin.json 不存在');
2058
+ return;
2059
+ }
2060
+ }
1865
2061
 
1866
2062
  /**
1867
2063
  * 将 packages 中的每个 package 下的文件内容合并到 outputDir 下,不保留 package name
@@ -1984,10 +2180,15 @@ async function debugPkgs(config) {
1984
2180
  }
1985
2181
 
1986
2182
  async function buildPkgs(config) {
2183
+ var _a;
2184
+ if (((_a = config === null || config === void 0 ? void 0 : config.build) === null || _a === void 0 ? void 0 : _a.type) === 'plugin') {
2185
+ await buildPlugin(config);
2186
+ return;
2187
+ }
1987
2188
  const { entry: entryDir, output: outputDir, build } = config;
1988
2189
  let startTime = Date.now();
1989
2190
  logger.init(outputDir, true);
1990
- logger.info(`TTMG_PACK_VERSION: ${"0.3.1"}`);
2191
+ logger.info(`TTMG_PACK_VERSION: ${"0.3.2"}`);
1991
2192
  logger.info(`pack start, startTime:${startTime}`);
1992
2193
  /**
1993
2194
  * 清理
@@ -2019,6 +2220,31 @@ async function buildPkgs(config) {
2019
2220
  }
2020
2221
  logger.info(`pack end:${Date.now() - startTime}ms`);
2021
2222
  }
2223
+ async function buildPlugin(originConfig) {
2224
+ const { entry: entryDir, output: outputDir, build } = originConfig;
2225
+ let startTime = Date.now();
2226
+ logger.init(outputDir, true);
2227
+ logger.info(`TTMG_PACK_VERSION: ${"0.3.2"}`);
2228
+ logger.info(`pack start, startTime:${startTime}`);
2229
+ /**
2230
+ * 打包
2231
+ */
2232
+ await makePlugin({
2233
+ config: originConfig,
2234
+ gameEntry: entryDir,
2235
+ gameOutput: outputDir,
2236
+ });
2237
+ if (build === null || build === void 0 ? void 0 : build.enableOdr) {
2238
+ /**
2239
+ * 等待文件全部读写完成后
2240
+ */
2241
+ await makeOdrPkgs(outputDir);
2242
+ /**
2243
+ * 分拆 odr 包
2244
+ */
2245
+ }
2246
+ logger.info(`pack end:${Date.now() - startTime}ms`);
2247
+ }
2022
2248
 
2023
2249
  async function getPkgs({ entryDir, }) {
2024
2250
  const independentPackages = getIndependentPackagesConfig(entryDir);