ttmg-pack 0.2.9 → 0.3.0

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.9
4
+ * @Version: 0.3.0
5
5
  * @Author: zhanghongyang.mocha
6
- * @Date: 2026-01-19 15:29:08
6
+ * @Date: 2026-01-22 19:41:56
7
7
  * ==========================================
8
8
  */
9
9
  'use strict';
@@ -617,6 +617,40 @@ function getCheckConfig(config) {
617
617
  }
618
618
  }
619
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
+
620
654
  /**
621
655
  * 将 packageConfig packages 中 分拆的包 配置迁移到 odr_packages 中
622
656
  */
@@ -636,7 +670,7 @@ async function writeOdrConfig(outputDir) {
636
670
  if (!odrPackages[relatedPkgName]) {
637
671
  odrPackages[relatedPkgName] = {};
638
672
  }
639
- 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 });
640
674
  delete packages[pkgName];
641
675
  }
642
676
  if (pkgName.endsWith(ODR_ASSET_DIR_SUFFIX)) {
@@ -665,12 +699,14 @@ async function makeOdrPkgs(outputDir) {
665
699
  const pkgOutput = path.join(outputDir, pkgName);
666
700
  const codeOutput = path.join(outputDir, `${pkgName}${ODR_CODE_DIR_SUFFIX}`);
667
701
  const assetOutput = path.join(outputDir, `${pkgName}${ODR_ASSET_DIR_SUFFIX}`);
668
- ensureDirSync(codeOutput);
669
- ensureDirSync(assetOutput);
670
- fs.cpSync(pkgOutput, codeOutput, { recursive: true });
671
- fs.cpSync(pkgOutput, assetOutput, { recursive: true });
672
- deleteNoJsFilesSync(codeOutput);
673
- 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
+ }
674
710
  /**
675
711
  * 写入配置
676
712
  */
@@ -1354,6 +1390,8 @@ async function setup(entryDir, outputDir) {
1354
1390
  root: '',
1355
1391
  main: 'game.js',
1356
1392
  output: `${GAME_MAIN_PACKAGE_NAME}${STTPKG_EXT}`,
1393
+ type: 'game',
1394
+ dependencies: []
1357
1395
  };
1358
1396
  transformSubPackages(gameJson.subpackages).forEach(({ name, root, main, independent }) => {
1359
1397
  // root 是目录
@@ -1364,6 +1402,8 @@ async function setup(entryDir, outputDir) {
1364
1402
  main,
1365
1403
  output: `${name}${STTPKG_EXT}`,
1366
1404
  independent,
1405
+ type: 'game',
1406
+ dependencies: []
1367
1407
  };
1368
1408
  if (independent) {
1369
1409
  fyfPackages.push({
@@ -1511,6 +1551,27 @@ function collectMaps(entryDir, packages) {
1511
1551
  logger.info('基于游戏源代码收集分包中的 JS 文件完成');
1512
1552
  return result;
1513
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
+ }
1514
1575
 
1515
1576
  function collectDeps(gameEntry, packages) {
1516
1577
  // 自动扫描 game 目录下的一级文件夹作为根前缀
@@ -1563,6 +1624,7 @@ function collectDeps(gameEntry, packages) {
1563
1624
  }
1564
1625
  requireCalls.push({
1565
1626
  file: path.relative(gameEntry, fullPath),
1627
+ type: 'game',
1566
1628
  callee: node.callee.name,
1567
1629
  requirePath,
1568
1630
  resolvedPath: resolvedPath
@@ -1570,6 +1632,20 @@ function collectDeps(gameEntry, packages) {
1570
1632
  : '',
1571
1633
  });
1572
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
+ }
1573
1649
  });
1574
1650
  });
1575
1651
  const newRequireCalls = requireCalls.map(i => {
@@ -1577,11 +1653,16 @@ function collectDeps(gameEntry, packages) {
1577
1653
  * 基于 packages 来判断
1578
1654
  */
1579
1655
  let depModule = '';
1580
- Object.keys(packages).forEach(pkg => {
1581
- if (i.resolvedPath && i.resolvedPath.startsWith(packages[pkg].root)) {
1582
- depModule = pkg;
1583
- }
1584
- });
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
+ }
1585
1666
  let curModule = '';
1586
1667
  Object.keys(packages).forEach(pkg => {
1587
1668
  if (i.file.startsWith(packages[pkg].root)) {
@@ -1595,17 +1676,26 @@ function collectDeps(gameEntry, packages) {
1595
1676
  const exts = ['.js', '.ts', '.jsx', '.tsx'];
1596
1677
  const isJsLike = f => exts.some(ext => f.endsWith(ext));
1597
1678
  const result = newRequireCalls.reduce((acc, cur) => {
1598
- if (!acc[cur.curModule]) {
1599
- 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] = [];
1600
1684
  }
1601
1685
  if (cur.curModule === cur.depModule) {
1602
1686
  return acc;
1603
1687
  }
1604
- if (cur.resolvedPath && isJsLike(cur.resolvedPath)) {
1605
- 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);
1606
1693
  }
1607
1694
  return acc;
1608
- }, {});
1695
+ }, {
1696
+ packageDeps: {},
1697
+ pluginDeps: {},
1698
+ });
1609
1699
  return result;
1610
1700
  }
1611
1701
  function getRootPrefixes(gameEntry) {
@@ -1651,7 +1741,7 @@ async function pack({ gameEntry, pkgName, allDeps, allMaps, pkgConfig, destRoot,
1651
1741
  const packMap = allMaps[pkgName];
1652
1742
  const moduleConfig = {
1653
1743
  main: pkgConfig.main,
1654
- deps: allDeps[pkgName] || {},
1744
+ deps: allDeps.packageDeps[pkgName] || {},
1655
1745
  map: allMaps[pkgName],
1656
1746
  };
1657
1747
  ensureDirSync(destRoot);
@@ -1702,7 +1792,8 @@ async function pack({ gameEntry, pkgName, allDeps, allMaps, pkgConfig, destRoot,
1702
1792
  // });
1703
1793
  // const code = fs.readFileSync(absPath, 'utf-8');
1704
1794
  const fileId = relPath.replace(/\\/g, '/');
1705
- 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`;
1706
1797
  const defineFooter = `\n}\n});\n`;
1707
1798
  const fileMagic = new MagicString(code, { filename: fileId });
1708
1799
  fileMagic.prepend(defineHeader);
@@ -1789,6 +1880,75 @@ async function partition({ pkgRoot, destRoot, packedFiles, gameEntry, gameOutput
1789
1880
  }
1790
1881
  }
1791
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: pluginPkgName,
1935
+ main: `${pluginPkgName}/index.js`,
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
+
1792
1952
  function getSkipDirs({ packages, entryDir, }) {
1793
1953
  const skipDirs = [];
1794
1954
  if (packages) {
@@ -1801,6 +1961,7 @@ async function makePkgs({ gameEntry, gameOutput, config, }) {
1801
1961
  logger.info(`pack start,startTime:${startTime}`);
1802
1962
  const { packages } = await setup(gameEntry, gameOutput);
1803
1963
  const allDeps = collectDeps(gameEntry, packages);
1964
+ await addDepsToPackages(gameEntry, gameOutput, allDeps.pluginDeps);
1804
1965
  const allMaps = collectMaps(gameEntry, packages);
1805
1966
  const pkgOutput = {};
1806
1967
  /**
@@ -1850,6 +2011,46 @@ async function makePkgs({ gameEntry, gameOutput, config, }) {
1850
2011
  logger.info(`pack end,duration:${Date.now() - startTime}ms`);
1851
2012
  return pkgOutput;
1852
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
+ }
1853
2054
 
1854
2055
  /**
1855
2056
  * 将 packages 中的每个 package 下的文件内容合并到 outputDir 下,不保留 package name
@@ -1972,10 +2173,15 @@ async function debugPkgs(config) {
1972
2173
  }
1973
2174
 
1974
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
+ }
1975
2181
  const { entry: entryDir, output: outputDir, build } = config;
1976
2182
  let startTime = Date.now();
1977
2183
  logger.init(outputDir, true);
1978
- logger.info(`TTMG_PACK_VERSION: ${"0.2.9"}`);
2184
+ logger.info(`TTMG_PACK_VERSION: ${"0.3.0"}`);
1979
2185
  logger.info(`pack start, startTime:${startTime}`);
1980
2186
  /**
1981
2187
  * 清理
@@ -2007,6 +2213,31 @@ async function buildPkgs(config) {
2007
2213
  }
2008
2214
  logger.info(`pack end:${Date.now() - startTime}ms`);
2009
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.3.0"}`);
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
+ }
2010
2241
 
2011
2242
  async function getPkgs({ entryDir, }) {
2012
2243
  const independentPackages = getIndependentPackagesConfig(entryDir);