sanity-plugin-seofields 1.9.0 → 1.10.1

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.
Files changed (56) hide show
  1. package/README.md +71 -0
  2. package/dist/{SeoPreview-LMZAWWOE.cjs → SeoPreview-A2XZVMT2.cjs} +4 -4
  3. package/dist/{SeoPreview-LMZAWWOE.cjs.map → SeoPreview-A2XZVMT2.cjs.map} +1 -1
  4. package/dist/{SeoPreview-UNQBKTQO.js → SeoPreview-TATJQFMP.js} +2 -2
  5. package/dist/chunk-CRCXC45D.js +56 -0
  6. package/dist/chunk-CRCXC45D.js.map +1 -0
  7. package/dist/{chunk-HHO2AKAP.js → chunk-DB5MBPRK.js} +13 -13
  8. package/dist/chunk-DB5MBPRK.js.map +1 -0
  9. package/dist/{chunk-3OK3S2F7.cjs → chunk-EUG4MNRC.cjs} +60 -20
  10. package/dist/chunk-EUG4MNRC.cjs.map +1 -0
  11. package/dist/{chunk-Y46ACXM4.cjs → chunk-LHUFL5LS.cjs} +13 -13
  12. package/dist/chunk-LHUFL5LS.cjs.map +1 -0
  13. package/dist/{chunk-Z5AOUU4H.js → chunk-NLEB47UV.js} +60 -20
  14. package/dist/chunk-NLEB47UV.js.map +1 -0
  15. package/dist/{chunk-FUUWBQE2.cjs → chunk-OULUDJPI.cjs} +66 -7
  16. package/dist/chunk-OULUDJPI.cjs.map +1 -0
  17. package/dist/{chunk-KIEO6QG3.js → chunk-XD4HKLEA.js} +66 -7
  18. package/dist/chunk-XD4HKLEA.js.map +1 -0
  19. package/dist/chunk-XYAZJ3WX.cjs +56 -0
  20. package/dist/chunk-XYAZJ3WX.cjs.map +1 -0
  21. package/dist/cli.js +1 -1
  22. package/dist/head.cjs +10 -2
  23. package/dist/head.cjs.map +1 -1
  24. package/dist/head.d.cts +61 -1
  25. package/dist/head.d.ts +61 -1
  26. package/dist/head.js +9 -1
  27. package/dist/hreflang-_VbSuWYD.d.cts +29 -0
  28. package/dist/hreflang-_VbSuWYD.d.ts +29 -0
  29. package/dist/index.cjs +173 -38
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +3 -2
  32. package/dist/index.d.ts +3 -2
  33. package/dist/index.js +336 -201
  34. package/dist/index.js.map +1 -1
  35. package/dist/next.cjs +11 -3
  36. package/dist/next.cjs.map +1 -1
  37. package/dist/next.d.cts +2 -1
  38. package/dist/next.d.ts +2 -1
  39. package/dist/next.js +9 -1
  40. package/dist/next.js.map +1 -1
  41. package/dist/{plugin-DoGeZP79.d.cts → plugin-HFuWZsJu.d.cts} +68 -1
  42. package/dist/{plugin-DQdThyxG.d.ts → plugin-oQUCim56.d.ts} +68 -1
  43. package/dist/server.cjs +2 -2
  44. package/dist/server.cjs.map +1 -1
  45. package/dist/server.d.cts +6 -8
  46. package/dist/server.d.ts +6 -8
  47. package/dist/server.js +1 -1
  48. package/dist/server.js.map +1 -1
  49. package/package.json +2 -2
  50. package/dist/chunk-3OK3S2F7.cjs.map +0 -1
  51. package/dist/chunk-FUUWBQE2.cjs.map +0 -1
  52. package/dist/chunk-HHO2AKAP.js.map +0 -1
  53. package/dist/chunk-KIEO6QG3.js.map +0 -1
  54. package/dist/chunk-Y46ACXM4.cjs.map +0 -1
  55. package/dist/chunk-Z5AOUU4H.js.map +0 -1
  56. /package/dist/{SeoPreview-UNQBKTQO.js.map → SeoPreview-TATJQFMP.js.map} +0 -0
@@ -1,5 +1,8 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
+ var _chunkXYAZJ3WXcjs = require('./chunk-XYAZJ3WX.cjs');
4
+
5
+
3
6
  var _chunkA57XNQC7cjs = require('./chunk-A57XNQC7.cjs');
4
7
 
5
8
  // src/helpers/seoMeta.ts
@@ -89,14 +92,12 @@ function customMetaUsesProperty(name) {
89
92
  }
