Smart changelog (#5205)

Added a smart Changelog :

- Publish the Changelog before the app release. If the release has not
yet been pushed to production, do not display it.
- When the app release is done, make the Changelog available with the
correct date.
- If the Changelog writing is delayed because the release has already
been made, publish it immediately.
- Display everything locally to be able to iterate on the changelog and
have a preview

Added an endpoint for the Changelog

---------

Co-authored-by: Ady Beraud <a.beraud96@gmail.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Ady Beraud
2024-05-01 09:35:11 +03:00
committed by GitHub
parent bf7c4a5a89
commit df5cb9a904
17 changed files with 232 additions and 11 deletions

View File

@ -63,8 +63,10 @@ const gabarito = Gabarito({
export const Release = ({
release,
mdxReleaseContent,
githubPublishedAt,
}: {
release: ReleaseNote;
githubPublishedAt: string;
mdxReleaseContent: ReactElement<any, string | JSXElementConstructor<any>>;
}) => {
return (
@ -73,9 +75,9 @@ export const Release = ({
<StyledVersion>
<StyledRelease>{release.release}</StyledRelease>
<StyledDate>
{release.date.endsWith(new Date().getFullYear().toString())
? release.date.slice(0, -5)
: release.date}
{githubPublishedAt.endsWith(new Date().getFullYear().toString())
? githubPublishedAt.slice(0, -5)
: githubPublishedAt}
</StyledDate>
</StyledVersion>
<ArticleContent>{mdxReleaseContent}</ArticleContent>

View File

@ -0,0 +1,41 @@
import { desc } from 'drizzle-orm';
import { getGithubReleaseDateFromReleaseNote } from '@/app/releases/utils/get-github-release-date-from-release-note';
import { getReleases } from '@/app/releases/utils/get-releases';
import { getVisibleReleases } from '@/app/releases/utils/get-visible-releases';
import { findAll } from '@/database/database';
import { GithubReleases, githubReleasesModel } from '@/database/model';
export const dynamic = 'force-dynamic';
export async function GET() {
try {
const githubReleases = (await findAll(
githubReleasesModel,
desc(githubReleasesModel.publishedAt),
)) as GithubReleases[];
const latestGithubRelease = githubReleases[0];
const releaseNotes = await getReleases();
const visibleReleasesNotes = getVisibleReleases(
releaseNotes,
latestGithubRelease.tagName,
);
const formattedReleasesNotes = visibleReleasesNotes.map((releaseNote) => ({
...releaseNote,
publishedAt: getGithubReleaseDateFromReleaseNote(
githubReleases,
releaseNote.release,
releaseNote.date,
),
}));
return Response.json(formattedReleasesNotes);
} catch (error: any) {
return new Response(`Github releases error: ${error?.message}`, {
status: 500,
});
}
}

View File

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { getReleases } from '@/app/releases/get-releases';
import { getReleases } from '@/app/releases/utils/get-releases';
export interface ReleaseNote {
slug: string;

View File

@ -1,14 +1,20 @@
import React from 'react';
import { desc } from 'drizzle-orm';
import { Metadata } from 'next';
import { Line } from '@/app/_components/releases/Line';
import { Release } from '@/app/_components/releases/Release';
import { Title } from '@/app/_components/releases/StyledTitle';
import { ContentContainer } from '@/app/_components/ui/layout/ContentContainer';
import { getGithubReleaseDateFromReleaseNote } from '@/app/releases/utils/get-github-release-date-from-release-note';
import {
getMdxReleasesContent,
getReleases,
} from '@/app/releases/get-releases';
} from '@/app/releases/utils/get-releases';
import { getVisibleReleases } from '@/app/releases/utils/get-visible-releases';
import { findAll } from '@/database/database';
import { GithubReleases, githubReleasesModel } from '@/database/model';
import { pgGithubReleasesModel } from '@/database/schema-postgres';
export const metadata: Metadata = {
title: 'Twenty - Releases',
@ -19,20 +25,37 @@ export const metadata: Metadata = {
export const dynamic = 'force-dynamic';
const Home = async () => {
const releases = await getReleases();
const mdxReleasesContent = await getMdxReleasesContent(releases);
const githubReleases = (await findAll(
githubReleasesModel,
desc(pgGithubReleasesModel.publishedAt),
)) as GithubReleases[];
const latestGithubRelease = githubReleases[0];
const releaseNotes = await getReleases();
const visibleReleasesNotes = getVisibleReleases(
releaseNotes,
latestGithubRelease.tagName,
);
const mdxReleasesContent = await getMdxReleasesContent(releaseNotes);
return (
<ContentContainer>
<Title />
{releases.map((note, index) => (
{visibleReleasesNotes.map((note, index) => (
<React.Fragment key={note.slug}>
<Release
githubPublishedAt={getGithubReleaseDateFromReleaseNote(
githubReleases,
note.release,
note.date,
)}
release={note}
mdxReleaseContent={mdxReleasesContent[index]}
/>
{index != releases.length - 1 && <Line />}
{index != releaseNotes.length - 1 && <Line />}
</React.Fragment>
))}
</ContentContainer>

View File

@ -0,0 +1,19 @@
export const getFormattedReleaseNumber = (versionNumber: string) => {
const formattedVersion = versionNumber.replace('v', '');
const parts = formattedVersion.split('.').map(Number);
if (parts.length !== 3) {
throw new Error('Version must be in the format major.minor.patch');
}
// Assign weights. Adjust these based on your needs.
const majorWeight = 10000;
const minorWeight = 100;
const patchWeight = 1;
const numericVersion =
parts[0] * majorWeight + parts[1] * minorWeight + parts[2] * patchWeight;
return numericVersion;
};

View File

@ -0,0 +1,43 @@
import { GithubReleases } from '@/database/model';
function formatDate(dateString: string) {
const date = new Date(dateString);
const formatter = new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric',
});
return formatter.format(date) + getOrdinal(date.getDate());
}
function getOrdinal(day: number) {
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';
}
}
export const getGithubReleaseDateFromReleaseNote = (
githubReleases: GithubReleases[],
noteTagName: string,
noteDate: string,
) => {
const formattedNoteTagName = `v${noteTagName}`;
const date = githubReleases?.find?.(
(githubRelease) => githubRelease?.tagName === formattedNoteTagName,
)?.publishedAt;
if (date) {
return formatDate(date);
}
return noteDate;
};

View File

@ -0,0 +1,18 @@
import { ReleaseNote } from '@/app/releases/api/route';
import { getFormattedReleaseNumber } from '@/app/releases/utils/get-formatted-release-number';
export const getVisibleReleases = (
releaseNotes: ReleaseNote[],
publishedReleaseVersion: string,
) => {
if (process.env.NODE_ENV !== 'production') return releaseNotes;
const publishedVersionNumber = getFormattedReleaseNumber(
publishedReleaseVersion,
);
return releaseNotes.filter(
(releaseNote) =>
getFormattedReleaseNumber(releaseNote.release) <= publishedVersionNumber,
);
};