11import * as fs from "fs/promises" ;
2- import * as path from "path" ;
3- import { getDirname } from "../common/utils.js" ;
42import type { RestEndpointMethodTypes } from "@octokit/rest" ;
53import { GitHubUtils } from "../common/utils.js" ;
64import { I18n } from "../common/i18n.js" ;
7- import { Config } from "../common/config.js" ;
8-
9- const __dirname = getDirname ( ) ;
5+ import { htmlAssets } from "../common/htmlAssets.js" ;
106
117type CommitListItem = RestEndpointMethodTypes [ "repos" ] [ "listCommits" ] [ "response" ] [ "data" ] [ number ] ;
128type GetCommitResponse = RestEndpointMethodTypes [ "repos" ] [ "getCommit" ] [ "response" ] [ "data" ] ;
@@ -47,6 +43,12 @@ function classifyType(msg?: string | null): CommitType {
4743 return "other" ;
4844}
4945
46+ const htmlEscape = ( s : string ) =>
47+ String ( s ?? "" ) . replace (
48+ / [ & < > " ' ] / g,
49+ ( ch ) => ( { "&" : "&" , "<" : "<" , ">" : ">" , '"' : """ , "'" : "'" } ) [ ch ] as string
50+ ) ;
51+
5052export class AuthorWorkPatternAnalyzer {
5153 private owner : string ;
5254 private repo : string ;
@@ -106,14 +108,13 @@ export class AuthorWorkPatternAnalyzer {
106108 const details : GetCommitResponse [ ] = await Promise . all (
107109 picked . map ( async ( c : CommitListItem ) => {
108110 const resp = await safeApiCall ( ( ) =>
109- octokit . repos . getCommit ( {
111+ GitHubUtils . createGitHubAPIClient ( this . githubToken ) . repos . getCommit ( {
110112 owner : this . owner ,
111113 repo : this . repo ,
112114 ref : c . sha ,
113115 } )
114116 ) ;
115- const { data } = resp ;
116- return data as GetCommitResponse ;
117+ return resp . data as GetCommitResponse ;
117118 } )
118119 ) ;
119120
@@ -155,7 +156,7 @@ export class AuthorWorkPatternAnalyzer {
155156 async generateReport ( payload : Awaited < ReturnType < AuthorWorkPatternAnalyzer [ "analyze" ] > > ) {
156157 I18n . setLocale ( this . locale ) ;
157158
158- const tplPath = path . join ( __dirname , "../html/ author-work-pattern.html") ;
159+ const tplPath = htmlAssets . path ( " author-work-pattern.html") ;
159160 const exists = await fs
160161 . access ( tplPath )
161162 . then ( ( ) => true )
@@ -170,17 +171,22 @@ export class AuthorWorkPatternAnalyzer {
170171 const from = new Date ( payload . period . from ) . toISOString ( ) . slice ( 0 , 10 ) ;
171172 const to = new Date ( payload . period . to ) . toISOString ( ) . slice ( 0 , 10 ) ;
172173
173- const notes = [
174- I18n . t ( "notes.author" , { author : payload . author } ) ,
175- I18n . t ( "notes.repo" , { repo : payload . repo } ) ,
176- I18n . t ( "notes.period" , { from, to } ) ,
177- ] . join ( " · " ) ;
174+ const titleEsc = htmlEscape ( `Author Work Pattern · ${ payload . repo } · ${ payload . author } ` ) ;
175+ const notesEsc = htmlEscape (
176+ [
177+ I18n . t ( "notes.author" , { author : payload . author } ) ,
178+ I18n . t ( "notes.repo" , { repo : payload . repo } ) ,
179+ I18n . t ( "notes.period" , { from, to } ) ,
180+ ] . join ( " · " )
181+ ) ;
178182
179183 const noDataText = I18n . t ( "messages.no_data" ) ;
180184 const typeRows =
181185 payload . typeMix . length === 0
182- ? `<tr><td colspan="2" class="val-right" style="color:#777;">${ noDataText } </td></tr>`
183- : payload . typeMix . map ( ( t ) => `<tr><td>${ t . label } </td><td class="val-right">${ t . value } </td></tr>` ) . join ( "" ) ;
186+ ? `<tr><td colspan="2" class="val-right" style="color:#777;">${ htmlEscape ( noDataText ) } </td></tr>`
187+ : payload . typeMix
188+ . map ( ( t ) => `<tr><td>${ htmlEscape ( t . label ) } </td><td class="val-right">${ t . value } </td></tr>` )
189+ . join ( "" ) ;
184190
185191 const barLabelsJson = JSON . stringify ( [ "Commits" , "Churn" ] ) ;
186192 const barValuesJson = JSON . stringify ( [ payload . metrics . commits , payload . metrics . churn ] ) ;
@@ -201,19 +207,20 @@ export class AuthorWorkPatternAnalyzer {
201207
202208 let html = await fs . readFile ( tplPath , "utf8" ) ;
203209 html = html
204- . replaceAll ( "{{TITLE}}" , `Author Work Pattern · ${ payload . repo } · ${ payload . author } ` )
205- . replaceAll ( "{{NOTES}}" , notes )
210+ . replaceAll ( "{{TITLE}}" , titleEsc )
211+ . replaceAll ( "{{NOTES}}" , notesEsc )
206212 . replaceAll ( "{{COMMITS}}" , String ( payload . metrics . commits ) )
207213 . replaceAll ( "{{INSERTIONS}}" , String ( payload . metrics . insertions ) )
208214 . replaceAll ( "{{DELETIONS}}" , String ( payload . metrics . deletions ) )
209215 . replaceAll ( "{{CHURN}}" , String ( payload . metrics . churn ) )
210- . replaceAll ( "{{BRANCH}}" , payload . branch )
216+ . replaceAll ( "{{BRANCH}}" , htmlEscape ( payload . branch ) )
211217 . replaceAll ( "{{TYPE_TABLE_ROWS}}" , typeRows )
212218 . replaceAll ( "{{BAR_LABELS_JSON}}" , barLabelsJson )
213219 . replaceAll ( "{{BAR_VALUES_JSON}}" , barValuesJson )
214220 . replaceAll ( "{{DONUT_LABELS_JSON}}" , donutLabelsJson )
215221 . replaceAll ( "{{DONUT_VALUES_JSON}}" , donutValuesJson )
216- . replaceAll ( "{{DONUT_COLORS_JSON}}" , donutColorsJson ) ;
222+ . replaceAll ( "{{DONUT_COLORS_JSON}}" , donutColorsJson )
223+ . replaceAll ( "</script>" , "<\\/script>" ) ;
217224
218225 return { content : [ { type : "text" as const , text : html } ] } ;
219226 }
0 commit comments