@@ -6,47 +6,78 @@ import {
66 type GistForm ,
77 updateGist as updateGistApi
88} from "@src/api/gist"
9+ import { CHROME_ID } from "@src/util/constant/meta"
910import fs from "fs"
1011import { exitWith } from "../util/process"
1112import { type Browser , descriptionOf , filenameOf , getExistGist , type UserCount , validateTokenFromEnv } from "./common"
1213
13- type AddArgv = {
14+ type AutoMode = {
15+ mode : 'auto'
16+ dirPath : string
17+ }
18+
19+ type ManualMode = {
20+ mode : 'manual'
1421 browser : Browser
1522 fileName : string
1623}
1724
25+ type AddArgv = AutoMode | ManualMode
26+
27+ const BROWSER_MAP : Record < string , Browser > = {
28+ c : 'chrome' ,
29+ e : 'edge' ,
30+ f : 'firefox' ,
31+ }
32+
1833function parseArgv ( ) : AddArgv {
1934 const argv = process . argv . slice ( 2 )
20- const browserArgv = argv [ 0 ]
21- const fileName = argv [ 1 ]
22- if ( ! browserArgv || ! fileName ) {
23- exitWith ( "add.ts [c/e/f] [file_name]" )
35+ const [ a0 , a1 ] = argv
36+
37+ if ( ! a0 ) {
38+ exitWith ( "add.ts [c/e/f] [file_name] OR add.ts auto [dir_path] " )
2439 }
25- const browserArgvMap : Record < string , Browser > = {
26- c : 'chrome' ,
27- e : 'edge' ,
28- f : 'firefox' ,
40+
41+ if ( a0 === 'auto' ) {
42+ if ( ! a1 ) exitWith ( "add.ts auto [dir_path]" )
43+ return { mode : 'auto' , dirPath : a1 }
2944 }
30- const browser : Browser = browserArgvMap [ browserArgv ]
45+
46+ if ( ! a1 ) {
47+ exitWith ( "add.ts [c/e/f] [file_name] OR add.ts auto [dir_path]" )
48+ }
49+
50+ const browser : Browser = BROWSER_MAP [ a0 ]
3151 if ( ! browser ) {
3252 exitWith ( "add.ts [c/e/f] [file_name]" )
3353 }
34- return {
35- browser,
36- fileName
37- }
54+
55+ return { mode : 'manual' , browser, fileName : a1 }
3856}
3957
40- async function createGist ( token : string , browser : Browser , data : UserCount ) {
41- const description = descriptionOf ( browser )
42- const filename = filenameOf ( browser )
58+ function detectBrowser ( fileName : string ) : Browser | null {
59+ if ( fileName . includes ( CHROME_ID ) ) return 'chrome'
60+ if ( fileName . includes ( 'usage-day-' ) ) return 'firefox'
61+ if ( fileName . includes ( 'edgeaddon_analytics' ) ) return 'edge'
62+ return null
63+ }
4364
44- // 1. sort by key
65+ function sortDataByKey ( data : UserCount ) : UserCount {
4566 const sorted : UserCount = { }
4667 Object . keys ( data ) . sort ( ) . forEach ( key => sorted [ key ] = data [ key ] )
47- // 2. create
48- const files : Record < string , FileForm > = { }
49- files [ filename ] = { filename : filename , content : JSON . stringify ( sorted , null , 2 ) }
68+ return sorted
69+ }
70+
71+ async function createGist ( token : string , browser : Browser , data : UserCount ) {
72+ const description = descriptionOf ( browser )
73+ const filename = filenameOf ( browser )
74+ const sorted = sortDataByKey ( data )
75+ const files : Record < string , FileForm > = {
76+ [ filename ] : {
77+ filename,
78+ content : JSON . stringify ( sorted , null , 2 )
79+ }
80+ }
5081 const gistForm : GistForm = {
5182 public : true ,
5283 description,
@@ -67,12 +98,8 @@ async function updateGist(token: string, browser: Browser, data: UserCount, gist
6798 Object . keys ( existData ) . sort ( ) . forEach ( key => sorted [ key ] = existData [ key ] )
6899 const files : Record < string , FileForm > = { }
69100 files [ filename ] = { filename : filename , content : JSON . stringify ( sorted , null , 2 ) }
70- const gistForm : GistForm = {
71- public : true ,
72- description,
73- files
74- }
75- updateGistApi ( token , gist . id , gistForm )
101+ const gistForm : GistForm = { public : true , description, files }
102+ await updateGistApi ( token , gist . id , gistForm )
76103}
77104
78105function parseChrome ( content : string ) : UserCount {
@@ -142,22 +169,86 @@ function rjust(str: string, num: number, padding: string): string {
142169 return Array . from ( new Array ( num - str . length ) . keys ( ) ) . map ( _ => padding ) . join ( '' ) + str
143170}
144171
172+ const PARSERS : Record < Browser , ( content : string ) => UserCount > = {
173+ chrome : parseChrome ,
174+ edge : parseEdge ,
175+ firefox : parseFirefox ,
176+ }
177+
178+ async function processFile ( browser : Browser , filePath : string ) : Promise < UserCount > {
179+ const content = fs . readFileSync ( filePath , { encoding : 'utf-8' } )
180+ return PARSERS [ browser ] ( content )
181+ }
182+
183+ function groupFilesByBrowser ( files : string [ ] ) : Record < Browser , string [ ] > {
184+ const grouped : Record < Browser , string [ ] > = { chrome : [ ] , edge : [ ] , firefox : [ ] }
185+ for ( const file of files ) {
186+ const browser = detectBrowser ( file )
187+ if ( browser ) {
188+ grouped [ browser ] . push ( file )
189+ } else {
190+ console . warn ( `Unknown file format, skipping: ${ file } ` )
191+ }
192+ }
193+ return grouped
194+ }
195+
196+ function mergeData ( target : UserCount , source : UserCount ) : void {
197+ Object . entries ( source ) . forEach ( ( [ key , val ] ) => {
198+ target [ key ] = target [ key ] ? Math . max ( target [ key ] , val ) : val
199+ } )
200+ }
201+
202+ async function processDirectory ( token : string , dirPath : string ) {
203+ const files = fs . readdirSync ( dirPath ) . filter ( f => f . endsWith ( '.csv' ) )
204+ if ( files . length === 0 ) exitWith ( `No CSV files found in ${ dirPath } ` )
205+
206+ const filesByBrowser = groupFilesByBrowser ( files )
207+
208+ for ( const [ browser , browserFiles ] of Object . entries ( filesByBrowser ) ) {
209+ if ( browserFiles . length === 0 ) continue
210+
211+ const typedBrowser = browser as Browser
212+ console . log ( `Processing ${ browser . toUpperCase ( ) } : ${ browserFiles . length } file(s)` )
213+
214+ let mergedData : UserCount = { }
215+ for ( const file of browserFiles ) {
216+ const filePath = `${ dirPath } /${ file } `
217+ console . log ( `Processing: ${ file } ` )
218+ const data = await processFile ( typedBrowser , filePath )
219+ mergeData ( mergedData , data )
220+ }
221+
222+ if ( Object . keys ( mergedData ) . length === 0 ) {
223+ console . log ( `No valid data found for ${ browser } ` )
224+ continue
225+ }
226+
227+ console . log ( `Merged ${ Object . keys ( mergedData ) . length } date entries for ${ browser } ` )
228+
229+ const gist = await getExistGist ( token , typedBrowser )
230+ if ( ! gist ) {
231+ console . log ( `Creating new gist for ${ browser } ...` )
232+ await createGist ( token , typedBrowser , mergedData )
233+ } else {
234+ console . log ( `Updating existing gist for ${ browser } ...` )
235+ await updateGist ( token , typedBrowser , mergedData , gist )
236+ }
237+ console . log ( `${ browser . toUpperCase ( ) } completed successfully!` )
238+ }
239+ }
240+
145241async function main ( ) {
146242 const token = validateTokenFromEnv ( )
147- const argv : AddArgv = parseArgv ( )
148- const browser = argv . browser
149- const fileName = argv . fileName
150- const content = fs . readFileSync ( fileName , { encoding : 'utf-8' } )
151- let newData : UserCount = { }
152- if ( browser === 'chrome' ) {
153- newData = parseChrome ( content )
154- } else if ( browser === 'edge' ) {
155- newData = parseEdge ( content )
156- } else if ( browser === 'firefox' ) {
157- newData = parseFirefox ( content )
158- } else {
159- exitWith ( "Un-supported browser: " + browser )
243+ const argv = parseArgv ( )
244+
245+ if ( argv . mode === 'auto' ) {
246+ return await processDirectory ( token , argv . dirPath )
160247 }
248+
249+ const { browser, fileName } = argv
250+
251+ const newData = await processFile ( browser , fileName )
161252 const gist = await getExistGist ( token , browser )
162253 if ( ! gist ) {
163254 await createGist ( token , browser , newData )
0 commit comments