90
93
  function buildSeoMeta(options) {
91
94
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N;
92
- const { seo, baseUrl = "", path = "", defaults = {}, imageUrlResolver } = options;
93
- const normalizedBase = baseUrl.replace(/\/+$/, "");
94
- const normalizedPath = path.replace(/^\/+/, "");
95
- const fullUrl = [normalizedBase, normalizedPath].filter(Boolean).join("/");
95
+ const { seo, baseUrl = "", path = "", defaults = {}, imageUrlResolver, hreflangs } = options;
96
+ const fullUrl = _chunkXYAZJ3WXcjs.joinUrl.call(void 0, baseUrl, path);
96
97
  const ogImageURL = resolveOgImage(seo, defaults, imageUrlResolver);
97
98
  const twitterImageURL = resolveTwitterImage(seo, ogImageURL, imageUrlResolver);
98
99
  const other = buildCustomMetaMap(seo);
99
- const languages = buildHreflangMap(seo == null ? void 0 : seo.hreflangs);
100
+ const languages = buildHreflangMap(hreflangs != null ? hreflangs : seo == null ? void 0 : seo.hreflangs);
100
101
  const ogUrl = ((_a = seo == null ? void 0 : seo.openGraph) == null ? void 0 : _a.url) || fullUrl;
101
102
  return _chunkA57XNQC7cjs.__spreadValues.call(void 0, {
102
103
  title: (_c = (_b = seo == null ? void 0 : seo.title) != null ? _b : defaults.title) != null ? _c : null,
@@ -186,10 +187,68 @@ function buildSeoHead(options) {
186
187
  };
187
188
  }
188
189
 
190
+ // src/helpers/llmsTxt.ts
191
+ function resolveLinkUrl(url, baseUrl) {
192
+ if (!baseUrl) return url;
193
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(url) || url.startsWith("//")) return url;
194
+ return _chunkXYAZJ3WXcjs.joinUrl.call(void 0, baseUrl, url);
195
+ }
196
+ function renderSection(section, baseUrl) {
197
+ const links = section.links.filter((l) => l && l.title && l.url);
198
+ if (links.length === 0) return null;
199
+ const lines = links.map((l) => {
200
+ const url = resolveLinkUrl(l.url, baseUrl);
201
+ const desc = l.description ? `: ${l.description}` : "";
202
+ return `- [${l.title}](${url})${desc}`;
203
+ });
204
+ return `## ${section.title}
205
+
206
+ ${lines.join("\n")}`;
207
+ }
208
+ function buildLlmsTxt(options) {
209
+ const { title, summary, details, sections = [], version, updated, baseUrl } = options;
210
+ const blocks = [];
211
+ if (version || updated) {
212
+ const parts = [version ? `Version: ${version}` : "", updated ? `Updated: ${updated}` : ""].filter(Boolean).join(" | ");
213
+ blocks.push(`> ${parts}`);
214
+ }
215
+ blocks.push(`# ${title}`);
216
+ if (summary) blocks.push(`> ${summary}`);
217
+ if (details) blocks.push(details.trim());
218
+ for (const section of sections) {
219
+ const rendered = renderSection(section, baseUrl);
220
+ if (rendered) blocks.push(rendered);
221
+ }
222
+ return `${blocks.join("\n\n")}
223
+ `;
224
+ }
225
+ function slugToPath(slug) {
226
+ var _a;
227
+ const value = typeof slug === "string" ? slug : (_a = slug == null ? void 0 : slug.current) != null ? _a : void 0;
228
+ return value ? `/${value.replace(/^\/+/, "")}` : void 0;
229
+ }
230
+ function docsToLlmsSection(docs, options) {
231
+ var _a, _b, _c;
232
+ const { title, baseUrl, resolvePath, resolveTitle, resolveDescription } = options;
233
+ const links = [];
234
+ for (const doc of docs != null ? docs : []) {
235
+ if (!doc) continue;
236
+ const linkTitle = (_a = resolveTitle ? resolveTitle(doc) : doc.title) != null ? _a : void 0;
237
+ const path = (_b = resolvePath ? resolvePath(doc) : slugToPath(doc.slug)) != null ? _b : void 0;
238
+ if (!linkTitle || !path) continue;
239
+ const url = baseUrl ? _chunkXYAZJ3WXcjs.joinUrl.call(void 0, baseUrl, path) : path;
240
+ const description = (_c = resolveDescription ? resolveDescription(doc) : doc.description) != null ? _c : void 0;
241
+ links.push(_chunkA57XNQC7cjs.__spreadValues.call(void 0, { title: linkTitle, url }, description ? { description } : {}));
242
+ }
243
+ return { title, links };
244
+ }
245
+
246
+
247
+
189
248
 
190
249
 
191
250
 
192
251
 
193
252
 
194
- exports.sanitizeOGType = sanitizeOGType; exports.sanitizeTwitterCard = sanitizeTwitterCard; exports.buildSeoMeta = buildSeoMeta; exports.buildSeoHead = buildSeoHead;
195
- //# sourceMappingURL=chunk-FUUWBQE2.cjs.map
253
+ exports.sanitizeOGType = sanitizeOGType; exports.sanitizeTwitterCard = sanitizeTwitterCard; exports.buildSeoMeta = buildSeoMeta; exports.buildSeoHead = buildSeoHead; exports.buildLlmsTxt = buildLlmsTxt; exports.docsToLlmsSection = docsToLlmsSection;
254
+ //# sourceMappingURL=chunk-OULUDJPI.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/hardik/GITHUB/sanity-plugin-seofields/npm/dist/chunk-OULUDJPI.cjs","../src/helpers/seoMeta.ts","../src/helpers/llmsTxt.ts"],"names":[],"mappings":"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","file":"/Users/hardik/GITHUB/sanity-plugin-seofields/npm/dist/chunk-OULUDJPI.cjs","sourcesContent":[null,"/**\n * Headless CMS integration helpers for sanity-plugin-seofields\n *\n * Provides framework-agnostic SEO metadata utilities for use with:\n * - Next.js App Router → buildSeoMeta() inside generateMetadata()\n * - Next.js Pages Router → <SeoMetaTags> inside Next.js <Head>\n * - Astro / Nuxt / Vue / SvelteKit / Remix → buildSeoHead() into native head APIs\n */\n\nimport type {SanityImage, SanityImageWithAlt} from '../types'\nimport {joinUrl} from './url'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n/** Structured metadata returned by buildSeoMeta(). Compatible with Next.js Metadata (App Router). */\nexport interface SeoMetadata {\n title?: string | null\n description?: string | null\n keywords?: string[]\n robots?: {\n index?: boolean\n follow?: boolean\n notranslate?: boolean\n noimageindex?: boolean\n googleBot?: {\n index?: boolean\n follow?: boolean\n notranslate?: boolean\n noimageindex?: boolean\n }\n }\n openGraph?: {\n type?: string\n url?: string\n title?: string\n description?: string\n siteName?: string\n images?: Array<{url: string; width?: number; height?: number; alt?: string}>\n }\n twitter?: {\n card?: string\n site?: string\n creator?: string\n title?: string\n description?: string\n images?: string[]\n }\n alternates?: {\n canonical?: string\n languages?: Record<string, string>\n }\n /** Any custom meta attributes from seo.metaAttributes */\n other?: Record<string, string>\n}\n\n/** Plain `<meta>` tag data returned by buildSeoHead(). */\nexport type SeoHeadMetaTag = {name: string; content: string} | {property: string; content: string}\n\n/** Plain `<link>` tag data returned by buildSeoHead(). */\nexport interface SeoHeadLinkTag {\n rel: string\n href: string\n hreflang?: string\n}\n\n/**\n * Framework-neutral head data returned by buildSeoHead().\n *\n * Use this in Astro, Nuxt, Vue, SvelteKit, Remix, or any frontend where you\n * need plain values instead of React elements or Next.js Metadata.\n */\nexport interface SeoHead {\n title?: string | null\n meta: SeoHeadMetaTag[]\n link: SeoHeadLinkTag[]\n}\n\n/** Default values used when SEO fields are missing. */\nexport interface SeoMetaDefaults {\n title?: string\n description?: string\n siteName?: string\n twitterSite?: string\n twitterCreator?: string\n /** Fallback image URL when no OG / Twitter image is set. */\n ogImage?: string\n}\n\n/**\n * Permissive image shape accepted by buildSeoMeta — compatible with both the\n * plugin's SanityImage and Sanity's code-generated image type (where `asset`\n * and `alt` are optional).\n */\ninterface SeoImageInput {\n _type?: string\n asset?: {_ref: string; _type: string; _weak?: boolean; [key: string]: unknown}\n hotspot?: unknown\n crop?: unknown\n alt?: string\n}\n\n/**\n * Input-compatible variant of SeoFields. Structurally matches Sanity's\n * code-generated types (where `asset`, `alt`, `key`, and `type` are all\n * optional), so you can pass `data.seo` from a sanityFetch result directly\n * without any `as any` or manual casting.\n */\nexport interface SeoFieldsInput {\n _type?: string\n robots?: {\n noIndex?: boolean | null\n noFollow?: boolean | null\n noTranslate?: boolean | null\n noImageIndex?: boolean | null\n } | null\n title?: string | null\n description?: string | null\n metaImage?: SeoImageInput | null\n metaAttributes?: Array<{_key?: string; key?: string; value?: string; type?: string}> | null\n keywords?: string[] | null\n canonicalUrl?: string | null\n hreflangs?: Array<{locale?: string | null; url?: string | null}> | null\n openGraph?: {\n _type?: string\n url?: string | null\n title?: string | null\n description?: string | null\n siteName?: string | null\n type?: string | null\n imageType?: string | null\n image?: SeoImageInput | null\n imageUrl?: string | null\n } | null\n twitter?: {\n _type?: string\n card?: string | null\n site?: string | null\n creator?: string | null\n title?: string | null\n description?: string | null\n imageType?: string | null\n image?: SeoImageInput | null\n imageUrl?: string | null\n } | null\n}\n\n/** Options accepted by buildSeoMeta(). */\nexport interface BuildSeoMetaOptions {\n /**\n * The raw SEO object from Sanity (_type excluded or included — both work).\n * Pass `null` or `undefined` to fall back entirely to `defaults`.\n *\n * Accepts both the strict plugin `SeoFields` type and Sanity's code-generated\n * type (which has all nested fields optional) without any `as any` cast.\n */\n seo?: SeoFieldsInput | null\n\n /**\n * The base URL of your site, e.g. \"https://example.com\".\n * Used for canonical URL and OpenGraph URL construction.\n */\n baseUrl?: string\n\n /**\n * The path for the current page, e.g. \"/about\".\n * Combined with baseUrl to produce the canonical + OG url.\n * Defaults to \"\".\n */\n path?: string\n\n /**\n * Default values used when the Sanity SEO fields are empty / missing.\n */\n defaults?: SeoMetaDefaults\n\n /**\n * Resolve a Sanity image asset to a plain URL string.\n *\n * @example (using @sanity/image-url)\n * imageUrlResolver: (img) => urlFor(img).width(1200).url()\n */\n imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined\n\n /**\n * Override the hreflang alternates. When provided, this supersedes `seo.hreflangs` — pair it with\n * `buildHreflangs()` to derive alternates from `@sanity/document-internationalization` translations.\n *\n * @example\n * hreflangs: buildHreflangs(data._translations, { baseUrl })\n */\n hreflangs?: Array<{locale?: string | null; url?: string | null}> | null\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\nconst VALID_OG_TYPES = [\n 'website',\n 'article',\n 'profile',\n 'book',\n 'music',\n 'video',\n 'product',\n] as const\ntype OGType = (typeof VALID_OG_TYPES)[number]\n\n/**\n * Coerce an arbitrary string to a valid OpenGraph type.\n * Falls back to \"website\" when the value is invalid.\n */\nexport function sanitizeOGType(value?: string): OGType {\n if (value && (VALID_OG_TYPES as readonly string[]).includes(value)) {\n return value as OGType\n }\n return 'website'\n}\n\nconst VALID_TWITTER_CARDS = ['summary', 'summary_large_image', 'app', 'player'] as const\ntype TwitterCard = (typeof VALID_TWITTER_CARDS)[number]\n\n/**\n * Coerce an arbitrary string to a valid Twitter card type.\n * Falls back to \"summary_large_image\" when the value is invalid.\n */\nexport function sanitizeTwitterCard(value?: string): TwitterCard {\n if (value && (VALID_TWITTER_CARDS as readonly string[]).includes(value)) {\n return value as TwitterCard\n }\n return 'summary_large_image'\n}\n\n/**\n * Resolve OG image URL from SEO fields or defaults.\n */\nfunction resolveOgImage(\n seo: SeoFieldsInput | null | undefined,\n defaults: SeoMetaDefaults,\n imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined,\n): string {\n if (seo?.openGraph?.imageType === 'url' && seo.openGraph.imageUrl) {\n return seo.openGraph.imageUrl\n }\n if (seo?.openGraph?.image && imageUrlResolver) {\n return imageUrlResolver(seo.openGraph.image as SanityImage) || defaults.ogImage || ''\n }\n if (seo?.metaImage && imageUrlResolver) {\n return imageUrlResolver(seo.metaImage as SanityImage) || defaults.ogImage || ''\n }\n return defaults.ogImage || ''\n}\n\n/**\n * Resolve Twitter image URL from SEO fields, falling back to OG image.\n */\nfunction resolveTwitterImage(\n seo: SeoFieldsInput | null | undefined,\n ogImageURL: string,\n imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined,\n): string {\n if (seo?.twitter?.imageType === 'url' && seo.twitter.imageUrl) {\n return seo.twitter.imageUrl\n }\n if (seo?.twitter?.image && imageUrlResolver) {\n return imageUrlResolver(seo.twitter.image as SanityImage) || ogImageURL\n }\n return ogImageURL\n}\n\n/**\n * Build custom meta attributes map from SEO fields.\n */\nfunction buildCustomMetaMap(seo: SeoFieldsInput | null | undefined): Record<string, string> {\n const other: Record<string, string> = {}\n if (Array.isArray(seo?.metaAttributes)) {\n for (const attr of seo!.metaAttributes!) {\n if (attr.key && attr.value) {\n other[attr.key] = attr.value\n }\n }\n }\n return other\n}\n\nfunction buildHreflangMap(\n hreflangs: SeoFieldsInput['hreflangs'] | null | undefined,\n): Record<string, string> | undefined {\n if (!Array.isArray(hreflangs)) return undefined\n\n const languages: Record<string, string> = {}\n for (const entry of hreflangs) {\n if (entry?.locale && entry.url) {\n languages[entry.locale] = entry.url\n }\n }\n\n return Object.keys(languages).length > 0 ? languages : undefined\n}\n\nfunction robotsToContent(meta: SeoMetadata): string {\n return [\n meta.robots?.index === false ? 'noindex' : 'index',\n meta.robots?.follow === false ? 'nofollow' : 'follow',\n meta.robots?.notranslate ? 'notranslate' : null,\n meta.robots?.noimageindex ? 'noimageindex' : null,\n ]\n .filter(Boolean)\n .join(', ')\n}\n\nfunction pushNameMeta(meta: SeoHeadMetaTag[], name: string, content?: string | null): void {\n if (content) meta.push({name, content})\n}\n\nfunction pushPropertyMeta(meta: SeoHeadMetaTag[], property: string, content?: string | null): void {\n if (content) meta.push({property, content})\n}\n\nfunction customMetaUsesProperty(name: string): boolean {\n return /^(og|fb|article|book|music|profile|product|video):/.test(name)\n}\n\n// ─── Core builder ─────────────────────────────────────────────────────────────\n\n/**\n * Convert a Sanity SEO object into a structured metadata object.\n *\n * The return value is structurally compatible with Next.js App Router's\n * `Metadata` type, so you can return it directly from `generateMetadata()`.\n *\n * @example Next.js App Router\n * ```ts\n * import { buildSeoMeta } from 'sanity-plugin-seofields/next'\n * import { urlFor } from '@/sanity/lib/image'\n *\n * export async function generateMetadata(): Promise<Metadata> {\n * const { seo } = await sanityFetch({ query: PAGE_SEO_QUERY })\n * return buildSeoMeta({\n * seo,\n * baseUrl: process.env.NEXT_PUBLIC_SITE_URL,\n * path: '/about',\n * defaults: { title: 'My Site', siteName: 'My Site' },\n * imageUrlResolver: (img) => urlFor(img).width(1200).url(),\n * })\n * }\n * ```\n */\nexport function buildSeoMeta(options: BuildSeoMetaOptions): SeoMetadata {\n const {seo, baseUrl = '', path = '', defaults = {}, imageUrlResolver, hreflangs} = options\n\n const fullUrl = joinUrl(baseUrl, path)\n\n const ogImageURL = resolveOgImage(seo, defaults, imageUrlResolver)\n const twitterImageURL = resolveTwitterImage(seo, ogImageURL, imageUrlResolver)\n const other = buildCustomMetaMap(seo)\n const languages = buildHreflangMap(hreflangs ?? seo?.hreflangs)\n\n const ogUrl = seo?.openGraph?.url || fullUrl\n\n return {\n title: seo?.title ?? defaults.title ?? null,\n description: seo?.description ?? defaults.description ?? null,\n keywords: seo?.keywords?.length ? (seo.keywords as string[]) : undefined,\n robots: {\n index: !seo?.robots?.noIndex,\n follow: !seo?.robots?.noFollow,\n notranslate: !!seo?.robots?.noTranslate,\n noimageindex: !!seo?.robots?.noImageIndex,\n googleBot: {\n index: !seo?.robots?.noIndex,\n follow: !seo?.robots?.noFollow,\n notranslate: !!seo?.robots?.noTranslate,\n noimageindex: !!seo?.robots?.noImageIndex,\n },\n },\n openGraph: {\n type: sanitizeOGType(seo?.openGraph?.type ?? undefined),\n url: ogUrl || undefined,\n title: seo?.openGraph?.title ?? seo?.title ?? defaults.title,\n description: seo?.openGraph?.description ?? seo?.description ?? defaults.description,\n siteName: seo?.openGraph?.siteName ?? defaults.siteName,\n images: ogImageURL ? [{url: ogImageURL}] : [],\n },\n twitter: {\n card: sanitizeTwitterCard(seo?.twitter?.card ?? undefined),\n site: seo?.twitter?.site ?? defaults.twitterSite,\n creator: seo?.twitter?.creator ?? defaults.twitterCreator,\n title: seo?.twitter?.title ?? seo?.openGraph?.title ?? seo?.title ?? defaults.title,\n description:\n seo?.twitter?.description ??\n seo?.openGraph?.description ??\n seo?.description ??\n defaults.description,\n images: twitterImageURL ? [twitterImageURL] : [],\n },\n alternates: {\n canonical: seo?.canonicalUrl || fullUrl || undefined,\n ...(languages ? {languages} : {}),\n },\n ...(Object.keys(other).length > 0 ? {other} : {}),\n }\n}\n\n/**\n * Convert Sanity SEO fields into plain head tag data.\n *\n * This helper is framework-neutral: it returns serializable `title`, `meta`,\n * and `link` arrays that can be passed to Astro layouts, Nuxt/Vue `useHead`,\n * SvelteKit `<svelte:head>`, Remix `meta()` exports, or any custom renderer.\n *\n * @example Astro / Nuxt / SvelteKit\n * ```ts\n * import { buildSeoHead } from 'sanity-plugin-seofields/head'\n *\n * const head = buildSeoHead({\n * seo: page.seo,\n * baseUrl: 'https://example.com',\n * path: `/blog/${page.slug}`,\n * defaults: { title: page.title, siteName: 'My Site' },\n * imageUrlResolver: (img) => urlFor(img).width(1200).height(630).url(),\n * })\n * ```\n */\nexport function buildSeoHead(options: BuildSeoMetaOptions): SeoHead {\n const metadata = buildSeoMeta(options)\n const meta: SeoHeadMetaTag[] = []\n const link: SeoHeadLinkTag[] = []\n const robotsContent = robotsToContent(metadata)\n\n pushNameMeta(meta, 'description', metadata.description)\n if (metadata.keywords?.length) {\n pushNameMeta(meta, 'keywords', metadata.keywords.join(', '))\n }\n pushNameMeta(meta, 'robots', robotsContent)\n pushNameMeta(meta, 'googlebot', robotsContent)\n\n pushPropertyMeta(meta, 'og:type', metadata.openGraph?.type)\n pushPropertyMeta(meta, 'og:url', metadata.openGraph?.url)\n pushPropertyMeta(meta, 'og:title', metadata.openGraph?.title)\n pushPropertyMeta(meta, 'og:description', metadata.openGraph?.description)\n pushPropertyMeta(meta, 'og:site_name', metadata.openGraph?.siteName)\n\n for (const image of metadata.openGraph?.images || []) {\n pushPropertyMeta(meta, 'og:image', image.url)\n if (image.width) pushPropertyMeta(meta, 'og:image:width', String(image.width))\n if (image.height) pushPropertyMeta(meta, 'og:image:height', String(image.height))\n if (image.alt) pushPropertyMeta(meta, 'og:image:alt', image.alt)\n }\n\n pushNameMeta(meta, 'twitter:card', metadata.twitter?.card)\n pushNameMeta(meta, 'twitter:site', metadata.twitter?.site)\n pushNameMeta(meta, 'twitter:creator', metadata.twitter?.creator)\n pushNameMeta(meta, 'twitter:title', metadata.twitter?.title)\n pushNameMeta(meta, 'twitter:description', metadata.twitter?.description)\n\n for (const imageUrl of metadata.twitter?.images || []) {\n pushNameMeta(meta, 'twitter:image', imageUrl)\n }\n\n for (const [name, content] of Object.entries(metadata.other || {})) {\n if (customMetaUsesProperty(name)) {\n pushPropertyMeta(meta, name, content)\n } else {\n pushNameMeta(meta, name, content)\n }\n }\n\n if (metadata.alternates?.canonical) {\n link.push({rel: 'canonical', href: metadata.alternates.canonical})\n }\n\n for (const [hreflang, href] of Object.entries(metadata.alternates?.languages || {})) {\n link.push({rel: 'alternate', hreflang, href})\n }\n\n return {\n title: metadata.title,\n meta,\n link,\n }\n}\n","/**\n * Generate an `llms.txt` file (https://llmstxt.org) from your content.\n *\n * `llms.txt` is a Markdown file that gives LLMs a concise, link-first map of a site: an H1 title, a\n * blockquote summary, optional prose, then `##` sections each holding a bullet list of\n * `- [title](url): description` links.\n *\n * Use `buildLlmsTxt()` with hand-written sections, or `docsToLlmsSection()` to map an array of Sanity\n * documents into one section.\n */\nimport {joinUrl} from './url'\n\nexport interface LlmsLink {\n title: string\n url: string\n description?: string\n}\n\nexport interface LlmsSection {\n title: string\n links: LlmsLink[]\n}\n\nexport interface BuildLlmsTxtOptions {\n /** H1 title, e.g. the site name. */\n title: string\n /** One-line blockquote summary rendered directly under the title. */\n summary?: string\n /** Free-form Markdown prose rendered after the summary. */\n details?: string\n /** Link sections rendered as `## {title}` + a bullet list. Empty sections are omitted. */\n sections?: LlmsSection[]\n /** Optional version, rendered in a leading `> Version: … | Updated: …` line. */\n version?: string\n /** Optional \"updated\" label, rendered alongside `version`. */\n updated?: string\n /** Prepended to any relative link URL. */\n baseUrl?: string\n}\n\nfunction resolveLinkUrl(url: string, baseUrl?: string): string {\n if (!baseUrl) return url\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(url) || url.startsWith('//')) return url // already absolute\n return joinUrl(baseUrl, url)\n}\n\nfunction renderSection(section: LlmsSection, baseUrl?: string): string | null {\n const links = section.links.filter((l) => l && l.title && l.url)\n if (links.length === 0) return null\n\n const lines = links.map((l) => {\n const url = resolveLinkUrl(l.url, baseUrl)\n const desc = l.description ? `: ${l.description}` : ''\n return `- [${l.title}](${url})${desc}`\n })\n return `## ${section.title}\\n\\n${lines.join('\\n')}`\n}\n\nexport function buildLlmsTxt(options: BuildLlmsTxtOptions): string {\n const {title, summary, details, sections = [], version, updated, baseUrl} = options\n\n const blocks: string[] = []\n\n if (version || updated) {\n const parts = [version ? `Version: ${version}` : '', updated ? `Updated: ${updated}` : '']\n .filter(Boolean)\n .join(' | ')\n blocks.push(`> ${parts}`)\n }\n\n blocks.push(`# ${title}`)\n if (summary) blocks.push(`> ${summary}`)\n if (details) blocks.push(details.trim())\n\n for (const section of sections) {\n const rendered = renderSection(section, baseUrl)\n if (rendered) blocks.push(rendered)\n }\n\n return `${blocks.join('\\n\\n')}\\n`\n}\n\n// ─── Sanity documents → section ───────────────────────────────────────────────\n\nexport interface LlmsDoc {\n title?: string | null\n slug?: {current?: string | null} | string | null\n description?: string | null\n [key: string]: unknown\n}\n\nexport interface DocsToLlmsSectionOptions {\n /** Section heading. */\n title: string\n /** Prepended to each document's path. */\n baseUrl?: string\n /** Build the path for a document. Defaults to `/${slug}`. */\n resolvePath?: (doc: LlmsDoc) => string | null | undefined\n /** Override the link title. Defaults to `doc.title`. */\n resolveTitle?: (doc: LlmsDoc) => string | null | undefined\n /** Override the link description. Defaults to `doc.description`. */\n resolveDescription?: (doc: LlmsDoc) => string | null | undefined\n}\n\nfunction slugToPath(slug: LlmsDoc['slug']): string | undefined {\n const value = typeof slug === 'string' ? slug : (slug?.current ?? undefined)\n return value ? `/${value.replace(/^\\/+/, '')}` : undefined\n}\n\nexport function docsToLlmsSection(\n docs: LlmsDoc[] | null | undefined,\n options: DocsToLlmsSectionOptions,\n): LlmsSection {\n const {title, baseUrl, resolvePath, resolveTitle, resolveDescription} = options\n\n const links: LlmsLink[] = []\n for (const doc of docs ?? []) {\n if (!doc) continue\n const linkTitle = (resolveTitle ? resolveTitle(doc) : doc.title) ?? undefined\n const path = (resolvePath ? resolvePath(doc) : slugToPath(doc.slug)) ?? undefined\n if (!linkTitle || !path) continue\n\n const url = baseUrl ? joinUrl(baseUrl, path) : path\n const description =\n (resolveDescription ? resolveDescription(doc) : doc.description) ?? undefined\n links.push({title: linkTitle, url, ...(description ? {description} : {})})\n }\n\n return {title, links}\n}\n"]}
@@ -1,3 +1,6 @@
1
+ import {
2
+ joinUrl
3
+ } from "./chunk-CRCXC45D.js";
1
4
  import {
2
5
  __spreadValues
3
6
  } from "./chunk-7N4MLTMR.js";
@@ -89,14 +92,12 @@ function customMetaUsesProperty(name) {
89
92
  }
90
93
  function buildSeoMeta(options) {
91
94
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N;
92
- const { seo, baseUrl = "", path = "", defaults = {}, imageUrlResolver } = options;
93
- const normalizedBase = baseUrl.replace(/\/+$/, "");
94
- const normalizedPath = path.replace(/^\/+/, "");
95
- const fullUrl = [normalizedBase, normalizedPath].filter(Boolean).join("/");
95
+ const { seo, baseUrl = "", path = "", defaults = {}, imageUrlResolver, hreflangs } = options;
96
+ const fullUrl = joinUrl(baseUrl, path);
96
97
  const ogImageURL = resolveOgImage(seo, defaults, imageUrlResolver);
97
98
  const twitterImageURL = resolveTwitterImage(seo, ogImageURL, imageUrlResolver);
98
99
  const other = buildCustomMetaMap(seo);
99
- const languages = buildHreflangMap(seo == null ? void 0 : seo.hreflangs);
100
+ const languages = buildHreflangMap(hreflangs != null ? hreflangs : seo == null ? void 0 : seo.hreflangs);
100
101
  const ogUrl = ((_a = seo == null ? void 0 : seo.openGraph) == null ? void 0 : _a.url) || fullUrl;
101
102
  return __spreadValues({
102
103
  title: (_c = (_b = seo == null ? void 0 : seo.title) != null ? _b : defaults.title) != null ? _c : null,
@@ -186,10 +187,68 @@ function buildSeoHead(options) {
186
187
  };
187
188
  }
188
189
 
190
+ // src/helpers/llmsTxt.ts
191
+ function resolveLinkUrl(url, baseUrl) {
192
+ if (!baseUrl) return url;
193
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(url) || url.startsWith("//")) return url;
194
+ return joinUrl(baseUrl, url);
195
+ }
196
+ function renderSection(section, baseUrl) {
197
+ const links = section.links.filter((l) => l && l.title && l.url);
198
+ if (links.length === 0) return null;
199
+ const lines = links.map((l) => {
200
+ const url = resolveLinkUrl(l.url, baseUrl);
201
+ const desc = l.description ? `: ${l.description}` : "";
202
+ return `- [${l.title}](${url})${desc}`;
203
+ });
204
+ return `## ${section.title}
205
+
206
+ ${lines.join("\n")}`;
207
+ }
208
+ function buildLlmsTxt(options) {
209
+ const { title, summary, details, sections = [], version, updated, baseUrl } = options;
210
+ const blocks = [];
211
+ if (version || updated) {
212
+ const parts = [version ? `Version: ${version}` : "", updated ? `Updated: ${updated}` : ""].filter(Boolean).join(" | ");
213
+ blocks.push(`> ${parts}`);
214
+ }
215
+ blocks.push(`# ${title}`);
216
+ if (summary) blocks.push(`> ${summary}`);
217
+ if (details) blocks.push(details.trim());
218
+ for (const section of sections) {
219
+ const rendered = renderSection(section, baseUrl);
220
+ if (rendered) blocks.push(rendered);
221
+ }
222
+ return `${blocks.join("\n\n")}
223
+ `;
224
+ }
225
+ function slugToPath(slug) {
226
+ var _a;
227
+ const value = typeof slug === "string" ? slug : (_a = slug == null ? void 0 : slug.current) != null ? _a : void 0;
228
+ return value ? `/${value.replace(/^\/+/, "")}` : void 0;
229
+ }
230
+ function docsToLlmsSection(docs, options) {
231
+ var _a, _b, _c;
232
+ const { title, baseUrl, resolvePath, resolveTitle, resolveDescription } = options;
233
+ const links = [];
234
+ for (const doc of docs != null ? docs : []) {
235
+ if (!doc) continue;
236
+ const linkTitle = (_a = resolveTitle ? resolveTitle(doc) : doc.title) != null ? _a : void 0;
237
+ const path = (_b = resolvePath ? resolvePath(doc) : slugToPath(doc.slug)) != null ? _b : void 0;
238
+ if (!linkTitle || !path) continue;
239
+ const url = baseUrl ? joinUrl(baseUrl, path) : path;
240
+ const description = (_c = resolveDescription ? resolveDescription(doc) : doc.description) != null ? _c : void 0;
241
+ links.push(__spreadValues({ title: linkTitle, url }, description ? { description } : {}));
242
+ }
243
+ return { title, links };
244
+ }
245
+
189
246
  export {
190
247
  sanitizeOGType,
191
248
  sanitizeTwitterCard,
192
249
  buildSeoMeta,
193
- buildSeoHead
250
+ buildSeoHead,
251
+ buildLlmsTxt,
252
+ docsToLlmsSection
194
253
  };
195
- //# sourceMappingURL=chunk-KIEO6QG3.js.map
254
+ //# sourceMappingURL=chunk-XD4HKLEA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/helpers/seoMeta.ts","../src/helpers/llmsTxt.ts"],"sourcesContent":["/**\n * Headless CMS integration helpers for sanity-plugin-seofields\n *\n * Provides framework-agnostic SEO metadata utilities for use with:\n * - Next.js App Router → buildSeoMeta() inside generateMetadata()\n * - Next.js Pages Router → <SeoMetaTags> inside Next.js <Head>\n * - Astro / Nuxt / Vue / SvelteKit / Remix → buildSeoHead() into native head APIs\n */\n\nimport type {SanityImage, SanityImageWithAlt} from '../types'\nimport {joinUrl} from './url'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n/** Structured metadata returned by buildSeoMeta(). Compatible with Next.js Metadata (App Router). */\nexport interface SeoMetadata {\n title?: string | null\n description?: string | null\n keywords?: string[]\n robots?: {\n index?: boolean\n follow?: boolean\n notranslate?: boolean\n noimageindex?: boolean\n googleBot?: {\n index?: boolean\n follow?: boolean\n notranslate?: boolean\n noimageindex?: boolean\n }\n }\n openGraph?: {\n type?: string\n url?: string\n title?: string\n description?: string\n siteName?: string\n images?: Array<{url: string; width?: number; height?: number; alt?: string}>\n }\n twitter?: {\n card?: string\n site?: string\n creator?: string\n title?: string\n description?: string\n images?: string[]\n }\n alternates?: {\n canonical?: string\n languages?: Record<string, string>\n }\n /** Any custom meta attributes from seo.metaAttributes */\n other?: Record<string, string>\n}\n\n/** Plain `<meta>` tag data returned by buildSeoHead(). */\nexport type SeoHeadMetaTag = {name: string; content: string} | {property: string; content: string}\n\n/** Plain `<link>` tag data returned by buildSeoHead(). */\nexport interface SeoHeadLinkTag {\n rel: string\n href: string\n hreflang?: string\n}\n\n/**\n * Framework-neutral head data returned by buildSeoHead().\n *\n * Use this in Astro, Nuxt, Vue, SvelteKit, Remix, or any frontend where you\n * need plain values instead of React elements or Next.js Metadata.\n */\nexport interface SeoHead {\n title?: string | null\n meta: SeoHeadMetaTag[]\n link: SeoHeadLinkTag[]\n}\n\n/** Default values used when SEO fields are missing. */\nexport interface SeoMetaDefaults {\n title?: string\n description?: string\n siteName?: string\n twitterSite?: string\n twitterCreator?: string\n /** Fallback image URL when no OG / Twitter image is set. */\n ogImage?: string\n}\n\n/**\n * Permissive image shape accepted by buildSeoMeta — compatible with both the\n * plugin's SanityImage and Sanity's code-generated image type (where `asset`\n * and `alt` are optional).\n */\ninterface SeoImageInput {\n _type?: string\n asset?: {_ref: string; _type: string; _weak?: boolean; [key: string]: unknown}\n hotspot?: unknown\n crop?: unknown\n alt?: string\n}\n\n/**\n * Input-compatible variant of SeoFields. Structurally matches Sanity's\n * code-generated types (where `asset`, `alt`, `key`, and `type` are all\n * optional), so you can pass `data.seo` from a sanityFetch result directly\n * without any `as any` or manual casting.\n */\nexport interface SeoFieldsInput {\n _type?: string\n robots?: {\n noIndex?: boolean | null\n noFollow?: boolean | null\n noTranslate?: boolean | null\n noImageIndex?: boolean | null\n } | null\n title?: string | null\n description?: string | null\n metaImage?: SeoImageInput | null\n metaAttributes?: Array<{_key?: string; key?: string; value?: string; type?: string}> | null\n keywords?: string[] | null\n canonicalUrl?: string | null\n hreflangs?: Array<{locale?: string | null; url?: string | null}> | null\n openGraph?: {\n _type?: string\n url?: string | null\n title?: string | null\n description?: string | null\n siteName?: string | null\n type?: string | null\n imageType?: string | null\n image?: SeoImageInput | null\n imageUrl?: string | null\n } | null\n twitter?: {\n _type?: string\n card?: string | null\n site?: string | null\n creator?: string | null\n title?: string | null\n description?: string | null\n imageType?: string | null\n image?: SeoImageInput | null\n imageUrl?: string | null\n } | null\n}\n\n/** Options accepted by buildSeoMeta(). */\nexport interface BuildSeoMetaOptions {\n /**\n * The raw SEO object from Sanity (_type excluded or included — both work).\n * Pass `null` or `undefined` to fall back entirely to `defaults`.\n *\n * Accepts both the strict plugin `SeoFields` type and Sanity's code-generated\n * type (which has all nested fields optional) without any `as any` cast.\n */\n seo?: SeoFieldsInput | null\n\n /**\n * The base URL of your site, e.g. \"https://example.com\".\n * Used for canonical URL and OpenGraph URL construction.\n */\n baseUrl?: string\n\n /**\n * The path for the current page, e.g. \"/about\".\n * Combined with baseUrl to produce the canonical + OG url.\n * Defaults to \"\".\n */\n path?: string\n\n /**\n * Default values used when the Sanity SEO fields are empty / missing.\n */\n defaults?: SeoMetaDefaults\n\n /**\n * Resolve a Sanity image asset to a plain URL string.\n *\n * @example (using @sanity/image-url)\n * imageUrlResolver: (img) => urlFor(img).width(1200).url()\n */\n imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined\n\n /**\n * Override the hreflang alternates. When provided, this supersedes `seo.hreflangs` — pair it with\n * `buildHreflangs()` to derive alternates from `@sanity/document-internationalization` translations.\n *\n * @example\n * hreflangs: buildHreflangs(data._translations, { baseUrl })\n */\n hreflangs?: Array<{locale?: string | null; url?: string | null}> | null\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\nconst VALID_OG_TYPES = [\n 'website',\n 'article',\n 'profile',\n 'book',\n 'music',\n 'video',\n 'product',\n] as const\ntype OGType = (typeof VALID_OG_TYPES)[number]\n\n/**\n * Coerce an arbitrary string to a valid OpenGraph type.\n * Falls back to \"website\" when the value is invalid.\n */\nexport function sanitizeOGType(value?: string): OGType {\n if (value && (VALID_OG_TYPES as readonly string[]).includes(value)) {\n return value as OGType\n }\n return 'website'\n}\n\nconst VALID_TWITTER_CARDS = ['summary', 'summary_large_image', 'app', 'player'] as const\ntype TwitterCard = (typeof VALID_TWITTER_CARDS)[number]\n\n/**\n * Coerce an arbitrary string to a valid Twitter card type.\n * Falls back to \"summary_large_image\" when the value is invalid.\n */\nexport function sanitizeTwitterCard(value?: string): TwitterCard {\n if (value && (VALID_TWITTER_CARDS as readonly string[]).includes(value)) {\n return value as TwitterCard\n }\n return 'summary_large_image'\n}\n\n/**\n * Resolve OG image URL from SEO fields or defaults.\n */\nfunction resolveOgImage(\n seo: SeoFieldsInput | null | undefined,\n defaults: SeoMetaDefaults,\n imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined,\n): string {\n if (seo?.openGraph?.imageType === 'url' && seo.openGraph.imageUrl) {\n return seo.openGraph.imageUrl\n }\n if (seo?.openGraph?.image && imageUrlResolver) {\n return imageUrlResolver(seo.openGraph.image as SanityImage) || defaults.ogImage || ''\n }\n if (seo?.metaImage && imageUrlResolver) {\n return imageUrlResolver(seo.metaImage as SanityImage) || defaults.ogImage || ''\n }\n return defaults.ogImage || ''\n}\n\n/**\n * Resolve Twitter image URL from SEO fields, falling back to OG image.\n */\nfunction resolveTwitterImage(\n seo: SeoFieldsInput | null | undefined,\n ogImageURL: string,\n imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined,\n): string {\n if (seo?.twitter?.imageType === 'url' && seo.twitter.imageUrl) {\n return seo.twitter.imageUrl\n }\n if (seo?.twitter?.image && imageUrlResolver) {\n return imageUrlResolver(seo.twitter.image as SanityImage) || ogImageURL\n }\n return ogImageURL\n}\n\n/**\n * Build custom meta attributes map from SEO fields.\n */\nfunction buildCustomMetaMap(seo: SeoFieldsInput | null | undefined): Record<string, string> {\n const other: Record<string, string> = {}\n if (Array.isArray(seo?.metaAttributes)) {\n for (const attr of seo!.metaAttributes!) {\n if (attr.key && attr.value) {\n other[attr.key] = attr.value\n }\n }\n }\n return other\n}\n\nfunction buildHreflangMap(\n hreflangs: SeoFieldsInput['hreflangs'] | null | undefined,\n): Record<string, string> | undefined {\n if (!Array.isArray(hreflangs)) return undefined\n\n const languages: Record<string, string> = {}\n for (const entry of hreflangs) {\n if (entry?.locale && entry.url) {\n languages[entry.locale] = entry.url\n }\n }\n\n return Object.keys(languages).length > 0 ? languages : undefined\n}\n\nfunction robotsToContent(meta: SeoMetadata): string {\n return [\n meta.robots?.index === false ? 'noindex' : 'index',\n meta.robots?.follow === false ? 'nofollow' : 'follow',\n meta.robots?.notranslate ? 'notranslate' : null,\n meta.robots?.noimageindex ? 'noimageindex' : null,\n ]\n .filter(Boolean)\n .join(', ')\n}\n\nfunction pushNameMeta(meta: SeoHeadMetaTag[], name: string, content?: string | null): void {\n if (content) meta.push({name, content})\n}\n\nfunction pushPropertyMeta(meta: SeoHeadMetaTag[], property: string, content?: string | null): void {\n if (content) meta.push({property, content})\n}\n\nfunction customMetaUsesProperty(name: string): boolean {\n return /^(og|fb|article|book|music|profile|product|video):/.test(name)\n}\n\n// ─── Core builder ─────────────────────────────────────────────────────────────\n\n/**\n * Convert a Sanity SEO object into a structured metadata object.\n *\n * The return value is structurally compatible with Next.js App Router's\n * `Metadata` type, so you can return it directly from `generateMetadata()`.\n *\n * @example Next.js App Router\n * ```ts\n * import { buildSeoMeta } from 'sanity-plugin-seofields/next'\n * import { urlFor } from '@/sanity/lib/image'\n *\n * export async function generateMetadata(): Promise<Metadata> {\n * const { seo } = await sanityFetch({ query: PAGE_SEO_QUERY })\n * return buildSeoMeta({\n * seo,\n * baseUrl: process.env.NEXT_PUBLIC_SITE_URL,\n * path: '/about',\n * defaults: { title: 'My Site', siteName: 'My Site' },\n * imageUrlResolver: (img) => urlFor(img).width(1200).url(),\n * })\n * }\n * ```\n */\nexport function buildSeoMeta(options: BuildSeoMetaOptions): SeoMetadata {\n const {seo, baseUrl = '', path = '', defaults = {}, imageUrlResolver, hreflangs} = options\n\n const fullUrl = joinUrl(baseUrl, path)\n\n const ogImageURL = resolveOgImage(seo, defaults, imageUrlResolver)\n const twitterImageURL = resolveTwitterImage(seo, ogImageURL, imageUrlResolver)\n const other = buildCustomMetaMap(seo)\n const languages = buildHreflangMap(hreflangs ?? seo?.hreflangs)\n\n const ogUrl = seo?.openGraph?.url || fullUrl\n\n return {\n title: seo?.title ?? defaults.title ?? null,\n description: seo?.description ?? defaults.description ?? null,\n keywords: seo?.keywords?.length ? (seo.keywords as string[]) : undefined,\n robots: {\n index: !seo?.robots?.noIndex,\n follow: !seo?.robots?.noFollow,\n notranslate: !!seo?.robots?.noTranslate,\n noimageindex: !!seo?.robots?.noImageIndex,\n googleBot: {\n index: !seo?.robots?.noIndex,\n follow: !seo?.robots?.noFollow,\n notranslate: !!seo?.robots?.noTranslate,\n noimageindex: !!seo?.robots?.noImageIndex,\n },\n },\n openGraph: {\n type: sanitizeOGType(seo?.openGraph?.type ?? undefined),\n url: ogUrl || undefined,\n title: seo?.openGraph?.title ?? seo?.title ?? defaults.title,\n description: seo?.openGraph?.description ?? seo?.description ?? defaults.description,\n siteName: seo?.openGraph?.siteName ?? defaults.siteName,\n images: ogImageURL ? [{url: ogImageURL}] : [],\n },\n twitter: {\n card: sanitizeTwitterCard(seo?.twitter?.card ?? undefined),\n site: seo?.twitter?.site ?? defaults.twitterSite,\n creator: seo?.twitter?.creator ?? defaults.twitterCreator,\n title: seo?.twitter?.title ?? seo?.openGraph?.title ?? seo?.title ?? defaults.title,\n description:\n seo?.twitter?.description ??\n seo?.openGraph?.description ??\n seo?.description ??\n defaults.description,\n images: twitterImageURL ? [twitterImageURL] : [],\n },\n alternates: {\n canonical: seo?.canonicalUrl || fullUrl || undefined,\n ...(languages ? {languages} : {}),\n },\n ...(Object.keys(other).length > 0 ? {other} : {}),\n }\n}\n\n/**\n * Convert Sanity SEO fields into plain head tag data.\n *\n * This helper is framework-neutral: it returns serializable `title`, `meta`,\n * and `link` arrays that can be passed to Astro layouts, Nuxt/Vue `useHead`,\n * SvelteKit `<svelte:head>`, Remix `meta()` exports, or any custom renderer.\n *\n * @example Astro / Nuxt / SvelteKit\n * ```ts\n * import { buildSeoHead } from 'sanity-plugin-seofields/head'\n *\n * const head = buildSeoHead({\n * seo: page.seo,\n * baseUrl: 'https://example.com',\n * path: `/blog/${page.slug}`,\n * defaults: { title: page.title, siteName: 'My Site' },\n * imageUrlResolver: (img) => urlFor(img).width(1200).height(630).url(),\n * })\n * ```\n */\nexport function buildSeoHead(options: BuildSeoMetaOptions): SeoHead {\n const metadata = buildSeoMeta(options)\n const meta: SeoHeadMetaTag[] = []\n const link: SeoHeadLinkTag[] = []\n const robotsContent = robotsToContent(metadata)\n\n pushNameMeta(meta, 'description', metadata.description)\n if (metadata.keywords?.length) {\n pushNameMeta(meta, 'keywords', metadata.keywords.join(', '))\n }\n pushNameMeta(meta, 'robots', robotsContent)\n pushNameMeta(meta, 'googlebot', robotsContent)\n\n pushPropertyMeta(meta, 'og:type', metadata.openGraph?.type)\n pushPropertyMeta(meta, 'og:url', metadata.openGraph?.url)\n pushPropertyMeta(meta, 'og:title', metadata.openGraph?.title)\n pushPropertyMeta(meta, 'og:description', metadata.openGraph?.description)\n pushPropertyMeta(meta, 'og:site_name', metadata.openGraph?.siteName)\n\n for (const image of metadata.openGraph?.images || []) {\n pushPropertyMeta(meta, 'og:image', image.url)\n if (image.width) pushPropertyMeta(meta, 'og:image:width', String(image.width))\n if (image.height) pushPropertyMeta(meta, 'og:image:height', String(image.height))\n if (image.alt) pushPropertyMeta(meta, 'og:image:alt', image.alt)\n }\n\n pushNameMeta(meta, 'twitter:card', metadata.twitter?.card)\n pushNameMeta(meta, 'twitter:site', metadata.twitter?.site)\n pushNameMeta(meta, 'twitter:creator', metadata.twitter?.creator)\n pushNameMeta(meta, 'twitter:title', metadata.twitter?.title)\n pushNameMeta(meta, 'twitter:description', metadata.twitter?.description)\n\n for (const imageUrl of metadata.twitter?.images || []) {\n pushNameMeta(meta, 'twitter:image', imageUrl)\n }\n\n for (const [name, content] of Object.entries(metadata.other || {})) {\n if (customMetaUsesProperty(name)) {\n pushPropertyMeta(meta, name, content)\n } else {\n pushNameMeta(meta, name, content)\n }\n }\n\n if (metadata.alternates?.canonical) {\n link.push({rel: 'canonical', href: metadata.alternates.canonical})\n }\n\n for (const [hreflang, href] of Object.entries(metadata.alternates?.languages || {})) {\n link.push({rel: 'alternate', hreflang, href})\n }\n\n return {\n title: metadata.title,\n meta,\n link,\n }\n}\n","/**\n * Generate an `llms.txt` file (https://llmstxt.org) from your content.\n *\n * `llms.txt` is a Markdown file that gives LLMs a concise, link-first map of a site: an H1 title, a\n * blockquote summary, optional prose, then `##` sections each holding a bullet list of\n * `- [title](url): description` links.\n *\n * Use `buildLlmsTxt()` with hand-written sections, or `docsToLlmsSection()` to map an array of Sanity\n * documents into one section.\n */\nimport {joinUrl} from './url'\n\nexport interface LlmsLink {\n title: string\n url: string\n description?: string\n}\n\nexport interface LlmsSection {\n title: string\n links: LlmsLink[]\n}\n\nexport interface BuildLlmsTxtOptions {\n /** H1 title, e.g. the site name. */\n title: string\n /** One-line blockquote summary rendered directly under the title. */\n summary?: string\n /** Free-form Markdown prose rendered after the summary. */\n details?: string\n /** Link sections rendered as `## {title}` + a bullet list. Empty sections are omitted. */\n sections?: LlmsSection[]\n /** Optional version, rendered in a leading `> Version: … | Updated: …` line. */\n version?: string\n /** Optional \"updated\" label, rendered alongside `version`. */\n updated?: string\n /** Prepended to any relative link URL. */\n baseUrl?: string\n}\n\nfunction resolveLinkUrl(url: string, baseUrl?: string): string {\n if (!baseUrl) return url\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(url) || url.startsWith('//')) return url // already absolute\n return joinUrl(baseUrl, url)\n}\n\nfunction renderSection(section: LlmsSection, baseUrl?: string): string | null {\n const links = section.links.filter((l) => l && l.title && l.url)\n if (links.length === 0) return null\n\n const lines = links.map((l) => {\n const url = resolveLinkUrl(l.url, baseUrl)\n const desc = l.description ? `: ${l.description}` : ''\n return `- [${l.title}](${url})${desc}`\n })\n return `## ${section.title}\\n\\n${lines.join('\\n')}`\n}\n\nexport function buildLlmsTxt(options: BuildLlmsTxtOptions): string {\n const {title, summary, details, sections = [], version, updated, baseUrl} = options\n\n const blocks: string[] = []\n\n if (version || updated) {\n const parts = [version ? `Version: ${version}` : '', updated ? `Updated: ${updated}` : '']\n .filter(Boolean)\n .join(' | ')\n blocks.push(`> ${parts}`)\n }\n\n blocks.push(`# ${title}`)\n if (summary) blocks.push(`> ${summary}`)\n if (details) blocks.push(details.trim())\n\n for (const section of sections) {\n const rendered = renderSection(section, baseUrl)\n if (rendered) blocks.push(rendered)\n }\n\n return `${blocks.join('\\n\\n')}\\n`\n}\n\n// ─── Sanity documents → section ───────────────────────────────────────────────\n\nexport interface LlmsDoc {\n title?: string | null\n slug?: {current?: string | null} | string | null\n description?: string | null\n [key: string]: unknown\n}\n\nexport interface DocsToLlmsSectionOptions {\n /** Section heading. */\n title: string\n /** Prepended to each document's path. */\n baseUrl?: string\n /** Build the path for a document. Defaults to `/${slug}`. */\n resolvePath?: (doc: LlmsDoc) => string | null | undefined\n /** Override the link title. Defaults to `doc.title`. */\n resolveTitle?: (doc: LlmsDoc) => string | null | undefined\n /** Override the link description. Defaults to `doc.description`. */\n resolveDescription?: (doc: LlmsDoc) => string | null | undefined\n}\n\nfunction slugToPath(slug: LlmsDoc['slug']): string | undefined {\n const value = typeof slug === 'string' ? slug : (slug?.current ?? undefined)\n return value ? `/${value.replace(/^\\/+/, '')}` : undefined\n}\n\nexport function docsToLlmsSection(\n docs: LlmsDoc[] | null | undefined,\n options: DocsToLlmsSectionOptions,\n): LlmsSection {\n const {title, baseUrl, resolvePath, resolveTitle, resolveDescription} = options\n\n const links: LlmsLink[] = []\n for (const doc of docs ?? []) {\n if (!doc) continue\n const linkTitle = (resolveTitle ? resolveTitle(doc) : doc.title) ?? undefined\n const path = (resolvePath ? resolvePath(doc) : slugToPath(doc.slug)) ?? undefined\n if (!linkTitle || !path) continue\n\n const url = baseUrl ? joinUrl(baseUrl, path) : path\n const description =\n (resolveDescription ? resolveDescription(doc) : doc.description) ?? undefined\n links.push({title: linkTitle, url, ...(description ? {description} : {})})\n }\n\n return {title, links}\n}\n"],"mappings":";;;;;;;;AAmMA,IAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,SAAS,eAAe,OAAwB;AACrD,MAAI,SAAU,eAAqC,SAAS,KAAK,GAAG;AAClE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,IAAM,sBAAsB,CAAC,WAAW,uBAAuB,OAAO,QAAQ;AAOvE,SAAS,oBAAoB,OAA6B;AAC/D,MAAI,SAAU,oBAA0C,SAAS,KAAK,GAAG;AACvE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAKA,SAAS,eACP,KACA,UACA,kBACQ;AA9OV;AA+OE,QAAI,gCAAK,cAAL,mBAAgB,eAAc,SAAS,IAAI,UAAU,UAAU;AACjE,WAAO,IAAI,UAAU;AAAA,EACvB;AACA,QAAI,gCAAK,cAAL,mBAAgB,UAAS,kBAAkB;AAC7C,WAAO,iBAAiB,IAAI,UAAU,KAAoB,KAAK,SAAS,WAAW;AAAA,EACrF;AACA,OAAI,2BAAK,cAAa,kBAAkB;AACtC,WAAO,iBAAiB,IAAI,SAAwB,KAAK,SAAS,WAAW;AAAA,EAC/E;AACA,SAAO,SAAS,WAAW;AAC7B;AAKA,SAAS,oBACP,KACA,YACA,kBACQ;AAlQV;AAmQE,QAAI,gCAAK,YAAL,mBAAc,eAAc,SAAS,IAAI,QAAQ,UAAU;AAC7D,WAAO,IAAI,QAAQ;AAAA,EACrB;AACA,QAAI,gCAAK,YAAL,mBAAc,UAAS,kBAAkB;AAC3C,WAAO,iBAAiB,IAAI,QAAQ,KAAoB,KAAK;AAAA,EAC/D;AACA,SAAO;AACT;AAKA,SAAS,mBAAmB,KAAgE;AAC1F,QAAM,QAAgC,CAAC;AACvC,MAAI,MAAM,QAAQ,2BAAK,cAAc,GAAG;AACtC,eAAW,QAAQ,IAAK,gBAAiB;AACvC,UAAI,KAAK,OAAO,KAAK,OAAO;AAC1B,cAAM,KAAK,GAAG,IAAI,KAAK;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,iBACP,WACoC;AACpC,MAAI,CAAC,MAAM,QAAQ,SAAS,EAAG,QAAO;AAEtC,QAAM,YAAoC,CAAC;AAC3C,aAAW,SAAS,WAAW;AAC7B,SAAI,+BAAO,WAAU,MAAM,KAAK;AAC9B,gBAAU,MAAM,MAAM,IAAI,MAAM;AAAA,IAClC;AAAA,EACF;AAEA,SAAO,OAAO,KAAK,SAAS,EAAE,SAAS,IAAI,YAAY;AACzD;AAEA,SAAS,gBAAgB,MAA2B;AA1SpD;AA2SE,SAAO;AAAA,MACL,UAAK,WAAL,mBAAa,WAAU,QAAQ,YAAY;AAAA,MAC3C,UAAK,WAAL,mBAAa,YAAW,QAAQ,aAAa;AAAA,MAC7C,UAAK,WAAL,mBAAa,eAAc,gBAAgB;AAAA,MAC3C,UAAK,WAAL,mBAAa,gBAAe,iBAAiB;AAAA,EAC/C,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AACd;AAEA,SAAS,aAAa,MAAwB,MAAc,SAA+B;AACzF,MAAI,QAAS,MAAK,KAAK,EAAC,MAAM,QAAO,CAAC;AACxC;AAEA,SAAS,iBAAiB,MAAwB,UAAkB,SAA+B;AACjG,MAAI,QAAS,MAAK,KAAK,EAAC,UAAU,QAAO,CAAC;AAC5C;AAEA,SAAS,uBAAuB,MAAuB;AACrD,SAAO,qDAAqD,KAAK,IAAI;AACvE;AA2BO,SAAS,aAAa,SAA2C;AA1VxE;AA2VE,QAAM,EAAC,KAAK,UAAU,IAAI,OAAO,IAAI,WAAW,CAAC,GAAG,kBAAkB,UAAS,IAAI;AAEnF,QAAM,UAAU,QAAQ,SAAS,IAAI;AAErC,QAAM,aAAa,eAAe,KAAK,UAAU,gBAAgB;AACjE,QAAM,kBAAkB,oBAAoB,KAAK,YAAY,gBAAgB;AAC7E,QAAM,QAAQ,mBAAmB,GAAG;AACpC,QAAM,YAAY,iBAAiB,gCAAa,2BAAK,SAAS;AAE9D,QAAM,UAAQ,gCAAK,cAAL,mBAAgB,QAAO;AAErC,SAAO;AAAA,IACL,QAAO,sCAAK,UAAL,YAAc,SAAS,UAAvB,YAAgC;AAAA,IACvC,cAAa,sCAAK,gBAAL,YAAoB,SAAS,gBAA7B,YAA4C;AAAA,IACzD,YAAU,gCAAK,aAAL,mBAAe,UAAU,IAAI,WAAwB;AAAA,IAC/D,QAAQ;AAAA,MACN,OAAO,GAAC,gCAAK,WAAL,mBAAa;AAAA,MACrB,QAAQ,GAAC,gCAAK,WAAL,mBAAa;AAAA,MACtB,aAAa,CAAC,GAAC,gCAAK,WAAL,mBAAa;AAAA,MAC5B,cAAc,CAAC,GAAC,gCAAK,WAAL,mBAAa;AAAA,MAC7B,WAAW;AAAA,QACT,OAAO,GAAC,gCAAK,WAAL,mBAAa;AAAA,QACrB,QAAQ,GAAC,gCAAK,WAAL,mBAAa;AAAA,QACtB,aAAa,CAAC,GAAC,gCAAK,WAAL,mBAAa;AAAA,QAC5B,cAAc,CAAC,GAAC,gCAAK,WAAL,mBAAa;AAAA,MAC/B;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,MAAM,gBAAe,sCAAK,cAAL,mBAAgB,SAAhB,YAAwB,MAAS;AAAA,MACtD,KAAK,SAAS;AAAA,MACd,QAAO,4CAAK,cAAL,mBAAgB,UAAhB,YAAyB,2BAAK,UAA9B,YAAuC,SAAS;AAAA,MACvD,cAAa,4CAAK,cAAL,mBAAgB,gBAAhB,YAA+B,2BAAK,gBAApC,YAAmD,SAAS;AAAA,MACzE,WAAU,sCAAK,cAAL,mBAAgB,aAAhB,YAA4B,SAAS;AAAA,MAC/C,QAAQ,aAAa,CAAC,EAAC,KAAK,WAAU,CAAC,IAAI,CAAC;AAAA,IAC9C;AAAA,IACA,SAAS;AAAA,MACP,MAAM,qBAAoB,sCAAK,YAAL,mBAAc,SAAd,YAAsB,MAAS;AAAA,MACzD,OAAM,sCAAK,YAAL,mBAAc,SAAd,YAAsB,SAAS;AAAA,MACrC,UAAS,sCAAK,YAAL,mBAAc,YAAd,YAAyB,SAAS;AAAA,MAC3C,QAAO,kDAAK,YAAL,mBAAc,UAAd,aAAuB,gCAAK,cAAL,mBAAgB,UAAvC,YAAgD,2BAAK,UAArD,YAA8D,SAAS;AAAA,MAC9E,cACE,kDAAK,YAAL,mBAAc,gBAAd,aACA,gCAAK,cAAL,mBAAgB,gBADhB,YAEA,2BAAK,gBAFL,YAGA,SAAS;AAAA,MACX,QAAQ,kBAAkB,CAAC,eAAe,IAAI,CAAC;AAAA,IACjD;AAAA,IACA,YAAY;AAAA,MACV,YAAW,2BAAK,iBAAgB,WAAW;AAAA,OACvC,YAAY,EAAC,UAAS,IAAI,CAAC;AAAA,KAE7B,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,EAAC,MAAK,IAAI,CAAC;AAEnD;AAsBO,SAAS,aAAa,SAAuC;AAtapE;AAuaE,QAAM,WAAW,aAAa,OAAO;AACrC,QAAM,OAAyB,CAAC;AAChC,QAAM,OAAyB,CAAC;AAChC,QAAM,gBAAgB,gBAAgB,QAAQ;AAE9C,eAAa,MAAM,eAAe,SAAS,WAAW;AACtD,OAAI,cAAS,aAAT,mBAAmB,QAAQ;AAC7B,iBAAa,MAAM,YAAY,SAAS,SAAS,KAAK,IAAI,CAAC;AAAA,EAC7D;AACA,eAAa,MAAM,UAAU,aAAa;AAC1C,eAAa,MAAM,aAAa,aAAa;AAE7C,mBAAiB,MAAM,YAAW,cAAS,cAAT,mBAAoB,IAAI;AAC1D,mBAAiB,MAAM,WAAU,cAAS,cAAT,mBAAoB,GAAG;AACxD,mBAAiB,MAAM,aAAY,cAAS,cAAT,mBAAoB,KAAK;AAC5D,mBAAiB,MAAM,mBAAkB,cAAS,cAAT,mBAAoB,WAAW;AACxE,mBAAiB,MAAM,iBAAgB,cAAS,cAAT,mBAAoB,QAAQ;AAEnE,aAAW,WAAS,cAAS,cAAT,mBAAoB,WAAU,CAAC,GAAG;AACpD,qBAAiB,MAAM,YAAY,MAAM,GAAG;AAC5C,QAAI,MAAM,MAAO,kBAAiB,MAAM,kBAAkB,OAAO,MAAM,KAAK,CAAC;AAC7E,QAAI,MAAM,OAAQ,kBAAiB,MAAM,mBAAmB,OAAO,MAAM,MAAM,CAAC;AAChF,QAAI,MAAM,IAAK,kBAAiB,MAAM,gBAAgB,MAAM,GAAG;AAAA,EACjE;AAEA,eAAa,MAAM,iBAAgB,cAAS,YAAT,mBAAkB,IAAI;AACzD,eAAa,MAAM,iBAAgB,cAAS,YAAT,mBAAkB,IAAI;AACzD,eAAa,MAAM,oBAAmB,cAAS,YAAT,mBAAkB,OAAO;AAC/D,eAAa,MAAM,kBAAiB,cAAS,YAAT,mBAAkB,KAAK;AAC3D,eAAa,MAAM,wBAAuB,cAAS,YAAT,mBAAkB,WAAW;AAEvE,aAAW,cAAY,cAAS,YAAT,mBAAkB,WAAU,CAAC,GAAG;AACrD,iBAAa,MAAM,iBAAiB,QAAQ;AAAA,EAC9C;AAEA,aAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,SAAS,SAAS,CAAC,CAAC,GAAG;AAClE,QAAI,uBAAuB,IAAI,GAAG;AAChC,uBAAiB,MAAM,MAAM,OAAO;AAAA,IACtC,OAAO;AACL,mBAAa,MAAM,MAAM,OAAO;AAAA,IAClC;AAAA,EACF;AAEA,OAAI,cAAS,eAAT,mBAAqB,WAAW;AAClC,SAAK,KAAK,EAAC,KAAK,aAAa,MAAM,SAAS,WAAW,UAAS,CAAC;AAAA,EACnE;AAEA,aAAW,CAAC,UAAU,IAAI,KAAK,OAAO,UAAQ,cAAS,eAAT,mBAAqB,cAAa,CAAC,CAAC,GAAG;AACnF,SAAK,KAAK,EAAC,KAAK,aAAa,UAAU,KAAI,CAAC;AAAA,EAC9C;AAEA,SAAO;AAAA,IACL,OAAO,SAAS;AAAA,IAChB;AAAA,IACA;AAAA,EACF;AACF;;;ACvbA,SAAS,eAAe,KAAa,SAA0B;AAC7D,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,2BAA2B,KAAK,GAAG,KAAK,IAAI,WAAW,IAAI,EAAG,QAAO;AACzE,SAAO,QAAQ,SAAS,GAAG;AAC7B;AAEA,SAAS,cAAc,SAAsB,SAAiC;AAC5E,QAAM,QAAQ,QAAQ,MAAM,OAAO,CAAC,MAAM,KAAK,EAAE,SAAS,EAAE,GAAG;AAC/D,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,QAAQ,MAAM,IAAI,CAAC,MAAM;AAC7B,UAAM,MAAM,eAAe,EAAE,KAAK,OAAO;AACzC,UAAM,OAAO,EAAE,cAAc,KAAK,EAAE,WAAW,KAAK;AACpD,WAAO,MAAM,EAAE,KAAK,KAAK,GAAG,IAAI,IAAI;AAAA,EACtC,CAAC;AACD,SAAO,MAAM,QAAQ,KAAK;AAAA;AAAA,EAAO,MAAM,KAAK,IAAI,CAAC;AACnD;AAEO,SAAS,aAAa,SAAsC;AACjE,QAAM,EAAC,OAAO,SAAS,SAAS,WAAW,CAAC,GAAG,SAAS,SAAS,QAAO,IAAI;AAE5E,QAAM,SAAmB,CAAC;AAE1B,MAAI,WAAW,SAAS;AACtB,UAAM,QAAQ,CAAC,UAAU,YAAY,OAAO,KAAK,IAAI,UAAU,YAAY,OAAO,KAAK,EAAE,EACtF,OAAO,OAAO,EACd,KAAK,KAAK;AACb,WAAO,KAAK,KAAK,KAAK,EAAE;AAAA,EAC1B;AAEA,SAAO,KAAK,KAAK,KAAK,EAAE;AACxB,MAAI,QAAS,QAAO,KAAK,KAAK,OAAO,EAAE;AACvC,MAAI,QAAS,QAAO,KAAK,QAAQ,KAAK,CAAC;AAEvC,aAAW,WAAW,UAAU;AAC9B,UAAM,WAAW,cAAc,SAAS,OAAO;AAC/C,QAAI,SAAU,QAAO,KAAK,QAAQ;AAAA,EACpC;AAEA,SAAO,GAAG,OAAO,KAAK,MAAM,CAAC;AAAA;AAC/B;AAwBA,SAAS,WAAW,MAA2C;AAxG/D;AAyGE,QAAM,QAAQ,OAAO,SAAS,WAAW,QAAQ,kCAAM,YAAN,YAAiB;AAClE,SAAO,QAAQ,IAAI,MAAM,QAAQ,QAAQ,EAAE,CAAC,KAAK;AACnD;AAEO,SAAS,kBACd,MACA,SACa;AAhHf;AAiHE,QAAM,EAAC,OAAO,SAAS,aAAa,cAAc,mBAAkB,IAAI;AAExE,QAAM,QAAoB,CAAC;AAC3B,aAAW,OAAO,sBAAQ,CAAC,GAAG;AAC5B,QAAI,CAAC,IAAK;AACV,UAAM,aAAa,oBAAe,aAAa,GAAG,IAAI,IAAI,UAAvC,YAAiD;AACpE,UAAM,QAAQ,mBAAc,YAAY,GAAG,IAAI,WAAW,IAAI,IAAI,MAApD,YAA0D;AACxE,QAAI,CAAC,aAAa,CAAC,KAAM;AAEzB,UAAM,MAAM,UAAU,QAAQ,SAAS,IAAI,IAAI;AAC/C,UAAM,eACH,0BAAqB,mBAAmB,GAAG,IAAI,IAAI,gBAAnD,YAAmE;AACtE,UAAM,KAAK,iBAAC,OAAO,WAAW,OAAS,cAAc,EAAC,YAAW,IAAI,CAAC,EAAG;AAAA,EAC3E;AAEA,SAAO,EAAC,OAAO,MAAK;AACtB;","names":[]}
@@ -0,0 +1,56 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/helpers/url.ts
2
+ function joinUrl(base, path) {
3
+ const normalizedBase = base.replace(/\/+$/, "");
4
+ const normalizedPath = path.replace(/^\/+/, "");
5
+ return [normalizedBase, normalizedPath].filter(Boolean).join("/");
6
+ }
7
+
8
+ // src/helpers/hreflang.ts
9
+ function normalizeSlug(slug) {
10
+ var _a;
11
+ if (typeof slug === "string") return slug || void 0;
12
+ if (slug && typeof slug === "object") return (_a = slug.current) != null ? _a : void 0;
13
+ return void 0;
14
+ }
15
+ function resolveUrl(t, baseUrl, resolvePath) {
16
+ if (t.url) return t.url;
17
+ let path;
18
+ if (resolvePath) {
19
+ path = resolvePath(t);
20
+ } else if (t.path) {
21
+ path = t.path;
22
+ } else {
23
+ const slug = normalizeSlug(t.slug);
24
+ path = slug ? `/${slug.replace(/^\/+/, "")}` : void 0;
25
+ }
26
+ if (path === null || path === void 0 || path === "") return void 0;
27
+ return baseUrl ? joinUrl(baseUrl, path) : path;
28
+ }
29
+ function buildHreflangs(translations, options = {}) {
30
+ var _a;
31
+ const { baseUrl, resolvePath, xDefault, current } = options;
32
+ const all = current ? [current, ...translations != null ? translations : []] : [...translations != null ? translations : []];
33
+ const entries = [];
34
+ const seen = /* @__PURE__ */ new Set();
35
+ for (const t of all) {
36
+ const locale = (_a = t == null ? void 0 : t.language) == null ? void 0 : _a.trim();
37
+ if (!locale || seen.has(locale)) continue;
38
+ const url = resolveUrl(t, baseUrl, resolvePath);
39
+ if (!url) continue;
40
+ seen.add(locale);
41
+ entries.push({ locale, url });
42
+ }
43
+ if (xDefault) {
44
+ const match = entries.find((e) => e.locale === xDefault);
45
+ if (match && !seen.has("x-default")) {
46
+ entries.push({ locale: "x-default", url: match.url });
47
+ }
48
+ }
49
+ return entries;
50
+ }
51
+
52
+
53
+
54
+
55
+ exports.joinUrl = joinUrl; exports.buildHreflangs = buildHreflangs;
56
+ //# sourceMappingURL=chunk-XYAZJ3WX.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/hardik/GITHUB/sanity-plugin-seofields/npm/dist/chunk-XYAZJ3WX.cjs","../src/helpers/url.ts","../src/helpers/hreflang.ts"],"names":[],"mappings":"AAAA;ACQO,SAAS,OAAA,CAAQ,IAAA,EAAc,IAAA,EAAsB;AAC1D,EAAA,MAAM,eAAA,EAAiB,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAC9C,EAAA,MAAM,eAAA,EAAiB,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAC9C,EAAA,OAAO,CAAC,cAAA,EAAgB,cAAc,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAClE;ADNA;AACA;AEoCA,SAAS,aAAA,CAAc,IAAA,EAAuD;AA3C9E,EAAA,IAAA,EAAA;AA4CE,EAAA,GAAA,CAAI,OAAO,KAAA,IAAS,QAAA,EAAU,OAAO,KAAA,GAAQ,KAAA,CAAA;AAC7C,EAAA,GAAA,CAAI,KAAA,GAAQ,OAAO,KAAA,IAAS,QAAA,EAAU,OAAA,CAAO,GAAA,EAAA,IAAA,CAAK,OAAA,EAAA,GAAL,KAAA,EAAA,GAAA,EAAgB,KAAA,CAAA;AAC7D,EAAA,OAAO,KAAA,CAAA;AACT;AAEA,SAAS,UAAA,CACP,CAAA,EACA,OAAA,EACA,WAAA,EACoB;AACpB,EAAA,GAAA,CAAI,CAAA,CAAE,GAAA,EAAK,OAAO,CAAA,CAAE,GAAA;AAEpB,EAAA,IAAI,IAAA;AACJ,EAAA,GAAA,CAAI,WAAA,EAAa;AACf,IAAA,KAAA,EAAO,WAAA,CAAY,CAAC,CAAA;AAAA,EACtB,EAAA,KAAA,GAAA,CAAW,CAAA,CAAE,IAAA,EAAM;AACjB,IAAA,KAAA,EAAO,CAAA,CAAE,IAAA;AAAA,EACX,EAAA,KAAO;AACL,IAAA,MAAM,KAAA,EAAO,aAAA,CAAc,CAAA,CAAE,IAAI,CAAA;AACjC,IAAA,KAAA,EAAO,KAAA,EAAO,CAAA,CAAA,EAAI,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAC,CAAA,EAAA;AAC5C,EAAA;AAE2C,EAAA;AACD,EAAA;AAC5C;AAIE;AAxEF,EAAA;AA0EyC,EAAA;AACH,EAAA;AAEE,EAAA;AACT,EAAA;AAER,EAAA;AACJ,IAAA;AACkB,IAAA;AACE,IAAA;AACzB,IAAA;AACK,IAAA;AACW,IAAA;AAC5B,EAAA;AAEc,EAAA;AACwB,IAAA;AACC,IAAA;AACK,MAAA;AAC1C,IAAA;AACF,EAAA;AAEO,EAAA;AACT;AF/C+C;AACA;AACA;AACA;AACA","file":"/Users/hardik/GITHUB/sanity-plugin-seofields/npm/dist/chunk-XYAZJ3WX.cjs","sourcesContent":[null,"/**\n * Join a base URL and a path into a single URL, tolerating leading/trailing slashes on either side.\n *\n * @example\n * joinUrl('https://example.com/', '/about') // 'https://example.com/about'\n * joinUrl('https://example.com', '') // 'https://example.com'\n * joinUrl('', 'about') // 'about'\n */\nexport function joinUrl(base: string, path: string): string {\n const normalizedBase = base.replace(/\\/+$/, '') // remove trailing /\n const normalizedPath = path.replace(/^\\/+/, '') // remove leading /\n return [normalizedBase, normalizedPath].filter(Boolean).join('/')\n}\n","/**\n * Build hreflang alternate-link entries from resolved document translations.\n *\n * Designed for `@sanity/document-internationalization`: a standard GROQ query resolves translations as\n *\n * ```groq\n * \"_translations\": *[_type == \"translation.metadata\" && references(^._id)].translations[].value->{\n * language, \"slug\": slug.current\n * }\n * ```\n *\n * Pass that array here to get entries matching the plugin's `hreflangEntry` shape (`{locale, url}`),\n * which are assignable to `seo.hreflangs` or to `buildSeoMeta({ hreflangs })`.\n */\nimport {joinUrl} from './url'\n\nexport interface HreflangTranslation {\n /** BCP 47 language/region tag, e.g. \"en\", \"fr-FR\". */\n language?: string | null\n /** Sanity slug — either the raw string or the `{ current }` object. */\n slug?: {current?: string | null} | string | null\n /** Pre-built path (e.g. \"/fr/about\"). Wins over `slug`. */\n path?: string | null\n /** Absolute URL. Wins over `baseUrl` + path. */\n url?: string | null\n}\n\nexport interface BuildHreflangsOptions {\n /** Site origin, e.g. \"https://example.com\". Prepended to each translation's path. */\n baseUrl?: string\n /** Build the path for a translation. Defaults to `/${slug}`. Return null/undefined to skip. */\n resolvePath?: (t: HreflangTranslation) => string | null | undefined\n /** Language tag to also emit as an `x-default` entry (pointing at that language's URL). */\n xDefault?: string\n /** Include the current document itself in the set. */\n current?: HreflangTranslation\n}\n\nexport interface HreflangLinkEntry {\n locale: string\n url: string\n}\n\nfunction normalizeSlug(slug: HreflangTranslation['slug']): string | undefined {\n if (typeof slug === 'string') return slug || undefined\n if (slug && typeof slug === 'object') return slug.current ?? undefined\n return undefined\n}\n\nfunction resolveUrl(\n t: HreflangTranslation,\n baseUrl: string | undefined,\n resolvePath: BuildHreflangsOptions['resolvePath'],\n): string | undefined {\n if (t.url) return t.url\n\n let path: string | null | undefined\n if (resolvePath) {\n path = resolvePath(t)\n } else if (t.path) {\n path = t.path\n } else {\n const slug = normalizeSlug(t.slug)\n path = slug ? `/${slug.replace(/^\\/+/, '')}` : undefined\n }\n\n if (path === null || path === undefined || path === '') return undefined\n return baseUrl ? joinUrl(baseUrl, path) : path\n}\n\nexport function buildHreflangs(\n translations: HreflangTranslation[] | null | undefined,\n options: BuildHreflangsOptions = {},\n): HreflangLinkEntry[] {\n const {baseUrl, resolvePath, xDefault, current} = options\n const all = current ? [current, ...(translations ?? [])] : [...(translations ?? [])]\n\n const entries: HreflangLinkEntry[] = []\n const seen = new Set<string>()\n\n for (const t of all) {\n const locale = t?.language?.trim()\n if (!locale || seen.has(locale)) continue\n const url = resolveUrl(t, baseUrl, resolvePath)\n if (!url) continue\n seen.add(locale)\n entries.push({locale, url})\n }\n\n if (xDefault) {\n const match = entries.find((e) => e.locale === xDefault)\n if (match && !seen.has('x-default')) {\n entries.push({locale: 'x-default', url: match.url})\n }\n }\n\n return entries\n}\n"]}
package/dist/cli.js CHANGED
@@ -61,7 +61,7 @@ ${e}`}function xt(e,t,o,n){a.success(`Updated ${e}`),a.dim(""),a.dim(" What was
61
61
  title,
62
62
  slug,
63
63
  seo
64
- } | order(_updatedAt desc)`;n.text="Fetching documents...";let l;try{l=await s.fetch(f);}catch(g){n.fail(`Failed to fetch: ${g.message}`),process.exit(1);}if(n.succeed(`Analysed ${l.length} document(s)`),l.length===0){a.warn("No documents with SEO fields found."),o&&$e(i);return}let u=l.map(g=>({doc:g,health:U(g)}));await kt(u,l,e),o&&$e(i);}async function kt(e,t,o){let n={excellent:0,good:0,fair:0,poor:0,missing:0},s=0;for(let{health:l}of e)n[l.status]=(n[l.status]||0)+1,s+=l.score;let i=Math.round(s/e.length);if(vt(t,i,n),o.format==="summary")return;o.format!=="table"&&(a.info(""),a.warn(`Unknown format "${o.format}", defaulting to table output.`)),It(e);let r={};for(let{health:l}of e)for(let u of l.issues)r[u]=(r[u]||0)+1;let f=Object.entries(r).sort(([,l],[,u])=>u-l).slice(0,10);f.length>0&&jt(f,t.length),a.info("");}function vt(e,t,o){a.heading("\u{1F4CA} Summary"),a.info(""),a.info(` Total documents: ${c.bold(String(e.length))}`),a.info(` Average score: ${c.bold(Te(t))}%`),a.info(""),a.info(` ${R.excellent} Excellent (80+): ${o.excellent}`),a.info(` ${R.good} Good (60-79): ${o.good}`),a.info(` ${R.fair} Fair (40-59): ${o.fair}`),a.info(` ${R.poor} Poor (1-39): ${o.poor}`),a.info(` ${R.missing} Missing (0): ${o.missing}`);}var Tt={excellent:"Excellent",good:"Good",fair:"Fair",poor:"Poor",missing:"Missing"},Ct={excellent:c.green,good:c.blue,fair:c.yellow,poor:c.red,missing:c.dim};function Ot(e,t=11){var s,i;let o=((s=Tt[e])!=null?s:e).padEnd(t);return ((i=Ct[e])!=null?i:(r=>r))(o)}function It(e){a.heading("\u{1F4CB} Documents"),a.info("");let t=Math.min(44,Math.max(5,...e.map(({doc:r})=>(r.title||r._id).length))),o=11,n=14,s=5,i=c.dim("\u2500".repeat(s+2+o+2+n+2+t));a.info(` ${c.dim("Score".padEnd(s))} ${c.dim("Status".padEnd(o))} ${c.dim("Type".padEnd(n))} ${c.dim("Title")}`),a.info(` ${i}`);for(let{doc:r,health:f}of e){let l=ke(r.title||r._id,t),u=String(f.score).padStart(s),d=Ot(f.status,o),g=c.dim(ke(r._type,n).padEnd(n));a.info(` ${Te(f.score,u)} ${d} ${g} ${l}`);}}function jt(e,t){a.heading("\u{1F50D} Top Issues"),a.info("");for(let[o,n]of e){let s=Math.round(n/t*100);a.info(` ${c.red(String(n).padStart(4))} docs (${s}%) ${o}`);}}function $e(e){a.info(""),a.info(` ${c.dim("\u2500".repeat(52))}`),a.info(` ${c.dim("Connection info")}`),a.info(` ${c.dim("Project ID:")} ${c.white(e.projectId)} ${c.dim(`\u2190 ${e.sources.projectId}`)}`),a.info(` ${c.dim("Dataset: ")} ${c.white(e.dataset)} ${c.dim(`\u2190 ${e.sources.dataset}`)}`),a.info(` ${c.dim("Token: ")} ${c.dim(e.sources.token)}`),a.info("");}function Te(e,t){let o=t!=null?t:String(e);return e>=80?c.green(o):e>=60?c.blue(o):e>=40?c.yellow(o):e>0?c.red(o):c.dim(o)}function ke(e,t){return e.length>t?`${e.slice(0,t-1)}\u2026`:e}H();var oe="1.9.0",Ce=[c.cyan,e=>c.bold(c.blue(e)),c.blue,c.magenta,c.red,c.yellow],x=Ce[Math.floor(Math.random()*Ce.length)],Oe=["Run `seofields doctor` first to verify your setup is correct.","Use `seofields report --format summary` for a quick overview.","Export your SEO data before making bulk changes: `seofields export -o backup.json`.","Add `seoFields` type to every document schema to track SEO health.","A meta title between 50\u201360 characters gets the best click-through rate.","Meta descriptions should be 140\u2013160 characters for optimal display.","Open Graph images should be 1200\xD7630px for best social sharing.","Use `seofields config --baseUrl=https://yoursite.com` to enable canonical URLs.","Twitter Card images require a separate `twitterImage` field for best results.","Schema.org markup helps search engines understand your content structure.","Use `seofields report --types post,page` to filter by document type.","The `healthDashboard` tool gives a live view of SEO scores inside Sanity Studio.","Keywords should appear naturally \u2014 avoid keyword stuffing.","Every page needs a unique meta title. Duplicates hurt rankings.","Use `seofields export --format csv` to open SEO data in a spreadsheet.","Set `noIndex: true`, `noFollow: true`, `noTranslate: true`, or `noImageIndex: true` when a page needs stricter crawler directives.","OG title and meta title can differ \u2014 OG is optimised for social, meta for search.","Run `seofields report` after content migrations to catch regressions.","Use `seofields init --schema-org` to add structured data support.","Structured data (Schema.org) can unlock rich results in Google Search.","A score of 80+ is excellent. Aim to get all key pages there.","The `seoPreview` option shows a live Google-style preview inside the editor.","Use `seofields report` in CI to catch SEO regressions before they ship.","Use `--dataset staging` to run reports against your staging dataset.","Keep your license key private \u2014 never commit it to version control.","Use `seofields config --healthDashboard.showDocumentId=true` for easier debugging.","Use `seofields export --format json --output report.json` to save SEO data to a file.","Alt text on images improves both accessibility and image search ranking.","Canonical URLs prevent duplicate content penalties from search engines.","A missing OG image means social shares show a blank card \u2014 always add one.","Use descriptive slugs: `/blog/seo-tips` ranks better than `/blog/post-123`.","Re-run `seofields report` monthly to track your SEO health over time.","The `types` filter helps you focus reports on high-priority content.","Use `seofields export` to back up SEO data before schema migrations.","Short meta titles (under 30 chars) waste valuable search result space.","Long meta descriptions get truncated \u2014 stay under 160 characters.","Twitter Cards need `twitter:card`, `twitter:title`, and `twitter:image` at minimum.","Use `seofields doctor` after upgrading the plugin to catch breaking changes.","JSON export includes health scores \u2014 pipe it into your analytics pipeline.","Set a `defaultHiddenFields` list to keep the SEO panel focused.","Internal links pass authority \u2014 make sure important pages are well-linked.","Page speed is a ranking factor \u2014 optimise images referenced in SEO fields.","Use consistent brand phrasing in meta titles across your site.","Structured data errors can be tested at schema.org/validator.","Focus first on pages with the most traffic, not just the lowest scores.","Add SEO review to your content publishing checklist.","Use `seofields config --healthDashboard.licenseKey=KEY` to unlock premium features.","The CSV export makes it easy to share SEO audits with non-technical stakeholders.","Every image on your page should have descriptive alt text.","Avoid duplicate meta descriptions \u2014 each page needs its own."];function Et(){return Oe[Math.floor(Math.random()*Oe.length)]}var Rt=62,q=Rt-2;function Ft(e,t){let o=e.split(" "),n=[],s="";for(let i of o)s.length+(s?1:0)+i.length>t?(s&&n.push(s),s=i):s=s?`${s} ${i}`:i;return s&&n.push(s),n}function D(e,t){let o=q-3,n=" ".repeat(Math.max(0,o-t));return ` ${x("\u2502")} ${e}${n} ${x("\u2502")}`}function P(){return D("",0)}function Ie(){let e="\u2500".repeat(q),t=Et(),o=q-3-5,n=Ft(t,o);console.log(),console.log(` ${x(`\u256D${e}\u256E`)}`),console.log(P()),console.log(D(`${c.bold(c.green("seofields"))}${c.dim(` v${oe}`)}`,`seofields v${oe}`.length)),console.log(D(c.dim("SEO tooling for Sanity CMS \u2014 manage from your terminal"),54)),console.log(P());for(let i=0;i<n.length;i++){let r=i===0?`${c.dim("Tip: ")}${c.white(n[i])}`:` ${c.white(n[i])}`,f=5+n[i].length;console.log(D(r,f));}console.log(P()),console.log(` ${x(`\u2570${e}\u256F`)}`),console.log(),console.log(` ${c.bold("Commands")}`),console.log();let s=[["init ","Add seofields() to sanity.config"],["config ","Update plugin configuration options"],["create-config ","Create seofields.cli.ts / .js interactively"],["report ","SEO health report across all documents"],["export ","Export SEO fields as JSON or CSV"],["doctor ","Check setup, deps & config"]];for(let[i,r]of s)console.log(` ${x(i)} ${c.dim(r)}`);console.log(),console.log(` ${c.dim("Run ")}${c.white("seofields <command> --help")}${c.dim(" for details")}`),console.log();}function _(e,t){let o="\u2500".repeat(q),n=`seofields \u203A ${e}`;return ["",` ${x(`\u256D${o}\u256E`)}`,P(),D(`${c.bold(c.green("seofields"))}${x(" \u203A ")}${c.bold(x(e))}`,n.length),D(c.dim(t),t.length),P(),` ${x(`\u2570${o}\u256F`)}`].join(`
64
+ } | order(_updatedAt desc)`;n.text="Fetching documents...";let l;try{l=await s.fetch(f);}catch(g){n.fail(`Failed to fetch: ${g.message}`),process.exit(1);}if(n.succeed(`Analysed ${l.length} document(s)`),l.length===0){a.warn("No documents with SEO fields found."),o&&$e(i);return}let u=l.map(g=>({doc:g,health:U(g)}));await kt(u,l,e),o&&$e(i);}async function kt(e,t,o){let n={excellent:0,good:0,fair:0,poor:0,missing:0},s=0;for(let{health:l}of e)n[l.status]=(n[l.status]||0)+1,s+=l.score;let i=Math.round(s/e.length);if(vt(t,i,n),o.format==="summary")return;o.format!=="table"&&(a.info(""),a.warn(`Unknown format "${o.format}", defaulting to table output.`)),It(e);let r={};for(let{health:l}of e)for(let u of l.issues)r[u]=(r[u]||0)+1;let f=Object.entries(r).sort(([,l],[,u])=>u-l).slice(0,10);f.length>0&&jt(f,t.length),a.info("");}function vt(e,t,o){a.heading("\u{1F4CA} Summary"),a.info(""),a.info(` Total documents: ${c.bold(String(e.length))}`),a.info(` Average score: ${c.bold(Te(t))}%`),a.info(""),a.info(` ${R.excellent} Excellent (80+): ${o.excellent}`),a.info(` ${R.good} Good (60-79): ${o.good}`),a.info(` ${R.fair} Fair (40-59): ${o.fair}`),a.info(` ${R.poor} Poor (1-39): ${o.poor}`),a.info(` ${R.missing} Missing (0): ${o.missing}`);}var Tt={excellent:"Excellent",good:"Good",fair:"Fair",poor:"Poor",missing:"Missing"},Ct={excellent:c.green,good:c.blue,fair:c.yellow,poor:c.red,missing:c.dim};function Ot(e,t=11){var s,i;let o=((s=Tt[e])!=null?s:e).padEnd(t);return ((i=Ct[e])!=null?i:(r=>r))(o)}function It(e){a.heading("\u{1F4CB} Documents"),a.info("");let t=Math.min(44,Math.max(5,...e.map(({doc:r})=>(r.title||r._id).length))),o=11,n=14,s=5,i=c.dim("\u2500".repeat(s+2+o+2+n+2+t));a.info(` ${c.dim("Score".padEnd(s))} ${c.dim("Status".padEnd(o))} ${c.dim("Type".padEnd(n))} ${c.dim("Title")}`),a.info(` ${i}`);for(let{doc:r,health:f}of e){let l=ke(r.title||r._id,t),u=String(f.score).padStart(s),d=Ot(f.status,o),g=c.dim(ke(r._type,n).padEnd(n));a.info(` ${Te(f.score,u)} ${d} ${g} ${l}`);}}function jt(e,t){a.heading("\u{1F50D} Top Issues"),a.info("");for(let[o,n]of e){let s=Math.round(n/t*100);a.info(` ${c.red(String(n).padStart(4))} docs (${s}%) ${o}`);}}function $e(e){a.info(""),a.info(` ${c.dim("\u2500".repeat(52))}`),a.info(` ${c.dim("Connection info")}`),a.info(` ${c.dim("Project ID:")} ${c.white(e.projectId)} ${c.dim(`\u2190 ${e.sources.projectId}`)}`),a.info(` ${c.dim("Dataset: ")} ${c.white(e.dataset)} ${c.dim(`\u2190 ${e.sources.dataset}`)}`),a.info(` ${c.dim("Token: ")} ${c.dim(e.sources.token)}`),a.info("");}function Te(e,t){let o=t!=null?t:String(e);return e>=80?c.green(o):e>=60?c.blue(o):e>=40?c.yellow(o):e>0?c.red(o):c.dim(o)}function ke(e,t){return e.length>t?`${e.slice(0,t-1)}\u2026`:e}H();var oe="1.10.1",Ce=[c.cyan,e=>c.bold(c.blue(e)),c.blue,c.magenta,c.red,c.yellow],x=Ce[Math.floor(Math.random()*Ce.length)],Oe=["Run `seofields doctor` first to verify your setup is correct.","Use `seofields report --format summary` for a quick overview.","Export your SEO data before making bulk changes: `seofields export -o backup.json`.","Add `seoFields` type to every document schema to track SEO health.","A meta title between 50\u201360 characters gets the best click-through rate.","Meta descriptions should be 140\u2013160 characters for optimal display.","Open Graph images should be 1200\xD7630px for best social sharing.","Use `seofields config --baseUrl=https://yoursite.com` to enable canonical URLs.","Twitter Card images require a separate `twitterImage` field for best results.","Schema.org markup helps search engines understand your content structure.","Use `seofields report --types post,page` to filter by document type.","The `healthDashboard` tool gives a live view of SEO scores inside Sanity Studio.","Keywords should appear naturally \u2014 avoid keyword stuffing.","Every page needs a unique meta title. Duplicates hurt rankings.","Use `seofields export --format csv` to open SEO data in a spreadsheet.","Set `noIndex: true`, `noFollow: true`, `noTranslate: true`, or `noImageIndex: true` when a page needs stricter crawler directives.","OG title and meta title can differ \u2014 OG is optimised for social, meta for search.","Run `seofields report` after content migrations to catch regressions.","Use `seofields init --schema-org` to add structured data support.","Structured data (Schema.org) can unlock rich results in Google Search.","A score of 80+ is excellent. Aim to get all key pages there.","The `seoPreview` option shows a live Google-style preview inside the editor.","Use `seofields report` in CI to catch SEO regressions before they ship.","Use `--dataset staging` to run reports against your staging dataset.","Keep your license key private \u2014 never commit it to version control.","Use `seofields config --healthDashboard.showDocumentId=true` for easier debugging.","Use `seofields export --format json --output report.json` to save SEO data to a file.","Alt text on images improves both accessibility and image search ranking.","Canonical URLs prevent duplicate content penalties from search engines.","A missing OG image means social shares show a blank card \u2014 always add one.","Use descriptive slugs: `/blog/seo-tips` ranks better than `/blog/post-123`.","Re-run `seofields report` monthly to track your SEO health over time.","The `types` filter helps you focus reports on high-priority content.","Use `seofields export` to back up SEO data before schema migrations.","Short meta titles (under 30 chars) waste valuable search result space.","Long meta descriptions get truncated \u2014 stay under 160 characters.","Twitter Cards need `twitter:card`, `twitter:title`, and `twitter:image` at minimum.","Use `seofields doctor` after upgrading the plugin to catch breaking changes.","JSON export includes health scores \u2014 pipe it into your analytics pipeline.","Set a `defaultHiddenFields` list to keep the SEO panel focused.","Internal links pass authority \u2014 make sure important pages are well-linked.","Page speed is a ranking factor \u2014 optimise images referenced in SEO fields.","Use consistent brand phrasing in meta titles across your site.","Structured data errors can be tested at schema.org/validator.","Focus first on pages with the most traffic, not just the lowest scores.","Add SEO review to your content publishing checklist.","Use `seofields config --healthDashboard.licenseKey=KEY` to unlock premium features.","The CSV export makes it easy to share SEO audits with non-technical stakeholders.","Every image on your page should have descriptive alt text.","Avoid duplicate meta descriptions \u2014 each page needs its own."];function Et(){return Oe[Math.floor(Math.random()*Oe.length)]}var Rt=62,q=Rt-2;function Ft(e,t){let o=e.split(" "),n=[],s="";for(let i of o)s.length+(s?1:0)+i.length>t?(s&&n.push(s),s=i):s=s?`${s} ${i}`:i;return s&&n.push(s),n}function D(e,t){let o=q-3,n=" ".repeat(Math.max(0,o-t));return ` ${x("\u2502")} ${e}${n} ${x("\u2502")}`}function P(){return D("",0)}function Ie(){let e="\u2500".repeat(q),t=Et(),o=q-3-5,n=Ft(t,o);console.log(),console.log(` ${x(`\u256D${e}\u256E`)}`),console.log(P()),console.log(D(`${c.bold(c.green("seofields"))}${c.dim(` v${oe}`)}`,`seofields v${oe}`.length)),console.log(D(c.dim("SEO tooling for Sanity CMS \u2014 manage from your terminal"),54)),console.log(P());for(let i=0;i<n.length;i++){let r=i===0?`${c.dim("Tip: ")}${c.white(n[i])}`:` ${c.white(n[i])}`,f=5+n[i].length;console.log(D(r,f));}console.log(P()),console.log(` ${x(`\u2570${e}\u256F`)}`),console.log(),console.log(` ${c.bold("Commands")}`),console.log();let s=[["init ","Add seofields() to sanity.config"],["config ","Update plugin configuration options"],["create-config ","Create seofields.cli.ts / .js interactively"],["report ","SEO health report across all documents"],["export ","Export SEO fields as JSON or CSV"],["doctor ","Check setup, deps & config"]];for(let[i,r]of s)console.log(` ${x(i)} ${c.dim(r)}`);console.log(),console.log(` ${c.dim("Run ")}${c.white("seofields <command> --help")}${c.dim(" for details")}`),console.log();}function _(e,t){let o="\u2500".repeat(q),n=`seofields \u203A ${e}`;return ["",` ${x(`\u256D${o}\u256E`)}`,P(),D(`${c.bold(c.green("seofields"))}${x(" \u203A ")}${c.bold(x(e))}`,n.length),D(c.dim(t),t.length),P(),` ${x(`\u2570${o}\u256F`)}`].join(`
65
65
  `)}program.name("seofields").description("CLI for sanity-plugin-seofields \u2014 manage SEO from your terminal").version(oe).action(Ie);program.command("config",{isDefault:false}).description("Update seofields() configuration in your sanity.config file").addHelpText("before",_("config","Update seofields() configuration in your sanity.config file")).allowUnknownOption().allowExcessArguments().helpOption("-h, --help","Show help and all available options").addHelpText("after",["",` ${c.bold("Usage")}`,` ${c.dim("Pass any option as")} ${c.white("--key=value")}${c.dim(". Nested keys use dot notation.")}`,"",` ${c.bold("Top-level options")}`,` ${x("--baseUrl")}${c.dim("=<url>").padEnd(38)} ${c.dim("Canonical base URL")}`,` ${x("--seoPreview")}${c.dim("=<bool>").padEnd(36)} ${c.dim("Enable live Google-style preview")}`,` ${x("--defaultHiddenFields")}${c.dim("=<f1,f2>").padEnd(28)} ${c.dim("Comma-separated fields to hide")}`,` ${x("--types")}${c.dim("=<t1,t2>").padEnd(40)} ${c.dim("Document types that have SEO fields")}`,"",` ${c.bold("healthDashboard options")} ${c.dim("(prefix: --healthDashboard.<key>)")}`,` ${x("--healthDashboard.licenseKey")}${c.dim("=<key>").padEnd(22)} ${c.dim("License key for premium features")}`,` ${x("--healthDashboard.toolTitle")}${c.dim("=<str>").padEnd(23)} ${c.dim("Custom title shown in Studio")}`,` ${x("--healthDashboard.showDocumentId")}${c.dim("=<bool>").padEnd(17)} ${c.dim("Show document _id in dashboard")}`,` ${x("--healthDashboard.showHealthScore")}${c.dim("=<bool>").padEnd(16)} ${c.dim("Show score badge per document")}`,` ${x("--healthDashboard.query.types")}${c.dim("=<t1,t2>").padEnd(21)} ${c.dim("Filter dashboard by document types")}`,"",` ${c.bold("Examples")}`,` ${c.white("seofields config --baseUrl=https://mysite.com")}`,` ${c.white("seofields config --healthDashboard.licenseKey=SEOF-1234")}`,` ${c.white("seofields config --healthDashboard.showDocumentId=true --seoPreview=true")}`,` ${c.white("seofields config --healthDashboard.query.types=post,page")}`,` ${c.white("seofields config --defaultHiddenFields=metaImage,openGraphUrl")}`,""].join(`
66
66
  `)).action(()=>{let e=process.argv.indexOf("config"),t=e>=0?process.argv.slice(e+1):[];ge(t);});program.command("create-config").description("Interactively create a seofields.cli.ts / .js config file").addHelpText("before",_("create-config","Interactively create a seofields.cli.ts / .js config file")).action(me);program.command("init").description("Add seofields() plugin to your sanity.config file").addHelpText("before",_("init","Add seofields() plugin to your sanity.config file")).option("--preview","Enable SEO preview in the plugin config").option("--dashboard","Explicitly enable the SEO Health Dashboard").option("--no-dashboard","Disable the SEO Health Dashboard").option("--schema-org","Also add schemaOrg() plugin registration").addHelpText("after",["",` ${c.dim("Note: ")}${c.yellow("--preview")}${c.dim(", ")}${c.yellow("--dashboard")}${c.dim(", and ")}${c.yellow("--no-dashboard")}${c.dim(" only apply during initial setup.")}`,` ${c.dim(" If seofields() is already in your config, use:")}`,"",` ${c.dim(" ")}${c.white("seofields config --healthDashboard=true --seoPreview=true")}`,""].join(`
67
67
  `)).action(xe);program.command("export").description("Export all documents with SEO fields as JSON or CSV").addHelpText("before",_("export","Export all documents with SEO fields as JSON or CSV")).option("-p, --project-id <id>","Sanity project ID").option("-d, --dataset <name>","Sanity dataset name").option("-t, --token <token>","Sanity API token").option("--types <types>","Comma-separated document types to export (e.g. post,page)").option("-f, --format <format>","Output format: json or csv","json").option("-o, --output <path>","Output file path (defaults to stdout)").action(Se);program.command("report").description("Generate an SEO health report for all documents").addHelpText("before",_("report","Generate an SEO health report for all documents")).option("-p, --project-id <id>","Sanity project ID").option("-d, --dataset <name>","Sanity dataset name").option("-t, --token <token>","Sanity API token").option("--types <types>","Comma-separated document types to include").option("--format <format>",'Output format: table or summary (default: "table")',"table").action(ve);program.command("doctor").description("Check plugin configuration, dependencies, and setup").addHelpText("before",_("doctor","Check plugin configuration, dependencies, and setup")).option("--cwd <path>","Working directory to check",process.cwd()).action(he);var E=process.argv.slice(2);E.length===1&&(E[0]==="--help"||E[0]==="-h")&&(Ie(),process.exit(0));if(E.length>=2&&E[E.length-1]==="help"){let e=program.commands.find(t=>t.name()===E[0]);e&&(e.help(),process.exit(0));}program.parse();
package/dist/head.cjs CHANGED
@@ -3,12 +3,20 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkFUUWBQE2cjs = require('./chunk-FUUWBQE2.cjs');
6
+
7
+
8
+ var _chunkOULUDJPIcjs = require('./chunk-OULUDJPI.cjs');
9
+
10
+
11
+ var _chunkXYAZJ3WXcjs = require('./chunk-XYAZJ3WX.cjs');
7
12
  require('./chunk-A57XNQC7.cjs');
8
13
 
9
14
 
10
15
 
11
16
 
12
17
 
13
- exports.buildSeoHead = _chunkFUUWBQE2cjs.buildSeoHead; exports.buildSeoMeta = _chunkFUUWBQE2cjs.buildSeoMeta; exports.sanitizeOGType = _chunkFUUWBQE2cjs.sanitizeOGType; exports.sanitizeTwitterCard = _chunkFUUWBQE2cjs.sanitizeTwitterCard;
18
+
19
+
20
+
21
+ exports.buildHreflangs = _chunkXYAZJ3WXcjs.buildHreflangs; exports.buildLlmsTxt = _chunkOULUDJPIcjs.buildLlmsTxt; exports.buildSeoHead = _chunkOULUDJPIcjs.buildSeoHead; exports.buildSeoMeta = _chunkOULUDJPIcjs.buildSeoMeta; exports.docsToLlmsSection = _chunkOULUDJPIcjs.docsToLlmsSection; exports.sanitizeOGType = _chunkOULUDJPIcjs.sanitizeOGType; exports.sanitizeTwitterCard = _chunkOULUDJPIcjs.sanitizeTwitterCard;
14
22
  //# sourceMappingURL=head.cjs.map
package/dist/head.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/hardik/GITHUB/sanity-plugin-seofields/npm/dist/head.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACA;AACA;AACF,6OAAC","file":"/Users/hardik/GITHUB/sanity-plugin-seofields/npm/dist/head.cjs"}
1
+ {"version":3,"sources":["/Users/hardik/GITHUB/sanity-plugin-seofields/npm/dist/head.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF,gaAAC","file":"/Users/hardik/GITHUB/sanity-plugin-seofields/npm/dist/head.cjs"}
package/dist/head.d.cts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { a as SanityImage, b as SanityImageWithAlt } from './types-DxlPXihz.cjs';
2
+ export { B as BuildHreflangsOptions, H as HreflangLinkEntry, a as HreflangTranslation, b as buildHreflangs } from './hreflang-_VbSuWYD.cjs';
2
3
 
3
4
  /**
4
5
  * Headless CMS integration helpers for sanity-plugin-seofields
@@ -190,6 +191,17 @@ interface BuildSeoMetaOptions {
190
191
  * imageUrlResolver: (img) => urlFor(img).width(1200).url()
191
192
  */
192
193
  imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined;
194
+ /**
195
+ * Override the hreflang alternates. When provided, this supersedes `seo.hreflangs` — pair it with
196
+ * `buildHreflangs()` to derive alternates from `@sanity/document-internationalization` translations.
197
+ *
198
+ * @example
199
+ * hreflangs: buildHreflangs(data._translations, { baseUrl })
200
+ */
201
+ hreflangs?: Array<{
202
+ locale?: string | null;
203
+ url?: string | null;
204
+ }> | null;
193
205
  }
194
206
  declare const VALID_OG_TYPES: readonly ["website", "article", "profile", "book", "music", "video", "product"];
195
207
  type OGType = (typeof VALID_OG_TYPES)[number];
@@ -251,4 +263,52 @@ declare function buildSeoMeta(options: BuildSeoMetaOptions): SeoMetadata;
251
263
  */
252
264
  declare function buildSeoHead(options: BuildSeoMetaOptions): SeoHead;
253
265
 
254
- export { type BuildSeoMetaOptions, type SeoFieldsInput, type SeoHead, type SeoHeadLinkTag, type SeoHeadMetaTag, type SeoMetaDefaults, type SeoMetadata, buildSeoHead, buildSeoMeta, sanitizeOGType, sanitizeTwitterCard };
266
+ interface LlmsLink {
267
+ title: string;
268
+ url: string;
269
+ description?: string;
270
+ }
271
+ interface LlmsSection {
272
+ title: string;
273
+ links: LlmsLink[];
274
+ }
275
+ interface BuildLlmsTxtOptions {
276
+ /** H1 title, e.g. the site name. */
277
+ title: string;
278
+ /** One-line blockquote summary rendered directly under the title. */
279
+ summary?: string;
280
+ /** Free-form Markdown prose rendered after the summary. */
281
+ details?: string;
282
+ /** Link sections rendered as `## {title}` + a bullet list. Empty sections are omitted. */
283
+ sections?: LlmsSection[];
284
+ /** Optional version, rendered in a leading `> Version: … | Updated: …` line. */
285
+ version?: string;
286
+ /** Optional "updated" label, rendered alongside `version`. */
287
+ updated?: string;
288
+ /** Prepended to any relative link URL. */
289
+ baseUrl?: string;
290
+ }
291
+ declare function buildLlmsTxt(options: BuildLlmsTxtOptions): string;
292
+ interface LlmsDoc {
293
+ title?: string | null;
294
+ slug?: {
295
+ current?: string | null;
296
+ } | string | null;
297
+ description?: string | null;
298
+ [key: string]: unknown;
299
+ }
300
+ interface DocsToLlmsSectionOptions {
301
+ /** Section heading. */
302
+ title: string;
303
+ /** Prepended to each document's path. */
304
+ baseUrl?: string;
305
+ /** Build the path for a document. Defaults to `/${slug}`. */
306
+ resolvePath?: (doc: LlmsDoc) => string | null | undefined;
307
+ /** Override the link title. Defaults to `doc.title`. */
308
+ resolveTitle?: (doc: LlmsDoc) => string | null | undefined;
309
+ /** Override the link description. Defaults to `doc.description`. */
310
+ resolveDescription?: (doc: LlmsDoc) => string | null | undefined;
311
+ }
312
+ declare function docsToLlmsSection(docs: LlmsDoc[] | null | undefined, options: DocsToLlmsSectionOptions): LlmsSection;
313
+
314
+ export { type BuildLlmsTxtOptions, type BuildSeoMetaOptions, type DocsToLlmsSectionOptions, type LlmsDoc, type LlmsLink, type LlmsSection, type SeoFieldsInput, type SeoHead, type SeoHeadLinkTag, type SeoHeadMetaTag, type SeoMetaDefaults, type SeoMetadata, buildLlmsTxt, buildSeoHead, buildSeoMeta, docsToLlmsSection, sanitizeOGType, sanitizeTwitterCard };