@next-md-blog/core 1.0.7 → 1.1.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/dist/core/llms.d.ts +34 -0
- package/dist/core/llms.d.ts.map +1 -0
- package/dist/core/llms.js +109 -0
- package/dist/core/organization-schema.d.ts.map +1 -1
- package/dist/core/organization-schema.js +18 -2
- package/dist/core/queries.d.ts +35 -0
- package/dist/core/queries.d.ts.map +1 -0
- package/dist/core/queries.js +138 -0
- package/dist/core/seo-metadata.d.ts +35 -2
- package/dist/core/seo-metadata.d.ts.map +1 -1
- package/dist/core/seo-metadata.js +33 -8
- package/dist/core/seo-schema.d.ts +32 -5
- package/dist/core/seo-schema.d.ts.map +1 -1
- package/dist/core/seo-schema.js +78 -20
- package/dist/core/seo-utils.d.ts +4 -2
- package/dist/core/seo-utils.d.ts.map +1 -1
- package/dist/core/seo-utils.js +8 -4
- package/dist/core/seo.d.ts +2 -1
- package/dist/core/seo.d.ts.map +1 -1
- package/dist/core/types.d.ts +47 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llms.txt / llms-full.txt generators — see https://llmstxt.org
|
|
3
|
+
*
|
|
4
|
+
* These return plain markdown strings. App code wraps them in a route handler:
|
|
5
|
+
*
|
|
6
|
+
* // app/llms.txt/route.ts
|
|
7
|
+
* import { generateLlmsTxt } from '@next-md-blog/core';
|
|
8
|
+
* export const dynamic = 'force-static';
|
|
9
|
+
* export const revalidate = 3600;
|
|
10
|
+
* export async function GET() {
|
|
11
|
+
* const body = await generateLlmsTxt({ config, locales: ['en','fr'] });
|
|
12
|
+
* return new Response(body, { headers: { 'content-type': 'text/plain' }});
|
|
13
|
+
* }
|
|
14
|
+
*/
|
|
15
|
+
import type { Config } from './types.js';
|
|
16
|
+
export interface LlmsTxtOptions {
|
|
17
|
+
config?: Config;
|
|
18
|
+
/** Locales to enumerate. Defaults to `[config.defaultLang ?? 'en']`. */
|
|
19
|
+
locales?: readonly string[];
|
|
20
|
+
/** Marks one locale as default (added to the heading). Defaults to `locales[0]`. */
|
|
21
|
+
defaultLocale?: string;
|
|
22
|
+
/** Override the site summary (defaults to siteName + siteUrl). */
|
|
23
|
+
summary?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generate the index `llms.txt` markdown: one section per locale listing posts.
|
|
27
|
+
*/
|
|
28
|
+
export declare function generateLlmsTxt(options?: LlmsTxtOptions): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Generate `llms-full.txt`: concatenated post bodies (default locale only,
|
|
31
|
+
* unless `locales` is overridden). Designed for LLM ingestion.
|
|
32
|
+
*/
|
|
33
|
+
export declare function generateLlmsFullTxt(options?: LlmsTxtOptions): Promise<string>;
|
|
34
|
+
//# sourceMappingURL=llms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms.d.ts","sourceRoot":"","sources":["../../src/core/llms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAoB,MAAM,EAAE,MAAM,YAAY,CAAC;AAO3D,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAuBD;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,CA6CjB;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,CA+CjB"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { getAllBlogPosts, getBlogPost } from './file-utils.js';
|
|
2
|
+
import { resolveFrontmatterField } from './type-guards.js';
|
|
3
|
+
import { getConfig } from './config.js';
|
|
4
|
+
import { DEFAULT_SITE_NAME } from './constants.js';
|
|
5
|
+
import { resolvePostUrlWithConfig } from './seo-utils.js';
|
|
6
|
+
function postLine(post, siteUrl, config, locale) {
|
|
7
|
+
const title = post.frontmatter.title ?? post.slug;
|
|
8
|
+
const description = resolveFrontmatterField(['description', 'excerpt'], post.frontmatter) ?? '';
|
|
9
|
+
const url = resolvePostUrlWithConfig(resolveFrontmatterField(['canonicalUrl'], post.frontmatter), post.slug, siteUrl, config, locale);
|
|
10
|
+
return `- [${title}](${url})${description ? `: ${description}` : ''}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate the index `llms.txt` markdown: one section per locale listing posts.
|
|
14
|
+
*/
|
|
15
|
+
export async function generateLlmsTxt(options = {}) {
|
|
16
|
+
const config = options.config || getConfig();
|
|
17
|
+
const siteName = config.siteName ?? DEFAULT_SITE_NAME;
|
|
18
|
+
const siteUrl = (config.siteUrl ?? '').replace(/\/$/, '');
|
|
19
|
+
const locales = options.locales ?? [config.defaultLang ?? 'en'];
|
|
20
|
+
const defaultLocale = options.defaultLocale ?? locales[0];
|
|
21
|
+
const lines = [];
|
|
22
|
+
lines.push(`# ${siteName}`);
|
|
23
|
+
lines.push('');
|
|
24
|
+
if (options.summary) {
|
|
25
|
+
lines.push(`> ${options.summary}`);
|
|
26
|
+
}
|
|
27
|
+
else if (siteUrl) {
|
|
28
|
+
lines.push(`> Canonical site: ${siteUrl}`);
|
|
29
|
+
}
|
|
30
|
+
lines.push('');
|
|
31
|
+
for (const locale of locales) {
|
|
32
|
+
let posts;
|
|
33
|
+
try {
|
|
34
|
+
posts = await getAllBlogPosts({ config, locale });
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
posts = [];
|
|
38
|
+
}
|
|
39
|
+
if (!posts.length)
|
|
40
|
+
continue;
|
|
41
|
+
const isDefault = locale === defaultLocale;
|
|
42
|
+
lines.push(`## Blog (${locale})${isDefault ? ' — default locale' : ''}`);
|
|
43
|
+
lines.push('');
|
|
44
|
+
for (const p of posts) {
|
|
45
|
+
lines.push(postLine(p, siteUrl, config, locale));
|
|
46
|
+
}
|
|
47
|
+
lines.push('');
|
|
48
|
+
}
|
|
49
|
+
if (siteUrl) {
|
|
50
|
+
lines.push('## Feeds');
|
|
51
|
+
lines.push('');
|
|
52
|
+
for (const locale of locales) {
|
|
53
|
+
lines.push(`- [RSS — ${locale}](${siteUrl}/${locale}/feed.xml)`);
|
|
54
|
+
}
|
|
55
|
+
lines.push('');
|
|
56
|
+
lines.push(`- [Full prose](${siteUrl}/llms-full.txt)`);
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generate `llms-full.txt`: concatenated post bodies (default locale only,
|
|
62
|
+
* unless `locales` is overridden). Designed for LLM ingestion.
|
|
63
|
+
*/
|
|
64
|
+
export async function generateLlmsFullTxt(options = {}) {
|
|
65
|
+
const config = options.config || getConfig();
|
|
66
|
+
const siteName = config.siteName ?? DEFAULT_SITE_NAME;
|
|
67
|
+
const siteUrl = (config.siteUrl ?? '').replace(/\/$/, '');
|
|
68
|
+
const locales = options.locales ?? [config.defaultLang ?? 'en'];
|
|
69
|
+
const out = [];
|
|
70
|
+
out.push(`# ${siteName}`);
|
|
71
|
+
out.push('');
|
|
72
|
+
if (options.summary)
|
|
73
|
+
out.push(`> ${options.summary}`);
|
|
74
|
+
if (siteUrl)
|
|
75
|
+
out.push(`> Source: ${siteUrl}`);
|
|
76
|
+
out.push('');
|
|
77
|
+
for (const locale of locales) {
|
|
78
|
+
let metadata;
|
|
79
|
+
try {
|
|
80
|
+
metadata = await getAllBlogPosts({ config, locale });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
metadata = [];
|
|
84
|
+
}
|
|
85
|
+
for (const m of metadata) {
|
|
86
|
+
const post = await getBlogPost(m.slug, { config, locale });
|
|
87
|
+
if (!post)
|
|
88
|
+
continue;
|
|
89
|
+
const url = resolvePostUrlWithConfig(resolveFrontmatterField(['canonicalUrl'], post.frontmatter), post.slug, siteUrl, config, locale);
|
|
90
|
+
const title = post.frontmatter.title ?? post.slug;
|
|
91
|
+
out.push('---');
|
|
92
|
+
out.push('');
|
|
93
|
+
out.push(`# ${title}`);
|
|
94
|
+
out.push('');
|
|
95
|
+
if (url)
|
|
96
|
+
out.push(`Source: ${url}`);
|
|
97
|
+
const published = resolveFrontmatterField(['date'], post.frontmatter);
|
|
98
|
+
if (published)
|
|
99
|
+
out.push(`Published: ${published}`);
|
|
100
|
+
const updated = resolveFrontmatterField(['updated', 'modifiedDate'], post.frontmatter);
|
|
101
|
+
if (updated)
|
|
102
|
+
out.push(`Updated: ${updated}`);
|
|
103
|
+
out.push('');
|
|
104
|
+
out.push(post.content.trim());
|
|
105
|
+
out.push('');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return out.join('\n');
|
|
109
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organization-schema.d.ts","sourceRoot":"","sources":["../../src/core/organization-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAIzC;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAQvE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOzE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"organization-schema.d.ts","sourceRoot":"","sources":["../../src/core/organization-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAIzC;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAQvE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOzE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAmD1F;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAE/F;AAED,sDAAsD;AACtD,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAM3F;AAED,kDAAkD;AAClD,wBAAgB,0BAA0B,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAE/F"}
|
|
@@ -57,8 +57,24 @@ export function buildOrganizationNode(config) {
|
|
|
57
57
|
url: org.logo,
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
-
if (org?.
|
|
61
|
-
node.
|
|
60
|
+
if (org?.foundingDate)
|
|
61
|
+
node.foundingDate = org.foundingDate;
|
|
62
|
+
if (org?.founder) {
|
|
63
|
+
node.founder = { '@type': 'Person', name: org.founder };
|
|
64
|
+
}
|
|
65
|
+
if (org?.address && Object.values(org.address).some(Boolean)) {
|
|
66
|
+
node.address = { '@type': 'PostalAddress', ...org.address };
|
|
67
|
+
}
|
|
68
|
+
if (org?.contactPoint && Object.values(org.contactPoint).some(Boolean)) {
|
|
69
|
+
node.contactPoint = { '@type': 'ContactPoint', ...org.contactPoint };
|
|
70
|
+
}
|
|
71
|
+
// Merge wikidata into sameAs (deduplicated).
|
|
72
|
+
const sameAsList = [
|
|
73
|
+
...(org?.sameAs ?? []),
|
|
74
|
+
...(org?.wikidata ? [org.wikidata] : []),
|
|
75
|
+
].filter((v, i, arr) => arr.indexOf(v) === i);
|
|
76
|
+
if (sameAsList.length > 0) {
|
|
77
|
+
node.sameAs = sameAsList.length === 1 ? sameAsList[0] : sameAsList;
|
|
62
78
|
}
|
|
63
79
|
return node;
|
|
64
80
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level query helpers built on top of getBlogPost / getAllBlogPosts.
|
|
3
|
+
*
|
|
4
|
+
* These cover the patterns app code keeps reinventing:
|
|
5
|
+
* - looking up the same slug across every locale (for hreflang)
|
|
6
|
+
* - filtering posts by author or series
|
|
7
|
+
* - enumerating slugs for `generateStaticParams`
|
|
8
|
+
*/
|
|
9
|
+
import type { BlogPost, BlogPostMetadata, GetBlogPostOptions } from './types.js';
|
|
10
|
+
export declare function slugifyAuthor(name: string): string;
|
|
11
|
+
export declare function slugifySeries(value: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Collect every author name referenced in a post's frontmatter (`author` and `authors`).
|
|
14
|
+
*/
|
|
15
|
+
export declare function authorNamesFromFrontmatter(fm: Record<string, unknown>): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Look up the same slug across every locale. Returned map preserves locale insertion order.
|
|
18
|
+
* Useful for building `alternates.languages` in `generateMetadata` without writing a loop.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getPostInAllLocales(slug: string, locales: readonly string[], options?: Omit<GetBlogPostOptions, 'locale'>): Promise<Map<string, BlogPost | null>>;
|
|
21
|
+
/**
|
|
22
|
+
* Returns posts authored (or co-authored) by `authorSlug`. Slugification matches
|
|
23
|
+
* `slugifyAuthor` so the same convention is used everywhere.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getPostsByAuthor(authorSlug: string, options?: GetBlogPostOptions): Promise<BlogPostMetadata[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Returns posts that share a `series` frontmatter value. Sorted by `seriesOrder`,
|
|
28
|
+
* falling back to publish date (ascending).
|
|
29
|
+
*/
|
|
30
|
+
export declare function getPostsBySeries(seriesSlug: string, options?: GetBlogPostOptions): Promise<BlogPostMetadata[]>;
|
|
31
|
+
/** Enumerate every author slug across the provided locales (or the default locale only). */
|
|
32
|
+
export declare function getAllAuthorSlugs(options?: GetBlogPostOptions, locales?: readonly string[]): Promise<string[]>;
|
|
33
|
+
/** Enumerate every series slug across the provided locales. */
|
|
34
|
+
export declare function getAllSeriesSlugs(options?: GetBlogPostOptions, locales?: readonly string[]): Promise<string[]>;
|
|
35
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/core/queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EACV,QAAQ,EACR,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAIpB,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOlD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAWD;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC1B,MAAM,EAAE,CAoBV;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,GAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAM,GAC/C,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC,CAAC,CAWvC;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAO7B;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAgB7B;AAED,4FAA4F;AAC5F,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,kBAAuB,EAChC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBnB;AAED,+DAA+D;AAC/D,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,kBAAuB,EAChC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAAC,MAAM,EAAE,CAAC,CAanB"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { getAllBlogPosts, getBlogPost } from './file-utils.js';
|
|
2
|
+
import { resolveFrontmatterField } from './type-guards.js';
|
|
3
|
+
export function slugifyAuthor(name) {
|
|
4
|
+
return name
|
|
5
|
+
.toLowerCase()
|
|
6
|
+
.trim()
|
|
7
|
+
.replace(/[^\p{Letter}\p{Number}\s-]/gu, '')
|
|
8
|
+
.replace(/\s+/g, '-')
|
|
9
|
+
.replace(/-+/g, '-');
|
|
10
|
+
}
|
|
11
|
+
export function slugifySeries(value) {
|
|
12
|
+
return slugifyAuthor(value);
|
|
13
|
+
}
|
|
14
|
+
function authorNameFrom(value) {
|
|
15
|
+
if (typeof value === 'string')
|
|
16
|
+
return value;
|
|
17
|
+
if (value && typeof value === 'object' && 'name' in value) {
|
|
18
|
+
const name = value.name;
|
|
19
|
+
if (typeof name === 'string')
|
|
20
|
+
return name;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Collect every author name referenced in a post's frontmatter (`author` and `authors`).
|
|
26
|
+
*/
|
|
27
|
+
export function authorNamesFromFrontmatter(fm) {
|
|
28
|
+
const out = new Set();
|
|
29
|
+
const author = fm.author;
|
|
30
|
+
if (Array.isArray(author)) {
|
|
31
|
+
for (const a of author) {
|
|
32
|
+
const n = authorNameFrom(a);
|
|
33
|
+
if (n)
|
|
34
|
+
out.add(n);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const n = authorNameFrom(author);
|
|
39
|
+
if (n)
|
|
40
|
+
out.add(n);
|
|
41
|
+
}
|
|
42
|
+
const authors = fm.authors;
|
|
43
|
+
if (Array.isArray(authors)) {
|
|
44
|
+
for (const a of authors) {
|
|
45
|
+
const n = authorNameFrom(a);
|
|
46
|
+
if (n)
|
|
47
|
+
out.add(n);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return [...out];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Look up the same slug across every locale. Returned map preserves locale insertion order.
|
|
54
|
+
* Useful for building `alternates.languages` in `generateMetadata` without writing a loop.
|
|
55
|
+
*/
|
|
56
|
+
export async function getPostInAllLocales(slug, locales, options = {}) {
|
|
57
|
+
const entries = await Promise.all(locales.map(async (locale) => {
|
|
58
|
+
try {
|
|
59
|
+
return [locale, await getBlogPost(slug, { ...options, locale })];
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [locale, null];
|
|
63
|
+
}
|
|
64
|
+
}));
|
|
65
|
+
return new Map(entries);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Returns posts authored (or co-authored) by `authorSlug`. Slugification matches
|
|
69
|
+
* `slugifyAuthor` so the same convention is used everywhere.
|
|
70
|
+
*/
|
|
71
|
+
export async function getPostsByAuthor(authorSlug, options = {}) {
|
|
72
|
+
const posts = await getAllBlogPosts(options);
|
|
73
|
+
return posts.filter((p) => authorNamesFromFrontmatter(p.frontmatter).some((name) => slugifyAuthor(name) === authorSlug));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Returns posts that share a `series` frontmatter value. Sorted by `seriesOrder`,
|
|
77
|
+
* falling back to publish date (ascending).
|
|
78
|
+
*/
|
|
79
|
+
export async function getPostsBySeries(seriesSlug, options = {}) {
|
|
80
|
+
const posts = await getAllBlogPosts(options);
|
|
81
|
+
const inSeries = posts.filter((p) => {
|
|
82
|
+
const raw = resolveFrontmatterField(['series'], p.frontmatter);
|
|
83
|
+
return raw ? slugifySeries(raw) === seriesSlug : false;
|
|
84
|
+
});
|
|
85
|
+
return inSeries.sort((a, b) => {
|
|
86
|
+
const oa = resolveFrontmatterField(['seriesOrder'], a.frontmatter);
|
|
87
|
+
const ob = resolveFrontmatterField(['seriesOrder'], b.frontmatter);
|
|
88
|
+
if (typeof oa === 'number' && typeof ob === 'number')
|
|
89
|
+
return oa - ob;
|
|
90
|
+
if (typeof oa === 'number')
|
|
91
|
+
return -1;
|
|
92
|
+
if (typeof ob === 'number')
|
|
93
|
+
return 1;
|
|
94
|
+
const da = a.frontmatter.date || '';
|
|
95
|
+
const db = b.frontmatter.date || '';
|
|
96
|
+
return da.localeCompare(db);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** Enumerate every author slug across the provided locales (or the default locale only). */
|
|
100
|
+
export async function getAllAuthorSlugs(options = {}, locales) {
|
|
101
|
+
const localeList = locales && locales.length > 0 ? locales : [options.locale ?? undefined];
|
|
102
|
+
const out = new Set();
|
|
103
|
+
for (const locale of localeList) {
|
|
104
|
+
const opts = { ...options };
|
|
105
|
+
if (locale)
|
|
106
|
+
opts.locale = locale;
|
|
107
|
+
const posts = await getAllBlogPosts(opts);
|
|
108
|
+
for (const p of posts) {
|
|
109
|
+
for (const name of authorNamesFromFrontmatter(p.frontmatter)) {
|
|
110
|
+
out.add(slugifyAuthor(name));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Also include authors defined in config but with no posts yet — useful for
|
|
115
|
+
// dedicated author landing pages.
|
|
116
|
+
for (const a of options.config?.authors ?? []) {
|
|
117
|
+
if (a.name)
|
|
118
|
+
out.add(slugifyAuthor(a.name));
|
|
119
|
+
}
|
|
120
|
+
return [...out];
|
|
121
|
+
}
|
|
122
|
+
/** Enumerate every series slug across the provided locales. */
|
|
123
|
+
export async function getAllSeriesSlugs(options = {}, locales) {
|
|
124
|
+
const localeList = locales && locales.length > 0 ? locales : [options.locale ?? undefined];
|
|
125
|
+
const out = new Set();
|
|
126
|
+
for (const locale of localeList) {
|
|
127
|
+
const opts = { ...options };
|
|
128
|
+
if (locale)
|
|
129
|
+
opts.locale = locale;
|
|
130
|
+
const posts = await getAllBlogPosts(opts);
|
|
131
|
+
for (const p of posts) {
|
|
132
|
+
const raw = resolveFrontmatterField(['series'], p.frontmatter);
|
|
133
|
+
if (raw)
|
|
134
|
+
out.add(slugifySeries(raw));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return [...out];
|
|
138
|
+
}
|
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
import type { BlogPost, BlogPostMetadata, Config } from './types.js';
|
|
2
2
|
import type { Metadata } from 'next';
|
|
3
|
+
/** Optional behaviour switches for `generateBlogPostMetadata`. */
|
|
4
|
+
export interface GenerateBlogPostMetadataOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Locale segment to inject into the canonical URL (`/{locale}/{segment}/{slug}`).
|
|
7
|
+
* Also sets `openGraph.locale` and is used to scope hreflang fallbacks.
|
|
8
|
+
*/
|
|
9
|
+
locale?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Controls the final `<title>` value.
|
|
12
|
+
* - `"site-suffix"` (default): `"{title} | {siteName}"` — same as 1.0.
|
|
13
|
+
* - `"absolute"`: returns `{ absolute: "{title} | {siteName}" }` so a parent
|
|
14
|
+
* layout's `title.template` does not double up.
|
|
15
|
+
* - `"bare"`: just the post title; let a parent layout's template handle the suffix.
|
|
16
|
+
* - A function: receives `{ title, siteName }` and returns the final string.
|
|
17
|
+
*/
|
|
18
|
+
titleTemplate?: 'site-suffix' | 'absolute' | 'bare' | ((args: {
|
|
19
|
+
title: string;
|
|
20
|
+
siteName: string;
|
|
21
|
+
}) => string);
|
|
22
|
+
/**
|
|
23
|
+
* Custom URL builder. Overrides the default `/{locale}/{segment}/{slug}` logic.
|
|
24
|
+
*/
|
|
25
|
+
urlBuilder?: (args: {
|
|
26
|
+
canonicalUrl: string | undefined;
|
|
27
|
+
slug: string;
|
|
28
|
+
siteUrl: string;
|
|
29
|
+
locale?: string;
|
|
30
|
+
config: Config;
|
|
31
|
+
}) => string;
|
|
32
|
+
/** Locale-aware hreflang map override. Useful when sibling URLs are computed externally. */
|
|
33
|
+
alternateLanguages?: Record<string, string>;
|
|
34
|
+
}
|
|
3
35
|
/**
|
|
4
|
-
* Generates comprehensive metadata for a blog post
|
|
36
|
+
* Generates comprehensive metadata for a blog post.
|
|
5
37
|
* @param post - The blog post
|
|
6
38
|
* @param config - SEO configuration
|
|
39
|
+
* @param options - Locale + URL + title overrides (see {@link GenerateBlogPostMetadataOptions})
|
|
7
40
|
* @returns Metadata object for Next.js
|
|
8
41
|
*/
|
|
9
|
-
export declare function generateBlogPostMetadata(post: BlogPost, config?: Config): Metadata;
|
|
42
|
+
export declare function generateBlogPostMetadata(post: BlogPost, config?: Config, options?: GenerateBlogPostMetadataOptions): Metadata;
|
|
10
43
|
/**
|
|
11
44
|
* Generates metadata for the blog listing page
|
|
12
45
|
* @param posts - Array of blog posts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seo-metadata.d.ts","sourceRoot":"","sources":["../../src/core/seo-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAgBrC
|
|
1
|
+
{"version":3,"file":"seo-metadata.d.ts","sourceRoot":"","sources":["../../src/core/seo-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAgBrC,kEAAkE;AAClE,MAAM,WAAW,+BAA+B;IAC9C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,aAAa,CAAC,EACV,aAAa,GACb,UAAU,GACV,MAAM,GACN,CAAC,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC,CAAC;IAC5D;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;QACjC,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,MAAM,CAAC;IACb,4FAA4F;IAC5F,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,+BAA+B,GACxC,QAAQ,CAyNV;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,gBAAgB,EAAE,EACzB,MAAM,CAAC,EAAE,MAAM,GACd,QAAQ,CAyBV"}
|
|
@@ -3,19 +3,35 @@ import { resolveFrontmatterField, isStringArray } from './type-guards.js';
|
|
|
3
3
|
import { DEFAULT_SITE_NAME, DEFAULT_LANG } from './constants.js';
|
|
4
4
|
import { normalizeKeywords, getAuthorNames, ensureAuthorsResolved, resolveDefaultAuthor, buildRobotsMeta, resolvePostUrlWithConfig, resolveHreflangMap, resolveBlogIndexUrl, resolveCanonicalUrl, } from './seo-utils.js';
|
|
5
5
|
/**
|
|
6
|
-
* Generates comprehensive metadata for a blog post
|
|
6
|
+
* Generates comprehensive metadata for a blog post.
|
|
7
7
|
* @param post - The blog post
|
|
8
8
|
* @param config - SEO configuration
|
|
9
|
+
* @param options - Locale + URL + title overrides (see {@link GenerateBlogPostMetadataOptions})
|
|
9
10
|
* @returns Metadata object for Next.js
|
|
10
11
|
*/
|
|
11
|
-
export function generateBlogPostMetadata(post, config) {
|
|
12
|
+
export function generateBlogPostMetadata(post, config, options) {
|
|
12
13
|
const blogConfig = config || getConfig();
|
|
14
|
+
const opts = options ?? {};
|
|
13
15
|
const { siteName = DEFAULT_SITE_NAME, siteUrl = '', defaultAuthor, authors: configAuthors, twitterHandle, defaultOgImage, defaultLang = DEFAULT_LANG, } = blogConfig;
|
|
14
16
|
const fm = post.frontmatter;
|
|
15
17
|
// Title resolution: seoTitle > title > slug
|
|
16
18
|
const baseTitle = resolveFrontmatterField(['title'], fm, post.slug) || post.slug;
|
|
17
19
|
const seoTitle = resolveFrontmatterField(['seoTitle', 'title'], fm, baseTitle) || baseTitle;
|
|
18
|
-
const
|
|
20
|
+
const titleMode = opts.titleTemplate ?? 'site-suffix';
|
|
21
|
+
const suffixed = `${seoTitle} | ${siteName}`;
|
|
22
|
+
let pageTitle;
|
|
23
|
+
if (typeof titleMode === 'function') {
|
|
24
|
+
pageTitle = titleMode({ title: seoTitle, siteName });
|
|
25
|
+
}
|
|
26
|
+
else if (titleMode === 'absolute') {
|
|
27
|
+
pageTitle = { absolute: suffixed };
|
|
28
|
+
}
|
|
29
|
+
else if (titleMode === 'bare') {
|
|
30
|
+
pageTitle = seoTitle;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
pageTitle = suffixed;
|
|
34
|
+
}
|
|
19
35
|
// Description resolution: seoDescription > description > excerpt > empty
|
|
20
36
|
const description = resolveFrontmatterField(['seoDescription', 'description', 'excerpt'], fm, '') || '';
|
|
21
37
|
// Use normalized authors from post, or fallback to default author (resolved from config if available)
|
|
@@ -57,9 +73,18 @@ export function generateBlogPostMetadata(post, config) {
|
|
|
57
73
|
const twitterDescription = resolveFrontmatterField(['twitterDescription'], fm) || ogDescription;
|
|
58
74
|
// Canonical URL - supports both absolute and relative URLs
|
|
59
75
|
// Relative URLs will be resolved against siteUrl
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
const fmCanonical = resolveFrontmatterField(['canonicalUrl'], fm);
|
|
77
|
+
const canonicalUrl = opts.urlBuilder
|
|
78
|
+
? opts.urlBuilder({
|
|
79
|
+
canonicalUrl: fmCanonical,
|
|
80
|
+
slug: post.slug,
|
|
81
|
+
siteUrl,
|
|
82
|
+
...(opts.locale ? { locale: opts.locale } : {}),
|
|
83
|
+
config: blogConfig,
|
|
84
|
+
})
|
|
85
|
+
: resolvePostUrlWithConfig(fmCanonical, post.slug, siteUrl, blogConfig, opts.locale);
|
|
86
|
+
// Language: explicit option > frontmatter.lang > config.defaultLang
|
|
87
|
+
const lang = opts.locale || resolveFrontmatterField(['lang'], fm) || defaultLang;
|
|
63
88
|
// Robots meta
|
|
64
89
|
const robots = buildRobotsMeta(fm);
|
|
65
90
|
const postUrl = canonicalUrl;
|
|
@@ -95,11 +120,11 @@ export function generateBlogPostMetadata(post, config) {
|
|
|
95
120
|
}
|
|
96
121
|
}
|
|
97
122
|
});
|
|
98
|
-
// Build alternates object
|
|
123
|
+
// Build alternates object — explicit option > frontmatter > config.
|
|
99
124
|
const alternates = {};
|
|
100
125
|
if (canonicalUrl)
|
|
101
126
|
alternates.canonical = canonicalUrl;
|
|
102
|
-
const hreflang = resolveHreflangMap(fm, blogConfig);
|
|
127
|
+
const hreflang = opts.alternateLanguages ?? resolveHreflangMap(fm, blogConfig);
|
|
103
128
|
if (hreflang && Object.keys(hreflang).length > 0) {
|
|
104
129
|
alternates.languages = siteUrl
|
|
105
130
|
? Object.fromEntries(Object.entries(hreflang).map(([k, v]) => [
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
import type { BlogPost, Config } from './types.js';
|
|
2
|
-
/** Options for `generateBlogPostSchema
|
|
2
|
+
/** Options for `generateBlogPostSchema`. */
|
|
3
3
|
export interface BlogPostSchemaOptions {
|
|
4
4
|
/** If true and an organization @id exists, publisher is `{ "@id": "..." }` only */
|
|
5
5
|
publisherReference?: boolean;
|
|
6
|
+
/** Locale segment, used for URL building and `inLanguage`. */
|
|
7
|
+
locale?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Speakable specification (https://schema.org/speakable). Pass `true` to use a
|
|
10
|
+
* sensible default (article header + first paragraph), or your own selectors.
|
|
11
|
+
*/
|
|
12
|
+
speakable?: boolean | {
|
|
13
|
+
cssSelector?: string[];
|
|
14
|
+
xpath?: string[];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Free-form mutation of the BlogPosting node before serialization. Use this
|
|
18
|
+
* for HowTo extensions, custom fields, isPartOf overrides, etc.
|
|
19
|
+
*/
|
|
20
|
+
extendArticle?: (node: Record<string, unknown>) => Record<string, unknown>;
|
|
6
21
|
}
|
|
7
22
|
/**
|
|
8
|
-
* Generates JSON-LD structured data (Schema.org) for a blog post
|
|
23
|
+
* Generates JSON-LD structured data (Schema.org) for a blog post.
|
|
9
24
|
* @param post - The blog post
|
|
10
25
|
* @param config - SEO configuration
|
|
26
|
+
* @param options - Locale, speakable, publisher reference, extension hook
|
|
11
27
|
* @returns JSON-LD schema object
|
|
12
28
|
*/
|
|
13
29
|
export declare function generateBlogPostSchema(post: BlogPost, config?: Config, options?: BlogPostSchemaOptions): Record<string, unknown>;
|
|
@@ -15,18 +31,29 @@ export declare function generateBlogPostSchema(post: BlogPost, config?: Config,
|
|
|
15
31
|
* Generates breadcrumbs schema for a blog post
|
|
16
32
|
* @param post - The blog post
|
|
17
33
|
* @param config - SEO configuration
|
|
18
|
-
* @param breadcrumbs - Optional custom breadcrumb items
|
|
34
|
+
* @param breadcrumbs - Optional custom breadcrumb items
|
|
35
|
+
* @param locale - Optional locale segment for URL building
|
|
19
36
|
* @returns Breadcrumbs JSON-LD schema object
|
|
20
37
|
*/
|
|
21
38
|
export declare function generateBreadcrumbsSchema(post: BlogPost, config?: Config, breadcrumbs?: Array<{
|
|
22
39
|
name: string;
|
|
23
40
|
url: string;
|
|
24
|
-
}
|
|
41
|
+
}>, locale?: string): Record<string, unknown>;
|
|
42
|
+
/** Options for `generateBlogPostSchemaGraph`. */
|
|
43
|
+
export interface BlogPostSchemaGraphOptions {
|
|
44
|
+
locale?: string;
|
|
45
|
+
speakable?: BlogPostSchemaOptions['speakable'];
|
|
46
|
+
extendArticle?: BlogPostSchemaOptions['extendArticle'];
|
|
47
|
+
}
|
|
25
48
|
/**
|
|
26
49
|
* Single JSON-LD `@graph` for Organization + BlogPosting + BreadcrumbList.
|
|
50
|
+
*
|
|
51
|
+
* Backwards-compatible: the 5th `options` parameter is optional. Passing
|
|
52
|
+
* `{ locale, speakable, extendArticle }` removes the need for app code to
|
|
53
|
+
* post-mutate the graph for locale-aware URLs and rich SEO fields.
|
|
27
54
|
*/
|
|
28
55
|
export declare function generateBlogPostSchemaGraph(post: BlogPost, config?: Config, breadcrumbs?: Array<{
|
|
29
56
|
name: string;
|
|
30
57
|
url: string;
|
|
31
|
-
}>, includeBreadcrumbs?: boolean): Record<string, unknown>;
|
|
58
|
+
}>, includeBreadcrumbs?: boolean, options?: BlogPostSchemaGraphOptions): Record<string, unknown>;
|
|
32
59
|
//# sourceMappingURL=seo-schema.d.ts.map
|
|
@@ -1 +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;AAiB3D,
|
|
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;AAiB3D,4CAA4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG;QAAE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACnE;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5E;AAgBD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,qBAAqB,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA8JzB;AAED;;;;;;;GAOG;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,EAClD,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqCzB;AAED,iDAAiD;AACjD,MAAM,WAAW,0BAA0B;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAC/C,aAAa,CAAC,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAC;CACxD;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,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,EAClD,kBAAkB,UAAO,EACzB,OAAO,CAAC,EAAE,0BAA0B,GACnC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0BzB"}
|
package/dist/core/seo-schema.js
CHANGED
|
@@ -4,31 +4,44 @@ import { resolveFrontmatterField, isStringArray } from './type-guards.js';
|
|
|
4
4
|
import { DEFAULT_SITE_NAME } from './constants.js';
|
|
5
5
|
import { ensureAuthorsResolved, resolveDefaultAuthor, resolvePostUrlWithConfig, resolveBlogIndexUrl, } from './seo-utils.js';
|
|
6
6
|
import { buildPublisherEmbedded, buildOrganizationGraphNode, resolveOrganizationId, } from './organization-schema.js';
|
|
7
|
+
const DEFAULT_SPEAKABLE_SELECTORS = [
|
|
8
|
+
'article > header h1',
|
|
9
|
+
'article > header p',
|
|
10
|
+
];
|
|
11
|
+
function slugifySeries(value) {
|
|
12
|
+
return value
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.trim()
|
|
15
|
+
.replace(/[^\p{Letter}\p{Number}\s-]/gu, '')
|
|
16
|
+
.replace(/\s+/g, '-')
|
|
17
|
+
.replace(/-+/g, '-');
|
|
18
|
+
}
|
|
7
19
|
/**
|
|
8
|
-
* Generates JSON-LD structured data (Schema.org) for a blog post
|
|
20
|
+
* Generates JSON-LD structured data (Schema.org) for a blog post.
|
|
9
21
|
* @param post - The blog post
|
|
10
22
|
* @param config - SEO configuration
|
|
23
|
+
* @param options - Locale, speakable, publisher reference, extension hook
|
|
11
24
|
* @returns JSON-LD schema object
|
|
12
25
|
*/
|
|
13
26
|
export function generateBlogPostSchema(post, config, options) {
|
|
14
27
|
const blogConfig = config || getConfig();
|
|
15
28
|
const { siteName = DEFAULT_SITE_NAME, siteUrl = '', defaultAuthor, authors: configAuthors, } = blogConfig;
|
|
16
29
|
const publisherReference = options?.publisherReference === true;
|
|
30
|
+
const locale = options?.locale;
|
|
17
31
|
const fm = post.frontmatter;
|
|
18
32
|
const title = resolveFrontmatterField(['seoTitle', 'title'], fm, post.slug) || post.slug;
|
|
19
33
|
const description = resolveFrontmatterField(['seoDescription', 'description', 'excerpt'], fm, '') || '';
|
|
20
|
-
// Use normalized authors from post, or fallback to default author (resolved from config if available)
|
|
21
|
-
// Note: post.authors should already be resolved from config.authors when the post was loaded (in file-utils.ts)
|
|
22
|
-
// However, we ensure they are resolved here as a safety net in case resolution didn't happen earlier
|
|
34
|
+
// Use normalized authors from post, or fallback to default author (resolved from config if available).
|
|
23
35
|
const resolvedDefaultAuthor = resolveDefaultAuthor(defaultAuthor, configAuthors);
|
|
24
36
|
const postAuthors = (post.authors && post.authors.length > 0)
|
|
25
37
|
? post.authors
|
|
26
38
|
: (resolvedDefaultAuthor ? [resolvedDefaultAuthor] : []);
|
|
27
|
-
// Ensure all authors are resolved from config (safety net)
|
|
28
39
|
const authors = ensureAuthorsResolved(postAuthors, configAuthors);
|
|
29
40
|
const publishedDate = resolveFrontmatterField(['publishedDate', 'date'], fm);
|
|
30
|
-
|
|
31
|
-
const
|
|
41
|
+
// `updated` is a first-class alias for `modifiedDate`.
|
|
42
|
+
const modifiedDate = resolveFrontmatterField(['modifiedDate', 'updated'], fm) ||
|
|
43
|
+
publishedDate;
|
|
44
|
+
const postUrl = resolvePostUrlWithConfig(resolveFrontmatterField(['canonicalUrl'], fm), post.slug, siteUrl, blogConfig, locale);
|
|
32
45
|
const ogImageUrl = resolveFrontmatterField(['ogImage', 'image'], fm);
|
|
33
46
|
// Calculate reading time and word count if not provided
|
|
34
47
|
const readingTime = resolveFrontmatterField(['readingTime'], fm) || calculateReadingTime(post.content);
|
|
@@ -49,6 +62,7 @@ export function generateBlogPostSchema(post, config, options) {
|
|
|
49
62
|
...(author.avatar && { image: author.avatar }),
|
|
50
63
|
};
|
|
51
64
|
};
|
|
65
|
+
const lang = locale ?? resolveFrontmatterField(['lang'], fm);
|
|
52
66
|
// Base schema
|
|
53
67
|
const schema = {
|
|
54
68
|
'@context': 'https://schema.org',
|
|
@@ -89,33 +103,68 @@ export function generateBlogPostSchema(post, config, options) {
|
|
|
89
103
|
...(isStringArray(fm.tags) && fm.tags.length > 0 && {
|
|
90
104
|
keywords: fm.tags.join(', '),
|
|
91
105
|
}),
|
|
92
|
-
...(
|
|
106
|
+
...(lang ? { inLanguage: lang } : {}),
|
|
93
107
|
...(wordCount > 0 && { wordCount }),
|
|
94
108
|
...(readingTime > 0 && {
|
|
95
109
|
timeRequired: `PT${readingTime}M`,
|
|
96
110
|
}),
|
|
97
111
|
};
|
|
98
|
-
//
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
// Speakable spec (voice-assistant friendly).
|
|
113
|
+
if (options?.speakable) {
|
|
114
|
+
const sp = options.speakable === true
|
|
115
|
+
? { cssSelector: DEFAULT_SPEAKABLE_SELECTORS }
|
|
116
|
+
: options.speakable;
|
|
117
|
+
schema.speakable = { '@type': 'SpeakableSpecification', ...sp };
|
|
118
|
+
}
|
|
119
|
+
// E-E-A-T: reviewer / fact-checker / last reviewed.
|
|
120
|
+
const reviewedBy = resolveFrontmatterField(['reviewedBy'], fm);
|
|
121
|
+
if (reviewedBy) {
|
|
122
|
+
schema.reviewedBy = { '@type': 'Person', name: reviewedBy };
|
|
123
|
+
}
|
|
124
|
+
const factCheckedBy = resolveFrontmatterField(['factCheckedBy'], fm);
|
|
125
|
+
if (factCheckedBy) {
|
|
126
|
+
schema.factCheckedBy = { '@type': 'Person', name: factCheckedBy };
|
|
127
|
+
}
|
|
128
|
+
const lastReviewed = resolveFrontmatterField(['lastReviewed'], fm);
|
|
129
|
+
if (lastReviewed) {
|
|
130
|
+
schema.lastReviewed = lastReviewed;
|
|
131
|
+
}
|
|
132
|
+
// Series → isPartOf the pillar CollectionPage.
|
|
133
|
+
const series = resolveFrontmatterField(['series'], fm);
|
|
134
|
+
if (series && siteUrl) {
|
|
135
|
+
const localeSeg = locale ? `/${locale}/` : '/';
|
|
136
|
+
const seriesSlug = slugifySeries(series);
|
|
137
|
+
const seriesTitle = resolveFrontmatterField(['seriesTitle'], fm) || series;
|
|
138
|
+
schema.isPartOf = {
|
|
139
|
+
'@type': 'CollectionPage',
|
|
140
|
+
'@id': `${siteUrl.replace(/\/$/, '')}${localeSeg}topics/${seriesSlug}`,
|
|
141
|
+
name: seriesTitle,
|
|
103
142
|
};
|
|
104
143
|
}
|
|
105
|
-
|
|
144
|
+
// Merge with custom schema from frontmatter (frontmatter wins).
|
|
145
|
+
let merged = schema;
|
|
146
|
+
if (fm.schema && typeof fm.schema === 'object') {
|
|
147
|
+
merged = { ...schema, ...fm.schema };
|
|
148
|
+
}
|
|
149
|
+
// Free-form mutation hook (runs last).
|
|
150
|
+
if (options?.extendArticle) {
|
|
151
|
+
return options.extendArticle({ ...merged });
|
|
152
|
+
}
|
|
153
|
+
return merged;
|
|
106
154
|
}
|
|
107
155
|
/**
|
|
108
156
|
* Generates breadcrumbs schema for a blog post
|
|
109
157
|
* @param post - The blog post
|
|
110
158
|
* @param config - SEO configuration
|
|
111
|
-
* @param breadcrumbs - Optional custom breadcrumb items
|
|
159
|
+
* @param breadcrumbs - Optional custom breadcrumb items
|
|
160
|
+
* @param locale - Optional locale segment for URL building
|
|
112
161
|
* @returns Breadcrumbs JSON-LD schema object
|
|
113
162
|
*/
|
|
114
|
-
export function generateBreadcrumbsSchema(post, config, breadcrumbs) {
|
|
163
|
+
export function generateBreadcrumbsSchema(post, config, breadcrumbs, locale) {
|
|
115
164
|
const blogConfig = config || getConfig();
|
|
116
165
|
const { siteUrl = '' } = blogConfig;
|
|
117
166
|
const title = resolveFrontmatterField(['seoTitle', 'title'], post.frontmatter, post.slug) || post.slug;
|
|
118
|
-
const postUrl = resolvePostUrlWithConfig(resolveFrontmatterField(['canonicalUrl'], post.frontmatter), post.slug, siteUrl, blogConfig);
|
|
167
|
+
const postUrl = resolvePostUrlWithConfig(resolveFrontmatterField(['canonicalUrl'], post.frontmatter), post.slug, siteUrl, blogConfig, locale);
|
|
119
168
|
const blogIndexUrl = resolveBlogIndexUrl(siteUrl, blogConfig);
|
|
120
169
|
// Default breadcrumbs: Home > Blog > Post
|
|
121
170
|
const defaultBreadcrumbs = [
|
|
@@ -137,10 +186,19 @@ export function generateBreadcrumbsSchema(post, config, breadcrumbs) {
|
|
|
137
186
|
}
|
|
138
187
|
/**
|
|
139
188
|
* Single JSON-LD `@graph` for Organization + BlogPosting + BreadcrumbList.
|
|
189
|
+
*
|
|
190
|
+
* Backwards-compatible: the 5th `options` parameter is optional. Passing
|
|
191
|
+
* `{ locale, speakable, extendArticle }` removes the need for app code to
|
|
192
|
+
* post-mutate the graph for locale-aware URLs and rich SEO fields.
|
|
140
193
|
*/
|
|
141
|
-
export function generateBlogPostSchemaGraph(post, config, breadcrumbs, includeBreadcrumbs = true) {
|
|
194
|
+
export function generateBlogPostSchemaGraph(post, config, breadcrumbs, includeBreadcrumbs = true, options) {
|
|
142
195
|
const orgNode = buildOrganizationGraphNode(config);
|
|
143
|
-
const article = generateBlogPostSchema(post, config, {
|
|
196
|
+
const article = generateBlogPostSchema(post, config, {
|
|
197
|
+
publisherReference: true,
|
|
198
|
+
...(options?.locale !== undefined && { locale: options.locale }),
|
|
199
|
+
...(options?.speakable !== undefined && { speakable: options.speakable }),
|
|
200
|
+
...(options?.extendArticle !== undefined && { extendArticle: options.extendArticle }),
|
|
201
|
+
});
|
|
144
202
|
const articleBody = { ...article };
|
|
145
203
|
delete articleBody['@context'];
|
|
146
204
|
const graph = [];
|
|
@@ -148,7 +206,7 @@ export function generateBlogPostSchemaGraph(post, config, breadcrumbs, includeBr
|
|
|
148
206
|
graph.push(orgNode);
|
|
149
207
|
graph.push(articleBody);
|
|
150
208
|
if (includeBreadcrumbs) {
|
|
151
|
-
const crumbs = generateBreadcrumbsSchema(post, config, breadcrumbs);
|
|
209
|
+
const crumbs = generateBreadcrumbsSchema(post, config, breadcrumbs, options?.locale);
|
|
152
210
|
const crumbsBody = { ...crumbs };
|
|
153
211
|
delete crumbsBody['@context'];
|
|
154
212
|
graph.push(crumbsBody);
|
package/dist/core/seo-utils.d.ts
CHANGED
|
@@ -55,12 +55,14 @@ export declare function resolveCanonicalUrl(url: string, siteUrl: string): strin
|
|
|
55
55
|
export declare function getBlogPostPathSegment(config?: Config): string;
|
|
56
56
|
/**
|
|
57
57
|
* Resolves a post URL from canonical URL or generates default `/{segment}/{slug}` under siteUrl.
|
|
58
|
+
* When `locale` is provided, builds `/{locale}/{segment}/{slug}` (skipped when a canonicalUrl is set).
|
|
58
59
|
*/
|
|
59
|
-
export declare function resolvePostUrl(canonicalUrl: string | undefined, slug: string, siteUrl: string, blogPostPathSegment?: string): string;
|
|
60
|
+
export declare function resolvePostUrl(canonicalUrl: string | undefined, slug: string, siteUrl: string, blogPostPathSegment?: string, locale?: string): string;
|
|
60
61
|
/**
|
|
61
62
|
* Resolves post URL using optional blog config for path segment.
|
|
63
|
+
* Pass `locale` to prefix the URL with the locale segment.
|
|
62
64
|
*/
|
|
63
|
-
export declare function resolvePostUrlWithConfig(canonicalUrl: string | undefined, slug: string, siteUrl: string, config?: Config): string;
|
|
65
|
+
export declare function resolvePostUrlWithConfig(canonicalUrl: string | undefined, slug: string, siteUrl: string, config?: Config, locale?: string): string;
|
|
64
66
|
/**
|
|
65
67
|
* Default blog listing URL for breadcrumbs (config.blogIndexPath or `{siteUrl}/blogs`).
|
|
66
68
|
*/
|
|
@@ -1 +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,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGhF;;;;;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;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED
|
|
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,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGhF;;;;;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;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,mBAAmB,SAAS,EAC5B,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAOR;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAQR;AAED;;GAEG;AACH;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,mBAAmB,EACvB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAkBpC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAe5E;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOhD"}
|
package/dist/core/seo-utils.js
CHANGED
|
@@ -120,18 +120,22 @@ export function getBlogPostPathSegment(config) {
|
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
122
|
* Resolves a post URL from canonical URL or generates default `/{segment}/{slug}` under siteUrl.
|
|
123
|
+
* When `locale` is provided, builds `/{locale}/{segment}/{slug}` (skipped when a canonicalUrl is set).
|
|
123
124
|
*/
|
|
124
|
-
export function resolvePostUrl(canonicalUrl, slug, siteUrl, blogPostPathSegment = 'blog') {
|
|
125
|
+
export function resolvePostUrl(canonicalUrl, slug, siteUrl, blogPostPathSegment = 'blog', locale) {
|
|
125
126
|
const seg = blogPostPathSegment.replace(/^\/+|\/+$/g, '') || 'blog';
|
|
126
127
|
const base = siteUrl.replace(/\/$/, '');
|
|
127
|
-
const
|
|
128
|
+
const localePrefix = locale ? `/${locale.replace(/^\/+|\/+$/g, '')}` : '';
|
|
129
|
+
const path = `${localePrefix}/${seg}/${slug}`;
|
|
130
|
+
const urlRaw = canonicalUrl || (base ? `${base}${path}` : path);
|
|
128
131
|
return siteUrl ? resolveCanonicalUrl(urlRaw, siteUrl) : urlRaw;
|
|
129
132
|
}
|
|
130
133
|
/**
|
|
131
134
|
* Resolves post URL using optional blog config for path segment.
|
|
135
|
+
* Pass `locale` to prefix the URL with the locale segment.
|
|
132
136
|
*/
|
|
133
|
-
export function resolvePostUrlWithConfig(canonicalUrl, slug, siteUrl, config) {
|
|
134
|
-
return resolvePostUrl(canonicalUrl, slug, siteUrl, getBlogPostPathSegment(config));
|
|
137
|
+
export function resolvePostUrlWithConfig(canonicalUrl, slug, siteUrl, config, locale) {
|
|
138
|
+
return resolvePostUrl(canonicalUrl, slug, siteUrl, getBlogPostPathSegment(config), locale);
|
|
135
139
|
}
|
|
136
140
|
/**
|
|
137
141
|
* Default blog listing URL for breadcrumbs (config.blogIndexPath or `{siteUrl}/blogs`).
|
package/dist/core/seo.d.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* RSS feeds, and sitemap entry builders for blog posts.
|
|
6
6
|
*/
|
|
7
7
|
export { generateBlogPostMetadata, generateBlogListMetadata, } from './seo-metadata.js';
|
|
8
|
+
export type { GenerateBlogPostMetadataOptions } from './seo-metadata.js';
|
|
8
9
|
export { generateBlogPostSchema, generateBreadcrumbsSchema, generateBlogPostSchemaGraph, } from './seo-schema.js';
|
|
9
|
-
export type { BlogPostSchemaOptions } from './seo-schema.js';
|
|
10
|
+
export type { BlogPostSchemaOptions, BlogPostSchemaGraphOptions, } from './seo-schema.js';
|
|
10
11
|
export { generateRSSFeed } from './seo-feeds.js';
|
|
11
12
|
export { generateOrganizationSchema } from './organization-schema.js';
|
|
12
13
|
export { getBlogSitemapEntries } from './sitemap-data.js';
|
package/dist/core/seo.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;AAC3B,YAAY,EAAE,+BAA+B,EAAE,MAAM,mBAAmB,CAAC;AAEzE,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAEtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,EAClB,mBAAmB,EACnB,SAAS,GACV,MAAM,gBAAgB,CAAC"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postal address (Schema.org PostalAddress).
|
|
3
|
+
*/
|
|
4
|
+
export interface PostalAddress {
|
|
5
|
+
streetAddress?: string;
|
|
6
|
+
addressLocality?: string;
|
|
7
|
+
addressRegion?: string;
|
|
8
|
+
postalCode?: string;
|
|
9
|
+
addressCountry?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Contact point (Schema.org ContactPoint). Used for Organization JSON-LD.
|
|
13
|
+
*/
|
|
14
|
+
export interface ContactPoint {
|
|
15
|
+
email?: string;
|
|
16
|
+
telephone?: string;
|
|
17
|
+
/** e.g. "customer support", "sales", "press". */
|
|
18
|
+
contactType?: string;
|
|
19
|
+
/** ISO country codes (e.g. ["US", "FR"]) or region names. */
|
|
20
|
+
areaServed?: string | string[];
|
|
21
|
+
/** BCP-47 codes (e.g. ["en", "fr"]). */
|
|
22
|
+
availableLanguage?: string | string[];
|
|
23
|
+
}
|
|
1
24
|
/**
|
|
2
25
|
* Publisher / Organization fields for JSON-LD (extends siteName + siteUrl).
|
|
3
26
|
*/
|
|
@@ -10,6 +33,16 @@ export interface SiteOrganization {
|
|
|
10
33
|
logo?: string;
|
|
11
34
|
/** Official profiles (Schema.org sameAs) */
|
|
12
35
|
sameAs?: string[];
|
|
36
|
+
/** ISO date string. */
|
|
37
|
+
foundingDate?: string;
|
|
38
|
+
/** Founder name (rendered as Person). */
|
|
39
|
+
founder?: string;
|
|
40
|
+
/** PostalAddress fields. */
|
|
41
|
+
address?: PostalAddress;
|
|
42
|
+
/** ContactPoint fields. */
|
|
43
|
+
contactPoint?: ContactPoint;
|
|
44
|
+
/** Wikidata entity URL (e.g. https://www.wikidata.org/wiki/Q1234567). Auto-merged into sameAs. */
|
|
45
|
+
wikidata?: string;
|
|
13
46
|
}
|
|
14
47
|
/**
|
|
15
48
|
* Blog configuration
|
|
@@ -85,6 +118,20 @@ export interface BlogPostFrontmatter {
|
|
|
85
118
|
readingTime?: number;
|
|
86
119
|
/** Per-post hreflang map (overrides config.alternateLanguages for this post) */
|
|
87
120
|
alternateLanguages?: Record<string, string>;
|
|
121
|
+
/** Last-updated date. Drives `dateModified` in JSON-LD and `lastmod` in sitemaps. */
|
|
122
|
+
updated?: string;
|
|
123
|
+
/** Topic-cluster identifier. Posts sharing the same series belong to the same pillar. */
|
|
124
|
+
series?: string;
|
|
125
|
+
/** Display name for the series pillar page (defaults to humanized series slug). */
|
|
126
|
+
seriesTitle?: string;
|
|
127
|
+
/** Sort order within a series (lower first). Falls back to date when absent. */
|
|
128
|
+
seriesOrder?: number;
|
|
129
|
+
/** E-E-A-T: editor / reviewer who validated the post. */
|
|
130
|
+
reviewedBy?: string;
|
|
131
|
+
/** E-E-A-T: fact-checker. */
|
|
132
|
+
factCheckedBy?: string;
|
|
133
|
+
/** E-E-A-T: date of last editorial review (yyyy-mm-dd). */
|
|
134
|
+
lastReviewed?: string;
|
|
88
135
|
[key: string]: unknown;
|
|
89
136
|
}
|
|
90
137
|
/**
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,11 +6,14 @@ export type { OgImageProps } from './components/OgImage.js';
|
|
|
6
6
|
export { BlogPostSEO } from './components/BlogPostSEO.js';
|
|
7
7
|
export type { BlogPostSEOProps } from './components/BlogPostSEO.js';
|
|
8
8
|
export { getBlogPost, getAllBlogPosts, getAllBlogPostSlugs } from './core/file-utils.js';
|
|
9
|
+
export { getPostInAllLocales, getPostsByAuthor, getPostsBySeries, getAllAuthorSlugs, getAllSeriesSlugs, slugifyAuthor, slugifySeries, authorNamesFromFrontmatter, } from './core/queries.js';
|
|
10
|
+
export { generateLlmsTxt, generateLlmsFullTxt } from './core/llms.js';
|
|
11
|
+
export type { LlmsTxtOptions } from './core/llms.js';
|
|
9
12
|
export { generateBlogPostMetadata, generateBlogListMetadata, generateBlogPostSchema, generateBreadcrumbsSchema, generateBlogPostSchemaGraph, generateRSSFeed, generateOrganizationSchema, getBlogSitemapEntries, } from './core/seo.js';
|
|
10
|
-
export type { BlogPostSchemaOptions, BlogSitemapEntry } from './core/seo.js';
|
|
13
|
+
export type { BlogPostSchemaOptions, BlogPostSchemaGraphOptions, BlogSitemapEntry, GenerateBlogPostMetadataOptions, } from './core/seo.js';
|
|
11
14
|
export { getBlogSitemap, getBlogRobots, createRssFeedResponse, } from './next.js';
|
|
12
15
|
export { createConfig, loadConfig, getConfig } from './core/config.js';
|
|
13
|
-
export type { BlogPost, BlogPostMetadata, BlogPostFrontmatter, GetBlogPostOptions, Author, Config, SiteOrganization, } from './core/types.js';
|
|
16
|
+
export type { BlogPost, BlogPostMetadata, BlogPostFrontmatter, GetBlogPostOptions, Author, Config, SiteOrganization, PostalAddress, ContactPoint, } from './core/types.js';
|
|
14
17
|
export { MdxBlogError, BlogPostNotFoundError, FileReadError, DirectoryError, } from './core/errors.js';
|
|
15
18
|
export { POSTS_DIR_NAME, getPostsDirectory } from './core/constants.js';
|
|
16
19
|
export { calculateReadingTime, calculateWordCount, normalizeAuthors } from './core/utils.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAChG,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGzF,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,yBAAyB,EACzB,2BAA2B,EAC3B,eAAe,EACf,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAChG,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGzF,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,0BAA0B,GAC3B,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,yBAAyB,EACzB,2BAA2B,EAC3B,eAAe,EACf,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,qBAAqB,EACrB,0BAA0B,EAC1B,gBAAgB,EAChB,+BAA+B,GAChC,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,cAAc,EACd,aAAa,EACb,qBAAqB,GACtB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGvE,YAAY,EACV,QAAQ,EACR,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,MAAM,EACN,MAAM,EACN,gBAAgB,EAChB,aAAa,EACb,YAAY,GACb,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,aAAa,EACb,cAAc,GACf,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,10 @@ export { OgImage } from './components/OgImage.js';
|
|
|
5
5
|
export { BlogPostSEO } from './components/BlogPostSEO.js';
|
|
6
6
|
// Utilities
|
|
7
7
|
export { getBlogPost, getAllBlogPosts, getAllBlogPostSlugs } from './core/file-utils.js';
|
|
8
|
+
// Query helpers (locale-aware, series, authors)
|
|
9
|
+
export { getPostInAllLocales, getPostsByAuthor, getPostsBySeries, getAllAuthorSlugs, getAllSeriesSlugs, slugifyAuthor, slugifySeries, authorNamesFromFrontmatter, } from './core/queries.js';
|
|
10
|
+
// llms.txt generators
|
|
11
|
+
export { generateLlmsTxt, generateLlmsFullTxt } from './core/llms.js';
|
|
8
12
|
// SEO
|
|
9
13
|
export { generateBlogPostMetadata, generateBlogListMetadata, generateBlogPostSchema, generateBreadcrumbsSchema, generateBlogPostSchemaGraph, generateRSSFeed, generateOrganizationSchema, getBlogSitemapEntries, } from './core/seo.js';
|
|
10
14
|
export { getBlogSitemap, getBlogRobots, createRssFeedResponse, } from './next.js';
|