@@ -11,7 +11,6 @@ const ASSET_NAMES: Record<Arch, string> = {
1111 arm64 : "vp-setup-aarch64-pc-windows-msvc.exe" ,
1212} ;
1313const LATEST_CACHE_TTL = 300 ; // 5 minutes
14- const TAGGED_CACHE_TTL = 86400 ; // 24 hours
1514const DEFAULT_DIST_TAG = "latest" ;
1615
1716type Arch = "x64" | "arm64" ;
@@ -21,11 +20,6 @@ interface CachedRelease {
2120 assets : Partial < Record < Arch , string > > ;
2221}
2322
24- interface GitHubRelease {
25- tag_name : string ;
26- assets : Array < { name : string ; browser_download_url : string } > ;
27- }
28-
2923export function detectArch (
3024 queryArch : string | undefined ,
3125 userAgent : string | undefined ,
@@ -40,58 +34,6 @@ export function detectArch(
4034 return "x64" ;
4135}
4236
43- export function parseRelease ( release : GitHubRelease ) : CachedRelease | null {
44- const assets : Partial < Record < Arch , string > > = { } ;
45- for ( const asset of release . assets ) {
46- if ( asset . name === ASSET_NAMES . x64 ) assets . x64 = asset . browser_download_url ;
47- if ( asset . name === ASSET_NAMES . arm64 ) assets . arm64 = asset . browser_download_url ;
48- }
49- if ( ! assets . x64 && ! assets . arm64 ) return null ;
50- return { tag : release . tag_name , assets } ;
51- }
52-
53- async function fetchGitHub ( path : string , githubToken : string | undefined ) : Promise < Response > {
54- const headers : Record < string , string > = {
55- Accept : "application/vnd.github.v3+json" ,
56- "User-Agent" : "vp-setup-exe-downloader" ,
57- } ;
58- if ( githubToken ) headers . Authorization = `Bearer ${ githubToken } ` ;
59- return fetch ( `https://api.github.com${ path } ` , { headers } ) ;
60- }
61-
62- // "not-found" means the tag definitively doesn't exist (GitHub 404).
63- // null means the API failed (rate limit, network error) — fallbacks may still work.
64- export async function fetchRelease (
65- tag : string | undefined ,
66- githubToken : string | undefined ,
67- ) : Promise < GitHubRelease | null | "not-found" > {
68- if ( tag ) {
69- const res = await fetchGitHub (
70- `/repos/${ GITHUB_OWNER } /${ GITHUB_REPO } /releases/tags/${ tag } ` ,
71- githubToken ,
72- ) ;
73- if ( ! res . ok ) {
74- console . error ( `GitHub API error: ${ res . status } ${ res . statusText } for tag ${ tag } ` ) ;
75- return res . status === 404 ? "not-found" : null ;
76- }
77- return ( await res . json ( ) ) as GitHubRelease ;
78- }
79-
80- const version = await fetchNpmDistTagVersion ( DEFAULT_DIST_TAG ) ;
81- if ( ! version ) return null ;
82- const res = await fetchGitHub (
83- `/repos/${ GITHUB_OWNER } /${ GITHUB_REPO } /releases/tags/v${ version } ` ,
84- githubToken ,
85- ) ;
86- if ( ! res . ok ) {
87- console . error ( `GitHub API error: ${ res . status } ${ res . statusText } for default tag v${ version } ` ) ;
88- // Treat all failures (incl. 404) as transient so getRelease doesn't cache a negative
89- // under `release:latest` when npm/GitHub are momentarily out of sync.
90- return null ;
91- }
92- return ( await res . json ( ) ) as GitHubRelease ;
93- }
94-
9537export function buildReleaseFromTag ( tag : string ) : CachedRelease {
9638 const base = `https://github.com/${ GITHUB_OWNER } /${ GITHUB_REPO } /releases/download/${ tag } ` ;
9739 return {
@@ -120,58 +62,36 @@ async function fetchNpmDistTagVersion(distTag: string): Promise<string | null> {
12062 }
12163}
12264
123- function cacheKey ( tag : string | undefined ) : string {
124- return tag ? `release:tag:${ tag } ` : "release:latest" ;
125- }
65+ const LATEST_CACHE_KEY = "release:latest" ;
66+ const LATEST_STALE_KEY = "release:latest:stale" ;
12667
127- function staleCacheKey ( tag : string | undefined ) : string {
128- return ` ${ cacheKey ( tag ) } :stale` ;
129- }
68+ async function getRelease ( tag : string | undefined ) : Promise < CachedRelease | null > {
69+ // Tags are immutable — construct the download URL directly, no network or cache needed
70+ if ( tag ) return buildReleaseFromTag ( tag ) ;
13071
131- async function getRelease (
132- tag : string | undefined ,
133- githubToken : string | undefined ,
134- ) : Promise < CachedRelease | null > {
135- const key = cacheKey ( tag ) ;
136- const cached = await kv . get < CachedRelease > ( key ) ;
72+ // "Latest" path: use KV cache to avoid hitting npm on every request
73+ const cached = await kv . get < CachedRelease > ( LATEST_CACHE_KEY ) ;
13774 if ( cached ) return cached ;
13875
139- try {
140- const release = await fetchRelease ( tag , githubToken ) ;
141- if ( release === "not-found" ) {
142- // Cache the negative result to avoid repeated API calls for the same bad tag
143- await kv . put ( key , null , { ttl : LATEST_CACHE_TTL } ) ;
144- return null ;
145- }
146- if ( release ) {
147- const parsed = parseRelease ( release ) ;
148- if ( parsed ) {
149- const ttl = tag ? TAGGED_CACHE_TTL : LATEST_CACHE_TTL ;
150- const staleTtl = ttl + 3600 ;
151- await Promise . all ( [
152- kv . put ( key , parsed , { ttl } ) ,
153- kv . put ( staleCacheKey ( tag ) , parsed , { ttl : staleTtl } ) ,
154- ] ) ;
155- return parsed ;
156- }
76+ const version = await fetchNpmDistTagVersion ( DEFAULT_DIST_TAG ) ;
77+ if ( version ) {
78+ const release = buildReleaseFromTag ( `v${ version } ` ) ;
79+ try {
80+ await Promise . all ( [
81+ kv . put ( LATEST_CACHE_KEY , release , { ttl : LATEST_CACHE_TTL } ) ,
82+ kv . put ( LATEST_STALE_KEY , release , { ttl : LATEST_CACHE_TTL + 3600 } ) ,
83+ ] ) ;
84+ } catch ( err ) {
85+ console . error ( "KV write failed:" , err ) ;
15786 }
158- } catch ( err ) {
159- console . error ( "Failed to fetch release from GitHub:" , err ) ;
87+ return release ;
16088 }
16189
162- // Fallback 1: stale KV cache
163- const stale = await kv . get < CachedRelease > ( staleCacheKey ( tag ) ) ;
164- if ( stale ) return stale ;
165-
166- // Fallback 2: construct download URLs from tag or npm registry version
167- if ( tag ) return buildReleaseFromTag ( tag ) ;
168- const version = await fetchNpmDistTagVersion ( DEFAULT_DIST_TAG ) ;
169- if ( version ) return buildReleaseFromTag ( `v${ version } ` ) ;
170-
171- return null ;
90+ // npm unreachable — fall back to stale cache
91+ return kv . get < CachedRelease > ( LATEST_STALE_KEY ) ;
17292}
17393
174- function escapeHtml ( s : string ) : string {
94+ export function escapeHtml ( s : string ) : string {
17595 return s
17696 . replace ( / & / g, "&" )
17797 . replace ( / " / g, """ )
@@ -291,7 +211,12 @@ async function setupDownloadLink() {
291211 mainBtn.textContent = "Download for Windows (ARM64)";
292212
293213 if (altEl && x64Url) {
294- altEl.innerHTML = 'Also available: <a href="' + x64Url + '" download>Windows x64</a>';
214+ var link = document.createElement("a");
215+ link.href = x64Url;
216+ link.download = "";
217+ link.textContent = "Windows x64";
218+ altEl.textContent = "Also available: ";
219+ altEl.appendChild(link);
295220 }
296221}
297222
@@ -306,15 +231,14 @@ setupDownloadLink();
306231export const GET = defineHandler ( async ( c ) => {
307232 const queryArch = c . req . query ( "arch" ) ;
308233 const tag = c . req . query ( "tag" ) ;
309- const githubToken = c . env . GITHUB_TOKEN as string | undefined ;
310234
311235 // When ?arch= is specified, redirect directly (backward-compatible for CLI/curl)
312236 if ( queryArch ) {
313237 const arch = detectArch ( queryArch , c . req . header ( "user-agent" ) ) ;
314238 if ( arch === null ) {
315239 return c . json ( { error : "Invalid architecture. Use 'x64' or 'arm64'" } , 400 ) ;
316240 }
317- const release = await getRelease ( tag || undefined , githubToken ) ;
241+ const release = await getRelease ( tag || undefined ) ;
318242 if ( ! release ) {
319243 return c . json ( { error : tag ? `Release '${ tag } ' not found` : "No release found" } , 404 ) ;
320244 }
@@ -326,7 +250,7 @@ export const GET = defineHandler(async (c) => {
326250 }
327251
328252 // Serve the download page with client-side architecture detection
329- const release = await getRelease ( tag || undefined , githubToken ) ;
253+ const release = await getRelease ( tag || undefined ) ;
330254 if ( ! release ) {
331255 return c . json ( { error : tag ? `Release '${ tag } ' not found` : "No release found" } , 404 ) ;
332256 }
0 commit comments