fix(desktop): run git describe without a shell so version derivation works on Windows (#5097)

#5057 restricted version derivation to `git describe --tags --match 'v[0-9]*'`,
but the command was passed to `execSync` as a shell string. On Windows the
shell is cmd.exe, which does not strip the POSIX single quotes around
'v[0-9]*', so git received the quotes literally, matched no tag, fell through
to `--always`, and the version degraded to the `0.0.0-g<hash>` fallback.

That is what shipped a `0.0.0-gc05b67ae4` Windows Desktop build (electron-builder
`--publish always` then auto-created a bogus release) during the v0.3.41 release,
even though the tag was sitting exactly on HEAD. Linux/macOS were unaffected
because /bin/sh strips the quotes.

Fix: invoke git with an argv array via execFileSync in every version-derivation
path, so the match pattern reaches git as one literal argument regardless of
platform:

- apps/desktop/scripts/package.mjs      (Desktop version → electron-builder)
- apps/desktop/scripts/bundle-cli.mjs   (bundled CLI ldflags version)
- apps/desktop/src/main/app-version.ts  (dev-mode version fallback)

The Makefile is intentionally left as-is: make's `$(shell ...)` always runs via
/bin/sh (even on Windows) and the CLI release runs on Linux, so its single
quotes are stripped correctly.

Tests: export `deriveVersion` and `DESCRIBE_ARGS` and add coverage that runs the
real `git describe` against throwaway repos (clean semver tag, semver tag chosen
over a nearer non-semver tag, and the no-tag fallback), plus a structural check
that the match pattern is a bare argv token with no embedded quotes. The prior
suite only unit-tested the `normalizeGitVersion` string transform, which is why
this slipped through.

MUL-4256

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Bohan Jiang
2026-07-08 18:49:46 +08:00
committed by GitHub
parent c05b67ae4a
commit 3790ca78e7
4 changed files with 132 additions and 21 deletions

View File

@@ -74,9 +74,14 @@ const srcBinary = join(serverDir, "bin", `${goos}-${goarch}`, binName);
const destDir = join(repoRoot, "apps", "desktop", "resources", "bin");
const destBinary = join(destDir, binName);
function sh(cmd) {
// Hand git arguments straight to the binary (no shell). A match pattern like
// `v[0-9]*` must reach git as one literal argument; routing it through a shell
// string breaks on Windows, where cmd.exe keeps the POSIX single quotes and
// git matches no tag — degrading the bundled CLI's version to the
// 0.0.0-g<hash> fallback.
function git(...args) {
try {
return execSync(cmd, { encoding: "utf-8" }).trim();
return execFileSync("git", args, { encoding: "utf-8" }).trim();
} catch {
return "";
}
@@ -101,8 +106,10 @@ async function exists(p) {
}
if (hasGo()) {
const version = sh("git describe --tags --match 'v[0-9]*' --always --dirty") || "dev";
const commit = sh("git rev-parse --short HEAD") || "unknown";
const version =
git("describe", "--tags", "--match", "v[0-9]*", "--always", "--dirty") ||
"dev";
const commit = 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}`;