@actuate-media/cms-core 0.65.0 → 0.67.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/CHANGELOG.md +143 -0
- package/dist/__tests__/api/ai-quality.test.js +73 -0
- package/dist/__tests__/api/ai-quality.test.js.map +1 -1
- package/dist/__tests__/api/llms-txt.test.js +59 -0
- package/dist/__tests__/api/llms-txt.test.js.map +1 -1
- package/dist/__tests__/api/public-seo.test.js +402 -4
- package/dist/__tests__/api/public-seo.test.js.map +1 -1
- package/dist/__tests__/api/route-contracts.test.js +7 -0
- package/dist/__tests__/api/route-contracts.test.js.map +1 -1
- package/dist/__tests__/content/slug.test.d.ts +2 -0
- package/dist/__tests__/content/slug.test.d.ts.map +1 -0
- package/dist/__tests__/content/slug.test.js +68 -0
- package/dist/__tests__/content/slug.test.js.map +1 -0
- package/dist/__tests__/middleware.test.js +47 -0
- package/dist/__tests__/middleware.test.js.map +1 -1
- package/dist/__tests__/redirects/auto-301.test.d.ts +2 -0
- package/dist/__tests__/redirects/auto-301.test.d.ts.map +1 -0
- package/dist/__tests__/redirects/auto-301.test.js +172 -0
- package/dist/__tests__/redirects/auto-301.test.js.map +1 -0
- package/dist/__tests__/seo/ai-bots.test.d.ts +2 -0
- package/dist/__tests__/seo/ai-bots.test.d.ts.map +1 -0
- package/dist/__tests__/seo/ai-bots.test.js +53 -0
- package/dist/__tests__/seo/ai-bots.test.js.map +1 -0
- package/dist/__tests__/seo/alt-text.test.d.ts +2 -0
- package/dist/__tests__/seo/alt-text.test.d.ts.map +1 -0
- package/dist/__tests__/seo/alt-text.test.js +47 -0
- package/dist/__tests__/seo/alt-text.test.js.map +1 -0
- package/dist/__tests__/seo/feeds.test.d.ts +2 -0
- package/dist/__tests__/seo/feeds.test.d.ts.map +1 -0
- package/dist/__tests__/seo/feeds.test.js +61 -0
- package/dist/__tests__/seo/feeds.test.js.map +1 -0
- package/dist/__tests__/seo/freshness.test.d.ts +2 -0
- package/dist/__tests__/seo/freshness.test.d.ts.map +1 -0
- package/dist/__tests__/seo/freshness.test.js +89 -0
- package/dist/__tests__/seo/freshness.test.js.map +1 -0
- package/dist/__tests__/seo/meta-tags-alternates.test.d.ts +2 -0
- package/dist/__tests__/seo/meta-tags-alternates.test.d.ts.map +1 -0
- package/dist/__tests__/seo/meta-tags-alternates.test.js +52 -0
- package/dist/__tests__/seo/meta-tags-alternates.test.js.map +1 -0
- package/dist/__tests__/seo/page-meta.test.js +230 -14
- package/dist/__tests__/seo/page-meta.test.js.map +1 -1
- package/dist/__tests__/seo/sitemap-notify.test.js +77 -1
- package/dist/__tests__/seo/sitemap-notify.test.js.map +1 -1
- package/dist/__tests__/validation/publish.test.d.ts +2 -0
- package/dist/__tests__/validation/publish.test.d.ts.map +1 -0
- package/dist/__tests__/validation/publish.test.js +164 -0
- package/dist/__tests__/validation/publish.test.js.map +1 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +172 -8
- package/dist/actions.js.map +1 -1
- package/dist/api/route-helpers.d.ts +8 -0
- package/dist/api/route-helpers.d.ts.map +1 -1
- package/dist/api/route-helpers.js +20 -0
- package/dist/api/route-helpers.js.map +1 -1
- package/dist/api/routes/ai-seo.d.ts.map +1 -1
- package/dist/api/routes/ai-seo.js +32 -1
- package/dist/api/routes/ai-seo.js.map +1 -1
- package/dist/api/routes/auth.d.ts.map +1 -1
- package/dist/api/routes/auth.js +2 -1
- package/dist/api/routes/auth.js.map +1 -1
- package/dist/api/routes/documents.d.ts.map +1 -1
- package/dist/api/routes/documents.js +3 -3
- package/dist/api/routes/documents.js.map +1 -1
- package/dist/api/routes/seo-public.d.ts.map +1 -1
- package/dist/api/routes/seo-public.js +371 -62
- package/dist/api/routes/seo-public.js.map +1 -1
- package/dist/api/routes/seo.d.ts.map +1 -1
- package/dist/api/routes/seo.js +21 -10
- package/dist/api/routes/seo.js.map +1 -1
- package/dist/api/routes/url-resolution.d.ts.map +1 -1
- package/dist/api/routes/url-resolution.js +40 -1
- package/dist/api/routes/url-resolution.js.map +1 -1
- package/dist/api/routes/users.d.ts.map +1 -1
- package/dist/api/routes/users.js +6 -3
- package/dist/api/routes/users.js.map +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -0
- package/dist/client.js.map +1 -1
- package/dist/config/types.d.ts +35 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/content/index.d.ts +1 -0
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +1 -0
- package/dist/content/index.js.map +1 -1
- package/dist/content/slug.d.ts +40 -0
- package/dist/content/slug.d.ts.map +1 -0
- package/dist/content/slug.js +78 -0
- package/dist/content/slug.js.map +1 -0
- package/dist/content/structured-data.d.ts +6 -0
- package/dist/content/structured-data.d.ts.map +1 -1
- package/dist/content/structured-data.js +6 -0
- package/dist/content/structured-data.js.map +1 -1
- package/dist/content-health/scanner.d.ts +4 -3
- package/dist/content-health/scanner.d.ts.map +1 -1
- package/dist/content-health/scanner.js +6 -12
- package/dist/content-health/scanner.js.map +1 -1
- package/dist/middleware.d.ts +1 -0
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +11 -2
- package/dist/middleware.js.map +1 -1
- package/dist/redirects/auto.d.ts +45 -0
- package/dist/redirects/auto.d.ts.map +1 -0
- package/dist/redirects/auto.js +55 -0
- package/dist/redirects/auto.js.map +1 -0
- package/dist/redirects/index.d.ts +2 -0
- package/dist/redirects/index.d.ts.map +1 -1
- package/dist/redirects/index.js +1 -0
- package/dist/redirects/index.js.map +1 -1
- package/dist/seo/ai-bots.d.ts +42 -0
- package/dist/seo/ai-bots.d.ts.map +1 -0
- package/dist/seo/ai-bots.js +104 -0
- package/dist/seo/ai-bots.js.map +1 -0
- package/dist/seo/alt-text.d.ts +39 -0
- package/dist/seo/alt-text.d.ts.map +1 -0
- package/dist/seo/alt-text.js +67 -0
- package/dist/seo/alt-text.js.map +1 -0
- package/dist/seo/analysis.d.ts.map +1 -1
- package/dist/seo/analysis.js +7 -11
- package/dist/seo/analysis.js.map +1 -1
- package/dist/seo/audit-engine.d.ts +1 -0
- package/dist/seo/audit-engine.d.ts.map +1 -1
- package/dist/seo/audit-engine.js +4 -4
- package/dist/seo/audit-engine.js.map +1 -1
- package/dist/seo/audit-runner.d.ts.map +1 -1
- package/dist/seo/audit-runner.js +1 -0
- package/dist/seo/audit-runner.js.map +1 -1
- package/dist/seo/feeds.d.ts +36 -0
- package/dist/seo/feeds.d.ts.map +1 -0
- package/dist/seo/feeds.js +71 -0
- package/dist/seo/feeds.js.map +1 -0
- package/dist/seo/index.d.ts +9 -3
- package/dist/seo/index.d.ts.map +1 -1
- package/dist/seo/index.js +5 -2
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/llms-txt.d.ts.map +1 -1
- package/dist/seo/llms-txt.js +13 -2
- package/dist/seo/llms-txt.js.map +1 -1
- package/dist/seo/meta-tags.d.ts +14 -0
- package/dist/seo/meta-tags.d.ts.map +1 -1
- package/dist/seo/meta-tags.js +21 -2
- package/dist/seo/meta-tags.js.map +1 -1
- package/dist/seo/page-meta.d.ts +47 -0
- package/dist/seo/page-meta.d.ts.map +1 -1
- package/dist/seo/page-meta.js +280 -70
- package/dist/seo/page-meta.js.map +1 -1
- package/dist/seo/sitemap-notify.d.ts +30 -0
- package/dist/seo/sitemap-notify.d.ts.map +1 -1
- package/dist/seo/sitemap-notify.js +87 -0
- package/dist/seo/sitemap-notify.js.map +1 -1
- package/dist/validation/publish.d.ts +53 -0
- package/dist/validation/publish.d.ts.map +1 -0
- package/dist/validation/publish.js +75 -0
- package/dist/validation/publish.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-tags.d.ts","sourceRoot":"","sources":["../../src/seo/meta-tags.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;IACxC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,SAAS,GAAG,qBAAqB,CAAA;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"meta-tags.d.ts","sourceRoot":"","sources":["../../src/seo/meta-tags.ts"],"names":[],"mappings":"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"}
|
package/dist/seo/meta-tags.js
CHANGED
|
@@ -74,6 +74,9 @@ export function renderMetaTagsHtml(input) {
|
|
|
74
74
|
if (input.canonical) {
|
|
75
75
|
lines.push(`<link rel="canonical" href="${escapeHtml(input.canonical)}" />`);
|
|
76
76
|
}
|
|
77
|
+
for (const alt of input.alternates ?? []) {
|
|
78
|
+
lines.push(`<link rel="alternate" hreflang="${escapeHtml(alt.hreflang)}" href="${escapeHtml(alt.href)}" />`);
|
|
79
|
+
}
|
|
77
80
|
for (const [key, value] of Object.entries(tags)) {
|
|
78
81
|
const escaped = escapeHtml(value);
|
|
79
82
|
if (isPropertyTag(key)) {
|
|
@@ -182,8 +185,24 @@ export function generateNextMetadata(input) {
|
|
|
182
185
|
};
|
|
183
186
|
if (input.description)
|
|
184
187
|
metadata.description = input.description;
|
|
185
|
-
if (input.
|
|
186
|
-
|
|
188
|
+
if (input.metadataBase) {
|
|
189
|
+
try {
|
|
190
|
+
metadata.metadataBase = new URL(input.metadataBase);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Ignore an unparseable base — Next.js falls back to the deploy origin.
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const languages = {};
|
|
197
|
+
for (const alt of input.alternates ?? []) {
|
|
198
|
+
if (alt.hreflang && alt.href)
|
|
199
|
+
languages[alt.hreflang] = alt.href;
|
|
200
|
+
}
|
|
201
|
+
if (input.canonical || Object.keys(languages).length > 0) {
|
|
202
|
+
metadata.alternates = {
|
|
203
|
+
...(input.canonical ? { canonical: input.canonical } : {}),
|
|
204
|
+
...(Object.keys(languages).length > 0 ? { languages } : {}),
|
|
205
|
+
};
|
|
187
206
|
}
|
|
188
207
|
metadata.robots = {
|
|
189
208
|
index: !input.noIndex,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-tags.js","sourceRoot":"","sources":["../../src/seo/meta-tags.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"meta-tags.js","sourceRoot":"","sources":["../../src/seo/meta-tags.ts"],"names":[],"mappings":"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"}
|
package/dist/seo/page-meta.d.ts
CHANGED
|
@@ -54,6 +54,16 @@ export interface ComposePageMetaInput {
|
|
|
54
54
|
* when `seo.robots.noIndexNonProduction` is set.
|
|
55
55
|
*/
|
|
56
56
|
environment?: string;
|
|
57
|
+
/**
|
|
58
|
+
* hreflang alternates for this document's translations, emitted in `<head>`
|
|
59
|
+
* (and `alternates.languages` in the Next metadata). The caller (e.g.
|
|
60
|
+
* `/resolve`) supplies these since they require knowing sibling documents.
|
|
61
|
+
* Include an `x-default` entry where appropriate.
|
|
62
|
+
*/
|
|
63
|
+
alternates?: Array<{
|
|
64
|
+
hreflang: string;
|
|
65
|
+
href: string;
|
|
66
|
+
}>;
|
|
57
67
|
}
|
|
58
68
|
export interface ComposedPageMeta {
|
|
59
69
|
/** Canonical URL for the document. */
|
|
@@ -76,6 +86,43 @@ export interface ComposedPageMeta {
|
|
|
76
86
|
metadata: object;
|
|
77
87
|
}
|
|
78
88
|
declare function detectSchemaType(collectionSlug: string): string;
|
|
89
|
+
/**
|
|
90
|
+
* Resolve the canonical public URL for a document. This is the SINGLE source of
|
|
91
|
+
* truth shared by the public meta composer (`composePageMeta`, which renders the
|
|
92
|
+
* `<link rel="canonical">` / `og:url`) and the XML sitemap, so the two can never
|
|
93
|
+
* disagree and send conflicting signals to crawlers.
|
|
94
|
+
*
|
|
95
|
+
* Precedence mirrors the rest of the platform (`pages-service`,
|
|
96
|
+
* `page-editor-service`, `/resolve`):
|
|
97
|
+
* 1. An explicit `data.path` (the full URL path an editor set, e.g. `/` for
|
|
98
|
+
* the homepage or `/about/story`) always wins.
|
|
99
|
+
* 2. A `home`/`index`/empty slug maps to the site root.
|
|
100
|
+
* 3. Otherwise `<urlPrefix>/<slug>`.
|
|
101
|
+
*/
|
|
102
|
+
export declare function resolveDocumentUrl(params: {
|
|
103
|
+
siteUrl: string;
|
|
104
|
+
urlPrefix?: string | null;
|
|
105
|
+
slug?: string | null;
|
|
106
|
+
data?: Record<string, unknown> | null;
|
|
107
|
+
}): string;
|
|
108
|
+
/**
|
|
109
|
+
* Resolve the canonical freshness dates for a document so every public surface
|
|
110
|
+
* (JSON-LD, Open Graph, etc.) reports the same recency. `datePublished` prefers
|
|
111
|
+
* the DB `publishedAt` (set on first publish) but falls back to editorial date
|
|
112
|
+
* fields authors set in the body (`publishedAt`/`publishedDate`/`publishDate`/
|
|
113
|
+
* `date`). `dateModified` prefers `updatedAt` and never predates publication.
|
|
114
|
+
*/
|
|
115
|
+
export declare function resolveContentDates(params: {
|
|
116
|
+
doc: {
|
|
117
|
+
publishedAt?: unknown;
|
|
118
|
+
updatedAt?: unknown;
|
|
119
|
+
};
|
|
120
|
+
data?: Record<string, unknown> | null;
|
|
121
|
+
page?: Record<string, unknown> | null;
|
|
122
|
+
}): {
|
|
123
|
+
datePublished?: string;
|
|
124
|
+
dateModified?: string;
|
|
125
|
+
};
|
|
79
126
|
/**
|
|
80
127
|
* Compose page meta + JSON-LD from a CMS document + collection config.
|
|
81
128
|
* Pure function — safe to call in any runtime (Edge, Node, browser).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-meta.d.ts","sourceRoot":"","sources":["../../src/seo/page-meta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAiB,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"page-meta.d.ts","sourceRoot":"","sources":["../../src/seo/page-meta.ts"],"names":[],"mappings":"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"}
|
package/dist/seo/page-meta.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildSchemaGraph } from '../content/structured-data.js';
|
|
1
2
|
import { buildNextVerification, buildVerificationMetaEntries, generateMetaTags, generateNextMetadata, renderMetaTagsHtml, renderVerificationMetaHtml, } from './meta-tags.js';
|
|
2
3
|
import { resolveRobotsDirectives, resolveSeoEnvironment } from './robots.js';
|
|
3
4
|
import { applySiteTitleTemplate } from './title-templates.js';
|
|
@@ -54,14 +55,32 @@ function detectSchemaType(collectionSlug) {
|
|
|
54
55
|
function trimTrailingSlash(s) {
|
|
55
56
|
return s.replace(/\/+$/, '');
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the canonical public URL for a document. This is the SINGLE source of
|
|
60
|
+
* truth shared by the public meta composer (`composePageMeta`, which renders the
|
|
61
|
+
* `<link rel="canonical">` / `og:url`) and the XML sitemap, so the two can never
|
|
62
|
+
* disagree and send conflicting signals to crawlers.
|
|
63
|
+
*
|
|
64
|
+
* Precedence mirrors the rest of the platform (`pages-service`,
|
|
65
|
+
* `page-editor-service`, `/resolve`):
|
|
66
|
+
* 1. An explicit `data.path` (the full URL path an editor set, e.g. `/` for
|
|
67
|
+
* the homepage or `/about/story`) always wins.
|
|
68
|
+
* 2. A `home`/`index`/empty slug maps to the site root.
|
|
69
|
+
* 3. Otherwise `<urlPrefix>/<slug>`.
|
|
70
|
+
*/
|
|
71
|
+
export function resolveDocumentUrl(params) {
|
|
72
|
+
const base = trimTrailingSlash(params.siteUrl);
|
|
73
|
+
const data = params.data ?? {};
|
|
74
|
+
const explicitPath = typeof data.path === 'string' ? data.path.trim() : '';
|
|
75
|
+
if (explicitPath) {
|
|
76
|
+
const normalized = `/${explicitPath.replace(/^\/+|\/+$/g, '')}`;
|
|
77
|
+
return normalized === '/' ? base || '/' : `${base}${normalized}`;
|
|
78
|
+
}
|
|
79
|
+
const slug = (params.slug ?? data.slug ?? '') || '';
|
|
80
|
+
if (slug === 'home' || slug === 'index' || slug === '')
|
|
60
81
|
return base || '/';
|
|
61
|
-
const prefix = (urlPrefix ?? '').replace(/^\/|\/$/g, '');
|
|
62
|
-
|
|
63
|
-
return `${base}/${prefix}/${slug}`;
|
|
64
|
-
return `${base}/${slug}`;
|
|
82
|
+
const prefix = (params.urlPrefix ?? '').replace(/^\/|\/$/g, '');
|
|
83
|
+
return prefix ? `${base}/${prefix}/${slug}` : `${base}/${slug}`;
|
|
65
84
|
}
|
|
66
85
|
function pickString(...candidates) {
|
|
67
86
|
for (const c of candidates) {
|
|
@@ -70,6 +89,38 @@ function pickString(...candidates) {
|
|
|
70
89
|
}
|
|
71
90
|
return undefined;
|
|
72
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Return the first candidate that is a real boolean (coercing the common
|
|
94
|
+
* string forms `"true"`/`"false"` that some field editors persist). Used to
|
|
95
|
+
* unify the several robots field-name spellings without letting an explicit
|
|
96
|
+
* `false` be skipped the way `??` over mixed types would.
|
|
97
|
+
*/
|
|
98
|
+
function firstDefinedBool(...candidates) {
|
|
99
|
+
for (const c of candidates) {
|
|
100
|
+
if (typeof c === 'boolean')
|
|
101
|
+
return c;
|
|
102
|
+
if (c === 'true')
|
|
103
|
+
return true;
|
|
104
|
+
if (c === 'false')
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Coerce a media-ish value to a URL string. Accepts a bare URL string or a
|
|
111
|
+
* media object (`{ url }` / `{ src }`); returns undefined otherwise.
|
|
112
|
+
*/
|
|
113
|
+
function asImageUrl(value) {
|
|
114
|
+
if (typeof value === 'string' && value.trim().length > 0)
|
|
115
|
+
return value;
|
|
116
|
+
if (value && typeof value === 'object') {
|
|
117
|
+
const obj = value;
|
|
118
|
+
const url = obj.url ?? obj.src;
|
|
119
|
+
if (typeof url === 'string' && url.trim().length > 0)
|
|
120
|
+
return url;
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
73
124
|
function asIsoDate(v) {
|
|
74
125
|
if (!v)
|
|
75
126
|
return undefined;
|
|
@@ -82,76 +133,212 @@ function asIsoDate(v) {
|
|
|
82
133
|
return undefined;
|
|
83
134
|
}
|
|
84
135
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
136
|
+
* Resolve the canonical freshness dates for a document so every public surface
|
|
137
|
+
* (JSON-LD, Open Graph, etc.) reports the same recency. `datePublished` prefers
|
|
138
|
+
* the DB `publishedAt` (set on first publish) but falls back to editorial date
|
|
139
|
+
* fields authors set in the body (`publishedAt`/`publishedDate`/`publishDate`/
|
|
140
|
+
* `date`). `dateModified` prefers `updatedAt` and never predates publication.
|
|
141
|
+
*/
|
|
142
|
+
export function resolveContentDates(params) {
|
|
143
|
+
const data = params.data ?? {};
|
|
144
|
+
const page = params.page ?? {};
|
|
145
|
+
const datePublished = asIsoDate(params.doc.publishedAt ??
|
|
146
|
+
data.publishedAt ??
|
|
147
|
+
page.publishedAt ??
|
|
148
|
+
data.publishedDate ??
|
|
149
|
+
data.publishDate ??
|
|
150
|
+
data.date);
|
|
151
|
+
const dateModified = asIsoDate(params.doc.updatedAt ?? data.updatedAt ?? page.updatedAt) ?? datePublished;
|
|
152
|
+
return {
|
|
153
|
+
...(datePublished ? { datePublished } : {}),
|
|
154
|
+
...(dateModified ? { dateModified } : {}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** Schema.org types that the rich graph foundation already models as nodes. */
|
|
158
|
+
const FOUNDATION_TYPES = new Set(['WebPage', 'Article', 'BlogPosting', 'NewsArticle', 'FAQPage']);
|
|
159
|
+
/** Map a Schema.org type to the page-input content type the graph builder uses. */
|
|
160
|
+
function schemaTypeToPageType(type) {
|
|
161
|
+
switch (type) {
|
|
162
|
+
case 'BlogPosting':
|
|
163
|
+
return 'blog';
|
|
164
|
+
case 'Article':
|
|
165
|
+
case 'NewsArticle':
|
|
166
|
+
return 'article';
|
|
167
|
+
case 'FAQPage':
|
|
168
|
+
return 'faq';
|
|
169
|
+
default:
|
|
170
|
+
return 'page';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Title-case a URL slug segment for a readable breadcrumb name. */
|
|
174
|
+
function humanizeSegment(segment) {
|
|
175
|
+
const decoded = decodeURIComponent(segment).replace(/[-_]+/g, ' ').trim();
|
|
176
|
+
return decoded.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Derive a BreadcrumbList trail from the page URL: Home → each path segment →
|
|
180
|
+
* the current page (named with its real title). Returns an empty array for the
|
|
181
|
+
* homepage so we never emit a single-item or self-referential breadcrumb.
|
|
182
|
+
*/
|
|
183
|
+
function deriveBreadcrumbs(siteUrl, url, title) {
|
|
184
|
+
const base = trimTrailingSlash(siteUrl);
|
|
185
|
+
if (!url || url === base || url === `${base}/`)
|
|
186
|
+
return [];
|
|
187
|
+
const path = base && url.startsWith(base) ? url.slice(base.length) : url;
|
|
188
|
+
const segments = path
|
|
189
|
+
.replace(/^\/+|\/+$/g, '')
|
|
190
|
+
.split('/')
|
|
191
|
+
.filter(Boolean);
|
|
192
|
+
if (segments.length === 0)
|
|
193
|
+
return [];
|
|
194
|
+
const crumbs = [{ name: 'Home', url: base || '/' }];
|
|
195
|
+
let acc = base;
|
|
196
|
+
segments.forEach((seg, i) => {
|
|
197
|
+
acc = `${acc}/${seg}`;
|
|
198
|
+
const isLast = i === segments.length - 1;
|
|
199
|
+
crumbs.push({ name: isLast ? title : humanizeSegment(seg), url: acc });
|
|
200
|
+
});
|
|
201
|
+
return crumbs;
|
|
202
|
+
}
|
|
203
|
+
/** Extract FAQ pairs from common document shapes (faqItems / faqs / questions). */
|
|
204
|
+
function extractFaqItems(data, page) {
|
|
205
|
+
const raw = data.faqItems ?? data.faqs ?? data.questions ?? page.faqItems;
|
|
206
|
+
if (!Array.isArray(raw))
|
|
207
|
+
return [];
|
|
208
|
+
const items = [];
|
|
209
|
+
for (const entry of raw) {
|
|
210
|
+
if (!entry || typeof entry !== 'object')
|
|
211
|
+
continue;
|
|
212
|
+
const e = entry;
|
|
213
|
+
const question = pickString(e.question, e.q, e.title);
|
|
214
|
+
const answer = pickString(e.answer, e.a, e.body, e.text);
|
|
215
|
+
if (question && answer)
|
|
216
|
+
items.push({ question, answer });
|
|
217
|
+
}
|
|
218
|
+
return items;
|
|
219
|
+
}
|
|
220
|
+
function asStringArray(...candidates) {
|
|
221
|
+
for (const c of candidates) {
|
|
222
|
+
if (Array.isArray(c)) {
|
|
223
|
+
const strings = c.filter((x) => typeof x === 'string' && x.trim().length > 0);
|
|
224
|
+
if (strings.length > 0)
|
|
225
|
+
return strings;
|
|
226
|
+
}
|
|
227
|
+
if (typeof c === 'string' && c.trim().length > 0)
|
|
228
|
+
return [c];
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Build the additional entity node for Schema.org types the graph foundation
|
|
234
|
+
* doesn't model directly (LocalBusiness, Service, Product, Event, Person, …),
|
|
235
|
+
* linked to the page's WebPage node via `mainEntityOfPage`. Returns null for
|
|
236
|
+
* foundation types (already represented as WebPage/Article nodes).
|
|
88
237
|
*/
|
|
89
|
-
function
|
|
238
|
+
function buildSpecializedEntity(type, doc, meta, site) {
|
|
239
|
+
if (FOUNDATION_TYPES.has(type))
|
|
240
|
+
return null;
|
|
90
241
|
const data = (doc.data ?? {});
|
|
91
242
|
const page = (doc.pageSettings ?? {});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (doc.structuredData &&
|
|
95
|
-
typeof doc.structuredData === 'object' &&
|
|
96
|
-
Object.keys(doc.structuredData).length > 0) {
|
|
97
|
-
const sd = doc.structuredData;
|
|
98
|
-
return {
|
|
99
|
-
'@context': sd['@context'] ?? 'https://schema.org',
|
|
100
|
-
...sd,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const schema = {
|
|
104
|
-
'@context': 'https://schema.org',
|
|
243
|
+
const siteUrl = trimTrailingSlash(site?.siteUrl ?? '');
|
|
244
|
+
const node = {
|
|
105
245
|
'@type': type,
|
|
246
|
+
'@id': `${meta.url}#entity`,
|
|
106
247
|
name: meta.title,
|
|
107
|
-
headline: meta.title,
|
|
108
|
-
description: meta.description,
|
|
109
248
|
url: meta.url,
|
|
249
|
+
mainEntityOfPage: { '@id': meta.url },
|
|
110
250
|
};
|
|
251
|
+
if (meta.description)
|
|
252
|
+
node.description = meta.description;
|
|
111
253
|
if (meta.image)
|
|
112
|
-
|
|
113
|
-
// Article-family types.
|
|
114
|
-
if (type === 'Article' || type === 'BlogPosting' || type === 'NewsArticle') {
|
|
115
|
-
const datePublished = asIsoDate(doc.publishedAt ?? data.publishedAt ?? page.publishedAt);
|
|
116
|
-
const dateModified = asIsoDate(doc.updatedAt ?? data.updatedAt ?? page.updatedAt) ?? datePublished;
|
|
117
|
-
if (datePublished)
|
|
118
|
-
schema.datePublished = datePublished;
|
|
119
|
-
if (dateModified)
|
|
120
|
-
schema.dateModified = dateModified;
|
|
121
|
-
const authorName = pickString(data.authorName, data.author, data.author?.name);
|
|
122
|
-
if (authorName) {
|
|
123
|
-
schema.author = { '@type': 'Person', name: authorName };
|
|
124
|
-
}
|
|
125
|
-
const orgName = site?.organization?.name ?? site?.siteName;
|
|
126
|
-
if (orgName) {
|
|
127
|
-
schema.publisher = {
|
|
128
|
-
'@type': 'Organization',
|
|
129
|
-
name: orgName,
|
|
130
|
-
...(site?.organization?.logo
|
|
131
|
-
? { logo: { '@type': 'ImageObject', url: site.organization.logo } }
|
|
132
|
-
: {}),
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// LocalBusiness / Service.
|
|
254
|
+
node.image = { '@id': meta.image };
|
|
137
255
|
if (type === 'LocalBusiness' || type === 'Service') {
|
|
138
256
|
const address = data.address ?? page.address;
|
|
139
257
|
if (address && typeof address === 'object') {
|
|
140
|
-
|
|
258
|
+
node.address = { '@type': 'PostalAddress', ...address };
|
|
141
259
|
}
|
|
142
260
|
const phone = pickString(data.phone, data.telephone, page.phone);
|
|
143
261
|
if (phone)
|
|
144
|
-
|
|
262
|
+
node.telephone = phone;
|
|
145
263
|
const priceRange = pickString(data.priceRange, page.priceRange);
|
|
146
264
|
if (priceRange)
|
|
147
|
-
|
|
265
|
+
node.priceRange = priceRange;
|
|
266
|
+
if (siteUrl)
|
|
267
|
+
node.provider = { '@id': `${siteUrl}/#organization` };
|
|
268
|
+
}
|
|
269
|
+
return node;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Compose a connected Schema.org `@graph` for a document. Stacks the WebSite +
|
|
273
|
+
* Organization (+ sameAs / SearchAction) site nodes, the page node (WebPage or
|
|
274
|
+
* Article/BlogPosting), a derived BreadcrumbList, FAQ `mainEntity`, and an
|
|
275
|
+
* optional specialized entity node (LocalBusiness, Service, …) — so every page
|
|
276
|
+
* ships a single graph that search engines and AI crawlers can traverse,
|
|
277
|
+
* rather than an isolated flat object.
|
|
278
|
+
*
|
|
279
|
+
* Heuristic field mapping keeps default content working without per-collection
|
|
280
|
+
* wiring. A doc-level `structuredData` override is honored verbatim (the editor
|
|
281
|
+
* knows best).
|
|
282
|
+
*/
|
|
283
|
+
function composeJsonLd(type, doc, meta, site, config) {
|
|
284
|
+
const data = (doc.data ?? {});
|
|
285
|
+
const page = (doc.pageSettings ?? {});
|
|
286
|
+
// Doc-level override: if structuredData is already populated, use it verbatim.
|
|
287
|
+
if (doc.structuredData &&
|
|
288
|
+
typeof doc.structuredData === 'object' &&
|
|
289
|
+
Object.keys(doc.structuredData).length > 0) {
|
|
290
|
+
const sd = doc.structuredData;
|
|
291
|
+
return {
|
|
292
|
+
'@context': sd['@context'] ?? 'https://schema.org',
|
|
293
|
+
...sd,
|
|
294
|
+
};
|
|
148
295
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
296
|
+
const siteUrl = trimTrailingSlash(site?.siteUrl ?? '');
|
|
297
|
+
const defaultLanguage = config?.i18n?.defaultLocale;
|
|
298
|
+
const siteSchema = {
|
|
299
|
+
siteName: site?.siteName ?? 'Site',
|
|
300
|
+
siteUrl,
|
|
301
|
+
siteDescription: site?.defaultMetaDescription,
|
|
302
|
+
organizationName: site?.organization?.name ?? site?.siteName,
|
|
303
|
+
organizationLogo: site?.organization?.logo,
|
|
304
|
+
organizationSameAs: site?.organization?.sameAs,
|
|
305
|
+
defaultLanguage,
|
|
306
|
+
};
|
|
307
|
+
const { datePublished, dateModified } = resolveContentDates({ doc, data, page });
|
|
308
|
+
const authorName = pickString(data.authorName, data.author, data.author?.name);
|
|
309
|
+
const faqItems = extractFaqItems(data, page);
|
|
310
|
+
// GEO key takeaway → Schema.org `abstract` (a citable summary for AI engines).
|
|
311
|
+
const keyTakeaway = pickString(page.keyTakeaway, data.keyTakeaway);
|
|
312
|
+
const pageInput = {
|
|
313
|
+
url: meta.url,
|
|
314
|
+
title: meta.title,
|
|
315
|
+
description: meta.description,
|
|
316
|
+
type: schemaTypeToPageType(type),
|
|
317
|
+
...(datePublished ? { datePublished } : {}),
|
|
318
|
+
...(dateModified ? { dateModified } : {}),
|
|
319
|
+
...(authorName ? { author: { name: authorName } } : {}),
|
|
320
|
+
...(meta.image ? { featuredImage: { url: meta.image } } : {}),
|
|
321
|
+
...(faqItems.length > 0 ? { faqItems } : {}),
|
|
322
|
+
...(keyTakeaway ? { abstract: keyTakeaway } : {}),
|
|
323
|
+
breadcrumbs: deriveBreadcrumbs(siteUrl, meta.url, meta.title),
|
|
324
|
+
keywords: asStringArray(data.keywords, data.tags),
|
|
325
|
+
articleSection: asStringArray(data.category, data.categories),
|
|
326
|
+
language: defaultLanguage,
|
|
327
|
+
};
|
|
328
|
+
const graphObj = buildSchemaGraph(siteSchema, pageInput);
|
|
329
|
+
const graph = [...graphObj['@graph']];
|
|
330
|
+
const entity = buildSpecializedEntity(type, doc, meta, site);
|
|
331
|
+
if (entity) {
|
|
332
|
+
graph.push(entity);
|
|
333
|
+
// Cross-link the WebPage node to its primary entity for traversal.
|
|
334
|
+
const webPage = graph.find((n) => {
|
|
335
|
+
const t = n['@type'];
|
|
336
|
+
return t === 'WebPage' || (Array.isArray(t) && t.includes('WebPage'));
|
|
337
|
+
});
|
|
338
|
+
if (webPage)
|
|
339
|
+
webPage.mainEntity = { '@id': entity['@id'] };
|
|
153
340
|
}
|
|
154
|
-
return schema;
|
|
341
|
+
return { '@context': 'https://schema.org', '@graph': graph };
|
|
155
342
|
}
|
|
156
343
|
/**
|
|
157
344
|
* Compose page meta + JSON-LD from a CMS document + collection config.
|
|
@@ -163,11 +350,16 @@ export function composePageMeta(input) {
|
|
|
163
350
|
const collectionSeo = collection?.seo;
|
|
164
351
|
const data = (doc.data ?? {});
|
|
165
352
|
const page = (doc.pageSettings ?? {});
|
|
353
|
+
const contentDates = resolveContentDates({ doc, data, page });
|
|
166
354
|
const siteUrl = input.siteUrl?.replace(/\/+$/, '') ?? site?.siteUrl?.replace(/\/+$/, '') ?? '';
|
|
167
355
|
const siteName = site?.siteName ?? 'Site';
|
|
168
356
|
const slug = (doc.slug ?? data.slug ?? '') || '';
|
|
169
|
-
const
|
|
170
|
-
|
|
357
|
+
const url = resolveDocumentUrl({
|
|
358
|
+
siteUrl,
|
|
359
|
+
urlPrefix: collection?.urlPrefix,
|
|
360
|
+
slug,
|
|
361
|
+
data,
|
|
362
|
+
});
|
|
171
363
|
const schemaType = pickString(page.schemaType, data.schemaType, collectionSeo?.defaultSchemaType) ??
|
|
172
364
|
input.defaultSchemaType ??
|
|
173
365
|
(collection?.slug ? detectSchemaType(collection.slug) : 'WebPage');
|
|
@@ -192,9 +384,13 @@ export function composePageMeta(input) {
|
|
|
192
384
|
tag: pickString(data.tags?.[0], data.tag),
|
|
193
385
|
})
|
|
194
386
|
: rawTitle;
|
|
195
|
-
// Description: per-document value wins, then the
|
|
196
|
-
|
|
197
|
-
const
|
|
387
|
+
// Description: per-document value wins, then the GEO key takeaway (a concise
|
|
388
|
+
// citable summary), then other summaries, then the site-wide default.
|
|
389
|
+
const description = pickString(page.metaDescription, data.metaDescription, page.keyTakeaway, data.keyTakeaway, data.excerpt, data.description, site?.defaultMetaDescription);
|
|
390
|
+
// Canonical: accept both the cms-core (`canonical`) and plugin-seo / SEO
|
|
391
|
+
// workspace (`canonicalUrl`) field names so a URL set in either surface is
|
|
392
|
+
// honored on the public page.
|
|
393
|
+
const canonical = pickString(page.canonical, data.canonical, page.canonicalUrl, data.canonicalUrl) ?? url;
|
|
198
394
|
// Single source of truth for robots resolution (shared with the /seo/meta
|
|
199
395
|
// render path). Honors the `robotsPolicy` select field, explicit
|
|
200
396
|
// noindex/nofollow booleans, per-collection + site defaults, and the
|
|
@@ -202,8 +398,13 @@ export function composePageMeta(input) {
|
|
|
202
398
|
const robots = resolveRobotsDirectives({
|
|
203
399
|
pageData: {
|
|
204
400
|
robotsPolicy: page.robotsPolicy ?? data.robotsPolicy,
|
|
205
|
-
|
|
206
|
-
|
|
401
|
+
// Accept the cms-core (`noIndex`/`noFollow`), lowercase SEO-workspace
|
|
402
|
+
// (`noindex`/`nofollow`), and plugin-seo (`robotsNoindex`/`robotsNofollow`)
|
|
403
|
+
// field names so a directive set in any surface is respected.
|
|
404
|
+
noIndex: firstDefinedBool(page.noIndex, data.noIndex, data.noindex, data.robotsNoindex) ??
|
|
405
|
+
collectionSeo?.defaultRobots?.noIndex,
|
|
406
|
+
noFollow: firstDefinedBool(page.noFollow, data.noFollow, data.nofollow, data.robotsNofollow) ??
|
|
407
|
+
collectionSeo?.defaultRobots?.noFollow,
|
|
207
408
|
},
|
|
208
409
|
settings: {
|
|
209
410
|
defaultNoIndex: site?.robots?.defaultNoIndex,
|
|
@@ -212,7 +413,12 @@ export function composePageMeta(input) {
|
|
|
212
413
|
},
|
|
213
414
|
environment: resolveSeoEnvironment(input.environment),
|
|
214
415
|
});
|
|
215
|
-
|
|
416
|
+
// A real, author-supplied image (used for the JSON-LD ImageObject — we don't
|
|
417
|
+
// want the dynamic OG generator URL polluting structured data). Falls back to
|
|
418
|
+
// the page's hero / featured / cover image (string or `{ url }`) before the
|
|
419
|
+
// site default, so social cards use the page's own visual when set.
|
|
420
|
+
const realImageUrl = pickString(page.ogImage, data.ogImage, asImageUrl(data.featuredImage), asImageUrl(data.heroImage), asImageUrl(data.coverImage), asImageUrl(data.image), site?.defaultOgImage) ?? undefined;
|
|
421
|
+
const ogImageUrl = realImageUrl ??
|
|
216
422
|
// The dynamic OG endpoint is served by the CMS API router, which is mounted
|
|
217
423
|
// under /api/cms — not the site root — so the default must include it or it
|
|
218
424
|
// 404s for social crawlers.
|
|
@@ -230,10 +436,14 @@ export function composePageMeta(input) {
|
|
|
230
436
|
: 'website',
|
|
231
437
|
ogImage: ogImageUrl ? { url: ogImageUrl } : undefined,
|
|
232
438
|
twitterSite: site?.twitterHandle,
|
|
233
|
-
|
|
234
|
-
|
|
439
|
+
// Same resolved freshness dates the JSON-LD uses, so OG article times and
|
|
440
|
+
// structured data never disagree (and editorial dates are honored).
|
|
441
|
+
publishedTime: contentDates.datePublished,
|
|
442
|
+
modifiedTime: contentDates.dateModified,
|
|
235
443
|
author: pickString(data.authorName, data.author),
|
|
236
444
|
locale: config?.i18n?.defaultLocale,
|
|
445
|
+
metadataBase: siteUrl || undefined,
|
|
446
|
+
alternates: input.alternates,
|
|
237
447
|
};
|
|
238
448
|
const meta = generateMetaTags(metaInput);
|
|
239
449
|
let metaHtml = renderMetaTagsHtml(metaInput);
|
|
@@ -255,7 +465,7 @@ export function composePageMeta(input) {
|
|
|
255
465
|
if (nextVerification)
|
|
256
466
|
metadata = { ...metadata, verification: nextVerification };
|
|
257
467
|
}
|
|
258
|
-
const jsonLd = composeJsonLd(schemaType, doc, { title, description, url, image:
|
|
468
|
+
const jsonLd = composeJsonLd(schemaType, doc, { title, description, url, image: realImageUrl }, site, config);
|
|
259
469
|
const jsonLdHtml = jsonLd
|
|
260
470
|
? `<script type="application/ld+json">${JSON.stringify(jsonLd).replace(/</g, '\\u003c')}</script>`
|
|
261
471
|
: '';
|