@ttmg/cli 0.3.1-unity.8 → 0.3.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/CHANGELOG.md CHANGED
@@ -174,3 +174,9 @@ feat: 支持文件变更监听,告知浏览器文件变更,提醒开发者
174
174
  修复启动模式模块,自定义query 参数无法持久化的问题
175
175
  修复扫描二维码模块,内容过多,纵向无法滚动问题
176
176
 
177
+ ## 0.3.1
178
+ - 支持 unity 小游戏本地一些列调试功能,如 Wasm 分包、包大小校验、一键上传代码包至平台等 了解如何使用
179
+ - 增加用户登录过期的异常提示,提醒开发者重新登录
180
+ - 优化上传代码包至平台功能,开发者只用添加描述后点击上传便可自动压缩当前目录下的代码包上传至开发者平台
181
+ - 优化 ttmg login 终端网络代理异常导致无法登录的报错引导,提示用户检查网络设置
182
+
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ var cheerio = require('cheerio');
13
13
  var os = require('os');
14
14
  var worker_threads = require('worker_threads');
15
15
  var axios = require('axios');
16
+ var qs = require('qs');
16
17
  var handlebars = require('handlebars');
17
18
  var esbuild = require('esbuild');
18
19
  var archiver = require('archiver');
@@ -21,8 +22,9 @@ var WebSocket = require('ws');
21
22
  var glob = require('glob');
22
23
  var got = require('got');
23
24
  var FormData$1 = require('form-data');
24
- var http = require('http');
25
+ var stream = require('stream');
25
26
  var ttmgPack = require('ttmg-pack');
27
+ var http = require('http');
26
28
  var fs$1 = require('node:fs');
27
29
  var path$1 = require('node:path');
28
30
  var zlib = require('zlib');
@@ -276,7 +278,7 @@ const LOGIN_TT4D = 'https://developers.tiktok.com/passport/web/email/login';
276
278
  const params = {
277
279
  aid: '2471',
278
280
  account_sdk_source: 'web',
279
- sdk_version: '2.1.1',
281
+ sdk_version: '2.1.6-tiktok',
280
282
  };
281
283
  const prompt = inquirer.createPromptModule();
