supbuddy 3.1.17 → 3.1.18

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
@@ -19803,7 +19803,7 @@ function cliBuildKind(env3 = process.env) {
19803
19803
  return raw === "host" || raw === "npm" ? raw : "dev";
19804
19804
  }
19805
19805
  function cliVersion(env3 = process.env) {
19806
- return "3.1.17".trim() || "0.0.0-dev";
19806
+ return "3.1.18".trim() || "0.0.0-dev";
19807
19807
  }
19808
19808
  function isDevBuild(env3) {
19809
19809
  return cliBuildKind(env3) === "dev";
@@ -45400,6 +45400,8 @@ const executors = {
45400
45400
  resolversReapplied: false,
45401
45401
  resolverError: `${resolvers.error ?? resolvers.outcome}. Missing: [${(resolvers.missing ?? []).join(", ")}]. Those domains will not resolve until the files exist.`
45402
45402
  } : {};
45403
+ const anchor = await globalThis.reapplyPfAnchorIfDrifted?.("restart_proxy");
45404
+ const anchorReport = anchor && anchor.ok === false ? { pfAnchorUpdated: false, pfAnchorError: `${anchor.error ?? anchor.outcome}` } : anchor?.outcome === "applied" ? { pfAnchorUpdated: true } : {};
45403
45405
  const { getPortForwardingStatus: getPortForwardingStatus2, probe443: probe4432, waitForPort: waitForPort2 } = await __vitePreload(async () => {
45404
45406
  const { getPortForwardingStatus: getPortForwardingStatus3, probe443: probe4433, waitForPort: waitForPort3 } = await Promise.resolve().then(() => portForwarding);
45405
45407
  return { getPortForwardingStatus: getPortForwardingStatus3, probe443: probe4433, waitForPort: waitForPort3 };
@@ -45408,14 +45410,14 @@ const executors = {
45408
45410
  const caddyUp = await waitForPort2(httpsPort).catch(() => false);
45409
45411
  const pf = caddyUp ? await getPortForwardingStatus2(httpPort, httpsPort, { probe: probe4432 }).catch(() => null) : null;
45410
45412
  if (!caddyUp) {
45411
- return { ok: true, ...resolverReport, portForwardingChecked: false, note: `Caddy is not accepting on ${httpsPort}, so port forwarding was not verified.` };
45413
+ return { ok: true, ...resolverReport, ...anchorReport, portForwardingChecked: false, note: `Caddy is not accepting on ${httpsPort}, so port forwarding was not verified.` };
45412
45414
  }
45413
45415
  if (pf?.enabled && pf.enforced === false) {
45414
45416
  const repair = globalThis.repairPortForwarding;
45415
45417
  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 } };
45418
+ return { ok: true, ...resolverReport, ...anchorReport, portForwardingRepaired: r.ok, ...r.ok ? {} : { portForwardingError: r.error } };
45417
45419
  }
45418
- return { ok: true, ...resolverReport };
45420
+ return { ok: true, ...resolverReport, ...anchorReport };
45419
45421
  } finally {
45420
45422
  globalThis.scheduleDoctorAutoScan?.("proxy-restart", PROXY_RESCAN_SETTLE_MS);
45421
45423
  }
@@ -50321,6 +50323,10 @@ function buildPfAnchor(input) {
50321
50323
  }
50322
50324
  return lines.join("\n") + "\n";
50323
50325
  }
50326
+ function pfAnchorDrifted(desired, actual) {
50327
+ if (actual === null) return true;
50328
+ return actual !== desired;
50329
+ }
50324
50330
  const execAsync$5 = util$1.promisify(child_process.exec);
50325
50331
  const execFileAsync$2 = util$1.promisify(child_process.execFile);
50326
50332
  const MIGRATE_DIR = path.join(os$1.tmpdir(), "supbuddy-migrate");
@@ -51351,6 +51357,7 @@ async function applyProjectActivation(projectId, opts) {
51351
51357
  }
51352
51358
  globalThis.applyProjectActivation = applyProjectActivation;
51353
51359
  globalThis.reapplyDnsResolversIfDrifted = reapplyDnsResolversIfDrifted;
51360
+ globalThis.reapplyPfAnchorIfDrifted = reapplyPfAnchorIfDrifted;
51354
51361
  globalThis.repairPortForwarding = repairPortForwarding;
