@proxima-io/storefront-core 0.3.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -17
- package/dist/addresses/address-book.d.ts +36 -0
- package/dist/addresses/address-book.d.ts.map +1 -0
- package/dist/addresses/address-book.js +62 -0
- package/dist/addresses/address-book.js.map +1 -0
- package/dist/analytics/analytics.d.ts +28 -0
- package/dist/analytics/analytics.d.ts.map +1 -0
- package/dist/analytics/analytics.js +124 -0
- package/dist/analytics/analytics.js.map +1 -0
- package/dist/analytics/attribution.d.ts +28 -0
- package/dist/analytics/attribution.d.ts.map +1 -0
- package/dist/analytics/attribution.js +116 -0
- package/dist/analytics/attribution.js.map +1 -0
- package/dist/analytics/session.d.ts +12 -0
- package/dist/analytics/session.d.ts.map +1 -0
- package/dist/analytics/session.js +62 -0
- package/dist/analytics/session.js.map +1 -0
- package/dist/analytics/trackers.d.ts +29 -0
- package/dist/analytics/trackers.d.ts.map +1 -0
- package/dist/analytics/trackers.js +30 -0
- package/dist/analytics/trackers.js.map +1 -0
- package/dist/api/endpoints.d.ts +70 -0
- package/dist/api/endpoints.d.ts.map +1 -0
- package/dist/api/endpoints.js +70 -0
- package/dist/api/endpoints.js.map +1 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +3 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/storefront-client.d.ts +50 -0
- package/dist/api/storefront-client.d.ts.map +1 -0
- package/dist/api/storefront-client.js +123 -0
- package/dist/api/storefront-client.js.map +1 -0
- package/dist/buyer/auth.d.ts +105 -0
- package/dist/buyer/auth.d.ts.map +1 -0
- package/dist/buyer/auth.js +215 -0
- package/dist/buyer/auth.js.map +1 -0
- package/dist/cache/cache.d.ts +31 -0
- package/dist/cache/cache.d.ts.map +1 -0
- package/dist/cache/cache.js +71 -0
- package/dist/cache/cache.js.map +1 -0
- package/dist/campaign/countdown.d.ts +40 -0
- package/dist/campaign/countdown.d.ts.map +1 -0
- package/dist/campaign/countdown.js +71 -0
- package/dist/campaign/countdown.js.map +1 -0
- package/dist/cart/cart.d.ts +57 -0
- package/dist/cart/cart.d.ts.map +1 -0
- package/dist/cart/cart.js +64 -0
- package/dist/cart/cart.js.map +1 -0
- package/dist/catalog/listings.d.ts +87 -0
- package/dist/catalog/listings.d.ts.map +1 -0
- package/dist/catalog/listings.js +140 -0
- package/dist/catalog/listings.js.map +1 -0
- package/dist/cms/payment-methods.d.ts +13 -0
- package/dist/cms/payment-methods.d.ts.map +1 -0
- package/dist/cms/payment-methods.js +41 -0
- package/dist/cms/payment-methods.js.map +1 -0
- package/dist/cms/website.d.ts +76 -0
- package/dist/cms/website.d.ts.map +1 -0
- package/dist/cms/website.js +146 -0
- package/dist/cms/website.js.map +1 -0
- package/dist/cookie-consent/consent.d.ts +22 -0
- package/dist/cookie-consent/consent.d.ts.map +1 -0
- package/dist/cookie-consent/consent.js +93 -0
- package/dist/cookie-consent/consent.js.map +1 -0
- package/dist/index.d.ts +45 -1310
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -1576
- package/dist/index.js.map +1 -1
- package/dist/internal/http.d.ts +5 -0
- package/dist/internal/http.d.ts.map +1 -0
- package/dist/internal/http.js +27 -0
- package/dist/internal/http.js.map +1 -0
- package/dist/orders/guest.d.ts +9 -0
- package/dist/orders/guest.d.ts.map +1 -0
- package/dist/orders/guest.js +30 -0
- package/dist/orders/guest.js.map +1 -0
- package/dist/orders/orders.d.ts +23 -0
- package/dist/orders/orders.d.ts.map +1 -0
- package/dist/orders/orders.js +33 -0
- package/dist/orders/orders.js.map +1 -0
- package/dist/seo/engine-url.d.ts +26 -0
- package/dist/seo/engine-url.d.ts.map +1 -0
- package/dist/seo/engine-url.js +111 -0
- package/dist/seo/engine-url.js.map +1 -0
- package/dist/seo/hreflang.d.ts +19 -0
- package/dist/seo/hreflang.d.ts.map +1 -0
- package/dist/seo/hreflang.js +52 -0
- package/dist/seo/hreflang.js.map +1 -0
- package/dist/seo/indexnow.d.ts +24 -0
- package/dist/seo/indexnow.d.ts.map +1 -0
- package/dist/seo/indexnow.js +50 -0
- package/dist/seo/indexnow.js.map +1 -0
- package/dist/seo/json-ld.d.ts +57 -0
- package/dist/seo/json-ld.d.ts.map +1 -0
- package/dist/seo/json-ld.js +180 -0
- package/dist/seo/json-ld.js.map +1 -0
- package/dist/seo/page-seo.d.ts +21 -0
- package/dist/seo/page-seo.d.ts.map +1 -0
- package/dist/seo/page-seo.js +68 -0
- package/dist/seo/page-seo.js.map +1 -0
- package/dist/seo/robots.d.ts +23 -0
- package/dist/seo/robots.d.ts.map +1 -0
- package/dist/seo/robots.js +35 -0
- package/dist/seo/robots.js.map +1 -0
- package/dist/seo/sitemap.d.ts +35 -0
- package/dist/seo/sitemap.d.ts.map +1 -0
- package/dist/seo/sitemap.js +186 -0
- package/dist/seo/sitemap.js.map +1 -0
- package/dist/server/process.d.ts +136 -0
- package/dist/server/process.d.ts.map +1 -0
- package/dist/server/process.js +143 -0
- package/dist/server/process.js.map +1 -0
- package/dist/types/address.d.ts +40 -0
- package/dist/types/address.d.ts.map +1 -0
- package/dist/types/address.js +2 -0
- package/dist/types/address.js.map +1 -0
- package/dist/types/analytics.d.ts +79 -0
- package/dist/types/analytics.d.ts.map +1 -0
- package/dist/types/analytics.js +2 -0
- package/dist/types/analytics.js.map +1 -0
- package/dist/types/business.d.ts +95 -0
- package/dist/types/business.d.ts.map +1 -0
- package/dist/types/business.js +2 -0
- package/dist/types/business.js.map +1 -0
- package/dist/types/buyer.d.ts +144 -0
- package/dist/types/buyer.d.ts.map +1 -0
- package/dist/types/buyer.js +45 -0
- package/dist/types/buyer.js.map +1 -0
- package/dist/types/campaign.d.ts +51 -0
- package/dist/types/campaign.d.ts.map +1 -0
- package/dist/types/campaign.js +2 -0
- package/dist/types/campaign.js.map +1 -0
- package/dist/types/cart.d.ts +40 -0
- package/dist/types/cart.d.ts.map +1 -0
- package/dist/types/cart.js +2 -0
- package/dist/types/cart.js.map +1 -0
- package/dist/types/catalog.d.ts +164 -0
- package/dist/types/catalog.d.ts.map +1 -0
- package/dist/types/catalog.js +2 -0
- package/dist/types/catalog.js.map +1 -0
- package/dist/types/cms.d.ts +200 -0
- package/dist/types/cms.d.ts.map +1 -0
- package/dist/types/cms.js +2 -0
- package/dist/types/cms.js.map +1 -0
- package/dist/types/cookie-consent.d.ts +18 -0
- package/dist/types/cookie-consent.d.ts.map +1 -0
- package/dist/types/cookie-consent.js +7 -0
- package/dist/types/cookie-consent.js.map +1 -0
- package/dist/types/guest-order.d.ts +16 -0
- package/dist/types/guest-order.d.ts.map +1 -0
- package/dist/types/guest-order.js +9 -0
- package/dist/types/guest-order.js.map +1 -0
- package/dist/types/listing.d.ts +14 -0
- package/dist/types/listing.d.ts.map +1 -0
- package/dist/types/listing.js +2 -0
- package/dist/types/listing.js.map +1 -0
- package/dist/types/order.d.ts +40 -0
- package/dist/types/order.d.ts.map +1 -0
- package/dist/types/order.js +2 -0
- package/dist/types/order.js.map +1 -0
- package/dist/types/seo.d.ts +96 -0
- package/dist/types/seo.d.ts.map +1 -0
- package/dist/types/seo.js +2 -0
- package/dist/types/seo.js.map +1 -0
- package/dist/types/server-env.d.ts +19 -0
- package/dist/types/server-env.d.ts.map +1 -0
- package/dist/types/server-env.js +10 -0
- package/dist/types/server-env.js.map +1 -0
- package/dist/types/wishlist.d.ts +10 -0
- package/dist/types/wishlist.d.ts.map +1 -0
- package/dist/types/wishlist.js +2 -0
- package/dist/types/wishlist.js.map +1 -0
- package/dist/wishlist/wishlist.d.ts +28 -0
- package/dist/wishlist/wishlist.d.ts.map +1 -0
- package/dist/wishlist/wishlist.js +42 -0
- package/dist/wishlist/wishlist.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { BreadcrumbItem, JsonLdLocalBusinessMeta, JsonLdProductMeta, JsonLdWebsiteMeta } from '../types/seo.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build a `WebSite` JSON-LD object.
|
|
4
|
+
* Enables Google's Search Action box in SERPs.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* <script type="application/ld+json" set:html={JSON.stringify(buildWebSiteJsonLd(website))} />
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildWebSiteJsonLd(website: JsonLdWebsiteMeta): Record<string, any>;
|
|
10
|
+
/**
|
|
11
|
+
* Build an `Organization` JSON-LD object.
|
|
12
|
+
* Returns `null` when `website.logo_url` is absent (Google ignores logo-less org markup).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* {orgJsonLd && <script type="application/ld+json" set:html={JSON.stringify(orgJsonLd)} />}
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildOrganizationJsonLd(website: JsonLdWebsiteMeta): Record<string, any> | null;
|
|
18
|
+
/**
|
|
19
|
+
* Build a `Product` JSON-LD object for a product detail page.
|
|
20
|
+
* Includes `Offer` with pricing, currency, and availability.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* <script type="application/ld+json" set:html={JSON.stringify(buildProductJsonLd(product, website))} />
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildProductJsonLd(product: JsonLdProductMeta, website: {
|
|
26
|
+
domain: string;
|
|
27
|
+
currency: string;
|
|
28
|
+
}, options?: {
|
|
29
|
+
productUrl?: string;
|
|
30
|
+
locale?: string;
|
|
31
|
+
localizedPaths?: Record<string, string>;
|
|
32
|
+
defaultLocale?: string;
|
|
33
|
+
}): Record<string, any>;
|
|
34
|
+
/**
|
|
35
|
+
* Build a `BreadcrumbList` JSON-LD object.
|
|
36
|
+
*
|
|
37
|
+
* @param items Array of breadcrumb steps. The last item typically has no `href`.
|
|
38
|
+
* @param siteUrl Absolute site URL, e.g. `https://example.com`
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const crumbs = buildBreadcrumbJsonLd(
|
|
42
|
+
* [{ label: "Inicio", href: "/" }, { label: "Zapatos", href: "/categoria/zapatos" }, { label: "Nike Air Max" }],
|
|
43
|
+
* `https://${website.domain}`
|
|
44
|
+
* );
|
|
45
|
+
* <script type="application/ld+json" set:html={JSON.stringify(crumbs)} />
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildBreadcrumbJsonLd(items: BreadcrumbItem[], siteUrl: string): Record<string, any>;
|
|
48
|
+
/**
|
|
49
|
+
* Build a `LocalBusiness` JSON-LD object for brick-and-mortar stores.
|
|
50
|
+
* Returns `null` when `seo` is falsy so callers can gate rendering easily.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* const schema = buildLocalBusinessJsonLd(seo);
|
|
54
|
+
* {schema && <script type="application/ld+json" set:html={JSON.stringify(schema)} />}
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildLocalBusinessJsonLd(seo: JsonLdLocalBusinessMeta | null | undefined): Record<string, unknown> | null;
|
|
57
|
+
//# sourceMappingURL=json-ld.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-ld.d.ts","sourceRoot":"","sources":["../../src/seo/json-ld.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGrH;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAgBlF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAU5B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAC7C,OAAO,CAAC,EAAE;IACR,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACA,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAqDrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAarB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,uBAAuB,GAAG,IAAI,GAAG,SAAS,GAC9C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA6ChC"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { buildEnginePageUrl } from './engine-url.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build a `WebSite` JSON-LD object.
|
|
4
|
+
* Enables Google's Search Action box in SERPs.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* <script type="application/ld+json" set:html={JSON.stringify(buildWebSiteJsonLd(website))} />
|
|
8
|
+
*/
|
|
9
|
+
export function buildWebSiteJsonLd(website) {
|
|
10
|
+
const siteUrl = `https://${website.domain}`;
|
|
11
|
+
return {
|
|
12
|
+
"@context": "https://schema.org",
|
|
13
|
+
"@type": "WebSite",
|
|
14
|
+
"name": website.name,
|
|
15
|
+
"url": `${siteUrl}/`,
|
|
16
|
+
"potentialAction": {
|
|
17
|
+
"@type": "SearchAction",
|
|
18
|
+
"target": {
|
|
19
|
+
"@type": "EntryPoint",
|
|
20
|
+
"urlTemplate": `${siteUrl}/buscar?q={search_term_string}`,
|
|
21
|
+
},
|
|
22
|
+
"query-input": "required name=search_term_string",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build an `Organization` JSON-LD object.
|
|
28
|
+
* Returns `null` when `website.logo_url` is absent (Google ignores logo-less org markup).
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* {orgJsonLd && <script type="application/ld+json" set:html={JSON.stringify(orgJsonLd)} />}
|
|
32
|
+
*/
|
|
33
|
+
export function buildOrganizationJsonLd(website) {
|
|
34
|
+
if (!website.logo_url)
|
|
35
|
+
return null;
|
|
36
|
+
const siteUrl = `https://${website.domain}`;
|
|
37
|
+
return {
|
|
38
|
+
"@context": "https://schema.org",
|
|
39
|
+
"@type": "Organization",
|
|
40
|
+
"name": website.name,
|
|
41
|
+
"url": `${siteUrl}/`,
|
|
42
|
+
"logo": { "@type": "ImageObject", "url": website.logo_url },
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build a `Product` JSON-LD object for a product detail page.
|
|
47
|
+
* Includes `Offer` with pricing, currency, and availability.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* <script type="application/ld+json" set:html={JSON.stringify(buildProductJsonLd(product, website))} />
|
|
51
|
+
*/
|
|
52
|
+
export function buildProductJsonLd(product, website, options) {
|
|
53
|
+
const siteUrl = `https://${website.domain}`;
|
|
54
|
+
const defaultLocale = options?.defaultLocale ?? 'es';
|
|
55
|
+
const productUrl = options?.productUrl ??
|
|
56
|
+
(options?.localizedPaths && options.locale
|
|
57
|
+
? buildEnginePageUrl(website.domain, options.locale, options.localizedPaths, { slug: product.slug }, defaultLocale)
|
|
58
|
+
: `${siteUrl}/producto/${product.slug}`);
|
|
59
|
+
// Deduplicated image list: primary first, then extras
|
|
60
|
+
const images = [
|
|
61
|
+
product.image,
|
|
62
|
+
...(product.images?.filter((img) => img && img !== product.image) ?? []),
|
|
63
|
+
].filter(Boolean);
|
|
64
|
+
const result = {
|
|
65
|
+
"@context": "https://schema.org",
|
|
66
|
+
"@type": "Product",
|
|
67
|
+
"name": product.name,
|
|
68
|
+
"image": images.length === 1 ? images[0] : images,
|
|
69
|
+
"offers": {
|
|
70
|
+
"@type": "Offer",
|
|
71
|
+
"url": productUrl,
|
|
72
|
+
"price": product.priceRaw,
|
|
73
|
+
"priceCurrency": website.currency,
|
|
74
|
+
"availability": product.inStock === false
|
|
75
|
+
? "https://schema.org/OutOfStock"
|
|
76
|
+
: "https://schema.org/InStock",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
if (product.description)
|
|
80
|
+
result["description"] = product.description;
|
|
81
|
+
if (product.sku)
|
|
82
|
+
result["sku"] = product.sku;
|
|
83
|
+
if (product.productId != null)
|
|
84
|
+
result["identifier"] = String(product.productId);
|
|
85
|
+
if (product.brand)
|
|
86
|
+
result["brand"] = { "@type": "Brand", "name": product.brand };
|
|
87
|
+
// Strikethrough price — only when compare-at is higher than current
|
|
88
|
+
if (product.compareAtPrice && product.compareAtPrice > product.priceRaw) {
|
|
89
|
+
result["offers"]["priceSpecification"] = {
|
|
90
|
+
"@type": "PriceSpecification",
|
|
91
|
+
"price": product.compareAtPrice,
|
|
92
|
+
"priceCurrency": website.currency,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build a `BreadcrumbList` JSON-LD object.
|
|
99
|
+
*
|
|
100
|
+
* @param items Array of breadcrumb steps. The last item typically has no `href`.
|
|
101
|
+
* @param siteUrl Absolute site URL, e.g. `https://example.com`
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* const crumbs = buildBreadcrumbJsonLd(
|
|
105
|
+
* [{ label: "Inicio", href: "/" }, { label: "Zapatos", href: "/categoria/zapatos" }, { label: "Nike Air Max" }],
|
|
106
|
+
* `https://${website.domain}`
|
|
107
|
+
* );
|
|
108
|
+
* <script type="application/ld+json" set:html={JSON.stringify(crumbs)} />
|
|
109
|
+
*/
|
|
110
|
+
export function buildBreadcrumbJsonLd(items, siteUrl) {
|
|
111
|
+
return {
|
|
112
|
+
"@context": "https://schema.org",
|
|
113
|
+
"@type": "BreadcrumbList",
|
|
114
|
+
"itemListElement": items.map((item, i) => ({
|
|
115
|
+
"@type": "ListItem",
|
|
116
|
+
"position": i + 1,
|
|
117
|
+
"name": item.label,
|
|
118
|
+
...(item.href
|
|
119
|
+
? { "item": item.href.startsWith("http") ? item.href : `${siteUrl}${item.href}` }
|
|
120
|
+
: {}),
|
|
121
|
+
})),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Build a `LocalBusiness` JSON-LD object for brick-and-mortar stores.
|
|
126
|
+
* Returns `null` when `seo` is falsy so callers can gate rendering easily.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* const schema = buildLocalBusinessJsonLd(seo);
|
|
130
|
+
* {schema && <script type="application/ld+json" set:html={JSON.stringify(schema)} />}
|
|
131
|
+
*/
|
|
132
|
+
export function buildLocalBusinessJsonLd(seo) {
|
|
133
|
+
if (!seo)
|
|
134
|
+
return null;
|
|
135
|
+
const result = {
|
|
136
|
+
"@context": "https://schema.org",
|
|
137
|
+
"@type": "LocalBusiness",
|
|
138
|
+
"name": seo.name,
|
|
139
|
+
"url": seo.url,
|
|
140
|
+
"@id": `${seo.url}#localbusiness`,
|
|
141
|
+
};
|
|
142
|
+
if (seo.image)
|
|
143
|
+
result["image"] = seo.image;
|
|
144
|
+
if (seo.telephone)
|
|
145
|
+
result["telephone"] = seo.telephone;
|
|
146
|
+
const hasAddress = seo.street_address || seo.address_locality || seo.address_country;
|
|
147
|
+
if (hasAddress) {
|
|
148
|
+
const address = { "@type": "PostalAddress" };
|
|
149
|
+
if (seo.street_address)
|
|
150
|
+
address["streetAddress"] = seo.street_address;
|
|
151
|
+
if (seo.address_locality)
|
|
152
|
+
address["addressLocality"] = seo.address_locality;
|
|
153
|
+
if (seo.address_region)
|
|
154
|
+
address["addressRegion"] = seo.address_region;
|
|
155
|
+
if (seo.postal_code)
|
|
156
|
+
address["postalCode"] = seo.postal_code;
|
|
157
|
+
if (seo.address_country)
|
|
158
|
+
address["addressCountry"] = seo.address_country;
|
|
159
|
+
result["address"] = address;
|
|
160
|
+
}
|
|
161
|
+
if (seo.latitude != null && seo.longitude != null) {
|
|
162
|
+
result["geo"] = {
|
|
163
|
+
"@type": "GeoCoordinates",
|
|
164
|
+
"latitude": seo.latitude,
|
|
165
|
+
"longitude": seo.longitude,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (seo.opening_hours?.length || seo.opens || seo.closes) {
|
|
169
|
+
result["openingHoursSpecification"] = [{
|
|
170
|
+
"@type": "OpeningHoursSpecification",
|
|
171
|
+
...(seo.opening_hours?.length ? { "dayOfWeek": seo.opening_hours } : {}),
|
|
172
|
+
...(seo.opens ? { "opens": seo.opens } : {}),
|
|
173
|
+
...(seo.closes ? { "closes": seo.closes } : {}),
|
|
174
|
+
}];
|
|
175
|
+
}
|
|
176
|
+
if (seo.social_links?.length)
|
|
177
|
+
result["sameAs"] = seo.social_links;
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=json-ld.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-ld.js","sourceRoot":"","sources":["../../src/seo/json-ld.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PageSeoMeta, PageSeoWebsiteMeta } from '../types/seo.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build fully resolved SEO metadata for a page.
|
|
4
|
+
*
|
|
5
|
+
* Data priority:
|
|
6
|
+
* 1. Admin-set `PageSEO` fields in `composition.seo` (explicit overrides)
|
|
7
|
+
* 2. Entity-derived data in `composition.seo.entity_name` / `entity_image` (auto-populated by API)
|
|
8
|
+
* 3. Website-level defaults (`website.og_image_url`, etc.)
|
|
9
|
+
* 4. Hard fallbacks (empty strings)
|
|
10
|
+
*
|
|
11
|
+
* @param seoData The `seo` object from `ProximaCompositionResponse` (may be null)
|
|
12
|
+
* @param website Website-level SEO fields
|
|
13
|
+
* @param locale Locale code for resolving localized strings (e.g. "es")
|
|
14
|
+
* @param currentUrl Absolute URL of the current page — used as canonical fallback
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const seo = buildPageSeo(composition.seo, website, website.locale, canonicalUrl);
|
|
18
|
+
* // → pass to <SiteLayout seo={seo} />
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildPageSeo(seoData: Record<string, any> | null | undefined, website: PageSeoWebsiteMeta, locale: string, currentUrl: string): PageSeoMeta;
|
|
21
|
+
//# sourceMappingURL=page-seo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-seo.d.ts","sourceRoot":"","sources":["../../src/seo/page-seo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEvE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,SAAS,EAC/C,OAAO,EAAE,kBAAkB,EAC3B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,WAAW,CAwDb"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build fully resolved SEO metadata for a page.
|
|
3
|
+
*
|
|
4
|
+
* Data priority:
|
|
5
|
+
* 1. Admin-set `PageSEO` fields in `composition.seo` (explicit overrides)
|
|
6
|
+
* 2. Entity-derived data in `composition.seo.entity_name` / `entity_image` (auto-populated by API)
|
|
7
|
+
* 3. Website-level defaults (`website.og_image_url`, etc.)
|
|
8
|
+
* 4. Hard fallbacks (empty strings)
|
|
9
|
+
*
|
|
10
|
+
* @param seoData The `seo` object from `ProximaCompositionResponse` (may be null)
|
|
11
|
+
* @param website Website-level SEO fields
|
|
12
|
+
* @param locale Locale code for resolving localized strings (e.g. "es")
|
|
13
|
+
* @param currentUrl Absolute URL of the current page — used as canonical fallback
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const seo = buildPageSeo(composition.seo, website, website.locale, canonicalUrl);
|
|
17
|
+
* // → pass to <SiteLayout seo={seo} />
|
|
18
|
+
*/
|
|
19
|
+
export function buildPageSeo(seoData, website, locale, currentUrl) {
|
|
20
|
+
/** Resolve a value that may be a localized dict `{ es: "...", en: "..." }` or a plain string */
|
|
21
|
+
function resolveLocalized(value) {
|
|
22
|
+
if (!value)
|
|
23
|
+
return null;
|
|
24
|
+
if (typeof value === "string")
|
|
25
|
+
return value || null;
|
|
26
|
+
if (typeof value === "object") {
|
|
27
|
+
const map = value;
|
|
28
|
+
return map[locale] ?? map["es"] ?? Object.values(map).find(Boolean) ?? null;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const entityName = seoData?.entity_name ?? null;
|
|
33
|
+
const entityImage = seoData?.entity_image ?? null;
|
|
34
|
+
// Title: admin-set > entity name + site name > site name
|
|
35
|
+
const adminTitle = resolveLocalized(seoData?.meta_title);
|
|
36
|
+
const title = adminTitle ?? (entityName ? `${entityName} | ${website.name}` : website.name);
|
|
37
|
+
// Description: admin-set > entity-based fallback > site name
|
|
38
|
+
const adminDescription = resolveLocalized(seoData?.meta_description);
|
|
39
|
+
const description = adminDescription ??
|
|
40
|
+
(entityName ? `${entityName} en ${website.name}` : website.name);
|
|
41
|
+
// OG image: admin-set > entity image > website og_image_url
|
|
42
|
+
const ogImage = seoData?.og_image ??
|
|
43
|
+
entityImage ??
|
|
44
|
+
(website.og_image_url ?? null);
|
|
45
|
+
const ogType = seoData?.og_type ?? "website";
|
|
46
|
+
const canonicalUrl = seoData?.canonical_url ?? currentUrl;
|
|
47
|
+
const robots = seoData?.robots === "noindex"
|
|
48
|
+
? "noindex, nofollow"
|
|
49
|
+
: "index, follow";
|
|
50
|
+
const rawHandle = website.twitter_handle ?? null;
|
|
51
|
+
const twitterSite = rawHandle ? `@${rawHandle.replace(/^@/, "")}` : null;
|
|
52
|
+
return {
|
|
53
|
+
title,
|
|
54
|
+
description,
|
|
55
|
+
ogTitle: title,
|
|
56
|
+
ogDescription: description,
|
|
57
|
+
ogImage,
|
|
58
|
+
ogType,
|
|
59
|
+
ogSiteName: website.name,
|
|
60
|
+
canonicalUrl,
|
|
61
|
+
robots,
|
|
62
|
+
twitterCard: "summary_large_image",
|
|
63
|
+
twitterSite,
|
|
64
|
+
twitterImage: ogImage,
|
|
65
|
+
faviconUrl: website.favicon_url ?? null,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=page-seo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-seo.js","sourceRoot":"","sources":["../../src/seo/page-seo.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,YAAY,CAC1B,OAA+C,EAC/C,OAA2B,EAC3B,MAAc,EACd,UAAkB;IAElB,gGAAgG;IAChG,SAAS,gBAAgB,CAAC,KAAc;QACtC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,IAAI,IAAI,CAAC;QACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,KAA+B,CAAC;YAC5C,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;QAC9E,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,UAAU,GAAI,OAAO,EAAE,WAAyC,IAAI,IAAI,CAAC;IAC/E,MAAM,WAAW,GAAI,OAAO,EAAE,YAA0C,IAAI,IAAI,CAAC;IAEjF,yDAAyD;IACzD,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACzD,MAAM,KAAK,GAAG,UAAU,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5F,6DAA6D;IAC7D,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;IACrE,MAAM,WAAW,GACf,gBAAgB;QAChB,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnE,4DAA4D;IAC5D,MAAM,OAAO,GACV,OAAO,EAAE,QAAsC;QAChD,WAAW;QACX,CAAC,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC;IAEjC,MAAM,MAAM,GAAI,OAAO,EAAE,OAAqC,IAAI,SAAS,CAAC;IAC5E,MAAM,YAAY,GAAI,OAAO,EAAE,aAA2C,IAAI,UAAU,CAAC;IACzF,MAAM,MAAM,GACT,OAAO,EAAE,MAAoC,KAAK,SAAS;QAC1D,CAAC,CAAC,mBAAmB;QACrB,CAAC,CAAC,eAAe,CAAC;IAEtB,MAAM,SAAS,GAAG,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC;IACjD,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAEzE,OAAO;QACL,KAAK;QACL,WAAW;QACX,OAAO,EAAE,KAAK;QACd,aAAa,EAAE,WAAW;QAC1B,OAAO;QACP,MAAM;QACN,UAAU,EAAE,OAAO,CAAC,IAAI;QACxB,YAAY;QACZ,MAAM;QACN,WAAW,EAAE,qBAAqB;QAClC,WAAW;QACX,YAAY,EAAE,OAAO;QACrB,UAAU,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;KACxC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a `robots.txt` for a storefront.
|
|
3
|
+
*
|
|
4
|
+
* Blocks all private buyer routes and API paths.
|
|
5
|
+
* Adds a `Sitemap:` directive pointing to `{siteUrl}/sitemap.xml`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // apps/{slug}/src/pages/robots.txt.ts
|
|
9
|
+
* import type { APIRoute } from "astro";
|
|
10
|
+
* import { resolveWebsiteOnly } from "@/lib/resolver";
|
|
11
|
+
* import { generateRobotsTxt } from "@proxima-io/storefront-core";
|
|
12
|
+
*
|
|
13
|
+
* export const GET: APIRoute = async () => {
|
|
14
|
+
* const website = await resolveWebsiteOnly();
|
|
15
|
+
* return new Response(generateRobotsTxt(website), {
|
|
16
|
+
* headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "public, max-age=86400" },
|
|
17
|
+
* });
|
|
18
|
+
* };
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateRobotsTxt(website: {
|
|
21
|
+
domain: string;
|
|
22
|
+
}): string;
|
|
23
|
+
//# sourceMappingURL=robots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../../src/seo/robots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAcrE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a `robots.txt` for a storefront.
|
|
3
|
+
*
|
|
4
|
+
* Blocks all private buyer routes and API paths.
|
|
5
|
+
* Adds a `Sitemap:` directive pointing to `{siteUrl}/sitemap.xml`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // apps/{slug}/src/pages/robots.txt.ts
|
|
9
|
+
* import type { APIRoute } from "astro";
|
|
10
|
+
* import { resolveWebsiteOnly } from "@/lib/resolver";
|
|
11
|
+
* import { generateRobotsTxt } from "@proxima-io/storefront-core";
|
|
12
|
+
*
|
|
13
|
+
* export const GET: APIRoute = async () => {
|
|
14
|
+
* const website = await resolveWebsiteOnly();
|
|
15
|
+
* return new Response(generateRobotsTxt(website), {
|
|
16
|
+
* headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "public, max-age=86400" },
|
|
17
|
+
* });
|
|
18
|
+
* };
|
|
19
|
+
*/
|
|
20
|
+
export function generateRobotsTxt(website) {
|
|
21
|
+
const siteUrl = `https://${website.domain}`;
|
|
22
|
+
return [
|
|
23
|
+
"User-agent: *",
|
|
24
|
+
"Allow: /",
|
|
25
|
+
"",
|
|
26
|
+
"Disallow: /cuenta",
|
|
27
|
+
"Disallow: /carrito",
|
|
28
|
+
"Disallow: /checkout",
|
|
29
|
+
"Disallow: /api/",
|
|
30
|
+
"Disallow: /dev/",
|
|
31
|
+
"",
|
|
32
|
+
`Sitemap: ${siteUrl}/sitemap.xml`,
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=robots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/seo/robots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,MAAM,OAAO,GAAG,WAAW,OAAO,CAAC,MAAM,EAAE,CAAC;IAC5C,OAAO;QACL,eAAe;QACf,UAAU;QACV,EAAE;QACF,mBAAmB;QACnB,oBAAoB;QACpB,qBAAqB;QACrB,iBAAiB;QACjB,iBAAiB;QACjB,EAAE;QACF,YAAY,OAAO,cAAc;KAClC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SitemapWebsiteMeta } from '../types/seo.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a complete `sitemap.xml` for a storefront.
|
|
4
|
+
*
|
|
5
|
+
* Includes:
|
|
6
|
+
* 1. Content pages from the website manifest (priority 1.0 for home, 0.8 for others)
|
|
7
|
+
* 2. Category pages from the recursive nav tree (priority 0.8)
|
|
8
|
+
* 3. Brand pages from the brands directory (priority 0.7)
|
|
9
|
+
* 4. Product pages — paginated up to `maxProducts` (priority 0.9)
|
|
10
|
+
*
|
|
11
|
+
* All entries use today's date as `lastmod`.
|
|
12
|
+
*
|
|
13
|
+
* @param website Resolved website object (domain + pages array)
|
|
14
|
+
* @param apiUrl Base URL of the Proxima API (e.g. `http://localhost:8000`)
|
|
15
|
+
* @param options Optional overrides: pageSize (default 60), maxProducts (default 3000)
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // apps/{slug}/src/pages/sitemap.xml.ts
|
|
19
|
+
* import type { APIRoute } from "astro";
|
|
20
|
+
* import { resolveWebsiteOnly } from "@/lib/resolver";
|
|
21
|
+
* import { generateSitemapXml } from "@proxima-io/storefront-core";
|
|
22
|
+
*
|
|
23
|
+
* export const GET: APIRoute = async () => {
|
|
24
|
+
* const website = await resolveWebsiteOnly();
|
|
25
|
+
* const xml = await generateSitemapXml(website, import.meta.env.PROXIMA_API_URL ?? "http://localhost:8000");
|
|
26
|
+
* return new Response(xml, {
|
|
27
|
+
* headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400" },
|
|
28
|
+
* });
|
|
29
|
+
* };
|
|
30
|
+
*/
|
|
31
|
+
export declare function generateSitemapXml(website: SitemapWebsiteMeta, apiUrl: string, options?: {
|
|
32
|
+
pageSize?: number;
|
|
33
|
+
maxProducts?: number;
|
|
34
|
+
}): Promise<string>;
|
|
35
|
+
//# sourceMappingURL=sitemap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../../src/seo/sitemap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAoD1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,kBAAkB,EAC3B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,MAAM,CAAC,CAqIjB"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { buildCanonicalUrl } from './hreflang.js';
|
|
2
|
+
import { fillPathTemplate, findWebsitePageByResolver, slugFromCatalogHref, } from './engine-url.js';
|
|
3
|
+
import { fetchBrandsDirectory, fetchCategoryNavTree, fetchStorefrontProducts, } from '../catalog/listings.js';
|
|
4
|
+
/** Private resolver kinds that must never appear in a sitemap */
|
|
5
|
+
const SITEMAP_PRIVATE_KINDS = new Set([
|
|
6
|
+
"cart",
|
|
7
|
+
"checkout",
|
|
8
|
+
"buyer_login",
|
|
9
|
+
"buyer_account",
|
|
10
|
+
"buyer_register",
|
|
11
|
+
"buyer_password_reset",
|
|
12
|
+
"order_list",
|
|
13
|
+
"order_detail",
|
|
14
|
+
"product_compare",
|
|
15
|
+
]);
|
|
16
|
+
function _xmlEscape(str) {
|
|
17
|
+
return str
|
|
18
|
+
.replace(/&/g, "&")
|
|
19
|
+
.replace(/</g, "<")
|
|
20
|
+
.replace(/>/g, ">")
|
|
21
|
+
.replace(/"/g, """)
|
|
22
|
+
.replace(/'/g, "'");
|
|
23
|
+
}
|
|
24
|
+
function _urlEntry(loc, priority = "0.7", changefreq = "weekly", lastmod) {
|
|
25
|
+
const lines = [
|
|
26
|
+
` <url>`,
|
|
27
|
+
` <loc>${_xmlEscape(loc)}</loc>`,
|
|
28
|
+
` <changefreq>${changefreq}</changefreq>`,
|
|
29
|
+
` <priority>${priority}</priority>`,
|
|
30
|
+
];
|
|
31
|
+
if (lastmod)
|
|
32
|
+
lines.push(` <lastmod>${lastmod}</lastmod>`);
|
|
33
|
+
lines.push(` </url>`);
|
|
34
|
+
return lines.join("\n");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generate a complete `sitemap.xml` for a storefront.
|
|
38
|
+
*
|
|
39
|
+
* Includes:
|
|
40
|
+
* 1. Content pages from the website manifest (priority 1.0 for home, 0.8 for others)
|
|
41
|
+
* 2. Category pages from the recursive nav tree (priority 0.8)
|
|
42
|
+
* 3. Brand pages from the brands directory (priority 0.7)
|
|
43
|
+
* 4. Product pages — paginated up to `maxProducts` (priority 0.9)
|
|
44
|
+
*
|
|
45
|
+
* All entries use today's date as `lastmod`.
|
|
46
|
+
*
|
|
47
|
+
* @param website Resolved website object (domain + pages array)
|
|
48
|
+
* @param apiUrl Base URL of the Proxima API (e.g. `http://localhost:8000`)
|
|
49
|
+
* @param options Optional overrides: pageSize (default 60), maxProducts (default 3000)
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // apps/{slug}/src/pages/sitemap.xml.ts
|
|
53
|
+
* import type { APIRoute } from "astro";
|
|
54
|
+
* import { resolveWebsiteOnly } from "@/lib/resolver";
|
|
55
|
+
* import { generateSitemapXml } from "@proxima-io/storefront-core";
|
|
56
|
+
*
|
|
57
|
+
* export const GET: APIRoute = async () => {
|
|
58
|
+
* const website = await resolveWebsiteOnly();
|
|
59
|
+
* const xml = await generateSitemapXml(website, import.meta.env.PROXIMA_API_URL ?? "http://localhost:8000");
|
|
60
|
+
* return new Response(xml, {
|
|
61
|
+
* headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400" },
|
|
62
|
+
* });
|
|
63
|
+
* };
|
|
64
|
+
*/
|
|
65
|
+
export async function generateSitemapXml(website, apiUrl, options = {}) {
|
|
66
|
+
const PAGE_SIZE = Math.min(60, options.pageSize ?? 60);
|
|
67
|
+
const MAX_PRODUCTS = options.maxProducts ?? 3000;
|
|
68
|
+
const MAX_PAGES = Math.ceil(MAX_PRODUCTS / PAGE_SIZE);
|
|
69
|
+
const TODAY = new Date().toISOString().split("T")[0];
|
|
70
|
+
const siteUrl = `https://${website.domain}`;
|
|
71
|
+
const defaultLocale = website.default_locale ?? "es";
|
|
72
|
+
const enabledLocales = website.enabled_locales?.length
|
|
73
|
+
? website.enabled_locales
|
|
74
|
+
: [defaultLocale];
|
|
75
|
+
const entries = [];
|
|
76
|
+
function localizedTemplatesFor(resolverKind, fallbackTemplate) {
|
|
77
|
+
const page = findWebsitePageByResolver(website.pages, resolverKind);
|
|
78
|
+
const localizedPaths = page?.localized_paths ?? {};
|
|
79
|
+
const hasLocalized = Object.keys(localizedPaths).length > 0;
|
|
80
|
+
if (!hasLocalized) {
|
|
81
|
+
return [{ locale: defaultLocale, template: page?.path ?? fallbackTemplate }];
|
|
82
|
+
}
|
|
83
|
+
return enabledLocales
|
|
84
|
+
.map((locale) => ({
|
|
85
|
+
locale,
|
|
86
|
+
template: localizedPaths[locale] ?? localizedPaths[defaultLocale] ?? page?.path ?? fallbackTemplate,
|
|
87
|
+
}))
|
|
88
|
+
.filter((item) => Boolean(item.template));
|
|
89
|
+
}
|
|
90
|
+
function pushEngineUrls(resolverKind, fallbackTemplate, slug, priority, changefreq) {
|
|
91
|
+
for (const { locale, template } of localizedTemplatesFor(resolverKind, fallbackTemplate)) {
|
|
92
|
+
const logicalPath = template.includes('{')
|
|
93
|
+
? fillPathTemplate(template, { slug })
|
|
94
|
+
: template;
|
|
95
|
+
const loc = buildCanonicalUrl(website.domain, locale, logicalPath, defaultLocale);
|
|
96
|
+
entries.push(_urlEntry(loc, priority, changefreq, TODAY));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// 1. Content pages from the website manifest
|
|
100
|
+
for (const page of website.pages ?? []) {
|
|
101
|
+
if (SITEMAP_PRIVATE_KINDS.has(page.resolver_kind))
|
|
102
|
+
continue;
|
|
103
|
+
if (page.resolver_kind !== "content_page")
|
|
104
|
+
continue;
|
|
105
|
+
const localizedPaths = page.localized_paths ?? {};
|
|
106
|
+
const localesToEmit = Object.keys(localizedPaths).length > 0
|
|
107
|
+
? enabledLocales.filter((locale) => localizedPaths[locale] || page.path)
|
|
108
|
+
: [defaultLocale];
|
|
109
|
+
for (const locale of localesToEmit) {
|
|
110
|
+
const path = localizedPaths[locale] ?? page.path;
|
|
111
|
+
if (!path)
|
|
112
|
+
continue;
|
|
113
|
+
const priority = path === "/" ? "1.0" : "0.8";
|
|
114
|
+
const changefreq = path === "/" ? "daily" : "weekly";
|
|
115
|
+
const loc = buildCanonicalUrl(website.domain, locale, path, defaultLocale);
|
|
116
|
+
entries.push(_urlEntry(loc, priority, changefreq, TODAY));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// 2. Category pages (recursive nav tree)
|
|
120
|
+
try {
|
|
121
|
+
const tree = await fetchCategoryNavTree({ baseUrl: apiUrl }, website);
|
|
122
|
+
function collectHrefs(nodes) {
|
|
123
|
+
for (const node of nodes) {
|
|
124
|
+
const slug = slugFromCatalogHref(node.href, 'categoria') ??
|
|
125
|
+
slugFromCatalogHref(node.href, 'category') ??
|
|
126
|
+
node.href.split('/').filter(Boolean).pop();
|
|
127
|
+
if (slug) {
|
|
128
|
+
pushEngineUrls('category_detail', '/categoria/{slug}', slug, '0.8', 'daily');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
entries.push(_urlEntry(`${siteUrl}${node.href}`, '0.8', 'daily', TODAY));
|
|
132
|
+
}
|
|
133
|
+
if (node.children.length > 0)
|
|
134
|
+
collectHrefs(node.children);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
collectHrefs(tree.nodes);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
/* API offline — skip category URLs */
|
|
141
|
+
}
|
|
142
|
+
// 3. Brand pages
|
|
143
|
+
try {
|
|
144
|
+
const brandsResult = await fetchBrandsDirectory({ baseUrl: apiUrl }, website);
|
|
145
|
+
for (const brand of brandsResult.items) {
|
|
146
|
+
const slug = slugFromCatalogHref(brand.href, 'marca') ??
|
|
147
|
+
slugFromCatalogHref(brand.href, 'brand') ??
|
|
148
|
+
brand.slug;
|
|
149
|
+
if (slug) {
|
|
150
|
+
pushEngineUrls('brand_detail', '/marca/{slug}', slug, '0.7', 'weekly');
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
entries.push(_urlEntry(`${siteUrl}${brand.href}`, '0.7', 'weekly', TODAY));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
/* API offline — skip brand URLs */
|
|
159
|
+
}
|
|
160
|
+
// 4. Product pages (paginated)
|
|
161
|
+
try {
|
|
162
|
+
let currentPage = 1;
|
|
163
|
+
let totalPages = 1;
|
|
164
|
+
while (currentPage <= totalPages && currentPage <= MAX_PAGES) {
|
|
165
|
+
const result = await fetchStorefrontProducts({ baseUrl: apiUrl }, website, {
|
|
166
|
+
page: currentPage,
|
|
167
|
+
pageSize: PAGE_SIZE,
|
|
168
|
+
});
|
|
169
|
+
for (const product of result.items) {
|
|
170
|
+
pushEngineUrls('product_detail', '/producto/{slug}', product.slug, '0.9', 'weekly');
|
|
171
|
+
}
|
|
172
|
+
totalPages = result.pagination.total_pages;
|
|
173
|
+
currentPage++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
/* API offline — skip product URLs */
|
|
178
|
+
}
|
|
179
|
+
return [
|
|
180
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
181
|
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
182
|
+
...entries,
|
|
183
|
+
"</urlset>",
|
|
184
|
+
].join("\n");
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=sitemap.js.map
|