From f8c67e64379317b5529187bad3445661cb139f5b Mon Sep 17 00:00:00 2001 From: seesharprun Date: Wed, 3 Dec 2025 11:30:46 -0500 Subject: [PATCH 1/4] Add sitemap functionality for SEO --- app/sitemap.ts | 174 ++++++++++++++++++++++++++++++++++++++++++++++ public/robots.txt | 11 +++ 2 files changed, 185 insertions(+) create mode 100644 app/sitemap.ts create mode 100644 public/robots.txt diff --git a/app/sitemap.ts b/app/sitemap.ts new file mode 100644 index 0000000..e5b3f81 --- /dev/null +++ b/app/sitemap.ts @@ -0,0 +1,174 @@ +import { MetadataRoute } from 'next'; +import { getAllArticlePaths } from './services/articleService'; +import { getAllReferenceParams } from './services/referenceService'; +import { execSync } from 'child_process'; +import path from 'path'; + +// Required to evaluate at build time +export const dynamic = 'force-static'; + +// Determine base URL +const getBaseUrl = (): string => 'https://documentdb.io'; + +// Get the last git commit date for a specific file +const getGitLastModified = (filePath: string): Date => { + try { + // Convert absolute path to relative path from repo root + const relativePath = path.relative(process.cwd(), filePath).replace(/\\/g, '/'); + const output = execSync( + `git log -1 --format=%cI -- "${relativePath}"`, + { encoding: 'utf-8', cwd: process.cwd() } + ).trim(); + + return output ? new Date(output) : new Date(); + } catch (error) { + console.warn(`Could not get git date for ${filePath}`); + return new Date(); + } +}; + +// Get the most recent git commit date for a directory +const getDirectoryLastModified = (dirPath: string): Date => { + try { + // Convert absolute path to relative path from repo root + const relativePath = path.relative(process.cwd(), dirPath).replace(/\\/g, '/'); + const output = execSync( + `git log -1 --format=%cI -- "${relativePath}"`, + { encoding: 'utf-8', cwd: process.cwd() } + ).trim(); + + return output ? new Date(output) : new Date(); + } catch (error) { + console.warn(`Could not get git date for directory ${dirPath}`); + return new Date(); + } +}; + +export default function sitemap(): MetadataRoute.Sitemap { + const baseUrl = getBaseUrl(); + const currentDate = new Date(); + + const sitemapEntries: MetadataRoute.Sitemap = []; + + // 1. Homepage + sitemapEntries.push({ + url: baseUrl, + lastModified: currentDate, + changeFrequency: 'yearly', + priority: 1.0, + }); + + // 2. Main section pages + const mainSections = [ + { path: '/docs', dirPath: 'articles' }, + { path: '/blogs', dirPath: 'blogs' }, + { path: '/packages', dirPath: 'app/packages' }, + { path: '/docs/reference', dirPath: 'reference' }, + ]; + + mainSections.forEach(section => { + const dirFullPath = path.join(process.cwd(), section.dirPath); + const lastModified = getDirectoryLastModified(dirFullPath); + + sitemapEntries.push({ + url: `${baseUrl}${section.path}`, + lastModified, + changeFrequency: 'monthly', + priority: 0.70, + }); + }); + + // 3. Article/Documentation pages + try { + const articlePaths = getAllArticlePaths(); + + articlePaths.forEach(({ section, slug }) => { + // Build the URL path + let urlPath = `/docs/${section}`; + if (slug.length > 0) { + urlPath += `/${slug.join('/')}`; + } + + // Determine the markdown file path + const mdFilePath = path.join(process.cwd(), 'articles', section, ...slug, 'index.md'); + const lastModified = getGitLastModified(mdFilePath); + + sitemapEntries.push({ + url: `${baseUrl}${urlPath}`, + lastModified, + changeFrequency: 'weekly', + priority: 0.70, + }); + }); + } catch (error) { + console.error('Error generating article sitemap entries:', error); + } + + // 4. Blog posts + // Note: Blog posts are external URIs, so we don't include them in the sitemap + // as they redirect to external sites. The /blogs page itself is already included above. + + // 5. Reference documentation pages + try { + const referenceParams = getAllReferenceParams(); + + referenceParams.forEach(({ type, category, name }) => { + const urlPath = `/docs/reference/${type}/${category}/${name}`; + + // Determine the markdown file path + const mdFilePath = path.join(process.cwd(), 'reference', type, category, `${name}.md`); + const lastModified = getGitLastModified(mdFilePath); + + sitemapEntries.push({ + url: `${baseUrl}${urlPath}`, + lastModified, + changeFrequency: 'weekly', + priority: 0.85, + }); + }); + } catch (error) { + console.error('Error generating reference sitemap entries:', error); + } + + // 6. Reference type index pages (e.g., /docs/reference/commands, /docs/reference/operators) + try { + const referenceParams = getAllReferenceParams(); + const uniqueTypes = [...new Set(referenceParams.map(p => p.type))]; + + uniqueTypes.forEach(type => { + const dirPath = path.join(process.cwd(), 'reference', type); + const lastModified = getDirectoryLastModified(dirPath); + + sitemapEntries.push({ + url: `${baseUrl}/docs/reference/${type}`, + lastModified, + changeFrequency: 'monthly', + priority: 0.55, + }); + }); + } catch (error) { + console.error('Error generating reference type index sitemap entries:', error); + } + + // 7. Reference category pages (e.g., /docs/reference/operators/miscellaneous-query) + try { + const referenceParams = getAllReferenceParams(); + const uniqueCategories = [...new Set(referenceParams.map(p => `${p.type}/${p.category}`))]; + + uniqueCategories.forEach(typeCategory => { + const dirPath = path.join(process.cwd(), 'reference', ...typeCategory.split('/')); + const lastModified = getDirectoryLastModified(dirPath); + + sitemapEntries.push({ + url: `${baseUrl}/docs/reference/${typeCategory}`, + lastModified, + changeFrequency: 'monthly', + priority: 0.55, + }); + }); + } catch (error) { + console.error('Error generating reference category sitemap entries:', error); + } + + return sitemapEntries; +} diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..50970b8 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,11 @@ +# robots.txt for DocumentDB documentation site + +# Allow all crawlers +User-agent: * +Allow: / + +# Sitemap location +Sitemap: https://documentdb.io/sitemap.xml + +# Crawl-delay (optional, uncomment if needed) +# Crawl-delay: 1 From 94d23faa1ee066836746a1e07f329d7c7afec0cf Mon Sep 17 00:00:00 2001 From: seesharprun Date: Thu, 4 Dec 2025 10:18:33 -0500 Subject: [PATCH 2/4] Split content collection into reusable service --- app/services/contentService.ts | 183 +++++++++++++++++++++++++++++++++ app/services/siteService.ts | 4 + app/sitemap.ts | 180 +++++++------------------------- app/types/Entity.ts | 10 ++ 4 files changed, 235 insertions(+), 142 deletions(-) create mode 100644 app/services/contentService.ts create mode 100644 app/services/siteService.ts create mode 100644 app/types/Entity.ts diff --git a/app/services/contentService.ts b/app/services/contentService.ts new file mode 100644 index 0000000..f70ec89 --- /dev/null +++ b/app/services/contentService.ts @@ -0,0 +1,183 @@ +import { getAllArticlePaths, getArticleByPath } from './articleService'; +import { getAllPosts } from './blogService'; +import { getReferencesGroupedByTypeAndCategory, getAllReferenceParams, getReferenceByPath } from './referenceService'; +import path from 'path'; +import { Entity } from '../types/Entity'; + +export function getAllContent(): Entity[] { + const pages: Entity[] = []; + + // 1. Home page + pages.push({ + url: '/', + slug: 'home.png', + title: 'DocumentDB', + description: 'A powerful, scalable open-source document database solution', + type: 'home', + }); + + // 2. Docs landing + pages.push({ + url: '/docs', + slug: 'docs.png', + title: 'Documentation', + description: 'Complete DocumentDB documentation and guides', + section: 'docs', + type: 'landing', + }); + + // 3. All article/documentation pages + const articlePaths = getAllArticlePaths(); + for (const articlePath of articlePaths) { + const article = getArticleByPath(articlePath.section, articlePath.slug); + if (article) { + const selectedNavItem = article.navigation.find((item: any) => + item.link.includes(articlePath.slug[articlePath.slug.length - 1] || 'index') + ); + const title = article.frontmatter.title || selectedNavItem?.title || articlePath.section; + + let url = `/docs/${articlePath.section}`; + if (articlePath.slug.length > 0) { + url += `/${articlePath.slug.join('/')}`; + } + + const mdFilePath = path.join( + process.cwd(), + 'articles', + articlePath.section, + ...articlePath.slug, + 'index.md' + ); + + // Convert URL to slug filename + const slug = url === '/' + ? 'home.png' + : url.slice(1).replace(/\//g, '-') + '.png'; + + pages.push({ + url, + slug, + title, + description: article.frontmatter.description || `${title} - DocumentDB Documentation`, + section: articlePath.section, + type: 'docs', + filePath: mdFilePath, + }); + } + } + + // 4. Blogs landing + pages.push({ + url: '/blogs', + slug: 'blogs.png', + title: 'Blog', + description: 'Latest insights and updates from DocumentDB', + section: 'blog', + type: 'landing', + }); + + // 5. Individual blog posts (external URIs) + const posts = getAllPosts(); + for (const post of posts) { + // Generate slug from title (for external blog posts without slugs) + const slug = post.title + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); + + const url = `/blogs/${slug}`; + const filename = url.slice(1).replace(/\//g, '-') + '.png'; + + pages.push({ + url, + slug: filename, + title: post.title, + description: post.description, + section: 'blog', + type: 'blog', + isExternal: true, // Mark as external since these redirect to external URIs + }); + } + + // 6. Reference landing page + pages.push({ + url: '/docs/reference', + slug: 'docs-reference.png', + title: 'API Reference', + description: 'Complete DocumentDB API reference documentation', + section: 'reference', + type: 'landing', + }); + + // 7. Reference type pages (e.g., /docs/reference/commands) + const referenceContent = getReferencesGroupedByTypeAndCategory(); + for (const [type] of Object.entries(referenceContent)) { + const url = `/docs/reference/${type}`; + const slug = url.slice(1).replace(/\//g, '-') + '.png'; + + pages.push({ + url, + slug, + title: `${type.charAt(0).toUpperCase() + type.slice(1)} Reference`, + description: `DocumentDB ${type} reference documentation`, + section: 'reference', + type: 'landing', + }); + } + + // 8. Reference category pages (e.g., /docs/reference/operators/aggregation) + for (const [type, categories] of Object.entries(referenceContent)) { + for (const [category] of Object.entries(categories)) { + const url = `/docs/reference/${type}/${category}`; + const slug = url.slice(1).replace(/\//g, '-') + '.png'; + + pages.push({ + url, + slug, + title: `${category} - ${type.charAt(0).toUpperCase() + type.slice(1)}`, + description: `${category} ${type} reference documentation`, + section: 'reference', + type: 'landing', + }); + } + } + + // 9. Individual reference items + const referenceParams = getAllReferenceParams(); + for (const param of referenceParams) { + const reference = getReferenceByPath(param.type, param.category, param.name); + if (reference) { + const url = `/docs/reference/${param.type}/${param.category}/${param.name}`; + const slug = url.slice(1).replace(/\//g, '-') + '.png'; + + const mdFilePath = path.join( + process.cwd(), + 'reference', + param.type, + param.category, + `${param.name}.yml` + ); + + pages.push({ + url, + slug, + title: reference.name || param.name, + description: reference.description || `${param.name} - DocumentDB Reference`, + section: 'reference', + type: 'reference', + filePath: mdFilePath, + }); + } + } + + // 10. Packages page + pages.push({ + url: '/packages', + slug: 'packages.png', + title: 'Packages', + description: 'Download and install DocumentDB packages', + type: 'packages', + }); + + return pages; +} diff --git a/app/services/siteService.ts b/app/services/siteService.ts new file mode 100644 index 0000000..ed2cf5a --- /dev/null +++ b/app/services/siteService.ts @@ -0,0 +1,4 @@ +// Determine base URL for entire site +export function getBaseUrl(): string { + return 'https://documentdb.io'; +} \ No newline at end of file diff --git a/app/sitemap.ts b/app/sitemap.ts index e5b3f81..8368a4c 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,15 +1,12 @@ import { MetadataRoute } from 'next'; -import { getAllArticlePaths } from './services/articleService'; -import { getAllReferenceParams } from './services/referenceService'; +import { getAllContent } from './services/contentService'; +import { getBaseUrl } from './services/siteService'; import { execSync } from 'child_process'; import path from 'path'; // Required to evaluate at build time export const dynamic = 'force-static'; -// Determine base URL -const getBaseUrl = (): string => 'https://documentdb.io'; - // Get the last git commit date for a specific file const getGitLastModified = (filePath: string): Date => { try { @@ -27,148 +24,47 @@ const getGitLastModified = (filePath: string): Date => { } }; -// Get the most recent git commit date for a directory -const getDirectoryLastModified = (dirPath: string): Date => { - try { - // Convert absolute path to relative path from repo root - const relativePath = path.relative(process.cwd(), dirPath).replace(/\\/g, '/'); - const output = execSync( - `git log -1 --format=%cI -- "${relativePath}"`, - { encoding: 'utf-8', cwd: process.cwd() } - ).trim(); - - return output ? new Date(output) : new Date(); - } catch (error) { - console.warn(`Could not get git date for directory ${dirPath}`); - return new Date(); - } -}; - export default function sitemap(): MetadataRoute.Sitemap { const baseUrl = getBaseUrl(); - const currentDate = new Date(); - - const sitemapEntries: MetadataRoute.Sitemap = []; - - // 1. Homepage - sitemapEntries.push({ - url: baseUrl, - lastModified: currentDate, - changeFrequency: 'yearly', - priority: 1.0, - }); - - // 2. Main section pages - const mainSections = [ - { path: '/docs', dirPath: 'articles' }, - { path: '/blogs', dirPath: 'blogs' }, - { path: '/packages', dirPath: 'app/packages' }, - { path: '/docs/reference', dirPath: 'reference' }, - ]; - - mainSections.forEach(section => { - const dirFullPath = path.join(process.cwd(), section.dirPath); - const lastModified = getDirectoryLastModified(dirFullPath); - - sitemapEntries.push({ - url: `${baseUrl}${section.path}`, - lastModified, - changeFrequency: 'monthly', - priority: 0.70, - }); - }); - - // 3. Article/Documentation pages - try { - const articlePaths = getAllArticlePaths(); - - articlePaths.forEach(({ section, slug }) => { - // Build the URL path - let urlPath = `/docs/${section}`; - if (slug.length > 0) { - urlPath += `/${slug.join('/')}`; + const entities = getAllContent(); + + return entities + .filter(entity => !entity.isExternal) // Exclude external blog posts + .map(entity => { + // Determine priority and change frequency based on page type and depth + let priority = 0.00; + let changeFrequency: 'yearly' | 'monthly' | 'weekly' = 'yearly'; + + if (entity.type === 'home') { + priority = 1.0; + changeFrequency = 'yearly'; + } else if (entity.type === 'docs') { + priority = 0.85; + changeFrequency = 'weekly'; + } else if (entity.type === 'packages') { + priority = 0.70; + changeFrequency = 'weekly'; + } else if (entity.type === 'reference') { + priority = 0.55; + changeFrequency = 'monthly'; + } else if (entity.type === 'blog') { + priority = 0.40; + changeFrequency = 'monthly'; + } else if (entity.type === 'landing') { + priority = 0.25; + changeFrequency = 'monthly'; } - // Determine the markdown file path - const mdFilePath = path.join(process.cwd(), 'articles', section, ...slug, 'index.md'); - const lastModified = getGitLastModified(mdFilePath); - - sitemapEntries.push({ - url: `${baseUrl}${urlPath}`, - lastModified, - changeFrequency: 'weekly', - priority: 0.70, - }); - }); - } catch (error) { - console.error('Error generating article sitemap entries:', error); - } - - // 4. Blog posts - // Note: Blog posts are external URIs, so we don't include them in the sitemap - // as they redirect to external sites. The /blogs page itself is already included above. - - // 5. Reference documentation pages - try { - const referenceParams = getAllReferenceParams(); - - referenceParams.forEach(({ type, category, name }) => { - const urlPath = `/docs/reference/${type}/${category}/${name}`; - - // Determine the markdown file path - const mdFilePath = path.join(process.cwd(), 'reference', type, category, `${name}.md`); - const lastModified = getGitLastModified(mdFilePath); - - sitemapEntries.push({ - url: `${baseUrl}${urlPath}`, - lastModified, - changeFrequency: 'weekly', - priority: 0.85, - }); - }); - } catch (error) { - console.error('Error generating reference sitemap entries:', error); - } - - // 6. Reference type index pages (e.g., /docs/reference/commands, /docs/reference/operators) - try { - const referenceParams = getAllReferenceParams(); - const uniqueTypes = [...new Set(referenceParams.map(p => p.type))]; - - uniqueTypes.forEach(type => { - const dirPath = path.join(process.cwd(), 'reference', type); - const lastModified = getDirectoryLastModified(dirPath); + // Get last modified date from git + const lastModified = entity.filePath + ? getGitLastModified(entity.filePath) + : new Date(); - sitemapEntries.push({ - url: `${baseUrl}/docs/reference/${type}`, + return { + url: `${baseUrl}${entity.url}`, lastModified, - changeFrequency: 'monthly', - priority: 0.55, - }); + changeFrequency, + priority, + }; }); - } catch (error) { - console.error('Error generating reference type index sitemap entries:', error); - } - - // 7. Reference category pages (e.g., /docs/reference/operators/miscellaneous-query) - try { - const referenceParams = getAllReferenceParams(); - const uniqueCategories = [...new Set(referenceParams.map(p => `${p.type}/${p.category}`))]; - - uniqueCategories.forEach(typeCategory => { - const dirPath = path.join(process.cwd(), 'reference', ...typeCategory.split('/')); - const lastModified = getDirectoryLastModified(dirPath); - - sitemapEntries.push({ - url: `${baseUrl}/docs/reference/${typeCategory}`, - lastModified, - changeFrequency: 'monthly', - priority: 0.55, - }); - }); - } catch (error) { - console.error('Error generating reference category sitemap entries:', error); - } - - return sitemapEntries; } diff --git a/app/types/Entity.ts b/app/types/Entity.ts new file mode 100644 index 0000000..72a79af --- /dev/null +++ b/app/types/Entity.ts @@ -0,0 +1,10 @@ +export interface Entity { + url: string; // Relative URL path (e.g., '/docs/quickstart') + slug?: string; // Where to save assets (relative path) + title: string; // Page title + description: string; // Page description + section?: string; // Section category (e.g., 'docs', 'blog', 'reference') + filePath?: string; // Source file path (for git lastModified) + isExternal?: boolean; // Indicates if the content is external + type: 'home' | 'landing' | 'docs' | 'blog' | 'reference' | 'packages'; +} \ No newline at end of file From a5bf57ac0d503f5748df9a7154951fb38a0312bf Mon Sep 17 00:00:00 2001 From: Sidney Andrews Date: Thu, 4 Dec 2025 10:30:54 -0500 Subject: [PATCH 3/4] Apply suggestion from @seesharprun --- app/types/Entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/types/Entity.ts b/app/types/Entity.ts index 72a79af..34acf38 100644 --- a/app/types/Entity.ts +++ b/app/types/Entity.ts @@ -1,6 +1,6 @@ export interface Entity { url: string; // Relative URL path (e.g., '/docs/quickstart') - slug?: string; // Where to save assets (relative path) + slug: string; // Where to save assets (relative path) title: string; // Page title description: string; // Page description section?: string; // Section category (e.g., 'docs', 'blog', 'reference') From bf43978d5a55ed4376139ff0ca778e6ebd73cdaa Mon Sep 17 00:00:00 2001 From: seesharprun Date: Fri, 5 Dec 2025 11:20:03 -0500 Subject: [PATCH 4/4] Enhance SEO image generation --- .github/workflows/continuous-deployment.yml | 23 +- .gitignore | 5 +- app/docs/[section]/[[...slug]]/page.tsx | 8 +- .../[type]/[category]/[name]/page.tsx | 3 +- app/docs/reference/[type]/[category]/page.tsx | 3 +- app/docs/reference/[type]/page.tsx | 9 +- app/docs/reference/layout.tsx | 10 +- app/services/contentService.ts | 183 +++ app/services/metadataService.ts | 82 +- app/services/siteService.ts | 9 + app/types/Entity.ts | 10 + package-lock.json | 1404 +++++++++++++++-- package.json | 14 +- scripts/generate-open-graph-images.tsx | 446 ++++++ scripts/package.json | 3 + 15 files changed, 2057 insertions(+), 155 deletions(-) create mode 100644 app/services/contentService.ts create mode 100644 app/services/siteService.ts create mode 100644 app/types/Entity.ts create mode 100644 scripts/generate-open-graph-images.tsx create mode 100644 scripts/package.json diff --git a/.github/workflows/continuous-deployment.yml b/.github/workflows/continuous-deployment.yml index d86b912..6614670 100644 --- a/.github/workflows/continuous-deployment.yml +++ b/.github/workflows/continuous-deployment.yml @@ -50,28 +50,11 @@ jobs: # Configure DocumentDB version (can be overridden by repository variables) echo "DOCUMENTDB_VERSION=${{ vars.DOCUMENTDB_VERSION || 'latest' }}" >> $GITHUB_ENV echo "MULTI_VERSION=${{ vars.MULTI_VERSION || 'true' }}" >> $GITHUB_ENV - - name: Detect package manager - id: detect-package-manager - run: | - if [ -f "${{ github.workspace }}/yarn.lock" ]; then - echo "manager=yarn" >> $GITHUB_OUTPUT - echo "command=install" >> $GITHUB_OUTPUT - echo "runner=yarn" >> $GITHUB_OUTPUT - exit 0 - elif [ -f "${{ github.workspace }}/package.json" ]; then - echo "manager=npm" >> $GITHUB_OUTPUT - echo "command=ci" >> $GITHUB_OUTPUT - echo "runner=npx --no-install" >> $GITHUB_OUTPUT - exit 0 - else - echo "Unable to determine package manager" - exit 1 - fi - name: Setup Node.js uses: actions/setup-node@v5 with: node-version: 24 - cache: ${{ steps.detect-package-manager.outputs.manager }} + cache: npm - name: Restore cache uses: actions/cache@v4 with: @@ -83,11 +66,11 @@ jobs: restore-keys: | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}- - name: Install dependencies - run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} + run: npm ci - name: Build with Next.js env: NEXT_BASE_PATH: ${{ github.event.repository.name }} - run: ${{ steps.detect-package-manager.outputs.runner }} next build + run: npm run build - name: Download DocumentDB packages from latest release run: .github/scripts/download_packages.sh - name: Upload artifact diff --git a/.gitignore b/.gitignore index 8180dc6..78ef856 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,7 @@ yarn-error.log* # typescript *.tsbuildinfo -next-env.d.ts \ No newline at end of file +next-env.d.ts + +# Built resources +/public/open-graph/**/* \ No newline at end of file diff --git a/app/docs/[section]/[[...slug]]/page.tsx b/app/docs/[section]/[[...slug]]/page.tsx index 12d8ebc..519a808 100644 --- a/app/docs/[section]/[[...slug]]/page.tsx +++ b/app/docs/[section]/[[...slug]]/page.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; import { notFound } from 'next/navigation'; import { getAllArticlePaths, getArticleByPath } from "../../../services/articleService"; +import { getMetadata } from "../../../services/metadataService"; import ComingSoon from "../../../components/ComingSoon"; import Markdown from "../../../components/Markdown"; @@ -34,10 +35,11 @@ export async function generateMetadata({ params }: PageProps) { const selectedNavItem = navigation.find((item) => item.link.includes(file)); const pageTitle = frontmatter.title || selectedNavItem?.title || section; - return { + return getMetadata({ title: `${pageTitle} - DocumentDB Documentation`, - description: frontmatter.description || undefined, - }; + description: frontmatter.description || `${pageTitle} - DocumentDB Documentation`, + pagePath: `docs/${section}${slug.length ? '/' + slug.join('/') : ''}` + }); } export default async function ArticlePage({ params }: PageProps) { diff --git a/app/docs/reference/[type]/[category]/[name]/page.tsx b/app/docs/reference/[type]/[category]/[name]/page.tsx index 1cc9eda..db4050f 100644 --- a/app/docs/reference/[type]/[category]/[name]/page.tsx +++ b/app/docs/reference/[type]/[category]/[name]/page.tsx @@ -12,7 +12,8 @@ export async function generateMetadata({ params }: { params: Promise<{ type: str return getMetadata({ title: `${data?.name || 'Reference'} - DocumentDB MQL Reference`, description: data?.description || data?.summary || '', - extraKeywords: ['reference', type, category, name] + extraKeywords: ['reference', type, category, name], + pagePath: `docs/reference/${type}/${category}/${name}` }); } diff --git a/app/docs/reference/[type]/[category]/page.tsx b/app/docs/reference/[type]/[category]/page.tsx index 3f68527..20bf850 100644 --- a/app/docs/reference/[type]/[category]/page.tsx +++ b/app/docs/reference/[type]/[category]/page.tsx @@ -18,7 +18,8 @@ export async function generateMetadata({ params }: { params: Promise<{ type: str return getMetadata({ title: `${title} - DocumentDB MQL Reference`, description: description || '', - extraKeywords: ['reference', type, category] + extraKeywords: ['reference', type, category], + pagePath: `docs/reference/${type}/${category}` }); } diff --git a/app/docs/reference/[type]/page.tsx b/app/docs/reference/[type]/page.tsx index d43991b..c57647b 100644 --- a/app/docs/reference/[type]/page.tsx +++ b/app/docs/reference/[type]/page.tsx @@ -7,11 +7,11 @@ import { getMetadata } from "../../../services/metadataService"; import pluralize from 'pluralize'; import { capitalCase } from 'change-case'; -const allowed_types = ['operator', 'command']; +const allowed_types = ['operators', 'commands']; export const generateStaticParams = async (): Promise<{ type: string }[]> => [ - { type: 'operator' }, - { type: 'command' } + { type: 'operators' }, + { type: 'commands' } ]; export async function generateMetadata({ params }: { params: Promise<{ type: string }> }) { @@ -21,7 +21,8 @@ export async function generateMetadata({ params }: { params: Promise<{ type: str return getMetadata({ title: `${title} - DocumentDB MQL Reference`, description: description || '', - extraKeywords: ['reference', type] + extraKeywords: ['reference', type], + pagePath: `docs/reference/${type}` }); } diff --git a/app/docs/reference/layout.tsx b/app/docs/reference/layout.tsx index b54a3a4..8e50411 100644 --- a/app/docs/reference/layout.tsx +++ b/app/docs/reference/layout.tsx @@ -18,7 +18,7 @@ export default function ReferenceLayout({ const groupedReferences = getReferencesGroupedByTypeAndCategory(); return ( -
+
{/* Background elements */}
@@ -28,9 +28,9 @@ export default function ReferenceLayout({ style={{ animationDelay: "1.5s" }} >
-
-
-
+
+
+
-
+
{children}
diff --git a/app/services/contentService.ts b/app/services/contentService.ts new file mode 100644 index 0000000..f70ec89 --- /dev/null +++ b/app/services/contentService.ts @@ -0,0 +1,183 @@ +import { getAllArticlePaths, getArticleByPath } from './articleService'; +import { getAllPosts } from './blogService'; +import { getReferencesGroupedByTypeAndCategory, getAllReferenceParams, getReferenceByPath } from './referenceService'; +import path from 'path'; +import { Entity } from '../types/Entity'; + +export function getAllContent(): Entity[] { + const pages: Entity[] = []; + + // 1. Home page + pages.push({ + url: '/', + slug: 'home.png', + title: 'DocumentDB', + description: 'A powerful, scalable open-source document database solution', + type: 'home', + }); + + // 2. Docs landing + pages.push({ + url: '/docs', + slug: 'docs.png', + title: 'Documentation', + description: 'Complete DocumentDB documentation and guides', + section: 'docs', + type: 'landing', + }); + + // 3. All article/documentation pages + const articlePaths = getAllArticlePaths(); + for (const articlePath of articlePaths) { + const article = getArticleByPath(articlePath.section, articlePath.slug); + if (article) { + const selectedNavItem = article.navigation.find((item: any) => + item.link.includes(articlePath.slug[articlePath.slug.length - 1] || 'index') + ); + const title = article.frontmatter.title || selectedNavItem?.title || articlePath.section; + + let url = `/docs/${articlePath.section}`; + if (articlePath.slug.length > 0) { + url += `/${articlePath.slug.join('/')}`; + } + + const mdFilePath = path.join( + process.cwd(), + 'articles', + articlePath.section, + ...articlePath.slug, + 'index.md' + ); + + // Convert URL to slug filename + const slug = url === '/' + ? 'home.png' + : url.slice(1).replace(/\//g, '-') + '.png'; + + pages.push({ + url, + slug, + title, + description: article.frontmatter.description || `${title} - DocumentDB Documentation`, + section: articlePath.section, + type: 'docs', + filePath: mdFilePath, + }); + } + } + + // 4. Blogs landing + pages.push({ + url: '/blogs', + slug: 'blogs.png', + title: 'Blog', + description: 'Latest insights and updates from DocumentDB', + section: 'blog', + type: 'landing', + }); + + // 5. Individual blog posts (external URIs) + const posts = getAllPosts(); + for (const post of posts) { + // Generate slug from title (for external blog posts without slugs) + const slug = post.title + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); + + const url = `/blogs/${slug}`; + const filename = url.slice(1).replace(/\//g, '-') + '.png'; + + pages.push({ + url, + slug: filename, + title: post.title, + description: post.description, + section: 'blog', + type: 'blog', + isExternal: true, // Mark as external since these redirect to external URIs + }); + } + + // 6. Reference landing page + pages.push({ + url: '/docs/reference', + slug: 'docs-reference.png', + title: 'API Reference', + description: 'Complete DocumentDB API reference documentation', + section: 'reference', + type: 'landing', + }); + + // 7. Reference type pages (e.g., /docs/reference/commands) + const referenceContent = getReferencesGroupedByTypeAndCategory(); + for (const [type] of Object.entries(referenceContent)) { + const url = `/docs/reference/${type}`; + const slug = url.slice(1).replace(/\//g, '-') + '.png'; + + pages.push({ + url, + slug, + title: `${type.charAt(0).toUpperCase() + type.slice(1)} Reference`, + description: `DocumentDB ${type} reference documentation`, + section: 'reference', + type: 'landing', + }); + } + + // 8. Reference category pages (e.g., /docs/reference/operators/aggregation) + for (const [type, categories] of Object.entries(referenceContent)) { + for (const [category] of Object.entries(categories)) { + const url = `/docs/reference/${type}/${category}`; + const slug = url.slice(1).replace(/\//g, '-') + '.png'; + + pages.push({ + url, + slug, + title: `${category} - ${type.charAt(0).toUpperCase() + type.slice(1)}`, + description: `${category} ${type} reference documentation`, + section: 'reference', + type: 'landing', + }); + } + } + + // 9. Individual reference items + const referenceParams = getAllReferenceParams(); + for (const param of referenceParams) { + const reference = getReferenceByPath(param.type, param.category, param.name); + if (reference) { + const url = `/docs/reference/${param.type}/${param.category}/${param.name}`; + const slug = url.slice(1).replace(/\//g, '-') + '.png'; + + const mdFilePath = path.join( + process.cwd(), + 'reference', + param.type, + param.category, + `${param.name}.yml` + ); + + pages.push({ + url, + slug, + title: reference.name || param.name, + description: reference.description || `${param.name} - DocumentDB Reference`, + section: 'reference', + type: 'reference', + filePath: mdFilePath, + }); + } + } + + // 10. Packages page + pages.push({ + url: '/packages', + slug: 'packages.png', + title: 'Packages', + description: 'Download and install DocumentDB packages', + type: 'packages', + }); + + return pages; +} diff --git a/app/services/metadataService.ts b/app/services/metadataService.ts index f88ed07..6232b47 100644 --- a/app/services/metadataService.ts +++ b/app/services/metadataService.ts @@ -1,34 +1,64 @@ import { Metadata } from "next"; +import { getBaseUrl, getOpenGraphImageRelativePath } from './siteService'; -export const getMetadata = ({ title, description, extraKeywords = [] }: { title: string, description: string, extraKeywords?: string[] }): Metadata => ({ - keywords: [...getBaseKeywords(), ...extraKeywords], - title, - description, - openGraph: { - type: 'article', - title, - description, - images: [ - { - url: 'https://documentdb.io/images/social-card.png', - } - ] - }, - twitter: { - card: 'summary_large_image', +interface MetadataParams { + title: string; + description: string; + extraKeywords?: string[]; + pagePath?: string; +} + +export const getMetadata = ({ + title, + description, + extraKeywords = [], + pagePath +}: MetadataParams): Metadata => { + const baseUrl = getBaseUrl(); + + // Always generate the custom OG image URL + const customOGImage = `${baseUrl}${getImagePath(pagePath)}`; + + return { + keywords: [...getBaseKeywords(), ...extraKeywords], title, description, - images: [ - { - url: 'https://documentdb.io/images/social-card.png', - } - ] - }, - robots: { - index: true, - follow: true + openGraph: { + type: 'article', + title, + description, + // Array provides implicit fallback - first available image is used + images: [ + { url: customOGImage }, + { url: `${baseUrl}/images/social-card.png` } + ] + }, + twitter: { + card: 'summary_large_image', + title, + description, + images: [ + { url: customOGImage }, + { url: `${baseUrl}/images/social-card.png` } + ] + }, + robots: { + index: true, + follow: true + } + }; +}; + +function getImagePath(pagePath?: string): string { + const relativePath = getOpenGraphImageRelativePath(); + + if (!pagePath) { + return `${relativePath}/home.png`; } -}); + + const sanitizedPath = pagePath.replace(/^\//, '').replace(/\//g, '-'); + return `${relativePath}/${sanitizedPath}.png`; +} const getBaseKeywords = (): string[] => [ 'DocumentDB', diff --git a/app/services/siteService.ts b/app/services/siteService.ts new file mode 100644 index 0000000..fafb46c --- /dev/null +++ b/app/services/siteService.ts @@ -0,0 +1,9 @@ +// Determine base URL for entire site +export function getBaseUrl(): string { + return 'https://documentdb.io'; +} + +// Determine relative path for Open Graph images +export function getOpenGraphImageRelativePath(): string { + return '/open-graph'; +} \ No newline at end of file diff --git a/app/types/Entity.ts b/app/types/Entity.ts new file mode 100644 index 0000000..34acf38 --- /dev/null +++ b/app/types/Entity.ts @@ -0,0 +1,10 @@ +export interface Entity { + url: string; // Relative URL path (e.g., '/docs/quickstart') + slug: string; // Where to save assets (relative path) + title: string; // Page title + description: string; // Page description + section?: string; // Section category (e.g., 'docs', 'blog', 'reference') + filePath?: string; // Source file path (for git lastModified) + isExternal?: boolean; // Indicates if the content is external + type: 'home' | 'landing' | 'docs' | 'blog' | 'reference' | 'packages'; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2b31f8b..6b74683 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,15 +13,17 @@ "handlebars": "^4.7.8", "js-yaml": "^4.1.0", "next": "^15.5.4", + "p-limit": "^7.2.0", "pluralize": "^8.0.0", - "react": "19.1.0", - "react-dom": "19.1.0", + "react": "19.2.0", + "react-dom": "19.2.0", "react-markdown": "^10.1.0", "react-syntax-highlighter": "^16.1.0", "remark-gfm": "^4.0.1" }, "devDependencies": { "@eslint/eslintrc": "^3", + "@next/eslint-plugin-next": "^16.0.6", "@tailwindcss/postcss": "^4", "@types/js-yaml": "^4.0.9", "@types/node": "^20", @@ -32,7 +34,9 @@ "autoprefixer": "^10.0.1", "eslint": "^9", "eslint-config-next": "15.5.4", + "ink": "^6.5.1", "tailwindcss": "^4", + "tsx": "^4.21.0", "typescript": "^5" } }, @@ -45,6 +49,33 @@ "node": ">=0.10.0" } }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.2.tgz", + "integrity": "sha512-mkOh+Wwawzuf5wa30bvc4nA+Qb6DIrGWgBhRR/Pw4T9nsgYait8izvXkNyU78D6Wcu3Z+KUdwCmLCxlWjEotYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/ansi-tokenize/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -99,6 +130,448 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -143,6 +616,19 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/config-helpers": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", @@ -184,10 +670,23 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, "node_modules/@eslint/js": { @@ -783,15 +1282,15 @@ } }, "node_modules/@next/env": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.4.tgz", - "integrity": "sha512-27SQhYp5QryzIT5uO8hq99C69eLQ7qkzkDPsk3N+GuS2XgOgoYEeOav7Pf8Tn4drECOVDsDg8oj+/DVy8qQL2A==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz", + "integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.4.tgz", - "integrity": "sha512-SR1vhXNNg16T4zffhJ4TS7Xn7eq4NfKfcOsRwea7RIAHrjRpI9ALYbamqIJqkAhowLlERffiwk0FMvTLNdnVtw==", + "version": "16.0.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.0.6.tgz", + "integrity": "sha512-9INsBF3/4XL0/tON8AGsh0svnTtDMLwv3iREGWnWkewGdOnd790tguzq9rX8xwrVthPyvaBHhw1ww0GZz0jO5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -799,9 +1298,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.4.tgz", - "integrity": "sha512-nopqz+Ov6uvorej8ndRX6HlxCYWCO3AHLfKK2TYvxoSB2scETOcfm/HSS3piPqc3A+MUgyHoqE6je4wnkjfrOA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz", + "integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==", "cpu": [ "arm64" ], @@ -815,9 +1314,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.4.tgz", - "integrity": "sha512-QOTCFq8b09ghfjRJKfb68kU9k2K+2wsC4A67psOiMn849K9ZXgCSRQr0oVHfmKnoqCbEmQWG1f2h1T2vtJJ9mA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz", + "integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==", "cpu": [ "x64" ], @@ -831,9 +1330,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.4.tgz", - "integrity": "sha512-eRD5zkts6jS3VfE/J0Kt1VxdFqTnMc3QgO5lFE5GKN3KDI/uUpSyK3CjQHmfEkYR4wCOl0R0XrsjpxfWEA++XA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz", + "integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==", "cpu": [ "arm64" ], @@ -847,9 +1346,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.4.tgz", - "integrity": "sha512-TOK7iTxmXFc45UrtKqWdZ1shfxuL4tnVAOuuJK4S88rX3oyVV4ZkLjtMT85wQkfBrOOvU55aLty+MV8xmcJR8A==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz", + "integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==", "cpu": [ "arm64" ], @@ -863,9 +1362,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.4.tgz", - "integrity": "sha512-7HKolaj+481FSW/5lL0BcTkA4Ueam9SPYWyN/ib/WGAFZf0DGAN8frNpNZYFHtM4ZstrHZS3LY3vrwlIQfsiMA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz", + "integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==", "cpu": [ "x64" ], @@ -879,9 +1378,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.4.tgz", - "integrity": "sha512-nlQQ6nfgN0nCO/KuyEUwwOdwQIGjOs4WNMjEUtpIQJPR2NUfmGpW2wkJln1d4nJ7oUzd1g4GivH5GoEPBgfsdw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz", + "integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==", "cpu": [ "x64" ], @@ -895,9 +1394,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.4.tgz", - "integrity": "sha512-PcR2bN7FlM32XM6eumklmyWLLbu2vs+D7nJX8OAIoWy69Kef8mfiN4e8TUv2KohprwifdpFKPzIP1njuCjD0YA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz", + "integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==", "cpu": [ "arm64" ], @@ -911,9 +1410,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.4.tgz", - "integrity": "sha512-1ur2tSHZj8Px/KMAthmuI9FMp/YFusMMGoRNJaRZMOlSkgvLjzosSdQI0cJAKogdHl3qXUQKL9MGaYvKwA7DXg==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz", + "integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==", "cpu": [ "x64" ], @@ -2057,6 +2556,35 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2264,6 +2792,19 @@ "node": ">= 0.4" } }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/autoprefixer": { "version": "10.4.16", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", @@ -2561,12 +3102,71 @@ "node": ">=18" } }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2599,7 +3199,18 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -2849,6 +3460,19 @@ "node": ">=10.13.0" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -3026,6 +3650,59 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.42.0.tgz", + "integrity": "sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -3137,6 +3814,16 @@ } } }, + "node_modules/eslint-config-next/node_modules/@next/eslint-plugin-next": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.4.tgz", + "integrity": "sha512-SR1vhXNNg16T4zffhJ4TS7Xn7eq4NfKfcOsRwea7RIAHrjRpI9ALYbamqIJqkAhowLlERffiwk0FMvTLNdnVtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -3289,6 +3976,19 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -3329,6 +4029,19 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-react": { "version": "7.37.5", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", @@ -3375,6 +4088,19 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -3445,6 +4171,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -3731,6 +4470,21 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3780,6 +4534,19 @@ "node": ">= 0.4" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -3944,9 +4711,9 @@ } }, "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -4201,6 +4968,93 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.5.1.tgz", + "integrity": "sha512-wF3j/DmkM8q5E+OtfdQhCRw8/0ahkc8CUTgEddxZzpEWPslu7YPL3t64MWRoI9m6upVGpfAg4ms2BBvxCdKRLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.1", + "ansi-escapes": "^7.2.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^5.1.1", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^7.1.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.0", + "type-fest": "^4.27.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": "^6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ink/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/inline-style-parser": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", @@ -4434,6 +5288,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -4472,8 +5342,24 @@ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", "license": "MIT", "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "dev": true, + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-map": { @@ -4734,9 +5620,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -5411,9 +6297,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -6058,16 +6944,14 @@ "node": ">=8.6" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=6" } }, "node_modules/minimist": { @@ -6154,12 +7038,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.4.tgz", - "integrity": "sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz", + "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==", "license": "MIT", "dependencies": { - "@next/env": "15.5.4", + "@next/env": "15.5.7", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -6172,14 +7056,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.4", - "@next/swc-darwin-x64": "15.5.4", - "@next/swc-linux-arm64-gnu": "15.5.4", - "@next/swc-linux-arm64-musl": "15.5.4", - "@next/swc-linux-x64-gnu": "15.5.4", - "@next/swc-linux-x64-musl": "15.5.4", - "@next/swc-win32-arm64-msvc": "15.5.4", - "@next/swc-win32-x64-msvc": "15.5.4", + "@next/swc-darwin-arm64": "15.5.7", + "@next/swc-darwin-x64": "15.5.7", + "@next/swc-linux-arm64-gnu": "15.5.7", + "@next/swc-linux-arm64-musl": "15.5.7", + "@next/swc-linux-x64-gnu": "15.5.7", + "@next/swc-linux-x64-musl": "15.5.7", + "@next/swc-win32-arm64-msvc": "15.5.7", + "@next/swc-win32-x64-msvc": "15.5.7", "sharp": "^0.34.3" }, "peerDependencies": { @@ -6342,6 +7226,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -6378,15 +7278,15 @@ } }, "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.2.0.tgz", + "integrity": "sha512-ATHLtwoTNDloHRFFxFJdHnG6n2WUeFjaR8XQMFdKIv0xkXjrER8/iG9iu265jOM95zXHAfv9oTkqhrfbIzosrQ==", + "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "yocto-queue": "^1.2.1" }, "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6407,6 +7307,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6438,6 +7367,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6605,9 +7544,9 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", "peer": true, "engines": { @@ -6615,16 +7554,16 @@ } }, "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", "peer": true, "dependencies": { - "scheduler": "^0.26.0" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.1.0" + "react": "^19.2.0" } }, "node_modules/react-is": { @@ -6661,6 +7600,22 @@ "react": ">=18" } }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, "node_modules/react-syntax-highlighter": { "version": "16.1.0", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz", @@ -6843,6 +7798,23 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -6934,9 +7906,9 @@ } }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/section-matter": { @@ -7154,6 +8126,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7195,6 +8204,29 @@ "dev": true, "license": "MIT" }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -7209,6 +8241,23 @@ "node": ">= 0.4" } }, + "node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -7336,6 +8385,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -7584,6 +8649,26 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7596,6 +8681,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -8067,12 +9165,131 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "license": "MIT" }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -8084,17 +9301,24 @@ } }, "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "dev": true, + "license": "MIT" + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 824151f..a93a651 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,10 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev --turbopack", - "build": "next build --turbopack", + "dev": "npm run generate && next dev --turbopack", + "build": "npm run generate && next build --turbopack", + "generate": "npm run generate:open-graph", + "generate:open-graph": "tsx scripts/generate-open-graph-images.tsx", "start": "next start", "lint": "eslint" }, @@ -14,15 +16,17 @@ "handlebars": "^4.7.8", "js-yaml": "^4.1.0", "next": "^15.5.4", + "p-limit": "^7.2.0", "pluralize": "^8.0.0", - "react": "19.1.0", - "react-dom": "19.1.0", + "react": "19.2.0", + "react-dom": "19.2.0", "react-markdown": "^10.1.0", "react-syntax-highlighter": "^16.1.0", "remark-gfm": "^4.0.1" }, "devDependencies": { "@eslint/eslintrc": "^3", + "@next/eslint-plugin-next": "^16.0.6", "@tailwindcss/postcss": "^4", "@types/js-yaml": "^4.0.9", "@types/node": "^20", @@ -33,7 +37,9 @@ "autoprefixer": "^10.0.1", "eslint": "^9", "eslint-config-next": "15.5.4", + "ink": "^6.5.1", "tailwindcss": "^4", + "tsx": "^4.21.0", "typescript": "^5" } } diff --git a/scripts/generate-open-graph-images.tsx b/scripts/generate-open-graph-images.tsx new file mode 100644 index 0000000..b8de541 --- /dev/null +++ b/scripts/generate-open-graph-images.tsx @@ -0,0 +1,446 @@ +import fs from 'fs'; +import path from 'path'; +import React from 'react'; +import { render, Box, Text } from 'ink'; +import { ImageResponse } from 'next/og'; +import pLimit from 'p-limit'; +import type { Entity } from '../app/types/Entity'; + +/** + * Dynamically imports the content service to handle module resolution + */ +async function getContentEntities(): Promise { + const { getAllContent } = await import('../app/services/contentService.js'); + return getAllContent(); +} + +/** + * Configuration for Open Graph image generation + */ +const CONFIG = { + /** + * Maximum number of concurrent image generation tasks + */ + concurrency: 10, + + /** + * Image dimensions (px) + */ + dimensions: { + width: 1200, + height: 630, + }, +} as const; + +interface MkdirOptions { + readonly recursive: boolean; +} + +/** + * Tracks image generation progress and results. + */ +class ImageGenerationTracker { + private readonly results: Array<{ success: boolean; path: string; sourceUrl?: string; error?: unknown }> = []; + private readonly onProgress?: (count: number) => void; + + constructor(onProgress?: (count: number) => void) { + this.onProgress = onProgress; + } + + /** + * Records the result of an image generation attempt. + */ + recordResult(result: { success: boolean; path: string; sourceUrl?: string; error?: unknown }): void { + this.results.push(result); + + if (this.onProgress) { + this.onProgress(this.results.length); + } + } + + /** + * Get all results with success/failure status. + */ + getResults(): Array<{ success: boolean; path: string; sourceUrl?: string; error?: unknown }> { + return this.results; + } + + /** + * Get count of successful generations. + */ + getSuccessCount(): number { + return this.results.filter(r => r.success).length; + } + + /** + * Get count of failed generations. + */ + getErrorCount(): number { + return this.results.filter(r => !r.success).length; + } +} + +/** + * Generates an Open Graph image for the given parameters. + */ +async function generateOpenGraphImage(params: { + title: string; + description: string; + section?: string; +}): Promise { + const { title, description, section } = params; + + const response = new ImageResponse( + ( +
+ {/* Background decorative elements */} +
+
+ + {/* Safe zone container - max 750px width */} +
+ {/* Header with branding */} +
+
+ DocumentDB +
+
+ + {/* Main content */} +
+
+ {title} +
+
+ {description} +
+
+ + {/* Footer with section indicator */} +
+ {section && ( +
+ {section} +
+ )} +
+ documentdb.io +
+
+
+
+ ), + { + width: CONFIG.dimensions.width, + height: CONFIG.dimensions.height, + } + ); + + return await response.arrayBuffer(); +} + +/** + * Main execution function to generate all Open Graph images. + */ +async function generateAllImages(onProgress?: (count: number) => void, onTotal?: (total: number) => void): Promise<{ + success: boolean; + results?: Array<{ success: boolean; path: string; sourceUrl?: string; error?: unknown }>; + duration?: number; + message?: string; +}> { + try { + const startTime: number = Date.now(); + const targetDir: string = path.join(process.cwd(), 'public', 'open-graph'); + + // Ensure output directory exists + if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir, { recursive: true } as MkdirOptions); + } + + const entities = await getContentEntities(); + const tracker: ImageGenerationTracker = new ImageGenerationTracker(onProgress); + + // Notify total count + if (onTotal) { + onTotal(entities.length); + } + + // p-limit automatically manages concurrency + const limit = pLimit(CONFIG.concurrency); + + // Generate all images with automatic concurrency control + const promises = entities.map((entity) => + limit(async () => { + try { + const buffer: ArrayBuffer = await generateOpenGraphImage({ + title: entity.title, + description: entity.description, + section: entity.section, + }); + + const outputPath: string = path.join(targetDir, entity.slug); + // Convert ArrayBuffer to Uint8Array for fs.writeFile + await fs.promises.writeFile(outputPath, new Uint8Array(buffer)); + + tracker.recordResult({ success: true, path: entity.slug, sourceUrl: entity.url }); + } catch (error: unknown) { + tracker.recordResult({ success: false, path: entity.slug, sourceUrl: entity.url, error }); + } + }) + ); + + await Promise.all(promises); + + const duration: number = Date.now() - startTime; + + return { + success: true, + results: tracker.getResults(), + duration, + }; + } catch (error: unknown) { + return { + success: false, + message: error instanceof Error ? error.message : 'Unknown error occurred' + }; + } +} + +/** + * Main Console component for Ink rendering + */ +const Console: React.FC = () => { + const [status, setStatus] = React.useState<'running' | 'success' | 'error'>('running'); + const [errorMessage, setErrorMessage] = React.useState(''); + const [results, setResults] = React.useState>([]); + const [duration, setDuration] = React.useState(0); + const [imageCount, setImageCount] = React.useState(0); + const [totalCount, setTotalCount] = React.useState(0); + + React.useEffect(() => { + const run = async () => { + const result = await generateAllImages( + (count) => { + setImageCount(count); + }, + (total) => { + setTotalCount(total); + } + ); + + if (result.success) { + setStatus('success'); + setResults(result.results || []); + setDuration(result.duration || 0); + } else { + setStatus('error'); + setErrorMessage(result.message || 'Unknown error'); + } + + // Exit after a brief delay to show the final message + setTimeout(() => { + process.exit(result.success ? 0 : 1); + }, 100); + }; + + run(); + }, []); + + /** + * Truncates a string to a maximum length with ellipsis + */ + const truncate = (str: string, maxLength: number): string => { + if (str.length <= maxLength) return str; + return str.slice(0, maxLength - 3) + '...'; + }; + + const successCount = results.filter(r => r.success).length; + const errorCount = results.filter(r => !r.success).length; + const durationSeconds = (duration / 1000).toFixed(2); + + return ( + + + 🎨 Generating Open Graph Images + + + {status === 'running' && ( + + ✓ Progress: {imageCount}/{totalCount} images generated... + + )} + + {status === 'success' && ( + + + ✅ Generated {successCount} image{successCount !== 1 ? 's' : ''} successfully in {durationSeconds}s + + + {errorCount > 0 && ( + + ⚠️ {errorCount} image{errorCount !== 1 ? 's' : ''} failed to generate + + )} + + {results.length > 0 && ( + + Generated images: + + + Source + + + Target + + + Status + + + {results.slice(0, 10).map((result, index) => { + const terminalWidth = process.stdout.columns || 80; + const sourceColumnWidth = Math.floor((terminalWidth - 4) * 0.35); + const targetColumnWidth = Math.floor((terminalWidth - 4) * 0.45); + + return ( + + + {truncate(result.sourceUrl || '', sourceColumnWidth)} + + + {truncate(result.path, targetColumnWidth)} + + + {result.success ? ( + ✓ Success + ) : ( + ✗ Failed + )} + + + ); + })} + {results.length > 10 && ( + + ... and {results.length - 10} more + + )} + + )} + + )} + + {status === 'error' && ( + + + ❌ Error generating images: + + + {errorMessage} + + + )} + + ); +}; + +render(); diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +}