🎨 Added separate Server and Admin build versions to About dialog#26769
🎨 Added separate Server and Admin build versions to About dialog#26769
Conversation
WalkthroughCI now computes a build version from ghost/core/package.json and the commit SHA for non-tag builds, exporting it as GHOST_BUILD_VERSION and passing it into Docker builds as a build-arg. Dockerfile exposes GHOST_BUILD_VERSION into the runtime environment. Server public-config prefers GHOST_BUILD_VERSION when set. Vite exposes GHOST_BUILD_VERSION to the admin bundle. The admin About UI shows Server and Admin versions when available. The GitHub releases link utility gained parsing for build-metadata formats (epoch.sha, sha, full SHA) and tests were added to cover these cases. 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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.
🧹 Nitpick comments (1)
apps/admin-x-settings/test/unit/utils/link-to-github-releases.test.ts (1)
35-49: Cover the non-SHA build-metadata fallback as well.These additions pin the new positive SHA cases, but the utility now also has a new fallback path for build metadata that is neither
moyanor a trailing hex SHA. One regression test like6.21.2+build.1 -> releases/tag/v6.21.2would protect the tagged/self-hosted path too.💡 Suggested test
it('handles full 40-char sha in build metadata', function () { const sha = 'a'.repeat(40); const link = linkToGitHubReleases(`6.21.2+1710072000.${sha}`); assert.equal(link, `https://github.com/TryGhost/Ghost/commit/${sha}`); }); + + it('falls back to the release tag for non-SHA build metadata', function () { + const link = linkToGitHubReleases('6.21.2+build.1'); + assert.equal(link, 'https://github.com/TryGhost/Ghost/releases/tag/v6.21.2'); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/admin-x-settings/test/unit/utils/link-to-github-releases.test.ts` around lines 35 - 49, Add a regression test for linkToGitHubReleases that covers the non-SHA build-metadata fallback path: call linkToGitHubReleases with a version like "6.21.2+build.1" (or similar non-hex metadata) and assert it returns the releases/tag/v6.21.2 URL; update the test suite in link-to-github-releases.test.ts to include this case alongside the existing SHA-positive tests so the tagged/self-hosted fallback in linkToGitHubReleases is verified.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@apps/admin-x-settings/test/unit/utils/link-to-github-releases.test.ts`:
- Around line 35-49: Add a regression test for linkToGitHubReleases that covers
the non-SHA build-metadata fallback path: call linkToGitHubReleases with a
version like "6.21.2+build.1" (or similar non-hex metadata) and assert it
returns the releases/tag/v6.21.2 URL; update the test suite in
link-to-github-releases.test.ts to include this case alongside the existing
SHA-positive tests so the tagged/self-hosted fallback in linkToGitHubReleases is
verified.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7ad4adbb-edd2-4e06-b35b-156467261049
📒 Files selected for processing (7)
.github/workflows/ci.ymlDockerfile.productionapps/admin-x-framework/src/vite.tsapps/admin-x-settings/src/components/settings/general/about.tsxapps/admin-x-settings/src/utils/link-to-github-releases.tsapps/admin-x-settings/test/unit/utils/link-to-github-releases.test.tsghost/core/core/server/services/public-config/config.js
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #26769 +/- ##
==========================================
- Coverage 73.20% 73.19% -0.02%
==========================================
Files 1534 1534
Lines 120966 121014 +48
Branches 14630 14627 -3
==========================================
+ Hits 88559 88571 +12
- Misses 31393 31414 +21
- Partials 1014 1029 +15
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
🤖 Velo CI Failure AnalysisClassification: 🟠 SOFT FAIL
|
43e812a to
ff57916
Compare
On Ghost(Pro), where CI sets GHOST_BUILD_VERSION and VITE_ADMIN_BUILD_VERSION, the About dialog now shows two lines — Server and Admin — each linking to their respective commits on GitHub. For self-hosters (or tagged releases where neither env var is set), the dialog continues to show a single "Version" line exactly as before. The server version is injected via a Docker build arg baked into the container as an ENV var, read at runtime by the public-config endpoint. The admin version is baked into the Vite bundle at build time via a define constant. The link-to-github-releases utility was updated to handle the new semver+epoch.sha and semver+sha formats alongside the existing legacy formats.
ff57916 to
fff1feb
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/admin-x-settings/src/utils/link-to-github-releases.ts`:
- Around line 33-53: The fallback to a releases/tag URL must only be used for
valid semver strings: in link-to-github-releases.ts, after computing
versionWithoutBuild and before returning the tag URL, call
semverParse(versionWithoutBuild, {includePrerelease: true}) (same helper used
earlier) and if it returns null/undefined, return '' instead of constructing
`https://github.com/TryGhost/Ghost/releases/tag/v${versionWithoutBuild}`; keep
the existing prerelease/commit-hash logic (the current try block with
semverParse and prerelease handling) but add this explicit semver validity guard
so raw SHAs or non-semver values produce an empty string.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3f2c7e0b-c020-40b9-9798-f78da64f50bd
📒 Files selected for processing (7)
.github/workflows/ci.ymlDockerfile.productionapps/admin-x-framework/src/vite.tsapps/admin-x-settings/src/components/settings/general/about.tsxapps/admin-x-settings/src/utils/link-to-github-releases.tsapps/admin-x-settings/test/unit/utils/link-to-github-releases.test.tsghost/core/core/server/services/public-config/config.js
🚧 Files skipped from review as they are similar to previous changes (6)
- apps/admin-x-settings/src/components/settings/general/about.tsx
- apps/admin-x-settings/test/unit/utils/link-to-github-releases.test.ts
- .github/workflows/ci.yml
- Dockerfile.production
- apps/admin-x-framework/src/vite.ts
- ghost/core/core/server/services/public-config/config.js
| try { | ||
| const semverVersion = semverParse(cleanedVersion, {includePrerelease: true} as any); | ||
| const semverVersion = semverParse(versionWithoutBuild, {includePrerelease: true} as any); | ||
| const prerelease = semverVersion?.prerelease; | ||
|
|
||
| if (prerelease && prerelease?.length > 0) { | ||
| if (prerelease && prerelease.length > 0) { | ||
| const splitPrerelease = String(prerelease[0]).split('-'); | ||
| const commitHash = splitPrerelease[1]; | ||
|
|
||
| if (!commitHash || !commitHash.startsWith('g')) { | ||
| return ''; | ||
| if (commitHash?.startsWith('g')) { | ||
| return `https://github.com/TryGhost/Ghost/commit/${commitHash.slice(1)}`; | ||
| } | ||
|
|
||
| const commitHashWithoutG = commitHash.slice(1); | ||
|
|
||
| return `https://github.com/TryGhost/Ghost/commit/${commitHashWithoutG}`; | ||
| // Has pre-release but no recognizable commit hash | ||
| return ''; | ||
| } | ||
|
|
||
| return `https://github.com/TryGhost/Ghost/releases/tag/v${cleanedVersion}`; | ||
| } catch { | ||
| return ''; | ||
| } | ||
|
|
||
| // Plain semver (with or without +moya) → release tag | ||
| return `https://github.com/TryGhost/Ghost/releases/tag/v${versionWithoutBuild}`; |
There was a problem hiding this comment.
Guard the release-tag fallback for unsupported version strings.
apps/admin-x-framework/src/vite.ts:47-51 passes GHOST_BUILD_VERSION through verbatim, and apps/admin-x-settings/src/components/settings/general/about.tsx:9-17 will render any non-empty URL this helper returns. With the current fallback, a raw SHA or other non-semver value still becomes releases/tag/v..., which gives the About dialog a broken link instead of plain text. Return '' when versionWithoutBuild is not a valid semver before constructing the tag URL.
🔧 Proposed fix
try {
const semverVersion = semverParse(versionWithoutBuild, {includePrerelease: true} as any);
- const prerelease = semverVersion?.prerelease;
+ if (!semverVersion) {
+ return '';
+ }
+
+ const prerelease = semverVersion.prerelease;
if (prerelease && prerelease.length > 0) {
const splitPrerelease = String(prerelease[0]).split('-');
const commitHash = splitPrerelease[1];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| const semverVersion = semverParse(cleanedVersion, {includePrerelease: true} as any); | |
| const semverVersion = semverParse(versionWithoutBuild, {includePrerelease: true} as any); | |
| const prerelease = semverVersion?.prerelease; | |
| if (prerelease && prerelease?.length > 0) { | |
| if (prerelease && prerelease.length > 0) { | |
| const splitPrerelease = String(prerelease[0]).split('-'); | |
| const commitHash = splitPrerelease[1]; | |
| if (!commitHash || !commitHash.startsWith('g')) { | |
| return ''; | |
| if (commitHash?.startsWith('g')) { | |
| return `https://github.com/TryGhost/Ghost/commit/${commitHash.slice(1)}`; | |
| } | |
| const commitHashWithoutG = commitHash.slice(1); | |
| return `https://github.com/TryGhost/Ghost/commit/${commitHashWithoutG}`; | |
| // Has pre-release but no recognizable commit hash | |
| return ''; | |
| } | |
| return `https://github.com/TryGhost/Ghost/releases/tag/v${cleanedVersion}`; | |
| } catch { | |
| return ''; | |
| } | |
| // Plain semver (with or without +moya) → release tag | |
| return `https://github.com/TryGhost/Ghost/releases/tag/v${versionWithoutBuild}`; | |
| try { | |
| const semverVersion = semverParse(versionWithoutBuild, {includePrerelease: true} as any); | |
| if (!semverVersion) { | |
| return ''; | |
| } | |
| const prerelease = semverVersion.prerelease; | |
| if (prerelease && prerelease.length > 0) { | |
| const splitPrerelease = String(prerelease[0]).split('-'); | |
| const commitHash = splitPrerelease[1]; | |
| if (commitHash?.startsWith('g')) { | |
| return `https://github.com/TryGhost/Ghost/commit/${commitHash.slice(1)}`; | |
| } | |
| // Has pre-release but no recognizable commit hash | |
| return ''; | |
| } | |
| } catch { | |
| return ''; | |
| } | |
| // Plain semver (with or without +moya) → release tag | |
| return `https://github.com/TryGhost/Ghost/releases/tag/v${versionWithoutBuild}`; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/admin-x-settings/src/utils/link-to-github-releases.ts` around lines 33 -
53, The fallback to a releases/tag URL must only be used for valid semver
strings: in link-to-github-releases.ts, after computing versionWithoutBuild and
before returning the tag URL, call semverParse(versionWithoutBuild,
{includePrerelease: true}) (same helper used earlier) and if it returns
null/undefined, return '' instead of constructing
`https://github.com/TryGhost/Ghost/releases/tag/v${versionWithoutBuild}`; keep
the existing prerelease/commit-hash logic (the current try block with
semverParse and prerelease handling) but add this explicit semver validity guard
so raw SHAs or non-semver values produce an empty string.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@ghost/core/test/unit/server/services/public-config/config.test.js`:
- Around line 48-65: The tests set process.env.GHOST_BUILD_VERSION inside the it
blocks but manually delete it at the end of each test, which is fragile; add
cleanup in the existing afterEach hook to delete process.env.GHOST_BUILD_VERSION
so the environment is reset even when a test fails, then remove the manual
delete lines from the two tests that call getConfigProperties() (the "should
return GHOST_BUILD_VERSION as version when set" and "should return package
version when GHOST_BUILD_VERSION is not set" tests) so they rely on the
centralized afterEach cleanup.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3418adb2-00a3-4b46-bdb7-8ba3823e768e
📒 Files selected for processing (8)
.github/workflows/ci.ymlDockerfile.productionapps/admin-x-framework/src/vite.tsapps/admin-x-settings/src/components/settings/general/about.tsxapps/admin-x-settings/src/utils/link-to-github-releases.tsapps/admin-x-settings/test/unit/utils/link-to-github-releases.test.tsghost/core/core/server/services/public-config/config.jsghost/core/test/unit/server/services/public-config/config.test.js
🚧 Files skipped from review as they are similar to previous changes (4)
- ghost/core/core/server/services/public-config/config.js
- Dockerfile.production
- apps/admin-x-framework/src/vite.ts
- apps/admin-x-settings/src/utils/link-to-github-releases.ts
| it('should return GHOST_BUILD_VERSION as version when set', function () { | ||
| process.env.GHOST_BUILD_VERSION = '6.21.2+abc1234'; | ||
|
|
||
| const configProperties = getConfigProperties(); | ||
|
|
||
| assert.equal(configProperties.version, '6.21.2+abc1234'); | ||
|
|
||
| delete process.env.GHOST_BUILD_VERSION; | ||
| }); | ||
|
|
||
| it('should return package version when GHOST_BUILD_VERSION is not set', function () { | ||
| delete process.env.GHOST_BUILD_VERSION; | ||
|
|
||
| const configProperties = getConfigProperties(); | ||
|
|
||
| assert.match(configProperties.version, /^\d+\.\d+\.\d+/); | ||
| assert.notEqual(configProperties.version, '6.21.2+abc1234'); | ||
| }); |
There was a problem hiding this comment.
Improve test isolation by cleaning up env var in afterEach.
The current pattern of manually deleting process.env.GHOST_BUILD_VERSION at the end of the test is fragile. If the test fails before reaching the cleanup line, the env var remains set and could pollute subsequent tests.
Consider moving the cleanup to the existing afterEach hook to ensure proper isolation regardless of test outcome:
Proposed fix
afterEach(async function () {
await configUtils.restore();
sinon.restore();
+ delete process.env.GHOST_BUILD_VERSION;
});Then simplify the tests:
it('should return GHOST_BUILD_VERSION as version when set', function () {
process.env.GHOST_BUILD_VERSION = '6.21.2+abc1234';
const configProperties = getConfigProperties();
assert.equal(configProperties.version, '6.21.2+abc1234');
-
- delete process.env.GHOST_BUILD_VERSION;
});
it('should return package version when GHOST_BUILD_VERSION is not set', function () {
- delete process.env.GHOST_BUILD_VERSION;
-
const configProperties = getConfigProperties();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ghost/core/test/unit/server/services/public-config/config.test.js` around
lines 48 - 65, The tests set process.env.GHOST_BUILD_VERSION inside the it
blocks but manually delete it at the end of each test, which is fragile; add
cleanup in the existing afterEach hook to delete process.env.GHOST_BUILD_VERSION
so the environment is reset even when a test fails, then remove the manual
delete lines from the two tests that call getConfigProperties() (the "should
return GHOST_BUILD_VERSION as version when set" and "should return package
version when GHOST_BUILD_VERSION is not set" tests) so they rely on the
centralized afterEach cleanup.
On Ghost(Pro), the About dialog currently shows a single version string that doesn't distinguish between the server container and the admin client — both ship independently and can be at different commits. This makes it harder to debug issues or verify which code is actually running.
This change introduces two new optional build-time inputs:
GHOST_BUILD_VERSION(a Docker build arg baked as an ENV var, read by the public-config endpoint at runtime) andVITE_ADMIN_BUILD_VERSION(a Vite define constant baked into the admin bundle). When both are set — as they will be on Ghost(Pro) CI builds — the About dialog shows separate "Server" and "Admin" lines, each linking to their respective GitHub commit. When neither is set (self-hosted installs, tagged releases), the dialog continues to show a single "Version" line pointing at the release tag, identical to today's behavior.The
link-to-github-releasesutility was extended to handle the newsemver+epoch.sha(server) andsemver+sha(admin) formats, alongside the existing legacy+moyaand git-describe formats. New test cases cover all format variations.