@ttmg/cli 0.4.6-vibe-beta.14 → 0.4.6-vibe-beta.16
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 +74 -29
- package/dist/index.js +414 -121
- package/dist/index.js.map +1 -1
- package/dist/package.json +2 -2
- package/dist/scripts/verify-agent-contract.js +16 -2
- package/dist/scripts/vibe-upload-smoke.js +60 -20
- package/dist/scripts/wasm-collect-smoke.js +78 -0
- package/dist/scripts/wasm-collect-test-preload.cjs +13 -0
- package/package.json +2 -2
- package/vibe-upload.md +97 -245
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttmg/cli",
|
|
3
|
-
"version": "0.4.6-vibe-beta.
|
|
3
|
+
"version": "0.4.6-vibe-beta.16",
|
|
4
4
|
"description": "TikTok Mini Game Command Line Tool",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"bin": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"test:check-ai": "node scripts/run-ts-test.js src/commands/native/check/ai.test.ts",
|
|
49
49
|
"test:build-structured": "node scripts/run-ts-test.js src/commands/native/build/build.test.ts",
|
|
50
50
|
"test:doctor": "node scripts/run-ts-test.js src/commands/native/doctor/doctor.test.ts",
|
|
51
|
-
"test:wasm": "node scripts/run-ts-test.js src/commands/native/wasm/wasm.test.ts && node scripts/acceptance-fixture.test.js",
|
|
51
|
+
"test:wasm": "node scripts/run-ts-test.js src/commands/native/wasm/wasm.test.ts && node scripts/run-ts-test.js src/commands/native/wasm/collect.test.ts && node scripts/acceptance-fixture.test.js",
|
|
52
52
|
"test:agent-contract": "npm run test:login && npm run test:auth && npm run test:capabilities && npm run test:test-account && npm run test:upload-preview && npm run test:preview-page && npm run test:check && npm run test:check-ai && npm run test:build-structured && npm run test:doctor && npm run test:wasm && npm run test:dev-session && npm run test:dev-session-command && npm run test:dev-runtime-event && npm run test:dev-runtime-client && npm run test:dev-session-control && npm run test:dev-runtime-session-follow && npm run test:dev-client-preview-orchestrator && npm run test:dev-watch-change && npm run test:network && npm run test:vibe && npx tsc --noEmit && npm run build && npm run test:vibe-process && npm run test:preview-page-process && npm run test:preview-page-detached && npm run verify:dist-version && npm run verify:agent-contract",
|
|
53
53
|
"test:agent-contract:full": "npm run test:agent-contract && npm run test:dev-default-process && npm run test:dev-game-ready-process && npm run test:dev-client-preview-control-process && node scripts/dev-workflow-smoke.js",
|
|
54
54
|
"publish:beta": "npm publish --registry=https://registry.npmjs.org/ --access public --tag beta",
|
|
@@ -133,7 +133,8 @@ assert.equal(categoryCatalog.platformWrite, false);
|
|
|
133
133
|
assert.equal(categoryCatalog.categoryCount, 12);
|
|
134
134
|
assert.equal(categoryCatalog.subcategoryCount, 114);
|
|
135
135
|
assert.equal(capabilities.commands.gameCategories.catalogVersion, categoryCatalog.catalogVersion);
|
|
136
|
-
assert.match(capabilities.commands.vibeUpload.command, /--vibe --
|
|
136
|
+
assert.match(capabilities.commands.vibeUpload.command, /--vibe --dir/);
|
|
137
|
+
assert.equal(capabilities.commands.vibeUpload.webAuthorization.available, false);
|
|
137
138
|
assert.equal(capabilities.commands.vibeUpload.skillWorkflow.keepUploadProcessRunning, true);
|
|
138
139
|
assert.equal(capabilities.commands.vibeUpload.skillWorkflow.realUploadSuccess.simulation, false);
|
|
139
140
|
assert.equal(capabilities.commands.vibeUpload.appInfoFields.defaultedByCli.includes('privacy_policy'), true);
|
|
@@ -400,10 +401,12 @@ const uploadHelp = runCli(['upload', '--help']);
|
|
|
400
401
|
assert.doesNotMatch(uploadHelp.stdout, /--app-info/);
|
|
401
402
|
assert.match(uploadHelp.stdout.replace(/\s+/g, ' '), /only title and icon|只收集标题和头像/);
|
|
402
403
|
assertExit(uploadHelp, 0, 'upload help');
|
|
403
|
-
assert.match(uploadHelp.stdout, /--vibe --
|
|
404
|
+
assert.match(uploadHelp.stdout, /--vibe --dir/);
|
|
404
405
|
assert.match(uploadHelp.stdout, /authorizationOpenCommand/);
|
|
405
406
|
assert.match(uploadHelp.stdout, /simulation=false/);
|
|
406
407
|
assert.match(uploadHelp.stdout, /--category <category>/);
|
|
408
|
+
assert.match(uploadHelp.stdout, /--description <description>/);
|
|
409
|
+
assert.match(uploadHelp.stdout, /Discover the fun of TikTok Mini Games\./);
|
|
407
410
|
assert.match(uploadHelp.stdout, /--subcategory <subcategory>/);
|
|
408
411
|
assert.match(uploadHelp.stdout, /--preview\b/);
|
|
409
412
|
assert.match(uploadHelp.stdout, /--serve-preview-page\b/);
|
|
@@ -451,6 +454,17 @@ const wasmSmoke = spawnSync(process.execPath, [path.join(cliRoot, 'scripts/wasm-
|
|
|
451
454
|
});
|
|
452
455
|
assertExit(wasmSmoke, 0, 'Wasm built-artifact process smoke');
|
|
453
456
|
console.log(wasmSmoke.stdout.trim());
|
|
457
|
+
for (const operation of ['start', 'export']) {
|
|
458
|
+
assertExit(runWithoutWasmBinding(['wasm', 'collect', operation, '--help']), 0, `collect ${operation} help without binding`);
|
|
459
|
+
const invalid = runCli(['wasm', 'collect', operation, '--format', 'json']);
|
|
460
|
+
assertExit(invalid, 1, `collect ${operation} missing input`);
|
|
461
|
+
assert.equal(parseSingleJson(invalid, `collect ${operation}`).errorCode, 'WASM_COLLECT_INVALID_OPTIONS');
|
|
462
|
+
}
|
|
463
|
+
const collectSmoke = spawnSync(process.execPath, [path.join(cliRoot, 'scripts/wasm-collect-smoke.js')], {
|
|
464
|
+
cwd: cliRoot, encoding: 'utf8', timeout: 60000,
|
|
465
|
+
});
|
|
466
|
+
assertExit(collectSmoke, 0, 'Wasm collection built-artifact process smoke');
|
|
467
|
+
console.log(collectSmoke.stdout.trim());
|
|
454
468
|
const captureHelp = runCli(['dev', 'capture', '--help']);
|
|
455
469
|
assertExit(captureHelp, 0, 'capture help');
|
|
456
470
|
assert.match(captureHelp.stdout, /--timeout/);
|
|
@@ -31,6 +31,7 @@ let webMode = false;
|
|
|
31
31
|
let webPolls = 0;
|
|
32
32
|
let webFailureEndpoint;
|
|
33
33
|
let expectedWebCategory = { category: 'Other', subcategory: 'Other' };
|
|
34
|
+
let expectedWebDescription = 'Discover the fun of TikTok Mini Games.';
|
|
34
35
|
const env = {
|
|
35
36
|
...process.env,
|
|
36
37
|
HOME: temp,
|
|
@@ -144,10 +145,10 @@ async function main() {
|
|
|
144
145
|
}
|
|
145
146
|
const launches = fs.readFileSync(browserReceipt, 'utf8').trim().split('\n').map(JSON.parse);
|
|
146
147
|
assert.equal(launches.length, 2);
|
|
147
|
-
assert.ok(launches.every(item => item.args.includes(ready.url
|
|
148
|
+
assert.ok(launches.every(item => item.args.includes(ready.url)));
|
|
148
149
|
const redirect = await fetch(ready.url + '/authorize', {redirect: 'manual'});
|
|
149
|
-
assert.equal(redirect.headers.get('location'),
|
|
150
|
-
assert.equal(redirect.status,
|
|
150
|
+
assert.equal(redirect.headers.get('location'), null);
|
|
151
|
+
assert.equal(redirect.status, 410);
|
|
151
152
|
rendered = workerMessage(message => message.type === 'rendered' && message.id === 2);
|
|
152
153
|
authWorker.send({type: 'render', id: 2, html: '<h1>Complete</h1>'});
|
|
153
154
|
await rendered;
|
|
@@ -194,7 +195,7 @@ async function main() {
|
|
|
194
195
|
assert.equal(info.app_info.icon_uri, 'web-icon-receipt');
|
|
195
196
|
assert.equal(info.app_info.app_name, input[input.indexOf('--title') + 1]);
|
|
196
197
|
assert.deepEqual(info.app_info, {
|
|
197
|
-
description:
|
|
198
|
+
description: expectedWebDescription,
|
|
198
199
|
...expectedWebCategory,
|
|
199
200
|
terms_of_service: 'https://www.tiktok.com',
|
|
200
201
|
privacy_policy: 'https://www.tiktok.com',
|
|
@@ -265,6 +266,19 @@ async function main() {
|
|
|
265
266
|
assert.equal(events(completeDry)[0].classification.category, 'Casual');
|
|
266
267
|
assert.equal(events(completeDry)[0].classification.subcategory, 'Puzzle');
|
|
267
268
|
assert.equal(events(completeDry)[0].classification.source, 'app-info');
|
|
269
|
+
assert.equal(events(completeDry)[0].description, 'Process contract fixture');
|
|
270
|
+
assert.equal(events(completeDry)[0].descriptionSource, 'app-info');
|
|
271
|
+
const describedDry = await run([...input, '--description', ' Stack cats 🐈 ', '--dry-run', '--format', 'json']);
|
|
272
|
+
assert.equal(describedDry.code, 0, describedDry.stderr);
|
|
273
|
+
assert.equal(events(describedDry)[0].description, 'Stack cats 🐈');
|
|
274
|
+
assert.equal(events(describedDry)[0].descriptionSource, 'arguments');
|
|
275
|
+
assert.equal(events(describedDry)[0].classification.source, 'defaults');
|
|
276
|
+
assert.equal(events(describedDry)[0].platformWrite, false);
|
|
277
|
+
const descriptionOverrideDry = await run([...input, '--app-info', appInfoPath, '--description', 'New description', '--dry-run', '--format', 'json']);
|
|
278
|
+
assert.equal(descriptionOverrideDry.code, 0, descriptionOverrideDry.stderr);
|
|
279
|
+
assert.equal(events(descriptionOverrideDry)[0].description, 'New description');
|
|
280
|
+
assert.equal(events(descriptionOverrideDry)[0].descriptionSource, 'arguments');
|
|
281
|
+
assert.equal(events(descriptionOverrideDry)[0].classification.source, 'app-info');
|
|
268
282
|
const overrideDry = await run([...input, '--app-info', appInfoPath, '--category', 'Puzzle', '--subcategory', 'Physics', '--dry-run', '--format', 'json']);
|
|
269
283
|
assert.equal(overrideDry.code, 0, overrideDry.stderr);
|
|
270
284
|
assert.equal(events(overrideDry)[0].classification.category, 'Puzzle');
|
|
@@ -375,18 +389,20 @@ async function main() {
|
|
|
375
389
|
|
|
376
390
|
const beforeWeb = requests.length;
|
|
377
391
|
const webConflict = await run([...input, '--web', '--mock', '--format', 'json']);
|
|
378
|
-
assert.equal(events(webConflict)[0].errorCode, '
|
|
392
|
+
assert.equal(events(webConflict)[0].errorCode, 'VIBE_WEB_DISABLED');
|
|
379
393
|
const legacyWeb = await run(['upload', '--web', '--format', 'json']);
|
|
380
394
|
assert.equal(events(legacyWeb)[0].errorCode, 'VIBE_OPTION_REQUIRES_VIBE');
|
|
381
395
|
assert.equal(requests.length, beforeWeb);
|
|
382
396
|
for (const [args, code] of [
|
|
397
|
+
[['--description', ''], 'VIBE_INVALID_DESCRIPTION'],
|
|
398
|
+
[['--description', ' \n '], 'VIBE_INVALID_DESCRIPTION'],
|
|
383
399
|
[['--category', 'Puzzle'], 'VIBE_CATEGORY_PAIR_REQUIRED'],
|
|
384
400
|
[['--subcategory', 'Physics'], 'VIBE_CATEGORY_PAIR_REQUIRED'],
|
|
385
401
|
[['--category', 'Other', '--subcategory', 'Physics'], 'VIBE_INVALID_CATEGORY'],
|
|
386
402
|
[['--category', 'Unknown', '--subcategory', 'Other'], 'VIBE_INVALID_CATEGORY'],
|
|
387
403
|
[['--category', '', '--subcategory', ''], 'VIBE_INVALID_CATEGORY'],
|
|
388
404
|
]) {
|
|
389
|
-
const invalid = await run([...input
|
|
405
|
+
const invalid = await run([...input, ...args, '--format', 'json']);
|
|
390
406
|
assert.equal(invalid.code, 1);
|
|
391
407
|
const result = events(invalid)[0];
|
|
392
408
|
assert.equal(result.errorCode, code);
|
|
@@ -394,35 +410,48 @@ async function main() {
|
|
|
394
410
|
assert.equal(result.taskId, undefined);
|
|
395
411
|
assert.equal(result.pageUrl, undefined);
|
|
396
412
|
}
|
|
397
|
-
assert.equal(requests.length, beforeWeb, 'Invalid
|
|
413
|
+
assert.equal(requests.length, beforeWeb, 'Invalid metadata must not create tasks or upload');
|
|
398
414
|
webMode = true;
|
|
415
|
+
for (const authorizationMode of ['qr']) {
|
|
399
416
|
for (const format of ['json', 'ndjson']) {
|
|
417
|
+
expectedWebDescription = format === 'json' ? 'Discover the fun of TikTok Mini Games.' : 'Stack cats on a rooftop 🐈';
|
|
418
|
+
const descriptionArgs = format === 'json' ? [] : ['--description', expectedWebDescription];
|
|
400
419
|
expectedWebCategory = format === 'json' ? {category: 'Other', subcategory: 'Other'} : {category: 'Puzzle', subcategory: 'Physics'};
|
|
401
420
|
const categoryArgs = format === 'json' ? [] : ['--category', 'Puzzle', '--subcategory', 'Physics'];
|
|
402
|
-
const receipt = path.join(temp, `upload
|
|
421
|
+
const receipt = path.join(temp, `upload-${authorizationMode}-${format}.ndjson`);
|
|
403
422
|
const requestStart = requests.length;
|
|
404
|
-
const web = await run([...input.filter(v => v !== '--no-open'), '--web', ...categoryArgs, '--format', format, '--poll-interval', '0.2'], async event => {
|
|
423
|
+
const web = await run([...input.filter(v => v !== '--no-open'), ...(authorizationMode === 'web' ? ['--web'] : []), ...categoryArgs, ...descriptionArgs, '--format', format, '--poll-interval', '0.2'], async event => {
|
|
424
|
+
if (event.status === 'prepared') assert.equal(event.description, expectedWebDescription);
|
|
405
425
|
if (event.status === 'prepared') assert.deepEqual(
|
|
406
426
|
{category: event.classification.category, subcategory: event.classification.subcategory},
|
|
407
427
|
expectedWebCategory);
|
|
408
428
|
if (event.status === 'waiting_authorization') {
|
|
409
|
-
assert.equal(event.authorizationMode,
|
|
429
|
+
assert.equal(event.authorizationMode, authorizationMode);
|
|
410
430
|
assert.equal(event.browserOpenRequested, true);
|
|
411
431
|
assert.equal(event.simulation, false);
|
|
412
|
-
assert.equal(event.nextAction, 'open_browser');
|
|
413
|
-
assert.equal(event.qrPurpose, undefined);
|
|
432
|
+
assert.equal(event.nextAction, authorizationMode === 'web' ? 'open_browser' : 'scan_qr');
|
|
433
|
+
assert.equal(event.qrPurpose, authorizationMode === 'web' ? undefined : 'authorization_binding');
|
|
414
434
|
const response = await fetch(event.pageUrl + '/authorize', {redirect: 'manual'});
|
|
415
|
-
assert.equal(response.status,
|
|
416
|
-
assert.
|
|
435
|
+
assert.equal(response.status, 410);
|
|
436
|
+
assert.equal(response.headers.get('location'), null);
|
|
417
437
|
const html = await (await fetch(event.pageUrl)).text();
|
|
418
|
-
assert.
|
|
419
|
-
assert.
|
|
438
|
+
assert.ok(html.includes(expectedWebDescription));
|
|
439
|
+
assert.ok(html.includes(expectedWebCategory.category));
|
|
440
|
+
assert.ok(html.includes(expectedWebCategory.subcategory));
|
|
441
|
+
assert.doesNotMatch(html, /<header class="brand"/);
|
|
442
|
+
if (authorizationMode === 'web') {
|
|
443
|
+
assert.match(html, /Authorize in your browser/);
|
|
444
|
+
assert.doesNotMatch(html, /alt="Authorization QR"|Scan the QR code/);
|
|
445
|
+
} else {
|
|
446
|
+
assert.match(html, /alt="Authorization QR"/);
|
|
447
|
+
assert.match(html, /data:image\/png;base64/);
|
|
448
|
+
}
|
|
420
449
|
}
|
|
421
450
|
}, {
|
|
422
451
|
NODE_OPTIONS: env.NODE_OPTIONS + ' --require=' + path.join(__dirname, 'vibe-auth-open-test-preload.cjs'),
|
|
423
452
|
TTMG_AUTH_OPEN_TEST_RECEIPT: receipt,
|
|
424
453
|
// Explicit --web must not inherit an old Mock backend setting.
|
|
425
|
-
TTMG_VIBE_BACKEND: 'mock',
|
|
454
|
+
TTMG_VIBE_BACKEND: authorizationMode === 'web' ? 'mock' : '',
|
|
426
455
|
TTMG_VIBE_TEST_RETAIN_UPLOAD_SOCKET: '1',
|
|
427
456
|
});
|
|
428
457
|
assert.equal(web.code, 0, web.stderr);
|
|
@@ -437,6 +466,8 @@ async function main() {
|
|
|
437
466
|
{ category: output.at(-1).classification.category, subcategory: output.at(-1).classification.subcategory },
|
|
438
467
|
expectedWebCategory);
|
|
439
468
|
assert.equal(output.at(-1).classification.source, format === 'json' ? 'defaults' : 'arguments');
|
|
469
|
+
assert.equal(output.at(-1).description, expectedWebDescription);
|
|
470
|
+
assert.equal(output.at(-1).descriptionSource, format === 'json' ? 'defaults' : 'arguments');
|
|
440
471
|
assert.equal(output.at(-1).taskStatusRaw, 3);
|
|
441
472
|
assert.equal(output.filter(e => e.terminal).length, 1);
|
|
442
473
|
assert.equal(webPolls, 5, 'Only true backend readiness unlocks authorization');
|
|
@@ -447,13 +478,17 @@ async function main() {
|
|
|
447
478
|
if (format === 'json') assert.equal(output.length, 1);
|
|
448
479
|
const opens = fs.readFileSync(receipt, 'utf8').trim().split('\n').map(line => JSON.parse(line));
|
|
449
480
|
assert.equal(opens.length, 1);
|
|
450
|
-
assert.ok(opens[0].args.some(arg =>
|
|
481
|
+
assert.ok(opens[0].args.some(arg => (authorizationMode === 'web'
|
|
482
|
+
? /^http:\/\/127\.0\.0\.1:\d+\/[a-f0-9]{48}\/authorize$/
|
|
483
|
+
: /^http:\/\/127\.0\.0\.1:\d+\/[a-f0-9]{48}$/).test(arg)));
|
|
451
484
|
assert.doesNotMatch(JSON.stringify(opens) + web.stdout + web.stderr, /process-test-secret|import_token|web-icon-receipt|web-package-receipt|aweme:\/\//);
|
|
452
485
|
}
|
|
486
|
+
}
|
|
453
487
|
expectedWebCategory = {category: 'Other', subcategory: 'Other'};
|
|
488
|
+
expectedWebDescription = 'Discover the fun of TikTok Mini Games.';
|
|
454
489
|
webFailureEndpoint = '/bff/app_info/minis_import/submit_minis_import_info_for_agent/';
|
|
455
490
|
const failedStart = requests.length;
|
|
456
|
-
const webUploadFailure = await run([...input.filter(v => v !== '--no-open'), '--
|
|
491
|
+
const webUploadFailure = await run([...input.filter(v => v !== '--no-open'), '--format', 'json'], undefined, {
|
|
457
492
|
NODE_OPTIONS: env.NODE_OPTIONS + ' --require=' + path.join(__dirname, 'vibe-auth-open-test-preload.cjs'),
|
|
458
493
|
TTMG_AUTH_OPEN_TEST_RECEIPT: path.join(temp, 'web-failure-open.ndjson'),
|
|
459
494
|
TTMG_VIBE_TEST_RETAIN_UPLOAD_SOCKET: '1',
|
|
@@ -578,7 +613,9 @@ async function main() {
|
|
|
578
613
|
assert.equal(events(projectRun)[0].status, 'prepared');
|
|
579
614
|
assert.ok(events(projectRun)[0].packageSizeBytes > 0);
|
|
580
615
|
|
|
581
|
-
const missingBackend = await run([...input, '--format', 'json']
|
|
616
|
+
const missingBackend = await run([...input, '--format', 'json'], undefined, {
|
|
617
|
+
TTMG_VIBE_BACKEND: 'unsupported',
|
|
618
|
+
});
|
|
582
619
|
assert.equal(missingBackend.code, 1);
|
|
583
620
|
assert.equal(
|
|
584
621
|
events(missingBackend)[0].errorCode,
|
|
@@ -588,6 +625,9 @@ async function main() {
|
|
|
588
625
|
assert.equal(badMode.code, 1);
|
|
589
626
|
assert.equal(events(badMode)[0].errorCode, 'VIBE_OPTION_REQUIRES_VIBE');
|
|
590
627
|
const badCategoryMode = await run(['upload', '--category', 'Puzzle', '--subcategory', 'Physics', '--format', 'json']);
|
|
628
|
+
const badDescriptionMode = await run(['upload', '--description', 'Game description', '--format', 'json']);
|
|
629
|
+
assert.equal(badDescriptionMode.code, 1);
|
|
630
|
+
assert.equal(events(badDescriptionMode)[0].errorCode, 'VIBE_OPTION_REQUIRES_VIBE');
|
|
591
631
|
assert.equal(events(badCategoryMode)[0].errorCode, 'VIBE_OPTION_REQUIRES_VIBE');
|
|
592
632
|
const beforeCatalog = requests.length;
|
|
593
633
|
const catalog = await run(['game', 'categories', '--format', 'json']);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const crypto = require('node:crypto');
|
|
6
|
+
const http = require('node:http');
|
|
7
|
+
const { spawn } = require('node:child_process');
|
|
8
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ttmg-collect-process-'));
|
|
9
|
+
const entry = path.resolve(__dirname, '../dist/index.js');
|
|
10
|
+
const preload = path.resolve(__dirname, 'wasm-collect-test-preload.cjs');
|
|
11
|
+
const input = path.join(root, 'original.wasm');
|
|
12
|
+
const wasm = Buffer.from('0061736d010000000105016000017f0304030000000404017000030707010372756e00000909010041000b030001020a1003040010010b0400412a0b040041070b', 'hex');
|
|
13
|
+
fs.writeFileSync(input, wasm);
|
|
14
|
+
const md5 = crypto.createHash('md5').update(wasm).digest('hex');
|
|
15
|
+
const rc = path.join(root, 'rc.json');
|
|
16
|
+
const secret = 'sessionid=TEST_ONLY_COLLECT_SECRET';
|
|
17
|
+
fs.writeFileSync(rc, JSON.stringify({ cookie: secret }));
|
|
18
|
+
let mode = 'success'; const calls = [];
|
|
19
|
+
const server = http.createServer(async (req, res) => {
|
|
20
|
+
let body = ''; for await (const b of req) body += b;
|
|
21
|
+
calls.push({ url: req.url, method: req.method, body: body ? JSON.parse(body) : null, cookie: req.headers.cookie });
|
|
22
|
+
res.setHeader('content-type', 'application/json');
|
|
23
|
+
const data = mode === 'auth' ? { code: -401, message: secret }
|
|
24
|
+
: req.url.includes('/start') ? { code: 0, collect_state: 'open', cookie: secret }
|
|
25
|
+
: { code: 0, func_ids: [1, 0, 1], boot_func_ids: [0], cookie: secret };
|
|
26
|
+
res.end(JSON.stringify(data));
|
|
27
|
+
});
|
|
28
|
+
async function run(args) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const child = spawn(process.execPath, ['-r', preload, entry, ...args], { cwd: root,
|
|
31
|
+
env: { ...process.env, TTMGRC_PATH: rc, TTMG_COLLECT_TEST_PORT: String(server.address().port),
|
|
32
|
+
HTTP_PROXY: '', HTTPS_PROXY: '', ALL_PROXY: '', http_proxy: '', https_proxy: '', all_proxy: '',
|
|
33
|
+
NO_COLOR: '1', FORCE_COLOR: '0' } });
|
|
34
|
+
let stdout = '', stderr = '';
|
|
35
|
+
child.stdout.on('data', b => stdout += b); child.stderr.on('data', b => stderr += b);
|
|
36
|
+
const timer = setTimeout(() => { child.kill('SIGKILL'); reject(Error('CLI timeout')); }, 15000);
|
|
37
|
+
child.on('error', reject); child.on('close', code => { clearTimeout(timer); resolve({ code, stdout, stderr }); });
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function result(r, code = 0) {
|
|
41
|
+
assert.equal(r.code, code, r.stderr || r.stdout);
|
|
42
|
+
assert.equal(r.stdout.trim().split('\n').length, 1, r.stdout);
|
|
43
|
+
assert.equal((r.stdout+r.stderr).includes(secret), false);
|
|
44
|
+
return JSON.parse(r.stdout);
|
|
45
|
+
}
|
|
46
|
+
(async () => {
|
|
47
|
+
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
48
|
+
const args = ['--client-key','fixture','--wasm-md5',md5,'--format','json'];
|
|
49
|
+
const a = result(await run(['wasm','collect','start', ...args, '--verbose']));
|
|
50
|
+
assert.equal(a.collectState,'open'); assert.equal(calls[0].cookie,secret);
|
|
51
|
+
assert.equal(calls[0].body.reset,false);
|
|
52
|
+
result(await run(['wasm','collect','start',...args,'--reset']));
|
|
53
|
+
assert.equal(calls[1].body.reset,true);
|
|
54
|
+
const beforeDefault = calls.length;
|
|
55
|
+
const defaultJson = result(await run(['--verbose', 'wasm','collect','start','--client-key','fixture','--wasm-md5',md5]));
|
|
56
|
+
assert.equal(defaultJson.collectState,'open');assert.equal(calls.length,beforeDefault+1);
|
|
57
|
+
const output=path.join(root,'collection.json');
|
|
58
|
+
const exported=result(await run(['wasm','collect','export',...args,'--input',input,'--output',output,'--verbose']));
|
|
59
|
+
assert.equal(exported.collection.path,output);assert.equal(calls[3].method,'GET');
|
|
60
|
+
assert.equal(calls[3].cookie,secret);assert.match(calls[3].url,/strategy=union/);
|
|
61
|
+
const collection=JSON.parse(fs.readFileSync(output,'utf8'));
|
|
62
|
+
assert.deepEqual(collection.funcIds,[0,1]);assert.deepEqual(collection.bootFuncIds,[0]);
|
|
63
|
+
assert.equal(fs.readFileSync(output,'utf8').includes(secret),false);
|
|
64
|
+
const split=result(await run(['wasm','split','--input',input,'--collection',output,
|
|
65
|
+
'--output',path.join(root,'split'),'--dry-run','--format','json']));
|
|
66
|
+
assert.equal(split.status,'ready');
|
|
67
|
+
mode='auth';
|
|
68
|
+
assert.equal(result(await run(['wasm','collect','start',...args,'--verbose']),1).errorCode,'WASM_AUTH_REQUIRED');
|
|
69
|
+
const count=calls.length;fs.writeFileSync(rc,'{}');
|
|
70
|
+
assert.equal(result(await run(['wasm','collect','start',...args]),1).errorCode,'WASM_AUTH_REQUIRED');
|
|
71
|
+
assert.equal(calls.length,count);
|
|
72
|
+
const unknown=await run(['wasm','collect','export',...args,'--reset']);assert.notEqual(unknown.code,0);
|
|
73
|
+
assert.equal(calls.length,count);
|
|
74
|
+
assert.deepEqual(fs.readFileSync(input),wasm);
|
|
75
|
+
console.log('Wasm collect process contract passed: local login transport, reset, export-to-split, clean verbose output; fixture only');
|
|
76
|
+
})().catch(e => { console.error(e); process.exitCode=1; }).finally(() => {
|
|
77
|
+
server.closeAllConnections();server.close();fs.rmSync(root,{recursive:true,force:true});
|
|
78
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Test-only transport: credentials and platform responses are synthetic.
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const https = require('node:https');
|
|
4
|
+
const http = require('node:http');
|
|
5
|
+
const port = Number(process.env.TTMG_COLLECT_TEST_PORT);
|
|
6
|
+
assert.ok(Number.isInteger(port) && port > 0 && port < 65536);
|
|
7
|
+
https.request = (options, callback) => {
|
|
8
|
+
assert.equal(options.hostname, 'developers.tiktok.com');
|
|
9
|
+
assert.match(options.path, /^\/api\/wasm-collect\/v1\/(start|export)(\?|$)/);
|
|
10
|
+
return http.request({ ...options, protocol: 'http:', hostname: '127.0.0.1', host: undefined,
|
|
11
|
+
port, agent: undefined, servername: undefined }, callback);
|
|
12
|
+
};
|
|
13
|
+
https.get = (options, callback) => { const req = https.request(options, callback); req.end(); return req; };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttmg/cli",
|
|
3
|
-
"version": "0.4.6-vibe-beta.
|
|
3
|
+
"version": "0.4.6-vibe-beta.16",
|
|
4
4
|
"description": "TikTok Mini Game Command Line Tool",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"bin": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"test:check-ai": "node scripts/run-ts-test.js src/commands/native/check/ai.test.ts",
|
|
49
49
|
"test:build-structured": "node scripts/run-ts-test.js src/commands/native/build/build.test.ts",
|
|
50
50
|
"test:doctor": "node scripts/run-ts-test.js src/commands/native/doctor/doctor.test.ts",
|
|
51
|
-
"test:wasm": "node scripts/run-ts-test.js src/commands/native/wasm/wasm.test.ts && node scripts/acceptance-fixture.test.js",
|
|
51
|
+
"test:wasm": "node scripts/run-ts-test.js src/commands/native/wasm/wasm.test.ts && node scripts/run-ts-test.js src/commands/native/wasm/collect.test.ts && node scripts/acceptance-fixture.test.js",
|
|
52
52
|
"test:agent-contract": "npm run test:login && npm run test:auth && npm run test:capabilities && npm run test:test-account && npm run test:upload-preview && npm run test:preview-page && npm run test:check && npm run test:check-ai && npm run test:build-structured && npm run test:doctor && npm run test:wasm && npm run test:dev-session && npm run test:dev-session-command && npm run test:dev-runtime-event && npm run test:dev-runtime-client && npm run test:dev-session-control && npm run test:dev-runtime-session-follow && npm run test:dev-client-preview-orchestrator && npm run test:dev-watch-change && npm run test:network && npm run test:vibe && npx tsc --noEmit && npm run build && npm run test:vibe-process && npm run test:preview-page-process && npm run test:preview-page-detached && npm run verify:dist-version && npm run verify:agent-contract",
|
|
53
53
|
"test:agent-contract:full": "npm run test:agent-contract && npm run test:dev-default-process && npm run test:dev-game-ready-process && npm run test:dev-client-preview-control-process && node scripts/dev-workflow-smoke.js",
|
|
54
54
|
"publish:beta": "npm publish --registry=https://registry.npmjs.org/ --access public --tag beta",
|