UNPKG

@salesforce/plugin-release-management

Version:
224 lines 9.29 kB
"use strict"; /* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ Object.defineProperty(exports, "__esModule", { value: true }); const os = require("os"); const command_1 = require("@salesforce/command"); const ts_types_1 = require("@salesforce/ts-types"); const kit_1 = require("@salesforce/kit"); const core_1 = require("@octokit/core"); const chalk_1 = require("chalk"); const core_2 = require("@salesforce/core"); const shelljs_1 = require("shelljs"); const semver = require("semver"); const types_1 = require("../../types"); const package_1 = require("../../package"); core_2.Messages.importMessagesDirectory(__dirname); const messages = core_2.Messages.loadMessages('@salesforce/plugin-release-management', 'cli.releasenotes'); function isNotEmpty(obj) { return Object.keys(obj).length > 0; } class ReleaseNotes extends command_1.SfdxCommand { constructor() { super(...arguments); this.usernames = {}; } async run() { const auth = (0, ts_types_1.ensureString)(new kit_1.Env().getString('GH_TOKEN'), 'GH_TOKEN is required to be set in the environment'); this.octokit = new core_1.Octokit({ auth }); const cli = (0, ts_types_1.ensure)(this.flags.cli); const fullName = cli === types_1.CLI.SF ? '@salesforce/cli' : 'sfdx-cli'; const npmPackage = this.getNpmPackage(fullName, this.flags.since ?? 'latest'); const latestrc = this.getNpmPackage(fullName, 'latest-rc'); const oldPlugins = this.normalizePlugins(npmPackage); const newPlugins = this.normalizePlugins(latestrc); const differences = this.findDifferences(oldPlugins, newPlugins); if (isNotEmpty(differences.upgraded)) { this.ux.styledHeader('Upgraded Plugins'); for (const [plugin, version] of Object.entries(differences.upgraded)) { this.ux.log(`• ${plugin} ${oldPlugins[plugin]} => ${version}`); } } if (isNotEmpty(differences.downgraded)) { this.ux.styledHeader('Downgraded Plugins'); for (const [plugin, version] of Object.entries(differences.downgraded)) { this.ux.log(`• ${plugin} ${oldPlugins[plugin]} => ${version}`); } } if (isNotEmpty(differences.added)) { this.ux.styledHeader('Added Plugins'); for (const [plugin, version] of Object.entries(differences.added)) { this.ux.log(`• ${plugin} ${version}`); } } if (isNotEmpty(differences.removed)) { this.ux.styledHeader('Removed Plugins'); for (const [plugin, version] of Object.entries(differences.removed)) { this.ux.log(`• ${plugin} ${version}`); } } const changesByPlugin = {}; for (const [plugin] of Object.entries(differences.upgraded)) { const pkg = this.getNpmPackage(plugin, oldPlugins[plugin]); const publishDate = pkg.time[pkg.version]; const changes = await this.getPullsForPlugin(plugin, publishDate); if (changes.length) changesByPlugin[plugin] = changes; } if (this.flags.markdown) { this.logChangesMarkdown(changesByPlugin); } else { this.logChanges(changesByPlugin); } return changesByPlugin; } getNpmPackage(name, version = 'latest') { const result = (0, shelljs_1.exec)(`npm view ${name}@${version} --json`, { silent: true }); return JSON.parse(result.stdout); } normalizePlugins(npmPackage) { const plugins = npmPackage.oclif?.plugins ?? []; const normalized = { [npmPackage.name]: npmPackage.version }; plugins.forEach((p) => { const version = (0, package_1.parsePackageVersion)(npmPackage.dependencies[p]); if (npmPackage.dependencies[p].startsWith('npm:')) { const name = (0, package_1.parseAliasedPackageName)(npmPackage.dependencies[p]); normalized[name] = version; } else { normalized[p] = version; } }); return normalized; } findDifferences(oldPlugins, newPlugins) { const removed = {}; const added = {}; const upgraded = {}; const downgraded = {}; const unchanged = {}; for (const [name, version] of Object.entries(oldPlugins)) { if (!newPlugins[name]) removed[name] = version; } for (const [name, version] of Object.entries(newPlugins)) { if (!oldPlugins[name]) added[name] = version; else if (semver.gt(version, oldPlugins[name])) upgraded[name] = version; else if (semver.lt(version, oldPlugins[name])) downgraded[name] = version; else unchanged[name] = version; } return { removed, added, upgraded, downgraded, unchanged }; } async getNameOfUser(username) { if (this.usernames[username]) return this.usernames[username]; const { data } = await this.octokit.request('GET /users/{username}', { username }); const name = (data.name ?? data.login ?? username); this.usernames[username] = name; return name; } async getPullsForPlugin(plugin, publishDate) { const npmPackage = this.getNpmPackage(plugin); const homepage = npmPackage.homepage ?? (npmPackage.name === 'salesforce-alm' ? 'salesforcecli/toolbelt' : null); if (!homepage) { throw new core_2.SfError(`No github url found for ${npmPackage.name}`, 'GitUrlNotFound'); } const [owner, repo] = homepage.replace('https://github.com/', '').replace(/#(.*)/g, '').split('/'); const pullRequests = await this.octokit.request('GET /repos/{owner}/{repo}/pulls', { owner, repo, state: 'closed', base: 'main', // eslint-disable-next-line camelcase per_page: 100, }); const changes = (await Promise.all(pullRequests.data .filter((pr) => { return pr.merged_at && pr.merged_at > publishDate && !pr.user.login.includes('dependabot'); }) .map(async (pr) => { const username = await this.getNameOfUser(pr.user.login); const author = pr.user.login === username ? username : `${username} (${pr.user.login})`; return { author, mergedAt: pr.merged_at, mergedInto: pr.base.ref, link: pr.html_url, title: pr.title, description: (pr.body ?? '').trim(), plugin, }; }))); return changes; } logChanges(changesByPlugin) { for (const [plugin, changes] of Object.entries(changesByPlugin)) { this.ux.styledHeader((0, chalk_1.cyan)(plugin)); for (const change of changes) { this.log((0, chalk_1.bold)(`${change.title}`)); for (const [key, value] of Object.entries(change)) { if (['title', 'plugin'].includes(key)) continue; if (key === 'description') { this.log(`${key}:\n${(0, chalk_1.dim)(value)}`); } else { this.log(`${key}: ${(0, chalk_1.dim)(value)}`); } } this.log(); } this.log(); } } logChangesMarkdown(changesByPlugin) { for (const [plugin, changes] of Object.entries(changesByPlugin)) { this.log(`## ${plugin}`); for (const change of changes) { this.log(`\n### ${change.title}`); for (const [key, value] of Object.entries(change)) { if (['title', 'plugin'].includes(key)) continue; if (key === 'description') { this.log(`- ${key}:\n\`\`\`\n${value}\n\`\`\``); } else { this.log(`- ${key}: ${value}`); } } this.log(); } this.log(); } } } exports.default = ReleaseNotes; ReleaseNotes.description = messages.getMessage('description'); ReleaseNotes.examples = messages.getMessage('examples').split(os.EOL); ReleaseNotes.flagsConfig = { cli: command_1.flags.string({ description: messages.getMessage('cliFlag'), options: Object.values(types_1.CLI), char: 'c', required: true, }), since: command_1.flags.string({ description: messages.getMessage('sinceFlag'), char: 's', }), markdown: command_1.flags.boolean({ description: messages.getMessage('markdownFlag'), char: 'm', default: false, }), }; //# sourceMappingURL=releasenotes.js.map