11import { GITHUB_TOKEN , KV_REST_API_TOKEN , KV_REST_API_URL } from "$env/static/private" ;
22import { Redis } from "@upstash/redis" ;
33import { Octokit } from "octokit" ;
4+ import type { Repository } from "$lib/repositories" ;
5+ import parseChangelog from "$lib/changelog-parser" ;
46
57export type GitHubRelease = Awaited <
68 ReturnType < InstanceType < typeof Octokit > [ "rest" ] [ "repos" ] [ "listReleases" ] >
@@ -61,12 +63,11 @@ export class GitHubCache {
6163 /**
6264 * Get all the releases for a given repository
6365 *
64- * @param owner the owner of the GitHub repository to get releases from
65- * @param repo the name of the GitHub repository to get releases from
66+ * @param repository the repository to get the releases for
6667 * @returns the releases, either cached or fetched
6768 */
68- async getReleases ( owner : string , repo : string ) {
69- const cacheKey = this . #getRepoKey( owner , repo ) ;
69+ async getReleases ( repository : Repository ) {
70+ const cacheKey = this . #getRepoKey( repository . owner , repository . repoName ) ;
7071
7172 const cachedReleases = await this . #redis. json . get < GitHubRelease [ ] > ( cacheKey ) ;
7273 if ( cachedReleases ) {
@@ -76,11 +77,7 @@ export class GitHubCache {
7677
7778 console . log ( `Cache miss for ${ cacheKey } , fetching from GitHub API` ) ;
7879
79- const { data : releases } = await this . #octokit. rest . repos . listReleases ( {
80- owner,
81- repo,
82- per_page
83- } ) ;
80+ const releases = await this . #fetchReleases( repository ) ;
8481
8582 await this . #redis. json . set ( cacheKey , "$" , releases ) ;
8683
@@ -146,6 +143,130 @@ export class GitHubCache {
146143 return releases ;
147144 }
148145
146+ /**
147+ * A utility method to fetch the releases based on the
148+ * mode we want to use to get them
149+ *
150+ * @param repository the repository to fetch the releases for
151+ * @returns the fetched releases
152+ * @private
153+ */
154+ async #fetchReleases( repository : Repository ) : Promise < GitHubRelease [ ] > {
155+ const { owner, repoName : repo , changesMode, changelogContentsReplacer } = repository ;
156+ if ( changesMode === "releases" || ! changesMode ) {
157+ const { data : releases } = await this . #octokit. rest . repos . listReleases ( {
158+ owner,
159+ repo,
160+ per_page
161+ } ) ;
162+ return releases ;
163+ }
164+
165+ // Changelog mode: we'll need to get the tags and re-build releases from them
166+
167+ // 1. Fetch tags
168+ const { data : tags } = await this . #octokit. rest . repos . listTags ( {
169+ owner,
170+ repo,
171+ per_page
172+ } ) ;
173+
174+ // 2. Fetch changelog
175+ const { data : changelogResult } = await this . #octokit. rest . repos . getContent ( {
176+ owner,
177+ repo,
178+ ref :
179+ owner === "sveltejs" &&
180+ repo === "prettier-plugin-svelte" && // this repo is a bit of a mess
181+ tags [ 0 ] &&
182+ repository . metadataFromTag ( tags [ 0 ] . name ) [ 1 ] . startsWith ( "3" )
183+ ? "version-3" // a temporary fix to get the changelog from the right branch while v4 isn't out yet
184+ : undefined ,
185+ path : "CHANGELOG.md"
186+ } ) ;
187+
188+ if ( ! ( "content" in changelogResult ) ) return [ ] ; // filter out empty or multiple results
189+ const { content, encoding, type } = changelogResult ;
190+ if ( type !== "file" || ! content ) return [ ] ; // filter out directories and empty files
191+ const changelogFileContents =
192+ encoding === "base64" ? Buffer . from ( content , "base64" ) . toString ( ) : content ;
193+ // Actually parse the changelog file
194+ const { versions } = await parseChangelog (
195+ changelogContentsReplacer ?.( changelogFileContents ) ?? changelogFileContents
196+ ) ;
197+
198+ /**
199+ * Returns a simili-hash for local ID creation purposes
200+ *
201+ * @param input the input string
202+ * @returns a (hopefully unique) and pure hashcode
203+ */
204+ function simpleHash ( input : string ) {
205+ return Math . abs (
206+ input . split ( "" ) . reduce ( ( hash , char ) => ( hash * 31 + char . charCodeAt ( 0 ) ) & 0xffffffff , 0 )
207+ ) ;
208+ }
209+
210+ // 3. Return the recreated releases
211+ return await Promise . all (
212+ tags . map (
213+ async (
214+ { name : tag_name , commit : { sha } , zipball_url, tarball_url, node_id } ,
215+ tagIndex
216+ ) => {
217+ const {
218+ data : { author, committer }
219+ } = await this . #octokit. rest . git . getCommit ( { owner, repo, commit_sha : sha } ) ;
220+ const [ , cleanVersion ] = repository . metadataFromTag ( tag_name ) ;
221+ const changelogVersion = versions . find (
222+ ( { version } ) => ! ! version ?. includes ( cleanVersion )
223+ ) ;
224+ return {
225+ url : "" ,
226+ html_url : `https://github.com/${ owner } /${ repo } /releases/tag/${ tag_name } ` ,
227+ assets_url : "" ,
228+ upload_url : "" ,
229+ tarball_url,
230+ zipball_url,
231+ id : simpleHash ( `${ owner } /${ repo } ` ) + tagIndex ,
232+ node_id,
233+ tag_name,
234+ target_commitish : "main" ,
235+ name : `${ repo } @${ cleanVersion } ` ,
236+ body : changelogVersion ?. body ?? "_No changelog provided._" ,
237+ draft : false ,
238+ prerelease : tag_name . includes ( "-" ) ,
239+ created_at : committer . date ,
240+ published_at : null ,
241+ author : {
242+ name : author . name ,
243+ login : "" ,
244+ email : author . email ,
245+ id : 0 ,
246+ node_id : "" ,
247+ avatar_url : "" ,
248+ gravatar_id : null ,
249+ url : "" ,
250+ html_url : "" ,
251+ followers_url : "" ,
252+ following_url : "" ,
253+ gists_url : "" ,
254+ starred_url : "" ,
255+ subscriptions_url : "" ,
256+ organizations_url : "" ,
257+ repos_url : "" ,
258+ events_url : "" ,
259+ received_events_url : "" ,
260+ type : "" ,
261+ site_admin : false
262+ } ,
263+ assets : [ ]
264+ } satisfies GitHubRelease ;
265+ }
266+ )
267+ ) ;
268+ }
269+
149270 /**
150271 * Checks if releases are present in the cache for the
151272 * given GitHub info
0 commit comments