2023-08-11 17:23:56 +02:00
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import yargs from 'yargs' ;
2024-05-11 15:50:05 +02:00
import tokens from './tokens.json' ;
2023-08-11 17:23:56 +02:00
const { sha } = yargs
. options ({
sha : {
description : 'The hash of the last commit of the previous release' ,
type : 'string' ,
demandOption : true ,
},
})
. parseSync ();
2024-02-02 18:59:57 +01:00
if ( ! sha ) {
throw new Error ( 'Sha of last commit of the previous release has to be passed!' );
}
2023-12-13 23:48:35 +01:00
2024-02-02 18:59:57 +01:00
type GithubAuthor = {
name : string ;
user : { login : string } | null ;
};
2023-12-13 23:48:35 +01:00
2024-02-02 18:59:57 +01:00
type GithubPullRequest = {
number : string ;
url : string ;
};
2023-12-13 23:48:35 +01:00
2024-02-02 18:59:57 +01:00
type GithubCommit = {
oid : string ;
url : string ;
message : string ;
authors : {
totalCount : number ;
nodes : GithubAuthor [];
};
associatedPullRequests : {
nodes : GithubPullRequest [];
};
};
2023-12-13 23:48:35 +01:00
2024-02-02 18:59:57 +01:00
type CommitQueryResponse = {
data : {
repository : {
ref : {
target : {
history : {
totalCount : number ;
pageInfo : {
hasNextPage : boolean ;
endCursor : string ;
};
nodes : GithubCommit [];
};
};
};
};
};
};
type Commit = {
repoUrl : string ;
revision : number ;
url : string ;
authors : GithubAuthor [];
title : string ;
pullRequest? : GithubPullRequest ;
2023-12-13 23:48:35 +01:00
};
2023-08-11 17:23:56 +02:00
const fetchCommits = async (
owner : string ,
repo : string ,
loadUntilSha : string ,
2024-02-02 18:59:57 +01:00
endCursor? : string ,
) : Promise < { totalRepoCommitCount : number ; commits : GithubCommit [] } > => {
const query = `query ($owner: String!, $name: String!, $afterSha: String) {
repository(owner: $owner, name: $name) {
ref(qualifiedName: "master") {
target {
... on Commit {
history(first: 100, after: $afterSha) {
totalCount
pageInfo {
hasNextPage
endCursor
}
nodes {
oid
# use "message" instead of "messageHeadline" because GitHub truncates titles if they are too long
message
url
authors(first: 100) {
totalCount
nodes {
name
user {
login
}
}
}
associatedPullRequests(first: 1) {
nodes {
number
url
}
}
}
}
}
}
}
}
}` ;
const variables = {
owner ,
name : repo ,
afterSha : endCursor ,
};
const response = ( await (
await fetch ( 'https://api.github.com/graphql' , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
2024-05-11 15:50:05 +02:00
Authorization : `Bearer ${ tokens . githubToken } ` ,
2024-02-02 18:59:57 +01:00
},
body : JSON.stringify ({ query , variables }),
})
). json ()) as CommitQueryResponse ;
const repoHistory = response . data . repository . ref . target . history ;
const { hasNextPage , endCursor : repoHistoryEndCursor } = repoHistory . pageInfo ;
const commits = repoHistory . nodes ;
const indexOfOldestCommitToLoad = commits . findIndex (( commit ) => commit . oid === loadUntilSha );
2023-08-11 17:23:56 +02:00
const loadedAllCommits = indexOfOldestCommitToLoad !== - 1 ;
if ( loadedAllCommits ) {
2024-02-02 18:59:57 +01:00
return { totalRepoCommitCount : repoHistory.totalCount , commits : commits.slice ( 0 , indexOfOldestCommitToLoad ) };
2023-08-11 17:23:56 +02:00
}
2024-02-02 18:59:57 +01:00
if ( ! hasNextPage ) {
throw new Error ( `No commit with sha " ${ loadUntilSha } " found!` );
}
const loadedCommits = [
... commits ,
...( await fetchCommits ( owner , repo , loadUntilSha , repoHistoryEndCursor )). commits ,
];
return { totalRepoCommitCount : repoHistory.totalCount , commits : loadedCommits };
};
const createCommitAuthorCredit = ( authors : GithubAuthor []) : string => {
const authorsWithGithubAccount = authors . filter (( author ) => !! author . user ? . login );
if ( ! authorsWithGithubAccount . length ) {
return `by @ ${ authors [ 0 ]. name } ` ;
}
const commitAuthorsString = authorsWithGithubAccount
. map (( author ) => author . user ! . login )
. reduce (( authorCredit , author ) => ` ${ authorCredit } , @ ${ author } ` );
return `by @ ${ commitAuthorsString } ` ;
2023-08-11 17:23:56 +02:00
};
const createChangelogCommitLine = ( commit : Commit ) : string => {
2024-02-02 18:59:57 +01:00
try {
const authorCredit = createCommitAuthorCredit ( commit . authors );
const revision = `([r ${ commit . revision } ]( ${ commit . url } ))` ;
2023-08-11 17:23:56 +02:00
2024-02-02 18:59:57 +01:00
if ( ! commit . pullRequest ) {
return ` ${ revision } ${ commit . title } ( ${ authorCredit } )` ;
}
const title = commit . title . replace ( /(.*) \(#[0-9]+\)$/g , '$1' ); // remove the possible pr number from the title (e.g. "my commit title (#420)" => "my commit title")
const prId = commit . pullRequest . number ;
const prUrl = commit . pullRequest . url ;
const prLink = `[# ${ prId } ]( ${ prUrl } )` ;
return ` ${ revision } ${ title } ( ${ prLink } ${ authorCredit } )` ;
} catch ( e ) {
console . log ( 'Unexpected commit format' , commit );
throw e ;
2023-08-11 17:23:56 +02:00
}
};
2024-02-23 21:19:35 +01:00
const getContributors = ( commits : Commit []) : string [] => [
... new Set (
commits
. map (( commit ) => commit . authors . map (( author ) => author . user ? . login ! ). filter (( author ) => !! author ))
. flat (),
),
];
2023-08-11 17:23:56 +02:00
const createChangelog = async ( prevReleaseLastCommitSha : string ) => {
const owner = 'Suwayomi' ;
2024-01-05 20:11:29 +01:00
const repo = 'Suwayomi-WebUI' ;
2023-08-11 17:23:56 +02:00
2024-02-02 18:59:57 +01:00
const { totalRepoCommitCount : numberOfCommits , commits : githubCommits } = await fetchCommits (
owner ,
repo ,
prevReleaseLastCommitSha ,
);
2023-08-11 17:23:56 +02:00
const commits : Commit [] = githubCommits . map (( githubCommit , index ) => ({
repoUrl : `https://github.com/ ${ owner } / ${ repo } ` ,
revision : numberOfCommits - index ,
2024-02-02 18:59:57 +01:00
url : githubCommit.url ,
authors : githubCommit.authors.nodes ,
title : githubCommit.message.split ( '\n' )[ 0 ],
pullRequest : githubCommit.associatedPullRequests.nodes [ 0 ],
2023-08-11 17:23:56 +02:00
}));
const commitChangelogLines = commits . map ( createChangelogCommitLine );
commitChangelogLines . forEach (( commit ) => console . log ( '-' , commit ));
2024-02-23 21:19:35 +01:00
const contributors = getContributors ( commits );
const contributorString = contributors . reduce (( authorCredit , author ) => ` ${ authorCredit } , @ ${ author } ` );
console . log ( ` \ n \ nContributors: \ n@ ${ contributorString } ` );
2023-08-11 17:23:56 +02:00
};
createChangelog ( sha ). catch (( error ) => {
console . error ( 'Failed due to' , error );
process . exit ( 1 );
});