diff --git a/env.d.ts b/env.d.ts index 938ed41..75daf8d 100644 --- a/env.d.ts +++ b/env.d.ts @@ -12,5 +12,6 @@ declare namespace Cloudflare { interface Env { DPRINT_PLUGINS_GH_TOKEN?: string; PLUGIN_CACHE: R2Bucket; + DPRINT_PLUGIN_DOWNLOAD_ANALYTICS: AnalyticsEngineDataset; } } diff --git a/handleRequest.ts b/handleRequest.ts index 60fb039..dbd5db6 100644 --- a/handleRequest.ts +++ b/handleRequest.ts @@ -1,6 +1,13 @@ +import { env } from "cloudflare:workers"; import { renderHome } from "./home.jsx"; import oldMappings from "./old_redirects.json" with { type: "json" }; -import { tryResolveAssetUrl, tryResolveLatestJson, tryResolvePluginUrl, tryResolveSchemaUrl } from "./plugins.js"; +import { + type PluginUrlResult, + tryResolveAssetUrl, + tryResolveLatestJson, + tryResolvePluginUrl, + tryResolveSchemaUrl, +} from "./plugins.js"; import { readInfoFile } from "./readInfoFile.js"; import robotsTxt from "./robots.txt"; import styleCSS from "./style.css"; @@ -190,9 +197,16 @@ export function createRequestHandler() { } export async function resolvePluginOrSchemaUrl(url: URL) { - return (oldMappings as { [oldUrl: string]: string })[url.toString()] - ?? await tryResolvePluginUrl(url) - ?? await tryResolveSchemaUrl(url); + const oldMapping = (oldMappings as { [oldUrl: string]: string })[url.toString()]; + if (oldMapping != null) { + return oldMapping; + } + const pluginResult = await tryResolvePluginUrl(url); + if (pluginResult != null) { + trackPluginDownload(pluginResult); + return pluginResult.githubUrl; + } + return await tryResolveSchemaUrl(url); } function getAccessControlAllowOrigin(request: Request) { @@ -235,6 +249,18 @@ function contentTypeForUrl(url: string) { return contentTypes.octetStream; } +function trackPluginDownload(result: PluginUrlResult) { + try { + env.DPRINT_PLUGIN_DOWNLOAD_ANALYTICS.writeDataPoint({ + indexes: [`${result.username}/${result.repo}`], + blobs: [result.tag], + doubles: [1], + }); + } catch (err) { + console.warn("Failed to write analytics:", err); + } +} + function create404Response() { return new Response(null, { status: 404, diff --git a/plugins.ts b/plugins.ts index 0a9009e..7990cce 100644 --- a/plugins.ts +++ b/plugins.ts @@ -84,15 +84,23 @@ export function tryResolveAssetUrl(url: URL): { githubUrl: string; shouldCache: return { githubUrl, shouldCache }; } -export async function tryResolvePluginUrl(url: URL) { +export interface PluginUrlResult { + githubUrl: string; + username: string; + repo: string; + tag: string; +} + +export async function tryResolvePluginUrl(url: URL): Promise { return dprintPluginTagPatternMapper(dprintWasmPluginPattern, url, "plugin.wasm") ?? dprintPluginTagPatternMapper(dprintProcessPluginPattern, url, "plugin.json") ?? (await userRepoTagPatternMapper(userWasmPluginPattern, url, "plugin.wasm")) ?? (await userRepoTagPatternMapper(userProcessPluginPattern, url, "plugin.json")); } -export function tryResolveSchemaUrl(url: URL) { - return userRepoTagPatternMapper(userSchemaPattern, url, "schema.json"); +export async function tryResolveSchemaUrl(url: URL) { + const result = await userRepoTagPatternMapper(userSchemaPattern, url, "schema.json"); + return result?.githubUrl; } const userLatestPattern = new URLPattern({ @@ -146,16 +154,16 @@ function dprintPluginTagPatternMapper( pattern: URLPattern, url: URL, fileName: string, -) { +): PluginUrlResult | undefined { const result = pattern.exec(url); if (result) { - const pluginShortName = result.pathname.groups[0]; - const tag = result.pathname.groups[1]; - if (tag === "latest") { - return `https://github.com/dprint/dprint-plugin-${pluginShortName}/releases/latest/download/${fileName}`; - } else { - return `https://github.com/dprint/dprint-plugin-${pluginShortName}/releases/download/${tag}/${fileName}`; - } + const pluginShortName = result.pathname.groups[0]!; + const tag = result.pathname.groups[1]!; + const repo = `dprint-plugin-${pluginShortName}`; + const githubUrl = tag === "latest" + ? `https://github.com/dprint/${repo}/releases/latest/download/${fileName}` + : `https://github.com/dprint/${repo}/releases/download/${tag}/${fileName}`; + return { githubUrl, username: "dprint", repo, tag }; } return undefined; } @@ -164,7 +172,7 @@ async function userRepoTagPatternMapper( pattern: URLPattern, url: URL, fileName: string, -) { +): Promise { const result = pattern.exec(url); if (result) { const username = result.pathname.groups[0]!; @@ -179,12 +187,11 @@ async function userRepoTagPatternMapper( break; } } - const tag = result.pathname.groups[2]; - if (tag === "latest") { - return `https://github.com/${username}/${repo}/releases/latest/download/${fileName}`; - } else { - return `https://github.com/${username}/${repo}/releases/download/${tag}/${fileName}`; - } + const tag = result.pathname.groups[2]!; + const githubUrl = tag === "latest" + ? `https://github.com/${username}/${repo}/releases/latest/download/${fileName}` + : `https://github.com/${username}/${repo}/releases/download/${tag}/${fileName}`; + return { githubUrl, username, repo, tag }; } return undefined; } diff --git a/wrangler.toml b/wrangler.toml index 753892e..fbd5016 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -14,3 +14,7 @@ invocation_logs = true [[r2_buckets]] binding = "PLUGIN_CACHE" bucket_name = "dprint-plugin-cache" + +[[analytics_engine_datasets]] +binding = "DPRINT_PLUGIN_DOWNLOAD_ANALYTICS" +dataset = "dprint-plugin-downloads"