Skip to content

Commit

Permalink
feat(nx-dev): add changelog page
Browse files Browse the repository at this point in the history
  • Loading branch information
juristr committed May 11, 2023
1 parent f2e48e4 commit 06ef1a3
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 3 deletions.
19 changes: 19 additions & 0 deletions docs/changelog/16_0_0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Nx 16 is out 🔥

We’re proud to announce the release of Nx version 16!

{% youtube
src="https://www.youtube.com/embed/JIhOyJtuxEA"
title="Nx 16.0 Rejlease"
width="100%" /%}

Here are some of our feature highlights:

{% cards cols="2" %}
{% card title="Changing NPM Scope" type="video" url="https://youtu.be/HzkvhPKAepA" /%}
{% card title="Migrating React Query" type="video" url="https://youtu.be/X1I1Aw2sV-Y" /%}
{% card title="Angular LTS support" type="video" url="https://youtu.be/AQV4WFldwlY" /%}
{% card title="Cypress Feature Testing" type="video" url="https://youtu.be/d5i9_Y8Ip54" /%}
{% card title="Task Graph Improvements" type="video" url="https://youtu.be/9_Y6Mop-Kac" /%}
{% card title="IntelliJ Nx Console Feature Parity" type="video" url="https://youtu.be/XCoeNiyM6hw" /%}
{% /cards %}
39 changes: 39 additions & 0 deletions nx-dev/data-access-documents/src/lib/changelog.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';

export interface ChangelogRawContentEntry {
version: string;
content: string;
filePath: string;
}

export class ChangelogApi {
constructor(
private readonly options: {
id: string;
changelogRoot: string;
}
) {
if (!options.id) {
throw new Error('id cannot be undefined');
}
if (!options.changelogRoot) {
throw new Error('public docs root cannot be undefined');
}
}

getChangelogEntries(): ChangelogRawContentEntry[] {
const files = readdirSync(this.options.changelogRoot);

return files.map((file) => {
const filePath = join(this.options.changelogRoot, file);
const content = readFileSync(filePath, 'utf8');
const version = file.replace('.md', '').replace(/_/g, '.');
return {
content,
version,
filePath,
};
});
}
}
1 change: 1 addition & 0 deletions nx-dev/data-access-documents/src/node.index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './lib/documents.api';
export * from './lib/changelog.api';
export * from './lib/tags.api';
6 changes: 6 additions & 0 deletions nx-dev/nx-dev/lib/changelog.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ChangelogApi } from '@nx/nx-dev/data-access-documents/node-only';

export const changeLogApi = new ChangelogApi({
id: 'changelog',
changelogRoot: 'nx-dev/nx-dev/public/documentation/changelog',
});
273 changes: 273 additions & 0 deletions nx-dev/nx-dev/pages/changelog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
import { Footer, Header } from '@nx/nx-dev/ui-common';
import { Octokit } from 'octokit';
import { NextSeo } from 'next-seo';
import { useRouter } from 'next/router';
import { GithubRepository, renderMarkdown } from '@nx/nx-dev/ui-markdoc';
import { compare, parse } from 'semver';
import { changeLogApi } from '../lib/changelog.api';

interface ChangelogEntry {
version: string;
date: string | null;
content?: string;
patches?: string[];
// used for markdown rendering in the component
filePath?: string;
}

interface ChangeLogProps {
changelog: ChangelogEntry[];
}

function formatDate(dateStr: string) {
const date = new Date(dateStr);

const options: Intl.DateTimeFormatOptions = {
month: 'long',
day: 'numeric',
year: 'numeric',
};

const monthDayYear = date.toLocaleDateString('en-US', options);

const day = date.getDate();
const daySuffix = getDaySuffix(day);

return monthDayYear.replace(day.toString(), day + daySuffix);

// Function to get the day suffix (st, nd, rd, or th)
function getDaySuffix(day) {
if (day > 3 && day < 21) return 'th';
switch (day % 10) {
case 1:
return 'st';
case 2:
return 'nd';
case 3:
return 'rd';
default:
return 'th';
}
}
}

