From 0e18c380b3926a53f4e76e4562d6c4f951513b05 Mon Sep 17 00:00:00 2001 From: Maximilian Kaske Date: Fri, 27 Dec 2024 10:59:21 +0100 Subject: [PATCH] chore: blog post --- apps/server/src/v1/middleware.ts | 4 +- apps/web/content-collections.ts | 11 +- apps/web/package.json | 1 - .../event-analytics-implementation.png | Bin 0 -> 28124 bytes apps/web/src/components/content/article.tsx | 16 +- apps/web/src/components/content/mdx.tsx | 2 +- apps/web/src/components/content/pre.tsx | 5 +- .../src/content/posts/2023-year-review.mdx | 2 + .../posts/dynamic-breadcrumb-nextjs.mdx | 2 + .../posts/event-analytics-implementation.mdx | 195 +++++++++++++++++ .../migration-auth-clerk-to-next-auth.mdx | 2 + .../migration-backend-from-vercel-to-fly.mdx | 2 + .../posts/migration-planetscale-to-turso.mdx | 2 + ...cy-cf-workers-fly-koyeb-raylway-render.mdx | 2 + ...ring-latency-vercel-edge-vs-serverless.mdx | 2 + .../posts/our-new-pricing-explained.mdx | 2 + .../posts/pricing-update-july-2024.mdx | 2 + apps/web/src/content/posts/q1-2024-update.mdx | 2 + .../reflecting-1-year-building-openstatus.mdx | 2 + .../content/posts/secure-api-with-unkey.mdx | 2 + .../src/content/posts/the-first-48-hours.mdx | 2 + apps/web/src/styles/globals.css | 8 + packages/api/src/trpc.ts | 5 + pnpm-lock.yaml | 200 +----------------- 24 files changed, 259 insertions(+), 214 deletions(-) create mode 100644 apps/web/public/assets/posts/event-analytics-implementation/event-analytics-implementation.png create mode 100644 apps/web/src/content/posts/event-analytics-implementation.mdx diff --git a/apps/server/src/v1/middleware.ts b/apps/server/src/v1/middleware.ts index 0b40518a04..d6619fcd5b 100644 --- a/apps/server/src/v1/middleware.ts +++ b/apps/server/src/v1/middleware.ts @@ -55,12 +55,12 @@ export function trackMiddleware(event: EventProps, eventProps?: string[]) { await next(); // REMINDER: only track the event if the request was successful - // REMINDER: use setTimeout to avoid blocking the response - if (c.finalized) { + if (!c.error) { // We have checked the request to be valid already const json = (await c.req.json()) as unknown; const additionalProps = parseInputToProps(json, eventProps); + // REMINDER: use setTimeout to avoid blocking the response setTimeout(async () => { const analytics = await setupAnalytics({ userId: `api_${c.get("workspaceId")}`, diff --git a/apps/web/content-collections.ts b/apps/web/content-collections.ts index 87f1e8c00a..6b3d7740c6 100644 --- a/apps/web/content-collections.ts +++ b/apps/web/content-collections.ts @@ -28,6 +28,8 @@ const prettyCode = [ dark: "github-dark-dimmed", light: "github-light", }, + grid: true, + keepBackground: false, // biome-ignore lint/suspicious/noExplicitAny: onVisitLine(node: any) { // Prevent lines from collapsing in `display: grid` mode, and @@ -36,14 +38,6 @@ const prettyCode = [ node.children = [{ type: "text", value: " " }]; } }, - // biome-ignore lint/suspicious/noExplicitAny: - onVisitHighlightedLine(node: any) { - node.properties.className.push("highlighted"); - }, - // biome-ignore lint/suspicious/noExplicitAny: - onVisitHighlightedWord(node: any) { - node.properties.className = ["word"]; - }, }, ]; @@ -61,6 +55,7 @@ const posts = defineCollection({ url: z.string().optional(), avatar: z.string().optional(), }), + tag: z.enum(["company", "engineering", "education"]), }), transform: async (document, context) => { const mdx = await compileMDX(context, document, { diff --git a/apps/web/package.json b/apps/web/package.json index 8b65e61baa..9a8596b7d7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -97,7 +97,6 @@ "postcss": "8.4.38", "rehype-autolink-headings": "7.1.0", "rehype-slug": "5.1.0", - "remark-gfm": "3.0.1", "tailwindcss": "3.4.3", "typescript": "5.6.2", "unified": "10.1.2" diff --git a/apps/web/public/assets/posts/event-analytics-implementation/event-analytics-implementation.png b/apps/web/public/assets/posts/event-analytics-implementation/event-analytics-implementation.png new file mode 100644 index 0000000000000000000000000000000000000000..f81be4a1fd50084623cbef095a65198064202e41 GIT binary patch literal 28124 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 PQsTCm^}ngtJs$lJ_|b$< literal 0 HcmV?d00001 diff --git a/apps/web/src/components/content/article.tsx b/apps/web/src/components/content/article.tsx index 01eecc463d..c1e81021b8 100644 --- a/apps/web/src/components/content/article.tsx +++ b/apps/web/src/components/content/article.tsx @@ -2,7 +2,7 @@ import type { Post } from "content-collections"; import Image from "next/image"; import Link from "next/link"; -import { Avatar, AvatarFallback, AvatarImage } from "@openstatus/ui"; +import { Avatar, AvatarFallback, AvatarImage, Badge } from "@openstatus/ui"; import { Mdx } from "@/components/content/mdx"; import { formatDate } from "@/lib/utils"; @@ -40,11 +40,15 @@ export function Article({ post }: { post: Post }) { > {post.author.name} -

- {formatDate(post.publishedAt)} - - {post.readingTime} -

+
+ + + {post.readingTime} + + + {post.tag} + +
diff --git a/apps/web/src/components/content/mdx.tsx b/apps/web/src/components/content/mdx.tsx index 8ebbc9218c..acf0602b54 100644 --- a/apps/web/src/components/content/mdx.tsx +++ b/apps/web/src/components/content/mdx.tsx @@ -13,7 +13,7 @@ export function Mdx({ code, className }: MdxProps) { // FIXME: weird behaviour when `prose-headings:font-cal` and on mouse movement font gets bigger
diff --git a/apps/web/src/components/content/pre.tsx b/apps/web/src/components/content/pre.tsx index a0ff9e4b02..e2021627f6 100644 --- a/apps/web/src/components/content/pre.tsx +++ b/apps/web/src/components/content/pre.tsx @@ -1,11 +1,12 @@ "use client"; +import { cn } from "@/lib/utils"; import { Button } from "@openstatus/ui/src/components/button"; import { Clipboard, ClipboardCopy } from "lucide-react"; import React from "react"; export interface PreProps extends React.HTMLAttributes {} -export default function Pre({ children, ...props }: PreProps) { +export default function Pre({ children, className, ...props }: PreProps) { const [copied, setCopied] = React.useState(false); const ref = React.useRef(null); @@ -43,7 +44,7 @@ export default function Pre({ children, ...props }: PreProps) { )} -
+      
         {children}
       
diff --git a/apps/web/src/content/posts/2023-year-review.mdx b/apps/web/src/content/posts/2023-year-review.mdx index ebcec053d8..25219465ba 100644 --- a/apps/web/src/content/posts/2023-year-review.mdx +++ b/apps/web/src/content/posts/2023-year-review.mdx @@ -6,8 +6,10 @@ description: author: name: Thibault Le Ouay Ducasse url: https://bsky.app/profile/thibaultleouay.dev + avatar: /assets/authors/thibault.jpeg publishedAt: 2023-12-29 image: /assets/posts/2023-year-review/title.png +tag: company --- It has been a wild six months for us at OpenStatus. In late June, we began diff --git a/apps/web/src/content/posts/dynamic-breadcrumb-nextjs.mdx b/apps/web/src/content/posts/dynamic-breadcrumb-nextjs.mdx index 375387a0a4..abab0a0cf1 100644 --- a/apps/web/src/content/posts/dynamic-breadcrumb-nextjs.mdx +++ b/apps/web/src/content/posts/dynamic-breadcrumb-nextjs.mdx @@ -5,8 +5,10 @@ description: author: name: Thibault Le Ouay Ducasse url: https://bsky.app/profile/thibaultleouay.dev + avatar: /assets/authors/thibault.jpeg publishedAt: 2024-08-19 image: /assets/posts/dynamic-breadcrumb-nextjs/breadcrumb.png +tag: engineering --- In this post, we'll dive into the process of creating dynamic breadcrumbs in Next.js using parallel routes. Our goal is to build a breadcrumb component that automatically updates based on the current page and its hierarchy, all while leveraging server-side rendering for optimal performance. diff --git a/apps/web/src/content/posts/event-analytics-implementation.mdx b/apps/web/src/content/posts/event-analytics-implementation.mdx new file mode 100644 index 0000000000..04e08fe9da --- /dev/null +++ b/apps/web/src/content/posts/event-analytics-implementation.mdx @@ -0,0 +1,195 @@ +--- +title: How We Implemented Event Analytics with OpenPanel +description: + Leveraging Hono OpenAPI middleware and tRPC metadata + middleware to implement event analytics easily. +author: + name: Maximilian Kaske + url: https://x.com/mxkaske + avatar: /assets/authors/max.png +publishedAt: 2024-12-27 +image: /assets/posts/event-analytics-implementation/event-analytics-implementation.png +tag: engineering +--- + +We had never really tracked events properly. We had some basic tracking in place, but it was not very useful. It is time to improve that with [OpenPanel](https://openpanel.dev?ref=openstatus). + +After some research, we finally settled on leveraging tRPC and Hono middlewares. Shoutout to [Midday](https://midday.ai?ref=openstatus) for the (tRPC) inspiration. They use a similar approach with [next-safe-action](https://next-safe-action.dev?ref=openstatus) for their server actions. + +This post is not a step-by-step guide but instead presents the core concepts and ideas behind the implementation. Please refer to the [Hono](https://hono.dev?ref=openstatus) or [tRPC](https://trpc.io?ref=openstatus) documentation for more detailed information and our [GitHub](https://openstatus.dev/github) repository for the full implementation. + +--- + +First, let's start with the basics. We need to define the events we want to track, like `page_created`, `user_created`, etc. + +```ts +// packages/analytics/src/events.ts +export type EventProps = { + name: string; + channel: string; +}; + +export const Events = { + CreatePage: { + name: "page_created", + channel: "page", + }, + UpdatePage: { + name: "page_upated", + channel: "page", + }, + // ... add more events +} as const satisfies Record; +``` + +Next, we need to initialize OpenPanel (see [installation](https://openpanel.dev/docs/sdks/javascript)) and set up the analytics in our application. + +```ts +// packages/analytics/src/index.ts +import { + OpenPanel, + type PostEventPayload, + type IdentifyPayload, +} from "@openpanel/sdk"; +import { type EventProps } from "@openstatus/analytics"; + +const op = new OpenPanel({ + clientId: process.env.OPENPANEL_CLIENT_ID, + clientSecret: process.env.OPENPANEL_CLIENT_SECRET, +}); + +export async function setupAnalytics(props: Partial) { + if (props.profileId) { + await op.identify(props): + } + + return { + track: (opts: EventProps & PostEventPayload["properties"]) => { + const { name, ...rest } = opts; + return op.track(name, rest); + }, + }; +} +``` + +Now that we have the basic setup in place, we can start implementing the tracking in our application. We will use the tRPC middleware and metadata to track events. Below, we define the `trackEvent` middleware that will track the event after the procedure has been executed. An `enforceUserSession` middleware can be added to include the user's ID for tracking. + +```ts {8,12,35} /trackEvent/ +// packages/trpc/src/index.ts +import { after } from "next/server"; +import { initTRPC } from "@trpc/server"; +import { setupAnalytics, type EventProps } from "@openstatus/analytics"; +import type { User } from "@openstatus/auth"; + +type Context = { user?: User }; +type Meta = { track?: EventProps }; + +export const t = initTRPC + .context() + .meta() + .create({ /* ... */ }); + + +const trackEvent = t.middleware(async opts => { + const result = await opts.next(opts.ctx); + + if (!result.ok) return result; + + if (opts.meta.track) { + after(async () => { + const identify = opts.ctx.user ? { userId: opts.ctx.user.id } : {}; + const analytics = await setupAnalytics(identify); + await analytics.track(opts.meta.track); + }) + } + return result; +}); + +const enforceUserSession = t.middleware(async opts => { + // ... set user to ctx +}); + +export const protectedProcedure = t.procedure + .use(enforceUserSession) + .use(trackEvent); +``` + +The `after` function (similar to `waitUntil`) will execute the tracking after the procedure has been executed and won't block the response. + +The `next()` return value has an `ok` boolean property to check if the procedure was successful. If not, we don't want to track the event. + +How will we use it in a procedure? Adding a `meta` property will allow us to track the event by defining the event we want to track. + +```ts {6} +// packages/trpc/src/router/page.ts +import { Events } from '@openstatus/analytics'; +import { insertPageSchema } from "@openstatus/db"; +import { createTRPCRouter, protectedProcedure } from "../trpc"; + +export const pageRouter = createTRPCRouter({ + create: protectedProcedure + .meta({ track: Events.CreatePage }) + .input(insertPageSchema) + .mutation(async (opts) => { /* ... */ }) +}); +``` + +Voilà! Each time you want to add tracking to a new procedure, you only need to add the `meta` property with the event you want to track. The middleware handles the rest. + +--- + +Now, how do we track the events within our API? Let's start by adding the `trackMiddleware` function and only track the event if the response has been finalized. + +```ts /trackMiddleware/ +// app/server/src/middleware.ts +import { setupAnalytics, type EventProps } from "@openstatus/analytics"; +import type { Context, Next } from "hono"; +import type { User } from "@openstatus/auth"; + +export function trackMiddleware(event: EventProps) { + return async (c: Context<{ Variables: { user?: User } }, "/*">, next: Next) => { + await next(); + + if (!c.error) { + setTimeout(async () => { + const analytics = await setupAnalytics({ + profileId: c.get("user")?.id, + }); + await analytics.track(event); + }, 0); + } + }; +} +``` + +Depending on where you are running the server, you might want to replace `setTimeout` with `waitUntil` (cf workers, Vercel) or other functions that extend the lifetime of the request without blocking the response. + +And again, we check if there was an `error` before tracking the event. We don't want to track unsuccessful events. + +The [`@hono/zod-openapi`](https://github.com/honojs/middleware/tree/main/packages/zod-openapi) routes have a `middleware` property that allows you to add middleware to the route. This is where we will add the tracking middleware. + +```ts {11} +// apps/web/src/pages/post.ts +import { createRoute } from "@hono/zod-openapi"; +import { Events } from "@openstatus/analytics"; +import { trackMiddleware } from "../middleware"; + +const postRoute = createRoute({ + method: "post", + tags: ["page"], + description: "Create a new Page", + path: "/", + middleware: [trackMiddleware(Events.CreatePage)], + request: { /* ... */ }, + responses: { /* ... */}, +}); + +// ... +``` + +--- + +And that's it. With minimal code changes and the help of middlewares, we have implemented event tracking in our application. You can swap [OpenPanel](https://openpanel.dev?ref=openstatus) with any other analytics provider like PostHog, but give it a try, it's an amazing tool! + +By the way, this approach can be used for audit log tracking for example as well. + +Check out our [GitHub](https://openstatus.dev/github) repository for the full implementation and don't forget to leave a star if you found this helpful. \ No newline at end of file diff --git a/apps/web/src/content/posts/migration-auth-clerk-to-next-auth.mdx b/apps/web/src/content/posts/migration-auth-clerk-to-next-auth.mdx index 4d21346842..7a2e6c0ed0 100644 --- a/apps/web/src/content/posts/migration-auth-clerk-to-next-auth.mdx +++ b/apps/web/src/content/posts/migration-auth-clerk-to-next-auth.mdx @@ -5,8 +5,10 @@ description: author: name: Thibault Le Ouay Ducasse url: https://bsky.app/profile/thibaultleouay.dev + avatar: /assets/authors/thibault.jpeg publishedAt: 2024-05-15 image: /assets/posts/migration-auth-clerk-to-next-auth/authjs.png +tag: engineering --- We recently switched from [Clerk](https://clerk.com) to NextAuth diff --git a/apps/web/src/content/posts/migration-backend-from-vercel-to-fly.mdx b/apps/web/src/content/posts/migration-backend-from-vercel-to-fly.mdx index 9952224ca9..ff41f11df1 100644 --- a/apps/web/src/content/posts/migration-backend-from-vercel-to-fly.mdx +++ b/apps/web/src/content/posts/migration-backend-from-vercel-to-fly.mdx @@ -6,8 +6,10 @@ description: author: name: Thibault Le Ouay Ducasse url: https://bsky.app/profile/thibaultleouay.dev + avatar: /assets/authors/thibault.jpeg publishedAt: 2023-10-29 image: /assets/posts/migration-backend-from-vercel-to-fly/fly.png +tag: company --- In this article, we are going to see the reasons that made us change our backend diff --git a/apps/web/src/content/posts/migration-planetscale-to-turso.mdx b/apps/web/src/content/posts/migration-planetscale-to-turso.mdx index 16955f50e2..de7aa9bdef 100644 --- a/apps/web/src/content/posts/migration-planetscale-to-turso.mdx +++ b/apps/web/src/content/posts/migration-planetscale-to-turso.mdx @@ -6,7 +6,9 @@ description: author: name: Thibault Le Ouay Ducasse url: https://bsky.app/profile/thibaultleouay.dev + avatar: /assets/authors/thibault.jpeg publishedAt: 2023-08-20 +tag: company --- ## What are we building ? 🏗️ diff --git a/apps/web/src/content/posts/monitoring-latency-cf-workers-fly-koyeb-raylway-render.mdx b/apps/web/src/content/posts/monitoring-latency-cf-workers-fly-koyeb-raylway-render.mdx index 3e15466a7e..3906c9e654 100644 --- a/apps/web/src/content/posts/monitoring-latency-cf-workers-fly-koyeb-raylway-render.mdx +++ b/apps/web/src/content/posts/monitoring-latency-cf-workers-fly-koyeb-raylway-render.mdx @@ -7,8 +7,10 @@ description: author: name: Thibault Le Ouay Ducasse url: https://bsky.app/profile/thibaultleouay.dev + avatar: /assets/authors/thibault.jpeg publishedAt: 2024-02-19 image: /assets/posts/monitoring-latency/all-hosting-providers.png +tag: education --- > ⚠️ We are using the default settings for each provider and conducting diff --git a/apps/web/src/content/posts/monitoring-latency-vercel-edge-vs-serverless.mdx b/apps/web/src/content/posts/monitoring-latency-vercel-edge-vs-serverless.mdx index 38755594a2..060122cd1e 100644 --- a/apps/web/src/content/posts/monitoring-latency-vercel-edge-vs-serverless.mdx +++ b/apps/web/src/content/posts/monitoring-latency-vercel-edge-vs-serverless.mdx @@ -6,8 +6,10 @@ description: author: name: Thibault Le Ouay Ducasse url: https://bsky.app/profile/thibaultleouay.dev + avatar: /assets/authors/thibault.jpeg publishedAt: 2024-03-14 image: /assets/posts/monitoring-vercel/serverless-vs-edge.png +tag: education --- In our previous diff --git a/apps/web/src/content/posts/our-new-pricing-explained.mdx b/apps/web/src/content/posts/our-new-pricing-explained.mdx index 48a8dbde79..b4ec6ac9fe 100644 --- a/apps/web/src/content/posts/our-new-pricing-explained.mdx +++ b/apps/web/src/content/posts/our-new-pricing-explained.mdx @@ -6,8 +6,10 @@ description: author: name: Thibault Le Ouay Ducasse url: https://bsky.app/profile/thibaultleouay.dev + avatar: /assets/authors/thibault.jpeg publishedAt: 2024-01-15 image: /assets/posts/our-new-pricing-explained/pricing-hard.png +tag: company --- We began the new year with two goals: to increase profitability and attract more diff --git a/apps/web/src/content/posts/pricing-update-july-2024.mdx b/apps/web/src/content/posts/pricing-update-july-2024.mdx index 6bacef27eb..ddb98a1b00 100644 --- a/apps/web/src/content/posts/pricing-update-july-2024.mdx +++ b/apps/web/src/content/posts/pricing-update-july-2024.mdx @@ -4,8 +4,10 @@ description: We have update our pricing again, let's deep dive into it. author: name: Thibault Le Ouay Ducasse url: https://bsky.app/profile/thibaultleouay.dev + avatar: /assets/authors/thibault.jpeg publishedAt: 2024-07-24 image: /assets/posts/pricing-update-july-2024/pricing.png +tag: company --- diff --git a/apps/web/src/content/posts/q1-2024-update.mdx b/apps/web/src/content/posts/q1-2024-update.mdx index d56eea2a63..ab10ff3927 100644 --- a/apps/web/src/content/posts/q1-2024-update.mdx +++ b/apps/web/src/content/posts/q1-2024-update.mdx @@ -4,8 +4,10 @@ description: Let's review our first quarter of 2024. author: name: Thibault Le Ouay Ducasse url: https://bsky.app/profile/thibaultleouay.dev + avatar: /assets/authors/thibault.jpeg publishedAt: 2024-04-29 image: /assets/posts/q1-2024-update/q1-2024-update.png +tag: company --- A lot has happened in the first quarter of 2024. We have been working hard to diff --git a/apps/web/src/content/posts/reflecting-1-year-building-openstatus.mdx b/apps/web/src/content/posts/reflecting-1-year-building-openstatus.mdx index d8da090804..9688b5ed60 100644 --- a/apps/web/src/content/posts/reflecting-1-year-building-openstatus.mdx +++ b/apps/web/src/content/posts/reflecting-1-year-building-openstatus.mdx @@ -5,8 +5,10 @@ description: Reflecting on our first year of building OpenStatus, an open source author: name: Thibault Le Ouay url: https://bsky.app/profile/thibaultleouay.dev + avatar: /assets/authors/thibault.jpeg publishedAt: 2024-09-26 image: /assets/posts/reflecting-1-year-building-openstatus/first-year.png +tag: company --- In one year, we've grown our open-source synthetic monitoring platform to more than 6,000 GitHub stars and 5,000 users. This article will reflect on our first year of building OpenStatus. diff --git a/apps/web/src/content/posts/secure-api-with-unkey.mdx b/apps/web/src/content/posts/secure-api-with-unkey.mdx index c5d51acd4c..7c8ec66bb1 100644 --- a/apps/web/src/content/posts/secure-api-with-unkey.mdx +++ b/apps/web/src/content/posts/secure-api-with-unkey.mdx @@ -4,8 +4,10 @@ description: The simplest way to secure your API routes within seconds. author: name: Maximilian Kaske url: https://twitter.com/mxkaske + avatar: /assets/authors/max.png publishedAt: 2023-10-01 image: /assets/posts/secure-api-with-unkey/unkey.png +tag: engineering --- ## Introduction diff --git a/apps/web/src/content/posts/the-first-48-hours.mdx b/apps/web/src/content/posts/the-first-48-hours.mdx index bebf22571a..222cc0e41a 100644 --- a/apps/web/src/content/posts/the-first-48-hours.mdx +++ b/apps/web/src/content/posts/the-first-48-hours.mdx @@ -5,7 +5,9 @@ description: The numbers, limits we faced and consequences we have taken. author: name: Maximilian Kaske url: https://twitter.com/mxkaske + avatar: /assets/authors/max.png publishedAt: 2023-08-02 +tag: company --- ## 48 hours of Rollercoaster 🎢 diff --git a/apps/web/src/styles/globals.css b/apps/web/src/styles/globals.css index 6a9f3a0b05..cacf0fa5fd 100644 --- a/apps/web/src/styles/globals.css +++ b/apps/web/src/styles/globals.css @@ -102,4 +102,12 @@ body { @apply bg-background text-foreground; } + + [data-highlighted-chars] { + @apply bg-muted rounded; + } + + [data-highlighted-line] { + @apply bg-muted rounded; + } } diff --git a/packages/api/src/trpc.ts b/packages/api/src/trpc.ts index 64e5adddbb..48127ff6c5 100644 --- a/packages/api/src/trpc.ts +++ b/packages/api/src/trpc.ts @@ -194,6 +194,11 @@ const enforceUserIsAuthed = t.middleware(async (opts) => { return result; } + // REMINDER: We only track the event if the request was successful + if (!result.ok) { + return result; + } + // REMINDER: We only track the event if the request was successful // REMINDER: We are not blocking the request after(async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 385c1d48b2..36104bd5a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -459,9 +459,6 @@ importers: rehype-slug: specifier: 5.1.0 version: 5.1.0 - remark-gfm: - specifier: 3.0.1 - version: 3.0.1 tailwindcss: specifier: 3.4.3 version: 3.4.3(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.6.2)) @@ -7636,9 +7633,6 @@ packages: mdast-util-directive@3.0.0: resolution: {integrity: sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==} - mdast-util-find-and-replace@2.2.2: - resolution: {integrity: sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==} - mdast-util-find-and-replace@3.0.1: resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} @@ -7651,39 +7645,21 @@ packages: mdast-util-frontmatter@2.0.1: resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} - mdast-util-gfm-autolink-literal@1.0.3: - resolution: {integrity: sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==} - mdast-util-gfm-autolink-literal@2.0.0: resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==} - mdast-util-gfm-footnote@1.0.2: - resolution: {integrity: sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==} - mdast-util-gfm-footnote@2.0.0: resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} - mdast-util-gfm-strikethrough@1.0.3: - resolution: {integrity: sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==} - mdast-util-gfm-strikethrough@2.0.0: resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} - mdast-util-gfm-table@1.0.7: - resolution: {integrity: sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==} - mdast-util-gfm-table@2.0.0: resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} - mdast-util-gfm-task-list-item@1.0.2: - resolution: {integrity: sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==} - mdast-util-gfm-task-list-item@2.0.0: resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} - mdast-util-gfm@2.0.2: - resolution: {integrity: sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==} - mdast-util-gfm@3.0.0: resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} @@ -7699,9 +7675,6 @@ packages: mdast-util-mdxjs-esm@2.0.1: resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} - mdast-util-phrasing@3.0.1: - resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==} - mdast-util-phrasing@4.1.0: resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} @@ -7711,9 +7684,6 @@ packages: mdast-util-to-hast@13.2.0: resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} - mdast-util-to-markdown@1.5.0: - resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==} - mdast-util-to-markdown@2.1.0: resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} @@ -7748,45 +7718,24 @@ packages: micromark-extension-frontmatter@2.0.0: resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} - micromark-extension-gfm-autolink-literal@1.0.5: - resolution: {integrity: sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==} - micromark-extension-gfm-autolink-literal@2.1.0: resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} - micromark-extension-gfm-footnote@1.1.2: - resolution: {integrity: sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==} - micromark-extension-gfm-footnote@2.1.0: resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} - micromark-extension-gfm-strikethrough@1.0.7: - resolution: {integrity: sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==} - micromark-extension-gfm-strikethrough@2.1.0: resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} - micromark-extension-gfm-table@1.0.7: - resolution: {integrity: sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==} - micromark-extension-gfm-table@2.1.0: resolution: {integrity: sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==} - micromark-extension-gfm-tagfilter@1.0.2: - resolution: {integrity: sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==} - micromark-extension-gfm-tagfilter@2.0.0: resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} - micromark-extension-gfm-task-list-item@1.0.5: - resolution: {integrity: sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==} - micromark-extension-gfm-task-list-item@2.1.0: resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} - micromark-extension-gfm@2.0.3: - resolution: {integrity: sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==} - micromark-extension-gfm@3.0.0: resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} @@ -8954,9 +8903,6 @@ packages: remark-frontmatter@5.0.0: resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==} - remark-gfm@3.0.1: - resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} - remark-gfm@4.0.0: resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==} @@ -9245,8 +9191,8 @@ packages: react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - sort-keys@5.0.0: - resolution: {integrity: sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==} + sort-keys@5.1.0: + resolution: {integrity: sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ==} engines: {node: '>=12'} source-map-js@1.2.0: @@ -13854,7 +13800,7 @@ snapshots: nanoid: 5.0.7 postcss-nested: 6.0.1(postcss@8.4.49) unhead: 1.9.15 - unified: 11.0.4 + unified: 11.0.5 vue: 3.4.31(typescript@5.7.2) transitivePeerDependencies: - '@jest/globals' @@ -17152,7 +17098,7 @@ snapshots: hash-obj@4.0.0: dependencies: is-obj: 3.0.0 - sort-keys: 5.0.0 + sort-keys: 5.1.0 type-fest: 1.4.0 hasown@2.0.2: @@ -18013,13 +17959,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-find-and-replace@2.2.2: - dependencies: - '@types/mdast': 3.0.14 - escape-string-regexp: 5.0.0 - unist-util-is: 5.2.1 - unist-util-visit-parents: 5.1.3 - mdast-util-find-and-replace@3.0.1: dependencies: '@types/mdast': 4.0.4 @@ -18072,13 +18011,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-gfm-autolink-literal@1.0.3: - dependencies: - '@types/mdast': 3.0.14 - ccount: 2.0.1 - mdast-util-find-and-replace: 2.2.2 - micromark-util-character: 1.2.0 - mdast-util-gfm-autolink-literal@2.0.0: dependencies: '@types/mdast': 4.0.4 @@ -18087,12 +18019,6 @@ snapshots: mdast-util-find-and-replace: 3.0.1 micromark-util-character: 2.1.0 - mdast-util-gfm-footnote@1.0.2: - dependencies: - '@types/mdast': 3.0.14 - mdast-util-to-markdown: 1.5.0 - micromark-util-normalize-identifier: 1.1.0 - mdast-util-gfm-footnote@2.0.0: dependencies: '@types/mdast': 4.0.4 @@ -18103,11 +18029,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-gfm-strikethrough@1.0.3: - dependencies: - '@types/mdast': 3.0.14 - mdast-util-to-markdown: 1.5.0 - mdast-util-gfm-strikethrough@2.0.0: dependencies: '@types/mdast': 4.0.4 @@ -18116,15 +18037,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-gfm-table@1.0.7: - dependencies: - '@types/mdast': 3.0.14 - markdown-table: 3.0.3 - mdast-util-from-markdown: 1.3.1 - mdast-util-to-markdown: 1.5.0 - transitivePeerDependencies: - - supports-color - mdast-util-gfm-table@2.0.0: dependencies: '@types/mdast': 4.0.4 @@ -18135,11 +18047,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-gfm-task-list-item@1.0.2: - dependencies: - '@types/mdast': 3.0.14 - mdast-util-to-markdown: 1.5.0 - mdast-util-gfm-task-list-item@2.0.0: dependencies: '@types/mdast': 4.0.4 @@ -18149,18 +18056,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-gfm@2.0.2: - dependencies: - mdast-util-from-markdown: 1.3.1 - mdast-util-gfm-autolink-literal: 1.0.3 - mdast-util-gfm-footnote: 1.0.2 - mdast-util-gfm-strikethrough: 1.0.3 - mdast-util-gfm-table: 1.0.7 - mdast-util-gfm-task-list-item: 1.0.2 - mdast-util-to-markdown: 1.5.0 - transitivePeerDependencies: - - supports-color - mdast-util-gfm@3.0.0: dependencies: mdast-util-from-markdown: 2.0.1 @@ -18222,11 +18117,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-phrasing@3.0.1: - dependencies: - '@types/mdast': 3.0.14 - unist-util-is: 5.2.1 - mdast-util-phrasing@4.1.0: dependencies: '@types/mdast': 4.0.4 @@ -18255,17 +18145,6 @@ snapshots: unist-util-visit: 5.0.0 vfile: 6.0.3 - mdast-util-to-markdown@1.5.0: - dependencies: - '@types/mdast': 3.0.14 - '@types/unist': 2.0.9 - longest-streak: 3.1.0 - mdast-util-phrasing: 3.0.1 - mdast-util-to-string: 3.2.0 - micromark-util-decode-string: 1.1.0 - unist-util-visit: 4.1.2 - zwitch: 2.0.4 - mdast-util-to-markdown@2.1.0: dependencies: '@types/mdast': 4.0.4 @@ -18360,13 +18239,6 @@ snapshots: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - micromark-extension-gfm-autolink-literal@1.0.5: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-sanitize-uri: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - micromark-extension-gfm-autolink-literal@2.1.0: dependencies: micromark-util-character: 2.1.0 @@ -18374,17 +18246,6 @@ snapshots: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - micromark-extension-gfm-footnote@1.1.2: - dependencies: - micromark-core-commonmark: 1.1.0 - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-sanitize-uri: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-extension-gfm-footnote@2.1.0: dependencies: devlop: 1.1.0 @@ -18396,15 +18257,6 @@ snapshots: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - micromark-extension-gfm-strikethrough@1.0.7: - dependencies: - micromark-util-chunked: 1.1.0 - micromark-util-classify-character: 1.1.0 - micromark-util-resolve-all: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-extension-gfm-strikethrough@2.1.0: dependencies: devlop: 1.1.0 @@ -18414,14 +18266,6 @@ snapshots: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - micromark-extension-gfm-table@1.0.7: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-extension-gfm-table@2.1.0: dependencies: devlop: 1.1.0 @@ -18430,22 +18274,10 @@ snapshots: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - micromark-extension-gfm-tagfilter@1.0.2: - dependencies: - micromark-util-types: 1.1.0 - micromark-extension-gfm-tagfilter@2.0.0: dependencies: micromark-util-types: 2.0.0 - micromark-extension-gfm-task-list-item@1.0.5: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-extension-gfm-task-list-item@2.1.0: dependencies: devlop: 1.1.0 @@ -18454,17 +18286,6 @@ snapshots: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - micromark-extension-gfm@2.0.3: - dependencies: - micromark-extension-gfm-autolink-literal: 1.0.5 - micromark-extension-gfm-footnote: 1.1.2 - micromark-extension-gfm-strikethrough: 1.0.7 - micromark-extension-gfm-table: 1.0.7 - micromark-extension-gfm-tagfilter: 1.0.2 - micromark-extension-gfm-task-list-item: 1.0.5 - micromark-util-combine-extensions: 1.1.0 - micromark-util-types: 1.1.0 - micromark-extension-gfm@3.0.0: dependencies: micromark-extension-gfm-autolink-literal: 2.1.0 @@ -19920,7 +19741,7 @@ snapshots: rehype-parse@9.0.0: dependencies: '@types/hast': 3.0.4 - hast-util-from-html: 2.0.1 + hast-util-from-html: 2.0.3 unified: 11.0.5 rehype-pretty-code@0.10.0(shiki@0.14.4): @@ -20005,15 +19826,6 @@ snapshots: transitivePeerDependencies: - supports-color - remark-gfm@3.0.1: - dependencies: - '@types/mdast': 3.0.14 - mdast-util-gfm: 2.0.2 - micromark-extension-gfm: 2.0.3 - unified: 10.1.2 - transitivePeerDependencies: - - supports-color - remark-gfm@4.0.0: dependencies: '@types/mdast': 4.0.4 @@ -20456,7 +20268,7 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - sort-keys@5.0.0: + sort-keys@5.1.0: dependencies: is-plain-obj: 4.1.0