@ttmg/cli 0.4.6-vibe-beta.16 → 0.4.6-vibe-beta.18
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 +6 -12
- package/dist/index.js +172 -47
- package/dist/index.js.map +1 -1
- package/dist/package.json +2 -2
- package/dist/scripts/vibe-ppe-test-preload.cjs +1 -1
- package/dist/scripts/vibe-upload-smoke.js +21 -7
- package/package.json +2 -2
- package/vibe-upload.md +23 -12
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
### @ttmg/cli
|
|
2
2
|
|
|
3
|
+
`0.4.6-vibe-beta.18` 在上传成功后短暂显示成功提示,再平滑返回原扫码界面,保留最初的绑定二维码,终态 `qrPurpose=authorization_binding`。CLI 继续查询同一任务,并通过 `previewUrl` 返回后端预览链接;电脑页面不切换为试玩码。预览超时或查询失败仍保留上传回执,请勿直接重传。详见 [Vibe 接入说明](vibe-upload.md)。
|
|
4
|
+
|
|
5
|
+
Version `0.4.6-vibe-beta.18`: after upload, the page briefly confirms success, then smoothly returns to the original binding QR. The CLI keeps polling the same task and returns its preview link in `previewUrl`; the page QR remains for binding. A preview timeout or lookup failure preserves the upload receipt; do not upload again.
|
|
6
|
+
|
|
3
7
|
Vibe 手机扫码授权与上传:`ttmg upload --vibe --dir ./game --title "My game" --icon ./avatar.png --format ndjson`。用户在手机 TikTok 扫码授权后,原命令自动上传。只收集标题和本地头像,并使用游戏目录或代码包;其他应用资料由 CLI 补齐。页面展示标题、头像、描述、类型和子类型。见 [Vibe 接入说明](vibe-upload.md)。
|
|
4
8
|
|
|
5
|
-
扫码上传使用 `0.4.6-vibe-beta.
|
|
9
|
+
扫码上传使用 `0.4.6-vibe-beta.18`,安装后核对版本与能力声明。Snow Crew 已在 PPE 完成真实绑定上传,本次返回原绑定码的页面体验已由用户验证。预览链接的完整链路此前已在 beta.17 验证;正式环境未开放。
|
|
6
10
|
|
|
7
11
|
`ttmg` is a command-line tool designed for managing and developing mini-game projects. It supports initialization, development, debugging, and packaging for both H5 and native mini-games.
|
|
8
12
|
`ttmg` 是一款专为小游戏项目管理与开发设计的命令行工具,支持 H5 小游戏和原生小游戏的初始化、开发调试及打包构建。
|
|
@@ -14,7 +18,7 @@ Vibe 版本支持手机扫码授权上传、可选 `--description`、分类查
|
|
|
14
18
|
固定版本安装(发布标签为 `vibe-beta`):
|
|
15
19
|
|
|
16
20
|
```bash
|
|
17
|
-
npm install -g @ttmg/cli@0.4.6-vibe-beta.
|
|
21
|
+
npm install -g @ttmg/cli@0.4.6-vibe-beta.18
|
|
18
22
|
ttmg --version
|
|
19
23
|
ttmg capabilities --format json
|
|
20
24
|
ttmg upload --help
|
|
@@ -89,16 +93,6 @@ ttmg login --implicit --email <email> --password <password>
|
|
|
89
93
|
options when credentials are provided by the execution environment.
|
|
90
94
|
执行环境也可以通过 `TTMG_LOGIN_EMAIL` 和 `TTMG_LOGIN_PASSWORD` 提供凭据。
|
|
91
95
|
|
|
92
|
-
Inspect the reusable local session without exposing its Cookie. The status is
|
|
93
|
-
local-only; it does not make a network request. `--require-auth` is useful for
|
|
94
|
-
Agent preflight because it exits non-zero when login is missing.
|
|
95
|
-
可在不暴露 Cookie 的情况下读取本地会话是否可复用。该状态只检查本地配置,不发起网络请求;Agent 预检可增加 `--require-auth`,未登录时以非零状态退出。
|
|
96
|
-
|
|
97
|
-
```
|
|
98
|
-
ttmg auth status --format json
|
|
99
|
-
ttmg auth status --format json --require-auth
|
|
100
|
-
```
|
|
101
|
-
|
|
102
96
|
Agents can discover the exact command and runtime capability surface before
|
|
103
97
|
acting. Unsupported device and Runtime features are explicitly reported as
|
|
104
98
|
`false`; commands that create a platform version are marked with
|
package/dist/index.js
CHANGED
|
@@ -7263,6 +7263,8 @@ Vibe QR upload (real PPE trial):
|
|
|
7263
7263
|
Keep this process running. At waiting_authorization, scan the QR on pageUrl with TikTok on your phone.
|
|
7264
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, pageUrl returns to the original binding QR. waiting_preview keeps polling the same task until previewUrl is available.
|
|
7267
|
+
previewReady=true means the preview link is available; qrPurpose=authorization_binding describes the original QR retained on pageUrl. A preview timeout preserves uploaded=true; 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.
|
|
7268
7270
|
The page displays the submitted title, icon, description, category and subcategory.
|
|
@@ -7711,6 +7713,8 @@ Vibe 扫码上传(真实 PPE 试用):
|
|
|
7711
7713
|
保持原进程运行。在 waiting_authorization 阶段,使用手机 TikTok 扫描 pageUrl 页面中的二维码。
|
|
7712
7714
|
pageUrl 是 CLI 所在电脑的二维码/进度页;重新打开时,执行返回的 authorizationOpenCommand。
|
|
7713
7715
|
CLI 负责轮询,授权后自动上传 avatar、app_info、package,不再启动第二次上传。
|
|
7716
|
+
上传成功后 pageUrl 平滑返回原绑定二维码;waiting_preview 继续查询同一任务直到取得 previewUrl。
|
|
7717
|
+
previewReady=true 表示预览链接已返回;qrPurpose=authorization_binding 表示 pageUrl 保留原绑定码。等待超时仍保留 uploaded=true,请核对任务,不要重传。
|
|
7714
7718
|
真实成功:terminal=true、status=uploaded、uploaded=true、simulation=false。失败时读取 errorCode 和 nextAction。
|
|
7715
7719
|
所有输出格式都会打开本地二维码页;--no-open 可关闭自动打开。
|
|
7716
7720
|
页面展示本次提交的标题、头像、描述、类型和子类型。
|
|
@@ -16378,7 +16382,7 @@ async function authStatus(options = {}) {
|
|
|
16378
16382
|
return result;
|
|
16379
16383
|
}
|
|
16380
16384
|
|
|
16381
|
-
var version = "0.4.6-vibe-beta.
|
|
16385
|
+
var version = "0.4.6-vibe-beta.18";
|
|
16382
16386
|
var engines = {
|
|
16383
16387
|
node: ">=20.19.1 <21 || >=22"
|
|
16384
16388
|
};
|
|
@@ -17265,6 +17269,18 @@ function getCapabilities() {
|
|
|
17265
17269
|
httpContract: 'minis-import-task',
|
|
17266
17270
|
authorizationQr: true,
|
|
17267
17271
|
previewQr: false,
|
|
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: 'authorization_binding',
|
|
17278
|
+
pageQrSource: 'original-landing-url',
|
|
17279
|
+
sameTask: true,
|
|
17280
|
+
timeout: 'shared-operation-timeout',
|
|
17281
|
+
failurePreservesUploaded: true,
|
|
17282
|
+
deviceRunVerified: false,
|
|
17283
|
+
},
|
|
17268
17284
|
realAuthorizationVerified: true,
|
|
17269
17285
|
realUploadVerified: true,
|
|
17270
17286
|
verificationScope: { backend: 'ppe', authorizationMode: 'qr', assets: 'test-fixtures', date: '2026-09-16', production: false },
|
|
@@ -18618,7 +18634,7 @@ var authOpen = /*#__PURE__*/Object.freeze({
|
|
|
18618
18634
|
const prefix = '/bff/app_info/minis_import/';
|
|
18619
18635
|
const MINIS_IMPORT_PATHS = {
|
|
18620
18636
|
create: `${prefix}create_minis_import_task_for_agent/`,
|
|
18621
|
-
status: `${prefix}
|
|
18637
|
+
status: `${prefix}get_minis_import_task_detail_for_agent/`,
|
|
18622
18638
|
icon: `${prefix}upload_minis_import_app_icon_for_agent/`,
|
|
18623
18639
|
info: `${prefix}submit_minis_import_info_for_agent/`,
|
|
18624
18640
|
package: `${prefix}upload_minis_import_package_for_agent/`,
|
|
@@ -18677,6 +18693,31 @@ function createBackend(mode, transport) {
|
|
|
18677
18693
|
});
|
|
18678
18694
|
return {
|
|
18679
18695
|
mode,
|
|
18696
|
+
async getTaskDetail(task, signal) {
|
|
18697
|
+
const data = await call({ method: 'GET', path: MINIS_IMPORT_PATHS.status,
|
|
18698
|
+
data: credentials(task), signal });
|
|
18699
|
+
const raw = status(data.status);
|
|
18700
|
+
if (raw === 8)
|
|
18701
|
+
throw new VibeError('VIBE_AUTH_EXPIRED');
|
|
18702
|
+
if (raw === 7)
|
|
18703
|
+
throw new VibeError('VIBE_TASK_FAILED');
|
|
18704
|
+
if (data.ready_for_input !== undefined && typeof data.ready_for_input !== 'boolean')
|
|
18705
|
+
throw new VibeError('VIBE_INVALID_RESPONSE');
|
|
18706
|
+
const detail = { statusRaw: raw, readyForInput: data.ready_for_input === true };
|
|
18707
|
+
for (const [wire, field] of [['app_id', 'appId'], ['code_asset_id', 'codeAssetId'],
|
|
18708
|
+
['minis_center_history_id', 'minisCenterHistoryId']]) {
|
|
18709
|
+
if (data[wire] !== undefined && data[wire] !== '')
|
|
18710
|
+
detail[field] = taskId(data[wire]);
|
|
18711
|
+
}
|
|
18712
|
+
if (data.client_key !== undefined && data.client_key !== '') {
|
|
18713
|
+
if (!nonempty(data.client_key))
|
|
18714
|
+
throw new VibeError('VIBE_INVALID_RESPONSE');
|
|
18715
|
+
detail.clientKey = data.client_key;
|
|
18716
|
+
}
|
|
18717
|
+
if (data.preview_url !== undefined && data.preview_url !== '')
|
|
18718
|
+
detail.previewUrl = safeWebUrl(data.preview_url);
|
|
18719
|
+
return detail;
|
|
18720
|
+
},
|
|
18680
18721
|
async createTask(input, signal) {
|
|
18681
18722
|
const data = await call({
|
|
18682
18723
|
method: 'POST',
|
|
@@ -18807,6 +18848,7 @@ function createPpeMockBackend(lane, adapter, scenario = 'success') {
|
|
|
18807
18848
|
};
|
|
18808
18849
|
return {
|
|
18809
18850
|
mode: 'ppe-mock',
|
|
18851
|
+
getTaskDetail: (task, signal) => real.getTaskDetail(task, signal),
|
|
18810
18852
|
uploadAvailable: true,
|
|
18811
18853
|
dispose: () => real.dispose?.(),
|
|
18812
18854
|
async createTask(input, signal) {
|
|
@@ -19283,9 +19325,9 @@ const vibePageStyles = `
|
|
|
19283
19325
|
|
|
19284
19326
|
: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}
|
|
19285
19327
|
*{box-sizing:border-box}
|
|
19286
|
-
body{margin:0;min-height:100vh;min-height:100svh;display:flex;align-items:center;justify-content:center;padding:
|
|
19287
|
-
.shell{width:100%;max-width:400px}
|
|
19288
|
-
.steps{display:flex;align-items:center;justify-content:center;gap:10px;margin:0 0
|
|
19328
|
+
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}
|
|
19329
|
+
.shell{position:relative;width:100%;max-width:400px}
|
|
19330
|
+
.steps{display:flex;align-items:center;justify-content:center;gap:10px;margin:0 0 24px;padding:0;list-style:none}
|
|
19289
19331
|
.step{display:flex;align-items:center;gap:6px;font-size:11px;color:#9298a2;white-space:nowrap}
|
|
19290
19332
|
.step:after{content:"";height:1px;width:25px;margin-left:4px;background:#e9ebef}.step:last-child:after{display:none}
|
|
19291
19333
|
.step-number{height:18px;min-width:18px;border-radius:50%;border:1px solid #e2e5e9;display:grid;place-items:center;font-size:10px}
|
|
@@ -19293,17 +19335,17 @@ body{margin:0;min-height:100vh;min-height:100svh;display:flex;align-items:center
|
|
|
19293
19335
|
.step.done{color:#537565}.step.done .step-number{background:#edf6f0;color:#287c54;border-color:#edf6f0}
|
|
19294
19336
|
.heading{text-align:center}h1{font-size:26px;font-weight:650;line-height:1.35;letter-spacing:-.6px;margin:0 0 10px}
|
|
19295
19337
|
.subtitle{color:#7a818c;font-size:12px;line-height:1.7;margin:0;overflow-wrap:anywhere}
|
|
19296
|
-
.game{margin:
|
|
19297
|
-
.game-summary{display:flex;align-items:center;gap:12px}.game-title{margin:0;font-size:15px;font-weight:600;overflow-wrap:anywhere;min-width:0}
|
|
19338
|
+
.game{margin:24px 0 0;padding-top:20px;border-top:1px solid #eceef1;text-align:center}
|
|
19339
|
+
.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}
|
|
19298
19340
|
.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}
|
|
19299
|
-
.game-details{margin:
|
|
19300
|
-
.game-description{white-space:pre-wrap;line-height:1.65}.game-categories{display:grid;grid-template-columns:1fr
|
|
19341
|
+
.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}
|
|
19342
|
+
.game-description{white-space:pre-wrap;line-height:1.65}.game-categories{display:grid;grid-template-columns:1fr;gap:16px;margin-top:16px}
|
|
19301
19343
|
.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%}
|
|
19344
|
+
.game+.qr-area{margin-top:20px}.scan-hint{text-align:center;font-size:11px;color:#7a818c;line-height:1.7;margin:8px 0 0}
|
|
19302
19345
|
.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}
|
|
19303
19346
|
.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}
|
|
19304
|
-
.footer{text-align:center;font-size:10px;line-height:1.7;color:#a0a5ae;margin:
|
|
19305
|
-
@media(max-width:380px){body{padding:
|
|
19306
|
-
@media(max-height:640px){body{align-items:flex-start;padding-top:28px;padding-bottom:28px}}
|
|
19347
|
+
.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}
|
|
19348
|
+
@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}}
|
|
19307
19349
|
`;
|
|
19308
19350
|
|
|
19309
19351
|
const VIBE_PAGE_WORKER = '__vibe-page-server';
|
|
@@ -19311,7 +19353,38 @@ const VIBE_PAGE_WORKER = '__vibe-page-server';
|
|
|
19311
19353
|
const VIBE_PAGE_HTML_LIMIT = 16 * 1024 * 1024;
|
|
19312
19354
|
const pageUpdates = `(() => {
|
|
19313
19355
|
let etag;
|
|
19314
|
-
const terminal = () => ['complete', 'failed'].includes(document.querySelector('main')?.dataset.phase);
|
|
19356
|
+
const terminal = () => ['complete', 'preview', 'failed'].includes(document.querySelector('main')?.dataset.phase);
|
|
19357
|
+
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
19358
|
+
async function replace(current, updated) {
|
|
19359
|
+
const animate = current.dataset.phase !== updated.dataset.phase && current.animate &&
|
|
19360
|
+
!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
19361
|
+
const height = current.getBoundingClientRect().height;
|
|
19362
|
+
if (animate) await current.animate([{opacity: 1}, {opacity: 0}], {duration: 140, fill: 'forwards'}).finished.catch(() => {});
|
|
19363
|
+
current.replaceWith(updated);
|
|
19364
|
+
if (animate) await updated.animate([
|
|
19365
|
+
{opacity: 0, height: height + 'px', overflow: 'hidden'},
|
|
19366
|
+
{opacity: 1, height: updated.getBoundingClientRect().height + 'px', overflow: 'hidden'}
|
|
19367
|
+
], {duration: 280, easing: 'ease-out'}).finished.catch(() => {});
|
|
19368
|
+
}
|
|
19369
|
+
async function show(updated) {
|
|
19370
|
+
let current = document.querySelector('main');
|
|
19371
|
+
if (!current || current.outerHTML === updated.outerHTML) return;
|
|
19372
|
+
if (updated.dataset.uploadedBinding === 'true' && current.dataset.uploadedBinding !== 'true') {
|
|
19373
|
+
// Return to the original binding QR after showing the upload receipt,
|
|
19374
|
+
// even when polling skips intermediate upload states.
|
|
19375
|
+
const success = updated.cloneNode(true);
|
|
19376
|
+
success.dataset.phase = 'upload_success';
|
|
19377
|
+
delete success.dataset.uploadedBinding;
|
|
19378
|
+
const heading = success.querySelector('.heading');
|
|
19379
|
+
success.querySelector('h1').textContent = heading.dataset.uploadTitle;
|
|
19380
|
+
success.querySelector('.subtitle').textContent = heading.dataset.uploadSubtitle;
|
|
19381
|
+
success.querySelectorAll('.qr-area, .scan-action, .scan-hint').forEach(node => node.remove());
|
|
19382
|
+
await replace(current, success);
|
|
19383
|
+
current = success;
|
|
19384
|
+
await delay(1200);
|
|
19385
|
+
}
|
|
19386
|
+
await replace(current, updated);
|
|
19387
|
+
}
|
|
19315
19388
|
async function refresh() {
|
|
19316
19389
|
if (terminal()) return;
|
|
19317
19390
|
try {
|
|
@@ -19319,8 +19392,8 @@ const pageUpdates = `(() => {
|
|
|
19319
19392
|
if (response.ok) {
|
|
19320
19393
|
etag = response.headers.get('etag');
|
|
19321
19394
|
const next = new DOMParser().parseFromString(await response.text(), 'text/html');
|
|
19322
|
-
const
|
|
19323
|
-
if (
|
|
19395
|
+
const updated = next.querySelector('main');
|
|
19396
|
+
if (updated) await show(updated);
|
|
19324
19397
|
document.title = next.title;
|
|
19325
19398
|
}
|
|
19326
19399
|
} catch { /* Keep the current page readable during a temporary connection failure. */ }
|
|
@@ -19333,6 +19406,8 @@ async function renderVibePage(input) {
|
|
|
19333
19406
|
const zh = input.language !== 'en-US';
|
|
19334
19407
|
const phase = input.phase ||
|
|
19335
19408
|
(input.purpose === 'authorization_binding' ? 'binding' : 'uploading');
|
|
19409
|
+
const uploadedBinding = !input.simulation && input.purpose === 'authorization_binding' &&
|
|
19410
|
+
(phase === 'waiting_preview' || phase === 'preview' || phase === 'complete');
|
|
19336
19411
|
const copy = zh
|
|
19337
19412
|
? {
|
|
19338
19413
|
steps: ['扫码授权', '上传游戏', '完成'],
|
|
@@ -19357,7 +19432,6 @@ async function renderVibePage(input) {
|
|
|
19357
19432
|
bindingCode: '授权绑定二维码',
|
|
19358
19433
|
accepted: input.simulation ? '模拟完成' : '已上传',
|
|
19359
19434
|
mockNote: input.hybrid ? '真实创建 / 查询任务 · 授权 / 上传为模拟 · 不改变真实任务状态' : '模拟运行 · 无需扫码,不会真实上传',
|
|
19360
|
-
footer: '临时页面 · 15 分钟后关闭',
|
|
19361
19435
|
}
|
|
19362
19436
|
: {
|
|
19363
19437
|
steps: ['Authorize', 'Upload', 'Done'],
|
|
@@ -19386,7 +19460,6 @@ async function renderVibePage(input) {
|
|
|
19386
19460
|
bindingCode: 'Authorization QR',
|
|
19387
19461
|
accepted: input.simulation ? 'Simulation complete' : 'Uploaded',
|
|
19388
19462
|
mockNote: input.hybrid ? 'Real task creation / polling · Mock authorization / upload · Real task status unchanged' : 'Mock only · no scan needed, nothing is uploaded',
|
|
19389
|
-
footer: 'Temporary page · closes after 15 minutes',
|
|
19390
19463
|
};
|
|
19391
19464
|
if (input.web) {
|
|
19392
19465
|
copy.steps[0] = zh ? '网页授权' : 'Web authorization';
|
|
@@ -19405,28 +19478,42 @@ async function renderVibePage(input) {
|
|
|
19405
19478
|
: '<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>';
|
|
19406
19479
|
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>`;
|
|
19407
19480
|
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>';
|
|
19408
|
-
const
|
|
19409
|
-
|
|
19410
|
-
|
|
19411
|
-
|
|
19412
|
-
|
|
19413
|
-
|
|
19414
|
-
|
|
19415
|
-
|
|
19416
|
-
|
|
19417
|
-
|
|
19418
|
-
|
|
19419
|
-
|
|
19420
|
-
|
|
19421
|
-
|
|
19422
|
-
const
|
|
19481
|
+
const uploadSuccessTitle = zh ? '上传成功' : 'Upload complete';
|
|
19482
|
+
const uploadSuccessSubtitle = zh
|
|
19483
|
+
? '游戏内容已上传,正在返回扫码页面。'
|
|
19484
|
+
: 'Your game has been uploaded. Returning to the scan page.';
|
|
19485
|
+
const title = uploadedBinding ? (zh ? '扫码打开绑定页面' : 'Scan to open the binding page')
|
|
19486
|
+
: phase === 'waiting_preview' ? (zh ? '正在准备预览' : 'Preparing preview')
|
|
19487
|
+
: phase === 'preview' ? (zh ? '扫码预览游戏' : 'Scan to preview')
|
|
19488
|
+
: phase === 'binding'
|
|
19489
|
+
? copy.bindingTitle
|
|
19490
|
+
: phase === 'uploading'
|
|
19491
|
+
? copy.uploadingTitle
|
|
19492
|
+
: phase === 'failed'
|
|
19493
|
+
? copy.failedTitle
|
|
19494
|
+
: copy.completeTitle;
|
|
19495
|
+
const subtitle = uploadedBinding ? (phase === 'complete' ? escape(input.message) : zh
|
|
19496
|
+
? '本次游戏代码包已上传完成,再次扫码即可查看预览版本并提交审核。'
|
|
19497
|
+
: 'Your game package has been uploaded. Scan again to view the preview version and submit it for review.')
|
|
19498
|
+
: phase === 'waiting_preview' ? (zh ? '游戏内容已上传,预览就绪后会自动显示二维码。' : 'Game content uploaded. The preview QR will appear when ready.')
|
|
19499
|
+
: phase === 'preview' ? (zh ? '上传完成,使用手机 TikTok 扫描下方二维码。' : 'Upload complete. Scan the QR below with TikTok on your phone.')
|
|
19500
|
+
: phase === 'complete' && !input.simulation ? escape(input.message)
|
|
19501
|
+
: phase === 'binding'
|
|
19502
|
+
? copy.bindingSubtitle
|
|
19503
|
+
: phase === 'uploading'
|
|
19504
|
+
? copy.uploadingSubtitle
|
|
19505
|
+
: phase === 'failed'
|
|
19506
|
+
? escape(input.message)
|
|
19507
|
+
: copy.completeSubtitle;
|
|
19508
|
+
const qr = (phase === 'binding' || phase === 'preview' || uploadedBinding) && !input.web && input.qrUrl
|
|
19423
19509
|
? await QRCode.toDataURL(input.qrUrl, {
|
|
19424
19510
|
width: 384,
|
|
19425
19511
|
margin: 4,
|
|
19426
19512
|
errorCorrectionLevel: 'M',
|
|
19427
19513
|
})
|
|
19428
19514
|
: '';
|
|
19429
|
-
const purpose = copy.bindingCode;
|
|
19515
|
+
const purpose = phase === 'preview' && !uploadedBinding ? (zh ? '游戏预览二维码' : 'Game preview QR') : copy.bindingCode;
|
|
19516
|
+
const done = phase === 'complete' || phase === 'preview' || phase === 'waiting_preview';
|
|
19430
19517
|
const progress = typeof input.progressPercent === 'number' &&
|
|
19431
19518
|
Number.isFinite(input.progressPercent)
|
|
19432
19519
|
? Math.max(0, Math.min(100, Math.round(input.progressPercent)))
|
|
@@ -19437,11 +19524,11 @@ async function renderVibePage(input) {
|
|
|
19437
19524
|
? `<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>`
|
|
19438
19525
|
: '';
|
|
19439
19526
|
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>
|
|
19440
|
-
<body><main class="shell" data-phase="${phase}">${input.simulation ? `<p class="mock-badge">${copy.mock}</p>` : ''}
|
|
19441
|
-
<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 ${
|
|
19442
|
-
<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}
|
|
19527
|
+
<body><main class="shell" data-phase="${phase}"${uploadedBinding ? ' data-uploaded-binding="true"' : ''}>${input.simulation ? `<p class="mock-badge">${copy.mock}</p>` : ''}
|
|
19528
|
+
<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>`}
|
|
19529
|
+
<div class="heading"${uploadedBinding ? ` data-upload-title="${escape(uploadSuccessTitle)}" data-upload-subtitle="${escape(uploadSuccessSubtitle)}"` : ''}><h1 id="page-title">${title}</h1><p class="subtitle" role="status">${subtitle}</p></div>${game}${phase === 'binding' || uploadedBinding ? `<p class="scan-action">${copy.bindingAction}</p>` : ''}${content}${uploadedBinding ? `<p class="scan-hint">${zh ? '这是本次上传的原绑定二维码,无需重复上传。' : 'This is the original binding QR for this upload. No need to upload again.'}</p>` : ''}
|
|
19443
19530
|
</section>
|
|
19444
|
-
|
|
19531
|
+
${input.simulation ? `<p class="footer"><span class="dot" aria-hidden="true"></span>${copy.mockNote}</p>` : ''}</main></body></html>`;
|
|
19445
19532
|
}
|
|
19446
19533
|
async function createVibePageServer() {
|
|
19447
19534
|
const route = `/${crypto.randomBytes(24).toString('hex')}`;
|
|
@@ -19634,6 +19721,7 @@ const stageText = {
|
|
|
19634
19721
|
],
|
|
19635
19722
|
uploading: ['授权完成,正在自动上传', 'Authorized; uploading automatically'],
|
|
19636
19723
|
uploaded: ['上传完成', 'Upload complete'],
|
|
19724
|
+
waiting_preview: ['上传完成,正在准备预览', 'Upload complete; preparing preview'],
|
|
19637
19725
|
failed: [
|
|
19638
19726
|
'本次操作失败,请根据错误码处理',
|
|
19639
19727
|
'Operation failed; check the error code',
|
|
@@ -19701,6 +19789,7 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19701
19789
|
let timedOut = false;
|
|
19702
19790
|
let page;
|
|
19703
19791
|
let backend = deps.backend;
|
|
19792
|
+
let task;
|
|
19704
19793
|
let detached = false;
|
|
19705
19794
|
let progressUpdates = Promise.resolve();
|
|
19706
19795
|
let ppeLane;
|
|
@@ -19844,7 +19933,7 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19844
19933
|
};
|
|
19845
19934
|
const sourceProjectId = deps.projectId(options);
|
|
19846
19935
|
emit('creating_task', { platformWrite: backend.mode === 'ppe' || backend.mode === 'ppe-mock' });
|
|
19847
|
-
|
|
19936
|
+
task = await backend.createTask({
|
|
19848
19937
|
source_type: 1,
|
|
19849
19938
|
source_project_id: sourceProjectId,
|
|
19850
19939
|
task_idempotency_id: result.operationId,
|
|
@@ -19979,7 +20068,6 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19979
20068
|
}
|
|
19980
20069
|
}
|
|
19981
20070
|
});
|
|
19982
|
-
task.importToken = '';
|
|
19983
20071
|
task.landingUrl = '';
|
|
19984
20072
|
// A validated upload receipt is authoritative even if result-page rendering fails.
|
|
19985
20073
|
result = { ...result, uploaded: true, ...(backend.mode === 'ppe-mock'
|
|
@@ -19988,20 +20076,49 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19988
20076
|
await progressUpdates;
|
|
19989
20077
|
if (progressPageError)
|
|
19990
20078
|
throw progressPageError;
|
|
20079
|
+
if (!result.simulation) {
|
|
20080
|
+
emit('waiting_preview', { previewReady: false, terminal: false, nextAction: 'wait',
|
|
20081
|
+
qrPurpose: 'authorization_binding', authorizationOpenCommand: undefined });
|
|
20082
|
+
await page.update({ ...pageGame, language: getCurrentLanguage(), phase: 'waiting_preview',
|
|
20083
|
+
qrUrl: authorizationUrl, purpose: 'authorization_binding',
|
|
20084
|
+
simulation: false, message: text('waiting_preview') });
|
|
20085
|
+
while (true) {
|
|
20086
|
+
ensureActive();
|
|
20087
|
+
const detail = await backend.getTaskDetail(task, controller.signal);
|
|
20088
|
+
ensureActive();
|
|
20089
|
+
const fields = { taskStatusRaw: detail.statusRaw,
|
|
20090
|
+
...(detail.clientKey ? { clientKey: detail.clientKey } : {}),
|
|
20091
|
+
...(detail.appId ? { appId: detail.appId } : {}),
|
|
20092
|
+
...(detail.codeAssetId ? { codeAssetId: detail.codeAssetId } : {}),
|
|
20093
|
+
...(detail.minisCenterHistoryId ? { minisCenterHistoryId: detail.minisCenterHistoryId } : {}) };
|
|
20094
|
+
if (detail.previewUrl) {
|
|
20095
|
+
result = { ...result, ...fields, previewUrl: detail.previewUrl, previewReady: true };
|
|
20096
|
+
break;
|
|
20097
|
+
}
|
|
20098
|
+
if (Object.entries(fields).some(([key, value]) => result[key] !== value))
|
|
20099
|
+
emit('waiting_preview', fields);
|
|
20100
|
+
await deps.sleep(pollMs, controller.signal);
|
|
20101
|
+
}
|
|
20102
|
+
}
|
|
19991
20103
|
await page.update({
|
|
19992
20104
|
...pageGame,
|
|
19993
20105
|
language: getCurrentLanguage(),
|
|
19994
|
-
phase: 'complete',
|
|
20106
|
+
phase: result.previewReady ? 'preview' : 'complete',
|
|
20107
|
+
qrUrl: result.simulation ? undefined : authorizationUrl,
|
|
20108
|
+
purpose: result.simulation ? undefined : 'authorization_binding',
|
|
19995
20109
|
simulation: result.simulation,
|
|
19996
20110
|
message: text('uploaded', result.simulation),
|
|
19997
20111
|
});
|
|
19998
20112
|
page.detach();
|
|
19999
20113
|
detached = true;
|
|
20000
|
-
return emit('uploaded', { terminal: true, nextAction: 'none'
|
|
20114
|
+
return emit('uploaded', { terminal: true, nextAction: result.previewReady ? 'scan_qr' : 'none',
|
|
20115
|
+
qrPurpose: result.previewReady ? 'authorization_binding' : undefined,
|
|
20116
|
+
...(result.previewReady ? { message: getCurrentLanguage() === 'zh-CN'
|
|
20117
|
+
? '上传完成。原绑定二维码已保留,可使用 TikTok 再次扫码打开绑定页面。' : 'Upload complete. The original binding QR is available to reopen the binding page with TikTok.' } : {}) });
|
|
20001
20118
|
}
|
|
20002
20119
|
catch (error) {
|
|
20003
20120
|
const code = timedOut
|
|
20004
|
-
? 'VIBE_TIMEOUT'
|
|
20121
|
+
? result.uploaded ? 'VIBE_PREVIEW_TIMEOUT' : 'VIBE_TIMEOUT'
|
|
20005
20122
|
: controller.signal.aborted
|
|
20006
20123
|
? 'VIBE_CANCELLED'
|
|
20007
20124
|
: error instanceof VibeError
|
|
@@ -20090,14 +20207,14 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20090
20207
|
};
|
|
20091
20208
|
const message = result.uploaded
|
|
20092
20209
|
? getCurrentLanguage() === 'zh-CN'
|
|
20093
|
-
? '
|
|
20094
|
-
:
|
|
20210
|
+
? `游戏内容已上传,但${code === 'VIBE_PREVIEW_TIMEOUT' ? '等待预览超时' : code === 'VIBE_CANCELLED' ? '已停止等待预览' : '预览查询或页面显示未完成'}。请核对任务,请勿重复上传。`
|
|
20211
|
+
: `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.`
|
|
20095
20212
|
: messages[code]?.[getCurrentLanguage() === 'zh-CN' ? 0 : 1] ||
|
|
20096
20213
|
text(status);
|
|
20097
20214
|
controller.abort();
|
|
20098
20215
|
await progressUpdates;
|
|
20099
20216
|
// Remove the binding QR on failure too. Keep a readable terminal result page.
|
|
20100
|
-
if (page && code !== 'VIBE_CANCELLED') {
|
|
20217
|
+
if (page && (code !== 'VIBE_CANCELLED' || result.uploaded)) {
|
|
20101
20218
|
try {
|
|
20102
20219
|
await page.update({
|
|
20103
20220
|
...pageMetadata,
|
|
@@ -20106,7 +20223,11 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20106
20223
|
simulation: result.simulation,
|
|
20107
20224
|
hybrid: result.backend === 'ppe-mock',
|
|
20108
20225
|
phase: result.uploaded ? 'complete' : 'failed',
|
|
20109
|
-
message
|
|
20226
|
+
message: result.uploaded && !result.simulation
|
|
20227
|
+
? getCurrentLanguage() === 'zh-CN'
|
|
20228
|
+
? '本次游戏代码包已上传完成,可在手机端查看预览版本并提交审核。'
|
|
20229
|
+
: 'Your game package has been uploaded. You can view the preview version and submit it for review on your phone.'
|
|
20230
|
+
: message,
|
|
20110
20231
|
});
|
|
20111
20232
|
page.detach();
|
|
20112
20233
|
detached = true;
|
|
@@ -20117,7 +20238,7 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20117
20238
|
}
|
|
20118
20239
|
return emit(status, {
|
|
20119
20240
|
terminal: true,
|
|
20120
|
-
nextAction: result.uploaded
|
|
20241
|
+
nextAction: result.uploaded ? 'check_task' : code === 'VIBE_CANCELLED'
|
|
20121
20242
|
? 'none'
|
|
20122
20243
|
: result.uploadAttempted ||
|
|
20123
20244
|
(result.taskId &&
|
|
@@ -20141,6 +20262,10 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20141
20262
|
});
|
|
20142
20263
|
}
|
|
20143
20264
|
finally {
|
|
20265
|
+
if (task) {
|
|
20266
|
+
task.importToken = '';
|
|
20267
|
+
task.landingUrl = '';
|
|
20268
|
+
}
|
|
20144
20269
|
clearTimeout(timeout);
|
|
20145
20270
|
externalSignal?.removeEventListener('abort', cancel);
|
|
20146
20271
|
backend?.dispose?.();
|