225 lines
7.8 KiB
JavaScript
225 lines
7.8 KiB
JavaScript
const fetch = require('node-fetch');
|
|
const { writeFileSync } = require('fs');
|
|
|
|
// Get start date from command line argument (format: YYYY-MM-DD)
|
|
const startDate = process.argv[2] ? new Date(process.argv[2]) : new Date('1970-01-01');
|
|
if (isNaN(startDate.getTime())) {
|
|
console.error('Invalid date format. Please use YYYY-MM-DD');
|
|
process.exit(1);
|
|
}
|
|
|
|
const repoUrl = 'https://gitlab.com/api/v4/projects/Shinobi-Systems%2FShinobi/repository/commits';
|
|
const perPage = 100; // Max allowed by GitLab API
|
|
let allCommits = [];
|
|
let shouldContinueFetching = true;
|
|
|
|
async function fetchCommitDetails(commitId) {
|
|
try {
|
|
const response = await fetch(`${repoUrl}/${commitId}`);
|
|
const commitDetails = await response.json();
|
|
return commitDetails;
|
|
} catch (error) {
|
|
console.error(`Error fetching details for commit ${commitId}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function parseMultiCommitDescription(description) {
|
|
if (!description) return [];
|
|
|
|
// Split the description into individual commit messages
|
|
const commitSections = description.split(/\n\s*-\scommit\s/).filter(section => section.trim() !== '');
|
|
|
|
const formattedCommits = [];
|
|
|
|
commitSections.forEach(section => {
|
|
if (!section) return;
|
|
|
|
// Add back the "commit " prefix we split on
|
|
const lines = [`commit ${section.split('\n')[0]}`, ...section.split('\n').slice(1)];
|
|
|
|
const commitInfo = {
|
|
hash: lines[0].replace('commit ', '').trim(),
|
|
details: []
|
|
};
|
|
|
|
lines.forEach(line => {
|
|
const trimmedLine = line.replace(/^\s*-\s*/, '').trim();
|
|
if (trimmedLine) {
|
|
commitInfo.details.push(trimmedLine);
|
|
}
|
|
});
|
|
|
|
formattedCommits.push(commitInfo);
|
|
});
|
|
|
|
return formattedCommits;
|
|
}
|
|
|
|
async function fetchCommits(page = 1) {
|
|
if (!shouldContinueFetching) return;
|
|
|
|
try {
|
|
const response = await fetch(`${repoUrl}?ref_name=dev&per_page=${perPage}&page=${page}`);
|
|
const commits = await response.json();
|
|
|
|
if (!commits || commits.length === 0) {
|
|
shouldContinueFetching = false;
|
|
return;
|
|
}
|
|
|
|
// Process commits
|
|
for (const commit of commits) {
|
|
const commitDate = new Date(commit.created_at);
|
|
|
|
if (commitDate >= startDate) {
|
|
// Fetch full commit details including description
|
|
const commitDetails = await fetchCommitDetails(commit.id);
|
|
if (commitDetails && commitDetails.message) {
|
|
let description = commitDetails.message || '';
|
|
// Remove the first line (commit title) if it exists in description
|
|
description = description.split('\n').slice(1).join('\n').trim();
|
|
|
|
// Check if this is a multi-commit description
|
|
if (description.includes('\n - commit ')) {
|
|
const multiCommits = parseMultiCommitDescription(description);
|
|
allCommits.push({
|
|
...commit,
|
|
isMultiCommit: true,
|
|
multiCommits: multiCommits
|
|
});
|
|
} else {
|
|
allCommits.push({
|
|
...commit,
|
|
description: description.split('\n').filter(line => line.trim() !== '')
|
|
});
|
|
}
|
|
} else {
|
|
allCommits.push(commit);
|
|
}
|
|
} else {
|
|
// We've reached commits older than our start date
|
|
shouldContinueFetching = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we got a full page and all commits were recent, check next page
|
|
if (shouldContinueFetching && commits.length === perPage) {
|
|
await fetchCommits(page + 1);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching commits:', error);
|
|
shouldContinueFetching = false;
|
|
}
|
|
}
|
|
|
|
function formatChangelog(commits) {
|
|
const changelog = {};
|
|
|
|
commits.forEach(commit => {
|
|
const date = new Date(commit.created_at);
|
|
const monthYear = date.toLocaleString('default', { month: 'long', year: 'numeric' });
|
|
const day = date.getDate();
|
|
|
|
if (!changelog[monthYear]) {
|
|
changelog[monthYear] = {};
|
|
}
|
|
|
|
if (!changelog[monthYear][day]) {
|
|
changelog[monthYear][day] = [];
|
|
}
|
|
|
|
// Extract author name if not Moe
|
|
let authorNote = '';
|
|
if (commit.author_name && !commit.author_name.includes('Moe')) {
|
|
authorNote = ` (${commit.author_name})`;
|
|
}
|
|
|
|
changelog[monthYear][day].push({
|
|
message: commit.title,
|
|
description: commit.description || [],
|
|
isMultiCommit: commit.isMultiCommit || false,
|
|
multiCommits: commit.multiCommits || [],
|
|
author: authorNote,
|
|
date: commit.created_at
|
|
});
|
|
});
|
|
|
|
// Generate markdown output
|
|
let output = `### Changelog (${startDate.toISOString().split('T')[0]} to ${new Date().toISOString().split('T')[0]})\n\n`;
|
|
|
|
// Sort months newest first
|
|
const sortedMonths = Object.keys(changelog).sort((a, b) => {
|
|
return new Date(b) - new Date(a);
|
|
});
|
|
|
|
for (const monthYear of sortedMonths) {
|
|
output += `#### ${monthYear}\n`;
|
|
|
|
// Sort days newest first
|
|
const sortedDays = Object.keys(changelog[monthYear]).sort((a, b) => b - a);
|
|
|
|
for (const day of sortedDays) {
|
|
const dateObj = new Date(changelog[monthYear][day][0].date);
|
|
const weekday = dateObj.toLocaleString('default', { weekday: 'long' });
|
|
|
|
output += `- **${monthYear.split(' ')[0]} ${day} (${weekday})**\n`;
|
|
|
|
changelog[monthYear][day].forEach(commit => {
|
|
output += ` - ${commit.message}${commit.author}\n`;
|
|
|
|
if (commit.isMultiCommit) {
|
|
commit.multiCommits.forEach(multiCommit => {
|
|
output += ` - commit ${multiCommit.hash}\n`;
|
|
multiCommit.details.forEach((detail, index) => {
|
|
if (index > 0) { // Skip the hash line we already printed
|
|
output += ` - ${detail}\n`;
|
|
}
|
|
});
|
|
});
|
|
} else if (commit.description && commit.description.length > 0) {
|
|
commit.description.forEach(descLine => {
|
|
output += ` - ${descLine}\n`;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
output += '\n';
|
|
}
|
|
|
|
return output
|
|
.replaceAll('- ', ' - ')
|
|
.replaceAll('- + ', ' - ')
|
|
.replaceAll('- - ', ' - ')
|
|
.replaceAll('- + ', ' - ')
|
|
.split('\n').filter(item =>
|
|
!item.includes('- Author: ')
|
|
&& !item.includes('- Date: ')
|
|
&& !item.includes('- commit ')
|
|
).join('\n')
|
|
// .replaceAll('- Date: ', ' - Date: ')
|
|
// .replaceAll('- Author: ', ' - Author: ')
|
|
}
|
|
|
|
async function main() {
|
|
console.log(`Fetching commits since ${startDate.toISOString().split('T')[0]}...`);
|
|
await fetchCommits();
|
|
|
|
if (allCommits.length === 0) {
|
|
console.log('No commits found since the specified date.');
|
|
return;
|
|
}
|
|
|
|
console.log(`Found ${allCommits.length} commits since ${startDate.toISOString().split('T')[0]}`);
|
|
|
|
const changelog = formatChangelog(allCommits);
|
|
const outputFilename = `changelog_${startDate.toISOString().split('T')[0]}_to_${new Date().toISOString().split('T')[0]}.md`;
|
|
|
|
writeFileSync(outputFilename, changelog);
|
|
console.log(`Changelog written to ${outputFilename}`);
|
|
}
|
|
|
|
main().catch(console.error);
|