feat(ota): add release selection primitives
This commit is contained in:
parent
90256160e3
commit
862e0e0bc9
|
|
@ -0,0 +1,83 @@
|
|||
import { describe, expect, it } from "vitest"
|
||||
|
||||
import type { OtaRelease } from "../lib/schema"
|
||||
import { compareSemver, selectLatestCompatibleRelease } from "../lib/version"
|
||||
|
||||
function createRelease(overrides: Partial<OtaRelease> = {}): OtaRelease {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
product: "mobile",
|
||||
channel: "production",
|
||||
releaseVersion: "0.4.2",
|
||||
releaseKind: "ota",
|
||||
runtimeVersion: "0.4.1",
|
||||
publishedAt: "2026-04-10T12:00:00Z",
|
||||
git: {
|
||||
tag: "mobile/v0.4.2",
|
||||
commit: "abcdef1234567890",
|
||||
},
|
||||
policy: {
|
||||
storeRequired: false,
|
||||
minSupportedBinaryVersion: "0.4.1",
|
||||
message: null,
|
||||
},
|
||||
platforms: {
|
||||
ios: {
|
||||
launchAsset: {
|
||||
path: "bundles/ios-main.js",
|
||||
sha256: "a".repeat(64),
|
||||
contentType: "application/javascript",
|
||||
},
|
||||
assets: [],
|
||||
},
|
||||
},
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
describe("compareSemver", () => {
|
||||
it("orders plain x.y.z versions numerically", () => {
|
||||
expect(compareSemver("0.4.10", "0.4.2")).toBeGreaterThan(0)
|
||||
expect(compareSemver("1.0.0", "1.0.0")).toBe(0)
|
||||
expect(compareSemver("0.4.2", "0.5.0")).toBeLessThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("selectLatestCompatibleRelease", () => {
|
||||
it("selects the highest OTA release for the same runtime", () => {
|
||||
const result = selectLatestCompatibleRelease(
|
||||
[
|
||||
createRelease({
|
||||
releaseVersion: "0.4.2",
|
||||
git: {
|
||||
tag: "mobile/v0.4.2",
|
||||
commit: "abcdef1234567890",
|
||||
},
|
||||
}),
|
||||
createRelease({
|
||||
releaseVersion: "0.4.3",
|
||||
git: {
|
||||
tag: "mobile/v0.4.3",
|
||||
commit: "bcdef1234567890a",
|
||||
},
|
||||
}),
|
||||
createRelease({
|
||||
releaseVersion: "0.4.4",
|
||||
runtimeVersion: "0.4.3",
|
||||
git: {
|
||||
tag: "mobile/v0.4.4",
|
||||
commit: "cdef1234567890ab",
|
||||
},
|
||||
}),
|
||||
],
|
||||
{
|
||||
product: "mobile",
|
||||
channel: "production",
|
||||
runtimeVersion: "0.4.1",
|
||||
platform: "ios",
|
||||
},
|
||||
)
|
||||
|
||||
expect(result?.releaseVersion).toBe("0.4.3")
|
||||
})
|
||||
})
|
||||
|
|
@ -2,6 +2,9 @@ import { z } from "zod"
|
|||
|
||||
const semver = z.string().regex(/^\d+\.\d+\.\d+$/)
|
||||
const sha256 = z.string().regex(/^[a-f0-9]{64}$/)
|
||||
const otaPlatforms = ["ios", "android", "macos", "windows", "linux"] as const
|
||||
|
||||
export const otaPlatformSchema = z.enum(otaPlatforms)
|
||||
|
||||
const assetSchema = z.object({
|
||||
path: z.string().min(1),
|
||||
|
|
@ -46,3 +49,6 @@ export const otaReleaseSchema = z.object({
|
|||
}),
|
||||
platforms: platformsSchema,
|
||||
})
|
||||
|
||||
export type OtaPlatform = z.infer<typeof otaPlatformSchema>
|
||||
export type OtaRelease = z.infer<typeof otaReleaseSchema>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import type { OtaPlatform, OtaRelease } from "./schema"
|
||||
|
||||
export function compareSemver(left: string, right: string) {
|
||||
const leftParts = left.split(".").map(Number)
|
||||
const rightParts = right.split(".").map(Number)
|
||||
|
||||
for (let index = 0; index < 3; index += 1) {
|
||||
const diff = leftParts[index]! - rightParts[index]!
|
||||
if (diff !== 0) {
|
||||
return diff
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
export function selectLatestCompatibleRelease(
|
||||
releases: OtaRelease[],
|
||||
input: {
|
||||
product: OtaRelease["product"]
|
||||
channel: OtaRelease["channel"]
|
||||
runtimeVersion: OtaRelease["runtimeVersion"]
|
||||
platform: OtaPlatform
|
||||
},
|
||||
) {
|
||||
return (
|
||||
releases
|
||||
.filter((release) => release.product === input.product)
|
||||
.filter((release) => release.channel === input.channel)
|
||||
.filter((release) => release.releaseKind === "ota")
|
||||
.filter((release) => release.runtimeVersion === input.runtimeVersion)
|
||||
.filter((release) => Object.prototype.hasOwnProperty.call(release.platforms, input.platform))
|
||||
.sort((left, right) => compareSemver(right.releaseVersion, left.releaseVersion))[0] ?? null
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue