@better-seo/core 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +390 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -7
- package/dist/index.d.ts +106 -7
- package/dist/index.js +385 -59
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +1021 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +21 -0
- package/dist/node.d.ts +21 -0
- package/dist/node.js +980 -0
- package/dist/node.js.map +1 -0
- package/package.json +18 -12
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
var messages = {
|
|
3
3
|
VALIDATION: "Invalid or incomplete SEO input.",
|
|
4
4
|
ADAPTER_NOT_FOUND: "No adapter registered for this framework. Import the adapter package (e.g. @better-seo/next) before calling seoForFramework.",
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
USE_SEO_NOT_AVAILABLE: "useSEO is provided by @better-seo/react (Roadmap Wave 5 / FEATURES V3). App Router metadata should use seo() / prepareNextSeo from @better-seo/next.",
|
|
6
|
+
USE_SEO_NO_PROVIDER: "useSEO() must be used within <SEOProvider> from @better-seo/react."
|
|
7
7
|
};
|
|
8
8
|
var SEOError = class extends Error {
|
|
9
9
|
code;
|
|
@@ -40,6 +40,15 @@ function runAfterMergePlugins(seo, config) {
|
|
|
40
40
|
}
|
|
41
41
|
return acc;
|
|
42
42
|
}
|
|
43
|
+
function runOnRenderTagPlugins(tags, seo, config) {
|
|
44
|
+
const list = config?.plugins ?? [];
|
|
45
|
+
let acc = [...tags];
|
|
46
|
+
for (const p of list) {
|
|
47
|
+
const next = p.onRenderTags?.(acc, { seo, config });
|
|
48
|
+
if (next !== void 0) acc = [...next];
|
|
49
|
+
}
|
|
50
|
+
return acc;
|
|
51
|
+
}
|
|
43
52
|
|
|
44
53
|
// src/schema-dedupe.ts
|
|
45
54
|
function dedupeSchemaByIdAndType(schemas) {
|
|
@@ -98,12 +107,23 @@ function createSEO(input, config) {
|
|
|
98
107
|
const robots = mergedInput.meta?.robots ?? mergedInput.robots ?? config?.defaultRobots;
|
|
99
108
|
const langMap = mergedInput.meta?.alternates?.languages;
|
|
100
109
|
const alternates = langMap !== void 0 && Object.keys(langMap).length > 0 ? { languages: langMap } : void 0;
|
|
110
|
+
const verification = mergedInput.meta?.verification;
|
|
111
|
+
const hasVer = Boolean(
|
|
112
|
+
verification && (verification.google || verification.yahoo || verification.yandex || verification.me || verification.other !== void 0 && Object.keys(verification.other).length > 0)
|
|
113
|
+
);
|
|
114
|
+
const rawPag = mergedInput.meta?.pagination;
|
|
115
|
+
const pagination = rawPag?.previous !== void 0 || rawPag?.next !== void 0 ? {
|
|
116
|
+
...rawPag.previous !== void 0 ? { previous: rawPag.previous } : {},
|
|
117
|
+
...rawPag.next !== void 0 ? { next: rawPag.next } : {}
|
|
118
|
+
} : void 0;
|
|
101
119
|
const meta = {
|
|
102
120
|
title: applyTitleTemplate(title, config?.titleTemplate),
|
|
103
121
|
...description !== void 0 ? { description } : {},
|
|
104
122
|
...canonical !== void 0 ? { canonical } : {},
|
|
105
123
|
...robots !== void 0 ? { robots } : {},
|
|
106
|
-
...alternates !== void 0 ? { alternates } : {}
|
|
124
|
+
...alternates !== void 0 ? { alternates } : {},
|
|
125
|
+
...hasVer ? { verification } : {},
|
|
126
|
+
...pagination !== void 0 ? { pagination } : {}
|
|
107
127
|
};
|
|
108
128
|
const mergeOg = config?.features?.openGraphMerge !== false;
|
|
109
129
|
const ogBase = mergedInput.openGraph ?? {};
|
|
@@ -159,13 +179,34 @@ function mergeLanguageAlternates(parent, child) {
|
|
|
159
179
|
function withSEO(parent, child, config) {
|
|
160
180
|
return mergeSEO(parent, child, config);
|
|
161
181
|
}
|
|
182
|
+
function mergeVerification(parent, child) {
|
|
183
|
+
if (!parent && !child) return void 0;
|
|
184
|
+
const o = {
|
|
185
|
+
...parent,
|
|
186
|
+
...child,
|
|
187
|
+
other: parent?.other || child?.other ? { ...parent?.other ?? {}, ...child?.other ?? {} } : void 0
|
|
188
|
+
};
|
|
189
|
+
const has = o.google || o.yahoo || o.yandex || o.me || o.other && Object.keys(o.other).length > 0;
|
|
190
|
+
return has ? o : void 0;
|
|
191
|
+
}
|
|
192
|
+
function mergePagination(parent, child) {
|
|
193
|
+
if (!parent && !child) return void 0;
|
|
194
|
+
const out = {
|
|
195
|
+
...parent,
|
|
196
|
+
...child
|
|
197
|
+
};
|
|
198
|
+
if (out.previous === void 0 && out.next === void 0) return void 0;
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
162
201
|
function mergeSEO(parent, child, config) {
|
|
163
202
|
const pMeta = parent.meta;
|
|
164
203
|
const cMeta = child.meta ?? {};
|
|
165
|
-
const { alternates: pAlt, ...pRest } = pMeta;
|
|
166
|
-
const { alternates: cAlt, ...cRest } = cMeta;
|
|
204
|
+
const { alternates: pAlt, verification: pVer, pagination: pPag, ...pRest } = pMeta;
|
|
205
|
+
const { alternates: cAlt, verification: cVer, pagination: cPag, ...cRest } = cMeta;
|
|
167
206
|
const mergedLang = mergeLanguageAlternates(pAlt?.languages, cAlt?.languages);
|
|
168
207
|
const mergedAlternates = mergedLang !== void 0 ? { languages: mergedLang } : void 0;
|
|
208
|
+
const mergedVer = mergeVerification(pVer, cVer);
|
|
209
|
+
const mergedPag = mergePagination(pPag, cPag);
|
|
169
210
|
const mergedChild = {
|
|
170
211
|
...child,
|
|
171
212
|
meta: {
|
|
@@ -175,7 +216,9 @@ function mergeSEO(parent, child, config) {
|
|
|
175
216
|
description: cRest.description ?? child.description ?? pMeta.description,
|
|
176
217
|
canonical: cRest.canonical ?? child.canonical ?? pMeta.canonical,
|
|
177
218
|
robots: cRest.robots ?? child.robots ?? pMeta.robots,
|
|
178
|
-
...mergedAlternates !== void 0 ? { alternates: mergedAlternates } : {}
|
|
219
|
+
...mergedAlternates !== void 0 ? { alternates: mergedAlternates } : {},
|
|
220
|
+
...mergedVer !== void 0 ? { verification: mergedVer } : {},
|
|
221
|
+
...mergedPag !== void 0 ? { pagination: mergedPag } : {}
|
|
179
222
|
},
|
|
180
223
|
openGraph: { ...parent.openGraph ?? {}, ...child.openGraph },
|
|
181
224
|
twitter: { ...parent.twitter ?? {}, ...child.twitter },
|
|
@@ -323,7 +366,28 @@ function serializeJSONLD(data) {
|
|
|
323
366
|
}
|
|
324
367
|
|
|
325
368
|
// src/render.ts
|
|
326
|
-
function
|
|
369
|
+
function verificationMetaName(key) {
|
|
370
|
+
if (key === "google") return "google-site-verification";
|
|
371
|
+
if (key === "yahoo") return "y_key";
|
|
372
|
+
if (key === "yandex") return "yandex-verification";
|
|
373
|
+
if (key === "me") return "me";
|
|
374
|
+
return key;
|
|
375
|
+
}
|
|
376
|
+
function pushVerificationMeta(tags, v) {
|
|
377
|
+
const add = (name, content) => {
|
|
378
|
+
tags.push({ kind: "meta", name, content });
|
|
379
|
+
};
|
|
380
|
+
if (v.google) add(verificationMetaName("google"), v.google);
|
|
381
|
+
if (v.yahoo) add(verificationMetaName("yahoo"), v.yahoo);
|
|
382
|
+
if (v.yandex) add(verificationMetaName("yandex"), v.yandex);
|
|
383
|
+
if (v.me) add(verificationMetaName("me"), v.me);
|
|
384
|
+
if (v.other) {
|
|
385
|
+
for (const [k, val] of Object.entries(v.other)) {
|
|
386
|
+
add(verificationMetaName(k), val);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function renderTags(seo, config) {
|
|
327
391
|
const tags = [];
|
|
328
392
|
tags.push({ kind: "meta", name: "title", content: seo.meta.title });
|
|
329
393
|
if (seo.meta.description) {
|
|
@@ -335,6 +399,16 @@ function renderTags(seo) {
|
|
|
335
399
|
if (seo.meta.robots) {
|
|
336
400
|
tags.push({ kind: "meta", name: "robots", content: seo.meta.robots });
|
|
337
401
|
}
|
|
402
|
+
if (seo.meta.verification) {
|
|
403
|
+
pushVerificationMeta(tags, seo.meta.verification);
|
|
404
|
+
}
|
|
405
|
+
const pag = seo.meta.pagination;
|
|
406
|
+
if (pag?.previous) {
|
|
407
|
+
tags.push({ kind: "link", rel: "prev", href: pag.previous });
|
|
408
|
+
}
|
|
409
|
+
if (pag?.next) {
|
|
410
|
+
tags.push({ kind: "link", rel: "next", href: pag.next });
|
|
411
|
+
}
|
|
338
412
|
const langs = seo.meta.alternates?.languages;
|
|
339
413
|
if (langs) {
|
|
340
414
|
for (const [hreflang, href] of Object.entries(langs)) {
|
|
@@ -347,17 +421,83 @@ function renderTags(seo) {
|
|
|
347
421
|
if (seo.openGraph?.description) {
|
|
348
422
|
tags.push({ kind: "meta", property: "og:description", content: seo.openGraph.description });
|
|
349
423
|
}
|
|
424
|
+
if (seo.openGraph?.url) {
|
|
425
|
+
tags.push({ kind: "meta", property: "og:url", content: seo.openGraph.url });
|
|
426
|
+
}
|
|
427
|
+
if (seo.openGraph?.type) {
|
|
428
|
+
tags.push({ kind: "meta", property: "og:type", content: seo.openGraph.type });
|
|
429
|
+
}
|
|
430
|
+
if (seo.openGraph?.siteName) {
|
|
431
|
+
tags.push({ kind: "meta", property: "og:site_name", content: seo.openGraph.siteName });
|
|
432
|
+
}
|
|
433
|
+
if (seo.openGraph?.locale) {
|
|
434
|
+
tags.push({ kind: "meta", property: "og:locale", content: seo.openGraph.locale });
|
|
435
|
+
}
|
|
436
|
+
if (seo.openGraph?.publishedTime) {
|
|
437
|
+
tags.push({
|
|
438
|
+
kind: "meta",
|
|
439
|
+
property: "article:published_time",
|
|
440
|
+
content: seo.openGraph.publishedTime
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
if (seo.openGraph?.modifiedTime) {
|
|
444
|
+
tags.push({
|
|
445
|
+
kind: "meta",
|
|
446
|
+
property: "article:modified_time",
|
|
447
|
+
content: seo.openGraph.modifiedTime
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (seo.openGraph?.expirationTime) {
|
|
451
|
+
tags.push({
|
|
452
|
+
kind: "meta",
|
|
453
|
+
property: "article:expiration_time",
|
|
454
|
+
content: seo.openGraph.expirationTime
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
if (seo.openGraph?.section) {
|
|
458
|
+
tags.push({ kind: "meta", property: "article:section", content: seo.openGraph.section });
|
|
459
|
+
}
|
|
460
|
+
const authors = seo.openGraph?.authors;
|
|
461
|
+
if (authors?.length) {
|
|
462
|
+
for (const a of authors) {
|
|
463
|
+
if (a?.trim()) tags.push({ kind: "meta", property: "article:author", content: a.trim() });
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const ogTags = seo.openGraph?.tags;
|
|
467
|
+
if (ogTags?.length) {
|
|
468
|
+
for (const t of ogTags) {
|
|
469
|
+
if (t?.trim()) tags.push({ kind: "meta", property: "article:tag", content: t.trim() });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const ogVideos = seo.openGraph?.videos;
|
|
473
|
+
if (ogVideos?.length) {
|
|
474
|
+
for (const v of ogVideos) {
|
|
475
|
+
if (!v?.url) continue;
|
|
476
|
+
tags.push({ kind: "meta", property: "og:video", content: v.url });
|
|
477
|
+
if (v.secureUrl) {
|
|
478
|
+
tags.push({ kind: "meta", property: "og:video:secure_url", content: v.secureUrl });
|
|
479
|
+
}
|
|
480
|
+
if (v.type) tags.push({ kind: "meta", property: "og:video:type", content: v.type });
|
|
481
|
+
if (v.width !== void 0) {
|
|
482
|
+
tags.push({ kind: "meta", property: "og:video:width", content: String(v.width) });
|
|
483
|
+
}
|
|
484
|
+
if (v.height !== void 0) {
|
|
485
|
+
tags.push({ kind: "meta", property: "og:video:height", content: String(v.height) });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
350
489
|
const ogImages = seo.openGraph?.images;
|
|
351
490
|
if (ogImages?.length) {
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
491
|
+
for (const img of ogImages) {
|
|
492
|
+
if (img?.url) tags.push({ kind: "meta", property: "og:image", content: img.url });
|
|
493
|
+
if (img?.width !== void 0) {
|
|
494
|
+
tags.push({ kind: "meta", property: "og:image:width", content: String(img.width) });
|
|
495
|
+
}
|
|
496
|
+
if (img?.height !== void 0) {
|
|
497
|
+
tags.push({ kind: "meta", property: "og:image:height", content: String(img.height) });
|
|
498
|
+
}
|
|
499
|
+
if (img?.alt) tags.push({ kind: "meta", property: "og:image:alt", content: img.alt });
|
|
359
500
|
}
|
|
360
|
-
if (first?.alt) tags.push({ kind: "meta", property: "og:image:alt", content: first.alt });
|
|
361
501
|
}
|
|
362
502
|
if (seo.twitter?.card) {
|
|
363
503
|
tags.push({ kind: "meta", name: "twitter:card", content: seo.twitter.card });
|
|
@@ -368,13 +508,19 @@ function renderTags(seo) {
|
|
|
368
508
|
if (seo.twitter?.description) {
|
|
369
509
|
tags.push({ kind: "meta", name: "twitter:description", content: seo.twitter.description });
|
|
370
510
|
}
|
|
511
|
+
if (seo.twitter?.site) {
|
|
512
|
+
tags.push({ kind: "meta", name: "twitter:site", content: seo.twitter.site });
|
|
513
|
+
}
|
|
514
|
+
if (seo.twitter?.creator) {
|
|
515
|
+
tags.push({ kind: "meta", name: "twitter:creator", content: seo.twitter.creator });
|
|
516
|
+
}
|
|
371
517
|
if (seo.twitter?.image) {
|
|
372
518
|
tags.push({ kind: "meta", name: "twitter:image", content: seo.twitter.image });
|
|
373
519
|
}
|
|
374
520
|
for (const node of seo.schema) {
|
|
375
521
|
tags.push({ kind: "script-jsonld", json: serializeJSONLD(node) });
|
|
376
522
|
}
|
|
377
|
-
return tags;
|
|
523
|
+
return runOnRenderTagPlugins(tags, seo, config);
|
|
378
524
|
}
|
|
379
525
|
|
|
380
526
|
// src/validate.ts
|
|
@@ -430,6 +576,15 @@ function validateSEO(seo, options) {
|
|
|
430
576
|
severity: "warning"
|
|
431
577
|
});
|
|
432
578
|
}
|
|
579
|
+
const ogType = seo.openGraph?.type;
|
|
580
|
+
if (ogType === "article" && !(seo.openGraph?.publishedTime && String(seo.openGraph.publishedTime).trim())) {
|
|
581
|
+
issues.push({
|
|
582
|
+
code: "ARTICLE_PUBLISHED_TIME_RECOMMENDED",
|
|
583
|
+
field: "openGraph.publishedTime",
|
|
584
|
+
message: "openGraph.type is article; set publishedTime (ISO-8601) for richer crawlers",
|
|
585
|
+
severity: "warning"
|
|
586
|
+
});
|
|
587
|
+
}
|
|
433
588
|
for (let i = 0; i < seo.schema.length; i++) {
|
|
434
589
|
const node = seo.schema[i];
|
|
435
590
|
if (!node?.["@type"]) {
|
|
@@ -482,47 +637,14 @@ function getAdapter(id) {
|
|
|
482
637
|
function listAdapterIds() {
|
|
483
638
|
return [...adapters.keys()];
|
|
484
639
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
return
|
|
489
|
-
config,
|
|
490
|
-
createSEO: (input) => createSEO(input, config),
|
|
491
|
-
mergeSEO: (parent, child) => mergeSEO(parent, child, config)
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// src/singleton.ts
|
|
496
|
-
var globalConfig;
|
|
497
|
-
function initSEO(config) {
|
|
498
|
-
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
499
|
-
console.warn(
|
|
500
|
-
"[@better-seo/core] \u26A0\uFE0F initSEO() uses global state and is NOT safe for multi-tenant or serverless environments. Use createSEOContext() instead. See: https://github.com/OWNER/better-seo-js/blob/main/internal-docs/ARCHITECTURE.md"
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
globalConfig = config;
|
|
504
|
-
}
|
|
505
|
-
function getGlobalSEOConfig() {
|
|
506
|
-
return globalConfig;
|
|
507
|
-
}
|
|
508
|
-
function resetSEOConfigForTests() {
|
|
509
|
-
globalConfig = void 0;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// src/voila.ts
|
|
513
|
-
function seoForFramework(adapterId, input, config) {
|
|
514
|
-
const adapter = getAdapter(adapterId);
|
|
515
|
-
if (!adapter) {
|
|
516
|
-
throw new SEOError(
|
|
517
|
-
"ADAPTER_NOT_FOUND",
|
|
518
|
-
`no adapter "${adapterId}" registered (import your framework package, e.g. @better-seo/next).`
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
const doc = createSEO(input, config);
|
|
522
|
-
return adapter.toFramework(doc);
|
|
640
|
+
function detectFramework() {
|
|
641
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
642
|
+
if (process.env.NEXT_RUNTIME) return "next";
|
|
643
|
+
return void 0;
|
|
523
644
|
}
|
|
524
|
-
function
|
|
525
|
-
|
|
645
|
+
function getDefaultAdapter() {
|
|
646
|
+
const id = detectFramework();
|
|
647
|
+
return id ? getAdapter(id) : void 0;
|
|
526
648
|
}
|
|
527
649
|
|
|
528
650
|
// src/rules.ts
|
|
@@ -610,11 +732,215 @@ function createSEOForRoute(route, input, rules, config) {
|
|
|
610
732
|
return createSEO(mergedInput, config);
|
|
611
733
|
}
|
|
612
734
|
|
|
735
|
+
// src/context.ts
|
|
736
|
+
function createSEOContext(config) {
|
|
737
|
+
const rules = config.rules ?? [];
|
|
738
|
+
return {
|
|
739
|
+
config,
|
|
740
|
+
createSEO: (input) => createSEO(input, config),
|
|
741
|
+
createSEOForRoute: (route, input) => createSEOForRoute(route, input, rules, config),
|
|
742
|
+
mergeSEO: (parent, child) => mergeSEO(parent, child, config)
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/singleton.ts
|
|
747
|
+
var globalConfig;
|
|
748
|
+
function initSEO(config) {
|
|
749
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
750
|
+
console.warn(
|
|
751
|
+
"[@better-seo/core] \u26A0\uFE0F initSEO() uses global state and is NOT safe for multi-tenant or serverless environments. Use createSEOContext() instead. See: https://github.com/OWNER/better-seo-js/blob/main/internal-docs/ARCHITECTURE.md"
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
globalConfig = config;
|
|
755
|
+
}
|
|
756
|
+
function getGlobalSEOConfig() {
|
|
757
|
+
return globalConfig;
|
|
758
|
+
}
|
|
759
|
+
function resetSEOConfigForTests() {
|
|
760
|
+
globalConfig = void 0;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// src/voila.ts
|
|
764
|
+
function seoForFramework(adapterId, input, config) {
|
|
765
|
+
const adapter = getAdapter(adapterId);
|
|
766
|
+
if (!adapter) {
|
|
767
|
+
throw new SEOError(
|
|
768
|
+
"ADAPTER_NOT_FOUND",
|
|
769
|
+
`no adapter "${adapterId}" registered (import your framework package, e.g. @better-seo/next).`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
const doc = createSEO(input, config);
|
|
773
|
+
return adapter.toFramework(doc);
|
|
774
|
+
}
|
|
775
|
+
function useSEO() {
|
|
776
|
+
throw new SEOError("USE_SEO_NOT_AVAILABLE");
|
|
777
|
+
}
|
|
778
|
+
function seoRoute(route, input, config) {
|
|
779
|
+
return createSEOForRoute(route, input, config?.rules ?? [], config);
|
|
780
|
+
}
|
|
781
|
+
|
|
613
782
|
// src/migrate.ts
|
|
614
|
-
function
|
|
615
|
-
|
|
783
|
+
function isObj(v) {
|
|
784
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
785
|
+
}
|
|
786
|
+
function pickStr(v) {
|
|
787
|
+
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
788
|
+
}
|
|
789
|
+
function mapOgImages(raw) {
|
|
790
|
+
if (!Array.isArray(raw)) return void 0;
|
|
791
|
+
const out = [];
|
|
792
|
+
for (const item of raw) {
|
|
793
|
+
if (typeof item === "string") {
|
|
794
|
+
out.push({ url: item });
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
if (!isObj(item)) continue;
|
|
798
|
+
const url = pickStr(item.url);
|
|
799
|
+
if (!url) continue;
|
|
800
|
+
out.push({
|
|
801
|
+
url,
|
|
802
|
+
...typeof item.width === "number" ? { width: item.width } : {},
|
|
803
|
+
...typeof item.height === "number" ? { height: item.height } : {},
|
|
804
|
+
...pickStr(item.alt) !== void 0 ? { alt: pickStr(item.alt) } : {}
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
return out.length > 0 ? out : void 0;
|
|
808
|
+
}
|
|
809
|
+
function fromNextSeo(nextSeoExport) {
|
|
810
|
+
if (!isObj(nextSeoExport)) {
|
|
811
|
+
throw new SEOError("VALIDATION", "fromNextSeo expects a plain object (e.g. next-seo props).");
|
|
812
|
+
}
|
|
813
|
+
const o = nextSeoExport;
|
|
814
|
+
const og = isObj(o.openGraph) ? o.openGraph : void 0;
|
|
815
|
+
const tw = isObj(o.twitter) ? o.twitter : void 0;
|
|
816
|
+
const title = pickStr(o.title) ?? pickStr(o.defaultTitle) ?? (og ? pickStr(og.title) : void 0) ?? (tw ? pickStr(tw.title) : void 0);
|
|
817
|
+
const description = pickStr(o.description) ?? (og ? pickStr(og.description) : void 0) ?? (tw ? pickStr(tw.description) : void 0);
|
|
818
|
+
if (!title) {
|
|
819
|
+
throw new SEOError(
|
|
820
|
+
"VALIDATION",
|
|
821
|
+
"fromNextSeo could not infer a title. Set title, defaultTitle, or openGraph.title."
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
const canonical = pickStr(o.canonical) ?? (og ? pickStr(og.url) : void 0);
|
|
825
|
+
const noindex = o.noindex === true;
|
|
826
|
+
const nofollow = o.nofollow === true;
|
|
827
|
+
const robots = noindex || nofollow ? `${noindex ? "noindex" : "index"}, ${nofollow ? "nofollow" : "follow"}` : void 0;
|
|
828
|
+
let openGraph;
|
|
829
|
+
if (og) {
|
|
830
|
+
const images = mapOgImages(og.images);
|
|
831
|
+
const siteName = pickStr(og.siteName) ?? pickStr(og.site_name);
|
|
832
|
+
openGraph = {
|
|
833
|
+
...pickStr(og.title) !== void 0 ? { title: pickStr(og.title) } : {},
|
|
834
|
+
...pickStr(og.description) !== void 0 ? { description: pickStr(og.description) } : {},
|
|
835
|
+
...pickStr(og.url) !== void 0 ? { url: pickStr(og.url) } : {},
|
|
836
|
+
...pickStr(og.type) !== void 0 ? { type: pickStr(og.type) } : {},
|
|
837
|
+
...siteName !== void 0 ? { siteName } : {},
|
|
838
|
+
...pickStr(og.locale) !== void 0 ? { locale: pickStr(og.locale) } : {},
|
|
839
|
+
...images !== void 0 ? { images } : {}
|
|
840
|
+
};
|
|
841
|
+
if (Object.keys(openGraph).length === 0) openGraph = void 0;
|
|
842
|
+
}
|
|
843
|
+
let twitter;
|
|
844
|
+
if (tw) {
|
|
845
|
+
const cardRaw = pickStr(tw.card);
|
|
846
|
+
const card = cardRaw === "summary" || cardRaw === "summary_large_image" ? cardRaw : void 0;
|
|
847
|
+
const twImage = pickStr(tw.image) ?? pickStr(tw.imageSrc);
|
|
848
|
+
const twSite = pickStr(tw.site);
|
|
849
|
+
const twCreator = pickStr(tw.handle) ?? pickStr(tw.creator);
|
|
850
|
+
twitter = {
|
|
851
|
+
...card !== void 0 ? { card } : {},
|
|
852
|
+
...pickStr(tw.title) !== void 0 ? { title: pickStr(tw.title) } : {},
|
|
853
|
+
...pickStr(tw.description) !== void 0 ? { description: pickStr(tw.description) } : {},
|
|
854
|
+
...twImage !== void 0 ? { image: twImage } : {},
|
|
855
|
+
...twSite !== void 0 ? { site: twSite } : {},
|
|
856
|
+
...twCreator !== void 0 ? { creator: twCreator } : {}
|
|
857
|
+
};
|
|
858
|
+
if (Object.keys(twitter).length === 0) twitter = void 0;
|
|
859
|
+
}
|
|
860
|
+
return {
|
|
861
|
+
title,
|
|
862
|
+
...description !== void 0 ? { description } : {},
|
|
863
|
+
...canonical !== void 0 ? { canonical } : {},
|
|
864
|
+
...robots !== void 0 ? { robots } : {},
|
|
865
|
+
...openGraph !== void 0 ? { openGraph } : {},
|
|
866
|
+
...twitter !== void 0 ? { twitter } : {}
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/from-content.ts
|
|
871
|
+
function stripHtmlish(s) {
|
|
872
|
+
return s.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
873
|
+
}
|
|
874
|
+
function parseSimpleFrontmatter(raw) {
|
|
875
|
+
if (!raw.startsWith("---\n")) return { body: raw };
|
|
876
|
+
const end = raw.indexOf("\n---\n", 4);
|
|
877
|
+
if (end === -1) return { body: raw };
|
|
878
|
+
const fmBlock = raw.slice(4, end);
|
|
879
|
+
const body = raw.slice(end + 5).trim();
|
|
880
|
+
let title;
|
|
881
|
+
let description;
|
|
882
|
+
for (const line of fmBlock.split("\n")) {
|
|
883
|
+
const m = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line.trim());
|
|
884
|
+
if (!m) continue;
|
|
885
|
+
const key = m[1].toLowerCase();
|
|
886
|
+
let val = m[2].trim();
|
|
887
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
888
|
+
val = val.slice(1, -1);
|
|
889
|
+
}
|
|
890
|
+
if (key === "title") title = val;
|
|
891
|
+
if (key === "description") description = val;
|
|
892
|
+
}
|
|
893
|
+
return { body, title, description };
|
|
894
|
+
}
|
|
895
|
+
function fromContent(markdownOrPlain, options) {
|
|
896
|
+
const maxD = options?.maxDescriptionLength ?? 300;
|
|
897
|
+
const inferTitle = options?.inferTitleFromBody !== false;
|
|
898
|
+
const normalized = markdownOrPlain.replace(/\r\n/g, "\n");
|
|
899
|
+
const { body: fmBody, title: fmTitle, description: fmDesc } = parseSimpleFrontmatter(normalized);
|
|
900
|
+
let body = fmBody.trim();
|
|
901
|
+
if (!body && !fmTitle) {
|
|
902
|
+
return { title: "Untitled" };
|
|
903
|
+
}
|
|
904
|
+
let title = fmTitle;
|
|
905
|
+
let description = fmDesc;
|
|
906
|
+
const importStripped = body.split("\n").filter((line) => !/^\s*import\s+/.test(line)).join("\n").trim();
|
|
907
|
+
body = importStripped || body;
|
|
908
|
+
if (inferTitle) {
|
|
909
|
+
const h1 = /^#\s+(.+)$/m.exec(body);
|
|
910
|
+
if (!title && h1) {
|
|
911
|
+
title = h1[1].trim();
|
|
912
|
+
body = body.replace(h1[0], "").trim();
|
|
913
|
+
}
|
|
914
|
+
if (!title) {
|
|
915
|
+
const lines = body.split("\n");
|
|
916
|
+
const first = lines.find((l) => l.trim().length > 0) ?? "";
|
|
917
|
+
title = stripHtmlish(first).slice(0, 200) || "Untitled";
|
|
918
|
+
const idx = body.indexOf(first);
|
|
919
|
+
if (idx >= 0) body = body.slice(idx + first.length).trim();
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
if (!description) {
|
|
923
|
+
const chunk = body.split(/\n\n+/).find((p) => p.trim().length > 0) ?? "";
|
|
924
|
+
const plain = stripHtmlish(chunk.replace(/^#+\s+.*$/m, "").trim());
|
|
925
|
+
description = plain.length > 0 ? plain.slice(0, maxD) : void 0;
|
|
926
|
+
}
|
|
927
|
+
const out = {};
|
|
928
|
+
if (title !== void 0) out.title = title;
|
|
929
|
+
if (description !== void 0) out.description = description;
|
|
930
|
+
if (out.title === void 0 && out.description === void 0) {
|
|
931
|
+
return { title: "Untitled" };
|
|
932
|
+
}
|
|
933
|
+
return out;
|
|
934
|
+
}
|
|
935
|
+
function fromMdxString(source, options) {
|
|
936
|
+
return fromContent(source, options);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// src/define-seo.ts
|
|
940
|
+
function defineSEO(input) {
|
|
941
|
+
return input;
|
|
616
942
|
}
|
|
617
943
|
|
|
618
|
-
export { SEOError, applyRules, applyRulesToSEO, article, breadcrumbList, createSEO, createSEOContext, createSEOForRoute, customSchema, defineSEOPlugin, faqPage, fromNextSeo, getAdapter, getGlobalSEOConfig, initSEO, isSEOError, listAdapterIds, mergeSEO, organization, person, product, registerAdapter, renderTags, resetSEOConfigForTests, seoForFramework, serializeJSONLD, techArticle, useSEO, validateSEO, webPage, withSEO };
|
|
944
|
+
export { SEOError, applyRules, applyRulesToSEO, article, breadcrumbList, createSEO, createSEOContext, createSEOForRoute, customSchema, defineSEO, defineSEOPlugin, detectFramework, faqPage, fromContent, fromMdxString, fromNextSeo, getAdapter, getDefaultAdapter, getGlobalSEOConfig, initSEO, isSEOError, listAdapterIds, mergeSEO, organization, person, product, registerAdapter, renderTags, resetSEOConfigForTests, seoForFramework, seoRoute, serializeJSONLD, techArticle, useSEO, validateSEO, webPage, withSEO };
|
|
619
945
|
//# sourceMappingURL=index.js.map
|
|
620
946
|
//# sourceMappingURL=index.js.map
|