@ttmg/cli 0.4.6-agent-beta.1 → 0.4.6-agent-beta.2
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 +15 -0
- package/dist/index.js +218 -11
- package/dist/index.js.map +1 -1
- package/dist/package.json +4 -3
- package/dist/scripts/dev-default-smoke.js +22 -1
- package/dist/scripts/verify-agent-contract.js +40 -2
- package/dist/scripts/vibe-auth-open-test-preload.cjs +11 -0
- package/dist/scripts/vibe-upload-smoke.js +54 -0
- package/package.json +4 -3
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttmg/cli",
|
|
3
|
-
"version": "0.4.6-agent-beta.
|
|
3
|
+
"version": "0.4.6-agent-beta.2",
|
|
4
4
|
"description": "TikTok Mini Game Command Line Tool",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"test:auth": "node scripts/run-ts-test.js src/commands/common/auth.test.ts",
|
|
31
31
|
"test:capabilities": "node scripts/run-ts-test.js src/commands/common/capabilities.test.ts",
|
|
32
32
|
"test:test-account": "node scripts/run-ts-test.js src/commands/common/testAccount.test.ts",
|
|
33
|
-
"test:dev-session": "node scripts/run-ts-test.js src/commands/native/dev/agentSession.test.ts",
|
|
33
|
+
"test:dev-session": "node scripts/run-ts-test.js src/commands/native/dev/agentSession.test.ts && npm run test:packaging-compatibility",
|
|
34
|
+
"test:packaging-compatibility": "node scripts/run-ts-test.js src/commands/native/dev/packagingCompatibility.test.ts",
|
|
34
35
|
"test:dev-session-command": "node scripts/run-ts-test.js src/commands/native/dev/session.test.ts && node scripts/run-ts-test.js src/commands/native/dev/projectSession.test.ts && node scripts/run-ts-test.js src/commands/native/dev/scanPage.test.ts && node scripts/run-ts-test.js src/commands/native/dev/runtime/logArchive.test.ts && node scripts/run-ts-test.js src/commands/native/dev/capture.test.ts",
|
|
35
36
|
"test:dev-runtime-event": "node scripts/run-ts-test.js src/commands/native/dev/utils/scoket.test.ts",
|
|
36
37
|
"test:dev-runtime-client": "node scripts/run-ts-test.js src/commands/native/dev/runtime/runtime.test.ts",
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
"qs": "6.12.1",
|
|
94
95
|
"semver": "^7.7.2",
|
|
95
96
|
"socks-proxy-agent": "^9.0.0",
|
|
96
|
-
"ttmg-pack": "0.4.13
|
|
97
|
+
"ttmg-pack": "0.4.13",
|
|
97
98
|
"ws": "^8.18.3"
|
|
98
99
|
},
|
|
99
100
|
"devDependencies": {
|
|
@@ -13,6 +13,10 @@ async function run() {
|
|
|
13
13
|
fs.mkdirSync(gameDirectory);
|
|
14
14
|
fs.writeFileSync(path.join(gameDirectory, 'game.js'), 'console.log("game");\n');
|
|
15
15
|
fs.writeFileSync(path.join(gameDirectory, 'game.json'), '{}\n');
|
|
16
|
+
for (const directory of ['.ttmg', '.ttmg-assets', 'assets/.ttmg']) {
|
|
17
|
+
fs.mkdirSync(path.join(gameDirectory, directory), { recursive: true });
|
|
18
|
+
fs.writeFileSync(path.join(gameDirectory, directory, 'data.txt'), 'before');
|
|
19
|
+
}
|
|
16
20
|
|
|
17
21
|
let output = '';
|
|
18
22
|
const child = spawn(
|
|
@@ -49,7 +53,24 @@ async function run() {
|
|
|
49
53
|
await delay(100);
|
|
50
54
|
}
|
|
51
55
|
assert.match(output, /Game resources compiled successfully|游戏资源编译成功/);
|
|
52
|
-
|
|
56
|
+
// Ordinary dev must not create an Agent session. Existing evidence must
|
|
57
|
+
// not trigger reloads, while similarly named runtime assets still do.
|
|
58
|
+
assert.equal(fs.existsSync(path.join(gameDirectory, '.ttmg/sessions')), false);
|
|
59
|
+
const compileCount = () => (output.match(/Game resources compiled successfully|游戏资源编译成功/g) || []).length;
|
|
60
|
+
const initialCount = compileCount();
|
|
61
|
+
fs.writeFileSync(path.join(gameDirectory, '.ttmg/data.txt'), 'new log');
|
|
62
|
+
await delay(4500);
|
|
63
|
+
assert.equal(compileCount(), initialCount, 'session writes triggered a rebuild');
|
|
64
|
+
for (const file of ['.ttmg-assets/data.txt', 'assets/.ttmg/data.txt']) {
|
|
65
|
+
const before = compileCount();
|
|
66
|
+
fs.writeFileSync(path.join(gameDirectory, file), 'updated runtime asset');
|
|
67
|
+
const deadline = Date.now() + 15000;
|
|
68
|
+
while (compileCount() === before && Date.now() < deadline) {
|
|
69
|
+
if (child.exitCode !== null) throw new Error(`Default dev exited while watching ${file}:\n${output}`);
|
|
70
|
+
await delay(100);
|
|
71
|
+
}
|
|
72
|
+
assert.ok(compileCount() > before, `${file} did not trigger a rebuild`);
|
|
73
|
+
}
|
|
53
74
|
} finally {
|
|
54
75
|
if (child.exitCode === null) {
|
|
55
76
|
child.kill('SIGTERM');
|
|
@@ -10,7 +10,7 @@ const packageJson = JSON.parse(
|
|
|
10
10
|
fs.readFileSync(path.join(cliRoot, 'package.json'), 'utf8'),
|
|
11
11
|
);
|
|
12
12
|
|
|
13
|
-
function runCli(args, environment = {}) {
|
|
13
|
+
function runCli(args, environment = {}, cwd = cliRoot) {
|
|
14
14
|
const env = {
|
|
15
15
|
...process.env,
|
|
16
16
|
NO_COLOR: '1',
|
|
@@ -20,7 +20,7 @@ function runCli(args, environment = {}) {
|
|
|
20
20
|
delete env.TTMG_LOGIN_EMAIL;
|
|
21
21
|
delete env.TTMG_LOGIN_PASSWORD;
|
|
22
22
|
return spawnSync(process.execPath, [cliEntry, ...args], {
|
|
23
|
-
cwd
|
|
23
|
+
cwd,
|
|
24
24
|
env,
|
|
25
25
|
encoding: 'utf8',
|
|
26
26
|
});
|
|
@@ -270,6 +270,44 @@ assert.equal(parseSingleJson(invalidBackground, 'background').errorCode, 'DEV_IT
|
|
|
270
270
|
const invalidManual = runCli(['dev', '--update-mode', 'invalid', '--format', 'json']);
|
|
271
271
|
assertExit(invalidManual, 1, 'update mode validation');
|
|
272
272
|
assert.equal(parseSingleJson(invalidManual, 'manual').errorCode, 'DEV_ITERATION_INVALID_OPTIONS');
|
|
273
|
+
// Validation must happen before writing evidence or starting local services.
|
|
274
|
+
const unsafeEvidence = path.join(cliRoot, 'unsafe-evidence-' + process.pid);
|
|
275
|
+
const invalidEvidence = runCli(['dev', '--client-key', 'fixture', '--mode', 'client-preview', '--no-open', '--format', 'json', '--evidence-dir', unsafeEvidence]);
|
|
276
|
+
assertExit(invalidEvidence, 1, 'evidence directory isolation');
|
|
277
|
+
assert.equal(parseSingleJson(invalidEvidence, 'evidence directory').errorCode, 'DEV_EVIDENCE_DIRECTORY_UNSAFE');
|
|
278
|
+
assert.equal(fs.existsSync(unsafeEvidence), false);
|
|
279
|
+
assert.deepEqual(capabilities.commands.dev.sessionLogs.evidenceDirectoryLocations, ['outside-project', 'project-root-.ttmg']);
|
|
280
|
+
const packagingFixture = fs.mkdtempSync(path.join(os.tmpdir(), 'ttmg-installed-pack-gate-'));
|
|
281
|
+
try {
|
|
282
|
+
fs.writeFileSync(path.join(packagingFixture, 'game.js'), 'console.log("fixture");');
|
|
283
|
+
fs.writeFileSync(path.join(packagingFixture, 'game.json'), '{}');
|
|
284
|
+
fs.mkdirSync(path.join(packagingFixture, '.ttmg'));
|
|
285
|
+
fs.writeFileSync(path.join(packagingFixture, '.ttmg/logs.ndjson'), Buffer.alloc(5 * 1024 * 1024));
|
|
286
|
+
fs.writeFileSync(path.join(packagingFixture, '.ttmg/旧构建.js'), 'tt.login();');
|
|
287
|
+
const checked = runCli(['check', '--dir', packagingFixture, '--format', 'json']);
|
|
288
|
+
assertExit(checked, 0, 'installed packaging evidence exclusion');
|
|
289
|
+
const report = parseSingleJson(checked, 'installed packaging evidence exclusion');
|
|
290
|
+
assert.ok(!report.diagnostics.some(item => item.type === 'size' && !item.passed), 'installed ttmg-pack counted CLI evidence in package size');
|
|
291
|
+
assert.ok(report.diagnostics.some(item => item.name === 'login' && !item.passed), 'installed ttmg-pack scanned APIs from old debug builds');
|
|
292
|
+
} finally {
|
|
293
|
+
fs.rmSync(packagingFixture, { recursive: true, force: true });
|
|
294
|
+
}
|
|
295
|
+
if (process.platform !== 'win32') {
|
|
296
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ttmg-evidence-symlink-'));
|
|
297
|
+
try {
|
|
298
|
+
fs.mkdirSync(path.join(root, 'assets'));
|
|
299
|
+
fs.writeFileSync(path.join(root, 'game.js'), 'console.log("fixture");');
|
|
300
|
+
fs.symlinkSync(path.join(root, 'assets'), path.join(root, '.ttmg'));
|
|
301
|
+
for (const extra of [[], ['--background']]) {
|
|
302
|
+
const result = runCli(['dev', '--mode', 'client-preview', '--update-mode', 'manual', '--client-key', 'fixture', '--no-open', '--format', 'json', ...extra], {}, root);
|
|
303
|
+
assertExit(result, 1, 'symlinked evidence isolation');
|
|
304
|
+
assert.equal(parseSingleJson(result, 'symlinked evidence').errorCode, 'DEV_EVIDENCE_DIRECTORY_UNSAFE');
|
|
305
|
+
assert.deepEqual(fs.readdirSync(path.join(root, 'assets')), [], 'validation wrote into game assets');
|
|
306
|
+
}
|
|
307
|
+
} finally {
|
|
308
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
273
311
|
// Both formats perform the same local build validation; the former text placeholder is retired.
|
|
274
312
|
const missingBuildDirectory = path.join(os.tmpdir(), 'ttmg-build-dir-that-does-not-exist-' + process.pid);
|
|
275
313
|
for (const format of ['text', 'json']) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Test-only browser boundary: never launch a real browser from process smoke.
|
|
2
|
+
const cp = require('node:child_process');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const original = cp.spawn;
|
|
5
|
+
cp.spawn = function(command, args, options) {
|
|
6
|
+
if (command === 'open' || /(?:xdg-open|powershell\.exe)$/i.test(command)) {
|
|
7
|
+
fs.appendFileSync(process.env.TTMG_AUTH_OPEN_TEST_RECEIPT, JSON.stringify({command, args}) + '\n');
|
|
8
|
+
return original(process.execPath, ['-e', 'process.exit(0)'], {stdio: 'ignore'});
|
|
9
|
+
}
|
|
10
|
+
return original.call(this, command, args, options);
|
|
11
|
+
};
|
|
@@ -99,6 +99,57 @@ function events(result) {
|
|
|
99
99
|
.map(line => JSON.parse(line));
|
|
100
100
|
}
|
|
101
101
|
async function main() {
|
|
102
|
+
// Exercise the built worker IPC and new command without creating a platform task.
|
|
103
|
+
const authWorker = spawn(process.execPath, [entry, '__vibe-page-server'], {
|
|
104
|
+
env: {...env, TTMG_VIBE_PAGE_TTL_MS: '30000'}, stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
|
|
105
|
+
});
|
|
106
|
+
children.add(authWorker);
|
|
107
|
+
const workerMessage = predicate => new Promise((resolve, reject) => {
|
|
108
|
+
const timer = setTimeout(() => {authWorker.off('message', receive); reject(new Error('worker timeout'));}, 5000);
|
|
109
|
+
function receive(message) {
|
|
110
|
+
if (predicate(message)) {clearTimeout(timer); authWorker.off('message', receive); resolve(message);}
|
|
111
|
+
}
|
|
112
|
+
authWorker.on('message', receive);
|
|
113
|
+
});
|
|
114
|
+
const ready = await workerMessage(message => message.type === 'ready');
|
|
115
|
+
const landing = 'https://developers.tiktok.com/portal_h5/minis_import?task_id=123&import_token=process-private-token';
|
|
116
|
+
let rendered = workerMessage(message => message.type === 'rendered' && message.id === 1);
|
|
117
|
+
authWorker.send({type: 'render', id: 1, html: '<h1>Waiting</h1>', browserLandingUrl: landing});
|
|
118
|
+
await rendered;
|
|
119
|
+
const browserReceipt = path.join(temp, 'browser-open.ndjson');
|
|
120
|
+
const browserEnv = {
|
|
121
|
+
NODE_OPTIONS: `--require=${path.join(__dirname, 'vibe-auth-open-test-preload.cjs')}`,
|
|
122
|
+
TTMG_AUTH_OPEN_TEST_RECEIPT: browserReceipt,
|
|
123
|
+
};
|
|
124
|
+
for (const format of ['json', 'ndjson']) {
|
|
125
|
+
const opened = await run(['auth', 'open', '--page-url', ready.url, '--format', format], undefined, browserEnv);
|
|
126
|
+
assert.equal(opened.code, 0, opened.stderr);
|
|
127
|
+
const result = JSON.parse(opened.stdout);
|
|
128
|
+
assert.equal(result.status, 'open_requested');
|
|
129
|
+
assert.equal(result.platformWrite, false);
|
|
130
|
+
assert.doesNotMatch(opened.stdout + opened.stderr, /import_token|process-private-token/);
|
|
131
|
+
}
|
|
132
|
+
const launches = fs.readFileSync(browserReceipt, 'utf8').trim().split('\n').map(JSON.parse);
|
|
133
|
+
assert.equal(launches.length, 2);
|
|
134
|
+
assert.ok(launches.every(item => item.args.includes(ready.url + '/authorize')));
|
|
135
|
+
const redirect = await fetch(ready.url + '/authorize', {redirect: 'manual'});
|
|
136
|
+
assert.equal(redirect.headers.get('location'), landing);
|
|
137
|
+
assert.equal(redirect.status, 302);
|
|
138
|
+
rendered = workerMessage(message => message.type === 'rendered' && message.id === 2);
|
|
139
|
+
authWorker.send({type: 'render', id: 2, html: '<h1>Complete</h1>'});
|
|
140
|
+
await rendered;
|
|
141
|
+
const stale = await run(['auth', 'open', '--page-url', ready.url, '--format', 'json'], undefined, browserEnv);
|
|
142
|
+
assert.equal(stale.code, 1);
|
|
143
|
+
assert.equal(JSON.parse(stale.stdout).errorCode, 'VIBE_AUTH_PAGE_UNAVAILABLE');
|
|
144
|
+
for (const args of [[], ['--page-url', landing]]) {
|
|
145
|
+
const invalid = await run(['auth', 'open', ...args, '--format', 'json'], undefined, browserEnv);
|
|
146
|
+
assert.equal(invalid.code, 1);
|
|
147
|
+
assert.equal(JSON.parse(invalid.stdout).errorCode, 'VIBE_INVALID_PAGE_URL');
|
|
148
|
+
assert.doesNotMatch(invalid.stdout + invalid.stderr, /process-private-token/);
|
|
149
|
+
}
|
|
150
|
+
assert.equal(fs.readFileSync(browserReceipt, 'utf8').trim().split('\n').length, 2);
|
|
151
|
+
authWorker.kill();
|
|
152
|
+
children.delete(authWorker);
|
|
102
153
|
fixtureServer = http.createServer((req, res) => {
|
|
103
154
|
const url = new URL(req.url, 'http://127.0.0.1');
|
|
104
155
|
requests.push({ path: url.pathname, method: req.method, headers: req.headers });
|
|
@@ -381,6 +432,9 @@ async function main() {
|
|
|
381
432
|
assert.equal(help.code, 0);
|
|
382
433
|
assert.match(help.stdout, /--vibe/);
|
|
383
434
|
assert.match(help.stdout, /--client-key/);
|
|
435
|
+
const authHelp = await run(['auth', 'open', '--help']);
|
|
436
|
+
assert.equal(authHelp.code, 0);
|
|
437
|
+
assert.match(authHelp.stdout, /--page-url/);
|
|
384
438
|
|
|
385
439
|
let cancelledPage;
|
|
386
440
|
const cancelled = await run(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttmg/cli",
|
|
3
|
-
"version": "0.4.6-agent-beta.
|
|
3
|
+
"version": "0.4.6-agent-beta.2",
|
|
4
4
|
"description": "TikTok Mini Game Command Line Tool",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"test:auth": "node scripts/run-ts-test.js src/commands/common/auth.test.ts",
|
|
31
31
|
"test:capabilities": "node scripts/run-ts-test.js src/commands/common/capabilities.test.ts",
|
|
32
32
|
"test:test-account": "node scripts/run-ts-test.js src/commands/common/testAccount.test.ts",
|
|
33
|
-
"test:dev-session": "node scripts/run-ts-test.js src/commands/native/dev/agentSession.test.ts",
|
|
33
|
+
"test:dev-session": "node scripts/run-ts-test.js src/commands/native/dev/agentSession.test.ts && npm run test:packaging-compatibility",
|
|
34
|
+
"test:packaging-compatibility": "node scripts/run-ts-test.js src/commands/native/dev/packagingCompatibility.test.ts",
|
|
34
35
|
"test:dev-session-command": "node scripts/run-ts-test.js src/commands/native/dev/session.test.ts && node scripts/run-ts-test.js src/commands/native/dev/projectSession.test.ts && node scripts/run-ts-test.js src/commands/native/dev/scanPage.test.ts && node scripts/run-ts-test.js src/commands/native/dev/runtime/logArchive.test.ts && node scripts/run-ts-test.js src/commands/native/dev/capture.test.ts",
|
|
35
36
|
"test:dev-runtime-event": "node scripts/run-ts-test.js src/commands/native/dev/utils/scoket.test.ts",
|
|
36
37
|
"test:dev-runtime-client": "node scripts/run-ts-test.js src/commands/native/dev/runtime/runtime.test.ts",
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
"qs": "6.12.1",
|
|
94
95
|
"semver": "^7.7.2",
|
|
95
96
|
"socks-proxy-agent": "^9.0.0",
|
|
96
|
-
"ttmg-pack": "0.4.13
|
|
97
|
+
"ttmg-pack": "0.4.13",
|
|
97
98
|
"ws": "^8.18.3"
|
|
98
99
|
},
|
|
99
100
|
"devDependencies": {
|