UNPKG

@atomist/sdm-pack-changelog

Version:

Extension Pack for an Atomist SDM to manage changelogs

231 lines (223 loc) 9.45 kB
"use strict"; /* * Copyright © 2018 Atomist, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const automation_client_1 = require("@atomist/automation-client"); const fs = require("fs-extra"); const _ = require("lodash"); const path = require("path"); const util_1 = require("util"); const changelogLabels_1 = require("../handler/command/changelogLabels"); const parseChangelog = require("./changelogParser"); exports.ChangelogTemplate = `# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [0.0.0] `; /** * Add entry to changelog for closed label or pull request * @param {ClosedIssueWithChangelogLabel.Issue} issue * @param {string} token * @returns {Promise<HandlerResult>} */ function addChangelogEntryForClosedIssue(issue, token) { return __awaiter(this, void 0, void 0, function* () { const p = yield automation_client_1.GitCommandGitProject.cloned({ token }, automation_client_1.GitHubRepoRef.from({ owner: issue.repo.owner, repo: issue.repo.name })); const url = `https://github.com/${issue.repo.owner}/${issue.repo.name}/issues/${issue.number}`; const categories = issue.labels.filter(l => l.name.startsWith("changelog:")).map(l => l.name.split(":")[1]); const qualifiers = issue.labels.some(l => l.name.toLocaleLowerCase() === "breaking") ? ["breaking"] : []; const entry = { title: issue.title, label: `#${issue.number.toString()}`, url, qualifiers, }; yield updateChangelog(p, categories, entry); return automation_client_1.Success; }); } exports.addChangelogEntryForClosedIssue = addChangelogEntryForClosedIssue; /** * Add entry to changelog for commits * @param {PushWithChangelogLabel.Push} commit * @param {string} token * @returns {Promise<HandlerResult>} */ function addChangelogEntryForCommit(push, token) { return __awaiter(this, void 0, void 0, function* () { const p = yield automation_client_1.GitCommandGitProject.cloned({ token }, automation_client_1.GitHubRepoRef.from({ owner: push.repo.owner, repo: push.repo.name, branch: push.branch })); for (const commit of push.commits) { const categories = []; changelogLabels_1.ChangelogLabels.forEach(l => { if (commit.message.toLowerCase().includes(`[changelog:${l}]`)) { categories.push(l); } }); const entry = { title: commit.message.split("\n")[0], label: commit.sha.slice(0, 7), url: `https://github.com/${push.repo.owner}/${push.repo.name}/commit/${commit.sha}`, qualifiers: [], }; if (categories.length > 0) { yield updateChangelog(p, categories, entry); } } return automation_client_1.Success; }); } exports.addChangelogEntryForCommit = addChangelogEntryForCommit; function updateChangelog(p, categories, entry) { return __awaiter(this, void 0, void 0, function* () { const cl = yield p.getFile("CHANGELOG.md"); if (cl) { // If changelog exists make sure it doesn't already contain the label const content = yield cl.getContent(); if (!content.includes(entry.url)) { yield updateAndWriteChangelog(p, categories, entry); } } else { yield updateAndWriteChangelog(p, categories, entry); } if (!(yield p.isClean())) { yield p.commit(`Changelog: ${entry.label} to ${categories.join(", ")} [atomist:generated]`); yield p.push(); } }); } function updateAndWriteChangelog(p, categories, entry) { return __awaiter(this, void 0, void 0, function* () { let changelog = yield readChangelog(p); for (const category of categories) { changelog = addEntryToChangelog(Object.assign(Object.assign({}, entry), { category }), changelog, p); } return writeChangelog(changelog, p); }); } /** * Read and parse the project changelog. If the changelog has * reference links, this function will rewrite the file with inline * links. If the project does not have a changelog, one will be * added. * * @param p project with changelog file, or not * @return an object representing the parsed changelog */ function readChangelog(p) { return __awaiter(this, void 0, void 0, function* () { const changelogFile = path.join(p.baseDir, "CHANGELOG.md"); if (!fs.existsSync(changelogFile)) { yield fs.writeFile(changelogFile, exports.ChangelogTemplate); } // Inline links as we would otherwise lose them const remark = require("remark"); const links = require("remark-inline-links"); const pr = util_1.promisify(remark().use(links).process); const inlined = yield pr(yield fs.readFile(changelogFile)); yield fs.writeFile(changelogFile, inlined.contents); return parseChangelog(changelogFile); }); } exports.readChangelog = readChangelog; function addEntryToChangelog(entry, cl, p) { const version = readUnreleasedVersion(cl, p); // Add the entry to the correct section const category = _.upperFirst(entry.category || "changed"); const qualifiers = (entry.qualifiers || []).map(q => `**${q.toLocaleUpperCase()}**`).join(" "); const title = entry.title.endsWith(".") ? entry.title : `${entry.title}.`; const prefix = (qualifiers && qualifiers.length > 0) ? `${qualifiers} ` : ""; const line = `- ${prefix}${title} [${entry.label}](${entry.url})`; if (version.parsed[category]) { version.parsed[category].push(line); } else { version.parsed[category] = [line]; } return cl; } exports.addEntryToChangelog = addEntryToChangelog; /** * Convert changelog object back to a string. * * @param changelog parsed changelog object * @return markdown formatted changelog file contents */ function changelogToString(changelog) { let content = `# ${changelog.title}`; if (changelog.description) { content = `${content} ${changelog.description}`; } (changelog.versions || []).filter((v) => v.version !== "0.0.0").forEach((v) => { content += ` ## ${v.title}`; const keys = Object.keys(v.parsed) .filter(k => k !== "_") .sort((k1, k2) => changelogLabels_1.ChangelogLabels.indexOf(k1.toLocaleLowerCase()) - changelogLabels_1.ChangelogLabels.indexOf(k2.toLocaleLowerCase())); for (const category of keys) { content += ` ### ${category} ${v.parsed[category].join("\n")}`; } }); return content + "\n"; } exports.changelogToString = changelogToString; /** * Write changelog back out to the CHANGELOG.md file * @param changelog * @param {GitProject} p * @returns {Promise<void>} */ function writeChangelog(changelog, p) { return __awaiter(this, void 0, void 0, function* () { const content = changelogToString(changelog); const changelogFile = path.join(p.baseDir, "CHANGELOG.md"); return fs.writeFile(changelogFile, content); }); } exports.writeChangelog = writeChangelog; function readUnreleasedVersion(cl, p) { let version; // Get Unreleased section or create if not already available if (cl && cl.versions && cl.versions.length > 0 // This github.com version is really odd. Not sure what the parser thinks here && (!cl.versions[0].version || cl.versions[0].version === "github.com")) { version = cl.versions[0]; } else { version = { title: `[Unreleased](https://github.com/${p.id.owner}/${p.id.repo}/${cl.versions && cl.versions.filter((v) => v.version !== "0.0.0").length > 0 ? `compare/${cl.versions[0].version}...HEAD` : "tree/HEAD"})`, parsed: {}, }; cl.versions = [version, ...cl.versions]; } return version; } //# sourceMappingURL=changelog.js.map