diff --git a/apps/web/.env.development b/apps/web/.env.development index f67182512..18b8608d1 100644 --- a/apps/web/.env.development +++ b/apps/web/.env.development @@ -7,6 +7,9 @@ NEXT_PUBLIC_DEFAULT_THEME_MODE=light NEXT_PUBLIC_THEME_COLOR="#ffffff" NEXT_PUBLIC_THEME_COLOR_DARK="#0a0a0a" +# CMS +CMS_CLIENT=contentlayer + # AUTH NEXT_PUBLIC_AUTH_PASSWORD=true NEXT_PUBLIC_AUTH_MAGIC_LINK=false diff --git a/apps/web/app/(marketing)/blog/[slug]/page.tsx b/apps/web/app/(marketing)/blog/[slug]/page.tsx index 275a97765..38a2e38ff 100644 --- a/apps/web/app/(marketing)/blog/[slug]/page.tsx +++ b/apps/web/app/(marketing)/blog/[slug]/page.tsx @@ -1,12 +1,10 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; -import Script from 'next/script'; -import { allPosts } from 'contentlayer/generated'; +import { createCmsClient } from '@kit/cms'; import Post from '~/(marketing)/blog/_components/post'; -import appConfig from '~/config/app.config'; import { withI18n } from '~/lib/i18n/with-i18n'; export async function generateMetadata({ @@ -14,14 +12,14 @@ export async function generateMetadata({ }: { params: { slug: string }; }): Promise { - const post = allPosts.find((post) => post.slug === params.slug); + const cms = await createCmsClient(); + const post = await cms.getContentItemById(params.slug); if (!post) { notFound(); } - const { title, date, description, image, slug } = post; - const url = [appConfig.url, 'blog', slug].join('/'); + const { title, publishedAt, description, image } = post; return Promise.resolve({ title, @@ -30,8 +28,8 @@ export async function generateMetadata({ title, description, type: 'article', - publishedTime: date, - url, + publishedTime: publishedAt.toDateString(), + url: post.url, images: image ? [ { @@ -49,8 +47,9 @@ export async function generateMetadata({ }); } -function BlogPost({ params }: { params: { slug: string } }) { - const post = allPosts.find((post) => post.slug === params.slug); +async function BlogPost({ params }: { params: { slug: string } }) { + const cms = await createCmsClient(); + const post = await cms.getContentItemById(params.slug); if (!post) { notFound(); @@ -58,11 +57,7 @@ function BlogPost({ params }: { params: { slug: string } }) { return (
- - - +
); } diff --git a/apps/web/app/(marketing)/blog/_components/post-header.tsx b/apps/web/app/(marketing)/blog/_components/post-header.tsx index 45c207520..8f3bcf2bf 100644 --- a/apps/web/app/(marketing)/blog/_components/post-header.tsx +++ b/apps/web/app/(marketing)/blog/_components/post-header.tsx @@ -1,5 +1,4 @@ -import type { Post } from 'contentlayer/generated'; - +import { Cms } from '@kit/cms'; import { Heading } from '@kit/ui/heading'; import { If } from '@kit/ui/if'; @@ -7,9 +6,9 @@ import { CoverImage } from '~/(marketing)/blog/_components/cover-image'; import { DateFormatter } from '~/(marketing)/blog/_components/date-formatter'; export const PostHeader: React.FC<{ - post: Post; + post: Cms.ContentItem; }> = ({ post }) => { - const { title, date, readingTime, description, image } = post; + const { title, publishedAt, description, image } = post; // NB: change this to display the post's image const displayImage = true; @@ -30,11 +29,8 @@ export const PostHeader: React.FC<{
- +
- - · - {readingTime} minutes reading
diff --git a/apps/web/app/(marketing)/blog/_components/post-preview.tsx b/apps/web/app/(marketing)/blog/_components/post-preview.tsx index 8c113f50c..5120d70e5 100644 --- a/apps/web/app/(marketing)/blog/_components/post-preview.tsx +++ b/apps/web/app/(marketing)/blog/_components/post-preview.tsx @@ -1,14 +1,13 @@ import Link from 'next/link'; -import type { Post } from 'contentlayer/generated'; - +import { Cms } from '@kit/cms'; import { If } from '@kit/ui/if'; import { CoverImage } from '~/(marketing)/blog/_components/cover-image'; import { DateFormatter } from '~/(marketing)/blog/_components/date-formatter'; type Props = { - post: Post; + post: Cms.ContentItem; preloadImage?: boolean; imageHeight?: string | number; }; @@ -20,15 +19,16 @@ export function PostPreview({ preloadImage, imageHeight, }: React.PropsWithChildren) { - const { title, image, date, readingTime, description } = post; + const { title, image, publishedAt, description } = post; const height = imageHeight ?? DEFAULT_IMAGE_HEIGHT; + const url = post.url; return ( -
+
{(imageUrl) => (
- +
-
-

- +
+

+ {title}

-
-
- +
+
+
- - · - - - {readingTime} mins reading -
-

+

{description}

diff --git a/apps/web/app/(marketing)/blog/_components/post.tsx b/apps/web/app/(marketing)/blog/_components/post.tsx index 97ea7b4d9..93e076981 100644 --- a/apps/web/app/(marketing)/blog/_components/post.tsx +++ b/apps/web/app/(marketing)/blog/_components/post.tsx @@ -1,15 +1,10 @@ -import dynamic from 'next/dynamic'; - -import type { Post as PostType } from 'contentlayer/generated'; +import type { Cms } from '@kit/cms'; +import { ContentRenderer } from '@kit/cms'; import { PostHeader } from './post-header'; -const Mdx = dynamic(() => - import('@kit/ui/mdx').then((mod) => ({ default: mod.Mdx })), -); - export const Post: React.FC<{ - post: PostType; + post: Cms.ContentItem; content: string; }> = ({ post, content }) => { return ( @@ -17,7 +12,7 @@ export const Post: React.FC<{
- +
); diff --git a/apps/web/app/(marketing)/blog/page.tsx b/apps/web/app/(marketing)/blog/page.tsx index 89af584f1..1f3f9db55 100644 --- a/apps/web/app/(marketing)/blog/page.tsx +++ b/apps/web/app/(marketing)/blog/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from 'next'; -import { allPosts } from 'contentlayer/generated'; +import { createCmsClient } from '@kit/cms'; import { GridList } from '~/(marketing)/_components/grid-list'; import { SitePageHeader } from '~/(marketing)/_components/site-page-header'; @@ -13,11 +13,11 @@ export const metadata: Metadata = { description: `Tutorials, Guides and Updates from our team`, }; -function BlogPage() { - const livePosts = allPosts.filter((post) => { - const isProduction = appConfig.production; +async function BlogPage() { + const cms = await createCmsClient(); - return isProduction ? post.live : true; + const posts = await cms.getContentItems({ + type: 'post', }); return ( @@ -29,7 +29,7 @@ function BlogPage() { /> - {livePosts.map((post, idx) => { + {posts.map((post, idx) => { return ; })} diff --git a/apps/web/app/(marketing)/docs/[...slug]/page.tsx b/apps/web/app/(marketing)/docs/[...slug]/page.tsx index 0a2417aaa..24eb264df 100644 --- a/apps/web/app/(marketing)/docs/[...slug]/page.tsx +++ b/apps/web/app/(marketing)/docs/[...slug]/page.tsx @@ -2,20 +2,20 @@ import { cache } from 'react'; import { notFound } from 'next/navigation'; -import { allDocumentationPages } from 'contentlayer/generated'; import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { ContentRenderer, createCmsClient } from '@kit/cms'; import { If } from '@kit/ui/if'; -import { Mdx } from '@kit/ui/mdx'; import { SitePageHeader } from '~/(marketing)/_components/site-page-header'; import { DocsCards } from '~/(marketing)/docs/_components/docs-cards'; import { DocumentationPageLink } from '~/(marketing)/docs/_components/documentation-page-link'; -import { getDocumentationPageTree } from '~/(marketing)/docs/_lib/get-documentation-page-tree'; import { withI18n } from '~/lib/i18n/with-i18n'; -const getPageBySlug = cache((slug: string) => { - return allDocumentationPages.find((post) => post.resolvedPath === slug); +const getPageBySlug = cache(async (slug: string) => { + const client = await createCmsClient(); + + return client.getContentItemById(slug); }); interface PageParams { @@ -24,8 +24,8 @@ interface PageParams { }; } -export const generateMetadata = ({ params }: PageParams) => { - const page = getPageBySlug(params.slug.join('/')); +export const generateMetadata = async ({ params }: PageParams) => { + const page = await getPageBySlug(params.slug.join('/')); if (!page) { notFound(); @@ -39,16 +39,13 @@ export const generateMetadata = ({ params }: PageParams) => { }; }; -function DocumentationPage({ params }: PageParams) { - const page = getPageBySlug(params.slug.join('/')); +async function DocumentationPage({ params }: PageParams) { + const page = await getPageBySlug(params.slug.join('/')); if (!page) { notFound(); } - const { nextPage, previousPage, children } = - getDocumentationPageTree(page.resolvedPath) ?? {}; - const description = page?.description ?? ''; return ( @@ -60,40 +57,11 @@ function DocumentationPage({ params }: PageParams) { className={'items-start'} /> - + - - + + - -
-
- - {(page) => ( - } - /> - )} - -
- -
- - {(page) => ( - } - /> - )} - -
-

); diff --git a/apps/web/app/(marketing)/docs/_components/docs-card.tsx b/apps/web/app/(marketing)/docs/_components/docs-card.tsx index a6b4836b9..d83c225b2 100644 --- a/apps/web/app/(marketing)/docs/_components/docs-card.tsx +++ b/apps/web/app/(marketing)/docs/_components/docs-card.tsx @@ -4,18 +4,18 @@ import { ChevronRight } from 'lucide-react'; export const DocsCard: React.FC< React.PropsWithChildren<{ - label: string; + title: string; subtitle?: string | null; link?: { url: string; label: string }; }> -> = ({ label, subtitle, children, link }) => { +> = ({ title, subtitle, children, link }) => { return (
-

{label}

+

{title}

{subtitle && (
@@ -31,7 +31,7 @@ export const DocsCard: React.FC< {link.label} diff --git a/apps/web/app/(marketing)/docs/_components/docs-cards.tsx b/apps/web/app/(marketing)/docs/_components/docs-cards.tsx index 0f017967d..696758fa1 100644 --- a/apps/web/app/(marketing)/docs/_components/docs-cards.tsx +++ b/apps/web/app/(marketing)/docs/_components/docs-cards.tsx @@ -1,19 +1,19 @@ -import type { DocumentationPage } from 'contentlayer/generated'; +import { Cms } from '@kit/cms'; import { DocsCard } from './docs-card'; -export function DocsCards({ pages }: { pages: DocumentationPage[] }) { +export function DocsCards({ pages }: { pages: Cms.ContentItem[] }) { return (
{pages.map((item) => { return ( ); diff --git a/apps/web/app/(marketing)/docs/_components/docs-navigation.tsx b/apps/web/app/(marketing)/docs/_components/docs-navigation.tsx index 266a7a00a..30bc82e55 100644 --- a/apps/web/app/(marketing)/docs/_components/docs-navigation.tsx +++ b/apps/web/app/(marketing)/docs/_components/docs-navigation.tsx @@ -5,33 +5,21 @@ import { useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; -import { ChevronDown, Menu } from 'lucide-react'; +import { Menu } from 'lucide-react'; +import { Cms } from '@kit/cms'; import { isBrowser } from '@kit/shared/utils'; import { Button } from '@kit/ui/button'; import { Heading } from '@kit/ui/heading'; import { If } from '@kit/ui/if'; import { cn } from '@kit/ui/utils'; -import type { ProcessedDocumentationPage } from '~/(marketing)/docs/_lib/build-documentation-tree'; - const DocsNavLink: React.FC<{ label: string; url: string; level: number; activePath: string; - collapsible: boolean; - collapsed: boolean; - toggleCollapsed: () => void; -}> = ({ - label, - url, - level, - activePath, - collapsible, - collapsed, - toggleCollapsed, -}) => { +}> = ({ label, url, level, activePath }) => { const isCurrent = url == activePath; const isFirstLevel = level === 0; @@ -39,76 +27,51 @@ const DocsNavLink: React.FC<{
{label} - - {collapsible && ( - - )}
); }; const Node: React.FC<{ - node: ProcessedDocumentationPage; + node: Cms.ContentItem; level: number; activePath: string; }> = ({ node, level, activePath }) => { - const [collapsed, setCollapsed] = useState(node.collapsed ?? false); - const toggleCollapsed = () => setCollapsed(!collapsed); - - useEffect(() => { - if ( - activePath == node.resolvedPath || - node.children.map((_) => _.resolvedPath).includes(activePath) - ) { - setCollapsed(false); - } - }, [activePath, node.children, node.resolvedPath]); - return ( <> - {node.children.length > 0 && !collapsed && ( - + {(node.children ?? []).length > 0 && ( + )} ); }; function Tree({ - tree, + pages, level, activePath, }: { - tree: ProcessedDocumentationPage[]; + pages: Cms.ContentItem[]; level: number; activePath: string; }) { return (
0 ? 'border-l' : '')}> - {tree.map((treeNode, index) => ( + {pages.map((treeNode, index) => ( - +
- +
); @@ -159,10 +121,10 @@ function getNavLinkClassName(isCurrent: boolean, isFirstLevel: boolean) { } function FloatingDocumentationNavigation({ - tree, + pages, activePath, }: React.PropsWithChildren<{ - tree: ProcessedDocumentationPage[]; + pages: Cms.ContentItem[]; activePath: string; }>) { const body = useMemo(() => { @@ -210,7 +172,7 @@ function FloatingDocumentationNavigation({ > Table of Contents - +
diff --git a/apps/web/app/(marketing)/docs/_components/documentation-page-link.tsx b/apps/web/app/(marketing)/docs/_components/documentation-page-link.tsx index 449cf9db3..9dcfcc2a6 100644 --- a/apps/web/app/(marketing)/docs/_components/documentation-page-link.tsx +++ b/apps/web/app/(marketing)/docs/_components/documentation-page-link.tsx @@ -1,7 +1,5 @@ import Link from 'next/link'; -import type { DocumentationPage } from 'contentlayer/generated'; - import { If } from '@kit/ui/if'; import { cn } from '@kit/ui/utils'; @@ -10,7 +8,10 @@ export function DocumentationPageLink({ before, after, }: React.PropsWithChildren<{ - page: DocumentationPage; + page: { + url: string; + title: string; + }; before?: React.ReactNode; after?: React.ReactNode; }>) { @@ -23,7 +24,7 @@ export function DocumentationPageLink({ 'justify-end self-end': after, }, )} - href={`/docs/${page.resolvedPath}`} + href={page.url} > {(node) => <>{node}} diff --git a/apps/web/app/(marketing)/docs/_lib/build-documentation-tree.ts b/apps/web/app/(marketing)/docs/_lib/build-documentation-tree.ts deleted file mode 100644 index 60b34f7a9..000000000 --- a/apps/web/app/(marketing)/docs/_lib/build-documentation-tree.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { cache } from 'react'; - -import type { DocumentationPage } from 'contentlayer/generated'; - -export interface ProcessedDocumentationPage extends DocumentationPage { - collapsible: boolean; - pathSegments: string[]; - nextPage: ProcessedDocumentationPage | DocumentationPage | undefined; - previousPage: ProcessedDocumentationPage | DocumentationPage | undefined; - children: DocsTree; -} - -export type DocsTree = ProcessedDocumentationPage[]; - -/** - * Build a tree of documentation pages from a flat list of pages with path segments - * @param docs - * @param parentPathNames - */ -export const buildDocumentationTree = cache( - (docs: DocumentationPage[], parentPathNames: string[] = []): DocsTree => { - const level = parentPathNames.length; - - const pages = docs - .filter( - (_) => - _.pathSegments.length === level + 1 && - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - _.pathSegments - .map(({ pathName }: { pathName: string }) => pathName) - .join('/') - .startsWith(parentPathNames.join('/')), - ) - .sort( - (a, b) => a.pathSegments[level].order - b.pathSegments[level].order, - ); - - return pages.map((doc, index) => { - const children = buildDocumentationTree( - docs, - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - doc.pathSegments.map(({ pathName }: { pathName: string }) => pathName), - ); - - return { - ...doc, - pathSegments: doc.pathSegments || ([] as string[]), - collapsible: children.length > 0, - nextPage: children[0] ?? pages[index + 1], - previousPage: pages[index - 1], - children, - }; - }); - }, -); diff --git a/apps/web/app/(marketing)/docs/_lib/get-documentation-page-tree.ts b/apps/web/app/(marketing)/docs/_lib/get-documentation-page-tree.ts deleted file mode 100644 index ee7bc4ccc..000000000 --- a/apps/web/app/(marketing)/docs/_lib/get-documentation-page-tree.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { cache } from 'react'; - -import type { DocumentationPage } from 'contentlayer/generated'; -import { allDocumentationPages } from 'contentlayer/generated'; - -import { buildDocumentationTree } from './build-documentation-tree'; - -/** - * Retrieves a specific documentation page from the page tree by its path. - * - * @param {string} pagePath - The path of the documentation page to retrieve. - * @returns {DocumentationPageWithChildren | undefined} The documentation page found in the tree, if any. - */ -export const getDocumentationPageTree = cache((pagePath: string) => { - const tree = buildDocumentationTree(allDocumentationPages); - - type DocumentationPageWithChildren = DocumentationPage & { - previousPage?: DocumentationPage | null; - nextPage?: DocumentationPage | null; - children?: DocumentationPage[]; - }; - - const findPageInTree = ( - pages: DocumentationPageWithChildren[], - path: string, - ): DocumentationPageWithChildren | undefined => { - for (const page of pages) { - if (page.resolvedPath === path) { - return page; - } - - const hasChildren = page.children && page.children.length > 0; - - if (hasChildren) { - const foundPage = findPageInTree(page.children ?? [], path); - - if (foundPage) { - return foundPage; - } - } - } - }; - - return findPageInTree(tree, pagePath); -}); diff --git a/apps/web/app/(marketing)/docs/layout.tsx b/apps/web/app/(marketing)/docs/layout.tsx index a2c52ef27..5ed6ba6a5 100644 --- a/apps/web/app/(marketing)/docs/layout.tsx +++ b/apps/web/app/(marketing)/docs/layout.tsx @@ -1,15 +1,22 @@ -import { allDocumentationPages } from 'contentlayer/generated'; +import { createCmsClient } from '@kit/cms'; -import DocsNavigation from '~/(marketing)/docs/_components/docs-navigation'; -import { buildDocumentationTree } from '~/(marketing)/docs/_lib/build-documentation-tree'; +import { DocsNavigation } from '~/(marketing)/docs/_components/docs-navigation'; -function DocsLayout({ children }: React.PropsWithChildren) { - const tree = buildDocumentationTree(allDocumentationPages); +async function DocsLayout({ children }: React.PropsWithChildren) { + const cms = await createCmsClient(); + + const pages = await cms.getContentItems({ + type: 'page', + categories: ['documentation'], + depth: 1, + }); + + console.log(pages); return (
- +
{children}
diff --git a/apps/web/app/(marketing)/docs/page.tsx b/apps/web/app/(marketing)/docs/page.tsx index 8455dea81..9108f0fa8 100644 --- a/apps/web/app/(marketing)/docs/page.tsx +++ b/apps/web/app/(marketing)/docs/page.tsx @@ -1,8 +1,7 @@ -import { allDocumentationPages } from 'contentlayer/generated'; +import { createCmsClient } from '@kit/cms'; import { SitePageHeader } from '~/(marketing)/_components/site-page-header'; import { DocsCards } from '~/(marketing)/docs/_components/docs-cards'; -import { buildDocumentationTree } from '~/(marketing)/docs/_lib/build-documentation-tree'; import appConfig from '~/config/app.config'; import { withI18n } from '~/lib/i18n/with-i18n'; @@ -10,8 +9,16 @@ export const metadata = { title: `Documentation - ${appConfig.name}`, }; -function DocsPage() { - const tree = buildDocumentationTree(allDocumentationPages); +async function DocsPage() { + const client = await createCmsClient(); + + const docs = await client.getContentItems({ + type: 'page', + categories: ['documentation'], + depth: 1, + }); + + console.log(docs); return (
@@ -21,7 +28,7 @@ function DocsPage() { />
- +
); diff --git a/apps/web/app/server-sitemap.xml/route.ts b/apps/web/app/server-sitemap.xml/route.ts index e1ca73c1c..bce59c57e 100644 --- a/apps/web/app/server-sitemap.xml/route.ts +++ b/apps/web/app/server-sitemap.xml/route.ts @@ -1,17 +1,26 @@ import { invariant } from '@epic-web/invariant'; -import { allDocumentationPages, allPosts } from 'contentlayer/generated'; import { getServerSideSitemap } from 'next-sitemap'; +import { createCmsClient } from '@kit/cms'; + import appConfig from '~/config/app.config'; invariant(appConfig.url, 'No NEXT_PUBLIC_SITE_URL environment variable found'); export async function GET() { const urls = getSiteUrls(); - const posts = getPostsSitemap(); - const docs = getDocsSitemap(); + const client = await createCmsClient(); + const contentItems = await client.getContentItems(); - return getServerSideSitemap([...urls, ...posts, ...docs]); + return getServerSideSitemap([ + ...urls, + ...contentItems.map((item) => { + return { + loc: new URL(item.url, appConfig.url).href, + lastmod: new Date().toISOString(), + }; + }), + ]); } function getSiteUrls() { @@ -24,21 +33,3 @@ function getSiteUrls() { }; }); } - -function getPostsSitemap() { - return allPosts.map((post) => { - return { - loc: new URL(post.url, appConfig.url).href, - lastmod: new Date().toISOString(), - }; - }); -} - -function getDocsSitemap() { - return allDocumentationPages.map((page) => { - return { - loc: new URL(page.url, appConfig.url).href, - lastmod: new Date().toISOString(), - }; - }); -} diff --git a/apps/web/content/docs/001-getting_started/003-running-the-application/001-nextjs.mdx b/apps/web/content/docs/001-getting_started/003-running-the-application/001-nextjs.mdx deleted file mode 100644 index 7fbc498b7..000000000 --- a/apps/web/content/docs/001-getting_started/003-running-the-application/001-nextjs.mdx +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Running the Next.js Server -label: Next.js -description: Learn how to run the Next.js server on your local machine. ---- - -First, we can run the Next.js Server by running the following command: - -```bash -npm run dev -``` - -If everything goes well, your server should be running at -[http://localhost:3000](http://localhost:3000). - -With the server running, we can now set up our Supabase containers using -Docker. Jump to the next section to learn how to do that. \ No newline at end of file diff --git a/apps/web/content/docs/001-getting_started/003-running-the-application/002-supabase.mdx b/apps/web/content/docs/001-getting_started/003-running-the-application/002-supabase.mdx deleted file mode 100644 index a3d8571c9..000000000 --- a/apps/web/content/docs/001-getting_started/003-running-the-application/002-supabase.mdx +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: Running the Supabase Containers -label: Supabase -description: Running the Supabase containers locally for development ---- - -Before we can run the Supabase local environment, we need to run Docker, as Supabase uses it for running its local environment. - -You can use Docker Desktop, Colima, OrbStack, or any other Docker-compatible solution. - -### Running the Supabase Environment - -First, let's run the Supabase environment, which will spin up a local -instance using Docker. We can do this by running the following command: - -```bash -npm run supabase:start -``` - -Additionally, it imports the default seed data. We use it this data to -populate the database with some initial data and execute the E2E tests. - -After running the command above, you will be able to access the Supabase -Studio UI at [http://localhost:54323/](http://localhost:54323/). - -### Adding the Supabase Keys to the Environment Variables - -If this is the first time you run this command, we will need to get the -Supabase keys and add them to our local environment variables configuration -file `.env`. - -When running the command, we will see a message like this: - -```bash -> supabase start - -Applying migration 20221215192558_schema.sql... -Seeding data supabase/seed.sql... -Started supabase local development setup. - - API URL: http://localhost:54321 - DB URL: postgresql://postgres:postgres@localhost:54322/postgres - Studio URL: http://localhost:54323 - Inbucket URL: http://localhost:54324 - JWT secret: super-secret-jwt-token-with-at-least-32-characters-long - anon key: **************************************************** -service_role key: **************************************************** -``` - -Now, we need to copy the `anon key` and `service_role key` values and add -them to the `.env` file: - -``` -NEXT_PUBLIC_SUPABASE_ANON_KEY=**************************************************** -SUPABASE_SERVICE_ROLE_KEY=**************************************************** -``` - - -### Running the Stripe CLI - -Run the Stripe CLI with the following command: - -```bash -npm run stripe:listen -``` - -#### Add the Stripe Webhooks Key to your environment file - -If this is the first time you run this command, you will need to copy the Webhooks key printed on the console and add it to your development environment variables file: - -```bash title=".env.development" -STRIPE_WEBHOOKS_KEY= -``` - -#### Signing In for the first time - -You should now be able to sign in. To quickly get started, use the following credentials: - -``` -email = test@makerkit.dev -password = testingpassword -``` - -#### Email Confirmations - -When signing up, Supabase sends an email confirmation to a testing account. You can access the InBucket testing emails [using the following link](http://localhost:54324/monitor), and can follow the links to complete the sign up process. - - diff --git a/apps/web/content/docs/001-getting_started/003-running-the-application/003-stripe-cli.mdx b/apps/web/content/docs/001-getting_started/003-running-the-application/003-stripe-cli.mdx deleted file mode 100644 index 7bb5a85fd..000000000 --- a/apps/web/content/docs/001-getting_started/003-running-the-application/003-stripe-cli.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Running the Stripe CLI for Webhooks -label: Stripe -description: How to run the Stripe CLI for Webhooks in a local development environment ---- - -Run the Stripe CLI with the following command: - -```bash -npm run stripe:listen -``` - -#### Add the Stripe Webhooks Key to your environment file - -If this is the first time you run this command, you will need to copy the Webhooks key printed on the console and add it to your development environment variables file: - -```bash title=".env.development" -STRIPE_WEBHOOKS_KEY= -``` diff --git a/apps/web/content/docs/001-getting_started/003-running-the-application/index.mdx b/apps/web/content/docs/001-getting_started/003-running-the-application/index.mdx deleted file mode 100644 index 49e582b0a..000000000 --- a/apps/web/content/docs/001-getting_started/003-running-the-application/index.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Running the Application -label: Running the Application -description: How to run the application in development mode ---- - -After installing the modules, we can finally run the -application in development mode. - -We need to execute two commands (and an optional one for Stripe): - -1. **Next.js Server**: the first command is for running the Next.js server -2. **Supabase Environment**: the second command is for running the Supabase -environment with Docker -3. **Stripe CLI**: finally, the Stripe CLI is needed to dispatch webhooks to -our local server (optional, only needed when interacting with Stripe) - -## About this Documentation - -This documentation complements the Supabase one and is not meant to be a replacement. We recommend reading the Supabase documentation to get a better understanding of the Supabase concepts and how to use it. diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index 0710cda7b..2ae0d02f0 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -16,7 +16,8 @@ const INTERNAL_PACKAGES = [ '@kit/billing-gateway', '@kit/stripe', '@kit/email-templates', - '@kit/database-webhooks' + '@kit/database-webhooks', + '@kit/cms' ]; /** @type {import('next').NextConfig} */ diff --git a/apps/web/package.json b/apps/web/package.json index 6eba11dea..d5f20201c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -30,6 +30,7 @@ "@kit/supabase": "workspace:^", "@kit/team-accounts": "workspace:^", "@kit/ui": "workspace:^", + "@kit/cms": "workspace:^", "@next/mdx": "^14.1.4", "@radix-ui/react-icons": "^1.3.0", "@supabase/ssr": "^0.1.0", @@ -37,13 +38,11 @@ "@tanstack/react-query": "5.28.6", "@tanstack/react-query-next-experimental": "^5.28.9", "@tanstack/react-table": "^8.15.0", - "contentlayer": "0.3.4", "date-fns": "^3.6.0", "edge-csrf": "^1.0.9", "i18next": "^23.10.1", "i18next-resources-to-backend": "^1.2.0", - "next": "v14.2.0-canary.49", - "next-contentlayer": "0.3.4", + "next": "v14.2.0-canary.50", "next-sitemap": "^4.2.3", "next-themes": "0.3.0", "react": "18.2.0", @@ -51,8 +50,6 @@ "react-hook-form": "^7.51.2", "react-i18next": "^14.1.0", "recharts": "^2.12.3", - "rehype-autolink-headings": "^7.1.0", - "rehype-slug": "^6.0.0", "sonner": "^1.4.41", "tailwindcss-animate": "^1.0.7", "zod": "^3.22.4" diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 4f2c711a9..e795af41f 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -6,8 +6,7 @@ "~/*": ["./app/*"], "~/config/*": ["./config/*"], "~/components/*": ["./components/*"], - "~/lib/*": ["./lib/*"], - "contentlayer/generated": ["./.contentlayer/generated"] + "~/lib/*": ["./lib/*"] }, "plugins": [ { @@ -22,7 +21,7 @@ "*.ts", "*.tsx", "*.mjs", - "config/**/*.ts", + "./config/**/*.ts", "components/**/*.{tsx|ts}", "lib/**/*.ts", "app" diff --git a/package.json b/package.json index 795e49b0f..34b08d9aa 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "apps/*", "packages/*", "packages/features/*", + "packages/cms/*", "tooling/*", "supabase" ], diff --git a/packages/billing-gateway/src/components/current-plan-card.tsx b/packages/billing-gateway/src/components/current-plan-card.tsx index 70a9905ce..7d8663be1 100644 --- a/packages/billing-gateway/src/components/current-plan-card.tsx +++ b/packages/billing-gateway/src/components/current-plan-card.tsx @@ -70,7 +70,7 @@ export function CurrentPlanCard({ - +
-
- -
+ {/* + Only show the alert if the subscription requires action + (e.g. trial ending soon, subscription canceled, etc.) + */} + +
+ +
+
- - - - - + +
+ + + - - -
- - - +
+ + {subscription.trial_ends_at + ? formatDate(subscription.trial_ends_at, 'P') + : ''} + +
+
+
-
- - {subscription.trial_ends_at - ? formatDate(subscription.trial_ends_at, 'P') - : ''} - -
-
-
+ +
+ + + - -
- - - +
+ + {formatDate(subscription.period_ends_at ?? '', 'P')} + +
+
+
-
- - {formatDate(subscription.period_ends_at ?? '', 'P')} - -
-
-
+
+ + + -
- - - - - -
- -
- - - - -
    - {product.features.map((item) => { - return ( -
  • - - - - - -
  • - ); - })} -
-
- - - + +
diff --git a/packages/cms/contentlayer/README.md b/packages/cms/contentlayer/README.md new file mode 100644 index 000000000..69500c60b --- /dev/null +++ b/packages/cms/contentlayer/README.md @@ -0,0 +1,9 @@ +# CMS/Contentlayer - @kit/contentlayer + +Implementation of the CMS layer using the [Contentlayer](https://contentlayer.dev) library. + +This implementation is used when the host app's environment variable is set as: + +``` +CMS_TYPE=contentlayer +``` \ No newline at end of file diff --git a/apps/web/content/docs/001-getting_started/001-getting_started.mdx b/packages/cms/contentlayer/content/docs/001-getting_started/001-getting_started.mdx similarity index 100% rename from apps/web/content/docs/001-getting_started/001-getting_started.mdx rename to packages/cms/contentlayer/content/docs/001-getting_started/001-getting_started.mdx diff --git a/apps/web/content/docs/001-getting_started/002-clone-repository.mdx b/packages/cms/contentlayer/content/docs/001-getting_started/002-clone-repository.mdx similarity index 100% rename from apps/web/content/docs/001-getting_started/002-clone-repository.mdx rename to packages/cms/contentlayer/content/docs/001-getting_started/002-clone-repository.mdx diff --git a/apps/web/content/docs/001-getting_started/index.mdx b/packages/cms/contentlayer/content/docs/001-getting_started/index.mdx similarity index 91% rename from apps/web/content/docs/001-getting_started/index.mdx rename to packages/cms/contentlayer/content/docs/001-getting_started/index.mdx index 0169c7a4e..a84d8d0c1 100644 --- a/apps/web/content/docs/001-getting_started/index.mdx +++ b/packages/cms/contentlayer/content/docs/001-getting_started/index.mdx @@ -9,4 +9,4 @@ Makerkit is a Next.js/Remix SaaS Starter that helps you build your own SaaS in m In this section, we learn how to install and run the SaaS kit on your local machine. -Buckle up and let's get started! +Buckle up and let's get started! \ No newline at end of file diff --git a/apps/web/content/docs/002-authentication/001-configuration.mdx b/packages/cms/contentlayer/content/docs/002-authentication/001-configuration.mdx similarity index 100% rename from apps/web/content/docs/002-authentication/001-configuration.mdx rename to packages/cms/contentlayer/content/docs/002-authentication/001-configuration.mdx diff --git a/apps/web/content/docs/002-authentication/002-setup.mdx b/packages/cms/contentlayer/content/docs/002-authentication/002-setup.mdx similarity index 100% rename from apps/web/content/docs/002-authentication/002-setup.mdx rename to packages/cms/contentlayer/content/docs/002-authentication/002-setup.mdx diff --git a/apps/web/content/docs/002-authentication/index.mdx b/packages/cms/contentlayer/content/docs/002-authentication/index.mdx similarity index 100% rename from apps/web/content/docs/002-authentication/index.mdx rename to packages/cms/contentlayer/content/docs/002-authentication/index.mdx diff --git a/apps/web/content/posts/post-01.mdx b/packages/cms/contentlayer/content/posts/post-01.mdx similarity index 97% rename from apps/web/content/posts/post-01.mdx rename to packages/cms/contentlayer/content/posts/post-01.mdx index e4b98b3cb..32630c3c0 100644 --- a/apps/web/content/posts/post-01.mdx +++ b/packages/cms/contentlayer/content/posts/post-01.mdx @@ -4,6 +4,9 @@ date: 2021-12-24 live: false description: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. image: /assets/images/posts/lorem-ipsum.webp +author: John Doe +categories: + - posts --- ## Fecerat avis invenio mentis diff --git a/apps/web/contentlayer.config.js b/packages/cms/contentlayer/contentlayer.config.js similarity index 87% rename from apps/web/contentlayer.config.js rename to packages/cms/contentlayer/contentlayer.config.js index 3d24a07a3..2251a15cc 100644 --- a/apps/web/contentlayer.config.js +++ b/packages/cms/contentlayer/contentlayer.config.js @@ -19,6 +19,11 @@ export const Post = defineDocumentType(() => ({ description: 'The date of the post', required: true, }, + author: { + type: 'string', + description: 'The author of the post', + required: true, + }, live: { type: 'boolean', description: 'Whether the post is live or not', @@ -33,12 +38,22 @@ export const Post = defineDocumentType(() => ({ type: 'string', description: 'The description of the post', }, + tags: { + type: 'list', + required: false, + of: { + type: 'string', + }, + }, + categories: { + type: 'list', + required: false, + of: { + type: 'string', + }, + }, }, computedFields: { - url: { - type: 'string', - resolve: (post) => `/blog/${getSlug(post._raw.sourceFileName)}`, - }, readingTime: { type: 'number', resolve: (post) => calculateReadingTime(post.body.raw), @@ -47,6 +62,10 @@ export const Post = defineDocumentType(() => ({ type: 'string', resolve: (post) => getSlug(post._raw.sourceFileName), }, + url: { + type: 'string', + resolve: (post) => `/blog/${getSlug(post._raw.sourceFileName)}`, + }, structuredData: { type: 'object', resolve: (doc) => ({ @@ -57,7 +76,6 @@ export const Post = defineDocumentType(() => ({ dateModified: doc.date, description: doc.description, image: [siteUrl, doc.image].join(''), - url: [siteUrl, 'blog', doc._raw.flattenedPath].join('/'), author: { '@type': 'Organization', name: `Makerkit`, @@ -82,43 +100,30 @@ export const DocumentationPage = defineDocumentType(() => ({ description: 'The label of the page in the sidebar', required: true, }, - cardCTA: { - type: 'string', - description: 'The label of the CTA link on the card', - required: false, - }, description: { type: 'string', description: 'The description of the post', }, - show_child_cards: { - type: 'boolean', - default: false, - }, - collapsible: { - type: 'boolean', + tags: { + type: 'list', required: false, - default: false, + of: { + type: 'string', + }, }, - collapsed: { - type: 'boolean', + categories: { + type: 'list', required: false, - default: false, + of: { + type: 'string', + }, }, }, computedFields: { - url: { - type: 'string', - resolve: (post) => `/blog/${getSlug(post._raw.sourceFileName)}`, - }, readingTime: { type: 'number', resolve: (post) => calculateReadingTime(post.body.raw), }, - slug: { - type: 'string', - resolve: (post) => getSlug(post._raw.sourceFileName), - }, structuredData: { type: 'object', resolve: (doc) => ({ @@ -150,13 +155,24 @@ export const DocumentationPage = defineDocumentType(() => ({ type: 'json', resolve: (doc) => getPathSegments(doc).map(getMetaFromFolderName), }, - resolvedPath: { + slug: { type: 'string', - resolve: (doc) => { - return getPathSegments(doc) + resolve: (doc) => + getPathSegments(doc) .map(getMetaFromFolderName) .map(({ pathName }) => pathName) - .join('/'); + .join('/'), + }, + url: { + type: 'string', + resolve: (doc) => { + return ( + '/docs/' + + getPathSegments(doc) + .map(getMetaFromFolderName) + .map(({ pathName }) => pathName) + .join('/') + ); }, }, }, diff --git a/packages/cms/contentlayer/package.json b/packages/cms/contentlayer/package.json new file mode 100644 index 000000000..42ca7fe6d --- /dev/null +++ b/packages/cms/contentlayer/package.json @@ -0,0 +1,45 @@ +{ + "name": "@kit/contentlayer", + "private": true, + "version": "0.1.0", + "scripts": { + "clean": "git clean -xdf .turbo node_modules", + "format": "prettier --check \"**/*.{ts,tsx}\"", + "lint": "eslint .", + "typecheck": "tsc --noEmit", + "build": "contentlayer build" + }, + "prettier": "@kit/prettier-config", + "exports": { + ".": "./src/index.ts" + }, + "dependencies": { + "contentlayer": "0.3.4", + "next-contentlayer": "0.3.4", + "rehype-slug": "^6.0.0", + "rehype-autolink-headings": "^6.0.0" + }, + "peerDependencies": { + "@kit/cms": "workspace:^", + "@kit/ui": "workspace:^" + }, + "devDependencies": { + "@kit/eslint-config": "workspace:*", + "@kit/prettier-config": "workspace:*", + "@kit/tsconfig": "workspace:*" + }, + "eslintConfig": { + "root": true, + "extends": [ + "@kit/eslint-config/base", + "@kit/eslint-config/react" + ] + }, + "typesVersions": { + "*": { + "*": [ + "src/*" + ] + } + } +} \ No newline at end of file diff --git a/packages/cms/contentlayer/src/client.ts b/packages/cms/contentlayer/src/client.ts new file mode 100644 index 000000000..3873a9579 --- /dev/null +++ b/packages/cms/contentlayer/src/client.ts @@ -0,0 +1,192 @@ +import { Cms, CmsClient } from '@kit/cms'; + +import type { DocumentationPage, Post } from '../.contentlayer/generated'; + +async function getAllContentItems() { + const { allDocumentationPages, allPosts } = await import( + '../.contentlayer/generated' + ); + + return [ + ...allPosts.map((item) => { + return { ...item, type: 'post' }; + }), + ...allDocumentationPages.map((item) => { + return { ...item, type: 'page', categories: ['documentation'] }; + }), + ]; +} + +/** + * A class that represents a Content Layer CMS client. + * This class implements the base CmsClient class. + * + * @class ContentlayerClient + * @extends {CmsClient} + */ +export class ContentlayerClient implements CmsClient { + async getContentItems(options?: Cms.GetContentItemsOptions) { + const allContentItems = await getAllContentItems(); + const { startOffset, endOffset } = this.getOffset(options); + + const promise = allContentItems + .filter((item) => { + const tagMatch = options?.tags + ? item.tags?.some((tag) => options.tags?.includes(tag)) + : true; + + const categoryMatch = options?.categories + ? item.categories?.some((category) => + options.categories?.includes(category), + ) + : true; + + const typeMatch = options?.type ? item.type === options.type : true; + const path = item._raw.flattenedPath; + const splitPath = path.split('/'); + + const depthMatch = + options?.depth !== undefined + ? splitPath.length - 1 === options.depth + : true; + + return tagMatch && categoryMatch && typeMatch && depthMatch; + }) + .slice(startOffset, endOffset) + .map((post) => { + const children: Cms.ContentItem[] = []; + + for (const item of allContentItems) { + if (item.url.startsWith(post.url + '/')) { + children.push(this.mapPost(item)); + } + } + + return this.mapPost(post, children); + }); + + return Promise.resolve(promise); + } + + async getContentItemById(id: string) { + const allContentItems = await getAllContentItems(); + const post = allContentItems.find((item) => item.slug === id); + + if (!post) { + return Promise.resolve(undefined); + } + + const children: Cms.ContentItem[] = []; + + for (const item of allContentItems) { + if (item.url.startsWith(post.url + '/')) { + children.push(this.mapPost(item)); + } + } + + return Promise.resolve(post ? this.mapPost(post, children) : undefined); + } + + async getCategoryBySlug(slug: string) { + return Promise.resolve({ + id: slug, + name: slug, + slug, + }); + } + + async getTagBySlug(slug: string) { + return Promise.resolve({ + id: slug, + name: slug, + slug, + }); + } + + async getCategories(options?: Cms.GetCategoriesOptions) { + const { startOffset, endOffset } = this.getOffset(options); + const allContentItems = await getAllContentItems(); + + const categories = allContentItems + .filter((item) => { + if (options?.type) { + return item.type === options.type; + } + + return true; + }) + .slice(startOffset, endOffset) + .flatMap((post) => post.categories) + .filter((category): category is string => !!category) + .map((category) => ({ + id: category, + name: category, + slug: category, + })); + + return Promise.resolve(categories); + } + + async getTags(options?: Cms.GetTagsOptions) { + const { startOffset, endOffset } = this.getOffset(options); + const allContentItems = await getAllContentItems(); + + const tags = allContentItems + .filter((item) => { + if (options?.type) { + return item.type === options.type; + } + + return true; + }) + .slice(startOffset, endOffset) + .flatMap((post) => post.tags) + .filter((tag): tag is string => !!tag) + .map((tag) => ({ + id: tag, + name: tag, + slug: tag, + })); + + return Promise.resolve(tags); + } + + private getOffset(options?: { offset?: number; limit?: number }) { + const startOffset = options?.offset ?? 0; + const endOffset = options?.limit ? startOffset + options.limit : undefined; + + return { startOffset, endOffset }; + } + + private mapPost( + post: Post | DocumentationPage, + children: Array = [], + ): Cms.ContentItem { + console.log(post); + return { + id: post.slug, + title: post.title, + description: post.description ?? '', + content: post.body?.code, + image: 'image' in post ? post.image : undefined, + publishedAt: 'date' in post ? new Date(post.date) : new Date(), + parentId: 'parentId' in post ? post.parentId : undefined, + url: post.url, + slug: post.slug, + author: 'author' in post ? post.author : '', + children: children.map((child) => this.mapPost(child)), + categories: + post.categories?.map((category) => ({ + id: category, + name: category, + slug: category, + })) ?? [], + tags: + post.tags?.map((tag) => ({ + id: tag, + name: tag, + slug: tag, + })) ?? [], + }; + } +} diff --git a/packages/cms/contentlayer/src/content-renderer.tsx b/packages/cms/contentlayer/src/content-renderer.tsx new file mode 100644 index 000000000..6b08019dc --- /dev/null +++ b/packages/cms/contentlayer/src/content-renderer.tsx @@ -0,0 +1,5 @@ +import { Mdx } from './mdx/mdx-renderer'; + +export function ContentRenderer(props: { content: string }) { + return ; +} diff --git a/packages/cms/contentlayer/src/index.ts b/packages/cms/contentlayer/src/index.ts new file mode 100644 index 000000000..7399b62ba --- /dev/null +++ b/packages/cms/contentlayer/src/index.ts @@ -0,0 +1,3 @@ +export * from './client'; +export * from './mdx/mdx-renderer'; +export * from './content-renderer'; diff --git a/packages/ui/src/makerkit/mdx/mdx-renderer.module.css b/packages/cms/contentlayer/src/mdx/mdx-renderer.module.css similarity index 100% rename from packages/ui/src/makerkit/mdx/mdx-renderer.module.css rename to packages/cms/contentlayer/src/mdx/mdx-renderer.module.css diff --git a/packages/ui/src/makerkit/mdx/mdx-renderer.tsx b/packages/cms/contentlayer/src/mdx/mdx-renderer.tsx similarity index 61% rename from packages/ui/src/makerkit/mdx/mdx-renderer.tsx rename to packages/cms/contentlayer/src/mdx/mdx-renderer.tsx index 9540fb302..b0025a213 100644 --- a/packages/ui/src/makerkit/mdx/mdx-renderer.tsx +++ b/packages/cms/contentlayer/src/mdx/mdx-renderer.tsx @@ -1,7 +1,8 @@ -import type { MDXComponents } from 'mdx/types'; +import type { MDXComponents as MDXComponentsType } from 'mdx/types'; import { getMDXComponent } from 'next-contentlayer/hooks'; -import Components from './mdx-components'; +import { MDXComponents } from '@kit/ui/mdx-components'; + // @ts-ignore: ignore weird error import styles from './mdx-renderer.module.css'; @@ -14,7 +15,7 @@ export function Mdx({ return (
- +
); } diff --git a/packages/cms/contentlayer/tsconfig.json b/packages/cms/contentlayer/tsconfig.json new file mode 100644 index 000000000..c92857ed3 --- /dev/null +++ b/packages/cms/contentlayer/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@kit/tsconfig/base.json", + "compilerOptions": { + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/packages/cms/core/README.md b/packages/cms/core/README.md new file mode 100644 index 000000000..9f552a43e --- /dev/null +++ b/packages/cms/core/README.md @@ -0,0 +1,3 @@ +# CMS - @kit/cms + +CMS abstraction layer for the Makerkit framework. \ No newline at end of file diff --git a/packages/cms/core/package.json b/packages/cms/core/package.json new file mode 100644 index 000000000..99632d524 --- /dev/null +++ b/packages/cms/core/package.json @@ -0,0 +1,38 @@ +{ + "name": "@kit/cms", + "private": true, + "version": "0.1.0", + "scripts": { + "clean": "git clean -xdf .turbo node_modules", + "format": "prettier --check \"**/*.{ts,tsx}\"", + "lint": "eslint .", + "typecheck": "tsc --noEmit" + }, + "prettier": "@kit/prettier-config", + "exports": { + ".": "./src/index.ts" + }, + "peerDependencies": { + "@kit/contentlayer": "workspace:*", + "@kit/wordpress": "workspace:*" + }, + "devDependencies": { + "@kit/eslint-config": "workspace:*", + "@kit/prettier-config": "workspace:*", + "@kit/tsconfig": "workspace:*" + }, + "eslintConfig": { + "root": true, + "extends": [ + "@kit/eslint-config/base", + "@kit/eslint-config/react" + ] + }, + "typesVersions": { + "*": { + "*": [ + "src/*" + ] + } + } +} \ No newline at end of file diff --git a/packages/cms/core/src/cms-client.ts b/packages/cms/core/src/cms-client.ts new file mode 100644 index 000000000..057298645 --- /dev/null +++ b/packages/cms/core/src/cms-client.ts @@ -0,0 +1,72 @@ +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Cms { + export type ContentType = 'post' | 'page'; + + export interface ContentItem { + id: string; + title: string; + type: ContentType; + url: string; + description: string | undefined; + content: string; + author: string; + publishedAt: Date; + image: string | undefined; + slug: string; + categories: Category[]; + tags: Tag[]; + parentId?: string; + children?: ContentItem[]; + } + + export interface Category { + id: string; + name: string; + slug: string; + } + + export interface Tag { + id: string; + name: string; + slug: string; + } + + export interface GetContentItemsOptions { + type?: ContentType; + limit?: number; + offset?: number; + categories?: string[]; + tags?: string[]; + depth?: number; + } + + export interface GetCategoriesOptions { + type?: ContentType; + limit?: number; + offset?: number; + } + + export interface GetTagsOptions { + type?: ContentType; + limit?: number; + offset?: number; + } +} + +export abstract class CmsClient { + abstract getContentItems( + options?: Cms.GetContentItemsOptions, + ): Promise; + + abstract getContentItemById(id: string): Promise; + + abstract getCategories( + options?: Cms.GetCategoriesOptions, + ): Promise; + + abstract getCategoryBySlug(slug: string): Promise; + + abstract getTags(options?: Cms.GetTagsOptions): Promise; + + abstract getTagBySlug(slug: string): Promise; +} diff --git a/packages/cms/core/src/cms.type.ts b/packages/cms/core/src/cms.type.ts new file mode 100644 index 000000000..91b3ad08d --- /dev/null +++ b/packages/cms/core/src/cms.type.ts @@ -0,0 +1,3 @@ +// we can add more types here if we have more CMSs +// ex. export type CmsType = 'contentlayer' | 'other-cms'; +export type CmsType = 'contentlayer' | 'wordpress'; diff --git a/packages/cms/core/src/content-renderer.tsx b/packages/cms/core/src/content-renderer.tsx new file mode 100644 index 000000000..37e16a4e5 --- /dev/null +++ b/packages/cms/core/src/content-renderer.tsx @@ -0,0 +1,17 @@ +import { CmsType } from './cms.type'; + +export async function ContentRenderer({ + content, + type = process.env.CMS_CLIENT as CmsType, +}: { + content: string; + type?: CmsType; +}) { + switch (type) { + case 'contentlayer': { + const { ContentRenderer } = await import('@kit/contentlayer'); + + return ContentRenderer({ content }); + } + } +} diff --git a/packages/cms/core/src/create-cms-client.ts b/packages/cms/core/src/create-cms-client.ts new file mode 100644 index 000000000..52cf7417c --- /dev/null +++ b/packages/cms/core/src/create-cms-client.ts @@ -0,0 +1,36 @@ +import { CmsClient } from './cms-client'; +import { CmsType } from './cms.type'; + +/** + * Creates a CMS client based on the specified type. + * + * @param {CmsType} type - The type of CMS client to create. Defaults to the value of the CMS_CLIENT environment variable. + * @returns {Promise} A Promise that resolves to the created CMS client. + * @throws {Error} If the specified CMS type is unknown. + */ +export async function createCmsClient( + type: CmsType = process.env.CMS_CLIENT as CmsType, +): Promise { + switch (type) { + case 'contentlayer': + return getContentLayerClient(); + + case 'wordpress': + return getWordpressClient(); + + default: + throw new Error(`Unknown CMS type: ${type}`); + } +} + +async function getContentLayerClient() { + const { ContentlayerClient } = await import('@kit/contentlayer'); + + return new ContentlayerClient(); +} + +async function getWordpressClient() { + const { WordpressClient } = await import('@kit/wordpress'); + + return new WordpressClient(); +} diff --git a/packages/cms/core/src/index.ts b/packages/cms/core/src/index.ts new file mode 100644 index 000000000..3f75ccf29 --- /dev/null +++ b/packages/cms/core/src/index.ts @@ -0,0 +1,4 @@ +export * from './cms-client'; +export * from './create-cms-client'; +export * from './cms.type'; +export * from './content-renderer'; diff --git a/packages/cms/core/tsconfig.json b/packages/cms/core/tsconfig.json new file mode 100644 index 000000000..c92857ed3 --- /dev/null +++ b/packages/cms/core/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@kit/tsconfig/base.json", + "compilerOptions": { + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/packages/cms/wordpress/README.md b/packages/cms/wordpress/README.md new file mode 100644 index 000000000..d71ba7e39 --- /dev/null +++ b/packages/cms/wordpress/README.md @@ -0,0 +1,9 @@ +# CMS/Wordpress - @kit/wordpress + +Implementation of the CMS layer using the [Wordpress](https://wordpress.org) library. [WIP - not yet working] + +This implementation is used when the host app's environment variable is set as: + +``` +CMS_TYPE=wordpress +``` \ No newline at end of file diff --git a/packages/cms/wordpress/package.json b/packages/cms/wordpress/package.json new file mode 100644 index 000000000..a704ef61c --- /dev/null +++ b/packages/cms/wordpress/package.json @@ -0,0 +1,41 @@ +{ + "name": "@kit/wordpress", + "private": true, + "version": "0.1.0", + "scripts": { + "clean": "git clean -xdf .turbo node_modules", + "format": "prettier --check \"**/*.{ts,tsx}\"", + "lint": "eslint .", + "typecheck": "tsc --noEmit", + "build": "contentlayer build" + }, + "prettier": "@kit/prettier-config", + "exports": { + ".": "./src/index.ts" + }, + "dependencies": {}, + "peerDependencies": { + "@kit/cms": "workspace:^", + "@kit/ui": "workspace:^" + }, + "devDependencies": { + "@kit/eslint-config": "workspace:*", + "@kit/prettier-config": "workspace:*", + "@kit/tsconfig": "workspace:*", + "wp-types": "^3.64.0" + }, + "eslintConfig": { + "root": true, + "extends": [ + "@kit/eslint-config/base", + "@kit/eslint-config/react" + ] + }, + "typesVersions": { + "*": { + "*": [ + "src/*" + ] + } + } +} \ No newline at end of file diff --git a/packages/cms/wordpress/src/index.ts b/packages/cms/wordpress/src/index.ts new file mode 100644 index 000000000..3f535eac4 --- /dev/null +++ b/packages/cms/wordpress/src/index.ts @@ -0,0 +1 @@ +export * from './wp-client'; \ No newline at end of file diff --git a/packages/cms/wordpress/src/wp-client.ts b/packages/cms/wordpress/src/wp-client.ts new file mode 100644 index 000000000..f8fb2d897 --- /dev/null +++ b/packages/cms/wordpress/src/wp-client.ts @@ -0,0 +1,284 @@ +import type { + WP_REST_API_Category, + WP_REST_API_Post, + WP_REST_API_Tag, +} from 'wp-types'; + +import { Cms, CmsClient } from '@kit/cms'; + +import GetTagsOptions = Cms.GetTagsOptions; + +/** + * @name WordpressClient + * @description Represents a client for interacting with a Wordpress CMS. + * Implements the CmsClient interface. + */ +export class WordpressClient implements CmsClient { + private readonly apiUrl: string; + + constructor(apiUrl = process.env.WORDPRESS_API_URL as string) { + this.apiUrl = apiUrl; + } + + async getContentItems(options?: Cms.GetContentItemsOptions) { + let endpoint: string; + + switch (options?.type) { + case 'post': + endpoint = '/wp-json/wp/v2/posts'; + break; + + case 'page': + endpoint = '/wp-json/wp/v2/pages'; + break; + + default: + endpoint = '/wp-json/wp/v2/posts'; + } + + const url = new URL(this.apiUrl + endpoint); + + if (options?.limit) { + url.searchParams.append('per_page', options.limit.toString()); + } + + if (options?.offset) { + url.searchParams.append('offset', options.offset.toString()); + } + + if (options?.categories) { + url.searchParams.append('categories', options.categories.join(',')); + } + + if (options?.tags) { + url.searchParams.append('tags', options.tags.join(',')); + } + + const response = await fetch(url.toString()); + const data = (await response.json()) as WP_REST_API_Post[]; + + return Promise.all( + data.map(async (item) => { + // Fetch author, categories, and tags as before... + + let parentId: string | undefined; + + if (item.parent) { + parentId = item.parent.toString(); + } + + let children: Cms.ContentItem[] = []; + + const embeddedChildren = ( + item._embedded ? item._embedded['wp:children'] : [] + ) as WP_REST_API_Post[]; + + if (options?.depth && options.depth > 0) { + children = await Promise.all( + embeddedChildren.map(async (child) => { + const childAuthor = await this.getAuthor(child.author); + + const childCategories = await this.getCategoriesByIds( + child.categories ?? [], + ); + + const childTags = await this.getTagsByIds(child.tags ?? []); + + return { + id: child.id.toString(), + title: child.title.rendered, + type: child.type as Cms.ContentType, + image: child.featured_media, + description: child.excerpt.rendered, + url: child.link, + content: child.content.rendered, + slug: child.slug, + publishedAt: new Date(child.date), + author: childAuthor?.name, + categories: childCategories.map((category) => category.name), + tags: childTags.map((tag) => tag.name), + parentId: child.parent?.toString(), + }; + }), + ); + } + + const author = await this.getAuthor(item.author); + const categories = await this.getCategoriesByIds(item.categories ?? []); + const tags = await this.getTagsByIds(item.tags ?? []); + + return { + id: item.id.toString(), + title: item.title.rendered, + content: item.content.rendered, + description: item.excerpt.rendered, + image: item.featured_media, + url: item.link, + slug: item.slug, + publishedAt: new Date(item.date), + author: author?.name, + categories: categories.map((category) => category.name), + tags: tags.map((tag) => tag.name), + type: item.type as Cms.ContentType, + parentId, + children, + }; + }), + ); + } + + async getContentItemById(slug: string) { + const url = `${this.apiUrl}/wp-json/wp/v2/posts?slug=${slug}`; + const response = await fetch(url); + const data = (await response.json()) as WP_REST_API_Post[]; + const item = data[0]; + + if (!item) { + return; + } + + const author = await this.getAuthor(item.author); + const categories = await this.getCategoriesByIds(item.categories ?? []); + const tags = await this.getTagsByIds(item.tags ?? []); + + return { + id: item.id, + image: item.featured_media, + url: item.link, + description: item.excerpt.rendered, + type: item.type as Cms.ContentType, + children: [], + title: item.title.rendered, + content: item.content.rendered, + slug: item.slug, + publishedAt: new Date(item.date), + author: author?.name, + categories: categories.map((category) => category.name), + tags: tags.map((tag) => tag.name), + }; + } + + async getCategoryBySlug(slug: string) { + const url = `${this.apiUrl}/wp-json/wp/v2/categories?slug=${slug}`; + const response = await fetch(url); + const data = await response.json(); + + if (data.length === 0) { + return; + } + + const item = data[0] as WP_REST_API_Category; + + return { + id: item.id.toString(), + name: item.name, + slug: item.slug, + }; + } + + async getTagBySlug(slug: string) { + const url = `${this.apiUrl}/wp-json/wp/v2/tags?slug=${slug}`; + const response = await fetch(url); + const data = await response.json(); + + if (data.length === 0) { + return; + } + + const item = data[0] as WP_REST_API_Tag; + + return { + id: item.id.toString(), + name: item.name, + slug: item.slug, + }; + } + + async getCategories(options?: Cms.GetCategoriesOptions) { + const queryParams = new URLSearchParams(); + + if (options?.limit) { + queryParams.append('per_page', options.limit.toString()); + } + + if (options?.offset) { + queryParams.append('offset', options.offset.toString()); + } + + const response = await fetch( + `${this.apiUrl}/wp-json/wp/v2/categories?${queryParams.toString()}`, + ); + + const data = (await response.json()) as WP_REST_API_Category[]; + + return data.map((item) => ({ + id: item.id.toString(), + name: item.name, + slug: item.slug, + })); + } + + async getTags(options: GetTagsOptions) { + const queryParams = new URLSearchParams(); + + if (options?.limit) { + queryParams.append('per_page', options.limit.toString()); + } + + if (options?.offset) { + queryParams.append('offset', options.offset.toString()); + } + + const response = await fetch( + `${this.apiUrl}/wp-json/wp/v2/tags?${queryParams.toString()}`, + ); + + const data = (await response.json()) as WP_REST_API_Tag[]; + + return data.map((item) => ({ + id: item.id.toString(), + name: item.name, + slug: item.slug, + })); + } + + private async getTagsByIds(ids: number[]) { + const promises = ids.map((id) => + fetch(`${this.apiUrl}/wp-json/wp/v2/tags/${id}`), + ); + + const responses = await Promise.all(promises); + + const data = (await Promise.all( + responses.map((response) => response.json()), + )) as WP_REST_API_Tag[]; + + return data.map((item) => ({ id: item.id, name: item.name })); + } + + private async getCategoriesByIds(ids: number[]) { + const promises = ids.map((id) => + fetch(`${this.apiUrl}/wp-json/wp/v2/categories/${id}`), + ); + + const responses = await Promise.all(promises); + + const data = (await Promise.all( + responses.map((response) => response.json()), + )) as WP_REST_API_Category[]; + + return data.map((item) => ({ id: item.id, name: item.name })); + } + + private async getAuthor(id: number) { + const response = await fetch(`${this.apiUrl}/wp-json/wp/v2/users/${id}`); + + if (!response.ok) { + return undefined; + } + + const data = await response.json(); + + return { name: data.name }; + } +} diff --git a/packages/cms/wordpress/tsconfig.json b/packages/cms/wordpress/tsconfig.json new file mode 100644 index 000000000..c92857ed3 --- /dev/null +++ b/packages/cms/wordpress/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@kit/tsconfig/base.json", + "compilerOptions": { + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 62d80bd35..f4f491755 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -119,7 +119,7 @@ "./auth-change-listener": "./src/makerkit/auth-change-listener.tsx", "./loading-overlay": "./src/makerkit/loading-overlay.tsx", "./profile-avatar": "./src/makerkit/profile-avatar.tsx", - "./mdx": "./src/makerkit/mdx/mdx-renderer.tsx", + "./mdx-components": "./src/makerkit/mdx-components.tsx", "./mode-toggle": "./src/makerkit/mode-toggle.tsx" }, "typesVersions": { diff --git a/packages/ui/src/makerkit/AuthenticityToken.tsx b/packages/ui/src/makerkit/authenticity-token.tsx similarity index 100% rename from packages/ui/src/makerkit/AuthenticityToken.tsx rename to packages/ui/src/makerkit/authenticity-token.tsx diff --git a/packages/ui/src/makerkit/mdx/mdx-components.tsx b/packages/ui/src/makerkit/mdx-components.tsx similarity index 91% rename from packages/ui/src/makerkit/mdx/mdx-components.tsx rename to packages/ui/src/makerkit/mdx-components.tsx index c046d3ac0..09fd0a21c 100644 --- a/packages/ui/src/makerkit/mdx/mdx-components.tsx +++ b/packages/ui/src/makerkit/mdx-components.tsx @@ -2,8 +2,8 @@ import { forwardRef } from 'react'; import Image from 'next/image'; -import { cn } from '../../utils'; -import { LazyRender } from '../lazy-render'; +import { cn } from '../utils'; +import { LazyRender } from './lazy-render'; const NextImage: React.FC<{ width: number; @@ -72,11 +72,9 @@ const Video: React.FC<{ ); }; -const Components = { +export const MDXComponents = { img: NextImage, a: ExternalLink, Video, Image: NextImage, }; - -export default Components; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d30d6357..f43ddb9b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: '@kit/billing-gateway': specifier: workspace:^ version: link:../../packages/billing-gateway + '@kit/cms': + specifier: workspace:^ + version: link:../../packages/cms/core '@kit/database-webhooks': specifier: workspace:^ version: link:../../packages/database-webhooks @@ -94,19 +97,16 @@ importers: version: 5.28.6(react@18.2.0) '@tanstack/react-query-next-experimental': specifier: ^5.28.9 - version: 5.28.9(@tanstack/react-query@5.28.6)(next@14.2.0-canary.49)(react@18.2.0) + version: 5.28.9(@tanstack/react-query@5.28.6)(next@14.2.0-canary.50)(react@18.2.0) '@tanstack/react-table': specifier: ^8.15.0 version: 8.15.0(react-dom@18.2.0)(react@18.2.0) - contentlayer: - specifier: 0.3.4 - version: 0.3.4(esbuild@0.20.2) date-fns: specifier: ^3.6.0 version: 3.6.0 edge-csrf: specifier: ^1.0.9 - version: 1.0.9(next@14.2.0-canary.49) + version: 1.0.9(next@14.2.0-canary.50) i18next: specifier: ^23.10.1 version: 23.10.1 @@ -114,14 +114,11 @@ importers: specifier: ^1.2.0 version: 1.2.0 next: - specifier: v14.2.0-canary.49 - version: 14.2.0-canary.49(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) - next-contentlayer: - specifier: 0.3.4 - version: 0.3.4(contentlayer@0.3.4)(esbuild@0.20.2)(next@14.2.0-canary.49)(react-dom@18.2.0)(react@18.2.0) + specifier: v14.2.0-canary.50 + version: 14.2.0-canary.50(react-dom@18.2.0)(react@18.2.0) next-sitemap: specifier: ^4.2.3 - version: 4.2.3(next@14.2.0-canary.49) + version: 4.2.3(next@14.2.0-canary.50) next-themes: specifier: 0.3.0 version: 0.3.0(react-dom@18.2.0)(react@18.2.0) @@ -140,12 +137,6 @@ importers: recharts: specifier: ^2.12.3 version: 2.12.3(react-dom@18.2.0)(react@18.2.0) - rehype-autolink-headings: - specifier: ^7.1.0 - version: 7.1.0 - rehype-slug: - specifier: ^6.0.0 - version: 6.0.0 sonner: specifier: ^1.4.41 version: 1.4.41(react-dom@18.2.0)(react@18.2.0) @@ -268,6 +259,78 @@ importers: specifier: ^3.22.4 version: 3.22.4 + packages/cms/contentlayer: + dependencies: + '@kit/cms': + specifier: workspace:^ + version: link:../core + '@kit/ui': + specifier: workspace:^ + version: link:../../ui + contentlayer: + specifier: 0.3.4 + version: 0.3.4(esbuild@0.20.2) + next-contentlayer: + specifier: 0.3.4 + version: 0.3.4(contentlayer@0.3.4)(esbuild@0.20.2)(next@13.5.6)(react-dom@18.2.0)(react@18.2.0) + rehype-autolink-headings: + specifier: ^6.0.0 + version: 6.1.1 + rehype-slug: + specifier: ^6.0.0 + version: 6.0.0 + devDependencies: + '@kit/eslint-config': + specifier: workspace:* + version: link:../../../tooling/eslint + '@kit/prettier-config': + specifier: workspace:* + version: link:../../../tooling/prettier + '@kit/tsconfig': + specifier: workspace:* + version: link:../../../tooling/typescript + + packages/cms/core: + dependencies: + '@kit/contentlayer': + specifier: workspace:* + version: link:../contentlayer + '@kit/wordpress': + specifier: workspace:* + version: link:../wordpress + devDependencies: + '@kit/eslint-config': + specifier: workspace:* + version: link:../../../tooling/eslint + '@kit/prettier-config': + specifier: workspace:* + version: link:../../../tooling/prettier + '@kit/tsconfig': + specifier: workspace:* + version: link:../../../tooling/typescript + + packages/cms/wordpress: + dependencies: + '@kit/cms': + specifier: workspace:^ + version: link:../core + '@kit/ui': + specifier: workspace:^ + version: link:../../ui + devDependencies: + '@kit/eslint-config': + specifier: workspace:* + version: link:../../../tooling/eslint + '@kit/prettier-config': + specifier: workspace:* + version: link:../../../tooling/prettier + '@kit/tsconfig': + specifier: workspace:* + version: link:../../../tooling/typescript + wp-types: + specifier: ^3.64.0 + version: 3.64.0 + packages/database-webhooks: dependencies: '@kit/billing-gateway': @@ -2074,8 +2137,8 @@ packages: resolution: {integrity: sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==} dev: false - /@next/env@14.2.0-canary.49: - resolution: {integrity: sha512-rQaBRv0PRO3+4lx90zB9eBL0xk230G+6avgCyBL272hckH4XsGgXY6adtBBmZJF1QuDI+pS+DqppXSJvfexsdw==} + /@next/env@14.2.0-canary.50: + resolution: {integrity: sha512-COLktqbQGmSANtTTKVs4heykkT4YSLM+GU1CbHKpSXnyEP98yrWcfMTMeTwcEZCgilvI1gPT5zVO/ISU1o/X5A==} dev: false /@next/eslint-plugin-next@14.1.4: @@ -2098,6 +2161,15 @@ packages: source-map: 0.7.4 dev: false + /@next/swc-darwin-arm64@13.5.6: + resolution: {integrity: sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-darwin-arm64@14.1.0: resolution: {integrity: sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==} engines: {node: '>= 10'} @@ -2107,8 +2179,8 @@ packages: dev: false optional: true - /@next/swc-darwin-arm64@14.2.0-canary.49: - resolution: {integrity: sha512-tFFCgRJOk28rIiEGjz2bafqp3G5lV7hXyYjZ7d+gt/MjpLRrtTwu+lRBv/W1VFdTkPv8+k2hvXZNNTHO1n57Ow==} + /@next/swc-darwin-arm64@14.2.0-canary.50: + resolution: {integrity: sha512-el2drGIjRNuLqqahuCoKou50pEqacrcGvhOphiU8wQPWOku3d762sN9pTyunyLVshjLNOI/gDxh1Ja2dDcmXzg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -2116,6 +2188,15 @@ packages: dev: false optional: true + /@next/swc-darwin-x64@13.5.6: + resolution: {integrity: sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-darwin-x64@14.1.0: resolution: {integrity: sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==} engines: {node: '>= 10'} @@ -2125,8 +2206,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@14.2.0-canary.49: - resolution: {integrity: sha512-NR4Meb67q8M2pNP5a8Tp3Zfar2Ao8ChHWcD3wEBgICcgJ4ZyCQCWXdM+VBsf8a3yuAoXmu1/cwOwWu1KXVC96A==} + /@next/swc-darwin-x64@14.2.0-canary.50: + resolution: {integrity: sha512-gulXuO14RZODSB3hU+Rb+CHWymH7kGAcvsP7SA95wUXx2CAuugFfI90sHL4ieQib5N058DoIQPvYILiqP9RpxQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -2134,6 +2215,15 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-gnu@13.5.6: + resolution: {integrity: sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-arm64-gnu@14.1.0: resolution: {integrity: sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==} engines: {node: '>= 10'} @@ -2143,8 +2233,17 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@14.2.0-canary.49: - resolution: {integrity: sha512-2bFQUNYnz6L7xOAzvejMj09iqmWwkjFyguGEfmNiFN0kPgJ4viSCKZvoiuG/MPh3VoDSz5N2qx1tehSCy7KbFA==} + /@next/swc-linux-arm64-gnu@14.2.0-canary.50: + resolution: {integrity: sha512-6ZXM32VGQU1liB9+r3AHIsUZBbBQGMBpWdGnRzvCQ+AUVhL01x2P77AXCjhxeLUgrUisl3Cw+UDc+WvuLcygjQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-musl@13.5.6: + resolution: {integrity: sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -2161,8 +2260,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@14.2.0-canary.49: - resolution: {integrity: sha512-68PjCGC1JghA2tuznu+ExeSP+L6qpf6afblB4wFhDRniP+0hRrZB+1E3jJ3PmBgHtitJJMaplTFeKYQ8xbF8xw==} + /@next/swc-linux-arm64-musl@14.2.0-canary.50: + resolution: {integrity: sha512-Is7FNrgY1ifBMKs9Y7fx6OJp7OjwfMMl8BhlN+UzbkMtZF9R45qLnRSWOu0gkQLqfqR0wx//Bmkr/d25qqZxjg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -2170,6 +2269,15 @@ packages: dev: false optional: true + /@next/swc-linux-x64-gnu@13.5.6: + resolution: {integrity: sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-gnu@14.1.0: resolution: {integrity: sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==} engines: {node: '>= 10'} @@ -2179,8 +2287,17 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@14.2.0-canary.49: - resolution: {integrity: sha512-eiDvo0bnYCI59UhaZrNV1k7wZPFHyQ2uJ7/MUH9yvZZcSKBxRDtNc3FmCAZjKiNx/SclMFRAtENLOlDzceRp5g==} + /@next/swc-linux-x64-gnu@14.2.0-canary.50: + resolution: {integrity: sha512-rKcciKNtCVrcj9zZ+JBK1AgIbeISHZz2OcTa/i1O3l+VwNDN25YAPaVDL0aPX6e9N0SR5W33b+bSQMHOc1FGhA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-musl@13.5.6: + resolution: {integrity: sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -2197,8 +2314,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@14.2.0-canary.49: - resolution: {integrity: sha512-XgwiLB/WkRjuhWoKZmlRsZl1b8C7dsYlRD3zqHPkrgWhERyyn3AoeRjIa/eHR6nxj7oTu2KHET1oSJoYobH70g==} + /@next/swc-linux-x64-musl@14.2.0-canary.50: + resolution: {integrity: sha512-tVFgS5lOa/h6h5//4p9mhcV7XThMAzMJQoC+j7y+yhnGnb17t4pQPR3FXAonncgm9OyCkA2N0O0hqwsnj6oCLA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -2206,6 +2323,15 @@ packages: dev: false optional: true + /@next/swc-win32-arm64-msvc@13.5.6: + resolution: {integrity: sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-arm64-msvc@14.1.0: resolution: {integrity: sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==} engines: {node: '>= 10'} @@ -2215,8 +2341,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@14.2.0-canary.49: - resolution: {integrity: sha512-jqC5vhFOAewsGdWriuQqR2aalQ8dHJ1WkSl1psluTxpo5UgICBk+H0wQ93a0CEfD0Rj+8QjUFh+U1oYTqE4YIg==} + /@next/swc-win32-arm64-msvc@14.2.0-canary.50: + resolution: {integrity: sha512-1FmGWELLW7XdrNmJQca7vbBUIVOd84LGZQCO6gRIvmAw3Oh7S3UwP2rAsZ9K24Ox44TnK2xV4C4t9BV8PnHzMQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -2224,6 +2350,15 @@ packages: dev: false optional: true + /@next/swc-win32-ia32-msvc@13.5.6: + resolution: {integrity: sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-ia32-msvc@14.1.0: resolution: {integrity: sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==} engines: {node: '>= 10'} @@ -2233,8 +2368,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@14.2.0-canary.49: - resolution: {integrity: sha512-Zcfe1+FuFtMCtG0L7F9yh0yRhmLM2gGAUHW41FYN+Rtbi/JFS8qhs/M7pOPkqhEWWKqo3at64q7z8KQh+21VsQ==} + /@next/swc-win32-ia32-msvc@14.2.0-canary.50: + resolution: {integrity: sha512-GmIQ0VdGEExzZSh00wCjAILfdqR4dzSFnnXvjAnNBehS8uadHhlLY7fpsVOtNh7byd5gxgbt+dFz7Y4GrrCRbA==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -2242,6 +2377,15 @@ packages: dev: false optional: true + /@next/swc-win32-x64-msvc@13.5.6: + resolution: {integrity: sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-x64-msvc@14.1.0: resolution: {integrity: sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==} engines: {node: '>= 10'} @@ -2251,8 +2395,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@14.2.0-canary.49: - resolution: {integrity: sha512-yeCjnmqMmI9aNbRk3DTrKvCuImUWXU+Kl0XC9KFo8iLpOztpCQrMA+pf5s3GRqv1HRzbRoHsj+1VCPXzTmZrLA==} + /@next/swc-win32-x64-msvc@14.2.0-canary.50: + resolution: {integrity: sha512-kNqwVNRCoujVBe2C4YdtwfrF8103nMmsV3B/IvMnxB3pAotvYLzUTboflT2Wx5AMFTNY1KGYY8GGFjotX5/GRQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -4230,7 +4374,7 @@ packages: /@tanstack/query-core@5.28.6: resolution: {integrity: sha512-hnhotV+DnQtvtR3jPvbQMPNMW4KEK0J4k7c609zJ8muiNknm+yoDyMHmxTWM5ZnlZpsz0zOxYFr+mzRJNHWJsA==} - /@tanstack/react-query-next-experimental@5.28.9(@tanstack/react-query@5.28.6)(next@14.2.0-canary.49)(react@18.2.0): + /@tanstack/react-query-next-experimental@5.28.9(@tanstack/react-query@5.28.6)(next@14.2.0-canary.50)(react@18.2.0): resolution: {integrity: sha512-cihvqAme8nX6O5jeWtk19fnMsgXTX5puHwj6ya2Gf6FZIKhcFTrXQ9npH3ACcbinmVYPcQrShk/D3XAGKR/AUg==} peerDependencies: '@tanstack/react-query': ^5.28.9 @@ -4238,7 +4382,7 @@ packages: react: ^18.0.0 dependencies: '@tanstack/react-query': 5.28.6(react@18.2.0) - next: 14.2.0-canary.49(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.0-canary.50(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 dev: false @@ -5887,12 +6031,6 @@ packages: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} dev: false - /devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - dependencies: - dequal: 2.0.3 - dev: false - /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -6001,12 +6139,12 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - /edge-csrf@1.0.9(next@14.2.0-canary.49): + /edge-csrf@1.0.9(next@14.2.0-canary.50): resolution: {integrity: sha512-3F89YTh42UDdISr3s9AEcgJDLi4ysgjGfnybzF0LuZGaG2W31h1ZwgWwEQBLMj04lAklcP4XHZYi7vk9o8zcbg==} peerDependencies: next: ^13.0.0 || ^14.0.0 dependencies: - next: 14.2.0-canary.49(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.0-canary.50(react-dom@18.2.0)(react@18.2.0) dev: false /editorconfig@1.0.4: @@ -7147,16 +7285,27 @@ packages: web-namespaces: 2.0.1 dev: false + /hast-util-has-property@2.0.1: + resolution: {integrity: sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg==} + dev: false + + /hast-util-heading-rank@2.1.1: + resolution: {integrity: sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==} + dependencies: + '@types/hast': 2.3.10 + dev: false + /hast-util-heading-rank@3.0.0: resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} dependencies: '@types/hast': 3.0.4 dev: false - /hast-util-is-element@3.0.0: - resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + /hast-util-is-element@2.1.3: + resolution: {integrity: sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 2.3.10 + '@types/unist': 2.0.10 dev: false /hast-util-parse-selector@3.1.1: @@ -8655,7 +8804,7 @@ packages: engines: {node: '>= 0.4.0'} dev: false - /next-contentlayer@0.3.4(contentlayer@0.3.4)(esbuild@0.20.2)(next@14.2.0-canary.49)(react-dom@18.2.0)(react@18.2.0): + /next-contentlayer@0.3.4(contentlayer@0.3.4)(esbuild@0.20.2)(next@13.5.6)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-UtUCwgAl159KwfhNaOwyiI7Lg6sdioyKMeh+E7jxx0CJ29JuXGxBEYmCI6+72NxFGIFZKx8lvttbbQhbnYWYSw==} peerDependencies: contentlayer: 0.3.4 @@ -8666,7 +8815,7 @@ packages: '@contentlayer/core': 0.3.4(esbuild@0.20.2) '@contentlayer/utils': 0.3.4 contentlayer: 0.3.4(esbuild@0.20.2) - next: 14.2.0-canary.49(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) + next: 13.5.6(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) transitivePeerDependencies: @@ -8676,7 +8825,7 @@ packages: - supports-color dev: false - /next-sitemap@4.2.3(next@14.2.0-canary.49): + /next-sitemap@4.2.3(next@14.2.0-canary.50): resolution: {integrity: sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==} engines: {node: '>=14.18'} hasBin: true @@ -8687,7 +8836,7 @@ packages: '@next/env': 13.5.6 fast-glob: 3.3.2 minimist: 1.2.8 - next: 14.2.0-canary.49(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.0-canary.50(react-dom@18.2.0)(react@18.2.0) dev: false /next-themes@0.3.0(react-dom@18.2.0)(react@18.2.0): @@ -8699,6 +8848,46 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + /next@13.5.6(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==} + engines: {node: '>=16.14.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + sass: + optional: true + dependencies: + '@next/env': 13.5.6 + '@opentelemetry/api': 1.8.0 + '@swc/helpers': 0.5.2 + busboy: 1.6.0 + caniuse-lite: 1.0.30001600 + postcss: 8.4.31 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + styled-jsx: 5.1.1(react@18.2.0) + watchpack: 2.4.0 + optionalDependencies: + '@next/swc-darwin-arm64': 13.5.6 + '@next/swc-darwin-x64': 13.5.6 + '@next/swc-linux-arm64-gnu': 13.5.6 + '@next/swc-linux-arm64-musl': 13.5.6 + '@next/swc-linux-x64-gnu': 13.5.6 + '@next/swc-linux-x64-musl': 13.5.6 + '@next/swc-win32-arm64-msvc': 13.5.6 + '@next/swc-win32-ia32-msvc': 13.5.6 + '@next/swc-win32-x64-msvc': 13.5.6 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + /next@14.1.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==} engines: {node: '>=18.17.0'} @@ -8738,8 +8927,8 @@ packages: - babel-plugin-macros dev: false - /next@14.2.0-canary.49(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-sfryWP84xmqUOYAilbiojczTpTGCRTMch3w+EVppzPj0DS6gOWv9vPUHp/6uMWWZ+YX+n3GkYhwRK80Q+FG+kg==} + /next@14.2.0-canary.50(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-7uNL5MrCx7YXJO1B/H3619HkLQhlXdAWIsgMHzetrz7ffE3isZoy6u5aXkkITfyKBfbvMbyhUcd2MH7HCdivfg==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -8756,8 +8945,7 @@ packages: sass: optional: true dependencies: - '@next/env': 14.2.0-canary.49 - '@opentelemetry/api': 1.8.0 + '@next/env': 14.2.0-canary.50 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001600 @@ -8767,15 +8955,15 @@ packages: react-dom: 18.2.0(react@18.2.0) styled-jsx: 5.1.1(react@18.2.0) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.0-canary.49 - '@next/swc-darwin-x64': 14.2.0-canary.49 - '@next/swc-linux-arm64-gnu': 14.2.0-canary.49 - '@next/swc-linux-arm64-musl': 14.2.0-canary.49 - '@next/swc-linux-x64-gnu': 14.2.0-canary.49 - '@next/swc-linux-x64-musl': 14.2.0-canary.49 - '@next/swc-win32-arm64-msvc': 14.2.0-canary.49 - '@next/swc-win32-ia32-msvc': 14.2.0-canary.49 - '@next/swc-win32-x64-msvc': 14.2.0-canary.49 + '@next/swc-darwin-arm64': 14.2.0-canary.50 + '@next/swc-darwin-x64': 14.2.0-canary.50 + '@next/swc-linux-arm64-gnu': 14.2.0-canary.50 + '@next/swc-linux-arm64-musl': 14.2.0-canary.50 + '@next/swc-linux-x64-gnu': 14.2.0-canary.50 + '@next/swc-linux-x64-musl': 14.2.0-canary.50 + '@next/swc-win32-arm64-msvc': 14.2.0-canary.50 + '@next/swc-win32-ia32-msvc': 14.2.0-canary.50 + '@next/swc-win32-x64-msvc': 14.2.0-canary.50 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -9920,15 +10108,16 @@ packages: rc: 1.2.8 dev: false - /rehype-autolink-headings@7.1.0: - resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==} + /rehype-autolink-headings@6.1.1: + resolution: {integrity: sha512-NMYzZIsHM3sA14nC5rAFuUPIOfg+DFmf9EY1YMhaNlB7+3kK/ZlE6kqPfuxr1tsJ1XWkTrMtMoyHosU70d35mA==} dependencies: - '@types/hast': 3.0.4 - '@ungap/structured-clone': 1.2.0 - hast-util-heading-rank: 3.0.0 - hast-util-is-element: 3.0.0 - unified: 11.0.4 - unist-util-visit: 5.0.0 + '@types/hast': 2.3.10 + extend: 3.0.2 + hast-util-has-property: 2.0.1 + hast-util-heading-rank: 2.1.1 + hast-util-is-element: 2.1.3 + unified: 10.1.2 + unist-util-visit: 4.1.2 dev: false /rehype-slug@6.0.0: @@ -11096,18 +11285,6 @@ packages: vfile: 5.3.7 dev: false - /unified@11.0.4: - resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} - dependencies: - '@types/unist': 3.0.2 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 6.0.1 - dev: false - /unist-util-generated@2.0.1: resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} dev: false @@ -11149,12 +11326,6 @@ packages: '@types/unist': 2.0.10 dev: false - /unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - dependencies: - '@types/unist': 3.0.2 - dev: false - /unist-util-visit-parents@5.1.3: resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} dependencies: @@ -11313,13 +11484,6 @@ packages: unist-util-stringify-position: 3.0.3 dev: false - /vfile-message@4.0.2: - resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} - dependencies: - '@types/unist': 3.0.2 - unist-util-stringify-position: 4.0.0 - dev: false - /vfile@5.3.7: resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} dependencies: @@ -11329,14 +11493,6 @@ packages: vfile-message: 3.1.4 dev: false - /vfile@6.0.1: - resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} - dependencies: - '@types/unist': 3.0.2 - unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.2 - dev: false - /victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} dependencies: @@ -11360,6 +11516,14 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + /watchpack@2.4.0: + resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} + engines: {node: '>=10.13.0'} + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + dev: false + /watchpack@2.4.1: resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} engines: {node: '>=10.13.0'} @@ -11527,6 +11691,12 @@ packages: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} dev: false + /wp-types@3.64.0: + resolution: {integrity: sha512-8rwHUQFxI18jezvObymV0eKEhnU2xaSci5ra6YG+dV6EDZLfNR2z3NZA82mFkOzZRWpl/Dlj+a4u8aqk08UPGQ==} + dependencies: + typescript: 5.4.3 + dev: true + /wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1ad9bef03..964b1f635 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - apps/* - packages/* - - tooling/* - packages/features/* + - packages/cms/* + - tooling/* - supabase \ No newline at end of file diff --git a/tooling/eslint/base.js b/tooling/eslint/base.js index 68fb2be04..f964e1a65 100644 --- a/tooling/eslint/base.js +++ b/tooling/eslint/base.js @@ -37,6 +37,7 @@ const config = { '**/.eslintrc.cjs', '**/*.config.js', '**/*.config.cjs', + '**/node_modules', '.next', 'dist', 'pnpm-lock.yaml', diff --git a/tooling/prettier/index.mjs b/tooling/prettier/index.mjs index a876f89da..63e5b6d51 100644 --- a/tooling/prettier/index.mjs +++ b/tooling/prettier/index.mjs @@ -19,7 +19,7 @@ const config = { '^@supabase/supabase-js$', '^@supabase/gotrue-js$', '', - '^@kit/(.*)$', + '^@kit/(.*)$', // package imports '^~/(.*)$', // app-specific imports '^[./]', // relative imports ], diff --git a/tooling/tailwind/index.ts b/tooling/tailwind/index.ts index 072e02a11..d3086f382 100644 --- a/tooling/tailwind/index.ts +++ b/tooling/tailwind/index.ts @@ -6,7 +6,8 @@ export default { content: [ '../../packages/**/*.tsx', '../../apps/**/*.tsx', - '!**/node_modules', + '!../../packages/**/node_modules', + '!../../apps/**/node_modules', ], theme: { container: {