@sma1lboy/kobe 0.5.11 → 0.5.13
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/bin/kobed.js +88 -30
- package/dist/cli/index.js +2251 -1516
- package/package.json +2 -1
package/dist/bin/kobed.js
CHANGED
|
@@ -99,30 +99,36 @@ class KobeDaemonClient {
|
|
|
99
99
|
nextId = 1;
|
|
100
100
|
pending = new Map;
|
|
101
101
|
handlers = new Map;
|
|
102
|
+
lifecycleHandlers = new Map;
|
|
103
|
+
connecting = null;
|
|
104
|
+
disposed = false;
|
|
102
105
|
constructor(socketPath) {
|
|
103
106
|
this.socketPath = socketPath;
|
|
104
107
|
}
|
|
105
108
|
connect() {
|
|
106
109
|
if (this.socket)
|
|
107
110
|
return Promise.resolve();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
});
|
|
111
|
+
if (this.disposed)
|
|
112
|
+
return Promise.reject(new Error("daemon client disposed"));
|
|
113
|
+
if (this.connecting)
|
|
114
|
+
return this.connecting;
|
|
115
|
+
const p = this.openSocket();
|
|
116
|
+
this.connecting = p;
|
|
117
|
+
const cleanup = () => {
|
|
118
|
+
if (this.connecting === p)
|
|
119
|
+
this.connecting = null;
|
|
120
|
+
};
|
|
121
|
+
p.then(cleanup, cleanup);
|
|
122
|
+
return p;
|
|
121
123
|
}
|
|
122
124
|
close() {
|
|
125
|
+
this.disposed = true;
|
|
123
126
|
this.socket?.end();
|
|
124
127
|
this.socket = null;
|
|
125
128
|
}
|
|
129
|
+
forceDisconnect() {
|
|
130
|
+
this.socket?.destroy();
|
|
131
|
+
}
|
|
126
132
|
on(name, handler) {
|
|
127
133
|
let set = this.handlers.get(name);
|
|
128
134
|
if (!set) {
|
|
@@ -136,6 +142,19 @@ class KobeDaemonClient {
|
|
|
136
142
|
this.handlers.delete(name);
|
|
137
143
|
};
|
|
138
144
|
}
|
|
145
|
+
onLifecycle(name, handler) {
|
|
146
|
+
let set = this.lifecycleHandlers.get(name);
|
|
147
|
+
if (!set) {
|
|
148
|
+
set = new Set;
|
|
149
|
+
this.lifecycleHandlers.set(name, set);
|
|
150
|
+
}
|
|
151
|
+
set.add(handler);
|
|
152
|
+
return () => {
|
|
153
|
+
set?.delete(handler);
|
|
154
|
+
if (set?.size === 0)
|
|
155
|
+
this.lifecycleHandlers.delete(name);
|
|
156
|
+
};
|
|
157
|
+
}
|
|
139
158
|
async request(name, payload) {
|
|
140
159
|
await this.connect();
|
|
141
160
|
const socket = this.socket;
|
|
@@ -148,6 +167,44 @@ class KobeDaemonClient {
|
|
|
148
167
|
socket.write(frameToLine({ type: "request", id, name, payload }));
|
|
149
168
|
return promise;
|
|
150
169
|
}
|
|
170
|
+
openSocket() {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const socket = connect(this.socketPath);
|
|
173
|
+
this.socket = socket;
|
|
174
|
+
const onConnect = () => {
|
|
175
|
+
socket.off("error", onError);
|
|
176
|
+
resolve();
|
|
177
|
+
};
|
|
178
|
+
const onError = (err) => {
|
|
179
|
+
socket.off("connect", onConnect);
|
|
180
|
+
if (this.socket === socket)
|
|
181
|
+
this.socket = null;
|
|
182
|
+
reject(err);
|
|
183
|
+
};
|
|
184
|
+
socket.once("connect", onConnect);
|
|
185
|
+
socket.once("error", onError);
|
|
186
|
+
socket.on("data", (chunk) => this.onData(chunk.toString("utf8")));
|
|
187
|
+
socket.on("close", () => this.onSocketClose(socket));
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
onSocketClose(which) {
|
|
191
|
+
if (this.socket !== which && this.socket !== null)
|
|
192
|
+
return;
|
|
193
|
+
this.socket = null;
|
|
194
|
+
for (const pending of this.pending.values())
|
|
195
|
+
pending.reject(new Error("daemon connection closed"));
|
|
196
|
+
this.pending.clear();
|
|
197
|
+
this.emitLifecycle("close");
|
|
198
|
+
}
|
|
199
|
+
emitLifecycle(name) {
|
|
200
|
+
for (const handler of this.lifecycleHandlers.get(name) ?? []) {
|
|
201
|
+
try {
|
|
202
|
+
handler();
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.error(`[kobe] lifecycle handler for "${name}" threw:`, err);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
151
208
|
onData(chunk) {
|
|
152
209
|
this.buffer += chunk;
|
|
153
210
|
let nl = this.buffer.indexOf(`
|
|
@@ -192,11 +249,10 @@ import { spawn } from "child_process";
|
|
|
192
249
|
import { existsSync } from "fs";
|
|
193
250
|
import { dirname, join as join2, resolve } from "path";
|
|
194
251
|
import { fileURLToPath } from "url";
|
|
195
|
-
async function
|
|
252
|
+
async function ensureDaemonReachable() {
|
|
196
253
|
const socketPath = defaultDaemonSocketPath();
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return client;
|
|
254
|
+
if (await testCanConnect(socketPath))
|
|
255
|
+
return socketPath;
|
|
200
256
|
const { entry, runWithBun } = resolveKobedEntry();
|
|
201
257
|
const child = runWithBun ? spawn(process.execPath, [entry, "start"], {
|
|
202
258
|
detached: true,
|
|
@@ -211,24 +267,26 @@ async function connectOrStartDaemon() {
|
|
|
211
267
|
const deadline = Date.now() + 5000;
|
|
212
268
|
let lastErr;
|
|
213
269
|
while (Date.now() < deadline) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return next;
|
|
218
|
-
} catch (err) {
|
|
219
|
-
lastErr = err;
|
|
220
|
-
next.close();
|
|
221
|
-
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
222
|
-
}
|
|
270
|
+
if (await testCanConnect(socketPath))
|
|
271
|
+
return socketPath;
|
|
272
|
+
await new Promise((resolveTimer) => setTimeout(resolveTimer, 100));
|
|
223
273
|
}
|
|
224
|
-
throw new Error(`kobe: daemon did not start at ${socketPath}: ${lastErr instanceof Error ? lastErr.message :
|
|
274
|
+
throw new Error(`kobe: daemon did not start at ${socketPath}: ${lastErr instanceof Error ? lastErr.message : "timeout"}`);
|
|
225
275
|
}
|
|
226
|
-
async function
|
|
276
|
+
async function connectOrStartDaemon() {
|
|
277
|
+
const socketPath = await ensureDaemonReachable();
|
|
278
|
+
const client = new KobeDaemonClient(socketPath);
|
|
279
|
+
await client.connect();
|
|
280
|
+
return client;
|
|
281
|
+
}
|
|
282
|
+
async function testCanConnect(socketPath) {
|
|
283
|
+
const probe = new KobeDaemonClient(socketPath);
|
|
227
284
|
try {
|
|
228
|
-
await
|
|
285
|
+
await probe.connect();
|
|
286
|
+
probe.close();
|
|
229
287
|
return true;
|
|
230
288
|
} catch {
|
|
231
|
-
|
|
289
|
+
probe.close();
|
|
232
290
|
return false;
|
|
233
291
|
}
|
|
234
292
|
}
|