Skip to content

Commit 5f9fba2

Browse files
ConalMullanclaude
andcommitted
feat: add git-based versioning with DEV badge
- Pass GIT_COMMIT, GIT_REF, BUILD_TIME as Docker build args - Generate version.json at build time with git info - Update version.ts to read from version.json in production - Add getDisplayVersion() showing version + commit hash - Add isDevBuild() to detect development builds - Add DEV badge (coral color) on landing page for dev builds - Production builds (tags starting with 'v') show clean version Examples: - Dev: "v1.0.0-rc.1 (e5f1b19)" with DEV badge - Prod: "v1.0.0" without badge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e5f1b19 commit 5f9fba2

6 files changed

Lines changed: 132 additions & 14 deletions

File tree

.github/workflows/deploy-dev.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ jobs:
8080
tags: |
8181
${{ secrets.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
8282
${{ secrets.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
83+
build-args: |
84+
GIT_COMMIT=${{ github.sha }}
85+
GIT_REF=${{ github.ref_name }}
86+
BUILD_TIME=${{ github.event.head_commit.timestamp }}
8387
cache-from: type=gha
8488
cache-to: type=gha,mode=max
8589

.github/workflows/deploy-prod.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ jobs:
9494
${{ secrets.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
9595
${{ secrets.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
9696
${{ secrets.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:production
97+
build-args: |
98+
GIT_COMMIT=${{ github.sha }}
99+
GIT_REF=${{ github.ref_name }}
100+
BUILD_TIME=${{ github.event.head_commit.timestamp }}
97101
cache-from: type=gha
98102
cache-to: type=gha,mode=max
99103

Dockerfile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111

1212
FROM node:20-alpine AS builder
1313

14+
# Build arguments for version info
15+
ARG GIT_COMMIT=unknown
16+
ARG GIT_REF=unknown
17+
ARG BUILD_TIME=unknown
18+
1419
WORKDIR /app
1520

1621
# Copy package files
@@ -22,6 +27,9 @@ RUN npm ci
2227
# Copy source
2328
COPY . .
2429

30+
# Generate version info file
31+
RUN echo "{\"commit\":\"${GIT_COMMIT}\",\"ref\":\"${GIT_REF}\",\"buildTime\":\"${BUILD_TIME}\"}" > version.json
32+
2533
# Build TypeScript
2634
RUN npm run build
2735

@@ -36,9 +44,10 @@ COPY package*.json ./
3644
# Install production dependencies only
3745
RUN npm ci --omit=dev && npm cache clean --force
3846

39-
# Copy built files
47+
# Copy built files and version info
4048
COPY --from=builder /app/dist ./dist
4149
COPY --from=builder /app/bin ./bin
50+
COPY --from=builder /app/version.json ./version.json
4251

4352
# Set environment variables
4453
ENV NODE_ENV=production

src/server.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,14 @@ import {
9090
executeExportTool,
9191
} from "./tools/export-tools/index.js";
9292

93-
import { VERSION, VERSION_INFO } from "./version.js";
93+
import {
94+
VERSION,
95+
VERSION_INFO,
96+
GIT_COMMIT,
97+
GIT_REF,
98+
getDisplayVersion,
99+
isDevBuild,
100+
} from "./version.js";
94101

95102
// API client instance cache - keyed by apiKey:apiUrl to support different URLs per session
96103
let apiClientCache: Map<string, DigitalSambaApiClient> = new Map();
@@ -382,4 +389,11 @@ export function createServer(config: ServerConfig = {}): Server {
382389
return server;
383390
}
384391

385-
export { VERSION, VERSION_INFO };
392+
export {
393+
VERSION,
394+
VERSION_INFO,
395+
GIT_COMMIT,
396+
GIT_REF,
397+
getDisplayVersion,
398+
isDevBuild,
399+
};

src/transports/http.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ import { randomUUID } from "node:crypto";
1010
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1111
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
1212

13-
import { createServer, VERSION, VERSION_INFO } from "../server.js";
13+
import {
14+
createServer,
15+
VERSION,
16+
VERSION_INFO,
17+
getDisplayVersion,
18+
isDevBuild,
19+
} from "../server.js";
1420
import logger from "../logger.js";
1521
import apiKeyContext from "../auth.js";
1622
import {
@@ -954,6 +960,19 @@ export async function startHttpServer(config: HttpTransportConfig = {}): Promise
954960
padding: 4px 10px;
955961
border-radius: 6px;
956962
margin-left: auto;
963+
display: flex;
964+
align-items: center;
965+
gap: 6px;
966+
}
967+
.dev-badge {
968+
background: var(--ds-coral);
969+
color: white;
970+
font-size: 10px;
971+
font-weight: 600;
972+
padding: 2px 6px;
973+
border-radius: 4px;
974+
text-transform: uppercase;
975+
letter-spacing: 0.5px;
957976
}
958977
</style>
959978
</head>
@@ -978,7 +997,7 @@ export async function startHttpServer(config: HttpTransportConfig = {}): Promise
978997
<div class="card-header">
979998
<div class="card-icon">⚡</div>
980999
<h2>Quick Connect</h2>
981-
<span class="version-tag">v${VERSION}</span>
1000+
<span class="version-tag">${isDevBuild() ? '<span class="dev-badge">Dev</span>' : ""}v${getDisplayVersion()}</span>
9821001
</div>
9831002
<div class="card-body">
9841003
<div class="url-box">

src/version.ts

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,62 @@
11
/**
22
* Version information module
3-
* In development, this reads directly from package.json
4-
* In production, this is replaced by the build script
3+
*
4+
* In development: reads from package.json
5+
* In production: reads from version.json (generated at build time)
6+
*
7+
* version.json contains git info:
8+
* - commit: short SHA
9+
* - ref: branch name or tag
10+
* - buildTime: ISO timestamp
511
*/
612

7-
// Try to read package.json in development
8-
let versionInfo: any;
13+
import { readFileSync } from "fs";
14+
import { join, dirname } from "path";
15+
import { fileURLToPath } from "url";
16+
17+
interface VersionInfo {
18+
version: string;
19+
name: string;
20+
commit: string;
21+
ref: string;
22+
buildTime: string;
23+
nodeVersion: string;
24+
}
25+
26+
let versionInfo: VersionInfo;
27+
928
try {
10-
// Development path - this will fail in production build
11-
// eslint-disable-next-line @typescript-eslint/no-var-requires
12-
const packageJson = require("../package.json");
29+
// Get the directory of this file
30+
const __dirname = dirname(fileURLToPath(import.meta.url));
31+
32+
// Try to read package.json
33+
const packageJsonPath = join(__dirname, "..", "..", "package.json");
34+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
35+
36+
// Try to read version.json (production builds)
37+
let gitInfo = { commit: "dev", ref: "local", buildTime: "development" };
38+
try {
39+
const versionJsonPath = join(__dirname, "..", "..", "version.json");
40+
gitInfo = JSON.parse(readFileSync(versionJsonPath, "utf-8"));
41+
} catch {
42+
// version.json doesn't exist in development - that's fine
43+
}
44+
1345
versionInfo = {
1446
version: packageJson.version,
1547
name: packageJson.name,
16-
buildTime: "development",
48+
commit: gitInfo.commit,
49+
ref: gitInfo.ref,
50+
buildTime: gitInfo.buildTime,
1751
nodeVersion: process.version,
1852
};
1953
} catch {
20-
// Production build - use injected values
54+
// Fallback if nothing works
2155
versionInfo = {
2256
version: "unknown",
2357
name: "@digitalsamba/embedded-api-mcp-server",
58+
commit: "unknown",
59+
ref: "unknown",
2460
buildTime: "unknown",
2561
nodeVersion: process.version,
2662
};
@@ -30,3 +66,35 @@ export const VERSION_INFO = versionInfo;
3066
export const VERSION = versionInfo.version;
3167
export const PACKAGE_NAME = versionInfo.name;
3268
export const BUILD_TIME = versionInfo.buildTime;
69+
export const GIT_COMMIT = versionInfo.commit;
70+
export const GIT_REF = versionInfo.ref;
71+
72+
/**
73+
* Get a formatted version string for display
74+
* Examples:
75+
* - Production (on tag): "1.0.0"
76+
* - Development: "1.0.0-rc.1 (e5f1b19)"
77+
*/
78+
export function getDisplayVersion(): string {
79+
// If commit is a tag ref or matches version, just show version
80+
if (
81+
versionInfo.ref.startsWith("v") ||
82+
versionInfo.commit === "dev" ||
83+
versionInfo.commit === "unknown"
84+
) {
85+
return versionInfo.version;
86+
}
87+
// Otherwise show version with commit
88+
return `${versionInfo.version} (${versionInfo.commit})`;
89+
}
90+
91+
/**
92+
* Check if this is a development build
93+
*/
94+
export function isDevBuild(): boolean {
95+
return (
96+
versionInfo.buildTime === "development" ||
97+
versionInfo.ref === "develop" ||
98+
versionInfo.ref.includes("dev")
99+
);
100+
}

0 commit comments

Comments
 (0)