@buildautomaton/cli 0.1.10 → 0.1.11

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/cli.js CHANGED
@@ -25191,6 +25191,26 @@ function applyCliOutboundNetworkPreferences() {
25191
25191
 
25192
25192
  // src/bridge/connection/cli-ws-client.ts
25193
25193
  import https from "node:https";
25194
+ var CLI_WEBSOCKET_CLIENT_PING_MS = 25e3;
25195
+ function attachWebSocketClientPing(ws, intervalMs) {
25196
+ let timer = null;
25197
+ function clear() {
25198
+ if (timer != null) {
25199
+ clearInterval(timer);
25200
+ timer = null;
25201
+ }
25202
+ }
25203
+ clear();
25204
+ timer = setInterval(() => {
25205
+ if (ws.readyState === wrapper_default.OPEN) {
25206
+ try {
25207
+ ws.ping();
25208
+ } catch {
25209
+ }
25210
+ }
25211
+ }, intervalMs);
25212
+ return clear;
25213
+ }
25194
25214
  function buildCliWebSocketClientOptions(wsUrl) {
25195
25215
  const wsOptions = { perMessageDeflate: false, family: 4 };
25196
25216
  if (wsUrl.startsWith("wss://")) {
@@ -25219,6 +25239,15 @@ function safeCloseWebSocket(ws) {
25219
25239
  }
25220
25240
  }
25221
25241
  }
25242
+ function safeSendWebSocketBinary(ws, data) {
25243
+ if (ws.readyState !== wrapper_default.OPEN) return false;
25244
+ try {
25245
+ ws.send(data, { binary: true });
25246
+ return true;
25247
+ } catch {
25248
+ return false;
25249
+ }
25250
+ }
25222
25251
 
25223
25252
  // src/bridge/connection/create-ws-bridge.ts
25224
25253
  var BRIDGE_AUTH_ERROR_HEADER = "x-bridge-auth-error";
@@ -25227,12 +25256,10 @@ function createWsBridge(options) {
25227
25256
  const { url: url2, onMessage, onOpen, onClose, onError: onError2, onAuthInvalid, clientPingIntervalMs } = options;
25228
25257
  applyCliOutboundNetworkPreferences();
25229
25258
  const ws = new wrapper_default(url2, buildCliWebSocketClientOptions(url2));
25230
- let clientPingTimer = null;
25231
- function clearClientPing() {
25232
- if (clientPingTimer != null) {
25233
- clearInterval(clientPingTimer);
25234
- clientPingTimer = null;
25235
- }
25259
+ let clearClientPing = null;
25260
+ function disposeClientPing() {
25261
+ clearClientPing?.();
25262
+ clearClientPing = null;
25236
25263
  }
25237
25264
  ws.on("unexpected-response", (request, response) => {
25238
25265
  const status = response?.statusCode ?? 0;
@@ -25243,16 +25270,9 @@ function createWsBridge(options) {
25243
25270
  }
25244
25271
  });
25245
25272
  ws.on("open", () => {
25246
- clearClientPing();
25273
+ disposeClientPing();
25247
25274
  if (clientPingIntervalMs != null && clientPingIntervalMs > 0) {
25248
- clientPingTimer = setInterval(() => {
25249
- if (ws.readyState === wrapper_default.OPEN) {
25250
- try {
25251
- ws.ping();
25252
- } catch {
25253
- }
25254
- }
25255
- }, clientPingIntervalMs);
25275
+ clearClientPing = attachWebSocketClientPing(ws, clientPingIntervalMs);
25256
25276
  }
25257
25277
  onOpen?.();
25258
25278
  });
@@ -25273,11 +25293,11 @@ function createWsBridge(options) {
25273
25293
  }
25274
25294
  });
25275
25295
  ws.on("close", (code, reason) => {
25276
- clearClientPing();
25296
+ disposeClientPing();
25277
25297
  onClose?.(code, reason.toString());
25278
25298
  });
25279
25299
  ws.on("error", (err) => {
25280
- clearClientPing();
25300
+ disposeClientPing();
25281
25301
  onError2?.(err);
25282
25302
  });
25283
25303
  return ws;
@@ -34156,12 +34176,64 @@ var DevServerManager = class {
34156
34176
  }
34157
34177
  };
