@next-md-blog/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -0
- package/dist/components/BlogPostSEO.d.ts +28 -0
- package/dist/components/BlogPostSEO.d.ts.map +1 -0
- package/dist/components/BlogPostSEO.js +22 -0
- package/dist/components/MarkdownContent.d.ts +48 -0
- package/dist/components/MarkdownContent.d.ts.map +1 -0
- package/dist/components/MarkdownContent.js +44 -0
- package/dist/components/OgImage.d.ts +35 -0
- package/dist/components/OgImage.d.ts.map +1 -0
- package/dist/components/OgImage.js +52 -0
- package/dist/components/markdown/a.d.ts +6 -0
- package/dist/components/markdown/a.d.ts.map +1 -0
- package/dist/components/markdown/a.js +7 -0
- package/dist/components/markdown/blockquote.d.ts +6 -0
- package/dist/components/markdown/blockquote.d.ts.map +1 -0
- package/dist/components/markdown/blockquote.js +7 -0
- package/dist/components/markdown/code.d.ts +6 -0
- package/dist/components/markdown/code.d.ts.map +1 -0
- package/dist/components/markdown/code.js +7 -0
- package/dist/components/markdown/defaults.d.ts +12 -0
- package/dist/components/markdown/defaults.d.ts.map +1 -0
- package/dist/components/markdown/defaults.js +59 -0
- package/dist/components/markdown/em.d.ts +6 -0
- package/dist/components/markdown/em.d.ts.map +1 -0
- package/dist/components/markdown/em.js +7 -0
- package/dist/components/markdown/h1.d.ts +6 -0
- package/dist/components/markdown/h1.d.ts.map +1 -0
- package/dist/components/markdown/h1.js +7 -0
- package/dist/components/markdown/h2.d.ts +6 -0
- package/dist/components/markdown/h2.d.ts.map +1 -0
- package/dist/components/markdown/h2.js +7 -0
- package/dist/components/markdown/h3.d.ts +6 -0
- package/dist/components/markdown/h3.d.ts.map +1 -0
- package/dist/components/markdown/h3.js +7 -0
- package/dist/components/markdown/h4.d.ts +6 -0
- package/dist/components/markdown/h4.d.ts.map +1 -0
- package/dist/components/markdown/h4.js +7 -0
- package/dist/components/markdown/h5.d.ts +6 -0
- package/dist/components/markdown/h5.d.ts.map +1 -0
- package/dist/components/markdown/h5.js +7 -0
- package/dist/components/markdown/h6.d.ts +6 -0
- package/dist/components/markdown/h6.d.ts.map +1 -0
- package/dist/components/markdown/h6.js +7 -0
- package/dist/components/markdown/hr.d.ts +6 -0
- package/dist/components/markdown/hr.d.ts.map +1 -0
- package/dist/components/markdown/hr.js +7 -0
- package/dist/components/markdown/img.d.ts +6 -0
- package/dist/components/markdown/img.d.ts.map +1 -0
- package/dist/components/markdown/img.js +9 -0
- package/dist/components/markdown/index.d.ts +29 -0
- package/dist/components/markdown/index.d.ts.map +1 -0
- package/dist/components/markdown/index.js +28 -0
- package/dist/components/markdown/li.d.ts +6 -0
- package/dist/components/markdown/li.d.ts.map +1 -0
- package/dist/components/markdown/li.js +7 -0
- package/dist/components/markdown/ol.d.ts +6 -0
- package/dist/components/markdown/ol.d.ts.map +1 -0
- package/dist/components/markdown/ol.js +7 -0
- package/dist/components/markdown/p.d.ts +6 -0
- package/dist/components/markdown/p.d.ts.map +1 -0
- package/dist/components/markdown/p.js +7 -0
- package/dist/components/markdown/pre.d.ts +6 -0
- package/dist/components/markdown/pre.d.ts.map +1 -0
- package/dist/components/markdown/pre.js +7 -0
- package/dist/components/markdown/strong.d.ts +6 -0
- package/dist/components/markdown/strong.d.ts.map +1 -0
- package/dist/components/markdown/strong.js +7 -0
- package/dist/components/markdown/table.d.ts +6 -0
- package/dist/components/markdown/table.d.ts.map +1 -0
- package/dist/components/markdown/table.js +7 -0
- package/dist/components/markdown/tbody.d.ts +6 -0
- package/dist/components/markdown/tbody.d.ts.map +1 -0
- package/dist/components/markdown/tbody.js +7 -0
- package/dist/components/markdown/td.d.ts +6 -0
- package/dist/components/markdown/td.d.ts.map +1 -0
- package/dist/components/markdown/td.js +7 -0
- package/dist/components/markdown/th.d.ts +6 -0
- package/dist/components/markdown/th.d.ts.map +1 -0
- package/dist/components/markdown/th.js +7 -0
- package/dist/components/markdown/thead.d.ts +6 -0
- package/dist/components/markdown/thead.d.ts.map +1 -0
- package/dist/components/markdown/thead.js +7 -0
- package/dist/components/markdown/tr.d.ts +6 -0
- package/dist/components/markdown/tr.d.ts.map +1 -0
- package/dist/components/markdown/tr.js +7 -0
- package/dist/components/markdown/ul.d.ts +6 -0
- package/dist/components/markdown/ul.d.ts.map +1 -0
- package/dist/components/markdown/ul.js +7 -0
- package/dist/components/markdown/utils.d.ts +6 -0
- package/dist/components/markdown/utils.d.ts.map +1 -0
- package/dist/components/markdown/utils.js +7 -0
- package/dist/core/config.d.ts +36 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +63 -0
- package/dist/core/constants.d.ts +36 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/constants.js +44 -0
- package/dist/core/errors.d.ts +48 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +57 -0
- package/dist/core/file-utils.d.ts +22 -0
- package/dist/core/file-utils.d.ts.map +1 -0
- package/dist/core/file-utils.js +180 -0
- package/dist/core/seo-feeds.d.ts +16 -0
- package/dist/core/seo-feeds.d.ts.map +1 -0
- package/dist/core/seo-feeds.js +73 -0
- package/dist/core/seo-metadata.d.ts +17 -0
- package/dist/core/seo-metadata.d.ts.map +1 -0
- package/dist/core/seo-metadata.js +197 -0
- package/dist/core/seo-schema.d.ts +20 -0
- package/dist/core/seo-schema.d.ts.map +1 -0
- package/dist/core/seo-schema.js +131 -0
- package/dist/core/seo-utils.d.ts +66 -0
- package/dist/core/seo-utils.d.ts.map +1 -0
- package/dist/core/seo-utils.js +135 -0
- package/dist/core/seo.d.ts +11 -0
- package/dist/core/seo.d.ts.map +1 -0
- package/dist/core/seo.js +12 -0
- package/dist/core/type-guards.d.ts +58 -0
- package/dist/core/type-guards.d.ts.map +1 -0
- package/dist/core/type-guards.js +83 -0
- package/dist/core/types.d.ts +116 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +1 -0
- package/dist/core/utils.d.ts +49 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +175 -0
- package/dist/core/validation.d.ts +22 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +50 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/package.json +80 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { calculateReadingTime, calculateWordCount } from './utils.js';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
import { resolveFrontmatterField, isStringArray } from './type-guards.js';
|
|
4
|
+
import { DEFAULT_SITE_NAME, DEFAULT_LANG } from './constants.js';
|
|
5
|
+
import { normalizeKeywords, getAuthorNames, ensureAuthorsResolved, resolveDefaultAuthor, buildRobotsMeta, resolvePostUrl, } from './seo-utils.js';
|
|
6
|
+
/**
|
|
7
|
+
* Generates comprehensive metadata for a blog post
|
|
8
|
+
* @param post - The blog post
|
|
9
|
+
* @param config - SEO configuration
|
|
10
|
+
* @returns Metadata object for Next.js
|
|
11
|
+
*/
|
|
12
|
+
export function generateBlogPostMetadata(post, config) {
|
|
13
|
+
const blogConfig = config || getConfig();
|
|
14
|
+
const { siteName = DEFAULT_SITE_NAME, siteUrl = '', defaultAuthor, authors: configAuthors, twitterHandle, defaultOgImage, defaultLang = DEFAULT_LANG, alternateLanguages, } = blogConfig;
|
|
15
|
+
const fm = post.frontmatter;
|
|
16
|
+
// Title resolution: seoTitle > title > slug
|
|
17
|
+
const baseTitle = resolveFrontmatterField(['title'], fm, post.slug) || post.slug;
|
|
18
|
+
const seoTitle = resolveFrontmatterField(['seoTitle', 'title'], fm, baseTitle) || baseTitle;
|
|
19
|
+
const pageTitle = `${seoTitle} | ${siteName}`;
|
|
20
|
+
// Description resolution: seoDescription > description > excerpt > empty
|
|
21
|
+
const description = resolveFrontmatterField(['seoDescription', 'description', 'excerpt'], fm, '') || '';
|
|
22
|
+
// Use normalized authors from post, or fallback to default author (resolved from config if available)
|
|
23
|
+
// Note: post.authors should already be resolved from config.authors when the post was loaded (in file-utils.ts)
|
|
24
|
+
// However, we ensure they are resolved here as a safety net in case resolution didn't happen earlier
|
|
25
|
+
const resolvedDefaultAuthor = resolveDefaultAuthor(defaultAuthor, configAuthors);
|
|
26
|
+
const postAuthors = (post.authors && post.authors.length > 0)
|
|
27
|
+
? post.authors
|
|
28
|
+
: (resolvedDefaultAuthor ? [resolvedDefaultAuthor] : []);
|
|
29
|
+
// Ensure all authors are resolved from config (safety net)
|
|
30
|
+
const authors = ensureAuthorsResolved(postAuthors, configAuthors);
|
|
31
|
+
const authorNames = getAuthorNames(authors);
|
|
32
|
+
// Extract author Twitter handles for Twitter metadata
|
|
33
|
+
// Works for both frontmatter authors (already resolved) and default author (just resolved above)
|
|
34
|
+
const authorTwitterHandles = authors
|
|
35
|
+
.map((author) => {
|
|
36
|
+
if (typeof author === 'string')
|
|
37
|
+
return undefined;
|
|
38
|
+
return author.twitter;
|
|
39
|
+
})
|
|
40
|
+
.filter((handle) => Boolean(handle));
|
|
41
|
+
// Date resolution: publishedDate > date
|
|
42
|
+
const publishedDate = resolveFrontmatterField(['publishedDate', 'date'], fm);
|
|
43
|
+
const modifiedDate = resolveFrontmatterField(['modifiedDate'], fm);
|
|
44
|
+
// Tags and keywords
|
|
45
|
+
const tags = isStringArray(fm.tags) ? fm.tags : [];
|
|
46
|
+
const keywords = normalizeKeywords(fm.keywords);
|
|
47
|
+
const allKeywords = [...tags, ...keywords].filter((k, i, arr) => arr.indexOf(k) === i); // Remove duplicates
|
|
48
|
+
// Calculate reading time if not provided
|
|
49
|
+
const readingTime = resolveFrontmatterField(['readingTime'], fm) || calculateReadingTime(post.content);
|
|
50
|
+
const wordCount = calculateWordCount(post.content);
|
|
51
|
+
// OG image resolution: ogImage > image > defaultOgImage
|
|
52
|
+
// If not provided, Next.js will automatically use opengraph-image.tsx file convention
|
|
53
|
+
const ogImageUrl = resolveFrontmatterField(['ogImage', 'image'], fm) ||
|
|
54
|
+
defaultOgImage;
|
|
55
|
+
const imageAlt = resolveFrontmatterField(['imageAlt'], fm) || seoTitle;
|
|
56
|
+
// OG title/description: ogTitle/ogDescription > seoTitle/seoDescription > title/description
|
|
57
|
+
const ogTitle = resolveFrontmatterField(['ogTitle'], fm) || seoTitle;
|
|
58
|
+
const ogDescription = resolveFrontmatterField(['ogDescription'], fm) || description;
|
|
59
|
+
// Twitter title/description: twitterTitle/twitterDescription > ogTitle/ogDescription > seoTitle/description
|
|
60
|
+
const twitterTitle = resolveFrontmatterField(['twitterTitle'], fm) || ogTitle;
|
|
61
|
+
const twitterDescription = resolveFrontmatterField(['twitterDescription'], fm) || ogDescription;
|
|
62
|
+
// Canonical URL - supports both absolute and relative URLs
|
|
63
|
+
// Relative URLs will be resolved against siteUrl
|
|
64
|
+
const canonicalUrl = resolvePostUrl(resolveFrontmatterField(['canonicalUrl'], fm), post.slug, siteUrl);
|
|
65
|
+
// Language
|
|
66
|
+
const lang = resolveFrontmatterField(['lang'], fm) || defaultLang;
|
|
67
|
+
// Robots meta
|
|
68
|
+
const robots = buildRobotsMeta(fm);
|
|
69
|
+
// Post URL
|
|
70
|
+
const postUrl = `${siteUrl}/blog/${post.slug}`;
|
|
71
|
+
// Article meta tags (for better SEO)
|
|
72
|
+
const articleMeta = {};
|
|
73
|
+
if (publishedDate)
|
|
74
|
+
articleMeta['article:published_time'] = publishedDate;
|
|
75
|
+
if (modifiedDate)
|
|
76
|
+
articleMeta['article:modified_time'] = modifiedDate;
|
|
77
|
+
const category = resolveFrontmatterField(['category'], fm);
|
|
78
|
+
if (category)
|
|
79
|
+
articleMeta['article:section'] = category;
|
|
80
|
+
if (tags.length > 0) {
|
|
81
|
+
tags.forEach((tag, index) => {
|
|
82
|
+
articleMeta[`article:tag${index > 0 ? index + 1 : ''}`] = tag;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (authorNames.length > 0) {
|
|
86
|
+
authorNames.forEach((authorName, index) => {
|
|
87
|
+
articleMeta[`article:author${index > 0 ? index + 1 : ''}`] = authorName;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Add author email and other details from Author objects to meta tags
|
|
91
|
+
authors.forEach((author, index) => {
|
|
92
|
+
if (typeof author === 'object') {
|
|
93
|
+
// Add author email if available
|
|
94
|
+
if (author.email) {
|
|
95
|
+
articleMeta[`author:email${index > 0 ? index + 1 : ''}`] = author.email;
|
|
96
|
+
}
|
|
97
|
+
// Add author URL if available
|
|
98
|
+
if (author.url) {
|
|
99
|
+
articleMeta[`author:url${index > 0 ? index + 1 : ''}`] = author.url;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
// Build alternates object
|
|
104
|
+
const alternates = {};
|
|
105
|
+
if (canonicalUrl)
|
|
106
|
+
alternates.canonical = canonicalUrl;
|
|
107
|
+
if (alternateLanguages && Object.keys(alternateLanguages).length > 0) {
|
|
108
|
+
alternates.languages = alternateLanguages;
|
|
109
|
+
}
|
|
110
|
+
// Merge all custom meta tags into the 'other' field
|
|
111
|
+
const otherMeta = {};
|
|
112
|
+
if (lang)
|
|
113
|
+
otherMeta['lang'] = lang;
|
|
114
|
+
Object.assign(otherMeta, articleMeta);
|
|
115
|
+
const metadata = {
|
|
116
|
+
title: pageTitle,
|
|
117
|
+
description,
|
|
118
|
+
...(Object.keys(alternates).length > 0 && { alternates }),
|
|
119
|
+
...(robots && { robots }),
|
|
120
|
+
...(Object.keys(otherMeta).length > 0 && { other: otherMeta }),
|
|
121
|
+
openGraph: {
|
|
122
|
+
title: ogTitle,
|
|
123
|
+
description: ogDescription,
|
|
124
|
+
type: 'article',
|
|
125
|
+
url: postUrl,
|
|
126
|
+
siteName,
|
|
127
|
+
...(ogImageUrl && {
|
|
128
|
+
images: [
|
|
129
|
+
{
|
|
130
|
+
url: ogImageUrl,
|
|
131
|
+
width: 1200,
|
|
132
|
+
height: 630,
|
|
133
|
+
alt: imageAlt,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
}),
|
|
137
|
+
...(publishedDate && { publishedTime: publishedDate }),
|
|
138
|
+
...(modifiedDate && { modifiedTime: modifiedDate }),
|
|
139
|
+
...(authorNames.length > 0 && { authors: authorNames }),
|
|
140
|
+
...(tags.length > 0 && { tags }),
|
|
141
|
+
...(lang && { locale: lang }),
|
|
142
|
+
},
|
|
143
|
+
twitter: {
|
|
144
|
+
card: 'summary_large_image',
|
|
145
|
+
title: twitterTitle,
|
|
146
|
+
description: twitterDescription,
|
|
147
|
+
...(ogImageUrl && { images: [ogImageUrl] }),
|
|
148
|
+
// Use author's Twitter handle if available, otherwise fallback to site Twitter handle
|
|
149
|
+
...(authorTwitterHandles.length > 0
|
|
150
|
+
? { creator: `@${authorTwitterHandles[0].replace('@', '')}` }
|
|
151
|
+
: twitterHandle && { creator: `@${twitterHandle.replace('@', '')}` }),
|
|
152
|
+
},
|
|
153
|
+
...(allKeywords.length > 0 && { keywords: allKeywords }),
|
|
154
|
+
...(authors.length > 0 && {
|
|
155
|
+
authors: authors.map((author) => {
|
|
156
|
+
if (typeof author === 'string') {
|
|
157
|
+
return { name: author };
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
name: author.name,
|
|
161
|
+
...(author.email && { email: author.email }),
|
|
162
|
+
...(author.url && { url: author.url }),
|
|
163
|
+
};
|
|
164
|
+
})
|
|
165
|
+
}),
|
|
166
|
+
};
|
|
167
|
+
return metadata;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Generates metadata for the blog listing page
|
|
171
|
+
* @param posts - Array of blog posts
|
|
172
|
+
* @param config - SEO configuration
|
|
173
|
+
* @returns Metadata object for Next.js
|
|
174
|
+
*/
|
|
175
|
+
export function generateBlogListMetadata(posts, config) {
|
|
176
|
+
const blogConfig = config || getConfig();
|
|
177
|
+
const { siteName = DEFAULT_SITE_NAME, siteUrl = '' } = blogConfig;
|
|
178
|
+
const title = 'Blog Posts';
|
|
179
|
+
const description = `Browse all ${posts.length} blog posts`;
|
|
180
|
+
const metadata = {
|
|
181
|
+
title: `${title} | ${siteName}`,
|
|
182
|
+
description,
|
|
183
|
+
openGraph: {
|
|
184
|
+
title,
|
|
185
|
+
description,
|
|
186
|
+
type: 'website',
|
|
187
|
+
url: `${siteUrl}/blogs`,
|
|
188
|
+
siteName,
|
|
189
|
+
},
|
|
190
|
+
twitter: {
|
|
191
|
+
card: 'summary',
|
|
192
|
+
title,
|
|
193
|
+
description,
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
return metadata;
|
|
197
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BlogPost, Config } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates JSON-LD structured data (Schema.org) for a blog post
|
|
4
|
+
* @param post - The blog post
|
|
5
|
+
* @param config - SEO configuration
|
|
6
|
+
* @returns JSON-LD schema object
|
|
7
|
+
*/
|
|
8
|
+
export declare function generateBlogPostSchema(post: BlogPost, config?: Config): Record<string, unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* Generates breadcrumbs schema for a blog post
|
|
11
|
+
* @param post - The blog post
|
|
12
|
+
* @param config - SEO configuration
|
|
13
|
+
* @param breadcrumbs - Optional custom breadcrumb items (e.g., [{ name: 'Home', url: '/' }, { name: 'Blog', url: '/blog' }])
|
|
14
|
+
* @returns Breadcrumbs JSON-LD schema object
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateBreadcrumbsSchema(post: BlogPost, config?: Config, breadcrumbs?: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
url: string;
|
|
19
|
+
}>): Record<string, unknown>;
|
|
20
|
+
//# sourceMappingURL=seo-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-schema.d.ts","sourceRoot":"","sources":["../../src/core/seo-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,EAAE,MAAM,YAAY,CAAC;AAY3D;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA8GzB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GACjD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAiCzB"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { calculateReadingTime, calculateWordCount } from './utils.js';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
import { resolveFrontmatterField, isStringArray } from './type-guards.js';
|
|
4
|
+
import { DEFAULT_SITE_NAME } from './constants.js';
|
|
5
|
+
import { ensureAuthorsResolved, resolveDefaultAuthor, getAuthorNames, resolvePostUrl, } from './seo-utils.js';
|
|
6
|
+
/**
|
|
7
|
+
* Generates JSON-LD structured data (Schema.org) for a blog post
|
|
8
|
+
* @param post - The blog post
|
|
9
|
+
* @param config - SEO configuration
|
|
10
|
+
* @returns JSON-LD schema object
|
|
11
|
+
*/
|
|
12
|
+
export function generateBlogPostSchema(post, config) {
|
|
13
|
+
const blogConfig = config || getConfig();
|
|
14
|
+
const { siteName = DEFAULT_SITE_NAME, siteUrl = '', defaultAuthor, authors: configAuthors, } = blogConfig;
|
|
15
|
+
const fm = post.frontmatter;
|
|
16
|
+
const title = resolveFrontmatterField(['seoTitle', 'title'], fm, post.slug) || post.slug;
|
|
17
|
+
const description = resolveFrontmatterField(['seoDescription', 'description', 'excerpt'], fm, '') || '';
|
|
18
|
+
// Use normalized authors from post, or fallback to default author (resolved from config if available)
|
|
19
|
+
// Note: post.authors should already be resolved from config.authors when the post was loaded (in file-utils.ts)
|
|
20
|
+
// However, we ensure they are resolved here as a safety net in case resolution didn't happen earlier
|
|
21
|
+
const resolvedDefaultAuthor = resolveDefaultAuthor(defaultAuthor, configAuthors);
|
|
22
|
+
const postAuthors = (post.authors && post.authors.length > 0)
|
|
23
|
+
? post.authors
|
|
24
|
+
: (resolvedDefaultAuthor ? [resolvedDefaultAuthor] : []);
|
|
25
|
+
// Ensure all authors are resolved from config (safety net)
|
|
26
|
+
const authors = ensureAuthorsResolved(postAuthors, configAuthors);
|
|
27
|
+
const authorNames = getAuthorNames(authors);
|
|
28
|
+
const publishedDate = resolveFrontmatterField(['publishedDate', 'date'], fm);
|
|
29
|
+
const modifiedDate = resolveFrontmatterField(['modifiedDate'], fm) || publishedDate;
|
|
30
|
+
const postUrl = resolvePostUrl(resolveFrontmatterField(['canonicalUrl'], fm), post.slug, siteUrl);
|
|
31
|
+
const ogImageUrl = resolveFrontmatterField(['ogImage', 'image'], fm);
|
|
32
|
+
// Calculate reading time and word count if not provided
|
|
33
|
+
const readingTime = resolveFrontmatterField(['readingTime'], fm) || calculateReadingTime(post.content);
|
|
34
|
+
const wordCount = calculateWordCount(post.content);
|
|
35
|
+
// Build author schema with full author info if available
|
|
36
|
+
const buildAuthorSchema = (author) => {
|
|
37
|
+
if (typeof author === 'string') {
|
|
38
|
+
return {
|
|
39
|
+
'@type': 'Person',
|
|
40
|
+
name: author,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
'@type': 'Person',
|
|
45
|
+
name: author.name,
|
|
46
|
+
...(author.email && { email: author.email }),
|
|
47
|
+
...(author.url && { url: author.url }),
|
|
48
|
+
...(author.avatar && { image: author.avatar }),
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
// Base schema
|
|
52
|
+
const schema = {
|
|
53
|
+
'@context': 'https://schema.org',
|
|
54
|
+
'@type': resolveFrontmatterField(['type'], fm, 'BlogPosting') || 'BlogPosting',
|
|
55
|
+
headline: title,
|
|
56
|
+
description,
|
|
57
|
+
url: postUrl,
|
|
58
|
+
mainEntityOfPage: {
|
|
59
|
+
'@type': 'WebPage',
|
|
60
|
+
'@id': postUrl,
|
|
61
|
+
},
|
|
62
|
+
...(publishedDate && { datePublished: publishedDate }),
|
|
63
|
+
...(modifiedDate && { dateModified: modifiedDate }),
|
|
64
|
+
...(authors.length > 0 && {
|
|
65
|
+
author: authors.length === 1
|
|
66
|
+
? buildAuthorSchema(authors[0])
|
|
67
|
+
: authors.map(buildAuthorSchema),
|
|
68
|
+
}),
|
|
69
|
+
...(siteName && {
|
|
70
|
+
publisher: {
|
|
71
|
+
'@type': 'Organization',
|
|
72
|
+
name: siteName,
|
|
73
|
+
...(siteUrl && { url: siteUrl }),
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
...(ogImageUrl && {
|
|
77
|
+
image: {
|
|
78
|
+
'@type': 'ImageObject',
|
|
79
|
+
url: ogImageUrl,
|
|
80
|
+
...(resolveFrontmatterField(['imageAlt'], fm) ? { caption: resolveFrontmatterField(['imageAlt'], fm) } : {}),
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
...(resolveFrontmatterField(['category'], fm) ? { articleSection: resolveFrontmatterField(['category'], fm) } : {}),
|
|
84
|
+
...(isStringArray(fm.tags) && fm.tags.length > 0 && {
|
|
85
|
+
keywords: fm.tags.join(', '),
|
|
86
|
+
}),
|
|
87
|
+
...(resolveFrontmatterField(['lang'], fm) ? { inLanguage: resolveFrontmatterField(['lang'], fm) } : {}),
|
|
88
|
+
...(wordCount > 0 && { wordCount }),
|
|
89
|
+
...(readingTime > 0 && {
|
|
90
|
+
timeRequired: `PT${readingTime}M`,
|
|
91
|
+
}),
|
|
92
|
+
};
|
|
93
|
+
// Merge with custom schema from frontmatter
|
|
94
|
+
if (fm.schema && typeof fm.schema === 'object') {
|
|
95
|
+
return {
|
|
96
|
+
...schema,
|
|
97
|
+
...fm.schema,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return schema;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Generates breadcrumbs schema for a blog post
|
|
104
|
+
* @param post - The blog post
|
|
105
|
+
* @param config - SEO configuration
|
|
106
|
+
* @param breadcrumbs - Optional custom breadcrumb items (e.g., [{ name: 'Home', url: '/' }, { name: 'Blog', url: '/blog' }])
|
|
107
|
+
* @returns Breadcrumbs JSON-LD schema object
|
|
108
|
+
*/
|
|
109
|
+
export function generateBreadcrumbsSchema(post, config, breadcrumbs) {
|
|
110
|
+
const blogConfig = config || getConfig();
|
|
111
|
+
const { siteUrl = '' } = blogConfig;
|
|
112
|
+
const title = resolveFrontmatterField(['seoTitle', 'title'], post.frontmatter, post.slug) || post.slug;
|
|
113
|
+
const postUrl = resolvePostUrl(resolveFrontmatterField(['canonicalUrl'], post.frontmatter), post.slug, siteUrl);
|
|
114
|
+
// Default breadcrumbs: Home > Blog > Post
|
|
115
|
+
const defaultBreadcrumbs = [
|
|
116
|
+
{ name: 'Home', url: siteUrl || '/' },
|
|
117
|
+
{ name: 'Blog', url: `${siteUrl}/blogs` },
|
|
118
|
+
{ name: title, url: postUrl },
|
|
119
|
+
];
|
|
120
|
+
const items = breadcrumbs || defaultBreadcrumbs;
|
|
121
|
+
return {
|
|
122
|
+
'@context': 'https://schema.org',
|
|
123
|
+
'@type': 'BreadcrumbList',
|
|
124
|
+
itemListElement: items.map((item, index) => ({
|
|
125
|
+
'@type': 'ListItem',
|
|
126
|
+
position: index + 1,
|
|
127
|
+
name: item.name,
|
|
128
|
+
item: item.url,
|
|
129
|
+
})),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { BlogPost, Author } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes keywords to an array of strings
|
|
4
|
+
* Handles both string (comma-separated) and array formats
|
|
5
|
+
* @param keywords - Keywords as string (comma-separated) or array of strings
|
|
6
|
+
* @returns Array of normalized keyword strings
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizeKeywords(keywords?: string | string[]): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Extracts author name from string or Author object
|
|
11
|
+
* @param author - Author as string or Author object
|
|
12
|
+
* @returns Author name as string
|
|
13
|
+
*/
|
|
14
|
+
export declare function getAuthorName(author: string | Author): string;
|
|
15
|
+
/**
|
|
16
|
+
* Converts authors array (string | Author)[] to array of author names
|
|
17
|
+
* @param authors - Array of authors (strings or Author objects)
|
|
18
|
+
* @returns Array of author names as strings
|
|
19
|
+
*/
|
|
20
|
+
export declare function getAuthorNames(authors: (string | Author)[]): string[];
|
|
21
|
+
/**
|
|
22
|
+
* Ensures all authors in the array are resolved from config
|
|
23
|
+
* This is a safety net to ensure authors are resolved even if they weren't resolved earlier
|
|
24
|
+
* @param authors - Array of authors (strings or Author objects)
|
|
25
|
+
* @param configAuthors - Array of authors from config
|
|
26
|
+
* @returns Array of resolved authors (Author objects or strings)
|
|
27
|
+
*/
|
|
28
|
+
export declare function ensureAuthorsResolved(authors: (string | Author)[], configAuthors?: Author[]): (string | Author)[];
|
|
29
|
+
/**
|
|
30
|
+
* Resolves defaultAuthor from config.authors array if available
|
|
31
|
+
* @param defaultAuthor - Default author name from config
|
|
32
|
+
* @param configAuthors - Array of authors from config
|
|
33
|
+
* @returns Author object if found, otherwise returns the name as string
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveDefaultAuthor(defaultAuthor: string | undefined, configAuthors?: Author[]): string | Author | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Builds robots meta string from frontmatter
|
|
38
|
+
* Combines robots directive string with boolean flags (noindex, nofollow)
|
|
39
|
+
* @param frontmatter - Blog post frontmatter containing robots directives
|
|
40
|
+
* @returns Robots meta string (e.g., 'noindex, nofollow') or undefined
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildRobotsMeta(frontmatter: BlogPost['frontmatter']): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Resolves a URL to an absolute URL
|
|
45
|
+
* If the URL is already absolute (starts with http:// or https://), returns it as-is
|
|
46
|
+
* If the URL is relative (starts with /), resolves it against siteUrl
|
|
47
|
+
* @param url - The URL to resolve (can be absolute or relative)
|
|
48
|
+
* @param siteUrl - The base site URL to resolve relative URLs against
|
|
49
|
+
* @returns Absolute URL
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveCanonicalUrl(url: string, siteUrl: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Resolves a post URL from canonical URL or generates default
|
|
54
|
+
* @param canonicalUrl - Optional canonical URL from frontmatter
|
|
55
|
+
* @param slug - Post slug
|
|
56
|
+
* @param siteUrl - Base site URL
|
|
57
|
+
* @returns Resolved absolute URL
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolvePostUrl(canonicalUrl: string | undefined, slug: string, siteUrl: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Escapes XML special characters to prevent injection attacks
|
|
62
|
+
* @param unsafe - String that may contain XML special characters
|
|
63
|
+
* @returns Escaped string safe for XML output
|
|
64
|
+
*/
|
|
65
|
+
export declare function escapeXml(unsafe: string): string;
|
|
66
|
+
//# sourceMappingURL=seo-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-utils.d.ts","sourceRoot":"","sources":["../../src/core/seo-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAInD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAOxE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAErE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EAC5B,aAAa,CAAC,EAAE,MAAM,EAAE,GACvB,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAcrB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,aAAa,CAAC,EAAE,MAAM,EAAE,GACvB,MAAM,GAAG,MAAM,GAAG,SAAS,CAI7B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,MAAM,GAAG,SAAS,CAqBxF;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBxE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOhD"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { resolveAuthorFromConfig } from './utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes keywords to an array of strings
|
|
4
|
+
* Handles both string (comma-separated) and array formats
|
|
5
|
+
* @param keywords - Keywords as string (comma-separated) or array of strings
|
|
6
|
+
* @returns Array of normalized keyword strings
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeKeywords(keywords) {
|
|
9
|
+
if (!keywords)
|
|
10
|
+
return [];
|
|
11
|
+
if (Array.isArray(keywords))
|
|
12
|
+
return keywords;
|
|
13
|
+
if (typeof keywords === 'string') {
|
|
14
|
+
return keywords.split(',').map((k) => k.trim()).filter(Boolean);
|
|
15
|
+
}
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Extracts author name from string or Author object
|
|
20
|
+
* @param author - Author as string or Author object
|
|
21
|
+
* @returns Author name as string
|
|
22
|
+
*/
|
|
23
|
+
export function getAuthorName(author) {
|
|
24
|
+
return typeof author === 'string' ? author : author.name;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Converts authors array (string | Author)[] to array of author names
|
|
28
|
+
* @param authors - Array of authors (strings or Author objects)
|
|
29
|
+
* @returns Array of author names as strings
|
|
30
|
+
*/
|
|
31
|
+
export function getAuthorNames(authors) {
|
|
32
|
+
return authors.map(getAuthorName);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Ensures all authors in the array are resolved from config
|
|
36
|
+
* This is a safety net to ensure authors are resolved even if they weren't resolved earlier
|
|
37
|
+
* @param authors - Array of authors (strings or Author objects)
|
|
38
|
+
* @param configAuthors - Array of authors from config
|
|
39
|
+
* @returns Array of resolved authors (Author objects or strings)
|
|
40
|
+
*/
|
|
41
|
+
export function ensureAuthorsResolved(authors, configAuthors) {
|
|
42
|
+
if (!configAuthors || configAuthors.length === 0) {
|
|
43
|
+
return authors;
|
|
44
|
+
}
|
|
45
|
+
return authors.map((author) => {
|
|
46
|
+
// If already an Author object, return as-is
|
|
47
|
+
if (typeof author === 'object') {
|
|
48
|
+
return author;
|
|
49
|
+
}
|
|
50
|
+
// If it's a string, try to resolve it from config
|
|
51
|
+
return resolveAuthorFromConfig(author, configAuthors);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolves defaultAuthor from config.authors array if available
|
|
56
|
+
* @param defaultAuthor - Default author name from config
|
|
57
|
+
* @param configAuthors - Array of authors from config
|
|
58
|
+
* @returns Author object if found, otherwise returns the name as string
|
|
59
|
+
*/
|
|
60
|
+
export function resolveDefaultAuthor(defaultAuthor, configAuthors) {
|
|
61
|
+
if (!defaultAuthor)
|
|
62
|
+
return undefined;
|
|
63
|
+
return resolveAuthorFromConfig(defaultAuthor, configAuthors);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Builds robots meta string from frontmatter
|
|
67
|
+
* Combines robots directive string with boolean flags (noindex, nofollow)
|
|
68
|
+
* @param frontmatter - Blog post frontmatter containing robots directives
|
|
69
|
+
* @returns Robots meta string (e.g., 'noindex, nofollow') or undefined
|
|
70
|
+
*/
|
|
71
|
+
export function buildRobotsMeta(frontmatter) {
|
|
72
|
+
const directives = [];
|
|
73
|
+
const robots = typeof frontmatter.robots === 'string' ? frontmatter.robots : undefined;
|
|
74
|
+
if (frontmatter.noindex || (robots && robots.includes('noindex'))) {
|
|
75
|
+
directives.push('noindex');
|
|
76
|
+
}
|
|
77
|
+
if (frontmatter.nofollow || (robots && robots.includes('nofollow'))) {
|
|
78
|
+
directives.push('nofollow');
|
|
79
|
+
}
|
|
80
|
+
// If explicit robots string is provided, use it (unless overridden by boolean flags)
|
|
81
|
+
if (robots && !frontmatter.noindex && !frontmatter.nofollow) {
|
|
82
|
+
return robots;
|
|
83
|
+
}
|
|
84
|
+
if (directives.length > 0) {
|
|
85
|
+
return directives.join(', ');
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolves a URL to an absolute URL
|
|
91
|
+
* If the URL is already absolute (starts with http:// or https://), returns it as-is
|
|
92
|
+
* If the URL is relative (starts with /), resolves it against siteUrl
|
|
93
|
+
* @param url - The URL to resolve (can be absolute or relative)
|
|
94
|
+
* @param siteUrl - The base site URL to resolve relative URLs against
|
|
95
|
+
* @returns Absolute URL
|
|
96
|
+
*/
|
|
97
|
+
export function resolveCanonicalUrl(url, siteUrl) {
|
|
98
|
+
// If already absolute, return as-is
|
|
99
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
100
|
+
return url;
|
|
101
|
+
}
|
|
102
|
+
// If relative, resolve against siteUrl
|
|
103
|
+
if (url.startsWith('/')) {
|
|
104
|
+
// Remove trailing slash from siteUrl if present
|
|
105
|
+
const baseUrl = siteUrl.replace(/\/$/, '');
|
|
106
|
+
return `${baseUrl}${url}`;
|
|
107
|
+
}
|
|
108
|
+
// If it's a relative path without leading slash, treat it as relative to root
|
|
109
|
+
const baseUrl = siteUrl.replace(/\/$/, '');
|
|
110
|
+
return `${baseUrl}/${url}`;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolves a post URL from canonical URL or generates default
|
|
114
|
+
* @param canonicalUrl - Optional canonical URL from frontmatter
|
|
115
|
+
* @param slug - Post slug
|
|
116
|
+
* @param siteUrl - Base site URL
|
|
117
|
+
* @returns Resolved absolute URL
|
|
118
|
+
*/
|
|
119
|
+
export function resolvePostUrl(canonicalUrl, slug, siteUrl) {
|
|
120
|
+
const urlRaw = canonicalUrl || `${siteUrl}/blog/${slug}`;
|
|
121
|
+
return siteUrl ? resolveCanonicalUrl(urlRaw, siteUrl) : urlRaw;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Escapes XML special characters to prevent injection attacks
|
|
125
|
+
* @param unsafe - String that may contain XML special characters
|
|
126
|
+
* @returns Escaped string safe for XML output
|
|
127
|
+
*/
|
|
128
|
+
export function escapeXml(unsafe) {
|
|
129
|
+
return unsafe
|
|
130
|
+
.replace(/&/g, '&')
|
|
131
|
+
.replace(/</g, '<')
|
|
132
|
+
.replace(/>/g, '>')
|
|
133
|
+
.replace(/"/g, '"')
|
|
134
|
+
.replace(/'/g, ''');
|
|
135
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO utilities and generators for next-md-blog
|
|
3
|
+
*
|
|
4
|
+
* This module provides functions for generating metadata, schema.org structured data,
|
|
5
|
+
* and RSS/sitemap feeds for blog posts.
|
|
6
|
+
*/
|
|
7
|
+
export { generateBlogPostMetadata, generateBlogListMetadata, } from './seo-metadata.js';
|
|
8
|
+
export { generateBlogPostSchema, generateBreadcrumbsSchema, } from './seo-schema.js';
|
|
9
|
+
export { generateSitemap, generateRSSFeed, } from './seo-feeds.js';
|
|
10
|
+
export { normalizeKeywords, getAuthorName, getAuthorNames, ensureAuthorsResolved, resolveDefaultAuthor, buildRobotsMeta, resolveCanonicalUrl, resolvePostUrl, escapeXml, } from './seo-utils.js';
|
|
11
|
+
//# sourceMappingURL=seo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../../src/core/seo.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,eAAe,EACf,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,SAAS,GACV,MAAM,gBAAgB,CAAC"}
|
package/dist/core/seo.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO utilities and generators for next-md-blog
|
|
3
|
+
*
|
|
4
|
+
* This module provides functions for generating metadata, schema.org structured data,
|
|
5
|
+
* and RSS/sitemap feeds for blog posts.
|
|
6
|
+
*/
|
|
7
|
+
// Re-export all SEO functions from their respective modules
|
|
8
|
+
export { generateBlogPostMetadata, generateBlogListMetadata, } from './seo-metadata.js';
|
|
9
|
+
export { generateBlogPostSchema, generateBreadcrumbsSchema, } from './seo-schema.js';
|
|
10
|
+
export { generateSitemap, generateRSSFeed, } from './seo-feeds.js';
|
|
11
|
+
// Re-export utility functions that might be useful
|
|
12
|
+
export { normalizeKeywords, getAuthorName, getAuthorNames, ensureAuthorsResolved, resolveDefaultAuthor, buildRobotsMeta, resolveCanonicalUrl, resolvePostUrl, escapeXml, } from './seo-utils.js';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { BlogPostFrontmatter, Author } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Type guard to check if a value is a non-empty string
|
|
4
|
+
*/
|
|
5
|
+
export declare function isString(value: unknown): value is string;
|
|
6
|
+
/**
|
|
7
|
+
* Type guard to check if a value is a string array
|
|
8
|
+
*/
|
|
9
|
+
export declare function isStringArray(value: unknown): value is string[];
|
|
10
|
+
/**
|
|
11
|
+
* Type guard to check if a value is a number
|
|
12
|
+
*/
|
|
13
|
+
export declare function isNumber(value: unknown): value is number;
|
|
14
|
+
/**
|
|
15
|
+
* Type guard to check if a value is an Author object
|
|
16
|
+
*/
|
|
17
|
+
export declare function isAuthorObject(value: unknown): value is {
|
|
18
|
+
name: string;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Type guard to check if a value is an Author
|
|
23
|
+
*/
|
|
24
|
+
export declare function isAuthor(value: unknown): value is Author;
|
|
25
|
+
/**
|
|
26
|
+
* Gets a string field from frontmatter with fallback
|
|
27
|
+
* @param frontmatter - Blog post frontmatter object
|
|
28
|
+
* @param field - Field name to extract
|
|
29
|
+
* @param fallback - Optional fallback value if field is missing or invalid
|
|
30
|
+
* @returns String value or undefined
|
|
31
|
+
*/
|
|
32
|
+
export declare function getStringField(frontmatter: BlogPostFrontmatter, field: keyof BlogPostFrontmatter, fallback?: string): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Gets a number field from frontmatter with fallback
|
|
35
|
+
* @param frontmatter - Blog post frontmatter object
|
|
36
|
+
* @param field - Field name to extract
|
|
37
|
+
* @param fallback - Optional fallback value if field is missing or invalid
|
|
38
|
+
* @returns Number value or undefined
|
|
39
|
+
*/
|
|
40
|
+
export declare function getNumberField(frontmatter: BlogPostFrontmatter, field: keyof BlogPostFrontmatter, fallback?: number): number | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Resolves a frontmatter field from multiple possible field names
|
|
43
|
+
* Checks fields in order and returns the first valid value found
|
|
44
|
+
* @param fields - Array of field names to check (in priority order)
|
|
45
|
+
* @param frontmatter - Blog post frontmatter object
|
|
46
|
+
* @param fallback - Optional fallback value if no fields are found
|
|
47
|
+
* @returns First valid field value or fallback
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const title = resolveFrontmatterField<string>(
|
|
51
|
+
* ['seoTitle', 'title'],
|
|
52
|
+
* frontmatter,
|
|
53
|
+
* 'Default Title'
|
|
54
|
+
* );
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveFrontmatterField<T>(fields: Array<keyof BlogPostFrontmatter>, frontmatter: BlogPostFrontmatter, fallback?: T): T | undefined;
|
|
58
|
+
//# sourceMappingURL=type-guards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-guards.d.ts","sourceRoot":"","sources":["../../src/core/type-guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAE9D;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,EAAE,CAE/D;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAOnD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAOxD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,mBAAmB,EAChC,KAAK,EAAE,MAAM,mBAAmB,EAChC,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAGpB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,mBAAmB,EAChC,KAAK,EAAE,MAAM,mBAAmB,EAChC,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAGpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EACvC,MAAM,EAAE,KAAK,CAAC,MAAM,mBAAmB,CAAC,EACxC,WAAW,EAAE,mBAAmB,EAChC,QAAQ,CAAC,EAAE,CAAC,GACX,CAAC,GAAG,SAAS,CAQf"}
|