supbuddy 3.1.17 → 3.1.22

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/bin.js CHANGED
@@ -19207,6 +19207,12 @@ function startRelayServer(child, opts) {
19207
19207
  let hostBridgeOwner = null;
19208
19208
  let seq2 = 0;
19209
19209
  const pending = /* @__PURE__ */ new Map();
19210
+ const sendOwnerReady = () => {
19211
+ try {
19212
+ child.send?.({ type: "host-bridge:owner-ready" });
19213
+ } catch {
19214
+ }
19215
+ };
19210
19216
  const failAllPending = (reason) => {
19211
19217
  for (const [, p] of pending) {
19212
19218
  clearTimeout(p.timer);
@@ -19225,17 +19231,11 @@ function startRelayServer(child, opts) {
19225
19231
  }
19226
19232
  });
19227
19233
  socket2.on("host-bridge:claim", () => {
19228
- const hadOwner = hostBridgeOwner !== null;
19229
19234
  if (hostBridgeOwner && hostBridgeOwner.id !== socket2.id) {
19230
19235
  hostBridgeOwner.emit("host-bridge:revoked");
19231
19236
  }
19232
19237
  hostBridgeOwner = socket2;
19233
- if (!hadOwner) {
19234
- try {
19235
- child.send?.({ type: "host-bridge:owner-ready" });
19236
- } catch {
19237
- }
19238
- }
19238
+ sendOwnerReady();
19239
19239
  });
19240
19240
  socket2.on("host-bridge:reply", ({ correlationId, reply }) => {
19241
19241
  const p = pending.get(correlationId);
@@ -19265,6 +19265,10 @@ function startRelayServer(child, opts) {
19265
19265
  });
19266
19266
  };
19267
19267
  const onChildMessage = (m) => {
19268
+ if (m?.type === "host-bridge:owner?") {
19269
+ if (hostBridgeOwner) sendOwnerReady();
19270
+ return;
19271
+ }
19268
19272
  if (isHostBridgeMessage(m)) return;
19269
19273
  io2.emit("worker:message", m);
19270
19274
  };
@@ -19803,7 +19807,7 @@ function cliBuildKind(env3 = process.env) {
19803
19807
  return raw === "host" || raw === "npm" ? raw : "dev";
19804
19808
  }
19805
19809
  function cliVersion(env3 = process.env) {
19806
- return "3.1.17".trim() || "0.0.0-dev";
19810
+ return "3.1.22".trim() || "0.0.0-dev";
19807
19811
  }
19808
19812
  function isDevBuild(env3) {
19809
19813
  return cliBuildKind(env3) === "dev";
@@ -39154,7 +39158,10 @@ async function probe443(timeoutMs = 1e3) {
39154
39158
  }
39155
39159
  async function probePort(port, timeoutMs = 1e3) {
39156
39160
  const results = await Promise.all(["127.0.0.1", "::1"].map((host) => probeHost(host, port, timeoutMs)));
39157
- return results.some(Boolean);
39161
+ if (results.some(Boolean)) return true;
39162
+ await new Promise((r) => setTimeout(r, 150));
39163
+ const confirm = await Promise.all(["127.0.0.1", "::1"].map((host) => probeHost(host, port, timeoutMs)));
39164
+ return confirm.some(Boolean);
39158
39165
  }
39159
39166
  async function probeHost(host, port, timeoutMs) {
39160
39167
  const net2 = await import("net");
@@ -30248,7 +30248,10 @@ async function probe443(timeoutMs = 1e3) {
30248
30248
  }
30249
30249
  async function probePort(port, timeoutMs = 1e3) {
30250
30250
  const results = await Promise.all(["127.0.0.1", "::1"].map((host) => probeHost(host, port, timeoutMs)));
30251
- return results.some(Boolean);
30251
+ if (results.some(Boolean)) return true;
30252
+ await new Promise((r) => setTimeout(r, 150));
30253
+ const confirm2 = await Promise.all(["127.0.0.1", "::1"].map((host) => probeHost(host, port, timeoutMs)));
30254
+ return confirm2.some(Boolean);
30252
30255
  }
30253
30256
  async function probeHost(host, port, timeoutMs) {
30254
30257
  const net2 = await __vitePreload(() => import("net"), false ? __VITE_PRELOAD__ : void 0);
@@ -45400,6 +45403,8 @@ const executors = {
45400
45403
  resolversReapplied: false,
45401
45404
  resolverError: `${resolvers.error ?? resolvers.outcome}. Missing: [${(resolvers.missing ?? []).join(", ")}]. Those domains will not resolve until the files exist.`
45402
45405
  } : {};
45406
+ const anchor = await globalThis.reapplyPfAnchorIfDrifted?.("restart_proxy");
45407
+ const anchorReport = anchor && anchor.ok === false ? { pfAnchorUpdated: false, pfAnchorError: `${anchor.error ?? anchor.outcome}` } : anchor?.outcome === "applied" ? { pfAnchorUpdated: true } : {};
45403
45408
  const { getPortForwardingStatus: getPortForwardingStatus2, probe443: probe4432, waitForPort: waitForPort2 } = await __vitePreload(async () => {
45404
45409
  const { getPortForwardingStatus: getPortForwardingStatus3, probe443: probe4433, waitForPort: waitForPort3 } = await Promise.resolve().then(() => portForwarding);
45405
45410
  return { getPortForwardingStatus: getPortForwardingStatus3, probe443: probe4433, waitForPort: waitForPort3 };
@@ -45408,14 +45413,14 @@ const executors = {
45408
45413
  const caddyUp = await waitForPort2(httpsPort).catch(() => false);
45409
45414
  const pf = caddyUp ? await getPortForwardingStatus2(httpPort, httpsPort, { probe: probe4432 }).catch(() => null) : null;
45410
45415
  if (!caddyUp) {
45411
- return { ok: true, ...resolverReport, portForwardingChecked: false, note: `Caddy is not accepting on ${httpsPort}, so port forwarding was not verified.` };
45416
+ return { ok: true, ...resolverReport, ...anchorReport, portForwardingChecked: false, note: `Caddy is not accepting on ${httpsPort}, so port forwarding was not verified.` };
45412
45417
  }
45413
45418
  if (pf?.enabled && pf.enforced === false) {
45414
45419
  const repair = globalThis.repairPortForwarding;
45415
45420
  const r = repair ? await repair({ prompt: "Supbuddy needs to re-apply the port-forwarding rules." }).catch((e) => ({ ok: false, error: e instanceof Error ? e.message : String(e) })) : { ok: false, error: "pf repair is unavailable in this process" };
45416
- return { ok: true, ...resolverReport, portForwardingRepaired: r.ok, ...r.ok ? {} : { portForwardingError: r.error } };
45421
+ return { ok: true, ...resolverReport, ...anchorReport, portForwardingRepaired: r.ok, ...r.ok ? {} : { portForwardingError: r.error } };
45417
45422
  }
45418
- return { ok: true, ...resolverReport };
45423
+ return { ok: true, ...resolverReport, ...anchorReport };
45419
45424
  } finally {
45420
45425
  globalThis.scheduleDoctorAutoScan?.("proxy-restart", PROXY_RESCAN_SETTLE_MS);
45421
45426
  }
@@ -50321,6 +50326,10 @@ function buildPfAnchor(input) {
50321
50326
  }
50322
50327
  return lines.join("\n") + "\n";
50323
50328
  }
50329
+ function pfAnchorDrifted(desired, actual) {
50330
+ if (actual === null) return true;
50331
+ return actual !== desired;
50332
+ }
50324
50333
  const execAsync$5 = util$1.promisify(child_process.exec);
50325
50334
  const execFileAsync$2 = util$1.promisify(child_process.execFile);
50326
50335
  const MIGRATE_DIR = path.join(os$1.tmpdir(), "supbuddy-migrate");
@@ -51351,6 +51360,7 @@ async function applyProjectActivation(projectId, opts) {
51351
51360
  }
51352
51361
  globalThis.applyProjectActivation = applyProjectActivation;
51353
51362
  globalThis.reapplyDnsResolversIfDrifted = reapplyDnsResolversIfDrifted;
51363
+ globalThis.reapplyPfAnchorIfDrifted = reapplyPfAnchorIfDrifted;
51354
51364
  globalThis.repairPortForwarding = repairPortForwarding;
51355
51365
  async function testPortForwarding(_httpsPort) {
51356
51366
  return probe443();
@@ -51574,9 +51584,15 @@ async function requestPrivilegedBatch(command, prompt, tmpFiles = []) {
51574
51584
  process.on("message", handler);
51575
51585
  });
51576
51586
  }
51587
+ let privilegedRepairAttempted = false;
51577
51588
  let guiProxyAutoStartDone = false;
51578
51589
  let lastPrivilegedError = null;
51590
+ let privilegedWorkPending = null;
51579
51591
  const TRANSIENT_PRIVILEGED_FAILURE = /TTY|ASKPASS|detached/i;
51592
+ function shouldFinishPrivilegedSetup(proxyState, privilegedError, workPending) {
51593
+ if (shouldRetryPrivilegedSetup(proxyState, privilegedError)) return true;
51594
+ return TRANSIENT_PRIVILEGED_FAILURE.test(workPending || "");
51595
+ }
51580
51596
  function shouldRetryPrivilegedSetup(proxyState, privilegedError) {
51581
51597
  if (!proxyState.needsPrivilegedRepair) return false;
51582
51598
  return TRANSIENT_PRIVILEGED_FAILURE.test(privilegedError || "");
@@ -51587,14 +51603,24 @@ function isPfRulesetRejection(err, stderr) {
51587
51603
  return stderr.trim().length > 0;
51588
51604
  }
51589
51605
  async function maybeAutoStartProxyForGui() {
51590
- if (guiProxyAutoStartDone) return;
51591
51606
  const store2 = useStore.getState();
51592
- if (!store2.settings.autoStart) return;
51593
51607
  const status = store2.proxyState.status;
51608
+ if (privilegedWorkPending && !privilegedRepairAttempted) {
51609
+ console.log(
51610
+ `[Proxy] owner-ready: status=${status} repairAttempted=${privilegedRepairAttempted} needsRepair=${!!store2.proxyState.needsPrivilegedRepair} pending=${JSON.stringify(privilegedWorkPending)}`
51611
+ );
51612
+ }
51613
+ if (status === "running" && !privilegedRepairAttempted && shouldFinishPrivilegedSetup(store2.proxyState, lastPrivilegedError, privilegedWorkPending)) {
51614
+ privilegedRepairAttempted = true;
51615
+ await retryPrivilegedSetupOnce("proxy is running but its privileged setup never ran (no GUI owner at boot)");
51616
+ return;
51617
+ }
51618
+ if (guiProxyAutoStartDone) return;
51594
51619
  if (status === "running" || status === "starting") {
51595
51620
  guiProxyAutoStartDone = true;
51596
51621
  return;
51597
51622
  }
51623
+ if (!store2.settings.autoStart) return;
51598
51624
  guiProxyAutoStartDone = true;
51599
51625
  console.log("[Proxy] GUI host-bridge owner ready — auto-starting proxy (autoStart on)");
51600
51626
  try {
@@ -51604,24 +51630,25 @@ async function maybeAutoStartProxyForGui() {
51604
51630
  }
51605
51631
  const afterFirst = useStore.getState().proxyState;
51606
51632
  if (shouldRetryPrivilegedSetup(afterFirst, lastPrivilegedError)) {
51607
- console.warn(
51608
- "[Proxy] auto-start hit the host-bridge owner race (TTY/ASKPASS) — retrying privileged setup once in 2s"
51633
+ await retryPrivilegedSetupOnce("auto-start hit the host-bridge owner race (TTY/ASKPASS)");
51634
+ }
51635
+ }
51636
+ async function retryPrivilegedSetupOnce(reason) {
51637
+ console.warn(`[Proxy] ${reason} — retrying privileged setup once in 2s`);
51638
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
51639
+ try {
51640
+ await handleStartProxy();
51641
+ const after = useStore.getState().proxyState;
51642
+ console.log(
51643
+ after.needsPrivilegedRepair ? "[Proxy] privileged retry still degraded — setup failed again (manual Retry remains available)" : "[Proxy] privileged retry recovered — setup applied"
51609
51644
  );
51610
- await new Promise((resolve) => setTimeout(resolve, 2e3));
51611
- try {
51612
- await handleStartProxy();
51613
- const afterRetry = useStore.getState().proxyState;
51614
- console.log(
51615
- afterRetry.needsPrivilegedRepair ? "[Proxy] auto-start retry still degraded — privileged setup failed again (manual Retry remains available)" : "[Proxy] auto-start retry recovered — privileged setup applied"
51616
- );
51617
- } catch (e) {
51618
- console.error("[Proxy] auto-start retry failed:", e);
51619
- }
51645
+ } catch (e) {
51646
+ console.error("[Proxy] privileged retry failed:", e);
51620
51647
  }
51621
51648
  }
51622
51649
  async function handleStartProxy() {
51623
51650
  const alreadyRunning = isCaddyRunning();
51624
- const repairPending = !!useStore.getState().proxyState.needsPrivilegedRepair;
51651
+ const repairPending = !!useStore.getState().proxyState.needsPrivilegedRepair || !!privilegedWorkPending;
51625
51652
  if (alreadyRunning && !repairPending) {
51626
51653
  const { probeProxyReachability: probeProxyReachability2 } = await __vitePreload(async () => {
51627
51654
  const { probeProxyReachability: probeProxyReachability3 } = await Promise.resolve().then(() => dnsPlatform);
@@ -51751,11 +51778,13 @@ async function handleStartProxy() {
51751
51778
  });
51752
51779
  if (!sudoResult.success) {
51753
51780
  privilegedFailure = sudoResult.error || "unknown error";
51781
+ privilegedWorkPending = privilegedFailure;
51754
51782
  console.error(
51755
51783
  `[Proxy] privileged setup failed (continuing degraded — Caddy still starts): ${privilegedFailure}`
51756
51784
  );
51757
51785
  } else {
51758
51786
  console.log("[Proxy] sudo setup result: success");
51787
+ privilegedWorkPending = null;
51759
51788
  const resolverAudit = await auditResolverState().catch(() => null);
51760
51789
  if (resolverAudit && resolverAudit.in_sync === false && resolverAudit.missing.length > 0) {
51761
51790
  privilegedFailure = `DNS resolver files missing after privileged setup: ${resolverAudit.missing.join(", ")}`;
@@ -51764,6 +51793,7 @@ async function handleStartProxy() {
51764
51793
  }
51765
51794
  } catch (privErr) {
51766
51795
  privilegedFailure = privErr?.message || String(privErr);
51796
+ privilegedWorkPending = privilegedFailure;
51767
51797
  console.error(
51768
51798
  "[Proxy] privileged setup threw (continuing degraded — Caddy still starts):",
51769
51799
  privilegedFailure
@@ -51921,6 +51951,75 @@ async function repairPortForwarding(opts) {
51921
51951
  await clearProxyFaultIfHealthy("a confirmed pf repair");
51922
51952
  return { ok: true, enforced: true };
51923
51953
  }
51954
+ async function reapplyPfAnchorIfDrifted(reason) {
51955
+ const store2 = useStore.getState();
51956
+ const { httpPort, httpsPort, lanSharing } = store2.settings;
51957
+ const desired = buildPfAnchor({
51958
+ httpPort,
51959
+ httpsPort,
51960
+ lanSharing,
51961
+ lanInterfaces: lanSharing ? getLanInterfaces() : []
51962
+ });
51963
+ let actual = null;
51964
+ try {
51965
+ actual = fsSync.readFileSync("/etc/pf.anchors/virtual.localhost", "utf-8");
51966
+ } catch {
51967
+ actual = null;
51968
+ }
51969
+ let pfConf = "";
51970
+ try {
51971
+ pfConf = fsSync.readFileSync("/etc/pf.conf", "utf-8");
51972
+ } catch {
51973
+ pfConf = "";
51974
+ }
51975
+ if (!pfConf.includes("virtual.localhost")) return { ok: true, outcome: "not-configured" };
51976
+ if (!pfAnchorDrifted(desired, actual)) return { ok: true, outcome: "no-drift" };
51977
+ console.log(`[Proxy] pf anchor is out of date (${reason}) — rewriting`);
51978
+ const tmpSuffix = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
51979
+ const tmpAnchor = path.join(os$1.tmpdir(), `supbuddy-pf-anchor-${tmpSuffix}`);
51980
+ const tmpValidation = path.join(os$1.tmpdir(), `supbuddy-pf-validate-${tmpSuffix}`);
51981
+ fsSync.writeFileSync(tmpAnchor, desired);
51982
+ try {
51983
+ const { content: correctedPfConf } = buildCorrectedPfConf(pfConf);
51984
+ fsSync.writeFileSync(tmpValidation, buildValidationPfConf(correctedPfConf, tmpAnchor));
51985
+ try {
51986
+ child_process.execSync(`pfctl -nf "${tmpValidation}"`, { stdio: "pipe" });
51987
+ } catch (validationErr) {
51988
+ const stderr = (validationErr?.stderr?.toString() ?? "").trim();
51989
+ if (isPfRulesetRejection(validationErr, stderr)) {
51990
+ console.error(`[Proxy] refusing to install pf anchor — ruleset rejected: ${stderr}`);
51991
+ return { ok: false, outcome: "invalid", error: stderr || "pf rejected the generated ruleset" };
51992
+ }
51993
+ }
51994
+ if (!process.send) return { ok: false, outcome: "no-bridge", error: "no host bridge in this process" };
51995
+ const res = await defaultElevate$1({
51996
+ command: `cp ${JSON.stringify(tmpAnchor)} /etc/pf.anchors/virtual.localhost && ${buildPfLoadCommand({ restoreBackupOnFailure: false })}`,
51997
+ prompt: "Supbuddy needs to update its port-forwarding rules."
51998
+ }).catch((e) => ({ success: false, error: e instanceof Error ? e.message : String(e) }));
51999
+ if (!res.success) {
52000
+ console.warn(`[Proxy] pf anchor rewrite failed: ${res.error ?? "unknown error"}`);
52001
+ return { ok: false, outcome: "failed", error: res.error };
52002
+ }
52003
+ let after = null;
52004
+ try {
52005
+ after = fsSync.readFileSync("/etc/pf.anchors/virtual.localhost", "utf-8");
52006
+ } catch {
52007
+ after = null;
52008
+ }
52009
+ if (pfAnchorDrifted(desired, after)) {
52010
+ return { ok: false, outcome: "failed", error: "the elevated write reported success but the anchor on disk is unchanged" };
52011
+ }
52012
+ console.log("[Proxy] pf anchor rewritten and reloaded");
52013
+ return { ok: true, outcome: "applied" };
52014
+ } finally {
52015
+ for (const f2 of [tmpAnchor, tmpValidation]) {
52016
+ try {
52017
+ fsSync.unlinkSync(f2);
52018
+ } catch {
52019
+ }
52020
+ }
52021
+ }
52022
+ }
51924
52023
  async function clearProxyFaultIfHealthy(reason) {
51925
52024
  const store2 = useStore.getState();
51926
52025
  if (store2.proxyState.status !== "error") return;
@@ -54588,6 +54687,10 @@ async function init() {
54588
54687
  process.exit(0);
54589
54688
  }
54590
54689
  };
54690
+ try {
54691
+ process.send?.({ type: "host-bridge:owner?" });
54692
+ } catch {
54693
+ }
54591
54694
  process.on("SIGINT", () => cleanup());
54592
54695
  process.on("SIGTERM", () => cleanup());
54593
54696
  shutdownHandler = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supbuddy",
3
- "version": "3.1.17",
3
+ "version": "3.1.22",
4
4
  "description": "Run multiple Supabase projects at once on custom local domains with HTTPS. A headless CLI and daemon (proxy, DNS, Supabase/Compose lifecycle, MCP) for macOS and Linux.",
5
5
  "keywords": [
6
6
  "supabase",