34158
34178
 
34179
+ // src/net/transient-local-fetch-retry.ts
34180
+ var LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS = [30, 100, 200, 400];
34181
+ function sleepMs(ms) {
34182
+ return new Promise((r) => setTimeout(r, ms));
34183
+ }
34184
+ function collectErrorText(err) {
34185
+ const parts = [];
34186
+ let e = err;
34187
+ for (let depth = 0; depth < 6 && e != null; depth += 1) {
34188
+ if (e instanceof Error) {
34189
+ parts.push(e.message);
34190
+ e = e.cause;
34191
+ } else {
34192
+ parts.push(String(e));
34193
+ break;
34194
+ }
34195
+ }
34196
+ return parts.join(" ").toLowerCase();
34197
+ }
34198
+ function isTransientLocalServiceError(err) {
34199
+ const text = collectErrorText(err);
34200
+ if (!text) return false;
34201
+ return text.includes("fetch failed") || text.includes("econnrefused") || text.includes("econnreset") || text.includes("epipe") || text.includes("etimedout") || text.includes("socket hang up") || text.includes("network connection lost") || text.includes("ecanceled") || text.includes("aborted") || text.includes("und_err_socket") || text.includes("other side closed") || text.includes("eai_again");
34202
+ }
34203
+ function isIdempotentHttpMethod(method) {
34204
+ const m = method.toUpperCase();
34205
+ return m === "GET" || m === "HEAD" || m === "OPTIONS";
34206
+ }
34207
+ async function fetchWithLocalTransientRetries(url2, init, options) {
34208
+ const method = (init?.method ?? "GET").toUpperCase();
34209
+ const allowRetry = isIdempotentHttpMethod(method);
34210
+ const delays = options?.delaysMs ?? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS;
34211
+ const maxAttempts = options?.maxAttempts ?? (allowRetry ? Math.max(1, delays.length + 1) : 1);
34212
+ let lastErr;
34213
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
34214
+ try {
34215
+ return await fetch(url2, init);
34216
+ } catch (e) {
34217
+ lastErr = e;
34218
+ const canRetry = allowRetry && attempt < maxAttempts - 1 && isTransientLocalServiceError(e);
34219
+ if (!canRetry) throw e;
34220
+ const waitMs = delays[Math.min(attempt, delays.length - 1)] ?? 100;
34221
+ await sleepMs(waitMs);
34222
+ }
34223
+ }
34224
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
34225
+ }
34226
+
34159
34227
  // src/firehose/proxy/local-proxy.ts
34160
34228
  var ALLOWED_HOSTS = ["localhost", "127.0.0.1", "::1"];
34161
34229
  function isAllowedHost(host) {
34162
34230
  const h = host.replace(/^\[|\]$/g, "");
34163
34231
  return ALLOWED_HOSTS.includes(host) || ALLOWED_HOSTS.includes(h);
34164
34232
  }