51355
51362
  async function testPortForwarding(_httpsPort) {
51356
51363
  return probe443();
@@ -51589,12 +51596,15 @@ function isPfRulesetRejection(err, stderr) {
51589
51596
  async function maybeAutoStartProxyForGui() {
51590
51597
  if (guiProxyAutoStartDone) return;
51591
51598
  const store2 = useStore.getState();
51592
- if (!store2.settings.autoStart) return;
51593
51599
  const status = store2.proxyState.status;
51594
51600
  if (status === "running" || status === "starting") {
51595
51601
  guiProxyAutoStartDone = true;
51602
+ if (status === "running" && shouldRetryPrivilegedSetup(store2.proxyState, lastPrivilegedError)) {
51603
+ await retryPrivilegedSetupOnce("proxy is running but its privileged setup never ran (no GUI owner at boot)");
51604
+ }
51596
51605
  return;
51597
51606
  }
51607
+ if (!store2.settings.autoStart) return;
51598
51608
  guiProxyAutoStartDone = true;
51599
51609
  console.log("[Proxy] GUI host-bridge owner ready — auto-starting proxy (autoStart on)");
51600
51610
  try {
@@ -51604,19 +51614,20 @@ async function maybeAutoStartProxyForGui() {
51604
51614
  }
51605
51615
  const afterFirst = useStore.getState().proxyState;
51606
51616
  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"
51617
+ await retryPrivilegedSetupOnce("auto-start hit the host-bridge owner race (TTY/ASKPASS)");
51618
+ }
51619
+ }
51620
+ async function retryPrivilegedSetupOnce(reason) {
51621
+ console.warn(`[Proxy] ${reason} — retrying privileged setup once in 2s`);
51622
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
51623
+ try {
51624
+ await handleStartProxy();
51625
+ const after = useStore.getState().proxyState;
51626
+ console.log(
51627
+ after.needsPrivilegedRepair ? "[Proxy] privileged retry still degraded — setup failed again (manual Retry remains available)" : "[Proxy] privileged retry recovered — setup applied"
51609
51628
  );
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
- }
51629
+ } catch (e) {
51630
+ console.error("[Proxy] privileged retry failed:", e);
51620
51631
  }
51621
51632
  }
51622
51633
  async function handleStartProxy() {
@@ -51921,6 +51932,75 @@ async function repairPortForwarding(opts) {
51921
51932
  await clearProxyFaultIfHealthy("a confirmed pf repair");
51922
51933
  return { ok: true, enforced: true };
51923
51934
  }
51935
+ async function reapplyPfAnchorIfDrifted(reason) {
51936
+ const store2 = useStore.getState();
51937
+ const { httpPort, httpsPort, lanSharing } = store2.settings;
51938
+ const desired = buildPfAnchor({
51939
+ httpPort,
51940
+ httpsPort,
51941
+ lanSharing,
51942
+ lanInterfaces: lanSharing ? getLanInterfaces() : []
51943
+ });
51944
+ let actual = null;
51945
+ try {
51946
+ actual = fsSync.readFileSync("/etc/pf.anchors/virtual.localhost", "utf-8");
51947
+ } catch {
51948
+ actual = null;
51949
+ }
51950
+ let pfConf = "";
51951
+ try {
51952
+ pfConf = fsSync.readFileSync("/etc/pf.conf", "utf-8");
51953
+ } catch {
51954
+ pfConf = "";
51955
+ }
51956
+ if (!pfConf.includes("virtual.localhost")) return { ok: true, outcome: "not-configured" };
51957
+ if (!pfAnchorDrifted(desired, actual)) return { ok: true, outcome: "no-drift" };
51958
+ console.log(`[Proxy] pf anchor is out of date (${reason}) — rewriting`);
51959
+ const tmpSuffix = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
51960
+ const tmpAnchor = path.join(os$1.tmpdir(), `supbuddy-pf-anchor-${tmpSuffix}`);
51961
+ const tmpValidation = path.join(os$1.tmpdir(), `supbuddy-pf-validate-${tmpSuffix}`);
51962
+ fsSync.writeFileSync(tmpAnchor, desired);
51963
+ try {
51964
+ const { content: correctedPfConf } = buildCorrectedPfConf(pfConf);
51965
+ fsSync.writeFileSync(tmpValidation, buildValidationPfConf(correctedPfConf, tmpAnchor));
51966
+ try {
51967
+ child_process.execSync(`pfctl -nf "${tmpValidation}"`, { stdio: "pipe" });
51968
+ } catch (validationErr) {
51969
+ const stderr = (validationErr?.stderr?.toString() ?? "").trim();
51970
+ if (isPfRulesetRejection(validationErr, stderr)) {
51971
+ console.error(`[Proxy] refusing to install pf anchor — ruleset rejected: ${stderr}`);
51972
+ return { ok: false, outcome: "invalid", error: stderr || "pf rejected the generated ruleset" };
51973
+ }
51974
+ }
51975
+ if (!process.send) return { ok: false, outcome: "no-bridge", error: "no host bridge in this process" };
51976
+ const res = await defaultElevate$1({
51977
+ command: `cp ${JSON.stringify(tmpAnchor)} /etc/pf.anchors/virtual.localhost && ${buildPfLoadCommand({ restoreBackupOnFailure: false })}`,
51978
+ prompt: "Supbuddy needs to update its port-forwarding rules."
51979
+ }).catch((e) => ({ success: false, error: e instanceof Error ? e.message : String(e) }));
51980
+ if (!res.success) {
51981
+ console.warn(`[Proxy] pf anchor rewrite failed: ${res.error ?? "unknown error"}`);
51982
+ return { ok: false, outcome: "failed", error: res.error };
51983
+ }
51984
+ let after = null;
51985
+ try {
51986
+ after = fsSync.readFileSync("/etc/pf.anchors/virtual.localhost", "utf-8");
51987
+ } catch {
51988
+ after = null;
51989
+ }
51990
+ if (pfAnchorDrifted(desired, after)) {
51991
+ return { ok: false, outcome: "failed", error: "the elevated write reported success but the anchor on disk is unchanged" };
51992
+ }
51993
+ console.log("[Proxy] pf anchor rewritten and reloaded");
51994
+ return { ok: true, outcome: "applied" };
51995
+ } finally {
51996
+ for (const f2 of [tmpAnchor, tmpValidation]) {
51997
+ try {
51998
+ fsSync.unlinkSync(f2);
51999
+ } catch {
52000
+ }
52001
+ }
52002
+ }
52003
+ }
51924
52004
  async function clearProxyFaultIfHealthy(reason) {
51925
52005
  const store2 = useStore.getState();
51926
52006
  if (store2.proxyState.status !== "error") return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supbuddy",
3
- "version": "3.1.17",
3
+ "version": "3.1.18",
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",