diff --git a/env.d.ts b/env.d.ts
index 75daf8d..3e7eed2 100644
--- a/env.d.ts
+++ b/env.d.ts
@@ -11,6 +11,10 @@ declare module "*.txt" {
declare namespace Cloudflare {
interface Env {
DPRINT_PLUGINS_GH_TOKEN?: string;
+ // account id and a token with Account Analytics read access, used to query
+ // download counts back out of the analytics engine dataset
+ CLOUDFLARE_ACCOUNT_ID?: string;
+ DPRINT_PLUGINS_ANALYTICS_TOKEN?: string;
PLUGIN_CACHE: R2Bucket;
DPRINT_PLUGIN_DOWNLOAD_ANALYTICS: AnalyticsEngineDataset;
}
diff --git a/home.tsx b/home.tsx
index 349b401..11e10f3 100644
--- a/home.tsx
+++ b/home.tsx
@@ -71,7 +71,7 @@ function renderPlugins(data: PluginsData) {
{data.latest.map((plugin) => renderPlugin(plugin))}
diff --git a/plugins.ts b/plugins.ts
index 1d28232..06a1eb8 100644
--- a/plugins.ts
+++ b/plugins.ts
@@ -149,7 +149,10 @@ export async function getLatestInfo(username: string, repoName: string, origin:
: `${origin}/${username}/${displayRepoName}-${releaseInfo.tagName}.${extension}`,
version: releaseInfo.tagName.replace(/^v/, ""),
checksum: releaseInfo.checksum,
- downloadCount: releaseInfo.downloadCount,
+ // identifies this plugin's download analytics: the `username/repo` key that
+ // downloads are recorded under and the tag of the latest release
+ downloadKey: `${username}/${repoName}`,
+ tag: releaseInfo.tagName,
};
}
diff --git a/readInfoFile.ts b/readInfoFile.ts
index 3125593..d623ae2 100644
--- a/readInfoFile.ts
+++ b/readInfoFile.ts
@@ -1,7 +1,7 @@
import { env } from "cloudflare:workers";
import infoJson from "./info.json" with { type: "json" };
import { getLatestInfo } from "./plugins.js";
-import { getAllDownloadCount } from "./utils/github.js";
+import { getDownloadCounts, type PluginDownloadCounts } from "./utils/analytics.js";
// only typing what's used on the server
export interface PluginsData {
@@ -125,6 +125,7 @@ async function buildInfoFile(origin: string): Promise> {
};
async function getLatest(latest: typeof infoJson.latest) {
+ const downloadCounts = await getDownloadCounts();
const results = [];
for (const plugin of latest) {
const [username, pluginName] = plugin.name.split("/");
@@ -132,15 +133,14 @@ async function buildInfoFile(origin: string): Promise> {
? await getLatestInfo(username, pluginName, origin)
: await getLatestInfo("dprint", plugin.name, origin);
if (info != null) {
+ const counts = downloadCounts.get(info.downloadKey);
results.push({
...plugin,
version: info.version,
url: info.url,
downloadCount: {
- currentVersion: info.downloadCount,
- allVersions: pluginName
- ? await getAllDownloadCount(username, pluginName)
- : await getAllDownloadCount("dprint", plugin.name),
+ currentVersion: currentVersionDownloads(counts, info.tag),
+ allVersions: counts?.allVersions ?? 0,
},
});
}
@@ -148,3 +148,12 @@ async function buildInfoFile(origin: string): Promise> {
return results;
}
}
+
+// downloads of the latest release over the last 30 days, counting both the exact
+// version tag and the "latest" alias (which always resolves to the current release)
+function currentVersionDownloads(counts: PluginDownloadCounts | undefined, tag: string) {
+ if (counts == null) {
+ return 0;
+ }
+ return (counts.byTag.get(tag) ?? 0) + (counts.byTag.get("latest") ?? 0);
+}
diff --git a/utils/analytics.ts b/utils/analytics.ts
new file mode 100644
index 0000000..da747f2
--- /dev/null
+++ b/utils/analytics.ts
@@ -0,0 +1,86 @@
+import { env } from "cloudflare:workers";
+import { fetchWithRetries } from "./fetchWithRetries.js";
+import { LazyExpirableValue } from "./LazyExpirableValue.js";
+
+// download counts come from the Analytics Engine dataset that each plugin
+// download writes a data point to (see trackPluginDownload in handleRequest.ts).
+// the dataset only retains roughly the last 90 days, so these are downloads over
+// the trailing 30 days ("monthly") rather than all-time totals.
+
+export interface PluginDownloadCounts {
+ // downloads over the last 30 days across every version
+ allVersions: number;
+ // downloads over the last 30 days keyed by the tag that appeared in the request
+ // url (a version like "0.1.0" or the "latest" alias)
+ byTag: Map;
+}
+
+const DATASET = "dprint-plugin-downloads";
+const WINDOW_DAYS = 30;
+
+const downloadCounts = new LazyExpirableValue