UNPKG

smooth-release

Version:

Smart CLI tool to safely and automatically do every step to release a new version of a library hosted on GitHub and published on npm

399 lines (303 loc) 13.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); var _keys = require('babel-runtime/core-js/object/keys'); var _keys2 = _interopRequireDefault(_keys); var _defineProperty2 = require('babel-runtime/helpers/defineProperty'); var _defineProperty3 = _interopRequireDefault(_defineProperty2); var _regenerator = require('babel-runtime/regenerator'); var _regenerator2 = _interopRequireDefault(_regenerator); var _extends3 = require('babel-runtime/helpers/extends'); var _extends4 = _interopRequireDefault(_extends3); var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); var _staggerjs = require('staggerjs'); var _staggerjs2 = _interopRequireDefault(_staggerjs); var _lodash = require('lodash'); var _utils = require('../utils'); var _getAllTags = require('../modules/getAllTags'); var _getAllTags2 = _interopRequireDefault(_getAllTags); var _getAllClosedIssues = require('../modules/getAllClosedIssues'); var _getAllClosedIssues2 = _interopRequireDefault(_getAllClosedIssues); var _getAllMergedPullRequests = require('../modules/getAllMergedPullRequests'); var _getAllMergedPullRequests2 = _interopRequireDefault(_getAllMergedPullRequests); var _config = require('../config'); var _config2 = _interopRequireDefault(_config); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var _getGithubOwnerAndRep = (0, _utils.getGithubOwnerAndRepo)(), owner = _getGithubOwnerAndRep.owner, repo = _getGithubOwnerAndRep.repo; var addCreatedAtInfoToTags = function () { var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(tags) { return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: _context2.next = 2; return (0, _staggerjs2.default)(tags.map(function (tag) { return (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() { var tagCommit; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return _utils.github.commits(tag.commit.sha).fetch(); case 2: tagCommit = _context.sent; return _context.abrupt('return', (0, _extends4.default)({}, tag, { createdAt: new Date(tagCommit.commit.author.date) })); case 4: case 'end': return _context.stop(); } } }, _callee, undefined); })); }), { maxOngoingMethods: 10, perSecond: 20 }); case 2: return _context2.abrupt('return', _context2.sent); case 3: case 'end': return _context2.stop(); } } }, _callee2, undefined); })); return function addCreatedAtInfoToTags(_x) { return _ref.apply(this, arguments); }; }(); var hasAtLeastOneLabel = function hasAtLeastOneLabel(issue, labels) { return (0, _lodash.some)(labels, function (label) { return (0, _lodash.find)(issue.labels, { name: label }); }); }; var groupIssuesByTag = function groupIssuesByTag(closedIssues, tags) { var tagsSortedAsc = (0, _lodash.sortBy)(tags, ['createdAt']); return closedIssues.reduce(function (issuesByTag, issue) { var tag = (0, _lodash.find)(tagsSortedAsc, function (tag) { return tag.createdAt > issue.closedAt; }); if (tag) { return (0, _extends4.default)({}, issuesByTag, (0, _defineProperty3.default)({}, tag.name, (issuesByTag[tag.name] || []).concat(issue))); } return (0, _extends4.default)({}, issuesByTag, { unreleased: (issuesByTag.unreleased || []).concat(issue) }); }, {}); }; var groupIssuesByType = function groupIssuesByType(issues) { var isBreaking = function isBreaking(issue) { return hasAtLeastOneLabel(issue, _config2.default.github.changelog.breaking.labels); }; var isBug = function isBug(issue) { return hasAtLeastOneLabel(issue, _config2.default.github.changelog.bug.labels); }; return issues.reduce(function (issuesByType, issue) { if (isBreaking(issue)) { return (0, _extends4.default)({}, issuesByType, { breaking: (issuesByType.breaking || []).concat(issue) }); } else if (isBug(issue)) { return (0, _extends4.default)({}, issuesByType, { bug: (issuesByType.bug || []).concat(issue) }); } else { return (0, _extends4.default)({}, issuesByType, { feature: (issuesByType.feature || []).concat(issue) }); } }, {}); }; var createChangelogSection = function createChangelogSection(_ref3) { var previousTag = _ref3.previousTag, tag = _ref3.tag, _ref3$issues = _ref3.issues, issues = _ref3$issues === undefined ? [] : _ref3$issues; var dateTime = tag ? ' (' + tag.createdAt.toISOString().slice(0, 10) + ')' : ''; var tagLink = '## [' + (tag ? tag.name : 'Unreleased') + '](https://github.com/' + owner + '/' + repo + '/tree/' + (tag ? tag.name : 'HEAD') + ')' + dateTime; var fullChangelogLink = previousTag ? '[Full Changelog](https://github.com/' + owner + '/' + repo + '/compare/' + previousTag.name + '...' + (tag ? tag.name : 'HEAD') + ')' : ''; var header = tagLink + '\n' + fullChangelogLink; if (issues.length === 0) { return header; } var issuesGroupedByType = groupIssuesByType(issues); var types = (0, _keys2.default)(issuesGroupedByType); var content = types.reduce(function (acc, type) { var issues = issuesGroupedByType[type].map(function (issue) { return '- ' + issue.title + ' [#' + issue.number + '](' + issue.htmlUrl + ')'; }).join('\n'); return acc + '\n\n' + _config2.default.github.changelog[type].title + '\n\n' + issues; }, ''); return header + '\n\n' + content.trim(); }; var getDataFromGitHub = function () { var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(dataType) { var data, tags, tagsWithCreatedAt; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: (0, _utils.info)('Get data from GitHub'); _utils.status.addSteps(['Get all ' + (dataType === 'pullRequests' ? 'merged pull requests' : 'closed issues') + ' from GitHub', 'Get all tags from GitHub', 'Add "createdAt" date-time info to each tag']); // GET data from GitHub data = void 0; _context3.t0 = dataType; _context3.next = _context3.t0 === 'issues' ? 6 : _context3.t0 === 'pullRequests' ? 11 : 16; break; case 6: _context3.next = 8; return (0, _getAllClosedIssues2.default)(); case 8: _context3.t1 = function (i) { return !hasAtLeastOneLabel(i, _config2.default.github.changelog.ignoredLabels); }; data = _context3.sent.filter(_context3.t1); return _context3.abrupt('break', 16); case 11: _context3.next = 13; return (0, _getAllMergedPullRequests2.default)(); case 13: _context3.t2 = function (i) { return !hasAtLeastOneLabel(i, _config2.default.github.changelog.ignoredLabels); }; data = _context3.sent.filter(_context3.t2); return _context3.abrupt('break', 16); case 16: _utils.status.doneStep(true); // GET tags _context3.next = 19; return (0, _getAllTags2.default)(); case 19: tags = _context3.sent; _utils.status.doneStep(true); // ADD "created-at" info to each tag if (!tags.length) { _context3.next = 27; break; } _context3.next = 24; return addCreatedAtInfoToTags(tags); case 24: _context3.t3 = _context3.sent; _context3.next = 28; break; case 27: _context3.t3 = tags; case 28: tagsWithCreatedAt = _context3.t3; _utils.status.doneStep(true); return _context3.abrupt('return', { data: data, tagsWithCreatedAt: tagsWithCreatedAt }); case 31: case 'end': return _context3.stop(); } } }, _callee3, undefined); })); return function getDataFromGitHub(_x2) { return _ref4.apply(this, arguments); }; }(); var generateChangelog = function generateChangelog(_ref5) { var data = _ref5.data, dataType = _ref5.dataType, tagsWithCreatedAt = _ref5.tagsWithCreatedAt, hasIncreasedVersion = _ref5.hasIncreasedVersion; (0, _utils.info)('Generate the changelog'); _utils.status.addSteps(['Group ' + (dataType === 'pullRequests' ? 'merged pull requests' : 'closed issues') + ' by relative tag', 'Generate changelog for each tag']); var tags = hasIncreasedVersion ? [{ name: 'v' + (0, _utils.getPackageJsonVersion)(), createdAt: new Date() }].concat((0, _toConsumableArray3.default)(tagsWithCreatedAt)) : tagsWithCreatedAt; // GROUP data by tag var dataGroupedByTag = void 0; switch (dataType) { case 'issues': dataGroupedByTag = groupIssuesByTag(data, tags); break; case 'pullRequests': dataGroupedByTag = groupIssuesByTag(data, tags); break; } _utils.status.doneStep(true); // WRITE changelog for each tag var changelogSections = tags.map(function (tag, i) { return createChangelogSection({ tag: tag, previousTag: tags[i + 1], issues: dataGroupedByTag[tag.name] }); }); // WRITE changelog for unreleased issues (without tag) var unreleased = dataGroupedByTag.unreleased ? createChangelogSection({ previousTag: tags[0], tag: null, issues: dataGroupedByTag.unreleased }) : ''; // WRITE complete changelog var changelogMarkdown = '# Change Log\n\n' + [unreleased].concat(changelogSections).join('\n\n'); _utils.status.doneStep(true); return changelogMarkdown; }; var saveChangelog = function () { var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(changelogMarkdown) { return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: (0, _utils.info)('Update CHANGELOG.md'); _utils.status.addSteps(['Update CHANGELOG.md locally']); // SAVE changelog _fs2.default.writeFileSync((0, _utils.getRootFolderPath)() + '/' + _config2.default.github.changelog.outputPath, changelogMarkdown); // THROW error if changelog hasn't changed _context4.next = 5; return (0, _utils.exec)('git status --porcelain -- ' + _config2.default.github.changelog.outputPath); case 5: _context4.t0 = _context4.sent.trim().length; if (!(_context4.t0 === 0)) { _context4.next = 8; break; } throw new _utils.SmoothReleaseError('CHANGELOG.md hasn\'t changed'); case 8: _utils.status.doneStep(true); case 9: case 'end': return _context4.stop(); } } }, _callee4, undefined); })); return function saveChangelog(_x3) { return _ref6.apply(this, arguments); }; }(); exports.default = function () { var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(_ref7) { var hasIncreasedVersion = _ref7.hasIncreasedVersion, dataType = _ref7.dataType; var _ref9, data, tagsWithCreatedAt, changelogMarkdown; return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: (0, _utils.title)('Update changelog'); _context5.next = 3; return getDataFromGitHub(dataType); case 3: _ref9 = _context5.sent; data = _ref9.data; tagsWithCreatedAt = _ref9.tagsWithCreatedAt; changelogMarkdown = generateChangelog({ data: data, dataType: dataType, tagsWithCreatedAt: tagsWithCreatedAt, hasIncreasedVersion: hasIncreasedVersion }); _context5.next = 9; return saveChangelog(changelogMarkdown); case 9: return _context5.abrupt('return', changelogMarkdown); case 10: case 'end': return _context5.stop(); } } }, _callee5, undefined); })); return function (_x4) { return _ref8.apply(this, arguments); }; }();