You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
233 lines
8.7 KiB
233 lines
8.7 KiB
/*
|
|
* Copyright (c) 2021-2023, Arm Limited. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
/* eslint-env es6 */
|
|
|
|
"use strict";
|
|
|
|
const Handlebars = require("handlebars");
|
|
const Q = require("q");
|
|
const _ = require("lodash");
|
|
|
|
const ccConventionalChangelog = require("conventional-changelog-conventionalcommits/conventional-changelog");
|
|
const ccParserOpts = require("conventional-changelog-conventionalcommits/parser-opts");
|
|
const ccRecommendedBumpOpts = require("conventional-changelog-conventionalcommits/conventional-recommended-bump");
|
|
const ccWriterOpts = require("conventional-changelog-conventionalcommits/writer-opts");
|
|
|
|
const execa = require("execa");
|
|
|
|
const readFileSync = require("fs").readFileSync;
|
|
const resolve = require("path").resolve;
|
|
|
|
/*
|
|
* Register a Handlebars helper that lets us generate Markdown lists that can support multi-line
|
|
* strings. This is driven by inconsistent formatting of breaking changes, which may be multiple
|
|
* lines long and can terminate the list early unintentionally.
|
|
*/
|
|
Handlebars.registerHelper("tf-a-mdlist", function (indent, options) {
|
|
const spaces = new Array(indent + 1).join(" ");
|
|
const first = spaces + "- ";
|
|
const nth = spaces + " ";
|
|
|
|
return first + options.fn(this).replace(/\n(?!\s*\n)/gm, `\n${nth}`).trim() + "\n";
|
|
});
|
|
|
|
/*
|
|
* Register a Handlebars helper that concatenates multiple variables. We use this to generate the
|
|
* title for the section partials.
|
|
*/
|
|
Handlebars.registerHelper("tf-a-concat", function () {
|
|
let argv = Array.prototype.slice.call(arguments, 0);
|
|
|
|
argv.pop();
|
|
|
|
return argv.join("");
|
|
});
|
|
|
|
function writerOpts(config) {
|
|
/*
|
|
* Flatten the configuration's sections list. This helps us iterate over all of the sections
|
|
* when we don't care about the hierarchy.
|
|
*/
|
|
|
|
const flattenSections = function (sections) {
|
|
return sections.flatMap(section => {
|
|
const subsections = flattenSections(section.sections || []);
|
|
|
|
return [section].concat(subsections);
|
|
})
|
|
};
|
|
|
|
const flattenedSections = flattenSections(config.sections);
|
|
|
|
/*
|
|
* Register a helper to return a restructured version of the note groups that includes notes
|
|
* categorized by their section.
|
|
*/
|
|
Handlebars.registerHelper("tf-a-notes", function (noteGroups, options) {
|
|
const generateTemplateData = function (sections, notes) {
|
|
return (sections || []).flatMap(section => {
|
|
const templateData = {
|
|
title: section.title,
|
|
sections: generateTemplateData(section.sections, notes),
|
|
notes: notes.filter(note => section.scopes?.includes(note.commit.scope)),
|
|
};
|
|
|
|
/*
|
|
* Don't return a section if it contains no notes and no sub-sections.
|
|
*/
|
|
if ((templateData.sections.length == 0) && (templateData.notes.length == 0)) {
|
|
return [];
|
|
}
|
|
|
|
return [templateData];
|
|
});
|
|
};
|
|
|
|
return noteGroups.map(noteGroup => {
|
|
return {
|
|
title: noteGroup.title,
|
|
sections: generateTemplateData(config.sections, noteGroup.notes),
|
|
notes: noteGroup.notes.filter(note =>
|
|
!flattenedSections.some(section => section.scopes?.includes(note.commit.scope))),
|
|
};
|
|
});
|
|
});
|
|
|
|
/*
|
|
* Register a helper to return a restructured version of the commit groups that includes commits
|
|
* categorized by their section.
|
|
*/
|
|
Handlebars.registerHelper("tf-a-commits", function (commitGroups, options) {
|
|
const generateTemplateData = function (sections, commits) {
|
|
return (sections || []).flatMap(section => {
|
|
const templateData = {
|
|
title: section.title,
|
|
sections: generateTemplateData(section.sections, commits),
|
|
commits: commits.filter(commit => section.scopes?.includes(commit.scope)),
|
|
};
|
|
|
|
/*
|
|
* Don't return a section if it contains no notes and no sub-sections.
|
|
*/
|
|
if ((templateData.sections.length == 0) && (templateData.commits.length == 0)) {
|
|
return [];
|
|
}
|
|
|
|
return [templateData];
|
|
});
|
|
};
|
|
|
|
return commitGroups.map(commitGroup => {
|
|
return {
|
|
title: commitGroup.title,
|
|
sections: generateTemplateData(config.sections, commitGroup.commits),
|
|
commits: commitGroup.commits.filter(commit =>
|
|
!flattenedSections.some(section => section.scopes?.includes(commit.scope))),
|
|
};
|
|
});
|
|
});
|
|
|
|
const writerOpts = ccWriterOpts(config)
|
|
.then(writerOpts => {
|
|
const ccWriterOptsTransform = writerOpts.transform;
|
|
|
|
/*
|
|
* These configuration properties can't be injected directly into the template because
|
|
* they themselves are templates. Instead, we register them as partials, which allows
|
|
* them to be evaluated as part of the templates they're used in.
|
|
*/
|
|
Handlebars.registerPartial("commitUrl", config.commitUrlFormat);
|
|
Handlebars.registerPartial("compareUrl", config.compareUrlFormat);
|
|
Handlebars.registerPartial("issueUrl", config.issueUrlFormat);
|
|
|
|
/*
|
|
* Register the partials that allow us to recursively create changelog sections.
|
|
*/
|
|
|
|
const notePartial = readFileSync(resolve(__dirname, "./templates/note.hbs"), "utf-8");
|
|
const noteSectionPartial = readFileSync(resolve(__dirname, "./templates/note-section.hbs"), "utf-8");
|
|
const commitSectionPartial = readFileSync(resolve(__dirname, "./templates/commit-section.hbs"), "utf-8");
|
|
|
|
Handlebars.registerPartial("tf-a-note", notePartial);
|
|
Handlebars.registerPartial("tf-a-note-section", noteSectionPartial);
|
|
Handlebars.registerPartial("tf-a-commit-section", commitSectionPartial);
|
|
|
|
/*
|
|
* Override the base templates so that we can generate a changelog that looks at least
|
|
* similar to the pre-Conventional Commits TF-A changelog.
|
|
*/
|
|
writerOpts.mainTemplate = readFileSync(resolve(__dirname, "./templates/template.hbs"), "utf-8");
|
|
writerOpts.headerPartial = readFileSync(resolve(__dirname, "./templates/header.hbs"), "utf-8");
|
|
writerOpts.commitPartial = readFileSync(resolve(__dirname, "./templates/commit.hbs"), "utf-8");
|
|
writerOpts.footerPartial = readFileSync(resolve(__dirname, "./templates/footer.hbs"), "utf-8");
|
|
|
|
writerOpts.transform = function (commit, context) {
|
|
/*
|
|
* Feedback on the generated changelog has shown that having build system changes
|
|
* appear at the top of a section throws some people off. We make an exception for
|
|
* scopeless `build`-type changes and treat them as though they actually have the
|
|
* `build` scope.
|
|
*/
|
|
|
|
if ((commit.type === "build") && (commit.scope == null)) {
|
|
commit.scope = "build";
|
|
}
|
|
|
|
/*
|
|
* Fix up commit trailers, which for some reason are not correctly recognized and
|
|
* end up showing up in the breaking changes.
|
|
*/
|
|
|
|
commit.notes.forEach(note => {
|
|
const trailers = execa.sync("git", ["interpret-trailers", "--parse"], {
|
|
input: note.text
|
|
}).stdout;
|
|
|
|
note.text = note.text.replace(trailers, "").trim();
|
|
});
|
|
|
|
return ccWriterOptsTransform(commit, context);
|
|
};
|
|
|
|
return writerOpts;
|
|
});
|
|
|
|
return writerOpts;
|
|
}
|
|
|
|
module.exports = function (parameter) {
|
|
const config = parameter || {};
|
|
|
|
return Q.all([
|
|
ccConventionalChangelog(config),
|
|
ccParserOpts(config),
|
|
ccRecommendedBumpOpts(config),
|
|
writerOpts(config)
|
|
]).spread((
|
|
conventionalChangelog,
|
|
parserOpts,
|
|
recommendedBumpOpts,
|
|
writerOpts
|
|
) => {
|
|
if (_.isFunction(parameter)) {
|
|
return parameter(null, {
|
|
gitRawCommitsOpts: { noMerges: null },
|
|
conventionalChangelog,
|
|
parserOpts,
|
|
recommendedBumpOpts,
|
|
writerOpts
|
|
});
|
|
} else {
|
|
return {
|
|
conventionalChangelog,
|
|
parserOpts,
|
|
recommendedBumpOpts,
|
|
writerOpts
|
|
};
|
|
}
|
|
});
|
|
};
|
|
|