mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
refactor(server): version logic (#9615)
* refactor(server): version * test: better version and log checks
This commit is contained in:
parent
5f25f28c42
commit
1df7be8436
19 changed files with 404 additions and 509 deletions
|
|
@ -3,8 +3,8 @@ import { OpenAPIObject } from '@nestjs/swagger';
|
|||
import { SchemaObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { SemVer } from 'semver';
|
||||
import { ADDED_IN_PREFIX, DEPRECATED_IN_PREFIX, LIFECYCLE_EXTENSION, NEXT_RELEASE } from 'src/constants';
|
||||
import { Version } from 'src/utils/version';
|
||||
|
||||
const outputPath = resolve(process.cwd(), '../open-api/immich-openapi-specs.json');
|
||||
const spec = JSON.parse(readFileSync(outputPath).toString()) as OpenAPIObject;
|
||||
|
|
@ -69,9 +69,7 @@ const sortedVersions = Object.keys(metadata).sort((a, b) => {
|
|||
return 1;
|
||||
}
|
||||
|
||||
const versionA = Version.fromString(a);
|
||||
const versionB = Version.fromString(b);
|
||||
return versionB.compareTo(versionA);
|
||||
return new SemVer(b).compare(new SemVer(a));
|
||||
});
|
||||
|
||||
for (const version of sortedVersions) {
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
import { Version, VersionType } from 'src/utils/version';
|
||||
|
||||
describe('Version', () => {
|
||||
const tests = [
|
||||
{ this: new Version(0, 0, 1), other: new Version(0, 0, 0), compare: 1, type: VersionType.PATCH },
|
||||
{ this: new Version(0, 1, 0), other: new Version(0, 0, 0), compare: 1, type: VersionType.MINOR },
|
||||
{ this: new Version(1, 0, 0), other: new Version(0, 0, 0), compare: 1, type: VersionType.MAJOR },
|
||||
{ this: new Version(0, 0, 0), other: new Version(0, 0, 1), compare: -1, type: VersionType.PATCH },
|
||||
{ this: new Version(0, 0, 0), other: new Version(0, 1, 0), compare: -1, type: VersionType.MINOR },
|
||||
{ this: new Version(0, 0, 0), other: new Version(1, 0, 0), compare: -1, type: VersionType.MAJOR },
|
||||
{ this: new Version(0, 0, 0), other: new Version(0, 0, 0), compare: 0, type: VersionType.EQUAL },
|
||||
{ this: new Version(0, 0, 1), other: new Version(0, 0, 1), compare: 0, type: VersionType.EQUAL },
|
||||
{ this: new Version(0, 1, 0), other: new Version(0, 1, 0), compare: 0, type: VersionType.EQUAL },
|
||||
{ this: new Version(1, 0, 0), other: new Version(1, 0, 0), compare: 0, type: VersionType.EQUAL },
|
||||
{ this: new Version(1, 0), other: new Version(1, 0, 0), compare: 0, type: VersionType.EQUAL },
|
||||
{ this: new Version(1, 0), other: new Version(1, 0, 1), compare: -1, type: VersionType.PATCH },
|
||||
{ this: new Version(1, 1), other: new Version(1, 0, 1), compare: 1, type: VersionType.MINOR },
|
||||
{ this: new Version(1), other: new Version(1, 0, 0), compare: 0, type: VersionType.EQUAL },
|
||||
{ this: new Version(1), other: new Version(1, 0, 1), compare: -1, type: VersionType.PATCH },
|
||||
];
|
||||
|
||||
describe('isOlderThan', () => {
|
||||
for (const { this: thisVersion, other: otherVersion, compare, type } of tests) {
|
||||
const expected = compare < 0 ? type : VersionType.EQUAL;
|
||||
it(`should return '${expected}' when comparing ${thisVersion} to ${otherVersion}`, () => {
|
||||
expect(thisVersion.isOlderThan(otherVersion)).toEqual(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('isEqual', () => {
|
||||
for (const { this: thisVersion, other: otherVersion, compare } of tests) {
|
||||
const bool = compare === 0;
|
||||
it(`should return ${bool} when comparing ${thisVersion} to ${otherVersion}`, () => {
|
||||
expect(thisVersion.isEqual(otherVersion)).toEqual(bool);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('isNewerThan', () => {
|
||||
for (const { this: thisVersion, other: otherVersion, compare, type } of tests) {
|
||||
const expected = compare > 0 ? type : VersionType.EQUAL;
|
||||
it(`should return ${expected} when comparing ${thisVersion} to ${otherVersion}`, () => {
|
||||
expect(thisVersion.isNewerThan(otherVersion)).toEqual(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('fromString', () => {
|
||||
const tests = [
|
||||
{ scenario: 'leading v', value: 'v1.72.2', expected: new Version(1, 72, 2) },
|
||||
{ scenario: 'uppercase v', value: 'V1.72.2', expected: new Version(1, 72, 2) },
|
||||
{ scenario: 'missing v', value: '1.72.2', expected: new Version(1, 72, 2) },
|
||||
{ scenario: 'large patch', value: '1.72.123', expected: new Version(1, 72, 123) },
|
||||
{ scenario: 'large minor', value: '1.123.0', expected: new Version(1, 123, 0) },
|
||||
{ scenario: 'large major', value: '123.0.0', expected: new Version(123, 0, 0) },
|
||||
{ scenario: 'major bump', value: 'v2.0.0', expected: new Version(2, 0, 0) },
|
||||
{ scenario: 'has dash', value: '14.10-1', expected: new Version(14, 10, 1) },
|
||||
{ scenario: 'missing patch', value: '14.10', expected: new Version(14, 10, 0) },
|
||||
{ scenario: 'only major', value: '14', expected: new Version(14, 0, 0) },
|
||||
];
|
||||
|
||||
for (const { scenario, value, expected } of tests) {
|
||||
it(`should correctly parse ${scenario}`, () => {
|
||||
const actual = Version.fromString(value);
|
||||
expect(actual.major).toEqual(expected.major);
|
||||
expect(actual.minor).toEqual(expected.minor);
|
||||
expect(actual.patch).toEqual(expected.patch);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
export type IVersion = { major: number; minor: number; patch: number };
|
||||
|
||||
export enum VersionType {
|
||||
EQUAL = 0,
|
||||
PATCH = 1,
|
||||
MINOR = 2,
|
||||
MAJOR = 3,
|
||||
}
|
||||
|
||||
export class Version implements IVersion {
|
||||
public readonly types = ['major', 'minor', 'patch'] as const;
|
||||
|
||||
constructor(
|
||||
public major: number,
|
||||
public minor: number = 0,
|
||||
public patch: number = 0,
|
||||
) {}
|
||||
|
||||
toString() {
|
||||
return `${this.major}.${this.minor}.${this.patch}`;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const { major, minor, patch } = this;
|
||||
return { major, minor, patch };
|
||||
}
|
||||
|
||||
static fromString(version: string): Version {
|
||||
const regex = /v?(?<major>\d+)(?:\.(?<minor>\d+))?(?:[.-](?<patch>\d+))?/i;
|
||||
const matchResult = version.match(regex);
|
||||
if (matchResult) {
|
||||
const { major, minor = '0', patch = '0' } = matchResult.groups as { [K in keyof IVersion]: string };
|
||||
return new Version(Number(major), Number(minor), Number(patch));
|
||||
} else {
|
||||
throw new Error(`Invalid version format: ${version}`);
|
||||
}
|
||||
}
|
||||
|
||||
private compare(version: Version): [number, VersionType] {
|
||||
for (const [i, key] of this.types.entries()) {
|
||||
const diff = this[key] - version[key];
|
||||
if (diff !== 0) {
|
||||
return [diff > 0 ? 1 : -1, (VersionType.MAJOR - i) as VersionType];
|
||||
}
|
||||
}
|
||||
|
||||
return [0, VersionType.EQUAL];
|
||||
}
|
||||
|
||||
isOlderThan(version: Version): VersionType {
|
||||
const [bool, type] = this.compare(version);
|
||||
return bool < 0 ? type : VersionType.EQUAL;
|
||||
}
|
||||
|
||||
isEqual(version: Version): boolean {
|
||||
const [bool] = this.compare(version);
|
||||
return bool === 0;
|
||||
}
|
||||
|
||||
isNewerThan(version: Version): VersionType {
|
||||
const [bool, type] = this.compare(version);
|
||||
return bool > 0 ? type : VersionType.EQUAL;
|
||||
}
|
||||
|
||||
compareTo(other: Version) {
|
||||
if (this.isEqual(other)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.isNewerThan(other) ? 1 : -1;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue