Skip to content

Commit 3b803d0

Browse files
committed
feat: improve releasenotes command
1 parent 1e70035 commit 3b803d0

File tree

2 files changed

+95
-15
lines changed

2 files changed

+95
-15
lines changed

src/commands/cli/releasenotes.ts

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import { Octokit } from '@octokit/core';
1313
import { bold, cyan, dim } from 'chalk';
1414
import { Messages, SfdxError } from '@salesforce/core';
1515
import { exec } from 'shelljs';
16+
import * as semver from 'semver';
1617
import { CLI } from '../../types';
17-
import { NpmPackage, parseAliasedPackageName } from '../../package';
18+
import { NpmPackage, parseAliasedPackageName, parseAliasedPackageVersion } from '../../package';
1819

1920
Messages.importMessagesDirectory(__dirname);
2021
const messages = Messages.loadMessages('@salesforce/plugin-release-management', 'cli.releasenotes');
@@ -31,6 +32,18 @@ export type Change = {
3132

3233
export type ChangesByPlugin = Record<string, Change[]>;
3334

35+
type Differences = {
36+
removed: Record<string, string>;
37+
added: Record<string, string>;
38+
upgraded: Record<string, string>;
39+
downgraded: Record<string, string>;
40+
unchanged: Record<string, string>;
41+
};
42+
43+
function isNotEmpty(obj: Record<string, unknown>): boolean {
44+
return Object.keys(obj).length > 0;
45+
}
46+
3447
export default class ReleaseNotes extends SfdxCommand {
3548
public static readonly description = messages.getMessage('description');
3649
public static readonly examples = messages.getMessage('examples').split(os.EOL);
@@ -59,15 +72,52 @@ export default class ReleaseNotes extends SfdxCommand {
5972
this.octokit = new Octokit({ auth });
6073
const cli = ensure<CLI>(this.flags.cli);
6174
const fullName = cli === CLI.SF ? '@salesforce/cli' : 'sfdx-cli';
62-
const npmPackage = this.getNpmPackage(fullName, this.flags.since ?? 'latest-rc');
63-
const publishDate = npmPackage.time[npmPackage.version];
64-
const plugins = this.normalizePlugins(npmPackage);
75+
76+
const npmPackage = this.getNpmPackage(fullName, this.flags.since ?? 'latest');
77+
const latestrc = this.getNpmPackage(fullName, 'latest-rc');
78+
79+
const oldPlugins = this.normalizePlugins(npmPackage);
80+
const newPlugins = this.normalizePlugins(latestrc);
81+
82+
const differences = this.findDifferences(oldPlugins, newPlugins);
83+
84+
if (isNotEmpty(differences.upgraded)) {
85+
this.ux.styledHeader('Upgraded Plugins');
86+
for (const [plugin, version] of Object.entries(differences.upgraded)) {
87+
this.ux.log(`• ${plugin} ${oldPlugins[plugin]} => ${version}`);
88+
}
89+
}
90+
91+
if (isNotEmpty(differences.downgraded)) {
92+
this.ux.styledHeader('Downgraded Plugins');
93+
for (const [plugin, version] of Object.entries(differences.downgraded)) {
94+
this.ux.log(`• ${plugin} ${version} => ${oldPlugins[plugin]}`);
95+
}
96+
}
97+
98+
if (isNotEmpty(differences.added)) {
99+
this.ux.styledHeader('Added Plugins');
100+
for (const [plugin, version] of Object.entries(differences.added)) {
101+
this.ux.log(`• ${plugin} ${version}`);
102+
}
103+
}
104+
105+
if (isNotEmpty(differences.removed)) {
106+
this.ux.styledHeader('Removed Plugins');
107+
for (const [plugin, version] of Object.entries(differences.removed)) {
108+
this.ux.log(`• ${plugin} ${version}`);
109+
}
110+
}
111+
65112
const changesByPlugin: ChangesByPlugin = {};
66-
for (const plugin of plugins) {
113+
for (const [plugin] of Object.entries(differences.upgraded)) {
114+
const pkg = this.getNpmPackage(plugin, oldPlugins[plugin]);
115+
const publishDate = pkg.time[pkg.version];
67116
const changes = await this.getPullsForPlugin(plugin, publishDate);
68117
if (changes.length) changesByPlugin[plugin] = changes;
69118
}
70119

120+
this.ux.log();
71121
if (this.flags.markdown) {
72122
this.logChangesMarkdown(changesByPlugin);
73123
} else {
@@ -82,17 +132,41 @@ export default class ReleaseNotes extends SfdxCommand {
82132
return JSON.parse(result.stdout) as NpmPackage;
83133
}
84134

85-
private normalizePlugins(npmPackage: NpmPackage): string[] {
135+
private normalizePlugins(npmPackage: NpmPackage): Record<string, string> {
86136
const plugins = npmPackage.oclif?.plugins ?? [];
87-
const normalized = plugins
88-
.filter((p) => !p.startsWith('@oclif'))
89-
.map((p) => {
90-
if (npmPackage.dependencies[p].startsWith('npm:')) {
91-
return parseAliasedPackageName(npmPackage.dependencies[p]);
92-
}
93-
return p;
94-
});
95-
return [npmPackage.name, ...normalized];
137+
const normalized = { [npmPackage.name]: npmPackage.version };
138+
plugins.forEach((p) => {
139+
if (npmPackage.dependencies[p].startsWith('npm:')) {
140+
const name = parseAliasedPackageName(npmPackage.dependencies[p]);
141+
const version = parseAliasedPackageVersion(npmPackage.dependencies[p]);
142+
normalized[name] = version;
143+
} else {
144+
normalized[p] = npmPackage.dependencies[p];
145+
}
146+
});
147+
148+
return normalized;
149+
}
150+
151+
private findDifferences(oldPlugins: Record<string, string>, newPlugins: Record<string, string>): Differences {
152+
const removed = {};
153+
const added = {};
154+
const upgraded = {};
155+
const downgraded = {};
156+
const unchanged = {};
157+
158+
for (const [name, version] of Object.entries(oldPlugins)) {
159+
if (!newPlugins[name]) removed[name] = version;
160+
}
161+
162+
for (const [name, version] of Object.entries(newPlugins)) {
163+
if (!oldPlugins[name]) added[name] = version;
164+
else if (semver.gt(version, oldPlugins[name])) upgraded[name] = version;
165+
else if (semver.lt(version, oldPlugins[name])) downgraded[name] = version;
166+
else unchanged[name] = version;
167+
}
168+
169+
return { removed, added, upgraded, downgraded, unchanged };
96170
}
97171

98172
private async getNameOfUser(username: string): Promise<string> {
@@ -115,6 +189,7 @@ export default class ReleaseNotes extends SfdxCommand {
115189
owner,
116190
repo,
117191
state: 'closed',
192+
base: 'main',
118193
// eslint-disable-next-line camelcase
119194
per_page: 100,
120195
});

src/package.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ export function parseAliasedPackageName(alias: string): string {
6767
return alias.replace('npm:', '').replace(/@(\^|~)?[0-9]{1,3}(?:.[0-9]{1,3})?(?:.[0-9]{1,3})?(.*?)$/, '');
6868
}
6969

70+
export function parseAliasedPackageVersion(alias: string): string {
71+
const regex = /@(\^|~)?[0-9]{1,3}(?:.[0-9]{1,3})?(?:.[0-9]{1,3})?(.*?)$/;
72+
return regex.exec(alias.replace('npm:', ''))[0].replace('@', '');
73+
}
74+
7075
export class Package extends AsyncOptionalCreatable {
7176
public name: string;
7277
public npmPackage: NpmPackage;

0 commit comments

Comments
 (0)