supbuddy 3.2.5 → 3.2.7
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 +109 -1
- 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.7".trim() || "0.0.0-dev";
|
|
19811
19811
|
}
|
|
19812
19812
|
function isDevBuild(env3) {
|
|
19813
19813
|
return cliBuildKind(env3) === "dev";
|
package/dist/daemon/worker.cjs
CHANGED
|
@@ -44969,6 +44969,18 @@ async function daemonSyncStart(projectId, authority) {
|
|
|
44969
44969
|
const m = await __vitePreload(() => Promise.resolve().then(() => projectSync), false ? __VITE_PRELOAD__ : void 0);
|
|
44970
44970
|
return m.syncStart(syncDeps(), { projectId, authority });
|
|
44971
44971
|
}
|
|
44972
|
+
async function daemonSyncReattach() {
|
|
44973
|
+
const m = await __vitePreload(() => Promise.resolve().then(() => daemonSync), false ? __VITE_PRELOAD__ : void 0);
|
|
44974
|
+
const known = useStore.getState().projects.filter((p) => p.cloud?.stackId && p.path).map((p) => ({
|
|
44975
|
+
projectId: p.id,
|
|
44976
|
+
stackId: p.cloud.stackId,
|
|
44977
|
+
localPath: p.path,
|
|
44978
|
+
// Unknowable after a restart and inert for an adopted session — see reattachSessions.
|
|
44979
|
+
authority: "cloud"
|
|
44980
|
+
}));
|
|
44981
|
+
if (known.length === 0) return [];
|
|
44982
|
+
return m.reattachSessions(syncDeps(), known);
|
|
44983
|
+
}
|
|
44972
44984
|
async function daemonSyncStatus(projectId) {
|
|
44973
44985
|
const m = await __vitePreload(() => Promise.resolve().then(() => projectSync), false ? __VITE_PRELOAD__ : void 0);
|
|
44974
44986
|
let stackStopped = false;
|
|
@@ -52012,6 +52024,9 @@ async function handleStartProxy() {
|
|
|
52012
52024
|
reconcileAllProjectServices().catch((err) => {
|
|
52013
52025
|
console.warn("[Worker] boot rescan failed:", err.message);
|
|
52014
52026
|
});
|
|
52027
|
+
daemonSyncReattach().then((ids) => {
|
|
52028
|
+
if (ids.length) console.log(`[Worker] re-attached ${ids.length} sync session(s)`);
|
|
52029
|
+
}).catch((err) => console.warn("[Worker] sync re-attach failed:", err.message));
|
|
52015
52030
|
if (store2.settings.lanSharing) {
|
|
52016
52031
|
startMdnsResponder();
|
|
52017
52032
|
}
|
|
@@ -57186,6 +57201,43 @@ exec /usr/bin/${name2} ${opts} "$@"
|
|
|
57186
57201
|
});
|
|
57187
57202
|
return { ok: true, session };
|
|
57188
57203
|
}
|
|
57204
|
+
async function inspectLocalWork(git, localPath) {
|
|
57205
|
+
const out = { uncommitted: 0, unpushed: 0, noUpstream: false, notAGitRepo: false };
|
|
57206
|
+
const inside = await git(["rev-parse", "--is-inside-work-tree"], localPath).catch(() => ({ exitCode: 1, stdout: "" }));
|
|
57207
|
+
if (inside.exitCode !== 0 || inside.stdout.trim() !== "true") {
|
|
57208
|
+
out.notAGitRepo = true;
|
|
57209
|
+
return out;
|
|
57210
|
+
}
|
|
57211
|
+
const status = await git(["status", "--porcelain"], localPath).catch(() => ({ exitCode: 1, stdout: "" }));
|
|
57212
|
+
if (status.exitCode === 0) {
|
|
57213
|
+
out.uncommitted = status.stdout.split("\n").filter((l) => l.trim()).length;
|
|
57214
|
+
}
|
|
57215
|
+
const counts = await git(["rev-list", "--count", "@{upstream}..HEAD"], localPath).catch(() => ({ exitCode: 1, stdout: "" }));
|
|
57216
|
+
if (counts.exitCode === 0) {
|
|
57217
|
+
out.unpushed = Number.parseInt(counts.stdout.trim(), 10) || 0;
|
|
57218
|
+
} else {
|
|
57219
|
+
out.noUpstream = true;
|
|
57220
|
+
}
|
|
57221
|
+
return out;
|
|
57222
|
+
}
|
|
57223
|
+
function hasWorkAtRisk(risk) {
|
|
57224
|
+
return risk.notAGitRepo || risk.noUpstream || risk.uncommitted > 0 || risk.unpushed > 0;
|
|
57225
|
+
}
|
|
57226
|
+
function describeWorkAtRisk(risk, localPath) {
|
|
57227
|
+
if (risk.notAGitRepo) {
|
|
57228
|
+
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.`;
|
|
57229
|
+
}
|
|
57230
|
+
const parts = [];
|
|
57231
|
+
if (risk.uncommitted > 0) {
|
|
57232
|
+
parts.push(`${risk.uncommitted} uncommitted change${risk.uncommitted === 1 ? "" : "s"} (commit or stash them)`);
|
|
57233
|
+
}
|
|
57234
|
+
if (risk.noUpstream) {
|
|
57235
|
+
parts.push("a branch that has never been pushed, so none of its commits are on the box (push it first)");
|
|
57236
|
+
} else if (risk.unpushed > 0) {
|
|
57237
|
+
parts.push(`${risk.unpushed} unpushed commit${risk.unpushed === 1 ? "" : "s"} (push them first)`);
|
|
57238
|
+
}
|
|
57239
|
+
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.`;
|
|
57240
|
+
}
|
|
57189
57241
|
const execFileAsync$1 = require$$0$3.promisify(node_child_process.execFile);
|
|
57190
57242
|
const sessions = /* @__PURE__ */ new Map();
|
|
57191
57243
|
const shimDirFor = (dataDir) => path$1.join(tailnetPaths(dataDir).dir, "shim");
|
|
@@ -57237,14 +57289,30 @@ function runtimeDeps(deps) {
|
|
|
57237
57289
|
})
|
|
57238
57290
|
};
|
|
57239
57291
|
}
|
|
57292
|
+
const defaultGit = async (gitArgs, cwd) => {
|
|
57293
|
+
try {
|
|
57294
|
+
const { stdout } = await execFileAsync$1("git", gitArgs, { cwd, timeout: 15e3 });
|
|
57295
|
+
return { exitCode: 0, stdout };
|
|
57296
|
+
} catch (e) {
|
|
57297
|
+
const err = e;
|
|
57298
|
+
return { exitCode: typeof err.code === "number" ? err.code : 1, stdout: String(err.stdout ?? "") };
|
|
57299
|
+
}
|
|
57300
|
+
};
|
|
57240
57301
|
async function startProjectSync(deps, args) {
|
|
57241
57302
|
const existing = sessions.get(args.projectId);
|
|
57242
57303
|
if (existing) return { ok: true, session: existing };
|
|
57304
|
+
if (args.authority === "cloud" && !args.force) {
|
|
57305
|
+
const risk = await inspectLocalWork(deps.git ?? defaultGit, args.localPath);
|
|
57306
|
+
if (hasWorkAtRisk(risk)) {
|
|
57307
|
+
return { ok: false, unavailable: describeWorkAtRisk(risk, args.localPath) };
|
|
57308
|
+
}
|
|
57309
|
+
}
|
|
57243
57310
|
const res = await startSync(runtimeDeps(deps), args);
|
|
57244
57311
|
if (!res.ok) return res;
|
|
57245
57312
|
sessions.set(args.projectId, res.session);
|
|
57246
57313
|
return res;
|
|
57247
57314
|
}
|
|
57315
|
+
const PAUSED_WHEN_STOPPED = /* @__PURE__ */ new Set(["connecting", "idle", "unknown"]);
|
|
57248
57316
|
async function projectSyncStatus(deps, projectId, opts = {}) {
|
|
57249
57317
|
const state = sessions.get(projectId);
|
|
57250
57318
|
if (!state) return null;
|
|
@@ -57258,7 +57326,7 @@ async function projectSyncStatus(deps, projectId, opts = {}) {
|
|
|
57258
57326
|
next = { ...next, promoteError: e instanceof Error ? e.message : String(e) };
|
|
57259
57327
|
}
|
|
57260
57328
|
}
|
|
57261
|
-
if (opts.stackStopped && (next.phase
|
|
57329
|
+
if (opts.stackStopped && PAUSED_WHEN_STOPPED.has(next.phase)) {
|
|
57262
57330
|
next = { ...next, phase: "paused" };
|
|
57263
57331
|
}
|
|
57264
57332
|
sessions.set(projectId, next);
|
|
@@ -57273,6 +57341,46 @@ async function stopProjectSync(deps, projectId) {
|
|
|
57273
57341
|
sessions.delete(projectId);
|
|
57274
57342
|
return { stopped: true };
|
|
57275
57343
|
}
|
|
57344
|
+
async function reattachSessions(deps, known) {
|
|
57345
|
+
const rt = runtimeDeps(deps);
|
|
57346
|
+
const reattached = [];
|
|
57347
|
+
for (const k of known) {
|
|
57348
|
+
if (sessions.has(k.projectId)) continue;
|
|
57349
|
+
const probe2 = await rt.runMutagen(["sync", "list", sessionName(k.stackId)]);
|
|
57350
|
+
if (probe2.exitCode !== 0) continue;
|
|
57351
|
+
sessions.set(k.projectId, {
|
|
57352
|
+
sessionId: sessionName(k.stackId),
|
|
57353
|
+
stackId: k.stackId,
|
|
57354
|
+
localPath: k.localPath,
|
|
57355
|
+
remoteEndpoint: "",
|
|
57356
|
+
// filled by the next observe; not needed to adopt or to stop
|
|
57357
|
+
authority: k.authority,
|
|
57358
|
+
phase: "connecting",
|
|
57359
|
+
conflictCount: 0,
|
|
57360
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
57361
|
+
// Adopted sessions are already past their first pass — Mutagen would not be running them
|
|
57362
|
+
// otherwise — so treating one as unseeded could re-trigger a one-way replica over live work.
|
|
57363
|
+
seeded: true,
|
|
57364
|
+
// And already PROMOTED, for the same reason. Without this the next status poll would "promote"
|
|
57365
|
+
// a session that is already two-way, by TERMINATING and recreating it — and recreate orders
|
|
57366
|
+
// alpha/beta from `authority`, which a restarted daemon cannot know because nothing persists it.
|
|
57367
|
+
promoted: true
|
|
57368
|
+
});
|
|
57369
|
+
reattached.push(k.projectId);
|
|
57370
|
+
}
|
|
57371
|
+
return reattached;
|
|
57372
|
+
}
|
|
57373
|
+
function __resetSessionsForTest() {
|
|
57374
|
+
sessions.clear();
|
|
57375
|
+
}
|
|
57376
|
+
const daemonSync = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
57377
|
+
__proto__: null,
|
|
57378
|
+
__resetSessionsForTest,
|
|
57379
|
+
projectSyncStatus,
|
|
57380
|
+
reattachSessions,
|
|
57381
|
+
startProjectSync,
|
|
57382
|
+
stopProjectSync
|
|
57383
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
57276
57384
|
function syncDataDir() {
|
|
57277
57385
|
const override = nodeProcess.env.SUPBUDDY_STATE_DIR;
|
|
57278
57386
|
if (override) return override;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supbuddy",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.7",
|
|
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",
|