@ttmg/cli 0.3.6 → 0.3.7-beta.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.
Files changed (75) hide show
  1. package/dist/index.js +548 -66
  2. package/dist/index.js.map +1 -1
  3. package/dist/package.json +1 -1
  4. package/dist/public/assets/Detail-BInCTDTx.js +1 -0
  5. package/dist/public/assets/Detail-BInCTDTx.js.br +0 -0
  6. package/dist/public/assets/Detail-BaQ9eM95.css +1 -0
  7. package/dist/public/assets/{baseForm-DBmW2kD6.js → baseForm-B7tArmif.js} +4 -4
  8. package/dist/public/assets/baseForm-B7tArmif.js.br +0 -0
  9. package/dist/public/assets/{index-UihZn1LL.css → index-6LE--ADL.css} +1 -1
  10. package/dist/public/assets/index-6LE--ADL.css.br +0 -0
  11. package/dist/public/assets/index-AtMMzkp0.css +1 -0
  12. package/dist/public/assets/index-B-rc2X1H.js +1 -0
  13. package/dist/public/assets/index-B0CT2rYr.js +1 -0
  14. package/dist/public/assets/index-BACeBrQR.css +1 -0
  15. package/dist/public/assets/index-BOiN8aIS.js +1 -0
  16. package/dist/public/assets/index-BVi9swz_.js +1 -0
  17. package/dist/public/assets/index-BVi9swz_.js.br +0 -0
  18. package/dist/public/assets/index-BlXhGm9R.js +1 -0
  19. package/dist/public/assets/{index-BkiB8M4Y.css → index-BnU4EHWL.css} +1 -1
  20. package/dist/public/assets/index-BnU4EHWL.css.br +0 -0
  21. package/dist/public/assets/index-BswgGVlz.js +1 -0
  22. package/dist/public/assets/index-BswgGVlz.js.br +0 -0
  23. package/dist/public/assets/index-C4wGgEDm.css +1 -0
  24. package/dist/public/assets/{index-Cd9f0YbK.js → index-CDrq3KnY.js} +1 -1
  25. package/dist/public/assets/index-CH7igbHY.css +1 -0
  26. package/dist/public/assets/index-Cj-lTEG1.js +1 -0
  27. package/dist/public/assets/index-Cj-lTEG1.js.br +0 -0
  28. package/dist/public/assets/index-D35C4ac4.js +1 -0
  29. package/dist/public/assets/{index-C5O6E6j7.js → index-DQqzytdw.js} +1 -1
  30. package/dist/public/assets/index-DQqzytdw.js.br +0 -0
  31. package/dist/public/assets/index-DbiZkOAh.css +1 -0
  32. package/dist/public/assets/{index-CSkpTvuk.js → index-DdH9Yin-.js} +1 -1
  33. package/dist/public/assets/index-DkXWo3Jg.js +14 -0
  34. package/dist/public/assets/index-DkXWo3Jg.js.br +0 -0
  35. package/dist/public/assets/index-OIQapQiA.js +1 -0
  36. package/dist/public/assets/{index-BQ6wyzF6.js → index-Sk6u8HWP.js} +1 -1
  37. package/dist/public/assets/index-TYa4eXxF.css +1 -0
  38. package/dist/public/assets/{index-ntZMdlto.js → index-aD1InxYk.js} +1 -1
  39. package/dist/public/assets/index-jaMAKGHW.css +1 -0
  40. package/dist/public/assets/{index-GP4RgOVw.js → index-tXDZpXMk.js} +1 -1
  41. package/dist/public/assets/index-tXDZpXMk.js.br +0 -0
  42. package/dist/public/assets/times-CCIAbLYL.js +1 -0
  43. package/dist/public/index.html +2 -2
  44. package/dist/scripts/docs/capture.js +255 -0
  45. package/dist/scripts/docs/render.js +199 -0
  46. package/dist/scripts/docs/server.js +164 -0
  47. package/package.json +1 -1
  48. package/dist/public/assets/baseForm-DBmW2kD6.js.br +0 -0
  49. package/dist/public/assets/index-B23_YRwh.js +0 -1
  50. package/dist/public/assets/index-BB5kLk47.css +0 -1
  51. package/dist/public/assets/index-BedicqfR.css +0 -1
  52. package/dist/public/assets/index-BkiB8M4Y.css.br +0 -0
  53. package/dist/public/assets/index-BnowjDc1.js +0 -1
  54. package/dist/public/assets/index-Bq6YxKLX.css +0 -1
  55. package/dist/public/assets/index-BwbPFgZF.css +0 -1
  56. package/dist/public/assets/index-C1J0YqA1.js +0 -1
  57. package/dist/public/assets/index-C1J0YqA1.js.br +0 -0
  58. package/dist/public/assets/index-C1Q-_9hH.js +0 -1
  59. package/dist/public/assets/index-C1Q-_9hH.js.br +0 -0
  60. package/dist/public/assets/index-C5O6E6j7.js.br +0 -0
  61. package/dist/public/assets/index-C8PgWsLj.js +0 -1
  62. package/dist/public/assets/index-C8f539tK.css +0 -1
  63. package/dist/public/assets/index-CKPV-F8X.js +0 -1
  64. package/dist/public/assets/index-D7Tq5FjS.js +0 -1
  65. package/dist/public/assets/index-DgjAALHn.js +0 -14
  66. package/dist/public/assets/index-DgjAALHn.js.br +0 -0
  67. package/dist/public/assets/index-DhmPFuxl.css +0 -1
  68. package/dist/public/assets/index-GP4RgOVw.js.br +0 -0
  69. package/dist/public/assets/index-UihZn1LL.css.br +0 -0
  70. package/dist/public/assets/index-Y0U0Z3sY.js +0 -1
  71. package/dist/public/assets/index-_ym9MjBw.js +0 -1
  72. package/dist/public/assets/index-yh4tKekV.css +0 -1
  73. package/dist/public/assets/isPlainObject-CeJzawac.js +0 -1
  74. package/dist/public/assets/times-CELSlX9c.js +0 -1
  75. /package/dist/public/assets/{index-C7C5ulLg.css → times-C7C5ulLg.css} +0 -0
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ var inquirer = require('inquirer');
6
6
  var path = require('path');
