Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// stores no per-user UI preferences, so this plugin supplies the missing
// shared place: plugin KV holds the canonical snapshot, and every connected
// frontend pulls it, pushes its own edits, and gets told when it changes.
import { execFileSync } from "node:child_process";
import { defineRpcContract, type BbPluginApi } from "@bb/plugin-sdk";
import { z } from "zod";
import { parseSyncedKeys, type Snapshot, type StoredValue } from "./lib/sync.ts";
Expand Down Expand Up @@ -44,6 +45,40 @@ export const rpcContract = defineRpcContract({
},
});

/**
* Which commit is this PROCESS running? (MX-139/MX-141)
*
* bb bundles a `path:` plugin FROM SOURCE at reload, so a revision read here — at module
* load, the same moment — is by construction the code now executing. Nothing else can say:
* `bb plugin list` prints `running` and the source path but no revision, `bb plugin source`
* has none to record for a path: source, and dist/ is NOT the loaded artifact (its mtime was
* measured lying by 15 minutes). So a checkout can sit clean on main, every drift check
* green, while the process runs something older.
*
* Synchronous on purpose: the value must be fixed before anything can observe it, and it is
* one git call per load. Failure yields rev: null rather than a guess — a tarball install has
* no git dir, and that must stay distinguishable from a real mismatch so a checker reports
* UNKNOWN rather than OK. `dirty` rides along because a bundle built from an edited tree
* matches NO commit, and comparing revisions alone would call that a match.
*/
const BUILD_STAMP: { rev: string | null; dirty: boolean | null; sourceDir: string; loadedAt: string; why: string | null } = (() => {
const here = import.meta.dirname;
const loadedAt = new Date().toISOString();
try {
const git = (args: string[]): string =>
execFileSync("git", ["-C", here, ...args], { encoding: "utf8", timeout: 5000 }).trim();
return {
rev: git(["rev-parse", "HEAD"]),
dirty: git(["status", "--porcelain"]).length > 0,
sourceDir: git(["rev-parse", "--show-toplevel"]),
loadedAt,
why: null,
};
} catch (e) {
return { rev: null, dirty: null, sourceDir: here, loadedAt, why: e instanceof Error ? e.message : String(e) };
}
})();

export default async function plugin(bb: BbPluginApi) {
const settings = bb.settings.define({
syncedKeys: {
Expand Down Expand Up @@ -108,8 +143,26 @@ export default async function plugin(bb: BbPluginApi) {
commands: [
{ name: "status", summary: "Show the shared snapshot", usage: "bb sidebar-sync status" },
{ name: "reset", summary: "Forget it and let the next UI reseed", usage: "bb sidebar-sync reset" },
{
name: "build",
summary: "Which commit this RUNNING process was loaded from (not the checkout)",
usage: "bb sidebar-sync build [--json]",
},
],
async run(argv) {
// Answered FIRST, ahead of `reset` (which DELETES the shared snapshot) and ahead of
// the default path that loads it: "what is running" must stay answerable when the
// thing running is broken, and must never have a side effect of its own.
if (argv[0] === "build") {
if (argv.includes("--json")) return { exitCode: 0, stdout: JSON.stringify(BUILD_STAMP) };
const dirty = BUILD_STAMP.dirty === null ? "" : BUILD_STAMP.dirty ? " +dirty" : "";
const why = BUILD_STAMP.why ? ` (${BUILD_STAMP.why})` : "";
return {
exitCode: 0,
stdout: `loaded ${BUILD_STAMP.rev ?? "unknown"}${dirty} from ${BUILD_STAMP.sourceDir} at ${BUILD_STAMP.loadedAt}${why}`,
};
}

if (argv[0] === "reset") {
await bb.storage.kv.delete(SNAPSHOT_KEY);
return { exitCode: 0, stdout: "snapshot cleared" };
Expand Down
Loading