123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- /*
- * 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
- };
- }
- });
- };
|