From ffc27af3baca2aec6fc615584bb1f896dfba8d2e Mon Sep 17 00:00:00 2001 From: DarkSky <25152247+darkskygit@users.noreply.github.com> Date: Sun, 5 Apr 2026 10:56:05 +0800 Subject: [PATCH] fix(server): update version check (#14784) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #14780 #### PR Dependency Tree * **PR #14784** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) ## Summary by CodeRabbit ## Release Notes * **Bug Fixes** * Improved upgrade availability detection to properly compare semantic versions, including support for prerelease and canary versions. The system now accurately identifies when new versions are available, ensuring users receive timely update notifications. * **Tests** * Added comprehensive unit tests for version comparison and upgrade detection functionality. --- packages/backend/server/package.json | 4 +-- .../server/src/__tests__/version.spec.ts | 9 +++++++ .../server/src/base/utils/client-version.ts | 25 +++++++++++++++++++ .../server/src/core/config/resolver.ts | 4 +-- yarn.lock | 12 ++++----- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/packages/backend/server/package.json b/packages/backend/server/package.json index 41f4a92c33..0cf40d31fe 100644 --- a/packages/backend/server/package.json +++ b/packages/backend/server/package.json @@ -101,7 +101,7 @@ "react-dom": "19.2.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", - "semver": "^7.7.3", + "semver": "^7.7.4", "socket.io": "^4.8.1", "stripe": "^17.7.0", "tldts": "^7.0.19", @@ -128,7 +128,7 @@ "@types/nodemailer": "^7.0.0", "@types/on-headers": "^1.0.3", "@types/react": "^19.0.1", - "@types/semver": "^7.5.8", + "@types/semver": "^7.7.1", "@types/sinon": "^21.0.0", "@types/supertest": "^7.0.0", "ava": "^7.0.0", diff --git a/packages/backend/server/src/__tests__/version.spec.ts b/packages/backend/server/src/__tests__/version.spec.ts index 418bf579bc..15e61907c6 100644 --- a/packages/backend/server/src/__tests__/version.spec.ts +++ b/packages/backend/server/src/__tests__/version.spec.ts @@ -6,6 +6,7 @@ import { AppModule } from '../app.module'; import { CANARY_CLIENT_VERSION_MAX_AGE_DAYS, ConfigFactory, + hasNewerVersion, UseNamedGuard, } from '../base'; import { Public } from '../core/auth/guard'; @@ -249,3 +250,11 @@ test('should reject old canary date version in canary namespace', async t => { env.NAMESPACE = prevNamespace; } }); + +test('should compare release versions for available upgrades', t => { + t.false(hasNewerVersion('0.26.5', '0.26.4')); + t.false(hasNewerVersion('0.26.5', '0.26.5')); + t.true(hasNewerVersion('0.26.5', '0.26.6')); + t.true(hasNewerVersion('0.26.5', '0.26.6-beta.1')); + t.false(hasNewerVersion('0.26.6-beta.2', '0.26.6-beta.1')); +}); diff --git a/packages/backend/server/src/base/utils/client-version.ts b/packages/backend/server/src/base/utils/client-version.ts index 66be07cba9..28b4860ea3 100644 --- a/packages/backend/server/src/base/utils/client-version.ts +++ b/packages/backend/server/src/base/utils/client-version.ts @@ -1,3 +1,5 @@ +import semver from 'semver'; + const DAY_MS = 24 * 60 * 60 * 1000; // Example: 2026.2.6-canary.015 @@ -89,3 +91,26 @@ export function checkCanaryDateClientVersion( normalized: parsed.normalized, }; } + +function normalizeComparableVersion(version: string): string | null { + const canary = parseCanaryDateClientVersion(version); + return semver.valid(canary?.normalized ?? version.trim(), { + loose: true, + }); +} + +export function hasNewerVersion( + currentVersion: string, + nextVersion: string +): boolean { + const current = normalizeComparableVersion(currentVersion); + const next = normalizeComparableVersion(nextVersion); + + if (!current || !next) { + return currentVersion.trim() !== nextVersion.trim(); + } + + return semver.gt(next, current, { + loose: true, + }); +} diff --git a/packages/backend/server/src/core/config/resolver.ts b/packages/backend/server/src/core/config/resolver.ts index 10831d7ce9..2c989615a2 100644 --- a/packages/backend/server/src/core/config/resolver.ts +++ b/packages/backend/server/src/core/config/resolver.ts @@ -12,7 +12,7 @@ import { } from '@nestjs/graphql'; import { GraphQLJSON, GraphQLJSONObject } from 'graphql-scalars'; -import { Config, URLHelper } from '../../base'; +import { Config, hasNewerVersion, URLHelper } from '../../base'; import { Namespace } from '../../env'; import { Feature, type WorkspaceFeatureName } from '../../models'; import { CurrentUser, Public } from '../auth'; @@ -143,7 +143,7 @@ export class ServerConfigResolver { }>; const latest = releases.at(0); - if (!latest || latest.name === env.version) { + if (!latest || !hasNewerVersion(env.version, latest.name)) { return null; } diff --git a/yarn.lock b/yarn.lock index 2c35bccf88..600fd5ac05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -977,7 +977,7 @@ __metadata: "@types/nodemailer": "npm:^7.0.0" "@types/on-headers": "npm:^1.0.3" "@types/react": "npm:^19.0.1" - "@types/semver": "npm:^7.5.8" + "@types/semver": "npm:^7.7.1" "@types/sinon": "npm:^21.0.0" "@types/supertest": "npm:^7.0.0" ava: "npm:^7.0.0" @@ -1019,7 +1019,7 @@ __metadata: react-email: "npm:^4.3.2" reflect-metadata: "npm:^0.2.2" rxjs: "npm:^7.8.2" - semver: "npm:^7.7.3" + semver: "npm:^7.7.4" sinon: "npm:^21.0.1" socket.io: "npm:^4.8.1" socket.io-client: "npm:^4.8.3" @@ -16451,10 +16451,10 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7, @types/semver@npm:^7.5.8": - version: 7.7.0 - resolution: "@types/semver@npm:7.7.0" - checksum: 10/ee4514c6c852b1c38f951239db02f9edeea39f5310fad9396a00b51efa2a2d96b3dfca1ae84c88181ea5b7157c57d32d7ef94edacee36fbf975546396b85ba5b +"@types/semver@npm:^7, @types/semver@npm:^7.7.1": + version: 7.7.1 + resolution: "@types/semver@npm:7.7.1" + checksum: 10/8f09e7e6ca3ded67d78ba7a8f7535c8d9cf8ced83c52e7f3ac3c281fe8c689c3fe475d199d94390dc04fc681d51f2358b430bb7b2e21c62de24f2bee2c719068 languageName: node linkType: hard