@@ -8,6 +8,8 @@ export type GitHubRelease = Awaited<
88 ReturnType < InstanceType < typeof Octokit > [ "rest" ] [ "repos" ] [ "listReleases" ] >
99> [ "data" ] [ number ] ;
1010
11+ type KeyType = "releases" | "descriptions" ;
12+
1113/**
1214 * The maximum items amount to get per-page
1315 * when fetching from GitHub API.
@@ -57,11 +59,12 @@ export class GitHubCache {
5759 *
5860 * @param owner the GitHub repository owner
5961 * @param repo the GitHub repository name
62+ * @param type the kind of cache to use
6063 * @returns the pure computed key
6164 * @private
6265 */
63- #getRepoKey( owner : string , repo : string ) {
64- return `repo:${ owner } /${ repo } :releases ` ;
66+ #getRepoKey( owner : string , repo : string , type : KeyType ) {
67+ return `repo:${ owner } /${ repo } :${ type } ` ;
6568 }
6669
6770 /**
@@ -71,15 +74,15 @@ export class GitHubCache {
7174 * @returns the releases, either cached or fetched
7275 */
7376 async getReleases ( repository : Repository ) {
74- const cacheKey = this . #getRepoKey( repository . owner , repository . repoName ) ;
77+ const cacheKey = this . #getRepoKey( repository . owner , repository . repoName , "releases" ) ;
7578
7679 const cachedReleases = await this . #redis. json . get < GitHubRelease [ ] > ( cacheKey ) ;
7780 if ( cachedReleases ) {
78- console . log ( `Cache hit for ${ cacheKey } ` ) ;
81+ console . log ( `Cache hit for releases for ${ cacheKey } ` ) ;
7982 return cachedReleases ;
8083 }
8184
82- console . log ( `Cache miss for ${ cacheKey } , fetching from GitHub API` ) ;
85+ console . log ( `Cache miss for releases for ${ cacheKey } , fetching from GitHub API` ) ;
8386
8487 const releases = await this . #fetchReleases( repository ) ;
8588
@@ -213,6 +216,72 @@ export class GitHubCache {
213216 ) ;
214217 }
215218
219+ /**
220+ * Get a map that contains the descriptions
221+ * of all the packages in the given repository.
222+ * Irrelevant paths (e.g., tests) or empty descriptions
223+ * are excluded.
224+ *
225+ * @param repository the repository to fetch the
226+ * descriptions in
227+ * @returns a map of paths to descriptions.
228+ * @private
229+ */
230+ async getDescriptions ( repository : Repository ) {
231+ const cacheKey = this . #getRepoKey( repository . owner , repository . repoName , "descriptions" ) ;
232+
233+ const cachedDescriptions = await this . #redis. json . get < { [ key : string ] : string } > ( cacheKey ) ;
234+ if ( cachedDescriptions ) {
235+ console . log ( `Cache hit for descriptions for ${ cacheKey } ` ) ;
236+ return cachedDescriptions ;
237+ }
238+
239+ console . log ( `Cache miss for releases for ${ cacheKey } , fetching from GitHub API` ) ;
240+
241+ const { owner, repoName : repo } = repository ;
242+
243+ const { data : allFiles } = await this . #octokit. rest . git . getTree ( {
244+ owner,
245+ repo,
246+ tree_sha : "HEAD" ,
247+ recursive : "true"
248+ } ) ;
249+
250+ const allPackageJson = allFiles . tree
251+ . map ( ( { path } ) => path )
252+ . filter ( path => path !== undefined )
253+ . filter (
254+ path =>
255+ ! path . includes ( "/test/" ) && ( path === "package.json" || path . endsWith ( "/package.json" ) )
256+ ) ;
257+
258+ const descriptions = new Map < string , string > ( ) ;
259+ for ( const path of allPackageJson ) {
260+ const { data : packageJson } = await this . #octokit. rest . repos . getContent ( {
261+ owner,
262+ repo,
263+ path
264+ } ) ;
265+
266+ if ( ! ( "content" in packageJson ) ) continue ; // filter out empty or multiple results
267+ const { content, encoding, type } = packageJson ;
268+ if ( type !== "file" || ! content ) continue ; // filter out directories and empty files
269+ const packageFile =
270+ encoding === "base64" ? Buffer . from ( content , "base64" ) . toString ( ) : content ;
271+
272+ try {
273+ const { description } = JSON . parse ( packageFile ) as { description : string } ;
274+ if ( description ) descriptions . set ( path , description ) ;
275+ } catch {
276+ // ignore
277+ }
278+ }
279+
280+ await this . #redis. json . set ( cacheKey , "$" , Object . fromEntries ( descriptions ) ) ;
281+
282+ return Object . fromEntries ( descriptions ) ;
283+ }
284+
216285 /**
217286 * Checks if releases are present in the cache for the
218287 * given GitHub info
@@ -221,10 +290,11 @@ export class GitHubCache {
221290 * existence in the cache for
222291 * @param repo the name of the GitHub repository to check the
223292 * existence in the cache for
293+ * @param type the kind of cache to target
224294 * @returns whether the repository is cached or not
225295 */
226- async exists ( owner : string , repo : string ) {
227- const cacheKey = this . #getRepoKey( owner , repo ) ;
296+ async exists ( owner : string , repo : string , type : KeyType ) {
297+ const cacheKey = this . #getRepoKey( owner , repo , type ) ;
228298 const result = await this . #redis. exists ( cacheKey ) ;
229299 return result === 1 ;
230300 }
@@ -236,9 +306,10 @@ export class GitHubCache {
236306 * from the cache
237307 * @param repo the name of the GitHub repository to remove
238308 * from the cache
309+ * @param type the kind of cache to target
239310 */
240- async deleteEntry ( owner : string , repo : string ) {
241- const cacheKey = this . #getRepoKey( owner , repo ) ;
311+ async deleteEntry ( owner : string , repo : string , type : KeyType ) {
312+ const cacheKey = this . #getRepoKey( owner , repo , type ) ;
242313 await this . #redis. del ( cacheKey ) ;
243314 }
244315}
0 commit comments