282
284
  async function login() {
@@ -318,8 +320,11 @@ async function login() {
318
320
  const url = LOGIN_TT4D + '?' + new URLSearchParams(params);
319
321
  const headers = {
320
322
  'Content-Type': 'application/x-www-form-urlencoded',
321
- 'User-Agent': 'curl/8.5.0',
322
323
  Accept: '*/*',
324
+ 'Accept-Encoding': 'gzip, deflate, br',
325
+ Origin: 'https://developers.tiktok.com',
326
+ Referer: 'https://developers.tiktok.com/passport/web/email/login',
327
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36',
323
328
  };
324
329
  const ora = await import('ora');
325
330
  spinner$1 = ora.default({
@@ -327,13 +332,28 @@ async function login() {
327
332
  spinner: 'dots',
328
333
  });
329
334
  spinner$1.start();
335
+ const data = qs.stringify({
336
+ email,
337
+ password,
338
+ mix_mode: '1',
339
+ fixed_mix_mode: '1',
340
+ });
330
341
  try {
331
- const response = await axios.post(url, {
332
- email,
333
- password,
334
- }, { headers, maxRedirects: 20, timeout: 30000 });
335
- if (response?.data?.data?.error_code) {
336
- spinner$1.fail(chalk.red(`login failed: ${response.data?.data?.description || response.data?.data?.error_code}`));
342
+ const response = await axios.post(url, data, {
343
+ headers,
344
+ maxRedirects: 20,
345
+ timeout: 30000,
346
+ });
347
+ if (!response?.data?.data?.user_id) {
348
+ const errCode = response.data?.data?.error_code;
349
+ const errMsg = response.data?.data?.description;
350
+ if (errCode || errMsg) {
351
+ spinner$1.fail(chalk.red(`login failed: ${errMsg}${errCode ? `, error_code: ${errCode}` : ''}`));
352
+ }
353
+ else {
354
+ spinner$1.fail(chalk.red(`login failed`));
355
+ console.log(response.data);
356
+ }
337
357
  spinner$1.stop();
338
358
  process.exit(1);
339
359
  }
@@ -349,8 +369,13 @@ async function login() {
349
369
  }
350
370
  }
351
371
  catch (error) {
352
- spinner$1.fail(chalk.red(`❌:${error.message}, please check your network, make sure your Shell can access TikTok Developer Platform, you can try by run command 'curl https://developers.tiktok.com' to check if it's accessible;\n`));
353
- console.log(chalk.yellow('🔔 You can follow this doc to modify your terminal network settings:'), chalk.underline.yellow('https://bytedance.larkoffice.com/wiki/ZblJwT0ZNil9jJkS8EgcFlcQnFc'));
372
+ // 1. Error Title: Red and bold
373
+ spinner$1.fail(chalk.red.bold('Failed to connect to login service'));
374
+ // 2. Description: Normal text with bold keywords
375
+ console.log(" Detected that the current terminal's " +
376
+ chalk.bold('network proxy settings') +
377
+ ' are preventing external network access.', chalk.yellow('Please check your local terminal proxy configuration.'));
378
+ console.log(chalk.yellow('You can follow this doc to modify your terminal network settings:'), chalk.underline.yellow('https://bytedance.larkoffice.com/wiki/ZblJwT0ZNil9jJkS8EgcFlcQnFc'));
354
379
  process.exit(1);
355
380
  }
356
381
  }
@@ -366,8 +391,8 @@ async function request({ url, method, data, headers, params, }) {
366
391
  data,
367
392
  params,
368
393
  headers: {
369
- ...(headers || {}),
370
394
  Cookie: cookie,
395
+ ...(headers || {}),
371
396
  },
372
397
  });
373
398
  // @ts-ignore
@@ -495,7 +520,7 @@ async function fetchGameInfo(clientKey) {
495
520
  params: {
496
521
  client_key: clientKey,
497
522
  // version_type: 1,
498
- },
523
+ }
499
524
  // headers: {
500
525
  // 'x-use-ppe': '1',
501
526
  // 'x-tt-env': 'ppe_mg_revamp',
@@ -1795,14 +1820,58 @@ function showTips(context) {
1795
1820
  console.log(chalk.gray('─────────────────────────────────────────────'));
1796
1821
  }
1797
1822
 
1798
- const UNITY_PPE_ENV = 'ppe_op_unity';
1823
+ /**
1824
+ * 辅助函数:将当前工作目录打包为 Buffer
1825
+ * @param customIgnores - 可选的自定义忽略规则数组 (支持 glob 模式,如 ['dist/**', '*.log'])
1826
+ */
1827
+ const zipCwdToBuffer = (customIgnores = []) => {
1828
+ return new Promise((resolve, reject) => {
1829
+ const chunks = [];
1830
+ const output = new stream.Writable({
1831
+ write(chunk, encoding, next) {
1832
+ chunks.push(chunk);
1833
+ next();
1834
+ },
1835
+ });
1836
+ const archive = archiver('zip', { zlib: { level: 9 } });
1837
+ output.on('finish', () => {
1838
+ resolve(Buffer.concat(chunks));
1839
+ });
1840
+ archive.on('error', err => {
1841
+ reject(err);
1842
+ });
1843
+ archive.pipe(output);
1844
+ const cwd = process.cwd();
1845
+ // 1. 基础忽略列表 (建议保留这些基础规则,防止包过大)
1846
+ const defaultIgnores = [
1847
+ 'node_modules/**',
1848
+ '.git/**',
1849
+ '.DS_Store',
1850
+ ttmgPack.TTMG_TEMP_DIR,
1851
+ '*.zip', // 忽略自身生成的 zip
1852
+ ];
1853
+ // 2. 合并自定义规则
1854
+ // Set 去重,防止重复添加
1855
+ const finalIgnores = Array.from(new Set([...defaultIgnores, ...customIgnores]));
1856
+ // 3. 执行打包
1857
+ // 注意:ignore 规则必须使用正斜杠 /,即使在 Windows 下
1858
+ archive.glob('**/*', {
1859
+ cwd: cwd,
1860
+ ignore: finalIgnores,
1861
+ dot: true, // 包含 .env 等点文件
1862
+ });
1863
+ archive.finalize();
1864
+ });
1865
+ };
1866
+
1799
1867
  const BASE_URL = 'https://developers.tiktok.com';
1800
1868
  const DEV_HEADERS = {
1801
- 'x-use-ppe': '1',
1802
- 'x-tt-env': UNITY_PPE_ENV,
1803
- // 'x-tt-target-idc': 'sg',
1869
+ // 'x-use-ppe': '1',
1870
+ // 'x-tt-env': UNITY_PPE_ENV,
1871
+ // 'x-tt-target-idc': 'sg',
1804
1872
  };
1805
- const WASM_SPLIT_CACHE_DIR = '__TTMG_TEMP__/wasmcode/';
1873
+ const TTMG_TEMP_DIR = '__TTMG_TEMP__';
1874
+ const WASM_SPLIT_CACHE_DIR = `${TTMG_TEMP_DIR}/wasmcode/`;
1806
1875
  const WASM_SYMBOL_FILE_NAME = 'webgl.symbols.json';
1807
1876
  const UNITY_WASM_SPLIT_CONFIG_FIELD_SCHEME = {
1808
1877
  ENABLEWASMCOLLECT: `"$ENABLEWASMCOLLECT"`,
@@ -1835,13 +1904,14 @@ const WASM_SPLIT_SUBPACKAGE_CONFIG = {
1835
1904
  root: 'wasmcode1-android/',
1836
1905
  },
1837
1906
  ios: {
1838
- name: 'wasmcode-ios',
1839
1907
  root: 'wasmcode-ios/',
1840
1908
  },
1841
1909
  iosMain: {
1910
+ name: 'wasmcode-ios',
1842
1911
  root: 'wasmcode-ios/',
1843
1912
  },
1844
1913
  iosSub: {
1914
+ name: 'wasmcode1-ios',
1845
1915
  root: 'wasmcode1-ios/',
1846
1916
  },
1847
1917
  };
@@ -1869,7 +1939,34 @@ async function startPrepare(params) {
1869
1939
  // 部分后端会依赖 content-type;如果不确定就用 application/octet-stream
1870
1940
  contentType: 'application/wasm',
1871
1941
  });
1872
- form.append('wasm_symbol_file', fs$1.createReadStream(path$1.join(process.cwd(), WASM_SYMBOL_FILE_NAME)), {
1942
+ /**
1943
+ * 兼容 WASM_SYMBOL_FILE_NAME
1944
+ * case 1:项目根目录有 webgl.symbols.json 文件
1945
+ * case 2:项目根目录没有 webgl.symbols.json 文件,但 TTMG_TEMP_DIR 有
1946
+ * 优先读 2
1947
+ */
1948
+ let symbolFilePath = path$1.join(process.cwd(), TTMG_TEMP_DIR, WASM_SYMBOL_FILE_NAME);
1949
+ if (!fs$1.existsSync(symbolFilePath)) {
1950
+ symbolFilePath = path$1.join(process.cwd(), WASM_SYMBOL_FILE_NAME);
1951
+ }
1952
+ /**
1953
+ * 判断是否有 symbol 文件,有则上传,没有直接接口报错
1954
+ */
1955
+ if (!fs$1.existsSync(symbolFilePath)) {
1956
+ return {
1957
+ error: {
1958
+ code: 400,
1959
+ message: `${WASM_SYMBOL_FILE_NAME} not found at ${path$1.join(process.cwd())},use unity plugin to rebuild`,
1960
+ client_key: params.client_key,
1961
+ },
1962
+ data: null,
1963
+ ctx: {
1964
+ logid: '',
1965
+ httpStatusCode: 400,
1966
+ },
1967
+ };
1968
+ }
1969
+ form.append('wasm_symbol_file', fs$1.createReadStream(symbolFilePath), {
1873
1970
  filename: WASM_SYMBOL_FILE_NAME,
1874
1971
  contentType: 'application/octet-stream',
1875
1972
  });
@@ -2303,7 +2400,8 @@ function updateSubpackageConfigSync() {
2303
2400
  */
2304
2401
  filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidMain);
2305
2402
  filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.androidSub);
2306
- filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.ios);
2403
+ filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosMain);
2404
+ filtered.push(WASM_SPLIT_SUBPACKAGE_CONFIG.iosSub);
2307
2405
  // 合并去重:存在则更新 root,不存在则新增
