Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/services/versions/v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ GET /versions/v1
"size_bytes": 15485760,
"is_prerelease": false,
"github_release_id": 987654321,
"changelog": "## 0.5.0\n- Major feature updates..."
"changelog": "## 0.5.0\n- Major feature updates...",
"digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
}
},
"error_code": 0,
Expand Down Expand Up @@ -140,3 +141,4 @@ When GitHub is unavailable and no cached data exists:
- `details` includes the GitHub HTTP status and error code when available.
- `GITHUB_TOKEN` is required for GitHub API access.
- Releases before 0.5.0 read `src/composer.json`; newer releases use `composer.json`.
- `digest` is the SHA-256 digest of the release zip asset (`sha256:<hex>`), computed by GitHub; `null` if GitHub has not computed one for that asset.
11 changes: 10 additions & 1 deletion src/services/versions/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ interface ReleaseAsset {
name: string;
browser_download_url: string;
size: number;
digest: string | null;
}

function getReleaseZipAsset(
Expand Down Expand Up @@ -483,7 +484,8 @@ export async function getReleases(
size_bytes: zipAsset.size,
is_prerelease: Boolean(release.prerelease),
github_release_id: release.id ?? 0,
changelog: release.body || ""
changelog: release.body || "",
digest: zipAsset.digest ?? null
};
return [tag, releaseDetails];
}
Expand Down Expand Up @@ -583,6 +585,13 @@ function parseCachedReleases(
try {
const parsedCache = JSON.parse(cachedReleases);
if (parsedCache && typeof parsedCache === "object") {
// Cache entries written before the `digest` field existed lack the
// key entirely; normalize them to the documented `null` fallback.
for (const release of Object.values(parsedCache as Releases)) {
if (release.digest === undefined) {
release.digest = null;
}
}
return parsedCache as Releases;
}
} catch (parseError) {
Expand Down
3 changes: 3 additions & 0 deletions src/services/versions/v1/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export type ReleaseDetails = {
is_prerelease: boolean;
github_release_id: number;
changelog: string;
// SHA-256 digest of the release zip (`sha256:<hex>`), as computed by
// GitHub; null for older assets predating GitHub's digest support.
digest: string | null;
Comment thread
admdly marked this conversation as resolved.
};

export type Releases = {
Expand Down
4 changes: 3 additions & 1 deletion test/mocks/github-releases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ export const mockGitHubReleases = [
name: "FOSSBilling.zip",
browser_download_url:
"https://github.com/FOSSBilling/FOSSBilling/releases/download/0.6.0/FOSSBilling.zip",
size: 1030000
size: 1030000,
digest:
"sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
}
]
}
Expand Down
2 changes: 2 additions & 0 deletions test/services/versions/v1/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ describe("Versions API v1 - Error Handling", () => {
expect(data.error_code).toBe(0);
expect(data.result["1.0.0"]).toBeDefined();
expect(data.stale).toBe(false);
// Cached before the `digest` field existed; should be normalized to null.
expect(data.result["1.0.0"]).toHaveProperty("digest", null);
});

it("should handle GitHub API returning invalid JSON", async () => {
Expand Down
6 changes: 6 additions & 0 deletions test/services/versions/v1/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ describe("Versions API v1", () => {
expect(data.result).toHaveProperty("is_prerelease", false);
expect(data.result).toHaveProperty("github_release_id");
expect(data.result).toHaveProperty("changelog");
expect(data.result).toHaveProperty(
"digest",
"sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
);
});
});

Expand All @@ -227,6 +231,8 @@ describe("Versions API v1", () => {
throw new Error("Expected version info for 0.5.0");
}
expect(data.result).toHaveProperty("version", "0.5.0");
// 0.5.0's mock asset predates GitHub's asset-digest feature.
expect(data.result).toHaveProperty("digest", null);
});

it("should return 404 for non-existent version", async () => {
Expand Down
1 change: 1 addition & 0 deletions test/utils/test-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export interface VersionInfo {
is_prerelease: boolean;
github_release_id: number;
changelog: string;
digest: string | null;
}

// GitHub Types
Expand Down
Loading