-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Adds build metadata, build-info API and admin panel build infos #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Adds build-time metadata (build date, commit SHA/ref) and persists it as a structured build-info file. Updates CI/Docker to pass and embed BUILD_DATE/COMMIT_SHA/COMMIT_REF, replaces simple .version handling with a .build-info.json reader and cached BuildInfo API, and exposes getBuildInfo/getCurrentVersion. Updates version endpoint and client hook to return buildDate, short commit, github URL, isDev flag and improved latest-release/update detection and caching. Removes reliance on branch env var and improves GitHub URL resolution for dev builds, enabling accurate build provenance and more reliable update checks.
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughThis PR moves version and build metadata to be injected at build time: CI captures a BUILD_DATE timestamp and passes BUILD_DATE, COMMIT_SHA, and COMMIT_REF as Docker build-args. The Dockerfile writes a structured Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant CI as CI / GitHub Actions
participant Docker as Docker build (Dockerfile)
participant Container as App Runtime (container)
participant Utils as lib/version.ts
participant API as /api/version route
Note over CI,Docker: Build-time
CI->>CI: Capture BUILD_DATE (ISO UTC) -> GITHUB_OUTPUT
CI->>Docker: Build & push (args: BUILD_DATE, COMMIT_SHA, COMMIT_REF, VERSION)
Docker->>Docker: Create /app/.build-info.json (version, buildDate, commitSha, commitRef)
Docker->>Container: Image includes .build-info.json
Note over Container,API: Runtime
API->>Utils: getBuildInfo()
Utils->>Container: Read /app/.build-info.json (or fallback)
Utils-->>API: Return BuildInfo
API->>Utils: buildGitHubUrl(buildInfo.version, buildInfo.commitSha)
Utils-->>API: Return github URL
API->>API: Build VersionResponse (version, buildDate, commitHash, githubUrl, isDev, latest/version checks)
API-->>Client: Return VersionResponse (cached)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR modernizes the build metadata system by replacing the simple .version file with a structured .build-info.json file that captures comprehensive build information including version, build date, commit SHA, and commit reference. The changes improve build provenance tracking and enable more accurate update detection by eliminating the need for runtime GitHub API calls to fetch commit information.
Key changes:
- Replaces
.versionfile with.build-info.jsoncontaining structured metadata (version, buildDate, commitSha, commitRef) - Updates version API endpoint to return build metadata directly instead of fetching commit hash at runtime
- Removes dependency on GITHUB_BRANCH environment variable for cleaner configuration
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
lib/version.ts |
Refactored to read structured build info from JSON file; added BuildInfo interface and getBuildInfo() function while maintaining backward compatibility |
hooks/useVersionUpdate.ts |
Added buildDate field to VersionInfo interface for client-side consumption |
app/api/version/route.ts |
Simplified to use pre-built metadata instead of runtime GitHub API calls; removed fetchCommitHash() and buildGitHubUrl() functions (moved to lib/version.ts) |
Dockerfile |
Updated to write structured JSON metadata file instead of plain text version; accepts BUILD_DATE, COMMIT_SHA, and COMMIT_REF build args |
.github/workflows/release.yml |
Added BUILD_DATE, COMMIT_SHA, and COMMIT_REF build arguments to Docker build step |
.github/workflows/docker-dev.yml |
Added BUILD_DATE, COMMIT_SHA, and COMMIT_REF build arguments to Docker build step |
.env.example |
Removed GITHUB_BRANCH environment variable as it's no longer needed |
Comments suppressed due to low confidence (1)
app/api/version/route.ts:103
- The version comparison logic only compares the first 3 segments (major.minor.patch) but doesn't handle pre-release versions (e.g., 1.0.0-alpha, 1.0.0-beta) or build metadata. If a version string contains these, they will be ignored in the comparison. Consider documenting this limitation or handling pre-release versions according to SemVer specification.
function compareVersions(current: string, latest: string): boolean {
// Returns true if latest is newer than current
// Remove 'v' prefix if present
const cleanCurrent = current.replace(/^v/, "");
const cleanLatest = latest.replace(/^v/, "");
const parseCurrent = cleanCurrent.split(".").map(Number);
const parseLatest = cleanLatest.split(".").map(Number);
for (let i = 0; i < 3; i++) {
const curr = parseCurrent[i] || 0;
const lat = parseLatest[i] || 0;
if (lat > curr) return true;
if (lat < curr) return false;
}
return false;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (3)
app/api/version/route.ts (1)
106-109: Consider a more precise dev version check.The current implementation uses
.includes("dev"), which might match unintended version strings (e.g.,"1.2.3-devops","1.2.3-development").🔎 Suggested refinement for more precise matching
function isDevVersion(version: string): boolean { - // Check if version is 'dev' or contains 'dev' - return version === "dev" || version.includes("dev"); + // Check if version is exactly 'dev' or starts with 'dev-' + return version === "dev" || version.startsWith("dev-"); }This approach prevents false positives while still matching common dev version patterns like
"dev","dev-123","dev-feature-branch".lib/version.ts (2)
17-38: Consider simplifying file reading and validating buildDate format.Two improvements to consider:
The
access()check at line 22 is unnecessary sincereadFile()will fail anyway if the file doesn't exist. This creates a minor TOCTOU (time-of-check-time-of-use) pattern.Per coding guidelines, dates should be in
YYYY-MM-DDformat. Consider validating thebuildDatefield format to catch misconfiguration early.🔎 Proposed improvements
async function getDockerBuildInfo(): Promise<BuildInfo | null> { try { const buildInfoPath = join(process.cwd(), ".build-info.json"); - - // Check if file exists - await access(buildInfoPath); // Read and parse build info const content = await readFile(buildInfoPath, "utf-8"); const info = JSON.parse(content); + + // Validate buildDate format (YYYY-MM-DD) + const datePattern = /^\d{4}-\d{2}-\d{2}$/; + if (info.buildDate && !datePattern.test(info.buildDate) && info.buildDate !== "unknown") { + console.warn(`Invalid buildDate format: ${info.buildDate}, expected YYYY-MM-DD`); + } return { version: info.version || "unknown", buildDate: info.buildDate || "unknown", commitSha: info.commitSha || "unknown", commitRef: info.commitRef || "unknown", }; } catch { // File doesn't exist or can't be read return null; } }Based on coding guidelines requiring
YYYY-MM-DDdate format.
130-149: LGTM! Enhanced GitHub URL generation.The addition of commit SHA support for dev builds is a valuable improvement. The function correctly handles semver versions, dev builds with commit info, and fallback scenarios.
Optional note: The semver pattern
/^v?\d+\.\d+\.\d+$/only matches basic versions (e.g., "1.2.3") and doesn't handle pre-release versions (e.g., "1.2.3-beta.1") or build metadata. This is likely intentional for the current use case, but if you need to support pre-release versions in the future, the pattern would need adjustment.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
.env.example.github/workflows/docker-dev.yml.github/workflows/release.ymlDockerfileapp/api/version/route.tshooks/useVersionUpdate.tslib/version.ts
💤 Files with no reviewable changes (1)
- .env.example
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Store and display dates asYYYY-MM-DDstrings (local dates without timezone conversion). UseformatDateToLocal()fromlib/date-utils.tsbefore saving to database anddate-fnswith locale fromgetDateLocale()for display.
Use Drizzle-inferred types from schema directly (noanytypes). TypeScript must remain in strict mode with proper type safety throughout the application.
All React files must be in strict mode with TypeScript 5. Noanytypes allowed. Import inferred types from Drizzle schema.
UseformatDateToLocal(date)utility fromlib/date-utils.tsto convert dates toYYYY-MM-DDformat before saving to database.
Files:
hooks/useVersionUpdate.tsapp/api/version/route.tslib/version.ts
hooks/use*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Implement data fetching hooks following the pattern:
useShifts,usePresets,useNotes,useCalendarswith accompanying action hooks likeuseShiftActions,useNoteActionsfor CRUD operations with optimistic updates.
Files:
hooks/useVersionUpdate.ts
app/**/*.{tsx,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
All components under
app/are Server Components by default unless marked with"use client". Use"use client"directive only for interactive UI components.
Files:
app/api/version/route.ts
app/api/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/api/**/*.ts: All API routes must includeGET/POST/PUT/PATCH/DELETEexports inapp/api/**directory and follow the pattern: check permissions → perform database operation → emit SSE event.
UsegetSessionUser(request.headers)fromlib/auth/sessions.tsto get current user in API routes. Use Better Auth'sauthClientmethods on the client side for session management.
After any database mutation (create/update/delete), emit an SSE event usingeventEmitter.emit('calendar-change', {...})with appropriate type and action to keep clients in sync.
For calendar permission hierarchy in SQL queries and permission checks, follow:owner(highest) >admin>write>read(lowest). UsegetUserAccessibleCalendars(userId)to get all accessible calendars.
UsegetUserCalendarPermission(userId, calendarId)fromlib/auth/permissions.tsto retrieve the exact permission level, which returns permission string ornullif user has no access.
Files:
app/api/version/route.ts
🧬 Code graph analysis (1)
app/api/version/route.ts (1)
lib/version.ts (2)
getBuildInfo(91-107)buildGitHubUrl(130-149)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Agent
- GitHub Check: build-dev
🔇 Additional comments (11)
hooks/useVersionUpdate.ts (1)
6-6: LGTM!The addition of the
buildDatefield aligns with the new BuildInfo-based versioning model and matches the response from the updated/api/versionendpoint.app/api/version/route.ts (4)
12-21: LGTM!The new
VersionResponseinterface provides a clear, type-safe structure for the API response, including all necessary fields for version tracking, update detection, and build provenance.
86-104: LGTM!The updated
compareVersionsfunction now correctly handles version strings with or without the 'v' prefix, improving robustness when comparing versions from different sources.
111-164: Excellent refactoring to build-time metadata approach.The new implementation eliminates runtime GitHub API calls for commit information, instead relying on build-time injected metadata. This improves reliability, reduces external dependencies, and provides faster response times. The caching strategy is well-documented and consistent across the codebase.
140-151: No action required. ThecommitSha.substring(0, 7)operation is type-safe and properly guarded. ThegetBuildInfo()function guaranteescommitShais always a string through both the TypeScript interface definition and implementation-level fallbacks ("unknown"for missing values,"dev"for development mode).Likely an incorrect or invalid review comment.
lib/version.ts (6)
4-9: LGTM! Clean interface design.The
BuildInfointerface is well-structured with appropriate string types for all fields.
59-73: LGTM! Proper fallback handling.The fallback chain (Docker build-info → package.json → dev defaults) is well-implemented and ensures the function always returns valid
BuildInfo.
79-85: LGTM! Correct initialization logic.The initialization properly caches
BuildInfoand handles the promise lifecycle correctly.
91-107: LGTM! Well-implemented caching pattern.The function correctly handles concurrent calls and prevents redundant file reads with proper promise-based synchronization.
113-116: LGTM! Proper delegation.The function correctly delegates to
getBuildInfo()and extracts the version field, maintaining the expected API surface.
154-165: LGTM! Properly integrated with BuildInfo.The function correctly uses the new
getBuildInfo()API and passes the commit SHA for accurate GitHub URL generation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
Dockerfile (1)
77-78: Node.js approach properly addresses JSON escaping concerns.The current implementation using
node -ewithJSON.stringifycorrectly handles special characters and escaping, addressing the concerns raised in previous reviews. The fallback pattern||''ensures that missing build args result in empty strings rather than undefined values in the JSON.lib/version.ts (1)
17-45: LGTM! Proper error handling for JSON parsing added.The nested try-catch properly separates JSON parsing errors from file access errors, with appropriate logging for both cases. This addresses the concern raised in previous reviews.
🧹 Nitpick comments (3)
app/admin/page.tsx (3)
61-63: Missing dependency in useEffect or define function inside.The
useEffectreferencesfetchVersionInfobut it's not in the dependency array. While this works becausefetchVersionInfois stable (no dependencies on state/props), ESLint'sreact-hooks/exhaustive-depsrule will flag this. Either movefetchVersionInfoinside the effect or wrap it withuseCallback.🔎 Proposed fix
- // Fetch version info - useEffect(() => { - fetchVersionInfo(); - }, []); - - const fetchVersionInfo = async () => { + useEffect(() => { + const fetchVersionInfo = async () => { + try { + const response = await fetch("/api/version"); + if (!response.ok) return; + const data = await response.json(); + setVersionInfo(data); + } catch (error) { + console.error("Failed to fetch version info:", error); + } finally { + setIsLoadingVersion(false); + } + }; + + fetchVersionInfo(); + }, []); - try { - const response = await fetch("/api/version"); - if (!response.ok) return; - const data = await response.json(); - setVersionInfo(data); - } catch (error) { - console.error("Failed to fetch version info:", error); - } finally { - setIsLoadingVersion(false); - } - };
167-173: Consider defensive date parsing.If
buildDateis neither "dev"/"unknown" nor a valid ISO date string,new Date()returns an Invalid Date, andformat()may throw or display unexpected output. Consider adding a validity check.🔎 Proposed fix
- {versionInfo.buildDate !== "dev" && - versionInfo.buildDate !== "unknown" - ? format(new Date(versionInfo.buildDate), "PPp", { - locale: dateLocale, - }) - : t("admin.systemInfo.unknown")} + {(() => { + if (versionInfo.buildDate === "dev" || versionInfo.buildDate === "unknown") { + return t("admin.systemInfo.unknown"); + } + const date = new Date(versionInfo.buildDate); + return isNaN(date.getTime()) + ? t("admin.systemInfo.unknown") + : format(date, "PPp", { locale: dateLocale }); + })()}
33-42: Consider importing the shared type from a central location.The
VersionInfointerface is defined locally here and also asVersionResponseinapp/api/version/route.ts. To avoid drift, consider exporting the type from a shared location (e.g.,lib/version.tsor atypes/file) and importing it in both places.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
.github/workflows/docker-dev.yml.github/workflows/release.ymlDockerfileapp/admin/page.tsxapp/api/version/route.tslib/version.tsmessages/de.jsonmessages/en.jsonmessages/it.json
🚧 Files skipped from review as they are similar to previous changes (2)
- .github/workflows/release.yml
- .github/workflows/docker-dev.yml
🧰 Additional context used
📓 Path-based instructions (3)
app/**/*.{tsx,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
All components under
app/are Server Components by default unless marked with"use client". Use"use client"directive only for interactive UI components.
Files:
app/api/version/route.tsapp/admin/page.tsx
app/api/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
app/api/**/*.ts: All API routes must includeGET/POST/PUT/PATCH/DELETEexports inapp/api/**directory and follow the pattern: check permissions → perform database operation → emit SSE event.
UsegetSessionUser(request.headers)fromlib/auth/sessions.tsto get current user in API routes. Use Better Auth'sauthClientmethods on the client side for session management.
After any database mutation (create/update/delete), emit an SSE event usingeventEmitter.emit('calendar-change', {...})with appropriate type and action to keep clients in sync.
For calendar permission hierarchy in SQL queries and permission checks, follow:owner(highest) >admin>write>read(lowest). UsegetUserAccessibleCalendars(userId)to get all accessible calendars.
UsegetUserCalendarPermission(userId, calendarId)fromlib/auth/permissions.tsto retrieve the exact permission level, which returns permission string ornullif user has no access.
Files:
app/api/version/route.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Store and display dates asYYYY-MM-DDstrings (local dates without timezone conversion). UseformatDateToLocal()fromlib/date-utils.tsbefore saving to database anddate-fnswith locale fromgetDateLocale()for display.
Use Drizzle-inferred types from schema directly (noanytypes). TypeScript must remain in strict mode with proper type safety throughout the application.
All React files must be in strict mode with TypeScript 5. Noanytypes allowed. Import inferred types from Drizzle schema.
UseformatDateToLocal(date)utility fromlib/date-utils.tsto convert dates toYYYY-MM-DDformat before saving to database.
Files:
app/api/version/route.tslib/version.tsapp/admin/page.tsx
🧠 Learnings (1)
📚 Learning: 2026-01-03T02:03:48.622Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-03T02:03:48.622Z
Learning: All user-facing strings must use the `t()` function from `next-intl` for translations. Never use hardcoded English strings in components or API responses.
Applied to files:
app/admin/page.tsx
🧬 Code graph analysis (2)
app/api/version/route.ts (1)
lib/version.ts (2)
getBuildInfo(98-114)buildGitHubUrl(137-156)
app/admin/page.tsx (3)
components/ui/button.tsx (1)
Button(60-60)components/ui/card.tsx (5)
Card(85-85)CardHeader(86-86)CardTitle(88-88)CardDescription(90-90)CardContent(91-91)components/ui/badge.tsx (1)
Badge(36-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-dev
🔇 Additional comments (15)
messages/en.json (1)
648-661: LGTM! Translation keys align with BuildInfo structure.The new
systemInfotranslation keys are well-structured and consistent with the admin UI pattern. They properly support the new build metadata display functionality.messages/it.json (1)
648-661: LGTM! Italian translations are consistent.The Italian translations for the
systemInfosection are well-structured and align with the English version.messages/de.json (1)
648-661: LGTM! German translations are consistent.The German translations for the
systemInfosection are well-structured and align with the other locales.lib/version.ts (6)
4-9: LGTM! BuildInfo interface is well-defined.The interface clearly defines the structure for build metadata, matching the data generated by the Dockerfile.
11-12: LGTM! Caching pattern prevents redundant file reads.The dual caching approach (cached result + in-flight promise) properly handles both the loaded state and concurrent access scenarios.
66-80: LGTM! Fallback chain ensures robustness.The function gracefully degrades from production build info to development defaults, ensuring a valid BuildInfo is always returned.
98-114: LGTM! Async caching pattern is correctly implemented.The function properly handles caching, in-flight promises, and loading, ensuring efficient and race-condition-free access to build info.
120-123: LGTM! Clean wrapper maintains backward compatibility.The function provides a convenient way to get just the version string while leveraging the full BuildInfo infrastructure.
137-156: LGTM! GitHub URL generation enhanced with commit support.The function now properly generates commit URLs for development builds while maintaining backward compatibility with release tag URLs for semantic versions.
app/admin/page.tsx (1)
106-242: LGTM!The System Information Card is well-implemented:
- Proper loading and error states
- External links use
rel="noopener noreferrer"for security- Translation keys are used consistently via
t()(as per learnings)- Conditional rendering for update badges and dev mode is clean
app/api/version/route.ts (5)
2-2: LGTM!Clean import of build utilities and well-defined response interface. The
VersionResponsestructure properly captures all the version metadata fields needed by the frontend.Also applies to: 12-21
86-104: LGTM!The version comparison correctly handles the 'v' prefix and performs proper semver comparison for standard
major.minor.patchversions.
142-145: Previous feedback addressed.The commit hash handling now explicitly checks length before truncating, which addresses the concern from the past review about short strings like "unknown" or "dev".
111-167: LGTM!The GET handler is well-structured:
- Efficient caching with early return when cache is valid
- Proper use of
getBuildInfoandbuildGitHubUrlfrom the shared library- Smart optimization: only fetches latest release for non-dev versions
- Consistent Cache-Control headers on both cached and fresh responses
44-84: Well-implemented with graceful degradation.The
getLatestReleasefunction handles errors gracefully by returningnulland properly manages both in-memory and Next.js fetch caching layers. The optionalGITHUB_TOKENhandling is clean.
Adds build-time metadata (build date, commit SHA/ref) and persists it as a structured build-info file.
Updates CI/Docker to pass and embed BUILD_DATE/COMMIT_SHA/COMMIT_REF, replaces simple .version handling with a .build-info.json reader and cached BuildInfo API, and exposes getBuildInfo/getCurrentVersion.
Updates version endpoint and client hook to return buildDate, short commit, github URL, isDev flag and improved latest-release/update detection and caching.
Removes reliance on branch env var and improves GitHub URL resolution for dev builds, enabling accurate build provenance and more reliable update checks.
Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.