diff --git a/lib/modules/datasource/github-releases/cache/cache-base.spec.ts b/lib/modules/datasource/github-releases/cache/cache-base.spec.ts index 20166e40a844f20d3e7ad561745c0d5edf5353b2..407ca669dd6c146c8eb4c9d205529944159b0963 100644 --- a/lib/modules/datasource/github-releases/cache/cache-base.spec.ts +++ b/lib/modules/datasource/github-releases/cache/cache-base.spec.ts @@ -2,8 +2,8 @@ import { DateTime } from 'luxon'; import { mocked } from '../../../../../test/util'; import * as _packageCache from '../../../../util/cache/package'; import type { - QueryResponse, - StoredItemBase, + GithubCachedItem, + GithubGraphqlRepoResponse, } from '../../../../util/github/types'; import { GithubGraphqlResponse, @@ -20,14 +20,14 @@ interface FetchedItem { foo: string; } -interface StoredItem extends StoredItemBase { +interface StoredItem extends GithubCachedItem { bar: string; } type GraphqlDataResponse = { statusCode: 200; headers: Record<string, string>; - body: GithubGraphqlResponse<QueryResponse<FetchedItem>>; + body: GithubGraphqlResponse<GithubGraphqlRepoResponse<FetchedItem>>; }; type GraphqlResponse = GraphqlDataResponse | Error; diff --git a/lib/modules/datasource/github-releases/cache/cache-base.ts b/lib/modules/datasource/github-releases/cache/cache-base.ts index ec2db09f54bdb92ec6d21f76cc5f069d72f290f8..a105abd8a9975e273a4a57e6a4408f1670889103 100644 --- a/lib/modules/datasource/github-releases/cache/cache-base.ts +++ b/lib/modules/datasource/github-releases/cache/cache-base.ts @@ -6,10 +6,10 @@ import * as packageCache from '../../../../util/cache/package'; import type { CacheOptions, ChangelogRelease, + GithubCachedItem, GithubDatasourceCache, - GithubQueryParams, - QueryResponse, - StoredItemBase, + GithubGraphqlRepoParams, + GithubGraphqlRepoResponse, } from '../../../../util/github/types'; import { getApiBaseUrl } from '../../../../util/github/url'; import type { @@ -100,7 +100,7 @@ function isExpired( } export abstract class AbstractGithubDatasourceCache< - StoredItem extends StoredItemBase, + CachedItem extends GithubCachedItem, FetchedItem = unknown > { private updateDuration: DurationLikeObject; @@ -163,16 +163,18 @@ export abstract class AbstractGithubDatasourceCache< * Transform `fetchedItem` for storing in the package cache. * @param fetchedItem Node obtained from GraphQL response */ - abstract coerceFetched(fetchedItem: FetchedItem): StoredItem | null; + abstract coerceFetched(fetchedItem: FetchedItem): CachedItem | null; private async queryPayload( baseUrl: string, - variables: GithubQueryParams, + variables: GithubGraphqlRepoParams, options: GithubHttpOptions - ): Promise<QueryResponse<FetchedItem>['repository']['payload'] | Error> { + ): Promise< + GithubGraphqlRepoResponse<FetchedItem>['repository']['payload'] | Error + > { try { const graphqlRes = await this.http.postJson< - GithubGraphqlResponse<QueryResponse<FetchedItem>> + GithubGraphqlResponse<GithubGraphqlRepoResponse<FetchedItem>> >('/graphql', { ...options, baseUrl, @@ -222,14 +224,14 @@ export abstract class AbstractGithubDatasourceCache< async getItemsImpl( releasesConfig: GetReleasesConfig, changelogRelease?: ChangelogRelease - ): Promise<StoredItem[]> { + ): Promise<CachedItem[]> { const { packageName, registryUrl } = releasesConfig; // The time meant to be used across the function const now = DateTime.now(); // Initialize items and timestamps for the new cache - let cacheItems: Record<string, StoredItem> = {}; + let cacheItems: Record<string, CachedItem> = {}; // Add random minutes to the creation date in order to // provide back-off time during mass cache invalidation. @@ -244,7 +246,7 @@ export abstract class AbstractGithubDatasourceCache< if (owner && name) { const baseUrl = this.getBaseUrl(registryUrl); const cacheKey = this.getCacheKey(registryUrl, packageName); - const cache = await packageCache.get<GithubDatasourceCache<StoredItem>>( + const cache = await packageCache.get<GithubDatasourceCache<CachedItem>>( this.cacheNs, cacheKey ); @@ -280,7 +282,7 @@ export abstract class AbstractGithubDatasourceCache< cacheItems ) ) { - const variables: GithubQueryParams = { + const variables: GithubGraphqlRepoParams = { owner, name, cursor: null, @@ -386,7 +388,7 @@ export abstract class AbstractGithubDatasourceCache< .diff(now, ['minutes']) .toObject(); if (ttlMinutes && ttlMinutes > 0) { - const cacheValue: GithubDatasourceCache<StoredItem> = { + const cacheValue: GithubDatasourceCache<CachedItem> = { items: cacheItems, createdAt: cacheCreatedAt, updatedAt: now.toISO(), @@ -413,12 +415,12 @@ export abstract class AbstractGithubDatasourceCache< getItems( releasesConfig: GetReleasesConfig, changelogRelease?: ChangelogRelease - ): Promise<StoredItem[]> { + ): Promise<CachedItem[]> { const { packageName, registryUrl } = releasesConfig; const cacheKey = this.getCacheKey(registryUrl, packageName); const promiseKey = `github-datasource-cache:${this.cacheNs}:${cacheKey}`; const res = - memCache.get<Promise<StoredItem[]>>(promiseKey) ?? + memCache.get<Promise<CachedItem[]>>(promiseKey) ?? this.getItemsImpl(releasesConfig, changelogRelease); memCache.set(promiseKey, res); return res; @@ -430,7 +432,7 @@ export abstract class AbstractGithubDatasourceCache< } public getLastReleaseTimestamp( - items: Record<string, StoredItem> + items: Record<string, CachedItem> ): string | null { let result: string | null = null; let latest: DateTime | null = null; @@ -454,7 +456,7 @@ export abstract class AbstractGithubDatasourceCache< changelogRelease: ChangelogRelease | undefined, now: DateTime, updateDuration: DurationLikeObject, - cacheItems: Record<string, StoredItem> + cacheItems: Record<string, CachedItem> ): boolean { if (!changelogRelease?.date) { return false; diff --git a/lib/modules/datasource/github-releases/cache/index.spec.ts b/lib/modules/datasource/github-releases/cache/index.spec.ts index 9839f49432109f69cf7ab1781e9074cf35d03949..2e5c70cf29c52b1338a080f3febf4aa33103246f 100644 --- a/lib/modules/datasource/github-releases/cache/index.spec.ts +++ b/lib/modules/datasource/github-releases/cache/index.spec.ts @@ -1,11 +1,12 @@ +import type { GithubGraphqlRelease } from '../../../../util/github/types'; import { GithubHttp } from '../../../../util/http/github'; -import { CacheableGithubReleases, FetchedRelease } from '.'; +import { CacheableGithubReleases } from '.'; describe('modules/datasource/github-releases/cache/index', () => { const http = new GithubHttp(); const cache = new CacheableGithubReleases(http, { resetDeltaMinutes: 0 }); - const fetchedItem: FetchedRelease = { + const fetchedItem: GithubGraphqlRelease = { version: '1.2.3', releaseTimestamp: '2020-04-09T10:00:00.000Z', isDraft: false, diff --git a/lib/modules/datasource/github-releases/cache/index.ts b/lib/modules/datasource/github-releases/cache/index.ts index 01da1bc6bbd1c00df6dd6fd514ba6d93c9ec4cb4..1c63e040a9d760b4f55540b3aff8ea06bcfc1d3f 100644 --- a/lib/modules/datasource/github-releases/cache/index.ts +++ b/lib/modules/datasource/github-releases/cache/index.ts @@ -1,6 +1,7 @@ import type { CacheOptions, - StoredItemBase, + GithubCachedRelease, + GithubGraphqlRelease, } from '../../../../util/github/types'; import type { GithubHttp } from '../../../../util/http/github'; import { AbstractGithubDatasourceCache } from './cache-base'; @@ -32,28 +33,9 @@ query ($owner: String!, $name: String!, $cursor: String, $count: Int!) { } `; -export interface FetchedRelease { - version: string; - releaseTimestamp: string; - isDraft: boolean; - isPrerelease: boolean; - url: string; - id: number; - name: string; - description: string; -} - -export interface StoredRelease extends StoredItemBase { - isStable?: boolean; - url: string; - id: number; - name: string; - description: string; -} - export class CacheableGithubReleases extends AbstractGithubDatasourceCache< - StoredRelease, - FetchedRelease + GithubCachedRelease, + GithubGraphqlRelease > { cacheNs = 'github-datasource-graphql-releases'; graphqlQuery = query; @@ -62,7 +44,7 @@ export class CacheableGithubReleases extends AbstractGithubDatasourceCache< super(http, opts); } - coerceFetched(item: FetchedRelease): StoredRelease | null { + coerceFetched(item: GithubGraphqlRelease): GithubCachedRelease | null { const { version, releaseTimestamp, @@ -78,7 +60,7 @@ export class CacheableGithubReleases extends AbstractGithubDatasourceCache< return null; } - const result: StoredRelease = { + const result: GithubCachedRelease = { version, releaseTimestamp, url, diff --git a/lib/modules/datasource/github-releases/digest.spec.ts b/lib/modules/datasource/github-releases/digest.spec.ts index 3a14f7a218a754489545024e672e0f9feefe68c5..35fff7e25297ff82587da7e267113e453f07c9d6 100644 --- a/lib/modules/datasource/github-releases/digest.spec.ts +++ b/lib/modules/datasource/github-releases/digest.spec.ts @@ -1,6 +1,6 @@ import hasha from 'hasha'; import * as httpMock from '../../../../test/http-mock'; -import type { DigestAsset } from '../../../util/github/types'; +import type { GithubDigestFile } from '../../../util/github/types'; import { GitHubReleaseMocker } from './test'; import { GithubReleasesDatasource } from '.'; @@ -77,7 +77,7 @@ describe('modules/datasource/github-releases/digest', () => { describe('mapDigestAssetToRelease', () => { describe('with digest file', () => { - const digestAsset: DigestAsset = { + const digestAsset: GithubDigestFile = { assetName: 'SHASUMS.txt', currentVersion: 'v1.0.0', currentDigest: 'old-digest', @@ -136,7 +136,7 @@ describe('modules/datasource/github-releases/digest', () => { }); describe('with digested file', () => { - const digestAsset: DigestAsset = { + const digestAsset: GithubDigestFile = { assetName: 'asset.zip', currentVersion: 'v1.0.0', currentDigest: '0'.repeat(64), diff --git a/lib/modules/datasource/github-releases/index.ts b/lib/modules/datasource/github-releases/index.ts index aff1caabd70b8e0c71a07d7ccfe71b836eafc136..19726031233960136713aa92910e13d7dfa2fe95 100644 --- a/lib/modules/datasource/github-releases/index.ts +++ b/lib/modules/datasource/github-releases/index.ts @@ -2,9 +2,9 @@ import hasha from 'hasha'; import { logger } from '../../../logger'; import type { - DigestAsset, - GithubRelease, - GithubReleaseAsset, + GithubDigestFile, + GithubRestAsset, + GithubRestRelease, } from '../../../util/github/types'; import { getApiBaseUrl, getSourceUrl } from '../../../util/github/url'; import { GithubHttp } from '../../../util/http/github'; @@ -37,20 +37,20 @@ export class GithubReleasesDatasource extends Datasource { } async findDigestFile( - release: GithubRelease, + release: GithubRestRelease, digest: string - ): Promise<DigestAsset | null> { + ): Promise<GithubDigestFile | null> { const smallAssets = release.assets.filter( - (a: GithubReleaseAsset) => a.size < 5 * 1024 + (a: GithubRestAsset) => a.size < 5 * 1024 ); for (const asset of smallAssets) { const res = await this.http.get(asset.browser_download_url); for (const line of res.body.split(newlineRegex)) { - const [lineDigest, lineFn] = line.split(regEx(/\s+/), 2); + const [lineDigest, lineFilename] = line.split(regEx(/\s+/), 2); if (lineDigest === digest) { return { assetName: asset.name, - digestedFileName: lineFn, + digestedFileName: lineFilename, currentVersion: release.tag_name, currentDigest: lineDigest, }; @@ -61,7 +61,7 @@ export class GithubReleasesDatasource extends Datasource { } async downloadAndDigest( - asset: GithubReleaseAsset, + asset: GithubRestAsset, algorithm: string ): Promise<string> { const res = this.http.stream(asset.browser_download_url); @@ -70,12 +70,12 @@ export class GithubReleasesDatasource extends Datasource { } async findAssetWithDigest( - release: GithubRelease, + release: GithubRestRelease, digest: string - ): Promise<DigestAsset | null> { + ): Promise<GithubDigestFile | null> { const algorithm = inferHashAlg(digest); const assetsBySize = release.assets.sort( - (a: GithubReleaseAsset, b: GithubReleaseAsset) => { + (a: GithubRestAsset, b: GithubRestAsset) => { if (a.size < b.size) { return -1; } @@ -101,9 +101,9 @@ export class GithubReleasesDatasource extends Datasource { /** Identify the asset associated with a known digest. */ async findDigestAsset( - release: GithubRelease, + release: GithubRestRelease, digest: string - ): Promise<DigestAsset | null> { + ): Promise<GithubDigestFile | null> { const digestFile = await this.findDigestFile(release, digest); if (digestFile) { return digestFile; @@ -115,8 +115,8 @@ export class GithubReleasesDatasource extends Datasource { /** Given a digest asset, find the equivalent digest in a different release. */ async mapDigestAssetToRelease( - digestAsset: DigestAsset, - release: GithubRelease + digestAsset: GithubDigestFile, + release: GithubRestRelease ): Promise<string | null> { const current = digestAsset.currentVersion.replace(regEx(/^v/), ''); const next = release.tag_name.replace(regEx(/^v/), ''); @@ -125,7 +125,7 @@ export class GithubReleasesDatasource extends Datasource { next ); const releaseAsset = release.assets.find( - (a: GithubReleaseAsset) => a.name === releaseChecksumAssetName + (a: GithubRestAsset) => a.name === releaseChecksumAssetName ); if (!releaseAsset) { return null; @@ -183,7 +183,7 @@ export class GithubReleasesDatasource extends Datasource { } const apiBaseUrl = getApiBaseUrl(registryUrl); - const { body: currentRelease } = await this.http.getJson<GithubRelease>( + const { body: currentRelease } = await this.http.getJson<GithubRestRelease>( `${apiBaseUrl}repos/${repo}/releases/tags/${currentValue}` ); const digestAsset = await this.findDigestAsset( @@ -194,7 +194,7 @@ export class GithubReleasesDatasource extends Datasource { if (!digestAsset || newValue === currentValue) { newDigest = currentDigest; } else { - const { body: newRelease } = await this.http.getJson<GithubRelease>( + const { body: newRelease } = await this.http.getJson<GithubRestRelease>( `${apiBaseUrl}repos/${repo}/releases/tags/${newValue}` ); newDigest = await this.mapDigestAssetToRelease(digestAsset, newRelease); @@ -216,7 +216,7 @@ export class GithubReleasesDatasource extends Datasource { const { packageName: repo, registryUrl } = config; const apiBaseUrl = getApiBaseUrl(registryUrl); const url = `${apiBaseUrl}repos/${repo}/releases?per_page=100`; - const res = await this.http.getJson<GithubRelease[]>(url, { + const res = await this.http.getJson<GithubRestRelease[]>(url, { paginate: true, }); const githubReleases = res.body; diff --git a/lib/modules/datasource/github-releases/test/index.ts b/lib/modules/datasource/github-releases/test/index.ts index 4343fff599b16621988343ee2cb7aa18f8aaecc9..e7dfcc82c91828b57e1f8c753aecb5103beb8cdf 100644 --- a/lib/modules/datasource/github-releases/test/index.ts +++ b/lib/modules/datasource/github-releases/test/index.ts @@ -1,6 +1,6 @@ import * as httpMock from '../../../../../test/http-mock'; import { partial } from '../../../../../test/util'; -import type { GithubRelease } from '../../../../util/github/types'; +import type { GithubRestRelease } from '../../../../util/github/types'; export class GitHubReleaseMocker { constructor( @@ -8,15 +8,15 @@ export class GitHubReleaseMocker { private readonly packageName: string ) {} - release(version: string): GithubRelease { + release(version: string): GithubRestRelease { return this.withAssets(version, {}); } withAssets( version: string, assets: { [key: string]: string } - ): GithubRelease { - const releaseData = partial<GithubRelease>({ + ): GithubRestRelease { + const releaseData = partial<GithubRestRelease>({ tag_name: version, published_at: '2020-03-09T11:00:00Z', prerelease: false, @@ -46,7 +46,10 @@ export class GitHubReleaseMocker { return releaseData; } - withDigestFileAsset(version: string, ...digests: string[]): GithubRelease { + withDigestFileAsset( + version: string, + ...digests: string[] + ): GithubRestRelease { return this.withAssets(version, { 'SHASUMS.txt': digests.join('\n') }); } } diff --git a/lib/modules/datasource/github-tags/cache.spec.ts b/lib/modules/datasource/github-tags/cache.spec.ts index 9e87272f9d88eb45779e2ed49c6d482ea89a4902..18f61aecdf667bdd26c081d8de280bfdd70b23ff 100644 --- a/lib/modules/datasource/github-tags/cache.spec.ts +++ b/lib/modules/datasource/github-tags/cache.spec.ts @@ -1,11 +1,12 @@ +import type { GithubGraphqlTag } from '../../../util/github/types'; import { GithubHttp } from '../../../util/http/github'; -import { CacheableGithubTags, FetchedTag } from './cache'; +import { CacheableGithubTags } from './cache'; describe('modules/datasource/github-tags/cache', () => { const http = new GithubHttp(); const cache = new CacheableGithubTags(http, { resetDeltaMinutes: 0 }); - const fetchedItem: FetchedTag = { + const fetchedItem: GithubGraphqlTag = { version: '1.2.3', target: { type: 'Commit', diff --git a/lib/modules/datasource/github-tags/cache.ts b/lib/modules/datasource/github-tags/cache.ts index 00d271354792a1caa57769ff69813e3fa9846823..6f7469e4c6a5dd55acc7d3c062d5a57f96c83132 100644 --- a/lib/modules/datasource/github-tags/cache.ts +++ b/lib/modules/datasource/github-tags/cache.ts @@ -1,4 +1,8 @@ -import type { CacheOptions, StoredItemBase } from '../../../util/github/types'; +import type { + CacheOptions, + GithubCachedTag, + GithubGraphqlTag, +} from '../../../util/github/types'; import type { GithubHttp } from '../../../util/http/github'; import { AbstractGithubDatasourceCache } from '../github-releases/cache/cache-base'; @@ -40,33 +44,9 @@ query ($owner: String!, $name: String!, $cursor: String, $count: Int!) { } `; -export interface FetchedTag { - version: string; - target: - | { - type: 'Commit'; - hash: string; - releaseTimestamp: string; - } - | { - type: 'Tag'; - target: { - hash: string; - }; - tagger: { - releaseTimestamp: string; - }; - }; -} - -export interface StoredTag extends StoredItemBase { - hash: string; - releaseTimestamp: string; -} - export class CacheableGithubTags extends AbstractGithubDatasourceCache< - StoredTag, - FetchedTag + GithubCachedTag, + GithubGraphqlTag > { readonly cacheNs = 'github-datasource-graphql-tags-v2'; readonly graphqlQuery = query; @@ -75,7 +55,7 @@ export class CacheableGithubTags extends AbstractGithubDatasourceCache< super(http, opts); } - coerceFetched(item: FetchedTag): StoredTag | null { + coerceFetched(item: GithubGraphqlTag): GithubCachedTag | null { const { version, target } = item; if (target.type === 'Commit') { const { hash, releaseTimestamp } = target; diff --git a/lib/modules/datasource/github-tags/index.ts b/lib/modules/datasource/github-tags/index.ts index 65b057dc1ea7aa212eddd8d65ed342355c5bcedf..83c6f5ebe62f8a1aca149219701fff39622f983b 100644 --- a/lib/modules/datasource/github-tags/index.ts +++ b/lib/modules/datasource/github-tags/index.ts @@ -1,5 +1,5 @@ import { logger } from '../../../logger'; -import type { GitHubTag, TagResponse } from '../../../util/github/types'; +import type { GithubRestRef, GithubRestTag } from '../../../util/github/types'; import { getApiBaseUrl, getSourceUrl } from '../../../util/github/url'; import { GithubHttp } from '../../../util/http/github'; import { Datasource } from '../datasource'; @@ -26,11 +26,11 @@ export class GithubTagsDatasource extends Datasource { let digest: string | null = null; try { const url = `${apiBaseUrl}repos/${githubRepo}/git/refs/tags/${tag}`; - const res = (await this.http.getJson<TagResponse>(url)).body.object; + const res = (await this.http.getJson<GithubRestRef>(url)).body.object; if (res.type === 'commit') { digest = res.sha; } else if (res.type === 'tag') { - digest = (await this.http.getJson<TagResponse>(res.url)).body.object + digest = (await this.http.getJson<GithubRestRef>(res.url)).body.object .sha; } else { logger.warn({ res }, 'Unknown git tag refs type'); @@ -88,7 +88,7 @@ export class GithubTagsDatasource extends Datasource { const url = `${apiBaseUrl}repos/${repo}/tags?per_page=100`; const versions = ( - await this.http.getJson<GitHubTag[]>(url, { + await this.http.getJson<GithubRestTag[]>(url, { paginate: true, }) ).body.map((o) => o.name); diff --git a/lib/modules/datasource/hermit/index.ts b/lib/modules/datasource/hermit/index.ts index 3ea3f975b711a49569e4787c82dddd164be65eb3..6afdd997ff81bf4b0a08a3972569b80fe248d672 100644 --- a/lib/modules/datasource/hermit/index.ts +++ b/lib/modules/datasource/hermit/index.ts @@ -1,6 +1,6 @@ import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; -import type { GithubRelease } from '../../../util/github/types'; +import type { GithubRestRelease } from '../../../util/github/types'; import { getApiBaseUrl } from '../../../util/github/url'; import { GithubHttp } from '../../../util/http/github'; import { regEx } from '../../../util/regex'; @@ -118,7 +118,7 @@ export class HermitDatasource extends Datasource { const apiBaseUrl = getApiBaseUrl(`https://${host}`); - const indexRelease = await this.http.getJson<GithubRelease>( + const indexRelease = await this.http.getJson<GithubRestRelease>( `${apiBaseUrl}repos/${owner}/${repo}/releases/tags/index` ); diff --git a/lib/util/github/types.ts b/lib/util/github/types.ts index b58fe4572bc37989b6c4d482747b014cb28cc6c9..40c65b805aa80fefea95b2b7cb43a0cd05f87e6d 100644 --- a/lib/util/github/types.ts +++ b/lib/util/github/types.ts @@ -1,31 +1,27 @@ -export interface GithubRelease { +/** + * REST responses + */ +export interface GithubRestRelease { id: number; tag_name: string; published_at: string; prerelease: boolean; draft?: boolean; - assets: GithubReleaseAsset[]; + assets: GithubRestAsset[]; html_url: string; name: string; body: string; } -export interface GithubReleaseAsset { +export interface GithubRestAsset { name: string; url: string; browser_download_url: string; size: number; } -export interface DigestAsset { - assetName: string; - currentVersion: string; - currentDigest: string; - digestedFileName?: string; -} - -export interface TagResponse { +export interface GithubRestRef { object: { type: string; url: string; @@ -33,16 +29,24 @@ export interface TagResponse { }; } -export interface GitHubTag { +export interface GithubRestTag { name: string; } /** - * Every `AbstractGithubDatasourceCache` implementation - * should have `graphqlQuery` that uses parameters - * defined this interface. + * Release asset + */ +export interface GithubDigestFile { + assetName: string; + currentVersion: string; + currentDigest: string; + digestedFileName?: string; +} + +/** + * Parameters used for GraphQL queries with pagination */ -export interface GithubQueryParams { +export interface GithubGraphqlRepoParams { owner: string; name: string; cursor: string | null; @@ -50,11 +54,9 @@ export interface GithubQueryParams { } /** - * Every `AbstractGithubDatasourceCache` implementation - * should have `graphqlQuery` that resembles the structure - * of this interface. + * Common shape for GraphQL responses for repository items */ -export interface QueryResponse<T = unknown> { +export interface GithubGraphqlRepoResponse<T = unknown> { repository: { payload: { nodes: T[]; @@ -67,35 +69,80 @@ export interface QueryResponse<T = unknown> { } /** - * Base interface meant to be extended by all implementations. - * Must have `version` and `releaseTimestamp` fields. + * GraphQL shape for releases + */ +export interface GithubGraphqlRelease { + version: string; + releaseTimestamp: string; + isDraft: boolean; + isPrerelease: boolean; + url: string; + id: number; + name: string; + description: string; +} + +/** + * GraphQL shape for tags + */ +export interface GithubGraphqlTag { + version: string; + target: + | { + type: 'Commit'; + hash: string; + releaseTimestamp: string; + } + | { + type: 'Tag'; + target: { + hash: string; + }; + tagger: { + releaseTimestamp: string; + }; + }; +} + +/** + * The structures being stored with long-term caching */ -export interface StoredItemBase { - /** The values of `version` field meant to be unique. */ +export interface GithubCachedItem { version: string; + releaseTimestamp: string; +} + +export interface GithubCachedRelease extends GithubCachedItem { + isStable?: boolean; + url: string; + id: number; + name: string; + description: string; +} - /** The `releaseTimestamp` field meant to be ISO-encoded date. */ +export interface GithubCachedTag extends GithubCachedItem { + hash: string; releaseTimestamp: string; } /** - * The data structure stored in the package cache. + * The common structure of datasource cache */ -export interface GithubDatasourceCache<StoredItem extends StoredItemBase> { - items: Record<string, StoredItem>; +export interface GithubDatasourceCache<CachedItem extends GithubCachedItem> { + items: Record<string, CachedItem>; - /** Cache full reset decision is based on `createdAt` value. */ + /** Used for determining hard reset time */ createdAt: string; - /** Cache soft updates are performed depending on `updatedAt` value. */ + /** Used for determining soft reset time */ updatedAt: string; - /** Latest release timestamp (`releaseTimestamp`) of all releases. */ + /** The most fresh `releaseTimestamp` of all items */ lastReleasedAt?: string; } /** - * The configuration for cache. + * The configuration for datasource cache */ export interface CacheOptions { /** @@ -160,6 +207,17 @@ export interface CacheOptions { maxUpdatePages?: number; } +/** + * This type is used to handle the following edge-case: + * + * 1. Package is being released on both NPM and GitHub + * 2. Renovate know there is new release in NPM + * 3. Renovate didn't update it's cache for GitHub datasource + * 4. We can't obtain release notes from GitHub because of this + * + * By providing this additional structure, we can soft reset cache + * once we know it's released for NPM or any other package manager. + */ export interface ChangelogRelease { date: string | Date; version: string; diff --git a/lib/workers/repository/update/pr/changelog/github/index.ts b/lib/workers/repository/update/pr/changelog/github/index.ts index cd4706b6ed60410883b9f92f64581079f78c9316..693794e27670ccf9c957b746b2a38ab151d79ce3 100644 --- a/lib/workers/repository/update/pr/changelog/github/index.ts +++ b/lib/workers/repository/update/pr/changelog/github/index.ts @@ -6,8 +6,8 @@ import type { GithubGitTreeNode, } from '../../../../../../types/platform/github'; import type { - GitHubTag, - GithubRelease, + GithubRestRelease, + GithubRestTag, } from '../../../../../../util/github/types'; import { GithubHttp } from '../../../../../../util/http/github'; import { fromBase64 } from '../../../../../../util/string'; @@ -29,7 +29,7 @@ export async function getTags( logger.trace('github.getTags()'); try { const url = `${endpoint}repos/${repository}/tags?per_page=100`; - const res = await http.getJson<GitHubTag[]>(url, { + const res = await http.getJson<GithubRestTag[]>(url, { paginate: true, }); const tags = res.body; @@ -121,7 +121,7 @@ export async function getReleaseList( const apiBaseUrl = project.apiBaseUrl!; const repository = project.repository; const url = `${ensureTrailingSlash(apiBaseUrl)}repos/${repository}/releases`; - const res = await http.getJson<GithubRelease[]>(`${url}?per_page=100`, { + const res = await http.getJson<GithubRestRelease[]>(`${url}?per_page=100`, { paginate: true, });