UNPKG

@adinsure-ops/ops-cli

Version:

Operations CLI for working with AdInsure

165 lines (164 loc) 7.52 kB
import { __awaiter } from "tslib"; import { Flags, ux } from '@oclif/core'; import fs from 'fs-extra'; import matter from 'gray-matter'; import _ from 'lodash-es'; import moment from 'moment'; import path from 'path'; import { rimraf } from 'rimraf'; import CommandBase from '../../command.base.js'; class Generate extends CommandBase { constructor() { super(...arguments); // eslint-disable-next-line @typescript-eslint/no-explicit-any this._entryTypes = [ { types: ['BreakingChange', 'breaking'], text: 'Breaking Changes' }, { types: ['Feature', 'feature'], text: 'New Features' }, { types: ['Improvement', 'improve'], text: 'Improvements' }, { types: ['Fixed', 'fixed'], text: 'Fixed' }, ]; this._mdFileTypeRegex = /\.md$/i; } readChangelogs(dir) { return __awaiter(this, void 0, void 0, function* () { const result = []; const files = yield this.getFilesRecursive(dir, dir, this._mdFileTypeRegex); for (const fileName of files) { const filePath = path.resolve(dir, fileName); let fileComponent = path.basename(path.dirname(filePath)); fileComponent = fileComponent === 'unreleased' ? '' : fileComponent; const contents = matter.read(filePath); result.push({ type: contents.data.type, issue: contents.data.issue, component: fileComponent, content: contents.content.trim(), }); } return result; }); } generateLog(entries, components) { return __awaiter(this, void 0, void 0, function* () { let content = ''; // First group by component, then re-sort the components to get the desired output ordering const componentGroups = _.groupBy(entries, 'component'); for (const group of components) { if (group !== '') { content += `## ${_.capitalize(group)}\r\n\r\n`; } if (componentGroups[group] === undefined || componentGroups[group].length === 0) { content += `No changes were introduced in this version.\r\n\r\n`; continue; } for (const type of this._entryTypes) { const batch = _.remove(componentGroups[group], e => _.indexOf(type.types, e.type) > -1); if (batch.length === 0) { continue; } content += `### ${type.text} (${batch.length} changes)\r\n\r\n`; for (const log of batch) { if (log.content.startsWith('-') || log.content.startsWith('* ')) { content += `- [${log.issue}](https://jira.adacta-fintech.com/browse/${log.issue}):\r\n`; content += ' ' + log.content.replaceAll('\n', '\n '); } else { content += `- [${log.issue}](https://jira.adacta-fintech.com/browse/${log.issue}): `; content += log.content.replaceAll('\n', '\n ') + '\r\n'; } content += '\r\n'; } content += '\r\n'; } } // We only want 1 new line at the end of this changelog return content.trimEnd() + '\r\n\r\n'; }); } run() { const _super = Object.create(null, { run: { get: () => super.run } }); return __awaiter(this, void 0, void 0, function* () { var _a; _super.run.call(this); const { flags } = yield this.parse(Generate); const root = path.resolve(process.cwd()); const changelogFolder = yield this.getChangelogsRoot(); const version = (_a = flags.version) !== null && _a !== void 0 ? _a : yield this.getVersion(); const components = flags.components.split(','); this.log(`Inspecting ${changelogFolder} ...`); ux.action.start(`generating for ${version}`, undefined, { stdout: true }); const changelogs = yield this.readChangelogs(changelogFolder); if (!flags['ignore-no-changelogs'] && changelogs.length === 0) { ux.action.stop('done, no changelogs found.'); return; } const content = `# ${version} (${moment().format('YYYY-MM-DD')})\r\n\r\n${yield this.generateLog(changelogs, components)}`; if (flags.preview) { fs.writeFileSync(path.resolve(root, 'preview.md'), content, 'utf8'); ux.action.stop('generated preview.md'); return; } this.warn(`The following action will update ${flags.file} and remove all unreleased changelogs in '${changelogFolder}'`); const confirm = flags.force || (yield ux.confirm(`Do you want to continue? [y/n/yes/no]`)); if (confirm) { const changelogFile = path.resolve(root, flags.file); const currentContents = fs.readFileSync(changelogFile, { encoding: 'utf8', flag: 'a+' }); fs.writeFileSync(changelogFile, content + currentContents, { encoding: 'utf8', flag: 'w' }); // delete unreleased changelogs for (const group of components) { const componentChangelogFolder = path.join(changelogFolder, group); this.log(`Removing unreleased changelogs from ${componentChangelogFolder}`); const changelogFiles = yield fs.readdir(componentChangelogFolder); for (const file of changelogFiles) { if (this._mdFileTypeRegex.test(file)) { rimraf.sync(path.resolve(componentChangelogFolder, file)); } } } } ux.action.stop('done'); }); } } Generate.aliases = ['log:gen']; Generate.description = 'generates final changelog from unreleased notes'; Generate.usage = 'log:gen [options]'; Generate.examples = [ `$ ops log:gen`, `$ ops log:gen -p`, `$ ops log:gen -c Platform`, `$ ops log:gen -c Platform,Configuration`, `$ ops log:gen -c Implementation -f CHANGELOG.Implementation.md`, ]; Generate.flags = { components: Flags.string({ char: 'c', description: 'components separated by comma (,) to process for final changelog', default: '', }), file: Flags.string({ char: 'f', description: 'file to update with new changelogs', default: 'CHANGELOG.md', }), force: Flags.boolean({ description: 'update target file without prompting for confirmation', default: false, }), preview: Flags.boolean({ char: 'p', description: 'generates a preview markdown of current unreleased changelogs', default: false, }), version: Flags.string({ char: 'v', description: 'version for which you\'re generating changelogs', }), 'ignore-no-changelogs': Flags.boolean({ description: 'generate final changelog even if no unreleased changelogs exist', default: false, }), }; export default Generate;