Skip to content

Commit 514e424

Browse files
committed
feat: introduce changelogs
1 parent 68d4ba8 commit 514e424

File tree

10 files changed

+308
-8
lines changed

10 files changed

+308
-8
lines changed

app/changelog/[...slug]/page.tsx

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Metadata, ResolvingMetadata } from "next";
2+
import { notFound } from "next/navigation";
3+
import * as React from "react";
4+
5+
import { getChangelogEntryBySlug, getChangelogs } from "@/lib/changelog-api";
6+
7+
import Changelogs from "../changelogs";
8+
9+
type Props = {
10+
params: { slug: string[] };
11+
};
12+
13+
export async function generateStaticParams() {
14+
const changelogs = await getChangelogs();
15+
return changelogs
16+
.filter((changelog) => changelog.slug !== "")
17+
.map((changelog) => ({
18+
slug: changelog.slug.split("/"),
19+
}));
20+
}
21+
22+
async function getChangelogFromParams(params: Props["params"]) {
23+
const slug = params.slug.join("/");
24+
return getChangelogEntryBySlug(slug);
25+
}
26+
27+
export async function generateMetadata({ params }: Props): Promise<Metadata> {
28+
const changelog = await getChangelogFromParams(params);
29+
if (!changelog) {
30+
notFound();
31+
}
32+
33+
const url = `https://argos-ci.com/changelog/${changelog.slug}`;
34+
const title = `${changelog.title} — Changelog`;
35+
return {
36+
title: {
37+
absolute: title,
38+
},
39+
description: changelog.description,
40+
alternates: {
41+
canonical: url,
42+
},
43+
openGraph: {
44+
title,
45+
description: changelog.description,
46+
type: "article",
47+
publishedTime: changelog.date,
48+
modifiedTime: changelog.date,
49+
url,
50+
siteName: "Argos",
51+
locale: "en_US",
52+
},
53+
twitter: {
54+
title,
55+
description: changelog.description,
56+
card: "summary_large_image",
57+
site: "@argos_ci",
58+
},
59+
};
60+
}
61+
62+
const dateFormatter = new Intl.DateTimeFormat("en-US", {
63+
dateStyle: "long",
64+
});
65+
66+
export default async function Page({ params }: Props) {
67+
const changelog = await getChangelogFromParams(params);
68+
if (!changelog) {
69+
notFound();
70+
}
71+
return <Changelogs changelogs={[changelog]} single />;
72+
}

app/changelog/changelogs.tsx

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import NextLink from "next/link";
2+
3+
import { Container } from "@/components/Container";
4+
import { Link } from "@/components/Link";
5+
import { ChangelogEntry } from "@/lib/changelog-api";
6+
7+
const dateFormatter = new Intl.DateTimeFormat("en-US", {
8+
dateStyle: "long",
9+
});
10+
11+
export default function Changelogs(props: {
12+
changelogs: ChangelogEntry[];
13+
single?: boolean;
14+
}) {
15+
return (
16+
<Container className="my-10" style={{ contain: "none" }}>
17+
<div className="sm:ml-[calc(25%+1rem)]">
18+
<h1 className="mb-2 text-4xl font-semibold">Changelog</h1>
19+
<div className="mb-2 text-low">
20+
New updates and improvements to Argos.
21+
</div>
22+
<div className="text-sm">
23+
<Link
24+
href="https://x.com/argos_ci"
25+
target="_blank"
26+
rel="noopener noreferrer"
27+
>
28+
Follow us on X.com
29+
</Link>
30+
</div>
31+
</div>
32+
<hr className="my-8 border-0 border-b" />
33+
{props.changelogs.map((changelog) => {
34+
return (
35+
<article
36+
key={changelog.slug}
37+
className="grid items-start gap-4 sm:grid-cols-[minmax(0,25%)_minmax(0,36rem)]"
38+
style={{ contain: "none" }}
39+
>
40+
<div className="text-sm text-low sm:sticky sm:top-[60px] sm:pt-10">
41+
{props.single && (
42+
<div className="mb-4">
43+
<NextLink
44+
href="/changelog"
45+
className="font-semibold hover:text"
46+
>
47+
← All posts
48+
</NextLink>
49+
</div>
50+
)}
51+
<time dateTime={changelog.date}>
52+
{dateFormatter.format(new Date(changelog.date))}
53+
</time>
54+
</div>
55+
<div className="prose dark:prose-invert prose-h2:mt-4 sm:prose-h2:mt-8">
56+
<header>
57+
<NextLink
58+
href={`/changelog/${changelog.slug}`}
59+
className="no-underline hover:underline"
60+
>
61+
<h2>{changelog.title}</h2>
62+
</NextLink>
63+
</header>
64+
{changelog.source}
65+
</div>
66+
</article>
67+
);
68+
})}
69+
</Container>
70+
);
71+
}