7
7
  var os = require('os');
8
8
  var axios = require('axios');
9
+ var chalk = require('chalk');
9
10
  var net = require('net');
10
11
  var require$$1$2 = require('tls');
11
12
  var require$$2 = require('url');
@@ -21,7 +22,6 @@ var dns = require('dns');
21
22
  var fs = require('fs');
22
23
  var handlebars = require('handlebars');
23
24
  var esbuild = require('esbuild');
24
- var chalk = require('chalk');
25
25
  var boxen = require('boxen');
26
26
  var semver = require('semver');
27
27
  var jsdom = require('jsdom');
@@ -6504,6 +6504,9 @@ const getCurrentUser = () => {
6504
6504
  }
6505
6505
  };
6506
6506
 
6507
+ function isVerboseEnabled() {
6508
+ return process.argv.includes('--verbose');
6509
+ }
6507
6510
  // ppe_dev_tool
6508
6511
  function getAxiosProxyConfig() {
6509
6512
  const config = getTTMGRC();
@@ -6532,25 +6535,134 @@ function getAxiosProxyConfig() {
6532
6535
  }
6533
6536
  return {};
6534
6537
  }
6538
+ function getRequestParams(params, data) {
6539
+ const hasQueryParams = !!params && Object.keys(params).length > 0;
6540
+ const normalizeRequestData = () => {
6541
+ if (!data) {
6542
+ return undefined;
6543
+ }
6544
+ if (Buffer.isBuffer(data)) {
6545
+ return `[Buffer ${data.length} bytes]`;
6546
+ }
6547
+ if (typeof data?.pipe === 'function') {
6548
+ return '[ReadableStream]';
6549
+ }
6550
+ if (typeof FormData !== 'undefined' && data instanceof FormData) {
6551
+ const entries = Array.from(data.entries()).map(([key, value]) => {
6552
+ if (typeof File !== 'undefined' && value instanceof File) {
6553
+ return [key, `[File ${value.name}, ${value.size} bytes]`];
6554
+ }
6555
+ return [key, value];
6556
+ });
6557
+ return Object.fromEntries(entries);
6558
+ }
6559
+ return data;
6560
+ };
6561
+ const normalizedData = normalizeRequestData();
6562
+ if (hasQueryParams && normalizedData !== undefined) {
6563
+ return {
6564
+ query: params,
6565
+ body: normalizedData,
6566
+ };
6567
+ }
6568
+ if (hasQueryParams) {
6569
+ return params;
6570
+ }
6571
+ if (normalizedData !== undefined) {
6572
+ return normalizedData;
6573
+ }
6574
+ return undefined;
6575
+ }
6576
+ function safeStringify(value) {
6577
+ if (typeof value === 'string') {
6578
+ return value;
6579
+ }
6580
+ if (value === undefined) {
6581
+ return '-';
6582
+ }
6583
+ const seen = new WeakSet();
6584
+ try {
6585
+ return JSON.stringify(value, (_key, currentValue) => {
6586
+ if (typeof currentValue === 'bigint') {
6587
+ return currentValue.toString();
6588
+ }
6589
+ if (Buffer.isBuffer(currentValue)) {
6590
+ return `[Buffer ${currentValue.length} bytes]`;
6591
+ }
6592
+ if (currentValue && typeof currentValue === 'object') {
6593
+ if (typeof currentValue.pipe === 'function') {
6594
+ return '[ReadableStream]';
6595
+ }
6596
+ if (seen.has(currentValue)) {
6597
+ return '[Circular]';
6598
+ }
6599
+ seen.add(currentValue);
6600
+ }
6601
+ return currentValue;
6602
+ }, 2);
6603
+ }
6604
+ catch {
6605
+ return String(value);
6606
+ }
6607
+ }
6608
+ function getToneColor(tone) {
6609
+ if (tone === 'success') {
6610
+ return chalk.green;
6611
+ }
6612
+ if (tone === 'error') {
6613
+ return chalk.red;
6614
+ }
6615
+ return chalk.cyan;
6616
+ }
6617
+ function formatLogField(label, value) {
6618
+ const renderedValue = safeStringify(value);
6619
+ const lines = renderedValue.split('\n');
6620
+ const formattedLabel = chalk.whiteBright.bold(label.padEnd(12, ' '));
6621
+ if (lines.length === 1) {
6622
+ return [`│ ${formattedLabel} ${chalk.white(renderedValue)}`];
6623
+ }
6624
+ return [
6625
+ `│ ${formattedLabel}`,
6626
+ ...lines.map(line => `│ ${chalk.dim(' '.repeat(13))}${chalk.white(line)}`),
6627
+ ];
6628
+ }
6629
+ function printApiSection(title, tone, fields) {
6630
+ const color = getToneColor(tone);
6631
+ const topBorder = color.bold(`┌─ ${title}`);
6632
+ const bottomBorder = color.bold('└' + '─'.repeat(Math.max(title.length + 2, 28)));
6633
+ console.log('');
6634
+ console.log(topBorder);
6635
+ fields.forEach(([label, value]) => {
6636
+ formatLogField(label, value).forEach(line => console.log(line));
6637
+ });
6638
+ console.log(bottomBorder);
6639
+ console.log('');
6640
+ }
6641
+ function printApiRequestLog(payload) {
6642
+ printApiSection('API Request', 'request', [
6643
+ ['URL', payload.url],
6644
+ ['Method', payload.method],
6645
+ ['Params', payload.params],
6646
+ ]);
6647
+ }
6648
+ function printApiResponseLog(title, result) {
6649
+ printApiSection(title, title === 'API Response' ? 'success' : 'error', [
6650
+ ['Status', result.status ?? '-'],
6651
+ ['LogID', result.logid || '-'],
6652
+ ['Return Value', result.data],
6653
+ ]);
6654
+ }
6535
6655
  async function request({ url, method, data, headers, params, }) {
6536
6656
  const config = getTTMGRC();
6537
6657
  const cookie = config?.cookie;
6538
6658
  const proxyConfig = getAxiosProxyConfig();
6539
- // 打印请求信息
6540
- console.log('\n========== API Request ==========');
6541
- console.log('URL:', url);
6542
- console.log('Method:', method);
6543
- console.log('Headers:', JSON.stringify({
6544
- Cookie: cookie,
6545
- ...(headers || {}),
6546
- }, null, 2));
6547
- if (params) {
6548
- console.log('Query Params:', JSON.stringify(params, null, 2));
6549
- }
6550
- if (data) {
6551
- console.log('Request Body:', JSON.stringify(data, null, 2));
6552
- }
6553
- console.log('=================================\n');
6659
+ if (isVerboseEnabled()) {
6660
+ printApiRequestLog({
6661
+ url,
6662
+ method,
6663
+ params: getRequestParams(params, data),
6664
+ });
6665
+ }
6554
6666
  try {
6555
6667
  const res = await axios({
6556
6668
  url,
@@ -6565,12 +6677,13 @@ async function request({ url, method, data, headers, params, }) {
6565
6677
  },
6566
6678
  ...proxyConfig,
6567
6679
  });
6568
- // 打印响应信息
6569
- console.log('\n========== API Response ==========');
6570
- console.log('Status:', res?.status);
6571
- console.log('LogID:', res?.headers['x-tt-logid']);
6572
- console.log('Data:', JSON.stringify(res.data, null, 2));
6573
- console.log('==================================\n');
6680
+ if (isVerboseEnabled()) {
6681
+ printApiResponseLog('API Response', {
6682
+ status: res?.status,
6683
+ logid: res?.headers['x-tt-logid'],
6684
+ data: res.data,
6685
+ });
6686
+ }
6574
6687
  // @ts-ignore
6575
6688
  return {
6576
6689
  data: res.data,
@@ -6582,12 +6695,13 @@ async function request({ url, method, data, headers, params, }) {
6582
6695
  };
6583
6696
  }
6584
6697
  catch (err) {
6585
- // 打印错误响应
6586
- console.log('\n========== API Error ==========');
6587
- console.log('Status:', err?.response?.status);
6588
- console.log('LogID:', err?.response?.headers['x-tt-logid']);
6589
- console.log('Error:', JSON.stringify(err?.response?.data, null, 2));
6590
- console.log('================================\n');
6698
+ if (isVerboseEnabled()) {
6699
+ printApiResponseLog('API Error', {
6700
+ status: err?.response?.status,
6701
+ logid: err?.response?.headers['x-tt-logid'],
6702
+ data: err?.response?.data,
6703
+ });
6704
+ }
6591
6705
  // @ts-ignore
6592
6706
  return {
6593
6707
  data: null,
@@ -6658,6 +6772,46 @@ function isAxiosError(e) {
6658
6772
  return !!e?.isAxiosError;
6659
6773
  }
6660
6774
 
6775
+ const GET_GAME_ASSET_PREVIEW_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/asset/preview_url';
6776
+ async function fetchGameAssetPreviewUrl({ assetId, }) {
6777
+ return request({
6778
+ url: GET_GAME_ASSET_PREVIEW_URL,
6779
+ method: 'GET',
6780
+ params: {
6781
+ asset_id: assetId,
6782
+ },
6783
+ });
6784
+ }
6785
+
6786
+ const GET_DIRECT_FEED_CARD_ASSET_PREVIEW_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/fyf_card/get_direct_feed_card_asset_preview_url';
6787
+ const DIRECT_FEED_CARD_HEADERS = {
6788
+ 'Content-Type': 'application/json',
6789
+ 'x-use-ppe': '1',
6790
+ 'x-tt-env': 'ppe_feed_play',
6791
+ };
6792
+ async function fetchDirectFeedCardAssetPreviewUrl({ assetId, contentId, }) {
6793
+ return request({
6794
+ url: GET_DIRECT_FEED_CARD_ASSET_PREVIEW_URL,
6795
+ method: 'POST',
6796
+ headers: DIRECT_FEED_CARD_HEADERS,
6797
+ data: {
6798
+ asset_id: assetId,
6799
+ content_id: contentId,
6800
+ },
6801
+ });
6802
+ }
6803
+
6804
+ const GAME_ASSETS_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/assets';
6805
+ async function fetchGameAssets({ miniGameId, }) {
6806
+ return request({
6807
+ url: GAME_ASSETS_URL,
6808
+ method: 'GET',
6809
+ params: {
6810
+ mini_game_id: miniGameId,
6811
+ },
6812
+ });
6813
+ }
6814
+
6661
6815
  async function fetchGameInfo(clientKey) {
6662
6816
  // 访问 V4 接口
6663
6817
  const response = await request({
@@ -6694,6 +6848,24 @@ async function fetchGameInfo(clientKey) {
6694
6848
  }
6695
6849
  }
6696
6850
 
6851
+ const LIST_DIRECT_FEED_CARD_URL = 'https://developers.tiktok.com/tiktok/v4/devportal/mini_game/fyf_card/list_direct_feed_card';
6852
+ const LIST_DIRECT_FEED_CARD_HEADERS = {
6853
+ 'Content-Type': 'application/json',
6854
+ 'x-use-ppe': '1',
6855
+ 'x-tt-env': 'ppe_feed_play',
6856
+ };
6857
+ async function listDirectFeedCards({ appId, clientKey, }) {
6858
+ return request({
6859
+ url: LIST_DIRECT_FEED_CARD_URL,
6860
+ method: 'POST',
6861
+ headers: LIST_DIRECT_FEED_CARD_HEADERS,
6862
+ data: {
6863
+ app_id: appId,
6864
+ client_key: clientKey,
6865
+ },
6866
+ });
6867
+ }
6868
+
6697
6869
  async function uploadGameToPlatform({ data, name, clientKey, note = '--', appId, }) {
6698
6870
  if (!appId) {
6699
6871
  return {
@@ -6809,10 +6981,10 @@ const messages = {
6809
6981
  'en-US': {
6810
6982
  'cli.description': 'TikTok Mini Games Command Line Tool',
6811
6983
  'cli.version.desc': 'Show version',
6984
+ 'cli.option.verbose': 'Print detailed debug logs',
6812
6985
  'cli.option.dev.client': 'Debug TikTok Mini Games for Client',
6813
6986
  'cli.option.dev.h5': 'Debug TikTok Mini Games for Web',
6814
6987
  'cli.command.login.desc': 'Login with developer account',
6815
- 'cli.command.login.verbose': 'Print verbose logs for debugging',
6816
6988
  'cli.command.logout.desc': 'Logout and clear local user data',
6817
6989
  'cli.command.setup.desc': 'Initialize ttmg environment',
6818
6990
  'cli.command.setup.lang': 'Language (only supports): en-US | zh-CN',
@@ -6940,10 +7112,10 @@ const messages = {
6940
7112
  'zh-CN': {
6941
7113
  'cli.description': 'TikTok 小游戏命令行工具',
6942
7114
  'cli.version.desc': '显示版本号',
7115
+ 'cli.option.verbose': '输出详细调试日志',
6943
7116
  'cli.option.dev.client': '客户端调试 TikTok 小游戏',
6944
7117
  'cli.option.dev.h5': 'Web 调试 TikTok 小游戏',
6945
7118
  'cli.command.login.desc': '使用开发者账号登录',
6946
- 'cli.command.login.verbose': '输出调试用详细日志',
6947
7119
  'cli.command.logout.desc': '退出登录并清除本地用户信息',
6948
7120
  'cli.command.setup.desc': '初始化 ttmg 环境',
6949
7121
  'cli.command.setup.lang': '语言(仅支持):en-US | zh-CN',
@@ -7807,17 +7979,6 @@ class WsServer {
7807
7979
  else {
7808
7980
  const method = clientMessage.method;
7809
7981
  switch (method) {
7810
- /**
7811
- * 客户端完成扫码成功,返回客户端的 host 和 port
7812
- */
7813
- case 'startScanQRcode': {
7814
- const payload = clientMessage.payload;
7815
- console.log('startQRcode', payload);
7816
- this.send({
7817
- method: 'startScanQRcode',
7818
- });
7819
- break;
7820
- }
7821
7982
  case 'scanQRCodeResult': {
7822
7983
  const payload = clientMessage.payload || {};
7823
7984
  const { host, port, wsPort, errMsg, isSuccess } = payload;
@@ -8740,6 +8901,159 @@ function setupMiddlewares(app, options) {
8740
8901
  const successCode = 0;
8741
8902
  const errorCode = -1;
8742
8903
 
8904
+ const gameAssetPreviewUrlRoute = {
8905
+ method: 'post',
8906
+ path: '/game/asset/preview-url',
8907
+ handler: async (req, res) => {
8908
+ const body = req.body;
8909
+ const assetId = body?.assetId?.trim() || body?.asset_id?.trim() || '';
8910
+ if (!assetId) {
8911
+ res.send({
8912
+ code: errorCode,
8913
+ error: 'Missing required field `assetId`.',
8914
+ data: null,
8915
+ });
8916
+ return;
8917
+ }
8918
+ const response = await fetchGameAssetPreviewUrl({
8919
+ assetId,
8920
+ });
8921
+ if (response.error) {
8922
+ res.send({
8923
+ code: errorCode,
8924
+ error: response.error,
8925
+ ctx: response.ctx,
8926
+ });
8927
+ return;
8928
+ }
8929
+ res.send({
8930
+ code: successCode,
8931
+ data: response.data,
8932
+ ctx: response.ctx,
8933
+ });
8934
+ },
8935
+ };
8936
+
8937
+ function getAssetStatusValue(asset) {
8938
+ if (!asset) {
8939
+ return '';
8940
+ }
8941
+ return String(asset.status ?? asset.cdn_deploy_status ?? asset.live_status ?? '').toLowerCase();
8942
+ }
8943
+ function isUsableAsset(asset) {
8944
+ if (!asset) {
8945
+ return false;
8946
+ }
8947
+ if (Number(asset.cdn_deploy_status) === 2) {
8948
+ return true;
8949
+ }
8950
+ const rawStatus = getAssetStatusValue(asset);
8951
+ return ['ready', 'success', 'succeeded', 'available'].includes(rawStatus);
8952
+ }
8953
+ function filterAssetList(assets, { includePending, pinnedAssetId, }) {
8954
+ if (!Array.isArray(assets)) {
8955
+ return [];
8956
+ }
8957
+ return assets.filter(item => {
8958
+ const asset = item;
8959
+ if (isUsableAsset(asset)) {
8960
+ return true;
8961
+ }
8962
+ if (!includePending || !pinnedAssetId) {
8963
+ return false;
8964
+ }
8965
+ return String(asset.asset_id || '') === pinnedAssetId;
8966
+ });
8967
+ }
8968
+ async function resolveMiniGameId(body) {
8969
+ let miniGameId = body?.miniGameId?.trim() ||
8970
+ body?.mini_game_id?.trim() ||
8971
+ body?.appId?.trim() ||
8972
+ body?.app_id?.trim() ||
8973
+ store.getState().appId?.trim();
8974
+ const clientKey = body?.clientKey?.trim() ||
8975
+ body?.client_key?.trim() ||
8976
+ getClientKey().clientKey?.trim();
8977
+ if (!miniGameId && clientKey) {
8978
+ const gameInfoResponse = await fetchGameInfo(clientKey);
8979
+ if (gameInfoResponse.error) {
8980
+ return {
8981
+ miniGameId: '',
8982
+ error: gameInfoResponse.error,
8983
+ };
8984
+ }
8985
+ miniGameId = gameInfoResponse.data?.app_id?.trim() || '';
8986
+ if (miniGameId) {
8987
+ store.setState({ appId: miniGameId });
8988
+ }
8989
+ }
8990
+ if (!miniGameId) {
8991
+ return {
8992
+ miniGameId: '',
8993
+ error: 'Missing mini game id. Please open project detail first or pass `miniGameId` / `appId` from IDE.',
8994
+ };
8995
+ }
8996
+ return {
8997
+ miniGameId,
8998
+ error: null,
8999
+ };
9000
+ }
9001
+ const gameAssetsRoute = {
9002
+ method: 'post',
9003
+ path: '/game/assets',
9004
+ handler: async (req, res) => {
9005
+ const body = req.body;
9006
+ const identity = await resolveMiniGameId(body);
9007
+ if (identity.error) {
9008
+ res.send({
9009
+ code: errorCode,
9010
+ error: identity.error,
9011
+ data: null,
9012
+ });
9013
+ return;
9014
+ }
9015
+ const response = await fetchGameAssets({
9016
+ miniGameId: identity.miniGameId,
9017
+ });
9018
+ if (response.error) {
9019
+ res.send({
9020
+ code: errorCode,
9021
+ error: response.error,
9022
+ ctx: response.ctx,
9023
+ });
9024
+ return;
9025
+ }
9026
+ const pinnedAssetId = body?.assetId?.trim() || body?.asset_id?.trim() || '';
9027
+ const includePending = body?.includePending === true || body?.include_pending === true;
9028
+ const filteredData = response.data
9029
+ ? {
9030
+ ...response.data,
9031
+ preview_assets: filterAssetList(response.data.preview_assets, {
9032
+ includePending,
9033
+ pinnedAssetId,
9034
+ }),
9035
+ in_review_assets: filterAssetList(response.data.in_review_assets, {
9036
+ includePending,
9037
+ pinnedAssetId,
9038
+ }),
9039
+ live_assets: filterAssetList(response.data.live_assets, {
9040
+ includePending,
9041
+ pinnedAssetId,
9042
+ }),
9043
+ assets: filterAssetList(response.data.assets, {
9044
+ includePending,
9045
+ pinnedAssetId,
9046
+ }),
9047
+ }
9048
+ : null;
9049
+ res.send({
9050
+ code: successCode,
9051
+ data: filteredData,
9052
+ ctx: response.ctx,
9053
+ });
9054
+ },
9055
+ };
9056
+
8743
9057
  function buildCheckPkgsOptions(outputDir) {
8744
9058
  return {
8745
9059
  entry: process.cwd(),
@@ -8843,6 +9157,170 @@ const gameCheckRoute = {
8843
9157
  },
8844
9158
  };
8845
9159
 
9160
+ const gameDirectFeedCardAssetPreviewRoute = {
9161
+ method: 'post',
9162
+ path: '/game/direct-feed-card/asset-preview',
9163
+ handler: async (req, res) => {
9164
+ const body = req.body;
9165
+ const assetId = body?.assetId?.trim() || body?.asset_id?.trim() || '';
9166
+ const contentId = body?.contentId?.trim() || body?.content_id?.trim() || '';
9167
+ if (!assetId || !contentId) {
9168
+ res.send({
9169
+ code: errorCode,
9170
+ error: 'Missing required fields. Please pass both `assetId` and `contentId` from IDE.',
9171
+ data: null,
9172
+ });
9173
+ return;
9174
+ }
9175
+ const response = await fetchDirectFeedCardAssetPreviewUrl({
9176
+ assetId,
9177
+ contentId,
9178
+ });
9179
+ if (response.error) {
9180
+ res.send({
9181
+ code: errorCode,
9182
+ error: response.error,
9183
+ ctx: response.ctx,
9184
+ });
9185
+ return;
9186
+ }
9187
+ res.send({
9188
+ code: successCode,
9189
+ data: response.data,
9190
+ ctx: response.ctx,
9191
+ });
9192
+ },
9193
+ };
9194
+
9195
+ function hasBlockingCardStatus(value) {
9196
+ if (value === null || value === undefined || value === '') {
9197
+ return false;
9198
+ }
9199
+ if (typeof value === 'number') {
9200
+ return value <= 0;
9201
+ }
9202
+ const normalized = String(value).toLowerCase();
9203
+ return [
9204
+ 'pending',
9205
+ 'review',
9206
+ 'reviewing',
9207
+ 'processing',
9208
+ 'reject',
9209
+ 'rejected',
9210
+ 'fail',
9211
+ 'failed',
9212
+ 'disable',
9213
+ 'disabled',
9214
+ 'offline',
9215
+ 'draft',
9216
+ 'delete',
9217
+ 'deleted',
9218
+ ].some(keyword => normalized.includes(keyword));
9219
+ }
9220
+ function isUsableDirectFeedCard(card) {
9221
+ if (!card?.content_id) {
9222
+ return false;
9223
+ }
9224
+ if (hasBlockingCardStatus(card.status)) {
9225
+ return false;
9226
+ }
9227
+ // Dev Portal returns numeric audit statuses; `1` is already usable in practice,
9228
+ // so only block clearly invalid numeric values and known textual blocking states.
9229
+ if (typeof card.audit_status === 'number') {
9230
+ if (card.audit_status <= 0) {
9231
+ return false;
9232
+ }
9233
+ }
9234
+ else if (hasBlockingCardStatus(card.audit_status)) {
9235
+ return false;
9236
+ }
9237
+ return true;
9238
+ }
9239
+ async function resolveAppIdentity(body) {
9240
+ const clientKey = body?.clientKey?.trim() ||
9241
+ body?.client_key?.trim() ||
9242
+ getClientKey().clientKey?.trim();
9243
+ let appId = body?.appId?.trim() ||
9244
+ body?.app_id?.trim() ||
9245
+ store.getState().appId?.trim();
9246
+ if (!clientKey) {
9247
+ return {
9248
+ clientKey: '',
9249
+ appId,
9250
+ error: 'Missing client key. Please run `ttmg init` or pass `clientKey` from IDE.',
9251
+ };
9252
+ }
9253
+ if (!appId) {
9254
+ const gameInfoResponse = await fetchGameInfo(clientKey);
9255
+ if (gameInfoResponse.error) {
9256
+ return {
9257
+ clientKey,
9258
+ appId: '',
9259
+ error: gameInfoResponse.error,
9260
+ };
9261
+ }
9262
+ appId = gameInfoResponse.data?.app_id?.trim() || '';
9263
+ if (appId) {
9264
+ store.setState({ appId });
9265
+ }
9266
+ }
9267
+ if (!appId) {
9268
+ return {
9269
+ clientKey,
9270
+ appId: '',
9271
+ error: 'Missing app id. Please open project detail first or pass `appId` from IDE.',
9272
+ };
9273
+ }
9274
+ return {
9275
+ clientKey,
9276
+ appId,
9277
+ error: null,
9278
+ };
9279
+ }
9280
+ const gameDirectFeedCardListRoute = {
9281
+ method: 'post',
9282
+ path: '/game/direct-feed-card/list',
9283
+ handler: async (req, res) => {
9284
+ const identity = await resolveAppIdentity(req.body);
9285
+ if (identity.error) {
9286
+ res.send({
9287
+ code: errorCode,
9288
+ error: identity.error,
9289
+ data: null,
9290
+ });
9291
+ return;
9292
+ }
9293
+ const response = await listDirectFeedCards({
9294
+ appId: identity.appId,
9295
+ clientKey: identity.clientKey,
9296
+ });
9297
+ if (response.error) {
9298
+ res.send({
9299
+ code: errorCode,
9300
+ error: response.error,
9301
+ ctx: response.ctx,
9302
+ });
9303
+ return;
9304
+ }
9305
+ const filteredCards = Array.isArray(response.data?.cards)
9306
+ ? (response.data.cards || []).filter(isUsableDirectFeedCard)
9307
+ : [];
9308
+ const total = filteredCards.length;
9309
+ const data = response.data
9310
+ ? {
9311
+ ...response.data,
9312
+ cards: filteredCards,
9313
+ total,
9314
+ }
9315
+ : null;
9316
+ res.send({
9317
+ code: successCode,
9318
+ data,
9319
+ ctx: response.ctx,
9320
+ });
9321
+ },
9322
+ };
9323
+
8846
9324
  const gameDetailRoute = {
8847
9325
  method: 'get',
8848
9326
  path: '/game/detail',
@@ -10222,10 +10700,14 @@ function getGameFallbackRoute(publicPath) {
10222
10700
  }
10223
10701
 
10224
10702
  const routes = [
10703
+ gameAssetPreviewUrlRoute,
10704
+ gameAssetsRoute,
10225
10705
  gameConfigRoute,
10226
10706
  gameConfigFillbackRoute,
10227
10707
  gameDetailRoute,
10228
10708
  gameCheckRoute,
10709
+ gameDirectFeedCardAssetPreviewRoute,
10710
+ gameDirectFeedCardListRoute,
10229
10711
  gameUploadRoute,
10230
10712
  gameWasmSplitConfigRoute,
10231
10713
  gameWasmSplitOptionsRoute,
@@ -10684,78 +11166,78 @@ async function upload({ clientKey, note = '--', dir, }) {
10684
11166
  }
10685
11167
  }
10686
11168
 
10687
- var version = "0.3.6";
11169
+ var version = "0.3.7-beta.2";
10688
11170
  var pkg = {
10689
11171
  version: version};
10690
11172
 
10691
11173
  const program = new commander.Command();
11174
+ const withVerboseOption = (command) => command.option('--verbose', t('cli.option.verbose'));
10692
11175
  maybeShowPostInstallNotice(pkg.version);
10693
- program
11176
+ withVerboseOption(program)
10694
11177
  .name('ttmg')
10695
11178
  .description(t('cli.description'))
10696
11179
  .version(pkg.version, '-v, --version', t('cli.version.desc'))
10697
11180
  .option('dev', t('cli.option.dev.client'))
10698
11181
  .option('dev --h5', t('cli.option.dev.h5'));
10699
- program
11182
+ withVerboseOption(program
10700
11183
  .command('login')
10701
- .description(t('cli.command.login.desc'))
10702
- .option('--verbose', t('cli.command.login.verbose'))
11184
+ .description(t('cli.command.login.desc')))
10703
11185
  .action(async (cmd) => {
10704
11186
  await login({ verbose: cmd.verbose });
10705
11187
  });
10706
- program
11188
+ withVerboseOption(program
10707
11189
  .command('logout')
10708
- .description(t('cli.command.logout.desc'))
11190
+ .description(t('cli.command.logout.desc')))
10709
11191
  .action(async () => {
10710
11192
  await logout();
10711
11193
  });
10712
- program
11194
+ withVerboseOption(program
10713
11195
  .command('upload')
10714
11196
  .description(t('cli.command.upload.desc'))
10715
11197
  .option('-ck, --client-key <clientKey>', t('cli.command.upload.clientKey'))
10716
11198
  .option('-n, --note <note>', t('cli.command.upload.note'))
10717
- .option('-d, --dir <dir>', t('cli.command.upload.dir'))
11199
+ .option('-d, --dir <dir>', t('cli.command.upload.dir')))
10718
11200
  .action(async (cmd) => {
10719
11201
  await upload({ clientKey: cmd.clientKey, note: cmd.note, dir: cmd.dir });
10720
11202
  });
10721
- program
11203
+ withVerboseOption(program
10722
11204
  .command('setup')
10723
11205
  .description(t('cli.command.setup.desc'))
10724
- .option('--lang <lang>', t('cli.command.setup.lang'))
11206
+ .option('--lang <lang>', t('cli.command.setup.lang')))
10725
11207
  .action(async (cmd) => {
10726
11208
  await setup({ lang: cmd.lang });
10727
11209
  });
10728
- program
11210
+ withVerboseOption(program
10729
11211
  .command('reset')
10730
- .description(t('cli.command.reset.desc'))
11212
+ .description(t('cli.command.reset.desc')))
10731
11213
  .action(async () => {
10732
11214
  await reset();
10733
11215
  });
10734
11216
  const configCmd = program
10735
11217
  .command('config')
10736
11218
  .description(t('cli.command.config.desc'));
10737
- configCmd
11219
+ withVerboseOption(configCmd
10738
11220
  .command('set <key> [value]')
10739
- .description(t('cli.command.config.set.desc'))
11221
+ .description(t('cli.command.config.set.desc')))
10740
11222
  .action(async (key, value) => {
10741
11223
  await config.set(key, value);
10742
11224
  });
10743
- configCmd
11225
+ withVerboseOption(configCmd
10744
11226
  .command('get <key>')
10745
- .description(t('cli.command.config.get.desc'))
11227
+ .description(t('cli.command.config.get.desc')))
10746
11228
  .action(async (key) => {
10747
11229
  await config.get(key);
10748
11230
  });
10749
- configCmd
11231
+ withVerboseOption(configCmd
10750
11232
  .command('delete <key>')
10751
- .description(t('cli.command.config.delete.desc'))
11233
+ .description(t('cli.command.config.delete.desc')))
10752
11234
  .action(async (key) => {
10753
11235
  await config.delete(key);
10754
11236
  });
10755
- program
11237
+ withVerboseOption(program
10756
11238
  .option('--h5', t('cli.option.h5'))
10757
11239
  .command('init')
10758
- .description(t('cli.command.init.desc'))
11240
+ .description(t('cli.command.init.desc')))
10759
11241
  .action(() => {
10760
11242
  const options = program.opts(); // 获取 options
10761
11243
  if (options.h5) {
@@ -10768,10 +11250,10 @@ program
10768
11250
  /**
10769
11251
  * ttmg dev 命令
10770
11252
  */
10771
- program
11253
+ withVerboseOption(program
10772
11254
  .option('--h5', t('cli.option.h5'))
10773
11255
  .command('dev')
10774
- .description(t('cli.command.dev.desc'))
11256
+ .description(t('cli.command.dev.desc')))
10775
11257
  .action(async () => {
10776
11258
  const options = program.opts();
10777
11259
  if (options.h5) {
@@ -10785,10 +11267,10 @@ program
10785
11267
  /**
10786
11268
  * ttmg build 命令
10787
11269
  */
10788
- program
11270
+ withVerboseOption(program
10789
11271
  .option('--h5', t('cli.option.h5'))
10790
11272
  .command('build')
10791
- .description(t('cli.command.build.desc'))
11273
+ .description(t('cli.command.build.desc')))
10792
11274
  .action(() => {
10793
11275
  const options = program.opts(); // 获取 options
10794
11276
  if (options.h5) {