@ttmg/cli 0.4.6-vibe-beta.5 → 0.4.6-vibe-beta.7
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 +88 -5
- package/dist/index.js +4287 -424
- package/dist/index.js.map +1 -1
- package/dist/package.json +14 -4
- package/dist/scripts/dev-client-preview-control-smoke.js +322 -0
- package/dist/scripts/verify-agent-contract.js +24 -0
- package/dist/scripts/vibe-ppe-test-preload.cjs +23 -0
- package/dist/scripts/vibe-upload-demo.js +72 -0
- package/dist/scripts/vibe-upload-smoke.js +341 -0
- package/package.json +14 -4
- package/vibe-upload.md +183 -0
package/dist/package.json
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttmg/cli",
|
|
3
|
-
"version": "0.4.6-vibe-beta.
|
|
3
|
+
"version": "0.4.6-vibe-beta.7",
|
|
4
4
|
"description": "TikTok Mini Game Command Line Tool",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ttmg": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
10
|
+
"dist",
|
|
11
|
+
"vibe-upload.md"
|
|
11
12
|
],
|
|
12
13
|
"publishConfig": {
|
|
13
14
|
"access": "public",
|
|
14
15
|
"registry": "https://registry.npmjs.org/"
|
|
15
16
|
},
|
|
16
17
|
"scripts": {
|
|
18
|
+
"demo:vibe": "npm run build && node scripts/vibe-upload-demo.js",
|
|
19
|
+
"test:vibe": "node scripts/run-ts-test.js src/commands/common/vibe/vibe.test.ts",
|
|
20
|
+
"test:vibe-process": "node scripts/vibe-upload-smoke.js",
|
|
17
21
|
"prepack": "npm run build && npm run verify:dist-version && npm run verify:agent-contract",
|
|
18
22
|
"build": "node scripts/build.js",
|
|
19
23
|
"verify:dist-version": "node scripts/verify-dist-version.js",
|
|
@@ -30,14 +34,20 @@
|
|
|
30
34
|
"test:dev-session": "node scripts/run-ts-test.js src/commands/native/dev/agentSession.test.ts",
|
|
31
35
|
"test:dev-session-command": "node scripts/run-ts-test.js src/commands/native/dev/session.test.ts",
|
|
32
36
|
"test:dev-runtime-event": "node scripts/run-ts-test.js src/commands/native/dev/utils/scoket.test.ts",
|
|
37
|
+
"test:dev-runtime-client": "node scripts/run-ts-test.js src/commands/native/dev/runtime/runtime.test.ts",
|
|
38
|
+
"test:dev-session-control": "node scripts/run-ts-test.js src/commands/native/dev/runtime/sessionControl.test.ts",
|
|
39
|
+
"test:dev-runtime-session-follow": "node scripts/run-ts-test.js src/commands/native/dev/runtime/runtimeSessionFollow.test.ts",
|
|
40
|
+
"test:dev-client-preview-orchestrator": "node scripts/run-ts-test.js src/commands/native/dev/runtime/clientPreviewOrchestrator.test.ts",
|
|
41
|
+
"test:dev-watch-change": "node scripts/run-ts-test.js src/commands/native/dev/utils/watchChange.test.ts",
|
|
33
42
|
"test:dev-game-ready-process": "npm run build && node scripts/dev-game-ready-smoke.js",
|
|
34
43
|
"test:dev-default-process": "npm run build && node scripts/dev-default-smoke.js",
|
|
44
|
+
"test:dev-client-preview-control-process": "npm run build && node scripts/dev-client-preview-control-smoke.js",
|
|
35
45
|
"test:check": "node scripts/run-ts-test.js src/commands/native/check/check.test.ts",
|
|
36
46
|
"test:check-ai": "node scripts/run-ts-test.js src/commands/native/check/ai.test.ts",
|
|
37
47
|
"test:build-structured": "node scripts/run-ts-test.js src/commands/native/build/build.test.ts",
|
|
38
48
|
"test:doctor": "node scripts/run-ts-test.js src/commands/native/doctor/doctor.test.ts",
|
|
39
|
-
"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:dev-session && npm run test:dev-session-command && npm run test:dev-runtime-event && npm run test:network && npx tsc --noEmit && npm run build && npm run test:preview-page-process && npm run test:preview-page-detached && npm run verify:dist-version && npm run verify:agent-contract",
|
|
40
|
-
"test:agent-contract:full": "npm run test:agent-contract && npm run test:dev-default-process && npm run test:dev-game-ready-process",
|
|
49
|
+
"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: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",
|
|
50
|
+
"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",
|
|
41
51
|
"publish:beta": "npm publish --registry=https://registry.npmjs.org/ --access public --tag beta",
|
|
42
52
|
"setup": "node scripts/setup.js",
|
|
43
53
|
"reset:local-state": "node scripts/resetLocalState.js",
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const net = require('node:net');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawn } = require('node:child_process');
|
|
7
|
+
|
|
8
|
+
function runCli(args, cwd) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const child = spawn(
|
|
11
|
+
process.execPath,
|
|
12
|
+
[path.resolve(__dirname, '../dist/index.js'), ...args],
|
|
13
|
+
{
|
|
14
|
+
cwd,
|
|
15
|
+
env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },
|
|
16
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
let stdout = '';
|
|
20
|
+
let stderr = '';
|
|
21
|
+
child.stdout.on('data', chunk => {
|
|
22
|
+
stdout += chunk;
|
|
23
|
+
});
|
|
24
|
+
child.stderr.on('data', chunk => {
|
|
25
|
+
stderr += chunk;
|
|
26
|
+
});
|
|
27
|
+
child.once('error', reject);
|
|
28
|
+
child.once('close', status => resolve({ status, stdout, stderr }));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function write(socket, value) {
|
|
33
|
+
socket.write(`${JSON.stringify(value)}\n`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function run() {
|
|
37
|
+
const shortTmp = process.platform === 'win32' ? os.tmpdir() : '/tmp';
|
|
38
|
+
const root = fs.mkdtempSync(path.join(shortTmp, 'ttmg-preview-control-'));
|
|
39
|
+
const gameDirectory = path.join(root, 'game');
|
|
40
|
+
const previewGameDirectory = path.join(
|
|
41
|
+
root,
|
|
42
|
+
`client-preview-project-${'x'.repeat(80)}`,
|
|
43
|
+
);
|
|
44
|
+
const sessionId = 'dev_control_smoke';
|
|
45
|
+
const sessionDirectory = path.join(
|
|
46
|
+
gameDirectory,
|
|
47
|
+
'.ttmg',
|
|
48
|
+
'sessions',
|
|
49
|
+
sessionId,
|
|
50
|
+
);
|
|
51
|
+
const screenshotsDirectory = path.join(sessionDirectory, 'screenshots');
|
|
52
|
+
fs.mkdirSync(screenshotsDirectory, { recursive: true });
|
|
53
|
+
fs.mkdirSync(previewGameDirectory, { recursive: true });
|
|
54
|
+
fs.writeFileSync(
|
|
55
|
+
path.join(previewGameDirectory, 'game.js'),
|
|
56
|
+
'console.log("game");\n',
|
|
57
|
+
);
|
|
58
|
+
fs.writeFileSync(path.join(previewGameDirectory, 'game.json'), '{}\n');
|
|
59
|
+
const control =
|
|
60
|
+
process.platform === 'win32'
|
|
61
|
+
? {
|
|
62
|
+
transport: 'named-pipe',
|
|
63
|
+
path: `\\\\.\\pipe\\ttmg-${sessionId}`,
|
|
64
|
+
}
|
|
65
|
+
: {
|
|
66
|
+
transport: 'unix',
|
|
67
|
+
path: path.join(sessionDirectory, 'ipc'),
|
|
68
|
+
};
|
|
69
|
+
fs.writeFileSync(
|
|
70
|
+
path.join(sessionDirectory, 'manifest.json'),
|
|
71
|
+
JSON.stringify({
|
|
72
|
+
schemaVersion: '1',
|
|
73
|
+
command: 'dev',
|
|
74
|
+
platformWrite: false,
|
|
75
|
+
sessionId,
|
|
76
|
+
status: 'ready',
|
|
77
|
+
mode: 'client-preview',
|
|
78
|
+
project: { clientKey: 'smoke-client', directory: gameDirectory },
|
|
79
|
+
process: { pid: process.pid, startedAt: Date.now(), command: 'ttmg dev' },
|
|
80
|
+
runtime: {
|
|
81
|
+
status: 'ready',
|
|
82
|
+
runtimeSessionId: 'runtime-control-smoke',
|
|
83
|
+
protocolVersion: '1.0',
|
|
84
|
+
capabilities: { logs: true, screenshot: true },
|
|
85
|
+
},
|
|
86
|
+
control,
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
const server = net.createServer(socket => {
|
|
90
|
+
let buffer = '';
|
|
91
|
+
socket.on('data', chunk => {
|
|
92
|
+
buffer += chunk.toString();
|
|
93
|
+
if (!buffer.includes('\n')) return;
|
|
94
|
+
const request = JSON.parse(buffer.slice(0, buffer.indexOf('\n')));
|
|
95
|
+
if (request.operation === 'logs.snapshot') {
|
|
96
|
+
write(socket, {
|
|
97
|
+
requestId: request.requestId,
|
|
98
|
+
type: 'result',
|
|
99
|
+
result: {
|
|
100
|
+
records: [
|
|
101
|
+
{
|
|
102
|
+
sequence: '90071992547409930002',
|
|
103
|
+
timestamp_ms: 2,
|
|
104
|
+
severity: 'info',
|
|
105
|
+
kind: 'text',
|
|
106
|
+
source: 'native_runtime',
|
|
107
|
+
message: 'snapshot',
|
|
108
|
+
formatted_line: '[I][native_runtime] snapshot',
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
nextCursor: '90071992547409930002',
|
|
112
|
+
oldestCursor: '90071992547409930001',
|
|
113
|
+
snapshotCursor: '90071992547409930002',
|
|
114
|
+
hasMore: false,
|
|
115
|
+
cursorExpired: false,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
socket.end();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (request.operation === 'logs.follow') {
|
|
122
|
+
write(socket, {
|
|
123
|
+
requestId: request.requestId,
|
|
124
|
+
type: 'result',
|
|
125
|
+
result: {
|
|
126
|
+
records: [],
|
|
127
|
+
nextCursor: request.params.after,
|
|
128
|
+
oldestCursor: '1',
|
|
129
|
+
snapshotCursor: request.params.after,
|
|
130
|
+
hasMore: false,
|
|
131
|
+
cursorExpired: false,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
write(socket, {
|
|
135
|
+
requestId: request.requestId,
|
|
136
|
+
type: 'event',
|
|
137
|
+
records: [
|
|
138
|
+
{
|
|
139
|
+
sequence: '90071992547409930003',
|
|
140
|
+
timestamp_ms: 3,
|
|
141
|
+
severity: 'error',
|
|
142
|
+
kind: 'text',
|
|
143
|
+
source: 'structured_trace',
|
|
144
|
+
message: 'live',
|
|
145
|
+
formatted_line: '[E][structured_trace] live',
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
cursor: '90071992547409930003',
|
|
149
|
+
});
|
|
150
|
+
write(socket, {
|
|
151
|
+
requestId: request.requestId,
|
|
152
|
+
type: 'end',
|
|
153
|
+
cursor: '90071992547409930003',
|
|
154
|
+
reason: 'timeout',
|
|
155
|
+
});
|
|
156
|
+
socket.end();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (request.operation === 'screenshot.capture') {
|
|
160
|
+
assert.equal(request.params.format, 'jpeg');
|
|
161
|
+
assert.equal(request.params.quality, 0.8);
|
|
162
|
+
const output = request.params.output;
|
|
163
|
+
fs.writeFileSync(output, Buffer.from([0xff, 0xd8, 0xff, 0xd9]));
|
|
164
|
+
write(socket, {
|
|
165
|
+
requestId: request.requestId,
|
|
166
|
+
type: 'result',
|
|
167
|
+
result: {
|
|
168
|
+
path: output,
|
|
169
|
+
mime: 'image/jpeg',
|
|
170
|
+
size: 4,
|
|
171
|
+
width: 1,
|
|
172
|
+
height: 1,
|
|
173
|
+
captureTime: '1787700000789',
|
|
174
|
+
sha256: 'smoke-hash',
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
socket.end();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const previewRun = await runCli(
|
|
184
|
+
[
|
|
185
|
+
'dev',
|
|
186
|
+
'--mode',
|
|
187
|
+
'client-preview',
|
|
188
|
+
'--client-key',
|
|
189
|
+
'smoke-client',
|
|
190
|
+
'--events',
|
|
191
|
+
'ndjson',
|
|
192
|
+
'--no-open',
|
|
193
|
+
'--timeout',
|
|
194
|
+
'1',
|
|
195
|
+
],
|
|
196
|
+
previewGameDirectory,
|
|
197
|
+
);
|
|
198
|
+
assert.equal(previewRun.status, 2, previewRun.stderr || previewRun.stdout);
|
|
199
|
+
const previewEvents = previewRun.stdout
|
|
200
|
+
.trim()
|
|
201
|
+
.split(/\r?\n/)
|
|
202
|
+
.filter(Boolean)
|
|
203
|
+
.map(JSON.parse);
|
|
204
|
+
const readyForScan = previewEvents.find(
|
|
205
|
+
event =>
|
|
206
|
+
event.type === 'lifecycle' &&
|
|
207
|
+
event.payload?.state === 'READY_FOR_SCAN',
|
|
208
|
+
);
|
|
209
|
+
assert.ok(readyForScan, previewRun.stderr || previewRun.stdout);
|
|
210
|
+
assert.equal(fs.existsSync(readyForScan.payload.qrPngPath), true);
|
|
211
|
+
const previewManifest = JSON.parse(
|
|
212
|
+
fs.readFileSync(readyForScan.payload.manifest, 'utf8'),
|
|
213
|
+
);
|
|
214
|
+
if (previewManifest.control?.transport === 'unix') {
|
|
215
|
+
assert.equal(Buffer.byteLength(previewManifest.control.path) <= 100, true);
|
|
216
|
+
}
|
|
217
|
+
assert.match(previewRun.stderr, /QR image:/);
|
|
218
|
+
assert.equal(
|
|
219
|
+
previewEvents.filter(
|
|
220
|
+
event =>
|
|
221
|
+
event.type === 'compile' && event.payload?.state === 'succeeded',
|
|
222
|
+
).length,
|
|
223
|
+
1,
|
|
224
|
+
'session evidence must not trigger an endless recompilation loop',
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
await new Promise((resolve, reject) => {
|
|
228
|
+
server.once('error', reject);
|
|
229
|
+
server.listen(control.path, resolve);
|
|
230
|
+
});
|
|
231
|
+
if (control.transport === 'unix') fs.chmodSync(control.path, 0o600);
|
|
232
|
+
|
|
233
|
+
const snapshotRun = await runCli(
|
|
234
|
+
[
|
|
235
|
+
'dev',
|
|
236
|
+
'session',
|
|
237
|
+
'logs',
|
|
238
|
+
sessionId,
|
|
239
|
+
'--after',
|
|
240
|
+
'90071992547409930001',
|
|
241
|
+
'--format',
|
|
242
|
+
'json',
|
|
243
|
+
],
|
|
244
|
+
gameDirectory,
|
|
245
|
+
);
|
|
246
|
+
assert.equal(
|
|
247
|
+
snapshotRun.status,
|
|
248
|
+
0,
|
|
249
|
+
snapshotRun.stderr || snapshotRun.stdout,
|
|
250
|
+
);
|
|
251
|
+
const snapshotLines = snapshotRun.stdout.trim().split(/\r?\n/);
|
|
252
|
+
assert.equal(snapshotLines.length, 1);
|
|
253
|
+
const snapshot = JSON.parse(snapshotLines[0]);
|
|
254
|
+
assert.equal(snapshot.command, 'dev.session.logs');
|
|
255
|
+
assert.equal(snapshot.records[0].sequence, '90071992547409930002');
|
|
256
|
+
assert.equal(typeof snapshot.records[0].sequence, 'string');
|
|
257
|
+
|
|
258
|
+
const followRun = await runCli(
|
|
259
|
+
[
|
|
260
|
+
'dev',
|
|
261
|
+
'session',
|
|
262
|
+
'logs',
|
|
263
|
+
sessionId,
|
|
264
|
+
'--after',
|
|
265
|
+
'90071992547409930002',
|
|
266
|
+
'--follow',
|
|
267
|
+
'--timeout',
|
|
268
|
+
'1',
|
|
269
|
+
'--format',
|
|
270
|
+
'ndjson',
|
|
271
|
+
],
|
|
272
|
+
gameDirectory,
|
|
273
|
+
);
|
|
274
|
+
assert.equal(followRun.status, 0, followRun.stderr || followRun.stdout);
|
|
275
|
+
const frames = followRun.stdout.trim().split(/\r?\n/).map(JSON.parse);
|
|
276
|
+
assert.deepEqual(
|
|
277
|
+
frames.map(frame => frame.type),
|
|
278
|
+
['log_snapshot', 'log_batch', 'follow_end'],
|
|
279
|
+
);
|
|
280
|
+
assert.equal(
|
|
281
|
+
frames.every(frame => frame.platformWrite === false),
|
|
282
|
+
true,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const screenshotOutput = path.join(root, 'screen.jpeg');
|
|
286
|
+
const screenshotRun = await runCli(
|
|
287
|
+
[
|
|
288
|
+
'dev',
|
|
289
|
+
'session',
|
|
290
|
+
'screenshot',
|
|
291
|
+
sessionId,
|
|
292
|
+
'--output',
|
|
293
|
+
screenshotOutput,
|
|
294
|
+
'--format',
|
|
295
|
+
'json',
|
|
296
|
+
],
|
|
297
|
+
gameDirectory,
|
|
298
|
+
);
|
|
299
|
+
assert.equal(
|
|
300
|
+
screenshotRun.status,
|
|
301
|
+
0,
|
|
302
|
+
screenshotRun.stderr || screenshotRun.stdout,
|
|
303
|
+
);
|
|
304
|
+
const screenshotLines = screenshotRun.stdout.trim().split(/\r?\n/);
|
|
305
|
+
assert.equal(screenshotLines.length, 1);
|
|
306
|
+
const screenshot = JSON.parse(screenshotLines[0]);
|
|
307
|
+
assert.equal(screenshot.path, screenshotOutput);
|
|
308
|
+
assert.equal(screenshot.mime, 'image/jpeg');
|
|
309
|
+
assert.equal('data' in screenshot, false);
|
|
310
|
+
assert.equal(fs.readFileSync(screenshotOutput).toString('hex'), 'ffd8ffd9');
|
|
311
|
+
} finally {
|
|
312
|
+
await new Promise(resolve => server.close(resolve));
|
|
313
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
console.log('client preview control process smoke passed');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
run().catch(error => {
|
|
320
|
+
console.error(error);
|
|
321
|
+
process.exitCode = 1;
|
|
322
|
+
});
|
|
@@ -103,6 +103,13 @@ assert.equal(
|
|
|
103
103
|
'ttmg dev --events ndjson --no-open',
|
|
104
104
|
);
|
|
105
105
|
assert.equal(capabilities.commands.dev.outputModesMutuallyExclusive, true);
|
|
106
|
+
assert.equal(capabilities.commands.dev.clientPreview.available, true);
|
|
107
|
+
assert.equal(capabilities.commands.dev.clientPreview.protocolVersion, '1.0');
|
|
108
|
+
assert.equal(capabilities.commands.dev.clientPreview.humanScanRequired, true);
|
|
109
|
+
assert.equal(capabilities.commands.dev.clientPreview.deviceLaunch, false);
|
|
110
|
+
assert.equal(capabilities.commands.dev.clientPreview.verifiedOnDevice, false);
|
|
111
|
+
assert.equal(capabilities.commands.dev.sessionLogs.available, true);
|
|
112
|
+
assert.equal(capabilities.commands.dev.sessionScreenshot.available, true);
|
|
106
113
|
assert.equal(capabilities.commands.uploadPreview.platformWrite, true);
|
|
107
114
|
assert.equal(
|
|
108
115
|
capabilities.commands.uploadPreview.previewPage.option,
|
|
@@ -153,6 +160,10 @@ assert.equal(
|
|
|
153
160
|
);
|
|
154
161
|
assert.equal(capabilities.dev.gameReadyProtocol, true);
|
|
155
162
|
assert.equal(capabilities.dev.gameReady, false);
|
|
163
|
+
assert.equal(capabilities.dev.runtimeLogs, false);
|
|
164
|
+
assert.equal(capabilities.dev.screenshot, false);
|
|
165
|
+
assert.equal(capabilities.dev.clientPreviewProtocol.runtimeLogsVerified, false);
|
|
166
|
+
assert.equal(capabilities.dev.clientPreviewProtocol.screenshotVerified, false);
|
|
156
167
|
assertNoSecretKeys(capabilities, 'capabilities');
|
|
157
168
|
|
|
158
169
|
const conflictingDevOutput = runCli([
|
|
@@ -174,6 +185,19 @@ assert.match(
|
|
|
174
185
|
/output modes cannot be combined|输出模式不能同时使用/,
|
|
175
186
|
);
|
|
176
187
|
|
|
188
|
+
const devHelp = runCli(['dev', '--help']);
|
|
189
|
+
assertExit(devHelp, 0, 'dev help');
|
|
190
|
+
assert.match(devHelp.stdout, /--mode <mode>/);
|
|
191
|
+
|
|
192
|
+
const logsHelp = runCli(['dev', 'session', 'logs', '--help']);
|
|
193
|
+
assertExit(logsHelp, 0, 'dev session logs help');
|
|
194
|
+
assert.match(logsHelp.stdout, /--follow\b/);
|
|
195
|
+
assert.match(logsHelp.stdout, /--until <cursor>/);
|
|
196
|
+
|
|
197
|
+
const screenshotHelp = runCli(['dev', 'session', 'screenshot', '--help']);
|
|
198
|
+
assertExit(screenshotHelp, 0, 'dev session screenshot help');
|
|
199
|
+
assert.match(screenshotHelp.stdout, /--image-format <format>/);
|
|
200
|
+
|
|
177
201
|
const checkHelp = runCli(['check', '--help']);
|
|
178
202
|
assertExit(checkHelp, 0, 'check help');
|
|
179
203
|
assert.match(checkHelp.stdout, /Usage: ttmg check \[options\]/);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Explicit test-process preload only; never loaded by the CLI. Replace HTTPS
|
|
2
|
+
// transport with a local fixture so --mock regressions cannot create real tasks.
|
|
3
|
+
const assert = require('node:assert/strict');
|
|
4
|
+
const http = require('node:http');
|
|
5
|
+
const https = require('node:https');
|
|
6
|
+
const port = Number(process.env.TTMG_VIBE_TEST_PORT);
|
|
7
|
+
assert.ok(Number.isInteger(port) && port > 0 && port < 65536);
|
|
8
|
+
https.request = (options, callback) => {
|
|
9
|
+
assert.equal(typeof options, 'object');
|
|
10
|
+
assert.equal(options.hostname, 'developers.tiktok.com');
|
|
11
|
+
assert.equal(options.protocol, 'https:');
|
|
12
|
+
assert.match(options.path, /^\/bff\/app_info\/minis_import\/(create_minis_import_task_for_agent|get_minis_import_task_status_for_agent)\//);
|
|
13
|
+
return http.request({
|
|
14
|
+
...options,
|
|
15
|
+
protocol: 'http:', hostname: '127.0.0.1', host: undefined, port,
|
|
16
|
+
agent: undefined, servername: undefined,
|
|
17
|
+
}, callback);
|
|
18
|
+
};
|
|
19
|
+
https.get = (options, callback) => {
|
|
20
|
+
const request = https.request(options, callback);
|
|
21
|
+
request.end();
|
|
22
|
+
return request;
|
|
23
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// A local fixture runner, not a second upload implementation.
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { spawn } = require('node:child_process');
|
|
6
|
+
|
|
7
|
+
const entry = path.resolve(__dirname, '../dist/index.js');
|
|
8
|
+
if (!fs.existsSync(entry)) throw new Error('Run npm run build first.');
|
|
9
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'ttmg-vibe-demo-'));
|
|
10
|
+
const project = path.join(directory, 'game');
|
|
11
|
+
fs.mkdirSync(project);
|
|
12
|
+
fs.writeFileSync(
|
|
13
|
+
path.join(project, 'game.js'),
|
|
14
|
+
'console.log("Vibe mock demo");\n',
|
|
15
|
+
);
|
|
16
|
+
fs.writeFileSync(path.join(project, 'game.json'), '{}\n');
|
|
17
|
+
// Localize this Chinese fixture demo without changing the user's CLI settings.
|
|
18
|
+
fs.writeFileSync(
|
|
19
|
+
path.join(directory, '.ttmgrc'),
|
|
20
|
+
JSON.stringify({ lang: 'zh-CN' }),
|
|
21
|
+
);
|
|
22
|
+
const icon = path.join(directory, 'fixture-icon.png');
|
|
23
|
+
// Tiny valid PNG fixture; normal usage still takes the user's own local image.
|
|
24
|
+
fs.writeFileSync(
|
|
25
|
+
icon,
|
|
26
|
+
Buffer.from(
|
|
27
|
+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aXyoAAAAASUVORK5CYII=',
|
|
28
|
+
'base64',
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
console.error(
|
|
32
|
+
'Integration demo: creates a real PPE task; authorization and uploads are simulated. No environment setup or phone authorization required.',
|
|
33
|
+
);
|
|
34
|
+
const child = spawn(
|
|
35
|
+
process.execPath,
|
|
36
|
+
[
|
|
37
|
+
entry,
|
|
38
|
+
'upload',
|
|
39
|
+
'--vibe',
|
|
40
|
+
'--mock',
|
|
41
|
+
'--title',
|
|
42
|
+
'星环守卫',
|
|
43
|
+
'--icon',
|
|
44
|
+
icon,
|
|
45
|
+
'--dir',
|
|
46
|
+
project,
|
|
47
|
+
...process.argv.slice(2),
|
|
48
|
+
],
|
|
49
|
+
{
|
|
50
|
+
cwd: directory,
|
|
51
|
+
stdio: 'inherit',
|
|
52
|
+
env: {
|
|
53
|
+
...process.env,
|
|
54
|
+
HOME: directory,
|
|
55
|
+
USERPROFILE: directory,
|
|
56
|
+
TTMGRC_PATH: path.join(directory, '.ttmgrc'),
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
const cancel = () => child.kill('SIGINT');
|
|
61
|
+
process.once('SIGINT', cancel);
|
|
62
|
+
process.once('SIGTERM', cancel);
|
|
63
|
+
child.once('error', () => {
|
|
64
|
+
console.error('Unable to start CLI demo.');
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
});
|
|
67
|
+
child.once('close', code => {
|
|
68
|
+
process.off('SIGINT', cancel);
|
|
69
|
+
process.off('SIGTERM', cancel);
|
|
70
|
+
process.exitCode = code ?? 1;
|
|
71
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
72
|
+
});
|