app/changelog/page.tsx

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Metadata } from "next";
2+
3+
import { Container } from "@/components/Container";
4+
import { Link } from "@/components/Link";
5+
import { getChangelogs } from "@/lib/changelog-api";
6+
import { getMetadata } from "@/lib/metadata";
7+
8+
import Changelogs from "./changelogs";
9+
10+
export const metadata: Metadata = getMetadata({
11+
title: "Changelog",
12+
description: "New updates and improvements to Argos.",
13+
pathname: "/changelog",
14+
});
15+
16+
export default async function Page() {
17+
const changelogs = await getChangelogs();
18+
return <Changelogs changelogs={changelogs} />;
19+
}

app/changelog/sitemap.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { MetadataRoute } from "next";
2+
3+
import { getChangelogs } from "@/lib/changelog-api";
4+
5+
const BASE_URL = "https://argos-ci.com";
6+
7+
export default async function sitemap({
8+
id,
9+
}: {
10+
id: number;
11+
}): Promise<MetadataRoute.Sitemap> {
12+
const changelogs = await getChangelogs();
13+
return changelogs.map((changelog) => ({
14+
url: `${BASE_URL}/changelogs/${changelog.slug}`,
15+
lastModified: changelog.date,
16+
}));
17+
}

app/navbar.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const AppNavbar: React.FC = () => {
2020
Docs
2121
</NavbarLink>
2222
<NavbarLink href="/pricing">Pricing</NavbarLink>
23+
<NavbarLink href="/changelog">Changelog</NavbarLink>
2324
<NavbarLink href="/blog">Blog</NavbarLink>
2425
</>
2526
}

app/sitemap.xml

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
33
<sitemap><loc>https://argos-ci.com/main-sitemap.xml</loc></sitemap>
44
<sitemap><loc>https://argos-ci.com/blog/sitemap.xml</loc></sitemap>
5+
<sitemap><loc>https://argos-ci.com/changelog/sitemap.xml</loc></sitemap>
56
<sitemap><loc>https://argos-ci.com/docs/sitemap.xml</loc></sitemap>
67
</sitemapindex>
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
title: New changes trigger
3+
description: Added and removed screenshots are now considered as diff in Argos build.
4+
slug: new-changes-trigger
5+
date: 2024-02-25
6+
---
7+
8+
<img
9+
className="rounded-lg"
10+
src="/assets/changelogs/new-changes-trigger/added-removed.png"
11+
alt="Added and removed screenshot from Argos in GitHub commit status"
12+
/>
13+
14+
We've listened to your input on how changes to screenshots within Argos should impact your workflow. With our latest update, Argos now treats the addition and removal of screenshots as diffs. This change ensures that any modifications to your visual assets are highlighted and require user approval, enhancing your control over visual consistency across your projects.
15+
16+
- Added and removed screenshots in your repositories will now trigger an error status on GitHub and GitLab, drawing attention to visual changes.
17+
- These diffs will require manual approval from a user, ensuring that all visual changes are intentional and reviewed.
18+
- This update allows teams to maintain a high standard of visual quality and consistency, as any alterations to screenshots are explicitly flagged and reviewed.
19+
20+
This change is part of our ongoing commitment to providing powerful tools for visual testing, ensuring that Argos remains at the forefront of visual regression testing technologies.

