@ttmg/cli 0.4.6-vibe-beta.15 → 0.4.6-vibe-beta.17
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 +14 -40
- package/dist/index.js +270 -136
- package/dist/index.js.map +1 -1
- package/dist/package.json +2 -2
- package/dist/scripts/verify-agent-contract.js +3 -2
- package/dist/scripts/vibe-ppe-test-preload.cjs +1 -1
- package/dist/scripts/vibe-upload-smoke.js +53 -25
- package/package.json +2 -2
- package/vibe-upload.md +95 -272
package/dist/index.js
CHANGED
|
@@ -7241,8 +7241,8 @@ const messages = {
|
|
|
7241
7241
|
'cli.command.login.password': 'Developer account password (implicit mode)',
|
|
7242
7242
|
'cli.command.logout.desc': 'Logout and clear local user data',
|
|
7243
7243
|
'cli.command.auth.desc': 'Inspect local authentication state',
|
|
7244
|
-
'cli.command.auth.open.desc': '
|
|
7245
|
-
'cli.command.upload.vibeWeb': '
|
|
7244
|
+
'cli.command.auth.open.desc': 'Reopen the local QR page for an existing Vibe task',
|
|
7245
|
+
'cli.command.upload.vibeWeb': 'Disabled; use --vibe and scan with TikTok on your phone',
|
|
7246
7246
|
'cli.command.game.desc': 'Inspect game metadata supported by the platform',
|
|
7247
7247
|
'cli.command.game.categories.desc': 'List game categories and subcategories with gameplay descriptions (offline, read-only)',
|
|
7248
7248
|
'cli.command.game.categories.category': 'Filter by an exact category value, e.g. Puzzle',
|
|
@@ -7252,23 +7252,26 @@ const messages = {
|
|
|
7252
7252
|
'cli.command.upload.vibeDescription': 'Optional game description (--vibe); default: Discover the fun of TikTok Mini Games.',
|
|
7253
7253
|
'cli.command.upload.vibeSubcategory': 'Optional subcategory belonging to --category; omit both to keep Other/Other (--vibe)',
|
|
7254
7254
|
'cli.command.upload.vibeWebGuide': `
|
|
7255
|
-
Vibe
|
|
7256
|
-
ttmg upload --vibe --
|
|
7255
|
+
Vibe QR upload (real PPE trial):
|
|
7256
|
+
ttmg upload --vibe --dir ./game --title "My game" --icon ./avatar.png --format ndjson
|
|
7257
7257
|
Use --package ./game.zip instead of --dir for an existing ZIP.
|
|
7258
7258
|
Collect only title and icon; the CLI fills other app_info fields with PPE trial defaults.
|
|
7259
7259
|
Skill may pass --description "Game description"; otherwise use "Discover the fun of TikTok Mini Games." (legacy metadata-file descriptions are preserved).
|
|
7260
7260
|
Skill may query ttmg game categories --format json and pass --category Puzzle --subcategory Physics together.
|
|
7261
7261
|
Omit both to keep Other/Other (or the legacy metadata-file classification). Invalid pairs fail before task creation.
|
|
7262
7262
|
prepared and terminal events expose the submitted classification and its source.
|
|
7263
|
-
Keep this process running. At waiting_authorization,
|
|
7264
|
-
pageUrl is the local
|
|
7263
|
+
Keep this process running. At waiting_authorization, scan the QR on pageUrl with TikTok on your phone.
|
|
7264
|
+
pageUrl is the local QR/progress page on the CLI computer. Reopen it with authorizationOpenCommand.
|
|
7265
7265
|
The CLI polls and uploads avatar, app_info and package automatically after authorization. Do not start a second upload.
|
|
7266
|
+
After upload, waiting_preview keeps polling the same task until previewUrl is available; pageUrl then shows the game preview QR.
|
|
7267
|
+
Preview ready: previewReady=true and qrPurpose=game_preview. A preview timeout preserves uploaded=true; check the task, do not reupload.
|
|
7266
7268
|
Real success: terminal=true, status=uploaded, uploaded=true, simulation=false. On failure, read errorCode and nextAction.
|
|
7267
|
-
|
|
7269
|
+
The local QR page opens for every output format; --no-open disables automatic opening.
|
|
7270
|
+
The page displays the submitted title, icon, description, category and subcategory.
|
|
7268
7271
|
Upload completion is not review approval or production release. See bundled vibe-upload.md and ttmg capabilities --format json.
|
|
7269
7272
|
`,
|
|
7270
7273
|
'cli.command.auth.open.pageUrl': 'Local pageUrl returned by the running Vibe upload command',
|
|
7271
|
-
'cli.command.auth.open.requested': '
|
|
7274
|
+
'cli.command.auth.open.requested': 'QR page open requested. Scan with TikTok on your phone; the existing upload process continues polling.',
|
|
7272
7275
|
'cli.command.auth.open.invalidPage': 'Pass the local pageUrl from the running Vibe upload command, not the platform URL.',
|
|
7273
7276
|
'cli.command.auth.open.unavailable': 'Authorization page is unavailable, expired, or no longer waiting. Check the existing upload process; no new task was created.',
|
|
7274
7277
|
'cli.command.auth.open.invalidFormat': 'Use text, json, or ndjson output.',
|
|
@@ -7467,9 +7470,9 @@ Vibe web upload (real PPE trial):
|
|
|
7467
7470
|
'cli.command.upload.dir': 'Game project directory (default: current directory)',
|
|
7468
7471
|
'cli.command.upload.preview': 'Wait for platform processing and output the preview payload',
|
|
7469
7472
|
'cli.command.upload.servePreviewPage': 'Serve a temporary local preview page with test-account and preview QR codes',
|
|
7470
|
-
'cli.command.upload.noOpen': 'Do not open the local preview
|
|
7473
|
+
'cli.command.upload.noOpen': 'Do not automatically open the local preview or Vibe QR page',
|
|
7471
7474
|
'cli.command.upload.format': 'Output format: text | json | ndjson',
|
|
7472
|
-
'cli.command.upload.vibe': '
|
|
7475
|
+
'cli.command.upload.vibe': 'Scan with TikTok and upload: provide title and icon; CLI fills other fields with PPE trial defaults (schema v2)',
|
|
7473
7476
|
'cli.command.upload.vibeTitle': 'Game title supplied by the Skill (--vibe)',
|
|
7474
7477
|
'cli.command.upload.vibeAppInfo': 'Legacy metadata file compatibility; not needed for current uploads and must not be requested from users',
|
|
7475
7478
|
'cli.command.upload.vibeIcon': 'Local PNG/JPEG/WebP selected by the user (--vibe)',
|
|
@@ -7688,8 +7691,8 @@ Vibe web upload (real PPE trial):
|
|
|
7688
7691
|
'cli.command.login.password': '开发者账号密码(implicit 模式)',
|
|
7689
7692
|
'cli.command.logout.desc': '退出登录并清除本地用户信息',
|
|
7690
7693
|
'cli.command.auth.desc': '查看本地登录状态',
|
|
7691
|
-
'cli.command.auth.open.desc': '
|
|
7692
|
-
'cli.command.upload.vibeWeb': '
|
|
7694
|
+
'cli.command.auth.open.desc': '重新打开已有 Vibe 任务的本地二维码页',
|
|
7695
|
+
'cli.command.upload.vibeWeb': '已关闭;使用 --vibe 并在手机 TikTok 中扫码授权',
|
|
7693
7696
|
'cli.command.game.desc': '查询平台支持的游戏资料',
|
|
7694
7697
|
'cli.command.game.categories.desc': '查询游戏类型、子类型及玩法说明(离线、只读)',
|
|
7695
7698
|
'cli.command.game.categories.category': '按类型的精确值筛选,例如 Puzzle',
|
|
@@ -7699,23 +7702,26 @@ Vibe web upload (real PPE trial):
|
|
|
7699
7702
|
'cli.command.upload.vibeDescription': '可选游戏描述(--vibe);默认值:Discover the fun of TikTok Mini Games.',
|
|
7700
7703
|
'cli.command.upload.vibeSubcategory': '所属类型的可选子类型;两者均不传保留 Other/Other(--vibe)',
|
|
7701
7704
|
'cli.command.upload.vibeWebGuide': `
|
|
7702
|
-
Vibe
|
|
7703
|
-
ttmg upload --vibe --
|
|
7705
|
+
Vibe 扫码上传(真实 PPE 试用):
|
|
7706
|
+
ttmg upload --vibe --dir ./game --title "游戏名称" --icon ./avatar.png --format ndjson
|
|
7704
7707
|
已有 ZIP 时,用 --package ./game.zip 替换 --dir。
|
|
7705
7708
|
只收集标题和头像;其他 app_info 字段由 CLI 使用 PPE 试用默认值补齐。
|
|
7706
7709
|
Skill 可传 --description "游戏描述";省略时使用 Discover the fun of TikTok Mini Games.(旧资料文件中的描述保留)。
|
|
7707
7710
|
Skill 可先查 ttmg game categories --format json,再成对传入 --category Puzzle --subcategory Physics。
|
|
7708
7711
|
两者均不传保留 Other/Other(旧资料文件调用保留文件分类);无效组合在创建任务前报错。
|
|
7709
7712
|
prepared 和终态事件返回最终 classification 及其来源。
|
|
7710
|
-
保持原进程运行。在 waiting_authorization
|
|
7711
|
-
pageUrl
|
|
7713
|
+
保持原进程运行。在 waiting_authorization 阶段,使用手机 TikTok 扫描 pageUrl 页面中的二维码。
|
|
7714
|
+
pageUrl 是 CLI 所在电脑的二维码/进度页;重新打开时,执行返回的 authorizationOpenCommand。
|
|
7712
7715
|
CLI 负责轮询,授权后自动上传 avatar、app_info、package,不再启动第二次上传。
|
|
7716
|
+
上传后进入 waiting_preview,继续查询同一任务;拿到 previewUrl 后,原 pageUrl 页面展示游戏预览二维码。
|
|
7717
|
+
previewReady=true 且 qrPurpose=game_preview 表示可扫码预览;等待超时仍保留 uploaded=true,请核对任务,不要重传。
|
|
7713
7718
|
真实成功:terminal=true、status=uploaded、uploaded=true、simulation=false。失败时读取 errorCode 和 nextAction。
|
|
7714
|
-
|
|
7719
|
+
所有输出格式都会打开本地二维码页;--no-open 可关闭自动打开。
|
|
7720
|
+
页面展示本次提交的标题、头像、描述、类型和子类型。
|
|
7715
7721
|
上传完成不代表审核通过或正式上线。详见包内 vibe-upload.md 和 ttmg capabilities --format json。
|
|
7716
7722
|
`,
|
|
7717
7723
|
'cli.command.auth.open.pageUrl': '正在运行的 Vibe 上传命令返回的本地 pageUrl',
|
|
7718
|
-
'cli.command.auth.open.requested': '
|
|
7724
|
+
'cli.command.auth.open.requested': '已请求打开二维码页。请用手机 TikTok 扫码授权,原上传进程继续轮询。',
|
|
7719
7725
|
'cli.command.auth.open.invalidPage': '请传入正在运行的 Vibe 上传命令返回的本地 pageUrl,不是平台授权链接。',
|
|
7720
7726
|
'cli.command.auth.open.unavailable': '授权页不可用、已过期或已不在等待授权。请查看原上传进程状态;未新建任务。',
|
|
7721
7727
|
'cli.command.auth.open.invalidFormat': '输出格式请使用 text、json 或 ndjson。',
|
|
@@ -7914,9 +7920,9 @@ Vibe 网页上传(真实 PPE 试用):
|
|
|
7914
7920
|
'cli.command.upload.dir': '小游戏项目根目录(默认为当前目录)',
|
|
7915
7921
|
'cli.command.upload.preview': '等待平台处理并输出预览二维码原始内容',
|
|
7916
7922
|
'cli.command.upload.servePreviewPage': '启动包含测试账号与预览二维码的临时本地页面',
|
|
7917
|
-
'cli.command.upload.noOpen': '
|
|
7923
|
+
'cli.command.upload.noOpen': '不自动打开本地预览页面或 Vibe 二维码页',
|
|
7918
7924
|
'cli.command.upload.format': '输出格式:text | json | ndjson',
|
|
7919
|
-
'cli.command.upload.vibe': '
|
|
7925
|
+
'cli.command.upload.vibe': 'TikTok 扫码上传:只收集标题和头像,CLI 自动补齐其他字段的 PPE 试用默认值(schema v2)',
|
|
7920
7926
|
'cli.command.upload.vibeAppInfo': '仅兼容旧资料文件;当前上传无需此参数,不应向用户索要',
|
|
7921
7927
|
'cli.command.upload.vibeTitle': 'Skill 提供的游戏标题(--vibe)',
|
|
7922
7928
|
'cli.command.upload.vibeIcon': '用户指定的本地 PNG/JPEG/WebP 图片(--vibe)',
|
|
@@ -16376,7 +16382,7 @@ async function authStatus(options = {}) {
|
|
|
16376
16382
|
return result;
|
|
16377
16383
|
}
|
|
16378
16384
|
|
|
16379
|
-
var version = "0.4.6-vibe-beta.
|
|
16385
|
+
var version = "0.4.6-vibe-beta.17";
|
|
16380
16386
|
var engines = {
|
|
16381
16387
|
node: ">=20.19.1 <21 || >=22"
|
|
16382
16388
|
};
|
|
@@ -17136,7 +17142,7 @@ function getCapabilities() {
|
|
|
17136
17142
|
networkRequired: false,
|
|
17137
17143
|
},
|
|
17138
17144
|
vibeUpload: {
|
|
17139
|
-
command: 'ttmg upload --vibe --
|
|
17145
|
+
command: 'ttmg upload --vibe --dir <gameDir> --title <title> --icon <iconPath> --format ndjson',
|
|
17140
17146
|
mockCommand: 'ttmg upload --vibe --mock --dir <gameDir> --title <title> --icon <iconPath> --format ndjson',
|
|
17141
17147
|
guide: 'vibe-upload.md',
|
|
17142
17148
|
option: '--vibe',
|
|
@@ -17159,7 +17165,7 @@ function getCapabilities() {
|
|
|
17159
17165
|
host: 'developers.tiktok.com',
|
|
17160
17166
|
taskCreation: true,
|
|
17161
17167
|
statusPolling: true,
|
|
17162
|
-
authorizationScheme: '
|
|
17168
|
+
authorizationScheme: 'landing_url',
|
|
17163
17169
|
uploadEnabled: true,
|
|
17164
17170
|
},
|
|
17165
17171
|
terminalSuccess: 'uploaded',
|
|
@@ -17186,7 +17192,27 @@ function getCapabilities() {
|
|
|
17186
17192
|
legacyAppInfo: 'preserved-when-options-omitted; explicit-pair-overrides-category-fields',
|
|
17187
17193
|
resultField: 'classification',
|
|
17188
17194
|
},
|
|
17189
|
-
webAuthorization: { option: '--web',
|
|
17195
|
+
webAuthorization: { option: '--web', available: false, errorCode: 'VIBE_WEB_DISABLED', replacement: '--vibe', materialUploadEnabled: false },
|
|
17196
|
+
qrAuthorization: {
|
|
17197
|
+
command: 'ttmg upload --vibe --dir <gameDir> --title <title> --icon <iconPath> --format ndjson',
|
|
17198
|
+
defaultWithoutWeb: true,
|
|
17199
|
+
backend: 'ppe',
|
|
17200
|
+
defaultLane: 'ppe_op_vc',
|
|
17201
|
+
requiresEnvironmentConfiguration: false,
|
|
17202
|
+
simulation: false,
|
|
17203
|
+
opensLocalQrPage: true,
|
|
17204
|
+
formatIndependent: true,
|
|
17205
|
+
disablePageOpenOption: '--no-open',
|
|
17206
|
+
pageUrlField: 'pageUrl',
|
|
17207
|
+
authorizationMode: 'qr',
|
|
17208
|
+
nextAction: 'scan_qr',
|
|
17209
|
+
userAction: 'Open pageUrl on the CLI computer and scan the authorization QR with TikTok on the phone',
|
|
17210
|
+
continuesPolling: true,
|
|
17211
|
+
materialUploadEnabled: true,
|
|
17212
|
+
phoneAuthorizationVerified: true,
|
|
17213
|
+
authorizationQrPayload: 'landing_url',
|
|
17214
|
+
pageMetadata: ['title', 'icon', 'description', 'category', 'subcategory'],
|
|
17215
|
+
},
|
|
17190
17216
|
skillWorkflow: {
|
|
17191
17217
|
preferredFormat: 'ndjson',
|
|
17192
17218
|
packageAlternative: '--package <zipPath> replaces --dir <gameDir>',
|
|
@@ -17195,7 +17221,7 @@ function getCapabilities() {
|
|
|
17195
17221
|
uploadOwner: 'cli',
|
|
17196
17222
|
waitingAuthorization: {
|
|
17197
17223
|
status: 'waiting_authorization',
|
|
17198
|
-
userAction: '
|
|
17224
|
+
userAction: 'Open pageUrl on the CLI computer and scan the QR with TikTok on the phone',
|
|
17199
17225
|
pageUrlField: 'pageUrl',
|
|
17200
17226
|
pageUrlPurpose: 'local binding and upload progress page',
|
|
17201
17227
|
reopenCommandField: 'authorizationOpenCommand',
|
|
@@ -17242,10 +17268,21 @@ function getCapabilities() {
|
|
|
17242
17268
|
provisionalHttpAdapter: 'loopback-only',
|
|
17243
17269
|
httpContract: 'minis-import-task',
|
|
17244
17270
|
authorizationQr: true,
|
|
17245
|
-
previewQr:
|
|
17271
|
+
previewQr: true,
|
|
17272
|
+
preview: {
|
|
17273
|
+
taskEndpoint: 'get_minis_import_task_detail_for_agent',
|
|
17274
|
+
waitingStatus: 'waiting_preview',
|
|
17275
|
+
resultFields: ['previewUrl', 'previewReady', 'clientKey', 'appId', 'codeAssetId', 'minisCenterHistoryId'],
|
|
17276
|
+
readiness: 'nonempty-valid-preview-url',
|
|
17277
|
+
qrPurpose: 'game_preview',
|
|
17278
|
+
sameTask: true,
|
|
17279
|
+
timeout: 'shared-operation-timeout',
|
|
17280
|
+
failurePreservesUploaded: true,
|
|
17281
|
+
deviceRunVerified: false,
|
|
17282
|
+
},
|
|
17246
17283
|
realAuthorizationVerified: true,
|
|
17247
17284
|
realUploadVerified: true,
|
|
17248
|
-
verificationScope: { backend: 'ppe', assets: 'test-fixtures', date: '2026-09-
|
|
17285
|
+
verificationScope: { backend: 'ppe', authorizationMode: 'qr', assets: 'test-fixtures', date: '2026-09-16', production: false },
|
|
17249
17286
|
autoUploadAfterAuthorization: true,
|
|
17250
17287
|
boundedPolling: true,
|
|
17251
17288
|
cancellation: true,
|
|
@@ -17263,7 +17300,7 @@ function getCapabilities() {
|
|
|
17263
17300
|
requiresRunningUpload: true,
|
|
17264
17301
|
createsTask: false,
|
|
17265
17302
|
outputContainsLandingCredentials: false,
|
|
17266
|
-
target: '
|
|
17303
|
+
target: 'local-qr-page',
|
|
17267
17304
|
successStatus: 'open_requested',
|
|
17268
17305
|
provesAuthorization: false,
|
|
17269
17306
|
},
|
|
@@ -18001,7 +18038,7 @@ withVerboseOption(program
|
|
|
18001
18038
|
.option('--serve-preview-page', t('cli.command.upload.servePreviewPage'))
|
|
18002
18039
|
.option('--no-open', t('cli.command.upload.noOpen'))
|
|
18003
18040
|
.option('--vibe', t('cli.command.upload.vibe'))
|
|
18004
|
-
.
|
|
18041
|
+
.addOption(new commander.Option('--web', t('cli.command.upload.vibeWeb')).hideHelp())
|
|
18005
18042
|
// Vibe v2 backend selection (including opt-in ppe-mock) stays outside the Skill workflow.
|
|
18006
18043
|
.option('--title <title>', t('cli.command.upload.vibeTitle'))
|
|
18007
18044
|
.option('--description <description>', t('cli.command.upload.vibeDescription'))
|
|
@@ -18555,7 +18592,7 @@ async function executeVibeAuthOpen(options, deps = { open: openVibeBrowser }) {
|
|
|
18555
18592
|
const url = authorizationOpenUrl(options.pageUrl);
|
|
18556
18593
|
await checkAuthorizationPage(url);
|
|
18557
18594
|
try {
|
|
18558
|
-
await deps.open(
|
|
18595
|
+
await deps.open(options.pageUrl);
|
|
18559
18596
|
}
|
|
18560
18597
|
catch {
|
|
18561
18598
|
throw new VibeError('VIBE_BROWSER_OPEN_FAILED');
|
|
@@ -18596,7 +18633,7 @@ var authOpen = /*#__PURE__*/Object.freeze({
|
|
|
18596
18633
|
const prefix = '/bff/app_info/minis_import/';
|
|
18597
18634
|
const MINIS_IMPORT_PATHS = {
|
|
18598
18635
|
create: `${prefix}create_minis_import_task_for_agent/`,
|
|
18599
|
-
status: `${prefix}
|
|
18636
|
+
status: `${prefix}get_minis_import_task_detail_for_agent/`,
|
|
18600
18637
|
icon: `${prefix}upload_minis_import_app_icon_for_agent/`,
|
|
18601
18638
|
info: `${prefix}submit_minis_import_info_for_agent/`,
|
|
18602
18639
|
package: `${prefix}upload_minis_import_package_for_agent/`,
|
|
@@ -18655,6 +18692,31 @@ function createBackend(mode, transport) {
|
|
|
18655
18692
|
});
|
|
18656
18693
|
return {
|
|
18657
18694
|
mode,
|
|
18695
|
+
async getTaskDetail(task, signal) {
|
|
18696
|
+
const data = await call({ method: 'GET', path: MINIS_IMPORT_PATHS.status,
|
|
18697
|
+
data: credentials(task), signal });
|
|
18698
|
+
const raw = status(data.status);
|
|
18699
|
+
if (raw === 8)
|
|
18700
|
+
throw new VibeError('VIBE_AUTH_EXPIRED');
|
|
18701
|
+
if (raw === 7)
|
|
18702
|
+
throw new VibeError('VIBE_TASK_FAILED');
|
|
18703
|
+
if (data.ready_for_input !== undefined && typeof data.ready_for_input !== 'boolean')
|
|
18704
|
+
throw new VibeError('VIBE_INVALID_RESPONSE');
|
|
18705
|
+
const detail = { statusRaw: raw, readyForInput: data.ready_for_input === true };
|
|
18706
|
+
for (const [wire, field] of [['app_id', 'appId'], ['code_asset_id', 'codeAssetId'],
|
|
18707
|
+
['minis_center_history_id', 'minisCenterHistoryId']]) {
|
|
18708
|
+
if (data[wire] !== undefined && data[wire] !== '')
|
|
18709
|
+
detail[field] = taskId(data[wire]);
|
|
18710
|
+
}
|
|
18711
|
+
if (data.client_key !== undefined && data.client_key !== '') {
|
|
18712
|
+
if (!nonempty(data.client_key))
|
|
18713
|
+
throw new VibeError('VIBE_INVALID_RESPONSE');
|
|
18714
|
+
detail.clientKey = data.client_key;
|
|
18715
|
+
}
|
|
18716
|
+
if (data.preview_url !== undefined && data.preview_url !== '')
|
|
18717
|
+
detail.previewUrl = safeWebUrl(data.preview_url);
|
|
18718
|
+
return detail;
|
|
18719
|
+
},
|
|
18658
18720
|
async createTask(input, signal) {
|
|
18659
18721
|
const data = await call({
|
|
18660
18722
|
method: 'POST',
|
|
@@ -18785,6 +18847,7 @@ function createPpeMockBackend(lane, adapter, scenario = 'success') {
|
|
|
18785
18847
|
};
|
|
18786
18848
|
return {
|
|
18787
18849
|
mode: 'ppe-mock',
|
|
18850
|
+
getTaskDetail: (task, signal) => real.getTaskDetail(task, signal),
|
|
18788
18851
|
uploadAvailable: true,
|
|
18789
18852
|
dispose: () => real.dispose?.(),
|
|
18790
18853
|
async createTask(input, signal) {
|
|
@@ -18970,8 +19033,9 @@ function createMockBackend(scenario = 'success') {
|
|
|
18970
19033
|
}
|
|
18971
19034
|
|
|
18972
19035
|
function createAuthorizationScheme(landingUrl) {
|
|
18973
|
-
//
|
|
18974
|
-
|
|
19036
|
+
// QR and browser authorization use the same backend-provided landing URL.
|
|
19037
|
+
// Preserve its query exactly; do not wrap it in an app scheme or re-encode it.
|
|
19038
|
+
return safeWebUrl(landingUrl);
|
|
18975
19039
|
}
|
|
18976
19040
|
|
|
18977
19041
|
const DEV_PORT = 3000;
|
|
@@ -19257,20 +19321,53 @@ async function prepareVibeInput(options, onPackaging, signal, deps = { check: ex
|
|
|
19257
19321
|
}
|
|
19258
19322
|
|
|
19259
19323
|
const vibePageStyles = `
|
|
19260
|
-
|
|
19261
|
-
:root{font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;color:#
|
|
19262
|
-
*{box-sizing:border-box}
|
|
19263
|
-
|
|
19264
|
-
.
|
|
19265
|
-
.
|
|
19266
|
-
.
|
|
19267
|
-
.
|
|
19268
|
-
.
|
|
19269
|
-
|
|
19324
|
+
|
|
19325
|
+
:root{font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;color:#24282f;background:#fff;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}
|
|
19326
|
+
*{box-sizing:border-box}
|
|
19327
|
+
body{margin:0;min-height:100vh;min-height:100svh;display:flex;align-items:center;justify-content:center;padding:64px 24px;font-size:14px;line-height:1.5}
|
|
19328
|
+
.shell{position:relative;width:100%;max-width:400px}
|
|
19329
|
+
.steps{display:flex;align-items:center;justify-content:center;gap:10px;margin:0 0 24px;padding:0;list-style:none}
|
|
19330
|
+
.step{display:flex;align-items:center;gap:6px;font-size:11px;color:#9298a2;white-space:nowrap}
|
|
19331
|
+
.step:after{content:"";height:1px;width:25px;margin-left:4px;background:#e9ebef}.step:last-child:after{display:none}
|
|
19332
|
+
.step-number{height:18px;min-width:18px;border-radius:50%;border:1px solid #e2e5e9;display:grid;place-items:center;font-size:10px}
|
|
19333
|
+
.step.active{color:#272c34;font-weight:600}.step.active .step-number{background:#272c34;color:#fff;border-color:#272c34}
|
|
19334
|
+
.step.done{color:#537565}.step.done .step-number{background:#edf6f0;color:#287c54;border-color:#edf6f0}
|
|
19335
|
+
.heading{text-align:center}h1{font-size:26px;font-weight:650;line-height:1.35;letter-spacing:-.6px;margin:0 0 10px}
|
|
19336
|
+
.subtitle{color:#7a818c;font-size:12px;line-height:1.7;margin:0;overflow-wrap:anywhere}
|
|
19337
|
+
.game{margin:24px 0 0;padding-top:20px;border-top:1px solid #eceef1;text-align:center}
|
|
19338
|
+
.game-summary{display:flex;align-items:center;justify-content:center;gap:12px}.game-title{max-width:100%;margin:0;font-size:15px;font-weight:600;overflow-wrap:anywhere;min-width:0}
|
|
19339
|
+
.game-icon,.game-avatar{width:44px;height:44px;border-radius:10px;flex-shrink:0;background:#f0f2f5}.game-avatar{object-fit:cover}.game-icon{display:grid;place-items:center;color:#707b8b}
|
|
19340
|
+
.game-details{margin:16px 0 0;font-size:13px}.game-details>div:first-child,.game-categories>div{display:flex;align-items:baseline;justify-content:center;gap:8px}.game-details dt{flex-shrink:0;font-size:11px;color:#9298a2;margin:0}.game-details dd{min-width:0;margin:0;text-align:left;overflow-wrap:anywhere}
|
|
19341
|
+
.game-description{white-space:pre-wrap;line-height:1.65}.game-categories{display:grid;grid-template-columns:1fr;gap:16px;margin-top:16px}
|
|
19342
|
+
.scan-action{text-align:center;font-size:13px;font-weight:500;margin:26px 0 10px}.qr-area{display:flex;justify-content:center}.qr-frame{padding:8px;width:208px;height:208px}.qr-frame img{display:block;width:192px;height:192px;max-width:100%}
|
|
19343
|
+
.upload-area{display:flex;align-items:center;flex-direction:column;gap:16px;padding-top:26px}.percentage{font-size:34px;line-height:1;font-variant-numeric:tabular-nums;font-weight:550}.percentage span{font-size:16px;color:#858e9d;padding-left:3px}
|
|
19344
|
+
.progress-wrap{width:100%}progress{display:block;width:100%;height:4px;border:none;border-radius:20px;overflow:hidden;background:#edf0f4;accent-color:#4c6378}progress::-webkit-progress-bar{background:#edf0f4;border-radius:20px}progress::-webkit-progress-value{background:#4c6378;border-radius:20px}progress::-moz-progress-bar{background:#4c6378;border-radius:20px}.transfer-label{text-align:center;font-size:11px;color:#8b939f;margin:8px 0 0}
|
|
19345
|
+
.footer{text-align:center;font-size:10px;line-height:1.7;color:#a0a5ae;margin:24px 0 0}.footer .dot{display:inline-block;height:5px;width:5px;border-radius:50%;background:#c1995e;vertical-align:middle;margin-right:5px}.mock-badge{width:fit-content;margin:0 0 10px auto;font-size:10px;padding:2px 7px;border:1px solid #dfe2e7;border-radius:5px;color:#707783}
|
|
19346
|
+
@media(max-width:380px){body{padding-left:20px;padding-right:20px}.steps{gap:7px}.step:after{width:16px;margin-left:1px}h1{font-size:24px}}
|
|
19347
|
+
`;
|
|
19270
19348
|
|
|
19271
19349
|
const VIBE_PAGE_WORKER = '__vibe-page-server';
|
|
19272
19350
|
// Includes the base64 form of the locally validated image (up to 10 MiB).
|
|
19273
19351
|
const VIBE_PAGE_HTML_LIMIT = 16 * 1024 * 1024;
|
|
19352
|
+
const pageUpdates = `(() => {
|
|
19353
|
+
let etag;
|
|
19354
|
+
const terminal = () => ['complete', 'preview', 'failed'].includes(document.querySelector('main')?.dataset.phase);
|
|
19355
|
+
async function refresh() {
|
|
19356
|
+
if (terminal()) return;
|
|
19357
|
+
try {
|
|
19358
|
+
const response = await fetch(location.pathname, {cache: 'no-store', headers: etag ? {'If-None-Match': etag} : {}});
|
|
19359
|
+
if (response.ok) {
|
|
19360
|
+
etag = response.headers.get('etag');
|
|
19361
|
+
const next = new DOMParser().parseFromString(await response.text(), 'text/html');
|
|
19362
|
+
const current = document.querySelector('main'), updated = next.querySelector('main');
|
|
19363
|
+
if (current && updated && current.outerHTML !== updated.outerHTML) current.replaceWith(updated);
|
|
19364
|
+
document.title = next.title;
|
|
19365
|
+
}
|
|
19366
|
+
} catch { /* Keep the current page readable during a temporary connection failure. */ }
|
|
19367
|
+
if (!terminal()) setTimeout(refresh, 2000);
|
|
19368
|
+
}
|
|
19369
|
+
if (!terminal()) setTimeout(refresh, 2000);
|
|
19370
|
+
})();`;
|
|
19274
19371
|
const escape = (s) => s.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
|
|
19275
19372
|
async function renderVibePage(input) {
|
|
19276
19373
|
const zh = input.language !== 'en-US';
|
|
@@ -19280,7 +19377,6 @@ async function renderVibePage(input) {
|
|
|
19280
19377
|
? {
|
|
19281
19378
|
steps: ['扫码授权', '上传游戏', '完成'],
|
|
19282
19379
|
mock: input.hybrid ? 'PPE + Mock' : '模拟',
|
|
19283
|
-
game: '当前游戏',
|
|
19284
19380
|
gameIcon: '游戏头像',
|
|
19285
19381
|
bindingTitle: '上传到 TikTok',
|
|
19286
19382
|
bindingAction: input.hybrid ? '真实授权二维码 · 混合联调' : '使用 TikTok 扫描二维码',
|
|
@@ -19293,7 +19389,6 @@ async function renderVibePage(input) {
|
|
|
19293
19389
|
uploadingTitle: '正在上传小游戏',
|
|
19294
19390
|
uploadingSubtitle: input.simulation ? '正在模拟授权后的上传流程,不会上传游戏内容。' : '授权已完成,正在上传本次游戏内容。',
|
|
19295
19391
|
uploadLabel: input.simulation ? '模拟上传进度' : '上传进度',
|
|
19296
|
-
uploadStatus: '正在上传游戏,请稍候',
|
|
19297
19392
|
completeTitle: input.simulation ? '模拟上传完成' : '上传完成',
|
|
19298
19393
|
completeSubtitle: input.simulation
|
|
19299
19394
|
? '绑定和上传流程模拟完成,未向 TikTok 上传游戏。'
|
|
@@ -19307,7 +19402,6 @@ async function renderVibePage(input) {
|
|
|
19307
19402
|
: {
|
|
19308
19403
|
steps: ['Authorize', 'Upload', 'Done'],
|
|
19309
19404
|
mock: input.hybrid ? 'PPE + Mock' : 'MOCK',
|
|
19310
|
-
game: 'YOUR GAME',
|
|
19311
19405
|
gameIcon: 'Game icon',
|
|
19312
19406
|
bindingTitle: 'Upload to TikTok',
|
|
19313
19407
|
bindingAction: input.hybrid ? 'Real authorization QR · integration demo' : 'Scan the QR code with TikTok',
|
|
@@ -19324,7 +19418,6 @@ async function renderVibePage(input) {
|
|
|
19324
19418
|
uploadLabel: input.simulation
|
|
19325
19419
|
? 'Simulated upload progress'
|
|
19326
19420
|
: 'Upload progress',
|
|
19327
|
-
uploadStatus: 'Uploading your game. Please wait.',
|
|
19328
19421
|
completeTitle: input.simulation ? 'Simulated upload complete' : 'Upload complete',
|
|
19329
19422
|
completeSubtitle: input.simulation
|
|
19330
19423
|
? 'Binding and upload simulation complete. Nothing was uploaded to TikTok.'
|
|
@@ -19350,78 +19443,77 @@ async function renderVibePage(input) {
|
|
|
19350
19443
|
/^data:image\/(?:png|jpeg|webp);base64,[A-Za-z0-9+/]+=*$/.test(input.iconDataUrl)
|
|
19351
19444
|
? `<img class="game-avatar" width="44" height="44" alt="${copy.gameIcon}" src="${input.iconDataUrl}">`
|
|
19352
19445
|
: '<span class="game-icon" aria-hidden="true"><svg width="22" height="22" viewBox="0 0 24 24" fill="none"><path d="M7 7h10c2 0 3 2 3 4l1 5c.4 2-1 3-2.5 2l-3-2h-7l-3 2C4 19 2.6 18 3 16l1-5c0-2 1-4 3-4Z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M8 10v4m-2-2h4m6-1h.01M18 13h.01" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></span>';
|
|
19353
|
-
const game = `<div class="game">${icon}<
|
|
19446
|
+
const game = `<div class="game"><div class="game-summary">${icon}<p class="game-title">${escape(input.title)}</p></div><dl class="game-details"><div><dt>${zh ? '描述' : 'Description'}</dt><dd class="game-description">${escape(input.description ?? VIBE_PPE_DEFAULT_APP_INFO.description)}</dd></div><div class="game-categories"><div><dt>${zh ? '类型' : 'Category'}</dt><dd>${escape(input.category ?? VIBE_PPE_DEFAULT_APP_INFO.category)}</dd></div><div><dt>${zh ? '子类型' : 'Subcategory'}</dt><dd>${escape(input.subcategory ?? VIBE_PPE_DEFAULT_APP_INFO.subcategory)}</dd></div></div></dl></div>`;
|
|
19354
19447
|
const checkIcon = '<svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="m3 8 3 3 7-7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
19355
|
-
const title = phase === '
|
|
19356
|
-
?
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
|
|
19366
|
-
: phase === '
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19448
|
+
const title = phase === 'waiting_preview' ? (zh ? '正在准备预览' : 'Preparing preview')
|
|
19449
|
+
: phase === 'preview' ? (zh ? '扫码预览游戏' : 'Scan to preview')
|
|
19450
|
+
: phase === 'binding'
|
|
19451
|
+
? copy.bindingTitle
|
|
19452
|
+
: phase === 'uploading'
|
|
19453
|
+
? copy.uploadingTitle
|
|
19454
|
+
: phase === 'failed'
|
|
19455
|
+
? copy.failedTitle
|
|
19456
|
+
: copy.completeTitle;
|
|
19457
|
+
const subtitle = phase === 'waiting_preview' ? (zh ? '游戏内容已上传,预览就绪后会自动显示二维码。' : 'Game content uploaded. The preview QR will appear when ready.')
|
|
19458
|
+
: phase === 'preview' ? (zh ? '上传完成,使用手机 TikTok 扫描下方二维码。' : 'Upload complete. Scan the QR below with TikTok on your phone.')
|
|
19459
|
+
: phase === 'complete' && !input.simulation ? escape(input.message)
|
|
19460
|
+
: phase === 'binding'
|
|
19461
|
+
? copy.bindingSubtitle
|
|
19462
|
+
: phase === 'uploading'
|
|
19463
|
+
? copy.uploadingSubtitle
|
|
19464
|
+
: phase === 'failed'
|
|
19465
|
+
? escape(input.message)
|
|
19466
|
+
: copy.completeSubtitle;
|
|
19467
|
+
const qr = (phase === 'binding' || phase === 'preview') && !input.web && input.qrUrl
|
|
19370
19468
|
? await QRCode.toDataURL(input.qrUrl, {
|
|
19371
19469
|
width: 384,
|
|
19372
19470
|
margin: 4,
|
|
19373
19471
|
errorCorrectionLevel: 'M',
|
|
19374
19472
|
})
|
|
19375
19473
|
: '';
|
|
19376
|
-
const purpose = copy.bindingCode;
|
|
19474
|
+
const purpose = phase === 'preview' ? (zh ? '游戏预览二维码' : 'Game preview QR') : copy.bindingCode;
|
|
19475
|
+
const done = phase === 'complete' || phase === 'preview';
|
|
19377
19476
|
const progress = typeof input.progressPercent === 'number' &&
|
|
19378
19477
|
Number.isFinite(input.progressPercent)
|
|
19379
19478
|
? Math.max(0, Math.min(100, Math.round(input.progressPercent)))
|
|
19380
19479
|
: undefined;
|
|
19381
|
-
const
|
|
19382
|
-
?
|
|
19383
|
-
: phase === 'uploading'
|
|
19384
|
-
? copy.
|
|
19385
|
-
:
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19389
|
-
|
|
19390
|
-
: qr
|
|
19391
|
-
? `<div class="qr-area"><div class="qr-frame"><img width="192" height="192" alt="${purpose}" src="${qr}"></div></div>`
|
|
19392
|
-
: phase === 'uploading'
|
|
19393
|
-
? `<div class="upload-area"><div class="upload-symbol"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M12 16V4m-4 4 4-4 4 4M5 15v5h14v-5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></div>${progress !== undefined ? `<div class="percentage">${progress}<span>%</span></div><div class="progress-wrap"><progress aria-label="${copy.uploadLabel}" max="100" value="${progress}"></progress><p class="transfer-label">${copy.uploadLabel}</p></div>` : '<span class="spinner" aria-hidden="true"></span>'}</div>`
|
|
19394
|
-
: `<div class="upload-area"><div class="pending-symbol">${phase === 'failed' ? '!' : checkIcon}</div></div>`;
|
|
19395
|
-
return `<!doctype html><html lang="${zh ? 'zh-CN' : 'en'}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta http-equiv="refresh" content="2"><title>${escape(title)} · ${escape(input.title)}</title><style>${vibePageStyles}</style></head>
|
|
19396
|
-
<body><main class="shell" data-phase="${phase}"><header class="brand"><span class="brand-mark" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 20 20" fill="none"><path d="m7 5-4 5 4 5m6-10 4 5-4 5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="brand-name">TikTok Minis</span><span class="brand-tool">DevTool</span>${input.simulation ? `<span class="mock-badge">${copy.mock}</span>` : ''}</header>
|
|
19397
|
-
<section class="card" aria-labelledby="page-title">${phase === 'failed' ? '' : `<ol class="steps" aria-label="${zh ? '上传步骤' : 'Upload steps'}">${copy.steps.map((label, i) => `<li class="step ${i < step ? 'done' : i === step ? 'active' : ''}"${i === step ? ' aria-current="step"' : ''}><span class="step-number">${i < step ? checkIcon : i + 1}</span>${label}</li>`).join('')}</ol>`}
|
|
19398
|
-
<div class="heading"><h1 id="page-title">${title}</h1></div>${game}<div class="heading">${phase === 'binding' ? `<p class="scan-action">${copy.bindingAction}</p>` : ''}<p class="subtitle">${subtitle}</p></div>${content}
|
|
19399
|
-
<p class="status${phase === 'complete' ? ' complete' : ''}" role="status">${phase === 'complete' ? `<span class="success-icon">${checkIcon}</span>` : phase === 'failed' ? '' : '<span class="spinner" aria-hidden="true"></span>'}${status}</p>
|
|
19480
|
+
const content = qr
|
|
19481
|
+
? `<div class="qr-area"><div class="qr-frame"><img width="192" height="192" alt="${purpose}" src="${qr}"></div></div>`
|
|
19482
|
+
: phase === 'uploading' && progress !== undefined
|
|
19483
|
+
? `<div class="upload-area"><div class="percentage">${progress}<span>%</span></div><div class="progress-wrap"><progress aria-label="${copy.uploadLabel}" max="100" value="${progress}"></progress><p class="transfer-label">${copy.uploadLabel}</p></div></div>`
|
|
19484
|
+
: '';
|
|
19485
|
+
return `<!doctype html><html lang="${zh ? 'zh-CN' : 'en'}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escape(title)} · ${escape(input.title)}</title><style>${vibePageStyles}</style><script src="?updates" defer></script></head>
|
|
19486
|
+
<body><main class="shell" data-phase="${phase}">${input.simulation ? `<p class="mock-badge">${copy.mock}</p>` : ''}
|
|
19487
|
+
<section class="card" aria-labelledby="page-title">${phase === 'failed' ? '' : `<ol class="steps" aria-label="${zh ? '上传步骤' : 'Upload steps'}">${copy.steps.map((label, i) => `<li class="step ${done || i < step ? 'done' : i === step ? 'active' : ''}"${!done && i === step ? ' aria-current="step"' : ''}><span class="step-number">${done || i < step ? checkIcon : i + 1}</span>${label}</li>`).join('')}</ol>`}
|
|
19488
|
+
<div class="heading"><h1 id="page-title">${title}</h1><p class="subtitle" role="status">${subtitle}</p></div>${game}${phase === 'binding' ? `<p class="scan-action">${copy.bindingAction}</p>` : ''}${content}
|
|
19400
19489
|
</section>
|
|
19401
19490
|
<p class="footer">${input.simulation ? `<span class="dot" aria-hidden="true"></span>${copy.mockNote}` : copy.footer}</p></main></body></html>`;
|
|
19402
19491
|
}
|
|
19403
19492
|
async function createVibePageServer() {
|
|
19404
19493
|
const route = `/${crypto.randomBytes(24).toString('hex')}`;
|
|
19405
19494
|
let html = 'Starting…';
|
|
19495
|
+
let etag = '';
|
|
19406
19496
|
let landingUrl;
|
|
19407
19497
|
const server = http.createServer((req, res) => {
|
|
19408
19498
|
const address = server.address();
|
|
19409
19499
|
const host = address && typeof address !== 'string' ? `127.0.0.1:${address.port}` : '';
|
|
19500
|
+
if (req.method === 'GET' && req.url === `${route}?updates` && req.headers.host === host) {
|
|
19501
|
+
res.writeHead(200, { 'Content-Type': 'text/javascript; charset=utf-8', 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff' });
|
|
19502
|
+
res.end(pageUpdates);
|
|
19503
|
+
return;
|
|
19504
|
+
}
|
|
19410
19505
|
if (req.url === `${route}/authorize` && req.headers.host === host &&
|
|
19411
19506
|
['GET', 'HEAD'].includes(req.method || '') &&
|
|
19412
19507
|
(!req.headers.origin || req.headers.origin === `http://${host}`) &&
|
|
19413
19508
|
req.headers['sec-fetch-site'] !== 'cross-site') {
|
|
19414
19509
|
const headers = { 'Cache-Control': 'no-store', 'Referrer-Policy': 'no-referrer' };
|
|
19415
|
-
if (!landingUrl) {
|
|
19510
|
+
if (!landingUrl || req.method === 'GET') {
|
|
19416
19511
|
res.writeHead(410, { ...headers, 'Content-Type': 'text/plain; charset=utf-8' });
|
|
19417
|
-
res.end('
|
|
19512
|
+
res.end('Web authorization is disabled. Open the local QR page and scan with TikTok.');
|
|
19418
19513
|
}
|
|
19419
19514
|
else if (req.method === 'HEAD') {
|
|
19420
19515
|
res.writeHead(204, { ...headers, 'X-TTMG-Authorization': 'ready' }).end();
|
|
19421
19516
|
}
|
|
19422
|
-
else {
|
|
19423
|
-
res.writeHead(302, { ...headers, Location: landingUrl }).end();
|
|
19424
|
-
}
|
|
19425
19517
|
return;
|
|
19426
19518
|
}
|
|
19427
19519
|
if (req.method !== 'GET' ||
|
|
@@ -19430,12 +19522,17 @@ async function createVibePageServer() {
|
|
|
19430
19522
|
res.writeHead(404).end();
|
|
19431
19523
|
return;
|
|
19432
19524
|
}
|
|
19525
|
+
if (etag && req.headers['if-none-match'] === etag) {
|
|
19526
|
+
res.writeHead(304, { 'ETag': etag, 'Cache-Control': 'no-store' }).end();
|
|
19527
|
+
return;
|
|
19528
|
+
}
|
|
19433
19529
|
res.writeHead(200, {
|
|
19530
|
+
'ETag': etag,
|
|
19434
19531
|
'Content-Type': 'text/html; charset=utf-8',
|
|
19435
19532
|
'Cache-Control': 'no-store',
|
|
19436
19533
|
'Referrer-Policy': 'no-referrer',
|
|
19437
19534
|
'X-Content-Type-Options': 'nosniff',
|
|
19438
|
-
'Content-Security-Policy': "default-src 'none'; img-src data:; style-src 'unsafe-inline'; frame-ancestors 'none'",
|
|
19535
|
+
'Content-Security-Policy': "default-src 'none'; img-src data:; style-src 'unsafe-inline'; script-src 'self'; connect-src 'self'; frame-ancestors 'none'",
|
|
19439
19536
|
});
|
|
19440
19537
|
res.end(html);
|
|
19441
19538
|
});
|
|
@@ -19448,6 +19545,7 @@ async function createVibePageServer() {
|
|
|
19448
19545
|
url: `http://127.0.0.1:${address.port}${route}`,
|
|
19449
19546
|
update: (value, authorization) => {
|
|
19450
19547
|
html = value;
|
|
19548
|
+
etag = '"' + crypto.createHash('sha256').update(value).digest('hex') + '"';
|
|
19451
19549
|
landingUrl = browserLandingUrl(authorization);
|
|
19452
19550
|
},
|
|
19453
19551
|
close: () => server.close(),
|
|
@@ -19582,6 +19680,7 @@ const stageText = {
|
|
|
19582
19680
|
],
|
|
19583
19681
|
uploading: ['授权完成,正在自动上传', 'Authorized; uploading automatically'],
|
|
19584
19682
|
uploaded: ['上传完成', 'Upload complete'],
|
|
19683
|
+
waiting_preview: ['上传完成,正在准备预览', 'Upload complete; preparing preview'],
|
|
19585
19684
|
failed: [
|
|
19586
19685
|
'本次操作失败,请根据错误码处理',
|
|
19587
19686
|
'Operation failed; check the error code',
|
|
@@ -19649,11 +19748,13 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19649
19748
|
let timedOut = false;
|
|
19650
19749
|
let page;
|
|
19651
19750
|
let backend = deps.backend;
|
|
19751
|
+
let task;
|
|
19652
19752
|
let detached = false;
|
|
19653
19753
|
let progressUpdates = Promise.resolve();
|
|
19654
19754
|
let ppeLane;
|
|
19655
19755
|
let ppeSelected = false;
|
|
19656
19756
|
let hybridSelected = false;
|
|
19757
|
+
let pageMetadata;
|
|
19657
19758
|
let result = {
|
|
19658
19759
|
schemaVersion: '2',
|
|
19659
19760
|
command: 'upload',
|
|
@@ -19689,20 +19790,11 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19689
19790
|
throw new VibeError('VIBE_CANCELLED');
|
|
19690
19791
|
};
|
|
19691
19792
|
try {
|
|
19692
|
-
|
|
19693
|
-
|
|
19694
|
-
//
|
|
19695
|
-
|
|
19696
|
-
|
|
19697
|
-
throw new VibeError('VIBE_WEB_OPTION_CONFLICT');
|
|
19698
|
-
ppeSelected = true;
|
|
19699
|
-
ppeLane = process.env.TTMG_VIBE_PPE || 'ppe_op_vc';
|
|
19700
|
-
if (!/^ppe_[a-zA-Z0-9_-]{1,64}$/.test(ppeLane))
|
|
19701
|
-
throw new VibeError('VIBE_INVALID_PPE_ENV');
|
|
19702
|
-
if (deps.backend && deps.backend.mode !== 'ppe')
|
|
19703
|
-
throw new VibeError('VIBE_WEB_OPTION_CONFLICT');
|
|
19704
|
-
}
|
|
19705
|
-
else if (!options.mock && !options.vibeApi && process.env.TTMG_VIBE_BACKEND) {
|
|
19793
|
+
if (options.web)
|
|
19794
|
+
throw new VibeError('VIBE_WEB_DISABLED');
|
|
19795
|
+
// Real QR uploads use the release-owned PPE route.
|
|
19796
|
+
// Explicit test backends remain opt-in; real failures never fall back to them.
|
|
19797
|
+
if (!options.mock && !options.vibeApi && process.env.TTMG_VIBE_BACKEND) {
|
|
19706
19798
|
const configured = process.env.TTMG_VIBE_BACKEND;
|
|
19707
19799
|
if (configured === 'mock')
|
|
19708
19800
|
options = { ...options, mock: true };
|
|
@@ -19720,6 +19812,12 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19720
19812
|
else
|
|
19721
19813
|
throw new VibeError('VIBE_BACKEND_NOT_CONFIGURED');
|
|
19722
19814
|
}
|
|
19815
|
+
else if (!options.mock && !options.vibeApi) {
|
|
19816
|
+
ppeSelected = true;
|
|
19817
|
+
ppeLane = process.env.TTMG_VIBE_PPE || 'ppe_op_vc';
|
|
19818
|
+
if (!/^ppe_[a-zA-Z0-9_-]{1,64}$/.test(ppeLane))
|
|
19819
|
+
throw new VibeError('VIBE_INVALID_PPE_ENV');
|
|
19820
|
+
}
|
|
19723
19821
|
if (!['text', 'json', 'ndjson'].includes(options.format || 'text'))
|
|
19724
19822
|
throw new VibeError('VIBE_INVALID_FORMAT');
|
|
19725
19823
|
if (options.clientKey ||
|
|
@@ -19779,16 +19877,22 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19779
19877
|
});
|
|
19780
19878
|
if (options.dryRun)
|
|
19781
19879
|
return result;
|
|
19880
|
+
const submittedInfo = input.appInfo || VIBE_PPE_DEFAULT_APP_INFO;
|
|
19881
|
+
pageMetadata = {
|
|
19882
|
+
iconDataUrl: `data:${input.icon.contentType};base64,${input.icon.bytes.toString('base64')}`,
|
|
19883
|
+
description: submittedInfo.description,
|
|
19884
|
+
category: submittedInfo.category,
|
|
19885
|
+
subcategory: submittedInfo.subcategory,
|
|
19886
|
+
};
|
|
19782
19887
|
const pageGame = {
|
|
19783
19888
|
title: input.title,
|
|
19784
|
-
|
|
19889
|
+
...pageMetadata,
|
|
19785
19890
|
uploadAvailable: backend.uploadAvailable !== false,
|
|
19786
19891
|
hybrid: backend.mode === 'ppe-mock',
|
|
19787
|
-
web: !!options.web,
|
|
19788
19892
|
};
|
|
19789
19893
|
const sourceProjectId = deps.projectId(options);
|
|
19790
19894
|
emit('creating_task', { platformWrite: backend.mode === 'ppe' || backend.mode === 'ppe-mock' });
|
|
19791
|
-
|
|
19895
|
+
task = await backend.createTask({
|
|
19792
19896
|
source_type: 1,
|
|
19793
19897
|
source_project_id: sourceProjectId,
|
|
19794
19898
|
task_idempotency_id: result.operationId,
|
|
@@ -19796,8 +19900,6 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19796
19900
|
}, controller.signal);
|
|
19797
19901
|
ensureActive();
|
|
19798
19902
|
const authorizationUrl = createAuthorizationScheme(task.landingUrl);
|
|
19799
|
-
if (options.web && !browserLandingUrl(task.landingUrl))
|
|
19800
|
-
throw new VibeError('VIBE_INVALID_RESPONSE');
|
|
19801
19903
|
const pollMs = seconds(options.pollInterval, task.pollIntervalSeconds);
|
|
19802
19904
|
result = {
|
|
19803
19905
|
...result,
|
|
@@ -19806,33 +19908,30 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19806
19908
|
};
|
|
19807
19909
|
page = await deps.startPage();
|
|
19808
19910
|
ensureActive();
|
|
19809
|
-
const bindingMessage =
|
|
19911
|
+
const bindingMessage = backend.mode === 'ppe-mock'
|
|
19810
19912
|
? (getCurrentLanguage() === 'zh-CN'
|
|
19811
|
-
? '
|
|
19812
|
-
: '
|
|
19813
|
-
: backend.
|
|
19814
|
-
? (getCurrentLanguage() === 'zh-CN'
|
|
19815
|
-
|
|
19816
|
-
: 'Real PPE task and authorization QR. After three polls, authorization and upload are simulated; phone authorization is not required.')
|
|
19817
|
-
: backend.uploadAvailable === false
|
|
19818
|
-
? (getCurrentLanguage() === 'zh-CN' ? '请扫码完成真实 PPE 授权;上传尚未开放。' : 'Scan to authorize in PPE. Upload is not enabled yet.')
|
|
19819
|
-
: text('waiting_authorization', result.simulation);
|
|
19913
|
+
? '真实 PPE 任务和授权二维码;查询三次后模拟授权、上传,无需完成手机授权。'
|
|
19914
|
+
: 'Real PPE task and authorization QR. After three polls, authorization and upload are simulated; phone authorization is not required.')
|
|
19915
|
+
: backend.uploadAvailable === false
|
|
19916
|
+
? (getCurrentLanguage() === 'zh-CN' ? '请扫码完成真实 PPE 授权;上传尚未开放。' : 'Scan to authorize in PPE. Upload is not enabled yet.')
|
|
19917
|
+
: text('waiting_authorization', result.simulation);
|
|
19820
19918
|
await page.update({
|
|
19821
19919
|
...pageGame,
|
|
19822
19920
|
language: getCurrentLanguage(),
|
|
19823
19921
|
phase: 'binding',
|
|
19824
19922
|
simulation: result.simulation,
|
|
19825
19923
|
message: bindingMessage,
|
|
19826
|
-
qrUrl:
|
|
19924
|
+
qrUrl: authorizationUrl,
|
|
19827
19925
|
browserLandingUrl: task.landingUrl,
|
|
19828
19926
|
purpose: 'authorization_binding',
|
|
19829
19927
|
});
|
|
19830
19928
|
ensureActive();
|
|
19831
19929
|
let browserOpenRequested = false;
|
|
19832
19930
|
let browserOpenErrorCode;
|
|
19833
|
-
|
|
19931
|
+
const opensRealPage = backend.mode === 'ppe' && options.open !== false;
|
|
19932
|
+
if (opensRealPage) {
|
|
19834
19933
|
try {
|
|
19835
|
-
await deps.open(
|
|
19934
|
+
await deps.open(page.url);
|
|
19836
19935
|
browserOpenRequested = true;
|
|
19837
19936
|
}
|
|
19838
19937
|
catch {
|
|
@@ -19842,19 +19941,19 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19842
19941
|
}
|
|
19843
19942
|
emit('waiting_authorization', {
|
|
19844
19943
|
message: browserOpenErrorCode
|
|
19845
|
-
? (getCurrentLanguage() === 'zh-CN' ? '
|
|
19944
|
+
? (getCurrentLanguage() === 'zh-CN' ? '二维码页面未能打开,请在 CLI 所在电脑打开 pageUrl 并使用 TikTok 扫码;当前任务仍在等待授权。' : 'Could not open the QR page. Open pageUrl on the CLI computer and scan with TikTok; this task is still waiting for authorization.')
|
|
19846
19945
|
: bindingMessage,
|
|
19847
19946
|
pageUrl: page.url,
|
|
19848
|
-
authorizationMode:
|
|
19849
|
-
...(
|
|
19947
|
+
authorizationMode: 'qr',
|
|
19948
|
+
...(backend.mode === 'ppe' ? { browserOpenRequested, browserOpenErrorCode } : {}),
|
|
19850
19949
|
authorizationOpenCommand: browserLandingUrl(task.landingUrl)
|
|
19851
19950
|
? `ttmg auth open --page-url ${page.url} --format json` : undefined,
|
|
19852
|
-
qrPurpose:
|
|
19853
|
-
nextAction: result.simulation ? 'wait' :
|
|
19951
|
+
qrPurpose: 'authorization_binding',
|
|
19952
|
+
nextAction: result.simulation ? 'wait' : 'scan_qr',
|
|
19854
19953
|
readyForInput: false,
|
|
19855
19954
|
});
|
|
19856
19955
|
// Opening a browser is presentation only and never gates token polling.
|
|
19857
|
-
if (!
|
|
19956
|
+
if (!opensRealPage && backend.mode !== 'ppe' && (options.format || 'text') === 'text' && options.open !== false)
|
|
19858
19957
|
void deps.open(page.url).catch(() => { });
|
|
19859
19958
|
while (true) {
|
|
19860
19959
|
ensureActive();
|
|
@@ -19928,7 +20027,6 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19928
20027
|
}
|
|
19929
20028
|
}
|
|
19930
20029
|
});
|
|
19931
|
-
task.importToken = '';
|
|
19932
20030
|
task.landingUrl = '';
|
|
19933
20031
|
// A validated upload receipt is authoritative even if result-page rendering fails.
|
|
19934
20032
|
result = { ...result, uploaded: true, ...(backend.mode === 'ppe-mock'
|
|
@@ -19937,20 +20035,48 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19937
20035
|
await progressUpdates;
|
|
19938
20036
|
if (progressPageError)
|
|
19939
20037
|
throw progressPageError;
|
|
20038
|
+
if (!result.simulation) {
|
|
20039
|
+
emit('waiting_preview', { previewReady: false, terminal: false, nextAction: 'wait',
|
|
20040
|
+
qrPurpose: undefined, authorizationOpenCommand: undefined });
|
|
20041
|
+
await page.update({ ...pageGame, language: getCurrentLanguage(), phase: 'waiting_preview',
|
|
20042
|
+
simulation: false, message: text('waiting_preview') });
|
|
20043
|
+
while (true) {
|
|
20044
|
+
ensureActive();
|
|
20045
|
+
const detail = await backend.getTaskDetail(task, controller.signal);
|
|
20046
|
+
ensureActive();
|
|
20047
|
+
const fields = { taskStatusRaw: detail.statusRaw,
|
|
20048
|
+
...(detail.clientKey ? { clientKey: detail.clientKey } : {}),
|
|
20049
|
+
...(detail.appId ? { appId: detail.appId } : {}),
|
|
20050
|
+
...(detail.codeAssetId ? { codeAssetId: detail.codeAssetId } : {}),
|
|
20051
|
+
...(detail.minisCenterHistoryId ? { minisCenterHistoryId: detail.minisCenterHistoryId } : {}) };
|
|
20052
|
+
if (detail.previewUrl) {
|
|
20053
|
+
result = { ...result, ...fields, previewUrl: detail.previewUrl, previewReady: true };
|
|
20054
|
+
break;
|
|
20055
|
+
}
|
|
20056
|
+
if (Object.entries(fields).some(([key, value]) => result[key] !== value))
|
|
20057
|
+
emit('waiting_preview', fields);
|
|
20058
|
+
await deps.sleep(pollMs, controller.signal);
|
|
20059
|
+
}
|
|
20060
|
+
}
|
|
19940
20061
|
await page.update({
|
|
19941
20062
|
...pageGame,
|
|
19942
20063
|
language: getCurrentLanguage(),
|
|
19943
|
-
phase: 'complete',
|
|
20064
|
+
phase: result.previewReady ? 'preview' : 'complete',
|
|
20065
|
+
qrUrl: result.previewUrl,
|
|
20066
|
+
purpose: result.previewReady ? 'game_preview' : undefined,
|
|
19944
20067
|
simulation: result.simulation,
|
|
19945
20068
|
message: text('uploaded', result.simulation),
|
|
19946
20069
|
});
|
|
19947
20070
|
page.detach();
|
|
19948
20071
|
detached = true;
|
|
19949
|
-
return emit('uploaded', { terminal: true, nextAction: 'none'
|
|
20072
|
+
return emit('uploaded', { terminal: true, nextAction: result.previewReady ? 'scan_qr' : 'none',
|
|
20073
|
+
qrPurpose: result.previewReady ? 'game_preview' : undefined,
|
|
20074
|
+
...(result.previewReady ? { message: getCurrentLanguage() === 'zh-CN'
|
|
20075
|
+
? '上传完成,请使用 TikTok 扫码预览游戏。' : 'Upload complete. Scan with TikTok to preview your game.' } : {}) });
|
|
19950
20076
|
}
|
|
19951
20077
|
catch (error) {
|
|
19952
20078
|
const code = timedOut
|
|
19953
|
-
? 'VIBE_TIMEOUT'
|
|
20079
|
+
? result.uploaded ? 'VIBE_PREVIEW_TIMEOUT' : 'VIBE_TIMEOUT'
|
|
19954
20080
|
: controller.signal.aborted
|
|
19955
20081
|
? 'VIBE_CANCELLED'
|
|
19956
20082
|
: error instanceof VibeError
|
|
@@ -19965,7 +20091,6 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19965
20091
|
VIBE_CATEGORY_PAIR_REQUIRED: ['--category 和 --subcategory 必须同时提供;均不传时保留默认分类。使用 ttmg game categories --format json 查询。', 'Provide --category and --subcategory together, or omit both to keep the default. Query ttmg game categories --format json.'],
|
|
19966
20092
|
VIBE_INVALID_CATEGORY: ['类型或子类型无效,或两者不匹配。使用 ttmg game categories --format json 查询有效组合。', 'Invalid category/subcategory pair. Query valid values with ttmg game categories --format json.'],
|
|
19967
20093
|
VIBE_INVALID_DESCRIPTION: ['描述必须是非空的有效 Unicode 文本;省略 --description 可使用默认描述。', 'Description must be non-empty valid Unicode text; omit --description to use the default.'],
|
|
19968
|
-
VIBE_WEB_OPTION_CONFLICT: ['--web 使用真实网页授权,不能与 --mock、--mock-scenario、--vibe-api 或 --no-open 同时使用。', '--web uses real browser authorization and cannot be combined with --mock, --mock-scenario, --vibe-api or --no-open.'],
|
|
19969
20094
|
VIBE_AUTH_DENIED: [
|
|
19970
20095
|
'已取消授权,本次未上传。',
|
|
19971
20096
|
'Authorization cancelled. No upload was performed.',
|
|
@@ -19997,6 +20122,10 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19997
20122
|
'请检查本地 ZIP 代码包。',
|
|
19998
20123
|
'Check the local ZIP package.',
|
|
19999
20124
|
],
|
|
20125
|
+
VIBE_WEB_DISABLED: [
|
|
20126
|
+
'网页授权已关闭。请移除 --web,使用 ttmg upload --vibe 并在手机 TikTok 中扫码授权。',
|
|
20127
|
+
'Web authorization is disabled. Remove --web and use ttmg upload --vibe to scan with TikTok on your phone.',
|
|
20128
|
+
],
|
|
20000
20129
|
VIBE_BACKEND_NOT_CONFIGURED: [
|
|
20001
20130
|
'尚未配置服务;本地模拟请使用 --mock。',
|
|
20002
20131
|
'No backend configured. Use --mock for local simulation.',
|
|
@@ -20036,16 +20165,17 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20036
20165
|
};
|
|
20037
20166
|
const message = result.uploaded
|
|
20038
20167
|
? getCurrentLanguage() === 'zh-CN'
|
|
20039
|
-
? '
|
|
20040
|
-
:
|
|
20168
|
+
? `游戏内容已上传,但${code === 'VIBE_PREVIEW_TIMEOUT' ? '等待预览超时' : code === 'VIBE_CANCELLED' ? '已停止等待预览' : '预览查询或页面显示未完成'}。请核对任务,请勿重复上传。`
|
|
20169
|
+
: `Game content was uploaded, but ${code === 'VIBE_PREVIEW_TIMEOUT' ? 'preview polling timed out' : code === 'VIBE_CANCELLED' ? 'preview polling was cancelled' : 'preview lookup or display did not complete'}. Check the task; do not upload again.`
|
|
20041
20170
|
: messages[code]?.[getCurrentLanguage() === 'zh-CN' ? 0 : 1] ||
|
|
20042
20171
|
text(status);
|
|
20043
20172
|
controller.abort();
|
|
20044
20173
|
await progressUpdates;
|
|
20045
20174
|
// Remove the binding QR on failure too. Keep a readable terminal result page.
|
|
20046
|
-
if (page && code !== 'VIBE_CANCELLED') {
|
|
20175
|
+
if (page && (code !== 'VIBE_CANCELLED' || result.uploaded)) {
|
|
20047
20176
|
try {
|
|
20048
20177
|
await page.update({
|
|
20178
|
+
...pageMetadata,
|
|
20049
20179
|
title: result.title || '',
|
|
20050
20180
|
language: getCurrentLanguage(),
|
|
20051
20181
|
simulation: result.simulation,
|
|
@@ -20062,7 +20192,7 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20062
20192
|
}
|
|
20063
20193
|
return emit(status, {
|
|
20064
20194
|
terminal: true,
|
|
20065
|
-
nextAction: result.uploaded
|
|
20195
|
+
nextAction: result.uploaded ? 'check_task' : code === 'VIBE_CANCELLED'
|
|
20066
20196
|
? 'none'
|
|
20067
20197
|
: result.uploadAttempted ||
|
|
20068
20198
|
(result.taskId &&
|
|
@@ -20086,6 +20216,10 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20086
20216
|
});
|
|
20087
20217
|
}
|
|
20088
20218
|
finally {
|
|
20219
|
+
if (task) {
|
|
20220
|
+
task.importToken = '';
|
|
20221
|
+
task.landingUrl = '';
|
|
20222
|
+
}
|
|
20089
20223
|
clearTimeout(timeout);
|
|
20090
20224
|
externalSignal?.removeEventListener('abort', cancel);
|
|
20091
20225
|
backend?.dispose?.();
|