@compforge/harness-toolbox 0.5.4 → 0.5.6

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.
Files changed (68) hide show
  1. package/README.md +43 -15
  2. package/dist/{chunk-z15gwjvg.js → chunk-2qgkk7n0.js} +1 -1
  3. package/dist/{chunk-5w85c7cs.js → chunk-361x998p.js} +2 -2
  4. package/dist/{chunk-rkt41za2.js → chunk-68zz17hs.js} +7 -1
  5. package/dist/{chunk-kxpecgy7.js → chunk-6dfcndra.js} +2 -2
  6. package/dist/{chunk-sb5qzv37.js → chunk-6rcyw5ek.js} +1 -1
  7. package/dist/{chunk-56fev9np.js → chunk-81agmerq.js} +2 -2
  8. package/dist/{chunk-0d8vs5q8.js → chunk-91xh8tsr.js} +1 -1
  9. package/dist/{chunk-43mcyrh4.js → chunk-9f9dsjnr.js} +1 -1
  10. package/dist/{chunk-ex429qjt.js → chunk-bkz0dyx5.js} +3 -3
  11. package/dist/{chunk-ae84rzq5.js → chunk-d2y5ewbb.js} +1 -1
  12. package/dist/{chunk-xeft7af0.js → chunk-dfpqr0r9.js} +74 -47
  13. package/dist/{chunk-hbhbragw.js → chunk-erbehkcz.js} +8 -8
  14. package/dist/{chunk-rq5fc007.js → chunk-evb5tj13.js} +11 -8
  15. package/dist/{chunk-3b877a0y.js → chunk-hj14hfvz.js} +3 -3
  16. package/dist/{chunk-dpcge1xt.js → chunk-jt8fn3wg.js} +1 -1
  17. package/dist/{chunk-7m95c7pn.js → chunk-mj6ckk1g.js} +1 -1
  18. package/dist/{chunk-92aacbwh.js → chunk-nhr4za0z.js} +2 -2
  19. package/dist/chunk-nr6ams69.js +50 -0
  20. package/dist/{chunk-18gcp0ck.js → chunk-ps7f1x05.js} +1 -1
  21. package/dist/{chunk-rncagjbc.js → chunk-rybhzd2y.js} +1 -1
  22. package/dist/{chunk-kap6ka3n.js → chunk-s7a3fqzc.js} +3 -3
  23. package/dist/{chunk-mvkxs8d0.js → chunk-v9rcxg0e.js} +1 -1
  24. package/dist/{chunk-xhfbark7.js → chunk-wnykdxz8.js} +2 -2
  25. package/dist/{chunk-ehmx0yhp.js → chunk-xv3nn2vd.js} +3 -3
  26. package/dist/{chunk-njs48b51.js → chunk-ywk81p8a.js} +3 -3
  27. package/dist/connection/acquire.js +2 -2
  28. package/dist/datasource.js +1 -1
  29. package/dist/errors.d.ts +8 -1
  30. package/dist/errors.js +3 -1
  31. package/dist/file/client.js +2 -2
  32. package/dist/file/index.js +4 -4
  33. package/dist/file/local.js +3 -3
  34. package/dist/file/pod.js +3 -3
  35. package/dist/http/client.js +10 -10
  36. package/dist/http/index.js +10 -10
  37. package/dist/kubernetes/client.js +9 -9
  38. package/dist/kubernetes/compute-quantity.js +2 -2
  39. package/dist/kubernetes/compute.js +2 -2
  40. package/dist/kubernetes/environment.js +9 -9
  41. package/dist/kubernetes/exec-transport.d.ts +3 -2
  42. package/dist/kubernetes/exec-transport.js +4 -2
  43. package/dist/kubernetes/executor.js +1 -1
  44. package/dist/kubernetes/pod-log-datasource.js +1 -1
  45. package/dist/kubernetes/port-forward.js +2 -2
  46. package/dist/kubernetes/resource-transport.js +3 -3
  47. package/dist/kubernetes/resources.js +2 -2
  48. package/dist/kubernetes/secret-datasource.js +2 -2
  49. package/dist/kubernetes/service-port-forward.js +3 -3
  50. package/dist/kubernetes/workload.js +2 -2
  51. package/dist/mysql/client.js +10 -10
  52. package/dist/mysql/index.js +11 -11
  53. package/dist/network/connectivity.d.ts +4 -8
  54. package/dist/network/connectivity.js +19 -5
  55. package/dist/network/endpoint.d.ts +9 -0
  56. package/dist/network/endpoint.js +8 -0
  57. package/dist/opensearch/client.js +10 -10
  58. package/dist/opensearch/index.js +10 -10
  59. package/dist/redis/client.js +10 -10
  60. package/dist/redis/index.js +10 -10
  61. package/dist/s3/client.js +10 -10
  62. package/dist/s3/datasource.js +11 -11
  63. package/dist/s3/index.js +11 -11
  64. package/dist/transport/index.js +9 -9
  65. package/dist/transport/lifecycle.js +3 -3
  66. package/dist/transport/pod-relay.js +6 -6
  67. package/dist/transport/service-port-forward.js +7 -7
  68. package/package.json +6 -2
