@ttmg/cli 0.4.6-vibe-beta.3 → 0.4.6-vibe-beta.4
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 +19 -0
- package/dist/index.js +1419 -569
- package/dist/index.js.map +1 -1
- package/dist/package.json +5 -2
- package/dist/scripts/preview-page-server-smoke.js +63 -0
- package/dist/scripts/verify-agent-contract.js +49 -0
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -13,6 +13,8 @@ var fs = require('fs');
|
|
|
13
13
|
var handlebars = require('handlebars');
|
|
14
14
|
var esbuild = require('esbuild');
|
|
15
15
|
var worker_threads = require('worker_threads');
|
|
16
|
+
var child_process = require('child_process');
|
|
17
|
+
var http = require('http');
|
|
16
18
|
var jsdom = require('jsdom');
|
|
17
19
|
var prettier = require('prettier');
|
|
18
20
|
var express = require('express');
|
|
@@ -29,7 +31,6 @@ var require$$0 = require('util');
|
|
|
29
31
|
var require$$0$1 = require('events');
|
|
30
32
|
var require$$0$2 = require('buffer');
|
|
31
33
|
var require$$1$2 = require('stream');
|
|
32
|
-
var http = require('http');
|
|
33
34
|
var https = require('https');
|
|
34
35
|
var dns = require('dns');
|
|
35
36
|
var boxen = require('boxen');
|
|
@@ -46,7 +47,6 @@ var fs$1 = require('node:fs');
|
|
|
46
47
|
var path$1 = require('node:path');
|
|
47
48
|
var zlib = require('zlib');
|
|
48
49
|
var promises = require('fs/promises');
|
|
49
|
-
var child_process = require('child_process');
|
|
50
50
|
|
|
51
51
|
function _interopNamespaceDefault(e) {
|
|
52
52
|
var n = Object.create(null);
|
|
@@ -68,9 +68,9 @@ function _interopNamespaceDefault(e) {
|
|
|
68
68
|
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
69
69
|
var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
|
|
70
70
|
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
71
|
+
var http__namespace = /*#__PURE__*/_interopNamespaceDefault(http);
|
|
71
72
|
var net__namespace = /*#__PURE__*/_interopNamespaceDefault(net);
|
|
72
73
|
var require$$1__namespace = /*#__PURE__*/_interopNamespaceDefault(require$$1$1);
|
|
73
|
-
var http__namespace = /*#__PURE__*/_interopNamespaceDefault(http);
|
|
74
74
|
var dns__namespace = /*#__PURE__*/_interopNamespaceDefault(dns);
|
|
75
75
|
var glob__namespace = /*#__PURE__*/_interopNamespaceDefault(glob);
|
|
76
76
|
var fs__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(fs$1);
|
|
@@ -7254,6 +7254,8 @@ const messages = {
|
|
|
7254
7254
|
'cli.command.upload.note': 'Note for the upload',
|
|
7255
7255
|
'cli.command.upload.dir': 'Game project directory (default: current directory)',
|
|
7256
7256
|
'cli.command.upload.preview': 'Wait for platform processing and output the preview payload',
|
|
7257
|
+
'cli.command.upload.servePreviewPage': 'Serve a temporary local preview page with test-account and preview QR codes',
|
|
7258
|
+
'cli.command.upload.noOpen': 'Do not open the local preview page in the default browser',
|
|
7257
7259
|
'cli.command.upload.format': 'Output format: text | json | ndjson',
|
|
7258
7260
|
'cli.command.testAccount.desc': 'Manage Mini Game test-account access',
|
|
7259
7261
|
'cli.command.testAccount.add.desc': 'Generate the authorization entry for adding a TikTok test account',
|
|
@@ -7387,6 +7389,17 @@ const messages = {
|
|
|
7387
7389
|
'upload.spinner.processing': 'Waiting for platform processing (status: {status})...',
|
|
7388
7390
|
'upload.success': 'Upload success! View version at: {url}',
|
|
7389
7391
|
'upload.previewSuccess': 'Preview version is ready: {url}',
|
|
7392
|
+
'upload.previewPageReady': 'Local preview page: {url}',
|
|
7393
|
+
'upload.previewPageHeading': 'Preview page',
|
|
7394
|
+
'upload.previewPageOpenInBrowser': 'Open in browser',
|
|
7395
|
+
'upload.previewPageAccessScope': 'Access scope',
|
|
7396
|
+
'upload.previewPageSameMachine': 'Same machine only',
|
|
7397
|
+
'upload.previewPageTerminalMode': 'Terminal fallback',
|
|
7398
|
+
'upload.previewPageClickableLink': 'Clickable link',
|
|
7399
|
+
'upload.previewPageExpiresAt': 'Expires at',
|
|
7400
|
+
'upload.previewPageOpening': 'Opening the preview page in your browser...',
|
|
7401
|
+
'upload.previewPageOpenFailed': 'Could not open the browser automatically. Use the link above.',
|
|
7402
|
+
'upload.previewPageFailed': 'The preview was uploaded, but the local preview page failed: {error}',
|
|
7390
7403
|
'upload.fail': 'Upload failed: {error}',
|
|
7391
7404
|
'upload.error.noClientKey': 'Client Key is required for upload. Use --client-key <key>',
|
|
7392
7405
|
'upload.error.notLoggedIn': 'Not logged in. Please run `ttmg login` first.',
|
|
@@ -7401,6 +7414,7 @@ const messages = {
|
|
|
7401
7414
|
'upload.error.assetTimeout': 'Timed out waiting for platform asset processing.',
|
|
7402
7415
|
'upload.error.previewTimeout': 'Timed out waiting for the preview URL.',
|
|
7403
7416
|
'upload.error.previewQueryFailed': 'Failed to query the preview URL from the platform.',
|
|
7417
|
+
'upload.error.previewPageRequiresPreview': '--serve-preview-page requires --preview.',
|
|
7404
7418
|
'testAccount.success': 'Test-account authorization entry generated.',
|
|
7405
7419
|
'testAccount.project': 'Project: {name} ({clientKey})',
|
|
7406
7420
|
'testAccount.authorizationUrl': 'Authorization URL: {url}',
|
|
@@ -7475,6 +7489,8 @@ const messages = {
|
|
|
7475
7489
|
'cli.command.upload.note': '上传备注',
|
|
7476
7490
|
'cli.command.upload.dir': '小游戏项目根目录(默认为当前目录)',
|
|
7477
7491
|
'cli.command.upload.preview': '等待平台处理并输出预览二维码原始内容',
|
|
7492
|
+
'cli.command.upload.servePreviewPage': '启动包含测试账号与预览二维码的临时本地页面',
|
|
7493
|
+
'cli.command.upload.noOpen': '不使用默认浏览器打开本地预览页面',
|
|
7478
7494
|
'cli.command.upload.format': '输出格式:text | json | ndjson',
|
|
7479
7495
|
'cli.command.testAccount.desc': '管理小游戏测试账号访问',
|
|
7480
7496
|
'cli.command.testAccount.add.desc': '生成添加 TikTok 测试账号的授权入口',
|
|
@@ -7608,6 +7624,17 @@ const messages = {
|
|
|
7608
7624
|
'upload.spinner.processing': '正在等待平台处理(状态:{status})...',
|
|
7609
7625
|
'upload.success': '上传成功!可在以下地址查看版本:{url}',
|
|
7610
7626
|
'upload.previewSuccess': '预览版本已就绪:{url}',
|
|
7627
|
+
'upload.previewPageReady': '本地预览页面:{url}',
|
|
7628
|
+
'upload.previewPageHeading': '预览页面',
|
|
7629
|
+
'upload.previewPageOpenInBrowser': '浏览器打开',
|
|
7630
|
+
'upload.previewPageAccessScope': '访问范围',
|
|
7631
|
+
'upload.previewPageSameMachine': '仅当前设备',
|
|
7632
|
+
'upload.previewPageTerminalMode': '终端降级',
|
|
7633
|
+
'upload.previewPageClickableLink': '可点击链接',
|
|
7634
|
+
'upload.previewPageExpiresAt': '过期时间',
|
|
7635
|
+
'upload.previewPageOpening': '正在使用浏览器打开预览页面...',
|
|
7636
|
+
'upload.previewPageOpenFailed': '无法自动打开浏览器,请使用上方链接。',
|
|
7637
|
+
'upload.previewPageFailed': '预览版本已上传,但本地预览页面启动失败:{error}',
|
|
7611
7638
|
'upload.fail': '上传失败:{error}',
|
|
7612
7639
|
'upload.error.noClientKey': '上传必须提供 Client Key,请使用 --client-key <key> 参数',
|
|
7613
7640
|
'upload.error.notLoggedIn': '未登录,请先执行 `ttmg login` 登录开发者平台账号。',
|
|
@@ -7622,6 +7649,7 @@ const messages = {
|
|
|
7622
7649
|
'upload.error.assetTimeout': '等待平台处理上传版本超时。',
|
|
7623
7650
|
'upload.error.previewTimeout': '等待平台生成预览地址超时。',
|
|
7624
7651
|
'upload.error.previewQueryFailed': '查询平台预览地址失败。',
|
|
7652
|
+
'upload.error.previewPageRequiresPreview': '--serve-preview-page 必须与 --preview 一起使用。',
|
|
7625
7653
|
'testAccount.success': '测试账号授权入口已生成。',
|
|
7626
7654
|
'testAccount.project': '项目:{name}({clientKey})',
|
|
7627
7655
|
'testAccount.authorizationUrl': '授权地址:{url}',
|
|
@@ -8250,495 +8278,159 @@ const zipCwdToBuffer = (customIgnores = [], targetDir = process.cwd()) => {
|
|
|
8250
8278
|
});
|
|
8251
8279
|
};
|
|
8252
8280
|
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
if (normalized !== 'text' &&
|
|
8270
|
-
normalized !== 'json' &&
|
|
8271
|
-
normalized !== 'ndjson') {
|
|
8272
|
-
throw new Error(t('upload.error.invalidFormat', { format: normalized }));
|
|
8273
|
-
}
|
|
8274
|
-
return normalized;
|
|
8275
|
-
}
|
|
8276
|
-
function getAssetStatusRaw(asset) {
|
|
8277
|
-
return asset?.cdn_deploy_status ?? asset?.status ?? null;
|
|
8278
|
-
}
|
|
8279
|
-
function normalizeUploadPlatformStatus(rawStatus) {
|
|
8280
|
-
if (rawStatus === null || rawStatus === undefined || rawStatus === '') {
|
|
8281
|
-
return 'waiting_for_asset';
|
|
8282
|
-
}
|
|
8283
|
-
const numericStatus = Number(rawStatus);
|
|
8284
|
-
if (Number.isInteger(numericStatus)) {
|
|
8285
|
-
if (numericStatus === 0)
|
|
8286
|
-
return 'unknown';
|
|
8287
|
-
if (numericStatus === 1)
|
|
8288
|
-
return 'pending';
|
|
8289
|
-
if (numericStatus === 2)
|
|
8290
|
-
return 'success';
|
|
8291
|
-
if (numericStatus === 3)
|
|
8292
|
-
return 'failed';
|
|
8293
|
-
if (numericStatus === 4)
|
|
8294
|
-
return 'processing';
|
|
8295
|
-
return 'unknown';
|
|
8296
|
-
}
|
|
8297
|
-
const normalized = String(rawStatus).trim().toLowerCase();
|
|
8298
|
-
const statusName = normalized.replace(/^deploy_status_/, '');
|
|
8299
|
-
if (['ready', 'success', 'succeeded', 'available'].includes(statusName)) {
|
|
8300
|
-
return 'success';
|
|
8301
|
-
}
|
|
8302
|
-
if (statusName.includes('fail') ||
|
|
8303
|
-
statusName.includes('error') ||
|
|
8304
|
-
statusName.includes('reject')) {
|
|
8305
|
-
return 'failed';
|
|
8306
|
-
}
|
|
8307
|
-
if (statusName.includes('process') ||
|
|
8308
|
-
statusName.includes('upload') ||
|
|
8309
|
-
statusName.includes('deploy')) {
|
|
8310
|
-
return 'processing';
|
|
8311
|
-
}
|
|
8312
|
-
if (statusName.includes('pending') ||
|
|
8313
|
-
statusName.includes('queue') ||
|
|
8314
|
-
statusName.includes('wait')) {
|
|
8315
|
-
return 'pending';
|
|
8316
|
-
}
|
|
8317
|
-
return 'unknown';
|
|
8318
|
-
}
|
|
8319
|
-
function getAssetState(asset) {
|
|
8320
|
-
const platformStatus = normalizeUploadPlatformStatus(getAssetStatusRaw(asset));
|
|
8321
|
-
if (platformStatus === 'success') {
|
|
8322
|
-
return 'ready';
|
|
8323
|
-
}
|
|
8324
|
-
if (platformStatus === 'failed') {
|
|
8325
|
-
return 'failed';
|
|
8326
|
-
}
|
|
8327
|
-
return 'pending';
|
|
8281
|
+
var server = {};
|
|
8282
|
+
|
|
8283
|
+
var canPromise;
|
|
8284
|
+
var hasRequiredCanPromise;
|
|
8285
|
+
|
|
8286
|
+
function requireCanPromise () {
|
|
8287
|
+
if (hasRequiredCanPromise) return canPromise;
|
|
8288
|
+
hasRequiredCanPromise = 1;
|
|
8289
|
+
// can-promise has a crash in some versions of react native that dont have
|
|
8290
|
+
// standard global objects
|
|
8291
|
+
// https://github.com/soldair/node-qrcode/issues/157
|
|
8292
|
+
|
|
8293
|
+
canPromise = function () {
|
|
8294
|
+
return typeof Promise === 'function' && Promise.prototype && Promise.prototype.then
|
|
8295
|
+
};
|
|
8296
|
+
return canPromise;
|
|
8328
8297
|
}
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8298
|
+
|
|
8299
|
+
var qrcode = {};
|
|
8300
|
+
|
|
8301
|
+
var utils$1 = {};
|
|
8302
|
+
|
|
8303
|
+
var hasRequiredUtils$1;
|
|
8304
|
+
|
|
8305
|
+
function requireUtils$1 () {
|
|
8306
|
+
if (hasRequiredUtils$1) return utils$1;
|
|
8307
|
+
hasRequiredUtils$1 = 1;
|
|
8308
|
+
let toSJISFunction;
|
|
8309
|
+
const CODEWORDS_COUNT = [
|
|
8310
|
+
0, // Not used
|
|
8311
|
+
26, 44, 70, 100, 134, 172, 196, 242, 292, 346,
|
|
8312
|
+
404, 466, 532, 581, 655, 733, 815, 901, 991, 1085,
|
|
8313
|
+
1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185,
|
|
8314
|
+
2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706
|
|
8315
|
+
];
|
|
8316
|
+
|
|
8317
|
+
/**
|
|
8318
|
+
* Returns the QR Code size for the specified version
|
|
8319
|
+
*
|
|
8320
|
+
* @param {Number} version QR Code version
|
|
8321
|
+
* @return {Number} size of QR code
|
|
8322
|
+
*/
|
|
8323
|
+
utils$1.getSymbolSize = function getSymbolSize (version) {
|
|
8324
|
+
if (!version) throw new Error('"version" cannot be null or undefined')
|
|
8325
|
+
if (version < 1 || version > 40) throw new Error('"version" should be in range from 1 to 40')
|
|
8326
|
+
return version * 4 + 17
|
|
8327
|
+
};
|
|
8328
|
+
|
|
8329
|
+
/**
|
|
8330
|
+
* Returns the total number of codewords used to store data and EC information.
|
|
8331
|
+
*
|
|
8332
|
+
* @param {Number} version QR Code version
|
|
8333
|
+
* @return {Number} Data length in bits
|
|
8334
|
+
*/
|
|
8335
|
+
utils$1.getSymbolTotalCodewords = function getSymbolTotalCodewords (version) {
|
|
8336
|
+
return CODEWORDS_COUNT[version]
|
|
8337
|
+
};
|
|
8338
|
+
|
|
8339
|
+
/**
|
|
8340
|
+
* Encode data with Bose-Chaudhuri-Hocquenghem
|
|
8341
|
+
*
|
|
8342
|
+
* @param {Number} data Value to encode
|
|
8343
|
+
* @return {Number} Encoded value
|
|
8344
|
+
*/
|
|
8345
|
+
utils$1.getBCHDigit = function (data) {
|
|
8346
|
+
let digit = 0;
|
|
8347
|
+
|
|
8348
|
+
while (data !== 0) {
|
|
8349
|
+
digit++;
|
|
8350
|
+
data >>>= 1;
|
|
8351
|
+
}
|
|
8352
|
+
|
|
8353
|
+
return digit
|
|
8354
|
+
};
|
|
8355
|
+
|
|
8356
|
+
utils$1.setToSJISFunction = function setToSJISFunction (f) {
|
|
8357
|
+
if (typeof f !== 'function') {
|
|
8358
|
+
throw new Error('"toSJISFunc" is not a valid function.')
|
|
8359
|
+
}
|
|
8360
|
+
|
|
8361
|
+
toSJISFunction = f;
|
|
8362
|
+
};
|
|
8363
|
+
|
|
8364
|
+
utils$1.isKanjiModeEnabled = function () {
|
|
8365
|
+
return typeof toSJISFunction !== 'undefined'
|
|
8366
|
+
};
|
|
8367
|
+
|
|
8368
|
+
utils$1.toSJIS = function toSJIS (kanji) {
|
|
8369
|
+
return toSJISFunction(kanji)
|
|
8370
|
+
};
|
|
8371
|
+
return utils$1;
|
|
8339
8372
|
}
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
}
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
response.error?.message ||
|
|
8402
|
-
t('upload.error.previewQueryFailed'));
|
|
8403
|
-
}
|
|
8404
|
-
const previewUrl = extractPreviewUrl(response.data);
|
|
8405
|
-
if (previewUrl) {
|
|
8406
|
-
return previewUrl;
|
|
8407
|
-
}
|
|
8408
|
-
await deps.sleep(deps.pollIntervalMs);
|
|
8409
|
-
}
|
|
8410
|
-
throw new Error(t('upload.error.previewTimeout'));
|
|
8411
|
-
}
|
|
8412
|
-
async function executeUpload(options, dependencies = {}) {
|
|
8413
|
-
const deps = { ...defaultDependencies$6, ...dependencies };
|
|
8414
|
-
const clientKey = options.clientKey?.trim();
|
|
8415
|
-
const note = options.note || '--';
|
|
8416
|
-
const eventBase = { operationId: options.operationId, clientKey };
|
|
8417
|
-
const emit = options.onEvent;
|
|
8418
|
-
emit(createEvent(eventBase, 'precheck', 0.05));
|
|
8419
|
-
const user = deps.getCurrentUser();
|
|
8420
|
-
if (!user || !user.cookie) {
|
|
8421
|
-
throw new Error(t('upload.error.notLoggedIn'));
|
|
8422
|
-
}
|
|
8423
|
-
if (!clientKey) {
|
|
8424
|
-
throw new Error(t('upload.error.noClientKey'));
|
|
8425
|
-
}
|
|
8426
|
-
const infoResponse = await deps.fetchGameInfo(clientKey);
|
|
8427
|
-
if (infoResponse.error) {
|
|
8428
|
-
throw new Error(infoResponse.error.errorMsg || t('upload.error.gameInfoFailed'));
|
|
8429
|
-
}
|
|
8430
|
-
const appId = infoResponse.data?.app_id || '';
|
|
8431
|
-
const projectName = infoResponse.data?.name || 'game';
|
|
8432
|
-
if (!appId) {
|
|
8433
|
-
throw new Error(t('upload.error.noAppId'));
|
|
8434
|
-
}
|
|
8435
|
-
const targetDir = options.dir
|
|
8436
|
-
? path.resolve(process.cwd(), options.dir)
|
|
8437
|
-
: process.cwd();
|
|
8438
|
-
if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
|
|
8439
|
-
throw new Error(t('upload.error.dirNotExist', { dir: targetDir }));
|
|
8440
|
-
}
|
|
8441
|
-
emit(createEvent(eventBase, 'packaging', 0.2, { appId, projectName }));
|
|
8442
|
-
const zipBuffer = await deps.zipDirectory(targetDir);
|
|
8443
|
-
const packageSizeBytes = zipBuffer.length;
|
|
8444
|
-
emit(createEvent(eventBase, 'uploading', 0.4, {
|
|
8445
|
-
appId,
|
|
8446
|
-
projectName,
|
|
8447
|
-
packageSizeBytes,
|
|
8448
|
-
}));
|
|
8449
|
-
const uploadResponse = await deps.uploadGameToPlatform({
|
|
8450
|
-
data: zipBuffer.buffer.slice(zipBuffer.byteOffset, zipBuffer.byteOffset + zipBuffer.byteLength),
|
|
8451
|
-
name: projectName,
|
|
8452
|
-
clientKey,
|
|
8453
|
-
note,
|
|
8454
|
-
appId,
|
|
8455
|
-
});
|
|
8456
|
-
if (uploadResponse.error) {
|
|
8457
|
-
throw new Error(uploadResponse.error.message || t('upload.error.platformUploadFailed'));
|
|
8458
|
-
}
|
|
8459
|
-
const assetId = String(uploadResponse.data?.asset_id || '');
|
|
8460
|
-
const platformUrl = uploadResponse.data?.detail || '';
|
|
8461
|
-
if (!assetId) {
|
|
8462
|
-
throw new Error(t('upload.error.missingAssetId'));
|
|
8463
|
-
}
|
|
8464
|
-
if (!options.preview) {
|
|
8465
|
-
const event = createEvent(eventBase, 'ready', 1, {
|
|
8466
|
-
appId,
|
|
8467
|
-
projectName,
|
|
8468
|
-
packageSizeBytes,
|
|
8469
|
-
versionId: assetId,
|
|
8470
|
-
platformUrl,
|
|
8471
|
-
platformWrite: true,
|
|
8472
|
-
});
|
|
8473
|
-
emit(event);
|
|
8474
|
-
return event;
|
|
8475
|
-
}
|
|
8476
|
-
const deadline = deps.now() + deps.pollTimeoutMs;
|
|
8477
|
-
await waitForAssetReady({
|
|
8478
|
-
assetId,
|
|
8479
|
-
miniGameId: appId,
|
|
8480
|
-
deadline,
|
|
8481
|
-
eventBase,
|
|
8482
|
-
onEvent: emit,
|
|
8483
|
-
deps,
|
|
8484
|
-
});
|
|
8485
|
-
emit(createEvent(eventBase, 'processing', 0.85, {
|
|
8486
|
-
appId,
|
|
8487
|
-
projectName,
|
|
8488
|
-
packageSizeBytes,
|
|
8489
|
-
versionId: assetId,
|
|
8490
|
-
platformStatus: 'resolving_preview',
|
|
8491
|
-
platformUrl,
|
|
8492
|
-
platformWrite: true,
|
|
8493
|
-
}));
|
|
8494
|
-
const previewUrl = await waitForPreviewUrl({ assetId, deadline, deps });
|
|
8495
|
-
const event = createEvent(eventBase, 'ready', 1, {
|
|
8496
|
-
appId,
|
|
8497
|
-
projectName,
|
|
8498
|
-
packageSizeBytes,
|
|
8499
|
-
versionId: assetId,
|
|
8500
|
-
previewUrl,
|
|
8501
|
-
qrPayload: previewUrl,
|
|
8502
|
-
platformUrl,
|
|
8503
|
-
platformStatus: 'ready',
|
|
8504
|
-
platformWrite: true,
|
|
8505
|
-
});
|
|
8506
|
-
emit(event);
|
|
8507
|
-
return event;
|
|
8508
|
-
}
|
|
8509
|
-
function getHumanEventText(event) {
|
|
8510
|
-
if (event.status === 'precheck') {
|
|
8511
|
-
return t('upload.spinner.fetchInfo', { clientKey: event.clientKey });
|
|
8512
|
-
}
|
|
8513
|
-
if (event.status === 'packaging') {
|
|
8514
|
-
return t('upload.spinner.zipping', { dir: '' });
|
|
8515
|
-
}
|
|
8516
|
-
if (event.status === 'uploading') {
|
|
8517
|
-
return t('upload.spinner.uploading', {
|
|
8518
|
-
size: ((event.packageSizeBytes || 0) / 1024 / 1024).toFixed(2),
|
|
8519
|
-
});
|
|
8520
|
-
}
|
|
8521
|
-
if (event.status === 'processing') {
|
|
8522
|
-
return t('upload.spinner.processing', {
|
|
8523
|
-
status: event.platformStatus || 'pending',
|
|
8524
|
-
});
|
|
8525
|
-
}
|
|
8526
|
-
return '';
|
|
8527
|
-
}
|
|
8528
|
-
async function upload(options) {
|
|
8529
|
-
const format = normalizeOutputFormat(options.format);
|
|
8530
|
-
const operationId = `upload_${crypto.randomUUID()}`;
|
|
8531
|
-
const ora = await import('ora');
|
|
8532
|
-
const spinner = format === 'text'
|
|
8533
|
-
? ora
|
|
8534
|
-
.default({ spinner: 'dots', text: t('upload.spinner.start') })
|
|
8535
|
-
.start()
|
|
8536
|
-
: null;
|
|
8537
|
-
let lastEvent = null;
|
|
8538
|
-
const onEvent = (event) => {
|
|
8539
|
-
lastEvent = event;
|
|
8540
|
-
if (format === 'ndjson') {
|
|
8541
|
-
console.log(JSON.stringify(event));
|
|
8542
|
-
return;
|
|
8543
|
-
}
|
|
8544
|
-
const nextText = getHumanEventText(event);
|
|
8545
|
-
if (nextText && spinner) {
|
|
8546
|
-
spinner.text = chalk.cyan.bold(nextText);
|
|
8547
|
-
}
|
|
8548
|
-
};
|
|
8549
|
-
try {
|
|
8550
|
-
const result = await executeUpload({ ...options, operationId, onEvent });
|
|
8551
|
-
if (format === 'json') {
|
|
8552
|
-
console.log(JSON.stringify(result));
|
|
8553
|
-
}
|
|
8554
|
-
else if (format === 'text' && spinner) {
|
|
8555
|
-
if (result.previewUrl) {
|
|
8556
|
-
spinner.succeed(chalk.green.bold(t('upload.previewSuccess', { url: result.previewUrl })));
|
|
8557
|
-
}
|
|
8558
|
-
else {
|
|
8559
|
-
spinner.succeed(chalk.green.bold(t('upload.success', { url: result.platformUrl })));
|
|
8560
|
-
}
|
|
8561
|
-
}
|
|
8562
|
-
return result;
|
|
8563
|
-
}
|
|
8564
|
-
catch (error) {
|
|
8565
|
-
const message = error?.message || String(error);
|
|
8566
|
-
const failedEvent = createEvent({ operationId, clientKey: options.clientKey || '' }, 'failed', lastEvent?.progress || 0, {
|
|
8567
|
-
appId: lastEvent?.appId,
|
|
8568
|
-
projectName: lastEvent?.projectName,
|
|
8569
|
-
packageSizeBytes: lastEvent?.packageSizeBytes,
|
|
8570
|
-
versionId: lastEvent?.versionId || '',
|
|
8571
|
-
platformUrl: lastEvent?.platformUrl || '',
|
|
8572
|
-
platformStatus: lastEvent?.platformStatus,
|
|
8573
|
-
platformStatusRaw: lastEvent?.platformStatusRaw,
|
|
8574
|
-
platformWrite: lastEvent?.platformWrite || false,
|
|
8575
|
-
errorCode: 'UPLOAD_FAILED',
|
|
8576
|
-
error: message,
|
|
8577
|
-
});
|
|
8578
|
-
if (format === 'json' || format === 'ndjson') {
|
|
8579
|
-
console.log(JSON.stringify(failedEvent));
|
|
8580
|
-
}
|
|
8581
|
-
else {
|
|
8582
|
-
spinner?.fail(chalk.red.bold(t('upload.fail', { error: message })));
|
|
8583
|
-
}
|
|
8584
|
-
process.exitCode = 1;
|
|
8585
|
-
return failedEvent;
|
|
8586
|
-
}
|
|
8587
|
-
}
|
|
8588
|
-
|
|
8589
|
-
var server = {};
|
|
8590
|
-
|
|
8591
|
-
var canPromise;
|
|
8592
|
-
var hasRequiredCanPromise;
|
|
8593
|
-
|
|
8594
|
-
function requireCanPromise () {
|
|
8595
|
-
if (hasRequiredCanPromise) return canPromise;
|
|
8596
|
-
hasRequiredCanPromise = 1;
|
|
8597
|
-
// can-promise has a crash in some versions of react native that dont have
|
|
8598
|
-
// standard global objects
|
|
8599
|
-
// https://github.com/soldair/node-qrcode/issues/157
|
|
8600
|
-
|
|
8601
|
-
canPromise = function () {
|
|
8602
|
-
return typeof Promise === 'function' && Promise.prototype && Promise.prototype.then
|
|
8603
|
-
};
|
|
8604
|
-
return canPromise;
|
|
8605
|
-
}
|
|
8606
|
-
|
|
8607
|
-
var qrcode = {};
|
|
8608
|
-
|
|
8609
|
-
var utils$1 = {};
|
|
8610
|
-
|
|
8611
|
-
var hasRequiredUtils$1;
|
|
8612
|
-
|
|
8613
|
-
function requireUtils$1 () {
|
|
8614
|
-
if (hasRequiredUtils$1) return utils$1;
|
|
8615
|
-
hasRequiredUtils$1 = 1;
|
|
8616
|
-
let toSJISFunction;
|
|
8617
|
-
const CODEWORDS_COUNT = [
|
|
8618
|
-
0, // Not used
|
|
8619
|
-
26, 44, 70, 100, 134, 172, 196, 242, 292, 346,
|
|
8620
|
-
404, 466, 532, 581, 655, 733, 815, 901, 991, 1085,
|
|
8621
|
-
1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185,
|
|
8622
|
-
2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706
|
|
8623
|
-
];
|
|
8624
|
-
|
|
8625
|
-
/**
|
|
8626
|
-
* Returns the QR Code size for the specified version
|
|
8627
|
-
*
|
|
8628
|
-
* @param {Number} version QR Code version
|
|
8629
|
-
* @return {Number} size of QR code
|
|
8630
|
-
*/
|
|
8631
|
-
utils$1.getSymbolSize = function getSymbolSize (version) {
|
|
8632
|
-
if (!version) throw new Error('"version" cannot be null or undefined')
|
|
8633
|
-
if (version < 1 || version > 40) throw new Error('"version" should be in range from 1 to 40')
|
|
8634
|
-
return version * 4 + 17
|
|
8635
|
-
};
|
|
8636
|
-
|
|
8637
|
-
/**
|
|
8638
|
-
* Returns the total number of codewords used to store data and EC information.
|
|
8639
|
-
*
|
|
8640
|
-
* @param {Number} version QR Code version
|
|
8641
|
-
* @return {Number} Data length in bits
|
|
8642
|
-
*/
|
|
8643
|
-
utils$1.getSymbolTotalCodewords = function getSymbolTotalCodewords (version) {
|
|
8644
|
-
return CODEWORDS_COUNT[version]
|
|
8645
|
-
};
|
|
8646
|
-
|
|
8647
|
-
/**
|
|
8648
|
-
* Encode data with Bose-Chaudhuri-Hocquenghem
|
|
8649
|
-
*
|
|
8650
|
-
* @param {Number} data Value to encode
|
|
8651
|
-
* @return {Number} Encoded value
|
|
8652
|
-
*/
|
|
8653
|
-
utils$1.getBCHDigit = function (data) {
|
|
8654
|
-
let digit = 0;
|
|
8655
|
-
|
|
8656
|
-
while (data !== 0) {
|
|
8657
|
-
digit++;
|
|
8658
|
-
data >>>= 1;
|
|
8659
|
-
}
|
|
8660
|
-
|
|
8661
|
-
return digit
|
|
8662
|
-
};
|
|
8663
|
-
|
|
8664
|
-
utils$1.setToSJISFunction = function setToSJISFunction (f) {
|
|
8665
|
-
if (typeof f !== 'function') {
|
|
8666
|
-
throw new Error('"toSJISFunc" is not a valid function.')
|
|
8667
|
-
}
|
|
8668
|
-
|
|
8669
|
-
toSJISFunction = f;
|
|
8670
|
-
};
|
|
8671
|
-
|
|
8672
|
-
utils$1.isKanjiModeEnabled = function () {
|
|
8673
|
-
return typeof toSJISFunction !== 'undefined'
|
|
8674
|
-
};
|
|
8675
|
-
|
|
8676
|
-
utils$1.toSJIS = function toSJIS (kanji) {
|
|
8677
|
-
return toSJISFunction(kanji)
|
|
8678
|
-
};
|
|
8679
|
-
return utils$1;
|
|
8680
|
-
}
|
|
8681
|
-
|
|
8682
|
-
var errorCorrectionLevel = {};
|
|
8683
|
-
|
|
8684
|
-
var hasRequiredErrorCorrectionLevel;
|
|
8685
|
-
|
|
8686
|
-
function requireErrorCorrectionLevel () {
|
|
8687
|
-
if (hasRequiredErrorCorrectionLevel) return errorCorrectionLevel;
|
|
8688
|
-
hasRequiredErrorCorrectionLevel = 1;
|
|
8689
|
-
(function (exports$1) {
|
|
8690
|
-
exports$1.L = { bit: 1 };
|
|
8691
|
-
exports$1.M = { bit: 0 };
|
|
8692
|
-
exports$1.Q = { bit: 3 };
|
|
8693
|
-
exports$1.H = { bit: 2 };
|
|
8694
|
-
|
|
8695
|
-
function fromString (string) {
|
|
8696
|
-
if (typeof string !== 'string') {
|
|
8697
|
-
throw new Error('Param is not a string')
|
|
8698
|
-
}
|
|
8699
|
-
|
|
8700
|
-
const lcStr = string.toLowerCase();
|
|
8701
|
-
|
|
8702
|
-
switch (lcStr) {
|
|
8703
|
-
case 'l':
|
|
8704
|
-
case 'low':
|
|
8705
|
-
return exports$1.L
|
|
8706
|
-
|
|
8707
|
-
case 'm':
|
|
8708
|
-
case 'medium':
|
|
8709
|
-
return exports$1.M
|
|
8710
|
-
|
|
8711
|
-
case 'q':
|
|
8712
|
-
case 'quartile':
|
|
8713
|
-
return exports$1.Q
|
|
8714
|
-
|
|
8715
|
-
case 'h':
|
|
8716
|
-
case 'high':
|
|
8717
|
-
return exports$1.H
|
|
8718
|
-
|
|
8719
|
-
default:
|
|
8720
|
-
throw new Error('Unknown EC Level: ' + string)
|
|
8721
|
-
}
|
|
8722
|
-
}
|
|
8723
|
-
|
|
8724
|
-
exports$1.isValid = function isValid (level) {
|
|
8725
|
-
return level && typeof level.bit !== 'undefined' &&
|
|
8726
|
-
level.bit >= 0 && level.bit < 4
|
|
8727
|
-
};
|
|
8728
|
-
|
|
8729
|
-
exports$1.from = function from (value, defaultValue) {
|
|
8730
|
-
if (exports$1.isValid(value)) {
|
|
8731
|
-
return value
|
|
8732
|
-
}
|
|
8733
|
-
|
|
8734
|
-
try {
|
|
8735
|
-
return fromString(value)
|
|
8736
|
-
} catch (e) {
|
|
8737
|
-
return defaultValue
|
|
8738
|
-
}
|
|
8739
|
-
};
|
|
8740
|
-
} (errorCorrectionLevel));
|
|
8741
|
-
return errorCorrectionLevel;
|
|
8373
|
+
|
|
8374
|
+
var errorCorrectionLevel = {};
|
|
8375
|
+
|
|
8376
|
+
var hasRequiredErrorCorrectionLevel;
|
|
8377
|
+
|
|
8378
|
+
function requireErrorCorrectionLevel () {
|
|
8379
|
+
if (hasRequiredErrorCorrectionLevel) return errorCorrectionLevel;
|
|
8380
|
+
hasRequiredErrorCorrectionLevel = 1;
|
|
8381
|
+
(function (exports$1) {
|
|
8382
|
+
exports$1.L = { bit: 1 };
|
|
8383
|
+
exports$1.M = { bit: 0 };
|
|
8384
|
+
exports$1.Q = { bit: 3 };
|
|
8385
|
+
exports$1.H = { bit: 2 };
|
|
8386
|
+
|
|
8387
|
+
function fromString (string) {
|
|
8388
|
+
if (typeof string !== 'string') {
|
|
8389
|
+
throw new Error('Param is not a string')
|
|
8390
|
+
}
|
|
8391
|
+
|
|
8392
|
+
const lcStr = string.toLowerCase();
|
|
8393
|
+
|
|
8394
|
+
switch (lcStr) {
|
|
8395
|
+
case 'l':
|
|
8396
|
+
case 'low':
|
|
8397
|
+
return exports$1.L
|
|
8398
|
+
|
|
8399
|
+
case 'm':
|
|
8400
|
+
case 'medium':
|
|
8401
|
+
return exports$1.M
|
|
8402
|
+
|
|
8403
|
+
case 'q':
|
|
8404
|
+
case 'quartile':
|
|
8405
|
+
return exports$1.Q
|
|
8406
|
+
|
|
8407
|
+
case 'h':
|
|
8408
|
+
case 'high':
|
|
8409
|
+
return exports$1.H
|
|
8410
|
+
|
|
8411
|
+
default:
|
|
8412
|
+
throw new Error('Unknown EC Level: ' + string)
|
|
8413
|
+
}
|
|
8414
|
+
}
|
|
8415
|
+
|
|
8416
|
+
exports$1.isValid = function isValid (level) {
|
|
8417
|
+
return level && typeof level.bit !== 'undefined' &&
|
|
8418
|
+
level.bit >= 0 && level.bit < 4
|
|
8419
|
+
};
|
|
8420
|
+
|
|
8421
|
+
exports$1.from = function from (value, defaultValue) {
|
|
8422
|
+
if (exports$1.isValid(value)) {
|
|
8423
|
+
return value
|
|
8424
|
+
}
|
|
8425
|
+
|
|
8426
|
+
try {
|
|
8427
|
+
return fromString(value)
|
|
8428
|
+
} catch (e) {
|
|
8429
|
+
return defaultValue
|
|
8430
|
+
}
|
|
8431
|
+
};
|
|
8432
|
+
} (errorCorrectionLevel));
|
|
8433
|
+
return errorCorrectionLevel;
|
|
8742
8434
|
}
|
|
8743
8435
|
|
|
8744
8436
|
var bitBuffer;
|
|
@@ -14894,7 +14586,732 @@ function requireLib () {
|
|
|
14894
14586
|
var libExports = requireLib();
|
|
14895
14587
|
var QRCode = /*@__PURE__*/getDefaultExportFromCjs(libExports);
|
|
14896
14588
|
|
|
14897
|
-
const
|
|
14589
|
+
const PREVIEW_PAGE_SERVER_WORKER_ARG = '__preview-page-server';
|
|
14590
|
+
const PREVIEW_PAGE_CONTENT_TYPE = 'text/html; charset=utf-8';
|
|
14591
|
+
const DEFAULT_PREVIEW_PAGE_TTL_MS = 60 * 60 * 1000;
|
|
14592
|
+
const DEFAULT_PREVIEW_PAGE_EXPIRED_NOTICE_TTL_MS = 60 * 60 * 1000;
|
|
14593
|
+
const PREVIEW_PAGE_PURPOSE = 'authorize_test_account_and_open_preview';
|
|
14594
|
+
const PREVIEW_PAGE_PRESENTATION = {
|
|
14595
|
+
audience: 'user',
|
|
14596
|
+
preferredSurface: 'browser',
|
|
14597
|
+
preferredAction: 'open_url',
|
|
14598
|
+
terminalInline: false,
|
|
14599
|
+
fallback: 'show_clickable_link',
|
|
14600
|
+
sameHostRequired: true,
|
|
14601
|
+
};
|
|
14602
|
+
const PREVIEW_PAGE_START_TIMEOUT_MS = 5000;
|
|
14603
|
+
const PREVIEW_PAGE_CONTROL_PATH = '/__ttmg/preview-page/update';
|
|
14604
|
+
const PREVIEW_PAGE_STATE_DIR = 'ttmg-preview-pages';
|
|
14605
|
+
const PREVIEW_PAGE_LOCK_TIMEOUT_MS = 3000;
|
|
14606
|
+
const PREVIEW_PAGE_LOCK_STALE_MS = 10000;
|
|
14607
|
+
function createPreviewPageServerResult(url, expiresAt) {
|
|
14608
|
+
return {
|
|
14609
|
+
status: 'ready',
|
|
14610
|
+
url,
|
|
14611
|
+
contentType: PREVIEW_PAGE_CONTENT_TYPE,
|
|
14612
|
+
purpose: PREVIEW_PAGE_PURPOSE,
|
|
14613
|
+
presentation: PREVIEW_PAGE_PRESENTATION,
|
|
14614
|
+
expiresAt,
|
|
14615
|
+
localhostOnly: true,
|
|
14616
|
+
latestOnly: true,
|
|
14617
|
+
};
|
|
14618
|
+
}
|
|
14619
|
+
function escapeHtml(value) {
|
|
14620
|
+
return value
|
|
14621
|
+
.replace(/&/g, '&')
|
|
14622
|
+
.replace(/</g, '<')
|
|
14623
|
+
.replace(/>/g, '>')
|
|
14624
|
+
.replace(/"/g, '"')
|
|
14625
|
+
.replace(/'/g, ''');
|
|
14626
|
+
}
|
|
14627
|
+
function isHttpUrl(value) {
|
|
14628
|
+
try {
|
|
14629
|
+
const url = new URL(value);
|
|
14630
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
14631
|
+
}
|
|
14632
|
+
catch {
|
|
14633
|
+
return false;
|
|
14634
|
+
}
|
|
14635
|
+
}
|
|
14636
|
+
function isPreviewPageInput(value) {
|
|
14637
|
+
if (!value || typeof value !== 'object')
|
|
14638
|
+
return false;
|
|
14639
|
+
const input = value;
|
|
14640
|
+
return (typeof input.authorizationUrl === 'string' &&
|
|
14641
|
+
isHttpUrl(input.authorizationUrl) &&
|
|
14642
|
+
typeof input.previewUrl === 'string' &&
|
|
14643
|
+
isHttpUrl(input.previewUrl) &&
|
|
14644
|
+
typeof input.clientKey === 'string' &&
|
|
14645
|
+
input.clientKey.length > 0 &&
|
|
14646
|
+
typeof input.projectName === 'string' &&
|
|
14647
|
+
input.projectName.length > 0 &&
|
|
14648
|
+
typeof input.versionId === 'string' &&
|
|
14649
|
+
input.versionId.length > 0);
|
|
14650
|
+
}
|
|
14651
|
+
function isWorkerRequest(value) {
|
|
14652
|
+
if (!value || typeof value !== 'object')
|
|
14653
|
+
return false;
|
|
14654
|
+
const request = value;
|
|
14655
|
+
if (request.type !== 'start' || !isPreviewPageInput(request))
|
|
14656
|
+
return false;
|
|
14657
|
+
const workerRequest = value;
|
|
14658
|
+
return (typeof workerRequest.token === 'string' &&
|
|
14659
|
+
workerRequest.token.length >= 32 &&
|
|
14660
|
+
typeof workerRequest.ttlMs === 'number' &&
|
|
14661
|
+
workerRequest.ttlMs > 0 &&
|
|
14662
|
+
(workerRequest.expiredNoticeTtlMs === undefined ||
|
|
14663
|
+
(typeof workerRequest.expiredNoticeTtlMs === 'number' &&
|
|
14664
|
+
workerRequest.expiredNoticeTtlMs >= 0)) &&
|
|
14665
|
+
(workerRequest.controlToken === undefined ||
|
|
14666
|
+
(typeof workerRequest.controlToken === 'string' &&
|
|
14667
|
+
workerRequest.controlToken.length >= 32)) &&
|
|
14668
|
+
(workerRequest.statePath === undefined ||
|
|
14669
|
+
typeof workerRequest.statePath === 'string'));
|
|
14670
|
+
}
|
|
14671
|
+
function getClientKeyHash(clientKey) {
|
|
14672
|
+
return crypto.createHash('sha256').update(clientKey).digest('hex').slice(0, 24);
|
|
14673
|
+
}
|
|
14674
|
+
function getPreviewPageStatePath(clientKey) {
|
|
14675
|
+
return path.join(os.tmpdir(), PREVIEW_PAGE_STATE_DIR, `${getClientKeyHash(clientKey)}.json`);
|
|
14676
|
+
}
|
|
14677
|
+
function getPreviewPageLockPath(clientKey) {
|
|
14678
|
+
return `${getPreviewPageStatePath(clientKey)}.lock`;
|
|
14679
|
+
}
|
|
14680
|
+
function ensureStateDirectory(statePath) {
|
|
14681
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true, mode: 0o700 });
|
|
14682
|
+
}
|
|
14683
|
+
function readServerState(statePath) {
|
|
14684
|
+
try {
|
|
14685
|
+
const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
14686
|
+
if (state.schemaVersion !== 1 ||
|
|
14687
|
+
!Number.isInteger(state.pid) ||
|
|
14688
|
+
!Number.isInteger(state.port) ||
|
|
14689
|
+
state.port <= 0 ||
|
|
14690
|
+
state.port > 65535 ||
|
|
14691
|
+
typeof state.controlToken !== 'string' ||
|
|
14692
|
+
state.controlToken.length < 32 ||
|
|
14693
|
+
typeof state.expiresAt !== 'string') {
|
|
14694
|
+
return null;
|
|
14695
|
+
}
|
|
14696
|
+
return state;
|
|
14697
|
+
}
|
|
14698
|
+
catch {
|
|
14699
|
+
return null;
|
|
14700
|
+
}
|
|
14701
|
+
}
|
|
14702
|
+
function writeServerState(statePath, state) {
|
|
14703
|
+
ensureStateDirectory(statePath);
|
|
14704
|
+
const tempPath = `${statePath}.${process.pid}.${crypto.randomBytes(4).toString('hex')}.tmp`;
|
|
14705
|
+
fs.writeFileSync(tempPath, JSON.stringify(state), { mode: 0o600 });
|
|
14706
|
+
fs.rmSync(statePath, { force: true });
|
|
14707
|
+
fs.renameSync(tempPath, statePath);
|
|
14708
|
+
}
|
|
14709
|
+
function removeMatchingServerState(statePath, pid, controlToken) {
|
|
14710
|
+
if (!statePath)
|
|
14711
|
+
return;
|
|
14712
|
+
const state = readServerState(statePath);
|
|
14713
|
+
if (state?.pid === pid && state.controlToken === controlToken) {
|
|
14714
|
+
fs.rmSync(statePath, { force: true });
|
|
14715
|
+
}
|
|
14716
|
+
}
|
|
14717
|
+
function tokensMatch(actual, expected) {
|
|
14718
|
+
const actualBuffer = Buffer.from(actual);
|
|
14719
|
+
const expectedBuffer = Buffer.from(expected);
|
|
14720
|
+
return (actualBuffer.length === expectedBuffer.length &&
|
|
14721
|
+
crypto.timingSafeEqual(actualBuffer, expectedBuffer));
|
|
14722
|
+
}
|
|
14723
|
+
async function withClientKeyLock(clientKey, action) {
|
|
14724
|
+
const lockPath = getPreviewPageLockPath(clientKey);
|
|
14725
|
+
ensureStateDirectory(lockPath);
|
|
14726
|
+
const deadline = Date.now() + PREVIEW_PAGE_LOCK_TIMEOUT_MS;
|
|
14727
|
+
let descriptor;
|
|
14728
|
+
while (descriptor === undefined && Date.now() < deadline) {
|
|
14729
|
+
try {
|
|
14730
|
+
descriptor = fs.openSync(lockPath, 'wx', 0o600);
|
|
14731
|
+
}
|
|
14732
|
+
catch (error) {
|
|
14733
|
+
if (error?.code !== 'EEXIST')
|
|
14734
|
+
throw error;
|
|
14735
|
+
try {
|
|
14736
|
+
const ageMs = Date.now() - fs.statSync(lockPath).mtimeMs;
|
|
14737
|
+
if (ageMs > PREVIEW_PAGE_LOCK_STALE_MS) {
|
|
14738
|
+
fs.rmSync(lockPath, { force: true });
|
|
14739
|
+
continue;
|
|
14740
|
+
}
|
|
14741
|
+
}
|
|
14742
|
+
catch {
|
|
14743
|
+
continue;
|
|
14744
|
+
}
|
|
14745
|
+
await new Promise(resolve => setTimeout(resolve, 40));
|
|
14746
|
+
}
|
|
14747
|
+
}
|
|
14748
|
+
if (descriptor === undefined) {
|
|
14749
|
+
throw new Error('Timed out waiting for the preview page service lock.');
|
|
14750
|
+
}
|
|
14751
|
+
try {
|
|
14752
|
+
return await action();
|
|
14753
|
+
}
|
|
14754
|
+
finally {
|
|
14755
|
+
fs.closeSync(descriptor);
|
|
14756
|
+
fs.rmSync(lockPath, { force: true });
|
|
14757
|
+
}
|
|
14758
|
+
}
|
|
14759
|
+
async function renderPreviewPage(input, expiresAt) {
|
|
14760
|
+
const [authorizationQr, previewQr] = await Promise.all([
|
|
14761
|
+
QRCode.toDataURL(input.authorizationUrl, {
|
|
14762
|
+
errorCorrectionLevel: 'M',
|
|
14763
|
+
margin: 2,
|
|
14764
|
+
width: 360,
|
|
14765
|
+
type: 'image/png',
|
|
14766
|
+
}),
|
|
14767
|
+
QRCode.toDataURL(input.previewUrl, {
|
|
14768
|
+
errorCorrectionLevel: 'M',
|
|
14769
|
+
margin: 2,
|
|
14770
|
+
width: 360,
|
|
14771
|
+
type: 'image/png',
|
|
14772
|
+
}),
|
|
14773
|
+
]);
|
|
14774
|
+
const projectName = escapeHtml(input.projectName);
|
|
14775
|
+
const clientKey = escapeHtml(input.clientKey);
|
|
14776
|
+
const versionId = escapeHtml(input.versionId);
|
|
14777
|
+
const authorizationUrl = escapeHtml(input.authorizationUrl);
|
|
14778
|
+
const previewUrl = escapeHtml(input.previewUrl);
|
|
14779
|
+
const rerunCommand = escapeHtml(`ttmg upload --preview --serve-preview-page --client-key ${input.clientKey}`);
|
|
14780
|
+
return `<!doctype html>
|
|
14781
|
+
<html lang="en">
|
|
14782
|
+
<head>
|
|
14783
|
+
<meta charset="utf-8" />
|
|
14784
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
14785
|
+
<meta name="color-scheme" content="light" />
|
|
14786
|
+
<title>${projectName} · Preview build</title>
|
|
14787
|
+
<style>
|
|
14788
|
+
:root {
|
|
14789
|
+
--ink: #15171f;
|
|
14790
|
+
--muted: #626879;
|
|
14791
|
+
--line: rgba(21, 23, 31, 0.1);
|
|
14792
|
+
--paper: rgba(255, 255, 255, 0.92);
|
|
14793
|
+
--cyan: #00b8c8;
|
|
14794
|
+
--cyan-soft: #e9fbfc;
|
|
14795
|
+
--pink: #fe2c55;
|
|
14796
|
+
--shadow: 0 10px 30px rgba(28, 34, 52, 0.07);
|
|
14797
|
+
}
|
|
14798
|
+
* { box-sizing: border-box; }
|
|
14799
|
+
body {
|
|
14800
|
+
margin: 0;
|
|
14801
|
+
min-height: 100vh;
|
|
14802
|
+
overflow-x: hidden;
|
|
14803
|
+
color: var(--ink);
|
|
14804
|
+
background:
|
|
14805
|
+
linear-gradient(180deg, rgba(0, 184, 200, 0.035), transparent 18rem),
|
|
14806
|
+
#f6f7f9;
|
|
14807
|
+
font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
14808
|
+
}
|
|
14809
|
+
.shell { width: min(1040px, calc(100% - 32px)); margin: 0 auto; padding: 30px 0 40px; }
|
|
14810
|
+
.topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 36px; }
|
|
14811
|
+
.brand { display: flex; align-items: center; gap: 11px; font-size: 13px; font-weight: 800; letter-spacing: 0.15em; }
|
|
14812
|
+
.mark { width: 28px; height: 28px; border-radius: 9px; background: linear-gradient(135deg, var(--cyan) 0 48%, var(--pink) 52% 100%); box-shadow: 0 7px 20px rgba(0, 184, 200, 0.24); }
|
|
14813
|
+
.status { display: inline-flex; align-items: center; gap: 8px; padding: 8px 11px; border: 1px solid rgba(0, 184, 200, 0.24); border-radius: 999px; color: #007d89; background: var(--cyan-soft); font-size: 12px; font-weight: 750; }
|
|
14814
|
+
.status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--cyan); box-shadow: 0 0 0 4px rgba(0, 184, 200, 0.12); }
|
|
14815
|
+
.guidance { margin: 0 0 20px; padding: 12px 15px; border: 1px solid var(--line); border-radius: 12px; color: var(--muted); background: rgba(255, 255, 255, 0.7); font-size: 13px; line-height: 1.5; }
|
|
14816
|
+
.cards { display: grid; min-width: 0; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
|
|
14817
|
+
.card { position: relative; min-width: 0; overflow: hidden; padding: 28px; border: 1px solid var(--line); border-radius: 20px; background: var(--paper); box-shadow: var(--shadow); }
|
|
14818
|
+
.card::after { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 4px; background: linear-gradient(90deg, var(--cyan), transparent 68%); }
|
|
14819
|
+
.card.preview::after { background: linear-gradient(90deg, var(--pink), transparent 68%); }
|
|
14820
|
+
.step { display: inline-flex; align-items: center; gap: 8px; margin-bottom: 15px; color: #007d89; font-size: 11px; font-weight: 850; letter-spacing: 0.15em; text-transform: uppercase; }
|
|
14821
|
+
.preview .step { color: #c82143; }
|
|
14822
|
+
h2 { margin: 0; font-size: 22px; letter-spacing: -0.025em; }
|
|
14823
|
+
.description { min-height: 54px; margin: 10px 0 22px; color: var(--muted); font-size: 14px; line-height: 1.55; }
|
|
14824
|
+
.qr { display: grid; place-items: center; width: min(290px, 100%); aspect-ratio: 1; margin: 0 auto 22px; padding: 15px; border: 1px solid var(--line); border-radius: 22px; background: white; }
|
|
14825
|
+
.qr img { display: block; width: 100%; height: 100%; image-rendering: pixelated; }
|
|
14826
|
+
.linkbox { min-width: 0; overflow: hidden; padding: 12px 13px; border: 1px solid var(--line); border-radius: 14px; background: #f8f9fb; }
|
|
14827
|
+
.linklabel { display: flex; align-items: center; justify-content: space-between; margin-bottom: 7px; color: #7b8190; font-size: 10px; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; }
|
|
14828
|
+
.copy { appearance: none; border: 0; color: #007d89; background: transparent; cursor: pointer; font: inherit; font-size: 10px; font-weight: 850; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
14829
|
+
.direct { display: block; overflow: hidden; color: #252936; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.45; text-decoration: none; text-overflow: ellipsis; white-space: nowrap; }
|
|
14830
|
+
.meta { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 1px; overflow: hidden; margin-top: 22px; border: 1px solid var(--line); border-radius: 18px; background: var(--line); }
|
|
14831
|
+
.meta div { min-width: 0; padding: 15px 17px; background: rgba(255, 255, 255, 0.82); }
|
|
14832
|
+
.meta small { display: block; margin-bottom: 5px; color: #858b98; font-size: 9px; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; }
|
|
14833
|
+
.meta strong { display: block; overflow: hidden; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
|
|
14834
|
+
.foot { margin: 17px 0 0; color: #7d8391; font-size: 11px; text-align: center; }
|
|
14835
|
+
[hidden] { display: none !important; }
|
|
14836
|
+
.expired-view { margin: 88px auto 0; max-width: 620px; padding: 42px; border: 1px solid var(--line); border-radius: 22px; background: var(--paper); box-shadow: var(--shadow); text-align: center; }
|
|
14837
|
+
.expired-view h1 { margin: 0; font-size: 28px; letter-spacing: -0.03em; }
|
|
14838
|
+
.expired-view p { margin: 13px auto 24px; max-width: 460px; color: var(--muted); font-size: 14px; line-height: 1.6; }
|
|
14839
|
+
.expired-command { display: block; padding: 14px 16px; border: 1px solid var(--line); border-radius: 14px; color: #252936; background: #f8f9fb; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.5; overflow-wrap: anywhere; text-align: left; white-space: normal; }
|
|
14840
|
+
.status.expired { border-color: rgba(254, 44, 85, 0.2); color: #c82143; background: #fff0f3; }
|
|
14841
|
+
.status.expired::before { background: var(--pink); box-shadow: 0 0 0 4px rgba(254, 44, 85, 0.1); }
|
|
14842
|
+
@media (max-width: 760px) {
|
|
14843
|
+
.shell { width: 100%; max-width: 560px; padding: 22px 11px 36px; }
|
|
14844
|
+
.topbar { margin-bottom: 34px; }
|
|
14845
|
+
.status { display: none; }
|
|
14846
|
+
.cards { grid-template-columns: minmax(0, 1fr); }
|
|
14847
|
+
.card { width: 100%; padding: 23px; border-radius: 22px; }
|
|
14848
|
+
.description { min-height: 0; }
|
|
14849
|
+
.qr { width: 100%; max-width: 290px; }
|
|
14850
|
+
.meta { grid-template-columns: 1fr; }
|
|
14851
|
+
}
|
|
14852
|
+
</style>
|
|
14853
|
+
</head>
|
|
14854
|
+
<body data-expires-at="${escapeHtml(expiresAt)}">
|
|
14855
|
+
<main class="shell">
|
|
14856
|
+
<header class="topbar">
|
|
14857
|
+
<div class="brand"><span class="mark" aria-hidden="true"></span>TTMG PREVIEW</div>
|
|
14858
|
+
<div class="status">Preview ready</div>
|
|
14859
|
+
</header>
|
|
14860
|
+
<div id="active-view">
|
|
14861
|
+
<p class="guidance">New test account: start with Step 1. Already authorized: go directly to Step 2.</p>
|
|
14862
|
+
<section class="cards">
|
|
14863
|
+
<article class="card">
|
|
14864
|
+
<div class="step">Step 01 · Account access</div>
|
|
14865
|
+
<h2>Authorize your test account</h2>
|
|
14866
|
+
<p class="description">Scan once with the TikTok account you will use for testing. If it is already a test user for this Mini Game, skip this step.</p>
|
|
14867
|
+
<div class="qr"><img src="${authorizationQr}" alt="QR code for test-account authorization" /></div>
|
|
14868
|
+
<div class="linkbox">
|
|
14869
|
+
<div class="linklabel">Open directly <button class="copy" data-target="authorization-link">Copy</button></div>
|
|
14870
|
+
<a id="authorization-link" class="direct" href="${authorizationUrl}" target="_blank" rel="noreferrer">${authorizationUrl}</a>
|
|
14871
|
+
</div>
|
|
14872
|
+
</article>
|
|
14873
|
+
<article class="card preview">
|
|
14874
|
+
<div class="step">Step 02 · Preview build</div>
|
|
14875
|
+
<h2>Open the game preview</h2>
|
|
14876
|
+
<p class="description">Already added as a test user? Scan this code directly. Otherwise, complete Step 1 first with the same account.</p>
|
|
14877
|
+
<div class="qr"><img src="${previewQr}" alt="QR code for the game preview" /></div>
|
|
14878
|
+
<div class="linkbox">
|
|
14879
|
+
<div class="linklabel">Open directly <button class="copy" data-target="preview-link">Copy</button></div>
|
|
14880
|
+
<a id="preview-link" class="direct" href="${previewUrl}" target="_blank" rel="noreferrer">${previewUrl}</a>
|
|
14881
|
+
</div>
|
|
14882
|
+
</article>
|
|
14883
|
+
</section>
|
|
14884
|
+
<section class="meta" aria-label="Preview metadata">
|
|
14885
|
+
<div><small>Mini Game</small><strong>${projectName}</strong></div>
|
|
14886
|
+
<div><small>Client key</small><strong>${clientKey}</strong></div>
|
|
14887
|
+
<div><small>Version</small><strong>${versionId}</strong></div>
|
|
14888
|
+
</section>
|
|
14889
|
+
<p class="foot">Local-only preview page · expires in <span id="countdown">60:00</span></p>
|
|
14890
|
+
</div>
|
|
14891
|
+
<section id="expired-view" class="expired-view" hidden>
|
|
14892
|
+
<h1>This preview page has expired</h1>
|
|
14893
|
+
<p>Run the upload command again from your Mini Game project to generate a new preview page and preview build.</p>
|
|
14894
|
+
<code class="expired-command">${rerunCommand}</code>
|
|
14895
|
+
</section>
|
|
14896
|
+
</main>
|
|
14897
|
+
<script>
|
|
14898
|
+
document.querySelectorAll('[data-target]').forEach(function (button) {
|
|
14899
|
+
button.addEventListener('click', async function () {
|
|
14900
|
+
var target = document.getElementById(button.dataset.target);
|
|
14901
|
+
if (!target) return;
|
|
14902
|
+
await navigator.clipboard.writeText(target.textContent || '');
|
|
14903
|
+
var previous = button.textContent;
|
|
14904
|
+
button.textContent = 'Copied';
|
|
14905
|
+
setTimeout(function () { button.textContent = previous; }, 1200);
|
|
14906
|
+
});
|
|
14907
|
+
});
|
|
14908
|
+
var expiresAt = Date.parse(document.body.dataset.expiresAt || '');
|
|
14909
|
+
var countdown = document.getElementById('countdown');
|
|
14910
|
+
var activeView = document.getElementById('active-view');
|
|
14911
|
+
var expiredView = document.getElementById('expired-view');
|
|
14912
|
+
var status = document.querySelector('.status');
|
|
14913
|
+
var timer;
|
|
14914
|
+
function updateCountdown() {
|
|
14915
|
+
var seconds = Math.max(0, Math.ceil((expiresAt - Date.now()) / 1000));
|
|
14916
|
+
var minutes = Math.floor(seconds / 60);
|
|
14917
|
+
countdown.textContent = String(minutes).padStart(2, '0') + ':' + String(seconds % 60).padStart(2, '0');
|
|
14918
|
+
if (seconds === 0) {
|
|
14919
|
+
activeView.hidden = true;
|
|
14920
|
+
activeView.replaceChildren();
|
|
14921
|
+
expiredView.hidden = false;
|
|
14922
|
+
status.textContent = 'Preview expired';
|
|
14923
|
+
status.classList.add('expired');
|
|
14924
|
+
clearInterval(timer);
|
|
14925
|
+
}
|
|
14926
|
+
}
|
|
14927
|
+
updateCountdown();
|
|
14928
|
+
timer = setInterval(updateCountdown, 1000);
|
|
14929
|
+
</script>
|
|
14930
|
+
</body>
|
|
14931
|
+
</html>`;
|
|
14932
|
+
}
|
|
14933
|
+
function renderExpiredPreviewPage(input) {
|
|
14934
|
+
const projectName = escapeHtml(input.projectName);
|
|
14935
|
+
const rerunCommand = escapeHtml(`ttmg upload --preview --serve-preview-page --client-key ${input.clientKey}`);
|
|
14936
|
+
return `<!doctype html>
|
|
14937
|
+
<html lang="en">
|
|
14938
|
+
<head>
|
|
14939
|
+
<meta charset="utf-8" />
|
|
14940
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
14941
|
+
<meta name="color-scheme" content="light" />
|
|
14942
|
+
<title>${projectName} · Preview expired</title>
|
|
14943
|
+
<style>
|
|
14944
|
+
* { box-sizing: border-box; }
|
|
14945
|
+
body { display: grid; min-height: 100vh; margin: 0; place-items: center; padding: 24px; color: #15171f; background: #f6f7f9; font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
14946
|
+
main { width: min(620px, 100%); padding: 42px; border: 1px solid rgba(21, 23, 31, 0.1); border-radius: 22px; background: rgba(255, 255, 255, 0.94); box-shadow: 0 10px 30px rgba(28, 34, 52, 0.07); text-align: center; }
|
|
14947
|
+
.mark { width: 30px; height: 30px; margin: 0 auto 22px; border-radius: 9px; background: linear-gradient(135deg, #00b8c8 0 48%, #fe2c55 52% 100%); }
|
|
14948
|
+
h1 { margin: 0; font-size: 28px; letter-spacing: -0.03em; }
|
|
14949
|
+
p { margin: 13px auto 24px; max-width: 460px; color: #626879; font-size: 14px; line-height: 1.6; }
|
|
14950
|
+
code { display: block; padding: 14px 16px; border: 1px solid rgba(21, 23, 31, 0.1); border-radius: 14px; color: #252936; background: #f8f9fb; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.5; overflow-wrap: anywhere; text-align: left; white-space: normal; }
|
|
14951
|
+
</style>
|
|
14952
|
+
</head>
|
|
14953
|
+
<body>
|
|
14954
|
+
<main>
|
|
14955
|
+
<div class="mark" aria-hidden="true"></div>
|
|
14956
|
+
<h1>This preview page has expired</h1>
|
|
14957
|
+
<p>Run the upload command again from your Mini Game project to generate a new preview page and preview build.</p>
|
|
14958
|
+
<code>${rerunCommand}</code>
|
|
14959
|
+
</main>
|
|
14960
|
+
</body>
|
|
14961
|
+
</html>`;
|
|
14962
|
+
}
|
|
14963
|
+
async function createPreviewPageHttpServer({ input, token, ttlMs = DEFAULT_PREVIEW_PAGE_TTL_MS, expiredNoticeTtlMs = DEFAULT_PREVIEW_PAGE_EXPIRED_NOTICE_TTL_MS, controlToken = crypto.randomBytes(24).toString('hex'), onClose, }) {
|
|
14964
|
+
if (!isPreviewPageInput(input)) {
|
|
14965
|
+
throw new Error('Preview page links must use HTTP or HTTPS.');
|
|
14966
|
+
}
|
|
14967
|
+
let port = 0;
|
|
14968
|
+
let closed = false;
|
|
14969
|
+
let expiryTimer;
|
|
14970
|
+
let shutdownTimer;
|
|
14971
|
+
let currentPage = {
|
|
14972
|
+
pathname: `/preview/${token}`,
|
|
14973
|
+
expiresAt: new Date(Date.now() + ttlMs).toISOString(),
|
|
14974
|
+
html: '',
|
|
14975
|
+
expiredHtml: renderExpiredPreviewPage(input),
|
|
14976
|
+
};
|
|
14977
|
+
currentPage.html = await renderPreviewPage(input, currentPage.expiresAt);
|
|
14978
|
+
const getResult = () => createPreviewPageServerResult(`http://127.0.0.1:${port}${currentPage.pathname}`, currentPage.expiresAt);
|
|
14979
|
+
let close;
|
|
14980
|
+
const scheduleLifecycle = () => {
|
|
14981
|
+
clearTimeout(expiryTimer);
|
|
14982
|
+
clearTimeout(shutdownTimer);
|
|
14983
|
+
expiryTimer = setTimeout(() => {
|
|
14984
|
+
currentPage.html = '';
|
|
14985
|
+
}, ttlMs);
|
|
14986
|
+
shutdownTimer = setTimeout(() => {
|
|
14987
|
+
void close();
|
|
14988
|
+
}, ttlMs + expiredNoticeTtlMs);
|
|
14989
|
+
};
|
|
14990
|
+
const update = async (nextInput) => {
|
|
14991
|
+
if (!isPreviewPageInput(nextInput)) {
|
|
14992
|
+
throw new Error('Preview page links must use HTTP or HTTPS.');
|
|
14993
|
+
}
|
|
14994
|
+
const nextToken = crypto.randomBytes(24).toString('hex');
|
|
14995
|
+
const nextExpiresAt = new Date(Date.now() + ttlMs).toISOString();
|
|
14996
|
+
const nextHtml = await renderPreviewPage(nextInput, nextExpiresAt);
|
|
14997
|
+
const nextExpiredHtml = renderExpiredPreviewPage(nextInput);
|
|
14998
|
+
currentPage = {
|
|
14999
|
+
pathname: `/preview/${nextToken}`,
|
|
15000
|
+
expiresAt: nextExpiresAt,
|
|
15001
|
+
html: nextHtml,
|
|
15002
|
+
expiredHtml: nextExpiredHtml,
|
|
15003
|
+
};
|
|
15004
|
+
scheduleLifecycle();
|
|
15005
|
+
return getResult();
|
|
15006
|
+
};
|
|
15007
|
+
const sendJson = (response, statusCode, value) => {
|
|
15008
|
+
const body = JSON.stringify(value);
|
|
15009
|
+
response.writeHead(statusCode, {
|
|
15010
|
+
'Cache-Control': 'no-store',
|
|
15011
|
+
'Content-Length': String(Buffer.byteLength(body)),
|
|
15012
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
15013
|
+
'X-Content-Type-Options': 'nosniff',
|
|
15014
|
+
});
|
|
15015
|
+
response.end(body);
|
|
15016
|
+
};
|
|
15017
|
+
const server = http.createServer(async (request, response) => {
|
|
15018
|
+
const requestUrl = new URL(request.url || '/', 'http://127.0.0.1');
|
|
15019
|
+
if (requestUrl.pathname === PREVIEW_PAGE_CONTROL_PATH) {
|
|
15020
|
+
const authorization = Array.isArray(request.headers.authorization)
|
|
15021
|
+
? request.headers.authorization[0] || ''
|
|
15022
|
+
: request.headers.authorization || '';
|
|
15023
|
+
const bearerToken = authorization.startsWith('Bearer ')
|
|
15024
|
+
? authorization.slice('Bearer '.length)
|
|
15025
|
+
: '';
|
|
15026
|
+
if (request.method !== 'POST' ||
|
|
15027
|
+
!tokensMatch(bearerToken, controlToken)) {
|
|
15028
|
+
sendJson(response, 404, { error: 'Not Found' });
|
|
15029
|
+
return;
|
|
15030
|
+
}
|
|
15031
|
+
try {
|
|
15032
|
+
const chunks = [];
|
|
15033
|
+
let totalBytes = 0;
|
|
15034
|
+
for await (const chunk of request) {
|
|
15035
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
15036
|
+
totalBytes += buffer.length;
|
|
15037
|
+
if (totalBytes > 64 * 1024) {
|
|
15038
|
+
throw new Error('Preview page update is too large.');
|
|
15039
|
+
}
|
|
15040
|
+
chunks.push(buffer);
|
|
15041
|
+
}
|
|
15042
|
+
const nextInput = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
15043
|
+
const result = await update(nextInput);
|
|
15044
|
+
sendJson(response, 200, result);
|
|
15045
|
+
}
|
|
15046
|
+
catch (error) {
|
|
15047
|
+
sendJson(response, 400, {
|
|
15048
|
+
error: error?.message || String(error),
|
|
15049
|
+
});
|
|
15050
|
+
}
|
|
15051
|
+
return;
|
|
15052
|
+
}
|
|
15053
|
+
const matchesPath = requestUrl.pathname === currentPage.pathname ||
|
|
15054
|
+
requestUrl.pathname === `${currentPage.pathname}/`;
|
|
15055
|
+
if (!matchesPath ||
|
|
15056
|
+
(request.method !== 'GET' && request.method !== 'HEAD')) {
|
|
15057
|
+
response.writeHead(404, {
|
|
15058
|
+
'Cache-Control': 'no-store',
|
|
15059
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
15060
|
+
'X-Content-Type-Options': 'nosniff',
|
|
15061
|
+
});
|
|
15062
|
+
response.end('Not Found');
|
|
15063
|
+
return;
|
|
15064
|
+
}
|
|
15065
|
+
const isExpired = Date.now() >= Date.parse(currentPage.expiresAt);
|
|
15066
|
+
if (isExpired)
|
|
15067
|
+
currentPage.html = '';
|
|
15068
|
+
const responseHtml = isExpired ? currentPage.expiredHtml : currentPage.html;
|
|
15069
|
+
response.writeHead(200, {
|
|
15070
|
+
'Cache-Control': 'no-store',
|
|
15071
|
+
'Content-Length': String(Buffer.byteLength(responseHtml)),
|
|
15072
|
+
'Content-Security-Policy': "default-src 'none'; img-src data:; style-src 'unsafe-inline'; script-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'",
|
|
15073
|
+
'Content-Type': PREVIEW_PAGE_CONTENT_TYPE,
|
|
15074
|
+
'Referrer-Policy': 'no-referrer',
|
|
15075
|
+
'X-Content-Type-Options': 'nosniff',
|
|
15076
|
+
});
|
|
15077
|
+
response.end(request.method === 'HEAD' ? undefined : responseHtml);
|
|
15078
|
+
});
|
|
15079
|
+
await new Promise((resolve, reject) => {
|
|
15080
|
+
const onError = (error) => {
|
|
15081
|
+
server.off('listening', onListening);
|
|
15082
|
+
reject(error);
|
|
15083
|
+
};
|
|
15084
|
+
const onListening = () => {
|
|
15085
|
+
server.off('error', onError);
|
|
15086
|
+
resolve();
|
|
15087
|
+
};
|
|
15088
|
+
server.once('error', onError);
|
|
15089
|
+
server.once('listening', onListening);
|
|
15090
|
+
server.listen(0, '127.0.0.1');
|
|
15091
|
+
});
|
|
15092
|
+
const address = server.address();
|
|
15093
|
+
if (!address || typeof address === 'string') {
|
|
15094
|
+
server.close();
|
|
15095
|
+
throw new Error('Unable to resolve the preview page server address.');
|
|
15096
|
+
}
|
|
15097
|
+
port = address.port;
|
|
15098
|
+
close = () => new Promise(resolve => {
|
|
15099
|
+
if (closed) {
|
|
15100
|
+
resolve();
|
|
15101
|
+
return;
|
|
15102
|
+
}
|
|
15103
|
+
closed = true;
|
|
15104
|
+
clearTimeout(expiryTimer);
|
|
15105
|
+
clearTimeout(shutdownTimer);
|
|
15106
|
+
server.close(() => {
|
|
15107
|
+
currentPage.html = '';
|
|
15108
|
+
currentPage.expiredHtml = '';
|
|
15109
|
+
onClose?.();
|
|
15110
|
+
resolve();
|
|
15111
|
+
});
|
|
15112
|
+
});
|
|
15113
|
+
scheduleLifecycle();
|
|
15114
|
+
return {
|
|
15115
|
+
result: getResult(),
|
|
15116
|
+
update,
|
|
15117
|
+
close,
|
|
15118
|
+
};
|
|
15119
|
+
}
|
|
15120
|
+
function runPreviewPageServerWorker() {
|
|
15121
|
+
if (!process.send) {
|
|
15122
|
+
process.exitCode = 1;
|
|
15123
|
+
return;
|
|
15124
|
+
}
|
|
15125
|
+
process.once('message', async (message) => {
|
|
15126
|
+
if (!isWorkerRequest(message)) {
|
|
15127
|
+
process.send?.({
|
|
15128
|
+
type: 'error',
|
|
15129
|
+
error: 'Invalid preview page server request.',
|
|
15130
|
+
});
|
|
15131
|
+
process.disconnect?.();
|
|
15132
|
+
process.exitCode = 1;
|
|
15133
|
+
return;
|
|
15134
|
+
}
|
|
15135
|
+
try {
|
|
15136
|
+
const controlToken = message.controlToken || crypto.randomBytes(24).toString('hex');
|
|
15137
|
+
const pageServer = await createPreviewPageHttpServer({
|
|
15138
|
+
input: message,
|
|
15139
|
+
token: message.token,
|
|
15140
|
+
ttlMs: message.ttlMs,
|
|
15141
|
+
expiredNoticeTtlMs: message.expiredNoticeTtlMs,
|
|
15142
|
+
controlToken,
|
|
15143
|
+
onClose: () => removeMatchingServerState(message.statePath, process.pid, controlToken),
|
|
15144
|
+
});
|
|
15145
|
+
process.send?.({
|
|
15146
|
+
type: 'ready',
|
|
15147
|
+
url: pageServer.result.url,
|
|
15148
|
+
contentType: pageServer.result.contentType,
|
|
15149
|
+
expiresAt: pageServer.result.expiresAt,
|
|
15150
|
+
});
|
|
15151
|
+
process.disconnect?.();
|
|
15152
|
+
const shutdown = () => {
|
|
15153
|
+
void pageServer.close().finally(() => process.exit(0));
|
|
15154
|
+
};
|
|
15155
|
+
process.once('SIGINT', shutdown);
|
|
15156
|
+
process.once('SIGTERM', shutdown);
|
|
15157
|
+
}
|
|
15158
|
+
catch (error) {
|
|
15159
|
+
process.send?.({
|
|
15160
|
+
type: 'error',
|
|
15161
|
+
error: error?.message || String(error),
|
|
15162
|
+
});
|
|
15163
|
+
process.disconnect?.();
|
|
15164
|
+
process.exitCode = 1;
|
|
15165
|
+
}
|
|
15166
|
+
});
|
|
15167
|
+
}
|
|
15168
|
+
async function updateExistingPreviewPageServer(state, input) {
|
|
15169
|
+
const body = JSON.stringify(input);
|
|
15170
|
+
return new Promise((resolve, reject) => {
|
|
15171
|
+
const request = http.request({
|
|
15172
|
+
hostname: '127.0.0.1',
|
|
15173
|
+
port: state.port,
|
|
15174
|
+
path: PREVIEW_PAGE_CONTROL_PATH,
|
|
15175
|
+
method: 'POST',
|
|
15176
|
+
headers: {
|
|
15177
|
+
Authorization: `Bearer ${state.controlToken}`,
|
|
15178
|
+
'Content-Length': String(Buffer.byteLength(body)),
|
|
15179
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
15180
|
+
},
|
|
15181
|
+
}, response => {
|
|
15182
|
+
const chunks = [];
|
|
15183
|
+
let totalBytes = 0;
|
|
15184
|
+
response.on('data', chunk => {
|
|
15185
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
15186
|
+
totalBytes += buffer.length;
|
|
15187
|
+
if (totalBytes <= 64 * 1024)
|
|
15188
|
+
chunks.push(buffer);
|
|
15189
|
+
});
|
|
15190
|
+
response.on('end', () => {
|
|
15191
|
+
if (totalBytes > 64 * 1024) {
|
|
15192
|
+
reject(new Error('Preview page service response is too large.'));
|
|
15193
|
+
return;
|
|
15194
|
+
}
|
|
15195
|
+
try {
|
|
15196
|
+
const payload = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
15197
|
+
if (response.statusCode !== 200 || payload?.status !== 'ready') {
|
|
15198
|
+
throw new Error(payload?.error || 'Preview page service update failed.');
|
|
15199
|
+
}
|
|
15200
|
+
resolve(payload);
|
|
15201
|
+
}
|
|
15202
|
+
catch (error) {
|
|
15203
|
+
reject(error);
|
|
15204
|
+
}
|
|
15205
|
+
});
|
|
15206
|
+
});
|
|
15207
|
+
request.setTimeout(1500, () => {
|
|
15208
|
+
request.destroy(new Error('Preview page service update timed out.'));
|
|
15209
|
+
});
|
|
15210
|
+
request.once('error', reject);
|
|
15211
|
+
request.end(body);
|
|
15212
|
+
});
|
|
15213
|
+
}
|
|
15214
|
+
async function spawnDetachedPreviewPageServer(input, statePath, options) {
|
|
15215
|
+
const ttlMs = options.ttlMs || DEFAULT_PREVIEW_PAGE_TTL_MS;
|
|
15216
|
+
const expiredNoticeTtlMs = options.expiredNoticeTtlMs ?? DEFAULT_PREVIEW_PAGE_EXPIRED_NOTICE_TTL_MS;
|
|
15217
|
+
const entryPath = options.entryPath || process.argv[1];
|
|
15218
|
+
const token = crypto.randomBytes(24).toString('hex');
|
|
15219
|
+
const controlToken = crypto.randomBytes(24).toString('hex');
|
|
15220
|
+
const child = child_process.spawn(process.execPath, [entryPath, PREVIEW_PAGE_SERVER_WORKER_ARG], {
|
|
15221
|
+
detached: true,
|
|
15222
|
+
windowsHide: true,
|
|
15223
|
+
stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
|
|
15224
|
+
});
|
|
15225
|
+
return new Promise((resolve, reject) => {
|
|
15226
|
+
let settled = false;
|
|
15227
|
+
const finish = (error, result) => {
|
|
15228
|
+
if (settled)
|
|
15229
|
+
return;
|
|
15230
|
+
settled = true;
|
|
15231
|
+
clearTimeout(timer);
|
|
15232
|
+
child.removeAllListeners('message');
|
|
15233
|
+
child.removeAllListeners('error');
|
|
15234
|
+
child.removeAllListeners('exit');
|
|
15235
|
+
if (error) {
|
|
15236
|
+
child.kill();
|
|
15237
|
+
reject(error);
|
|
15238
|
+
return;
|
|
15239
|
+
}
|
|
15240
|
+
if (child.connected)
|
|
15241
|
+
child.disconnect();
|
|
15242
|
+
child.unref();
|
|
15243
|
+
resolve(result);
|
|
15244
|
+
};
|
|
15245
|
+
const timer = setTimeout(() => {
|
|
15246
|
+
finish(new Error('Timed out starting the preview page server.'));
|
|
15247
|
+
}, options.startTimeoutMs || PREVIEW_PAGE_START_TIMEOUT_MS);
|
|
15248
|
+
child.once('error', error => finish(error));
|
|
15249
|
+
child.once('exit', code => {
|
|
15250
|
+
finish(new Error(`Preview page server exited before it was ready (code ${code ?? 'unknown'}).`));
|
|
15251
|
+
});
|
|
15252
|
+
child.on('message', message => {
|
|
15253
|
+
const response = message;
|
|
15254
|
+
if (response?.type === 'error') {
|
|
15255
|
+
finish(new Error(response.error));
|
|
15256
|
+
return;
|
|
15257
|
+
}
|
|
15258
|
+
if (response?.type === 'ready') {
|
|
15259
|
+
const result = createPreviewPageServerResult(response.url, response.expiresAt);
|
|
15260
|
+
try {
|
|
15261
|
+
const port = Number(new URL(result.url).port);
|
|
15262
|
+
writeServerState(statePath, {
|
|
15263
|
+
schemaVersion: 1,
|
|
15264
|
+
pid: child.pid,
|
|
15265
|
+
port,
|
|
15266
|
+
controlToken,
|
|
15267
|
+
expiresAt: result.expiresAt,
|
|
15268
|
+
});
|
|
15269
|
+
finish(undefined, result);
|
|
15270
|
+
}
|
|
15271
|
+
catch (error) {
|
|
15272
|
+
finish(error);
|
|
15273
|
+
}
|
|
15274
|
+
}
|
|
15275
|
+
});
|
|
15276
|
+
child.send({
|
|
15277
|
+
type: 'start',
|
|
15278
|
+
...input,
|
|
15279
|
+
token,
|
|
15280
|
+
ttlMs,
|
|
15281
|
+
expiredNoticeTtlMs,
|
|
15282
|
+
controlToken,
|
|
15283
|
+
statePath,
|
|
15284
|
+
});
|
|
15285
|
+
});
|
|
15286
|
+
}
|
|
15287
|
+
async function startDetachedPreviewPageServer(input, options = {}) {
|
|
15288
|
+
if (!isPreviewPageInput(input)) {
|
|
15289
|
+
throw new Error('Preview page links must use HTTP or HTTPS.');
|
|
15290
|
+
}
|
|
15291
|
+
return withClientKeyLock(input.clientKey, async () => {
|
|
15292
|
+
const statePath = getPreviewPageStatePath(input.clientKey);
|
|
15293
|
+
const existingState = readServerState(statePath);
|
|
15294
|
+
if (existingState) {
|
|
15295
|
+
try {
|
|
15296
|
+
const result = await updateExistingPreviewPageServer(existingState, input);
|
|
15297
|
+
writeServerState(statePath, {
|
|
15298
|
+
...existingState,
|
|
15299
|
+
expiresAt: result.expiresAt,
|
|
15300
|
+
});
|
|
15301
|
+
return result;
|
|
15302
|
+
}
|
|
15303
|
+
catch {
|
|
15304
|
+
fs.rmSync(statePath, { force: true });
|
|
15305
|
+
}
|
|
15306
|
+
}
|
|
15307
|
+
else if (fs.existsSync(statePath)) {
|
|
15308
|
+
fs.rmSync(statePath, { force: true });
|
|
15309
|
+
}
|
|
15310
|
+
return spawnDetachedPreviewPageServer(input, statePath, options);
|
|
15311
|
+
});
|
|
15312
|
+
}
|
|
15313
|
+
|
|
15314
|
+
const defaultDependencies$6 = {
|
|
14898
15315
|
getCurrentUser,
|
|
14899
15316
|
fetchGameInfo,
|
|
14900
15317
|
writeQrFile: async (outputPath, payload) => {
|
|
@@ -14920,114 +15337,511 @@ function normalizeFormat$3(format) {
|
|
|
14920
15337
|
if (normalized !== 'text' && normalized !== 'json') {
|
|
14921
15338
|
throw new TestAccountCommandError('INVALID_FORMAT', t('testAccount.error.invalidFormat', { format: normalized }));
|
|
14922
15339
|
}
|
|
14923
|
-
return normalized;
|
|
15340
|
+
return normalized;
|
|
15341
|
+
}
|
|
15342
|
+
function buildTestAccountAuthorizationUrl(clientKey) {
|
|
15343
|
+
return `https://www.tiktok.com/ttmg/dev/${encodeURIComponent(clientKey)}`;
|
|
15344
|
+
}
|
|
15345
|
+
async function generateTestAccountAuthorization(options, dependencies = {}) {
|
|
15346
|
+
const deps = { ...defaultDependencies$6, ...dependencies };
|
|
15347
|
+
const clientKey = options.clientKey?.trim();
|
|
15348
|
+
const operationId = deps.operationId();
|
|
15349
|
+
if (!clientKey) {
|
|
15350
|
+
throw new TestAccountCommandError('INVALID_ARGUMENT', t('testAccount.error.noClientKey'));
|
|
15351
|
+
}
|
|
15352
|
+
const user = deps.getCurrentUser();
|
|
15353
|
+
if (!user || !user.cookie) {
|
|
15354
|
+
throw new TestAccountCommandError('NOT_AUTHENTICATED', t('testAccount.error.notLoggedIn'));
|
|
15355
|
+
}
|
|
15356
|
+
const infoResponse = await deps.fetchGameInfo(clientKey);
|
|
15357
|
+
if (infoResponse.error) {
|
|
15358
|
+
throw new TestAccountCommandError('PROJECT_LOOKUP_FAILED', infoResponse.error.errorMsg || t('testAccount.error.gameInfoFailed'));
|
|
15359
|
+
}
|
|
15360
|
+
const appId = infoResponse.data?.app_id || '';
|
|
15361
|
+
if (!appId) {
|
|
15362
|
+
throw new TestAccountCommandError('PROJECT_RESPONSE_INVALID', t('testAccount.error.noAppId'));
|
|
15363
|
+
}
|
|
15364
|
+
const projectName = infoResponse.data?.name || 'game';
|
|
15365
|
+
const authorizationUrl = buildTestAccountAuthorizationUrl(clientKey);
|
|
15366
|
+
const safeClientKey = clientKey.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
15367
|
+
const qrFile = path.resolve(process.cwd(), options.qrOutput || path.join('.ttmg', `test-account-${safeClientKey}.png`));
|
|
15368
|
+
try {
|
|
15369
|
+
fs.mkdirSync(path.dirname(qrFile), { recursive: true });
|
|
15370
|
+
await deps.writeQrFile(qrFile, authorizationUrl);
|
|
15371
|
+
}
|
|
15372
|
+
catch (error) {
|
|
15373
|
+
throw new TestAccountCommandError('QR_WRITE_FAILED', error?.message || t('testAccount.error.qrWriteFailed'));
|
|
15374
|
+
}
|
|
15375
|
+
return {
|
|
15376
|
+
schemaVersion: '1',
|
|
15377
|
+
operationId,
|
|
15378
|
+
ts: deps.now(),
|
|
15379
|
+
operation: 'test-account.add',
|
|
15380
|
+
status: 'authorization_required',
|
|
15381
|
+
clientKey,
|
|
15382
|
+
appId,
|
|
15383
|
+
projectName,
|
|
15384
|
+
authorizationUrl,
|
|
15385
|
+
qrPayload: authorizationUrl,
|
|
15386
|
+
qrFile,
|
|
15387
|
+
portalUrl: `https://developers.tiktok.com/portal/game/${appId}`,
|
|
15388
|
+
platformWrite: false,
|
|
15389
|
+
verification: 'developer_portal',
|
|
15390
|
+
errorCode: null,
|
|
15391
|
+
error: null,
|
|
15392
|
+
};
|
|
15393
|
+
}
|
|
15394
|
+
async function testAccountAdd(options, dependencies = {}) {
|
|
15395
|
+
const deps = { ...defaultDependencies$6, ...dependencies };
|
|
15396
|
+
let format = 'text';
|
|
15397
|
+
const operationId = deps.operationId();
|
|
15398
|
+
try {
|
|
15399
|
+
format = normalizeFormat$3(options.format);
|
|
15400
|
+
const result = await generateTestAccountAuthorization(options, {
|
|
15401
|
+
...dependencies,
|
|
15402
|
+
operationId: () => operationId,
|
|
15403
|
+
});
|
|
15404
|
+
if (format === 'json') {
|
|
15405
|
+
console.log(JSON.stringify(result));
|
|
15406
|
+
}
|
|
15407
|
+
else {
|
|
15408
|
+
console.log(chalk.green.bold(t('testAccount.success')));
|
|
15409
|
+
console.log(t('testAccount.project', {
|
|
15410
|
+
name: result.projectName,
|
|
15411
|
+
clientKey: result.clientKey,
|
|
15412
|
+
}));
|
|
15413
|
+
console.log(t('testAccount.authorizationUrl', { url: result.authorizationUrl }));
|
|
15414
|
+
console.log(t('testAccount.qrFile', { file: result.qrFile }));
|
|
15415
|
+
console.log(chalk.yellow(t('testAccount.verifyInPortal')));
|
|
15416
|
+
}
|
|
15417
|
+
return result;
|
|
15418
|
+
}
|
|
15419
|
+
catch (error) {
|
|
15420
|
+
const message = error?.message || String(error);
|
|
15421
|
+
const errorCode = error instanceof TestAccountCommandError ? error.code : 'UNKNOWN_ERROR';
|
|
15422
|
+
const failedResult = {
|
|
15423
|
+
schemaVersion: '1',
|
|
15424
|
+
operationId,
|
|
15425
|
+
ts: deps.now(),
|
|
15426
|
+
operation: 'test-account.add',
|
|
15427
|
+
status: 'failed',
|
|
15428
|
+
clientKey: options.clientKey || '',
|
|
15429
|
+
appId: '',
|
|
15430
|
+
projectName: '',
|
|
15431
|
+
authorizationUrl: '',
|
|
15432
|
+
qrPayload: '',
|
|
15433
|
+
qrFile: '',
|
|
15434
|
+
portalUrl: '',
|
|
15435
|
+
platformWrite: false,
|
|
15436
|
+
verification: 'developer_portal',
|
|
15437
|
+
errorCode,
|
|
15438
|
+
error: message,
|
|
15439
|
+
};
|
|
15440
|
+
if (format === 'json') {
|
|
15441
|
+
console.log(JSON.stringify(failedResult));
|
|
15442
|
+
}
|
|
15443
|
+
else {
|
|
15444
|
+
console.error(chalk.red.bold(t('testAccount.fail', { error: message })));
|
|
15445
|
+
}
|
|
15446
|
+
process.exitCode = 1;
|
|
15447
|
+
return failedResult;
|
|
15448
|
+
}
|
|
15449
|
+
}
|
|
15450
|
+
|
|
15451
|
+
const DEFAULT_POLL_INTERVAL_MS = 3000;
|
|
15452
|
+
const DEFAULT_POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
15453
|
+
const defaultDependencies$5 = {
|
|
15454
|
+
getCurrentUser,
|
|
15455
|
+
fetchGameInfo,
|
|
15456
|
+
zipDirectory: targetDir => zipCwdToBuffer([], targetDir),
|
|
15457
|
+
uploadGameToPlatform,
|
|
15458
|
+
fetchGameAssets,
|
|
15459
|
+
fetchGameAssetPreviewUrl,
|
|
15460
|
+
startPreviewPageServer: startDetachedPreviewPageServer,
|
|
15461
|
+
sleep: ms => new Promise(resolve => setTimeout(resolve, ms)),
|
|
15462
|
+
now: () => Date.now(),
|
|
15463
|
+
pollIntervalMs: DEFAULT_POLL_INTERVAL_MS,
|
|
15464
|
+
pollTimeoutMs: DEFAULT_POLL_TIMEOUT_MS,
|
|
15465
|
+
};
|
|
15466
|
+
const defaultUploadRuntime = {
|
|
15467
|
+
openUrl: async (url) => {
|
|
15468
|
+
const openModule = await import('open');
|
|
15469
|
+
return openModule.default(url);
|
|
15470
|
+
},
|
|
15471
|
+
};
|
|
15472
|
+
function normalizeOutputFormat(format) {
|
|
15473
|
+
const normalized = (format || 'text').toLowerCase();
|
|
15474
|
+
if (normalized !== 'text' &&
|
|
15475
|
+
normalized !== 'json' &&
|
|
15476
|
+
normalized !== 'ndjson') {
|
|
15477
|
+
throw new Error(t('upload.error.invalidFormat', { format: normalized }));
|
|
15478
|
+
}
|
|
15479
|
+
return normalized;
|
|
15480
|
+
}
|
|
15481
|
+
function getAssetStatusRaw(asset) {
|
|
15482
|
+
return asset?.cdn_deploy_status ?? asset?.status ?? null;
|
|
15483
|
+
}
|
|
15484
|
+
function normalizeUploadPlatformStatus(rawStatus) {
|
|
15485
|
+
if (rawStatus === null || rawStatus === undefined || rawStatus === '') {
|
|
15486
|
+
return 'waiting_for_asset';
|
|
15487
|
+
}
|
|
15488
|
+
const numericStatus = Number(rawStatus);
|
|
15489
|
+
if (Number.isInteger(numericStatus)) {
|
|
15490
|
+
if (numericStatus === 0)
|
|
15491
|
+
return 'unknown';
|
|
15492
|
+
if (numericStatus === 1)
|
|
15493
|
+
return 'pending';
|
|
15494
|
+
if (numericStatus === 2)
|
|
15495
|
+
return 'success';
|
|
15496
|
+
if (numericStatus === 3)
|
|
15497
|
+
return 'failed';
|
|
15498
|
+
if (numericStatus === 4)
|
|
15499
|
+
return 'processing';
|
|
15500
|
+
return 'unknown';
|
|
15501
|
+
}
|
|
15502
|
+
const normalized = String(rawStatus).trim().toLowerCase();
|
|
15503
|
+
const statusName = normalized.replace(/^deploy_status_/, '');
|
|
15504
|
+
if (['ready', 'success', 'succeeded', 'available'].includes(statusName)) {
|
|
15505
|
+
return 'success';
|
|
15506
|
+
}
|
|
15507
|
+
if (statusName.includes('fail') ||
|
|
15508
|
+
statusName.includes('error') ||
|
|
15509
|
+
statusName.includes('reject')) {
|
|
15510
|
+
return 'failed';
|
|
15511
|
+
}
|
|
15512
|
+
if (statusName.includes('process') ||
|
|
15513
|
+
statusName.includes('upload') ||
|
|
15514
|
+
statusName.includes('deploy')) {
|
|
15515
|
+
return 'processing';
|
|
15516
|
+
}
|
|
15517
|
+
if (statusName.includes('pending') ||
|
|
15518
|
+
statusName.includes('queue') ||
|
|
15519
|
+
statusName.includes('wait')) {
|
|
15520
|
+
return 'pending';
|
|
15521
|
+
}
|
|
15522
|
+
return 'unknown';
|
|
15523
|
+
}
|
|
15524
|
+
function getAssetState(asset) {
|
|
15525
|
+
const platformStatus = normalizeUploadPlatformStatus(getAssetStatusRaw(asset));
|
|
15526
|
+
if (platformStatus === 'success') {
|
|
15527
|
+
return 'ready';
|
|
15528
|
+
}
|
|
15529
|
+
if (platformStatus === 'failed') {
|
|
15530
|
+
return 'failed';
|
|
15531
|
+
}
|
|
15532
|
+
return 'pending';
|
|
15533
|
+
}
|
|
15534
|
+
function extractAssets(data) {
|
|
15535
|
+
if (!data) {
|
|
15536
|
+
return [];
|
|
15537
|
+
}
|
|
15538
|
+
return [
|
|
15539
|
+
data.preview_assets,
|
|
15540
|
+
data.in_review_assets,
|
|
15541
|
+
data.live_assets,
|
|
15542
|
+
data.assets,
|
|
15543
|
+
].flatMap(group => (Array.isArray(group) ? group : []));
|
|
15544
|
+
}
|
|
15545
|
+
function extractPreviewUrl(data) {
|
|
15546
|
+
return typeof data?.preview_url === 'string' ? data.preview_url : '';
|
|
15547
|
+
}
|
|
15548
|
+
function createEvent(base, status, progress, overrides = {}) {
|
|
15549
|
+
return {
|
|
15550
|
+
schemaVersion: '1',
|
|
15551
|
+
operationId: base.operationId,
|
|
15552
|
+
ts: Date.now(),
|
|
15553
|
+
clientKey: base.clientKey,
|
|
15554
|
+
versionId: '',
|
|
15555
|
+
status,
|
|
15556
|
+
progress,
|
|
15557
|
+
previewUrl: '',
|
|
15558
|
+
qrPayload: '',
|
|
15559
|
+
platformUrl: '',
|
|
15560
|
+
error: null,
|
|
15561
|
+
errorCode: null,
|
|
15562
|
+
platformWrite: false,
|
|
15563
|
+
...overrides,
|
|
15564
|
+
};
|
|
15565
|
+
}
|
|
15566
|
+
async function waitForAssetReady({ assetId, miniGameId, deadline, eventBase, onEvent, deps, }) {
|
|
15567
|
+
let lastPlatformStatusRaw = null;
|
|
15568
|
+
let hasEmittedPlatformStatus = false;
|
|
15569
|
+
while (deps.now() < deadline) {
|
|
15570
|
+
const response = await deps.fetchGameAssets({ miniGameId });
|
|
15571
|
+
if (response.error) {
|
|
15572
|
+
throw new Error(response.error?.StatusMessage ||
|
|
15573
|
+
response.error?.message ||
|
|
15574
|
+
t('upload.error.assetQueryFailed'));
|
|
15575
|
+
}
|
|
15576
|
+
const asset = extractAssets(response.data).find(item => String(item.asset_id || '') === assetId) || null;
|
|
15577
|
+
const platformStatusRaw = getAssetStatusRaw(asset);
|
|
15578
|
+
const platformStatus = normalizeUploadPlatformStatus(platformStatusRaw);
|
|
15579
|
+
if (!hasEmittedPlatformStatus ||
|
|
15580
|
+
platformStatusRaw !== lastPlatformStatusRaw) {
|
|
15581
|
+
hasEmittedPlatformStatus = true;
|
|
15582
|
+
lastPlatformStatusRaw = platformStatusRaw;
|
|
15583
|
+
onEvent(createEvent(eventBase, 'processing', 0.7, {
|
|
15584
|
+
versionId: assetId,
|
|
15585
|
+
platformStatus,
|
|
15586
|
+
...(platformStatusRaw === null ? {} : { platformStatusRaw }),
|
|
15587
|
+
platformWrite: true,
|
|
15588
|
+
}));
|
|
15589
|
+
}
|
|
15590
|
+
const assetState = getAssetState(asset);
|
|
15591
|
+
if (assetState === 'ready') {
|
|
15592
|
+
return asset;
|
|
15593
|
+
}
|
|
15594
|
+
if (assetState === 'failed') {
|
|
15595
|
+
throw new Error(t('upload.error.assetFailed', { status: platformStatus }));
|
|
15596
|
+
}
|
|
15597
|
+
await deps.sleep(deps.pollIntervalMs);
|
|
15598
|
+
}
|
|
15599
|
+
throw new Error(t('upload.error.assetTimeout'));
|
|
14924
15600
|
}
|
|
14925
|
-
function
|
|
14926
|
-
|
|
15601
|
+
async function waitForPreviewUrl({ assetId, deadline, deps, }) {
|
|
15602
|
+
while (deps.now() < deadline) {
|
|
15603
|
+
const response = await deps.fetchGameAssetPreviewUrl({ assetId });
|
|
15604
|
+
if (response.error) {
|
|
15605
|
+
throw new Error(response.error?.StatusMessage ||
|
|
15606
|
+
response.error?.message ||
|
|
15607
|
+
t('upload.error.previewQueryFailed'));
|
|
15608
|
+
}
|
|
15609
|
+
const previewUrl = extractPreviewUrl(response.data);
|
|
15610
|
+
if (previewUrl) {
|
|
15611
|
+
return previewUrl;
|
|
15612
|
+
}
|
|
15613
|
+
await deps.sleep(deps.pollIntervalMs);
|
|
15614
|
+
}
|
|
15615
|
+
throw new Error(t('upload.error.previewTimeout'));
|
|
14927
15616
|
}
|
|
14928
|
-
async function
|
|
15617
|
+
async function executeUpload(options, dependencies = {}) {
|
|
14929
15618
|
const deps = { ...defaultDependencies$5, ...dependencies };
|
|
14930
15619
|
const clientKey = options.clientKey?.trim();
|
|
14931
|
-
const
|
|
14932
|
-
|
|
14933
|
-
|
|
15620
|
+
const note = options.note || '--';
|
|
15621
|
+
const eventBase = { operationId: options.operationId, clientKey };
|
|
15622
|
+
const emit = options.onEvent;
|
|
15623
|
+
if (options.servePreviewPage && !options.preview) {
|
|
15624
|
+
throw new Error(t('upload.error.previewPageRequiresPreview'));
|
|
14934
15625
|
}
|
|
15626
|
+
emit(createEvent(eventBase, 'precheck', 0.05));
|
|
14935
15627
|
const user = deps.getCurrentUser();
|
|
14936
15628
|
if (!user || !user.cookie) {
|
|
14937
|
-
throw new
|
|
15629
|
+
throw new Error(t('upload.error.notLoggedIn'));
|
|
15630
|
+
}
|
|
15631
|
+
if (!clientKey) {
|
|
15632
|
+
throw new Error(t('upload.error.noClientKey'));
|
|
14938
15633
|
}
|
|
14939
15634
|
const infoResponse = await deps.fetchGameInfo(clientKey);
|
|
14940
15635
|
if (infoResponse.error) {
|
|
14941
|
-
throw new
|
|
15636
|
+
throw new Error(infoResponse.error.errorMsg || t('upload.error.gameInfoFailed'));
|
|
14942
15637
|
}
|
|
14943
15638
|
const appId = infoResponse.data?.app_id || '';
|
|
14944
|
-
if (!appId) {
|
|
14945
|
-
throw new TestAccountCommandError('PROJECT_RESPONSE_INVALID', t('testAccount.error.noAppId'));
|
|
14946
|
-
}
|
|
14947
15639
|
const projectName = infoResponse.data?.name || 'game';
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
const qrFile = path.resolve(process.cwd(), options.qrOutput || path.join('.ttmg', `test-account-${safeClientKey}.png`));
|
|
14951
|
-
try {
|
|
14952
|
-
fs.mkdirSync(path.dirname(qrFile), { recursive: true });
|
|
14953
|
-
await deps.writeQrFile(qrFile, authorizationUrl);
|
|
15640
|
+
if (!appId) {
|
|
15641
|
+
throw new Error(t('upload.error.noAppId'));
|
|
14954
15642
|
}
|
|
14955
|
-
|
|
14956
|
-
|
|
15643
|
+
const targetDir = options.dir
|
|
15644
|
+
? path.resolve(process.cwd(), options.dir)
|
|
15645
|
+
: process.cwd();
|
|
15646
|
+
if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
|
|
15647
|
+
throw new Error(t('upload.error.dirNotExist', { dir: targetDir }));
|
|
14957
15648
|
}
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14963
|
-
|
|
15649
|
+
emit(createEvent(eventBase, 'packaging', 0.2, { appId, projectName }));
|
|
15650
|
+
const zipBuffer = await deps.zipDirectory(targetDir);
|
|
15651
|
+
const packageSizeBytes = zipBuffer.length;
|
|
15652
|
+
emit(createEvent(eventBase, 'uploading', 0.4, {
|
|
15653
|
+
appId,
|
|
15654
|
+
projectName,
|
|
15655
|
+
packageSizeBytes,
|
|
15656
|
+
}));
|
|
15657
|
+
const uploadResponse = await deps.uploadGameToPlatform({
|
|
15658
|
+
data: zipBuffer.buffer.slice(zipBuffer.byteOffset, zipBuffer.byteOffset + zipBuffer.byteLength),
|
|
15659
|
+
name: projectName,
|
|
14964
15660
|
clientKey,
|
|
15661
|
+
note,
|
|
15662
|
+
appId,
|
|
15663
|
+
});
|
|
15664
|
+
if (uploadResponse.error) {
|
|
15665
|
+
throw new Error(uploadResponse.error.message || t('upload.error.platformUploadFailed'));
|
|
15666
|
+
}
|
|
15667
|
+
const assetId = String(uploadResponse.data?.asset_id || '');
|
|
15668
|
+
const platformUrl = uploadResponse.data?.detail || '';
|
|
15669
|
+
if (!assetId) {
|
|
15670
|
+
throw new Error(t('upload.error.missingAssetId'));
|
|
15671
|
+
}
|
|
15672
|
+
if (!options.preview) {
|
|
15673
|
+
const event = createEvent(eventBase, 'ready', 1, {
|
|
15674
|
+
appId,
|
|
15675
|
+
projectName,
|
|
15676
|
+
packageSizeBytes,
|
|
15677
|
+
versionId: assetId,
|
|
15678
|
+
platformUrl,
|
|
15679
|
+
platformWrite: true,
|
|
15680
|
+
});
|
|
15681
|
+
emit(event);
|
|
15682
|
+
return event;
|
|
15683
|
+
}
|
|
15684
|
+
const deadline = deps.now() + deps.pollTimeoutMs;
|
|
15685
|
+
await waitForAssetReady({
|
|
15686
|
+
assetId,
|
|
15687
|
+
miniGameId: appId,
|
|
15688
|
+
deadline,
|
|
15689
|
+
eventBase,
|
|
15690
|
+
onEvent: emit,
|
|
15691
|
+
deps,
|
|
15692
|
+
});
|
|
15693
|
+
emit(createEvent(eventBase, 'processing', 0.85, {
|
|
14965
15694
|
appId,
|
|
14966
15695
|
projectName,
|
|
14967
|
-
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
platformWrite:
|
|
14972
|
-
|
|
14973
|
-
|
|
14974
|
-
|
|
14975
|
-
|
|
15696
|
+
packageSizeBytes,
|
|
15697
|
+
versionId: assetId,
|
|
15698
|
+
platformStatus: 'resolving_preview',
|
|
15699
|
+
platformUrl,
|
|
15700
|
+
platformWrite: true,
|
|
15701
|
+
}));
|
|
15702
|
+
const previewUrl = await waitForPreviewUrl({ assetId, deadline, deps });
|
|
15703
|
+
let previewPage;
|
|
15704
|
+
if (options.servePreviewPage) {
|
|
15705
|
+
try {
|
|
15706
|
+
previewPage = await deps.startPreviewPageServer({
|
|
15707
|
+
authorizationUrl: buildTestAccountAuthorizationUrl(clientKey),
|
|
15708
|
+
previewUrl,
|
|
15709
|
+
clientKey,
|
|
15710
|
+
projectName,
|
|
15711
|
+
versionId: assetId,
|
|
15712
|
+
});
|
|
15713
|
+
}
|
|
15714
|
+
catch (error) {
|
|
15715
|
+
previewPage = {
|
|
15716
|
+
status: 'failed',
|
|
15717
|
+
contentType: 'text/html; charset=utf-8',
|
|
15718
|
+
errorCode: 'PREVIEW_PAGE_SERVER_FAILED',
|
|
15719
|
+
error: error?.message || String(error),
|
|
15720
|
+
};
|
|
15721
|
+
}
|
|
15722
|
+
}
|
|
15723
|
+
const event = createEvent(eventBase, 'ready', 1, {
|
|
15724
|
+
appId,
|
|
15725
|
+
projectName,
|
|
15726
|
+
packageSizeBytes,
|
|
15727
|
+
versionId: assetId,
|
|
15728
|
+
previewUrl,
|
|
15729
|
+
qrPayload: previewUrl,
|
|
15730
|
+
...(previewPage ? { previewPage } : {}),
|
|
15731
|
+
platformUrl,
|
|
15732
|
+
platformStatus: 'ready',
|
|
15733
|
+
platformWrite: true,
|
|
15734
|
+
});
|
|
15735
|
+
emit(event);
|
|
15736
|
+
return event;
|
|
14976
15737
|
}
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
|
|
14980
|
-
|
|
14981
|
-
|
|
14982
|
-
|
|
14983
|
-
|
|
14984
|
-
|
|
14985
|
-
|
|
15738
|
+
function getHumanEventText(event) {
|
|
15739
|
+
if (event.status === 'precheck') {
|
|
15740
|
+
return t('upload.spinner.fetchInfo', { clientKey: event.clientKey });
|
|
15741
|
+
}
|
|
15742
|
+
if (event.status === 'packaging') {
|
|
15743
|
+
return t('upload.spinner.zipping', { dir: '' });
|
|
15744
|
+
}
|
|
15745
|
+
if (event.status === 'uploading') {
|
|
15746
|
+
return t('upload.spinner.uploading', {
|
|
15747
|
+
size: ((event.packageSizeBytes || 0) / 1024 / 1024).toFixed(2),
|
|
15748
|
+
});
|
|
15749
|
+
}
|
|
15750
|
+
if (event.status === 'processing') {
|
|
15751
|
+
return t('upload.spinner.processing', {
|
|
15752
|
+
status: event.platformStatus || 'pending',
|
|
14986
15753
|
});
|
|
15754
|
+
}
|
|
15755
|
+
return '';
|
|
15756
|
+
}
|
|
15757
|
+
function getPreviewPageConsoleText(page) {
|
|
15758
|
+
return [
|
|
15759
|
+
'',
|
|
15760
|
+
t('upload.previewPageHeading'),
|
|
15761
|
+
` ${t('upload.previewPageOpenInBrowser')} ${page.url}`,
|
|
15762
|
+
` ${t('upload.previewPageAccessScope')} ${t('upload.previewPageSameMachine')}`,
|
|
15763
|
+
` ${t('upload.previewPageTerminalMode')} ${t('upload.previewPageClickableLink')}`,
|
|
15764
|
+
` ${t('upload.previewPageExpiresAt')} ${page.expiresAt}`,
|
|
15765
|
+
].join('\n');
|
|
15766
|
+
}
|
|
15767
|
+
function shouldOpenPreviewPage(format, options, page) {
|
|
15768
|
+
return (format === 'text' && options.open !== false && page?.status === 'ready');
|
|
15769
|
+
}
|
|
15770
|
+
async function upload(options, runtime = defaultUploadRuntime) {
|
|
15771
|
+
const format = normalizeOutputFormat(options.format);
|
|
15772
|
+
const operationId = `upload_${crypto.randomUUID()}`;
|
|
15773
|
+
const ora = await import('ora');
|
|
15774
|
+
const spinner = format === 'text'
|
|
15775
|
+
? ora
|
|
15776
|
+
.default({ spinner: 'dots', text: t('upload.spinner.start') })
|
|
15777
|
+
.start()
|
|
15778
|
+
: null;
|
|
15779
|
+
let lastEvent = null;
|
|
15780
|
+
const onEvent = (event) => {
|
|
15781
|
+
lastEvent = event;
|
|
15782
|
+
if (format === 'ndjson') {
|
|
15783
|
+
console.log(JSON.stringify(event));
|
|
15784
|
+
return;
|
|
15785
|
+
}
|
|
15786
|
+
const nextText = getHumanEventText(event);
|
|
15787
|
+
if (nextText && spinner) {
|
|
15788
|
+
spinner.text = chalk.cyan.bold(nextText);
|
|
15789
|
+
}
|
|
15790
|
+
};
|
|
15791
|
+
try {
|
|
15792
|
+
const result = await executeUpload({ ...options, operationId, onEvent });
|
|
14987
15793
|
if (format === 'json') {
|
|
14988
15794
|
console.log(JSON.stringify(result));
|
|
14989
15795
|
}
|
|
14990
|
-
else {
|
|
14991
|
-
|
|
14992
|
-
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
|
|
14997
|
-
|
|
14998
|
-
|
|
15796
|
+
else if (format === 'text' && spinner) {
|
|
15797
|
+
if (result.previewUrl) {
|
|
15798
|
+
spinner.succeed(chalk.green.bold(t('upload.previewSuccess', { url: result.previewUrl })));
|
|
15799
|
+
if (result.previewPage?.status === 'ready') {
|
|
15800
|
+
console.log(getPreviewPageConsoleText(result.previewPage));
|
|
15801
|
+
if (shouldOpenPreviewPage(format, options, result.previewPage)) {
|
|
15802
|
+
console.log(t('upload.previewPageOpening'));
|
|
15803
|
+
try {
|
|
15804
|
+
await runtime.openUrl(result.previewPage.url);
|
|
15805
|
+
}
|
|
15806
|
+
catch {
|
|
15807
|
+
console.error(chalk.yellow(t('upload.previewPageOpenFailed')));
|
|
15808
|
+
}
|
|
15809
|
+
}
|
|
15810
|
+
}
|
|
15811
|
+
else if (result.previewPage?.status === 'failed') {
|
|
15812
|
+
console.error(chalk.yellow(t('upload.previewPageFailed', {
|
|
15813
|
+
error: result.previewPage.error,
|
|
15814
|
+
})));
|
|
15815
|
+
}
|
|
15816
|
+
}
|
|
15817
|
+
else {
|
|
15818
|
+
spinner.succeed(chalk.green.bold(t('upload.success', { url: result.platformUrl })));
|
|
15819
|
+
}
|
|
14999
15820
|
}
|
|
15000
15821
|
return result;
|
|
15001
15822
|
}
|
|
15002
15823
|
catch (error) {
|
|
15003
15824
|
const message = error?.message || String(error);
|
|
15004
|
-
const
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
authorizationUrl: '',
|
|
15015
|
-
qrPayload: '',
|
|
15016
|
-
qrFile: '',
|
|
15017
|
-
portalUrl: '',
|
|
15018
|
-
platformWrite: false,
|
|
15019
|
-
verification: 'developer_portal',
|
|
15020
|
-
errorCode,
|
|
15825
|
+
const failedEvent = createEvent({ operationId, clientKey: options.clientKey || '' }, 'failed', lastEvent?.progress || 0, {
|
|
15826
|
+
appId: lastEvent?.appId,
|
|
15827
|
+
projectName: lastEvent?.projectName,
|
|
15828
|
+
packageSizeBytes: lastEvent?.packageSizeBytes,
|
|
15829
|
+
versionId: lastEvent?.versionId || '',
|
|
15830
|
+
platformUrl: lastEvent?.platformUrl || '',
|
|
15831
|
+
platformStatus: lastEvent?.platformStatus,
|
|
15832
|
+
platformStatusRaw: lastEvent?.platformStatusRaw,
|
|
15833
|
+
platformWrite: lastEvent?.platformWrite || false,
|
|
15834
|
+
errorCode: 'UPLOAD_FAILED',
|
|
15021
15835
|
error: message,
|
|
15022
|
-
};
|
|
15023
|
-
if (format === 'json') {
|
|
15024
|
-
console.log(JSON.stringify(
|
|
15836
|
+
});
|
|
15837
|
+
if (format === 'json' || format === 'ndjson') {
|
|
15838
|
+
console.log(JSON.stringify(failedEvent));
|
|
15025
15839
|
}
|
|
15026
15840
|
else {
|
|
15027
|
-
|
|
15841
|
+
spinner?.fail(chalk.red.bold(t('upload.fail', { error: message })));
|
|
15028
15842
|
}
|
|
15029
15843
|
process.exitCode = 1;
|
|
15030
|
-
return
|
|
15844
|
+
return failedEvent;
|
|
15031
15845
|
}
|
|
15032
15846
|
}
|
|
15033
15847
|
|
|
@@ -15122,7 +15936,7 @@ async function authStatus(options = {}) {
|
|
|
15122
15936
|
return result;
|
|
15123
15937
|
}
|
|
15124
15938
|
|
|
15125
|
-
var version = "0.4.6-vibe-beta.
|
|
15939
|
+
var version = "0.4.6-vibe-beta.4";
|
|
15126
15940
|
var engines = {
|
|
15127
15941
|
node: ">=20.19.1 <21 || >=22"
|
|
15128
15942
|
};
|
|
@@ -15192,6 +16006,31 @@ function getCapabilities() {
|
|
|
15192
16006
|
command: 'ttmg upload --preview --format ndjson',
|
|
15193
16007
|
available: true,
|
|
15194
16008
|
platformWrite: true,
|
|
16009
|
+
previewPage: {
|
|
16010
|
+
option: '--serve-preview-page',
|
|
16011
|
+
command: 'ttmg upload --preview --serve-preview-page --format ndjson',
|
|
16012
|
+
available: true,
|
|
16013
|
+
localhostOnly: true,
|
|
16014
|
+
inMemory: true,
|
|
16015
|
+
reuseScope: 'clientKey',
|
|
16016
|
+
latestOnly: true,
|
|
16017
|
+
renewsTtlOnUpload: true,
|
|
16018
|
+
textModeAutoOpen: true,
|
|
16019
|
+
structuredModeAutoOpen: false,
|
|
16020
|
+
noOpenOption: '--no-open',
|
|
16021
|
+
presentation: {
|
|
16022
|
+
audience: 'user',
|
|
16023
|
+
preferredSurface: 'browser',
|
|
16024
|
+
preferredAction: 'open_url',
|
|
16025
|
+
terminalInline: false,
|
|
16026
|
+
fallback: 'show_clickable_link',
|
|
16027
|
+
sameHostRequired: true,
|
|
16028
|
+
},
|
|
16029
|
+
includesTestAccountAuthorization: true,
|
|
16030
|
+
includesPreviewQr: true,
|
|
16031
|
+
ttlSeconds: 3600,
|
|
16032
|
+
expiredNoticeTtlSeconds: 3600,
|
|
16033
|
+
},
|
|
15195
16034
|
},
|
|
15196
16035
|
},
|
|
15197
16036
|
dev: {
|
|
@@ -15616,6 +16455,7 @@ function getDesktopPath() {
|
|
|
15616
16455
|
}
|
|
15617
16456
|
|
|
15618
16457
|
const program = new commander.Command();
|
|
16458
|
+
const isPreviewPageServerWorker = process.argv[2] === '__preview-page-server';
|
|
15619
16459
|
const withVerboseOption = (command) => command.option('--verbose', t('cli.option.verbose'));
|
|
15620
16460
|
const structuredFormatIndex = process.argv.indexOf('--format');
|
|
15621
16461
|
const inlineStructuredFormat = process.argv.find(arg => arg.startsWith('--format='));
|
|
@@ -15624,7 +16464,8 @@ const structuredFormat = inlineStructuredFormat
|
|
|
15624
16464
|
: structuredFormatIndex >= 0
|
|
15625
16465
|
? process.argv[structuredFormatIndex + 1]
|
|
15626
16466
|
: '';
|
|
15627
|
-
if (
|
|
16467
|
+
if (!isPreviewPageServerWorker &&
|
|
16468
|
+
structuredFormat !== 'json' &&
|
|
15628
16469
|
structuredFormat !== 'ndjson' &&
|
|
15629
16470
|
!process.argv.includes('--implicit')) {
|
|
15630
16471
|
maybeShowPostInstallNotice(pkg.version);
|
|
@@ -15675,12 +16516,16 @@ withVerboseOption(program
|
|
|
15675
16516
|
.option('-n, --note <note>', t('cli.command.upload.note'))
|
|
15676
16517
|
.option('-d, --dir <dir>', t('cli.command.upload.dir'))
|
|
15677
16518
|
.option('--preview', t('cli.command.upload.preview'))
|
|
16519
|
+
.option('--serve-preview-page', t('cli.command.upload.servePreviewPage'))
|
|
16520
|
+
.option('--no-open', t('cli.command.upload.noOpen'))
|
|
15678
16521
|
.option('--format <format>', t('cli.command.upload.format'), 'text')).action(async (cmd) => {
|
|
15679
16522
|
await upload({
|
|
15680
16523
|
clientKey: cmd.clientKey,
|
|
15681
16524
|
note: cmd.note,
|
|
15682
16525
|
dir: cmd.dir,
|
|
15683
16526
|
preview: cmd.preview,
|
|
16527
|
+
servePreviewPage: cmd.servePreviewPage,
|
|
16528
|
+
open: cmd.open,
|
|
15684
16529
|
format: cmd.format,
|
|
15685
16530
|
});
|
|
15686
16531
|
});
|
|
@@ -15892,7 +16737,12 @@ withVerboseOption(program
|
|
|
15892
16737
|
console.log(t('cli.native.build.placeholder'));
|
|
15893
16738
|
}
|
|
15894
16739
|
});
|
|
15895
|
-
|
|
16740
|
+
if (isPreviewPageServerWorker) {
|
|
16741
|
+
runPreviewPageServerWorker();
|
|
16742
|
+
}
|
|
16743
|
+
else {
|
|
16744
|
+
program.parse(process.argv);
|
|
16745
|
+
}
|
|
15896
16746
|
|
|
15897
16747
|
/**
|
|
15898
16748
|
* 一个类型安全的、通用的事件发射器类。
|