@lixiangtian/webbridge 0.1.0

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.
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * webbridge CLI 入口。
4
+ *
5
+ * 用法:
6
+ * webbridge 前台启动(默认)
7
+ * webbridge start 后台启动(守护进程)
8
+ * webbridge stop 停止后台进程
9
+ * webbridge status 查看运行状态
10
+ * webbridge version 版本
11
+ *
12
+ * 需 Node 22+(pi-coding-agent 依赖 fs.globSync)。
13
+ */
14
+ import { spawn, spawnSync } from 'node:child_process';
15
+ import * as fs from 'node:fs';
16
+ import * as net from 'node:net';
17
+ import * as path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+
20
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
+ const BUNDLE = path.resolve(__dirname, '../dist/webbridge.bundle.mjs');
22
+ const PID_FILE = path.join(process.env.HOME ?? '.', '.webbridge.pid');
23
+ const PORT = 8765;
24
+
25
+ // ---- 版本检查 ----
26
+ const [major] = process.versions.node.split('.').map(Number);
27
+ if (major < 22) {
28
+ console.error(`✗ webbridge 需要 Node 22+(当前 v${process.versions.node})`);
29
+ console.error(' 升级:https://nodejs.org 或 nvm install 22');
30
+ process.exit(1);
31
+ }
32
+
33
+ if (!fs.existsSync(BUNDLE)) {
34
+ console.error('✗ 未找到 dist/webbridge.bundle.mjs,请重新安装本包(npm i -g @linglin/webbridge)');
35
+ process.exit(1);
36
+ }
37
+
38
+ const VERSION = JSON.parse(
39
+ fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8'),
40
+ ).version;
41
+
42
+ // ---- 工具 ----
43
+
44
+ /** 端口是否可连(bridge 存活探测) */
45
+ function isAlive() {
46
+ return new Promise((resolve) => {
47
+ const sock = net.connect({ host: '127.0.0.1', port: PORT, timeout: 800 });
48
+ sock.once('connect', () => {
49
+ sock.destroy();
50
+ resolve(true);
51
+ });
52
+ sock.once('error', () => resolve(false));
53
+ sock.once('timeout', () => {
54
+ sock.destroy();
55
+ resolve(false);
56
+ });
57
+ });
58
+ }
59
+
60
+ function readPid() {
61
+ try {
62
+ return Number(fs.readFileSync(PID_FILE, 'utf8').trim()) || null;
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+
68
+ function isPidAlive(pid) {
69
+ try {
70
+ process.kill(pid, 0);
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ // ---- 子命令 ----
78
+
79
+ async function cmdStart() {
80
+ if (await isAlive()) {
81
+ console.log('✓ webbridge 已在运行(ws://127.0.0.1:8765)');
82
+ return;
83
+ }
84
+ console.log('→ 后台启动 webbridge…');
85
+ const out = fs.openSync(path.join(process.env.HOME ?? '.', '.webbridge.log'), 'a');
86
+ const child = spawn(process.execPath, [BUNDLE], {
87
+ detached: true,
88
+ stdio: ['ignore', out, out],
89
+ env: process.env,
90
+ });
91
+ child.unref();
92
+ fs.writeFileSync(PID_FILE, String(child.pid));
93
+ // 等待就绪
94
+ for (let i = 0; i < 20; i++) {
95
+ await new Promise((r) => setTimeout(r, 300));
96
+ if (await isAlive()) {
97
+ console.log(`✓ webbridge 已启动(后台 pid ${child.pid},ws://127.0.0.1:${PORT})`);
98
+ console.log(' 日志:~/.webbridge.log 停止:webbridge stop');
99
+ return;
100
+ }
101
+ }
102
+ console.error('✗ 启动超时,查看 ~/.webbridge.log 排查');
103
+ process.exit(1);
104
+ }
105
+
106
+ async function cmdStop() {
107
+ const pid = readPid();
108
+ if (pid && isPidAlive(pid)) {
109
+ process.kill(pid, 'SIGTERM');
110
+ console.log(`✓ 已停止 webbridge(pid ${pid})`);
111
+ } else if (await isAlive()) {
112
+ console.log('! 找不到 pid 记录但端口在监听,可能由其他方式启动,未自动停止');
113
+ } else {
114
+ console.log('webbridge 未在运行');
115
+ }
116
+ try {
117
+ fs.unlinkSync(PID_FILE);
118
+ } catch {}
119
+ }
120
+
121
+ async function cmdStatus() {
122
+ const pid = readPid();
123
+ const alive = await isAlive();
124
+ if (alive) {
125
+ console.log(`✓ 运行中 ws://127.0.0.1:${PORT}${pid ? `(pid ${pid})` : '(外部启动)'}`);
126
+ } else {
127
+ console.log('○ 未运行');
128
+ }
129
+ }
130
+
131
+ function cmdRun() {
132
+ // 前台运行(Ctrl+C 退出),直接替换为 bundle 进程
133
+ const res = spawnSync(process.execPath, [BUNDLE], { stdio: 'inherit' });
134
+ process.exit(res.status ?? 0);
135
+ }
136
+
137
+ // ---- 分发 ----
138
+ const cmd = process.argv[2] ?? 'run';
139
+ switch (cmd) {
140
+ case 'start':
141
+ void cmdStart();
142
+ break;
143
+ case 'stop':
144
+ void cmdStop();
145
+ break;
146
+ case 'status':
147
+ void cmdStatus();
148
+ break;
149
+ case 'version':
150
+ case '-v':
151
+ case '--version':
152
+ console.log(`webbridge v${VERSION}`);
153
+ break;
154
+ case 'help':
155
+ case '-h':
156
+ case '--help':
157
+ console.log(`webbridge v${VERSION} — 灵犀本地桥接服务
158
+
159
+ 用法:
160
+ webbridge 前台启动(Ctrl+C 退出)
161
+ webbridge start 后台启动(守护)
162
+ webbridge stop 停止后台进程
163
+ webbridge status 查看运行状态
164
+ webbridge version 版本`);
165
+ break;
166
+ default:
167
+ console.error(`未知命令:${cmd}(webbridge help 查看用法)`);
168
+ process.exit(1);
169
+ }