package/README.md CHANGED
@@ -152,23 +152,27 @@ selectorless Services and failed access raise KubernetesError.
152
152
 
153
153
  The resource path uses client-node kubeconfig authentication/TLS and bounded native JSON reads,
154
154
  not kubectl error text. Configure request timeout, concurrency and response bytes with the
155
- client's ResourceLimits constructor argument. Native exec uses the same root capacity/byte policy
155
+ client's ResourceLimits constructor argument. Exec uses the same root capacity/byte policy
156
156
  with a separate slot pool; its per-call timeout defaults to that policy and covers queueing,
157
157
  authentication, upgrade and execution. Explicit per-call timeouts support longer commands.
158
158
 
159
- Exec requires `get pods/exec` permission for the WebSocket upgrade (kubectl/SPDY paths may use
160
- `create pods/exec`). It connects directly to the Pod exec WebSocket endpoint without a preliminary Pod GET or
161
- kubectl subprocess. Each command owns a stream session; failures are never retried or replayed.
162
- Kubeconfig controls authentication, TLS and proxy access. stdout/stderr and remote exit status
163
- remain separate; retained output and individual messages are bounded. Streaming callers may
164
- set `collectStdout: false` and enforce their own total byte budget. Root disposal cancels active
165
- and queued sessions. Protocol v4 is accepted for output-only commands; stdin requires v5 to
166
- close input without losing remaining output/status. Bun does not expose HTTP upgrade rejection
167
- status, so those failures retain an unknown connection error. Native exec with a kubeconfig
168
- proxy requires Node: Bun cannot reliably release proxy sockets or preserve TLS name overrides.
169
- It fails before opening a session on Bun; use Node or explicitly provide an Executor adapter.
170
- The transport never bypasses the proxy or TLS checks.
171
- Port-forward still uses kubectl.
159
+ Output-only exec connects directly to the Pod exec WebSocket endpoint without a preliminary
160
+ Pod GET and requires `get pods/exec`. Commands with explicit stdin (including empty input) use
161
+ `kubectl exec -i`, which handles input EOF through SPDY/v5 on older and newer clusters. This
162
+ path requires a compatible kubectl on the Host, Pod read permission and `create pods/exec`
163
+ for SPDY (`get pods/exec` for WebSocket). The channel is selected before execution; failures
164
+ are never retried or replayed by Toolbox. The result's `command` records the actual channel.
165
+ Kubeconfig controls authentication, TLS and proxy access. Both paths share the root exec slot
166
+ pool, deadline, retained-output byte limit and cancellation. stdout/stderr remain separate;
167
+ native exec uses remote status and kubectl uses process exit status, retaining raw CLI stderr
168
+ without guessing structured HTTP error kinds. Streaming callers may set `collectStdout: false`
169
+ and enforce their own total byte budget; individual output chunks remain bounded. Root disposal
170
+ cancels active and queued sessions and waits for the local kubectl process to exit.
171
+ Output-only sessions accept v4/v5. Bun does not expose HTTP upgrade rejection status, so those
172
+ failures retain an unknown connection error. Native exec with a kubeconfig proxy requires Node:
173
+ Bun cannot reliably release proxy sockets or preserve TLS name overrides. This restriction does
174
+ not apply to the kubectl stdin path. Neither path bypasses proxy or TLS checks.
175
+ Port-forward also uses kubectl.
172
176
  See [the shared Workload model](../../../docs/workload.md).
173
177
 
