xref: /rk3399_ARM-atf/tools/conventional-changelog-tf-a/index.js (revision c4e8edab2174fa9be35f6c9b0cee52ec6397ecc7)
1*c4e8edabSChris Kay/*
2*c4e8edabSChris Kay * Copyright (c) 2021, Arm Limited. All rights reserved.
3*c4e8edabSChris Kay *
4*c4e8edabSChris Kay * SPDX-License-Identifier: BSD-3-Clause
5*c4e8edabSChris Kay */
6*c4e8edabSChris Kay
7*c4e8edabSChris Kay/* eslint-env es6 */
8*c4e8edabSChris Kay
9*c4e8edabSChris Kay"use strict";
10*c4e8edabSChris Kay
11*c4e8edabSChris Kayconst Handlebars = require("handlebars");
12*c4e8edabSChris Kayconst Q = require("q");
13*c4e8edabSChris Kayconst _ = require("lodash");
14*c4e8edabSChris Kay
15*c4e8edabSChris Kayconst ccConventionalChangelog = require("conventional-changelog-conventionalcommits/conventional-changelog");
16*c4e8edabSChris Kayconst ccParserOpts = require("conventional-changelog-conventionalcommits/parser-opts");
17*c4e8edabSChris Kayconst ccRecommendedBumpOpts = require("conventional-changelog-conventionalcommits/conventional-recommended-bump");
18*c4e8edabSChris Kayconst ccWriterOpts = require("conventional-changelog-conventionalcommits/writer-opts");
19*c4e8edabSChris Kay
20*c4e8edabSChris Kayconst execa = require("execa");
21*c4e8edabSChris Kay
22*c4e8edabSChris Kayconst readFileSync = require("fs").readFileSync;
23*c4e8edabSChris Kayconst resolve = require("path").resolve;
24*c4e8edabSChris Kay
25*c4e8edabSChris Kay/*
26*c4e8edabSChris Kay * Register a Handlebars helper that lets us generate Markdown lists that can support multi-line
27*c4e8edabSChris Kay * strings. This is driven by inconsistent formatting of breaking changes, which may be multiple
28*c4e8edabSChris Kay * lines long and can terminate the list early unintentionally.
29*c4e8edabSChris Kay */
30*c4e8edabSChris KayHandlebars.registerHelper("tf-a-mdlist", function (indent, options) {
31*c4e8edabSChris Kay    const spaces = new Array(indent + 1).join(" ");
32*c4e8edabSChris Kay    const first = spaces + "- ";
33*c4e8edabSChris Kay    const nth = spaces + "  ";
34*c4e8edabSChris Kay
35*c4e8edabSChris Kay    return first + options.fn(this).replace(/\n(?!\s*\n)/gm, `\n${nth}`).trim() + "\n";
36*c4e8edabSChris Kay});
37*c4e8edabSChris Kay
38*c4e8edabSChris Kay/*
39*c4e8edabSChris Kay * Register a Handlebars helper that concatenates multiple variables. We use this to generate the
40*c4e8edabSChris Kay * title for the section partials.
41*c4e8edabSChris Kay */
42*c4e8edabSChris KayHandlebars.registerHelper("tf-a-concat", function () {
43*c4e8edabSChris Kay    let argv = Array.prototype.slice.call(arguments, 0);
44*c4e8edabSChris Kay
45*c4e8edabSChris Kay    argv.pop();
46*c4e8edabSChris Kay
47*c4e8edabSChris Kay    return argv.join("");
48*c4e8edabSChris Kay});
49*c4e8edabSChris Kay
50*c4e8edabSChris Kayfunction writerOpts(config) {
51*c4e8edabSChris Kay    /*
52*c4e8edabSChris Kay     * Flatten the configuration's sections list. This helps us iterate over all of the sections
53*c4e8edabSChris Kay     * when we don't care about the hierarchy.
54*c4e8edabSChris Kay     */
55*c4e8edabSChris Kay
56*c4e8edabSChris Kay    const flattenSections = function (sections) {
57*c4e8edabSChris Kay        return sections.flatMap(section => {
58*c4e8edabSChris Kay            const subsections = flattenSections(section.sections || []);
59*c4e8edabSChris Kay
60*c4e8edabSChris Kay            return [section].concat(subsections);
61*c4e8edabSChris Kay        })
62*c4e8edabSChris Kay    };
63*c4e8edabSChris Kay
64*c4e8edabSChris Kay    const flattenedSections = flattenSections(config.sections);
65*c4e8edabSChris Kay
66*c4e8edabSChris Kay    /*
67*c4e8edabSChris Kay     * Register a helper to return a restructured version of the note groups that includes notes
68*c4e8edabSChris Kay     * categorized by their section.
69*c4e8edabSChris Kay     */
70*c4e8edabSChris Kay    Handlebars.registerHelper("tf-a-notes", function (noteGroups, options) {
71*c4e8edabSChris Kay        const generateTemplateData = function (sections, notes) {
72*c4e8edabSChris Kay            return (sections || []).flatMap(section => {
73*c4e8edabSChris Kay                const templateData = {
74*c4e8edabSChris Kay                    title: section.title,
75*c4e8edabSChris Kay                    sections: generateTemplateData(section.sections, notes),
76*c4e8edabSChris Kay                    notes: notes.filter(note => section.scopes?.includes(note.commit.scope)),
77*c4e8edabSChris Kay                };
78*c4e8edabSChris Kay
79*c4e8edabSChris Kay                /*
80*c4e8edabSChris Kay                 * Don't return a section if it contains no notes and no sub-sections.
81*c4e8edabSChris Kay                 */
82*c4e8edabSChris Kay                if ((templateData.sections.length == 0) && (templateData.notes.length == 0)) {
83*c4e8edabSChris Kay                    return [];
84*c4e8edabSChris Kay                }
85*c4e8edabSChris Kay
86*c4e8edabSChris Kay                return [templateData];
87*c4e8edabSChris Kay            });
88*c4e8edabSChris Kay        };
89*c4e8edabSChris Kay
90*c4e8edabSChris Kay        return noteGroups.map(noteGroup => {
91*c4e8edabSChris Kay            return {
92*c4e8edabSChris Kay                title: noteGroup.title,
93*c4e8edabSChris Kay                sections: generateTemplateData(config.sections, noteGroup.notes),
94*c4e8edabSChris Kay                notes: noteGroup.notes.filter(note =>
95*c4e8edabSChris Kay                    !flattenedSections.some(section => section.scopes?.includes(note.commit.scope))),
96*c4e8edabSChris Kay            };
97*c4e8edabSChris Kay        });
98*c4e8edabSChris Kay    });
99*c4e8edabSChris Kay
100*c4e8edabSChris Kay    /*
101*c4e8edabSChris Kay     * Register a helper to return a restructured version of the commit groups that includes commits
102*c4e8edabSChris Kay     * categorized by their section.
103*c4e8edabSChris Kay     */
104*c4e8edabSChris Kay    Handlebars.registerHelper("tf-a-commits", function (commitGroups, options) {
105*c4e8edabSChris Kay        const generateTemplateData = function (sections, commits) {
106*c4e8edabSChris Kay            return (sections || []).flatMap(section => {
107*c4e8edabSChris Kay                const templateData = {
108*c4e8edabSChris Kay                    title: section.title,
109*c4e8edabSChris Kay                    sections: generateTemplateData(section.sections, commits),
110*c4e8edabSChris Kay                    commits: commits.filter(commit => section.scopes?.includes(commit.scope)),
111*c4e8edabSChris Kay                };
112*c4e8edabSChris Kay
113*c4e8edabSChris Kay                /*
114*c4e8edabSChris Kay                 * Don't return a section if it contains no notes and no sub-sections.
115*c4e8edabSChris Kay                 */
116*c4e8edabSChris Kay                if ((templateData.sections.length == 0) && (templateData.commits.length == 0)) {
117*c4e8edabSChris Kay                    return [];
118*c4e8edabSChris Kay                }
119*c4e8edabSChris Kay
120*c4e8edabSChris Kay                return [templateData];
121*c4e8edabSChris Kay            });
122*c4e8edabSChris Kay        };
123*c4e8edabSChris Kay
124*c4e8edabSChris Kay        return commitGroups.map(commitGroup => {
125*c4e8edabSChris Kay            return {
126*c4e8edabSChris Kay                title: commitGroup.title,
127*c4e8edabSChris Kay                sections: generateTemplateData(config.sections, commitGroup.commits),
128*c4e8edabSChris Kay                commits: commitGroup.commits.filter(commit =>
129*c4e8edabSChris Kay                    !flattenedSections.some(section => section.scopes?.includes(commit.scope))),
130*c4e8edabSChris Kay            };
131*c4e8edabSChris Kay        });
132*c4e8edabSChris Kay    });
133*c4e8edabSChris Kay
134*c4e8edabSChris Kay    const writerOpts = ccWriterOpts(config)
135*c4e8edabSChris Kay        .then(writerOpts => {
136*c4e8edabSChris Kay            const ccWriterOptsTransform = writerOpts.transform;
137*c4e8edabSChris Kay
138*c4e8edabSChris Kay            /*
139*c4e8edabSChris Kay             * These configuration properties can't be injected directly into the template because
140*c4e8edabSChris Kay             * they themselves are templates. Instead, we register them as partials, which allows
141*c4e8edabSChris Kay             * them to be evaluated as part of the templates they're used in.
142*c4e8edabSChris Kay             */
143*c4e8edabSChris Kay            Handlebars.registerPartial("commitUrl", config.commitUrlFormat);
144*c4e8edabSChris Kay            Handlebars.registerPartial("compareUrl", config.compareUrlFormat);
145*c4e8edabSChris Kay            Handlebars.registerPartial("issueUrl", config.issueUrlFormat);
146*c4e8edabSChris Kay
147*c4e8edabSChris Kay            /*
148*c4e8edabSChris Kay             * Register the partials that allow us to recursively create changelog sections.
149*c4e8edabSChris Kay             */
150*c4e8edabSChris Kay
151*c4e8edabSChris Kay            const notePartial = readFileSync(resolve(__dirname, "./templates/note.hbs"), "utf-8");
152*c4e8edabSChris Kay            const noteSectionPartial = readFileSync(resolve(__dirname, "./templates/note-section.hbs"), "utf-8");
153*c4e8edabSChris Kay            const commitSectionPartial = readFileSync(resolve(__dirname, "./templates/commit-section.hbs"), "utf-8");
154*c4e8edabSChris Kay
155*c4e8edabSChris Kay            Handlebars.registerPartial("tf-a-note", notePartial);
156*c4e8edabSChris Kay            Handlebars.registerPartial("tf-a-note-section", noteSectionPartial);
157*c4e8edabSChris Kay            Handlebars.registerPartial("tf-a-commit-section", commitSectionPartial);
158*c4e8edabSChris Kay
159*c4e8edabSChris Kay            /*
160*c4e8edabSChris Kay             * Override the base templates so that we can generate a changelog that looks at least
161*c4e8edabSChris Kay             * similar to the pre-Conventional Commits TF-A changelog.
162*c4e8edabSChris Kay             */
163*c4e8edabSChris Kay            writerOpts.mainTemplate = readFileSync(resolve(__dirname, "./templates/template.hbs"), "utf-8");
164*c4e8edabSChris Kay            writerOpts.headerPartial = readFileSync(resolve(__dirname, "./templates/header.hbs"), "utf-8");
165*c4e8edabSChris Kay            writerOpts.commitPartial = readFileSync(resolve(__dirname, "./templates/commit.hbs"), "utf-8");
166*c4e8edabSChris Kay            writerOpts.footerPartial = readFileSync(resolve(__dirname, "./templates/footer.hbs"), "utf-8");
167*c4e8edabSChris Kay
168*c4e8edabSChris Kay            writerOpts.transform = function (commit, context) {
169*c4e8edabSChris Kay                /*
170*c4e8edabSChris Kay                 * Fix up commit trailers, which for some reason are not correctly recognized and
171*c4e8edabSChris Kay                 * end up showing up in the breaking changes.
172*c4e8edabSChris Kay                 */
173*c4e8edabSChris Kay
174*c4e8edabSChris Kay                commit.notes.forEach(note => {
175*c4e8edabSChris Kay                    const trailers = execa.sync("git", ["interpret-trailers", "--parse"], {
176*c4e8edabSChris Kay                        input: note.text
177*c4e8edabSChris Kay                    }).stdout;
178*c4e8edabSChris Kay
179*c4e8edabSChris Kay                    note.text = note.text.replace(trailers, "").trim();
180*c4e8edabSChris Kay                });
181*c4e8edabSChris Kay
182*c4e8edabSChris Kay                return ccWriterOptsTransform(commit, context);
183*c4e8edabSChris Kay            };
184*c4e8edabSChris Kay
185*c4e8edabSChris Kay            return writerOpts;
186*c4e8edabSChris Kay        });
187*c4e8edabSChris Kay
188*c4e8edabSChris Kay    return writerOpts;
189*c4e8edabSChris Kay}
190*c4e8edabSChris Kay
191*c4e8edabSChris Kaymodule.exports = function (parameter) {
192*c4e8edabSChris Kay    const config = parameter || {};
193*c4e8edabSChris Kay
194*c4e8edabSChris Kay    return Q.all([
195*c4e8edabSChris Kay        ccConventionalChangelog(config),
196*c4e8edabSChris Kay        ccParserOpts(config),
197*c4e8edabSChris Kay        ccRecommendedBumpOpts(config),
198*c4e8edabSChris Kay        writerOpts(config)
199*c4e8edabSChris Kay    ]).spread((
200*c4e8edabSChris Kay        conventionalChangelog,
201*c4e8edabSChris Kay        parserOpts,
202*c4e8edabSChris Kay        recommendedBumpOpts,
203*c4e8edabSChris Kay        writerOpts
204*c4e8edabSChris Kay    ) => {
205*c4e8edabSChris Kay        if (_.isFunction(parameter)) {
206*c4e8edabSChris Kay            return parameter(null, {
207*c4e8edabSChris Kay                gitRawCommitsOpts: { noMerges: null },
208*c4e8edabSChris Kay                conventionalChangelog,
209*c4e8edabSChris Kay                parserOpts,
210*c4e8edabSChris Kay                recommendedBumpOpts,
211*c4e8edabSChris Kay                writerOpts
212*c4e8edabSChris Kay            });
213*c4e8edabSChris Kay        } else {
214*c4e8edabSChris Kay            return {
215*c4e8edabSChris Kay                conventionalChangelog,
216*c4e8edabSChris Kay                parserOpts,
217*c4e8edabSChris Kay                recommendedBumpOpts,
218*c4e8edabSChris Kay                writerOpts
219*c4e8edabSChris Kay            };
220*c4e8edabSChris Kay        }
221*c4e8edabSChris Kay    });
222*c4e8edabSChris Kay};
223