From cbb2cf0c6cbd4026619fc67e8f5b23d4cafb1e5b Mon Sep 17 00:00:00 2001 From: LinYushen Date: Tue, 14 Apr 2026 19:33:39 +0800 Subject: [PATCH] chore(desktop): rebuild CLI on every bundle-cli run (#999) bundle-cli.mjs now invokes `go build` with the same ldflags as `make build` (version/commit/date) before copying the binary into resources/bin/. Running this on every `pnpm dev:desktop`, `dev:remote` and `package` guarantees the bundled CLI matches the current Go source, so you can't accidentally ship a stale binary after editing server/ code. Go's build cache makes no-op builds ~a few hundred ms. Graceful fallback preserved: if `go` is not on PATH (frontend-only contributor), we warn, skip the build, and let cli-bootstrap download the latest release at runtime. Compile errors remain fatal so broken Go code blocks dev rather than silently falling back. Co-authored-by: Claude Opus 4.6 (1M context) --- apps/desktop/scripts/bundle-cli.mjs | 76 ++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/apps/desktop/scripts/bundle-cli.mjs b/apps/desktop/scripts/bundle-cli.mjs index f9796fcd3..03a8b7504 100644 --- a/apps/desktop/scripts/bundle-cli.mjs +++ b/apps/desktop/scripts/bundle-cli.mjs @@ -1,27 +1,50 @@ #!/usr/bin/env node -// Copies the locally-built `multica` CLI into apps/desktop/resources/bin/ -// so electron-vite (dev) and electron-builder (prod) pick it up. Desktop's -// cli-bootstrap prefers this bundled copy over the GitHub Releases download -// path, which keeps dev iteration fast (edit Go → make build → restart -// Desktop) and lets the released .app ship with a CLI out of the box. +// Builds the `multica` CLI from server/cmd/multica and copies the binary +// into apps/desktop/resources/bin/ so electron-vite (dev) and electron- +// builder (prod) pick it up. Running this on every dev/build/package +// invocation guarantees the bundled CLI always matches the current Go +// source — no more stale binary surprises. Go's build cache makes the +// no-op case (nothing changed) effectively free. // -// Graceful: if server/bin/multica is missing, prints a warning and exits -// with status 0 so the Desktop can still boot with auto-install fallback. +// ldflags mirror `make build` so `multica --version` reports a meaningful +// version / commit / date. +// +// Graceful: if `go` is not installed (e.g. frontend-only contributor), we +// skip the build and fall through to auto-install at runtime. A genuine +// Go compile error is fatal — you want that to block dev, not hide. import { access, chmod, copyFile, mkdir } from "node:fs/promises"; import { constants } from "node:fs"; -import { execSync } from "node:child_process"; +import { execFileSync, execSync } from "node:child_process"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); const repoRoot = resolve(here, "..", "..", ".."); +const serverDir = join(repoRoot, "server"); const binName = process.platform === "win32" ? "multica.exe" : "multica"; -const srcBinary = join(repoRoot, "server", "bin", binName); +const srcBinary = join(serverDir, "bin", binName); const destDir = join(repoRoot, "apps", "desktop", "resources", "bin"); const destBinary = join(destDir, binName); +function sh(cmd) { + try { + return execSync(cmd, { encoding: "utf-8" }).trim(); + } catch { + return ""; + } +} + +function hasGo() { + try { + execSync("go version", { stdio: "pipe" }); + return true; + } catch { + return false; + } +} + async function exists(p) { try { await access(p, constants.F_OK); @@ -31,10 +54,39 @@ async function exists(p) { } } +if (hasGo()) { + const version = sh("git describe --tags --always --dirty") || "dev"; + const commit = sh("git rev-parse --short HEAD") || "unknown"; + const date = new Date().toISOString().replace(/\.\d+Z$/, "Z"); + const ldflags = `-X main.version=${version} -X main.commit=${commit} -X main.date=${date}`; + + console.log( + `[bundle-cli] go build → ${srcBinary} (version=${version} commit=${commit})`, + ); + execFileSync( + "go", + [ + "build", + "-ldflags", + ldflags, + "-o", + join("bin", binName), + "./cmd/multica", + ], + { cwd: serverDir, stdio: "inherit" }, + ); +} else { + console.warn( + "[bundle-cli] `go` not found in PATH — skipping CLI build. " + + "Desktop will use whatever is already in resources/bin/, or fall back " + + "to auto-installing the latest release at runtime.", + ); +} + if (!(await exists(srcBinary))) { console.warn( - `[bundle-cli] ${srcBinary} not found — run 'make build' to bundle the CLI. ` + - `Desktop will fall back to auto-installing the latest release at runtime.`, + `[bundle-cli] ${srcBinary} not present — Desktop will fall back to ` + + `auto-installing the latest release at runtime.`, ); process.exit(0); } @@ -51,7 +103,7 @@ if (process.platform === "darwin") { stdio: "pipe", }); } catch { - // Non-fatal. Unsigned binaries still run when the parent is trusted. + // Non-fatal. Unsigned binaries still run when the parent app is trusted. } }