174
178
  ```ts
@@ -433,7 +437,31 @@ identifiers, never keys, values or errors. Logging does not change retention or
433
437
 
434
438
  ### Network connectivity
435
439
 
436
- `@compforge/harness-toolbox/network` exports `probeConnectivity({host, port, tls?}, execute, {timeoutMs, signal?})`.
440
+ `@compforge/harness-toolbox/network` exports `probeConnectivity(endpoint, execute, {timeoutMs, signal?})`.
441
+ It consumes `Endpoint` from `@compforge/harness-common`; `ConnectivityTarget` is removed.
442
+
443
+ ```typescript
444
+ import type { Endpoint } from "@compforge/harness-common";
445
+ import { endpointFromUrl, validateEndpoint } from "@compforge/harness-toolbox/endpoint";
446
+
447
+ const endpoint: Endpoint = endpointFromUrl("https://storage.example:9443/object?signature=private");
448
+ // { scheme: "https", host: "storage.example", port: 9443, secure: true }
449
+ validateEndpoint(endpoint); // Pure validation; no DNS or I/O.
450
+ ```
451
+
452
+ The URL projection drops credentials, path, query and fragment, and unbrackets IPv6 hosts.
453
+ HTTP(S), WS(S) and FTP have default ports. The `tls` scheme requires a port and sets `secure: true`.
454
+ Other schemes require an explicit port and leave
455
+ `secure` unspecified; callers supply any encryption requirement from their protocol configuration.
456
+ Errors do not expose the original URL. Keep the full URL separately for resource access.
457
+
458
+ `secure` replaces `tls` in probe inputs and result targets. HTTPS/WSS/TLS require `secure: true`;
459
+ HTTP/WS reject it. With `secure: true`, this probe supports direct TLS for `https`, `wss`, and
460
+ explicit `tls` schemes. Other schemes, or a missing scheme, throw `ProtocolError` (a `ToolboxError` with `kind: "protocol"`, `code: "Unsupported"`)
461
+ before execution: protocol-negotiated encryption such as MySQL or STARTTLS is not implemented.
462
+ Without an encryption requirement, a successful TCP check does not prove application-protocol
463
+ availability or encryption. TLS remains the name of the observed handshake stage.
464
+
437
465
  The caller supplies an executor in the consumer environment (host, Pod or sandbox),
438
466
  which runs Python 3 with the supplied argv and stdin and enforces timeout/cancellation.
439
467
  DNS, TCP and optional verified TLS have separate results and timings. Domain names,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  KubernetesError
3
- } from "./chunk-rkt41za2.js";
3
+ } from "./chunk-68zz17hs.js";
4
4
 
5
5
  // src/kubernetes/compute-quantity.ts
6
6
  function object(value) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resourcePath
3
- } from "./chunk-dpcge1xt.js";
3
+ } from "./chunk-jt8fn3wg.js";
4
4
  import {
5
5
  clientKey
6
6
  } from "./chunk-a9htb6cr.js";
@@ -11,7 +11,7 @@ import {
11
11
  KubernetesError,
12
12
  ToolboxError,
13
13
  kubernetesHttpError
14
- } from "./chunk-rkt41za2.js";
14
+ } from "./chunk-68zz17hs.js";
15
15
 
16
16
  // src/kubernetes/resource-transport.ts
17
17
  import { KubeConfig } from "@kubernetes/client-node";
@@ -10,6 +10,12 @@ class ToolboxError extends Error {
10
10
  }
11
11
  }
12
12
 
13
+ class ProtocolError extends ToolboxError {
14
+ constructor(message, options) {
15
+ super(message, { ...options, kind: "protocol" });
16
+ }
17
+ }
18
+
13
19
  class KubernetesError extends ToolboxError {
14
20
  }
15
21
 
@@ -47,4 +53,4 @@ function kubernetesCommandError(result) {
47
53
  return status ? kubernetesHttpError(status) : new KubernetesError("Kubernetes resource request failed", { kind: "operation_failed" });
48
54
  }
49
55
 
50
- export { ToolboxError, KubernetesError, FileError, TimeoutError, kubernetesHttpError, kubernetesCommandError };
56
+ export { ToolboxError, ProtocolError, KubernetesError, FileError, TimeoutError, kubernetesHttpError, kubernetesCommandError };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  isConnectionNetworkError
3
- } from "./chunk-18gcp0ck.js";
3
+ } from "./chunk-ps7f1x05.js";
4
4
  import {
5
5
  TimeoutError,
6
6
  ToolboxError
7
- } from "./chunk-rkt41za2.js";
7
+ } from "./chunk-68zz17hs.js";
8
8
 
9
9
  // src/opensearch/client.ts
10
10
  import { raceSignal } from "race-signal";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  acquireConnection
3
- } from "./chunk-43mcyrh4.js";
3
+ } from "./chunk-9f9dsjnr.js";
4
4
 
5
5
  // src/transport/lifecycle.ts
6
6
  class Transport {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isConnectionNetworkError
3
- } from "./chunk-18gcp0ck.js";
3
+ } from "./chunk-ps7f1x05.js";
4
4
  import {
5
5
  ConcurrencyPool
6
6
  } from "./chunk-v3hxvnrc.js";
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  TimeoutError,
12
12
  ToolboxError
13
- } from "./chunk-rkt41za2.js";
13
+ } from "./chunk-68zz17hs.js";
14
14
 
15
15
  // src/http/client.ts
16
16
  import { HttpRequest } from "@smithy/core/protocols";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-6fnhzhdb.js";
9
9
  import {
10
10
  PortForwardScope
11
- } from "./chunk-mvkxs8d0.js";
11
+ } from "./chunk-v9rcxg0e.js";
12
12
 
13
13
  // src/kubernetes/service-port-forward.ts
14
14
  class ServicePortForwarder {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  TimeoutError,
3
3
  ToolboxError
4
- } from "./chunk-rkt41za2.js";
4
+ } from "./chunk-68zz17hs.js";
5
5
 
6
6
  // src/connection/acquire.ts
7
7
  import { raceSignal } from "race-signal";
@@ -12,16 +12,16 @@ import {
12
12
  } from "./chunk-835fssgz.js";
13
13
  import {
14
14
  isConnectionNetworkError
15
- } from "./chunk-18gcp0ck.js";
15
+ } from "./chunk-ps7f1x05.js";
16
16
  import {
17
17
  ConcurrencyPool
18
18
  } from "./chunk-v3hxvnrc.js";
19
19
  import {
20
20
  Transport
21
- } from "./chunk-sb5qzv37.js";
21
+ } from "./chunk-6rcyw5ek.js";
22
22
  import {
23
23
  ToolboxError
24
- } from "./chunk-rkt41za2.js";
24
+ } from "./chunk-68zz17hs.js";
25
25
 
26
26
  // src/mysql/client.ts
27
27
  import { raceSignal } from "race-signal";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  KubernetesError
3
- } from "./chunk-rkt41za2.js";
3
+ } from "./chunk-68zz17hs.js";
4
4
 
5
5
  // src/kubernetes/workload.ts
6
6
  function failure(message, kind) {
@@ -1,38 +1,41 @@
1
1
  import {
2
2
  ConcurrencyPool
3
3
  } from "./chunk-v3hxvnrc.js";
4
+ import {
5
+ KubectlExecutor,
6
+ buildExecArgs
7
+ } from "./chunk-evb5tj13.js";
4
8
  import {
5
9
  KubernetesError,
6
10
  kubernetesHttpError
7
- } from "./chunk-rkt41za2.js";
11
+ } from "./chunk-68zz17hs.js";
8
12
 
9
13
  // src/kubernetes/exec-transport.ts
10
14
  import { KubeConfig } from "@kubernetes/client-node";
11
15
  import WebSocket from "ws";
12
16
  class KubernetesExecTransport {
17
+ connection;
13
18
  signal;
14
19
  limits;
15
20
  #config = new KubeConfig;
16
21
  #capacity;
17
22
  #server;
18
- constructor(options, signal, limits) {
23
+ constructor(connection, signal, limits) {
24
+ this.connection = connection;
19
25
  this.signal = signal;
20
26
  this.limits = limits;
21
27
  if (!Object.values(limits).every((value) => Number.isSafeInteger(value) && value > 0)) {
22
28
  throw new KubernetesError("Exec limits must be positive integers", { kind: "invalid_argument" });
23
29
  }
24
- if (options.kubeconfig)
25
- this.#config.loadFromFile(options.kubeconfig);
30
+ if (connection.kubeconfig)
31
+ this.#config.loadFromFile(connection.kubeconfig);
26
32
  else
27
33
  this.#config.loadFromDefault();
28
- if (options.context)
29
- this.#config.setCurrentContext(options.context);
34
+ if (connection.context)
35
+ this.#config.setCurrentContext(connection.context);
30
36
  const cluster = this.#config.getCurrentCluster();
31
37
  if (!cluster)
32
38
  throw new KubernetesError("Kubernetes context has no cluster", { kind: "invalid_argument" });
33
- if (process.versions.bun && cluster.proxyUrl) {
34
- throw new KubernetesError("Native exec through a kubeconfig proxy requires a Node host", { kind: "unsupported_operation" });
35
- }
36
39
  this.#server = cluster.server.replace(/\/$/, "");
37
40
  if (new URL(cluster.server).protocol === "http:")
38
41
  this.#config.clusters = this.#config.clusters.map((item) => item === cluster ? { ...item, skipTLSVerify: true } : item);
@@ -49,17 +52,71 @@ class KubernetesExecTransport {
49
52
  const signal = AbortSignal.any([caller, deadline.signal]);
50
53
  const timer = setTimeout(() => deadline.abort(), timeoutMs);
51
54
  try {
52
- return await this.#capacity.run(() => this.#session(namespace, target, command, options, signal, started, deadline.signal), signal);
55
+ return await this.#capacity.run(() => options.stdin === undefined ? this.#session(namespace, target, command, options, signal, started, deadline.signal) : this.#stdinSession(namespace, target, command, options, signal, started), signal);
53
56
  } catch (error) {
54
57
  if (caller.aborted)
55
58
  throw caller.reason;
56
- if (deadline.signal.aborted)
57
- return this.#result(namespace, target, command, started, null, "", "Exec deadline exceeded", true);
59
+ if (deadline.signal.aborted) {
60
+ const result = this.#result(namespace, target, command, started, null, "", "Exec deadline exceeded", true);
61
+ if (options.stdin !== undefined)
62
+ result.command = buildExecArgs({ ...this.connection, namespace }, target, command, true);
63
+ return result;
64
+ }
58
65
  throw error;
59
66
  } finally {
60
67
  clearTimeout(timer);
61
68
  }
62
69
  }
70
+ async#stdinSession(namespace, target, command, options, signal, started) {
71
+ const controller = new AbortController;
72
+ const combined = AbortSignal.any([signal, controller.signal]);
73
+ let retained = 0;
74
+ let stdout = "";
75
+ let failure;
76
+ const guard = (action) => {
77
+ if (combined.aborted)
78
+ return;
79
+ try {
80
+ action();
81
+ } catch (cause) {
82
+ failure = cause;
83
+ controller.abort(cause);
84
+ throw cause;
85
+ }
86
+ };
87
+ const count = (size, retain) => {
88
+ if (retain)
89
+ retained += size;
90
+ if (size > this.limits.maxBytes || retained > this.limits.maxBytes) {
91
+ throw new KubernetesError(`Kubernetes exec ${namespace}/${target.pod} output exceeds ${this.limits.maxBytes} bytes`, { kind: "limit_exceeded" });
92
+ }
93
+ };
94
+ const executor = new KubectlExecutor({ ...this.connection, namespace });
95
+ const result = await executor.exec(target, command, {
96
+ ...options,
97
+ signal: combined,
98
+ timeoutMs: Math.max(1, (options.timeoutMs ?? this.limits.timeoutMs) - (performance.now() - started)),
99
+ collectStdout: false,
100
+ onStdoutBytes: (bytes) => guard(() => {
101
+ count(bytes.length, options.collectStdout !== false);
102
+ options.onStdoutBytes?.(bytes);
103
+ }),
104
+ onStdout: (text) => guard(() => {
105
+ if (options.collectStdout !== false)
106
+ stdout += text;
107
+ options.onStdout?.(text);
108
+ }),
109
+ onStderr: (text) => guard(() => {
110
+ count(Buffer.byteLength(text), true);
111
+ options.onStderr?.(text);
112
+ })
113
+ });
114
+ if (controller.signal.aborted)
115
+ throw failure;
116
+ if (signal.aborted)
117
+ throw signal.reason;
118
+ return { ...result, stdout, durationMs: performance.now() - started };
119
+ }
63
120
  #result(namespace, target, command, started, exitCode, stdout, stderr, timedOut = false) {
64
121
  return {
65
122
  ok: exitCode === 0 && !timedOut,
@@ -81,10 +138,12 @@ class KubernetesExecTransport {
81
138
  };
82
139
  }
83
140
  #session(namespace, target, command, options, signal, started, deadline) {
141
+ if (process.versions.bun && this.#config.getCurrentCluster()?.proxyUrl) {
142
+ throw new KubernetesError("Native exec through a kubeconfig proxy requires a Node host", { kind: "unsupported_operation" });
143
+ }
84
144
  return new Promise((resolve, reject) => {
85
145
  let socket;
86
146
  let agent;
87
- let input;
88
147
  let settled = false;
89
148
  let stdout = "", stderr = "", retained = 0;
90
149
  const outDecoder = new TextDecoder, errDecoder = new TextDecoder;
@@ -95,7 +154,6 @@ class KubernetesExecTransport {
95
154
  signal.removeEventListener("abort", abort);
96
155
  socket?.terminate();
97
156
  agent?.destroy();
98
- input?.cancel().catch(() => {});
99
157
  const tail = outDecoder.decode(), errTail = errDecoder.decode();
100
158
  if (options.collectStdout !== false)
101
159
  stdout += tail;
@@ -140,11 +198,11 @@ Exec deadline exceeded`;
140
198
  url.searchParams.append("command", arg);
