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:
@ -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>
|
||||
|
||||
41
packages/twenty-website/src/app/api/releases/route.tsx
Normal file
41
packages/twenty-website/src/app/api/releases/route.tsx
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -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,
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user