Refactored CMS packages to remove a circular dependency (#62)

This commit is contained in:
Giancarlo Buomprisco
2024-09-04 03:10:50 +08:00
committed by GitHub
parent 51a40b6d40
commit d18f810c6e
23 changed files with 193 additions and 83 deletions

View File

@@ -1,113 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Cms {
export interface ContentItem {
id: string;
title: string;
url: string;
description: string | undefined;
content: unknown;
publishedAt: string;
image: string | undefined;
status: ContentItemStatus;
slug: string;
categories: Category[];
tags: Tag[];
order: number;
children: ContentItem[];
parentId: string | undefined;
}
export type ContentItemStatus = 'draft' | 'published' | 'review' | 'pending';
export interface Category {
id: string;
name: string;
slug: string;
}
export interface Tag {
id: string;
name: string;
slug: string;
}
export interface GetContentItemsOptions {
collection: string;
limit?: number;
offset?: number;
categories?: string[];
tags?: string[];
parentIds?: string[];
language?: string | undefined;
sortDirection?: 'asc' | 'desc';
sortBy?: 'publishedAt' | 'order' | 'title';
status?: ContentItemStatus;
}
export interface GetCategoriesOptions {
slugs?: string[];
limit?: number;
offset?: number;
}
export interface GetTagsOptions {
slugs?: string[];
limit?: number;
offset?: number;
}
}
/**
* Abstract class representing a CMS client.
*/
export abstract class CmsClient {
/**
* Retrieves content items based on the provided options.
* @param options - Options for filtering and pagination.
* @returns A promise that resolves to an array of content items.
*/
abstract getContentItems(options?: Cms.GetContentItemsOptions): Promise<{
total: number;
items: Cms.ContentItem[];
}>;
/**
* Retrieves a content item by its ID and type.
* @returns A promise that resolves to the content item, or undefined if not found.
*/
abstract getContentItemBySlug(params: {
slug: string;
collection: string;
status?: Cms.ContentItemStatus;
}): Promise<Cms.ContentItem | undefined>;
/**
* Retrieves categories based on the provided options.
* @param options - Options for filtering and pagination.
* @returns A promise that resolves to an array of categories.
*/
abstract getCategories(
options?: Cms.GetCategoriesOptions,
): Promise<Cms.Category[]>;
/**
* Retrieves a category by its slug.
* @param slug - The slug of the category.
* @returns A promise that resolves to the category, or undefined if not found.
*/
abstract getCategoryBySlug(slug: string): Promise<Cms.Category | undefined>;
/**
* Retrieves tags based on the provided options.
* @param options - Options for filtering and pagination.
* @returns A promise that resolves to an array of tags.
*/
abstract getTags(options?: Cms.GetTagsOptions): Promise<Cms.Tag[]>;
/**
* Retrieves a tag by its slug.
* @param slug - The slug of the tag.
* @returns A promise that resolves to the tag, or undefined if not found.
*/
abstract getTagBySlug(slug: string): Promise<Cms.Tag | undefined>;
}

View File

@@ -1,3 +0,0 @@
// we can add more types here if we have more CMSs
// ex. export type CmsType = 'contentlayer' | 'other-cms';
export type CmsType = 'wordpress' | 'keystatic';

View File

@@ -1,4 +1,4 @@
import type { CmsType } from './cms.type';
import type { CmsType } from '@kit/cms-types';
const CMS_CLIENT = process.env.CMS_CLIENT as CmsType;
@@ -11,21 +11,32 @@ export async function ContentRenderer({
content,
type = CMS_CLIENT,
}: ContentRendererProps) {
const Renderer = await getContentRenderer(type);
return Renderer ? <Renderer content={content} /> : null;
}
/**
* Gets the content renderer for the specified CMS client.
*
* @param {CmsType} type - The type of CMS client.
*/
async function getContentRenderer(type: CmsType) {
switch (type) {
case 'keystatic': {
const { KeystaticContentRenderer } = await import(
'../../keystatic/src/content-renderer'
'@kit/keystatic/renderer'
);
return <KeystaticContentRenderer content={content} />;
return KeystaticContentRenderer;
}
case 'wordpress': {
const { WordpressContentRenderer } = await import(
'../../wordpress/src/content-renderer'
'@kit/wordpress/renderer'
);
return <WordpressContentRenderer content={content} />;
return WordpressContentRenderer;
}
default: {

View File

@@ -1,5 +1,9 @@
import { CmsClient } from './cms-client';
import { CmsType } from './cms.type';
import { CmsClient, CmsType } from '@kit/cms-types';
/**
* The type of CMS client to use.
*/
const CMS_CLIENT = process.env.CMS_CLIENT as CmsType;
/**
* Creates a CMS client based on the specified type.
@@ -9,61 +13,32 @@ import { CmsType } from './cms.type';
* @throws {Error} If the specified CMS type is unknown.
*/
export async function createCmsClient(
type: CmsType = process.env.CMS_CLIENT as CmsType,
type: CmsType = CMS_CLIENT,
): Promise<CmsClient> {
return cmsClientFactory(type);
}
async function cmsClientFactory(type: CmsType) {
/**
* Creates a CMS client based on the specified type.
*
* @param {CmsType} type - The type of CMS client to create.
* @returns {Promise<CmsClient>} A Promise that resolves to the created CMS client.
*/
async function cmsClientFactory(type: CmsType): Promise<CmsClient> {
switch (type) {
case 'wordpress':
return getWordpressClient();
case 'wordpress': {
const { createWordpressClient } = await import('@kit/wordpress');
case 'keystatic':
return getKeystaticClient();
return createWordpressClient();
}
case 'keystatic': {
const { createKeystaticClient } = await import('@kit/keystatic');
return createKeystaticClient();
}
default:
throw new Error(`Unknown CMS type`);
}
}
async function getWordpressClient() {
const { createWordpressClient } = await import(
'../../wordpress/src/wp-client'
);
return createWordpressClient();
}
async function getKeystaticClient() {
if (
process.env.NEXT_RUNTIME === 'nodejs' ||
process.env.KEYSTATIC_STORAGE_KIND !== 'local'
) {
const { createKeystaticClient } = await import(
'../../keystatic/src/keystatic-client'
);
return createKeystaticClient();
}
console.error(
`[CMS] Keystatic client using "Local" mode is only available in Node.js runtime. Please choose a different CMS client. Returning a mock client instead of throwing an error.`,
);
return mockCMSClient() as unknown as CmsClient;
}
function mockCMSClient() {
return {
getContentItems() {
return Promise.resolve({
items: [],
total: 0,
});
},
getContentItemBySlug() {
return Promise.resolve(undefined);
},
};
}

View File

@@ -1,4 +1,6 @@
export * from './cms-client';
import { Cms } from '@kit/cms-types';
export * from './create-cms-client';
export * from './cms.type';
export * from './content-renderer';
export type { Cms };