lib/changelog-api.tsx

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import fg from "fast-glob";
2+
import * as matter from "gray-matter";
3+
import { compileMDX } from "next-mdx-remote/rsc";
4+
import { readFile } from "node:fs/promises";
5+
import * as React from "react";
6+
import rehypeHighlight from "rehype-highlight";
7+
import remarkFrontmatter from "remark-frontmatter";
8+
import remarkGfm from "remark-gfm";
9+
import { z } from "zod";
10+
11+
const FrontmatterSchema = z.object({
12+
title: z.string(),
13+
description: z.string(),
14+
slug: z.string(),
15+
date: z.date(),
16+
});
17+
18+
export type ChangelogEntry = {
19+
filepath: string;
20+
title: string;
21+
slug: string;
22+
description: string;
23+
date: string;
24+
source: React.ReactNode;
25+
};
26+
27+
function readMatterData(filepath: string) {
28+
try {
29+
const { data } = matter.read(filepath);
30+
return FrontmatterSchema.parse(data);
31+
} catch (error: unknown) {
32+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
33+
return null;
34+
}
35+
throw error;
36+
}
37+
}
38+
39+
export async function getDocMdxSource(filepath: string) {
40+
const source = await readFile(filepath, "utf-8");
41+
const result = await compileMDX({
42+
source,
43+
options: {
44+
mdxOptions: {
45+
rehypePlugins: [rehypeHighlight],
46+
remarkPlugins: [remarkGfm, remarkFrontmatter],
47+
},
48+
},
49+
});
50+
return result.content;
51+
}
52+
53+
async function getChangelogFromPath(
54+
filepath: string,
55+
): Promise<ChangelogEntry | null> {
56+
const frontmatter = readMatterData(filepath);
57+
if (!frontmatter) {
58+
return null;
59+
}
60+
const slug = filepath
61+
.replace(/^.\/changelogs\//, "")
62+
.replace(/\/index.mdx$/, "");
63+
64+
const DD_MM_YYYY = frontmatter.date.toISOString().split("T")[0];
65+
return {
66+
filepath,
67+
title: frontmatter.title,
68+
description: frontmatter.description,
69+
slug: `${DD_MM_YYYY}-${slug}`,
70+
date: frontmatter.date.toISOString(),
71+
source: await getDocMdxSource(filepath),
72+
};
73+
}
74+
75+
function validateAllChangelogs(
76+
changelogs: unknown[],
77+
): asserts changelogs is ChangelogEntry[] {
78+
if (changelogs.some((changelog) => changelog === null)) {
79+
throw new Error("Invalid changelog data");
80+
}
81+
}
82+
83+
export async function getChangelogs(): Promise<ChangelogEntry[]> {
84+
const files = await fg("./changelogs/**/*.mdx");
85+
const changelogs = await Promise.all(files.map(getChangelogFromPath));
86+
validateAllChangelogs(changelogs);
87+
return changelogs.sort(
88+
(a, b) => Number(new Date(b.date)) - Number(new Date(a.date)),
89+
);
90+
}
91+
92+
export async function getChangelogEntryBySlug(
93+
slug: string,
94+
): Promise<ChangelogEntry | null> {
95+
const slugWithoutDate = slug.split("-").slice(3).join("-");
96+
const filepath = `./changelogs/${slugWithoutDate}/index.mdx`;
97+
return getChangelogFromPath(filepath);
98+
}
Loading

public/main-sitemap.xml

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
3-
<url><loc>https://argos-ci.com/</loc><lastmod>2023-12-28T05:44:31.942Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
4-
<url><loc>https://argos-ci.com/playwright</loc><lastmod>2023-12-28T05:44:31.942Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
5-
<url><loc>https://argos-ci.com/pricing</loc><lastmod>2023-12-28T05:44:31.942Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
6-
<url><loc>https://argos-ci.com/blog</loc><lastmod>2023-12-28T05:44:31.942Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
7-
<url><loc>https://argos-ci.com/privacy</loc><lastmod>2023-12-28T05:44:31.942Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
8-
<url><loc>https://argos-ci.com/terms</loc><lastmod>2023-12-28T05:44:31.942Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
9-
<url><loc>https://argos-ci.com/security</loc><lastmod>2023-12-28T05:44:31.942Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
10-
<url><loc>https://argos-ci.com/oss-friends</loc><lastmod>2023-12-28T05:44:31.942Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
3+
<url><loc>https://argos-ci.com/</loc></url>
4+
<url><loc>https://argos-ci.com/playwright</loc></url>
5+
<url><loc>https://argos-ci.com/pricing</loc></url>
6+
<url><loc>https://argos-ci.com/changelog</loc></url>
7+
<url><loc>https://argos-ci.com/blog</loc></url>
8+
<url><loc>https://argos-ci.com/privacy</loc></url>
9+
<url><loc>https://argos-ci.com/terms</loc></url>
10+
<url><loc>https://argos-ci.com/security</loc></url>
11+
<url><loc>https://argos-ci.com/oss-friends</loc></url>
1112
</urlset>

0 commit comments

Comments
 (0)