| # | بخش | وضعیت | پیشرفت |
|---|---|---|---|
| 1️⃣ | تعریف دقیق «واحد محتوا» (Production-Grade) | ✅ COMPLETE | 100% |
| 2️⃣ | تعریف ساختار URL نهایی (تایید شده) | ✅ COMPLETE | 100% |
| 3️⃣ | تعریف قرارداد Frontmatter (تنظیمشده) | ✅ COMPLETE | 100% |
| 4️⃣ | Generator Functions (مشتقسازی) | ✅ COMPLETE | 100% |
| 5️⃣ | Content Loader & Integration | ✅ COMPLETE | 100% |
فایلهای ایجادشده:
- ✅
types/doc-page.ts(1,086 خط) - Core models + DocPage + RenderedDocPage + Validation - ✅
types/doc-page-generator.ts- Generator functions (generateDerivedSeo, generateArticleSchema, etc.) - ✅
types/doc-page.examples.ts- Real-world examples (doc، guide، service) - ✅
types/index.ts- Exports
تستهای انجامشده:
- ✅
tests/doc-page.validation.test.ts(41 تست) - ✅
tests/doc-page.generator.test.ts(33 تست) - ✅
tests/doc-page.seo-safety.test.ts(34 تست) - نتیجه: 108 تست ✅ PASS | 0 فیلیور ⏱️ 291ms
تصمیم نهایی: /docs/{slug} و /services/{slug}
Frontmatter Contract: YAML format با layout, type, title, description, category, order, index/robots, author, timestamps
فایلهای ایجادشده:
- ✅
types/frontmatter.ts(280 خط) - FrontmatterRaw, FrontmatterParsed, Validators, Helpers - ✅
types/frontmatter-parser.ts(520+ خط) - Parser functions, converters, URL/breadcrumb/TOC generators - ✅
types/frontmatter.examples.ts(400+ خط) - 5 real-world examples (doc, service, draft, farsi, invalid) - ✅
types/index.ts- Updated with frontmatter exports
تستهای انجامشده:
- ✅
tests/frontmatter.test.ts(48 تست)- Layer 1: Input Validation (26 tests)
- Layer 2: Conversion (7 tests)
- Layer 3: Helper Functions (9 tests)
- Layer 3: Error Cases (6 tests)
- نتیجه: 48 تست ✅ PASS | 0 فیلیور ⏱️ 306ms
فیچرهای پیادهشده:
- ✅ parseFrontmatter() - Validate YAML frontmatter with detailed errors
- ✅ frontmatterToDocPage() - Convert parsed frontmatter to DocPage
- ✅ frontmatterToDocPageE2E() - End-to-end pipeline with validation
- ✅ generateUrlPath() - Auto URL path generation
- ✅ generateBreadcrumbsFromPath() - Auto breadcrumb generation
- ✅ extractTableOfContents() - Auto TOC extraction from markdown
- ✅ Deterministic & pure functions
- ✅ Multi-language support (lang field preserved)
- ✅ Flexible input handling (string/object authors, multiple date formats)
TOTAL TEST RESULTS AFTER SECTION 4:
- ✅ 156 Tests Total
- Section 1 (DocPage): 108 ✅
- Section 4 (Frontmatter): 48 ✅
- ✅ 0 Failures
- ⏱️ 350ms
فایلهای ایجادشده:
- ✅
types/content-loader.ts(236 خط) - Type system (RawMarkdownFile, LoadedMarkdownFile, ContentLoaderConfig, ContentLoaderOptions, etc.) - ✅
types/content-loader-utils.ts(450+ خط) - Core functions for extracting, parsing, loading, and filtering - ✅
types/content-loader.examples.ts(369 خط) - Real-world examples (installation, web-scraping, draft, farsi) - ✅
types/index.ts- Updated with content-loader exports
تستهای انجامشده:
- ✅
tests/content-loader.test.ts(41 تست) - 4-layer comprehensive test suite- Layer 1: Extraction (Frontmatter & Markdown parsing) - 13 tests ✅
- Layer 2: File Loading (Parse & Validate) - 9 tests ✅
- Layer 3: DocPage Generation (Conversion & Validation) - 9 tests ✅
- Layer 4: Filtering & Sorting - 10 tests ✅
- نتیجه: 41 تست ✅ PASS | 0 فیلیور ⏱️ 317ms
فیچرهای پیادهشده:
- ✅ extractMarkdownParts() - Parse YAML/TOML/JSON frontmatter with nested object support
- ✅ parseYAMLFrontmatter() - Advanced YAML parser handling nested objects (author, etc.)
- ✅ extractFileMetadata() - Extract slug, category, ID from file path
- ✅ loadMarkdownFile() - Load and validate markdown files with frontmatter
- ✅ loadedFileToDocPage() - Convert loaded files to DocPage objects
- ✅ filterAndSortDocPages() - Query pages with filtering (category, lang, status, type), sorting, pagination
- ✅ Deterministic & pure functions
- ✅ Comprehensive error handling
- ✅ Multi-language support
- ✅ Draft status handling
TOTAL TEST RESULTS AFTER SECTION 5:
- ✅ 197 Tests Total
- Section 1 (DocPage): 108 ✅
- Section 4 (Frontmatter): 48 ✅
- Section 5 (Content Loader): 41 ✅
- ✅ 0 Failures
- ⏱️ 345ms
/**
* Core Content Model - SEO Indexable, API Independent
* این مدل هستهٔ محتوا است و هرگز تغییر نمیکند
*
* ✅ لاغر: فقط محتوا و metadata اساسی
* ✅ مستقل: از API و Dashboard بیطرف
* ✅ قابلنگهداری: Backward compatible
*/
type DocPageType = 'doc' | 'guide' | 'service'
type DocPageStatus = 'published' | 'draft'
type RobotsDirective = 'index,follow' | 'noindex,follow' | 'index,nofollow' | 'noindex,nofollow'
interface AuthorInfo {
name: string
url?: string
}
interface BreadcrumbItem {
name: string
item: string // Absolute URL
}
interface HeadingItem {
level: number
title: string
anchor: string
}
interface DocPage {
// === Core Identifiers ===
id: string
slug: string
lang: string
// === Content Type ===
type: DocPageType
// === Core Content ===
title: string // Unique, keyword-rich (50-60 chars)
description: string // 120-160 chars for meta tag
body: string // Markdown content
// === Organization ===
category: string // e.g., 'docs', 'services'
order: number // Display order
// === Authorship ===
author: AuthorInfo
// === Timestamps ===
publishedAt: Date
updatedAt: Date
// === URLs ===
urlPath: string // e.g., /docs/installation
// === Navigation (Generated) ===
breadcrumbs: BreadcrumbItem[]
toc: HeadingItem[]
// === SEO Control (Unified) ===
robots: RobotsDirective // 'index,follow' | 'noindex,follow' | etc.
// covers both indexing and following
// === Publishing Status ===
status: DocPageStatus // 'published' | 'draft'
}/**
* Rendered Model - توسط Generator ایجاد میشود
* این مدل DocPage را غنیسازی میکند بدون آلودگی هسته
*/
interface RenderedDocPage extends DocPage {
// === Derived SEO Metadata ===
derivedSeo: {
title: string
description: string
canonical: string // ✅ Generated from siteUrl + urlPath
ogTitle: string
ogDescription: string
ogImage?: string
}
// === Generated Metadata ===
schema: {
techArticle: any // JSON-LD Object – type depends on DocPageType
breadcrumbList: any // JSON-LD Object
}
// === Content Metrics (Optional – فقط برای rendering) ===
readingTime?: number
difficulty?: 'beginner' | 'intermediate' | 'advanced'
// === Head Tags (Generated) ===
head: {
meta: Array<{
name?: string
property?: string
content: string
}>
links: Array<{
rel: string
href: string
type?: string
}>
}
}---
# === Content Identity ===
layout: doc
type: doc
title: Installation & Setup
# === Content Description ===
description: Step-by-step guide to install and set up DevPaper
body: |
# Installation
...
# === Organization ===
category: docs
order: 1
# === SEO Control ===
index: true # false برای drafts
robots: index,follow
# === Authorship ===
author:
name: DevPaper Team
url: https://DevPaper.dev
# === Timestamps ===
publishedAt: 2025-01-10
updatedAt: 2025-01-15
---| موضوع | مشکل قدیم | حل جدید |
|---|---|---|
| God Object | viewCount, featured, pinned, tags | ❌ حذف شدند – فقط Core |
| Schema زودهازحد | SchemaMetadata خودش input | ✅ Generated توسط Generator |
| تداخل SEO vs Head | دو جا meta تعریف میشد | ✅ یک سیستم: derivedSeo + head generated |
| keywords + tags chaos | دو فیلد مختلف | ✅ حذف – فقط content-based |
| API Coupling | DocPage = API DTO | ✅ Decoupled – RenderedDocPage برای rendering |
| نگهداری | ❌ بسیار وابسته | ✅ Core frozen + Derived computed |
| robots duplication | index + robots | ✅ فقط robots (جامعتر) |
| canonicalUrl safety | User input (خطا ممکن) | ✅ Generated از siteUrl + urlPath |
| Schema rigidity | Hardcoded TechArticle | ✅ Dynamic based on doc.type |
-
✅ SELECTED:
/docs/{slug}و/services/{slug} -
دلایل:
- ✅ سادهتر و تمیزتر (کمتر نویسی)
- ✅ بهتر برای SEO (کلمات کلیدی در URL)
- ✅ سهلالاستفاده برای کاربران
- ✅ بهتر برای breadcrumb navigation
- ✅ مطابق ساختار فعلی پروژه
-
مثالهای واقعی:
/docs/installation→ نصب و راهاندازی/docs/configuration→ تنظیمات/services/web-scraping→ web scraping/services/api-service→ سرویس API
---
# === Page Identity ===
layout: doc # VitePress layout type
type: doc # 'doc' | 'guide' | 'service'
title: Installation Guide # 50-60 chars, keyword-rich
description: > # 120-160 chars, natural language
Step-by-step guide to install DevPaper on your system
# === Organization ===
category: docs # 'docs' | 'services'
order: 1 # Display order in sidebar
# === SEO Control ===
index: true # false برای drafts/internal
robots: index,follow # Unified directive (covers indexing + following)
# === Authorship ===
author:
name: DevPaper Team
url: https://DevPaper.dev
# === Timestamps (شامل در schema) ===
publishedAt: 2025-01-10
updatedAt: 2025-01-15
# === Note: Generated Fields (اینا توسط Generator تولید میشوند) ===
# - canonicalUrl
# - urlPath
# - breadcrumbs
# - toc (Table of Contents)
# - derivedSeo
# - schema
---
# Installation Guide
Your markdown content here...📥 INPUT (Frontmatter):
├── title
├── description
├── body
├── category
├── index/robots
└── author, publishedAt, updatedAt
🔄 PROCESSING (Generator):
├── Parse frontmatter → DocPage
├── Generate urlPath: /docs/installation
├── Generate canonicalUrl: https://DevPaper.dev/docs/installation
├── Generate breadcrumbs
├── Generate schemas (TechArticle, Breadcrumb)
└── Generate head meta tags
📤 OUTPUT (RenderedDocPage):
├── All INPUT fields
├── + derivedSeo (title, description, canonical, og*)
├── + schema (techArticle, breadcrumbList)
├── + head (meta, links)
└── Ready for HTML rendering
/**
* Map DocPageType to Schema Type
*/
const schemaTypeMap: Record<DocPageType, string> = {
doc: 'TechArticle',
guide: 'Article',
service: 'WebPage',
}
/**
* Generate appropriate Schema from DocPage
* ✅ Type is determined by docType, not hardcoded
* ✅ Pure function – no side effects
*/
function generateArticleSchema(doc: DocPage, siteUrl: string) {
const schemaType = schemaTypeMap[doc.type]
return {
'@context': 'https://schema.org',
'@type': schemaType,
headline: doc.title,
description: doc.description,
image: `${siteUrl}/images/og-default.png`,
datePublished: doc.publishedAt.toISOString(),
dateModified: doc.updatedAt.toISOString(),
author: {
'@type': 'Organization',
name: doc.author.name,
url: doc.author.url || siteUrl,
},
publisher: {
'@type': 'Organization',
name: 'DevPaper',
logo: {
'@type': 'ImageObject',
url: `${siteUrl}/logo.svg`,
},
},
url: `${siteUrl}${doc.urlPath}`, // ✅ Generated from urlPath
inLanguage: doc.lang,
}
}
/**
* Generate Breadcrumb Schema from DocPage
*/
function generateBreadcrumbSchema(doc: DocPage, siteUrl: string) {
const parts = doc.urlPath.split('/').filter(Boolean)
const breadcrumbs = [
{
'@type': 'ListItem',
position: 1,
name: 'Home',
item: siteUrl,
},
]
let currentPath = ''
for (let i = 0; i < parts.length - 1; i++) {
currentPath += '/' + parts[i]
breadcrumbs.push({
'@type': 'ListItem',
position: breadcrumbs.length + 1,
name: parts[i].charAt(0).toUpperCase() + parts[i].slice(1),
item: siteUrl + currentPath,
})
}
breadcrumbs.push({
'@type': 'ListItem',
position: breadcrumbs.length + 1,
name: doc.title,
item: `${siteUrl}${doc.urlPath}`, // ✅ Generated from urlPath
})
return {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: breadcrumbs,
}
}/**
* Generate derived SEO metadata from DocPage
* ✅ Deterministic: same input = same output
* ✅ canonical is computed from siteUrl + urlPath (never user input)
*/
function generateDerivedSeo(doc: DocPage, siteUrl: string) {
const canonical = `${siteUrl}${doc.urlPath}` // ✅ Generated, not input
return {
title: doc.title,
description: doc.description,
canonical,
ogTitle: doc.title,
ogDescription: doc.description,
ogImage: `${siteUrl}/images/og-default.png`,
robots: doc.robots, // ✅ Unified robots directive
}
}
/**
* Generate head meta tags from DocPage
*/
function generateHeadTags(doc: DocPage, siteUrl: string) {
const derivedSeo = generateDerivedSeo(doc, siteUrl)
return {
meta: [
{ name: 'description', content: derivedSeo.description },
{ name: 'robots', content: derivedSeo.robots },
{ property: 'og:title', content: derivedSeo.ogTitle },
{ property: 'og:description', content: derivedSeo.ogDescription },
{ property: 'og:image', content: derivedSeo.ogImage },
{ property: 'og:url', content: derivedSeo.canonical },
{ property: 'og:type', content: 'article' },
],
links: [
{ rel: 'canonical', href: derivedSeo.canonical },
],
}
}/**
* Transform raw DocPage → RenderedDocPage
* ✅ Single responsibility: decoration
* ✅ All outputs are derived, not input
*/
function renderDocPage(
doc: DocPage,
siteUrl: string,
): RenderedDocPage {
const derivedSeo = generateDerivedSeo(doc, siteUrl)
const head = generateHeadTags(doc, siteUrl)
const articleSchema = generateArticleSchema(doc, siteUrl) // ✅ Dynamic type
const breadcrumbList = generateBreadcrumbSchema(doc, siteUrl)
return {
...doc,
derivedSeo,
schema: {
techArticle: articleSchema, // ✅ Auto-determined based on doc.type
breadcrumbList,
},
head,
}
}// INPUT: Raw DocPage (فقط محتوا)
const docPage: DocPage = {
id: 'installation-001',
slug: 'installation',
lang: 'en',
type: 'doc', // ✅ 'doc' → generates TechArticle
title: 'Installation Guide',
description: 'Step-by-step guide to install DevPaper',
body: '# Installation\n...',
category: 'docs',
order: 1,
author: { name: 'DevPaper Team', url: 'https://DevPaper.dev' },
publishedAt: new Date('2025-01-10'),
updatedAt: new Date('2025-01-15'),
urlPath: '/docs/installation',
breadcrumbs: [
{ name: 'Home', item: 'https://DevPaper.dev' },
{ name: 'Docs', item: 'https://DevPaper.dev/docs' },
{ name: 'Installation', item: 'https://DevPaper.dev/docs/installation' },
],
toc: [
{ level: 2, title: 'Prerequisites', anchor: 'prerequisites' },
{ level: 2, title: 'Installation Steps', anchor: 'installation-steps' },
],
robots: 'index,follow', // ✅ Unified (covers both index + follow)
status: 'published',
}
// OUTPUT: RenderedDocPage (غنیشده)
const rendered = renderDocPage(docPage, 'https://DevPaper.dev')
// rendered.derivedSeo
// {
// title: 'Installation Guide',
// description: 'Step-by-step guide to install DevPaper',
// canonical: 'https://DevPaper.dev/docs/installation', // ✅ Generated
// ogTitle: 'Installation Guide',
// ogDescription: 'Step-by-step guide to install DevPaper',
// robots: 'index,follow'
// }
// rendered.schema.techArticle
// {
// "@type": "TechArticle", // ✅ Dynamic based on doc.type
// "headline": "Installation Guide",
// "url": "https://DevPaper.dev/docs/installation", // ✅ Generated from urlPath
// "datePublished": "2025-01-10T00:00:00.000Z",
// ...
// }
// rendered.head.meta
// [
// { name: 'description', content: 'Step-by-step guide to install DevPaper' },
// { name: 'robots', content: 'index,follow' },
// { property: 'og:title', content: 'Installation Guide' },
// { property: 'og:url', content: 'https://DevPaper.dev/docs/installation' }, // ✅ Generated
// ...
// ]
// rendered.head.links
// [
// { rel: 'canonical', href: 'https://DevPaper.dev/docs/installation' } // ✅ Generated
// ]// Example 1: Technical Documentation
const docType: DocPage = { type: 'doc', ... }
// → generateArticleSchema() returns '@type': 'TechArticle'
// Example 2: Guide / Tutorial
const guideType: DocPage = { type: 'guide', ... }
// → generateArticleSchema() returns '@type': 'Article'
// Example 3: Service Description
const serviceType: DocPage = { type: 'service', ... }
// → generateArticleSchema() returns '@type': 'WebPage'