supbuddy 3.2.5 → 3.2.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.
package/dist/bin.js CHANGED
@@ -19807,7 +19807,7 @@ function cliBuildKind(env3 = process.env) {
19807
19807
  return raw === "host" || raw === "npm" ? raw : "dev";
19808
19808
  }
19809
19809
  function cliVersion(env3 = process.env) {
19810
- return "3.2.5".trim() || "0.0.0-dev";
19810
+ return "3.2.6".trim() || "0.0.0-dev";
19811
19811
  }
19812
19812
  function isDevBuild(env3) {
19813
19813
  return cliBuildKind(env3) === "dev";
@@ -57186,6 +57186,43 @@ exec /usr/bin/${name2} ${opts} "$@"
57186
57186
  });
57187
57187
  return { ok: true, session };
57188
57188
  }
57189
+ async function inspectLocalWork(git, localPath) {
57190
+ const out = { uncommitted: 0, unpushed: 0, noUpstream: false, notAGitRepo: false };
57191
+ const inside = await git(["rev-parse", "--is-inside-work-tree"], localPath).catch(() => ({ exitCode: 1, stdout: "" }));
57192
+ if (inside.exitCode !== 0 || inside.stdout.trim() !== "true") {
57193
+ out.notAGitRepo = true;
57194
+ return out;
57195
+ }
57196
+ const status = await git(["status", "--porcelain"], localPath).catch(() => ({ exitCode: 1, stdout: "" }));
57197
+ if (status.exitCode === 0) {
57198
+ out.uncommitted = status.stdout.split("\n").filter((l) => l.trim()).length;
57199
+ }
57200
+ const counts = await git(["rev-list", "--count", "@{upstream}..HEAD"], localPath).catch(() => ({ exitCode: 1, stdout: "" }));
57201
+ if (counts.exitCode === 0) {
57202
+ out.unpushed = Number.parseInt(counts.stdout.trim(), 10) || 0;
57203
+ } else {
57204
+ out.noUpstream = true;
57205
+ }
57206
+ return out;
57207
+ }
57208
+ function hasWorkAtRisk(risk) {
57209
+ return risk.notAGitRepo || risk.noUpstream || risk.uncommitted > 0 || risk.unpushed > 0;
57210
+ }
57211
+ function describeWorkAtRisk(risk, localPath) {
57212
+ if (risk.notAGitRepo) {
57213
+ return `${localPath} is not a git repository, so a one-way sync from the cloud would delete anything here that the box does not have, with no way to recover it. Choose "this machine's copy" as the authority, or start sync on a directory under version control.`;
57214
+ }
57215
+ const parts = [];
57216
+ if (risk.uncommitted > 0) {
57217
+ parts.push(`${risk.uncommitted} uncommitted change${risk.uncommitted === 1 ? "" : "s"} (commit or stash them)`);
57218
+ }
57219
+ if (risk.noUpstream) {
57220
+ parts.push("a branch that has never been pushed, so none of its commits are on the box (push it first)");
57221
+ } else if (risk.unpushed > 0) {
57222
+ parts.push(`${risk.unpushed} unpushed commit${risk.unpushed === 1 ? "" : "s"} (push them first)`);
57223
+ }
57224
+ return `Refusing to overwrite this machine: ${localPath} has ${parts.join(", and ")}. The box clones the repository from its REMOTE, so it has never seen that work, and a first sync with the cloud as authority would delete it. Choose "this machine's copy" instead, or resolve the above and try again.`;
57225
+ }
57189
57226
  const execFileAsync$1 = require$$0$3.promisify(node_child_process.execFile);
57190
57227
  const sessions = /* @__PURE__ */ new Map();
57191
57228
  const shimDirFor = (dataDir) => path$1.join(tailnetPaths(dataDir).dir, "shim");
@@ -57237,14 +57274,30 @@ function runtimeDeps(deps) {
57237
57274
  })
57238
57275
  };
57239
57276
  }
57277
+ const defaultGit = async (gitArgs, cwd) => {
57278
+ try {
57279
+ const { stdout } = await execFileAsync$1("git", gitArgs, { cwd, timeout: 15e3 });
57280
+ return { exitCode: 0, stdout };
57281
+ } catch (e) {
57282
+ const err = e;
57283
+ return { exitCode: typeof err.code === "number" ? err.code : 1, stdout: String(err.stdout ?? "") };
57284
+ }
57285
+ };
57240
57286
  async function startProjectSync(deps, args) {
57241
57287
  const existing = sessions.get(args.projectId);
57242
57288
  if (existing) return { ok: true, session: existing };
57289
+ if (args.authority === "cloud" && !args.force) {
57290
+ const risk = await inspectLocalWork(deps.git ?? defaultGit, args.localPath);
57291
+ if (hasWorkAtRisk(risk)) {
57292
+ return { ok: false, unavailable: describeWorkAtRisk(risk, args.localPath) };
57293
+ }
57294
+ }
57243
57295
  const res = await startSync(runtimeDeps(deps), args);
57244
57296
  if (!res.ok) return res;
57245
57297
  sessions.set(args.projectId, res.session);
57246
57298
  return res;
57247
57299
  }
57300
+ const PAUSED_WHEN_STOPPED = /* @__PURE__ */ new Set(["connecting", "idle", "unknown"]);
57248
57301
  async function projectSyncStatus(deps, projectId, opts = {}) {
57249
57302
  const state = sessions.get(projectId);
57250
57303
  if (!state) return null;
@@ -57258,7 +57311,7 @@ async function projectSyncStatus(deps, projectId, opts = {}) {
57258
57311
  next = { ...next, promoteError: e instanceof Error ? e.message : String(e) };
57259
57312
  }
57260
57313
  }
57261
- if (opts.stackStopped && (next.phase === "connecting" || next.phase === "idle")) {
57314
+ if (opts.stackStopped && PAUSED_WHEN_STOPPED.has(next.phase)) {
57262
57315
  next = { ...next, phase: "paused" };
57263
57316
  }
57264
57317
  sessions.set(projectId, next);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supbuddy",
3
- "version": "3.2.5",
3
+ "version": "3.2.6",
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",