authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-02 18:05:17-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-02 18:53:21-08:00
loge7790490f16592d9b0a5418201921a58b1fb5fe7
tree07c71df0aa755ac6220f304eabd1d8b6c4659ad0
parenta638978b75d42e78daad25170098efdec7d76dd4

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

2 files changed, 79 insertions(+), 1 deletions(-)

apps/desktop/src/main.ts+59-1
......@@ -1,4 +1,4 @@
1import { spawn, type ChildProcess } from "node:child_process";
1import { execFileSync, spawn, type ChildProcess } from "node:child_process";
22import { randomBytes } from "node:crypto";
33import fs from "node:fs";
44import net from "node:net";
......@@ -26,6 +26,8 @@ const ROOT_DIR = path.resolve(__dirname, "../../..");
2626const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);
2727const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";
2828const APP_USER_MODEL_ID = "com.t3tools.t3code";
29const COMMIT_HASH_PATTERN = /^[0-9a-f]{7,40}$/i;
30const COMMIT_HASH_DISPLAY_LENGTH = 12;
2931
3032let mainWindow: BrowserWindow | null = null;
3133let backendProcess: ChildProcess | null = null;
......@@ -36,6 +38,7 @@ let restartAttempt = 0;
3638let restartTimer: ReturnType<typeof setTimeout> | null = null;
3739let isQuitting = false;
3840let desktopProtocolRegistered = false;
41let aboutCommitHashCache: string | null | undefined;
3942
4043let destructiveMenuIconCache: Electron.NativeImage | null | undefined;
4144
......@@ -81,6 +84,58 @@ function resolveAppRoot(): string {
8184 return app.getAppPath();
8285}
8386
87function normalizeCommitHash(value: unknown): string | null {
88 if (typeof value !== "string") {
89 return null;
90 }
91 const trimmed = value.trim();
92 if (!COMMIT_HASH_PATTERN.test(trimmed)) {
93 return null;
94 }
95 return trimmed.slice(0, COMMIT_HASH_DISPLAY_LENGTH).toLowerCase();
96}
97
98function resolveEmbeddedCommitHash(): string | null {
99 const packageJsonPath = path.join(resolveAppRoot(), "package.json");
100 if (!fs.existsSync(packageJsonPath)) {
101 return null;
102 }
103
104 try {
105 const raw = fs.readFileSync(packageJsonPath, "utf8");
106 const parsed = JSON.parse(raw) as { t3codeCommitHash?: unknown };
107 return normalizeCommitHash(parsed.t3codeCommitHash);
108 } catch {
109 return null;
110 }
111}
112
113function resolveGitCommitHash(): string | null {
114 try {
115 const stdout = execFileSync("git", ["rev-parse", `--short=${COMMIT_HASH_DISPLAY_LENGTH}`, "HEAD"], {
116 cwd: ROOT_DIR,
117 encoding: "utf8",
118 stdio: ["ignore", "pipe", "ignore"],
119 });
120 return normalizeCommitHash(stdout);
121 } catch {
122 return null;
123 }
124}
125
126function resolveAboutCommitHash(): string | null {
127 if (aboutCommitHashCache !== undefined) {
128 return aboutCommitHashCache;
129 }
130
131 aboutCommitHashCache =
132 normalizeCommitHash(process.env.T3CODE_COMMIT_HASH) ??
133 resolveEmbeddedCommitHash() ??
134 resolveGitCommitHash();
135
136 return aboutCommitHashCache;
137}
138
84139function resolveBackendEntry(): string {
85140 return path.join(resolveAppRoot(), "apps/server/dist/index.mjs");
86141}
......@@ -277,8 +332,11 @@ function resolveIconPath(ext: "ico" | "icns" | "png"): string | null {
277332
278333function configureAppIdentity(): void {
279334 app.setName(APP_DISPLAY_NAME);
335 const commitHash = resolveAboutCommitHash();
280336 app.setAboutPanelOptions({
281337 applicationName: APP_DISPLAY_NAME,
338 applicationVersion: app.getVersion(),
339 version: commitHash ?? "unknown",
282340 });
283341
284342 if (process.platform === "win32") {
scripts/build-desktop-artifact.ts+20
......@@ -1,5 +1,7 @@
11#!/usr/bin/env node
22
3import { spawnSync } from "node:child_process";
4
35import rootPackageJson from "../package.json" with { type: "json" };
46import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" };
57import serverPackageJson from "../apps/server/package.json" with { type: "json" };
......@@ -84,6 +86,21 @@ class BuildScriptError extends Data.TaggedError("BuildScriptError")<{
8486 readonly cause?: unknown;
8587}> {}
8688
89function resolveGitCommitHash(repoRoot: string): string {
90 const result = spawnSync("git", ["rev-parse", "--short=12", "HEAD"], {
91 cwd: repoRoot,
92 encoding: "utf8",
93 });
94 if (result.status !== 0) {
95 return "unknown";
96 }
97 const hash = result.stdout.trim();
98 if (!/^[0-9a-f]{7,40}$/i.test(hash)) {
99 return "unknown";
100 }
101 return hash.toLowerCase();
102}
103
87104interface ResolvedBuildOptions {
88105 readonly platform: typeof BuildPlatform.Type;
89106 readonly target: string;
......@@ -100,6 +117,7 @@ interface StagePackageJson {
100117 readonly name: string;
101118 readonly version: string;
102119 readonly buildVersion: string;
120 readonly t3codeCommitHash: string;
103121 readonly private: true;
104122 readonly description: string;
105123 readonly author: string;
......@@ -436,6 +454,7 @@ const buildDesktopArtifact = Effect.fn(function* (options: ResolvedBuildOptions)
436454 });
437455
438456 const appVersion = options.version ?? serverPackageJson.version;
457 const commitHash = resolveGitCommitHash(repoRoot);
439458 const mkdir = options.keepStage ? fs.makeTempDirectory : fs.makeTempDirectoryScoped;
440459 const stageRoot = yield* mkdir({
441460 prefix: `t3code-desktop-${options.platform}-stage-`,
......@@ -490,6 +509,7 @@ const buildDesktopArtifact = Effect.fn(function* (options: ResolvedBuildOptions)
490509 name: "t3-code-desktop",
491510 version: appVersion,
492511 buildVersion: appVersion,
512 t3codeCommitHash: commitHash,
493513 private: true,
494514 description: "T3 Code desktop build",
495515 author: "T3 Tools",