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