fix(server): update version check (#14784)

fix #14780


#### PR Dependency Tree


* **PR #14784** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## 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.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-04-05 10:56:05 +08:00
committed by GitHub
parent d975bf46fb
commit ffc27af3ba
5 changed files with 44 additions and 10 deletions

View File

@@ -101,7 +101,7 @@
"react-dom": "19.2.1", "react-dom": "19.2.1",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"semver": "^7.7.3", "semver": "^7.7.4",
"socket.io": "^4.8.1", "socket.io": "^4.8.1",
"stripe": "^17.7.0", "stripe": "^17.7.0",
"tldts": "^7.0.19", "tldts": "^7.0.19",
@@ -128,7 +128,7 @@
"@types/nodemailer": "^7.0.0", "@types/nodemailer": "^7.0.0",
"@types/on-headers": "^1.0.3", "@types/on-headers": "^1.0.3",
"@types/react": "^19.0.1", "@types/react": "^19.0.1",
"@types/semver": "^7.5.8", "@types/semver": "^7.7.1",
"@types/sinon": "^21.0.0", "@types/sinon": "^21.0.0",
"@types/supertest": "^7.0.0", "@types/supertest": "^7.0.0",
"ava": "^7.0.0", "ava": "^7.0.0",

View File

@@ -6,6 +6,7 @@ import { AppModule } from '../app.module';
import { import {
CANARY_CLIENT_VERSION_MAX_AGE_DAYS, CANARY_CLIENT_VERSION_MAX_AGE_DAYS,
ConfigFactory, ConfigFactory,
hasNewerVersion,
UseNamedGuard, UseNamedGuard,
} from '../base'; } from '../base';
import { Public } from '../core/auth/guard'; 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; 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'));
});

View File

@@ -1,3 +1,5 @@
import semver from 'semver';
const DAY_MS = 24 * 60 * 60 * 1000; const DAY_MS = 24 * 60 * 60 * 1000;
// Example: 2026.2.6-canary.015 // Example: 2026.2.6-canary.015
@@ -89,3 +91,26 @@ export function checkCanaryDateClientVersion(
normalized: parsed.normalized, 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,
});
}

View File

@@ -12,7 +12,7 @@ import {
} from '@nestjs/graphql'; } from '@nestjs/graphql';
import { GraphQLJSON, GraphQLJSONObject } from 'graphql-scalars'; import { GraphQLJSON, GraphQLJSONObject } from 'graphql-scalars';
import { Config, URLHelper } from '../../base'; import { Config, hasNewerVersion, URLHelper } from '../../base';
import { Namespace } from '../../env'; import { Namespace } from '../../env';
import { Feature, type WorkspaceFeatureName } from '../../models'; import { Feature, type WorkspaceFeatureName } from '../../models';
import { CurrentUser, Public } from '../auth'; import { CurrentUser, Public } from '../auth';
@@ -143,7 +143,7 @@ export class ServerConfigResolver {
}>; }>;
const latest = releases.at(0); const latest = releases.at(0);
if (!latest || latest.name === env.version) { if (!latest || !hasNewerVersion(env.version, latest.name)) {
return null; return null;
} }

View File

@@ -977,7 +977,7 @@ __metadata:
"@types/nodemailer": "npm:^7.0.0" "@types/nodemailer": "npm:^7.0.0"
"@types/on-headers": "npm:^1.0.3" "@types/on-headers": "npm:^1.0.3"
"@types/react": "npm:^19.0.1" "@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/sinon": "npm:^21.0.0"
"@types/supertest": "npm:^7.0.0" "@types/supertest": "npm:^7.0.0"
ava: "npm:^7.0.0" ava: "npm:^7.0.0"
@@ -1019,7 +1019,7 @@ __metadata:
react-email: "npm:^4.3.2" react-email: "npm:^4.3.2"
reflect-metadata: "npm:^0.2.2" reflect-metadata: "npm:^0.2.2"
rxjs: "npm:^7.8.2" rxjs: "npm:^7.8.2"
semver: "npm:^7.7.3" semver: "npm:^7.7.4"
sinon: "npm:^21.0.1" sinon: "npm:^21.0.1"
socket.io: "npm:^4.8.1" socket.io: "npm:^4.8.1"
socket.io-client: "npm:^4.8.3" socket.io-client: "npm:^4.8.3"
@@ -16451,10 +16451,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/semver@npm:^7, @types/semver@npm:^7.5.8": "@types/semver@npm:^7, @types/semver@npm:^7.7.1":
version: 7.7.0 version: 7.7.1
resolution: "@types/semver@npm:7.7.0" resolution: "@types/semver@npm:7.7.1"
checksum: 10/ee4514c6c852b1c38f951239db02f9edeea39f5310fad9396a00b51efa2a2d96b3dfca1ae84c88181ea5b7157c57d32d7ef94edacee36fbf975546396b85ba5b checksum: 10/8f09e7e6ca3ded67d78ba7a8f7535c8d9cf8ced83c52e7f3ac3c281fe8c689c3fe475d199d94390dc04fc681d51f2358b430bb7b2e21c62de24f2bee2c719068
languageName: node languageName: node
linkType: hard linkType: hard