From e6c8d40501de477ad09a106831925d138903d877 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 4 Sep 2025 16:32:10 +0200 Subject: [PATCH 1/4] ref: Log descriptive error message when no tags are found during release preparation --- src/commands/prepare.ts | 3 ++ src/logger.ts | 1 + src/utils/__tests__/git.test.ts | 54 +++++++++++++++++++++++++++++++++ src/utils/git.ts | 16 ++++++++-- 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/utils/__tests__/git.test.ts diff --git a/src/commands/prepare.ts b/src/commands/prepare.ts index 3a37358e..dcab08b6 100644 --- a/src/commands/prepare.ts +++ b/src/commands/prepare.ts @@ -498,6 +498,9 @@ export async function prepareMain(argv: PrepareOptions): Promise { // TL;DR - WARNING: // The order matters here, do not move this command above createReleaseBranch! const oldVersion = await getLatestTag(git); + if (!oldVersion) { + process.exit(1); + } // Check & update the changelog await prepareChangelog( diff --git a/src/logger.ts b/src/logger.ts index 1e01c7c0..ade14a93 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -34,6 +34,7 @@ class SentryBreadcrumbReporter extends BasicReporter { } export { LogLevel as LogLevel }; + const loggers: Consola[] = []; function createLogger(tag?: string) { const loggerInstance = consola.withDefaults({ tag }); diff --git a/src/utils/__tests__/git.test.ts b/src/utils/__tests__/git.test.ts new file mode 100644 index 00000000..b72b36b0 --- /dev/null +++ b/src/utils/__tests__/git.test.ts @@ -0,0 +1,54 @@ +import { getLatestTag } from '../git'; +import * as loggerModule from '../../logger'; + +describe('getLatestTag', () => { + loggerModule.setLevel(loggerModule.LogLevel.Debug); + const loggerErrorSpy = jest + .spyOn(loggerModule.logger, 'error') + .mockImplementation(() => { + // just to avoid spamming the test output + }); + + const loggerDebugSpy = jest + .spyOn(loggerModule.logger, 'debug') + .mockImplementation(() => { + // just to avoid spamming the test output + }); + + it('returns latest tag in the repo by calling `git describe`', async () => { + const git = { + raw: jest.fn().mockResolvedValue('1.0.0'), + } as any; + + const latestTag = await getLatestTag(git); + expect(latestTag).toBe('1.0.0'); + + expect(git.raw).toHaveBeenCalledWith('describe', '--tags', '--abbrev=0'); + }); + + it('returns `undefined` if the git call throws', async () => { + const git = { + raw: jest.fn().mockRejectedValue(new Error('Nothing to describe')), + } as any; + + const latestTag = await getLatestTag(git); + expect(latestTag).toBeUndefined(); + }); + + it('logs a helpful error message if the git call throws', async () => { + const error = new Error('Nothing to describe'); + const git = { + raw: jest.fn().mockRejectedValue(error), + } as any; + + const latestTag = await getLatestTag(git); + + expect(latestTag).toBeUndefined(); + expect(loggerErrorSpy).toHaveBeenCalledWith( + expect.stringContaining( + "If you're releasing for the first time, check if your repo contains any tags" + ) + ); + expect(loggerDebugSpy).toHaveBeenCalledWith(error); + }); +}); diff --git a/src/utils/git.ts b/src/utils/git.ts index cb5536c5..11adcfde 100644 --- a/src/utils/git.ts +++ b/src/utils/git.ts @@ -3,6 +3,7 @@ import simpleGit, { SimpleGit } from 'simple-git'; import { getConfigFileDir } from '../config'; import { ConfigurationError } from './errors'; import { logger } from '../logger'; +import { captureException } from '@sentry/node'; export interface GitChange { hash: string; @@ -33,9 +34,20 @@ export async function getDefaultBranch( ); } -export async function getLatestTag(git: SimpleGit): Promise { +export async function getLatestTag( + git: SimpleGit +): Promise { // This part is courtesy of https://stackoverflow.com/a/7261049/90297 - return (await git.raw('describe', '--tags', '--abbrev=0')).trim(); + try { + return (await git.raw('describe', '--tags', '--abbrev=0')).trim(); + } catch (e) { + captureException(e); + logger.error( + "Couldn't get the latest tag! If you're releasing for the first time, check if your repo contains any tags. If not, add one manually and try again." + ); + logger.debug(e); + return undefined; + } } export async function getChangesSince( From 892e61ef992f867919c1652e0897f4e9d15cfbb2 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 4 Sep 2025 16:39:18 +0200 Subject: [PATCH 2/4] rethrow to handle globally --- src/utils/__tests__/git.test.ts | 37 +++++++++++---------------------- src/utils/git.ts | 10 +++------ 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/utils/__tests__/git.test.ts b/src/utils/__tests__/git.test.ts index b72b36b0..c1fadfa5 100644 --- a/src/utils/__tests__/git.test.ts +++ b/src/utils/__tests__/git.test.ts @@ -2,19 +2,6 @@ import { getLatestTag } from '../git'; import * as loggerModule from '../../logger'; describe('getLatestTag', () => { - loggerModule.setLevel(loggerModule.LogLevel.Debug); - const loggerErrorSpy = jest - .spyOn(loggerModule.logger, 'error') - .mockImplementation(() => { - // just to avoid spamming the test output - }); - - const loggerDebugSpy = jest - .spyOn(loggerModule.logger, 'debug') - .mockImplementation(() => { - // just to avoid spamming the test output - }); - it('returns latest tag in the repo by calling `git describe`', async () => { const git = { raw: jest.fn().mockResolvedValue('1.0.0'), @@ -26,29 +13,29 @@ describe('getLatestTag', () => { expect(git.raw).toHaveBeenCalledWith('describe', '--tags', '--abbrev=0'); }); - it('returns `undefined` if the git call throws', async () => { - const git = { - raw: jest.fn().mockRejectedValue(new Error('Nothing to describe')), - } as any; - - const latestTag = await getLatestTag(git); - expect(latestTag).toBeUndefined(); - }); - it('logs a helpful error message if the git call throws', async () => { + loggerModule.setLevel(loggerModule.LogLevel.Debug); + const loggerErrorSpy = jest + .spyOn(loggerModule.logger, 'error') + .mockImplementation(() => { + // just to avoid spamming the test output + }); + const error = new Error('Nothing to describe'); const git = { raw: jest.fn().mockRejectedValue(error), } as any; - const latestTag = await getLatestTag(git); + try { + await getLatestTag(git); + } catch (e) { + expect(e).toBe(error); + } - expect(latestTag).toBeUndefined(); expect(loggerErrorSpy).toHaveBeenCalledWith( expect.stringContaining( "If you're releasing for the first time, check if your repo contains any tags" ) ); - expect(loggerDebugSpy).toHaveBeenCalledWith(error); }); }); diff --git a/src/utils/git.ts b/src/utils/git.ts index 11adcfde..4648a3f9 100644 --- a/src/utils/git.ts +++ b/src/utils/git.ts @@ -3,7 +3,6 @@ import simpleGit, { SimpleGit } from 'simple-git'; import { getConfigFileDir } from '../config'; import { ConfigurationError } from './errors'; import { logger } from '../logger'; -import { captureException } from '@sentry/node'; export interface GitChange { hash: string; @@ -34,19 +33,16 @@ export async function getDefaultBranch( ); } -export async function getLatestTag( - git: SimpleGit -): Promise { +export async function getLatestTag(git: SimpleGit): Promise { // This part is courtesy of https://stackoverflow.com/a/7261049/90297 try { return (await git.raw('describe', '--tags', '--abbrev=0')).trim(); } catch (e) { - captureException(e); logger.error( "Couldn't get the latest tag! If you're releasing for the first time, check if your repo contains any tags. If not, add one manually and try again." ); - logger.debug(e); - return undefined; + // handle this error in the global error handler + throw e; } } From ce862d413b1c9f9549d3d7e83a930e5474acf5a8 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 4 Sep 2025 17:01:15 +0200 Subject: [PATCH 3/4] cleanup --- src/commands/prepare.ts | 3 --- src/logger.ts | 1 - 2 files changed, 4 deletions(-) diff --git a/src/commands/prepare.ts b/src/commands/prepare.ts index dcab08b6..3a37358e 100644 --- a/src/commands/prepare.ts +++ b/src/commands/prepare.ts @@ -498,9 +498,6 @@ export async function prepareMain(argv: PrepareOptions): Promise { // TL;DR - WARNING: // The order matters here, do not move this command above createReleaseBranch! const oldVersion = await getLatestTag(git); - if (!oldVersion) { - process.exit(1); - } // Check & update the changelog await prepareChangelog( diff --git a/src/logger.ts b/src/logger.ts index ade14a93..1e01c7c0 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -34,7 +34,6 @@ class SentryBreadcrumbReporter extends BasicReporter { } export { LogLevel as LogLevel }; - const loggers: Consola[] = []; function createLogger(tag?: string) { const loggerInstance = consola.withDefaults({ tag }); From 57245bf8d884a29ec0802f5209762ebed94bb0a7 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 4 Sep 2025 17:22:03 +0200 Subject: [PATCH 4/4] add tag command to message --- src/utils/git.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/git.ts b/src/utils/git.ts index 4648a3f9..210c8f8f 100644 --- a/src/utils/git.ts +++ b/src/utils/git.ts @@ -39,7 +39,7 @@ export async function getLatestTag(git: SimpleGit): Promise { return (await git.raw('describe', '--tags', '--abbrev=0')).trim(); } catch (e) { logger.error( - "Couldn't get the latest tag! If you're releasing for the first time, check if your repo contains any tags. If not, add one manually and try again." + 'Couldn\'t get the latest tag! If you\'re releasing for the first time, check if your repo contains any tags. If not, add one manually and try again: `git tag 0.0.0 "$(git log -1 --reverse --format=%h)"' ); // handle this error in the global error handler throw e;