supbuddy 3.2.4 → 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 +1 -1
- package/dist/daemon/worker.cjs +60 -3
- package/package.json +1 -1
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.
|
|
19810
|
+
return "3.2.6".trim() || "0.0.0-dev";
|
|
19811
19811
|
}
|
|
19812
19812
|
function isDevBuild(env3) {
|
|
19813
19813
|
return cliBuildKind(env3) === "dev";
|
package/dist/daemon/worker.cjs
CHANGED
|
@@ -56955,15 +56955,19 @@ function sshProxyCommand(socksPort = TAILNET_SOCKS_PORT) {
|
|
|
56955
56955
|
return `nc -X 5 -x localhost:${socksPort} %h %p`;
|
|
56956
56956
|
}
|
|
56957
56957
|
function sshOptionsFor(args) {
|
|
56958
|
+
const q = (p) => {
|
|
56959
|
+
if (p.includes('"')) throw new Error(`refusing to build an ssh option for a path containing a double quote: ${p}`);
|
|
56960
|
+
return `"${p}"`;
|
|
56961
|
+
};
|
|
56958
56962
|
return [
|
|
56959
56963
|
"-o",
|
|
56960
56964
|
`ProxyCommand=${sshProxyCommand(args.socksPort)}`,
|
|
56961
56965
|
"-o",
|
|
56962
|
-
`UserKnownHostsFile=${args.knownHostsFile}`,
|
|
56966
|
+
`UserKnownHostsFile=${q(args.knownHostsFile)}`,
|
|
56963
56967
|
"-o",
|
|
56964
56968
|
"StrictHostKeyChecking=yes",
|
|
56965
56969
|
"-o",
|
|
56966
|
-
`IdentityFile=${args.identityFile}`,
|
|
56970
|
+
`IdentityFile=${q(args.identityFile)}`,
|
|
56967
56971
|
"-o",
|
|
56968
56972
|
"IdentitiesOnly=yes",
|
|
56969
56973
|
"-o",
|
|
@@ -57182,6 +57186,43 @@ exec /usr/bin/${name2} ${opts} "$@"
|
|
|
57182
57186
|
});
|
|
57183
57187
|
return { ok: true, session };
|
|
57184
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
|
+
}
|
|
57185
57226
|
const execFileAsync$1 = require$$0$3.promisify(node_child_process.execFile);
|
|
57186
57227
|
const sessions = /* @__PURE__ */ new Map();
|
|
57187
57228
|
const shimDirFor = (dataDir) => path$1.join(tailnetPaths(dataDir).dir, "shim");
|
|
@@ -57233,14 +57274,30 @@ function runtimeDeps(deps) {
|
|
|
57233
57274
|
})
|
|
57234
57275
|
};
|
|
57235
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
|
+
};
|
|
57236
57286
|
async function startProjectSync(deps, args) {
|
|
57237
57287
|
const existing = sessions.get(args.projectId);
|
|
57238
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
|
+
}
|
|
57239
57295
|
const res = await startSync(runtimeDeps(deps), args);
|
|
57240
57296
|
if (!res.ok) return res;
|
|
57241
57297
|
sessions.set(args.projectId, res.session);
|
|
57242
57298
|
return res;
|
|
57243
57299
|
}
|
|
57300
|
+
const PAUSED_WHEN_STOPPED = /* @__PURE__ */ new Set(["connecting", "idle", "unknown"]);
|
|
57244
57301
|
async function projectSyncStatus(deps, projectId, opts = {}) {
|
|
57245
57302
|
const state = sessions.get(projectId);
|
|
57246
57303
|
if (!state) return null;
|
|
@@ -57254,7 +57311,7 @@ async function projectSyncStatus(deps, projectId, opts = {}) {
|
|
|
57254
57311
|
next = { ...next, promoteError: e instanceof Error ? e.message : String(e) };
|
|
57255
57312
|
}
|
|
57256
57313
|
}
|
|
57257
|
-
if (opts.stackStopped && (next.phase
|
|
57314
|
+
if (opts.stackStopped && PAUSED_WHEN_STOPPED.has(next.phase)) {
|
|
57258
57315
|
next = { ...next, phase: "paused" };
|
|
57259
57316
|
}
|
|
57260
57317
|
sessions.set(projectId, next);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supbuddy",
|
|
3
|
-
"version": "3.2.
|
|
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",
|