141
199
  url.searchParams.set("stdout", "true");
142
200
  url.searchParams.set("stderr", "true");
143
- url.searchParams.set("stdin", String(options.stdin !== undefined));
201
+ url.searchParams.set("stdin", "false");
144
202
  url.searchParams.set("tty", "false");
145
203
  if (target.container)
146
204
  url.searchParams.set("container", target.container);
147
- socket = new WebSocket(url, options.stdin === undefined ? ["v5.channel.k8s.io", "v4.channel.k8s.io"] : ["v5.channel.k8s.io"], { ...request, ...process.versions.bun ? { tls: {
205
+ socket = new WebSocket(url, ["v5.channel.k8s.io", "v4.channel.k8s.io"], { ...request, ...process.versions.bun ? { tls: {
148
206
  ca: request.ca,
149
207
  cert: request.cert,
150
208
  key: request.key,
@@ -225,37 +283,6 @@ Exec deadline exceeded`;
225
283
  fail("invalid_response", error);
226
284
  }
227
285
  });
228
- socket.on("open", () => {
229
- if (options.stdin === undefined || settled)
230
- return;
231
- const send = (bytes) => new Promise((done, failed) => {
232
- if (settled) {
233
- failed(new Error("Exec closed"));
234
- return;
235
- }
236
- socket.send(Buffer.concat([Buffer.from([0]), bytes]), (error) => error ? failed(error) : done());
237
- });
238
- (async () => {
239
- const stdin = options.stdin;
240
- if (typeof stdin === "string" || stdin instanceof Uint8Array)
241
- await send(typeof stdin === "string" ? new TextEncoder().encode(stdin) : stdin);
242
- else {
243
- const reader = stdin.getReader();
244
- input = reader;
245
- while (!settled) {
246
- const chunk = await reader.read();
247
- if (chunk.done)
248
- break;
249
- await send(chunk.value);
250
- }
251
- }
252
- if (!settled)
253
- socket.send(Buffer.from([255, 0]));
254
- })().catch((error) => {
255
- if (!settled)
256
- fail("connection_failed", error);
257
- });
258
- });
259
286
  }).catch((error) => {
260
287
  if (!settled)
261
288
  fail("connection_failed", error);
@@ -1,21 +1,21 @@
1
+ import {
2
+ resolveWorkload
3
+ } from "./chunk-d2y5ewbb.js";
1
4
  import {
2
5
  KubernetesResourceTransport
3
- } from "./chunk-5w85c7cs.js";
6
+ } from "./chunk-361x998p.js";
4
7
  import {
5
8
  KubernetesExecTransport
6
- } from "./chunk-xeft7af0.js";
7
- import {
8
- resolveWorkload
9
- } from "./chunk-ae84rzq5.js";
9
+ } from "./chunk-dfpqr0r9.js";
10
10
  import {
11
11
  ServicePortForwarder
12
- } from "./chunk-0d8vs5q8.js";
12
+ } from "./chunk-91xh8tsr.js";
13
13
  import {
14
14
  KubectlExecutor
15
- } from "./chunk-rq5fc007.js";
15
+ } from "./chunk-evb5tj13.js";
16
16
  import {
17
17
  KubernetesError
18
- } from "./chunk-rkt41za2.js";
18
+ } from "./chunk-68zz17hs.js";
19
19
 
20
20
  // src/kubernetes/client.ts
21
21
  class KubernetesClient {
@@ -68,24 +68,25 @@ async function runArgv(argv, opts) {
68
68
  let aborted = false;
69
69
  const abort = () => {
70
70
  aborted = true;
71
- proc.kill();
71
+ proc.kill("SIGKILL");
72
72
  };
73
73
  const timer = setTimeout(() => {
74
74
  timedOut = true;
75
- proc.kill();
75
+ proc.kill("SIGKILL");
76
76
  }, timeoutMs);
77
77
  if (opts?.signal?.aborted)
78
78
  abort();
79
79
  else
80
80
  opts?.signal?.addEventListener("abort", abort, { once: true });
81
+ const outputs = [
82
+ readOutput(proc.stdout, opts?.onStdout, opts?.onStdoutBytes, opts?.collectStdout ?? true),
83
+ readOutput(proc.stderr, opts?.onStderr),
84
+ proc.exited
85
+ ];
81
86
  try {
82
- const [stdout, stderr, exitCode] = await Promise.all([
83
- readOutput(proc.stdout, opts?.onStdout, opts?.onStdoutBytes, opts?.collectStdout ?? true),
84
- readOutput(proc.stderr, opts?.onStderr),
85
- proc.exited
86
- ]);
87
+ const [stdout, stderr, exitCode] = await Promise.all(outputs);
87
88
  return {
88
- ok: exitCode === 0 && !timedOut,
89
+ ok: exitCode === 0 && !timedOut && !aborted,
89
90
  exitCode,
90
91
  stdout,
91
92
  stderr: timedOut ? `${stderr}
@@ -96,6 +97,8 @@ async function runArgv(argv, opts) {
96
97
  command: argv
97
98
  };
98
99
  } catch (err) {
100
+ proc.kill("SIGKILL");
101
+ await Promise.allSettled(outputs);
99
102
  return {
100
103
  ok: false,
101
104
  exitCode: null,
@@ -3,13 +3,13 @@ import {
3
3
  } from "./chunk-z5pm2emz.js";
4
4
  import {
5
5
  S3Client
6
- } from "./chunk-7m95c7pn.js";
6
+ } from "./chunk-mj6ckk1g.js";
7
7
  import {
8
8
  DirectTransport
9
- } from "./chunk-18gcp0ck.js";
9
+ } from "./chunk-ps7f1x05.js";
10
10
  import {
11
11
  Transport
12
- } from "./chunk-sb5qzv37.js";
12
+ } from "./chunk-6rcyw5ek.js";
13
13
 
14
14
  // src/s3/datasource.ts
15
15
  class S3DataSource {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  KubernetesError
3
- } from "./chunk-rkt41za2.js";
3
+ } from "./chunk-68zz17hs.js";
4
4
 
5
5
  // src/kubernetes/resources.ts
6
6
  function resourcePath(query, name) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isConnectionNetworkError
3
- } from "./chunk-18gcp0ck.js";
3
+ } from "./chunk-ps7f1x05.js";
4
4
  import {
5
5
  ConcurrencyPool
6
6
  } from "./chunk-v3hxvnrc.js";
@@ -2,10 +2,10 @@ import {
2
2
  BoundedFileClient,
3
3
  fileClientOptions,
4
4
  filePaths
5
- } from "./chunk-rncagjbc.js";
5
+ } from "./chunk-rybhzd2y.js";
6
6
  import {
7
7
  FileError
8
- } from "./chunk-rkt41za2.js";
8
+ } from "./chunk-68zz17hs.js";
9
9
 
10
10
  // src/file/local.ts
11
11
  import { constants } from "node:fs";
@@ -0,0 +1,50 @@
1
+ // src/network/endpoint.ts
2
+ import { isIP } from "node:net";
3
+ var URL_DEFAULTS = new Map([
4
+ ["http", { port: 80, secure: false }],
5
+ ["https", { port: 443, secure: true }],
6
+ ["ws", { port: 80, secure: false }],
7
+ ["wss", { port: 443, secure: true }],
8
+ ["ftp", { port: 21, secure: false }],
9
+ ["tls", { secure: true }]
10
+ ]);
11
+ function validateEndpoint(endpoint) {
12
+ if (typeof endpoint.host !== "string" || !endpoint.host || /[\s/\\?#@%\[\]]/.test(endpoint.host) || endpoint.host.includes(":") && isIP(endpoint.host) !== 6) {
13
+ throw new Error("Endpoint host must be a hostname/IP, with IPv6 unbracketed and no URL components");
14
+ }
15
+ if (!Number.isInteger(endpoint.port) || endpoint.port < 1 || endpoint.port > 65535) {
16
+ throw new Error("Endpoint port must be an integer between 1 and 65535");
17
+ }
18
+ if (endpoint.scheme !== undefined && !/^[a-z][a-z0-9+.-]*$/.test(endpoint.scheme)) {
19
+ throw new Error("Endpoint scheme must be lowercase and contain no colon");
20
+ }
21
+ if (endpoint.secure !== undefined && typeof endpoint.secure !== "boolean")
22
+ throw new Error("Endpoint secure must be boolean");
23
+ const defaults = endpoint.scheme ? URL_DEFAULTS.get(endpoint.scheme) : undefined;
24
+ if (defaults && endpoint.scheme !== "ftp" && (endpoint.secure ?? false) !== defaults.secure) {
25
+ throw new Error("Endpoint secure conflicts with its scheme; HTTPS/WSS/TLS require encryption");
26
+ }
27
+ }
28
+ function endpointFromUrl(value) {
29
+ let url;
30
+ try {
31
+ url = new URL(value);
32
+ } catch {
33
+ throw new Error("Endpoint requires a valid absolute network URL");
34
+ }
35
+ const scheme = url.protocol.slice(0, -1);
36
+ const defaults = URL_DEFAULTS.get(scheme);
37
+ const port = url.port ? Number(url.port) : defaults?.port;
38
+ if (port === undefined)
39
+ throw new Error("Endpoint URL requires an explicit port for this scheme");
40
+ const endpoint = {
41
+ scheme,
42
+ host: url.hostname.replace(/^\[|\]$/g, ""),
43
+ port,
44
+ ...defaults ? { secure: defaults.secure } : {}
45
+ };
46
+ validateEndpoint(endpoint);
47
+ return endpoint;
48
+ }
49
+
50
+ export { validateEndpoint, endpointFromUrl };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Transport
3
- } from "./chunk-sb5qzv37.js";
3
+ } from "./chunk-6rcyw5ek.js";
4
4
 