34233
+ function isIdempotentProxyMethod(method) {
34234
+ const m = method.toUpperCase();
34235
+ return m === "GET" || m === "HEAD" || m === "OPTIONS";
34236
+ }
34165
34237
  function checkUrlAndHost(request) {
34166
34238
  let url2;
34167
34239
  try {
@@ -34189,38 +34261,47 @@ async function proxyToLocal(request) {
34189
34261
  path: url2.pathname + url2.search,
34190
34262
  headers: request.headers
34191
34263
  };
34192
- return new Promise((resolve15) => {
34193
- const req = mod.request(opts, (res) => {
34194
- const chunks = [];
34195
- res.on("data", (c) => chunks.push(c));
34196
- res.on("end", () => {
34197
- const body = Buffer.concat(chunks).toString("utf8");
34198
- const headers = {};
34199
- for (const [k, v] of Object.entries(res.headers)) {
34200
- if (typeof v === "string") headers[k] = v;
34201
- else if (Array.isArray(v) && v[0]) headers[k] = v[0];
34202
- }
34264
+ const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
34265
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
34266
+ const once = await new Promise((resolve15) => {
34267
+ const req = mod.request(opts, (res) => {
34268
+ const chunks = [];
34269
+ res.on("data", (c) => chunks.push(c));
34270
+ res.on("end", () => {
34271
+ const body = Buffer.concat(chunks).toString("utf8");
34272
+ const headers = {};
34273
+ for (const [k, v] of Object.entries(res.headers)) {
34274
+ if (typeof v === "string") headers[k] = v;
34275
+ else if (Array.isArray(v) && v[0]) headers[k] = v[0];
34276
+ }
34277
+ resolve15({
34278
+ id: request.id,
34279
+ statusCode: res.statusCode ?? 0,
34280
+ headers,
34281
+ body
34282
+ });
34283
+ });
34284
+ });
34285
+ req.on("error", (err) => {
34203
34286
  resolve15({
34204
34287
  id: request.id,
34205
- statusCode: res.statusCode ?? 0,
34206
- headers,
34207
- body
34288
+ statusCode: 0,
34289
+ headers: {},
34290
+ body: "",
34291
+ error: err.message
34208
34292
  });
34209
34293
  });
34294
+ const method = request.method.toUpperCase();
34295
+ if (request.body && method !== "GET" && method !== "HEAD") req.write(request.body);
34296
+ req.end();
34210
34297
  });
34211
- req.on("error", (err) => {
34212
- resolve15({
34213
- id: request.id,
34214
- statusCode: 0,
34215
- headers: {},
34216
- body: "",
34217
- error: err.message
34218
- });
34219
- });
34220
- const method = request.method.toUpperCase();
34221
- if (request.body && method !== "GET" && method !== "HEAD") req.write(request.body);
34222
- req.end();
34223
- });
34298
+ const errMsg = once.error ?? "";
34299
+ const canRetry = attempt < maxAttempts - 1 && errMsg !== "" && isTransientLocalServiceError(new Error(errMsg));
34300
+ if (!canRetry) return once;
34301
+ const waitMs = LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS[Math.min(attempt, LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length - 1)] ?? 100;
34302
+ await sleepMs(waitMs);
34303
+ }
34304
+ throw new Error("Local proxy retry loop exited unexpectedly");
34224
34305
  }
34225
34306
  async function proxyToLocalStreaming(request, callbacks) {
34226
34307
  const checked = checkUrlAndHost(request);
@@ -34237,7 +34318,7 @@ async function proxyToLocalStreaming(request, callbacks) {
34237
34318
  if (request.body !== void 0 && request.body !== null && method !== "GET" && method !== "HEAD") {
34238
34319
  init.body = request.body;
34239
34320
  }
34240
- const res = await fetch(request.url, init);
34321
+ const res = await fetchWithLocalTransientRetries(request.url, init);
34241
34322
  const headers = {};
34242
34323
  res.headers.forEach((value, key) => {
34243
34324
  headers[key] = value;
@@ -34267,6 +34348,9 @@ async function proxyToLocalStreaming(request, callbacks) {
34267
34348
  function startStreamingProxy(ws, log2, pr) {
34268
34349
  proxyToLocalStreaming(pr, {
34269
34350
  onStart: (statusCode, headers) => {
34351
+ if (ws.readyState !== wrapper_default.OPEN) {
34352
+ throw new Error("Preview stream interrupted (firehose connection closed)");
34353
+ }
34270
34354
  const forwardedHeaders = { ...headers };
34271
34355
  const ce = "content-encoding";
34272
34356
  const cl = "content-length";
@@ -34279,7 +34363,10 @@ function startStreamingProxy(ws, log2, pr) {
34279
34363
  },
34280
34364
  onChunk: (chunk) => {
34281
34365
  const idBuf = Buffer.from(pr.id, "utf8");
34282
- ws.send(Buffer.concat([idBuf, Buffer.from(chunk)]), { binary: true });
34366
+ const buf = Buffer.concat([idBuf, Buffer.from(chunk)]);
34367
+ if (!safeSendWebSocketBinary(ws, buf)) {
34368
+ throw new Error("Preview stream interrupted (firehose connection closed)");
34369
+ }
34283
34370
  },
34284
34371
  onEnd: () => sendWsMessage(ws, { type: "proxy_result_end", id: pr.id }),
34285
34372
  onError: (error40) => {
@@ -34397,18 +34484,15 @@ function tryConsumeBinaryProxyBody(raw, deps) {
34397
34484
  }
34398
34485
 
34399
34486
  // src/firehose/connect-firehose.ts
34400
- var FIREHOSE_CLIENT_PING_MS = 25e3;
34401
34487
  function connectFirehose(options) {
34402
34488
  const { firehoseServerUrl, workspaceId, bridgeName, proxyPorts, log: log2, devServerManager, onOpen, onClose } = options;
34403
34489
  const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
34404
34490
  applyCliOutboundNetworkPreferences();
34405
34491
  const ws = new wrapper_default(wsUrl, buildCliWebSocketClientOptions(wsUrl));
34406
- let clientPingTimer = null;
34407
- function clearClientPing() {
34408
- if (clientPingTimer != null) {
34409
- clearInterval(clientPingTimer);
34410
- clientPingTimer = null;
34411
- }
34492
+ let clearClientPing = null;
34493
+ function disposeClientPing() {
34494
+ clearClientPing?.();
34495
+ clearClientPing = null;
34412
34496
  }
34413
34497
  const firehoseSend = (payload) => {
34414
34498
  sendWsMessage(ws, payload);
@@ -34422,15 +34506,8 @@ function connectFirehose(options) {
34422
34506
  startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
34423
34507
  };
34424
34508
  ws.on("open", () => {
34425
- clearClientPing();
34426
- clientPingTimer = setInterval(() => {
34427
- if (ws.readyState === wrapper_default.OPEN) {
34428
- try {
34429
- ws.ping();
34430
- } catch {
34431
- }
34432
- }
34433
- }, FIREHOSE_CLIENT_PING_MS);
34509
+ disposeClientPing();
34510
+ clearClientPing = attachWebSocketClientPing(ws, CLI_WEBSOCKET_CLIENT_PING_MS);
34434
34511
  onOpen?.();
34435
34512
  devServerManager.attachFirehose(firehoseSend);
34436
34513
  sendWsMessage(ws, { type: "identify", workspaceId, bridgeName, proxyPorts });
@@ -34446,18 +34523,21 @@ function connectFirehose(options) {
34446
34523
  }
34447
34524
  });
34448
34525
  ws.on("close", (code, reason) => {
34449
- clearClientPing();
34526
+ disposeClientPing();
34450
34527
  devServerManager.detachFirehose();
34451
34528
  const reasonStr = typeof reason === "string" ? reason : reason.toString();
34452
34529
  onClose?.(code, reasonStr);
34453
34530
  });
34454
34531
  ws.on("error", (err) => {
34455
- clearClientPing();
34532
+ disposeClientPing();
34456
34533
  logCliWebSocketError(log2, "[Proxy and log service]", err);
34534
+ if (ws.readyState === wrapper_default.CONNECTING || ws.readyState === wrapper_default.OPEN) {
34535
+ safeCloseWebSocket(ws);
34536
+ }
34457
34537
  });
34458
34538
  return {
34459
34539
  close() {
34460
- clearClientPing();
34540
+ disposeClientPing();
34461
34541
  devServerManager.detachFirehose();
34462
34542
  safeCloseWebSocket(ws);
34463
34543
  },
@@ -35812,7 +35892,6 @@ async function refreshBridgeTokens(params) {
35812
35892
  }
35813
35893
 
35814
35894
  // src/bridge/connection/main-bridge-ws-lifecycle.ts
35815
- var BRIDGE_CLIENT_PING_MS = 25e3;
35816
35895
  function createMainBridgeWebSocketLifecycle(params) {
35817
35896
  const {
35818
35897
  state,
@@ -35884,7 +35963,7 @@ function createMainBridgeWebSocketLifecycle(params) {
35884
35963
  const url2 = buildBridgeUrl(apiUrl, workspaceId, tokens.accessToken);
35885
35964
  state.currentWs = createWsBridge({
35886
35965
  url: url2,
35887
- clientPingIntervalMs: BRIDGE_CLIENT_PING_MS,
35966
+ clientPingIntervalMs: CLI_WEBSOCKET_CLIENT_PING_MS,
35888
35967
  onAuthInvalid: () => {
35889
35968
  if (authRefreshInFlight) return;
35890
35969
  void (async () => {