Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions apps/docsite/src/__tests__/changelog-linkify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
linkifyContributors,
linkifyComponents,
stripTitle,
fillEmptyReleases,
} from '../components/changelogLinkify';

describe('linkifyPRs', () => {
Expand Down Expand Up @@ -136,6 +137,69 @@ describe('stripTitle', () => {
});
});

describe('fillEmptyReleases', () => {
it('fills a version whose only body is the separator to the next heading', () => {
const md = [
'# 0.4.6',
'',
'---',
'',
'# 0.4.5',
'',
'#### Fixes',
'',
'- A real fix.',
].join('\n');
expect(fillEmptyReleases(md)).toBe(
[
'# 0.4.6',
'',
'No changes in this release.',
'',
'---',
'',
'# 0.4.5',
'',
'#### Fixes',
'',
'- A real fix.',
].join('\n'),
);
});

it('fills the last version in the file, which has no trailing separator', () => {
expect(fillEmptyReleases('# 0.4.2')).toBe(
'# 0.4.2\n\nNo changes in this release.\n\n',
);
});

it('leaves a version with real content untouched', () => {
const md = [
'# 0.4.3',
'',
'#### Fixes',
'',
'- A real fix.',
'',
'---',
'',
'# 0.4.2',
'',
'#### Fixes',
'',
'- Another real fix.',
].join('\n');
expect(fillEmptyReleases(md)).toBe(md);
});

it('does not mistake the stripped package title for an empty version', () => {
// fillEmptyReleases is documented to run after stripTitle, so the h1
// package-name line should already be gone by the time it sees this.
const md = ['# 0.4.6', '', '#### Fixes', '', '- A fix.'].join('\n');
expect(fillEmptyReleases(md)).toBe(md);
});
});

describe('full changelog pipeline', () => {
it('linkifies contributors without mangling package bullets or PRs', () => {
const md = [
Expand Down
51 changes: 44 additions & 7 deletions apps/docsite/src/components/ChangelogView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

'use client';

import {useState} from 'react';
import {Suspense} from 'react';
import {usePathname, useRouter, useSearchParams} from 'next/navigation';
import * as stylex from '@stylexjs/stylex';
import {Markdown} from '@astryxdesign/core/Markdown';
import {Text, Heading} from '@astryxdesign/core/Text';
Expand All @@ -17,6 +18,7 @@ import {
linkifyContributors,
linkifyComponents,
stripTitle,
fillEmptyReleases,
} from './changelogLinkify';

interface ChangelogEntry {
Expand All @@ -29,6 +31,10 @@ interface ChangelogViewProps {
componentNames: string[];
}

// The package most people land on this page to read about; picked over
// "whichever package happens to sort first" (@astryxdesign/cli).
const DEFAULT_PACKAGE = '@astryxdesign/core';

const styles = stylex.create({
section: {
marginInline: 'auto',
Expand All @@ -42,11 +48,32 @@ const styles = stylex.create({
},
});

export function ChangelogView({
changelogs,
componentNames,
}: ChangelogViewProps) {
const [activeTab, setActiveTab] = useState(changelogs[0]?.pkg ?? '');
function ChangelogViewInner({changelogs, componentNames}: ChangelogViewProps) {
const searchParams = useSearchParams();
const router = useRouter();
const pathname = usePathname();

const fallbackPackage =
changelogs.find(c => c.pkg === DEFAULT_PACKAGE)?.pkg ??
changelogs[0]?.pkg ??
'';
const requestedPackage = searchParams.get('package');
// Clamp to a package that actually has a changelog so a stale or
// hand-edited `?package=` never lands on a blank panel.
const activeTab =
requestedPackage != null && changelogs.some(c => c.pkg === requestedPackage)
? requestedPackage
: fallbackPackage;
const setActiveTab = (value: string) => {
const params = new URLSearchParams(searchParams.toString());
if (value === fallbackPackage) {
params.delete('package');
} else {
params.set('package', value);
}
const qs = params.toString();
router.replace(`${pathname}${qs ? `?${qs}` : ''}`, {scroll: false});
};
const active = changelogs.find(c => c.pkg === activeTab);

return (
Expand Down Expand Up @@ -77,7 +104,9 @@ export function ChangelogView({
{active != null && (
<Markdown headingLevelStart={2}>
{linkifyComponents(
linkifyContributors(linkifyPRs(stripTitle(active.content))),
linkifyContributors(
linkifyPRs(fillEmptyReleases(stripTitle(active.content))),
),
componentNames,
)}
</Markdown>
Expand All @@ -92,3 +121,11 @@ export function ChangelogView({
</Section>
);
}

export function ChangelogView(props: ChangelogViewProps) {
return (
<Suspense>
<ChangelogViewInner {...props} />
</Suspense>
);
}
30 changes: 30 additions & 0 deletions apps/docsite/src/components/changelogLinkify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,33 @@ export function linkifyComponents(
export function stripTitle(markdown: string): string {
return markdown.replace(/^#\s+.+\n+/, '');
}

const NO_CHANGES_TEXT = 'No changes in this release.';

/**
* A release with no notable changes still gets a version heading from the
* changeset tooling, with nothing between it and the next heading (or the
* `---` separator before it). Rendered as-is that reads as a broken page —
* a heading followed immediately by a divider or another heading, with no
* indication the release was simply empty. Fill each of those bodies with
* an explicit "no changes" line instead of leaving them bare.
*
* Call this after `stripTitle`, so the package-name h1 itself is not
* mistaken for an empty version section.
*/
export function fillEmptyReleases(markdown: string): string {
const parts = markdown.split(/(^#{1,2}\s+.+$)/gm);
for (let i = 2; i < parts.length; i += 2) {
const body = parts[i];
const separatorMatch = /^\s*---\s*$/m.exec(body);
const bodyWithoutSeparator = separatorMatch
? body.slice(0, separatorMatch.index) +
body.slice(separatorMatch.index + separatorMatch[0].length)
: body;
if (bodyWithoutSeparator.trim() === '') {
parts[i] =
'\n\n' + NO_CHANGES_TEXT + '\n\n' + (separatorMatch ? '---\n\n' : '');
}
}
return parts.join('');
}
Loading