Skip to content

Commit 22d6703

Browse files
authored
feat: add download count (#22)
1 parent ea071c9 commit 22d6703

File tree

4 files changed

+69
-18
lines changed

4 files changed

+69
-18
lines changed

Diff for: home.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@ function renderPlugins(data: PluginsData) {
7070
<div id="plugins-header">
7171
<div>Name</div>
7272
<div>URL</div>
73+
<div>Latest Downloads</div>
74+
<div>Total Downloads</div>
7375
<div></div>
7476
</div>
75-
{data.latest.map(plugin => renderPlugin(plugin))}
77+
{data.latest.map((plugin) => renderPlugin(plugin))}
7678
</div>
7779
);
7880
}
@@ -82,6 +84,8 @@ function renderPlugin(plugin: PluginData) {
8284
<div className="plugin" key={plugin.name}>
8385
<div>{plugin.name}</div>
8486
<div>{plugin.url}</div>
87+
<div>{plugin.downloadCount.currentVersion}</div>
88+
<div>{plugin.downloadCount.allVersions}</div>
8589
<div>
8690
<button className="copy-button" title="Copy to clipboard" data-url={plugin.url}>
8791
Copy

Diff for: plugins.ts

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export async function getLatestInfo(username: string, repoName: string) {
7676
: `https://plugins.dprint.dev/${username}/${displayRepoName}-${releaseInfo.tagName}.${extension}`,
7777
version: releaseInfo.tagName.replace(/^v/, ""),
7878
checksum: releaseInfo.checksum,
79+
downloadCount: releaseInfo.downloadCount,
7980
};
8081
}
8182

Diff for: readInfoFile.ts

+11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getLatestInfo } from "./plugins.ts";
2+
import { getAllDownloadCount } from "./utils/github.ts";
23
import { createAsyncLazy } from "./utils/mod.ts";
34

45
const infoLazy = createAsyncLazy<Readonly<PluginsData>>(async () => {
@@ -15,6 +16,10 @@ export interface PluginData {
1516
name: string;
1617
url: string;
1718
version: string;
19+
downloadCount: {
20+
currentVersion: number;
21+
allVersions: number;
22+
};
1823
}
1924

2025
export async function readInfoFile(): Promise<Readonly<PluginsData>> {
@@ -36,6 +41,12 @@ export async function readInfoFile(): Promise<Readonly<PluginsData>> {
3641
...plugin,
3742
version: info.version,
3843
url: info.url,
44+
downloadCount: {
45+
currentVersion: info.downloadCount,
46+
allVersions: pluginName
47+
? await getAllDownloadCount(username, pluginName)
48+
: await getAllDownloadCount("dprint", plugin.name),
49+
},
3950
});
4051
}
4152
}

Diff for: utils/github.ts

+52-17
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface ReleaseInfo {
3535
tagName: string;
3636
checksum: string | undefined;
3737
kind: "wasm" | "process";
38+
downloadCount: number;
3839
}
3940

4041
const latestReleaseTagNameCache = new LruCacheWithExpiry<string, ReleaseInfo | undefined>({
@@ -43,32 +44,20 @@ const latestReleaseTagNameCache = new LruCacheWithExpiry<string, ReleaseInfo | u
4344
});
4445

4546
export async function getLatestReleaseInfo(username: string, repoName: string) {
46-
if (!validateName(username) || !validateName(repoName)) {
47-
return undefined;
48-
}
49-
50-
const url = `https://api.github.com/repos/${username}/${repoName}/releases/latest`;
51-
return await latestReleaseTagNameCache.getOrSet(url, async () => {
52-
const response = await makeGitHubGetRequest(url, "GET");
53-
if (response.status === 404) {
54-
await response.text(); // todo: no way to mark this as used for the sanitizers?
55-
return undefined;
56-
} else if (!response.ok) {
57-
const text = await response.text();
58-
throw new Error(`Invalid response status: ${response.status}\n\n${text}`);
59-
}
60-
return getReleaseInfo(await response.json());
61-
});
47+
const releases = await getReleasesData(username, repoName);
48+
const latest = releases?.find((release) => !release.draft && !release.prerelease);
49+
return latest ? getReleaseInfo(latest) : undefined;
6250
}
6351

64-
function getReleaseInfo(data: { tag_name: string; body: string; assets: { name: string }[] }): ReleaseInfo {
52+
function getReleaseInfo(data: GitHubRelease): ReleaseInfo {
6553
if (typeof data.tag_name !== "string") {
6654
throw new Error("The tag name was not a string.");
6755
}
6856
return {
6957
tagName: data.tag_name,
7058
checksum: getChecksum(),
7159
kind: getPluginKind(),
60+
downloadCount: getDownloadCount(data.assets),
7261
};
7362

7463
function getChecksum() {
@@ -97,6 +86,52 @@ function getReleaseInfo(data: { tag_name: string; body: string; assets: { name:
9786
}
9887
}
9988

89+
function getDownloadCount(assets: ReleaseAsset[]) {
90+
return assets.find(({ name }) => name === "plugin.wasm" || name === "plugin.json")?.download_count ?? 0;
91+
}
92+
93+
interface ReleaseAsset {
94+
name: string;
95+
download_count: number;
96+
}
97+
98+
export async function getAllDownloadCount(username: string, repoName: string) {
99+
const releases = await getReleasesData(username, repoName);
100+
return releases?.reduce((total, current) => total + getDownloadCount(current.assets), 0) ?? 0;
101+
}
102+
103+
interface GitHubRelease {
104+
tag_name: string;
105+
body: string;
106+
draft: boolean;
107+
prerelease: boolean;
108+
assets: ReleaseAsset[];
109+
}
110+
111+
const releasesCache = new LruCacheWithExpiry<string, GitHubRelease[]>({
112+
size: 1000,
113+
expiryMs: 5 * 60 * 1_000, // keep entries for 5 minutes
114+
});
115+
116+
async function getReleasesData(username: string, repoName: string) {
117+
if (!validateName(username) || !validateName(repoName)) {
118+
return;
119+
}
120+
121+
const url = `https://api.github.com/repos/${username}/${repoName}/releases`;
122+
return await releasesCache.getOrSet(url, async () => {
123+
const response = await makeGitHubGetRequest(url, "GET");
124+
if (response.status === 404) {
125+
await response.text(); // todo: no way to mark this as used for the sanitizers?
126+
return;
127+
} else if (!response.ok) {
128+
const text = await response.text();
129+
throw new Error(`Invalid response status: ${response.status}\n\n${text}`);
130+
}
131+
return response.json();
132+
});
133+
}
134+
100135
const latestCliReleaseInfo = new LazyExpirableValue<any>({
101136
expiryMs: 10 * 60 * 1_000, // keep for 10 minutes
102137
createValue: async () => {

0 commit comments

Comments
 (0)