1- const path = require ( "path" ) ;
2- const fs = require ( "fs" ) ;
3- const { exec } = require ( "child_process" ) ;
4- const util = require ( "util" ) ;
5- const execAsync = util . promisify ( exec ) ;
1+ const gitUtils = require ( './utils/gitUtils' ) ;
2+ const fsUtils = require ( './utils/fsUtils' ) ;
3+ const EXCLUSIONS = require ( './constants/gitExclusions' ) ;
64
75class GitMarkdownDiff {
8- constructor ( outputDir = "git-diffs" ) {
9- this . outputDir = outputDir ;
10- }
11-
12- async validateGit ( ) {
13- try {
14- await execAsync ( "git status" ) ;
15- return true ;
16- } catch ( error ) {
17- console . error ( "Git command did not succeed. Script cannot proceed." ) ;
18- process . exit ( 1 ) ;
19- }
20- }
21-
22- cleanupOutputDir ( ) {
23- if ( fs . existsSync ( this . outputDir ) ) {
24- fs . rmSync ( this . outputDir , { recursive : true , force : true } ) ;
25- }
26- fs . mkdirSync ( this . outputDir , { recursive : true } ) ;
27- }
28-
29- buildGitRange ( startRange , endRange ) {
30- return startRange && endRange ? `${ endRange } ..${ startRange } ` : "" ;
31- }
32-
33- async getChangedFiles ( range , spinner ) {
6+ constructor ( outputDir = "git-diffs" ) {
7+ this . outputDir = outputDir ;
8+ }
9+
10+ async run ( startRange , endRange ) {
11+ const { default : ora } = await import ( "ora" ) ;
12+ const spinner = ora ( "Generating markdown diffs..." ) . start ( ) ;
13+
14+ try {
15+ await gitUtils . validateGit ( ) ;
16+ fsUtils . cleanupOutputDir ( this . outputDir ) ;
17+
18+ const range = this . #buildGitRange( startRange , endRange ) ;
19+
3420 spinner . text = "Getting list of changed files..." ;
35- const exclusions = this . getExclusions ( ) ;
36- const { stdout : filesOutput } = await execAsync (
37- `git diff ${ range } --name-only -- . ${ exclusions } ` ,
38- { maxBuffer : 10 * 1024 * 1024 }
39- ) ;
40- return filesOutput . split ( "\n" ) . filter ( Boolean ) ;
41- }
42-
43- getExclusions ( ) {
44- return [
45- // Package manager locks
46- ":!package-lock.json" ,
47- ":!yarn.lock" ,
48- ":!pnpm-lock.yaml" ,
49- ":!npm-shrinkwrap.json" ,
50- ":!package-lock.linux-x64.json" ,
51- ":!package-lock.macos-arm64.json" ,
52- ":!package-lock.windows-x64.json" ,
53- // Dependencies
54- ":!node_modules/*" ,
55- ":!vendor/*" ,
56- // Build outputs
57- ":!dist/*" ,
58- ":!build/*" ,
59- ":!out/*" ,
60- ":!.next/*" ,
61- ":!coverage/*" ,
62- ":!.nuxt/*" ,
63- // IDE and OS files
64- ":!.idea/*" ,
65- ":!.vscode/*" ,
66- ":!*.suo" ,
67- ":!*.ntvs*" ,
68- ":!*.njsproj" ,
69- ":!*.sln" ,
70- ":!.DS_Store" ,
71- // Logs
72- ":!*.log" ,
73- ":!logs/*" ,
74- ":!npm-debug.log*" ,
75- ":!yarn-debug.log*" ,
76- ":!yarn-error.log*" ,
77- // Environment and secrets
78- ":!.env*" ,
79- ":!*.pem" ,
80- ":!*.key" ,
81- // Generated files
82- ":!*.min.js" ,
83- ":!*.min.css" ,
84- ":!*.map" ,
85- ] . join ( " " ) ;
86- }
87-
88- async getTotalStats ( range ) {
89- const { stdout } = await execAsync ( `git diff ${ range } --shortstat` ) ;
90- return stdout ;
91- }
92-
93- async buildIndexContent ( startRange , endRange , totalStats , range ) {
94- const index = [
95- "# Git Diff Summary\n" ,
96- `> Comparing ${ startRange || "current" } to ${ endRange || "working tree" } \n` ,
97- `## Total Changes Stats\n\`\`\`\n${ totalStats } \`\`\`\n` ,
98- "## Commit Messages\n" ,
99- ] ;
100-
101- if ( startRange && endRange ) {
102- const { stdout : commitMessages } = await execAsync (
103- `git log --pretty=format:"- %s (%h)" ${ endRange } ..${ startRange } `
104- ) ;
105- index . push ( commitMessages + "\n\n## Changed Files\n" ) ;
106- } else {
107- index . push ( "## Changed Files\n" ) ;
108- }
21+ const changedFiles = await gitUtils . getChangedFiles ( range , EXCLUSIONS ) ;
22+ const totalStats = await gitUtils . getTotalStats ( range ) ;
10923
110- return index ;
111- }
112-
113- async processFiles ( changedFiles , range , spinner , index ) {
114- for ( let i = 0 ; i < changedFiles . length ; i ++ ) {
115- const file = changedFiles [ i ] ;
116- spinner . text = `Processing file ${ i + 1 } /${ changedFiles . length } : ${ file } ` ;
117-
118- const fileInfo = await this . getFileInfo ( file , range ) ;
119- const content = await this . generateFileContent ( file , range , fileInfo ) ;
120-
121- await this . saveFile ( file , content ) ;
122- this . updateIndex ( index , file , fileInfo ) ;
123- }
124- }
125-
126- async run ( startRange , endRange ) {
127- const { default : ora } = await import ( "ora" ) ;
128- const spinner = ora ( "Generating markdown diffs..." ) . start ( ) ;
129-
130- try {
131- await this . validateGit ( ) ;
132- this . cleanupOutputDir ( ) ;
133-
134- const range = this . buildGitRange ( startRange , endRange ) ;
135- const changedFiles = await this . getChangedFiles ( range , spinner ) ;
136- const totalStats = await this . getTotalStats ( range ) ;
137-
138- const index = await this . buildIndexContent ( startRange , endRange , totalStats , range ) ;
139- await this . processFiles ( changedFiles , range , spinner , index ) ;
140-
141- spinner . text = "Writing index file..." ;
142- fs . writeFileSync ( path . join ( this . outputDir , "DIFF_INDEX.md" ) , index . join ( "\n" ) ) ;
143-
144- spinner . succeed ( `Diffs saved to ${ this . outputDir } /` ) ;
145- } catch ( error ) {
146- spinner . fail ( "Failed to generate diffs" ) ;
147- console . error ( error ) ;
148- process . exit ( 1 ) ;
149- }
24+ const index = await this . #buildIndexContent( startRange , endRange , totalStats , range ) ;
25+ await this . #processFiles( changedFiles , range , spinner , index ) ;
26+
27+ spinner . text = "Writing index file..." ;
28+ fsUtils . writeIndexFile ( this . outputDir , index . join ( "\n" ) ) ;
29+
30+ spinner . succeed ( `Diffs saved to ${ this . outputDir } /` ) ;
31+ } catch ( error ) {
32+ spinner . fail ( "Failed to generate diffs" ) ;
33+ console . error ( error ) ;
34+ process . exit ( 1 ) ;
15035 }
151-
152- async getFileInfo ( file , range ) {
153- const { stdout } = await execAsync (
154- `git diff ${ range } --stat -- "${ file } "` ,
155- { maxBuffer : 10 * 1024 * 1024 }
156- ) ;
157- return stdout ;
36+ }
37+
38+ async #processFiles( changedFiles , range , spinner , index ) {
39+ for ( let i = 0 ; i < changedFiles . length ; i ++ ) {
40+ const file = changedFiles [ i ] ;
41+ spinner . text = `Processing file ${ i + 1 } /${ changedFiles . length } : ${ file } ` ;
42+
43+ const fileInfo = await gitUtils . getFileInfo ( file , range ) ;
44+ const content = await this . #generateFileContent( file , range , fileInfo ) ;
45+
46+ fsUtils . saveFile ( this . outputDir , file , content ) ;
47+ this . #updateIndex( index , file , fileInfo ) ;
15848 }
159-
160- async generateFileContent ( file , range , fileInfo ) {
161- const diffOutput = await execAsync ( `git diff ${ range } -- "${ file } "` , {
162- maxBuffer : 10 * 1024 * 1024 ,
163- } ) ;
164-
165- return [
166- this . getCssStyle ( ) ,
167- `generated at ${ new Date ( ) . toLocaleString ( ) } (${ Intl . DateTimeFormat ( ) . resolvedOptions ( ) . timeZone } )` ,
168- "" ,
169- `# Changes in \`${ file } \`` ,
170- "" ,
171- "## File Statistics" ,
172- "```" ,
173- fileInfo ,
174- "```" ,
175- "" ,
176- "## Changes" ,
177- `\`\`\`diff` ,
178- diffOutput . stdout ,
179- "```" ,
180- "" ,
181- "" ,
182- ] . join ( "\n" ) ;
49+ }
50+
51+ #buildGitRange( startRange , endRange ) {
52+ return startRange && endRange ? `${ endRange } ..${ startRange } ` : "" ;
53+ }
54+
55+ async #buildIndexContent( startRange , endRange , totalStats ) {
56+ const index = [
57+ "# Git Diff Summary\n" ,
58+ `> Comparing ${ startRange || "current" } to ${ endRange || "working tree" } \n` ,
59+ `## Total Changes Stats\n\`\`\`\n${ totalStats } \`\`\`\n` ,
60+ "## Commit Messages\n" ,
61+ ] ;
62+
63+ if ( startRange && endRange ) {
64+ const commitMessages = await gitUtils . getCommitMessages ( endRange , startRange ) ;
65+ index . push ( commitMessages + "\n\n## Changed Files\n" ) ;
66+ } else {
67+ index . push ( "## Changed Files\n" ) ;
18368 }
184-
185- getCssStyle ( ) {
186- return `
187- <!--
188- <style>
189- .markdown-body .highlight pre, .markdown-body pre {
190- background-color: #0d1117;
69+
70+ return index ;
19171 }
192- .markdown-body .diff-deletion {
193- color: #f85149;
194- background-color: #3c1618;
72+
73+ async #generateFileContent( file , range , fileInfo ) {
74+ const diffOutput = await gitUtils . getFileDiff ( file , range ) ;
75+
76+ return [
77+ this . #getCssStyle( ) ,
78+ `generated at ${ new Date ( ) . toLocaleString ( ) } (${ Intl . DateTimeFormat ( ) . resolvedOptions ( ) . timeZone } )` ,
79+ "" ,
80+ `# Changes in \`${ file } \`` ,
81+ "" ,
82+ "## File Statistics" ,
83+ "```" ,
84+ fileInfo ,
85+ "```" ,
86+ "" ,
87+ "## Changes" ,
88+ `\`\`\`diff` ,
89+ diffOutput ,
90+ "```" ,
91+ "" ,
92+ "" ,
93+ ] . join ( "\n" ) ;
19594 }
196- .markdown-body .diff-addition {
197- color: #56d364;
198- background-color: #1b4721;
95+
96+ #getCssStyle( ) {
97+ return `<!--
98+ <style>
99+ .markdown-body .highlight pre, .markdown-body pre {
100+ background-color: #0d1117;
101+ }
102+ .markdown-body .diff-deletion {
103+ color: #f85149;
104+ background-color: #3c1618;
105+ }
106+ .markdown-body .diff-addition {
107+ color: #56d364;
108+ background-color: #1b4721;
109+ }
110+ </style>
111+ -->` ;
199112 }
200- </style>
201- -->` ;
202- }
203-
204- async saveFile ( file , content ) {
205- const mdFilePath = path . join ( this . outputDir , file + ".md" ) ;
206- const mdFileDir = path . dirname ( mdFilePath ) ;
207- fs . mkdirSync ( mdFileDir , { recursive : true } ) ;
208- fs . writeFileSync ( mdFilePath , content ) ;
209- }
210-
211- updateIndex ( index , file , fileInfo ) {
212- const stats = fileInfo . match ( / ( \d + ) i n s e r t i o n .+ ( \d + ) d e l e t i o n / ) ?. [ 0 ] || "No changes" ;
213- index . push ( `- [${ file } ](./${ file } .md) - ${ stats } ` ) ;
214- }
113+
114+ #updateIndex( index , file , fileInfo ) {
115+ const stats = fileInfo . match ( / ( \d + ) i n s e r t i o n .+ ( \d + ) d e l e t i o n / ) ?. [ 0 ] || "No changes" ;
116+ index . push ( `- [${ file } ](./${ file } .md) - ${ stats } ` ) ;
215117 }
118+ }
216119
217120module . exports = GitMarkdownDiff ;
0 commit comments