Skip to content

Commit ff197a7

Browse files
refactor: logic decoupling
1 parent 08056bd commit ff197a7

File tree

5 files changed

+228
-205
lines changed

5 files changed

+228
-205
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "git-markdown-diff",
3-
"version": "0.0.38",
3+
"version": "0.0.39",
44
"description": "Git diff tool that outputs markdown formatted diffs",
55
"main": "src/sdk/GitMarkdownDiff.js",
66
"bin": {

src/sdk/GitMarkdownDiff.js

Lines changed: 107 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -1,217 +1,120 @@
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

75
class 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+) insertion.+(\d+) deletion/)?.[0] || "No changes";
213-
index.push(`- [${file}](./${file}.md) - ${stats}`);
214-
}
113+
114+
#updateIndex(index, file, fileInfo) {
115+
const stats = fileInfo.match(/(\d+) insertion.+(\d+) deletion/)?.[0] || "No changes";
116+
index.push(`- [${file}](./${file}.md) - ${stats}`);
215117
}
118+
}
216119

217120
module.exports = GitMarkdownDiff;

src/sdk/constants/gitExclusions.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const EXCLUSIONS = [
2+
// Package manager locks
3+
":!package-lock.json",
4+
":!yarn.lock",
5+
":!pnpm-lock.yaml",
6+
":!npm-shrinkwrap.json",
7+
":!package-lock.linux-x64.json",
8+
":!package-lock.macos-arm64.json",
9+
":!package-lock.windows-x64.json",
10+
// Dependencies
11+
":!node_modules/*",
12+
":!vendor/*",
13+
// Build outputs
14+
":!dist/*",
15+
":!build/*",
16+
":!out/*",
17+
":!.next/*",
18+
":!coverage/*",
19+
":!.nuxt/*",
20+
// IDE and OS files
21+
":!.idea/*",
22+
":!.vscode/*",
23+
":!*.suo",
24+
":!*.ntvs*",
25+
":!*.njsproj",
26+
":!*.sln",
27+
":!.DS_Store",
28+
// Logs
29+
":!*.log",
30+
":!logs/*",
31+
":!npm-debug.log*",
32+
":!yarn-debug.log*",
33+
":!yarn-error.log*",
34+
// Environment and secrets
35+
":!.env*",
36+
":!*.pem",
37+
":!*.key",
38+
// Generated files
39+
":!*.min.js",
40+
":!*.min.css",
41+
":!*.map",
42+
].join(" ");
43+
44+
module.exports = EXCLUSIONS;

0 commit comments

Comments
 (0)