async function fetchGithubRelease(octokit: Octokit, page: number = 1) {
return await octokit.request('GET /repos/{owner}/{repo}/releases', {
owner: 'nrwl',
repo: 'nx',
headers: {
'X-GitHub-Api-Version': '2022-11-28',
},
page,
per_page: 100,
});
}

export async function getStaticProps(): Promise<{ props: ChangeLogProps }> {
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });

// do 2 fetches of 100 records, which should be enough releases to display
const githubReleases = [
...(await fetchGithubRelease(octokit, 1)).data,
...(await fetchGithubRelease(octokit, 2)).data,
];

const releasesByMinorVersion: {
[tag_name: string]: ChangelogEntry;
} = {};

// group all releases by minor and add patch versions in the "patches" array of the corresponding minor version entry
githubReleases
.filter((data) => data.prerelease === false)
.forEach((release) => {
const semverVersion = parse(release.tag_name);
if (!semverVersion) {
return;
}

if (semverVersion?.patch === 0) {
// this is a minor or major
releasesByMinorVersion[release.tag_name] = {
version: release.tag_name,
date: release.published_at
? formatDate(release.published_at)
: '(unknown)',
patches: releasesByMinorVersion[release.tag_name]?.patches || [],
};
} else {
// find corresponding minor version & add "release" to the patches array of it
const minorVersion = `${semverVersion.major}.${semverVersion.minor}.0`;
const minorRelease = releasesByMinorVersion[minorVersion];
if (minorRelease) {
minorRelease.patches = minorRelease.patches || [];
minorRelease.patches.push(release.tag_name);
} else {
// create a new entry for the minor version
releasesByMinorVersion[minorVersion] = {
version: minorVersion,
date: release.published_at
? formatDate(release.published_at)
: '(unknown)',
patches: [release.tag_name],
};
}
}
});

const groupedReleases = Object.values(releasesByMinorVersion);

// fetch potential manually written changelog content
const changelogContent = changeLogApi.getChangelogEntries();

for (const entry of groupedReleases) {
const hasEntry = changelogContent.find((c) => c.version === entry.version);
if (hasEntry) {
entry.content = hasEntry.content;
entry.filePath = hasEntry.filePath;
}
}

// sort it by version desc
groupedReleases.sort((a, b) => compare(b.version, a.version));

return {
props: {
changelog: groupedReleases,
},
};
}