5
5
  // src/transport/index.ts
6
6
  class DirectTransport extends Transport {
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  FileError,
6
6
  ToolboxError
7
- } from "./chunk-rkt41za2.js";
7
+ } from "./chunk-68zz17hs.js";
8
8
 
9
9
  // src/file/client.ts
10
10
  function fileClientOptions(options) {
@@ -3,13 +3,13 @@ import {
3
3
  } from "./chunk-ssj93brx.js";
4
4
  import {
5
5
  startPortForward
6
- } from "./chunk-mvkxs8d0.js";
6
+ } from "./chunk-v9rcxg0e.js";
7
7
  import {
8
8
  KubectlExecutor
9
- } from "./chunk-rq5fc007.js";
9
+ } from "./chunk-evb5tj13.js";
10
10
  import {
11
11
  Transport
12
- } from "./chunk-sb5qzv37.js";
12
+ } from "./chunk-6rcyw5ek.js";
13
13
 
14
14
  // src/transport/pod-relay.ts
15
15
  import { raceSignal } from "race-signal";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildKubectlArgs
3
- } from "./chunk-rq5fc007.js";
3
+ } from "./chunk-evb5tj13.js";
4
4
  import {
5
5
  spawnProcess
6
6
  } from "./chunk-9shz0bw1.js";
@@ -2,10 +2,10 @@ import {
2
2
  BoundedFileClient,
3
3
  fileClientOptions,
4
4
  filePaths
5
- } from "./chunk-rncagjbc.js";
5
+ } from "./chunk-rybhzd2y.js";
6
6
  import {
7
7
  FileError
8
- } from "./chunk-rkt41za2.js";
8
+ } from "./chunk-68zz17hs.js";
9
9
 
10
10
  // src/file/pod.ts
11
11
  import { posix } from "node:path";