@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/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttmg/cli",
3
- "version": "0.4.6-vibe-beta.14",
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 --web/);
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 --web --dir/);
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 + '/authorize')));
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'), landing);
150
- assert.equal(redirect.status, 302);
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: 'DevTool PPE integration test fixture. Not a production game.',
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, 'VIBE_WEB_OPTION_CONFLICT');
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.filter(v => v !== '--no-open'), '--web', ...args, '--format', 'json']);
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 classification must not create tasks or upload');
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-web-${format}.ndjson`);
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, 'web');
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, 302);
416
- assert.match(response.headers.get('location'), /^https:\/\/developers\.tiktok\.com\/portal_h5\/minis_import\?/);
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.match(html, /Authorize in your browser/);
419
- assert.doesNotMatch(html, /alt="Authorization QR"|Scan the QR code/);
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 => /^http:\/\/127\.0\.0\.1:\d+\/[a-f0-9]{48}\/authorize$/.test(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'), '--web', '--format', 'json'], undefined, {
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.14",
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",