export default function Changelog(props: ChangeLogProps): JSX.Element {
const router = useRouter();

const renderedChangelog = props.changelog.map((entry) => {
if (entry.content) {
const { node } = renderMarkdown(entry.content, {
filePath: entry.filePath ?? '',
});

return {
version: entry.version,
content: node,
date: entry.date,
};
}

return entry;
});

return (
<>
<NextSeo
title="Nx Changelog"
description="Changelog bla bla"
openGraph={{
url: 'https://nx.dev' + router.asPath,
title: 'Nx Changelog',
description: 'Learn about all the changes',
images: [
{
url: 'https://nx.dev/images/nx-media.jpg',
width: 800,
height: 421,
alt: 'Nx: Smart, Fast and Extensible Build System',
type: 'image/jpeg',
},
],
siteName: 'NxDev',
type: 'website',
}}
/>
<Header />
<main id="main" role="main">
<div className="mx-auto flex max-w-7xl flex-col space-y-12 py-12 px-4 sm:px-6 lg:space-y-0 lg:space-x-20 lg:py-16 lg:px-8">
<header className="space-y-10 md:py-12">
<div className="">
<h1 className="text-3xl font-semibold tracking-tight text-slate-900 dark:text-slate-100 sm:text-5xl">
Nx Changelog
</h1>
<p className="mt-4">
All the Nx goodies in one page, sorted by release
</p>
</div>
</header>
<div>
{renderedChangelog.map((changelog) => (
<div
key={changelog.version}
className="grid border-l pb-24 lg:grid-cols-12 lg:gap-8"
>
<div className="col-span-12 mb-8 self-start lg:sticky lg:top-0 lg:col-span-4 lg:-mt-32 lg:pt-32 ">
<div className="flex w-full items-baseline gap-6">
<div className="bg-slate-800 dark:bg-scale-500 border-scale-400 dark:border-scale-600 text-scale-900 -ml-2.5 flex h-5 w-5 items-center justify-center rounded border drop-shadow-sm">
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className="sbui-icon "
>
<circle cx="12" cy="12" r="4"></circle>
<line x1="1.05" y1="12" x2="7" y2="12"></line>
<line x1="17.01" y1="12" x2="22.96" y2="12"></line>
</svg>
</div>
<div className="flex w-full flex-col gap-1">
<h3 className="text-2xl font-bold">
v{changelog.version}
</h3>
<p>{changelog.date}</p>
</div>
</div>
</div>
<div className="col-span-8 ml-8 lg:ml-0">
<article className="prose prose-slate dark:prose-invert max-w-none">
{changelog.content}

<GithubRepository
title="GitHub release"
url={`https://github.com/nrwl/nx/releases/tag/${changelog.version}`}
/>

{changelog.patches?.length > 0 && (
<h2 className="text-white">Patches</h2>
)}
{changelog.patches?.length > 0 && (
<div className="space-y-4">
{changelog.patches?.map((version) => (
<a
href={`https://github.com/nrwl/nx/releases/tag/${version}`}
className="inline-flex items-center justify-center px-4 py-2 mr-2 space-x-2 font-medium text-white bg-gray-800 rounded-md hover:bg-gray-700"
key={version}
>
<svg
className="h-8 w-8 rounded-full object-cover"
viewBox="0 0 16 16"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
></path>
</svg>
<span>{version}</span>
</a>
))}
</div>
)}
</article>
</div>
</div>
))}
</div>
</div>
</main>
<Footer />
</>
);
}
3 changes: 3 additions & 0 deletions nx-dev/ui-markdoc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ import { tab, tabs } from './lib/tags/tabs.schema';
import { YouTube } from './lib/tags/youtube.components';
import { youtube } from './lib/tags/youtube.schema';

// TODO fix this export
export { GithubRepository } from './lib/tags/github-repository.component';

export const getMarkdocCustomConfig = (
documentFilePath: string
): { config: any; components: any } => ({
Expand Down
10 changes: 8 additions & 2 deletions nx-dev/ui-markdoc/src/lib/tags/github-repository.component.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { ChevronRightIcon } from '@heroicons/react/24/outline';

export function GithubRepository({ url }: { url: string }): JSX.Element {
export function GithubRepository({
url,
title,
}: {
url: string;
title?: string;
}): JSX.Element {
return (
<div className="not-prose group relative my-12 mx-auto flex w-full max-w-md items-center gap-3 overflow-hidden rounded-lg bg-slate-50 shadow-md transition hover:text-white dark:bg-slate-800/60">
<div className="absolute inset-0 z-0 w-2 bg-blue-500 transition-all duration-150 group-hover:w-full dark:bg-sky-500"></div>
Expand All @@ -20,7 +26,7 @@ export function GithubRepository({ url }: { url: string }): JSX.Element {

<div className="mx-3">
<p>
Example repository
{title ? title : 'Example repository'}
<a
href={url}
target="_blank"
Expand Down
2 changes: 1 addition & 1 deletion scripts/documentation/map-link-checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const basePath = 'docs';
const sharedFilesPattern = 'shared/cli';

const readmePathList: string[] = glob
.sync(`${basePath}/**/!(documents)/!(README).md`)
.sync(`${basePath}/**/!(documents|changelog)/!(README).md`)
.map((path: string) => path.split(basePath)[1])
.map((path: string) => path.slice(1, -3)) // Removing first `/` and `.md`
.filter((path: string) => !path.startsWith(sharedFilesPattern));
Expand Down

0 comments on commit 06ef1a3

Please sign in to comment.