Skip to content

Commit 3fd7c05

Browse files
Initial commit
0 parents  commit 3fd7c05

File tree

4 files changed

+540
-0
lines changed

4 files changed

+540
-0
lines changed

.gitignore

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Dependencies
2+
node_modules/
3+
.pnp/
4+
.pnp.js
5+
6+
# Testing
7+
/coverage
8+
9+
# Production
10+
/build
11+
/dist
12+
13+
# Environment files
14+
.env
15+
.env.local
16+
.env.development.local
17+
.env.test.local
18+
.env.production.local
19+
20+
# Debug logs
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*
24+
25+
# Editor/IDE
26+
.idea/
27+
.vscode/
28+
*.swp
29+
*.swo
30+
31+
# OS generated
32+
.DS_Store
33+
.DS_Store?
34+
._*
35+
.Spotlight-V100
36+
.Trashes
37+
ehthumbs.db
38+
Thumbs.db
39+
40+
# Optional
41+
*.log
42+
*.pid
43+
*.seed

index.js

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

0 commit comments

Comments
 (0)