From c8979719485885af7989b5225e8d8319c66c74bc Mon Sep 17 00:00:00 2001 From: jayree Date: Tue, 17 Jan 2023 22:31:01 +0100 Subject: [PATCH] feat: support sfdx plugins with release notes or changelog --- messages/display.md | 4 +++ src/commands/info/releasenotes/display.ts | 23 +++++++++++----- src/shared/parseReleaseNotes.ts | 4 +-- .../info/releasenotes/display.test.ts | 27 ++++++++++++++++--- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/messages/display.md b/messages/display.md index 7dfb5f47..eb144567 100644 --- a/messages/display.md +++ b/messages/display.md @@ -14,6 +14,10 @@ CLI version or tag for which to display release notes. This hidden parameter is used in post install or update hooks. +# flags.plugin.summary + +Plugin name for which to display release notes. + # examples - Display release notes for the currently installed CLI version: diff --git a/src/commands/info/releasenotes/display.ts b/src/commands/info/releasenotes/display.ts index d33a5493..76210dd6 100644 --- a/src/commands/info/releasenotes/display.ts +++ b/src/commands/info/releasenotes/display.ts @@ -13,7 +13,7 @@ import { marked } from 'marked'; import * as TerminalRenderer from 'marked-terminal'; import { Env } from '@salesforce/kit'; import { Flags, SfCommand, loglevel } from '@salesforce/sf-plugins-core'; -import { Lifecycle, Logger, Messages } from '@salesforce/core'; +import { Lifecycle, Logger, Messages, SfError } from '@salesforce/core'; import { AnyJson, JsonMap } from '@salesforce/ts-types'; import { getInfoConfig } from '../../../shared/getInfoConfig'; import { getReleaseNotes } from '../../../shared/getReleaseNotes'; @@ -40,6 +40,13 @@ export default class Display extends SfCommand { public static readonly examples = messages.getMessages('examples', [Display.helpers.join(', ')]); + public static args = [ + { + name: 'plugin', + description: messages.getMessage('flags.plugin.summary'), + }, + ]; + public static readonly flags = { version: Flags.string({ char: 'v', @@ -54,7 +61,7 @@ export default class Display extends SfCommand { public async run(): Promise { const logger = Logger.childFromRoot(this.constructor.name); - const { flags } = await this.parse(Display); + const { flags, args } = await this.parse(Display); const env = new Env(); const isHook = !!flags.hook; @@ -70,9 +77,13 @@ export default class Display extends SfCommand { } try { - const installedVersion = this.config.pjson.version; + const [plugin] = args.plugin ? this.config.plugins.filter((p) => p.name === args.plugin) : [this.config]; + + if (!plugin) throw new SfError(`No plugin '${args.plugin as string}' found`); + + const installedVersion = plugin.pjson.version; - const infoConfig = await getInfoConfig(this.config.root); + const infoConfig = await getInfoConfig(plugin.root); const { distTagUrl, releaseNotesPath, releaseNotesFilename } = infoConfig.releasenotes; @@ -90,7 +101,7 @@ export default class Display extends SfCommand { renderer: new TerminalRenderer({ emoji: false }), }); - tokens.unshift(marked.lexer(`# Release notes for '${this.config.bin}':`)[0]); + tokens.unshift(marked.lexer(`# Release notes for '${plugin.name}':`)[0]); if (flags.json) { const body = tokens.map((token) => token.raw).join(os.EOL); @@ -104,7 +115,7 @@ export default class Display extends SfCommand { if (env.getBoolean(HIDE_FOOTER)) { await Lifecycle.getInstance().emitTelemetry({ eventName: 'FOOTER_HIDDEN' }); } else { - const footer = messages.getMessage('footer', [this.config.bin, releaseNotesPath, HIDE_NOTES, HIDE_FOOTER]); + const footer = messages.getMessage('footer', [plugin.name, releaseNotesPath, HIDE_NOTES, HIDE_FOOTER]); this.log(marked.parse(footer)); } } diff --git a/src/shared/parseReleaseNotes.ts b/src/shared/parseReleaseNotes.ts index cf507ea8..57aba6b6 100644 --- a/src/shared/parseReleaseNotes.ts +++ b/src/shared/parseReleaseNotes.ts @@ -22,8 +22,8 @@ const parseReleaseNotes = (notes: string, version: string, baseUrl: string): mar tokens = parsed.filter((token) => { // TODO: Could make header depth (2) a setting in oclif.info.releasenotes - if (token.type === 'heading' && token.depth === 2) { - const coercedVersion = semver.coerce(token.text).version; + if (token.type === 'heading' && token.depth <= 2) { + const coercedVersion = semver.coerce(token.text)?.version; // We will use this to find the closest patch if passed version is not found versions.push(coercedVersion); diff --git a/test/commands/info/releasenotes/display.test.ts b/test/commands/info/releasenotes/display.test.ts index 1607bead..c9d31735 100644 --- a/test/commands/info/releasenotes/display.test.ts +++ b/test/commands/info/releasenotes/display.test.ts @@ -14,7 +14,7 @@ import { shouldThrow } from '@salesforce/core/lib/testSetup'; import { marked } from 'marked'; import { Env } from '@salesforce/kit'; import { Lifecycle } from '@salesforce/core'; -import { Config } from '@oclif/core'; +import { Config, Plugin } from '@oclif/core'; import { SfCommand } from '@salesforce/sf-plugins-core'; import * as getInfoConfig from '../../../../src/shared/getInfoConfig'; import * as getReleaseNotes from '../../../../src/shared/getReleaseNotes'; @@ -39,6 +39,7 @@ describe('info:releasenotes:display', () => { let markedParserSpy: Sinon.SinonSpy; const oclifConfigStub = fromStub(stubInterface(sandbox)); + const oclifPluginStub = fromStub(stubInterface(sandbox)); class TestDisplay extends Display { public async runIt() { @@ -48,7 +49,8 @@ describe('info:releasenotes:display', () => { } const runDisplayCmd = async (params: string[]) => { - oclifConfigStub.bin = 'sfdx'; + oclifConfigStub.name = 'sfdx-cli'; + oclifPluginStub.name = 'sfdx-plugin'; const cmd = new TestDisplay(params, oclifConfigStub); @@ -70,6 +72,9 @@ describe('info:releasenotes:display', () => { oclifConfigStub.pjson.version = '3.3.3'; oclifConfigStub.root = '/root/path'; + oclifPluginStub.pjson.version = '3.3.3'; + oclifConfigStub.plugins = [oclifPluginStub]; + getBooleanStub = stubMethod(sandbox, Env.prototype, 'getBoolean'); getBooleanStub.withArgs('SFDX_HIDE_RELEASE_NOTES').returns(false); getBooleanStub.withArgs('SFDX_HIDE_RELEASE_NOTES_FOOTER').returns(false); @@ -168,7 +173,21 @@ describe('info:releasenotes:display', () => { it('logs logs a header with cli bin', async () => { await runDisplayCmd([]); - expect(uxLogStub.args[0][0]).to.contain("# Release notes for 'sfdx':"); + expect(uxLogStub.args[0][0]).to.contain("# Release notes for 'sfdx-cli':"); + }); + + it('logs logs a header with plugin', async () => { + await runDisplayCmd(['sfdx-plugin']); + + expect(uxLogStub.args[0][0]).to.contain("# Release notes for 'sfdx-plugin':"); + }); + + it('throws an error if plugin name is invalid', async () => { + try { + await shouldThrow(runDisplayCmd(['no-plugin'])); + } catch (err) { + expect((err as Error).message).to.contain("No plugin 'no-plugin' found"); + } }); it('calls getReleaseNotes with passed version', async () => { @@ -257,7 +276,7 @@ describe('info:releasenotes:display', () => { const json = await runDisplayCmd(['--json']); const expected = { - body: `# Release notes for 'sfdx':${os.EOL}## Release notes for 3.3.3`, + body: `# Release notes for 'sfdx-cli':${os.EOL}## Release notes for 3.3.3`, url: mockInfoConfig.releasenotes.releaseNotesPath, };