@@ -10,31 +10,31 @@ import { config } from "dotenv";
1010config ( ) ;
1111
1212const OUTPUT_FILE = join (
13- dirname ( fileURLToPath ( import . meta. url ) ) ,
14- ".." ,
15- "src" ,
16- "ghdata.json" ,
13+ dirname ( fileURLToPath ( import . meta. url ) ) ,
14+ ".." ,
15+ "src" ,
16+ "ghdata.json" ,
1717) ;
1818const REPO = { owner : "LanRhyme" , name : "MicYou" } ;
1919const EXCLUDE_USERS = new Set ( [
20- "LanRhyme" ,
21- "ChinsaaWei" ,
22- "dependabot[bot]" ,
23- "crowdin-bot" ,
20+ "LanRhyme" ,
21+ "ChinsaaWei" ,
22+ "dependabot[bot]" ,
23+ "crowdin-bot" ,
2424] ) ;
2525
2626interface OutputData {
27- version : string ;
28- releaseUrl : string ;
29- releaseDate : string ;
30- releaseNotes : string ;
31- contributors : Array < {
32- login : string ;
33- avatar_url : string ;
34- html_url : string ;
35- contributions : number ;
36- } > ;
37- fetchedAt : string ;
27+ version : string ;
28+ releaseUrl : string ;
29+ releaseDate : string ;
30+ releaseNotes : string ;
31+ contributors : Array < {
32+ login : string ;
33+ avatar_url : string ;
34+ html_url : string ;
35+ contributions : number ;
36+ } > ;
37+ fetchedAt : string ;
3838}
3939
4040// 合并的 GraphQL 查询
@@ -64,134 +64,143 @@ const QUERY = `
6464` ;
6565
6666async function fetchGraphQL (
67- query : string ,
68- variables : Record < string , string | null > ,
69- token : string ,
67+ query : string ,
68+ variables : Record < string , string | null > ,
69+ token : string ,
7070) {
71- const res = await fetch ( "https://api.github.com/graphql" , {
72- method : "POST" ,
73- headers : {
74- "Content-Type" : "application/json" ,
75- Authorization : `Bearer ${ token } ` ,
76- } ,
77- body : JSON . stringify ( { query, variables } ) ,
78- } ) ;
79- if ( ! res . ok ) throw new Error ( `GitHub API error: ${ res . status } ` ) ;
80- return res . json ( ) ;
71+ const res = await fetch ( "https://api.github.com/graphql" , {
72+ method : "POST" ,
73+ headers : {
74+ "Content-Type" : "application/json" ,
75+ Authorization : `Bearer ${ token } ` ,
76+ } ,
77+ body : JSON . stringify ( { query, variables } ) ,
78+ } ) ;
79+ if ( ! res . ok ) throw new Error ( `GitHub API error: ${ res . status } ` ) ;
80+ return res . json ( ) ;
8181}
8282
8383async function main ( ) {
84- // 支持 GH_TOKEN 或 GITHUB_TOKEN(GitHub Actions 默认提供)
85- const token = process . env . GH_TOKEN || process . env . GITHUB_TOKEN ;
86- if ( ! token ) {
87- console . error ( "Error: Set GH_TOKEN or GITHUB_TOKEN environment variable." ) ;
88- process . exit ( 1 ) ;
89- }
90-
91- console . log ( "Fetching GitHub data..." ) ;
92-
93- try {
94- // 获取 release
95- const releaseRes = await fetchGraphQL (
96- `query($owner: String!, $name: String!) {
84+ // 支持 GH_TOKEN 或 GITHUB_TOKEN(GitHub Actions 默认提供)
85+ const token = process . env . GH_TOKEN || process . env . GITHUB_TOKEN ;
86+ if ( ! token ) {
87+ console . error ( "Error: Set GH_TOKEN or GITHUB_TOKEN environment variable." ) ;
88+ process . exit ( 1 ) ;
89+ }
90+
91+ console . log ( "Fetching GitHub data..." ) ;
92+
93+ try {
94+ // 获取 release
95+ const releaseRes = await fetchGraphQL (
96+ `query($owner: String!, $name: String!) {
9797 repository(owner: $owner, name: $name) {
98- latestRelease { tagName publishedAt url description }
98+ releases(first: 20, orderBy: {field: CREATED_AT, direction: DESC}) {
99+ nodes {
100+ tagName
101+ publishedAt
102+ url
103+ description
104+ isPrerelease
105+ }
106+ }
99107 }
100108 }` ,
101- REPO ,
102- token ,
103- ) ;
104- const release = releaseRes . data ?. repository ?. latestRelease ;
105- if ( ! release ) throw new Error ( "No releases found" ) ;
106-
107- // 分页获取所有 commits
108- const commits : Array < {
109- author ?: {
110- user ?: { login : string ; avatarUrl : string ; url : string } | null ;
111- } | null ;
112- } > = [ ] ;
113- let after : string | null = null ;
114- let page = 0 ;
115-
116- while ( true ) {
117- page ++ ;
118- const res = await fetchGraphQL ( QUERY , { ...REPO , after } , token ) ;
119- const history = res . data ?. repository ?. defaultBranchRef ?. target ?. history ;
120- if ( ! history ) break ;
121- commits . push ( ...history . nodes ) ;
122- console . log ( ` Page ${ page } : ${ commits . length } commits` ) ;
123- if ( ! history . pageInfo . hasNextPage ) break ;
124- after = history . pageInfo . endCursor ;
125- }
126-
127- // 统计贡献者
128- const contributorMap = new Map <
129- string ,
130- {
131- login : string ;
132- avatar_url : string ;
133- html_url : string ;
134- contributions : number ;
135- }
136- > ( ) ;
137- for ( const c of commits ) {
138- const u = c . author ?. user ;
139- if ( ! u ?. login || EXCLUDE_USERS . has ( u . login ) || u . login . includes ( "[bot]" ) )
140- continue ;
141- const existing = contributorMap . get ( u . login ) ;
142- if ( existing ) existing . contributions ++ ;
143- else
144- contributorMap . set ( u . login , {
145- login : u . login ,
146- avatar_url : u . avatarUrl ,
147- html_url : u . url ,
148- contributions : 1 ,
149- } ) ;
150- }
151- const contributors = Array . from ( contributorMap . values ( ) ) . sort (
152- ( a , b ) => b . contributions - a . contributions ,
153- ) ;
154-
155- const newData = {
156- version : release . tagName . replace ( / ^ v / , "" ) ,
157- releaseUrl : release . url ,
158- releaseDate : release . publishedAt ,
159- releaseNotes : release . description || "" ,
160- contributors,
161- } ;
162-
163- // 检查是否有变化
164- if ( existsSync ( OUTPUT_FILE ) ) {
165- const existing : OutputData = JSON . parse (
166- readFileSync ( OUTPUT_FILE , "utf-8" ) ,
167- ) ;
168- if (
169- existing . version === newData . version &&
170- JSON . stringify ( existing . contributors ) ===
171- JSON . stringify ( newData . contributors )
172- ) {
173- console . log (
174- `✓ Version: ${ newData . version } , Contributors: ${ newData . contributors . length } (no changes)` ,
175- ) ;
176- return ;
177- }
178- }
179-
180- writeFileSync (
181- OUTPUT_FILE ,
182- JSON . stringify (
183- { ...newData , fetchedAt : new Date ( ) . toISOString ( ) } ,
184- null ,
185- 2 ,
186- ) ,
187- ) ;
188- console . log (
189- `✓ Version: ${ newData . version } , Contributors: ${ newData . contributors . length } ` ,
190- ) ;
191- } catch ( error ) {
192- console . error ( "Failed:" , error ) ;
193- process . exit ( 1 ) ;
194- }
109+ REPO ,
110+ token ,
111+ ) ;
112+ const releases = releaseRes . data ?. repository ?. releases ?. nodes || [ ] ;
113+ const release = releases . find ( ( r : any ) => r && ! r . isPrerelease ) ;
114+ if ( ! release ) throw new Error ( "No stable release found" ) ;
115+
116+ // 分页获取所有 commits
117+ const commits : Array < {
118+ author ?: {
119+ user ?: { login : string ; avatarUrl : string ; url : string } | null ;
120+ } | null ;
121+ } > = [ ] ;
122+ let after : string | null = null ;
123+ let page = 0 ;
124+
125+ while ( true ) {
126+ page ++ ;
127+ const res = await fetchGraphQL ( QUERY , { ...REPO , after } , token ) ;
128+ const history = res . data ?. repository ?. defaultBranchRef ?. target ?. history ;
129+ if ( ! history ) break ;
130+ commits . push ( ...history . nodes ) ;
131+ console . log ( ` Page ${ page } : ${ commits . length } commits` ) ;
132+ if ( ! history . pageInfo . hasNextPage ) break ;
133+ after = history . pageInfo . endCursor ;
134+ }
135+
136+ // 统计贡献者
137+ const contributorMap = new Map <
138+ string ,
139+ {
140+ login : string ;
141+ avatar_url : string ;
142+ html_url : string ;
143+ contributions : number ;
144+ }
145+ > ( ) ;
146+ for ( const c of commits ) {
147+ const u = c . author ?. user ;
148+ if ( ! u ?. login || EXCLUDE_USERS . has ( u . login ) || u . login . includes ( "[bot]" ) )
149+ continue ;
150+ const existing = contributorMap . get ( u . login ) ;
151+ if ( existing ) existing . contributions ++ ;
152+ else
153+ contributorMap . set ( u . login , {
154+ login : u . login ,
155+ avatar_url : u . avatarUrl ,
156+ html_url : u . url ,
157+ contributions : 1 ,
158+ } ) ;
159+ }
160+ const contributors = Array . from ( contributorMap . values ( ) ) . sort (
161+ ( a , b ) => b . contributions - a . contributions ,
162+ ) ;
163+
164+ const newData = {
165+ version : release . tagName . replace ( / ^ v / , "" ) ,
166+ releaseUrl : release . url ,
167+ releaseDate : release . publishedAt ,
168+ releaseNotes : release . description || "" ,
169+ contributors,
170+ } ;
171+
172+ // 检查是否有变化
173+ if ( existsSync ( OUTPUT_FILE ) ) {
174+ const existing : OutputData = JSON . parse (
175+ readFileSync ( OUTPUT_FILE , "utf-8" ) ,
176+ ) ;
177+ if (
178+ existing . version === newData . version &&
179+ JSON . stringify ( existing . contributors ) ===
180+ JSON . stringify ( newData . contributors )
181+ ) {
182+ console . log (
183+ `✓ Version: ${ newData . version } , Contributors: ${ newData . contributors . length } (no changes)` ,
184+ ) ;
185+ return ;
186+ }
187+ }
188+
189+ writeFileSync (
190+ OUTPUT_FILE ,
191+ JSON . stringify (
192+ { ...newData , fetchedAt : new Date ( ) . toISOString ( ) } ,
193+ null ,
194+ 2 ,
195+ ) ,
196+ ) ;
197+ console . log (
198+ `✓ Version: ${ newData . version } , Contributors: ${ newData . contributors . length } ` ,
199+ ) ;
200+ } catch ( error ) {
201+ console . error ( "Failed:" , error ) ;
202+ process . exit ( 1 ) ;
203+ }
195204}
196205
197- main ( ) ;
206+ main ( ) ;
0 commit comments