2308
2406
  const map = new Map(filtered.map(s => [s.name, s]));
2309
2407
  gameJson[fieldName] = Array.from(map.values());
@@ -2311,7 +2409,7 @@ function updateSubpackageConfigSync() {
2311
2409
  }
2312
2410
 
2313
2411
  async function downloadAndCompress(opts) {
2314
- const { startDownloadStatus, downloadDoneStatus, startCompressStatus, compressDoneStatus, url, out, } = opts;
2412
+ const { startDownloadStatus, downloadDoneStatus, startCompressStatus, compressDoneStatus, url, out, enableCompress = false, } = opts;
2315
2413
  if (!url)
2316
2414
  return;
2317
2415
  const willDownloadedFileIsBr = url.includes(BR_SUFFIX);
@@ -2332,7 +2430,7 @@ async function downloadAndCompress(opts) {
2332
2430
  await promises.rm(wasmBrOutName);
2333
2431
  throw e;
2334
2432
  }
2335
- if (!willDownloadedFileIsBr) {
2433
+ if (enableCompress) {
2336
2434
  console.log(`compress start: ${path.basename(out)}${BR_SUFFIX}`);
2337
2435
  // 压缩
2338
2436
  wsServer.sendUnitySplitStatus({ status: startCompressStatus });
@@ -2401,7 +2499,7 @@ async function downloadSplited(context) {
2401
2499
  url: context.main_wasm_h5_download_url,
2402
2500
  out: mainIosWasmCodeTempPath,
2403
2501
  })),
2404
- // 下载 ios sub range json
2502
+ // 下载 ios sub js range json
2405
2503
  limit(() => downloadAndCompress({
2406
2504
  startDownloadStatus: 'start_download_ios_range_json',
2407
2505
  downloadDoneStatus: 'download_ios_range_json_done',
@@ -2413,7 +2511,7 @@ async function downloadSplited(context) {
2413
2511
  startDownloadStatus: 'start_download_ios_js_data_br',
2414
2512
  downloadDoneStatus: 'download_ios_js_data_br_done',
2415
2513
  url: context.sub_js_data_download_url,
2416
- out: path.join(subIosDir, 'subjs.data.br'),
2514
+ out: path.join(subIosDir, 'subjs.data'),
2417
2515
  })),
2418
2516
  ]);
