diff --git a/src/services/versions/v1/index.ts b/src/services/versions/v1/index.ts
index d5fbd38..1f089a0 100644
--- a/src/services/versions/v1/index.ts
+++ b/src/services/versions/v1/index.ts
@@ -1,6 +1,5 @@
import { bearerAuth } from "hono/bearer-auth";
import { Hono, type Context, type Handler } from "hono";
-import { cache } from "hono/cache";
import { cors } from "hono/cors";
import { etag } from "hono/etag";
import { prettyJSON } from "hono/pretty-json";
@@ -15,7 +14,6 @@ import {
import { Releases, ReleaseDetails } from "./interfaces";
import { getPlatform } from "../../../lib/middleware";
import { ICache } from "../../../lib/interfaces";
-import { publicCacheKey } from "../../../lib/cache";
import { logError, logWarn, logInfo } from "../../../lib/logger";
import {
GitHubError,
@@ -28,7 +26,6 @@ import {
} from "../../../lib/github-errors";
const RELEASE_CACHE_KEY = "gh-fossbilling-releases";
-const RELEASES_CACHE_NAME = "versions-api-v1";
const RELEASES_CACHE_CONTROL = "max-age: 86400";
const RELEASE_CACHE_TTL = 86400;
const RELEASES_URL =
@@ -74,11 +71,10 @@ function registerCachedRoute
(
) {
return versionsV1.get(
path,
- cache({
- cacheName: RELEASES_CACHE_NAME,
- cacheControl: RELEASES_CACHE_CONTROL,
- keyGenerator: publicCacheKey
- }),
+ async (c, next) => {
+ c.header("Cache-Control", RELEASES_CACHE_CONTROL);
+ return next();
+ },
etag(),
prettyJSON(),
handler
@@ -125,6 +121,23 @@ function buildSuccessResponse(
};
}
+interface ReleaseAsset {
+ name: string;
+ browser_download_url: string;
+ size: number;
+}
+
+function getReleaseZipAsset(
+ assets: ReleaseAsset[],
+ tag: string
+): ReleaseAsset | undefined {
+ return assets.find(
+ (asset) =>
+ asset.name === "FOSSBilling.zip" ||
+ asset.name === `FOSSBilling-${tag}.zip`
+ );
+}
+
registerCachedRoute("/", async (c) => {
const result = await loadReleases(c);
const releases = result.releases;
@@ -387,9 +400,7 @@ export async function getReleases(
return null;
}
- const zipAsset = release.assets.find(
- (asset) => asset.name === "FOSSBilling.zip"
- );
+ const zipAsset = getReleaseZipAsset(release.assets, tag);
if (!zipAsset) {
return null;
}
diff --git a/test/services/versions/v1/index.test.ts b/test/services/versions/v1/index.test.ts
index f90947d..38d0482 100644
--- a/test/services/versions/v1/index.test.ts
+++ b/test/services/versions/v1/index.test.ts
@@ -98,6 +98,54 @@ describe("Versions API v1", () => {
}
});
+ it("should include releases with versioned zip asset names", async () => {
+ (vi.mocked(ghRequest) as MockGitHubRequest).mockImplementation(
+ async (route: string) => {
+ if (route === "GET /repos/{owner}/{repo}/releases") {
+ return {
+ data: [
+ ...mockGitHubReleases,
+ {
+ id: 1005,
+ tag_name: "0.8.0",
+ name: "0.8.0",
+ published_at: "2026-05-28T21:12:54Z",
+ prerelease: false,
+ body: "## 0.8.0\n- New release",
+ assets: [
+ {
+ name: "FOSSBilling-0.8.0.zip",
+ browser_download_url:
+ "https://github.com/FOSSBilling/FOSSBilling/releases/download/0.8.0/FOSSBilling-0.8.0.zip",
+ size: 2048000
+ }
+ ]
+ }
+ ]
+ };
+ }
+ if (route === "GET /repos/{owner}/{repo}/contents/{path}{?ref}") {
+ const content = btoa(JSON.stringify(mockComposerJson));
+ return { data: { content } };
+ }
+ throw new Error("Unexpected route");
+ }
+ );
+
+ const ctx = createExecutionContext();
+ const response = await app.request("/versions/v1", {}, env, ctx);
+ await waitOnExecutionContext(ctx);
+
+ expect(response.status).toBe(200);
+ const data: VersionsResponse = await response.json();
+ expect(data.result["0.8.0"]).toMatchObject({
+ version: "0.8.0",
+ download_url:
+ "https://github.com/FOSSBilling/FOSSBilling/releases/download/0.8.0/FOSSBilling-0.8.0.zip",
+ size_bytes: 2048000
+ });
+ });
+
it("should cache releases data", async () => {
const ctx = createExecutionContext();
await app.request("/versions/v1", {}, env, ctx);