mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
fix(changelog): resolve embedded notes by semver core (#373)
Whats New and the post-update modal required an exact ## [version] header, so prerelease package versions (rc, dev) often had no matching section. Match on major.minor.patch first; if several sections share the core, prefer a plain X.Y.Z heading when present.
This commit is contained in:
@@ -5,6 +5,7 @@ import { Sparkles } from 'lucide-react';
|
||||
import { version } from '../../package.json';
|
||||
import changelogRaw from '../../CHANGELOG.md?raw';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { findChangelogReleaseEntry } from '../utils/changelogReleaseMatch';
|
||||
|
||||
function renderInline(text: string): React.ReactNode[] {
|
||||
const parts = text.split(/(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g);
|
||||
@@ -29,15 +30,7 @@ export default function ChangelogModal({ onClose }: Props) {
|
||||
const setShowChangelogOnUpdate = useAuthStore(s => s.setShowChangelogOnUpdate);
|
||||
const setLastSeenChangelogVersion = useAuthStore(s => s.setLastSeenChangelogVersion);
|
||||
|
||||
const currentVersionData = useMemo(() => {
|
||||
const blocks = changelogRaw.split(/\n(?=## \[)/).filter((b: string) => b.startsWith('## ['));
|
||||
const block = blocks.find((b: string) => b.startsWith(`## [${version}]`));
|
||||
if (!block) return null;
|
||||
const lines = block.split('\n');
|
||||
const match = lines[0].match(/## \[([^\]]+)\](?:\s*-\s*(.+))?/);
|
||||
const body = lines.slice(1).join('\n').trim();
|
||||
return { version: match?.[1] ?? version, date: match?.[2] ?? '', body };
|
||||
}, []);
|
||||
const currentVersionData = useMemo(() => findChangelogReleaseEntry(changelogRaw, version), []);
|
||||
|
||||
const handleClose = () => {
|
||||
if (dontShow) setShowChangelogOnUpdate(false);
|
||||
|
||||
+3
-10
@@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { version } from '../../package.json';
|
||||
import changelogRaw from '../../CHANGELOG.md?raw';
|
||||
import { findChangelogReleaseEntry } from '../utils/changelogReleaseMatch';
|
||||
import { renderChangelogBody } from '../utils/changelogMarkdown';
|
||||
|
||||
export default function WhatsNew() {
|
||||
@@ -15,15 +16,7 @@ export default function WhatsNew() {
|
||||
else navigate('/');
|
||||
};
|
||||
|
||||
const entry = useMemo(() => {
|
||||
const blocks = changelogRaw.split(/\n(?=## \[)/).filter((b: string) => b.startsWith('## ['));
|
||||
const block = blocks.find((b: string) => b.startsWith(`## [${version}]`));
|
||||
if (!block) return null;
|
||||
const lines = block.split('\n');
|
||||
const match = lines[0].match(/## \[([^\]]+)\](?:\s*-\s*(.+))?/);
|
||||
const body = lines.slice(1).join('\n').trim();
|
||||
return { version: match?.[1] ?? version, date: match?.[2] ?? '', body };
|
||||
}, []);
|
||||
const entry = useMemo(() => findChangelogReleaseEntry(changelogRaw, version), []);
|
||||
|
||||
return (
|
||||
<div className="whats-new">
|
||||
@@ -33,7 +26,7 @@ export default function WhatsNew() {
|
||||
<div>
|
||||
<h1 className="whats-new__title">{t('whatsNew.title')}</h1>
|
||||
<div className="whats-new__subtitle">
|
||||
v{entry?.version ?? version}
|
||||
v{version}
|
||||
{entry?.date && <span className="whats-new__date"> · {entry.date}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Match CHANGELOG ## [version] sections to the app version from package.json.
|
||||
* Exact match first; otherwise same semver major.minor.patch (ignores -rc, -dev, etc.).
|
||||
*/
|
||||
|
||||
const SEMVER_CORE = /^v?(\d+\.\d+\.\d+)/i;
|
||||
|
||||
export function changelogVersionCore(version: string): string | null {
|
||||
const m = version.trim().match(SEMVER_CORE);
|
||||
return m ? m[1] : null;
|
||||
}
|
||||
|
||||
function isPlainSemverTriple(header: string): boolean {
|
||||
return /^\d+\.\d+\.\d+$/.test(header.trim());
|
||||
}
|
||||
|
||||
export function splitChangelogBlocks(changelogRaw: string): string[] {
|
||||
return changelogRaw.split(/\n(?=## \[)/).filter((b: string) => b.startsWith('## ['));
|
||||
}
|
||||
|
||||
export interface ChangelogReleaseEntry {
|
||||
headerVersion: string;
|
||||
date: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
function parseBlock(block: string): ChangelogReleaseEntry | null {
|
||||
const lines = block.split('\n');
|
||||
const m = lines[0].match(/## \[([^\]]+)\](?:\s*-\s*(.+))?/);
|
||||
if (!m) return null;
|
||||
return {
|
||||
headerVersion: m[1],
|
||||
date: (m[2] ?? '').trim(),
|
||||
body: lines.slice(1).join('\n').trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function headerVersionFromBlock(block: string): string | null {
|
||||
const m = block.match(/^## \[([^\]]+)\]/);
|
||||
return m ? m[1] : null;
|
||||
}
|
||||
|
||||
export function findChangelogReleaseEntry(
|
||||
changelogRaw: string,
|
||||
appVersion: string,
|
||||
): ChangelogReleaseEntry | null {
|
||||
const blocks = splitChangelogBlocks(changelogRaw);
|
||||
|
||||
const exact = blocks.find((b: string) => b.startsWith(`## [${appVersion}]`));
|
||||
if (exact) return parseBlock(exact);
|
||||
|
||||
const appCore = changelogVersionCore(appVersion);
|
||||
if (!appCore) return null;
|
||||
|
||||
const candidates = blocks.filter((b: string) => {
|
||||
const hv = headerVersionFromBlock(b);
|
||||
if (!hv) return false;
|
||||
return changelogVersionCore(hv) === appCore;
|
||||
});
|
||||
if (candidates.length === 0) return null;
|
||||
|
||||
const plain = candidates.find((b: string) => {
|
||||
const hv = headerVersionFromBlock(b);
|
||||
return hv !== null && isPlainSemverTriple(hv);
|
||||
});
|
||||
const chosen = plain ?? candidates[0];
|
||||
return parseBlock(chosen);
|
||||
}
|
||||
Reference in New Issue
Block a user