2419
2517
  // 复制 split/* 到项目根目录(递归、覆盖)——避免 EISDIR
@@ -2738,22 +2836,57 @@ async function start() {
2738
2836
  /**
2739
2837
  * @description 上传游戏代码到服务器
2740
2838
  */
2839
+ // app.post('/game/upload', async (req, res) => {
2840
+ // /**
2841
+ // * 我要新版本不做开发者选择,直接当前 cwd 作为 entryDir,压缩成 game.zip 进行上传
2842
+ // */
2843
+ // const fileKeys = Object.keys(req.files);
2844
+ // const uploadedFile = req.files[fileKeys[0]];
2845
+ // if (!uploadedFile) {
2846
+ // res.status(400).send({ code: errorCode, data: 'No file uploaded' }); // 使用正确的 HTTP 状态码
2847
+ // return;
2848
+ // }
2849
+ // try {
2850
+ // // 通过 header 获取 desc
2851
+ // const desc = req.headers['ttmg-game-desc'];
2852
+ // // 需要做 decodeURIComponent 处理
2853
+ // const decodedDesc = decodeURIComponent(desc || '--');
2854
+ // // 直接传递需要的信息
2855
+ // const { data, error } = await uploadGameToPlatform({
2856
+ // data: uploadedFile.data, // 这是 Buffer
2857
+ // name: uploadedFile.name, // 这是文件名
2858
+ // clientKey: getClientKey().clientKey,
2859
+ // note: decodedDesc,
2860
+ // appId: store.getState().appId,
2861
+ // sandboxId: store.getState().sandboxId,
2862
+ // });
2863
+ // if (error) {
2864
+ // res.send({ code: errorCode, error });
2865
+ // } else {
2866
+ // res.send({ code: successCode, data });
2867
+ // }
2868
+ // } catch (error) {
2869
+ // // 错误处理可以更具体
2870
+ // let errorMessage = 'An unknown error occurred.';
2871
+ // if (error instanceof Error) {
2872
+ // errorMessage = error.message;
2873
+ // }
2874
+ // // 打印详细错误到服务器日志,方便排查
2875
+ // res.status(500).send({ code: errorCode, data: errorMessage }); // 使用正确的 HTTP 状态码
2876
+ // }
2877
+ // });
2741
2878
  app.post('/game/upload', async (req, res) => {
2742
- const fileKeys = Object.keys(req.files);
2743
- const uploadedFile = req.files[fileKeys[0]];
2744
- if (!uploadedFile) {
2745
- res.status(400).send({ code: errorCode, data: 'No file uploaded' }); // 使用正确的 HTTP 状态码
2746
- return;
2747
- }
2748
2879
  try {
2749
- // 通过 header 获取 desc
2880
+ console.log(`正在打包当前目录: ${process.cwd()} ...`);
2881
+ const gameZipBuffer = await zipCwdToBuffer();
2882
+ // 变成 MB
2883
+ console.log(`打包完成,大小: ${(gameZipBuffer.length / 1024 / 1024).toFixed(2)} MB`);
2750
2884
  const desc = req.headers['ttmg-game-desc'];
2751
- // 需要做 decodeURIComponent 处理
2752
2885
  const decodedDesc = decodeURIComponent(desc || '--');
2753
- // 直接传递需要的信息
2754
2886
  const { data, error } = await uploadGameToPlatform({
2755
- data: uploadedFile.data, // 这是 Buffer
2756
- name: uploadedFile.name, // 这是文件名
2887
+ // @ts-ignore
2888
+ data: gameZipBuffer,
2889
+ name: 'game.zip',
2757
2890
  clientKey: getClientKey().clientKey,
2758
2891
  note: decodedDesc,
2759
2892
  appId: store.getState().appId,
@@ -2767,13 +2900,12 @@ async function start() {
2767
2900
  }
2768
2901
  }
2769
2902
  catch (error) {
2770
- // 错误处理可以更具体
2771
2903
  let errorMessage = 'An unknown error occurred.';
2772
2904
  if (error instanceof Error) {
2773
2905
  errorMessage = error.message;
2774
2906
  }
2775
- // 打印详细错误到服务器日志,方便排查
2776
- res.status(500).send({ code: errorCode, data: errorMessage }); // 使用正确的 HTTP 状态码
2907
+ console.error('Upload failed:', errorMessage);
2908
+ res.status(500).send({ code: errorCode, data: errorMessage });
2777
2909
  }
2778
2910
  });
2779
2911
  app.get('/game/wasm-split-config', (req, res) => {
@@ -2820,6 +2952,7 @@ async function start() {
2820
2952
  wasm_md5: codeMd5,
2821
2953
  wasm_file_path: codePath,
2822
2954
  });
2955
+ console.log('wasm-prepare-end', result);
2823
2956
  if (result.error) {
2824
2957
  res.send({
2825
2958
  code: errorCode,
@@ -3038,6 +3171,31 @@ async function start() {
3038
3171
  });
3039
3172
  }
3040
3173
  });
3174
+ /**
3175
+ *
3176
+ 高鹏
3177
+ 抄一下码下面的话
3178
+ 我试试这个咋样看[看]闵行的是好吃的
3179
+ 可以
3180
+ 下周大哥来 让他别整一群的 就咱们几个吃个串挺好
3181
+ 哪个unity筹备群拉你了哈
3182
+
3183
+ 新知识 30% 等于部分用户
3184
+ 0.3.1-unity.6
3185
+ {
3186
+ "code": 0,
3187
+ "data": {
3188
+ "isSuccess": true
3189
+ },
3190
+ "msg": "download success",
3191
+ "ctx":
3192
+ }
3193
+ 发现个小问题
3194
+ wasmcode1-ios 下面下载缺了一个func_bytes_range.json 以及subjs.data.br 多了一个 br 后缀
3195
+ ​​​
3196
+ Shift + Enter 换行
3197
+
3198
+ */
3041
3199
  app.post('/game/wasm-split-download-result', async (req, res) => {
3042
3200
  const { clientKey, codeMd5, codePath } = req.body;
3043
3201
  console.log('game/wasm-split-download-result-start', req.body);
@@ -3191,7 +3349,7 @@ async function dev() {
3191
3349
  watch();
3192
3350
  }
3193
3351
 
3194
- var version = "0.3.1-unity.8";
3352
+ var version = "0.3.1";
3195
3353
  var pkg = {
3196
3354
  version: version};
3197
3355