1
- import * as util from 'util'
2
- import { Octokit } from '@octokit/core' ;
3
- import { graphql } from '@octokit/graphql' ;
1
+ import { graphql } from '@octokit/graphql'
2
+ import shell from 'shelljs'
4
3
5
- const GH_GRAPHQL_URL = 'https://api.github.com' ;
6
- const GH_API_URL = 'https://api.github.com' ;
4
+ // Regexes to find the changelog contents within a PR.
5
+ const FIXES_REGEX = / ^ C H A N G E L O G - F I X E S : ( .* ) / gm
6
+ const ADDS_REGEX = / ^ C H A N G E L O G - A D D S : ( .* ) / gm
7
7
8
8
export interface Changelog {
9
- added : string [ ] ;
9
+ added : string [ ]
10
10
fixed : string [ ]
11
11
}
12
12
13
- export async function generateChangelog ( githubAuthToken : string , current_version : string ) : Promise < Changelog | null > {
14
- const octokit = new Octokit ( {
15
- baseUrl : GH_API_URL ,
16
- auth : githubAuthToken ,
17
- } ) ;
13
+ interface ReleaseInfo {
14
+ name : string
15
+ version : string
16
+ }
17
+
18
+ interface GraphQLRelease {
19
+ name : string
20
+ tag : {
21
+ name : string
22
+ }
23
+ }
18
24
25
+ // Generates a changelog by parsing PRs that were newly merged into the currentVersion.
26
+ export async function generateChangelog (
27
+ githubAuthToken : string ,
28
+ currentVersion : string ,
29
+ channel : string
30
+ ) : Promise < Changelog > {
19
31
const graphqlWithAuth = graphql . defaults ( {
20
32
headers : {
21
- authorization : `token ${ githubAuthToken } ` ,
22
- } ,
23
- } ) ;
24
-
25
-
26
- return fetchPullRequestMetadataFromCommits ( [
27
- "b8e834998860d062943d27ec0134e406016771d0" ,
28
- "239753dbce79925f83e8b378dd41c6b899f159d8" ,
29
- "9297e57f422596ffed102d6ec291d186d78fdb90"
30
- ] , graphqlWithAuth ) . then ( ( pullRequestMetadata ) => {
31
- console . log ( pullRequestMetadata ) ;
32
- return {
33
- added : [ "sdfsdf" ] ,
34
- fixed : [ "sdfsdf" ]
33
+ authorization : `token ${ githubAuthToken } `
35
34
}
36
35
} )
36
+
37
+ const releases = await getReleases ( graphqlWithAuth )
38
+
39
+ // Find the most recent release prior to this one, ignoring any releases that were the same version from a
40
+ // prior cherrypick (e.g. v0.2022.01.01.stable_00 is still part of the same release as v0.2022.01.01.stable_01).
41
+ let lastReleaseVersion
42
+ for ( const release of releases ) {
43
+ // Only consider releases of the same type as the current channel.
44
+ if ( release . name . startsWith ( channel ) ) {
45
+ if (
46
+ ! release . version . startsWith (
47
+ currentVersion . substring ( 0 , currentVersion . length - 1 - 2 )
48
+ )
49
+ ) {
50
+ // Check for a release where `release.version` is less then `currentVersion`, which means `localCompare` would return `1`.
51
+ if (
52
+ currentVersion . localeCompare ( release . version , undefined , {
53
+ numeric : true ,
54
+ sensitivity : 'base'
55
+ } ) === 1
56
+ ) {
57
+ lastReleaseVersion = release . version
58
+ break
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ if ( lastReleaseVersion ) {
65
+ // Find all the commits between the current release and the last release.
66
+ const commits = shell
67
+ . exec (
68
+ // eslint-disable-next-line no-useless-escape
69
+ `git --no-pager -C ~/Desktop/warp/warp log ${ lastReleaseVersion } ..${ currentVersion } --pretty=format:"\"%H\""` ,
70
+ { silent : true }
71
+ )
72
+ . stdout . trim ( )
73
+ . split ( '\n' )
74
+ const pullRequestMetadata = await fetchPullRequestBodyFromCommits (
75
+ commits ,
76
+ graphqlWithAuth
77
+ )
78
+ return parseChangelogFromPrDescriptions ( pullRequestMetadata )
79
+ } else {
80
+ return Promise . reject (
81
+ Error ( 'Unable to find last release prior to the given release' )
82
+ )
83
+ }
37
84
}
38
85
39
- async function fetchPullRequestMetadataFromCommits ( commits : string [ ] , graphql : Function ) : Promise < string [ ] > {
40
- let commitsSubQuery = '' ;
86
+ // Fetches PR body text from a series of commits.
87
+ async function fetchPullRequestBodyFromCommits (
88
+ commits : string [ ] ,
89
+ graphqlWithAuth : Function
90
+ ) : Promise < string [ ] > {
91
+ let commitsSubQuery = ''
41
92
for ( const oid of commits ) {
42
93
commitsSubQuery += `
43
94
commit_${ oid } : object(oid: "${ oid } ") {
@@ -53,72 +104,83 @@ async function fetchPullRequestMetadataFromCommits(commits: string[], graphql: F
53
104
}
54
105
}
55
106
}
56
- ` ;
107
+ `
57
108
}
58
109
59
- const response = await graphql ( `
110
+ const response = await graphqlWithAuth ( `
60
111
{
61
112
repository(owner: "warpdotdev", name: "warp-internal") {
62
113
${ commitsSubQuery }
63
114
}
64
115
}
65
- ` ) ;
116
+ ` )
66
117
67
- const commitsInfo : string [ ] = [ ] ;
68
-
118
+ const commitsInfo : string [ ] = [ ]
69
119
for ( const oid of commits ) {
70
- let commitResponse = response . repository [ 'commit_' + oid ] ;
71
- console . log ( commitResponse ) ;
120
+ const commitResponse = response . repository [ `commit_${ oid } ` ]
72
121
if ( commitResponse . associatedPullRequests . nodes . length > 0 ) {
73
- commitsInfo . push ( commitResponse . associatedPullRequests . nodes [ 0 ] . body ) ;
122
+ commitsInfo . push ( commitResponse . associatedPullRequests . nodes [ 0 ] . body )
74
123
}
75
124
}
76
- return commitsInfo ;
125
+ return commitsInfo
77
126
}
78
127
128
+ // Returns the most recent 100 releases, sorted by creation.
129
+ async function getReleases ( graphqlWithAuth : Function ) : Promise < ReleaseInfo [ ] > {
130
+ const releaseQuery = `releases(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {
131
+ nodes {
132
+ name
133
+ tag {
134
+ name
135
+ }
136
+ }
137
+ }`
138
+
139
+ const response = await graphqlWithAuth ( `
140
+ {
141
+ repository(owner: "warpdotdev", name: "warp-internal") {
142
+ ${ releaseQuery }
143
+ }
144
+ }
145
+ ` )
146
+
147
+ const releaseInfo : ReleaseInfo [ ] = [ ]
148
+ const releases = response . repository . releases . nodes as GraphQLRelease [ ]
79
149
150
+ for ( const release of releases ) {
151
+ releaseInfo . push ( { name : release . name , version : release . tag . name } )
152
+ }
80
153
81
- // export async function wait(milliseconds: number): Promise<string> {
82
- // return new Promise(resolve => {
83
- // if (isNaN(milliseconds)) {
84
- // throw new Error('milliseconds not a number')
85
- // }
86
-
87
- // setTimeout(() => resolve('done!'), milliseconds)
88
- // })
89
- // }
90
-
91
- // export async function fetchPullRequestFromCommit(commits: string[]): Promise<string[]> {
92
- // let commitsSubQuery = '';
93
- // for (const oid of commits) {
94
- // commitsSubQuery += `
95
- // commit_${oid}: object(oid: "${oid}") {
96
- // ... on Commit {
97
- // oid
98
- // associatedPullRequests(first: 1) {
99
- // nodes {
100
- // body
101
- // }
102
- // }
103
- // }
104
- // }
105
- // `;
106
- // }
107
-
108
- // const response = await graphqlRequest(`
109
- // {
110
- // repository(owner: "warpdotdev", name: "warp-internal") {
111
- // ${commitsSubQuery}
112
- // }
113
- // }
114
- // `);
115
-
116
- // const commitsInfo: string[] = [];
117
- // for (const oid of commits) {
118
- // commitsInfo.push(response.repository['commit_' + oid].associatedPullRequests.nodes[0].body);
119
- // }
120
- // return commitsInfo;
121
- // }
154
+ return releaseInfo
155
+ }
122
156
157
+ // Produces the changelog from an array of PR descriptions. At a high level, we
158
+ // traverse each PR description searching for `CHANGELOG-FIXES:` or `CHANGELOG-ADDS:`
159
+ // to determine what the changelog conntents should be.
160
+ function parseChangelogFromPrDescriptions ( prDescriptions : string [ ] ) : Changelog {
161
+ const changelog_fixed : string [ ] = [ ]
162
+ const changelog_new : string [ ] = [ ]
163
+
164
+ for ( const prDescription of prDescriptions ) {
165
+ const fixMatches = prDescription . matchAll ( FIXES_REGEX )
166
+ if ( fixMatches ) {
167
+ const fixMatchesArray = [ ...fixMatches ]
168
+ for ( const fixMatch of fixMatchesArray ) {
169
+ changelog_fixed . push ( fixMatch [ 1 ] . trim ( ) )
170
+ }
171
+ }
123
172
173
+ const addMatches = prDescription . matchAll ( ADDS_REGEX )
174
+ if ( addMatches ) {
175
+ const addMatchesArray = [ ...addMatches ]
176
+ for ( const addMatch of addMatchesArray ) {
177
+ changelog_new . push ( addMatch [ 1 ] . trim ( ) )
178
+ }
179
+ }
180
+ }
124
181
182
+ return {
183
+ added : changelog_new ,
184
+ fixed : changelog_fixed
185
+ }
186
+ }
0 commit comments