blun-king-cli 9.1.10 → 9.1.12

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.
@@ -5,6 +5,7 @@ const fs = require('node:fs');
5
5
  const net = require('node:net');
6
6
  const os = require('node:os');
7
7
  const path = require('node:path');
8
+ const { spawn } = require('node:child_process');
8
9
 
9
10
  const LEASE_ADOPT_MESSAGE = 'blun-update-lease-adopted';
10
11
  const LEASE_CANCEL_MESSAGE = 'blun-update-lease-cancel';
@@ -15,6 +16,7 @@ const LEASE_UNAVAILABLE_MESSAGE = 'blun-update-lease-unavailable';
15
16
  const LEASE_PROTOCOL_VERSION = 'BLUN_UPDATE_LEASE_V3';
16
17
  const LEASE_PROBE_TIMEOUT_MS = 500;
17
18
  const MAX_POSIX_SOCKET_PATH_BYTES = 96;
19
+ const LEASE_CONNECTIONS = Symbol('blunLeaseConnections');
18
20
 
19
21
  function canonicalPackageIdentity(packageRoot, options = {}) {
20
22
  if (typeof packageRoot !== 'string' || packageRoot.trim().length === 0) {
@@ -32,7 +34,11 @@ function canonicalPackageIdentity(packageRoot, options = {}) {
32
34
 
33
35
  function deriveUpdateLeaseEndpoint(packageRoot, options = {}) {
34
36
  const platform = options.platform || process.platform;
35
- const role = options.role === 'install' ? 'install' : 'notice';
37
+ const role = options.role === 'install'
38
+ ? 'install'
39
+ : options.role === 'runtime'
40
+ ? 'runtime'
41
+ : 'notice';
36
42
  const identity = canonicalPackageIdentity(packageRoot, options);
37
43
  const digest = createHash('sha256')
38
44
  .update(`blun-update-v3\0${role}\0${identity}`)
@@ -64,20 +70,201 @@ function deriveUpdateLeaseEndpoint(packageRoot, options = {}) {
64
70
  return Object.freeze({ directory, magic, path: socketPath, platform });
65
71
  }
66
72
 
67
- function closeServer(server) {
73
+ function connectRuntimeBroker(endpoint, options = {}) {
74
+ const netImpl = options.netImpl || net;
75
+ const timeoutMs = options.probeTimeoutMs || LEASE_PROBE_TIMEOUT_MS;
68
76
  return new Promise((resolve) => {
69
- if (!server.listening) {
70
- resolve();
71
- return;
72
- }
77
+ let settled = false;
78
+ let received = '';
79
+ let socket;
80
+ const finish = (result) => {
81
+ if (settled) return;
82
+ settled = true;
83
+ clearTimeout(timer);
84
+ if (!result.acquired) {
85
+ try {
86
+ socket?.destroy();
87
+ } catch {}
88
+ }
89
+ resolve(result);
90
+ };
91
+ const timer = setTimeout(() => finish({ acquired: false, reason: 'unavailable' }), timeoutMs);
92
+ timer.unref?.();
73
93
  try {
74
- server.close(() => resolve());
94
+ socket = netImpl.createConnection({ path: endpoint.path });
95
+ socket.setEncoding?.('utf8');
96
+ socket.once('connect', () => socket.write(endpoint.magic));
97
+ socket.on('data', (chunk) => {
98
+ received += String(chunk);
99
+ if (!received.includes('\n')) return;
100
+ if (received !== endpoint.magic) {
101
+ finish({ acquired: false, reason: 'unavailable' });
102
+ return;
103
+ }
104
+ let owned = true;
105
+ let released = false;
106
+ socket.once('close', () => { owned = false; });
107
+ socket.once('error', () => { owned = false; });
108
+ const lease = Object.freeze({
109
+ assertOwned() {
110
+ if (!owned || socket.destroyed) throw new Error('RUNTIME_LEASE_LOST');
111
+ },
112
+ async release() {
113
+ if (released) return;
114
+ released = true;
115
+ owned = false;
116
+ if (socket.destroyed) return;
117
+ socket.destroy();
118
+ },
119
+ unref() {
120
+ socket.unref?.();
121
+ },
122
+ });
123
+ finish({ acquired: true, lease });
124
+ });
125
+ socket.once('error', () => finish({ acquired: false, reason: 'unavailable' }));
126
+ socket.once('close', () => finish({ acquired: false, reason: 'unavailable' }));
75
127
  } catch {
76
- resolve();
128
+ finish({ acquired: false, reason: 'unavailable' });
77
129
  }
78
130
  });
79
131
  }
80
132
 
133
+ function spawnRuntimeBroker(packageRoot, options = {}) {
134
+ const spawnImpl = options.spawnImpl || spawn;
135
+ const child = spawnImpl(
136
+ options.execPath || process.execPath,
137
+ [__filename, '--runtime-broker'],
138
+ {
139
+ cwd: os.tmpdir(),
140
+ detached: true,
141
+ env: {
142
+ ...process.env,
143
+ BLUN_RUNTIME_BROKER_PACKAGE_ROOT: packageRoot,
144
+ },
145
+ stdio: 'ignore',
146
+ windowsHide: true,
147
+ },
148
+ );
149
+ child.unref?.();
150
+ }
151
+
152
+ async function acquireSharedRuntimeLease(options = {}) {
153
+ const packageRoot = canonicalPackageIdentity(options.packageRoot, options);
154
+ const endpoint = deriveUpdateLeaseEndpoint(packageRoot, { ...options, role: 'runtime' });
155
+ const deadline = Date.now() + (options.startTimeoutMs || 3_000);
156
+ let spawned = false;
157
+ while (Date.now() < deadline) {
158
+ const connected = await connectRuntimeBroker(endpoint, options);
159
+ if (connected.acquired) return connected;
160
+ if (!spawned) {
161
+ spawned = true;
162
+ spawnRuntimeBroker(packageRoot, options);
163
+ }
164
+ await new Promise((resolve) => setTimeout(resolve, 25));
165
+ }
166
+ const install = await tryAcquireUpdateLease({ ...options, packageRoot, role: 'install' });
167
+ if (install.acquired) {
168
+ await install.lease.release();
169
+ return { acquired: false, reason: 'unavailable' };
170
+ }
171
+ return { acquired: false, reason: install.reason };
172
+ }
173
+
174
+ async function runRuntimeBroker(options = {}) {
175
+ const packageRoot = options.packageRoot
176
+ || String(process.env.BLUN_RUNTIME_BROKER_PACKAGE_ROOT || '');
177
+ if (!path.isAbsolute(packageRoot)) throw new Error('RUNTIME_BROKER_PACKAGE_ROOT_REQUIRED');
178
+ const install = await tryAcquireUpdateLease({ packageRoot, role: 'install' });
179
+ if (!install.acquired) return false;
180
+
181
+ const endpoint = deriveUpdateLeaseEndpoint(packageRoot, { role: 'runtime' });
182
+ ensurePrivateSocketDirectory(endpoint);
183
+ const holders = new Set();
184
+ let idleTimer;
185
+ let closing = false;
186
+ let identity;
187
+ const closeBroker = async (server) => {
188
+ if (closing) return;
189
+ closing = true;
190
+ clearTimeout(idleTimer);
191
+ await closeServer(server);
192
+ removeOwnedSocket(endpoint, identity);
193
+ await install.lease.release();
194
+ };
195
+ const server = net.createServer((socket) => {
196
+ socket.setEncoding('utf8');
197
+ let received = '';
198
+ let admitted = false;
199
+ const reject = () => {
200
+ try {
201
+ socket.destroy();
202
+ } catch {}
203
+ };
204
+ const onData = (chunk) => {
205
+ if (admitted) return;
206
+ received += String(chunk);
207
+ if (Buffer.byteLength(received, 'utf8') > Buffer.byteLength(endpoint.magic, 'utf8')) {
208
+ reject();
209
+ return;
210
+ }
211
+ if (!received.includes('\n')) return;
212
+ if (received !== endpoint.magic) {
213
+ reject();
214
+ return;
215
+ }
216
+ admitted = true;
217
+ clearTimeout(idleTimer);
218
+ holders.add(socket);
219
+ socket.write(endpoint.magic);
220
+ };
221
+ socket.on('data', onData);
222
+ socket.on('error', () => {});
223
+ socket.once('close', () => {
224
+ holders.delete(socket);
225
+ if (holders.size === 0 && !closing) {
226
+ idleTimer = setTimeout(() => { void closeBroker(server); }, 0);
227
+ }
228
+ });
229
+ });
230
+ try {
231
+ await new Promise((resolve, reject) => {
232
+ const onError = (error) => {
233
+ server.removeListener('error', onError);
234
+ reject(error);
235
+ };
236
+ server.once('error', onError);
237
+ server.listen({ path: endpoint.path, exclusive: true }, () => {
238
+ server.removeListener('error', onError);
239
+ resolve();
240
+ });
241
+ });
242
+ identity = recordSocketIdentity(endpoint);
243
+ } catch {
244
+ await closeServer(server);
245
+ await install.lease.release();
246
+ return false;
247
+ }
248
+ install.lease.unref();
249
+ server.unref?.();
250
+ idleTimer = setTimeout(() => { void closeBroker(server); }, 5_000);
251
+ return true;
252
+ }
253
+
254
+ function closeServer(server) {
255
+ for (const socket of server[LEASE_CONNECTIONS] || []) {
256
+ try {
257
+ socket.destroy();
258
+ } catch {}
259
+ }
260
+ if (!server.listening) return Promise.resolve();
261
+ try {
262
+ server.close();
263
+ server.unref?.();
264
+ } catch {}
265
+ return Promise.resolve();
266
+ }
267
+
81
268
  function ensurePrivateSocketDirectory(endpoint, options = {}) {
82
269
  if (endpoint.platform === 'win32') return;
83
270
  const fsImpl = options.fsImpl || fs;
@@ -98,9 +285,12 @@ function listenForLease(endpoint, options = {}) {
98
285
  const netImpl = options.netImpl || net;
99
286
  return new Promise((resolve, reject) => {
100
287
  const server = netImpl.createServer((socket) => {
288
+ server[LEASE_CONNECTIONS].add(socket);
289
+ socket.once('close', () => server[LEASE_CONNECTIONS].delete(socket));
101
290
  socket.on('error', () => {});
102
291
  socket.end(endpoint.magic);
103
292
  });
293
+ server[LEASE_CONNECTIONS] = new Set();
104
294
  const onError = (error) => {
105
295
  server.removeListener('error', onError);
106
296
  reject(error);
@@ -391,9 +581,17 @@ function runInstallerWorker() {
391
581
 
392
582
  if (require.main === module && process.argv[2] === '--installer-worker') {
393
583
  runInstallerWorker();
584
+ } else if (require.main === module && process.argv[2] === '--runtime-broker') {
585
+ void runRuntimeBroker().then((started) => {
586
+ if (!started) process.exitCode = 75;
587
+ }).catch(() => {
588
+ process.exitCode = 1;
589
+ });
394
590
  }
395
591
 
396
592
  module.exports = {
593
+ acquireSharedRuntimeLease,
397
594
  deriveUpdateLeaseEndpoint,
595
+ runRuntimeBroker,
398
596
  tryAcquireUpdateLease,
399
597
  };