11import type { Octokit } from "./octokit" ;
2+ import { isEqual , none , Option , some } from "./option" ;
23
3- import type {
4+ import {
5+ isExactSemanticVersion ,
46 ExactSemanticVersion ,
57 RepositorySlug ,
68 SemanticVersion ,
@@ -19,18 +21,87 @@ type Tag = {
1921
2022export type TagsResponse = ReadonlyArray < Tag > ;
2123
22- // NOTE: there's a foot-gun here around lack of pagination
23- export async function fetchRepoTags (
24+ function containsExactTag (
25+ tags : readonly SemanticVersion [ ] | undefined
26+ ) : ExactSemanticVersion | undefined {
27+ if ( tags === undefined ) {
28+ return undefined ;
29+ }
30+ return tags . find ( isExactSemanticVersion ) ;
31+ }
32+
33+ // Find the exact semantic version tag that this tag maps to.
34+ //
35+ // We need an exact tag because that's the only accepted input
36+ // to GitHub's getReleaseByTag endpoint.
37+ export async function findExactSemanticVersionTag (
2438 octokit : Octokit ,
25- slug : RepositorySlug
26- ) : Promise < TagsResponse > {
27- const response = await octokit . rest . repos . listTags ( {
28- owner : slug . owner ,
29- repo : slug . repository ,
30- per_page : 100 ,
31- } ) ;
32- // NOTE: we are not parsing here, so this is an unlawful type cast
33- return response . data as unknown as TagsResponse ;
39+ slug : RepositorySlug ,
40+ givenTag : SemanticVersion
41+ ) : Promise < ExactSemanticVersion > {
42+ if ( isExactSemanticVersion ( givenTag ) ) {
43+ return givenTag ;
44+ }
45+
46+ const versionsBySha : Record < Sha , SemanticVersion [ ] > = { } ;
47+ let givenTagSha : Option < Sha > = none ( ) ;
48+
49+ // Conditions to stop looping are -- we know both the:
50+ //
51+ // - sha that the given tag points to
52+ // - exact version tag matching that sha
53+ //
54+ // These can be found in either order.
55+
56+ for await ( const response of octokit . paginate . iterator (
57+ octokit . rest . repos . listTags ,
58+ {
59+ owner : slug . owner ,
60+ repo : slug . repository ,
61+ per_page : 100 ,
62+ }
63+ ) ) {
64+ // NOTE: we are not parsing here, so this is an unlawful type cast
65+ for ( const tag of response . data as unknown as TagsResponse ) {
66+ const sha = tag . commit . sha ;
67+ const version = tag . name ;
68+
69+ // If we found the sha the given tag points to
70+ if ( version === givenTag ) {
71+ givenTagSha = some ( sha ) ;
72+ // check if we already knew the exact version tag matching that sha
73+ const maybeExactTag = containsExactTag ( versionsBySha [ sha ] ) ;
74+ if ( maybeExactTag !== undefined ) {
75+ return maybeExactTag ;
76+ }
77+ }
78+
79+ // If we're not looking at the given tag, and we're not looking
80+ // at an exact version, this data is of no use to us.
81+ if ( ! isExactSemanticVersion ( version ) ) {
82+ continue ;
83+ }
84+
85+ // It is possible that we know the sha for the given tag,
86+ // we're just looking for exact version tag matching that sha.
87+ if ( isEqual ( givenTagSha , sha ) ) {
88+ return version ;
89+ }
90+
91+ // Otherwise, record this map of sha -> exact version tag
92+ // so we can find it when we know the sha of the given tag.
93+ const associatedVersions = versionsBySha [ sha ] ;
94+ if ( associatedVersions === undefined ) {
95+ versionsBySha [ sha ] = [ version ] ;
96+ } else {
97+ associatedVersions . push ( version ) ;
98+ }
99+ }
100+ }
101+
102+ throw new Error (
103+ `Expected to find an exact semantic version tag matching ${ givenTag } `
104+ ) ;
34105}
35106
36107type ReleaseAssetMetadata = {
0 commit comments