Skip to content

Commit 625dde3

Browse files
Merge pull request #1542 from mittwald/fix/sort-versions-internal
fix: when sorting version numbers, only sort by internal version numbers
2 parents 17c3f7e + 7f6b846 commit 625dde3

File tree

3 files changed

+62
-21
lines changed

3 files changed

+62
-21
lines changed

src/commands/app/dependency/update.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import { assertStatus } from "@mittwald/api-client-commons";
99
import { ReactNode } from "react";
1010
import type { MittwaldAPIV2 } from "@mittwald/api-client";
1111
import { Success } from "../../../rendering/react/components/Success.js";
12-
import { Range, SemVer } from "semver";
12+
import { Range } from "semver";
1313
import { ProcessRenderer } from "../../../rendering/process/process.js";
14+
import { compareVersionsBy } from "../../../lib/resources/app/versions.js";
1415

1516
type AppSystemSoftwareUpdatePolicy =
1617
MittwaldAPIV2.Components.Schemas.AppSystemSoftwareUpdatePolicy;
@@ -140,7 +141,7 @@ export default class Update extends ExecRenderBaseCommand<typeof Update, void> {
140141
assertStatus(r, 204);
141142
});
142143

143-
process.complete(
144+
await process.complete(
144145
<Success>
145146
The dependencies of this app were successfully updated!
146147
</Success>,
@@ -167,13 +168,7 @@ export default class Update extends ExecRenderBaseCommand<typeof Update, void> {
167168
},
168169
);
169170

170-
versions.sort((a, b) => {
171-
return (
172-
new SemVer(a.externalVersion).compare(new SemVer(b.externalVersion)) *
173-
-1
174-
);
175-
});
176-
171+
versions.sort(compareVersionsBy("internal"));
177172
return versions;
178173
}
179174

src/commands/app/dependency/versions.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
SorterFunction,
66
} from "../../../lib/basecommands/ListBaseCommand.js";
77
import { ListColumns } from "../../../rendering/formatter/ListFormatter.js";
8-
import { SemVer } from "semver";
98
import { Args } from "@oclif/core";
9+
import { compareVersionsBy } from "../../../lib/resources/app/versions.js";
1010

1111
type ResponseItem = Simplify<
1212
MittwaldAPIV2.Paths.V2SystemSoftwaresSystemSoftwareIdVersions.Get.Responses.$200.Content.ApplicationJson[number]
@@ -32,8 +32,7 @@ export class Versions extends ListBaseCommand<
3232
...ListBaseCommand.baseFlags,
3333
};
3434

35-
sorter: SorterFunction<ResponseItem> = (a, b) =>
36-
new SemVer(a.externalVersion).compare(b.externalVersion);
35+
sorter: SorterFunction<ResponseItem> = compareVersionsBy("internal");
3736

3837
public async getData(): Promise<Response> {
3938
const systemSoftwareName = this.args["systemsoftware"];
@@ -49,9 +48,9 @@ export class Versions extends ListBaseCommand<
4948
throw new Error(`system software ${systemSoftwareName} not found`);
5049
}
5150

52-
return await this.apiClient.app.listSystemsoftwareversions({
51+
return this.apiClient.app.listSystemsoftwareversions({
5352
systemSoftwareId: systemSoftware.id,
54-
} as Parameters<typeof this.apiClient.app.listSystemsoftwareversions>[0]);
53+
});
5554
}
5655

5756
protected getColumns(data: ResponseItem[]): ListColumns<ResponseItem> {

src/lib/resources/app/versions.tsx

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { MittwaldAPIV2, MittwaldAPIV2Client } from "@mittwald/api-client";
22
import { assertStatus } from "@mittwald/api-client-commons";
3-
import { gt } from "semver";
3+
import { coerce, gt } from "semver";
44
import { ProcessRenderer } from "../../../rendering/process/process.js";
55
import { getAppInstallationFromUuid, getAppNameFromUuid } from "./uuid.js";
66
import { compare } from "semver";
77

88
type AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion;
99
type AppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion;
1010

11+
type ObjectWithVersions = {
12+
internalVersion: string;
13+
externalVersion: string;
14+
};
15+
1116
export async function normalizeToAppVersionUuid(
1217
apiClient: MittwaldAPIV2Client,
1318
version: string,
@@ -142,10 +147,52 @@ export async function getAppVersionUuidFromAppVersion(
142147
);
143148
}
144149

145-
export function sortArrayByExternalVersion(
146-
versions: AppVersion[],
147-
): AppVersion[] {
148-
return versions.sort((a: AppVersion, b: AppVersion) =>
149-
compare(b.externalVersion, a.externalVersion),
150-
);
150+
export function sortArrayByExternalVersion<T extends ObjectWithVersions>(
151+
versions: T[],
152+
): T[] {
153+
return versions.sort(compareVersionsBy("external"));
154+
}
155+
156+
export function sortArrayByInternalVersion<T extends ObjectWithVersions>(
157+
versions: T[],
158+
): T[] {
159+
return versions.sort(compareVersionsBy("internal"));
160+
}
161+
162+
export function compareVersionsBy<T extends ObjectWithVersions>(
163+
field: "internal" | "external",
164+
): (a: T, b: T) => -1 | 0 | 1 {
165+
const fullField = `${field}Version` as const;
166+
return (a, b) => {
167+
const aCoerced = coerce(a[fullField]);
168+
const bCoerced = coerce(b[fullField]);
169+
170+
if (!aCoerced || !bCoerced) {
171+
return naiveVersionCompare(a.internalVersion, b.internalVersion);
172+
}
173+
174+
return compare(aCoerced, bCoerced);
175+
};
176+
}
177+
178+
/**
179+
* A naive version comparison function that compares version strings in the
180+
* format "x.y.z". This function does not handle pre-release or build metadata.
181+
*/
182+
function naiveVersionCompare(a: string, b: string): -1 | 0 | 1 {
183+
const aParts = a.split(".").map((part) => parseInt(part, 10));
184+
const bParts = b.split(".").map((part) => parseInt(part, 10));
185+
186+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
187+
const aPart = aParts[i] || 0;
188+
const bPart = bParts[i] || 0;
189+
190+
if (aPart > bPart) {
191+
return 1;
192+
}
193+
if (aPart < bPart) {
194+
return -1;
195+
}
196+
}
197+
return 0;
151198
}

0 commit comments

Comments
 (0)