ai-commit-report-generator-cli
Version:
An AI-powered CLI tool that generates weekly reports from your Git activity
166 lines (165 loc) • 7.67 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchCommits = fetchCommits;
exports.getCommitStatistics = getCommitStatistics;
exports.fetchCommitsWithStatistics = fetchCommitsWithStatistics;
exports.fetchDiffs = fetchDiffs;
exports.getUniqueAuthors = getUniqueAuthors;
const child_process_1 = require("child_process");
const util_1 = require("util");
const date_fns_1 = require("date-fns");
function fetchCommits() {
return __awaiter(this, arguments, void 0, function* ({ filters = {}, path = "." } = {}) {
const execAsync = (0, util_1.promisify)(child_process_1.exec);
try {
// Build git log command with date range if specified
let gitCommand = `cd "${path}" && git log --format="%H|%an|%ad|%s" --date=short`;
if (filters.dateRange) {
const afterDate = (0, date_fns_1.format)(filters.dateRange.startDate, 'yyyy-MM-dd');
const beforeDate = (0, date_fns_1.format)(filters.dateRange.endDate, 'yyyy-MM-dd');
gitCommand += ` --after="${afterDate}" --before="${beforeDate}"`;
}
const { stdout, stderr } = yield execAsync(gitCommand);
if (stderr) {
console.warn(`Git log warning: ${stderr}`);
}
if (!stdout || !stdout.trim()) {
return [];
}
const commits = stdout.split('\n').filter(line => line.trim()).map(line => {
const [hash, username, date, message] = line.split('|');
return {
hash,
username,
date,
message
};
});
// Apply additional filters
const filteredCommits = commits.filter((commit) => {
const inHashes = filters.hashes ? filters.hashes.includes(commit.hash) : true;
const includesHash = filters.hash ? commit.hash.includes(filters.hash) : true;
const includesUsername = filters.username ? commit.username === filters.username : true;
const includesMessage = filters.message ? commit.message.includes(filters.message) : true;
return inHashes && includesHash && includesUsername && includesMessage;
});
return filteredCommits;
}
catch (error) {
console.error('Error fetching git commits:', error instanceof Error ? error.message : 'Unknown error');
return [];
}
});
}
function getCommitStatistics(commit_1) {
return __awaiter(this, arguments, void 0, function* (commit, path = ".") {
const execAsync = (0, util_1.promisify)(child_process_1.exec);
try {
// Get just the stats summary using --shortstat
const { stdout: globalStats, stderr } = yield execAsync(`cd "${path}" && git show --stat ${commit.hash}`);
if (stderr) {
console.warn(`Warning in git show: ${stderr}`);
}
if (!globalStats || !globalStats.trim()) {
return [];
}
const lines = globalStats.split('\n');
if (lines.length < 8) { // Need at least header lines + 1 stat line
return [];
}
// Ignore the commits details lines and the last line which consist of the summary
const slicedGlobalStatsArray = lines.slice(6, -2);
return slicedGlobalStatsArray
.filter(line => line && line.trim()) // Filter out empty lines
.map(line => {
const segments = line.split(" ").filter(segment => segment.length > 0);
if (segments.length < 4) {
return null;
}
const [fileName, , totalChangesStr, operationsGraph] = segments;
const totalChanges = parseInt(totalChangesStr, 10);
if (!operationsGraph || isNaN(totalChanges)) {
return null;
}
const plusSymbols = (operationsGraph.match(/\+/g) || []).length;
const minusSymbols = (operationsGraph.match(/-/g) || []).length;
const totalSymbols = plusSymbols + minusSymbols;
if (totalSymbols === 0) {
return {
fileName,
totalChanges,
numberOfInsertions: 0,
numberOfDeletions: 0
};
}
return {
fileName,
totalChanges,
numberOfInsertions: Math.round((plusSymbols / totalSymbols) * totalChanges),
numberOfDeletions: Math.round((minusSymbols / totalSymbols) * totalChanges)
};
})
.filter((entry) => entry !== null);
}
catch (error) {
console.error('Error fetching commit diff:', error instanceof Error ? error.message : 'Unknown error');
return []; // Return empty array instead of throwing to handle gracefully
}
});
}
function fetchCommitsWithStatistics() {
return __awaiter(this, arguments, void 0, function* (params = {}) {
const commits = yield fetchCommits(params);
const commitStatistics = yield Promise.all(commits.map(commit => getCommitStatistics(commit, params.path)));
return commits.map((commit, i) => {
return {
commit,
statistics: commitStatistics[i]
};
});
});
}
function fetchDiffs(_a) {
return __awaiter(this, arguments, void 0, function* ({ filePath, hash, path = "." }) {
const execAsync = (0, util_1.promisify)(child_process_1.exec);
try {
const { stdout, stderr } = yield execAsync(`cd "${path}" && git diff ${hash} ${filePath}`);
if (stderr) {
throw new Error(`Git diff error: ${stderr}`);
}
return stdout;
}
catch (err) {
console.error("Failed to fetch the diff:", err instanceof Error ? err.message : 'Unknown error');
throw err;
}
});
}
function getUniqueAuthors() {
return __awaiter(this, arguments, void 0, function* (path = ".") {
const execAsync = (0, util_1.promisify)(child_process_1.exec);
try {
const { stdout, stderr } = yield execAsync(`cd "${path}" && git log --format="%an" | sort -u`);
if (stderr) {
console.warn(`Git log warning: ${stderr}`);
}
if (!stdout || !stdout.trim()) {
return [];
}
return stdout.trim().split('\n');
}
catch (error) {
console.error('Error fetching git authors:', error instanceof Error ? error.message : 'Unknown error');
return [];
}
});
}