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