@actuate-media/cms-core 0.41.0 → 0.43.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/__tests__/ai/ai.test.d.ts +2 -0
- package/dist/__tests__/ai/ai.test.d.ts.map +1 -0
- package/dist/__tests__/ai/ai.test.js +261 -0
- package/dist/__tests__/ai/ai.test.js.map +1 -0
- package/dist/__tests__/seo/settings-defaults.test.d.ts +2 -0
- package/dist/__tests__/seo/settings-defaults.test.d.ts.map +1 -0
- package/dist/__tests__/seo/settings-defaults.test.js +189 -0
- package/dist/__tests__/seo/settings-defaults.test.js.map +1 -0
- package/dist/__tests__/seo/sitemap-notify.test.d.ts +2 -0
- package/dist/__tests__/seo/sitemap-notify.test.d.ts.map +1 -0
- package/dist/__tests__/seo/sitemap-notify.test.js +191 -0
- package/dist/__tests__/seo/sitemap-notify.test.js.map +1 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +80 -3
- package/dist/actions.js.map +1 -1
- package/dist/ai/catalog.d.ts +29 -0
- package/dist/ai/catalog.d.ts.map +1 -0
- package/dist/ai/catalog.js +340 -0
- package/dist/ai/catalog.js.map +1 -0
- package/dist/ai/config-store.d.ts +55 -0
- package/dist/ai/config-store.d.ts.map +1 -0
- package/dist/ai/config-store.js +177 -0
- package/dist/ai/config-store.js.map +1 -0
- package/dist/ai/feature-defaults.d.ts +20 -0
- package/dist/ai/feature-defaults.d.ts.map +1 -0
- package/dist/ai/feature-defaults.js +104 -0
- package/dist/ai/feature-defaults.js.map +1 -0
- package/dist/ai/index.d.ts +13 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +13 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/models.d.ts +37 -0
- package/dist/ai/models.d.ts.map +1 -0
- package/dist/ai/models.js +126 -0
- package/dist/ai/models.js.map +1 -0
- package/dist/ai/providers/anthropic.d.ts +9 -0
- package/dist/ai/providers/anthropic.d.ts.map +1 -0
- package/dist/ai/providers/anthropic.js +36 -0
- package/dist/ai/providers/anthropic.js.map +1 -0
- package/dist/ai/providers/custom.d.ts +11 -0
- package/dist/ai/providers/custom.d.ts.map +1 -0
- package/dist/ai/providers/custom.js +70 -0
- package/dist/ai/providers/custom.js.map +1 -0
- package/dist/ai/providers/http.d.ts +32 -0
- package/dist/ai/providers/http.d.ts.map +1 -0
- package/dist/ai/providers/http.js +90 -0
- package/dist/ai/providers/http.js.map +1 -0
- package/dist/ai/providers/index.d.ts +16 -0
- package/dist/ai/providers/index.d.ts.map +1 -0
- package/dist/ai/providers/index.js +25 -0
- package/dist/ai/providers/index.js.map +1 -0
- package/dist/ai/providers/openai.d.ts +10 -0
- package/dist/ai/providers/openai.d.ts.map +1 -0
- package/dist/ai/providers/openai.js +41 -0
- package/dist/ai/providers/openai.js.map +1 -0
- package/dist/ai/providers/xai.d.ts +9 -0
- package/dist/ai/providers/xai.d.ts.map +1 -0
- package/dist/ai/providers/xai.js +35 -0
- package/dist/ai/providers/xai.js.map +1 -0
- package/dist/ai/types.d.ts +161 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +11 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/ai/usage.d.ts +18 -0
- package/dist/ai/usage.d.ts.map +1 -0
- package/dist/ai/usage.js +70 -0
- package/dist/ai/usage.js.map +1 -0
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +539 -10
- package/dist/api/handlers.js.map +1 -1
- package/dist/config/types.d.ts +49 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/page-builder/schema.d.ts +2 -2
- package/dist/seo/config-store.d.ts +2 -0
- package/dist/seo/config-store.d.ts.map +1 -1
- package/dist/seo/config-store.js +2 -0
- package/dist/seo/config-store.js.map +1 -1
- package/dist/seo/index.d.ts +6 -4
- package/dist/seo/index.d.ts.map +1 -1
- package/dist/seo/index.js +3 -2
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/meta-tags.d.ts +36 -0
- package/dist/seo/meta-tags.d.ts.map +1 -1
- package/dist/seo/meta-tags.js +78 -0
- package/dist/seo/meta-tags.js.map +1 -1
- package/dist/seo/page-meta.d.ts.map +1 -1
- package/dist/seo/page-meta.js +47 -8
- package/dist/seo/page-meta.js.map +1 -1
- package/dist/seo/sitemap-notify.d.ts +85 -0
- package/dist/seo/sitemap-notify.d.ts.map +1 -0
- package/dist/seo/sitemap-notify.js +159 -0
- package/dist/seo/sitemap-notify.js.map +1 -0
- package/dist/seo/title-templates.d.ts +26 -0
- package/dist/seo/title-templates.d.ts.map +1 -1
- package/dist/seo/title-templates.js +83 -0
- package/dist/seo/title-templates.js.map +1 -1
- package/package.json +1 -1
package/dist/seo/page-meta.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { generateMetaTags, generateNextMetadata, renderMetaTagsHtml } from './meta-tags.js';
|
|
1
|
+
import { buildNextVerification, buildVerificationMetaEntries, generateMetaTags, generateNextMetadata, renderMetaTagsHtml, renderVerificationMetaHtml, } from './meta-tags.js';
|
|
2
2
|
import { resolveRobotsDirectives, resolveSeoEnvironment } from './robots.js';
|
|
3
|
+
import { applySiteTitleTemplate } from './title-templates.js';
|
|
3
4
|
/**
|
|
4
5
|
* Maps a collection slug to the most appropriate Schema.org type when the
|
|
5
6
|
* config doesn't specify one. Conservative fallback to WebPage.
|
|
@@ -167,8 +168,32 @@ export function composePageMeta(input) {
|
|
|
167
168
|
const slug = (doc.slug ?? data.slug ?? '') || '';
|
|
168
169
|
const isHome = slug === 'home' || slug === 'index' || slug === '';
|
|
169
170
|
const url = buildDocUrl(siteUrl, collection?.urlPrefix, slug, isHome);
|
|
170
|
-
const
|
|
171
|
-
|
|
171
|
+
const schemaType = pickString(page.schemaType, data.schemaType, collectionSeo?.defaultSchemaType) ??
|
|
172
|
+
input.defaultSchemaType ??
|
|
173
|
+
(collection?.slug ? detectSchemaType(collection.slug) : 'WebPage');
|
|
174
|
+
// Title: an explicit per-document SEO title always wins. Otherwise apply the
|
|
175
|
+
// site-wide title template (Settings → SEO "Meta Defaults"), falling back to
|
|
176
|
+
// the raw document title when no template is configured.
|
|
177
|
+
const explicitTitle = pickString(page.metaTitle, data.metaTitle);
|
|
178
|
+
const rawTitle = pickString(data.title, page.title) ?? 'Untitled';
|
|
179
|
+
// Apply the site-wide template only when one is configured, so sites that
|
|
180
|
+
// never set a template keep their existing bare-title behaviour.
|
|
181
|
+
const hasTitleTemplate = typeof site?.titleTemplate === 'string' && site.titleTemplate.trim() !== '';
|
|
182
|
+
const title = explicitTitle
|
|
183
|
+
? explicitTitle
|
|
184
|
+
: hasTitleTemplate
|
|
185
|
+
? applySiteTitleTemplate(site.titleTemplate, {
|
|
186
|
+
page: rawTitle,
|
|
187
|
+
site: siteName,
|
|
188
|
+
type: pickString(collection?.labels?.singular, schemaType),
|
|
189
|
+
author: pickString(data.authorName, data.author),
|
|
190
|
+
date: asIsoDate(doc.publishedAt ?? data.publishedAt)?.slice(0, 10),
|
|
191
|
+
category: pickString(data.category, data.categories?.[0]),
|
|
192
|
+
tag: pickString(data.tags?.[0], data.tag),
|
|
193
|
+
})
|
|
194
|
+
: rawTitle;
|
|
195
|
+
// Description: per-document value wins, then the site-wide default.
|
|
196
|
+
const description = pickString(page.metaDescription, data.metaDescription, data.excerpt, data.description, site?.defaultMetaDescription);
|
|
172
197
|
const canonical = pickString(page.canonical, data.canonical) ?? url;
|
|
173
198
|
// Single source of truth for robots resolution (shared with the /seo/meta
|
|
174
199
|
// render path). Honors the `robotsPolicy` select field, explicit
|
|
@@ -192,9 +217,6 @@ export function composePageMeta(input) {
|
|
|
192
217
|
// under /api/cms — not the site root — so the default must include it or it
|
|
193
218
|
// 404s for social crawlers.
|
|
194
219
|
(siteUrl ? `${siteUrl}/api/cms/og.png?title=${encodeURIComponent(title)}` : undefined);
|
|
195
|
-
const schemaType = pickString(page.schemaType, data.schemaType, collectionSeo?.defaultSchemaType) ??
|
|
196
|
-
input.defaultSchemaType ??
|
|
197
|
-
(collection?.slug ? detectSchemaType(collection.slug) : 'WebPage');
|
|
198
220
|
const metaInput = {
|
|
199
221
|
title,
|
|
200
222
|
description,
|
|
@@ -214,8 +236,25 @@ export function composePageMeta(input) {
|
|
|
214
236
|
locale: config?.i18n?.defaultLocale,
|
|
215
237
|
};
|
|
216
238
|
const meta = generateMetaTags(metaInput);
|
|
217
|
-
|
|
218
|
-
|
|
239
|
+
let metaHtml = renderMetaTagsHtml(metaInput);
|
|
240
|
+
let metadata = generateNextMetadata(metaInput);
|
|
241
|
+
// Site-wide search-engine verification meta tags (Settings → SEO). These are
|
|
242
|
+
// injected into every public page's head; values are escaped and emitted only
|
|
243
|
+
// as safe `<meta>` tags.
|
|
244
|
+
const verificationEntries = buildVerificationMetaEntries(site?.verification);
|
|
245
|
+
if (verificationEntries.length > 0) {
|
|
246
|
+
for (const e of verificationEntries) {
|
|
247
|
+
const key = e.name ?? e.property;
|
|
248
|
+
if (key)
|
|
249
|
+
meta[key] = e.content;
|
|
250
|
+
}
|
|
251
|
+
const verificationHtml = renderVerificationMetaHtml(site?.verification);
|
|
252
|
+
if (verificationHtml)
|
|
253
|
+
metaHtml = `${metaHtml}\n${verificationHtml}`;
|
|
254
|
+
const nextVerification = buildNextVerification(site?.verification);
|
|
255
|
+
if (nextVerification)
|
|
256
|
+
metadata = { ...metadata, verification: nextVerification };
|
|
257
|
+
}
|
|
219
258
|
const jsonLd = composeJsonLd(schemaType, doc, { title, description, url, image: ogImageUrl }, site);
|
|
220
259
|
const jsonLdHtml = jsonLd
|
|
221
260
|
? `<script type="application/ld+json">${JSON.stringify(jsonLd).replace(/</g, '\\u003c')}</script>`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-meta.js","sourceRoot":"","sources":["../../src/seo/page-meta.ts"],"names":[],"mappings":"AAkBA,OAAO,
|
|
1
|
+
{"version":3,"file":"page-meta.js","sourceRoot":"","sources":["../../src/seo/page-meta.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sitemap regeneration + search-engine ping orchestration.
|
|
3
|
+
*
|
|
4
|
+
* Triggered as a best-effort side effect after public content is published,
|
|
5
|
+
* updated, or removed (see `actions.ts` → `sitemap-notify` pending effect).
|
|
6
|
+
* The CMS serves `/sitemap.xml` dynamically, so "regeneration" here means:
|
|
7
|
+
* 1. bump `sitemapLastGeneratedAt` so the Settings/Technical UI reflects the
|
|
8
|
+
* change, and
|
|
9
|
+
* 2. invalidate the sitemap cache tag so the next request rebuilds it.
|
|
10
|
+
*
|
|
11
|
+
* Pinging is **production-gated** and **SSRF-aware**:
|
|
12
|
+
* - Pings only fire when the resolved environment is `production` (or the
|
|
13
|
+
* caller explicitly opts in via `allowNonProduction`), so preview/local
|
|
14
|
+
* deploys never notify Google/Bing about throwaway URLs.
|
|
15
|
+
* - The site URL is validated as a public http(s) URL before it's embedded
|
|
16
|
+
* in a ping request, so a misconfigured `siteUrl` can't point search
|
|
17
|
+
* engines (or our own fetch) at an internal address.
|
|
18
|
+
*
|
|
19
|
+
* Note on endpoints: Google retired its standalone sitemap-ping endpoint in
|
|
20
|
+
* 2023 (it now returns 404) and recommends discovery via robots.txt / Search
|
|
21
|
+
* Console instead. We keep the call for completeness/back-compat but treat any
|
|
22
|
+
* non-2xx response as a soft failure — Bing's endpoint still honors pings.
|
|
23
|
+
*/
|
|
24
|
+
import type { SeoModuleSettings } from './config-store.js';
|
|
25
|
+
export interface SitemapPingTarget {
|
|
26
|
+
name: string;
|
|
27
|
+
url: string;
|
|
28
|
+
}
|
|
29
|
+
/** Build the canonical `/sitemap.xml` URL from a configured site URL. */
|
|
30
|
+
export declare function buildSitemapUrl(siteUrl: string | null | undefined): string | null;
|
|
31
|
+
/** Search-engine ping targets for a given (already validated) sitemap URL. */
|
|
32
|
+
export declare function buildSearchEnginePings(sitemapUrl: string): SitemapPingTarget[];
|
|
33
|
+
/**
|
|
34
|
+
* Default debounce window for the expensive half of the cycle (timestamp write
|
|
35
|
+
* + search-engine ping). Bursts of publishes (bulk imports, "publish all")
|
|
36
|
+
* collapse to one regeneration per window so we don't hammer the single hot
|
|
37
|
+
* `__seo_config__` row or spam search engines. Cache invalidation is NOT
|
|
38
|
+
* debounced — it's cheap and must happen on every change for correctness.
|
|
39
|
+
*/
|
|
40
|
+
export declare const DEFAULT_SITEMAP_DEBOUNCE_MS = 60000;
|
|
41
|
+
export interface SitemapNotifyOptions {
|
|
42
|
+
settings: Pick<SeoModuleSettings, 'sitemapEnabled' | 'autoRegenerateSitemapOnPublish' | 'pingSearchEnginesOnPublish'>;
|
|
43
|
+
/** Configured public site URL (e.g. from `NEXT_PUBLIC_SITE_URL` or seo.siteUrl). */
|
|
44
|
+
siteUrl?: string | null;
|
|
45
|
+
/** Explicit environment override; defaults to `resolveSeoEnvironment()`. */
|
|
46
|
+
environment?: string;
|
|
47
|
+
/** ISO timestamp of the last regeneration, used to debounce the write + ping. */
|
|
48
|
+
lastGeneratedAt?: string | null;
|
|
49
|
+
/** Debounce window in ms (default {@link DEFAULT_SITEMAP_DEBOUNCE_MS}). */
|
|
50
|
+
debounceMs?: number;
|
|
51
|
+
/** Injectable fetch for tests; defaults to global `fetch`. */
|
|
52
|
+
fetchImpl?: typeof fetch;
|
|
53
|
+
/** Bypass the production gate (tests / explicit opt-in only). */
|
|
54
|
+
allowNonProduction?: boolean;
|
|
55
|
+
/** Per-ping timeout in ms (default 5000). */
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface SitemapNotifyResult {
|
|
59
|
+
/**
|
|
60
|
+
* Whether the caller should drop the sitemap cache tag. True on every
|
|
61
|
+
* sitemap-relevant change (not debounced) so the next request rebuilds.
|
|
62
|
+
*/
|
|
63
|
+
invalidateCache: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Whether the caller should persist a fresh `sitemapLastGeneratedAt`. Only
|
|
66
|
+
* true once per debounce window — this is the write that was amplifying.
|
|
67
|
+
*/
|
|
68
|
+
regenerated: boolean;
|
|
69
|
+
sitemapUrl: string | null;
|
|
70
|
+
/** Names of search engines successfully pinged (2xx). */
|
|
71
|
+
pinged: string[];
|
|
72
|
+
/** Targets that were not pinged, with a machine-readable reason. */
|
|
73
|
+
skipped: Array<{
|
|
74
|
+
target: string;
|
|
75
|
+
reason: string;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Decide what to do after a public-content change and perform the pings.
|
|
80
|
+
* Pure-ish: all I/O goes through `fetchImpl` and the result tells the caller
|
|
81
|
+
* whether to persist a regeneration timestamp. Never throws — sitemap upkeep
|
|
82
|
+
* must not fail a content write that already committed.
|
|
83
|
+
*/
|
|
84
|
+
export declare function notifySitemapOnPublish(opts: SitemapNotifyOptions): Promise<SitemapNotifyResult>;
|
|
85
|
+
//# sourceMappingURL=sitemap-notify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap-notify.d.ts","sourceRoot":"","sources":["../../src/seo/sitemap-notify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAE1D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CASjF;AAED,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAM9E;AAED;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,QAAS,CAAA;AAEjD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,IAAI,CACZ,iBAAiB,EACjB,gBAAgB,GAAG,gCAAgC,GAAG,4BAA4B,CACnF,CAAA;IACD,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,eAAe,EAAE,OAAO,CAAA;IACxB;;;OAGG;IACH,WAAW,EAAE,OAAO,CAAA;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,yDAAyD;IACzD,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,oEAAoE;IACpE,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnD;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,mBAAmB,CAAC,CA2G9B"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sitemap regeneration + search-engine ping orchestration.
|
|
3
|
+
*
|
|
4
|
+
* Triggered as a best-effort side effect after public content is published,
|
|
5
|
+
* updated, or removed (see `actions.ts` → `sitemap-notify` pending effect).
|
|
6
|
+
* The CMS serves `/sitemap.xml` dynamically, so "regeneration" here means:
|
|
7
|
+
* 1. bump `sitemapLastGeneratedAt` so the Settings/Technical UI reflects the
|
|
8
|
+
* change, and
|
|
9
|
+
* 2. invalidate the sitemap cache tag so the next request rebuilds it.
|
|
10
|
+
*
|
|
11
|
+
* Pinging is **production-gated** and **SSRF-aware**:
|
|
12
|
+
* - Pings only fire when the resolved environment is `production` (or the
|
|
13
|
+
* caller explicitly opts in via `allowNonProduction`), so preview/local
|
|
14
|
+
* deploys never notify Google/Bing about throwaway URLs.
|
|
15
|
+
* - The site URL is validated as a public http(s) URL before it's embedded
|
|
16
|
+
* in a ping request, so a misconfigured `siteUrl` can't point search
|
|
17
|
+
* engines (or our own fetch) at an internal address.
|
|
18
|
+
*
|
|
19
|
+
* Note on endpoints: Google retired its standalone sitemap-ping endpoint in
|
|
20
|
+
* 2023 (it now returns 404) and recommends discovery via robots.txt / Search
|
|
21
|
+
* Console instead. We keep the call for completeness/back-compat but treat any
|
|
22
|
+
* non-2xx response as a soft failure — Bing's endpoint still honors pings.
|
|
23
|
+
*/
|
|
24
|
+
import { resolveSeoEnvironment } from './robots.js';
|
|
25
|
+
import { validateWebhookUrl } from '../security/webhook.js';
|
|
26
|
+
/** Build the canonical `/sitemap.xml` URL from a configured site URL. */
|
|
27
|
+
export function buildSitemapUrl(siteUrl) {
|
|
28
|
+
if (!siteUrl)
|
|
29
|
+
return null;
|
|
30
|
+
try {
|
|
31
|
+
const u = new URL(siteUrl);
|
|
32
|
+
if (u.protocol !== 'https:' && u.protocol !== 'http:')
|
|
33
|
+
return null;
|
|
34
|
+
return `${u.origin}/sitemap.xml`;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Search-engine ping targets for a given (already validated) sitemap URL. */
|
|
41
|
+
export function buildSearchEnginePings(sitemapUrl) {
|
|
42
|
+
const enc = encodeURIComponent(sitemapUrl);
|
|
43
|
+
return [
|
|
44
|
+
{ name: 'google', url: `https://www.google.com/ping?sitemap=${enc}` },
|
|
45
|
+
{ name: 'bing', url: `https://www.bing.com/ping?sitemap=${enc}` },
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Default debounce window for the expensive half of the cycle (timestamp write
|
|
50
|
+
* + search-engine ping). Bursts of publishes (bulk imports, "publish all")
|
|
51
|
+
* collapse to one regeneration per window so we don't hammer the single hot
|
|
52
|
+
* `__seo_config__` row or spam search engines. Cache invalidation is NOT
|
|
53
|
+
* debounced — it's cheap and must happen on every change for correctness.
|
|
54
|
+
*/
|
|
55
|
+
export const DEFAULT_SITEMAP_DEBOUNCE_MS = 60_000;
|
|
56
|
+
/**
|
|
57
|
+
* Decide what to do after a public-content change and perform the pings.
|
|
58
|
+
* Pure-ish: all I/O goes through `fetchImpl` and the result tells the caller
|
|
59
|
+
* whether to persist a regeneration timestamp. Never throws — sitemap upkeep
|
|
60
|
+
* must not fail a content write that already committed.
|
|
61
|
+
*/
|
|
62
|
+
export async function notifySitemapOnPublish(opts) {
|
|
63
|
+
const { settings } = opts;
|
|
64
|
+
const result = {
|
|
65
|
+
invalidateCache: false,
|
|
66
|
+
regenerated: false,
|
|
67
|
+
sitemapUrl: null,
|
|
68
|
+
pinged: [],
|
|
69
|
+
skipped: [],
|
|
70
|
+
};
|
|
71
|
+
// Sitemap disabled entirely → nothing to do (no cache drop, no write).
|
|
72
|
+
if (!settings.sitemapEnabled) {
|
|
73
|
+
result.skipped.push({ target: 'sitemap', reason: 'sitemap-disabled' });
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
// Auto-regenerate off → the admin opted out of publish-time refresh entirely.
|
|
77
|
+
if (!settings.autoRegenerateSitemapOnPublish) {
|
|
78
|
+
result.skipped.push({ target: 'sitemap', reason: 'auto-regenerate-disabled' });
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
// The sitemap content changed and auto-regenerate is on → the cache must be
|
|
82
|
+
// dropped on EVERY change so the next request rebuilds. This is the cheap,
|
|
83
|
+
// un-debounced half of the cycle.
|
|
84
|
+
result.invalidateCache = true;
|
|
85
|
+
// Debounce the expensive half (DB timestamp write + outbound ping): if we
|
|
86
|
+
// regenerated within the window, collapse this publish into the prior one.
|
|
87
|
+
const debounceMs = opts.debounceMs ?? DEFAULT_SITEMAP_DEBOUNCE_MS;
|
|
88
|
+
const lastMs = opts.lastGeneratedAt ? Date.parse(opts.lastGeneratedAt) : NaN;
|
|
89
|
+
if (!Number.isNaN(lastMs) && Date.now() - lastMs < debounceMs) {
|
|
90
|
+
result.skipped.push({ target: 'sitemap', reason: 'debounced' });
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
result.regenerated = true;
|
|
94
|
+
if (!settings.pingSearchEnginesOnPublish) {
|
|
95
|
+
result.skipped.push({ target: 'ping', reason: 'ping-disabled' });
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
const environment = resolveSeoEnvironment(opts.environment);
|
|
99
|
+
const isProduction = environment === 'production';
|
|
100
|
+
if (!isProduction && !opts.allowNonProduction) {
|
|
101
|
+
result.skipped.push({ target: 'ping', reason: `non-production:${environment ?? 'unknown'}` });
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
const sitemapUrl = buildSitemapUrl(opts.siteUrl);
|
|
105
|
+
if (!sitemapUrl) {
|
|
106
|
+
result.skipped.push({ target: 'ping', reason: 'missing-or-invalid-site-url' });
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
result.sitemapUrl = sitemapUrl;
|
|
110
|
+
// In production the site URL must be a public https URL — reject internal /
|
|
111
|
+
// http / IP-literal targets so a misconfigured siteUrl can't be weaponized.
|
|
112
|
+
const urlCheck = validateWebhookUrl(sitemapUrl);
|
|
113
|
+
if (!urlCheck.valid) {
|
|
114
|
+
result.skipped.push({
|
|
115
|
+
target: 'ping',
|
|
116
|
+
reason: `unsafe-site-url:${urlCheck.error ?? 'invalid'}`,
|
|
117
|
+
});
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
if (isProduction && !sitemapUrl.startsWith('https://')) {
|
|
121
|
+
result.skipped.push({ target: 'ping', reason: 'non-https-site-url' });
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
const doFetch = opts.fetchImpl ?? globalThis.fetch;
|
|
125
|
+
if (typeof doFetch !== 'function') {
|
|
126
|
+
result.skipped.push({ target: 'ping', reason: 'fetch-unavailable' });
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
130
|
+
const targets = buildSearchEnginePings(sitemapUrl);
|
|
131
|
+
await Promise.all(targets.map(async (target) => {
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
134
|
+
try {
|
|
135
|
+
const res = await doFetch(target.url, {
|
|
136
|
+
method: 'GET',
|
|
137
|
+
redirect: 'error',
|
|
138
|
+
signal: controller.signal,
|
|
139
|
+
});
|
|
140
|
+
if (res.ok) {
|
|
141
|
+
result.pinged.push(target.name);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
result.skipped.push({ target: target.name, reason: `http-${res.status}` });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
result.skipped.push({
|
|
149
|
+
target: target.name,
|
|
150
|
+
reason: err instanceof Error ? `fetch-error:${err.name}` : 'fetch-error',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
clearTimeout(timer);
|
|
155
|
+
}
|
|
156
|
+
}));
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=sitemap-notify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap-notify.js","sourceRoot":"","sources":["../../src/seo/sitemap-notify.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,32 @@ export interface TitleTemplateConfig {
|
|
|
6
6
|
}
|
|
7
7
|
export type TitleVariable = 'title' | 'siteName' | 'sep' | 'collection' | 'category' | 'tag' | 'page' | 'year' | 'month';
|
|
8
8
|
export declare function resolveTitle(template: string, variables: Partial<Record<TitleVariable, string>>): string;
|
|
9
|
+
/** Tokens supported by the site-wide title template. */
|
|
10
|
+
export declare const SITE_TITLE_TOKENS: readonly ["page", "site", "type", "separator", "author", "date", "category", "tag"];
|
|
11
|
+
export type SiteTitleToken = (typeof SITE_TITLE_TOKENS)[number];
|
|
12
|
+
/** Fallback used when no template is configured or the configured one is blank. */
|
|
13
|
+
export declare const DEFAULT_SITE_TITLE_TEMPLATE = "%page% \u2014 %site%";
|
|
14
|
+
export interface TitleTemplateValidation {
|
|
15
|
+
valid: boolean;
|
|
16
|
+
/** Tokens present in the template that aren't recognised. */
|
|
17
|
+
unknownTokens: string[];
|
|
18
|
+
/** Human-readable error messages (empty when valid). */
|
|
19
|
+
errors: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validate a site-wide title template. An empty/whitespace template is treated
|
|
23
|
+
* as valid (the renderer falls back to {@link DEFAULT_SITE_TITLE_TEMPLATE}).
|
|
24
|
+
* Unknown `%tokens%` and a template with no token *and* no literal text are
|
|
25
|
+
* rejected.
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateTitleTemplate(template: string): TitleTemplateValidation;
|
|
28
|
+
/**
|
|
29
|
+
* Apply a site-wide title template. Unknown / empty tokens collapse cleanly so
|
|
30
|
+
* the result never contains dangling separators or double spaces. Falls back to
|
|
31
|
+
* {@link DEFAULT_SITE_TITLE_TEMPLATE} when the template is blank, and to the
|
|
32
|
+
* page title (then site name) if the rendered result is empty.
|
|
33
|
+
*/
|
|
34
|
+
export declare function applySiteTitleTemplate(template: string | undefined, vars: Partial<Record<SiteTitleToken, string>>): string;
|
|
9
35
|
export declare function getDocumentTitle(config: TitleTemplateConfig, options: {
|
|
10
36
|
title: string;
|
|
11
37
|
collection?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"title-templates.d.ts","sourceRoot":"","sources":["../../src/seo/title-templates.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC7C;AAED,MAAM,MAAM,aAAa,GACrB,OAAO,GACP,UAAU,GACV,KAAK,GACL,YAAY,GACZ,UAAU,GACV,KAAK,GACL,MAAM,GACN,MAAM,GACN,OAAO,CAAA;AAIX,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAChD,MAAM,CAIR;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE;IACP,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,GACA,MAAM,CAuBR"}
|
|
1
|
+
{"version":3,"file":"title-templates.d.ts","sourceRoot":"","sources":["../../src/seo/title-templates.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC7C;AAED,MAAM,MAAM,aAAa,GACrB,OAAO,GACP,UAAU,GACV,KAAK,GACL,YAAY,GACZ,UAAU,GACV,KAAK,GACL,MAAM,GACN,MAAM,GACN,OAAO,CAAA;AAIX,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAChD,MAAM,CAIR;AAUD,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,qFASpB,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE/D,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,yBAAoB,CAAA;AAI5D,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAA;IACd,6DAA6D;IAC7D,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,wDAAwD;IACxD,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,uBAAuB,CAkC/E;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,GAC5C,MAAM,CAuBR;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE;IACP,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,GACA,MAAM,CAuBR"}
|
|
@@ -4,6 +4,89 @@ export function resolveTitle(template, variables) {
|
|
|
4
4
|
return variables[key] ?? '';
|
|
5
5
|
});
|
|
6
6
|
}
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Site-wide title template (percent-token syntax).
|
|
9
|
+
//
|
|
10
|
+
// The Settings → SEO "Meta Defaults" title template uses `%token%` syntax so
|
|
11
|
+
// it reads naturally to non-developers (`%page% — %site%`). This is distinct
|
|
12
|
+
// from the `{{mustache}}` helper above, which powers per-collection templates.
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
/** Tokens supported by the site-wide title template. */
|
|
15
|
+
export const SITE_TITLE_TOKENS = [
|
|
16
|
+
'page',
|
|
17
|
+
'site',
|
|
18
|
+
'type',
|
|
19
|
+
'separator',
|
|
20
|
+
'author',
|
|
21
|
+
'date',
|
|
22
|
+
'category',
|
|
23
|
+
'tag',
|
|
24
|
+
];
|
|
25
|
+
/** Fallback used when no template is configured or the configured one is blank. */
|
|
26
|
+
export const DEFAULT_SITE_TITLE_TEMPLATE = '%page% — %site%';
|
|
27
|
+
const SITE_TOKEN_RE = /%(\w+)%/g;
|
|
28
|
+
/**
|
|
29
|
+
* Validate a site-wide title template. An empty/whitespace template is treated
|
|
30
|
+
* as valid (the renderer falls back to {@link DEFAULT_SITE_TITLE_TEMPLATE}).
|
|
31
|
+
* Unknown `%tokens%` and a template with no token *and* no literal text are
|
|
32
|
+
* rejected.
|
|
33
|
+
*/
|
|
34
|
+
export function validateTitleTemplate(template) {
|
|
35
|
+
const errors = [];
|
|
36
|
+
const unknownTokens = [];
|
|
37
|
+
if (!template || template.trim() === '') {
|
|
38
|
+
return { valid: true, unknownTokens, errors };
|
|
39
|
+
}
|
|
40
|
+
const known = new Set(SITE_TITLE_TOKENS);
|
|
41
|
+
const seen = [];
|
|
42
|
+
for (const match of template.matchAll(SITE_TOKEN_RE)) {
|
|
43
|
+
const token = match[1];
|
|
44
|
+
seen.push(token);
|
|
45
|
+
if (!known.has(token) && !unknownTokens.includes(token)) {
|
|
46
|
+
unknownTokens.push(token);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (unknownTokens.length > 0) {
|
|
50
|
+
errors.push(`Unsupported token${unknownTokens.length > 1 ? 's' : ''}: ${unknownTokens
|
|
51
|
+
.map((t) => `%${t}%`)
|
|
52
|
+
.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
// Must contain at least one known token OR some literal (non-token) text.
|
|
55
|
+
const literal = template.replace(SITE_TOKEN_RE, '').trim();
|
|
56
|
+
const hasKnownToken = seen.some((t) => known.has(t));
|
|
57
|
+
if (!hasKnownToken && literal === '') {
|
|
58
|
+
errors.push('Template must include at least one token or some literal text.');
|
|
59
|
+
}
|
|
60
|
+
return { valid: errors.length === 0, unknownTokens, errors };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Apply a site-wide title template. Unknown / empty tokens collapse cleanly so
|
|
64
|
+
* the result never contains dangling separators or double spaces. Falls back to
|
|
65
|
+
* {@link DEFAULT_SITE_TITLE_TEMPLATE} when the template is blank, and to the
|
|
66
|
+
* page title (then site name) if the rendered result is empty.
|
|
67
|
+
*/
|
|
68
|
+
export function applySiteTitleTemplate(template, vars) {
|
|
69
|
+
const tpl = template && template.trim() !== '' ? template : DEFAULT_SITE_TITLE_TEMPLATE;
|
|
70
|
+
const values = {
|
|
71
|
+
page: vars.page,
|
|
72
|
+
site: vars.site,
|
|
73
|
+
type: vars.type,
|
|
74
|
+
separator: vars.separator ?? '|',
|
|
75
|
+
author: vars.author,
|
|
76
|
+
date: vars.date,
|
|
77
|
+
category: vars.category,
|
|
78
|
+
tag: vars.tag,
|
|
79
|
+
};
|
|
80
|
+
const replaced = tpl.replace(SITE_TOKEN_RE, (_, key) => values[key] ?? '');
|
|
81
|
+
// Tidy up: collapse whitespace, then strip leading/trailing separator-only
|
|
82
|
+
// fragments left behind by empty tokens (e.g. "Page — " → "Page").
|
|
83
|
+
const cleaned = replaced
|
|
84
|
+
.replace(/\s+/g, ' ')
|
|
85
|
+
.replace(/^[\s\-–—|·•]+/, '')
|
|
86
|
+
.replace(/[\s\-–—|·•]+$/, '')
|
|
87
|
+
.trim();
|
|
88
|
+
return cleaned || vars.page?.trim() || vars.site?.trim() || '';
|
|
89
|
+
}
|
|
7
90
|
export function getDocumentTitle(config, options) {
|
|
8
91
|
if (options.metaTitle)
|
|
9
92
|
return options.metaTitle;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"title-templates.js","sourceRoot":"","sources":["../../src/seo/title-templates.ts"],"names":[],"mappings":"AAkBA,MAAM,cAAc,GAAG,gBAAgB,CAAA;AAEvC,MAAM,UAAU,YAAY,CAC1B,QAAgB,EAChB,SAAiD;IAEjD,OAAO,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,GAAW,EAAE,EAAE;QACzD,OAAQ,SAAgD,CAAC,GAAG,CAAC,IAAI,EAAE,CAAA;IACrE,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,MAA2B,EAC3B,OAOC;IAED,IAAI,OAAO,CAAC,SAAS;QAAE,OAAO,OAAO,CAAC,SAAS,CAAA;IAE/C,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,IAAI,KAAK,CAAA;IACrC,MAAM,QAAQ,GACZ,CAAC,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxE,MAAM,CAAC,eAAe;QACtB,8BAA8B,CAAA;IAEhC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;IACtB,MAAM,SAAS,GAA2C;QACxD,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG;QACH,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QAC7D,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QAC/B,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC;KACnD,CAAA;IAED,OAAO,YAAY,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;AAC1C,CAAC"}
|
|
1
|
+
{"version":3,"file":"title-templates.js","sourceRoot":"","sources":["../../src/seo/title-templates.ts"],"names":[],"mappings":"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"}
|