From ede3636b9ff004319dd6cd5052cc019045c9f224 Mon Sep 17 00:00:00 2001 From: Saurav Panda Date: Wed, 11 Dec 2024 20:48:43 -0800 Subject: [PATCH 1/2] fix: updated build for cloudflare pages --- packages/akiradocs/package.json | 3 +- .../akiradocs/scripts/generate-manifest.js | 28 +++ .../app/[locale]/[type]/[...slug]/page.tsx | 48 +++-- .../src/app/[locale]/[type]/page.tsx | 19 +- packages/akiradocs/src/app/page.tsx | 2 +- .../src/components/layout/Navigation.tsx | 4 +- packages/akiradocs/src/lib/content.ts | 169 ++++++++---------- packages/akiradocs/src/lib/getContents.ts | 26 --- 8 files changed, 148 insertions(+), 151 deletions(-) create mode 100644 packages/akiradocs/scripts/generate-manifest.js delete mode 100644 packages/akiradocs/src/lib/getContents.ts diff --git a/packages/akiradocs/package.json b/packages/akiradocs/package.json index 0c6f5fc..cd23c8a 100644 --- a/packages/akiradocs/package.json +++ b/packages/akiradocs/package.json @@ -10,7 +10,8 @@ "build": "npm run compile && npm run generate-sitemap && set NEXT_PUBLIC_AKIRADOCS_EDIT_MODE=false && next build", "start": "set NEXT_PUBLIC_AKIRADOCS_EDIT_MODE=false && next start", "lint": "next lint", - "migrate-gitbook": "node scripts/migrations/gitbook.js" + "migrate-gitbook": "node scripts/migrations/gitbook.js", + "generate-manifest": "node scripts/generate-manifest.js" }, "dependencies": { "@ai-sdk/anthropic": "^1.0.1", diff --git a/packages/akiradocs/scripts/generate-manifest.js b/packages/akiradocs/scripts/generate-manifest.js new file mode 100644 index 0000000..f88bc92 --- /dev/null +++ b/packages/akiradocs/scripts/generate-manifest.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const path = require('path'); + +function getAllFiles(dirPath, arrayOfFiles = []) { + const files = fs.readdirSync(dirPath); + + files.forEach(file => { + const fullPath = path.join(dirPath, file); + if (fs.statSync(fullPath).isDirectory()) { + getAllFiles(fullPath, arrayOfFiles); + } else { + arrayOfFiles.push( + fullPath.replace(path.join(process.cwd(), 'compiled/'), '') + ); + } + }); + + return arrayOfFiles; +} + +const manifest = { + files: getAllFiles(path.join(process.cwd(), 'compiled')) +}; + +fs.writeFileSync( + path.join(process.cwd(), 'compiled/manifest.json'), + JSON.stringify(manifest, null, 2) +); \ No newline at end of file diff --git a/packages/akiradocs/src/app/[locale]/[type]/[...slug]/page.tsx b/packages/akiradocs/src/app/[locale]/[type]/[...slug]/page.tsx index db4fce9..4111dac 100644 --- a/packages/akiradocs/src/app/[locale]/[type]/[...slug]/page.tsx +++ b/packages/akiradocs/src/app/[locale]/[type]/[...slug]/page.tsx @@ -13,14 +13,13 @@ import { PageNavigation } from '@/components/layout/PageNavigation' import { MainTitle, SubTitle } from '@/components/blocks/HeadingBlock' import { SEO } from '@/components/layout/SEO' import { NotFound } from '@/components/layout/NotFound' -import { TextToSpeech } from '@/components/tts/TextToSpeech' import { getAkiradocsConfig } from "@/lib/getAkiradocsConfig"; import { getTranslation } from '@/lib/staticTranslation'; import { ClientSideControls } from '@/components/layout/ClientSideControl'; import { Metadata } from 'next' -// export const runtime = 'edge' -export const dynamic = 'force-static'; +export const runtime = 'edge' +// export const dynamic = 'force-static'; const PostContainer = ({ children }: { children: React.ReactNode }) => (
@@ -36,33 +35,33 @@ type Props = { }>; } -export async function generateStaticParams() { - const locales = ['en', 'es', 'fr']; - const types = ['docs', 'api', 'articles']; - const allSlugs: { locale: string, type: string, slug: string[] }[] = []; +// export async function generateStaticParams() { +// const locales = ['en', 'es', 'fr']; +// const types = ['docs', 'api', 'articles']; +// const allSlugs: { locale: string, type: string, slug: string[] }[] = []; - locales.forEach(locale => { - types.forEach(type => { - const navigationItems = getContentNavigation({}, locale, type); - if (Array.isArray(navigationItems)) { - navigationItems.forEach(item => { - if (item.slug) { - allSlugs.push({ locale, type, slug: item.slug.split('/') }); - } - }); - } - }); - }); +// locales.forEach(locale => { +// types.forEach(type => { +// const navigationItems = getContentNavigation({}, locale, type); +// if (Array.isArray(navigationItems)) { +// navigationItems.forEach(item => { +// if (item.slug) { +// allSlugs.push({ locale, type, slug: item.slug.split('/') }); +// } +// }); +// } +// }); +// }); - return allSlugs; -} +// return allSlugs; +// } export async function generateMetadata({ params }: Props): Promise { const resolvedParams = await Promise.resolve(params); const { locale, type, slug: slugArray } = resolvedParams; const slug = slugArray.length ? slugArray.join('/') : ''; - const post = getContentBySlug(locale, type, slug); + const post = await getContentBySlug(locale, type, slug); const t = getTranslation(locale as 'en' | 'es' | 'fr'); const akiradocsConfig = getAkiradocsConfig(); @@ -94,15 +93,14 @@ export default async function ContentPage({ params }: Props) { const t = getTranslation(locale as 'en' | 'es' | 'de'); const slug = slugArray.length ? slugArray.join('/') : ''; - const post = getContentBySlug(locale, type, slug); - + const post = await getContentBySlug(locale, type, slug); if (!post) { return ; } const akiradocsConfig = getAkiradocsConfig(); const headerConfig = getHeaderConfig(); const footerConfig = getFooterConfig(); - const navigationItems = getContentNavigation({}, locale, type); + const navigationItems = await getContentNavigation({}, locale, type); const { prev, next } = getNextPrevPages(navigationItems, `/${type}/${slug}`); const pageTitle = t(post.title) || t('common.documentation'); const pageDescription = t(post.description) || t('common.documentationContent'); diff --git a/packages/akiradocs/src/app/[locale]/[type]/page.tsx b/packages/akiradocs/src/app/[locale]/[type]/page.tsx index 1b964cc..0b15df0 100644 --- a/packages/akiradocs/src/app/[locale]/[type]/page.tsx +++ b/packages/akiradocs/src/app/[locale]/[type]/page.tsx @@ -49,18 +49,33 @@ export async function generateMetadata({ params }: Props): Promise { } } +function formatRedirectUrl(locale: string, type: string, slug: string): string { + // Remove leading/trailing slashes + const cleanSlug = slug.replace(/^\/+|\/+$/g, ''); + + // Check if slug already contains locale and/or type + const parts = cleanSlug.split('/'); + const slugWithoutLocaleAndType = parts + .filter(part => part !== locale && part !== type) + .join('/'); + + return `/${locale}/${type}/${slugWithoutLocaleAndType}`; +} + export default async function Page({ params }: Props) { const resolvedParams = await Promise.resolve(params); const { locale, type } = resolvedParams; // Get the first/default content for this type - const recentContent = getRecentContent(`${locale}/${type}`); + const recentContent = await getRecentContent(`${locale}/${type}`); if (recentContent) { - const redirectUrl = `/${locale}/${type}/${recentContent.slug.replace(`${type}/`, '')}`; + const redirectUrl = formatRedirectUrl(locale, type, recentContent.slug); redirect(redirectUrl); } // Fallback redirect if no content found + console.log("redirecting to", `/${locale}`); redirect(`/${locale}`); } + diff --git a/packages/akiradocs/src/app/page.tsx b/packages/akiradocs/src/app/page.tsx index f91a87e..ce277e7 100644 --- a/packages/akiradocs/src/app/page.tsx +++ b/packages/akiradocs/src/app/page.tsx @@ -19,7 +19,7 @@ export async function generateMetadata() { export default async function DocPage() { const config = getAkiradocsConfig() const defaultLocale = config.localization.defaultLocale - const recentContent = getRecentContent(`${defaultLocale}/docs`) + const recentContent = await getRecentContent(`${defaultLocale}/docs`) if (recentContent) { const redirectUrl = `/${defaultLocale}/docs/${recentContent.slug.replace('docs/', '')}` diff --git a/packages/akiradocs/src/components/layout/Navigation.tsx b/packages/akiradocs/src/components/layout/Navigation.tsx index adecb05..5214cd3 100644 --- a/packages/akiradocs/src/components/layout/Navigation.tsx +++ b/packages/akiradocs/src/components/layout/Navigation.tsx @@ -133,8 +133,8 @@ const NavItem = React.memo(({ locale, item, pathname, depth = 0 }: NavItemProps) NavItem.displayName = 'NavItem' -export function ApiSidebar() { - const navigation = getApiNavigation(); +export async function ApiSidebar() { + const navigation = await getApiNavigation(); return ( diff --git a/packages/akiradocs/src/lib/content.ts b/packages/akiradocs/src/lib/content.ts index d44e357..65c14f2 100644 --- a/packages/akiradocs/src/lib/content.ts +++ b/packages/akiradocs/src/lib/content.ts @@ -1,26 +1,23 @@ import { Post } from '@/types/Block' -declare var require: { - context( - directory: string, - useSubdirectories: boolean, - regExp: RegExp - ): any; -}; -const contentContext = require.context(`../../compiled/`, true, /\.json$/) -export function getContentBySlug(locale: string, type: string, slug: string): Post | null { +// Create a function to get the JSON path +async function getJsonContent(path: string) { try { - // Construct the exact path we're looking for - const filePath = `./${locale}/${type}/${slug}.json`; - - // Check if the file exists in our context - if (!contentContext.keys().includes(filePath)) { - console.warn(`File not found: ${filePath}`); - return null; - } + // Dynamic import for JSON files + console.log(path) + const content = await import(`../../compiled/${path}`); + return content.default; + } catch (error) { + console.error(`Error loading content for ${path}:`, error); + return null; + } +} - // Load the content - const content = contentContext(filePath); +export async function getContentBySlug(locale: string, type: string, slug: string): Promise { + try { + const content = await getJsonContent(`${locale}/${type}/${slug}.json`); + if (!content) return null; + return { ...content, slug @@ -31,55 +28,40 @@ export function getContentBySlug(locale: string, type: string, slug: string): Po } } -export function getAllPosts(locale: string, type: string): Post[] { - const posts = contentContext.keys() - .filter((fileName: string) => { - // Only include files that match the exact locale and type path - const pattern = new RegExp(`^\./${locale}/${type}/[^/]+\.json$`); - return pattern.test(fileName) && !fileName.endsWith('/_meta.json'); - }) - .map((fileName: string) => { - try { - // Load the content directly using the full path - const content = contentContext(fileName); +export async function getAllPosts(locale: string, type: string): Promise { + // You'll need to maintain a manifest file that lists all available content + // For example, create a manifest.json in your compiled directory + const manifest = await import('../../compiled/manifest.json'); + + const posts = await Promise.all( + manifest.files + .filter((fileName: string) => { + const pattern = new RegExp(`^${locale}/${type}/[^/]+\.json$`); + return pattern.test(fileName) && !fileName.endsWith('/_meta.json'); + }) + .map(async (fileName: string) => { + const content = await getJsonContent(fileName); + if (!content) return null; + const slug = fileName - .replace(`\./${locale}/${type}/`, '') + .replace(`${locale}/${type}/`, '') .replace(/\.json$/, ''); + return { ...content, slug }; - } catch (error) { - console.error(`Error loading content for ${fileName}:`, error); - return null; - } - }) - .filter((post: unknown): post is Post => post !== null); + }) + ); - return posts; + return posts.filter((post): post is Post => post !== null); } -export function getContentNavigation(defaultValue: T, locale: string, type: string): T { +export async function getContentNavigation(defaultValue: T, locale: string, type: string): Promise { try { - const navigationFile = `./${locale}/${type}/_meta.json` - const navigation = contentContext(navigationFile) as T - - if (type === 'articles') { - // Get all articles and their content - const articles = contentContext.keys() - .filter((key: string) => key.startsWith(`./${locale}/${type}/`) && !key.endsWith('/_meta.json')) - .map((key: string) => ({ - path: key.replace(`./${locale}/${type}/`, '').replace('.json', ''), - content: contentContext(key) - })) - .sort((a: any, b: any) => new Date(b.content.date).getTime() - new Date(a.content.date).getTime()) - - // Assuming navigation is an object with a routes property - return { - ...navigation - } as T - } - + const navigationFile = `${locale}/${type}/_meta.json` + const navigation = await getJsonContent(navigationFile) + console.log(navigation) return navigation } catch (error) { console.warn(`Failed to read ${type} _meta.json file. Using default value.`) @@ -87,54 +69,52 @@ export function getContentNavigation(defaultValue: T, locale: string, type: s } } -export function getRecentContent(folderPath: string) { +export async function getRecentContent(folderPath: string) { try { // For non-article paths, get default route from _meta.json - if (!folderPath.includes('/articles/')) { - const metaContent = contentContext(`./${folderPath}/_meta.json`) + // if (!folderPath.includes('/articles/')) { + const metaContent = await getJsonContent(`${folderPath}/_meta.json`) if (metaContent?.defaultRoute) { return { slug: metaContent.defaultRoute } } - } - - // Existing logic for articles - const files = contentContext.keys() - .filter((key: string) => key.startsWith(`./${folderPath}/`) && key.endsWith('.json') && !key.endsWith('/_meta.json')) - - if (files.length === 0) { - return null - } - - // Sort files by their content's date field - const sortedFiles = files - .map((file: string) => ({ - name: file, - content: contentContext(file) - })) - .sort((a: any, b: any) => new Date(b.content.date).getTime() - new Date(a.content.date).getTime()) - - return { - slug: sortedFiles[0].name.replace(`./${folderPath}/`, '').replace('.json', '') - } + // } + + // // Existing logic for articles + // const metaContent = await getJsonContent(`${folderPath}/_meta.json`) + // const files = metaContent?.filter((key: string) => + // key.startsWith(`./${folderPath}/`) && + // key.endsWith('.json') && + // !key.endsWith('/_meta.json') + // ) + + // if (!files || files.length === 0) { + // return null + // } + + // // Sort files by their content's date field + // const sortedFiles = await Promise.all( + // files + // .map(async (file: string) => ({ + // name: file, + // content: await getJsonContent(file) + // })) + // .sort((a: any, b: any) => new Date(b.content.date).getTime() - new Date(a.content.date).getTime()) + // ) + // console.log("sortedFiles", sortedFiles) + // return { + // slug: sortedFiles[0].name.replace(`./${folderPath}/`, '').replace('.json', '') + // } } catch (error) { console.error('Error finding recent article:', error) return null } } -export function folderExists(folderPath: string): boolean { - try { - return contentContext.keys().some((key: string) => key.startsWith(`./${folderPath}/`)) - } catch (error) { - console.error('Error checking folder existence:', error) - return false - } -} -export function get_api_spec(): any { +export async function get_api_spec(): Promise { try { - return contentContext('./en/api/apiSpec.json') + return await getJsonContent('en/api/apiSpec.json') } catch (error) { console.error('Error reading API spec file:', error) return null @@ -148,9 +128,10 @@ interface ApiNavItem { children?: ApiNavItem[]; } -export function getApiNavigation(): ApiNavItem[] { +export async function getApiNavigation(): Promise { try { - const apiSpec = get_api_spec(); + const apiSpec = await get_api_spec(); + console.log("apiSpec", apiSpec) if (!apiSpec || !apiSpec.paths) { return []; } diff --git a/packages/akiradocs/src/lib/getContents.ts b/packages/akiradocs/src/lib/getContents.ts deleted file mode 100644 index f4534c5..0000000 --- a/packages/akiradocs/src/lib/getContents.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { getAkiradocsConfig } from "./getAkiradocsConfig"; -const config = getAkiradocsConfig(); -declare var require: { - context( - directory: string, - useSubdirectories: boolean, - regExp: RegExp - ): any; -}; -const contentContext = require.context( - `../../compiled/`, - true, // Include subdirectories - /^\.\/.*\.json$/ // Update this regex to be more specific -); - -export function fetchAllContent() { - const content: { [key: string]: any } = {}; - - contentContext.keys().forEach((key: string) => { - const fileName = key.replace(/^\.\//, ''); - const fileContent = contentContext(key); - content[fileName] = fileContent; - }); - - return content; -} From 6ebacc9f4b02f1d7ef0732c4db2d9d71f0ff518b Mon Sep 17 00:00:00 2001 From: Saurav Panda Date: Wed, 11 Dec 2024 21:04:49 -0800 Subject: [PATCH 2/2] fix: updated manifest generation --- docs/package.json | 7 +- docs/scripts/generate-manifest.js | 28 +++ .../app/[locale]/[type]/[...slug]/page.tsx | 48 +++-- docs/src/app/[locale]/[type]/page.tsx | 19 +- docs/src/app/page.tsx | 2 +- docs/src/components/layout/Navigation.tsx | 4 +- docs/src/lib/content.ts | 169 ++++++++---------- packages/akiradocs/package.json | 2 +- packages/create-app/package.json | 2 +- 9 files changed, 152 insertions(+), 129 deletions(-) create mode 100644 docs/scripts/generate-manifest.js diff --git a/docs/package.json b/docs/package.json index 4919faf..81898ab 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,16 +1,17 @@ { "name": "akiradocs", - "version": "1.0.46", + "version": "1.0.47", "private": true, "scripts": { "translate": "npm run compile && node scripts/translate.js", - "compile": "node scripts/compile.js", + "compile": "node scripts/compile.js && npm run generate-manifest", "generate-sitemap": "node scripts/generate-sitemap.mjs", "dev": "npm run compile && set NEXT_PUBLIC_AKIRADOCS_EDIT_MODE=true && next dev", "build": "npm run compile && npm run generate-sitemap && set NEXT_PUBLIC_AKIRADOCS_EDIT_MODE=false && next build", "start": "set NEXT_PUBLIC_AKIRADOCS_EDIT_MODE=false && next start", "lint": "next lint", - "migrate-gitbook": "node scripts/migrations/gitbook.js" + "migrate-gitbook": "node scripts/migrations/gitbook.js", + "generate-manifest": "node scripts/generate-manifest.js" }, "dependencies": { "@ai-sdk/anthropic": "^1.0.1", diff --git a/docs/scripts/generate-manifest.js b/docs/scripts/generate-manifest.js new file mode 100644 index 0000000..f88bc92 --- /dev/null +++ b/docs/scripts/generate-manifest.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const path = require('path'); + +function getAllFiles(dirPath, arrayOfFiles = []) { + const files = fs.readdirSync(dirPath); + + files.forEach(file => { + const fullPath = path.join(dirPath, file); + if (fs.statSync(fullPath).isDirectory()) { + getAllFiles(fullPath, arrayOfFiles); + } else { + arrayOfFiles.push( + fullPath.replace(path.join(process.cwd(), 'compiled/'), '') + ); + } + }); + + return arrayOfFiles; +} + +const manifest = { + files: getAllFiles(path.join(process.cwd(), 'compiled')) +}; + +fs.writeFileSync( + path.join(process.cwd(), 'compiled/manifest.json'), + JSON.stringify(manifest, null, 2) +); \ No newline at end of file diff --git a/docs/src/app/[locale]/[type]/[...slug]/page.tsx b/docs/src/app/[locale]/[type]/[...slug]/page.tsx index db4fce9..4111dac 100644 --- a/docs/src/app/[locale]/[type]/[...slug]/page.tsx +++ b/docs/src/app/[locale]/[type]/[...slug]/page.tsx @@ -13,14 +13,13 @@ import { PageNavigation } from '@/components/layout/PageNavigation' import { MainTitle, SubTitle } from '@/components/blocks/HeadingBlock' import { SEO } from '@/components/layout/SEO' import { NotFound } from '@/components/layout/NotFound' -import { TextToSpeech } from '@/components/tts/TextToSpeech' import { getAkiradocsConfig } from "@/lib/getAkiradocsConfig"; import { getTranslation } from '@/lib/staticTranslation'; import { ClientSideControls } from '@/components/layout/ClientSideControl'; import { Metadata } from 'next' -// export const runtime = 'edge' -export const dynamic = 'force-static'; +export const runtime = 'edge' +// export const dynamic = 'force-static'; const PostContainer = ({ children }: { children: React.ReactNode }) => (
@@ -36,33 +35,33 @@ type Props = { }>; } -export async function generateStaticParams() { - const locales = ['en', 'es', 'fr']; - const types = ['docs', 'api', 'articles']; - const allSlugs: { locale: string, type: string, slug: string[] }[] = []; +// export async function generateStaticParams() { +// const locales = ['en', 'es', 'fr']; +// const types = ['docs', 'api', 'articles']; +// const allSlugs: { locale: string, type: string, slug: string[] }[] = []; - locales.forEach(locale => { - types.forEach(type => { - const navigationItems = getContentNavigation({}, locale, type); - if (Array.isArray(navigationItems)) { - navigationItems.forEach(item => { - if (item.slug) { - allSlugs.push({ locale, type, slug: item.slug.split('/') }); - } - }); - } - }); - }); +// locales.forEach(locale => { +// types.forEach(type => { +// const navigationItems = getContentNavigation({}, locale, type); +// if (Array.isArray(navigationItems)) { +// navigationItems.forEach(item => { +// if (item.slug) { +// allSlugs.push({ locale, type, slug: item.slug.split('/') }); +// } +// }); +// } +// }); +// }); - return allSlugs; -} +// return allSlugs; +// } export async function generateMetadata({ params }: Props): Promise { const resolvedParams = await Promise.resolve(params); const { locale, type, slug: slugArray } = resolvedParams; const slug = slugArray.length ? slugArray.join('/') : ''; - const post = getContentBySlug(locale, type, slug); + const post = await getContentBySlug(locale, type, slug); const t = getTranslation(locale as 'en' | 'es' | 'fr'); const akiradocsConfig = getAkiradocsConfig(); @@ -94,15 +93,14 @@ export default async function ContentPage({ params }: Props) { const t = getTranslation(locale as 'en' | 'es' | 'de'); const slug = slugArray.length ? slugArray.join('/') : ''; - const post = getContentBySlug(locale, type, slug); - + const post = await getContentBySlug(locale, type, slug); if (!post) { return ; } const akiradocsConfig = getAkiradocsConfig(); const headerConfig = getHeaderConfig(); const footerConfig = getFooterConfig(); - const navigationItems = getContentNavigation({}, locale, type); + const navigationItems = await getContentNavigation({}, locale, type); const { prev, next } = getNextPrevPages(navigationItems, `/${type}/${slug}`); const pageTitle = t(post.title) || t('common.documentation'); const pageDescription = t(post.description) || t('common.documentationContent'); diff --git a/docs/src/app/[locale]/[type]/page.tsx b/docs/src/app/[locale]/[type]/page.tsx index 1b964cc..0b15df0 100644 --- a/docs/src/app/[locale]/[type]/page.tsx +++ b/docs/src/app/[locale]/[type]/page.tsx @@ -49,18 +49,33 @@ export async function generateMetadata({ params }: Props): Promise { } } +function formatRedirectUrl(locale: string, type: string, slug: string): string { + // Remove leading/trailing slashes + const cleanSlug = slug.replace(/^\/+|\/+$/g, ''); + + // Check if slug already contains locale and/or type + const parts = cleanSlug.split('/'); + const slugWithoutLocaleAndType = parts + .filter(part => part !== locale && part !== type) + .join('/'); + + return `/${locale}/${type}/${slugWithoutLocaleAndType}`; +} + export default async function Page({ params }: Props) { const resolvedParams = await Promise.resolve(params); const { locale, type } = resolvedParams; // Get the first/default content for this type - const recentContent = getRecentContent(`${locale}/${type}`); + const recentContent = await getRecentContent(`${locale}/${type}`); if (recentContent) { - const redirectUrl = `/${locale}/${type}/${recentContent.slug.replace(`${type}/`, '')}`; + const redirectUrl = formatRedirectUrl(locale, type, recentContent.slug); redirect(redirectUrl); } // Fallback redirect if no content found + console.log("redirecting to", `/${locale}`); redirect(`/${locale}`); } + diff --git a/docs/src/app/page.tsx b/docs/src/app/page.tsx index f91a87e..ce277e7 100644 --- a/docs/src/app/page.tsx +++ b/docs/src/app/page.tsx @@ -19,7 +19,7 @@ export async function generateMetadata() { export default async function DocPage() { const config = getAkiradocsConfig() const defaultLocale = config.localization.defaultLocale - const recentContent = getRecentContent(`${defaultLocale}/docs`) + const recentContent = await getRecentContent(`${defaultLocale}/docs`) if (recentContent) { const redirectUrl = `/${defaultLocale}/docs/${recentContent.slug.replace('docs/', '')}` diff --git a/docs/src/components/layout/Navigation.tsx b/docs/src/components/layout/Navigation.tsx index adecb05..5214cd3 100644 --- a/docs/src/components/layout/Navigation.tsx +++ b/docs/src/components/layout/Navigation.tsx @@ -133,8 +133,8 @@ const NavItem = React.memo(({ locale, item, pathname, depth = 0 }: NavItemProps) NavItem.displayName = 'NavItem' -export function ApiSidebar() { - const navigation = getApiNavigation(); +export async function ApiSidebar() { + const navigation = await getApiNavigation(); return ( diff --git a/docs/src/lib/content.ts b/docs/src/lib/content.ts index d44e357..65c14f2 100644 --- a/docs/src/lib/content.ts +++ b/docs/src/lib/content.ts @@ -1,26 +1,23 @@ import { Post } from '@/types/Block' -declare var require: { - context( - directory: string, - useSubdirectories: boolean, - regExp: RegExp - ): any; -}; -const contentContext = require.context(`../../compiled/`, true, /\.json$/) -export function getContentBySlug(locale: string, type: string, slug: string): Post | null { +// Create a function to get the JSON path +async function getJsonContent(path: string) { try { - // Construct the exact path we're looking for - const filePath = `./${locale}/${type}/${slug}.json`; - - // Check if the file exists in our context - if (!contentContext.keys().includes(filePath)) { - console.warn(`File not found: ${filePath}`); - return null; - } + // Dynamic import for JSON files + console.log(path) + const content = await import(`../../compiled/${path}`); + return content.default; + } catch (error) { + console.error(`Error loading content for ${path}:`, error); + return null; + } +} - // Load the content - const content = contentContext(filePath); +export async function getContentBySlug(locale: string, type: string, slug: string): Promise { + try { + const content = await getJsonContent(`${locale}/${type}/${slug}.json`); + if (!content) return null; + return { ...content, slug @@ -31,55 +28,40 @@ export function getContentBySlug(locale: string, type: string, slug: string): Po } } -export function getAllPosts(locale: string, type: string): Post[] { - const posts = contentContext.keys() - .filter((fileName: string) => { - // Only include files that match the exact locale and type path - const pattern = new RegExp(`^\./${locale}/${type}/[^/]+\.json$`); - return pattern.test(fileName) && !fileName.endsWith('/_meta.json'); - }) - .map((fileName: string) => { - try { - // Load the content directly using the full path - const content = contentContext(fileName); +export async function getAllPosts(locale: string, type: string): Promise { + // You'll need to maintain a manifest file that lists all available content + // For example, create a manifest.json in your compiled directory + const manifest = await import('../../compiled/manifest.json'); + + const posts = await Promise.all( + manifest.files + .filter((fileName: string) => { + const pattern = new RegExp(`^${locale}/${type}/[^/]+\.json$`); + return pattern.test(fileName) && !fileName.endsWith('/_meta.json'); + }) + .map(async (fileName: string) => { + const content = await getJsonContent(fileName); + if (!content) return null; + const slug = fileName - .replace(`\./${locale}/${type}/`, '') + .replace(`${locale}/${type}/`, '') .replace(/\.json$/, ''); + return { ...content, slug }; - } catch (error) { - console.error(`Error loading content for ${fileName}:`, error); - return null; - } - }) - .filter((post: unknown): post is Post => post !== null); + }) + ); - return posts; + return posts.filter((post): post is Post => post !== null); } -export function getContentNavigation(defaultValue: T, locale: string, type: string): T { +export async function getContentNavigation(defaultValue: T, locale: string, type: string): Promise { try { - const navigationFile = `./${locale}/${type}/_meta.json` - const navigation = contentContext(navigationFile) as T - - if (type === 'articles') { - // Get all articles and their content - const articles = contentContext.keys() - .filter((key: string) => key.startsWith(`./${locale}/${type}/`) && !key.endsWith('/_meta.json')) - .map((key: string) => ({ - path: key.replace(`./${locale}/${type}/`, '').replace('.json', ''), - content: contentContext(key) - })) - .sort((a: any, b: any) => new Date(b.content.date).getTime() - new Date(a.content.date).getTime()) - - // Assuming navigation is an object with a routes property - return { - ...navigation - } as T - } - + const navigationFile = `${locale}/${type}/_meta.json` + const navigation = await getJsonContent(navigationFile) + console.log(navigation) return navigation } catch (error) { console.warn(`Failed to read ${type} _meta.json file. Using default value.`) @@ -87,54 +69,52 @@ export function getContentNavigation(defaultValue: T, locale: string, type: s } } -export function getRecentContent(folderPath: string) { +export async function getRecentContent(folderPath: string) { try { // For non-article paths, get default route from _meta.json - if (!folderPath.includes('/articles/')) { - const metaContent = contentContext(`./${folderPath}/_meta.json`) + // if (!folderPath.includes('/articles/')) { + const metaContent = await getJsonContent(`${folderPath}/_meta.json`) if (metaContent?.defaultRoute) { return { slug: metaContent.defaultRoute } } - } - - // Existing logic for articles - const files = contentContext.keys() - .filter((key: string) => key.startsWith(`./${folderPath}/`) && key.endsWith('.json') && !key.endsWith('/_meta.json')) - - if (files.length === 0) { - return null - } - - // Sort files by their content's date field - const sortedFiles = files - .map((file: string) => ({ - name: file, - content: contentContext(file) - })) - .sort((a: any, b: any) => new Date(b.content.date).getTime() - new Date(a.content.date).getTime()) - - return { - slug: sortedFiles[0].name.replace(`./${folderPath}/`, '').replace('.json', '') - } + // } + + // // Existing logic for articles + // const metaContent = await getJsonContent(`${folderPath}/_meta.json`) + // const files = metaContent?.filter((key: string) => + // key.startsWith(`./${folderPath}/`) && + // key.endsWith('.json') && + // !key.endsWith('/_meta.json') + // ) + + // if (!files || files.length === 0) { + // return null + // } + + // // Sort files by their content's date field + // const sortedFiles = await Promise.all( + // files + // .map(async (file: string) => ({ + // name: file, + // content: await getJsonContent(file) + // })) + // .sort((a: any, b: any) => new Date(b.content.date).getTime() - new Date(a.content.date).getTime()) + // ) + // console.log("sortedFiles", sortedFiles) + // return { + // slug: sortedFiles[0].name.replace(`./${folderPath}/`, '').replace('.json', '') + // } } catch (error) { console.error('Error finding recent article:', error) return null } } -export function folderExists(folderPath: string): boolean { - try { - return contentContext.keys().some((key: string) => key.startsWith(`./${folderPath}/`)) - } catch (error) { - console.error('Error checking folder existence:', error) - return false - } -} -export function get_api_spec(): any { +export async function get_api_spec(): Promise { try { - return contentContext('./en/api/apiSpec.json') + return await getJsonContent('en/api/apiSpec.json') } catch (error) { console.error('Error reading API spec file:', error) return null @@ -148,9 +128,10 @@ interface ApiNavItem { children?: ApiNavItem[]; } -export function getApiNavigation(): ApiNavItem[] { +export async function getApiNavigation(): Promise { try { - const apiSpec = get_api_spec(); + const apiSpec = await get_api_spec(); + console.log("apiSpec", apiSpec) if (!apiSpec || !apiSpec.paths) { return []; } diff --git a/packages/akiradocs/package.json b/packages/akiradocs/package.json index cd23c8a..a391fc2 100644 --- a/packages/akiradocs/package.json +++ b/packages/akiradocs/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "translate": "npm run compile && node scripts/translate.js", - "compile": "node scripts/compile.js", + "compile": "node scripts/compile.js && npm run generate-manifest", "generate-sitemap": "node scripts/generate-sitemap.mjs", "dev": "npm run compile && set NEXT_PUBLIC_AKIRADOCS_EDIT_MODE=true && next dev", "build": "npm run compile && npm run generate-sitemap && set NEXT_PUBLIC_AKIRADOCS_EDIT_MODE=false && next build", diff --git a/packages/create-app/package.json b/packages/create-app/package.json index caf1ad8..0d2f666 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "create-akiradocs", - "version": "1.0.46", + "version": "1.0.47", "description": "Create Akira Docs documentation sites with one command", "main": "./dist/index.js", "type": "module",