From e7790490f16592d9b0a5418201921a58b1fb5fe7 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 2 Mar 2026 18:05:17 -0800 Subject: [PATCH] Show desktop commit hash in About panel - embed `t3codeCommitHash` into staged desktop package metadata during builds - resolve commit hash from env/package/git and display it as About panel version --- apps/desktop/src/main.ts | 60 ++++++++++++++++++++++++++++++- scripts/build-desktop-artifact.ts | 20 +++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index d28fdf172a2936509437e14f147b963f9eb803b5..6a43890c6d79d9a5bbde5176237f27d1bcefd35c 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -1,4 +1,4 @@ -import { spawn, type ChildProcess } from "node:child_process"; +import { execFileSync, spawn, type ChildProcess } from "node:child_process"; import { randomBytes } from "node:crypto"; import fs from "node:fs"; import net from "node:net"; @@ -26,6 +26,8 @@ const ROOT_DIR = path.resolve(__dirname, "../../.."); const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL); const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)"; const APP_USER_MODEL_ID = "com.t3tools.t3code"; +const COMMIT_HASH_PATTERN = /^[0-9a-f]{7,40}$/i; +const COMMIT_HASH_DISPLAY_LENGTH = 12; let mainWindow: BrowserWindow | null = null; let backendProcess: ChildProcess | null = null; @@ -36,6 +38,7 @@ let restartAttempt = 0; let restartTimer: ReturnType | null = null; let isQuitting = false; let desktopProtocolRegistered = false; +let aboutCommitHashCache: string | null | undefined; let destructiveMenuIconCache: Electron.NativeImage | null | undefined; @@ -81,6 +84,58 @@ function resolveAppRoot(): string { return app.getAppPath(); } +function normalizeCommitHash(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + if (!COMMIT_HASH_PATTERN.test(trimmed)) { + return null; + } + return trimmed.slice(0, COMMIT_HASH_DISPLAY_LENGTH).toLowerCase(); +} + +function resolveEmbeddedCommitHash(): string | null { + const packageJsonPath = path.join(resolveAppRoot(), "package.json"); + if (!fs.existsSync(packageJsonPath)) { + return null; + } + + try { + const raw = fs.readFileSync(packageJsonPath, "utf8"); + const parsed = JSON.parse(raw) as { t3codeCommitHash?: unknown }; + return normalizeCommitHash(parsed.t3codeCommitHash); + } catch { + return null; + } +} + +function resolveGitCommitHash(): string | null { + try { + const stdout = execFileSync("git", ["rev-parse", `--short=${COMMIT_HASH_DISPLAY_LENGTH}`, "HEAD"], { + cwd: ROOT_DIR, + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }); + return normalizeCommitHash(stdout); + } catch { + return null; + } +} + +function resolveAboutCommitHash(): string | null { + if (aboutCommitHashCache !== undefined) { + return aboutCommitHashCache; + } + + aboutCommitHashCache = + normalizeCommitHash(process.env.T3CODE_COMMIT_HASH) ?? + resolveEmbeddedCommitHash() ?? + resolveGitCommitHash(); + + return aboutCommitHashCache; +} + function resolveBackendEntry(): string { return path.join(resolveAppRoot(), "apps/server/dist/index.mjs"); } @@ -277,8 +332,11 @@ function resolveIconPath(ext: "ico" | "icns" | "png"): string | null { function configureAppIdentity(): void { app.setName(APP_DISPLAY_NAME); + const commitHash = resolveAboutCommitHash(); app.setAboutPanelOptions({ applicationName: APP_DISPLAY_NAME, + applicationVersion: app.getVersion(), + version: commitHash ?? "unknown", }); if (process.platform === "win32") { diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index 48f6c5f3a6a0744bf518879fe1431a2072c83ab2..76f926549705b00feed904bde98f13c1adf570a2 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -1,5 +1,7 @@ #!/usr/bin/env node +import { spawnSync } from "node:child_process"; + import rootPackageJson from "../package.json" with { type: "json" }; import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" }; import serverPackageJson from "../apps/server/package.json" with { type: "json" }; @@ -84,6 +86,21 @@ class BuildScriptError extends Data.TaggedError("BuildScriptError")<{ readonly cause?: unknown; }> {} +function resolveGitCommitHash(repoRoot: string): string { + const result = spawnSync("git", ["rev-parse", "--short=12", "HEAD"], { + cwd: repoRoot, + encoding: "utf8", + }); + if (result.status !== 0) { + return "unknown"; + } + const hash = result.stdout.trim(); + if (!/^[0-9a-f]{7,40}$/i.test(hash)) { + return "unknown"; + } + return hash.toLowerCase(); +} + interface ResolvedBuildOptions { readonly platform: typeof BuildPlatform.Type; readonly target: string; @@ -100,6 +117,7 @@ interface StagePackageJson { readonly name: string; readonly version: string; readonly buildVersion: string; + readonly t3codeCommitHash: string; readonly private: true; readonly description: string; readonly author: string; @@ -436,6 +454,7 @@ const buildDesktopArtifact = Effect.fn(function* (options: ResolvedBuildOptions) }); const appVersion = options.version ?? serverPackageJson.version; + const commitHash = resolveGitCommitHash(repoRoot); const mkdir = options.keepStage ? fs.makeTempDirectory : fs.makeTempDirectoryScoped; const stageRoot = yield* mkdir({ prefix: `t3code-desktop-${options.platform}-stage-`, @@ -490,6 +509,7 @@ const buildDesktopArtifact = Effect.fn(function* (options: ResolvedBuildOptions) name: "t3-code-desktop", version: appVersion, buildVersion: appVersion, + t3codeCommitHash: commitHash, private: true, description: "T3 Code desktop build", author: "T3 Tools", -- 2.54.0