SierraSoftworks/Iridium

View on GitHub
build/changelog.js

Summary

Maintainability
B
5 hrs
Test Coverage
"use strict";

const spawn = require('child_process').spawn;
const fs = require('fs');
const chalk = require('chalk');

const commitLineRegex = /^([a-f0-9]+) (?:\(([^)]+)\) )?(.+)$/;
const refspecRegex = /(?:\w+\/([^\s,]+))|(?:tag\: ([^\s,]+))|(?:HEAD -> ([^\s,]+))/g;

const gitLog = spawn("git", ["log", "--oneline", "--decorate"]);

const projectUrl = "https://github.com/SierraSoftworks/Iridium";

let parsedCommits = 0;
let changelog = [
    "# Iridium Changelog",
    "This changelog is generated automatically based on commits made to Iridium.",
    "For a human friendly description of what changed in each release, take a look",
    `at the [Releases](${projectUrl}/releases) page.`,
    ""
];

let hasRelease = false;

function generateTablet(content, colour, url) {
    return `<a style="border-radius: 2px; padding: 4px 8px; background: ${colour}; color: #fff;" href="${url}">${content}</a>`;
}

function parseRefspec(refSpec) {
    let properties = {
        localHead: false,
        version: false,
        tags: [],
        branches: []
    };
    
    let refspecMatch;
    while (refspecMatch = refspecRegex.exec(refSpec)) {
        if (refspecMatch[1]) {
            // origin/(xxxx)
            properties.branches.push(refspecMatch[1]);
        } else if(refspecMatch[2]) {
            // tag: (xxxx)
            const tag = refspecMatch[2];
            if (tag.match(/^v[\d\w\-\.]+$/)) {
                properties.version = tag.substring(1);
            }

            properties.tags.push(refspecMatch[2]);
        } else if(refspecMatch[3]) {
            // HEAD -> (xxxx)
            properties.localHead = refspecMatch[3];
        }
    }

    return properties;
}

gitLog.stdout.on('data', data => {
    const commits = data.toString('utf8');

    commits.split('\n').forEach(commit => {
        if (!commit) return;

        const commitLineMatch = commitLineRegex.exec(commit);
        if (!commitLineMatch) {
            console.error(chalk.red("failed to parse commit: ", chalk.white('%s')), commit);
            return;
        }

        const sha = commitLineMatch[1];
        const refSpec = commitLineMatch[2];
        const message = commitLineMatch[3];

        if (refSpec) {
            const properties = parseRefspec(refSpec);

            if (properties.version) {
                changelog.push("");
                changelog.push(`## Version [${properties.version}](${projectUrl}/releases/tag/v${properties.version})`);
                hasRelease = true;
            }

            if (properties.tags.length) {
                changelog.push(`${properties.tags.map(tag => generateTablet(`tag: **${tag}**`, "rgb(64, 120, 192)", `${projectUrl}/releases/tag/${tag}`)).join(' ')}`);
            }


            if (properties.version) {
                if (properties.branches.length) {
                    changelog.push(`${properties.branches.map(branch => generateTablet(`branch: **${branch}**`, "rgb(108, 198, 68)", `${projectUrl}/tree/${branch}`)).join(' ')}`);
                }

                changelog.push("");
                changelog.push("```sh");
                changelog.push(`npm install iridium@${properties.version}`);
                changelog.push("```");
                changelog.push("");
                changelog.push('### Changes');
            }
        }

        if (hasRelease) { 
            changelog.push(` - [${sha}](${projectUrl}/commit/${sha}) ${message}`);
        }
        parsedCommits++;
    });
});

gitLog.on('close', code => {
    if (code !== 0) {
        console.error(chalk.red("failed to generate changelog"));
        process.exit(code);
    }

    fs.writeFile('CHANGELOG.md', changelog.join("\n"), (err) => {
        if (err) {
            console.error("%s: %s", chalk.red("ERROR"), err.message);
            process.exit(1);
        }

        console.log(chalk.green("generated changelog from %d commits"), parsedCommits);
    });

});