@ttmg/cli 0.4.6-vibe-beta.1 → 0.4.6-vibe-beta.3
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/README.md +173 -0
- package/dist/index.js +2241 -218
- package/dist/index.js.map +1 -1
- package/dist/package.json +18 -3
- package/dist/public/assets/{Card-BAsimNoh.js → Card-CN2lxY0Y.js} +1 -1
- package/dist/public/assets/{Detail-rVxbEDgh.js → Detail-CELZZ-4h.js} +1 -1
- package/dist/public/assets/Detail-CELZZ-4h.js.br +0 -0
- package/dist/public/assets/{MonetizationMode-B20TAE9o.js → MonetizationMode-C7DNqium.js} +1 -1
- package/dist/public/assets/{MonetizationModeSummary-CnHqXyco.js → MonetizationModeSummary-DYE7XUq1.js} +1 -1
- package/dist/public/assets/{SectionHeader-Cn3FnOqh.js → SectionHeader-BKc3BWXP.js} +1 -1
- package/dist/public/assets/{baseForm-kBTxn8h8.js → baseForm-I9suIiQn.js} +1 -1
- package/dist/public/assets/baseForm-I9suIiQn.js.br +0 -0
- package/dist/public/assets/{col-VN3Ivd-x.js → col-w9Q-2mgM.js} +1 -1
- package/dist/public/assets/{compass-BabZVjAl.js → compass-D6waJ52O.js} +1 -1
- package/dist/public/assets/{index-DHdJsD10.js → index-8sDAOAcS.js} +1 -1
- package/dist/public/assets/index-8sDAOAcS.js.br +0 -0
- package/dist/public/assets/{index-DK4IvdRl.js → index-B6hY5GdS.js} +1 -1
- package/dist/public/assets/{index-Cl0_vMWE.js → index-BIrFkB6r.js} +1 -1
- package/dist/public/assets/index-BIrFkB6r.js.br +0 -0
- package/dist/public/assets/{index-BjQlJrX7.js → index-BLWgpW64.js} +1 -1
- package/dist/public/assets/{index-odEI7YQF.js → index-BY3eQmfP.js} +1 -1
- package/dist/public/assets/index-BY3eQmfP.js.br +0 -0
- package/dist/public/assets/{index-Bjn0ctEd.js → index-BegU9LBT.js} +2 -2
- package/dist/public/assets/index-BegU9LBT.js.br +0 -0
- package/dist/public/assets/{index-1WjGwqIV.js → index-BqwflQml.js} +1 -1
- package/dist/public/assets/{index-DXDZCuG4.js → index-BrOFdVbv.js} +1 -1
- package/dist/public/assets/{index-BxsB5E3u.js → index-BvPolBSn.js} +1 -1
- package/dist/public/assets/{index-BnOeYQAd.js → index-BwrZjkCc.js} +1 -1
- package/dist/public/assets/{index-CUuX90R9.js → index-CLKKui8G.js} +1 -1
- package/dist/public/assets/{index-Dohibgdp.js → index-Cb4Ja0tZ.js} +1 -1
- package/dist/public/assets/{index-C83QHLmD.js → index-CcUgN29Y.js} +1 -1
- package/dist/public/assets/{index-aSl6Wdrp.js → index-CgXOkYrX.js} +1 -1
- package/dist/public/assets/index-CgXOkYrX.js.br +0 -0
- package/dist/public/assets/{index-DulANwbq.js → index-CsiIrLKY.js} +1 -1
- package/dist/public/assets/{index-Ba-JWe4X.js → index-Csmg5oyu.js} +1 -1
- package/dist/public/assets/{index-DnnfZ7H_.js → index-DDecPuod.js} +1 -1
- package/dist/public/assets/{index-Clq0hTDI.js → index-DDy4PkWT.js} +1 -1
- package/dist/public/assets/{index-DIcz5ciL.js → index-DEQ2hVPA.js} +1 -1
- package/dist/public/assets/index-DEQ2hVPA.js.br +0 -0
- package/dist/public/assets/{index-CpM8t743.js → index-DGkgqtgJ.js} +1 -1
- package/dist/public/assets/{index-QNLvejKK.js → index-FVHabbDB.js} +1 -1
- package/dist/public/assets/{index-CzXMNZL3.js → index-M8jf-YAZ.js} +1 -1
- package/dist/public/assets/{index-BxokHznc.js → index-Q0GC8Irj.js} +1 -1
- package/dist/public/assets/index-Q0GC8Irj.js.br +0 -0
- package/dist/public/assets/{index--6iHQzho.js → index-YVB9ZW00.js} +1 -1
- package/dist/public/assets/{index-O_L1tU2b.js → index-mjEeEQcC.js} +1 -1
- package/dist/public/assets/index-mjEeEQcC.js.br +0 -0
- package/dist/public/assets/{index-BFFsJ47B.js → index-rLkONFpx.js} +1 -1
- package/dist/public/assets/{layers-B4zhx14P.js → layers-Dbwq9AyL.js} +1 -1
- package/dist/public/assets/{list-checks-Br6imjXC.js → list-checks-og1usOZf.js} +1 -1
- package/dist/public/assets/{sparkles-BjIkcbAi.js → sparkles-p-dZbjGb.js} +1 -1
- package/dist/public/assets/{triangle-alert-CgKAc3w5.js → triangle-alert-BZVAEUiL.js} +1 -1
- package/dist/public/assets/{zap-DFHw5liD.js → zap-BAhZUPPa.js} +1 -1
- package/dist/public/index.html +1 -1
- package/dist/scripts/dev-default-smoke.js +67 -0
- package/dist/scripts/dev-game-ready-smoke.js +155 -0
- package/dist/scripts/verify-agent-contract.js +178 -0
- package/dist/scripts/worker.js +28 -22
- package/package.json +18 -3
- package/CHANGELOG.md +0 -285
- package/dist/public/assets/Detail-rVxbEDgh.js.br +0 -0
- package/dist/public/assets/baseForm-kBTxn8h8.js.br +0 -0
- package/dist/public/assets/index-Bjn0ctEd.js.br +0 -0
- package/dist/public/assets/index-BxokHznc.js.br +0 -0
- package/dist/public/assets/index-Cl0_vMWE.js.br +0 -0
- package/dist/public/assets/index-DHdJsD10.js.br +0 -0
- package/dist/public/assets/index-DIcz5ciL.js.br +0 -0
- package/dist/public/assets/index-O_L1tU2b.js.br +0 -0
- package/dist/public/assets/index-aSl6Wdrp.js.br +0 -0
- package/dist/public/assets/index-odEI7YQF.js.br +0 -0
package/dist/index.js
CHANGED
|
@@ -6,18 +6,13 @@ var axios = require('axios');
|
|
|
6
6
|
var inquirer = require('inquirer');
|
|
7
7
|
var chalk = require('chalk');
|
|
8
8
|
var qs = require('qs');
|
|
9
|
+
var crypto = require('crypto');
|
|
9
10
|
var path = require('path');
|
|
10
11
|
var os = require('os');
|
|
11
12
|
var fs = require('fs');
|
|
12
13
|
var handlebars = require('handlebars');
|
|
13
14
|
var esbuild = require('esbuild');
|
|
14
15
|
var worker_threads = require('worker_threads');
|
|
15
|
-
var crypto = require('crypto');
|
|
16
|
-
var require$$0 = require('util');
|
|
17
|
-
var require$$1$2 = require('stream');
|
|
18
|
-
var zlib = require('zlib');
|
|
19
|
-
var require$$3 = require('assert');
|
|
20
|
-
var require$$0$2 = require('buffer');
|
|
21
16
|
var jsdom = require('jsdom');
|
|
22
17
|
var prettier = require('prettier');
|
|
23
18
|
var express = require('express');
|
|
@@ -28,8 +23,12 @@ var WebSocket = require('ws');
|
|
|
28
23
|
var net = require('net');
|
|
29
24
|
var require$$1$1 = require('tls');
|
|
30
25
|
var require$$2 = require('url');
|
|
26
|
+
var require$$3 = require('assert');
|
|
31
27
|
var require$$1 = require('tty');
|
|
28
|
+
var require$$0 = require('util');
|
|
32
29
|
var require$$0$1 = require('events');
|
|
30
|
+
var require$$0$2 = require('buffer');
|
|
31
|
+
var require$$1$2 = require('stream');
|
|
33
32
|
var http = require('http');
|
|
34
33
|
var https = require('https');
|
|
35
34
|
var dns = require('dns');
|
|
@@ -45,7 +44,9 @@ var ttmgWasmtool = require('@anrans001/ttmg-wasmtool');
|
|
|
45
44
|
var crypto$1 = require('node:crypto');
|
|
46
45
|
var fs$1 = require('node:fs');
|
|
47
46
|
var path$1 = require('node:path');
|
|
47
|
+
var zlib = require('zlib');
|
|
48
48
|
var promises = require('fs/promises');
|
|
49
|
+
var child_process = require('child_process');
|
|
49
50
|
|
|
50
51
|
function _interopNamespaceDefault(e) {
|
|
51
52
|
var n = Object.create(null);
|
|
@@ -7199,14 +7200,51 @@ const messages = {
|
|
|
7199
7200
|
'cli.option.dev.client': 'Debug TikTok Mini Games for Client',
|
|
7200
7201
|
'cli.option.dev.h5': 'Debug TikTok Mini Games for Web',
|
|
7201
7202
|
'cli.command.login.desc': 'Login with developer account',
|
|
7203
|
+
'cli.command.login.implicit': 'Login non-interactively and return one JSON result',
|
|
7204
|
+
'cli.command.login.email': 'Developer account email (implicit mode)',
|
|
7205
|
+
'cli.command.login.password': 'Developer account password (implicit mode)',
|
|
7202
7206
|
'cli.command.logout.desc': 'Logout and clear local user data',
|
|
7207
|
+
'cli.command.auth.desc': 'Inspect local authentication state',
|
|
7208
|
+
'cli.command.auth.status.desc': 'Return whether the local developer session is reusable',
|
|
7209
|
+
'cli.command.auth.status.format': 'Output format: text | json',
|
|
7210
|
+
'cli.command.auth.status.requireAuth': 'Exit non-zero when no reusable local session exists',
|
|
7211
|
+
'cli.command.capabilities.desc': 'Describe the CLI features available to developers and Agents',
|
|
7212
|
+
'cli.command.capabilities.format': 'Output format: text | json',
|
|
7203
7213
|
'cli.command.setup.desc': 'Initialize ttmg environment',
|
|
7204
7214
|
'cli.command.setup.lang': 'Language (only supports): en-US | zh-CN',
|
|
7205
7215
|
'cli.command.reset.desc': 'Reset local CLI state',
|
|
7206
7216
|
'cli.command.init.desc': 'Initialize project',
|
|
7207
7217
|
'cli.command.dev.desc': 'Open browser dev environment',
|
|
7208
7218
|
'cli.command.dev.clientKey': 'Client Key of the Mini Game',
|
|
7219
|
+
'cli.command.dev.format': 'Session output format: text | json',
|
|
7220
|
+
'cli.command.dev.events': 'Lifecycle event stream: none | ndjson',
|
|
7221
|
+
'cli.command.dev.noOpen': 'Do not open the DevTool page automatically',
|
|
7222
|
+
'cli.command.dev.evidenceDir': 'Session evidence output directory',
|
|
7223
|
+
'cli.command.dev.timeout': 'Stop a structured session after this many seconds',
|
|
7224
|
+
'cli.command.dev.outputConflict': 'Dev output modes cannot be combined. Use either --format json for one manifest or --events ndjson for a lifecycle event stream.',
|
|
7225
|
+
'cli.command.dev.session.desc': 'Inspect and stop local DevTool sessions',
|
|
7226
|
+
'cli.command.dev.session.inspect': 'Inspect one local DevTool session',
|
|
7227
|
+
'cli.command.dev.session.list': 'List local DevTool sessions',
|
|
7228
|
+
'cli.command.dev.session.events': 'Read new events from a DevTool session',
|
|
7229
|
+
'cli.command.dev.session.stop': 'Safely stop one local DevTool session',
|
|
7230
|
+
'cli.command.dev.session.dir': 'Session root directory (default: .ttmg/sessions)',
|
|
7231
|
+
'cli.command.dev.session.format': 'Output format: text | json',
|
|
7232
|
+
'cli.command.dev.session.after': 'Return events with seq greater than this',
|
|
7233
|
+
'cli.command.dev.session.limit': 'Maximum events to return (1-1000)',
|
|
7234
|
+
'cli.command.check.desc': 'Check the Mini Game project and return actionable diagnostics',
|
|
7235
|
+
'cli.command.check.dir': 'Mini Game project directory (default: current directory)',
|
|
7236
|
+
'cli.command.check.format': 'Output format: text | json',
|
|
7237
|
+
'cli.command.check.includePassed': 'Include passed checks in structured diagnostics',
|
|
7238
|
+
'cli.command.check.failOn': 'Non-zero exit threshold: error | warning',
|
|
7239
|
+
'cli.command.check.ai': 'Add static Agent-debug readiness checks',
|
|
7240
|
+
'cli.command.doctor.desc': 'Diagnose local CLI, project, auth, and network readiness',
|
|
7241
|
+
'cli.command.doctor.dir': 'Mini Game project directory (default: current directory)',
|
|
7242
|
+
'cli.command.doctor.format': 'Output format: text | json',
|
|
7243
|
+
'cli.command.doctor.failOn': 'Non-zero exit threshold: error | warning',
|
|
7209
7244
|
'cli.command.build.desc': 'Bundle project',
|
|
7245
|
+
'cli.command.build.dir': 'Mini Game project directory (structured build only)',
|
|
7246
|
+
'cli.command.build.format': 'Output format: text | json',
|
|
7247
|
+
'cli.command.build.includePassed': 'Include passed checks in the structured build result',
|
|
7210
7248
|
'cli.command.config.desc': 'Manage configuration',
|
|
7211
7249
|
'cli.command.config.set.desc': 'Set a configuration value',
|
|
7212
7250
|
'cli.command.config.get.desc': 'Get a configuration value',
|
|
@@ -7216,7 +7254,7 @@ const messages = {
|
|
|
7216
7254
|
'cli.command.upload.note': 'Note for the upload',
|
|
7217
7255
|
'cli.command.upload.dir': 'Game project directory (default: current directory)',
|
|
7218
7256
|
'cli.command.upload.preview': 'Wait for platform processing and output the preview payload',
|
|
7219
|
-
'cli.command.upload.format': 'Output format: text | ndjson',
|
|
7257
|
+
'cli.command.upload.format': 'Output format: text | json | ndjson',
|
|
7220
7258
|
'cli.command.testAccount.desc': 'Manage Mini Game test-account access',
|
|
7221
7259
|
'cli.command.testAccount.add.desc': 'Generate the authorization entry for adding a TikTok test account',
|
|
7222
7260
|
'cli.command.testAccount.clientKey': 'Client Key of the Mini Game',
|
|
@@ -7263,8 +7301,13 @@ const messages = {
|
|
|
7263
7301
|
'login.error.withCode': 'Login failed: {message}, error_code: {code}',
|
|
7264
7302
|
'login.error.withMessage': 'Login failed: {message}',
|
|
7265
7303
|
'login.error.noUserId': 'Login failed. No user_id in response.',
|
|
7304
|
+
'login.error.noSessionCookie': 'Login failed. No reusable session cookie was returned.',
|
|
7305
|
+
'login.error.implicitCredentialsRequired': 'Implicit login requires --email and --password, or TTMG_LOGIN_EMAIL and TTMG_LOGIN_PASSWORD.',
|
|
7266
7306
|
'login.warning.proxyIssue': 'Login failed. Please refer to the proxy setup doc:\nhttps://bytedance.larkoffice.com/wiki/PeIawT4M4ia9R8kYyLMcjwHknOg',
|
|
7267
7307
|
'login.error.connectService': 'Failed to connect to login service',
|
|
7308
|
+
'auth.status.authenticated': 'Authenticated as {account}.',
|
|
7309
|
+
'auth.status.notAuthenticated': 'No reusable local session. Run `ttmg login` first.',
|
|
7310
|
+
'auth.error.invalidFormat': 'Unsupported auth status output format: {format}. Use text or json.',
|
|
7268
7311
|
'login.error.networkBlocked': 'Network connection failed. Please refer to the proxy setup doc:\nhttps://bytedance.larkoffice.com/wiki/PeIawT4M4ia9R8kYyLMcjwHknOg',
|
|
7269
7312
|
'logout.spinner.loggingOut': 'Logging out...',
|
|
7270
7313
|
'logout.success': 'Logged out successfully!',
|
|
@@ -7292,6 +7335,21 @@ const messages = {
|
|
|
7292
7335
|
'native.server.failedAfterRetries': 'Failed to start server after trying {retries} ports.',
|
|
7293
7336
|
'native.check.notWorkspace': 'Current directory is not a Mini Game project entry directory, please enter the game project root directory for debugging',
|
|
7294
7337
|
'native.check.subpackagesCaseError': "Error: 'subPackages' (camelCase) is found in {file}. Please use 'subpackages' (all lowercase) instead.",
|
|
7338
|
+
'check.summary': 'Check {status}: {passed} passed, {warnings} warnings, {errors} errors',
|
|
7339
|
+
'check.error.invalidFormat': 'Unsupported check output format: {format}. Use text or json.',
|
|
7340
|
+
'check.error.invalidFailOn': 'Unsupported check failure threshold: {failOn}. Use error or warning.',
|
|
7341
|
+
'check.error.dirNotExist': 'The specified project directory does not exist: {dir}',
|
|
7342
|
+
'check.ai.gameReady.found': 'Detected reportScene({ sceneId: 9999 }) as a static GAME_READY checkpoint.',
|
|
7343
|
+
'check.ai.gameReady.missing': 'No statically detectable reportScene({ sceneId: 9999 }) GAME_READY checkpoint was found.',
|
|
7344
|
+
'check.ai.gameReady.help': 'Call reportScene({ sceneId: 9999 }) when the game becomes interactive, then verify it on a real device. Static detection does not prove runtime reachability.',
|
|
7345
|
+
'build.error.invalidFormat': 'Unsupported build output format: {format}. Use text or json.',
|
|
7346
|
+
'build.error.dirNotExist': 'The specified build directory does not exist: {dir}',
|
|
7347
|
+
'build.error.failed': 'Mini Game build failed.',
|
|
7348
|
+
'dev.session.error.invalidId': 'Invalid DevTool session ID: {sessionId}',
|
|
7349
|
+
'dev.session.error.notFound': 'DevTool session not found: {sessionId}',
|
|
7350
|
+
'dev.session.error.manifestMismatch': 'The session manifest ID does not match the requested session.',
|
|
7351
|
+
'dev.session.error.unverifiedProcess': 'Refusing to stop an unverified process. The recorded PID is not a ttmg dev process.',
|
|
7352
|
+
'dev.session.error.invalidCursor': 'Invalid event cursor. after must be >= 0 and limit must be between 1 and 1000.',
|
|
7295
7353
|
'native.compile.watching': '🔔 Watching game assets for local debugging...',
|
|
7296
7354
|
'native.compile.compiling': '🚀 Compiling game assets for local debugging...',
|
|
7297
7355
|
'native.compile.success': '✔ Game resources compiled successfully!',
|
|
@@ -7342,6 +7400,7 @@ const messages = {
|
|
|
7342
7400
|
'upload.error.assetFailed': 'The uploaded asset failed platform processing (status: {status}).',
|
|
7343
7401
|
'upload.error.assetTimeout': 'Timed out waiting for platform asset processing.',
|
|
7344
7402
|
'upload.error.previewTimeout': 'Timed out waiting for the preview URL.',
|
|
7403
|
+
'upload.error.previewQueryFailed': 'Failed to query the preview URL from the platform.',
|
|
7345
7404
|
'testAccount.success': 'Test-account authorization entry generated.',
|
|
7346
7405
|
'testAccount.project': 'Project: {name} ({clientKey})',
|
|
7347
7406
|
'testAccount.authorizationUrl': 'Authorization URL: {url}',
|
|
@@ -7353,6 +7412,7 @@ const messages = {
|
|
|
7353
7412
|
'testAccount.error.notLoggedIn': 'Not logged in. Please run `ttmg login` first.',
|
|
7354
7413
|
'testAccount.error.gameInfoFailed': 'Failed to fetch Mini Game project information.',
|
|
7355
7414
|
'testAccount.error.noAppId': 'The platform response did not include a Mini Game ID.',
|
|
7415
|
+
'testAccount.error.qrWriteFailed': 'Failed to write the QR code file.',
|
|
7356
7416
|
},
|
|
7357
7417
|
'zh-CN': {
|
|
7358
7418
|
'cli.description': 'TikTok 小游戏命令行工具',
|
|
@@ -7361,14 +7421,51 @@ const messages = {
|
|
|
7361
7421
|
'cli.option.dev.client': '客户端调试 TikTok 小游戏',
|
|
7362
7422
|
'cli.option.dev.h5': 'Web 调试 TikTok 小游戏',
|
|
7363
7423
|
'cli.command.login.desc': '使用开发者账号登录',
|
|
7424
|
+
'cli.command.login.implicit': '非交互登录并返回一个 JSON 结果',
|
|
7425
|
+
'cli.command.login.email': '开发者账号邮箱(implicit 模式)',
|
|
7426
|
+
'cli.command.login.password': '开发者账号密码(implicit 模式)',
|
|
7364
7427
|
'cli.command.logout.desc': '退出登录并清除本地用户信息',
|
|
7428
|
+
'cli.command.auth.desc': '查看本地登录状态',
|
|
7429
|
+
'cli.command.auth.status.desc': '返回本地开发者会话是否可复用',
|
|
7430
|
+
'cli.command.auth.status.format': '输出格式:text | json',
|
|
7431
|
+
'cli.command.auth.status.requireAuth': '没有可复用本地会话时以非零状态退出',
|
|
7432
|
+
'cli.command.capabilities.desc': '返回开发者和 Agent 可使用的 CLI 能力',
|
|
7433
|
+
'cli.command.capabilities.format': '输出格式:text | json',
|
|
7365
7434
|
'cli.command.setup.desc': '初始化 ttmg 环境',
|
|
7366
7435
|
'cli.command.setup.lang': '语言(仅支持):en-US | zh-CN',
|
|
7367
7436
|
'cli.command.reset.desc': '重置本地 CLI 状态',
|
|
7368
7437
|
'cli.command.init.desc': '初始化项目',
|
|
7369
7438
|
'cli.command.dev.desc': '打开浏览器调试环境',
|
|
7370
7439
|
'cli.command.dev.clientKey': '小游戏的 Client Key',
|
|
7440
|
+
'cli.command.dev.format': '会话输出格式:text | json',
|
|
7441
|
+
'cli.command.dev.events': '生命周期事件流:none | ndjson',
|
|
7442
|
+
'cli.command.dev.noOpen': '不自动打开 DevTool 页面',
|
|
7443
|
+
'cli.command.dev.evidenceDir': '会话证据输出目录',
|
|
7444
|
+
'cli.command.dev.timeout': '在指定秒数后停止结构化调试会话',
|
|
7445
|
+
'cli.command.dev.outputConflict': 'Dev 输出模式不能同时使用:请用 --format json 输出一份 manifest,或用 --events ndjson 输出生命周期事件流。',
|
|
7446
|
+
'cli.command.dev.session.desc': '查询和停止本地 DevTool 会话',
|
|
7447
|
+
'cli.command.dev.session.inspect': '查询一个本地 DevTool 会话',
|
|
7448
|
+
'cli.command.dev.session.list': '列出本地 DevTool 会话',
|
|
7449
|
+
'cli.command.dev.session.events': '增量读取 DevTool 会话事件',
|
|
7450
|
+
'cli.command.dev.session.stop': '安全停止一个本地 DevTool 会话',
|
|
7451
|
+
'cli.command.dev.session.dir': '会话根目录(默认:.ttmg/sessions)',
|
|
7452
|
+
'cli.command.dev.session.format': '输出格式:text | json',
|
|
7453
|
+
'cli.command.dev.session.after': '只返回 seq 大于该值的事件',
|
|
7454
|
+
'cli.command.dev.session.limit': '最多返回的事件数(1-1000)',
|
|
7455
|
+
'cli.command.check.desc': '检查小游戏项目并返回可执行的诊断结果',
|
|
7456
|
+
'cli.command.check.dir': '小游戏项目目录(默认为当前目录)',
|
|
7457
|
+
'cli.command.check.format': '输出格式:text | json',
|
|
7458
|
+
'cli.command.check.includePassed': '在结构化诊断中包含已通过的检查项',
|
|
7459
|
+
'cli.command.check.failOn': '非零退出阈值:error | warning',
|
|
7460
|
+
'cli.command.check.ai': '增加 Agent 调试准备度静态检查',
|
|
7461
|
+
'cli.command.doctor.desc': '诊断本地 CLI、工程、登录和网络准备状态',
|
|
7462
|
+
'cli.command.doctor.dir': '小游戏项目目录(默认为当前目录)',
|
|
7463
|
+
'cli.command.doctor.format': '输出格式:text | json',
|
|
7464
|
+
'cli.command.doctor.failOn': '非零退出阈值:error | warning',
|
|
7371
7465
|
'cli.command.build.desc': '打包项目',
|
|
7466
|
+
'cli.command.build.dir': '小游戏项目目录(仅结构化构建使用)',
|
|
7467
|
+
'cli.command.build.format': '输出格式:text | json',
|
|
7468
|
+
'cli.command.build.includePassed': '在结构化构建结果中包含已通过的检查项',
|
|
7372
7469
|
'cli.command.config.desc': '管理配置',
|
|
7373
7470
|
'cli.command.config.set.desc': '设置配置项',
|
|
7374
7471
|
'cli.command.config.get.desc': '获取配置项',
|
|
@@ -7378,7 +7475,7 @@ const messages = {
|
|
|
7378
7475
|
'cli.command.upload.note': '上传备注',
|
|
7379
7476
|
'cli.command.upload.dir': '小游戏项目根目录(默认为当前目录)',
|
|
7380
7477
|
'cli.command.upload.preview': '等待平台处理并输出预览二维码原始内容',
|
|
7381
|
-
'cli.command.upload.format': '输出格式:text | ndjson',
|
|
7478
|
+
'cli.command.upload.format': '输出格式:text | json | ndjson',
|
|
7382
7479
|
'cli.command.testAccount.desc': '管理小游戏测试账号访问',
|
|
7383
7480
|
'cli.command.testAccount.add.desc': '生成添加 TikTok 测试账号的授权入口',
|
|
7384
7481
|
'cli.command.testAccount.clientKey': '小游戏的 Client Key',
|
|
@@ -7425,8 +7522,13 @@ const messages = {
|
|
|
7425
7522
|
'login.error.withCode': '登录失败:{message},错误码:{code}',
|
|
7426
7523
|
'login.error.withMessage': '登录失败:{message}',
|
|
7427
7524
|
'login.error.noUserId': '登录失败,响应中未返回 user_id。',
|
|
7525
|
+
'login.error.noSessionCookie': '登录失败,响应中没有可复用的会话 Cookie。',
|
|
7526
|
+
'login.error.implicitCredentialsRequired': '隐式登录需要提供 --email 和 --password,或设置 TTMG_LOGIN_EMAIL 和 TTMG_LOGIN_PASSWORD。',
|
|
7428
7527
|
'login.warning.proxyIssue': '登录失败,请参考代理设置文档:\nhttps://bytedance.larkoffice.com/wiki/PeIawT4M4ia9R8kYyLMcjwHknOg',
|
|
7429
7528
|
'login.error.connectService': '连接登录服务失败',
|
|
7529
|
+
'auth.status.authenticated': '当前登录账号:{account}。',
|
|
7530
|
+
'auth.status.notAuthenticated': '没有可复用本地会话,请先执行 `ttmg login`。',
|
|
7531
|
+
'auth.error.invalidFormat': '不支持的登录状态输出格式:{format},请使用 text 或 json。',
|
|
7430
7532
|
'login.error.networkBlocked': '网络连接失败,请参考代理设置文档:\nhttps://bytedance.larkoffice.com/wiki/PeIawT4M4ia9R8kYyLMcjwHknOg',
|
|
7431
7533
|
'logout.spinner.loggingOut': '正在退出登录...',
|
|
7432
7534
|
'logout.success': '退出登录成功!',
|
|
@@ -7454,6 +7556,21 @@ const messages = {
|
|
|
7454
7556
|
'native.server.failedAfterRetries': '连续尝试 {retries} 个端口后,服务启动失败。',
|
|
7455
7557
|
'native.check.notWorkspace': '当前目录不是小游戏工程入口目录,请进入游戏项目根目录后再进行调试',
|
|
7456
7558
|
'native.check.subpackagesCaseError': "错误:在 {file} 中发现 'subPackages'(驼峰),请改为全小写 'subpackages'。",
|
|
7559
|
+
'check.summary': '检查状态 {status}:{passed} 项通过,{warnings} 项警告,{errors} 项错误',
|
|
7560
|
+
'check.error.invalidFormat': '不支持的检查输出格式:{format},请使用 text 或 json。',
|
|
7561
|
+
'check.error.invalidFailOn': '不支持的失败阈值:{failOn},请使用 error 或 warning。',
|
|
7562
|
+
'check.error.dirNotExist': '指定的项目目录不存在:{dir}',
|
|
7563
|
+
'check.ai.gameReady.found': '已静态检出 reportScene({ sceneId: 9999 }) GAME_READY 检查点。',
|
|
7564
|
+
'check.ai.gameReady.missing': '未静态检出 reportScene({ sceneId: 9999 }) GAME_READY 检查点。',
|
|
7565
|
+
'check.ai.gameReady.help': '请在游戏真正可交互时调用 reportScene({ sceneId: 9999 }),并继续完成真机验证;静态检出不代表运行时一定能够到达。',
|
|
7566
|
+
'build.error.invalidFormat': '不支持的构建输出格式:{format},请使用 text 或 json。',
|
|
7567
|
+
'build.error.dirNotExist': '指定的构建目录不存在:{dir}',
|
|
7568
|
+
'build.error.failed': '小游戏构建失败。',
|
|
7569
|
+
'dev.session.error.invalidId': '无效的 DevTool 会话 ID:{sessionId}',
|
|
7570
|
+
'dev.session.error.notFound': '未找到 DevTool 会话:{sessionId}',
|
|
7571
|
+
'dev.session.error.manifestMismatch': '会话 manifest ID 与请求的会话不一致。',
|
|
7572
|
+
'dev.session.error.unverifiedProcess': '拒绝停止未验证进程:记录的 PID 不是 ttmg dev 进程。',
|
|
7573
|
+
'dev.session.error.invalidCursor': '无效的事件游标:after 必须大于等于 0,limit 必须在 1 到 1000 之间。',
|
|
7457
7574
|
'native.compile.watching': '🔔 正在监听游戏资源变更(本地调试)...',
|
|
7458
7575
|
'native.compile.compiling': '🚀 正在编译游戏资源(本地调试)...',
|
|
7459
7576
|
'native.compile.success': '✔ 游戏资源编译成功!',
|
|
@@ -7504,6 +7621,7 @@ const messages = {
|
|
|
7504
7621
|
'upload.error.assetFailed': '上传版本处理失败(状态:{status})。',
|
|
7505
7622
|
'upload.error.assetTimeout': '等待平台处理上传版本超时。',
|
|
7506
7623
|
'upload.error.previewTimeout': '等待平台生成预览地址超时。',
|
|
7624
|
+
'upload.error.previewQueryFailed': '查询平台预览地址失败。',
|
|
7507
7625
|
'testAccount.success': '测试账号授权入口已生成。',
|
|
7508
7626
|
'testAccount.project': '项目:{name}({clientKey})',
|
|
7509
7627
|
'testAccount.authorizationUrl': '授权地址:{url}',
|
|
@@ -7515,6 +7633,7 @@ const messages = {
|
|
|
7515
7633
|
'testAccount.error.notLoggedIn': '未登录,请先执行 `ttmg login`。',
|
|
7516
7634
|
'testAccount.error.gameInfoFailed': '读取小游戏项目信息失败。',
|
|
7517
7635
|
'testAccount.error.noAppId': '平台响应中没有小游戏 ID。',
|
|
7636
|
+
'testAccount.error.qrWriteFailed': '写入二维码文件失败。',
|
|
7518
7637
|
},
|
|
7519
7638
|
};
|
|
7520
7639
|
|
|
@@ -7659,155 +7778,226 @@ function ensureDirSync(dirPath) {
|
|
|
7659
7778
|
}
|
|
7660
7779
|
}
|
|
7661
7780
|
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
}
|
|
7669
|
-
let spinner$1;
|
|
7781
|
+
const defaultDependencies$7 = {
|
|
7782
|
+
post: axios.post,
|
|
7783
|
+
getProxyConfig: getAxiosProxyConfig,
|
|
7784
|
+
setTTMGRC,
|
|
7785
|
+
now: () => Date.now(),
|
|
7786
|
+
operationId: () => `login_${crypto.randomUUID()}`,
|
|
7787
|
+
};
|
|
7670
7788
|
const LOGIN_TT4D = 'https://developers.tiktok.com/passport/web/email/login';
|
|
7671
7789
|
const params = {
|
|
7672
7790
|
aid: '2471',
|
|
7673
7791
|
account_sdk_source: 'web',
|
|
7674
7792
|
sdk_version: '2.1.6-tiktok',
|
|
7675
7793
|
};
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7794
|
+
class LoginCommandError extends Error {
|
|
7795
|
+
constructor(code, message) {
|
|
7796
|
+
super(message);
|
|
7797
|
+
this.code = code;
|
|
7798
|
+
this.name = 'LoginCommandError';
|
|
7799
|
+
}
|
|
7800
|
+
}
|
|
7801
|
+
function maskEmail(email) {
|
|
7802
|
+
return email.replace(/(.{2}).*(@.*)/, '$1***$2');
|
|
7803
|
+
}
|
|
7804
|
+
function createFailedResult({ operationId, email, errorCode, error, now, }) {
|
|
7805
|
+
return {
|
|
7806
|
+
schemaVersion: '1',
|
|
7807
|
+
operationId,
|
|
7808
|
+
ts: now(),
|
|
7809
|
+
operation: 'login',
|
|
7810
|
+
status: 'failed',
|
|
7811
|
+
authMethod: 'email_password',
|
|
7812
|
+
email,
|
|
7813
|
+
userId: '',
|
|
7814
|
+
credentialStore: 'ttmgrc',
|
|
7815
|
+
cookieStored: false,
|
|
7816
|
+
errorCode,
|
|
7817
|
+
error,
|
|
7683
7818
|
};
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
const
|
|
7688
|
-
{
|
|
7689
|
-
type: 'input',
|
|
7690
|
-
name: 'email',
|
|
7691
|
-
message: t('login.email.prompt'),
|
|
7692
|
-
validate: input => {
|
|
7693
|
-
if (!input) {
|
|
7694
|
-
return t('login.email.required');
|
|
7695
|
-
}
|
|
7696
|
-
else {
|
|
7697
|
-
if (!/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/.test(input)) {
|
|
7698
|
-
return t('login.email.invalid');
|
|
7699
|
-
}
|
|
7700
|
-
}
|
|
7701
|
-
return true;
|
|
7702
|
-
},
|
|
7703
|
-
},
|
|
7704
|
-
{
|
|
7705
|
-
type: 'password',
|
|
7706
|
-
name: 'password',
|
|
7707
|
-
message: t('login.password.prompt'),
|
|
7708
|
-
mask: '*',
|
|
7709
|
-
validate: input => {
|
|
7710
|
-
if (!input) {
|
|
7711
|
-
return t('login.password.required');
|
|
7712
|
-
}
|
|
7713
|
-
return true;
|
|
7714
|
-
},
|
|
7715
|
-
},
|
|
7716
|
-
]);
|
|
7819
|
+
}
|
|
7820
|
+
async function authenticateWithPassword(credentials, dependencies = {}) {
|
|
7821
|
+
const deps = { ...defaultDependencies$7, ...dependencies };
|
|
7822
|
+
const operationId = deps.operationId();
|
|
7717
7823
|
const url = LOGIN_TT4D + '?' + new URLSearchParams(params);
|
|
7718
|
-
log('Request URL', url);
|
|
7719
|
-
log('Request params', { ...params, email: email.replace(/(.{2}).*(@.*)/, '$1***$2') });
|
|
7720
7824
|
const headers = {
|
|
7721
7825
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
7722
7826
|
Accept: '*/*',
|
|
7723
7827
|
'Accept-Encoding': 'gzip, deflate, br',
|
|
7724
7828
|
Origin: 'https://developers.tiktok.com',
|
|
7725
|
-
Referer:
|
|
7829
|
+
Referer: LOGIN_TT4D,
|
|
7726
7830
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36',
|
|
7727
7831
|
};
|
|
7728
|
-
|
|
7729
|
-
spinner$1 = ora.default({
|
|
7730
|
-
text: chalk.bold.cyan(t('login.spinner.loggingIn')),
|
|
7731
|
-
spinner: 'dots',
|
|
7732
|
-
});
|
|
7733
|
-
spinner$1.start();
|
|
7734
|
-
const data = qs.stringify({
|
|
7735
|
-
email,
|
|
7736
|
-
password,
|
|
7737
|
-
mix_mode: '1',
|
|
7738
|
-
fixed_mix_mode: '1',
|
|
7739
|
-
});
|
|
7832
|
+
let response;
|
|
7740
7833
|
try {
|
|
7741
|
-
|
|
7742
|
-
|
|
7834
|
+
response = await deps.post(url, qs.stringify({
|
|
7835
|
+
email: credentials.email,
|
|
7836
|
+
password: credentials.password,
|
|
7837
|
+
mix_mode: '1',
|
|
7838
|
+
fixed_mix_mode: '1',
|
|
7839
|
+
}), {
|
|
7743
7840
|
headers,
|
|
7744
7841
|
maxRedirects: 20,
|
|
7745
7842
|
timeout: 30000,
|
|
7746
|
-
...
|
|
7843
|
+
...deps.getProxyConfig(),
|
|
7747
7844
|
});
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7845
|
+
}
|
|
7846
|
+
catch {
|
|
7847
|
+
throw new LoginCommandError('NETWORK_ERROR', t('login.error.networkBlocked'));
|
|
7848
|
+
}
|
|
7849
|
+
const userId = response?.data?.data?.user_id;
|
|
7850
|
+
if (!userId) {
|
|
7851
|
+
const errCode = response?.data?.data?.error_code;
|
|
7852
|
+
const errMsg = response?.data?.data?.description;
|
|
7853
|
+
const statusText = response?.statusText ?? '';
|
|
7854
|
+
const looksLikeProxyResponse = statusText === 'Connection established' ||
|
|
7855
|
+
(response?.status === 200 &&
|
|
7856
|
+
(response?.data == null ||
|
|
7857
|
+
typeof response.data !== 'object' ||
|
|
7858
|
+
!('data' in response.data)));
|
|
7859
|
+
if (looksLikeProxyResponse) {
|
|
7860
|
+
throw new LoginCommandError('PROXY_RESPONSE', t('login.warning.proxyIssue'));
|
|
7861
|
+
}
|
|
7862
|
+
if (errCode) {
|
|
7863
|
+
throw new LoginCommandError(`PLATFORM_${String(errCode)}`, t('login.error.withCode', {
|
|
7864
|
+
message: String(errMsg ?? ''),
|
|
7865
|
+
code: String(errCode),
|
|
7866
|
+
}));
|
|
7867
|
+
}
|
|
7868
|
+
if (errMsg) {
|
|
7869
|
+
throw new LoginCommandError('PLATFORM_REJECTED', t('login.error.withMessage', { message: String(errMsg) }));
|
|
7870
|
+
}
|
|
7871
|
+
throw new LoginCommandError('INVALID_RESPONSE', t('login.error.noUserId'));
|
|
7872
|
+
}
|
|
7873
|
+
const setCookies = response?.headers['set-cookie'];
|
|
7874
|
+
const cookie = buildCookieHeaderFromSetCookies(setCookies);
|
|
7875
|
+
if (!cookie) {
|
|
7876
|
+
throw new LoginCommandError('COOKIE_MISSING', t('login.error.noSessionCookie'));
|
|
7877
|
+
}
|
|
7878
|
+
deps.setTTMGRC({
|
|
7879
|
+
email: credentials.email,
|
|
7880
|
+
user_id: String(userId),
|
|
7881
|
+
cookie,
|
|
7882
|
+
});
|
|
7883
|
+
return {
|
|
7884
|
+
schemaVersion: '1',
|
|
7885
|
+
operationId,
|
|
7886
|
+
ts: deps.now(),
|
|
7887
|
+
operation: 'login',
|
|
7888
|
+
status: 'authenticated',
|
|
7889
|
+
authMethod: 'email_password',
|
|
7890
|
+
email: credentials.email,
|
|
7891
|
+
userId: String(userId),
|
|
7892
|
+
credentialStore: 'ttmgrc',
|
|
7893
|
+
cookieStored: true,
|
|
7894
|
+
errorCode: null,
|
|
7895
|
+
error: null,
|
|
7896
|
+
};
|
|
7897
|
+
}
|
|
7898
|
+
async function login(options = {}, dependencies = {}) {
|
|
7899
|
+
const deps = { ...defaultDependencies$7, ...dependencies };
|
|
7900
|
+
const format = options.implicit ? 'json' : 'text';
|
|
7901
|
+
let email = '';
|
|
7902
|
+
const operationId = deps.operationId();
|
|
7903
|
+
const verbose = options.verbose === true;
|
|
7904
|
+
const debug = (message, data) => {
|
|
7905
|
+
if (!verbose)
|
|
7906
|
+
return;
|
|
7907
|
+
const args = [chalk.gray('[ttmg login]'), message];
|
|
7908
|
+
if (data !== undefined)
|
|
7909
|
+
args.push(data);
|
|
7910
|
+
if (format === 'json')
|
|
7911
|
+
console.error(...args);
|
|
7912
|
+
else
|
|
7913
|
+
console.log(...args);
|
|
7914
|
+
};
|
|
7915
|
+
try {
|
|
7916
|
+
if (verbose)
|
|
7917
|
+
debug(t('login.verbose.enabled'));
|
|
7918
|
+
if (format === 'text')
|
|
7919
|
+
console.log(chalk.yellowBright(t('login.note')));
|
|
7920
|
+
let password = '';
|
|
7921
|
+
if (options.implicit) {
|
|
7922
|
+
email = (options.email || process.env.TTMG_LOGIN_EMAIL || '').trim();
|
|
7923
|
+
password = options.password || process.env.TTMG_LOGIN_PASSWORD || '';
|
|
7924
|
+
if (!email || !password) {
|
|
7925
|
+
throw new LoginCommandError('CREDENTIALS_REQUIRED', t('login.error.implicitCredentialsRequired'));
|
|
7787
7926
|
}
|
|
7788
|
-
spinner$1.stop();
|
|
7789
|
-
process.exit(1);
|
|
7790
7927
|
}
|
|
7791
7928
|
else {
|
|
7792
|
-
const
|
|
7793
|
-
const
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7929
|
+
const prompt = inquirer.createPromptModule();
|
|
7930
|
+
const answers = await prompt([
|
|
7931
|
+
{
|
|
7932
|
+
type: 'input',
|
|
7933
|
+
name: 'email',
|
|
7934
|
+
message: t('login.email.prompt'),
|
|
7935
|
+
validate: input => {
|
|
7936
|
+
if (!input)
|
|
7937
|
+
return t('login.email.required');
|
|
7938
|
+
if (!/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,}$/.test(input)) {
|
|
7939
|
+
return t('login.email.invalid');
|
|
7940
|
+
}
|
|
7941
|
+
return true;
|
|
7942
|
+
},
|
|
7943
|
+
},
|
|
7944
|
+
{
|
|
7945
|
+
type: 'password',
|
|
7946
|
+
name: 'password',
|
|
7947
|
+
message: t('login.password.prompt'),
|
|
7948
|
+
mask: '*',
|
|
7949
|
+
validate: input => (input ? true : t('login.password.required')),
|
|
7950
|
+
},
|
|
7951
|
+
]);
|
|
7952
|
+
email = answers.email;
|
|
7953
|
+
password = answers.password;
|
|
7954
|
+
}
|
|
7955
|
+
debug('Request', { email: maskEmail(email) });
|
|
7956
|
+
const ora = await import('ora');
|
|
7957
|
+
const spinner = format === 'text'
|
|
7958
|
+
? ora
|
|
7959
|
+
.default({
|
|
7960
|
+
text: chalk.bold.cyan(t('login.spinner.loggingIn')),
|
|
7961
|
+
spinner: 'dots',
|
|
7962
|
+
})
|
|
7963
|
+
.start()
|
|
7964
|
+
: null;
|
|
7965
|
+
try {
|
|
7966
|
+
const result = await authenticateWithPassword({ email, password }, { ...dependencies, operationId: () => operationId });
|
|
7967
|
+
if (format === 'json')
|
|
7968
|
+
console.log(JSON.stringify(result));
|
|
7969
|
+
else
|
|
7970
|
+
spinner?.succeed(chalk.bold.green(t('login.success')));
|
|
7971
|
+
return result;
|
|
7972
|
+
}
|
|
7973
|
+
catch (error) {
|
|
7974
|
+
spinner?.fail(chalk.red(t('login.failed')));
|
|
7975
|
+
throw error;
|
|
7804
7976
|
}
|
|
7805
7977
|
}
|
|
7806
7978
|
catch (error) {
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7979
|
+
const commandError = error instanceof LoginCommandError
|
|
7980
|
+
? error
|
|
7981
|
+
: new LoginCommandError('UNKNOWN_ERROR', error?.message || String(error));
|
|
7982
|
+
const failedResult = createFailedResult({
|
|
7983
|
+
operationId,
|
|
7984
|
+
email,
|
|
7985
|
+
errorCode: commandError.code,
|
|
7986
|
+
error: commandError.message,
|
|
7987
|
+
now: deps.now,
|
|
7988
|
+
});
|
|
7989
|
+
if (format === 'json') {
|
|
7990
|
+
console.log(JSON.stringify(failedResult));
|
|
7991
|
+
}
|
|
7992
|
+
else {
|
|
7993
|
+
const print = commandError.code === 'PROXY_RESPONSE' ? console.warn : console.error;
|
|
7994
|
+
const prefix = commandError.code === 'PROXY_RESPONSE'
|
|
7995
|
+
? chalk.yellow(t('login.warning.prefix'))
|
|
7996
|
+
: chalk.red(t('login.error.prefix'));
|
|
7997
|
+
print(prefix + commandError.message);
|
|
7998
|
+
}
|
|
7999
|
+
process.exitCode = 1;
|
|
8000
|
+
return failedResult;
|
|
7811
8001
|
}
|
|
7812
8002
|
}
|
|
7813
8003
|
|
|
@@ -8038,6 +8228,9 @@ const zipCwdToBuffer = (customIgnores = [], targetDir = process.cwd()) => {
|
|
|
8038
8228
|
const defaultIgnores = [
|
|
8039
8229
|
'node_modules/**',
|
|
8040
8230
|
'.git/**',
|
|
8231
|
+
'.agent/**',
|
|
8232
|
+
'.claude/**',
|
|
8233
|
+
'.trae/**',
|
|
8041
8234
|
'.DS_Store',
|
|
8042
8235
|
ttmgPack.TTMG_TEMP_DIR,
|
|
8043
8236
|
`${ttmgPack.TTMG_TEMP_DIR}/**`,
|
|
@@ -8059,7 +8252,7 @@ const zipCwdToBuffer = (customIgnores = [], targetDir = process.cwd()) => {
|
|
|
8059
8252
|
|
|
8060
8253
|
const DEFAULT_POLL_INTERVAL_MS = 3000;
|
|
8061
8254
|
const DEFAULT_POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
8062
|
-
const defaultDependencies$
|
|
8255
|
+
const defaultDependencies$6 = {
|
|
8063
8256
|
getCurrentUser,
|
|
8064
8257
|
fetchGameInfo,
|
|
8065
8258
|
zipDirectory: targetDir => zipCwdToBuffer([], targetDir),
|
|
@@ -8080,26 +8273,55 @@ function normalizeOutputFormat(format) {
|
|
|
8080
8273
|
}
|
|
8081
8274
|
return normalized;
|
|
8082
8275
|
}
|
|
8083
|
-
function
|
|
8084
|
-
|
|
8085
|
-
|
|
8276
|
+
function getAssetStatusRaw(asset) {
|
|
8277
|
+
return asset?.cdn_deploy_status ?? asset?.status ?? null;
|
|
8278
|
+
}
|
|
8279
|
+
function normalizeUploadPlatformStatus(rawStatus) {
|
|
8280
|
+
if (rawStatus === null || rawStatus === undefined || rawStatus === '') {
|
|
8281
|
+
return 'waiting_for_asset';
|
|
8282
|
+
}
|
|
8283
|
+
const numericStatus = Number(rawStatus);
|
|
8284
|
+
if (Number.isInteger(numericStatus)) {
|
|
8285
|
+
if (numericStatus === 0)
|
|
8286
|
+
return 'unknown';
|
|
8287
|
+
if (numericStatus === 1)
|
|
8288
|
+
return 'pending';
|
|
8289
|
+
if (numericStatus === 2)
|
|
8290
|
+
return 'success';
|
|
8291
|
+
if (numericStatus === 3)
|
|
8292
|
+
return 'failed';
|
|
8293
|
+
if (numericStatus === 4)
|
|
8294
|
+
return 'processing';
|
|
8295
|
+
return 'unknown';
|
|
8086
8296
|
}
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
return 'pending';
|
|
8297
|
+
const normalized = String(rawStatus).trim().toLowerCase();
|
|
8298
|
+
const statusName = normalized.replace(/^deploy_status_/, '');
|
|
8299
|
+
if (['ready', 'success', 'succeeded', 'available'].includes(statusName)) {
|
|
8300
|
+
return 'success';
|
|
8092
8301
|
}
|
|
8093
|
-
if (
|
|
8094
|
-
|
|
8302
|
+
if (statusName.includes('fail') ||
|
|
8303
|
+
statusName.includes('error') ||
|
|
8304
|
+
statusName.includes('reject')) {
|
|
8305
|
+
return 'failed';
|
|
8306
|
+
}
|
|
8307
|
+
if (statusName.includes('process') ||
|
|
8308
|
+
statusName.includes('upload') ||
|
|
8309
|
+
statusName.includes('deploy')) {
|
|
8310
|
+
return 'processing';
|
|
8311
|
+
}
|
|
8312
|
+
if (statusName.includes('pending') ||
|
|
8313
|
+
statusName.includes('queue') ||
|
|
8314
|
+
statusName.includes('wait')) {
|
|
8315
|
+
return 'pending';
|
|
8095
8316
|
}
|
|
8096
|
-
|
|
8097
|
-
|
|
8317
|
+
return 'unknown';
|
|
8318
|
+
}
|
|
8319
|
+
function getAssetState(asset) {
|
|
8320
|
+
const platformStatus = normalizeUploadPlatformStatus(getAssetStatusRaw(asset));
|
|
8321
|
+
if (platformStatus === 'success') {
|
|
8098
8322
|
return 'ready';
|
|
8099
8323
|
}
|
|
8100
|
-
if (
|
|
8101
|
-
rawStatus.includes('error') ||
|
|
8102
|
-
rawStatus.includes('reject')) {
|
|
8324
|
+
if (platformStatus === 'failed') {
|
|
8103
8325
|
return 'failed';
|
|
8104
8326
|
}
|
|
8105
8327
|
return 'pending';
|
|
@@ -8131,11 +8353,14 @@ function createEvent(base, status, progress, overrides = {}) {
|
|
|
8131
8353
|
qrPayload: '',
|
|
8132
8354
|
platformUrl: '',
|
|
8133
8355
|
error: null,
|
|
8356
|
+
errorCode: null,
|
|
8357
|
+
platformWrite: false,
|
|
8134
8358
|
...overrides,
|
|
8135
8359
|
};
|
|
8136
8360
|
}
|
|
8137
8361
|
async function waitForAssetReady({ assetId, miniGameId, deadline, eventBase, onEvent, deps, }) {
|
|
8138
|
-
let
|
|
8362
|
+
let lastPlatformStatusRaw = null;
|
|
8363
|
+
let hasEmittedPlatformStatus = false;
|
|
8139
8364
|
while (deps.now() < deadline) {
|
|
8140
8365
|
const response = await deps.fetchGameAssets({ miniGameId });
|
|
8141
8366
|
if (response.error) {
|
|
@@ -8144,12 +8369,17 @@ async function waitForAssetReady({ assetId, miniGameId, deadline, eventBase, onE
|
|
|
8144
8369
|
t('upload.error.assetQueryFailed'));
|
|
8145
8370
|
}
|
|
8146
8371
|
const asset = extractAssets(response.data).find(item => String(item.asset_id || '') === assetId) || null;
|
|
8147
|
-
const
|
|
8148
|
-
|
|
8149
|
-
|
|
8372
|
+
const platformStatusRaw = getAssetStatusRaw(asset);
|
|
8373
|
+
const platformStatus = normalizeUploadPlatformStatus(platformStatusRaw);
|
|
8374
|
+
if (!hasEmittedPlatformStatus ||
|
|
8375
|
+
platformStatusRaw !== lastPlatformStatusRaw) {
|
|
8376
|
+
hasEmittedPlatformStatus = true;
|
|
8377
|
+
lastPlatformStatusRaw = platformStatusRaw;
|
|
8150
8378
|
onEvent(createEvent(eventBase, 'processing', 0.7, {
|
|
8151
8379
|
versionId: assetId,
|
|
8152
8380
|
platformStatus,
|
|
8381
|
+
...(platformStatusRaw === null ? {} : { platformStatusRaw }),
|
|
8382
|
+
platformWrite: true,
|
|
8153
8383
|
}));
|
|
8154
8384
|
}
|
|
8155
8385
|
const assetState = getAssetState(asset);
|
|
@@ -8166,6 +8396,11 @@ async function waitForAssetReady({ assetId, miniGameId, deadline, eventBase, onE
|
|
|
8166
8396
|
async function waitForPreviewUrl({ assetId, deadline, deps, }) {
|
|
8167
8397
|
while (deps.now() < deadline) {
|
|
8168
8398
|
const response = await deps.fetchGameAssetPreviewUrl({ assetId });
|
|
8399
|
+
if (response.error) {
|
|
8400
|
+
throw new Error(response.error?.StatusMessage ||
|
|
8401
|
+
response.error?.message ||
|
|
8402
|
+
t('upload.error.previewQueryFailed'));
|
|
8403
|
+
}
|
|
8169
8404
|
const previewUrl = extractPreviewUrl(response.data);
|
|
8170
8405
|
if (previewUrl) {
|
|
8171
8406
|
return previewUrl;
|
|
@@ -8175,7 +8410,7 @@ async function waitForPreviewUrl({ assetId, deadline, deps, }) {
|
|
|
8175
8410
|
throw new Error(t('upload.error.previewTimeout'));
|
|
8176
8411
|
}
|
|
8177
8412
|
async function executeUpload(options, dependencies = {}) {
|
|
8178
|
-
const deps = { ...defaultDependencies$
|
|
8413
|
+
const deps = { ...defaultDependencies$6, ...dependencies };
|
|
8179
8414
|
const clientKey = options.clientKey?.trim();
|
|
8180
8415
|
const note = options.note || '--';
|
|
8181
8416
|
const eventBase = { operationId: options.operationId, clientKey };
|
|
@@ -8233,6 +8468,7 @@ async function executeUpload(options, dependencies = {}) {
|
|
|
8233
8468
|
packageSizeBytes,
|
|
8234
8469
|
versionId: assetId,
|
|
8235
8470
|
platformUrl,
|
|
8471
|
+
platformWrite: true,
|
|
8236
8472
|
});
|
|
8237
8473
|
emit(event);
|
|
8238
8474
|
return event;
|
|
@@ -8253,6 +8489,7 @@ async function executeUpload(options, dependencies = {}) {
|
|
|
8253
8489
|
versionId: assetId,
|
|
8254
8490
|
platformStatus: 'resolving_preview',
|
|
8255
8491
|
platformUrl,
|
|
8492
|
+
platformWrite: true,
|
|
8256
8493
|
}));
|
|
8257
8494
|
const previewUrl = await waitForPreviewUrl({ assetId, deadline, deps });
|
|
8258
8495
|
const event = createEvent(eventBase, 'ready', 1, {
|
|
@@ -8264,6 +8501,7 @@ async function executeUpload(options, dependencies = {}) {
|
|
|
8264
8501
|
qrPayload: previewUrl,
|
|
8265
8502
|
platformUrl,
|
|
8266
8503
|
platformStatus: 'ready',
|
|
8504
|
+
platformWrite: true,
|
|
8267
8505
|
});
|
|
8268
8506
|
emit(event);
|
|
8269
8507
|
return event;
|
|
@@ -8326,8 +8564,15 @@ async function upload(options) {
|
|
|
8326
8564
|
catch (error) {
|
|
8327
8565
|
const message = error?.message || String(error);
|
|
8328
8566
|
const failedEvent = createEvent({ operationId, clientKey: options.clientKey || '' }, 'failed', lastEvent?.progress || 0, {
|
|
8567
|
+
appId: lastEvent?.appId,
|
|
8568
|
+
projectName: lastEvent?.projectName,
|
|
8569
|
+
packageSizeBytes: lastEvent?.packageSizeBytes,
|
|
8329
8570
|
versionId: lastEvent?.versionId || '',
|
|
8330
8571
|
platformUrl: lastEvent?.platformUrl || '',
|
|
8572
|
+
platformStatus: lastEvent?.platformStatus,
|
|
8573
|
+
platformStatusRaw: lastEvent?.platformStatusRaw,
|
|
8574
|
+
platformWrite: lastEvent?.platformWrite || false,
|
|
8575
|
+
errorCode: 'UPLOAD_FAILED',
|
|
8331
8576
|
error: message,
|
|
8332
8577
|
});
|
|
8333
8578
|
if (format === 'json' || format === 'ndjson') {
|
|
@@ -14649,7 +14894,7 @@ function requireLib () {
|
|
|
14649
14894
|
var libExports = requireLib();
|
|
14650
14895
|
var QRCode = /*@__PURE__*/getDefaultExportFromCjs(libExports);
|
|
14651
14896
|
|
|
14652
|
-
const defaultDependencies = {
|
|
14897
|
+
const defaultDependencies$5 = {
|
|
14653
14898
|
getCurrentUser,
|
|
14654
14899
|
fetchGameInfo,
|
|
14655
14900
|
writeQrFile: async (outputPath, payload) => {
|
|
@@ -14660,11 +14905,20 @@ const defaultDependencies = {
|
|
|
14660
14905
|
type: 'png',
|
|
14661
14906
|
});
|
|
14662
14907
|
},
|
|
14908
|
+
now: () => Date.now(),
|
|
14909
|
+
operationId: () => `test_account_${crypto.randomUUID()}`,
|
|
14663
14910
|
};
|
|
14664
|
-
|
|
14911
|
+
class TestAccountCommandError extends Error {
|
|
14912
|
+
constructor(code, message) {
|
|
14913
|
+
super(message);
|
|
14914
|
+
this.code = code;
|
|
14915
|
+
this.name = 'TestAccountCommandError';
|
|
14916
|
+
}
|
|
14917
|
+
}
|
|
14918
|
+
function normalizeFormat$3(format) {
|
|
14665
14919
|
const normalized = (format || 'text').toLowerCase();
|
|
14666
14920
|
if (normalized !== 'text' && normalized !== 'json') {
|
|
14667
|
-
throw new
|
|
14921
|
+
throw new TestAccountCommandError('INVALID_FORMAT', t('testAccount.error.invalidFormat', { format: normalized }));
|
|
14668
14922
|
}
|
|
14669
14923
|
return normalized;
|
|
14670
14924
|
}
|
|
@@ -14672,31 +14926,39 @@ function buildTestAccountAuthorizationUrl(clientKey) {
|
|
|
14672
14926
|
return `https://www.tiktok.com/ttmg/dev/${encodeURIComponent(clientKey)}`;
|
|
14673
14927
|
}
|
|
14674
14928
|
async function generateTestAccountAuthorization(options, dependencies = {}) {
|
|
14675
|
-
const deps = { ...defaultDependencies, ...dependencies };
|
|
14929
|
+
const deps = { ...defaultDependencies$5, ...dependencies };
|
|
14676
14930
|
const clientKey = options.clientKey?.trim();
|
|
14931
|
+
const operationId = deps.operationId();
|
|
14677
14932
|
if (!clientKey) {
|
|
14678
|
-
throw new
|
|
14933
|
+
throw new TestAccountCommandError('INVALID_ARGUMENT', t('testAccount.error.noClientKey'));
|
|
14679
14934
|
}
|
|
14680
14935
|
const user = deps.getCurrentUser();
|
|
14681
14936
|
if (!user || !user.cookie) {
|
|
14682
|
-
throw new
|
|
14937
|
+
throw new TestAccountCommandError('NOT_AUTHENTICATED', t('testAccount.error.notLoggedIn'));
|
|
14683
14938
|
}
|
|
14684
14939
|
const infoResponse = await deps.fetchGameInfo(clientKey);
|
|
14685
14940
|
if (infoResponse.error) {
|
|
14686
|
-
throw new
|
|
14941
|
+
throw new TestAccountCommandError('PROJECT_LOOKUP_FAILED', infoResponse.error.errorMsg || t('testAccount.error.gameInfoFailed'));
|
|
14687
14942
|
}
|
|
14688
14943
|
const appId = infoResponse.data?.app_id || '';
|
|
14689
14944
|
if (!appId) {
|
|
14690
|
-
throw new
|
|
14945
|
+
throw new TestAccountCommandError('PROJECT_RESPONSE_INVALID', t('testAccount.error.noAppId'));
|
|
14691
14946
|
}
|
|
14692
14947
|
const projectName = infoResponse.data?.name || 'game';
|
|
14693
14948
|
const authorizationUrl = buildTestAccountAuthorizationUrl(clientKey);
|
|
14694
14949
|
const safeClientKey = clientKey.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
14695
14950
|
const qrFile = path.resolve(process.cwd(), options.qrOutput || path.join('.ttmg', `test-account-${safeClientKey}.png`));
|
|
14696
|
-
|
|
14697
|
-
|
|
14951
|
+
try {
|
|
14952
|
+
fs.mkdirSync(path.dirname(qrFile), { recursive: true });
|
|
14953
|
+
await deps.writeQrFile(qrFile, authorizationUrl);
|
|
14954
|
+
}
|
|
14955
|
+
catch (error) {
|
|
14956
|
+
throw new TestAccountCommandError('QR_WRITE_FAILED', error?.message || t('testAccount.error.qrWriteFailed'));
|
|
14957
|
+
}
|
|
14698
14958
|
return {
|
|
14699
14959
|
schemaVersion: '1',
|
|
14960
|
+
operationId,
|
|
14961
|
+
ts: deps.now(),
|
|
14700
14962
|
operation: 'test-account.add',
|
|
14701
14963
|
status: 'authorization_required',
|
|
14702
14964
|
clientKey,
|
|
@@ -14708,14 +14970,20 @@ async function generateTestAccountAuthorization(options, dependencies = {}) {
|
|
|
14708
14970
|
portalUrl: `https://developers.tiktok.com/portal/game/${appId}`,
|
|
14709
14971
|
platformWrite: false,
|
|
14710
14972
|
verification: 'developer_portal',
|
|
14973
|
+
errorCode: null,
|
|
14711
14974
|
error: null,
|
|
14712
14975
|
};
|
|
14713
14976
|
}
|
|
14714
|
-
async function testAccountAdd(options) {
|
|
14977
|
+
async function testAccountAdd(options, dependencies = {}) {
|
|
14978
|
+
const deps = { ...defaultDependencies$5, ...dependencies };
|
|
14715
14979
|
let format = 'text';
|
|
14980
|
+
const operationId = deps.operationId();
|
|
14716
14981
|
try {
|
|
14717
|
-
format = normalizeFormat(options.format);
|
|
14718
|
-
const result = await generateTestAccountAuthorization(options
|
|
14982
|
+
format = normalizeFormat$3(options.format);
|
|
14983
|
+
const result = await generateTestAccountAuthorization(options, {
|
|
14984
|
+
...dependencies,
|
|
14985
|
+
operationId: () => operationId,
|
|
14986
|
+
});
|
|
14719
14987
|
if (format === 'json') {
|
|
14720
14988
|
console.log(JSON.stringify(result));
|
|
14721
14989
|
}
|
|
@@ -14733,8 +15001,11 @@ async function testAccountAdd(options) {
|
|
|
14733
15001
|
}
|
|
14734
15002
|
catch (error) {
|
|
14735
15003
|
const message = error?.message || String(error);
|
|
15004
|
+
const errorCode = error instanceof TestAccountCommandError ? error.code : 'UNKNOWN_ERROR';
|
|
14736
15005
|
const failedResult = {
|
|
14737
15006
|
schemaVersion: '1',
|
|
15007
|
+
operationId,
|
|
15008
|
+
ts: deps.now(),
|
|
14738
15009
|
operation: 'test-account.add',
|
|
14739
15010
|
status: 'failed',
|
|
14740
15011
|
clientKey: options.clientKey || '',
|
|
@@ -14746,6 +15017,7 @@ async function testAccountAdd(options) {
|
|
|
14746
15017
|
portalUrl: '',
|
|
14747
15018
|
platformWrite: false,
|
|
14748
15019
|
verification: 'developer_portal',
|
|
15020
|
+
errorCode,
|
|
14749
15021
|
error: message,
|
|
14750
15022
|
};
|
|
14751
15023
|
if (format === 'json') {
|
|
@@ -14759,6 +15031,196 @@ async function testAccountAdd(options) {
|
|
|
14759
15031
|
}
|
|
14760
15032
|
}
|
|
14761
15033
|
|
|
15034
|
+
const defaultDependencies$4 = {
|
|
15035
|
+
getCurrentUser,
|
|
15036
|
+
operationId: () => `auth_${crypto.randomUUID()}`,
|
|
15037
|
+
};
|
|
15038
|
+
function maskAccountValue(value) {
|
|
15039
|
+
if (!value)
|
|
15040
|
+
return '';
|
|
15041
|
+
const emailMatch = value.match(/^(.{1,2})(.*)(@.+)$/);
|
|
15042
|
+
if (emailMatch) {
|
|
15043
|
+
return `${emailMatch[1]}***${emailMatch[3]}`;
|
|
15044
|
+
}
|
|
15045
|
+
if (value.length <= 4)
|
|
15046
|
+
return `${value[0]}***`;
|
|
15047
|
+
return `${value.slice(0, 2)}***${value.slice(-2)}`;
|
|
15048
|
+
}
|
|
15049
|
+
function normalizeFormat$2(format) {
|
|
15050
|
+
const value = (format || 'text').toLowerCase();
|
|
15051
|
+
if (value !== 'text' && value !== 'json') {
|
|
15052
|
+
throw new Error(t('auth.error.invalidFormat', { format: value }));
|
|
15053
|
+
}
|
|
15054
|
+
return value;
|
|
15055
|
+
}
|
|
15056
|
+
function getAuthStatus(dependencies = {}) {
|
|
15057
|
+
const deps = { ...defaultDependencies$4, ...dependencies };
|
|
15058
|
+
const user = deps.getCurrentUser();
|
|
15059
|
+
const authenticated = Boolean(user?.cookie);
|
|
15060
|
+
return {
|
|
15061
|
+
schemaVersion: '1',
|
|
15062
|
+
operationId: deps.operationId(),
|
|
15063
|
+
command: 'auth.status',
|
|
15064
|
+
status: authenticated ? 'authenticated' : 'not_authenticated',
|
|
15065
|
+
authenticated,
|
|
15066
|
+
account: {
|
|
15067
|
+
email: maskAccountValue(user?.email || ''),
|
|
15068
|
+
userId: maskAccountValue(user?.user_id || ''),
|
|
15069
|
+
},
|
|
15070
|
+
session: {
|
|
15071
|
+
source: 'local_config',
|
|
15072
|
+
reusable: authenticated,
|
|
15073
|
+
remotelyVerified: false,
|
|
15074
|
+
},
|
|
15075
|
+
errorCode: null,
|
|
15076
|
+
error: null,
|
|
15077
|
+
};
|
|
15078
|
+
}
|
|
15079
|
+
async function authStatus(options = {}) {
|
|
15080
|
+
let format = 'text';
|
|
15081
|
+
let result;
|
|
15082
|
+
try {
|
|
15083
|
+
format = normalizeFormat$2(options.format);
|
|
15084
|
+
result = getAuthStatus();
|
|
15085
|
+
}
|
|
15086
|
+
catch (error) {
|
|
15087
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15088
|
+
result = {
|
|
15089
|
+
schemaVersion: '1',
|
|
15090
|
+
operationId: `auth_${crypto.randomUUID()}`,
|
|
15091
|
+
command: 'auth.status',
|
|
15092
|
+
status: 'failed',
|
|
15093
|
+
authenticated: false,
|
|
15094
|
+
account: { email: '', userId: '' },
|
|
15095
|
+
session: {
|
|
15096
|
+
source: 'local_config',
|
|
15097
|
+
reusable: false,
|
|
15098
|
+
remotelyVerified: false,
|
|
15099
|
+
},
|
|
15100
|
+
errorCode: 'AUTH_STATUS_FAILED',
|
|
15101
|
+
error: message,
|
|
15102
|
+
};
|
|
15103
|
+
}
|
|
15104
|
+
if (format === 'json') {
|
|
15105
|
+
console.log(JSON.stringify(result));
|
|
15106
|
+
}
|
|
15107
|
+
else if (result.status === 'authenticated') {
|
|
15108
|
+
console.log(chalk.green(t('auth.status.authenticated', {
|
|
15109
|
+
account: result.account.email || result.account.userId,
|
|
15110
|
+
})));
|
|
15111
|
+
}
|
|
15112
|
+
else if (result.status === 'not_authenticated') {
|
|
15113
|
+
console.log(chalk.yellow(t('auth.status.notAuthenticated')));
|
|
15114
|
+
}
|
|
15115
|
+
else {
|
|
15116
|
+
console.error(chalk.red(result.error || t('common.unknownError')));
|
|
15117
|
+
}
|
|
15118
|
+
if (result.status === 'failed' ||
|
|
15119
|
+
(options.requireAuth && !result.authenticated)) {
|
|
15120
|
+
process.exitCode = 1;
|
|
15121
|
+
}
|
|
15122
|
+
return result;
|
|
15123
|
+
}
|
|
15124
|
+
|
|
15125
|
+
var version = "0.4.6-vibe-beta.3";
|
|
15126
|
+
var engines = {
|
|
15127
|
+
node: ">=20.19.1 <21 || >=22"
|
|
15128
|
+
};
|
|
15129
|
+
var pkg = {
|
|
15130
|
+
version: version,
|
|
15131
|
+
engines: engines
|
|
15132
|
+
};
|
|
15133
|
+
|
|
15134
|
+
function getCapabilities() {
|
|
15135
|
+
return {
|
|
15136
|
+
schemaVersion: '1',
|
|
15137
|
+
command: 'capabilities',
|
|
15138
|
+
cliVersion: pkg.version,
|
|
15139
|
+
structuredOutput: {
|
|
15140
|
+
json: true,
|
|
15141
|
+
ndjson: true,
|
|
15142
|
+
stdoutProtocolOnly: true,
|
|
15143
|
+
},
|
|
15144
|
+
commands: {
|
|
15145
|
+
authStatus: {
|
|
15146
|
+
command: 'ttmg auth status --format json',
|
|
15147
|
+
available: true,
|
|
15148
|
+
platformWrite: false,
|
|
15149
|
+
},
|
|
15150
|
+
loginImplicit: {
|
|
15151
|
+
command: 'ttmg login --implicit',
|
|
15152
|
+
available: true,
|
|
15153
|
+
interactive: false,
|
|
15154
|
+
persistsLocalSession: true,
|
|
15155
|
+
platformWrite: false,
|
|
15156
|
+
},
|
|
15157
|
+
testAccountAdd: {
|
|
15158
|
+
command: 'ttmg test-account add --format json',
|
|
15159
|
+
available: true,
|
|
15160
|
+
platformWrite: false,
|
|
15161
|
+
humanAuthorizationRequired: true,
|
|
15162
|
+
},
|
|
15163
|
+
check: {
|
|
15164
|
+
command: 'ttmg check --format json',
|
|
15165
|
+
available: true,
|
|
15166
|
+
platformWrite: false,
|
|
15167
|
+
ai: {
|
|
15168
|
+
option: '--ai',
|
|
15169
|
+
command: 'ttmg check --ai --format json',
|
|
15170
|
+
available: true,
|
|
15171
|
+
platformWrite: false,
|
|
15172
|
+
confidence: 'static',
|
|
15173
|
+
},
|
|
15174
|
+
},
|
|
15175
|
+
build: {
|
|
15176
|
+
command: 'ttmg build --format json',
|
|
15177
|
+
available: true,
|
|
15178
|
+
platformWrite: false,
|
|
15179
|
+
},
|
|
15180
|
+
dev: {
|
|
15181
|
+
command: 'ttmg dev --format json --no-open',
|
|
15182
|
+
eventsCommand: 'ttmg dev --events ndjson --no-open',
|
|
15183
|
+
outputModesMutuallyExclusive: true,
|
|
15184
|
+
available: true,
|
|
15185
|
+
platformWrite: false,
|
|
15186
|
+
timeout: true,
|
|
15187
|
+
sessionList: true,
|
|
15188
|
+
sessionInspect: true,
|
|
15189
|
+
sessionStop: true,
|
|
15190
|
+
},
|
|
15191
|
+
uploadPreview: {
|
|
15192
|
+
command: 'ttmg upload --preview --format ndjson',
|
|
15193
|
+
available: true,
|
|
15194
|
+
platformWrite: true,
|
|
15195
|
+
},
|
|
15196
|
+
},
|
|
15197
|
+
dev: {
|
|
15198
|
+
sessionManifest: true,
|
|
15199
|
+
lifecycleEvents: true,
|
|
15200
|
+
compileDiagnostics: true,
|
|
15201
|
+
rawLaunchUrl: true,
|
|
15202
|
+
qrCode: true,
|
|
15203
|
+
manualScan: true,
|
|
15204
|
+
gameReadyProtocol: true,
|
|
15205
|
+
gameReady: false,
|
|
15206
|
+
runtimeLogs: false,
|
|
15207
|
+
deviceLaunch: false,
|
|
15208
|
+
screenshot: false,
|
|
15209
|
+
actions: false,
|
|
15210
|
+
},
|
|
15211
|
+
};
|
|
15212
|
+
}
|
|
15213
|
+
async function capabilities(options = {}) {
|
|
15214
|
+
const result = getCapabilities();
|
|
15215
|
+
if ((options.format || 'text').toLowerCase() === 'json') {
|
|
15216
|
+
console.log(JSON.stringify(result));
|
|
15217
|
+
}
|
|
15218
|
+
else {
|
|
15219
|
+
console.log(JSON.stringify(result, null, 2));
|
|
15220
|
+
}
|
|
15221
|
+
return result;
|
|
15222
|
+
}
|
|
15223
|
+
|
|
14762
15224
|
const CONFIG_FILE_NAME = 'minigame.config.json';
|
|
14763
15225
|
const SDK_URL = 'https://connect.tiktok-minis.com/game/sdk.js';
|
|
14764
15226
|
const VCONSOLE_URL = 'https://connect.tiktok-minis.com/libs/vConsole.js';
|
|
@@ -15153,15 +15615,18 @@ function getDesktopPath() {
|
|
|
15153
15615
|
return defaultPath;
|
|
15154
15616
|
}
|
|
15155
15617
|
|
|
15156
|
-
var version = "0.4.6-vibe-beta.1";
|
|
15157
|
-
var pkg = {
|
|
15158
|
-
version: version};
|
|
15159
|
-
|
|
15160
15618
|
const program = new commander.Command();
|
|
15161
15619
|
const withVerboseOption = (command) => command.option('--verbose', t('cli.option.verbose'));
|
|
15162
15620
|
const structuredFormatIndex = process.argv.indexOf('--format');
|
|
15163
|
-
const
|
|
15164
|
-
|
|
15621
|
+
const inlineStructuredFormat = process.argv.find(arg => arg.startsWith('--format='));
|
|
15622
|
+
const structuredFormat = inlineStructuredFormat
|
|
15623
|
+
? inlineStructuredFormat.slice('--format='.length)
|
|
15624
|
+
: structuredFormatIndex >= 0
|
|
15625
|
+
? process.argv[structuredFormatIndex + 1]
|
|
15626
|
+
: '';
|
|
15627
|
+
if (structuredFormat !== 'json' &&
|
|
15628
|
+
structuredFormat !== 'ndjson' &&
|
|
15629
|
+
!process.argv.includes('--implicit')) {
|
|
15165
15630
|
maybeShowPostInstallNotice(pkg.version);
|
|
15166
15631
|
}
|
|
15167
15632
|
withVerboseOption(program)
|
|
@@ -15170,12 +15635,39 @@ withVerboseOption(program)
|
|
|
15170
15635
|
.version(pkg.version, '-v, --version', t('cli.version.desc'))
|
|
15171
15636
|
.option('dev', t('cli.option.dev.client'))
|
|
15172
15637
|
.option('dev --h5', t('cli.option.dev.h5'));
|
|
15173
|
-
withVerboseOption(program
|
|
15174
|
-
|
|
15638
|
+
withVerboseOption(program
|
|
15639
|
+
.command('login')
|
|
15640
|
+
.description(t('cli.command.login.desc'))
|
|
15641
|
+
.option('--implicit', t('cli.command.login.implicit'))
|
|
15642
|
+
.option('--email <email>', t('cli.command.login.email'))
|
|
15643
|
+
.option('--password <password>', t('cli.command.login.password'))).action(async (cmd) => {
|
|
15644
|
+
await login({
|
|
15645
|
+
verbose: cmd.verbose,
|
|
15646
|
+
implicit: cmd.implicit,
|
|
15647
|
+
email: cmd.email,
|
|
15648
|
+
password: cmd.password,
|
|
15649
|
+
});
|
|
15175
15650
|
});
|
|
15176
15651
|
withVerboseOption(program.command('logout').description(t('cli.command.logout.desc'))).action(async () => {
|
|
15177
15652
|
await logout();
|
|
15178
15653
|
});
|
|
15654
|
+
const authCmd = program.command('auth').description(t('cli.command.auth.desc'));
|
|
15655
|
+
withVerboseOption(authCmd
|
|
15656
|
+
.command('status')
|
|
15657
|
+
.description(t('cli.command.auth.status.desc'))
|
|
15658
|
+
.option('--format <format>', t('cli.command.auth.status.format'), 'text')
|
|
15659
|
+
.option('--require-auth', t('cli.command.auth.status.requireAuth'))).action(async (cmd) => {
|
|
15660
|
+
await authStatus({
|
|
15661
|
+
format: cmd.format,
|
|
15662
|
+
requireAuth: cmd.requireAuth,
|
|
15663
|
+
});
|
|
15664
|
+
});
|
|
15665
|
+
withVerboseOption(program
|
|
15666
|
+
.command('capabilities')
|
|
15667
|
+
.description(t('cli.command.capabilities.desc'))
|
|
15668
|
+
.option('--format <format>', t('cli.command.capabilities.format'), 'text')).action(async (cmd) => {
|
|
15669
|
+
await capabilities({ format: cmd.format });
|
|
15670
|
+
});
|
|
15179
15671
|
withVerboseOption(program
|
|
15180
15672
|
.command('upload')
|
|
15181
15673
|
.description(t('cli.command.upload.desc'))
|
|
@@ -15247,20 +15739,132 @@ withVerboseOption(program
|
|
|
15247
15739
|
/**
|
|
15248
15740
|
* ttmg dev 命令
|
|
15249
15741
|
*/
|
|
15250
|
-
|
|
15742
|
+
const devCmd = program
|
|
15251
15743
|
.option('--h5', t('cli.option.h5'))
|
|
15252
15744
|
.command('dev')
|
|
15253
15745
|
.description(t('cli.command.dev.desc'))
|
|
15254
|
-
.option('-ck, --client-key <clientKey>', t('cli.command.dev.clientKey'))
|
|
15746
|
+
.option('-ck, --client-key <clientKey>', t('cli.command.dev.clientKey'))
|
|
15747
|
+
.option('--format <format>', t('cli.command.dev.format'), 'text')
|
|
15748
|
+
.option('--events <events>', t('cli.command.dev.events'), 'none')
|
|
15749
|
+
.option('--no-open', t('cli.command.dev.noOpen'))
|
|
15750
|
+
.option('--evidence-dir <path>', t('cli.command.dev.evidenceDir'))
|
|
15751
|
+
.option('--timeout <seconds>', t('cli.command.dev.timeout'));
|
|
15752
|
+
withVerboseOption(devCmd).action(async (cmd) => {
|
|
15255
15753
|
const options = program.opts();
|
|
15754
|
+
const format = String(cmd.format || 'text').toLowerCase();
|
|
15755
|
+
const events = String(cmd.events || 'none').toLowerCase();
|
|
15756
|
+
if (format === 'json' && events === 'ndjson') {
|
|
15757
|
+
console.error(t('cli.command.dev.outputConflict'));
|
|
15758
|
+
process.exitCode = 1;
|
|
15759
|
+
return;
|
|
15760
|
+
}
|
|
15256
15761
|
if (options.h5) {
|
|
15257
15762
|
await dev$1();
|
|
15258
15763
|
}
|
|
15259
15764
|
else {
|
|
15260
15765
|
const Native = await Promise.resolve().then(function () { return index; });
|
|
15261
|
-
await Native.dev({
|
|
15766
|
+
await Native.dev({
|
|
15767
|
+
clientKey: cmd.clientKey,
|
|
15768
|
+
format: cmd.format,
|
|
15769
|
+
events: cmd.events,
|
|
15770
|
+
noOpen: cmd.noOpen,
|
|
15771
|
+
evidenceDir: cmd.evidenceDir,
|
|
15772
|
+
timeout: cmd.timeout,
|
|
15773
|
+
});
|
|
15774
|
+
}
|
|
15775
|
+
if (cmd.format === 'text' && cmd.events === 'none')
|
|
15776
|
+
checkUpdate();
|
|
15777
|
+
});
|
|
15778
|
+
const devSessionCmd = devCmd
|
|
15779
|
+
.command('session')
|
|
15780
|
+
.description(t('cli.command.dev.session.desc'));
|
|
15781
|
+
devSessionCmd
|
|
15782
|
+
.command('inspect <sessionId>')
|
|
15783
|
+
.description(t('cli.command.dev.session.inspect'))
|
|
15784
|
+
.option('--sessions-dir <path>', t('cli.command.dev.session.dir'))
|
|
15785
|
+
.option('--format <format>', t('cli.command.dev.session.format'))
|
|
15786
|
+
.action(async (sessionId, sessionOptions) => {
|
|
15787
|
+
const Native = await Promise.resolve().then(function () { return index; });
|
|
15788
|
+
await Native.devSessionInspectCommand(sessionId, {
|
|
15789
|
+
sessionsDir: sessionOptions.sessionsDir,
|
|
15790
|
+
format: sessionOptions.format || devCmd.opts().format,
|
|
15791
|
+
});
|
|
15792
|
+
});
|
|
15793
|
+
devSessionCmd
|
|
15794
|
+
.command('list')
|
|
15795
|
+
.description(t('cli.command.dev.session.list'))
|
|
15796
|
+
.option('--sessions-dir <path>', t('cli.command.dev.session.dir'))
|
|
15797
|
+
.option('--format <format>', t('cli.command.dev.session.format'))
|
|
15798
|
+
.action(async (sessionOptions) => {
|
|
15799
|
+
const Native = await Promise.resolve().then(function () { return index; });
|
|
15800
|
+
await Native.devSessionListCommand({
|
|
15801
|
+
sessionsDir: sessionOptions.sessionsDir,
|
|
15802
|
+
format: sessionOptions.format || devCmd.opts().format,
|
|
15803
|
+
});
|
|
15804
|
+
});
|
|
15805
|
+
devSessionCmd
|
|
15806
|
+
.command('events <sessionId>')
|
|
15807
|
+
.description(t('cli.command.dev.session.events'))
|
|
15808
|
+
.option('--sessions-dir <path>', t('cli.command.dev.session.dir'))
|
|
15809
|
+
.option('--after <seq>', t('cli.command.dev.session.after'), '0')
|
|
15810
|
+
.option('--limit <count>', t('cli.command.dev.session.limit'), '100')
|
|
15811
|
+
.option('--format <format>', t('cli.command.dev.session.format'))
|
|
15812
|
+
.action(async (sessionId, sessionOptions) => {
|
|
15813
|
+
const Native = await Promise.resolve().then(function () { return index; });
|
|
15814
|
+
await Native.devSessionEventsCommand(sessionId, {
|
|
15815
|
+
sessionsDir: sessionOptions.sessionsDir,
|
|
15816
|
+
after: sessionOptions.after,
|
|
15817
|
+
limit: sessionOptions.limit,
|
|
15818
|
+
format: sessionOptions.format || devCmd.opts().format,
|
|
15819
|
+
});
|
|
15820
|
+
});
|
|
15821
|
+
devSessionCmd
|
|
15822
|
+
.command('stop <sessionId>')
|
|
15823
|
+
.description(t('cli.command.dev.session.stop'))
|
|
15824
|
+
.option('--sessions-dir <path>', t('cli.command.dev.session.dir'))
|
|
15825
|
+
.option('--format <format>', t('cli.command.dev.session.format'))
|
|
15826
|
+
.action(async (sessionId, sessionOptions) => {
|
|
15827
|
+
const Native = await Promise.resolve().then(function () { return index; });
|
|
15828
|
+
await Native.devSessionStopCommand(sessionId, {
|
|
15829
|
+
sessionsDir: sessionOptions.sessionsDir,
|
|
15830
|
+
format: sessionOptions.format || devCmd.opts().format,
|
|
15831
|
+
});
|
|
15832
|
+
});
|
|
15833
|
+
const checkCmd = program
|
|
15834
|
+
.command('check')
|
|
15835
|
+
.description(t('cli.command.check.desc'))
|
|
15836
|
+
.allowExcessArguments(false)
|
|
15837
|
+
.option('-d, --dir <dir>', t('cli.command.check.dir'))
|
|
15838
|
+
.option('--format <format>', t('cli.command.check.format'), 'text')
|
|
15839
|
+
.option('--include-passed', t('cli.command.check.includePassed'))
|
|
15840
|
+
.option('--fail-on <level>', t('cli.command.check.failOn'), 'error')
|
|
15841
|
+
.option('--ai', t('cli.command.check.ai'));
|
|
15842
|
+
withVerboseOption(checkCmd).action(async (cmd) => {
|
|
15843
|
+
const Native = await Promise.resolve().then(function () { return index; });
|
|
15844
|
+
const options = {
|
|
15845
|
+
dir: cmd.dir,
|
|
15846
|
+
format: cmd.format,
|
|
15847
|
+
includePassed: cmd.includePassed,
|
|
15848
|
+
failOn: cmd.failOn,
|
|
15849
|
+
};
|
|
15850
|
+
if (cmd.ai) {
|
|
15851
|
+
await Native.checkAi(options);
|
|
15852
|
+
return;
|
|
15262
15853
|
}
|
|
15263
|
-
|
|
15854
|
+
await Native.check(options);
|
|
15855
|
+
});
|
|
15856
|
+
withVerboseOption(program
|
|
15857
|
+
.command('doctor')
|
|
15858
|
+
.description(t('cli.command.doctor.desc'))
|
|
15859
|
+
.option('-d, --dir <dir>', t('cli.command.doctor.dir'))
|
|
15860
|
+
.option('--format <format>', t('cli.command.doctor.format'), 'text')
|
|
15861
|
+
.option('--fail-on <level>', t('cli.command.doctor.failOn'), 'error')).action(async (cmd) => {
|
|
15862
|
+
const Native = await Promise.resolve().then(function () { return index; });
|
|
15863
|
+
await Native.doctor({
|
|
15864
|
+
dir: cmd.dir,
|
|
15865
|
+
format: cmd.format,
|
|
15866
|
+
failOn: cmd.failOn,
|
|
15867
|
+
});
|
|
15264
15868
|
});
|
|
15265
15869
|
/**
|
|
15266
15870
|
* ttmg build 命令
|
|
@@ -15268,11 +15872,22 @@ withVerboseOption(program
|
|
|
15268
15872
|
withVerboseOption(program
|
|
15269
15873
|
.option('--h5', t('cli.option.h5'))
|
|
15270
15874
|
.command('build')
|
|
15271
|
-
.description(t('cli.command.build.desc'))
|
|
15875
|
+
.description(t('cli.command.build.desc'))
|
|
15876
|
+
.option('-d, --dir <dir>', t('cli.command.build.dir'))
|
|
15877
|
+
.option('--format <format>', t('cli.command.build.format'), 'text')
|
|
15878
|
+
.option('--include-passed', t('cli.command.build.includePassed'))).action(async (cmd) => {
|
|
15272
15879
|
const options = program.opts(); // 获取 options
|
|
15273
15880
|
if (options.h5) {
|
|
15274
15881
|
build();
|
|
15275
15882
|
}
|
|
15883
|
+
else if (cmd.format !== 'text') {
|
|
15884
|
+
const Native = await Promise.resolve().then(function () { return index; });
|
|
15885
|
+
await Native.build({
|
|
15886
|
+
dir: cmd.dir,
|
|
15887
|
+
format: cmd.format,
|
|
15888
|
+
includePassed: cmd.includePassed,
|
|
15889
|
+
});
|
|
15890
|
+
}
|
|
15276
15891
|
else {
|
|
15277
15892
|
console.log(t('cli.native.build.placeholder'));
|
|
15278
15893
|
}
|
|
@@ -15430,20 +16045,51 @@ const store = Store.getInstance({
|
|
|
15430
16045
|
checkResults: [],
|
|
15431
16046
|
});
|
|
15432
16047
|
|
|
16048
|
+
function getGameReadyPayload(message) {
|
|
16049
|
+
if (!message || typeof message !== 'object')
|
|
16050
|
+
return null;
|
|
16051
|
+
const runtimeEvent = message;
|
|
16052
|
+
if (runtimeEvent.method !== 'reportScene' ||
|
|
16053
|
+
runtimeEvent.payload?.sceneId !== 9999) {
|
|
16054
|
+
return null;
|
|
16055
|
+
}
|
|
16056
|
+
return {
|
|
16057
|
+
sceneId: 9999,
|
|
16058
|
+
signal: 'reportScene',
|
|
16059
|
+
};
|
|
16060
|
+
}
|
|
16061
|
+
function emitRuntimeSessionEvent(message) {
|
|
16062
|
+
const gameReady = getGameReadyPayload(message);
|
|
16063
|
+
if (!gameReady)
|
|
16064
|
+
return false;
|
|
16065
|
+
eventEmitter.emit('devSession', {
|
|
16066
|
+
type: 'game_ready',
|
|
16067
|
+
payload: gameReady,
|
|
16068
|
+
});
|
|
16069
|
+
return true;
|
|
16070
|
+
}
|
|
15433
16071
|
class WsServer {
|
|
15434
16072
|
constructor() {
|
|
15435
16073
|
this.started = false;
|
|
16074
|
+
this.quiet = false;
|
|
15436
16075
|
}
|
|
15437
|
-
start() {
|
|
16076
|
+
start(options = {}) {
|
|
15438
16077
|
if (this.started) {
|
|
15439
16078
|
return;
|
|
15440
16079
|
}
|
|
16080
|
+
this.quiet = options.quiet === true;
|
|
15441
16081
|
this.started = true;
|
|
15442
16082
|
this.startWsServer(store.getState().nodeWsPort);
|
|
15443
16083
|
}
|
|
15444
16084
|
startWsServer(port) {
|
|
15445
16085
|
this.ws = new WebSocket.Server({ port });
|
|
15446
16086
|
this.ws.on('connection', ws => {
|
|
16087
|
+
ws.on('close', () => {
|
|
16088
|
+
eventEmitter.emit('devSession', {
|
|
16089
|
+
type: 'client_disconnected',
|
|
16090
|
+
payload: {},
|
|
16091
|
+
});
|
|
16092
|
+
});
|
|
15447
16093
|
const { clientHttpPort, clientHost, clientWsPort } = store.getState();
|
|
15448
16094
|
if (clientHost) {
|
|
15449
16095
|
this.send({
|
|
@@ -15476,8 +16122,13 @@ class WsServer {
|
|
|
15476
16122
|
case 'startUpload':
|
|
15477
16123
|
eventEmitter.emit('startUpload', {});
|
|
15478
16124
|
break;
|
|
16125
|
+
case 'runtimeEvent': {
|
|
16126
|
+
emitRuntimeSessionEvent(clientMessage.payload);
|
|
16127
|
+
break;
|
|
16128
|
+
}
|
|
15479
16129
|
case 'closeLocalDebug':
|
|
15480
|
-
|
|
16130
|
+
if (!this.quiet)
|
|
16131
|
+
console.log('closeLocalDebug');
|
|
15481
16132
|
/**
|
|
15482
16133
|
* 关闭调试服务
|
|
15483
16134
|
*/
|
|
@@ -15491,7 +16142,8 @@ class WsServer {
|
|
|
15491
16142
|
case 'scanQRCodeResult': {
|
|
15492
16143
|
const payload = clientMessage.payload || {};
|
|
15493
16144
|
const { host, port, wsPort, errMsg, isSuccess } = payload;
|
|
15494
|
-
|
|
16145
|
+
if (!this.quiet)
|
|
16146
|
+
console.log('scanQRCodeResult', payload);
|
|
15495
16147
|
if (isSuccess) {
|
|
15496
16148
|
store.setState({
|
|
15497
16149
|
clientHttpPort: port,
|
|
@@ -15506,9 +16158,18 @@ class WsServer {
|
|
|
15506
16158
|
clientWsPort: wsPort,
|
|
15507
16159
|
},
|
|
15508
16160
|
});
|
|
15509
|
-
|
|
16161
|
+
eventEmitter.emit('devSession', {
|
|
16162
|
+
type: 'client_connected',
|
|
16163
|
+
payload: { host, port, wsPort },
|
|
16164
|
+
});
|
|
16165
|
+
if (!this.quiet)
|
|
16166
|
+
console.log('scanQRcodeSuccess');
|
|
15510
16167
|
}
|
|
15511
16168
|
else {
|
|
16169
|
+
eventEmitter.emit('devSession', {
|
|
16170
|
+
type: 'client_connection_failed',
|
|
16171
|
+
payload: { error: errMsg || '' },
|
|
16172
|
+
});
|
|
15512
16173
|
this.send({
|
|
15513
16174
|
method: 'scanQRCodeFailed',
|
|
15514
16175
|
payload: {
|
|
@@ -15550,7 +16211,8 @@ class WsServer {
|
|
|
15550
16211
|
this.startWsServer(store.getState().nodeWsPort);
|
|
15551
16212
|
}
|
|
15552
16213
|
else {
|
|
15553
|
-
|
|
16214
|
+
if (!this.quiet)
|
|
16215
|
+
console.log(chalk.red.bold(err.message));
|
|
15554
16216
|
process.exit(1);
|
|
15555
16217
|
}
|
|
15556
16218
|
});
|
|
@@ -15621,21 +16283,35 @@ async function compile(context) {
|
|
|
15621
16283
|
const { clientKey, msg } = getClientKey();
|
|
15622
16284
|
if (!clientKey) {
|
|
15623
16285
|
if (context?.mode !== 'watch') {
|
|
15624
|
-
|
|
16286
|
+
if (!context?.quiet)
|
|
16287
|
+
console.log(chalk.red.bold(msg));
|
|
16288
|
+
if (context?.throwOnError) {
|
|
16289
|
+
throw new Error(msg);
|
|
16290
|
+
}
|
|
15625
16291
|
process.exit(1);
|
|
15626
16292
|
}
|
|
15627
|
-
|
|
16293
|
+
if (!context?.quiet)
|
|
16294
|
+
console.log(chalk.red.bold(msg));
|
|
15628
16295
|
}
|
|
15629
16296
|
const startTip = context?.mode === 'watch'
|
|
15630
16297
|
? t('native.compile.watching')
|
|
15631
16298
|
: t('native.compile.compiling');
|
|
15632
|
-
|
|
16299
|
+
if (!context?.quiet)
|
|
16300
|
+
console.log(chalk.bold.cyan(startTip));
|
|
16301
|
+
context?.onEvent?.('started');
|
|
15633
16302
|
wsServer.sendCompilationStatus('start');
|
|
15634
16303
|
store.setState({
|
|
15635
16304
|
isUnderCompiling: true,
|
|
15636
16305
|
});
|
|
15637
16306
|
return new Promise((resolve, reject) => {
|
|
15638
|
-
const worker = new worker_threads.Worker(path.resolve(__dirname, './scripts/worker.js')
|
|
16307
|
+
const worker = new worker_threads.Worker(path.resolve(__dirname, './scripts/worker.js'), {
|
|
16308
|
+
stdout: context?.quiet,
|
|
16309
|
+
stderr: context?.quiet,
|
|
16310
|
+
});
|
|
16311
|
+
if (context?.quiet) {
|
|
16312
|
+
worker.stdout?.on('data', chunk => process.stderr.write(chunk));
|
|
16313
|
+
worker.stderr?.on('data', chunk => process.stderr.write(chunk));
|
|
16314
|
+
}
|
|
15639
16315
|
worker.on('message', msg => {
|
|
15640
16316
|
if (msg.type === 'status' && msg.status === 'end') {
|
|
15641
16317
|
const { isSuccess, errorMsg, packages } = msg;
|
|
@@ -15647,10 +16323,20 @@ async function compile(context) {
|
|
|
15647
16323
|
errorMsg,
|
|
15648
16324
|
isSuccess: false,
|
|
15649
16325
|
});
|
|
15650
|
-
|
|
16326
|
+
if (!context?.quiet)
|
|
16327
|
+
console.log(chalk.red.bold(errorMsg));
|
|
16328
|
+
context?.onEvent?.('failed', errorMsg, msg.checkResults || []);
|
|
15651
16329
|
// 非 watch 模式下,校验失败直接退出进程
|
|
15652
16330
|
if (context?.mode !== 'watch') {
|
|
15653
|
-
|
|
16331
|
+
if (context?.throwOnError) {
|
|
16332
|
+
reject(new Error(errorMsg));
|
|
16333
|
+
}
|
|
16334
|
+
else {
|
|
16335
|
+
process.exit(1);
|
|
16336
|
+
}
|
|
16337
|
+
}
|
|
16338
|
+
else {
|
|
16339
|
+
resolve(msg);
|
|
15654
16340
|
}
|
|
15655
16341
|
}
|
|
15656
16342
|
else {
|
|
@@ -15665,7 +16351,10 @@ async function compile(context) {
|
|
|
15665
16351
|
wsServer.sendCompilationStatus('end', {
|
|
15666
16352
|
isSuccess: true,
|
|
15667
16353
|
});
|
|
15668
|
-
|
|
16354
|
+
context?.onEvent?.('succeeded', undefined, msg.checkResults || []);
|
|
16355
|
+
if (!context?.quiet) {
|
|
16356
|
+
console.log(chalk.green.bold(t('native.compile.success')));
|
|
16357
|
+
}
|
|
15669
16358
|
resolve(msg); // 编译结束,返回结果
|
|
15670
16359
|
}
|
|
15671
16360
|
worker.terminate();
|
|
@@ -15676,7 +16365,9 @@ async function compile(context) {
|
|
|
15676
16365
|
}
|
|
15677
16366
|
});
|
|
15678
16367
|
worker.on('error', err => {
|
|
15679
|
-
|
|
16368
|
+
if (!context?.quiet)
|
|
16369
|
+
console.log(chalk.red.bold(err.message));
|
|
16370
|
+
context?.onEvent?.('failed', err.message);
|
|
15680
16371
|
reject(err);
|
|
15681
16372
|
worker.terminate();
|
|
15682
16373
|
});
|
|
@@ -15755,10 +16446,14 @@ async function withWatchSuspended(fn, options = {}) {
|
|
|
15755
16446
|
resumeWatch({ emit: options.emitOnFinish ?? true });
|
|
15756
16447
|
}
|
|
15757
16448
|
}
|
|
15758
|
-
async function watch() {
|
|
16449
|
+
async function watch(options = {}) {
|
|
15759
16450
|
let debounceTimer = null;
|
|
15760
16451
|
const runUpdate = async () => {
|
|
15761
|
-
await compile({
|
|
16452
|
+
await compile({
|
|
16453
|
+
mode: 'watch',
|
|
16454
|
+
quiet: options.quiet,
|
|
16455
|
+
onEvent: options.onCompileEvent,
|
|
16456
|
+
});
|
|
15762
16457
|
wsServer.sendResourceChange();
|
|
15763
16458
|
};
|
|
15764
16459
|
triggerUpdate = () => {
|
|
@@ -15801,6 +16496,7 @@ async function watch() {
|
|
|
15801
16496
|
watcher.on('error', error => {
|
|
15802
16497
|
// console.error(chalk.red('[watch] 监听发生错误:'), error);
|
|
15803
16498
|
});
|
|
16499
|
+
return watcher;
|
|
15804
16500
|
}
|
|
15805
16501
|
|
|
15806
16502
|
let spinner;
|
|
@@ -16231,7 +16927,7 @@ function isWorkspace() {
|
|
|
16231
16927
|
}
|
|
16232
16928
|
}
|
|
16233
16929
|
|
|
16234
|
-
async function check() {
|
|
16930
|
+
async function check(options = {}) {
|
|
16235
16931
|
const boxenFn = boxen.default ??
|
|
16236
16932
|
boxen;
|
|
16237
16933
|
const printBox = (type, message) => {
|
|
@@ -16251,9 +16947,14 @@ async function check() {
|
|
|
16251
16947
|
// `You are not logged in, please login first, run ttmg login to login with your TikTok Dev Portal account, then try again`,
|
|
16252
16948
|
// );
|
|
16253
16949
|
// process.exit(1);
|
|
16254
|
-
// }
|
|
16950
|
+
// }
|
|
16255
16951
|
if (!isWorkspace()) {
|
|
16256
|
-
|
|
16952
|
+
if (options.throwOnError) {
|
|
16953
|
+
throw new Error(t('native.check.notWorkspace'));
|
|
16954
|
+
}
|
|
16955
|
+
if (!options.quiet) {
|
|
16956
|
+
printBox('Error', t('native.check.notWorkspace'));
|
|
16957
|
+
}
|
|
16257
16958
|
process.exit(1);
|
|
16258
16959
|
}
|
|
16259
16960
|
// const checkResult = await checkPkgs({
|
|
@@ -16309,19 +17010,26 @@ async function listen(app, options) {
|
|
|
16309
17010
|
const version = devToolVersion$1;
|
|
16310
17011
|
const server = http.createServer(app);
|
|
16311
17012
|
function tryListen(port) {
|
|
16312
|
-
return new Promise(resolve => {
|
|
17013
|
+
return new Promise((resolve, reject) => {
|
|
16313
17014
|
const onError = err => {
|
|
16314
17015
|
server.removeListener('listening', onListening);
|
|
16315
17016
|
if (err.code === 'EADDRINUSE') {
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
17017
|
+
if (!options?.quiet) {
|
|
17018
|
+
console.log(chalk(t('native.server.portInUse', {
|
|
17019
|
+
port,
|
|
17020
|
+
next: port + 1,
|
|
17021
|
+
})));
|
|
17022
|
+
}
|
|
16320
17023
|
resolve(false);
|
|
16321
17024
|
}
|
|
16322
17025
|
else {
|
|
16323
|
-
|
|
16324
|
-
|
|
17026
|
+
if (options?.quiet) {
|
|
17027
|
+
reject(err);
|
|
17028
|
+
}
|
|
17029
|
+
else {
|
|
17030
|
+
console.log(chalk.red.bold(err.message));
|
|
17031
|
+
process.exit(1);
|
|
17032
|
+
}
|
|
16325
17033
|
}
|
|
16326
17034
|
};
|
|
16327
17035
|
const onListening = () => {
|
|
@@ -16343,6 +17051,9 @@ async function listen(app, options) {
|
|
|
16343
17051
|
store.setState({ nodeServerPort: currentPort + 1 });
|
|
16344
17052
|
}
|
|
16345
17053
|
if (!isListening) {
|
|
17054
|
+
if (options?.quiet) {
|
|
17055
|
+
throw new Error(t('native.server.failedAfterRetries', { retries: maxRetries }));
|
|
17056
|
+
}
|
|
16346
17057
|
console.log(chalk.red.bold(t('native.server.failedAfterRetries', { retries: maxRetries })));
|
|
16347
17058
|
process.exit(1);
|
|
16348
17059
|
}
|
|
@@ -20790,37 +21501,1349 @@ function registerRoutes(app, options) {
|
|
|
20790
21501
|
|
|
20791
21502
|
const outputDir = getOutputDir();
|
|
20792
21503
|
const publicPath = path.join(__dirname, 'public');
|
|
20793
|
-
async function start() {
|
|
21504
|
+
async function start(options = {}) {
|
|
20794
21505
|
const startTime = Date.now();
|
|
20795
21506
|
const app = express();
|
|
20796
21507
|
setupMiddlewares(app, { publicPath, outputDir });
|
|
20797
21508
|
registerRoutes(app, { publicPath });
|
|
20798
|
-
const
|
|
20799
|
-
|
|
20800
|
-
|
|
21509
|
+
const result = await listen(app, {
|
|
21510
|
+
maxRetries: 20,
|
|
21511
|
+
quiet: options.quiet,
|
|
21512
|
+
});
|
|
21513
|
+
const { url, version } = result;
|
|
21514
|
+
if (!options.quiet) {
|
|
21515
|
+
console.log(chalk.green.bold(`TTMG`), chalk.green(`v${version}`), chalk.gray(t('native.server.readyIn')), chalk.bold(`${Date.now() - startTime}ms`));
|
|
21516
|
+
showTips({ server: url });
|
|
21517
|
+
}
|
|
20801
21518
|
// 联调场景下(root `dev:debug` 脚本)我们用 vite dev server 提供 IDE,
|
|
20802
21519
|
// 不需要 CLI 自己再开浏览器到 dist/public 里那份打包后的旧 IDE。设置
|
|
20803
21520
|
// `TTMG_DEV_NO_OPEN=1` 抑制 openUrl,由调度脚本统一负责开 5173。
|
|
20804
21521
|
// 普通用户场景(npm 安装 ttmg)不会设置这个 env var,行为不变。
|
|
20805
|
-
if (process.env.TTMG_DEV_NO_OPEN === '1') {
|
|
20806
|
-
|
|
20807
|
-
|
|
21522
|
+
if (options.openBrowser === false || process.env.TTMG_DEV_NO_OPEN === '1') {
|
|
21523
|
+
if (!options.quiet && process.env.TTMG_DEV_NO_OPEN === '1') {
|
|
21524
|
+
console.log(chalk.gray(`[dev-debug] TTMG_DEV_NO_OPEN=1 detected, skipping browser auto-open. Use vite dev server (likely http://localhost:5173) instead.`));
|
|
21525
|
+
}
|
|
21526
|
+
return result;
|
|
20808
21527
|
}
|
|
20809
21528
|
openUrl(url);
|
|
21529
|
+
return result;
|
|
20810
21530
|
}
|
|
20811
21531
|
|
|
21532
|
+
function buildDevLaunchUrl({ clientKey, host, hosts, wsPort, }) {
|
|
21533
|
+
return `https://www.tiktok.com/ttmg/dev/${encodeURIComponent(clientKey)}?host=${encodeURIComponent(host)}&port=${wsPort}&host_list=${encodeURIComponent(JSON.stringify(hosts))}`;
|
|
21534
|
+
}
|
|
21535
|
+
class DevSessionReporter {
|
|
21536
|
+
constructor(options) {
|
|
21537
|
+
this.options = options;
|
|
21538
|
+
this.seq = 0;
|
|
21539
|
+
this.sessionId = (options.id || (() => `dev_${crypto.randomUUID()}`))();
|
|
21540
|
+
this.startedAt = (this.options.now || Date.now)();
|
|
21541
|
+
this.directory = path.resolve(options.rootDir || path.join('.ttmg', 'sessions', this.sessionId));
|
|
21542
|
+
this.manifestFile = path.join(this.directory, 'manifest.json');
|
|
21543
|
+
this.eventsFile = path.join(this.directory, 'events.ndjson');
|
|
21544
|
+
this.actionsFile = path.join(this.directory, 'actions.ndjson');
|
|
21545
|
+
this.screenshotsDirectory = path.join(this.directory, 'screenshots');
|
|
21546
|
+
this.summaryFile = path.join(this.directory, 'summary.json');
|
|
21547
|
+
this.qrFile = path.join(this.directory, 'launch.png');
|
|
21548
|
+
fs.mkdirSync(this.directory, { recursive: true });
|
|
21549
|
+
fs.mkdirSync(this.screenshotsDirectory, { recursive: true });
|
|
21550
|
+
fs.writeFileSync(this.actionsFile, '', { flag: 'a' });
|
|
21551
|
+
}
|
|
21552
|
+
emit(type, payload, source = 'cli') {
|
|
21553
|
+
const event = {
|
|
21554
|
+
schemaVersion: '1',
|
|
21555
|
+
sessionId: this.sessionId,
|
|
21556
|
+
seq: ++this.seq,
|
|
21557
|
+
ts: (this.options.now || Date.now)(),
|
|
21558
|
+
source,
|
|
21559
|
+
type,
|
|
21560
|
+
payload,
|
|
21561
|
+
};
|
|
21562
|
+
const line = JSON.stringify(event);
|
|
21563
|
+
fs.appendFileSync(this.eventsFile, `${line}\n`, 'utf8');
|
|
21564
|
+
if (this.options.events === 'ndjson') {
|
|
21565
|
+
(this.options.stdout || console.log)(line);
|
|
21566
|
+
}
|
|
21567
|
+
return event;
|
|
21568
|
+
}
|
|
21569
|
+
async writeReadyManifest({ pageUrl, version, hosts, httpPort, wsPort, }) {
|
|
21570
|
+
const advertisedHost = hosts[0] || '127.0.0.1';
|
|
21571
|
+
const rawUrl = buildDevLaunchUrl({
|
|
21572
|
+
clientKey: this.options.clientKey,
|
|
21573
|
+
host: advertisedHost,
|
|
21574
|
+
hosts,
|
|
21575
|
+
wsPort,
|
|
21576
|
+
});
|
|
21577
|
+
await QRCode.toFile(this.qrFile, rawUrl, { width: 320, margin: 2 });
|
|
21578
|
+
const manifest = {
|
|
21579
|
+
schemaVersion: '1',
|
|
21580
|
+
cliVersion: version,
|
|
21581
|
+
sessionId: this.sessionId,
|
|
21582
|
+
status: 'ready',
|
|
21583
|
+
project: {
|
|
21584
|
+
clientKey: this.options.clientKey,
|
|
21585
|
+
directory: process.cwd(),
|
|
21586
|
+
},
|
|
21587
|
+
process: {
|
|
21588
|
+
pid: process.pid,
|
|
21589
|
+
startedAt: this.startedAt,
|
|
21590
|
+
command: 'ttmg dev',
|
|
21591
|
+
},
|
|
21592
|
+
endpoints: {
|
|
21593
|
+
page: pageUrl,
|
|
21594
|
+
runtimeWs: `ws://${advertisedHost}:${wsPort}`,
|
|
21595
|
+
},
|
|
21596
|
+
launch: {
|
|
21597
|
+
rawUrl,
|
|
21598
|
+
qrPayload: rawUrl,
|
|
21599
|
+
qrPngPath: this.qrFile,
|
|
21600
|
+
},
|
|
21601
|
+
network: {
|
|
21602
|
+
mode: hosts.length > 0 ? 'lan' : 'loopback',
|
|
21603
|
+
advertisedHost,
|
|
21604
|
+
hosts,
|
|
21605
|
+
httpPort,
|
|
21606
|
+
wsPort,
|
|
21607
|
+
},
|
|
21608
|
+
evidence: {
|
|
21609
|
+
directory: this.directory,
|
|
21610
|
+
manifest: this.manifestFile,
|
|
21611
|
+
events: this.eventsFile,
|
|
21612
|
+
actions: this.actionsFile,
|
|
21613
|
+
screenshots: this.screenshotsDirectory,
|
|
21614
|
+
summary: this.summaryFile,
|
|
21615
|
+
},
|
|
21616
|
+
capabilities: {
|
|
21617
|
+
structuredEvents: true,
|
|
21618
|
+
manualScan: true,
|
|
21619
|
+
gameReadyProtocol: true,
|
|
21620
|
+
gameReady: false,
|
|
21621
|
+
deviceLaunch: false,
|
|
21622
|
+
runtimeLogs: false,
|
|
21623
|
+
screenshot: false,
|
|
21624
|
+
actions: false,
|
|
21625
|
+
},
|
|
21626
|
+
errorCode: null,
|
|
21627
|
+
error: null,
|
|
21628
|
+
};
|
|
21629
|
+
fs.writeFileSync(this.manifestFile, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
21630
|
+
return manifest;
|
|
21631
|
+
}
|
|
21632
|
+
writeFailedManifest(errorCode, error) {
|
|
21633
|
+
const result = {
|
|
21634
|
+
schemaVersion: '1',
|
|
21635
|
+
sessionId: this.sessionId,
|
|
21636
|
+
status: 'failed',
|
|
21637
|
+
project: {
|
|
21638
|
+
clientKey: this.options.clientKey,
|
|
21639
|
+
directory: process.cwd(),
|
|
21640
|
+
},
|
|
21641
|
+
evidence: {
|
|
21642
|
+
directory: this.directory,
|
|
21643
|
+
manifest: this.manifestFile,
|
|
21644
|
+
events: this.eventsFile,
|
|
21645
|
+
actions: this.actionsFile,
|
|
21646
|
+
screenshots: this.screenshotsDirectory,
|
|
21647
|
+
summary: this.summaryFile,
|
|
21648
|
+
},
|
|
21649
|
+
errorCode,
|
|
21650
|
+
error,
|
|
21651
|
+
};
|
|
21652
|
+
fs.writeFileSync(this.manifestFile, `${JSON.stringify(result, null, 2)}\n`, 'utf8');
|
|
21653
|
+
return result;
|
|
21654
|
+
}
|
|
21655
|
+
writeSummary(status, reason, errorCode = null) {
|
|
21656
|
+
const summary = {
|
|
21657
|
+
schemaVersion: '1',
|
|
21658
|
+
sessionId: this.sessionId,
|
|
21659
|
+
status,
|
|
21660
|
+
reason,
|
|
21661
|
+
errorCode,
|
|
21662
|
+
completedAt: (this.options.now || Date.now)(),
|
|
21663
|
+
};
|
|
21664
|
+
fs.writeFileSync(this.summaryFile, `${JSON.stringify(summary, null, 2)}\n`, 'utf8');
|
|
21665
|
+
return summary;
|
|
21666
|
+
}
|
|
21667
|
+
}
|
|
21668
|
+
function resolveDevSessionCompletion({ signal, timeoutSeconds, clientConnectedObserved, gameReadyObserved, }) {
|
|
21669
|
+
if (gameReadyObserved) {
|
|
21670
|
+
return {
|
|
21671
|
+
status: 'passed',
|
|
21672
|
+
reason: 'GAME_READY was observed from reportScene({ sceneId: 9999 }).',
|
|
21673
|
+
errorCode: null,
|
|
21674
|
+
exitCode: 0,
|
|
21675
|
+
};
|
|
21676
|
+
}
|
|
21677
|
+
if (signal === 'TIMEOUT') {
|
|
21678
|
+
const errorCode = clientConnectedObserved
|
|
21679
|
+
? 'GAME_READY_TIMEOUT'
|
|
21680
|
+
: 'CLIENT_CONNECT_TIMEOUT';
|
|
21681
|
+
return {
|
|
21682
|
+
status: 'blocked',
|
|
21683
|
+
reason: clientConnectedObserved
|
|
21684
|
+
? `The local DevTool session timed out after ${timeoutSeconds} seconds before GAME_READY.`
|
|
21685
|
+
: `The local DevTool session timed out after ${timeoutSeconds} seconds before a client connected.`,
|
|
21686
|
+
errorCode,
|
|
21687
|
+
exitCode: 2,
|
|
21688
|
+
};
|
|
21689
|
+
}
|
|
21690
|
+
return {
|
|
21691
|
+
status: 'blocked',
|
|
21692
|
+
reason: 'The local DevTool session stopped without GAME_READY evidence; device launch and runtime log capture are not enabled in this beta.',
|
|
21693
|
+
errorCode: null,
|
|
21694
|
+
exitCode: 0,
|
|
21695
|
+
};
|
|
21696
|
+
}
|
|
21697
|
+
|
|
21698
|
+
const defaultDependencies$3 = {
|
|
21699
|
+
checkPackages: ttmgPack.checkPkgs,
|
|
21700
|
+
operationId: () => `check_${crypto.randomUUID()}`,
|
|
21701
|
+
};
|
|
21702
|
+
function normalizeFormat$1(format) {
|
|
21703
|
+
const value = (format || 'text').toLowerCase();
|
|
21704
|
+
if (value !== 'text' && value !== 'json') {
|
|
21705
|
+
throw new Error(t('check.error.invalidFormat', { format: value }));
|
|
21706
|
+
}
|
|
21707
|
+
return value;
|
|
21708
|
+
}
|
|
21709
|
+
function normalizeFailOn(failOn) {
|
|
21710
|
+
const value = (failOn || 'error').toLowerCase();
|
|
21711
|
+
if (value !== 'error' && value !== 'warning') {
|
|
21712
|
+
throw new Error(t('check.error.invalidFailOn', { failOn: value }));
|
|
21713
|
+
}
|
|
21714
|
+
return value;
|
|
21715
|
+
}
|
|
21716
|
+
function codePart(value) {
|
|
21717
|
+
return value
|
|
21718
|
+
.replace(/[^a-zA-Z0-9]+/g, '_')
|
|
21719
|
+
.replace(/^_+|_+$/g, '')
|
|
21720
|
+
.toUpperCase();
|
|
21721
|
+
}
|
|
21722
|
+
function toCheckDiagnostic(result) {
|
|
21723
|
+
const type = result.type || 'unknown';
|
|
21724
|
+
const name = result.name || result.dimension || 'project';
|
|
21725
|
+
return {
|
|
21726
|
+
code: `TTMG_CHECK_${codePart(type)}_${codePart(name)}`,
|
|
21727
|
+
severity: result.level || (result.passed ? 'info' : 'error'),
|
|
21728
|
+
passed: result.passed,
|
|
21729
|
+
type,
|
|
21730
|
+
dimension: result.dimension || 'project',
|
|
21731
|
+
name,
|
|
21732
|
+
required: result.required === true,
|
|
21733
|
+
message: result.msg,
|
|
21734
|
+
location: {
|
|
21735
|
+
file: result.file || null,
|
|
21736
|
+
files: Array.isArray(result.files) ? result.files : [],
|
|
21737
|
+
},
|
|
21738
|
+
metrics: {
|
|
21739
|
+
sizeBytes: typeof result.size === 'number' ? result.size : null,
|
|
21740
|
+
},
|
|
21741
|
+
help: {
|
|
21742
|
+
url: result.detail?.link || null,
|
|
21743
|
+
text: result.detail?.text || null,
|
|
21744
|
+
},
|
|
21745
|
+
};
|
|
21746
|
+
}
|
|
21747
|
+
function createFailedReport({ operationId, directory, code, message, }) {
|
|
21748
|
+
const diagnostic = {
|
|
21749
|
+
code,
|
|
21750
|
+
severity: 'error',
|
|
21751
|
+
passed: false,
|
|
21752
|
+
type: 'project',
|
|
21753
|
+
dimension: 'project',
|
|
21754
|
+
name: 'project',
|
|
21755
|
+
required: true,
|
|
21756
|
+
message,
|
|
21757
|
+
location: { file: null, files: [] },
|
|
21758
|
+
metrics: { sizeBytes: null },
|
|
21759
|
+
help: { url: null, text: null },
|
|
21760
|
+
};
|
|
21761
|
+
return {
|
|
21762
|
+
schemaVersion: '1',
|
|
21763
|
+
operationId,
|
|
21764
|
+
command: 'check',
|
|
21765
|
+
status: 'failed',
|
|
21766
|
+
project: { directory },
|
|
21767
|
+
summary: { total: 1, passed: 0, warnings: 0, errors: 1 },
|
|
21768
|
+
diagnostics: [diagnostic],
|
|
21769
|
+
errorCode: code,
|
|
21770
|
+
error: message,
|
|
21771
|
+
};
|
|
21772
|
+
}
|
|
21773
|
+
async function executeCheck(options, dependencies = {}) {
|
|
21774
|
+
const deps = { ...defaultDependencies$3, ...dependencies };
|
|
21775
|
+
const operationId = deps.operationId();
|
|
21776
|
+
const directory = path.resolve(process.cwd(), options.dir || '.');
|
|
21777
|
+
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {
|
|
21778
|
+
return createFailedReport({
|
|
21779
|
+
operationId,
|
|
21780
|
+
directory,
|
|
21781
|
+
code: 'CHECK_DIRECTORY_NOT_FOUND',
|
|
21782
|
+
message: t('check.error.dirNotExist', { dir: directory }),
|
|
21783
|
+
});
|
|
21784
|
+
}
|
|
21785
|
+
const hasEntry = NATIVE_GAME_ENTRY_FILES.some(file => fs.existsSync(path.join(directory, file)));
|
|
21786
|
+
if (!hasEntry) {
|
|
21787
|
+
return createFailedReport({
|
|
21788
|
+
operationId,
|
|
21789
|
+
directory,
|
|
21790
|
+
code: 'CHECK_INVALID_PROJECT',
|
|
21791
|
+
message: t('native.check.notWorkspace'),
|
|
21792
|
+
});
|
|
21793
|
+
}
|
|
21794
|
+
try {
|
|
21795
|
+
const results = await deps.checkPackages({
|
|
21796
|
+
entry: directory,
|
|
21797
|
+
output: directory,
|
|
21798
|
+
dev: {
|
|
21799
|
+
enable: true,
|
|
21800
|
+
enableSourcemap: false,
|
|
21801
|
+
enableLog: false,
|
|
21802
|
+
},
|
|
21803
|
+
build: {
|
|
21804
|
+
enableOdr: false,
|
|
21805
|
+
enableAPICheck: true,
|
|
21806
|
+
...PKG_SIZE_LIMIT,
|
|
21807
|
+
},
|
|
21808
|
+
}, { lang: getCurrentLanguage() });
|
|
21809
|
+
const allDiagnostics = results.map(toCheckDiagnostic);
|
|
21810
|
+
const errors = allDiagnostics.filter(item => !item.passed && item.severity === 'error').length;
|
|
21811
|
+
const warnings = allDiagnostics.filter(item => !item.passed && item.severity === 'warning').length;
|
|
21812
|
+
const passed = allDiagnostics.filter(item => item.passed).length;
|
|
21813
|
+
return {
|
|
21814
|
+
schemaVersion: '1',
|
|
21815
|
+
operationId,
|
|
21816
|
+
command: 'check',
|
|
21817
|
+
status: errors > 0 ? 'failed' : warnings > 0 ? 'warning' : 'passed',
|
|
21818
|
+
project: { directory },
|
|
21819
|
+
summary: {
|
|
21820
|
+
total: allDiagnostics.length,
|
|
21821
|
+
passed,
|
|
21822
|
+
warnings,
|
|
21823
|
+
errors,
|
|
21824
|
+
},
|
|
21825
|
+
diagnostics: options.includePassed
|
|
21826
|
+
? allDiagnostics
|
|
21827
|
+
: allDiagnostics.filter(item => !item.passed),
|
|
21828
|
+
errorCode: errors > 0 ? 'CHECK_FAILED' : null,
|
|
21829
|
+
error: null,
|
|
21830
|
+
};
|
|
21831
|
+
}
|
|
21832
|
+
catch (error) {
|
|
21833
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21834
|
+
return createFailedReport({
|
|
21835
|
+
operationId,
|
|
21836
|
+
directory,
|
|
21837
|
+
code: 'CHECK_EXECUTION_FAILED',
|
|
21838
|
+
message,
|
|
21839
|
+
});
|
|
21840
|
+
}
|
|
21841
|
+
}
|
|
21842
|
+
function printText(report) {
|
|
21843
|
+
const color = report.status === 'failed'
|
|
21844
|
+
? chalk.red
|
|
21845
|
+
: report.status === 'warning'
|
|
21846
|
+
? chalk.yellow
|
|
21847
|
+
: chalk.green;
|
|
21848
|
+
console.log(color.bold(t('check.summary', {
|
|
21849
|
+
status: report.status,
|
|
21850
|
+
passed: report.summary.passed,
|
|
21851
|
+
warnings: report.summary.warnings,
|
|
21852
|
+
errors: report.summary.errors,
|
|
21853
|
+
})));
|
|
21854
|
+
for (const diagnostic of report.diagnostics) {
|
|
21855
|
+
const location = diagnostic.location.file
|
|
21856
|
+
? ` (${diagnostic.location.file})`
|
|
21857
|
+
: '';
|
|
21858
|
+
console.log(`${diagnostic.severity.toUpperCase()} ${diagnostic.code}: ${diagnostic.message}${location}`);
|
|
21859
|
+
if (diagnostic.help.url)
|
|
21860
|
+
console.log(` ${diagnostic.help.url}`);
|
|
21861
|
+
}
|
|
21862
|
+
}
|
|
21863
|
+
async function checkCommand(options = {}) {
|
|
21864
|
+
let format = 'text';
|
|
21865
|
+
let failOn = 'error';
|
|
21866
|
+
try {
|
|
21867
|
+
format = normalizeFormat$1(options.format);
|
|
21868
|
+
failOn = normalizeFailOn(options.failOn);
|
|
21869
|
+
}
|
|
21870
|
+
catch (error) {
|
|
21871
|
+
const report = createFailedReport({
|
|
21872
|
+
operationId: `check_${crypto.randomUUID()}`,
|
|
21873
|
+
directory: path.resolve(process.cwd(), options.dir || '.'),
|
|
21874
|
+
code: 'CHECK_INVALID_ARGUMENT',
|
|
21875
|
+
message: error instanceof Error ? error.message : String(error),
|
|
21876
|
+
});
|
|
21877
|
+
if (options.format === 'json')
|
|
21878
|
+
console.log(JSON.stringify(report));
|
|
21879
|
+
else
|
|
21880
|
+
printText(report);
|
|
21881
|
+
process.exitCode = 1;
|
|
21882
|
+
return report;
|
|
21883
|
+
}
|
|
21884
|
+
const report = await executeCheck(options);
|
|
21885
|
+
if (format === 'json')
|
|
21886
|
+
console.log(JSON.stringify(report));
|
|
21887
|
+
else
|
|
21888
|
+
printText(report);
|
|
21889
|
+
if (report.status === 'failed' ||
|
|
21890
|
+
(failOn === 'warning' && report.status === 'warning')) {
|
|
21891
|
+
process.exitCode = 1;
|
|
21892
|
+
}
|
|
21893
|
+
return report;
|
|
21894
|
+
}
|
|
21895
|
+
|
|
21896
|
+
function normalizeFormat(format) {
|
|
21897
|
+
const value = (format || 'text').toLowerCase();
|
|
21898
|
+
if (value !== 'text' && value !== 'json') {
|
|
21899
|
+
throw new Error(`Unsupported dev output format: ${value}. Use text or json.`);
|
|
21900
|
+
}
|
|
21901
|
+
return value;
|
|
21902
|
+
}
|
|
21903
|
+
function normalizeEvents(events) {
|
|
21904
|
+
const value = (events || 'none').toLowerCase();
|
|
21905
|
+
if (value !== 'none' && value !== 'ndjson') {
|
|
21906
|
+
throw new Error(`Unsupported dev events format: ${value}. Use none or ndjson.`);
|
|
21907
|
+
}
|
|
21908
|
+
return value;
|
|
21909
|
+
}
|
|
21910
|
+
function normalizeTimeout(timeout) {
|
|
21911
|
+
if (timeout === undefined || timeout === '')
|
|
21912
|
+
return 0;
|
|
21913
|
+
const seconds = Number(timeout);
|
|
21914
|
+
if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 86400) {
|
|
21915
|
+
throw new Error('Unsupported dev timeout. Use a number of seconds between 1 and 86400.');
|
|
21916
|
+
}
|
|
21917
|
+
return Math.round(seconds * 1000);
|
|
21918
|
+
}
|
|
20812
21919
|
async function dev(options = {}) {
|
|
20813
|
-
|
|
20814
|
-
|
|
20815
|
-
|
|
20816
|
-
|
|
20817
|
-
|
|
20818
|
-
|
|
20819
|
-
|
|
21920
|
+
const format = normalizeFormat(options.format);
|
|
21921
|
+
const events = normalizeEvents(options.events);
|
|
21922
|
+
if (format === 'json' && events === 'ndjson') {
|
|
21923
|
+
throw new Error('Dev output modes cannot be combined. Use either --format json or --events ndjson.');
|
|
21924
|
+
}
|
|
21925
|
+
const structured = format === 'json' || events === 'ndjson';
|
|
21926
|
+
const timeoutMs = normalizeTimeout(options.timeout);
|
|
21927
|
+
if (timeoutMs > 0 && !structured) {
|
|
21928
|
+
throw new Error('Dev timeout requires --format json or --events ndjson.');
|
|
21929
|
+
}
|
|
21930
|
+
const clientKey = options.clientKey?.trim() || '';
|
|
21931
|
+
const reporter = structured
|
|
21932
|
+
? new DevSessionReporter({
|
|
21933
|
+
clientKey,
|
|
21934
|
+
rootDir: options.evidenceDir,
|
|
21935
|
+
events,
|
|
21936
|
+
})
|
|
21937
|
+
: null;
|
|
21938
|
+
let gameReadyObserved = false;
|
|
21939
|
+
let clientConnectedObserved = false;
|
|
21940
|
+
const unsubscribeClientEvents = reporter
|
|
21941
|
+
? eventEmitter.on('devSession', event => {
|
|
21942
|
+
if (event.type === 'game_ready') {
|
|
21943
|
+
gameReadyObserved = true;
|
|
21944
|
+
reporter.emit('lifecycle', {
|
|
21945
|
+
state: 'GAME_READY',
|
|
21946
|
+
...event.payload,
|
|
21947
|
+
}, 'runtime');
|
|
21948
|
+
return;
|
|
21949
|
+
}
|
|
21950
|
+
if (event.type === 'client_connected') {
|
|
21951
|
+
clientConnectedObserved = true;
|
|
21952
|
+
}
|
|
21953
|
+
reporter.emit('client', {
|
|
21954
|
+
state: event.type,
|
|
21955
|
+
...event.payload,
|
|
21956
|
+
}, 'node');
|
|
21957
|
+
})
|
|
21958
|
+
: null;
|
|
21959
|
+
try {
|
|
21960
|
+
reporter?.emit('lifecycle', { state: 'CREATED' });
|
|
21961
|
+
if (structured && !clientKey) {
|
|
21962
|
+
throw new Error('Agent-friendly dev requires --client-key to avoid an interactive prompt.');
|
|
21963
|
+
}
|
|
21964
|
+
reporter?.emit('lifecycle', { state: 'CHECKING' });
|
|
21965
|
+
await check({ throwOnError: structured, quiet: structured });
|
|
21966
|
+
await init({ clientKey: options.clientKey });
|
|
21967
|
+
wsServer.start({ quiet: structured });
|
|
21968
|
+
const serverResult = await start({
|
|
21969
|
+
openBrowser: options.noOpen !== true,
|
|
21970
|
+
quiet: structured,
|
|
21971
|
+
});
|
|
21972
|
+
reporter?.emit('lifecycle', {
|
|
21973
|
+
state: 'SERVER_READY',
|
|
21974
|
+
page: serverResult.url,
|
|
21975
|
+
httpPort: serverResult.port,
|
|
21976
|
+
wsPort: store.getState().nodeWsPort,
|
|
21977
|
+
}, 'node');
|
|
21978
|
+
await compile({
|
|
21979
|
+
mode: 'start',
|
|
21980
|
+
quiet: structured,
|
|
21981
|
+
throwOnError: structured,
|
|
21982
|
+
onEvent: (state, error, checkResults) => reporter?.emit('compile', {
|
|
21983
|
+
state,
|
|
21984
|
+
error: error || null,
|
|
21985
|
+
diagnostics: (checkResults || []).map(result => toCheckDiagnostic(result)),
|
|
21986
|
+
}),
|
|
21987
|
+
});
|
|
21988
|
+
listen$1();
|
|
21989
|
+
const watcher = await watch({
|
|
21990
|
+
quiet: structured,
|
|
21991
|
+
onCompileEvent: (state, error, checkResults) => reporter?.emit('compile', {
|
|
21992
|
+
state,
|
|
21993
|
+
error: error || null,
|
|
21994
|
+
diagnostics: (checkResults || []).map(result => toCheckDiagnostic(result)),
|
|
21995
|
+
}),
|
|
21996
|
+
});
|
|
21997
|
+
if (reporter) {
|
|
21998
|
+
const manifest = await reporter.writeReadyManifest({
|
|
21999
|
+
pageUrl: serverResult.url,
|
|
22000
|
+
version: serverResult.version,
|
|
22001
|
+
hosts: getLocalIPs(),
|
|
22002
|
+
httpPort: serverResult.port,
|
|
22003
|
+
wsPort: store.getState().nodeWsPort,
|
|
22004
|
+
});
|
|
22005
|
+
reporter.emit('lifecycle', {
|
|
22006
|
+
state: 'READY',
|
|
22007
|
+
manifest: reporter.manifestFile,
|
|
22008
|
+
});
|
|
22009
|
+
if (format === 'json')
|
|
22010
|
+
console.log(JSON.stringify(manifest));
|
|
22011
|
+
let stopping = false;
|
|
22012
|
+
let timeoutTimer = null;
|
|
22013
|
+
const stop = async (signal) => {
|
|
22014
|
+
if (stopping)
|
|
22015
|
+
return;
|
|
22016
|
+
stopping = true;
|
|
22017
|
+
reporter.emit('lifecycle', { state: 'STOPPING', signal });
|
|
22018
|
+
if (timeoutTimer)
|
|
22019
|
+
clearTimeout(timeoutTimer);
|
|
22020
|
+
await watcher.close();
|
|
22021
|
+
unsubscribeClientEvents?.();
|
|
22022
|
+
wsServer.close();
|
|
22023
|
+
await new Promise(resolve => serverResult.server.close(() => resolve()));
|
|
22024
|
+
const completion = resolveDevSessionCompletion({
|
|
22025
|
+
signal,
|
|
22026
|
+
timeoutSeconds: timeoutMs / 1000,
|
|
22027
|
+
clientConnectedObserved,
|
|
22028
|
+
gameReadyObserved,
|
|
22029
|
+
});
|
|
22030
|
+
reporter.emit('lifecycle', {
|
|
22031
|
+
state: 'COMPLETED',
|
|
22032
|
+
signal,
|
|
22033
|
+
gameReadyObserved,
|
|
22034
|
+
errorCode: completion.errorCode,
|
|
22035
|
+
});
|
|
22036
|
+
reporter.writeSummary(completion.status, completion.reason, completion.errorCode);
|
|
22037
|
+
process.exit(completion.exitCode);
|
|
22038
|
+
};
|
|
22039
|
+
if (timeoutMs > 0) {
|
|
22040
|
+
timeoutTimer = setTimeout(() => void stop('TIMEOUT'), timeoutMs);
|
|
22041
|
+
}
|
|
22042
|
+
process.once('SIGINT', () => void stop('SIGINT'));
|
|
22043
|
+
process.once('SIGTERM', () => void stop('SIGTERM'));
|
|
22044
|
+
}
|
|
22045
|
+
}
|
|
22046
|
+
catch (error) {
|
|
22047
|
+
if (!reporter)
|
|
22048
|
+
throw error;
|
|
22049
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22050
|
+
unsubscribeClientEvents?.();
|
|
22051
|
+
wsServer.close();
|
|
22052
|
+
const errorCode = !clientKey
|
|
22053
|
+
? 'CLIENT_KEY_REQUIRED'
|
|
22054
|
+
: message.includes('project entry') || message.includes('工程入口')
|
|
22055
|
+
? 'INVALID_PROJECT'
|
|
22056
|
+
: 'DEV_START_FAILED';
|
|
22057
|
+
reporter.emit('lifecycle', {
|
|
22058
|
+
state: 'FAILED',
|
|
22059
|
+
errorCode,
|
|
22060
|
+
error: message,
|
|
22061
|
+
});
|
|
22062
|
+
const failed = reporter.writeFailedManifest(errorCode, message);
|
|
22063
|
+
reporter.writeSummary('failed', message, errorCode);
|
|
22064
|
+
if (format === 'json')
|
|
22065
|
+
console.log(JSON.stringify(failed));
|
|
22066
|
+
process.exitCode = 1;
|
|
22067
|
+
return failed;
|
|
22068
|
+
}
|
|
22069
|
+
}
|
|
22070
|
+
|
|
22071
|
+
const sourceExtensions = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
|
|
22072
|
+
const ignoredDirectories = new Set([
|
|
22073
|
+
'.git',
|
|
22074
|
+
'.ttmg',
|
|
22075
|
+
'node_modules',
|
|
22076
|
+
'__TTMG__',
|
|
22077
|
+
]);
|
|
22078
|
+
const maxFileBytes = 5 * 1024 * 1024;
|
|
22079
|
+
const maxScannedBytes = 100 * 1024 * 1024;
|
|
22080
|
+
const maxScannedFiles = 5000;
|
|
22081
|
+
const gameReadyPattern = /\breportScene\s*\(\s*\{[\s\S]{0,300}?(?:\bsceneId\b|['"]sceneId['"])\s*:\s*9999\b/;
|
|
22082
|
+
function scanGameReadySignal(directory) {
|
|
22083
|
+
const pending = [directory];
|
|
22084
|
+
let scannedFiles = 0;
|
|
22085
|
+
let skippedFiles = 0;
|
|
22086
|
+
let scannedBytes = 0;
|
|
22087
|
+
let matchedFile = null;
|
|
22088
|
+
let truncated = false;
|
|
22089
|
+
while (pending.length > 0 && !matchedFile) {
|
|
22090
|
+
const current = pending.pop();
|
|
22091
|
+
let entries;
|
|
22092
|
+
try {
|
|
22093
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
22094
|
+
}
|
|
22095
|
+
catch {
|
|
22096
|
+
skippedFiles += 1;
|
|
22097
|
+
continue;
|
|
22098
|
+
}
|
|
22099
|
+
for (const entry of entries) {
|
|
22100
|
+
if (matchedFile)
|
|
22101
|
+
break;
|
|
22102
|
+
const absolute = path.join(current, entry.name);
|
|
22103
|
+
if (entry.isSymbolicLink()) {
|
|
22104
|
+
skippedFiles += 1;
|
|
22105
|
+
continue;
|
|
22106
|
+
}
|
|
22107
|
+
if (entry.isDirectory()) {
|
|
22108
|
+
if (!ignoredDirectories.has(entry.name))
|
|
22109
|
+
pending.push(absolute);
|
|
22110
|
+
continue;
|
|
22111
|
+
}
|
|
22112
|
+
if (!entry.isFile() || !sourceExtensions.has(path.extname(entry.name))) {
|
|
22113
|
+
continue;
|
|
22114
|
+
}
|
|
22115
|
+
if (scannedFiles >= maxScannedFiles ||
|
|
22116
|
+
scannedBytes >= maxScannedBytes) {
|
|
22117
|
+
truncated = true;
|
|
22118
|
+
break;
|
|
22119
|
+
}
|
|
22120
|
+
let stat;
|
|
22121
|
+
try {
|
|
22122
|
+
stat = fs.statSync(absolute);
|
|
22123
|
+
}
|
|
22124
|
+
catch {
|
|
22125
|
+
skippedFiles += 1;
|
|
22126
|
+
continue;
|
|
22127
|
+
}
|
|
22128
|
+
if (stat.size > maxFileBytes ||
|
|
22129
|
+
scannedBytes + stat.size > maxScannedBytes) {
|
|
22130
|
+
skippedFiles += 1;
|
|
22131
|
+
continue;
|
|
22132
|
+
}
|
|
22133
|
+
try {
|
|
22134
|
+
const source = fs.readFileSync(absolute, 'utf8');
|
|
22135
|
+
scannedFiles += 1;
|
|
22136
|
+
scannedBytes += stat.size;
|
|
22137
|
+
if (gameReadyPattern.test(source)) {
|
|
22138
|
+
matchedFile = path.relative(directory, absolute) || entry.name;
|
|
22139
|
+
}
|
|
22140
|
+
}
|
|
22141
|
+
catch {
|
|
22142
|
+
skippedFiles += 1;
|
|
22143
|
+
}
|
|
22144
|
+
}
|
|
22145
|
+
}
|
|
22146
|
+
return {
|
|
22147
|
+
confidence: 'static',
|
|
22148
|
+
gameReadySignal: {
|
|
22149
|
+
detected: matchedFile !== null,
|
|
22150
|
+
file: matchedFile,
|
|
22151
|
+
signal: 'reportScene({ sceneId: 9999 })',
|
|
22152
|
+
},
|
|
22153
|
+
scan: { scannedFiles, skippedFiles, scannedBytes, truncated },
|
|
22154
|
+
};
|
|
22155
|
+
}
|
|
22156
|
+
function readinessDiagnostic(evidence) {
|
|
22157
|
+
const detected = evidence.gameReadySignal.detected;
|
|
22158
|
+
return {
|
|
22159
|
+
code: 'TTMG_CHECK_AI_GAME_READY_SIGNAL',
|
|
22160
|
+
severity: detected ? 'info' : 'warning',
|
|
22161
|
+
passed: detected,
|
|
22162
|
+
type: 'ai',
|
|
22163
|
+
dimension: 'agent_debug',
|
|
22164
|
+
name: 'game_ready_signal',
|
|
22165
|
+
required: true,
|
|
22166
|
+
message: detected
|
|
22167
|
+
? t('check.ai.gameReady.found')
|
|
22168
|
+
: t('check.ai.gameReady.missing'),
|
|
22169
|
+
location: {
|
|
22170
|
+
file: evidence.gameReadySignal.file,
|
|
22171
|
+
files: evidence.gameReadySignal.file
|
|
22172
|
+
? [evidence.gameReadySignal.file]
|
|
22173
|
+
: [],
|
|
22174
|
+
},
|
|
22175
|
+
metrics: { sizeBytes: null },
|
|
22176
|
+
help: { url: null, text: t('check.ai.gameReady.help') },
|
|
22177
|
+
};
|
|
22178
|
+
}
|
|
22179
|
+
function emptyEvidence() {
|
|
22180
|
+
return {
|
|
22181
|
+
confidence: 'static',
|
|
22182
|
+
gameReadySignal: {
|
|
22183
|
+
detected: false,
|
|
22184
|
+
file: null,
|
|
22185
|
+
signal: 'reportScene({ sceneId: 9999 })',
|
|
22186
|
+
},
|
|
22187
|
+
scan: {
|
|
22188
|
+
scannedFiles: 0,
|
|
22189
|
+
skippedFiles: 0,
|
|
22190
|
+
scannedBytes: 0,
|
|
22191
|
+
truncated: false,
|
|
22192
|
+
},
|
|
22193
|
+
};
|
|
22194
|
+
}
|
|
22195
|
+
async function executeAiCheck(options, dependencies = {}) {
|
|
22196
|
+
const directory = path.resolve(process.cwd(), options.dir || '.');
|
|
22197
|
+
const base = await executeCheck({ ...options, includePassed: true }, {
|
|
22198
|
+
...dependencies,
|
|
22199
|
+
operationId: dependencies.operationId || (() => `check_ai_${crypto.randomUUID()}`),
|
|
22200
|
+
});
|
|
22201
|
+
const canScan = base.errorCode !== 'CHECK_DIRECTORY_NOT_FOUND' &&
|
|
22202
|
+
base.errorCode !== 'CHECK_INVALID_PROJECT';
|
|
22203
|
+
const aiReadiness = canScan ? scanGameReadySignal(directory) : emptyEvidence();
|
|
22204
|
+
const allDiagnostics = canScan
|
|
22205
|
+
? [...base.diagnostics, readinessDiagnostic(aiReadiness)]
|
|
22206
|
+
: base.diagnostics;
|
|
22207
|
+
const errors = allDiagnostics.filter(item => !item.passed && item.severity === 'error').length;
|
|
22208
|
+
const warnings = allDiagnostics.filter(item => !item.passed && item.severity === 'warning').length;
|
|
22209
|
+
const passed = allDiagnostics.filter(item => item.passed).length;
|
|
22210
|
+
return {
|
|
22211
|
+
schemaVersion: '1',
|
|
22212
|
+
operationId: base.operationId,
|
|
22213
|
+
command: 'check',
|
|
22214
|
+
mode: 'ai',
|
|
22215
|
+
status: errors > 0 ? 'failed' : warnings > 0 ? 'warning' : 'passed',
|
|
22216
|
+
project: { directory },
|
|
22217
|
+
summary: {
|
|
22218
|
+
total: allDiagnostics.length,
|
|
22219
|
+
passed,
|
|
22220
|
+
warnings,
|
|
22221
|
+
errors,
|
|
22222
|
+
},
|
|
22223
|
+
diagnostics: options.includePassed
|
|
22224
|
+
? allDiagnostics
|
|
22225
|
+
: allDiagnostics.filter(item => !item.passed),
|
|
22226
|
+
aiReadiness,
|
|
22227
|
+
platformWrite: false,
|
|
22228
|
+
errorCode: errors > 0 ? base.errorCode || 'CHECK_AI_FAILED' : null,
|
|
22229
|
+
error: base.error,
|
|
22230
|
+
};
|
|
22231
|
+
}
|
|
22232
|
+
function printAiText(report) {
|
|
22233
|
+
const color = report.status === 'failed'
|
|
22234
|
+
? chalk.red
|
|
22235
|
+
: report.status === 'warning'
|
|
22236
|
+
? chalk.yellow
|
|
22237
|
+
: chalk.green;
|
|
22238
|
+
console.log(color.bold(t('check.summary', {
|
|
22239
|
+
status: report.status,
|
|
22240
|
+
passed: report.summary.passed,
|
|
22241
|
+
warnings: report.summary.warnings,
|
|
22242
|
+
errors: report.summary.errors,
|
|
22243
|
+
})));
|
|
22244
|
+
for (const diagnostic of report.diagnostics) {
|
|
22245
|
+
const location = diagnostic.location.file
|
|
22246
|
+
? ` (${diagnostic.location.file})`
|
|
22247
|
+
: '';
|
|
22248
|
+
console.log(`${diagnostic.severity.toUpperCase()} ${diagnostic.code}: ${diagnostic.message}${location}`);
|
|
22249
|
+
if (diagnostic.help.text)
|
|
22250
|
+
console.log(` ${diagnostic.help.text}`);
|
|
22251
|
+
}
|
|
22252
|
+
}
|
|
22253
|
+
async function aiCheckCommand(options = {}) {
|
|
22254
|
+
let format = 'text';
|
|
22255
|
+
let failOn = 'error';
|
|
22256
|
+
try {
|
|
22257
|
+
format = normalizeFormat$1(options.format);
|
|
22258
|
+
failOn = normalizeFailOn(options.failOn);
|
|
22259
|
+
}
|
|
22260
|
+
catch (error) {
|
|
22261
|
+
const report = {
|
|
22262
|
+
schemaVersion: '1',
|
|
22263
|
+
operationId: `check_ai_${crypto.randomUUID()}`,
|
|
22264
|
+
command: 'check',
|
|
22265
|
+
mode: 'ai',
|
|
22266
|
+
status: 'failed',
|
|
22267
|
+
project: { directory: path.resolve(process.cwd(), options.dir || '.') },
|
|
22268
|
+
summary: { total: 1, passed: 0, warnings: 0, errors: 1 },
|
|
22269
|
+
diagnostics: [],
|
|
22270
|
+
aiReadiness: emptyEvidence(),
|
|
22271
|
+
platformWrite: false,
|
|
22272
|
+
errorCode: 'CHECK_INVALID_ARGUMENT',
|
|
22273
|
+
error: error instanceof Error ? error.message : String(error),
|
|
22274
|
+
};
|
|
22275
|
+
if (options.format === 'json')
|
|
22276
|
+
console.log(JSON.stringify(report));
|
|
22277
|
+
else
|
|
22278
|
+
printAiText(report);
|
|
22279
|
+
process.exitCode = 1;
|
|
22280
|
+
return report;
|
|
22281
|
+
}
|
|
22282
|
+
const report = await executeAiCheck(options);
|
|
22283
|
+
if (format === 'json')
|
|
22284
|
+
console.log(JSON.stringify(report));
|
|
22285
|
+
else
|
|
22286
|
+
printAiText(report);
|
|
22287
|
+
if (report.status === 'failed' ||
|
|
22288
|
+
(failOn === 'warning' && report.status === 'warning')) {
|
|
22289
|
+
process.exitCode = 1;
|
|
22290
|
+
}
|
|
22291
|
+
return report;
|
|
22292
|
+
}
|
|
22293
|
+
|
|
22294
|
+
const defaultDependencies$2 = {
|
|
22295
|
+
buildPackages: ttmgPack.debugPkgs,
|
|
22296
|
+
getPackages: ttmgPack.getPkgs,
|
|
22297
|
+
now: () => Date.now(),
|
|
22298
|
+
operationId: () => `build_${crypto.randomUUID()}`,
|
|
22299
|
+
};
|
|
22300
|
+
function failedReport({ operationId, directory, outputDirectory, durationMs, errorCode, error, diagnostics = [], }) {
|
|
22301
|
+
return {
|
|
22302
|
+
schemaVersion: '1',
|
|
22303
|
+
operationId,
|
|
22304
|
+
command: 'build',
|
|
22305
|
+
status: 'failed',
|
|
22306
|
+
project: { directory },
|
|
22307
|
+
artifact: {
|
|
22308
|
+
outputDirectory,
|
|
22309
|
+
engine: 'unknown',
|
|
22310
|
+
sourceSizeBytes: 0,
|
|
22311
|
+
packageSummaries: [],
|
|
22312
|
+
packages: {},
|
|
22313
|
+
packageConfig: null,
|
|
22314
|
+
},
|
|
22315
|
+
summary: {
|
|
22316
|
+
durationMs,
|
|
22317
|
+
checks: diagnostics.length,
|
|
22318
|
+
warnings: diagnostics.filter(item => !item.passed && item.severity === 'warning').length,
|
|
22319
|
+
errors: Math.max(1, diagnostics.filter(item => !item.passed && item.severity === 'error')
|
|
22320
|
+
.length),
|
|
22321
|
+
},
|
|
22322
|
+
diagnostics,
|
|
22323
|
+
platformWrite: false,
|
|
22324
|
+
errorCode,
|
|
22325
|
+
error,
|
|
22326
|
+
};
|
|
22327
|
+
}
|
|
22328
|
+
async function executeStructuredBuild(options, dependencies = {}) {
|
|
22329
|
+
const deps = { ...defaultDependencies$2, ...dependencies };
|
|
22330
|
+
const operationId = deps.operationId();
|
|
22331
|
+
const startedAt = deps.now();
|
|
22332
|
+
const directory = path.resolve(process.cwd(), options.dir || '.');
|
|
22333
|
+
const outputDirectory = path.join(os.homedir(), '__TTMG__', 'build', operationId);
|
|
22334
|
+
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {
|
|
22335
|
+
return failedReport({
|
|
22336
|
+
operationId,
|
|
22337
|
+
directory,
|
|
22338
|
+
outputDirectory,
|
|
22339
|
+
durationMs: deps.now() - startedAt,
|
|
22340
|
+
errorCode: 'BUILD_DIRECTORY_NOT_FOUND',
|
|
22341
|
+
error: t('build.error.dirNotExist', { dir: directory }),
|
|
22342
|
+
});
|
|
22343
|
+
}
|
|
22344
|
+
if (!NATIVE_GAME_ENTRY_FILES.some(file => fs.existsSync(path.join(directory, file)))) {
|
|
22345
|
+
return failedReport({
|
|
22346
|
+
operationId,
|
|
22347
|
+
directory,
|
|
22348
|
+
outputDirectory,
|
|
22349
|
+
durationMs: deps.now() - startedAt,
|
|
22350
|
+
errorCode: 'BUILD_INVALID_PROJECT',
|
|
22351
|
+
error: t('native.check.notWorkspace'),
|
|
22352
|
+
});
|
|
22353
|
+
}
|
|
22354
|
+
fs.mkdirSync(outputDirectory, { recursive: true });
|
|
22355
|
+
try {
|
|
22356
|
+
const result = await deps.buildPackages({
|
|
22357
|
+
entry: directory,
|
|
22358
|
+
output: outputDirectory,
|
|
22359
|
+
dev: {
|
|
22360
|
+
enable: true,
|
|
22361
|
+
port: 0,
|
|
22362
|
+
host: 'localhost',
|
|
22363
|
+
enableSourcemap: false,
|
|
22364
|
+
enableLog: false,
|
|
22365
|
+
},
|
|
22366
|
+
build: {
|
|
22367
|
+
enableAPICheck: true,
|
|
22368
|
+
enableOdr: false,
|
|
22369
|
+
...PKG_SIZE_LIMIT,
|
|
22370
|
+
},
|
|
22371
|
+
}, { lang: getCurrentLanguage() });
|
|
22372
|
+
const allDiagnostics = (result.checkResults || []).map(toCheckDiagnostic);
|
|
22373
|
+
const diagnostics = options.includePassed
|
|
22374
|
+
? allDiagnostics
|
|
22375
|
+
: allDiagnostics.filter(item => !item.passed);
|
|
22376
|
+
const errors = allDiagnostics.filter(item => !item.passed && item.severity === 'error').length;
|
|
22377
|
+
const warnings = allDiagnostics.filter(item => !item.passed && item.severity === 'warning').length;
|
|
22378
|
+
if (!result.isSuccess) {
|
|
22379
|
+
return failedReport({
|
|
22380
|
+
operationId,
|
|
22381
|
+
directory,
|
|
22382
|
+
outputDirectory,
|
|
22383
|
+
durationMs: deps.now() - startedAt,
|
|
22384
|
+
errorCode: 'BUILD_FAILED',
|
|
22385
|
+
error: result.errorMsg || t('build.error.failed'),
|
|
22386
|
+
diagnostics,
|
|
22387
|
+
});
|
|
22388
|
+
}
|
|
22389
|
+
const packageSummary = await deps.getPackages({ entryDir: directory });
|
|
22390
|
+
return {
|
|
22391
|
+
schemaVersion: '1',
|
|
22392
|
+
operationId,
|
|
22393
|
+
command: 'build',
|
|
22394
|
+
status: 'succeeded',
|
|
22395
|
+
project: { directory },
|
|
22396
|
+
artifact: {
|
|
22397
|
+
outputDirectory,
|
|
22398
|
+
engine: packageSummary.engine || 'unknown',
|
|
22399
|
+
sourceSizeBytes: packageSummary.size || 0,
|
|
22400
|
+
packageSummaries: packageSummary.packages || [],
|
|
22401
|
+
packages: result.packages || {},
|
|
22402
|
+
packageConfig: result.packageConfig || null,
|
|
22403
|
+
},
|
|
22404
|
+
summary: {
|
|
22405
|
+
durationMs: deps.now() - startedAt,
|
|
22406
|
+
checks: allDiagnostics.length,
|
|
22407
|
+
warnings,
|
|
22408
|
+
errors,
|
|
22409
|
+
},
|
|
22410
|
+
diagnostics,
|
|
22411
|
+
platformWrite: false,
|
|
22412
|
+
errorCode: null,
|
|
22413
|
+
error: null,
|
|
22414
|
+
};
|
|
22415
|
+
}
|
|
22416
|
+
catch (error) {
|
|
22417
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22418
|
+
return failedReport({
|
|
22419
|
+
operationId,
|
|
22420
|
+
directory,
|
|
22421
|
+
outputDirectory,
|
|
22422
|
+
durationMs: deps.now() - startedAt,
|
|
22423
|
+
errorCode: 'BUILD_EXECUTION_FAILED',
|
|
22424
|
+
error: message,
|
|
22425
|
+
});
|
|
22426
|
+
}
|
|
22427
|
+
}
|
|
22428
|
+
async function structuredBuildCommand(options = {}) {
|
|
22429
|
+
if ((options.format || '').toLowerCase() !== 'json') {
|
|
22430
|
+
console.error(t('build.error.invalidFormat', { format: options.format || '' }));
|
|
22431
|
+
process.exitCode = 1;
|
|
22432
|
+
return null;
|
|
22433
|
+
}
|
|
22434
|
+
const report = await executeStructuredBuild(options);
|
|
22435
|
+
console.log(JSON.stringify(report));
|
|
22436
|
+
if (report.status === 'failed')
|
|
22437
|
+
process.exitCode = 1;
|
|
22438
|
+
return report;
|
|
22439
|
+
}
|
|
22440
|
+
|
|
22441
|
+
const defaultDependencies$1 = {
|
|
22442
|
+
isProcessRunning: pid => {
|
|
22443
|
+
try {
|
|
22444
|
+
process.kill(pid, 0);
|
|
22445
|
+
return true;
|
|
22446
|
+
}
|
|
22447
|
+
catch (error) {
|
|
22448
|
+
return error?.code === 'EPERM';
|
|
22449
|
+
}
|
|
22450
|
+
},
|
|
22451
|
+
getProcessCommand: pid => {
|
|
22452
|
+
try {
|
|
22453
|
+
return child_process.execFileSync('ps', ['-p', String(pid), '-o', 'command='], {
|
|
22454
|
+
encoding: 'utf8',
|
|
22455
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
22456
|
+
}).trim();
|
|
22457
|
+
}
|
|
22458
|
+
catch {
|
|
22459
|
+
return '';
|
|
22460
|
+
}
|
|
22461
|
+
},
|
|
22462
|
+
killProcess: pid => process.kill(pid, 'SIGTERM'),
|
|
22463
|
+
sleep: ms => new Promise(resolve => setTimeout(resolve, ms)),
|
|
22464
|
+
};
|
|
22465
|
+
function parseJsonFile(file) {
|
|
22466
|
+
if (!fs.existsSync(file))
|
|
22467
|
+
return null;
|
|
22468
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
22469
|
+
}
|
|
22470
|
+
function resolveSessionDirectory(sessionId, sessionsDir) {
|
|
22471
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(sessionId)) {
|
|
22472
|
+
throw new Error(t('dev.session.error.invalidId', { sessionId }));
|
|
22473
|
+
}
|
|
22474
|
+
return path.resolve(sessionsDir || '.ttmg/sessions', sessionId);
|
|
22475
|
+
}
|
|
22476
|
+
function inspectDevSession(sessionId, options = {}, dependencies = {}) {
|
|
22477
|
+
const deps = { ...defaultDependencies$1, ...dependencies };
|
|
22478
|
+
let directory = '';
|
|
22479
|
+
try {
|
|
22480
|
+
directory = resolveSessionDirectory(sessionId, options.sessionsDir);
|
|
22481
|
+
const manifest = parseJsonFile(path.join(directory, 'manifest.json'));
|
|
22482
|
+
if (!manifest) {
|
|
22483
|
+
return {
|
|
22484
|
+
schemaVersion: '1',
|
|
22485
|
+
command: 'dev.session.inspect',
|
|
22486
|
+
status: 'not_found',
|
|
22487
|
+
sessionId,
|
|
22488
|
+
directory,
|
|
22489
|
+
manifest: null,
|
|
22490
|
+
summary: null,
|
|
22491
|
+
eventCount: 0,
|
|
22492
|
+
lastEvent: null,
|
|
22493
|
+
process: { pid: null, running: false },
|
|
22494
|
+
platformWrite: false,
|
|
22495
|
+
errorCode: 'DEV_SESSION_NOT_FOUND',
|
|
22496
|
+
error: t('dev.session.error.notFound', { sessionId }),
|
|
22497
|
+
};
|
|
22498
|
+
}
|
|
22499
|
+
if (manifest.sessionId !== sessionId) {
|
|
22500
|
+
throw new Error(t('dev.session.error.manifestMismatch'));
|
|
22501
|
+
}
|
|
22502
|
+
const summary = parseJsonFile(path.join(directory, 'summary.json'));
|
|
22503
|
+
const eventsFile = path.join(directory, 'events.ndjson');
|
|
22504
|
+
const eventLines = fs.existsSync(eventsFile)
|
|
22505
|
+
? fs.readFileSync(eventsFile, 'utf8').split('\n').filter(Boolean)
|
|
22506
|
+
: [];
|
|
22507
|
+
const lastEvent = eventLines.length
|
|
22508
|
+
? JSON.parse(eventLines[eventLines.length - 1])
|
|
22509
|
+
: null;
|
|
22510
|
+
const pid = Number(manifest.process?.pid) || null;
|
|
22511
|
+
return {
|
|
22512
|
+
schemaVersion: '1',
|
|
22513
|
+
command: 'dev.session.inspect',
|
|
22514
|
+
status: 'found',
|
|
22515
|
+
sessionId,
|
|
22516
|
+
directory,
|
|
22517
|
+
manifest,
|
|
22518
|
+
summary,
|
|
22519
|
+
eventCount: eventLines.length,
|
|
22520
|
+
lastEvent,
|
|
22521
|
+
process: {
|
|
22522
|
+
pid,
|
|
22523
|
+
running: pid ? deps.isProcessRunning(pid) : false,
|
|
22524
|
+
},
|
|
22525
|
+
platformWrite: false,
|
|
22526
|
+
errorCode: null,
|
|
22527
|
+
error: null,
|
|
22528
|
+
};
|
|
22529
|
+
}
|
|
22530
|
+
catch (error) {
|
|
22531
|
+
return {
|
|
22532
|
+
schemaVersion: '1',
|
|
22533
|
+
command: 'dev.session.inspect',
|
|
22534
|
+
status: 'failed',
|
|
22535
|
+
sessionId,
|
|
22536
|
+
directory,
|
|
22537
|
+
manifest: null,
|
|
22538
|
+
summary: null,
|
|
22539
|
+
eventCount: 0,
|
|
22540
|
+
lastEvent: null,
|
|
22541
|
+
process: { pid: null, running: false },
|
|
22542
|
+
platformWrite: false,
|
|
22543
|
+
errorCode: 'DEV_SESSION_READ_FAILED',
|
|
22544
|
+
error: error instanceof Error ? error.message : String(error),
|
|
22545
|
+
};
|
|
22546
|
+
}
|
|
22547
|
+
}
|
|
22548
|
+
function listDevSessions(options = {}, dependencies = {}) {
|
|
22549
|
+
const sessionsDirectory = path.resolve(options.sessionsDir || '.ttmg/sessions');
|
|
22550
|
+
const sessionIds = fs.existsSync(sessionsDirectory)
|
|
22551
|
+
? fs
|
|
22552
|
+
.readdirSync(sessionsDirectory, { withFileTypes: true })
|
|
22553
|
+
.filter(entry => entry.isDirectory())
|
|
22554
|
+
.map(entry => entry.name)
|
|
22555
|
+
.sort()
|
|
22556
|
+
: [];
|
|
22557
|
+
const sessions = sessionIds
|
|
22558
|
+
.map(sessionId => inspectDevSession(sessionId, options, dependencies))
|
|
22559
|
+
.filter(result => result.status === 'found')
|
|
22560
|
+
.map(result => ({
|
|
22561
|
+
sessionId: result.sessionId,
|
|
22562
|
+
directory: result.directory,
|
|
22563
|
+
status: result.summary?.status || result.manifest?.status || 'unknown',
|
|
22564
|
+
clientKey: result.manifest?.project?.clientKey || '',
|
|
22565
|
+
running: result.process.running,
|
|
22566
|
+
eventCount: result.eventCount,
|
|
22567
|
+
}));
|
|
22568
|
+
return {
|
|
22569
|
+
schemaVersion: '1',
|
|
22570
|
+
command: 'dev.session.list',
|
|
22571
|
+
status: 'succeeded',
|
|
22572
|
+
sessionsDirectory,
|
|
22573
|
+
count: sessions.length,
|
|
22574
|
+
sessions,
|
|
22575
|
+
platformWrite: false,
|
|
22576
|
+
errorCode: null,
|
|
22577
|
+
error: null,
|
|
22578
|
+
};
|
|
22579
|
+
}
|
|
22580
|
+
function readDevSessionEvents(sessionId, options = {}) {
|
|
22581
|
+
const inspected = inspectDevSession(sessionId, options);
|
|
22582
|
+
if (inspected.status !== 'found') {
|
|
22583
|
+
return { ...inspected, command: 'dev.session.events' };
|
|
22584
|
+
}
|
|
22585
|
+
const after = Number(options.after || 0);
|
|
22586
|
+
const limit = Number(options.limit || 100);
|
|
22587
|
+
if (!Number.isInteger(after) ||
|
|
22588
|
+
after < 0 ||
|
|
22589
|
+
!Number.isInteger(limit) ||
|
|
22590
|
+
limit < 1 ||
|
|
22591
|
+
limit > 1000) {
|
|
22592
|
+
return {
|
|
22593
|
+
schemaVersion: '1',
|
|
22594
|
+
command: 'dev.session.events',
|
|
22595
|
+
status: 'failed',
|
|
22596
|
+
sessionId,
|
|
22597
|
+
events: [],
|
|
22598
|
+
cursor: { after, next: after, hasMore: false },
|
|
22599
|
+
platformWrite: false,
|
|
22600
|
+
errorCode: 'DEV_SESSION_INVALID_CURSOR',
|
|
22601
|
+
error: t('dev.session.error.invalidCursor'),
|
|
22602
|
+
};
|
|
22603
|
+
}
|
|
22604
|
+
try {
|
|
22605
|
+
const eventsFile = path.join(inspected.directory, 'events.ndjson');
|
|
22606
|
+
const allEvents = fs.existsSync(eventsFile)
|
|
22607
|
+
? fs
|
|
22608
|
+
.readFileSync(eventsFile, 'utf8')
|
|
22609
|
+
.split('\n')
|
|
22610
|
+
.filter(Boolean)
|
|
22611
|
+
.map(line => JSON.parse(line))
|
|
22612
|
+
.filter(event => Number(event.seq) > after)
|
|
22613
|
+
: [];
|
|
22614
|
+
const events = allEvents.slice(0, limit);
|
|
22615
|
+
return {
|
|
22616
|
+
schemaVersion: '1',
|
|
22617
|
+
command: 'dev.session.events',
|
|
22618
|
+
status: 'succeeded',
|
|
22619
|
+
sessionId,
|
|
22620
|
+
events,
|
|
22621
|
+
cursor: {
|
|
22622
|
+
after,
|
|
22623
|
+
next: events.length ? Number(events[events.length - 1].seq) : after,
|
|
22624
|
+
hasMore: allEvents.length > events.length,
|
|
22625
|
+
},
|
|
22626
|
+
platformWrite: false,
|
|
22627
|
+
errorCode: null,
|
|
22628
|
+
error: null,
|
|
22629
|
+
};
|
|
22630
|
+
}
|
|
22631
|
+
catch (error) {
|
|
22632
|
+
return {
|
|
22633
|
+
schemaVersion: '1',
|
|
22634
|
+
command: 'dev.session.events',
|
|
22635
|
+
status: 'failed',
|
|
22636
|
+
sessionId,
|
|
22637
|
+
events: [],
|
|
22638
|
+
cursor: { after, next: after, hasMore: false },
|
|
22639
|
+
platformWrite: false,
|
|
22640
|
+
errorCode: 'DEV_SESSION_EVENTS_READ_FAILED',
|
|
22641
|
+
error: error instanceof Error ? error.message : String(error),
|
|
22642
|
+
};
|
|
22643
|
+
}
|
|
22644
|
+
}
|
|
22645
|
+
async function stopDevSession(sessionId, options = {}, dependencies = {}) {
|
|
22646
|
+
const deps = { ...defaultDependencies$1, ...dependencies };
|
|
22647
|
+
const inspected = inspectDevSession(sessionId, options, deps);
|
|
22648
|
+
if (inspected.status !== 'found') {
|
|
22649
|
+
return { ...inspected, command: 'dev.session.stop' };
|
|
22650
|
+
}
|
|
22651
|
+
const pid = inspected.process.pid;
|
|
22652
|
+
if (!pid || !inspected.process.running) {
|
|
22653
|
+
return {
|
|
22654
|
+
schemaVersion: '1',
|
|
22655
|
+
command: 'dev.session.stop',
|
|
22656
|
+
status: 'not_running',
|
|
22657
|
+
sessionId,
|
|
22658
|
+
pid,
|
|
22659
|
+
platformWrite: false,
|
|
22660
|
+
errorCode: null,
|
|
22661
|
+
error: null,
|
|
22662
|
+
};
|
|
22663
|
+
}
|
|
22664
|
+
const processCommand = deps.getProcessCommand(pid);
|
|
22665
|
+
if (!/(^|\s|\/)ttmg(\s|$).*\bdev\b|dist\/index\.js\s+dev\b/.test(processCommand)) {
|
|
22666
|
+
return {
|
|
22667
|
+
schemaVersion: '1',
|
|
22668
|
+
command: 'dev.session.stop',
|
|
22669
|
+
status: 'failed',
|
|
22670
|
+
sessionId,
|
|
22671
|
+
pid,
|
|
22672
|
+
platformWrite: false,
|
|
22673
|
+
errorCode: 'DEV_SESSION_PROCESS_UNVERIFIED',
|
|
22674
|
+
error: t('dev.session.error.unverifiedProcess'),
|
|
22675
|
+
};
|
|
22676
|
+
}
|
|
22677
|
+
try {
|
|
22678
|
+
deps.killProcess(pid);
|
|
22679
|
+
for (let attempt = 0; attempt < 25; attempt += 1) {
|
|
22680
|
+
if (!deps.isProcessRunning(pid)) {
|
|
22681
|
+
return {
|
|
22682
|
+
schemaVersion: '1',
|
|
22683
|
+
command: 'dev.session.stop',
|
|
22684
|
+
status: 'stopped',
|
|
22685
|
+
sessionId,
|
|
22686
|
+
pid,
|
|
22687
|
+
platformWrite: false,
|
|
22688
|
+
errorCode: null,
|
|
22689
|
+
error: null,
|
|
22690
|
+
};
|
|
22691
|
+
}
|
|
22692
|
+
await deps.sleep(100);
|
|
22693
|
+
}
|
|
22694
|
+
return {
|
|
22695
|
+
schemaVersion: '1',
|
|
22696
|
+
command: 'dev.session.stop',
|
|
22697
|
+
status: 'stopping',
|
|
22698
|
+
sessionId,
|
|
22699
|
+
pid,
|
|
22700
|
+
platformWrite: false,
|
|
22701
|
+
errorCode: null,
|
|
22702
|
+
error: null,
|
|
22703
|
+
};
|
|
22704
|
+
}
|
|
22705
|
+
catch (error) {
|
|
22706
|
+
return {
|
|
22707
|
+
schemaVersion: '1',
|
|
22708
|
+
command: 'dev.session.stop',
|
|
22709
|
+
status: 'failed',
|
|
22710
|
+
sessionId,
|
|
22711
|
+
pid,
|
|
22712
|
+
platformWrite: false,
|
|
22713
|
+
errorCode: 'DEV_SESSION_STOP_FAILED',
|
|
22714
|
+
error: error instanceof Error ? error.message : String(error),
|
|
22715
|
+
};
|
|
22716
|
+
}
|
|
22717
|
+
}
|
|
22718
|
+
function printResult(result, format) {
|
|
22719
|
+
if ((format || 'text').toLowerCase() === 'json') {
|
|
22720
|
+
console.log(JSON.stringify(result));
|
|
22721
|
+
}
|
|
22722
|
+
else {
|
|
22723
|
+
console.log(JSON.stringify(result, null, 2));
|
|
22724
|
+
}
|
|
22725
|
+
if (result.status === 'failed' || result.status === 'not_found') {
|
|
22726
|
+
process.exitCode = 1;
|
|
22727
|
+
}
|
|
22728
|
+
return result;
|
|
22729
|
+
}
|
|
22730
|
+
async function devSessionInspectCommand(sessionId, options = {}) {
|
|
22731
|
+
return printResult(inspectDevSession(sessionId, options), options.format);
|
|
22732
|
+
}
|
|
22733
|
+
async function devSessionListCommand(options = {}) {
|
|
22734
|
+
return printResult(listDevSessions(options), options.format);
|
|
22735
|
+
}
|
|
22736
|
+
async function devSessionEventsCommand(sessionId, options = {}) {
|
|
22737
|
+
return printResult(readDevSessionEvents(sessionId, options), options.format);
|
|
22738
|
+
}
|
|
22739
|
+
async function devSessionStopCommand(sessionId, options = {}) {
|
|
22740
|
+
return printResult(await stopDevSession(sessionId, options), options.format);
|
|
22741
|
+
}
|
|
22742
|
+
|
|
22743
|
+
const defaultDependencies = {
|
|
22744
|
+
nodeVersion: process.version,
|
|
22745
|
+
getLocalIPs,
|
|
22746
|
+
authenticated: () => Boolean(getCurrentUser()?.cookie),
|
|
22747
|
+
proxyEnvironment: () => ({
|
|
22748
|
+
http: Boolean(process.env.HTTP_PROXY || process.env.http_proxy),
|
|
22749
|
+
https: Boolean(process.env.HTTPS_PROXY || process.env.https_proxy),
|
|
22750
|
+
noProxy: Boolean(process.env.NO_PROXY || process.env.no_proxy),
|
|
22751
|
+
}),
|
|
22752
|
+
};
|
|
22753
|
+
function getDoctorReport(options = {}, dependencies = {}) {
|
|
22754
|
+
const deps = { ...defaultDependencies, ...dependencies };
|
|
22755
|
+
const directory = path.resolve(process.cwd(), options.dir || '.');
|
|
22756
|
+
const directoryExists = fs.existsSync(directory) && fs.statSync(directory).isDirectory();
|
|
22757
|
+
const projectDetected = directoryExists &&
|
|
22758
|
+
NATIVE_GAME_ENTRY_FILES.some(file => fs.existsSync(path.join(directory, file)));
|
|
22759
|
+
const hosts = deps.getLocalIPs();
|
|
22760
|
+
const nodeSupported = semver.satisfies(semver.coerce(deps.nodeVersion) || '0.0.0', pkg.engines.node);
|
|
22761
|
+
const authenticated = deps.authenticated();
|
|
22762
|
+
const diagnostics = [];
|
|
22763
|
+
if (!nodeSupported) {
|
|
22764
|
+
diagnostics.push({
|
|
22765
|
+
code: 'DOCTOR_NODE_VERSION_UNSUPPORTED',
|
|
22766
|
+
severity: 'error',
|
|
22767
|
+
message: `Node.js ${deps.nodeVersion} does not satisfy ${pkg.engines.node}.`,
|
|
22768
|
+
});
|
|
22769
|
+
}
|
|
22770
|
+
if (!projectDetected) {
|
|
22771
|
+
diagnostics.push({
|
|
22772
|
+
code: directoryExists
|
|
22773
|
+
? 'DOCTOR_PROJECT_NOT_FOUND'
|
|
22774
|
+
: 'DOCTOR_DIRECTORY_NOT_FOUND',
|
|
22775
|
+
severity: 'warning',
|
|
22776
|
+
message: directoryExists
|
|
22777
|
+
? 'No Mini Game project entry file was found in the target directory.'
|
|
22778
|
+
: 'The target directory does not exist.',
|
|
22779
|
+
});
|
|
22780
|
+
}
|
|
22781
|
+
if (hosts.length === 0) {
|
|
22782
|
+
diagnostics.push({
|
|
22783
|
+
code: 'DOCTOR_LAN_ADDRESS_NOT_FOUND',
|
|
22784
|
+
severity: 'warning',
|
|
22785
|
+
message: 'No physical LAN IPv4 address was found; device debugging will fall back to loopback and require a manual network solution.',
|
|
22786
|
+
});
|
|
22787
|
+
}
|
|
22788
|
+
if (!authenticated) {
|
|
22789
|
+
diagnostics.push({
|
|
22790
|
+
code: 'DOCTOR_AUTH_NOT_AVAILABLE',
|
|
22791
|
+
severity: 'warning',
|
|
22792
|
+
message: 'No reusable local developer session was found.',
|
|
22793
|
+
});
|
|
22794
|
+
}
|
|
22795
|
+
const errors = diagnostics.filter(item => item.severity === 'error').length;
|
|
22796
|
+
const warnings = diagnostics.filter(item => item.severity === 'warning').length;
|
|
22797
|
+
return {
|
|
22798
|
+
schemaVersion: '1',
|
|
22799
|
+
command: 'doctor',
|
|
22800
|
+
status: errors > 0 ? 'failed' : warnings > 0 ? 'warning' : 'passed',
|
|
22801
|
+
cliVersion: pkg.version,
|
|
22802
|
+
environment: {
|
|
22803
|
+
nodeVersion: deps.nodeVersion,
|
|
22804
|
+
requiredNodeRange: pkg.engines.node,
|
|
22805
|
+
nodeSupported,
|
|
22806
|
+
proxyConfigured: deps.proxyEnvironment(),
|
|
22807
|
+
},
|
|
22808
|
+
project: { directory, directoryExists, detected: projectDetected },
|
|
22809
|
+
auth: { authenticated, remotelyVerified: false },
|
|
22810
|
+
network: {
|
|
22811
|
+
mode: hosts.length > 0 ? 'lan' : 'loopback',
|
|
22812
|
+
selectedHost: hosts[0] || '127.0.0.1',
|
|
22813
|
+
hosts,
|
|
22814
|
+
},
|
|
22815
|
+
summary: { errors, warnings },
|
|
22816
|
+
diagnostics,
|
|
22817
|
+
platformWrite: false,
|
|
22818
|
+
errorCode: errors > 0 ? 'DOCTOR_FAILED' : null,
|
|
22819
|
+
error: null,
|
|
22820
|
+
};
|
|
22821
|
+
}
|
|
22822
|
+
async function doctor(options = {}) {
|
|
22823
|
+
const report = getDoctorReport(options);
|
|
22824
|
+
const format = (options.format || 'text').toLowerCase();
|
|
22825
|
+
if (format === 'json')
|
|
22826
|
+
console.log(JSON.stringify(report));
|
|
22827
|
+
else
|
|
22828
|
+
console.log(JSON.stringify(report, null, 2));
|
|
22829
|
+
const failOnWarning = (options.failOn || 'error').toLowerCase() === 'warning';
|
|
22830
|
+
if (report.status === 'failed' ||
|
|
22831
|
+
(failOnWarning && report.status === 'warning')) {
|
|
22832
|
+
process.exitCode = 1;
|
|
22833
|
+
}
|
|
22834
|
+
return report;
|
|
20820
22835
|
}
|
|
20821
22836
|
|
|
20822
22837
|
var index = /*#__PURE__*/Object.freeze({
|
|
20823
22838
|
__proto__: null,
|
|
20824
|
-
|
|
22839
|
+
build: structuredBuildCommand,
|
|
22840
|
+
check: checkCommand,
|
|
22841
|
+
checkAi: aiCheckCommand,
|
|
22842
|
+
dev: dev,
|
|
22843
|
+
devSessionEventsCommand: devSessionEventsCommand,
|
|
22844
|
+
devSessionInspectCommand: devSessionInspectCommand,
|
|
22845
|
+
devSessionListCommand: devSessionListCommand,
|
|
22846
|
+
devSessionStopCommand: devSessionStopCommand,
|
|
22847
|
+
doctor: doctor
|
|
20825
22848
|
});
|
|
20826
22849
|
//# sourceMappingURL=index.js.map
|