diff --git a/src/commands/release-notes/release-notes.ts b/src/commands/release-notes/release-notes.ts index dfd7aec..ccab88b 100644 --- a/src/commands/release-notes/release-notes.ts +++ b/src/commands/release-notes/release-notes.ts @@ -3,8 +3,10 @@ import { CHANGELOG_URL, fetchAndStoreChangelog, getAllReleaseNotes, + getRecentReleaseNoteGroups, getStoredChangelog, } from '../../utils/releaseNotes.js' +import { getGlobalConfig } from '../../utils/config.js' function formatReleaseNotes(notes: Array<[string, string[]]>): string { return notes @@ -18,7 +20,7 @@ function formatReleaseNotes(notes: Array<[string, string[]]>): string { export async function call(): Promise { // Try to fetch the latest changelog with a 500ms timeout - let freshNotes: Array<[string, string[]]> = [] + let changelog = '' try { const timeoutPromise = new Promise((_, reject) => { @@ -26,20 +28,28 @@ export async function call(): Promise { }) await Promise.race([fetchAndStoreChangelog(), timeoutPromise]) - freshNotes = getAllReleaseNotes(await getStoredChangelog()) + changelog = await getStoredChangelog() } catch { // Either fetch failed or timed out - just use cached notes } - // If we have fresh notes from the quick fetch, use those - if (freshNotes.length > 0) { - return { type: 'text', value: formatReleaseNotes(freshNotes) } + if (!changelog) { + changelog = await getStoredChangelog() } - // Otherwise check cached notes - const cachedNotes = getAllReleaseNotes(await getStoredChangelog()) - if (cachedNotes.length > 0) { - return { type: 'text', value: formatReleaseNotes(cachedNotes) } + const recentNotes = getRecentReleaseNoteGroups( + MACRO.VERSION, + getGlobalConfig().lastReleaseNotesSeen, + changelog, + 3, + ) + if (recentNotes.length > 0) { + return { type: 'text', value: formatReleaseNotes(recentNotes) } + } + + const latestNotes = getAllReleaseNotes(changelog).slice(-3).reverse() + if (latestNotes.length > 0) { + return { type: 'text', value: formatReleaseNotes(latestNotes) } } // Nothing available, show link diff --git a/src/components/LogoV2/LogoV2.tsx b/src/components/LogoV2/LogoV2.tsx index 23ad356..bc54168 100644 --- a/src/components/LogoV2/LogoV2.tsx +++ b/src/components/LogoV2/LogoV2.tsx @@ -74,7 +74,7 @@ export function LogoV2() { const config = getGlobalConfig(); let changelog; try { - changelog = getRecentReleaseNotesSync(3); + changelog = getRecentReleaseNotesSync(3, MACRO.VERSION, config.lastReleaseNotesSeen); } catch { changelog = []; } diff --git a/src/utils/logoV2Utils.ts b/src/utils/logoV2Utils.ts index f7f9720..63f4b6c 100644 --- a/src/utils/logoV2Utils.ts +++ b/src/utils/logoV2Utils.ts @@ -9,7 +9,10 @@ import { truncateToWidth, truncateToWidthNoEllipsis, } from './format.js' -import { getStoredChangelogFromMemory, parseChangelog } from './releaseNotes.js' +import { + getRecentReleaseNotes, + getStoredChangelogFromMemory, +} from './releaseNotes.js' import { gt } from './semver.js' import { loadMessageLogs } from './sessionStorage.js' import { getInitialSettings } from './settings/settings.js' @@ -309,7 +312,11 @@ export function formatModelAndBilling( * For ants, uses commits bundled at build time * For external users, uses public changelog */ -export function getRecentReleaseNotesSync(maxItems: number): string[] { +export function getRecentReleaseNotesSync( + maxItems: number, + currentVersion: string = MACRO.VERSION, + lastSeenVersion?: string | null, +): string[] { // For ants, use bundled changelog if (process.env.USER_TYPE === 'ant') { const changelog = MACRO.VERSION_CHANGELOG @@ -325,26 +332,8 @@ export function getRecentReleaseNotesSync(maxItems: number): string[] { return [] } - let parsed - try { - parsed = parseChangelog(changelog) - } catch { - return [] - } - - // Get notes from recent versions - const allNotes: string[] = [] - const versions = Object.keys(parsed) - .sort((a, b) => (gt(a, b) ? -1 : 1)) - .slice(0, 3) // Look at top 3 recent versions - - for (const version of versions) { - const notes = parsed[version] - if (notes) { - allNotes.push(...notes) - } - } - - // Return raw notes without filtering or premature truncation - return allNotes.slice(0, maxItems) + return getRecentReleaseNotes(currentVersion, lastSeenVersion, changelog).slice( + 0, + maxItems, + ) } diff --git a/src/utils/releaseNotes.ts b/src/utils/releaseNotes.ts index 3f0448e..83f9cc4 100644 --- a/src/utils/releaseNotes.ts +++ b/src/utils/releaseNotes.ts @@ -210,28 +210,14 @@ export function getRecentReleaseNotes( changelogContent: string = getStoredChangelogFromMemory(), ): string[] { try { - const releaseNotes = parseChangelog(changelogContent) - - // Strip SHA from both versions to compare only the base versions - const baseCurrentVersion = coerce(currentVersion) - const basePreviousVersion = previousVersion ? coerce(previousVersion) : null - - if ( - !basePreviousVersion || - (baseCurrentVersion && - gt(baseCurrentVersion.version, basePreviousVersion.version)) - ) { - // Get all versions that are newer than the last seen version - return Object.entries(releaseNotes) - .filter( - ([version]) => - !basePreviousVersion || gt(version, basePreviousVersion.version), - ) - .sort(([versionA], [versionB]) => (gt(versionA, versionB) ? -1 : 1)) // Sort newest first - .flatMap(([_, notes]) => notes) - .filter(Boolean) - .slice(0, MAX_RELEASE_NOTES_SHOWN) - } + return getRecentReleaseNoteGroups( + currentVersion, + previousVersion, + changelogContent, + ) + .flatMap(([, notes]) => notes) + .filter(Boolean) + .slice(0, MAX_RELEASE_NOTES_SHOWN) } catch (error) { logError(toError(error)) return [] @@ -239,6 +225,45 @@ export function getRecentReleaseNotes( return [] } +export function getRecentReleaseNoteGroups( + currentVersion: string, + previousVersion: string | null | undefined, + changelogContent: string = getStoredChangelogFromMemory(), + maxVersions: number = Number.POSITIVE_INFINITY, +): Array<[string, string[]]> { + try { + const releaseNotes = parseChangelog(changelogContent) + + // Strip SHA/build metadata from both versions to compare only the base semver. + const baseCurrentVersion = coerce(currentVersion) + const basePreviousVersion = previousVersion ? coerce(previousVersion) : null + + if (!baseCurrentVersion) { + return [] + } + + if ( + basePreviousVersion && + !gt(baseCurrentVersion.version, basePreviousVersion.version) + ) { + return [] + } + + return Object.entries(releaseNotes) + .filter( + ([version]) => + !basePreviousVersion || gt(version, basePreviousVersion.version), + ) + .sort(([versionA], [versionB]) => (gt(versionA, versionB) ? -1 : 1)) + .slice(0, maxVersions) + .map(([version, notes]) => [version, notes.filter(Boolean)] as [string, string[]]) + .filter(([, notes]) => notes.length > 0) + } catch (error) { + logError(toError(error)) + return [] + } +} + /** * Gets all release notes as an array of [version, notes] arrays. * Versions are sorted with oldest first.