@buildautomaton/cli 0.1.39 → 0.1.40
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 +124 -10
- package/dist/cli.js.map +4 -4
- package/dist/index.js +124 -10
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -25064,7 +25064,7 @@ var {
|
|
|
25064
25064
|
} = import_index.default;
|
|
25065
25065
|
|
|
25066
25066
|
// src/cli-version.ts
|
|
25067
|
-
var CLI_VERSION = "0.1.
|
|
25067
|
+
var CLI_VERSION = "0.1.40".length > 0 ? "0.1.40" : "0.0.0-dev";
|
|
25068
25068
|
|
|
25069
25069
|
// src/cli/defaults.ts
|
|
25070
25070
|
var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
|
|
@@ -25542,9 +25542,9 @@ function attachWebSocketClientPing(ws, intervalMs) {
|
|
|
25542
25542
|
return clear;
|
|
25543
25543
|
}
|
|
25544
25544
|
function buildCliWebSocketClientOptions(wsUrl) {
|
|
25545
|
-
const wsOptions = { perMessageDeflate: false
|
|
25545
|
+
const wsOptions = { perMessageDeflate: false };
|
|
25546
25546
|
if (wsUrl.startsWith("wss://")) {
|
|
25547
|
-
wsOptions.agent = new https.Agent({ rejectUnauthorized: false
|
|
25547
|
+
wsOptions.agent = new https.Agent({ rejectUnauthorized: false });
|
|
25548
25548
|
}
|
|
25549
25549
|
return wsOptions;
|
|
25550
25550
|
}
|
|
@@ -25579,11 +25579,47 @@ function safeSendWebSocketBinary(ws, data) {
|
|
|
25579
25579
|
}
|
|
25580
25580
|
}
|
|
25581
25581
|
|
|
25582
|
+
// src/connection/parse-compact-heartbeat-ack.ts
|
|
25583
|
+
function tryParseCompactHeartbeatAck(raw) {
|
|
25584
|
+
let str = null;
|
|
25585
|
+
if (typeof raw === "string") {
|
|
25586
|
+
str = raw;
|
|
25587
|
+
} else if (Buffer.isBuffer(raw)) {
|
|
25588
|
+
str = raw.toString("utf8");
|
|
25589
|
+
} else if (raw instanceof ArrayBuffer) {
|
|
25590
|
+
str = Buffer.from(raw).toString("utf8");
|
|
25591
|
+
} else {
|
|
25592
|
+
return null;
|
|
25593
|
+
}
|
|
25594
|
+
if (str.length > 64 || !str.includes('"ha"')) return null;
|
|
25595
|
+
try {
|
|
25596
|
+
const parsed = JSON.parse(str);
|
|
25597
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
25598
|
+
const o = parsed;
|
|
25599
|
+
if (o.t !== "ha") return null;
|
|
25600
|
+
const s = o.s;
|
|
25601
|
+
if (typeof s !== "number" || !Number.isFinite(s)) return null;
|
|
25602
|
+
return Math.trunc(s);
|
|
25603
|
+
} catch {
|
|
25604
|
+
return null;
|
|
25605
|
+
}
|
|
25606
|
+
}
|
|
25607
|
+
|
|
25582
25608
|
// src/connection/create-ws-bridge.ts
|
|
25583
25609
|
var BRIDGE_AUTH_ERROR_HEADER = "x-bridge-auth-error";
|
|
25584
25610
|
var BRIDGE_AUTH_ERROR_TOKEN_INVALID = "token_invalid";
|
|
25585
25611
|
function createWsBridge(options) {
|
|
25586
|
-
const {
|
|
25612
|
+
const {
|
|
25613
|
+
url: url2,
|
|
25614
|
+
onMessage,
|
|
25615
|
+
onOpen,
|
|
25616
|
+
onClose,
|
|
25617
|
+
onError: onError2,
|
|
25618
|
+
onAuthInvalid,
|
|
25619
|
+
clientPingIntervalMs,
|
|
25620
|
+
onCompactHeartbeatAck,
|
|
25621
|
+
isActiveSocket
|
|
25622
|
+
} = options;
|
|
25587
25623
|
applyCliOutboundNetworkPreferences();
|
|
25588
25624
|
const ws = new wrapper_default(url2, buildCliWebSocketClientOptions(url2));
|
|
25589
25625
|
let clearClientPing = null;
|
|
@@ -25607,7 +25643,13 @@ function createWsBridge(options) {
|
|
|
25607
25643
|
onOpen?.();
|
|
25608
25644
|
});
|
|
25609
25645
|
ws.on("message", (raw) => {
|
|
25646
|
+
const ackSeq = tryParseCompactHeartbeatAck(raw);
|
|
25647
|
+
if (ackSeq != null) {
|
|
25648
|
+
onCompactHeartbeatAck?.(ackSeq);
|
|
25649
|
+
return;
|
|
25650
|
+
}
|
|
25610
25651
|
setImmediate(() => {
|
|
25652
|
+
if (isActiveSocket && !isActiveSocket(ws)) return;
|
|
25611
25653
|
try {
|
|
25612
25654
|
let data;
|
|
25613
25655
|
if (typeof raw === "string") {
|
|
@@ -25618,9 +25660,9 @@ function createWsBridge(options) {
|
|
|
25618
25660
|
} else {
|
|
25619
25661
|
data = raw;
|
|
25620
25662
|
}
|
|
25621
|
-
onMessage?.(data);
|
|
25663
|
+
onMessage?.(data, ws);
|
|
25622
25664
|
} catch {
|
|
25623
|
-
onMessage?.(raw);
|
|
25665
|
+
onMessage?.(raw, ws);
|
|
25624
25666
|
}
|
|
25625
25667
|
});
|
|
25626
25668
|
});
|
|
@@ -26527,6 +26569,61 @@ function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
|
|
|
26527
26569
|
});
|
|
26528
26570
|
}
|
|
26529
26571
|
|
|
26572
|
+
// src/connection/reconnect/duplicate-bridge-connection-detect.ts
|
|
26573
|
+
var BRIDGE_SUPERSEDED_CLOSE_REASON_SNIPPET = "superseded";
|
|
26574
|
+
var DUPLICATE_BRIDGE_SHORT_SESSION_MS = 25e3;
|
|
26575
|
+
var DUPLICATE_BRIDGE_FLAP_MIN_COUNT = 2;
|
|
26576
|
+
var DUPLICATE_BRIDGE_FLAP_WINDOW_MS = 9e4;
|
|
26577
|
+
var DUPLICATE_BRIDGE_STABLE_SESSION_MS = BRIDGE_APP_HEARTBEAT_INTERVAL_MS * BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT + 5e3;
|
|
26578
|
+
var DUPLICATE_BRIDGE_WARNING_MESSAGE = "[Bridge service] It looks like two bridges are using the same token and disconnecting each other. Stop any duplicate @buildautomaton/cli process for this workspace/token (only one bridge should run).";
|
|
26579
|
+
function createEmptyDuplicateBridgeFlapTracker() {
|
|
26580
|
+
return {
|
|
26581
|
+
sessionOpenedAtMs: null,
|
|
26582
|
+
shortDisconnectAtMs: []
|
|
26583
|
+
};
|
|
26584
|
+
}
|
|
26585
|
+
function isSupersededByNewBridgeClose(code, reason) {
|
|
26586
|
+
if (code !== 1e3) return false;
|
|
26587
|
+
return reason.toLowerCase().includes(BRIDGE_SUPERSEDED_CLOSE_REASON_SNIPPET);
|
|
26588
|
+
}
|
|
26589
|
+
function pruneShortDisconnects(tracker, nowMs) {
|
|
26590
|
+
const cutoff = nowMs - DUPLICATE_BRIDGE_FLAP_WINDOW_MS;
|
|
26591
|
+
tracker.shortDisconnectAtMs = tracker.shortDisconnectAtMs.filter((t) => t >= cutoff);
|
|
26592
|
+
}
|
|
26593
|
+
function recordBridgeSessionOpened(tracker, nowMs) {
|
|
26594
|
+
tracker.sessionOpenedAtMs = nowMs;
|
|
26595
|
+
}
|
|
26596
|
+
function evaluateDuplicateBridgeDisconnect(tracker, nowMs, code, reason) {
|
|
26597
|
+
if (isSupersededByNewBridgeClose(code, reason)) {
|
|
26598
|
+
return { kind: "warn", trigger: "superseded_close" };
|
|
26599
|
+
}
|
|
26600
|
+
const openedAt = tracker.sessionOpenedAtMs;
|
|
26601
|
+
tracker.sessionOpenedAtMs = null;
|
|
26602
|
+
if (openedAt == null) {
|
|
26603
|
+
return { kind: "none" };
|
|
26604
|
+
}
|
|
26605
|
+
const sessionMs = nowMs - openedAt;
|
|
26606
|
+
if (sessionMs >= DUPLICATE_BRIDGE_STABLE_SESSION_MS) {
|
|
26607
|
+
tracker.shortDisconnectAtMs = [];
|
|
26608
|
+
return { kind: "none" };
|
|
26609
|
+
}
|
|
26610
|
+
if (sessionMs < DUPLICATE_BRIDGE_SHORT_SESSION_MS) {
|
|
26611
|
+
pruneShortDisconnects(tracker, nowMs);
|
|
26612
|
+
tracker.shortDisconnectAtMs.push(nowMs);
|
|
26613
|
+
if (tracker.shortDisconnectAtMs.length >= DUPLICATE_BRIDGE_FLAP_MIN_COUNT) {
|
|
26614
|
+
return { kind: "warn", trigger: "rapid_short_disconnects" };
|
|
26615
|
+
}
|
|
26616
|
+
}
|
|
26617
|
+
return { kind: "none" };
|
|
26618
|
+
}
|
|
26619
|
+
function logDuplicateBridgeWarning(log2, result) {
|
|
26620
|
+
if (result.kind !== "warn") return;
|
|
26621
|
+
try {
|
|
26622
|
+
log2(DUPLICATE_BRIDGE_WARNING_MESSAGE);
|
|
26623
|
+
} catch {
|
|
26624
|
+
}
|
|
26625
|
+
}
|
|
26626
|
+
|
|
26530
26627
|
// src/connection/reconnect/firehose-reconnect.ts
|
|
26531
26628
|
function beginFirehoseDeferredDisconnect(ctx, code, reason, log2) {
|
|
26532
26629
|
beginTieredSilentReconnectDisconnect({
|
|
@@ -40612,8 +40709,10 @@ function normalizeInboundBridgeWebSocketJson(data) {
|
|
|
40612
40709
|
}
|
|
40613
40710
|
return data;
|
|
40614
40711
|
}
|
|
40615
|
-
function handleBridgeMessage(data, deps) {
|
|
40616
|
-
|
|
40712
|
+
function handleBridgeMessage(data, deps, sourceWs) {
|
|
40713
|
+
const active = deps.getWs();
|
|
40714
|
+
if (!active) return;
|
|
40715
|
+
if (sourceWs != null && sourceWs !== active) return;
|
|
40617
40716
|
const msg = parseApiToBridgeMessage(normalizeInboundBridgeWebSocketJson(data), deps.log);
|
|
40618
40717
|
if (!msg) return;
|
|
40619
40718
|
dispatchBridgeMessage(msg, deps);
|
|
@@ -40665,6 +40764,7 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
40665
40764
|
} = params;
|
|
40666
40765
|
let authRefreshInFlight = false;
|
|
40667
40766
|
function handleOpen() {
|
|
40767
|
+
recordBridgeSessionOpened(state.duplicateBridgeFlap, Date.now());
|
|
40668
40768
|
const logOpenAsPostRefreshReconnect = state.logBridgeOpenAsReconnect;
|
|
40669
40769
|
clearMainBridgeReconnectQuietOnOpen(state, logFn);
|
|
40670
40770
|
state.reconnectAttempt = 0;
|
|
@@ -40699,6 +40799,15 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
40699
40799
|
state.currentWs = null;
|
|
40700
40800
|
if (was) was.removeAllListeners();
|
|
40701
40801
|
const willReconnect = !state.closedByUser;
|
|
40802
|
+
if (willReconnect) {
|
|
40803
|
+
const duplicateResult = evaluateDuplicateBridgeDisconnect(
|
|
40804
|
+
state.duplicateBridgeFlap,
|
|
40805
|
+
Date.now(),
|
|
40806
|
+
code,
|
|
40807
|
+
reason
|
|
40808
|
+
);
|
|
40809
|
+
logDuplicateBridgeWarning(logFn, duplicateResult);
|
|
40810
|
+
}
|
|
40702
40811
|
beginMainBridgeDeferredDisconnect(state, code, reason, logFn, willReconnect);
|
|
40703
40812
|
if (willReconnect) {
|
|
40704
40813
|
state.lastReconnectCloseMeta = { code, reason };
|
|
@@ -40748,6 +40857,10 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
40748
40857
|
state.currentWs = createWsBridge({
|
|
40749
40858
|
url: url2,
|
|
40750
40859
|
clientPingIntervalMs: CLI_WEBSOCKET_CLIENT_PING_MS,
|
|
40860
|
+
isActiveSocket: (socket) => state.currentWs === socket,
|
|
40861
|
+
onCompactHeartbeatAck: (seq) => {
|
|
40862
|
+
messageDeps.onBridgeHeartbeatAck?.(seq);
|
|
40863
|
+
},
|
|
40751
40864
|
onAuthInvalid: () => {
|
|
40752
40865
|
if (authRefreshInFlight) return;
|
|
40753
40866
|
void (async () => {
|
|
@@ -40801,9 +40914,9 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
40801
40914
|
} catch {
|
|
40802
40915
|
}
|
|
40803
40916
|
},
|
|
40804
|
-
onMessage: (data) => {
|
|
40917
|
+
onMessage: (data, sourceWs) => {
|
|
40805
40918
|
try {
|
|
40806
|
-
handleBridgeMessage(data, messageDeps);
|
|
40919
|
+
handleBridgeMessage(data, messageDeps, sourceWs);
|
|
40807
40920
|
} catch {
|
|
40808
40921
|
}
|
|
40809
40922
|
}
|
|
@@ -41168,6 +41281,7 @@ async function createBridgeConnection(options) {
|
|
|
41168
41281
|
currentWs: null,
|
|
41169
41282
|
mainQuiet: createEmptyReconnectQuietSlot(),
|
|
41170
41283
|
mainOutage: createEmptyReconnectOutageTracker(),
|
|
41284
|
+
duplicateBridgeFlap: createEmptyDuplicateBridgeFlapTracker(),
|
|
41171
41285
|
firehoseHandle: null,
|
|
41172
41286
|
lastFirehoseParams: null,
|
|
41173
41287
|
firehoseReconnectTimeout: null,
|