fix: normalize version number using semver.coerce in admin panel version info (#13348)

### Summary

This PR fixes an inconsistency in the display of application versions in
the Admin Panel. Previously, the "Current version" was displayed with a
"v" prefix (e.g., `v1.1.1`), while the "Latest version" was displayed
without it (e.g., `1.1.1`).

### Problem

The inconsistency originated from two different data sources being
handled differently in the backend:
- `currentVersion` was read directly from the `APP_VERSION`
configuration variable, which includes the "v" prefix.
- `latestVersion` was fetched from the Docker Hub API, and the
`semver.coerce()` function was used to parse it, which strips the "v"
prefix.

![Jb ScreenShot 2025-07-22 at 16 47
05@2x](https://github.com/user-attachments/assets/b6243d03-2730-4958-8ad9-68f7e461bbe2)


### Solution

The fix addresses the root cause in the [AdminPanelService] on the
backend. The `semver.coerce()` function is now also applied to the
`currentVersion`.

This ensures that both version numbers are normalized at the data
source, providing a consistent format before being sent to the frontend.
This is a cleaner approach than manipulating the data on the client
side.

**Before:**
- Current version: `v1.1.1`
- Latest version: `1.1.1`

**After:**
- Current version: `1.1.1`
- Latest version: `1.1.1`

---------

Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
Jean-Baptiste Ronssin
2025-07-23 17:44:09 +02:00
committed by GitHub
parent ae6adb3a63
commit f439a6cd9e

View File

@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import axios from 'axios'; import axios from 'axios';
import semver from 'semver'; import semver from 'semver';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import * as z from 'zod';
import { ConfigVariable } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto'; import { ConfigVariable } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
import { ConfigVariablesGroupData } from 'src/engine/core-modules/admin-panel/dtos/config-variables-group.dto'; import { ConfigVariablesGroupData } from 'src/engine/core-modules/admin-panel/dtos/config-variables-group.dto';
@ -199,23 +200,25 @@ export class AdminPanelService {
const currentVersion = this.twentyConfigService.get('APP_VERSION'); const currentVersion = this.twentyConfigService.get('APP_VERSION');
try { try {
const response = await axios.get( const rawResponse = await axios.get<unknown>(
'https://hub.docker.com/v2/repositories/twentycrm/twenty/tags?page_size=100', 'https://hub.docker.com/v2/repositories/twentycrm/twenty/tags?page_size=100',
); );
const response = z
.object({
data: z.object({
results: z.array(z.object({ name: z.string() })),
}),
})
.parse(rawResponse);
const versions = response.data.results const versions = response.data.results
// @ts-expect-error legacy noImplicitAny .map((tag) => tag.name)
.filter((tag) => tag && tag.name !== 'latest') .filter((name) => name !== 'latest' && semver.valid(name));
// @ts-expect-error legacy noImplicitAny
.map((tag) => semver.coerce(tag.name)?.version)
// @ts-expect-error legacy noImplicitAny
.filter((version) => version !== undefined);
if (versions.length === 0) { if (versions.length === 0) {
return { currentVersion, latestVersion: 'latest' }; return { currentVersion, latestVersion: 'latest' };
} }
// @ts-expect-error legacy noImplicitAny
versions.sort((a, b) => semver.compare(b, a)); versions.sort((a, b) => semver.compare(b, a));
const latestVersion = versions[0]; const latestVersion = versions[0];