matchlock-sdk 0.1.25 → 0.1.27
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/README.md +5 -2
- package/dist/client/create.d.ts +4 -0
- package/dist/client/create.js +182 -0
- package/dist/client/exec.d.ts +29 -0
- package/dist/client/exec.js +356 -0
- package/dist/client/network-hooks.d.ts +20 -0
- package/dist/client/network-hooks.js +243 -0
- package/dist/client/port-forward.d.ts +2 -0
- package/dist/client/port-forward.js +38 -0
- package/dist/client/transport.d.ts +29 -0
- package/dist/client/transport.js +292 -0
- package/dist/client/utils.d.ts +17 -0
- package/dist/client/utils.js +150 -0
- package/dist/client/vfs-hooks.d.ts +29 -0
- package/dist/client/vfs-hooks.js +308 -0
- package/dist/client/volumes.d.ts +8 -0
- package/dist/client/volumes.js +95 -0
- package/dist/client/wire.d.ts +97 -0
- package/dist/client/wire.js +2 -0
- package/dist/client.d.ts +9 -51
- package/dist/client.js +81 -1278
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,8 @@ try {
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
`client.launch(...)` starts image ENTRYPOINT/CMD in detached mode. Use `client.create(...)` when you want a VM without auto-starting image command.
|
|
49
|
+
|
|
48
50
|
Callback-based interception:
|
|
49
51
|
|
|
50
52
|
```ts
|
|
@@ -75,8 +77,9 @@ const sandbox = new Sandbox("alpine:latest").withNetworkInterception({
|
|
|
75
77
|
- Fluent sandbox builder (`Sandbox`) with network, secrets, mounts, env, VFS hooks, image config
|
|
76
78
|
- Typed network interception rules and local callback hooks via `withNetworkInterception(...)`
|
|
77
79
|
- Supports fully offline mode via `.withNoNetwork()` (no guest NIC / no egress)
|
|
78
|
-
- JSON-RPC `create`, `exec`, `exec_stream`, `write_file`, `read_file`, `list_files`, `port_forward`, `cancel`, `close`
|
|
79
|
-
- Streaming stdout/stderr via `execStream`
|
|
80
|
+
- JSON-RPC `create`, `exec`, `exec_stream`, `exec_pipe`, `exec_tty`, `write_file`, `read_file`, `list_files`, `port_forward`, `cancel`, `close`
|
|
81
|
+
- Streaming stdout/stderr via `execStream` and bidirectional stdin/stdout/stderr via `execPipe`
|
|
82
|
+
- Interactive PTY shell/commands via `execInteractive` (stdin/stdout + resize events)
|
|
80
83
|
- Local VFS callbacks (`hook`, `dangerousHook`, `mutateHook`, `actionHook`)
|
|
81
84
|
- Port forwarding API parity (`portForward`, `portForwardWithAddresses`)
|
|
82
85
|
- Lifecycle control (`close`, `remove`, `vmId`)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CreateOptions } from "../types";
|
|
2
|
+
import type { JSONObject, WireNetworkInterceptionConfig, WireVFSInterceptionConfig } from "./wire";
|
|
3
|
+
export declare function validateCreateOptions(options: CreateOptions): void;
|
|
4
|
+
export declare function buildCreateParams(options: CreateOptions, wireVFS: WireVFSInterceptionConfig | undefined, wireNetworkInterception: WireNetworkInterceptionConfig | undefined): JSONObject;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateCreateOptions = validateCreateOptions;
|
|
4
|
+
exports.buildCreateParams = buildCreateParams;
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
function validateCreateOptions(options) {
|
|
8
|
+
if (!options.image) {
|
|
9
|
+
throw new errors_1.MatchlockError("image is required (e.g., alpine:latest)");
|
|
10
|
+
}
|
|
11
|
+
if ((options.networkMtu ?? 0) < 0) {
|
|
12
|
+
throw new errors_1.MatchlockError("network mtu must be > 0");
|
|
13
|
+
}
|
|
14
|
+
if (options.noNetwork &&
|
|
15
|
+
((options.allowedHosts?.length ?? 0) > 0 ||
|
|
16
|
+
(options.secrets?.length ?? 0) > 0 ||
|
|
17
|
+
options.forceInterception === true ||
|
|
18
|
+
options.networkInterception !== undefined)) {
|
|
19
|
+
throw new errors_1.MatchlockError("no network cannot be combined with allowed hosts, secrets, forced interception, or network interception rules");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function buildCreateParams(options, wireVFS, wireNetworkInterception) {
|
|
23
|
+
const resources = {
|
|
24
|
+
cpus: options.cpus || utils_1.DEFAULT_CPUS,
|
|
25
|
+
memory_mb: options.memoryMb || utils_1.DEFAULT_MEMORY_MB,
|
|
26
|
+
disk_size_mb: options.diskSizeMb || utils_1.DEFAULT_DISK_SIZE_MB,
|
|
27
|
+
timeout_seconds: options.timeoutSeconds || utils_1.DEFAULT_TIMEOUT_SECONDS,
|
|
28
|
+
};
|
|
29
|
+
const params = {
|
|
30
|
+
image: options.image ?? "",
|
|
31
|
+
resources,
|
|
32
|
+
};
|
|
33
|
+
if (options.privileged) {
|
|
34
|
+
params.privileged = true;
|
|
35
|
+
}
|
|
36
|
+
const network = buildCreateNetworkParams(options, wireNetworkInterception);
|
|
37
|
+
if (network) {
|
|
38
|
+
params.network = network;
|
|
39
|
+
}
|
|
40
|
+
if ((options.mounts && Object.keys(options.mounts).length > 0) ||
|
|
41
|
+
options.workspace ||
|
|
42
|
+
wireVFS) {
|
|
43
|
+
const vfs = {};
|
|
44
|
+
if (options.mounts && Object.keys(options.mounts).length > 0) {
|
|
45
|
+
const mounts = {};
|
|
46
|
+
for (const [guestPath, config] of Object.entries(options.mounts)) {
|
|
47
|
+
const mount = {
|
|
48
|
+
type: config.type ?? "memory",
|
|
49
|
+
};
|
|
50
|
+
if (config.hostPath) {
|
|
51
|
+
mount.host_path = config.hostPath;
|
|
52
|
+
}
|
|
53
|
+
if (config.readonly) {
|
|
54
|
+
mount.readonly = true;
|
|
55
|
+
}
|
|
56
|
+
mounts[guestPath] = mount;
|
|
57
|
+
}
|
|
58
|
+
vfs.mounts = mounts;
|
|
59
|
+
}
|
|
60
|
+
if (options.workspace) {
|
|
61
|
+
vfs.workspace = options.workspace;
|
|
62
|
+
}
|
|
63
|
+
if (wireVFS) {
|
|
64
|
+
vfs.interception = wireVFS;
|
|
65
|
+
}
|
|
66
|
+
params.vfs = vfs;
|
|
67
|
+
}
|
|
68
|
+
if (options.env && Object.keys(options.env).length > 0) {
|
|
69
|
+
params.env = options.env;
|
|
70
|
+
}
|
|
71
|
+
if (options.imageConfig) {
|
|
72
|
+
const imageConfig = {};
|
|
73
|
+
if (options.imageConfig.user) {
|
|
74
|
+
imageConfig.user = options.imageConfig.user;
|
|
75
|
+
}
|
|
76
|
+
if (options.imageConfig.workingDir) {
|
|
77
|
+
imageConfig.working_dir = options.imageConfig.workingDir;
|
|
78
|
+
}
|
|
79
|
+
if (options.imageConfig.entrypoint) {
|
|
80
|
+
imageConfig.entrypoint = [...options.imageConfig.entrypoint];
|
|
81
|
+
}
|
|
82
|
+
if (options.imageConfig.cmd) {
|
|
83
|
+
imageConfig.cmd = [...options.imageConfig.cmd];
|
|
84
|
+
}
|
|
85
|
+
if (options.imageConfig.env) {
|
|
86
|
+
imageConfig.env = { ...options.imageConfig.env };
|
|
87
|
+
}
|
|
88
|
+
params.image_config = imageConfig;
|
|
89
|
+
}
|
|
90
|
+
if (options.launchEntrypoint) {
|
|
91
|
+
params.launch_entrypoint = true;
|
|
92
|
+
}
|
|
93
|
+
return params;
|
|
94
|
+
}
|
|
95
|
+
function resolveCreateBlockPrivateIPs(opts) {
|
|
96
|
+
if (opts.blockPrivateIPsSet) {
|
|
97
|
+
return { value: !!opts.blockPrivateIPs, hasOverride: true };
|
|
98
|
+
}
|
|
99
|
+
if (opts.blockPrivateIPs) {
|
|
100
|
+
return { value: true, hasOverride: true };
|
|
101
|
+
}
|
|
102
|
+
return { value: false, hasOverride: false };
|
|
103
|
+
}
|
|
104
|
+
function buildCreateNetworkParams(opts, wireInterception) {
|
|
105
|
+
const hasAllowedHosts = (opts.allowedHosts?.length ?? 0) > 0;
|
|
106
|
+
const hasAddHosts = (opts.addHosts?.length ?? 0) > 0;
|
|
107
|
+
const hasSecrets = (opts.secrets?.length ?? 0) > 0;
|
|
108
|
+
const hasDNSServers = (opts.dnsServers?.length ?? 0) > 0;
|
|
109
|
+
const hasHostname = (opts.hostname?.length ?? 0) > 0;
|
|
110
|
+
const hasMTU = (opts.networkMtu ?? 0) > 0;
|
|
111
|
+
const hasNoNetwork = opts.noNetwork === true;
|
|
112
|
+
const hasForceInterception = opts.forceInterception === true;
|
|
113
|
+
const hasNetworkInterception = wireInterception !== undefined;
|
|
114
|
+
const blockPrivate = resolveCreateBlockPrivateIPs(opts);
|
|
115
|
+
const includeNetwork = hasAllowedHosts ||
|
|
116
|
+
hasAddHosts ||
|
|
117
|
+
hasSecrets ||
|
|
118
|
+
hasDNSServers ||
|
|
119
|
+
hasHostname ||
|
|
120
|
+
hasMTU ||
|
|
121
|
+
hasNoNetwork ||
|
|
122
|
+
blockPrivate.hasOverride ||
|
|
123
|
+
hasForceInterception ||
|
|
124
|
+
hasNetworkInterception;
|
|
125
|
+
if (!includeNetwork) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
if (hasNoNetwork) {
|
|
129
|
+
const network = {
|
|
130
|
+
no_network: true,
|
|
131
|
+
};
|
|
132
|
+
if (hasAddHosts) {
|
|
133
|
+
network.add_hosts = (opts.addHosts ?? []).map((mapping) => ({
|
|
134
|
+
host: mapping.host,
|
|
135
|
+
ip: mapping.ip,
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
if (hasDNSServers) {
|
|
139
|
+
network.dns_servers = opts.dnsServers ?? [];
|
|
140
|
+
}
|
|
141
|
+
if (hasHostname) {
|
|
142
|
+
network.hostname = opts.hostname ?? "";
|
|
143
|
+
}
|
|
144
|
+
return network;
|
|
145
|
+
}
|
|
146
|
+
const network = {
|
|
147
|
+
allowed_hosts: opts.allowedHosts ?? [],
|
|
148
|
+
block_private_ips: blockPrivate.hasOverride ? blockPrivate.value : true,
|
|
149
|
+
};
|
|
150
|
+
if (hasForceInterception || hasNetworkInterception) {
|
|
151
|
+
network.intercept = true;
|
|
152
|
+
}
|
|
153
|
+
if (hasNetworkInterception) {
|
|
154
|
+
network.interception = wireInterception;
|
|
155
|
+
}
|
|
156
|
+
if (hasAddHosts) {
|
|
157
|
+
network.add_hosts = (opts.addHosts ?? []).map((mapping) => ({
|
|
158
|
+
host: mapping.host,
|
|
159
|
+
ip: mapping.ip,
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
if (hasSecrets) {
|
|
163
|
+
const secrets = {};
|
|
164
|
+
for (const secret of opts.secrets ?? []) {
|
|
165
|
+
secrets[secret.name] = {
|
|
166
|
+
value: secret.value,
|
|
167
|
+
hosts: secret.hosts ?? [],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
network.secrets = secrets;
|
|
171
|
+
}
|
|
172
|
+
if (hasDNSServers) {
|
|
173
|
+
network.dns_servers = opts.dnsServers ?? [];
|
|
174
|
+
}
|
|
175
|
+
if (hasHostname) {
|
|
176
|
+
network.hostname = opts.hostname ?? "";
|
|
177
|
+
}
|
|
178
|
+
if (hasMTU) {
|
|
179
|
+
network.mtu = opts.networkMtu ?? 0;
|
|
180
|
+
}
|
|
181
|
+
return network;
|
|
182
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ExecInteractiveOptions, ExecInteractiveResult, ExecOptions, ExecPipeOptions, ExecPipeResult, ExecResult, ExecStreamOptions, ExecStreamResult, RequestOptions, StreamReader, StreamWriter } from "../types";
|
|
2
|
+
import type { JSONObject, JSONValue } from "./wire";
|
|
3
|
+
type NotificationHandler = (method: string, params: JSONObject) => void;
|
|
4
|
+
type SendRequestFunc = (method: string, params?: JSONObject, options?: RequestOptions, onNotification?: NotificationHandler) => Promise<JSONValue>;
|
|
5
|
+
type SendFireAndForgetFunc = (method: string, params?: JSONObject) => Promise<void>;
|
|
6
|
+
export declare class ExecAPI {
|
|
7
|
+
private readonly sendRequest;
|
|
8
|
+
private readonly sendFireAndForget;
|
|
9
|
+
constructor(sendRequest: SendRequestFunc, sendFireAndForget: SendFireAndForgetFunc);
|
|
10
|
+
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
11
|
+
execWithDir(command: string, workingDir?: string, options?: RequestOptions): Promise<ExecResult>;
|
|
12
|
+
execStream(command: string, options?: ExecStreamOptions): Promise<ExecStreamResult>;
|
|
13
|
+
execStreamWithDir(command: string, workingDir?: string, stdout?: StreamWriter, stderr?: StreamWriter, options?: RequestOptions): Promise<ExecStreamResult>;
|
|
14
|
+
execPipe(command: string, options?: ExecPipeOptions): Promise<ExecPipeResult>;
|
|
15
|
+
execPipeWithDir(command: string, workingDir?: string, stdin?: StreamReader, stdout?: StreamWriter, stderr?: StreamWriter, options?: RequestOptions): Promise<ExecPipeResult>;
|
|
16
|
+
execInteractive(command: string, options?: ExecInteractiveOptions): Promise<ExecInteractiveResult>;
|
|
17
|
+
private createReadySignal;
|
|
18
|
+
private createStopSignal;
|
|
19
|
+
private decodeBase64;
|
|
20
|
+
private isAsyncIterable;
|
|
21
|
+
private isIterable;
|
|
22
|
+
private toAsyncIterable;
|
|
23
|
+
private pumpExecInput;
|
|
24
|
+
private pumpTTYResize;
|
|
25
|
+
private nextAsyncItem;
|
|
26
|
+
private closeAsyncIterator;
|
|
27
|
+
private writeStreamChunk;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExecAPI = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
class ExecAPI {
|
|
6
|
+
sendRequest;
|
|
7
|
+
sendFireAndForget;
|
|
8
|
+
constructor(sendRequest, sendFireAndForget) {
|
|
9
|
+
this.sendRequest = sendRequest;
|
|
10
|
+
this.sendFireAndForget = sendFireAndForget;
|
|
11
|
+
}
|
|
12
|
+
async exec(command, options = {}) {
|
|
13
|
+
return this.execWithDir(command, options.workingDir ?? "", options);
|
|
14
|
+
}
|
|
15
|
+
async execWithDir(command, workingDir = "", options = {}) {
|
|
16
|
+
const params = { command };
|
|
17
|
+
if (workingDir) {
|
|
18
|
+
params.working_dir = workingDir;
|
|
19
|
+
}
|
|
20
|
+
const result = (0, utils_1.asObject)(await this.sendRequest("exec", params, options));
|
|
21
|
+
return {
|
|
22
|
+
exitCode: (0, utils_1.asNumber)(result.exit_code),
|
|
23
|
+
stdout: Buffer.from((0, utils_1.asString)(result.stdout), "base64").toString("utf8"),
|
|
24
|
+
stderr: Buffer.from((0, utils_1.asString)(result.stderr), "base64").toString("utf8"),
|
|
25
|
+
durationMs: (0, utils_1.asNumber)(result.duration_ms),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async execStream(command, options = {}) {
|
|
29
|
+
return this.execStreamWithDir(command, options.workingDir ?? "", options.stdout, options.stderr, options);
|
|
30
|
+
}
|
|
31
|
+
async execStreamWithDir(command, workingDir = "", stdout, stderr, options = {}) {
|
|
32
|
+
const params = { command };
|
|
33
|
+
if (workingDir) {
|
|
34
|
+
params.working_dir = workingDir;
|
|
35
|
+
}
|
|
36
|
+
const onNotification = (method, payload) => {
|
|
37
|
+
const data = (0, utils_1.asString)(payload.data);
|
|
38
|
+
if (!data) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let decoded;
|
|
42
|
+
try {
|
|
43
|
+
decoded = Buffer.from(data, "base64");
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (method === "exec_stream.stdout") {
|
|
49
|
+
this.writeStreamChunk(stdout, decoded);
|
|
50
|
+
}
|
|
51
|
+
else if (method === "exec_stream.stderr") {
|
|
52
|
+
this.writeStreamChunk(stderr, decoded);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const result = (0, utils_1.asObject)(await this.sendRequest("exec_stream", params, options, onNotification));
|
|
56
|
+
return {
|
|
57
|
+
exitCode: (0, utils_1.asNumber)(result.exit_code),
|
|
58
|
+
durationMs: (0, utils_1.asNumber)(result.duration_ms),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async execPipe(command, options = {}) {
|
|
62
|
+
return this.execPipeWithDir(command, options.workingDir ?? "", options.stdin, options.stdout, options.stderr, options);
|
|
63
|
+
}
|
|
64
|
+
async execPipeWithDir(command, workingDir = "", stdin, stdout, stderr, options = {}) {
|
|
65
|
+
const params = { command };
|
|
66
|
+
if (workingDir) {
|
|
67
|
+
params.working_dir = workingDir;
|
|
68
|
+
}
|
|
69
|
+
const stop = this.createStopSignal();
|
|
70
|
+
const ready = this.createReadySignal();
|
|
71
|
+
const inputPump = this.pumpExecInput(stdin, ready.promise, stop, "exec_pipe.stdin", "exec_pipe.stdin_eof");
|
|
72
|
+
const onNotification = (method, payload) => {
|
|
73
|
+
if (method === "exec_pipe.ready") {
|
|
74
|
+
const reqID = (0, utils_1.asNumber)(payload.id, -1);
|
|
75
|
+
if (reqID >= 0) {
|
|
76
|
+
ready.markReady(reqID);
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const data = (0, utils_1.asString)(payload.data);
|
|
81
|
+
if (!data) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const decoded = this.decodeBase64(data);
|
|
85
|
+
if (!decoded) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (method === "exec_pipe.stdout") {
|
|
89
|
+
this.writeStreamChunk(stdout, decoded);
|
|
90
|
+
}
|
|
91
|
+
else if (method === "exec_pipe.stderr") {
|
|
92
|
+
this.writeStreamChunk(stderr, decoded);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
try {
|
|
96
|
+
const result = (0, utils_1.asObject)(await this.sendRequest("exec_pipe", params, options, onNotification));
|
|
97
|
+
return {
|
|
98
|
+
exitCode: (0, utils_1.asNumber)(result.exit_code),
|
|
99
|
+
durationMs: (0, utils_1.asNumber)(result.duration_ms),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
stop.stop();
|
|
104
|
+
ready.markReady(undefined);
|
|
105
|
+
await inputPump.catch(() => undefined);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async execInteractive(command, options = {}) {
|
|
109
|
+
const rows = (options.rows ?? 24) > 0 ? options.rows ?? 24 : 24;
|
|
110
|
+
const cols = (options.cols ?? 80) > 0 ? options.cols ?? 80 : 80;
|
|
111
|
+
const params = {
|
|
112
|
+
command,
|
|
113
|
+
rows,
|
|
114
|
+
cols,
|
|
115
|
+
};
|
|
116
|
+
if (options.workingDir) {
|
|
117
|
+
params.working_dir = options.workingDir;
|
|
118
|
+
}
|
|
119
|
+
const stop = this.createStopSignal();
|
|
120
|
+
const ready = this.createReadySignal();
|
|
121
|
+
const inputPump = this.pumpExecInput(options.stdin, ready.promise, stop, "exec_tty.stdin", "exec_tty.stdin_eof");
|
|
122
|
+
const resizePump = this.pumpTTYResize(options.resize, ready.promise, stop);
|
|
123
|
+
const onNotification = (method, payload) => {
|
|
124
|
+
if (method === "exec_tty.ready") {
|
|
125
|
+
const reqID = (0, utils_1.asNumber)(payload.id, -1);
|
|
126
|
+
if (reqID >= 0) {
|
|
127
|
+
ready.markReady(reqID);
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (method !== "exec_tty.stdout") {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const data = (0, utils_1.asString)(payload.data);
|
|
135
|
+
if (!data) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const decoded = this.decodeBase64(data);
|
|
139
|
+
if (!decoded) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.writeStreamChunk(options.stdout, decoded);
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
const result = (0, utils_1.asObject)(await this.sendRequest("exec_tty", params, options, onNotification));
|
|
146
|
+
return {
|
|
147
|
+
exitCode: (0, utils_1.asNumber)(result.exit_code),
|
|
148
|
+
durationMs: (0, utils_1.asNumber)(result.duration_ms),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
stop.stop();
|
|
153
|
+
ready.markReady(undefined);
|
|
154
|
+
await Promise.all([
|
|
155
|
+
inputPump.catch(() => undefined),
|
|
156
|
+
resizePump.catch(() => undefined),
|
|
157
|
+
]);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
createReadySignal() {
|
|
161
|
+
let resolved = false;
|
|
162
|
+
let resolveFn = () => { };
|
|
163
|
+
const promise = new Promise((resolve) => {
|
|
164
|
+
resolveFn = resolve;
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
promise,
|
|
168
|
+
markReady: (reqID) => {
|
|
169
|
+
if (resolved) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
resolved = true;
|
|
173
|
+
resolveFn(reqID);
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
createStopSignal() {
|
|
178
|
+
let stopped = false;
|
|
179
|
+
let resolveFn = () => { };
|
|
180
|
+
const promise = new Promise((resolve) => {
|
|
181
|
+
resolveFn = resolve;
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
promise,
|
|
185
|
+
isStopped: () => stopped,
|
|
186
|
+
stop: () => {
|
|
187
|
+
if (stopped) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
stopped = true;
|
|
191
|
+
resolveFn();
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
decodeBase64(data) {
|
|
196
|
+
try {
|
|
197
|
+
return Buffer.from(data, "base64");
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
isAsyncIterable(value) {
|
|
204
|
+
if (!value || typeof value !== "object") {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return typeof value[Symbol.asyncIterator] === "function";
|
|
208
|
+
}
|
|
209
|
+
isIterable(value) {
|
|
210
|
+
if (!value || typeof value !== "object") {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return typeof value[Symbol.iterator] === "function";
|
|
214
|
+
}
|
|
215
|
+
toAsyncIterable(value) {
|
|
216
|
+
if (this.isAsyncIterable(value)) {
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
return (async function* toAsync() {
|
|
220
|
+
for (const item of value) {
|
|
221
|
+
yield item;
|
|
222
|
+
}
|
|
223
|
+
})();
|
|
224
|
+
}
|
|
225
|
+
async pumpExecInput(stdin, readyPromise, stop, chunkMethod, eofMethod) {
|
|
226
|
+
const reqID = await Promise.race([
|
|
227
|
+
readyPromise,
|
|
228
|
+
stop.promise.then(() => undefined),
|
|
229
|
+
]);
|
|
230
|
+
if (stop.isStopped() || reqID === undefined) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!stdin) {
|
|
234
|
+
await this.sendFireAndForget(eofMethod, { id: reqID }).catch(() => undefined);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
let shouldSendEOF = true;
|
|
238
|
+
try {
|
|
239
|
+
const chunks = this.isAsyncIterable(stdin) || this.isIterable(stdin)
|
|
240
|
+
? this.toAsyncIterable(stdin)
|
|
241
|
+
: undefined;
|
|
242
|
+
if (!chunks) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const iterator = chunks[Symbol.asyncIterator]();
|
|
246
|
+
try {
|
|
247
|
+
for (;;) {
|
|
248
|
+
const next = await this.nextAsyncItem(iterator, stop.promise);
|
|
249
|
+
if (!next) {
|
|
250
|
+
shouldSendEOF = false;
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (next.done) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (stop.isStopped()) {
|
|
257
|
+
shouldSendEOF = false;
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const chunk = next.value;
|
|
261
|
+
let encoded = "";
|
|
262
|
+
try {
|
|
263
|
+
encoded = (0, utils_1.toBuffer)(chunk).toString("base64");
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (!encoded) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
await this.sendFireAndForget(chunkMethod, {
|
|
272
|
+
id: reqID,
|
|
273
|
+
data: encoded,
|
|
274
|
+
}).catch(() => undefined);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
this.closeAsyncIterator(iterator);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
if (shouldSendEOF && !stop.isStopped()) {
|
|
283
|
+
await this.sendFireAndForget(eofMethod, { id: reqID }).catch(() => undefined);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async pumpTTYResize(resize, readyPromise, stop) {
|
|
288
|
+
if (!resize) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const reqID = await Promise.race([
|
|
292
|
+
readyPromise,
|
|
293
|
+
stop.promise.then(() => undefined),
|
|
294
|
+
]);
|
|
295
|
+
if (stop.isStopped() || reqID === undefined) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const sizes = this.toAsyncIterable(resize);
|
|
299
|
+
const iterator = sizes[Symbol.asyncIterator]();
|
|
300
|
+
try {
|
|
301
|
+
for (;;) {
|
|
302
|
+
const next = await this.nextAsyncItem(iterator, stop.promise);
|
|
303
|
+
if (!next || next.done || stop.isStopped()) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const size = next.value;
|
|
307
|
+
if (!Array.isArray(size) || size.length < 2) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const rows = Math.trunc(Number(size[0]));
|
|
311
|
+
const cols = Math.trunc(Number(size[1]));
|
|
312
|
+
if (!Number.isFinite(rows) || !Number.isFinite(cols) || rows <= 0 || cols <= 0) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
await this.sendFireAndForget("exec_tty.resize", {
|
|
316
|
+
id: reqID,
|
|
317
|
+
rows,
|
|
318
|
+
cols,
|
|
319
|
+
}).catch(() => undefined);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
this.closeAsyncIterator(iterator);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async nextAsyncItem(iterator, stopPromise) {
|
|
327
|
+
const nextPromise = iterator.next();
|
|
328
|
+
const result = await Promise.race([
|
|
329
|
+
nextPromise.then((next) => ({ kind: "next", next })),
|
|
330
|
+
stopPromise.then(() => ({ kind: "stopped" })),
|
|
331
|
+
]);
|
|
332
|
+
if (result.kind === "stopped") {
|
|
333
|
+
void nextPromise.catch(() => undefined);
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
return result.next;
|
|
337
|
+
}
|
|
338
|
+
closeAsyncIterator(iterator) {
|
|
339
|
+
const close = iterator.return;
|
|
340
|
+
if (typeof close !== "function") {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
void Promise.resolve(close.call(iterator)).catch(() => undefined);
|
|
344
|
+
}
|
|
345
|
+
writeStreamChunk(writer, chunk) {
|
|
346
|
+
if (!writer) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (typeof writer === "function") {
|
|
350
|
+
void writer(chunk);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
writer.write(chunk);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
exports.ExecAPI = ExecAPI;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type NetworkInterceptionConfig } from "../types";
|
|
2
|
+
import type { CompiledNetworkHook, WireNetworkInterceptionConfig } from "./wire";
|
|
3
|
+
export declare class NetworkHooks {
|
|
4
|
+
private networkHooks;
|
|
5
|
+
private networkHookServer;
|
|
6
|
+
private networkHookSocketPath;
|
|
7
|
+
private networkHookTempDir;
|
|
8
|
+
compile(cfg: NetworkInterceptionConfig | undefined): [
|
|
9
|
+
WireNetworkInterceptionConfig | undefined,
|
|
10
|
+
Map<string, CompiledNetworkHook>
|
|
11
|
+
];
|
|
12
|
+
start(hooks: Map<string, CompiledNetworkHook>): Promise<string>;
|
|
13
|
+
stop(): Promise<void>;
|
|
14
|
+
private serveNetworkHookSocket;
|
|
15
|
+
private handleNetworkHookSocketLine;
|
|
16
|
+
private invokeNetworkHook;
|
|
17
|
+
private networkHookResultToWire;
|
|
18
|
+
private toStringMap;
|
|
19
|
+
private toStringSliceMap;
|
|
20
|
+
}
|