From 6b72206b0007ac63a75fe703ba6e6573f009bf42 Mon Sep 17 00:00:00 2001 From: giancarlo Date: Mon, 1 Apr 2024 19:47:51 +0800 Subject: [PATCH] Refactor CMS to handle ContentLayer and WordPress platforms This commit refactors the CMS to handle two platforms: ContentLayer and WordPress. The CMS layer is abstracted into a core package, and separate implementations for each platform are created. This change allows the app to switch the CMS type based on environment variable, which can improve the flexibility of content management. It also updates several functions in the `server-sitemap.xml` route to accommodate these changes and generate sitemaps based on the CMS client. Further, documentation content and posts have been relocated to align with the new structure. Notably, this refactor is a comprehensive update to the way the CMS is structured and managed. --- apps/web/.env.development | 3 + apps/web/app/(marketing)/blog/[slug]/page.tsx | 25 +- .../blog/_components/post-header.tsx | 12 +- .../blog/_components/post-preview.tsx | 32 +- .../app/(marketing)/blog/_components/post.tsx | 13 +- apps/web/app/(marketing)/blog/page.tsx | 12 +- .../app/(marketing)/docs/[...slug]/page.tsx | 56 +-- .../docs/_components/docs-card.tsx | 8 +- .../docs/_components/docs-cards.tsx | 12 +- .../docs/_components/docs-navigation.tsx | 88 ++-- .../_components/documentation-page-link.tsx | 9 +- .../docs/_lib/build-documentation-tree.ts | 55 --- .../docs/_lib/get-documentation-page-tree.ts | 45 -- apps/web/app/(marketing)/docs/layout.tsx | 19 +- apps/web/app/(marketing)/docs/page.tsx | 17 +- apps/web/app/server-sitemap.xml/route.ts | 35 +- .../001-nextjs.mdx | 17 - .../002-supabase.mdx | 88 ---- .../003-stripe-cli.mdx | 19 - .../003-running-the-application/index.mdx | 20 - apps/web/next.config.mjs | 3 +- apps/web/package.json | 7 +- apps/web/tsconfig.json | 5 +- package.json | 1 + .../src/components/current-plan-card.tsx | 119 +++--- packages/cms/contentlayer/README.md | 9 + .../001-getting_started.mdx | 0 .../002-clone-repository.mdx | 0 .../docs/001-getting_started/index.mdx | 2 +- .../002-authentication/001-configuration.mdx | 0 .../docs/002-authentication/002-setup.mdx | 0 .../content/docs/002-authentication/index.mdx | 0 .../contentlayer}/content/posts/post-01.mdx | 3 + .../cms/contentlayer}/contentlayer.config.js | 80 ++-- packages/cms/contentlayer/package.json | 45 ++ packages/cms/contentlayer/src/client.ts | 192 +++++++++ .../cms/contentlayer/src/content-renderer.tsx | 5 + packages/cms/contentlayer/src/index.ts | 3 + .../src}/mdx/mdx-renderer.module.css | 0 .../contentlayer/src}/mdx/mdx-renderer.tsx | 7 +- packages/cms/contentlayer/tsconfig.json | 8 + packages/cms/core/README.md | 3 + packages/cms/core/package.json | 38 ++ packages/cms/core/src/cms-client.ts | 72 ++++ packages/cms/core/src/cms.type.ts | 3 + packages/cms/core/src/content-renderer.tsx | 17 + packages/cms/core/src/create-cms-client.ts | 36 ++ packages/cms/core/src/index.ts | 4 + packages/cms/core/tsconfig.json | 8 + packages/cms/wordpress/README.md | 9 + packages/cms/wordpress/package.json | 41 ++ packages/cms/wordpress/src/index.ts | 1 + packages/cms/wordpress/src/wp-client.ts | 284 +++++++++++++ packages/cms/wordpress/tsconfig.json | 8 + packages/ui/package.json | 2 +- ...ticityToken.tsx => authenticity-token.tsx} | 0 .../src/makerkit/{mdx => }/mdx-components.tsx | 8 +- pnpm-lock.yaml | 386 +++++++++++++----- pnpm-workspace.yaml | 3 +- tooling/eslint/base.js | 1 + tooling/prettier/index.mjs | 2 +- tooling/tailwind/index.ts | 3 +- 62 files changed, 1313 insertions(+), 690 deletions(-) delete mode 100644 apps/web/app/(marketing)/docs/_lib/build-documentation-tree.ts delete mode 100644 apps/web/app/(marketing)/docs/_lib/get-documentation-page-tree.ts delete mode 100644 apps/web/content/docs/001-getting_started/003-running-the-application/001-nextjs.mdx delete mode 100644 apps/web/content/docs/001-getting_started/003-running-the-application/002-supabase.mdx delete mode 100644 apps/web/content/docs/001-getting_started/003-running-the-application/003-stripe-cli.mdx delete mode 100644 apps/web/content/docs/001-getting_started/003-running-the-application/index.mdx create mode 100644 packages/cms/contentlayer/README.md rename {apps/web => packages/cms/contentlayer}/content/docs/001-getting_started/001-getting_started.mdx (100%) rename {apps/web => packages/cms/contentlayer}/content/docs/001-getting_started/002-clone-repository.mdx (100%) rename {apps/web => packages/cms/contentlayer}/content/docs/001-getting_started/index.mdx (91%) rename {apps/web => packages/cms/contentlayer}/content/docs/002-authentication/001-configuration.mdx (100%) rename {apps/web => packages/cms/contentlayer}/content/docs/002-authentication/002-setup.mdx (100%) rename {apps/web => packages/cms/contentlayer}/content/docs/002-authentication/index.mdx (100%) rename {apps/web => packages/cms/contentlayer}/content/posts/post-01.mdx (97%) rename {apps/web => packages/cms/contentlayer}/contentlayer.config.js (87%) create mode 100644 packages/cms/contentlayer/package.json create mode 100644 packages/cms/contentlayer/src/client.ts create mode 100644 packages/cms/contentlayer/src/content-renderer.tsx create mode 100644 packages/cms/contentlayer/src/index.ts rename packages/{ui/src/makerkit => cms/contentlayer/src}/mdx/mdx-renderer.module.css (100%) rename packages/{ui/src/makerkit => cms/contentlayer/src}/mdx/mdx-renderer.tsx (61%) create mode 100644 packages/cms/contentlayer/tsconfig.json create mode 100644 packages/cms/core/README.md create mode 100644 packages/cms/core/package.json create mode 100644 packages/cms/core/src/cms-client.ts create mode 100644 packages/cms/core/src/cms.type.ts create mode 100644 packages/cms/core/src/content-renderer.tsx create mode 100644 packages/cms/core/src/create-cms-client.ts create mode 100644 packages/cms/core/src/index.ts create mode 100644 packages/cms/core/tsconfig.json create mode 100644 packages/cms/wordpress/README.md create mode 100644 packages/cms/wordpress/package.json create mode 100644 packages/cms/wordpress/src/index.ts create mode 100644 packages/cms/wordpress/src/wp-client.ts create mode 100644 packages/cms/wordpress/tsconfig.json rename packages/ui/src/makerkit/{AuthenticityToken.tsx => authenticity-token.tsx} (100%) rename packages/ui/src/makerkit/{mdx => }/mdx-components.tsx (91%) 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: {