From bd5cf2a53481dd03f90d9f43e131e00684d24925 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Sat, 5 Oct 2024 07:02:27 -0400 Subject: [PATCH] Ability to skip changelog creation when there are no changes. --- src/application/Apexdocs.ts | 1 - src/application/generators/changelog.ts | 17 ++++-- src/cli/commands/changelog.ts | 5 ++ .../__test__/generating-change-log.spec.ts | 54 ++++++++++++------- src/core/changelog/generate-change-log.ts | 20 ++++--- src/core/changelog/process-changelog.ts | 6 +++ src/core/shared/types.d.ts | 1 + src/defaults.ts | 1 + 8 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/application/Apexdocs.ts b/src/application/Apexdocs.ts index 5edd5542..7ee33925 100644 --- a/src/application/Apexdocs.ts +++ b/src/application/Apexdocs.ts @@ -76,7 +76,6 @@ async function processChangeLog(config: UserDefinedChangelogConfig) { return pipe( TE.tryCatch(loadFiles, (e) => new FileReadingError('An error occurred while reading files.', e)), TE.flatMap(([previous, current]) => changelog(previous, current, config)), - TE.map(() => '✔️ Changelog generated successfully!'), TE.mapLeft(toErrors), ); } diff --git a/src/application/generators/changelog.ts b/src/application/generators/changelog.ts index 0a245407..8556deb2 100644 --- a/src/application/generators/changelog.ts +++ b/src/application/generators/changelog.ts @@ -1,25 +1,32 @@ import { pipe } from 'fp-ts/function'; -import { PageData, UnparsedSourceFile, UserDefinedChangelogConfig } from '../../core/shared/types'; +import { PageData, Skip, UnparsedSourceFile, UserDefinedChangelogConfig } from '../../core/shared/types'; import * as TE from 'fp-ts/TaskEither'; import { writeFiles } from '../file-writer'; import { ChangeLogPageData, generateChangeLog } from '../../core/changelog/generate-change-log'; import { FileWritingError } from '../errors'; +import { isSkip } from '../../core/shared/utils'; export default function generate( oldBundles: UnparsedSourceFile[], newBundles: UnparsedSourceFile[], config: UserDefinedChangelogConfig, ) { - return pipe( - generateChangeLog(oldBundles, newBundles, config), - TE.flatMap((files) => writeFilesToSystem(files, config.targetDir)), - ); + function handleFile(file: ChangeLogPageData | Skip) { + if (isSkip(file)) { + return TE.right('✔️ Done! Skipped writing files to the system.'); + } + + return writeFilesToSystem(file, config.targetDir); + } + + return pipe(generateChangeLog(oldBundles, newBundles, config), TE.flatMap(handleFile)); } function writeFilesToSystem(pageData: ChangeLogPageData, outputDir: string) { return pipe( [pageData], (files) => writeFiles(files as PageData[], outputDir), + TE.map(() => '✔️ Changelog generated successfully!'), TE.mapLeft((error) => { return new FileWritingError('An error occurred while writing files to the system.', error); }), diff --git a/src/cli/commands/changelog.ts b/src/cli/commands/changelog.ts index 8c36cb0b..58dcd507 100644 --- a/src/cli/commands/changelog.ts +++ b/src/cli/commands/changelog.ts @@ -35,4 +35,9 @@ export const changeLogOptions: { [key: string]: Options } = { 'Values should be separated by a space, e.g --scope global public namespaceaccessible. ' + 'Annotations are supported and should be passed lowercased and without the @ symbol, e.g. namespaceaccessible auraenabled.', }, + skipIfNoChanges: { + type: 'boolean', + default: changeLogDefaults.skipIfNoChanges, + describe: 'Skip the changelog generation if there are no changes between the previous and current version.', + }, }; diff --git a/src/core/changelog/__test__/generating-change-log.spec.ts b/src/core/changelog/__test__/generating-change-log.spec.ts index 83212b37..55c9bf33 100644 --- a/src/core/changelog/__test__/generating-change-log.spec.ts +++ b/src/core/changelog/__test__/generating-change-log.spec.ts @@ -1,6 +1,7 @@ import { UnparsedSourceFile } from '../../shared/types'; -import { generateChangeLog } from '../generate-change-log'; +import { ChangeLogPageData, generateChangeLog } from '../generate-change-log'; import { assertEither } from '../../test-helpers/assert-either'; +import { isSkip } from '../../shared/utils'; const config = { fileName: 'changelog', @@ -9,13 +10,26 @@ const config = { currentVersionDir: '', previousVersionDir: '', exclude: [], + skipIfNoChanges: false, }; describe('when generating a changelog', () => { + it('should not skip when skipIfNoChanges, even if there are no changes', async () => { + const result = await generateChangeLog([], [], { ...config })(); + + assertEither(result, (data) => expect(isSkip(data)).toBe(false)); + }); + + it('should skip when there are no changes', async () => { + const result = await generateChangeLog([], [], { ...config, skipIfNoChanges: true })(); + + assertEither(result, (data) => expect(isSkip(data)).toBe(true)); + }); + it('should return a file path', async () => { const result = await generateChangeLog([], [], config)(); - assertEither(result, (data) => expect(data.outputDocPath).toContain('changelog.md')); + assertEither(result, (data) => expect((data as ChangeLogPageData).outputDocPath).toContain('changelog.md')); }); describe('that does not include new classes', () => { @@ -25,7 +39,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).not.toContain('## New Classes')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).not.toContain('## New Classes')); }); }); @@ -40,7 +54,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('## New Classes')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('## New Classes')); }); it('should include the new class name', async () => { @@ -53,7 +67,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('### Test')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('### Test')); }); it('should include the new class description', async () => { @@ -71,7 +85,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('This is a test class.')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('This is a test class.')); }); }); @@ -86,7 +100,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('## New Interfaces')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('## New Interfaces')); }); it('should include the new interface name', async () => { @@ -99,7 +113,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('### Test')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('### Test')); }); it('should include the new interface description', async () => { @@ -117,7 +131,9 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('This is a test interface.')); + assertEither(result, (data) => + expect((data as ChangeLogPageData).content).toContain('This is a test interface.'), + ); }); }); @@ -130,7 +146,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('## New Enums')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('## New Enums')); }); it('should include the new enum name', async () => { @@ -141,7 +157,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('### Test')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('### Test')); }); it('should include the new enum description', async () => { @@ -157,7 +173,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('This is a test enum.')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('This is a test enum.')); }); }); @@ -172,7 +188,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, { ...config, scope: ['global'] })(); - assertEither(result, (data) => expect(data.content).not.toContain('## New Classes')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).not.toContain('## New Classes')); }); }); @@ -187,7 +203,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('## Removed Types')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('## Removed Types')); }); it('should include the removed type name', async () => { @@ -200,7 +216,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('- Test')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('- Test')); }); }); @@ -219,7 +235,9 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('## New or Modified Members in Existing Types')); + assertEither(result, (data) => + expect((data as ChangeLogPageData).content).toContain('## New or Modified Members in Existing Types'), + ); }); it('should include the new or modified type name', async () => { @@ -236,7 +254,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('### Test')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('### Test')); }); it('should include the new or modified member name', async () => { @@ -253,7 +271,7 @@ describe('when generating a changelog', () => { const result = await generateChangeLog(oldBundle, newBundle, config)(); - assertEither(result, (data) => expect(data.content).toContain('myMethod')); + assertEither(result, (data) => expect((data as ChangeLogPageData).content).toContain('myMethod')); }); }); }); diff --git a/src/core/changelog/generate-change-log.ts b/src/core/changelog/generate-change-log.ts index ec936b86..fbce605a 100644 --- a/src/core/changelog/generate-change-log.ts +++ b/src/core/changelog/generate-change-log.ts @@ -1,14 +1,15 @@ -import { ParsedFile, UnparsedSourceFile, UserDefinedChangelogConfig } from '../shared/types'; +import { ParsedFile, Skip, UnparsedSourceFile, UserDefinedChangelogConfig } from '../shared/types'; import { pipe } from 'fp-ts/function'; import * as TE from 'fp-ts/TaskEither'; import { reflectBundles } from '../reflection/reflect-source'; -import { processChangelog, VersionManifest } from './process-changelog'; +import { Changelog, hasChanges, processChangelog, VersionManifest } from './process-changelog'; import { convertToRenderableChangelog, RenderableChangelog } from './renderable-changelog'; import { CompilationRequest, Template } from '../template'; import { changelogTemplate } from './templates/changelog-template'; import { ReflectionErrors } from '../errors/errors'; import { apply } from '#utils/fp'; import { filterScope } from '../reflection/filter-scope'; +import { skip } from '../../index'; export type ChangeLogPageData = { content: string; @@ -19,7 +20,7 @@ export function generateChangeLog( oldBundles: UnparsedSourceFile[], newBundles: UnparsedSourceFile[], config: Omit, -): TE.TaskEither { +): TE.TaskEither { const filterOutOfScope = apply(filterScope, config.scope); function reflect(sourceFiles: UnparsedSourceFile[]) { @@ -28,18 +29,23 @@ export function generateChangeLog( const convertToPageData = apply(toPageData, config.fileName); + function handleConversion({ changelog, newManifest }: { changelog: Changelog; newManifest: VersionManifest }) { + if (config.skipIfNoChanges && !hasChanges(changelog)) { + return skip(); + } + return pipe(convertToRenderableChangelog(changelog, newManifest.types), compile, convertToPageData); + } + return pipe( reflect(oldBundles), TE.bindTo('oldVersion'), TE.bind('newVersion', () => reflect(newBundles)), TE.map(toManifests), TE.map(({ oldManifest, newManifest }) => ({ - changeLog: processChangelog(oldManifest, newManifest), + changelog: processChangelog(oldManifest, newManifest), newManifest, })), - TE.map(({ changeLog, newManifest }) => convertToRenderableChangelog(changeLog, newManifest.types)), - TE.map(compile), - TE.map(convertToPageData), + TE.map(handleConversion), ); } diff --git a/src/core/changelog/process-changelog.ts b/src/core/changelog/process-changelog.ts index d1e339c9..6ae1ecdc 100644 --- a/src/core/changelog/process-changelog.ts +++ b/src/core/changelog/process-changelog.ts @@ -34,6 +34,12 @@ export type Changelog = { newOrModifiedMembers: NewOrModifiedMember[]; }; +export function hasChanges(changelog: Changelog): boolean { + return ( + changelog.newTypes.length > 0 || changelog.removedTypes.length > 0 || changelog.newOrModifiedMembers.length > 0 + ); +} + export function processChangelog(oldVersion: VersionManifest, newVersion: VersionManifest): Changelog { return { newTypes: getNewTypes(oldVersion, newVersion), diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index a34d3728..74d31854 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -48,6 +48,7 @@ export type UserDefinedChangelogConfig = { fileName: string; scope: string[]; exclude: string[]; + skipIfNoChanges: boolean; }; export type UserDefinedConfig = UserDefinedMarkdownConfig | UserDefinedOpenApiConfig | UserDefinedChangelogConfig; diff --git a/src/defaults.ts b/src/defaults.ts index f1931087..0182cb1f 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -27,4 +27,5 @@ export const changeLogDefaults = { fileName: 'changelog', scope: ['global'], exclude: [], + skipIfNoChanges: true, };