@slicervm/sdk 0.1.1 → 0.1.3
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/index.cjs +390 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +158 -4
- package/dist/index.d.ts +158 -4
- package/dist/index.js +386 -28
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/dist/index.cjs
CHANGED
|
@@ -4,14 +4,19 @@ var http = require('http');
|
|
|
4
4
|
var https = require('https');
|
|
5
5
|
var url = require('url');
|
|
6
6
|
var os = require('os');
|
|
7
|
-
var
|
|
7
|
+
var path2 = require('path');
|
|
8
|
+
var net = require('net');
|
|
9
|
+
var fs = require('fs');
|
|
10
|
+
var ws = require('ws');
|
|
8
11
|
|
|
9
12
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
13
|
|
|
11
14
|
var http__default = /*#__PURE__*/_interopDefault(http);
|
|
12
15
|
var https__default = /*#__PURE__*/_interopDefault(https);
|
|
13
16
|
var os__default = /*#__PURE__*/_interopDefault(os);
|
|
14
|
-
var
|
|
17
|
+
var path2__default = /*#__PURE__*/_interopDefault(path2);
|
|
18
|
+
var net__default = /*#__PURE__*/_interopDefault(net);
|
|
19
|
+
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
15
20
|
|
|
16
21
|
// src/types.ts
|
|
17
22
|
var ExecStdioText = "text";
|
|
@@ -27,11 +32,11 @@ var SlicerAPIError = class extends Error {
|
|
|
27
32
|
method;
|
|
28
33
|
path;
|
|
29
34
|
body;
|
|
30
|
-
constructor(method,
|
|
31
|
-
super(`slicer ${method} ${
|
|
35
|
+
constructor(method, path3, status, body) {
|
|
36
|
+
super(`slicer ${method} ${path3} failed: ${status} ${body}`);
|
|
32
37
|
this.name = "SlicerAPIError";
|
|
33
38
|
this.method = method;
|
|
34
|
-
this.path =
|
|
39
|
+
this.path = path3;
|
|
35
40
|
this.status = status;
|
|
36
41
|
this.body = body;
|
|
37
42
|
}
|
|
@@ -44,7 +49,7 @@ function resolveTransport(baseURL) {
|
|
|
44
49
|
if (!trimmed) throw new Error("Slicer baseURL is required");
|
|
45
50
|
let candidate = trimmed;
|
|
46
51
|
if (candidate.startsWith("unix://")) candidate = candidate.slice("unix://".length);
|
|
47
|
-
if (candidate.startsWith("~/")) candidate =
|
|
52
|
+
if (candidate.startsWith("~/")) candidate = path2__default.default.join(os__default.default.homedir(), candidate.slice(2));
|
|
48
53
|
const socketLike = candidate.startsWith("/") || candidate.startsWith("./") || candidate.startsWith("../") || candidate.endsWith(".sock");
|
|
49
54
|
if (socketLike) return { kind: "socket", socketPath: candidate };
|
|
50
55
|
return { kind: "net", url: new url.URL(trimmed) };
|
|
@@ -156,9 +161,9 @@ var TransportClient = class {
|
|
|
156
161
|
});
|
|
157
162
|
}
|
|
158
163
|
/** Streaming request producing a Node Readable of the response body. */
|
|
159
|
-
requestStreamRaw(method, reqPath, body, contentType = "application/octet-stream") {
|
|
164
|
+
requestStreamRaw(method, reqPath, body, contentType = "application/octet-stream", extraHeaders = {}) {
|
|
160
165
|
return new Promise((resolve, reject) => {
|
|
161
|
-
const headers = {};
|
|
166
|
+
const headers = { ...extraHeaders };
|
|
162
167
|
if (body instanceof Buffer) {
|
|
163
168
|
headers["Content-Type"] = contentType;
|
|
164
169
|
headers["Content-Length"] = String(body.length);
|
|
@@ -303,6 +308,258 @@ function secretFromWire(w) {
|
|
|
303
308
|
...w.modified_at !== void 0 && { modifiedAt: w.modified_at }
|
|
304
309
|
};
|
|
305
310
|
}
|
|
311
|
+
function looksLikeUnixSocketPath(s) {
|
|
312
|
+
if (!s) return false;
|
|
313
|
+
return s.startsWith("/") || s.startsWith("./") || s.startsWith("../") || s.includes("/");
|
|
314
|
+
}
|
|
315
|
+
function parseAddressMapping(spec) {
|
|
316
|
+
const lastColon = spec.lastIndexOf(":");
|
|
317
|
+
if (lastColon !== -1) {
|
|
318
|
+
const localPart = spec.slice(0, lastColon);
|
|
319
|
+
const remotePart = spec.slice(lastColon + 1);
|
|
320
|
+
if (looksLikeUnixSocketPath(localPart) && looksLikeUnixSocketPath(remotePart)) {
|
|
321
|
+
const localAbs = path2__default.default.isAbsolute(localPart) ? localPart : path2__default.default.resolve(localPart);
|
|
322
|
+
return { rawSpec: spec, listenUnixPath: localAbs, remoteUnixPath: remotePart };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const tcpToUnix = spec.indexOf(":/");
|
|
326
|
+
if (tcpToUnix !== -1) {
|
|
327
|
+
const listenPart = spec.slice(0, tcpToUnix);
|
|
328
|
+
const socketPath = spec.slice(tcpToUnix + 1);
|
|
329
|
+
const innerColon = listenPart.lastIndexOf(":");
|
|
330
|
+
if (innerColon === -1) {
|
|
331
|
+
return {
|
|
332
|
+
rawSpec: spec,
|
|
333
|
+
listenAddr: "0.0.0.0",
|
|
334
|
+
listenPort: parsePort(listenPart, spec),
|
|
335
|
+
remoteUnixPath: socketPath
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
rawSpec: spec,
|
|
340
|
+
listenAddr: listenPart.slice(0, innerColon),
|
|
341
|
+
listenPort: parsePort(listenPart.slice(innerColon + 1), spec),
|
|
342
|
+
remoteUnixPath: socketPath
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
const parts = spec.split(":");
|
|
346
|
+
switch (parts.length) {
|
|
347
|
+
case 2:
|
|
348
|
+
return {
|
|
349
|
+
rawSpec: spec,
|
|
350
|
+
listenAddr: parts[0],
|
|
351
|
+
listenPort: parsePort(parts[1], spec),
|
|
352
|
+
remoteHost: parts[0],
|
|
353
|
+
remotePort: parsePort(parts[1], spec)
|
|
354
|
+
};
|
|
355
|
+
case 3:
|
|
356
|
+
return {
|
|
357
|
+
rawSpec: spec,
|
|
358
|
+
listenAddr: parts[1],
|
|
359
|
+
listenPort: parsePort(parts[0], spec),
|
|
360
|
+
remoteHost: parts[1],
|
|
361
|
+
remotePort: parsePort(parts[2], spec)
|
|
362
|
+
};
|
|
363
|
+
case 4:
|
|
364
|
+
return {
|
|
365
|
+
rawSpec: spec,
|
|
366
|
+
listenAddr: parts[0],
|
|
367
|
+
listenPort: parsePort(parts[1], spec),
|
|
368
|
+
remoteHost: parts[2],
|
|
369
|
+
remotePort: parsePort(parts[3], spec)
|
|
370
|
+
};
|
|
371
|
+
default:
|
|
372
|
+
throw new Error(`invalid forward spec ${JSON.stringify(spec)}: expected 2-4 colon-separated parts`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function parsePort(s, spec) {
|
|
376
|
+
const n = Number(s);
|
|
377
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535) {
|
|
378
|
+
throw new Error(`invalid port ${JSON.stringify(s)} in forward spec ${JSON.stringify(spec)}`);
|
|
379
|
+
}
|
|
380
|
+
return n;
|
|
381
|
+
}
|
|
382
|
+
function isListenUnix(m) {
|
|
383
|
+
return m.listenUnixPath !== void 0;
|
|
384
|
+
}
|
|
385
|
+
function remoteTargetHeader(m) {
|
|
386
|
+
if (m.remoteUnixPath) return `unix:${m.remoteUnixPath}`;
|
|
387
|
+
return `${m.remoteHost}:${m.remotePort}`;
|
|
388
|
+
}
|
|
389
|
+
function listenAddressDescription(m) {
|
|
390
|
+
if (isListenUnix(m)) return m.listenUnixPath;
|
|
391
|
+
return `${m.listenAddr}:${m.listenPort}`;
|
|
392
|
+
}
|
|
393
|
+
var Forwarder = class _Forwarder {
|
|
394
|
+
constructor(init, mappings) {
|
|
395
|
+
this.init = init;
|
|
396
|
+
this.mappings = mappings;
|
|
397
|
+
}
|
|
398
|
+
init;
|
|
399
|
+
mappings;
|
|
400
|
+
listeners = [];
|
|
401
|
+
servers = [];
|
|
402
|
+
liveSockets = /* @__PURE__ */ new Set();
|
|
403
|
+
closed = false;
|
|
404
|
+
static async start(init) {
|
|
405
|
+
if (init.specs.length === 0) {
|
|
406
|
+
throw new Error("Forwarder requires at least one forward spec");
|
|
407
|
+
}
|
|
408
|
+
const mappings = init.specs.map(parseAddressMapping);
|
|
409
|
+
const fwd = new _Forwarder(init, mappings);
|
|
410
|
+
try {
|
|
411
|
+
await fwd.bindAll();
|
|
412
|
+
} catch (err) {
|
|
413
|
+
await fwd.close();
|
|
414
|
+
throw err;
|
|
415
|
+
}
|
|
416
|
+
return fwd;
|
|
417
|
+
}
|
|
418
|
+
async bindAll() {
|
|
419
|
+
for (const m of this.mappings) {
|
|
420
|
+
const server = net.createServer((socket) => this.handleAccept(m, socket));
|
|
421
|
+
const { local, port } = await listen(server, m);
|
|
422
|
+
this.servers.push(server);
|
|
423
|
+
this.listeners.push({
|
|
424
|
+
spec: m.rawSpec,
|
|
425
|
+
local,
|
|
426
|
+
remote: remoteTargetHeader(m),
|
|
427
|
+
...port !== void 0 && { port }
|
|
428
|
+
});
|
|
429
|
+
this.log(`listen ${local} \u2192 ${remoteTargetHeader(m)}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
handleAccept(mapping, socket) {
|
|
433
|
+
if (this.closed) {
|
|
434
|
+
socket.destroy();
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
socket.on("error", () => {
|
|
438
|
+
});
|
|
439
|
+
const ws$1 = openWebSocket(this.init, mapping);
|
|
440
|
+
let closed = false;
|
|
441
|
+
const cleanup = () => {
|
|
442
|
+
if (closed) return;
|
|
443
|
+
closed = true;
|
|
444
|
+
try {
|
|
445
|
+
socket.destroy();
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
ws$1.close();
|
|
450
|
+
} catch {
|
|
451
|
+
}
|
|
452
|
+
this.liveSockets.delete(handle);
|
|
453
|
+
};
|
|
454
|
+
const handle = { close: cleanup };
|
|
455
|
+
this.liveSockets.add(handle);
|
|
456
|
+
ws$1.binaryType = "nodebuffer";
|
|
457
|
+
ws$1.on("open", () => {
|
|
458
|
+
const wsStream = ws.createWebSocketStream(ws$1);
|
|
459
|
+
wsStream.on("error", cleanup);
|
|
460
|
+
socket.pipe(wsStream);
|
|
461
|
+
wsStream.pipe(socket);
|
|
462
|
+
});
|
|
463
|
+
ws$1.on("close", cleanup);
|
|
464
|
+
ws$1.on("error", (err) => {
|
|
465
|
+
this.log(`tunnel error: ${err.message}`);
|
|
466
|
+
cleanup();
|
|
467
|
+
});
|
|
468
|
+
socket.on("close", cleanup);
|
|
469
|
+
}
|
|
470
|
+
/** Tear down all listeners and any in-flight tunnel sockets. */
|
|
471
|
+
async close() {
|
|
472
|
+
this.closed = true;
|
|
473
|
+
for (const handle of [...this.liveSockets]) handle.close();
|
|
474
|
+
this.liveSockets.clear();
|
|
475
|
+
await Promise.all(
|
|
476
|
+
this.servers.map(
|
|
477
|
+
(s) => new Promise((resolve) => {
|
|
478
|
+
s.close(() => resolve());
|
|
479
|
+
})
|
|
480
|
+
)
|
|
481
|
+
);
|
|
482
|
+
for (const m of this.mappings) {
|
|
483
|
+
if (isListenUnix(m) && m.listenUnixPath) {
|
|
484
|
+
try {
|
|
485
|
+
fs__default.default.rmSync(m.listenUnixPath, { force: true });
|
|
486
|
+
} catch {
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
log(msg) {
|
|
492
|
+
this.init.options?.log?.(msg);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
function listen(server, mapping) {
|
|
496
|
+
return new Promise((resolve, reject) => {
|
|
497
|
+
server.once("error", reject);
|
|
498
|
+
if (isListenUnix(mapping)) {
|
|
499
|
+
const p = mapping.listenUnixPath;
|
|
500
|
+
try {
|
|
501
|
+
fs__default.default.rmSync(p, { force: true });
|
|
502
|
+
} catch {
|
|
503
|
+
}
|
|
504
|
+
server.listen(p, () => {
|
|
505
|
+
try {
|
|
506
|
+
fs__default.default.chmodSync(p, 432);
|
|
507
|
+
} catch {
|
|
508
|
+
}
|
|
509
|
+
resolve({ local: p });
|
|
510
|
+
});
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
server.listen({ host: mapping.listenAddr, port: mapping.listenPort }, () => {
|
|
514
|
+
const addr = server.address();
|
|
515
|
+
if (addr && typeof addr === "object") {
|
|
516
|
+
const local = `${mapping.listenAddr}:${addr.port}`;
|
|
517
|
+
resolve({ local, port: addr.port });
|
|
518
|
+
} else {
|
|
519
|
+
resolve({ local: listenAddressDescription(mapping) });
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
function openWebSocket(init, mapping) {
|
|
525
|
+
const url = wsURLForVM(init.transport, init.hostname);
|
|
526
|
+
const headers = {
|
|
527
|
+
"X-Inlets-Client-ID": init.options?.clientId ?? os__default.default.hostname(),
|
|
528
|
+
"X-Inlets-Mode": "local",
|
|
529
|
+
"X-Inlets-Upstream": remoteTargetHeader(mapping),
|
|
530
|
+
"User-Agent": init.userAgent
|
|
531
|
+
};
|
|
532
|
+
if (init.token) headers["Authorization"] = `Bearer ${init.token}`;
|
|
533
|
+
const opts = {
|
|
534
|
+
headers,
|
|
535
|
+
handshakeTimeout: init.options?.dialTimeoutMs ?? 1e4
|
|
536
|
+
};
|
|
537
|
+
if (init.transport.kind === "socket") {
|
|
538
|
+
opts.agent = unixAgent(init.transport.socketPath);
|
|
539
|
+
}
|
|
540
|
+
return new ws.WebSocket(url, opts);
|
|
541
|
+
}
|
|
542
|
+
function wsURLForVM(transport, hostname) {
|
|
543
|
+
if (transport.kind === "socket") {
|
|
544
|
+
return `ws://localhost/vm/${encodeURIComponent(hostname)}/forward`;
|
|
545
|
+
}
|
|
546
|
+
const u = transport.url;
|
|
547
|
+
const scheme = u.protocol === "https:" ? "wss" : "ws";
|
|
548
|
+
const port = u.port ? `:${u.port}` : "";
|
|
549
|
+
return `${scheme}://${u.hostname}${port}/vm/${encodeURIComponent(hostname)}/forward`;
|
|
550
|
+
}
|
|
551
|
+
function unixAgent(socketPath) {
|
|
552
|
+
const agent = new http__default.default.Agent({ keepAlive: false });
|
|
553
|
+
agent.createConnection = ((_opts, cb) => {
|
|
554
|
+
const conn = net__default.default.createConnection({ path: socketPath });
|
|
555
|
+
if (cb) {
|
|
556
|
+
conn.once("connect", () => cb(null, conn));
|
|
557
|
+
conn.once("error", (err) => cb(err));
|
|
558
|
+
}
|
|
559
|
+
return conn;
|
|
560
|
+
});
|
|
561
|
+
return agent;
|
|
562
|
+
}
|
|
306
563
|
|
|
307
564
|
// src/vm.ts
|
|
308
565
|
var VMFileSystem = class {
|
|
@@ -312,16 +569,16 @@ var VMFileSystem = class {
|
|
|
312
569
|
}
|
|
313
570
|
transport;
|
|
314
571
|
hostname;
|
|
315
|
-
async readDir(
|
|
316
|
-
const q = new URLSearchParams({ path:
|
|
572
|
+
async readDir(path3) {
|
|
573
|
+
const q = new URLSearchParams({ path: path3 });
|
|
317
574
|
const wire = await this.transport.request(
|
|
318
575
|
"GET",
|
|
319
576
|
`/vm/${encodeURIComponent(this.hostname)}/fs/readdir?${q.toString()}`
|
|
320
577
|
);
|
|
321
578
|
return (wire ?? []).map(fsEntryFromWire);
|
|
322
579
|
}
|
|
323
|
-
async stat(
|
|
324
|
-
const q = new URLSearchParams({ path:
|
|
580
|
+
async stat(path3) {
|
|
581
|
+
const q = new URLSearchParams({ path: path3 });
|
|
325
582
|
try {
|
|
326
583
|
const wire = await this.transport.request(
|
|
327
584
|
"GET",
|
|
@@ -333,8 +590,8 @@ var VMFileSystem = class {
|
|
|
333
590
|
throw err;
|
|
334
591
|
}
|
|
335
592
|
}
|
|
336
|
-
async exists(
|
|
337
|
-
return await this.stat(
|
|
593
|
+
async exists(path3) {
|
|
594
|
+
return await this.stat(path3) !== null;
|
|
338
595
|
}
|
|
339
596
|
async mkdir(req) {
|
|
340
597
|
await this.transport.request(
|
|
@@ -347,22 +604,22 @@ var VMFileSystem = class {
|
|
|
347
604
|
}
|
|
348
605
|
);
|
|
349
606
|
}
|
|
350
|
-
async remove(
|
|
351
|
-
const q = new URLSearchParams({ path:
|
|
607
|
+
async remove(path3, recursive = false) {
|
|
608
|
+
const q = new URLSearchParams({ path: path3, recursive: String(recursive) });
|
|
352
609
|
await this.transport.request(
|
|
353
610
|
"DELETE",
|
|
354
611
|
`/vm/${encodeURIComponent(this.hostname)}/fs/remove?${q.toString()}`
|
|
355
612
|
);
|
|
356
613
|
}
|
|
357
|
-
async readFile(
|
|
358
|
-
const q = new URLSearchParams({ path:
|
|
614
|
+
async readFile(path3) {
|
|
615
|
+
const q = new URLSearchParams({ path: path3, mode: "binary" });
|
|
359
616
|
return this.transport.requestRaw(
|
|
360
617
|
"GET",
|
|
361
618
|
`/vm/${encodeURIComponent(this.hostname)}/cp?${q.toString()}`
|
|
362
619
|
);
|
|
363
620
|
}
|
|
364
|
-
async writeFile(
|
|
365
|
-
const q = new URLSearchParams({ path:
|
|
621
|
+
async writeFile(path3, content, opts = {}) {
|
|
622
|
+
const q = new URLSearchParams({ path: path3, mode: "binary" });
|
|
366
623
|
if (opts.uid !== void 0) q.set("uid", String(opts.uid));
|
|
367
624
|
if (opts.gid !== void 0) q.set("gid", String(opts.gid));
|
|
368
625
|
if (opts.permissions) q.set("permissions", opts.permissions);
|
|
@@ -374,8 +631,8 @@ var VMFileSystem = class {
|
|
|
374
631
|
);
|
|
375
632
|
}
|
|
376
633
|
/** Upload a tar archive, expanded into the VM at `path`. */
|
|
377
|
-
async tarTo(
|
|
378
|
-
const q = new URLSearchParams({ path:
|
|
634
|
+
async tarTo(path3, tar) {
|
|
635
|
+
const q = new URLSearchParams({ path: path3, mode: "tar" });
|
|
379
636
|
if (tar instanceof Buffer) {
|
|
380
637
|
await this.transport.requestRaw(
|
|
381
638
|
"POST",
|
|
@@ -393,9 +650,88 @@ var VMFileSystem = class {
|
|
|
393
650
|
);
|
|
394
651
|
for await (const _ of res) void _;
|
|
395
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* Open a Server-Sent Events stream of filesystem events from the VM.
|
|
655
|
+
* Yields one `FSWatchEvent` per agent-side event. The stream stays open
|
|
656
|
+
* until the supplied request's `timeout` / `maxEvents` is hit, the daemon
|
|
657
|
+
* tears it down, or the caller breaks out of the loop.
|
|
658
|
+
*
|
|
659
|
+
* Heartbeat SSE comments and named `event:` lines are silently dropped.
|
|
660
|
+
*
|
|
661
|
+
* Example:
|
|
662
|
+
* ```ts
|
|
663
|
+
* for await (const e of vm.fs.watch({ paths: ['/tmp'], recursive: true })) {
|
|
664
|
+
* console.log(e.type, e.path);
|
|
665
|
+
* }
|
|
666
|
+
* ```
|
|
667
|
+
*/
|
|
668
|
+
async *watch(req) {
|
|
669
|
+
if (!req.paths || req.paths.length === 0) {
|
|
670
|
+
throw new Error("vm.fs.watch: paths is required");
|
|
671
|
+
}
|
|
672
|
+
const q = new URLSearchParams();
|
|
673
|
+
for (const p of req.paths) if (p) q.append("paths", p);
|
|
674
|
+
for (const p of req.patterns ?? []) if (p) q.append("patterns", p);
|
|
675
|
+
for (const e of req.events ?? []) if (e) q.append("events", e);
|
|
676
|
+
if (req.uid !== void 0 && req.uid !== 0) q.set("uid", String(req.uid));
|
|
677
|
+
if (req.recursive) q.set("recursive", "true");
|
|
678
|
+
if (req.oneShot) q.set("one_shot", "true");
|
|
679
|
+
if (req.debounce) q.set("debounce", req.debounce);
|
|
680
|
+
if (req.timeout) q.set("timeout", req.timeout);
|
|
681
|
+
if (req.maxEvents !== void 0 && req.maxEvents > 0) {
|
|
682
|
+
q.set("max_events", String(req.maxEvents));
|
|
683
|
+
}
|
|
684
|
+
const extraHeaders = { Accept: "text/event-stream" };
|
|
685
|
+
if (req.lastEventId) extraHeaders["Last-Event-ID"] = req.lastEventId;
|
|
686
|
+
const res = await this.transport.requestStreamRaw(
|
|
687
|
+
"GET",
|
|
688
|
+
`/vm/${encodeURIComponent(this.hostname)}/fs/watch?${q.toString()}`,
|
|
689
|
+
void 0,
|
|
690
|
+
void 0,
|
|
691
|
+
extraHeaders
|
|
692
|
+
);
|
|
693
|
+
res.setEncoding("utf8");
|
|
694
|
+
let buffer = "";
|
|
695
|
+
let dataLines = [];
|
|
696
|
+
let pendingId = 0;
|
|
697
|
+
for await (const chunk of res) {
|
|
698
|
+
buffer += chunk;
|
|
699
|
+
let nl;
|
|
700
|
+
while ((nl = buffer.indexOf("\n")) >= 0) {
|
|
701
|
+
const raw = buffer.slice(0, nl);
|
|
702
|
+
buffer = buffer.slice(nl + 1);
|
|
703
|
+
const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
|
|
704
|
+
if (line === "") {
|
|
705
|
+
if (dataLines.length > 0) {
|
|
706
|
+
const payload = dataLines.join("\n");
|
|
707
|
+
dataLines = [];
|
|
708
|
+
try {
|
|
709
|
+
const parsed = JSON.parse(payload);
|
|
710
|
+
const evt = {
|
|
711
|
+
id: typeof parsed.id === "number" && parsed.id !== 0 ? parsed.id : pendingId,
|
|
712
|
+
type: parsed.type ?? "",
|
|
713
|
+
path: parsed.path ?? "",
|
|
714
|
+
timestamp: parsed.timestamp ?? "",
|
|
715
|
+
size: parsed.size ?? 0,
|
|
716
|
+
isDir: parsed.isDir ?? false,
|
|
717
|
+
...parsed.message !== void 0 && { message: parsed.message }
|
|
718
|
+
};
|
|
719
|
+
yield evt;
|
|
720
|
+
} catch {
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
} else if (line.startsWith(":")) ; else if (line.startsWith("data:")) {
|
|
724
|
+
dataLines.push(line.slice(5).replace(/^ /, ""));
|
|
725
|
+
} else if (line.startsWith("id:")) {
|
|
726
|
+
const v = parseInt(line.slice(3).trim(), 10);
|
|
727
|
+
if (!Number.isNaN(v)) pendingId = v;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
396
732
|
/** Download `path` from the VM as a tar archive. */
|
|
397
|
-
async tarFrom(
|
|
398
|
-
const q = new URLSearchParams({ path:
|
|
733
|
+
async tarFrom(path3) {
|
|
734
|
+
const q = new URLSearchParams({ path: path3, mode: "tar" });
|
|
399
735
|
return this.transport.requestRaw(
|
|
400
736
|
"GET",
|
|
401
737
|
`/vm/${encodeURIComponent(this.hostname)}/cp?${q.toString()}`
|
|
@@ -500,6 +836,30 @@ var VM = class {
|
|
|
500
836
|
async restore() {
|
|
501
837
|
await this.transport.request("POST", `/vm/${encodeURIComponent(this.hostname)}/restore`);
|
|
502
838
|
}
|
|
839
|
+
// --- port forwarding ---------------------------------------------------
|
|
840
|
+
/**
|
|
841
|
+
* Open one or more port forwards from the host to this VM. Each spec follows
|
|
842
|
+
* the same syntax as `slicer vm forward -L`:
|
|
843
|
+
*
|
|
844
|
+
* `127.0.0.1:9000` — listen and forward on the same TCP port
|
|
845
|
+
* `8081:127.0.0.1:8080` — listen on `0.0.0.0:8081`, forward to `127.0.0.1:8080`
|
|
846
|
+
* `0.0.0.0:8080:127.0.0.1:8080` — fully explicit
|
|
847
|
+
* `9000:/var/run/docker.sock` — TCP listen, Unix socket forward
|
|
848
|
+
* `/tmp/docker.sock:/var/run/docker.sock` — Unix-to-Unix
|
|
849
|
+
*
|
|
850
|
+
* Returns a {@link Forwarder} handle. Call `forwarder.close()` to tear down
|
|
851
|
+
* all listeners and any in-flight tunnel sockets.
|
|
852
|
+
*/
|
|
853
|
+
async forward(specs, options) {
|
|
854
|
+
return Forwarder.start({
|
|
855
|
+
hostname: this.hostname,
|
|
856
|
+
transport: this.transport.transport,
|
|
857
|
+
...this.transport.token !== void 0 && { token: this.transport.token },
|
|
858
|
+
userAgent: this.transport.userAgent,
|
|
859
|
+
specs: typeof specs === "string" ? [specs] : specs,
|
|
860
|
+
...options !== void 0 && { options }
|
|
861
|
+
});
|
|
862
|
+
}
|
|
503
863
|
// --- exec -------------------------------------------------------------
|
|
504
864
|
/**
|
|
505
865
|
* Streaming exec — yields NDJSON frames (`started`, `stdout`, `stderr`, `exit`).
|
|
@@ -508,8 +868,8 @@ var VM = class {
|
|
|
508
868
|
* `dataBytes`/`stdoutBytes`/`stderrBytes` Buffers alongside for convenience.
|
|
509
869
|
*/
|
|
510
870
|
async *exec(req) {
|
|
511
|
-
const { path:
|
|
512
|
-
for await (const frame of this.transport.requestNDJSON("POST",
|
|
871
|
+
const { path: path3, body } = buildExecPath(this.hostname, req, false);
|
|
872
|
+
for await (const frame of this.transport.requestNDJSON("POST", path3, body)) {
|
|
513
873
|
if (frame.encoding === "base64") {
|
|
514
874
|
if (frame.data) frame.dataBytes = Buffer.from(frame.data, "base64");
|
|
515
875
|
if (frame.stdout) frame.stdoutBytes = Buffer.from(frame.stdout, "base64");
|
|
@@ -522,8 +882,8 @@ var VM = class {
|
|
|
522
882
|
if (req.stdin !== void 0) {
|
|
523
883
|
throw new Error("stdin is not supported by execBuffered; use exec() instead");
|
|
524
884
|
}
|
|
525
|
-
const { path:
|
|
526
|
-
const raw = await this.transport.requestRaw("POST",
|
|
885
|
+
const { path: path3, body } = buildExecPath(this.hostname, req, true);
|
|
886
|
+
const raw = await this.transport.requestRaw("POST", path3, body);
|
|
527
887
|
const text = raw.toString("utf8");
|
|
528
888
|
const parsed = text ? JSON.parse(text) : {};
|
|
529
889
|
const common = {
|
|
@@ -749,6 +1109,7 @@ var SlicerClient = class _SlicerClient {
|
|
|
749
1109
|
|
|
750
1110
|
exports.ExecStdioBase64 = ExecStdioBase64;
|
|
751
1111
|
exports.ExecStdioText = ExecStdioText;
|
|
1112
|
+
exports.Forwarder = Forwarder;
|
|
752
1113
|
exports.GiB = GiB;
|
|
753
1114
|
exports.HostGroupsAPI = HostGroupsAPI;
|
|
754
1115
|
exports.MiB = MiB;
|
|
@@ -760,6 +1121,7 @@ exports.SlicerClient = SlicerClient;
|
|
|
760
1121
|
exports.VM = VM;
|
|
761
1122
|
exports.VMFileSystem = VMFileSystem;
|
|
762
1123
|
exports.VMsAPI = VMsAPI;
|
|
1124
|
+
exports.parseAddressMapping = parseAddressMapping;
|
|
763
1125
|
exports.resolveTransport = resolveTransport;
|
|
764
1126
|
//# sourceMappingURL=index.cjs.map
|
|
765
1127
|
//# sourceMappingURL=index.cjs.map
|