@web-auto/webauto 0.1.8 → 0.1.11
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/apps/desktop-console/dist/main/index.mjs +909 -105
- package/apps/desktop-console/dist/main/preload.mjs +3 -0
- package/apps/desktop-console/dist/renderer/index.html +9 -1
- package/apps/desktop-console/dist/renderer/index.js +796 -331
- package/apps/desktop-console/entry/ui-cli.mjs +59 -9
- package/apps/desktop-console/entry/ui-console.mjs +8 -3
- package/apps/webauto/entry/account.mjs +70 -9
- package/apps/webauto/entry/lib/account-detect.mjs +106 -25
- package/apps/webauto/entry/lib/account-store.mjs +122 -35
- package/apps/webauto/entry/lib/profilepool.mjs +45 -13
- package/apps/webauto/entry/lib/schedule-store.mjs +1 -25
- package/apps/webauto/entry/profilepool.mjs +45 -3
- package/apps/webauto/entry/schedule.mjs +44 -2
- package/apps/webauto/entry/weibo-unified.mjs +2 -2
- package/apps/webauto/entry/xhs-install.mjs +248 -52
- package/apps/webauto/entry/xhs-unified.mjs +33 -6
- package/bin/webauto.mjs +137 -5
- package/dist/modules/camo-runtime/src/utils/browser-service.mjs +4 -0
- package/dist/services/unified-api/server.js +5 -0
- package/dist/services/unified-api/task-state.js +2 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +142 -14
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +16 -1
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +104 -0
- package/modules/camo-runtime/src/autoscript/runtime.mjs +14 -4
- package/modules/camo-runtime/src/autoscript/schema.mjs +9 -0
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +9 -2
- package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +107 -1
- package/modules/camo-runtime/src/container/runtime-core/subscription.mjs +24 -2
- package/modules/camo-runtime/src/utils/browser-service.mjs +4 -0
- package/package.json +7 -3
- package/runtime/infra/utils/README.md +13 -0
- package/runtime/infra/utils/scripts/README.md +0 -0
- package/runtime/infra/utils/scripts/development/eval-in-session.mjs +40 -0
- package/runtime/infra/utils/scripts/development/highlight-search-containers.mjs +35 -0
- package/runtime/infra/utils/scripts/service/kill-port.mjs +24 -0
- package/runtime/infra/utils/scripts/service/start-api.mjs +103 -0
- package/runtime/infra/utils/scripts/service/start-browser-service.mjs +173 -0
- package/runtime/infra/utils/scripts/service/stop-api.mjs +30 -0
- package/runtime/infra/utils/scripts/service/stop-browser-service.mjs +104 -0
- package/runtime/infra/utils/scripts/test-services.mjs +94 -0
- package/scripts/bump-version.mjs +120 -0
- package/services/unified-api/server.ts +4 -0
- package/services/unified-api/task-state.ts +5 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync, spawn } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { setTimeout as wait } from 'node:timers/promises';
|
|
7
|
+
|
|
8
|
+
function resolveWebautoRoot() {
|
|
9
|
+
const explicitHome = String(process.env.WEBAUTO_HOME || '').trim();
|
|
10
|
+
if (explicitHome) return path.resolve(explicitHome);
|
|
11
|
+
|
|
12
|
+
const legacyRoot = String(process.env.WEBAUTO_ROOT || process.env.WEBAUTO_PORTABLE_ROOT || '').trim();
|
|
13
|
+
if (legacyRoot) {
|
|
14
|
+
const normalized = path.resolve(legacyRoot);
|
|
15
|
+
const base = path.basename(normalized).toLowerCase();
|
|
16
|
+
if (base === '.webauto' || base === 'webauto') return normalized;
|
|
17
|
+
return path.join(normalized, '.webauto');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (process.platform === 'win32') {
|
|
21
|
+
try {
|
|
22
|
+
if (fs.existsSync('D:\\')) return 'D:\\webauto';
|
|
23
|
+
} catch {
|
|
24
|
+
// ignore probing errors
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return path.join(os.homedir(), '.webauto');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const RUN_DIR = path.join(resolveWebautoRoot(), 'run');
|
|
31
|
+
const PID_FILE = path.join(RUN_DIR, 'browser-service.pid');
|
|
32
|
+
const DEFAULT_HOST = process.env.WEBAUTO_BROWSER_HOST || '127.0.0.1';
|
|
33
|
+
const DEFAULT_PORT = Number(process.env.WEBAUTO_BROWSER_PORT || 7704);
|
|
34
|
+
|
|
35
|
+
function isAlive(pid) {
|
|
36
|
+
try {
|
|
37
|
+
process.kill(pid, 0);
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function health(host = DEFAULT_HOST, port = DEFAULT_PORT) {
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetch(`http://${host}:${port}/health`);
|
|
47
|
+
if (!res.ok) return false;
|
|
48
|
+
const body = await res.json().catch(() => ({}));
|
|
49
|
+
return Boolean(body?.ok ?? true);
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseArgs() {
|
|
56
|
+
const args = process.argv.slice(2);
|
|
57
|
+
const hostIdx = args.findIndex((item) => item === '--host');
|
|
58
|
+
const portIdx = args.findIndex((item) => item === '--port');
|
|
59
|
+
const host = hostIdx >= 0 && args[hostIdx + 1] ? String(args[hostIdx + 1]) : DEFAULT_HOST;
|
|
60
|
+
const port = portIdx >= 0 && args[portIdx + 1] ? Number(args[portIdx + 1]) : DEFAULT_PORT;
|
|
61
|
+
return {
|
|
62
|
+
host,
|
|
63
|
+
port: Number.isFinite(port) && port > 0 ? port : DEFAULT_PORT,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function latestMtimeMs(targetPath) {
|
|
68
|
+
if (!fs.existsSync(targetPath)) return 0;
|
|
69
|
+
const stat = fs.statSync(targetPath);
|
|
70
|
+
if (stat.isFile()) return Number(stat.mtimeMs || 0);
|
|
71
|
+
if (!stat.isDirectory()) return 0;
|
|
72
|
+
let latest = Number(stat.mtimeMs || 0);
|
|
73
|
+
const stack = [targetPath];
|
|
74
|
+
while (stack.length) {
|
|
75
|
+
const current = stack.pop();
|
|
76
|
+
if (!current) continue;
|
|
77
|
+
let entries = [];
|
|
78
|
+
try {
|
|
79
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
80
|
+
} catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
const full = path.join(current, entry.name);
|
|
85
|
+
let entryStat;
|
|
86
|
+
try {
|
|
87
|
+
entryStat = fs.statSync(full);
|
|
88
|
+
} catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const mtime = Number(entryStat.mtimeMs || 0);
|
|
92
|
+
if (mtime > latest) latest = mtime;
|
|
93
|
+
if (entryStat.isDirectory()) stack.push(full);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return latest;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function shouldRebuild(distEntry) {
|
|
100
|
+
if (!fs.existsSync(distEntry)) return true;
|
|
101
|
+
if (String(process.env.WEBAUTO_SKIP_BUILD_CHECK || '') === '1') return false;
|
|
102
|
+
if (!fs.existsSync(path.resolve('tsconfig.services.json'))) return false;
|
|
103
|
+
const distMtime = Number(fs.statSync(distEntry).mtimeMs || 0);
|
|
104
|
+
const watchRoots = [
|
|
105
|
+
path.resolve('modules/camo-backend/src'),
|
|
106
|
+
path.resolve('modules/logging/src'),
|
|
107
|
+
];
|
|
108
|
+
const latestSourceMtime = Math.max(...watchRoots.map((root) => latestMtimeMs(root)));
|
|
109
|
+
return latestSourceMtime > distMtime;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function ensureBuild() {
|
|
113
|
+
const distEntry = path.resolve('dist/modules/camo-backend/src/index.js');
|
|
114
|
+
if (!shouldRebuild(distEntry)) return distEntry;
|
|
115
|
+
console.log('[browser-service] backend entry missing/stale, running npm run -s build:services');
|
|
116
|
+
execSync('npm run -s build:services', { stdio: 'inherit' });
|
|
117
|
+
if (!fs.existsSync(distEntry)) {
|
|
118
|
+
throw new Error(`backend entry missing after build: ${distEntry}`);
|
|
119
|
+
}
|
|
120
|
+
return distEntry;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function main() {
|
|
124
|
+
const { host, port } = parseArgs();
|
|
125
|
+
fs.mkdirSync(RUN_DIR, { recursive: true });
|
|
126
|
+
|
|
127
|
+
if (await health(host, port)) {
|
|
128
|
+
console.log(`Browser service already healthy on http://${host}:${port}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (fs.existsSync(PID_FILE)) {
|
|
133
|
+
try {
|
|
134
|
+
const oldPid = Number(fs.readFileSync(PID_FILE, 'utf8'));
|
|
135
|
+
if (oldPid && isAlive(oldPid)) {
|
|
136
|
+
console.log(`Browser service already running (pid=${oldPid}).`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// stale pid file will be overwritten
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const entry = ensureBuild();
|
|
145
|
+
const child = spawn(process.execPath, [entry], {
|
|
146
|
+
detached: true,
|
|
147
|
+
stdio: 'ignore',
|
|
148
|
+
env: {
|
|
149
|
+
...process.env,
|
|
150
|
+
BROWSER_SERVICE_HOST: host,
|
|
151
|
+
BROWSER_SERVICE_PORT: String(port),
|
|
152
|
+
WEBAUTO_BROWSER_HOST: host,
|
|
153
|
+
WEBAUTO_BROWSER_PORT: String(port),
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
child.unref();
|
|
157
|
+
fs.writeFileSync(PID_FILE, String(child.pid));
|
|
158
|
+
|
|
159
|
+
for (let i = 0; i < 30; i += 1) {
|
|
160
|
+
if (await health(host, port)) {
|
|
161
|
+
console.log(`Browser service started (pid=${child.pid}) on http://${host}:${port}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await wait(300);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
throw new Error('browser service did not become healthy in time');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
main().catch((err) => {
|
|
171
|
+
console.error(`[browser-service] start failed: ${err?.message || String(err)}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, existsSync, unlinkSync } from 'node:fs';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
const PID_FILE = process.env.WEBAUTO_API_PID_FILE
|
|
8
|
+
? path.resolve(process.env.WEBAUTO_API_PID_FILE)
|
|
9
|
+
: path.join(os.tmpdir(), 'webauto-api.pid');
|
|
10
|
+
|
|
11
|
+
function killPid(pid){
|
|
12
|
+
if (process.platform === 'win32') {
|
|
13
|
+
try {
|
|
14
|
+
spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
|
|
15
|
+
} catch {}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try { process.kill(pid, 'SIGTERM'); } catch {}
|
|
19
|
+
setTimeout(() => { try { process.kill(pid, 'SIGKILL'); } catch {} }, 1000);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function main(){
|
|
23
|
+
if (!existsSync(PID_FILE)) { console.log('No pid file.'); return; }
|
|
24
|
+
const pid = Number(readFileSync(PID_FILE,'utf8').trim());
|
|
25
|
+
if (pid>0) killPid(pid);
|
|
26
|
+
try { unlinkSync(PID_FILE); } catch {}
|
|
27
|
+
console.log('API stopped.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
main().catch(e=>{ console.error(e); process.exit(1); });
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { setTimeout as wait } from 'node:timers/promises';
|
|
7
|
+
|
|
8
|
+
const RUN_DIR = path.join(os.homedir(), '.webauto', 'run');
|
|
9
|
+
const PID_FILE = path.join(RUN_DIR, 'browser-service.pid');
|
|
10
|
+
const DEFAULT_PORT = Number(process.env.WEBAUTO_BROWSER_PORT || 7704);
|
|
11
|
+
|
|
12
|
+
function isAlive(pid) {
|
|
13
|
+
try {
|
|
14
|
+
process.kill(pid, 0);
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function killByPort(port) {
|
|
22
|
+
try {
|
|
23
|
+
if (process.platform === 'win32') {
|
|
24
|
+
const out = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf8' });
|
|
25
|
+
const pids = new Set();
|
|
26
|
+
out.split(/\r?\n/).forEach((line) => {
|
|
27
|
+
const match = line.trim().match(/\s(\d+)\s*$/);
|
|
28
|
+
if (match) pids.add(Number(match[1]));
|
|
29
|
+
});
|
|
30
|
+
for (const pid of pids) {
|
|
31
|
+
try {
|
|
32
|
+
execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
|
|
33
|
+
} catch {
|
|
34
|
+
// ignore
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return pids.size > 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const out = execSync(`lsof -ti :${port} || true`, { encoding: 'utf8' });
|
|
41
|
+
const pids = out.split(/\s+/).map((item) => Number(item.trim())).filter(Boolean);
|
|
42
|
+
for (const pid of pids) {
|
|
43
|
+
try {
|
|
44
|
+
process.kill(pid, 'SIGKILL');
|
|
45
|
+
} catch {
|
|
46
|
+
// ignore
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return pids.length > 0;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function main() {
|
|
56
|
+
if (!fs.existsSync(PID_FILE)) {
|
|
57
|
+
const killed = killByPort(DEFAULT_PORT);
|
|
58
|
+
console.log(killed ? `Killed processes on :${DEFAULT_PORT}` : 'No PID file found. Service may not be running.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const pid = Number(fs.readFileSync(PID_FILE, 'utf8'));
|
|
63
|
+
if (!pid) {
|
|
64
|
+
fs.rmSync(PID_FILE, { force: true });
|
|
65
|
+
killByPort(DEFAULT_PORT);
|
|
66
|
+
console.log('Invalid PID file. Performed port cleanup.');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!isAlive(pid)) {
|
|
71
|
+
fs.rmSync(PID_FILE, { force: true });
|
|
72
|
+
killByPort(DEFAULT_PORT);
|
|
73
|
+
console.log(`Process ${pid} is not running. Cleaned stale PID.`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
process.kill(pid, 'SIGTERM');
|
|
79
|
+
} catch {
|
|
80
|
+
// ignore
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < 15; i += 1) {
|
|
84
|
+
if (!isAlive(pid)) break;
|
|
85
|
+
await wait(200);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (isAlive(pid)) {
|
|
89
|
+
try {
|
|
90
|
+
process.kill(pid, 'SIGKILL');
|
|
91
|
+
} catch {
|
|
92
|
+
// ignore
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fs.rmSync(PID_FILE, { force: true });
|
|
97
|
+
killByPort(DEFAULT_PORT);
|
|
98
|
+
console.log(`Browser service stopped (pid=${pid}).`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
main().catch((err) => {
|
|
102
|
+
console.error(`[browser-service] stop failed: ${err?.message || String(err)}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 测试脚本:验证服务是否能正常启动
|
|
3
|
+
import { spawn, execSync } from 'node:child_process';
|
|
4
|
+
import { setTimeout as wait } from 'node:timers/promises';
|
|
5
|
+
|
|
6
|
+
function log(msg){ console.log(`[test-services] ${msg}`); }
|
|
7
|
+
|
|
8
|
+
async function testHealth(port, name, timeout = 10000) {
|
|
9
|
+
const startTime = Date.now();
|
|
10
|
+
|
|
11
|
+
while (Date.now() - startTime < timeout) {
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(`http://127.0.0.1:${port}/health`);
|
|
14
|
+
if (response.ok) {
|
|
15
|
+
const data = await response.json();
|
|
16
|
+
log(`✅ ${name} (${port}): 健康检查通过 - ${JSON.stringify(data)}`);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
} catch (e) {
|
|
20
|
+
// 继续等待
|
|
21
|
+
}
|
|
22
|
+
await wait(500);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
log(`❌ ${name} (${port}): 健康检查失败`);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
log('🧪 测试服务启动...');
|
|
31
|
+
|
|
32
|
+
// 1. 构建服务
|
|
33
|
+
try {
|
|
34
|
+
log('构建服务...');
|
|
35
|
+
execSync('npm run -s build:services', { stdio: 'inherit' });
|
|
36
|
+
log('✅ 构建完成');
|
|
37
|
+
} catch (e) {
|
|
38
|
+
log('❌ 构建失败');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2. 清理端口
|
|
43
|
+
const ports = [7704, 7705, 7706];
|
|
44
|
+
for (const port of ports) {
|
|
45
|
+
try {
|
|
46
|
+
execSync(`lsof -ti :${port} | xargs kill -9 || true`, { stdio: 'ignore' });
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 3. 测试 Unified API
|
|
51
|
+
log('测试 Unified API...');
|
|
52
|
+
const unifiedProc = spawn('node', ['dist/apps/webauto/server.js'], {
|
|
53
|
+
env: { ...process.env, WEBAUTO_RUNTIME_MODE: 'unified', WEBAUTO_UNIFIED_PORT: '7704' },
|
|
54
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
unifiedProc.stdout.on('data', (data) => {
|
|
58
|
+
log(`[Unified API] ${data.toString().trim()}`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
unifiedProc.stderr.on('data', (data) => {
|
|
62
|
+
log(`[Unified API ERROR] ${data.toString().trim()}`);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
unifiedProc.on('error', (err) => {
|
|
66
|
+
log(`❌ Unified API 启动错误: ${err.message}`);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const unifiedOk = await testHealth(7704, 'Unified API', 15000);
|
|
70
|
+
|
|
71
|
+
if (unifiedOk) {
|
|
72
|
+
// 4. 测试基本 API
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch('http://127.0.0.1:7704/v1/system/state');
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
log(`📋 系统状态: ${JSON.stringify(data)}`);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
log(`❌ 系统状态获取失败: ${e.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 5. 清理
|
|
83
|
+
unifiedProc.kill('SIGTERM');
|
|
84
|
+
await wait(1000);
|
|
85
|
+
unifiedProc.kill('SIGKILL');
|
|
86
|
+
|
|
87
|
+
log('🧹 测试完成');
|
|
88
|
+
process.exit(unifiedOk ? 0 : 1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
main().catch(e => {
|
|
92
|
+
console.error('❌ 测试失败:', e.message);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
8
|
+
const ROOT_PACKAGE = path.join(ROOT, 'package.json');
|
|
9
|
+
const DESKTOP_PACKAGE = path.join(ROOT, 'apps', 'desktop-console', 'package.json');
|
|
10
|
+
const ROOT_LOCK = path.join(ROOT, 'package-lock.json');
|
|
11
|
+
|
|
12
|
+
function usage(exitCode = 0) {
|
|
13
|
+
console.log(`Usage:
|
|
14
|
+
node scripts/bump-version.mjs [patch|minor|major] [--json]
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
node scripts/bump-version.mjs
|
|
18
|
+
node scripts/bump-version.mjs patch
|
|
19
|
+
node scripts/bump-version.mjs minor --json`);
|
|
20
|
+
process.exit(exitCode);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readJson(filePath) {
|
|
24
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function writeJson(filePath, json) {
|
|
28
|
+
writeFileSync(filePath, `${JSON.stringify(json, null, 2)}\n`, 'utf8');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function bumpSemver(version, kind) {
|
|
32
|
+
const match = String(version || '').trim().match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
33
|
+
if (!match) {
|
|
34
|
+
throw new Error(`invalid semver: ${version}`);
|
|
35
|
+
}
|
|
36
|
+
let major = Number(match[1]);
|
|
37
|
+
let minor = Number(match[2]);
|
|
38
|
+
let patch = Number(match[3]);
|
|
39
|
+
if (kind === 'major') {
|
|
40
|
+
major += 1;
|
|
41
|
+
minor = 0;
|
|
42
|
+
patch = 0;
|
|
43
|
+
} else if (kind === 'minor') {
|
|
44
|
+
minor += 1;
|
|
45
|
+
patch = 0;
|
|
46
|
+
} else {
|
|
47
|
+
patch += 1;
|
|
48
|
+
}
|
|
49
|
+
return `${major}.${minor}.${patch}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function updatePackageVersion(filePath, nextVersion) {
|
|
53
|
+
const json = readJson(filePath);
|
|
54
|
+
const prev = String(json.version || '').trim();
|
|
55
|
+
if (!prev) throw new Error(`missing version in ${filePath}`);
|
|
56
|
+
json.version = nextVersion;
|
|
57
|
+
writeJson(filePath, json);
|
|
58
|
+
return prev;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function updateRootLockVersion(filePath, nextVersion) {
|
|
62
|
+
if (!existsSync(filePath)) return false;
|
|
63
|
+
const lock = readJson(filePath);
|
|
64
|
+
lock.version = nextVersion;
|
|
65
|
+
if (lock.packages && lock.packages['']) {
|
|
66
|
+
lock.packages[''].version = nextVersion;
|
|
67
|
+
}
|
|
68
|
+
writeJson(filePath, lock);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function main() {
|
|
73
|
+
const argv = process.argv.slice(2);
|
|
74
|
+
if (argv.includes('--help') || argv.includes('-h')) usage(0);
|
|
75
|
+
|
|
76
|
+
const jsonMode = argv.includes('--json');
|
|
77
|
+
const kindArg = argv.find((arg) => !arg.startsWith('-')) || 'patch';
|
|
78
|
+
const kind = String(kindArg || 'patch').trim().toLowerCase();
|
|
79
|
+
if (!['patch', 'minor', 'major'].includes(kind)) {
|
|
80
|
+
console.error(`[version] unsupported bump type: ${kind}`);
|
|
81
|
+
usage(2);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const rootPkg = readJson(ROOT_PACKAGE);
|
|
85
|
+
const prevRootVersion = String(rootPkg.version || '').trim();
|
|
86
|
+
if (!prevRootVersion) {
|
|
87
|
+
throw new Error(`missing version in ${ROOT_PACKAGE}`);
|
|
88
|
+
}
|
|
89
|
+
const nextVersion = bumpSemver(prevRootVersion, kind);
|
|
90
|
+
|
|
91
|
+
const prevDesktopVersion = updatePackageVersion(DESKTOP_PACKAGE, nextVersion);
|
|
92
|
+
const prevVersion = updatePackageVersion(ROOT_PACKAGE, nextVersion);
|
|
93
|
+
const lockUpdated = updateRootLockVersion(ROOT_LOCK, nextVersion);
|
|
94
|
+
|
|
95
|
+
const result = {
|
|
96
|
+
ok: true,
|
|
97
|
+
kind,
|
|
98
|
+
previous: prevVersion,
|
|
99
|
+
next: nextVersion,
|
|
100
|
+
desktopPrevious: prevDesktopVersion,
|
|
101
|
+
desktopNext: nextVersion,
|
|
102
|
+
lockUpdated,
|
|
103
|
+
files: [
|
|
104
|
+
ROOT_PACKAGE,
|
|
105
|
+
DESKTOP_PACKAGE,
|
|
106
|
+
...(lockUpdated ? [ROOT_LOCK] : []),
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (jsonMode) {
|
|
111
|
+
console.log(JSON.stringify(result, null, 2));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
console.log(`[version] bump ${kind}: ${prevVersion} -> ${nextVersion}`);
|
|
115
|
+
console.log(`[version] updated: ${ROOT_PACKAGE}`);
|
|
116
|
+
console.log(`[version] updated: ${DESKTOP_PACKAGE}`);
|
|
117
|
+
if (lockUpdated) console.log(`[version] updated: ${ROOT_LOCK}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
main();
|
|
@@ -296,11 +296,13 @@ class UnifiedApiServer {
|
|
|
296
296
|
if (existing) return existing;
|
|
297
297
|
const profileId = String(seed?.profileId || 'unknown').trim() || 'unknown';
|
|
298
298
|
const keyword = String(seed?.keyword || '').trim();
|
|
299
|
+
const uiTriggerId = String(seed?.uiTriggerId || seed?.triggerId || '').trim();
|
|
299
300
|
const phase = normalizeTaskPhase(seed?.phase);
|
|
300
301
|
return this.taskRegistry.createTask({
|
|
301
302
|
runId: normalizedRunId,
|
|
302
303
|
profileId,
|
|
303
304
|
keyword,
|
|
305
|
+
uiTriggerId,
|
|
304
306
|
phase,
|
|
305
307
|
});
|
|
306
308
|
};
|
|
@@ -311,11 +313,13 @@ class UnifiedApiServer {
|
|
|
311
313
|
const phase = normalizeTaskPhase(payload?.phase);
|
|
312
314
|
const profileId = String(payload?.profileId || '').trim();
|
|
313
315
|
const keyword = String(payload?.keyword || '').trim();
|
|
316
|
+
const uiTriggerId = String(payload?.uiTriggerId || payload?.triggerId || '').trim();
|
|
314
317
|
const details = payload?.details && typeof payload.details === 'object' ? payload.details : undefined;
|
|
315
318
|
const patch: any = {};
|
|
316
319
|
if (phase !== 'unknown') patch.phase = phase;
|
|
317
320
|
if (profileId) patch.profileId = profileId;
|
|
318
321
|
if (keyword) patch.keyword = keyword;
|
|
322
|
+
if (uiTriggerId) patch.uiTriggerId = uiTriggerId;
|
|
319
323
|
if (details) patch.details = details;
|
|
320
324
|
if (Object.keys(patch).length > 0) {
|
|
321
325
|
this.taskRegistry.updateTask(normalizedRunId, patch);
|
|
@@ -37,10 +37,12 @@ export interface TaskState {
|
|
|
37
37
|
runId: string;
|
|
38
38
|
profileId: string;
|
|
39
39
|
keyword: string;
|
|
40
|
+
uiTriggerId?: string;
|
|
40
41
|
phase: TaskPhase;
|
|
41
42
|
status: TaskStatus;
|
|
42
43
|
progress: TaskProgress;
|
|
43
44
|
stats: TaskStats;
|
|
45
|
+
createdAt: number;
|
|
44
46
|
startedAt: number;
|
|
45
47
|
updatedAt: number;
|
|
46
48
|
completedAt?: number;
|
|
@@ -71,6 +73,7 @@ class TaskStateRegistry {
|
|
|
71
73
|
runId: string;
|
|
72
74
|
profileId: string;
|
|
73
75
|
keyword: string;
|
|
76
|
+
uiTriggerId?: string;
|
|
74
77
|
phase?: TaskPhase;
|
|
75
78
|
}): TaskState {
|
|
76
79
|
const now = Date.now();
|
|
@@ -78,6 +81,7 @@ class TaskStateRegistry {
|
|
|
78
81
|
runId: partial.runId,
|
|
79
82
|
profileId: partial.profileId,
|
|
80
83
|
keyword: partial.keyword,
|
|
84
|
+
uiTriggerId: partial.uiTriggerId ? String(partial.uiTriggerId).trim() : undefined,
|
|
81
85
|
phase: partial.phase || 'unknown',
|
|
82
86
|
status: 'starting',
|
|
83
87
|
progress: { total: 0, processed: 0, failed: 0 },
|
|
@@ -89,6 +93,7 @@ class TaskStateRegistry {
|
|
|
89
93
|
imagesDownloaded: 0,
|
|
90
94
|
ocrProcessed: 0,
|
|
91
95
|
},
|
|
96
|
+
createdAt: now,
|
|
92
97
|
startedAt: now,
|
|
93
98
|
updatedAt: now,
|
|
94
99
|
details: {
|