@sixtyninecommerce/client 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/admin/forms.js +19 -0
- package/dist/admin/forms.js.map +1 -1
- package/dist/admin/http.d.ts +9 -0
- package/dist/admin/http.d.ts.map +1 -1
- package/dist/admin/http.js +3 -0
- package/dist/admin/http.js.map +1 -1
- package/dist/admin/index.d.ts +63 -0
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +67 -0
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/redirects.d.ts +6 -0
- package/dist/admin/redirects.d.ts.map +1 -1
- package/dist/admin/redirects.js +12 -0
- package/dist/admin/redirects.js.map +1 -1
- package/dist/admin/types.d.ts +63 -0
- package/dist/admin/types.d.ts.map +1 -1
- package/dist/cache-tags.d.ts +19 -0
- package/dist/cache-tags.d.ts.map +1 -1
- package/dist/cache-tags.js +19 -0
- package/dist/cache-tags.js.map +1 -1
- package/dist/client.d.ts +17 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +17 -0
- package/dist/client.js.map +1 -1
- package/dist/commerce/age-verification.js +3 -3
- package/dist/commerce/age-verification.js.map +1 -1
- package/dist/commerce/branding.js +7 -0
- package/dist/commerce/branding.js.map +1 -1
- package/dist/commerce/cart.js +25 -0
- package/dist/commerce/cart.js.map +1 -1
- package/dist/commerce/checkout.d.ts +68 -0
- package/dist/commerce/checkout.d.ts.map +1 -1
- package/dist/commerce/checkout.js +83 -0
- package/dist/commerce/checkout.js.map +1 -1
- package/dist/commerce/customer.d.ts +58 -0
- package/dist/commerce/customer.d.ts.map +1 -1
- package/dist/commerce/customer.js +65 -0
- package/dist/commerce/customer.js.map +1 -1
- package/dist/commerce/pickup-location-parser.d.ts +18 -0
- package/dist/commerce/pickup-location-parser.d.ts.map +1 -1
- package/dist/commerce/pickup-location-parser.js +28 -0
- package/dist/commerce/pickup-location-parser.js.map +1 -1
- package/dist/commerce/placement-error.d.ts +52 -0
- package/dist/commerce/placement-error.d.ts.map +1 -1
- package/dist/commerce/placement-error.js +87 -0
- package/dist/commerce/placement-error.js.map +1 -1
- package/dist/commerce/products.d.ts +48 -0
- package/dist/commerce/products.d.ts.map +1 -1
- package/dist/commerce/products.js +92 -0
- package/dist/commerce/products.js.map +1 -1
- package/dist/commerce/queries.d.ts +113 -27
- package/dist/commerce/queries.d.ts.map +1 -1
- package/dist/commerce/queries.js +188 -2
- package/dist/commerce/queries.js.map +1 -1
- package/dist/commerce/quotes.js +33 -0
- package/dist/commerce/quotes.js.map +1 -1
- package/dist/commerce/saved-lists.d.ts +7 -7
- package/dist/commerce/saved-lists.d.ts.map +1 -1
- package/dist/commerce/saved-lists.js +1 -1
- package/dist/commerce/search.js +3 -0
- package/dist/commerce/search.js.map +1 -1
- package/dist/commerce/shipping.js +3 -3
- package/dist/commerce/shipping.js.map +1 -1
- package/dist/commerce/stock-notifications.d.ts +11 -0
- package/dist/commerce/stock-notifications.d.ts.map +1 -1
- package/dist/commerce/stock-notifications.js +11 -0
- package/dist/commerce/stock-notifications.js.map +1 -1
- package/dist/commerce/vat-validation.js +4 -4
- package/dist/commerce/vat-validation.js.map +1 -1
- package/dist/constants.d.ts +31 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +32 -0
- package/dist/constants.js.map +1 -1
- package/dist/content/asset-urls.d.ts +50 -0
- package/dist/content/asset-urls.d.ts.map +1 -1
- package/dist/content/asset-urls.js +92 -0
- package/dist/content/asset-urls.js.map +1 -1
- package/dist/content/assets.d.ts +15 -0
- package/dist/content/assets.d.ts.map +1 -1
- package/dist/content/assets.js +15 -0
- package/dist/content/assets.js.map +1 -1
- package/dist/content/collection-pages.d.ts +5 -0
- package/dist/content/collection-pages.d.ts.map +1 -1
- package/dist/content/collection-pages.js +8 -0
- package/dist/content/collection-pages.js.map +1 -1
- package/dist/content/collections.d.ts +14 -0
- package/dist/content/collections.d.ts.map +1 -1
- package/dist/content/collections.js +16 -0
- package/dist/content/collections.js.map +1 -1
- package/dist/content/filtered-products.d.ts +18 -0
- package/dist/content/filtered-products.d.ts.map +1 -1
- package/dist/content/filtered-products.js +16 -0
- package/dist/content/filtered-products.js.map +1 -1
- package/dist/content/form-field-types.d.ts +6 -0
- package/dist/content/form-field-types.d.ts.map +1 -1
- package/dist/content/form-field-types.js +12 -0
- package/dist/content/form-field-types.js.map +1 -1
- package/dist/content/forms.d.ts +16 -0
- package/dist/content/forms.d.ts.map +1 -1
- package/dist/content/forms.js +23 -0
- package/dist/content/forms.js.map +1 -1
- package/dist/content/items.d.ts +26 -0
- package/dist/content/items.d.ts.map +1 -1
- package/dist/content/items.js +33 -0
- package/dist/content/items.js.map +1 -1
- package/dist/content/resolve.js +6 -0
- package/dist/content/resolve.js.map +1 -1
- package/dist/content/sitemap.d.ts +5 -0
- package/dist/content/sitemap.d.ts.map +1 -1
- package/dist/content/sitemap.js +5 -0
- package/dist/content/sitemap.js.map +1 -1
- package/dist/core/context.js +6 -0
- package/dist/core/context.js.map +1 -1
- package/dist/core/headers.js +2 -0
- package/dist/core/headers.js.map +1 -1
- package/dist/core/http.d.ts +8 -0
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +89 -1
- package/dist/core/http.js.map +1 -1
- package/dist/credit/credit-status.js +2 -2
- package/dist/credit/credit-status.js.map +1 -1
- package/dist/credit/payment-terms.js +2 -2
- package/dist/credit/payment-terms.js.map +1 -1
- package/dist/currency/exchange.d.ts +11 -0
- package/dist/currency/exchange.d.ts.map +1 -1
- package/dist/currency/exchange.js +11 -0
- package/dist/currency/exchange.js.map +1 -1
- package/dist/errors.d.ts +50 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +48 -2
- package/dist/errors.js.map +1 -1
- package/dist/events/envelope.d.ts +60 -0
- package/dist/events/envelope.d.ts.map +1 -1
- package/dist/events/envelope.js +12 -0
- package/dist/events/envelope.js.map +1 -1
- package/dist/events/index.d.ts +14 -1
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +13 -0
- package/dist/events/index.js.map +1 -1
- package/dist/events/payloads.d.ts +19 -0
- package/dist/events/payloads.d.ts.map +1 -1
- package/dist/events/payloads.js +12 -0
- package/dist/events/payloads.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/media/index.js +2 -0
- package/dist/media/index.js.map +1 -1
- package/dist/media/urls.d.ts +52 -0
- package/dist/media/urls.d.ts.map +1 -1
- package/dist/media/urls.js +38 -0
- package/dist/media/urls.js.map +1 -1
- package/dist/media/video.d.ts +16 -0
- package/dist/media/video.d.ts.map +1 -1
- package/dist/media/video.js +16 -0
- package/dist/media/video.js.map +1 -1
- package/dist/next/artifact-handler.d.ts +22 -0
- package/dist/next/artifact-handler.d.ts.map +1 -1
- package/dist/next/artifact-handler.js +22 -0
- package/dist/next/artifact-handler.js.map +1 -1
- package/dist/next/event-revalidate-handler.d.ts +67 -0
- package/dist/next/event-revalidate-handler.d.ts.map +1 -1
- package/dist/next/event-revalidate-handler.js +48 -0
- package/dist/next/event-revalidate-handler.js.map +1 -1
- package/dist/next/index.d.ts +6 -0
- package/dist/next/index.d.ts.map +1 -1
- package/dist/next/index.js +6 -0
- package/dist/next/index.js.map +1 -1
- package/dist/next/json-ld.d.ts +9 -0
- package/dist/next/json-ld.d.ts.map +1 -1
- package/dist/next/json-ld.js +9 -0
- package/dist/next/json-ld.js.map +1 -1
- package/dist/next/metadata.d.ts +18 -0
- package/dist/next/metadata.d.ts.map +1 -1
- package/dist/next/metadata.js +21 -0
- package/dist/next/metadata.js.map +1 -1
- package/dist/next/redirect-middleware.d.ts +39 -0
- package/dist/next/redirect-middleware.d.ts.map +1 -1
- package/dist/next/redirect-middleware.js +32 -0
- package/dist/next/redirect-middleware.js.map +1 -1
- package/dist/next/revalidate-handler.d.ts +30 -0
- package/dist/next/revalidate-handler.d.ts.map +1 -1
- package/dist/next/revalidate-handler.js +22 -0
- package/dist/next/revalidate-handler.js.map +1 -1
- package/dist/order-scope.d.ts +27 -0
- package/dist/order-scope.d.ts.map +1 -1
- package/dist/order-scope.js +29 -0
- package/dist/order-scope.js.map +1 -1
- package/dist/pricing/format.d.ts +17 -0
- package/dist/pricing/format.d.ts.map +1 -1
- package/dist/pricing/format.js +19 -0
- package/dist/pricing/format.js.map +1 -1
- package/dist/pricing/index.d.ts +8 -1
- package/dist/pricing/index.d.ts.map +1 -1
- package/dist/pricing/index.js +18 -2
- package/dist/pricing/index.js.map +1 -1
- package/dist/pricing/tax-display.d.ts +77 -0
- package/dist/pricing/tax-display.d.ts.map +1 -1
- package/dist/pricing/tax-display.js +85 -0
- package/dist/pricing/tax-display.js.map +1 -1
- package/dist/pricing/tiers.d.ts +9 -0
- package/dist/pricing/tiers.d.ts.map +1 -1
- package/dist/pricing/tiers.js +9 -0
- package/dist/pricing/tiers.js.map +1 -1
- package/dist/pricing/validation.d.ts +56 -1
- package/dist/pricing/validation.d.ts.map +1 -1
- package/dist/pricing/validation.js +61 -0
- package/dist/pricing/validation.js.map +1 -1
- package/dist/redirects/index.d.ts +1 -0
- package/dist/redirects/index.d.ts.map +1 -1
- package/dist/redirects/index.js +1 -0
- package/dist/redirects/index.js.map +1 -1
- package/dist/redirects/store.d.ts +20 -0
- package/dist/redirects/store.d.ts.map +1 -1
- package/dist/redirects/store.js +57 -0
- package/dist/redirects/store.js.map +1 -1
- package/dist/redirects/types.d.ts +24 -0
- package/dist/redirects/types.d.ts.map +1 -1
- package/dist/redirects/types.js +1 -0
- package/dist/redirects/types.js.map +1 -1
- package/dist/revalidation/event-plan.d.ts +48 -0
- package/dist/revalidation/event-plan.d.ts.map +1 -1
- package/dist/revalidation/event-plan.js +61 -0
- package/dist/revalidation/event-plan.js.map +1 -1
- package/dist/revalidation/event-verify.d.ts +25 -0
- package/dist/revalidation/event-verify.d.ts.map +1 -1
- package/dist/revalidation/event-verify.js +27 -0
- package/dist/revalidation/event-verify.js.map +1 -1
- package/dist/revalidation/types.d.ts +20 -0
- package/dist/revalidation/types.d.ts.map +1 -1
- package/dist/revalidation/types.js +10 -0
- package/dist/revalidation/types.js.map +1 -1
- package/dist/revalidation/url-context.d.ts +25 -0
- package/dist/revalidation/url-context.d.ts.map +1 -1
- package/dist/revalidation/url-context.js +17 -0
- package/dist/revalidation/url-context.js.map +1 -1
- package/dist/revalidation/verify.d.ts +8 -0
- package/dist/revalidation/verify.d.ts.map +1 -1
- package/dist/revalidation/verify.js +8 -0
- package/dist/revalidation/verify.js.map +1 -1
- package/dist/seo/ai.d.ts +17 -0
- package/dist/seo/ai.d.ts.map +1 -1
- package/dist/seo/ai.js +12 -0
- package/dist/seo/ai.js.map +1 -1
- package/dist/seo/alternates.js +13 -0
- package/dist/seo/alternates.js.map +1 -1
- package/dist/seo/artifacts.d.ts +13 -0
- package/dist/seo/artifacts.d.ts.map +1 -1
- package/dist/seo/artifacts.js +19 -0
- package/dist/seo/artifacts.js.map +1 -1
- package/dist/seo/canonical.d.ts +15 -0
- package/dist/seo/canonical.d.ts.map +1 -1
- package/dist/seo/canonical.js +24 -0
- package/dist/seo/canonical.js.map +1 -1
- package/dist/seo/index.js +6 -0
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/json-ld.d.ts +13 -0
- package/dist/seo/json-ld.d.ts.map +1 -1
- package/dist/seo/json-ld.js +32 -0
- package/dist/seo/json-ld.js.map +1 -1
- package/dist/seo/locale-tag.d.ts +31 -0
- package/dist/seo/locale-tag.d.ts.map +1 -1
- package/dist/seo/locale-tag.js +31 -0
- package/dist/seo/locale-tag.js.map +1 -1
- package/dist/seo/meta-tags.d.ts +29 -0
- package/dist/seo/meta-tags.d.ts.map +1 -1
- package/dist/seo/meta-tags.js +28 -0
- package/dist/seo/meta-tags.js.map +1 -1
- package/dist/seo/metadata-parser.d.ts +24 -0
- package/dist/seo/metadata-parser.d.ts.map +1 -1
- package/dist/seo/metadata-parser.js +38 -0
- package/dist/seo/metadata-parser.js.map +1 -1
- package/dist/seo/metadata-types.d.ts +16 -0
- package/dist/seo/metadata-types.d.ts.map +1 -1
- package/dist/seo/product-path.d.ts +96 -0
- package/dist/seo/product-path.d.ts.map +1 -1
- package/dist/seo/product-path.js +96 -0
- package/dist/seo/product-path.js.map +1 -1
- package/dist/seo/robots.d.ts +17 -0
- package/dist/seo/robots.d.ts.map +1 -1
- package/dist/seo/robots.js +12 -0
- package/dist/seo/robots.js.map +1 -1
- package/dist/seo/types.d.ts +39 -0
- package/dist/seo/types.d.ts.map +1 -1
- package/dist/seo/url-builder.d.ts +31 -0
- package/dist/seo/url-builder.d.ts.map +1 -1
- package/dist/seo/url-builder.js +36 -0
- package/dist/seo/url-builder.js.map +1 -1
- package/dist/types.d.ts +1163 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/seo/alternates.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { canonicalFor } from './canonical.js';
|
|
2
2
|
import { toBcp47 } from './locale-tag.js';
|
|
3
|
+
/**
|
|
4
|
+
* Languages an entity actually has a translation for. Google prefers hreflang
|
|
5
|
+
* pairs that resolve, so we only emit one row per existing translation.
|
|
6
|
+
*/
|
|
3
7
|
function translatedLanguagesFor(entity) {
|
|
4
8
|
switch (entity.kind) {
|
|
5
9
|
case 'product': {
|
|
6
10
|
const fromProduct = (entity.translations ?? []).map((t) => t.languageCode);
|
|
11
|
+
// Root canonical slugs come from collections — if the canonical root
|
|
12
|
+
// has a translation in a language the product doesn't, we still can't
|
|
13
|
+
// reliably build that variant. Stick with product translations as the
|
|
14
|
+
// source of truth for what languages to emit.
|
|
7
15
|
return fromProduct;
|
|
8
16
|
}
|
|
9
17
|
case 'collection':
|
|
@@ -14,9 +22,14 @@ function translatedLanguagesFor(entity) {
|
|
|
14
22
|
}
|
|
15
23
|
export function alternatesFor(cfg, entity) {
|
|
16
24
|
const languages = Array.from(new Set(translatedLanguagesFor(entity)));
|
|
25
|
+
// Always include the default language — if the entity has no translations
|
|
26
|
+
// at all we still want `x-default` to point somewhere sensible.
|
|
17
27
|
if (!languages.includes(cfg.defaultLanguageCode)) {
|
|
18
28
|
languages.unshift(cfg.defaultLanguageCode);
|
|
19
29
|
}
|
|
30
|
+
// `hreflang` must be BCP 47 — a raw `en_GB` makes Google drop the tag. The
|
|
31
|
+
// untouched Vendure code rides along in `languageCode` for the consumers that
|
|
32
|
+
// need that spelling (OpenGraph, and matching per-entity overrides).
|
|
20
33
|
const rows = languages.map((lang) => ({
|
|
21
34
|
hreflang: toBcp47(lang),
|
|
22
35
|
href: canonicalFor(cfg, entity, lang),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alternates.js","sourceRoot":"","sources":["../../src/seo/alternates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"alternates.js","sourceRoot":"","sources":["../../src/seo/alternates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAI1C;;;GAGG;AACH,SAAS,sBAAsB,CAAC,MAAsB;IACpD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,WAAW,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;YAC3E,qEAAqE;YACrE,sEAAsE;YACtE,sEAAsE;YACtE,8CAA8C;YAC9C,OAAO,WAAW,CAAC;QACrB,CAAC;QACD,KAAK,YAAY;YACf,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QAChE,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAsB,EAAE,MAAsB;IAC1E,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtE,0EAA0E;IAC1E,gEAAgE;IAChE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACjD,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC7C,CAAC;IACD,2EAA2E;IAC3E,8EAA8E;IAC9E,qEAAqE;IACrE,MAAM,IAAI,GAAmB,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACpD,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC;QACvB,IAAI,EAAE,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC;QACrC,YAAY,EAAE,IAAI;KACnB,CAAC,CAAC,CAAC;IACJ,IAAI,CAAC,IAAI,CAAC;QACR,QAAQ,EAAE,WAAW;QACrB,IAAI,EAAE,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,mBAAmB,CAAC;KACzD,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/seo/artifacts.d.ts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform artifact URLs + the 404 beacon. The platform renders sitemap.xml,
|
|
3
|
+
* robots.txt, llms.txt and the IndexNow key file per channel; the storefront
|
|
4
|
+
* proxies them on its own origin (see `@sixtyninecommerce/client/next`'s
|
|
5
|
+
* `createSeoArtifactHandler`).
|
|
6
|
+
*/
|
|
1
7
|
import type { StoreContext } from '../types.js';
|
|
2
8
|
export interface SeoArtifactUrls {
|
|
9
|
+
/** Platform URL of the sitemap index, or of one child file when `childFile` is given. */
|
|
3
10
|
sitemapUrl(childFile?: string): string;
|
|
4
11
|
robotsUrl(): string;
|
|
5
12
|
llmsTxtUrl(): string;
|
|
13
|
+
/** Key file for IndexNow ownership verification — proxy it at /{key}.txt. */
|
|
6
14
|
indexnowKeyUrl(): string;
|
|
7
15
|
}
|
|
8
16
|
export declare function createArtifactUrls(ctx: StoreContext): SeoArtifactUrls;
|
|
17
|
+
/**
|
|
18
|
+
* Fire-and-forget 404 report — call from the storefront's not-found handler
|
|
19
|
+
* so the platform can suggest redirects for dead URLs. Never throws, never
|
|
20
|
+
* blocks rendering; deduped per process/session.
|
|
21
|
+
*/
|
|
9
22
|
export declare function reportNotFound(ctx: StoreContext, path: string, options?: {
|
|
10
23
|
referrer?: string;
|
|
11
24
|
}): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../../src/seo/artifacts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../../src/seo/artifacts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,yFAAyF;IACzF,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvC,SAAS,IAAI,MAAM,CAAC;IACpB,UAAU,IAAI,MAAM,CAAC;IACrB,6EAA6E;IAC7E,cAAc,IAAI,MAAM,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,GAAG,eAAe,CAkBrE;AAYD;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9B,IAAI,CA0BN"}
|
package/dist/seo/artifacts.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform artifact URLs + the 404 beacon. The platform renders sitemap.xml,
|
|
3
|
+
* robots.txt, llms.txt and the IndexNow key file per channel; the storefront
|
|
4
|
+
* proxies them on its own origin (see `@sixtyninecommerce/client/next`'s
|
|
5
|
+
* `createSeoArtifactHandler`).
|
|
6
|
+
*/
|
|
1
7
|
export function createArtifactUrls(ctx) {
|
|
2
8
|
const base = `${ctx.pluginsUrl}/seo/artifacts/${encodeURIComponent(ctx.channel.code)}`;
|
|
3
9
|
return {
|
|
@@ -18,8 +24,19 @@ export function createArtifactUrls(ctx) {
|
|
|
18
24
|
};
|
|
19
25
|
}
|
|
20
26
|
const BEACON_TIMEOUT_MS = 2_000;
|
|
27
|
+
/**
|
|
28
|
+
* Per-process dedup set, capped so a long-lived server process being crawled
|
|
29
|
+
* for random URLs can't grow it without bound. Past the cap the set resets:
|
|
30
|
+
* occasional re-reports are fine (the platform aggregates), unbounded memory
|
|
31
|
+
* is not.
|
|
32
|
+
*/
|
|
21
33
|
const MAX_REPORTED_PATHS = 5_000;
|
|
22
34
|
const reportedPaths = new Set();
|
|
35
|
+
/**
|
|
36
|
+
* Fire-and-forget 404 report — call from the storefront's not-found handler
|
|
37
|
+
* so the platform can suggest redirects for dead URLs. Never throws, never
|
|
38
|
+
* blocks rendering; deduped per process/session.
|
|
39
|
+
*/
|
|
23
40
|
export function reportNotFound(ctx, path, options) {
|
|
24
41
|
if (!path.startsWith('/') || path.length > 2000 || reportedPaths.has(path)) {
|
|
25
42
|
return;
|
|
@@ -40,9 +57,11 @@ export function reportNotFound(ctx, path, options) {
|
|
|
40
57
|
signal: AbortSignal.timeout(BEACON_TIMEOUT_MS),
|
|
41
58
|
})
|
|
42
59
|
.catch(() => {
|
|
60
|
+
// beacon — silence is the contract
|
|
43
61
|
});
|
|
44
62
|
}
|
|
45
63
|
catch {
|
|
64
|
+
// even synchronous failures (bad ctx.fetch) must not surface
|
|
46
65
|
}
|
|
47
66
|
}
|
|
48
67
|
//# sourceMappingURL=artifacts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../src/seo/artifacts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../src/seo/artifacts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,MAAM,UAAU,kBAAkB,CAAC,GAAiB;IAClD,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,UAAU,kBAAkB,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;IACvF,OAAO;QACL,UAAU,CAAC,SAAkB;YAC3B,OAAO,SAAS;gBACd,CAAC,CAAC,GAAG,IAAI,aAAa,kBAAkB,CAAC,SAAS,CAAC,EAAE;gBACrD,CAAC,CAAC,GAAG,IAAI,cAAc,CAAC;QAC5B,CAAC;QACD,SAAS;YACP,OAAO,GAAG,IAAI,aAAa,CAAC;QAC9B,CAAC;QACD,UAAU;YACR,OAAO,GAAG,IAAI,WAAW,CAAC;QAC5B,CAAC;QACD,cAAc;YACZ,OAAO,GAAG,IAAI,mBAAmB,CAAC;QACpC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAChC;;;;;GAKG;AACH,MAAM,kBAAkB,GAAG,KAAK,CAAC;AACjC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;AAExC;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAiB,EACjB,IAAY,EACZ,OAA+B;IAE/B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3E,OAAO;IACT,CAAC;IACD,IAAI,aAAa,CAAC,IAAI,IAAI,kBAAkB,EAAE,CAAC;QAC7C,aAAa,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IACD,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAExB,IAAI,CAAC;QACH,KAAK,GAAG;aACL,KAAK,CAAC,GAAG,GAAG,CAAC,UAAU,wBAAwB,EAAE;YAChD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,cAAc,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE;aAC/B;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,IAAI,EAAE,CAAC;YACnE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC;SAC/C,CAAC;aACD,KAAK,CAAC,GAAG,EAAE;YACV,mCAAmC;QACrC,CAAC,CAAC,CAAC;IACP,CAAC;IAAC,MAAM,CAAC;QACP,6DAA6D;IAC/D,CAAC;AACH,CAAC"}
|
package/dist/seo/canonical.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import type { CollectionForSeo, ProductForSeo, SeoEntityInput } from './types.js';
|
|
2
2
|
import { type UrlStrategyConfig } from './url-builder.js';
|
|
3
|
+
/**
|
|
4
|
+
* Pick the canonical root collection for a product, or null when it has none —
|
|
5
|
+
* in which case the caller uses the flat product path.
|
|
6
|
+
*
|
|
7
|
+
* Selection is delegated to `pickCanonicalRootIndex` so this, the routes
|
|
8
|
+
* manifest in `services/plugins` and the BreadcrumbList in `json-ld.ts` cannot
|
|
9
|
+
* drift apart; the collection id is the tiebreak everywhere, because it is the
|
|
10
|
+
* only value that means the same thing on both sides in every language.
|
|
11
|
+
*
|
|
12
|
+
* Exported for the breadcrumb builder, which used to hand-roll its own sort
|
|
13
|
+
* with no tiebreak: on two canonical roots at equal `position` it kept Shop-API
|
|
14
|
+
* order while this picked the lowest slug, so one page could name one root in
|
|
15
|
+
* its BreadcrumbList and a different one in `<link rel="canonical">` — the very
|
|
16
|
+
* divergence this module exists to end.
|
|
17
|
+
*/
|
|
3
18
|
export declare function pickCanonicalRoot(product: ProductForSeo): CollectionForSeo | null;
|
|
4
19
|
export declare function canonicalFor(cfg: UrlStrategyConfig, entity: SeoEntityInput, locale?: string): string;
|
|
5
20
|
//# sourceMappingURL=canonical.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../../src/seo/canonical.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAA+B,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../../src/seo/canonical.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAA+B,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEvF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,GAAG,gBAAgB,GAAG,IAAI,CAQjF;AA4ED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,iBAAiB,EACtB,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAUR"}
|
package/dist/seo/canonical.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { isUserFacingRoot, normalizeProductPathSegment, pickCanonicalRootIndex, } from './product-path.js';
|
|
2
2
|
import { buildUrl, commercePrefixFor } from './url-builder.js';
|
|
3
|
+
/**
|
|
4
|
+
* Pick the canonical root collection for a product, or null when it has none —
|
|
5
|
+
* in which case the caller uses the flat product path.
|
|
6
|
+
*
|
|
7
|
+
* Selection is delegated to `pickCanonicalRootIndex` so this, the routes
|
|
8
|
+
* manifest in `services/plugins` and the BreadcrumbList in `json-ld.ts` cannot
|
|
9
|
+
* drift apart; the collection id is the tiebreak everywhere, because it is the
|
|
10
|
+
* only value that means the same thing on both sides in every language.
|
|
11
|
+
*
|
|
12
|
+
* Exported for the breadcrumb builder, which used to hand-roll its own sort
|
|
13
|
+
* with no tiebreak: on two canonical roots at equal `position` it kept Shop-API
|
|
14
|
+
* order while this picked the lowest slug, so one page could name one root in
|
|
15
|
+
* its BreadcrumbList and a different one in `<link rel="canonical">` — the very
|
|
16
|
+
* divergence this module exists to end.
|
|
17
|
+
*/
|
|
3
18
|
export function pickCanonicalRoot(product) {
|
|
4
19
|
const candidates = product.collections.filter((c) => c.customFields?.isCanonical === true && isUserFacingRoot(c));
|
|
5
20
|
const index = pickCanonicalRootIndex(candidates.map((c) => ({ position: c.position ?? 0, tiebreak: String(c.id) })));
|
|
@@ -16,6 +31,11 @@ function pickTranslatedSlug(translations, fallback, locale, defaultLanguageCode)
|
|
|
16
31
|
}
|
|
17
32
|
function canonicalForProduct(cfg, product, locale) {
|
|
18
33
|
const productSlug = pickTranslatedSlug(product.translations, product.slug, locale, cfg.defaultLanguageCode);
|
|
34
|
+
// The commerce prefix applies to products and collections, never to CMS —
|
|
35
|
+
// the same rule `RoutesService.applyCommercePrefix` applies to the manifest.
|
|
36
|
+
// Without it here, a channel with a prefix got `/store/products/x` in its
|
|
37
|
+
// sitemap and `/products/x` in its <link rel="canonical">: two URLs for one
|
|
38
|
+
// page, one of them a 404 — the divergence this is meant to end.
|
|
19
39
|
const prefix = commercePrefixFor(cfg, locale);
|
|
20
40
|
const root = pickCanonicalRoot(product);
|
|
21
41
|
if (!root) {
|
|
@@ -25,6 +45,10 @@ function canonicalForProduct(cfg, product, locale) {
|
|
|
25
45
|
return buildUrl(cfg, [prefix, rootSlug, productSlug], locale);
|
|
26
46
|
}
|
|
27
47
|
function canonicalForCollection(cfg, collection, locale) {
|
|
48
|
+
// Non-routable roots / subtrees shouldn't emit URLs — callers can detect
|
|
49
|
+
// this by comparing canonical to the empty-path form or by checking
|
|
50
|
+
// `collection.customFields.isRoutable` themselves. We still return a string
|
|
51
|
+
// pointing at the storefront root so `<link rel="canonical">` is never empty.
|
|
28
52
|
if (collection.customFields?.isRoutable === false) {
|
|
29
53
|
return buildUrl(cfg, [], locale);
|
|
30
54
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../src/seo/canonical.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAA0B,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../src/seo/canonical.ts"],"names":[],"mappings":"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"}
|
package/dist/seo/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import { composePerBotDirectives } from './robots.js';
|
|
|
8
8
|
import { buildUrl, readUrlStrategy } from './url-builder.js';
|
|
9
9
|
export function createSeoModule(ctx) {
|
|
10
10
|
const artifacts = createArtifactUrls(ctx);
|
|
11
|
+
// The SAME resolver the media module uses, so an og:image and an <img> src
|
|
12
|
+
// for one asset can never disagree.
|
|
11
13
|
const resolveAssetUrl = createContextAssetUrlResolver(ctx);
|
|
12
14
|
return {
|
|
13
15
|
sitemapUrl: artifacts.sitemapUrl,
|
|
@@ -44,6 +46,10 @@ export function createSeoModule(ctx) {
|
|
|
44
46
|
return metaTagsFor({ cfg, channel: ctx.channel, resolveAssetUrl }, entity);
|
|
45
47
|
},
|
|
46
48
|
robotsDirectiveFor(entity) {
|
|
49
|
+
// Per-bot AI directives (resolvedBotDirectives) are a product-only feature.
|
|
50
|
+
// Product → full per-bot composition. Collection / CMS → no per-bot AI
|
|
51
|
+
// surface, but the page-level index/follow is still derived from the
|
|
52
|
+
// parsed metaRobots so a noindex/nofollow page is reported correctly.
|
|
47
53
|
if (entity.kind === 'product') {
|
|
48
54
|
if (entity.metadata === null) {
|
|
49
55
|
return {
|
package/dist/seo/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/seo/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AAEjE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE7D,MAAM,UAAU,eAAe,CAAC,GAAiB;IAC/C,MAAM,SAAS,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/seo/index.ts"],"names":[],"mappings":"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"}
|
package/dist/seo/json-ld.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema.org JSON-LD composers (Product, BreadcrumbList, FAQPage). The
|
|
3
|
+
* Product composer is defensive — required fields are emitted only when
|
|
4
|
+
* available, optional fields are skipped entirely when null rather than
|
|
5
|
+
* emitted as `null` (Google's Rich Results validator rejects `null` values).
|
|
6
|
+
*/
|
|
1
7
|
import type { AssetUrlResolver } from '../media/urls.js';
|
|
2
8
|
import type { Asset } from '../types.js';
|
|
3
9
|
import type { ProductSeoExtras } from './meta-tags.js';
|
|
@@ -5,6 +11,7 @@ import type { BreadcrumbOverrideEntry, ProductMetadata, QAPair } from './metadat
|
|
|
5
11
|
import type { ProductForSeo } from './types.js';
|
|
6
12
|
import type { UrlStrategyConfig } from './url-builder.js';
|
|
7
13
|
export interface ProductJsonLdInput {
|
|
14
|
+
/** Turns stored asset KEYS into absolute URLs; see `createAssetUrlResolver`. */
|
|
8
15
|
resolveAssetUrl: AssetUrlResolver;
|
|
9
16
|
product: ProductForSeo;
|
|
10
17
|
metadata: ProductMetadata | null;
|
|
@@ -29,9 +36,15 @@ export interface PageJsonLdInput {
|
|
|
29
36
|
title: string;
|
|
30
37
|
description: string;
|
|
31
38
|
image: Asset | null;
|
|
39
|
+
/** Turns stored asset KEYS into absolute URLs; see `createAssetUrlResolver`. */
|
|
32
40
|
resolveAssetUrl: AssetUrlResolver;
|
|
33
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* `CollectionPage` JSON-LD for a collection / category landing page. Optional
|
|
44
|
+
* fields are omitted (not emitted as `null`) so Google's validator stays happy.
|
|
45
|
+
*/
|
|
34
46
|
export declare function composeCollectionJsonLd(input: PageJsonLdInput): Record<string, unknown>;
|
|
47
|
+
/** `WebPage` JSON-LD for a CMS content / marketing page. */
|
|
35
48
|
export declare function composeCmsJsonLd(input: PageJsonLdInput): Record<string, unknown>;
|
|
36
49
|
export declare function composeFaqJsonLd(pairs: ReadonlyArray<QAPair>): Record<string, unknown>;
|
|
37
50
|
//# sourceMappingURL=json-ld.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-ld.d.ts","sourceRoot":"","sources":["../../src/seo/json-ld.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"json-ld.d.ts","sourceRoot":"","sources":["../../src/seo/json-ld.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC5F,OAAO,KAAK,EAAoB,aAAa,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAuB1D,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,eAAe,EAAE,gBAAgB,CAAC;IAClC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACrC,OAAO,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC9D;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyDvF;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,iBAAiB,CAAC;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,aAAa,CAAC,uBAAuB,CAAC,CAAC;CAClD;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkB7F;AAqCD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,gFAAgF;IAChF,eAAe,EAAE,gBAAgB,CAAC;CACnC;AAQD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWvF;AAED,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWhF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUtF"}
|
package/dist/seo/json-ld.js
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema.org JSON-LD composers (Product, BreadcrumbList, FAQPage). The
|
|
3
|
+
* Product composer is defensive — required fields are emitted only when
|
|
4
|
+
* available, optional fields are skipped entirely when null rather than
|
|
5
|
+
* emitted as `null` (Google's Rich Results validator rejects `null` values).
|
|
6
|
+
*/
|
|
1
7
|
import { canonicalFor, pickCanonicalRoot } from './canonical.js';
|
|
8
|
+
/**
|
|
9
|
+
* Absolute image URLs for JSON-LD, largest preset first.
|
|
10
|
+
*
|
|
11
|
+
* The stored values are R2 STORAGE KEYS, so they must go through the caller's
|
|
12
|
+
* resolver — emitting them raw put `rollz/source/26/…/xl/…jpg` into
|
|
13
|
+
* `Product.image`, which resolves to nothing.
|
|
14
|
+
*/
|
|
2
15
|
function pickImageUrls(resolveAssetUrl, ...assets) {
|
|
3
16
|
const urls = [];
|
|
4
17
|
for (const asset of assets) {
|
|
@@ -36,6 +49,8 @@ export function composeProductJsonLd(input) {
|
|
|
36
49
|
url: canonical,
|
|
37
50
|
};
|
|
38
51
|
if (extras.availability) {
|
|
52
|
+
// schema.org expects an ItemAvailability URL, but text is also widely
|
|
53
|
+
// accepted by validators. Pass-through whatever the storefront provides.
|
|
39
54
|
offer.availability = extras.availability;
|
|
40
55
|
}
|
|
41
56
|
if (extras.condition) {
|
|
@@ -80,7 +95,16 @@ export function composeBreadcrumbJsonLd(input) {
|
|
|
80
95
|
})),
|
|
81
96
|
};
|
|
82
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Compute breadcrumb items from the product's collection tree when no
|
|
100
|
+
* override is provided. Walks the canonical root → product chain. The SDK
|
|
101
|
+
* only knows about top-level roots (descendants aren't exposed here), so
|
|
102
|
+
* the chain is short: [root, product]. Storefronts with deeper trees
|
|
103
|
+
* should supply `metadata.breadcrumbOverride`.
|
|
104
|
+
*/
|
|
83
105
|
function breadcrumbItemsFromCollections(cfg, product) {
|
|
106
|
+
// Same picker as the canonical URL, so the trail and the canonical can never
|
|
107
|
+
// name different roots for one product.
|
|
84
108
|
const canonicalRoot = pickCanonicalRoot(product);
|
|
85
109
|
const items = [];
|
|
86
110
|
if (canonicalRoot) {
|
|
@@ -96,6 +120,9 @@ function breadcrumbItemsFromCollections(cfg, product) {
|
|
|
96
120
|
return items;
|
|
97
121
|
}
|
|
98
122
|
function rootLabelFor(collection) {
|
|
123
|
+
// CollectionForSeo doesn't carry a display name (the SEO type is minimal),
|
|
124
|
+
// so we fall back to the slug. Storefronts that want richer labels can
|
|
125
|
+
// pass `metadata.breadcrumbOverride`.
|
|
99
126
|
return collection.slug;
|
|
100
127
|
}
|
|
101
128
|
function firstImageUrl(resolveAssetUrl, image) {
|
|
@@ -104,6 +131,10 @@ function firstImageUrl(resolveAssetUrl, image) {
|
|
|
104
131
|
const raw = image.presets?.xl?.url ?? image.presets?.l?.url ?? image.source;
|
|
105
132
|
return resolveAssetUrl(raw) || undefined;
|
|
106
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* `CollectionPage` JSON-LD for a collection / category landing page. Optional
|
|
136
|
+
* fields are omitted (not emitted as `null`) so Google's validator stays happy.
|
|
137
|
+
*/
|
|
107
138
|
export function composeCollectionJsonLd(input) {
|
|
108
139
|
const out = {
|
|
109
140
|
'@context': 'https://schema.org/',
|
|
@@ -118,6 +149,7 @@ export function composeCollectionJsonLd(input) {
|
|
|
118
149
|
out.image = img;
|
|
119
150
|
return out;
|
|
120
151
|
}
|
|
152
|
+
/** `WebPage` JSON-LD for a CMS content / marketing page. */
|
|
121
153
|
export function composeCmsJsonLd(input) {
|
|
122
154
|
const out = {
|
|
123
155
|
'@context': 'https://schema.org/',
|
package/dist/seo/json-ld.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-ld.js","sourceRoot":"","sources":["../../src/seo/json-ld.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"json-ld.js","sourceRoot":"","sources":["../../src/seo/json-ld.ts"],"names":[],"mappings":"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"}
|
package/dist/seo/locale-tag.d.ts
CHANGED
|
@@ -1,4 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale-tag spelling for SEO output.
|
|
3
|
+
*
|
|
4
|
+
* Vendure `LanguageCode` spells region and script variants with an underscore
|
|
5
|
+
* (`en_GB`, `zh_Hans`). The three places that code surfaces in a page each want a
|
|
6
|
+
* DIFFERENT spelling, so none of this can be normalized once at the source:
|
|
7
|
+
*
|
|
8
|
+
* - `hreflang` attributes are BCP 47 → hyphen (`en-GB`). Google ignores the tag
|
|
9
|
+
* entirely if it does not parse.
|
|
10
|
+
* - `og:locale` / `og:locale:alternate` are OpenGraph → underscore (`en_GB`).
|
|
11
|
+
* Normalizing these would be a regression, so they keep the raw code.
|
|
12
|
+
* - DNS labels forbid `_`, so a locale subdomain has to be hyphenated AND
|
|
13
|
+
* lower-cased (`en-gb.example.com`).
|
|
14
|
+
*
|
|
15
|
+
* URL PATH segments deliberately keep the raw Vendure spelling: the storefront
|
|
16
|
+
* reads the segment straight back out and passes it to the commerce/CMS APIs,
|
|
17
|
+
* which key on the Vendure code, so a hyphenated path would need a reverse map.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately duplicated rather than imported from `@sixtynine-digital/commerce-shared`:
|
|
20
|
+
* this package publishes to npm and must not carry a workspace-only dependency.
|
|
21
|
+
*/
|
|
22
|
+
/** Vendure `LanguageCode` → BCP 47. `en_GB` → `en-GB`. Bare codes pass through. */
|
|
1
23
|
export declare function toBcp47(code: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* A locale rendered as a DNS label: hyphenated and lower-cased, because
|
|
26
|
+
* hostnames are case-insensitive and reject `_`.
|
|
27
|
+
*/
|
|
2
28
|
export declare function toDnsLabel(code: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Base language subtag, lower-cased: `en_GB` → `en`, `nl-BE` → `nl`. Used when a
|
|
31
|
+
* caller supplies a language and a country separately and the two must compose
|
|
32
|
+
* into one well-formed tag — `en_GB` + `GB` has to yield `en-GB`, not `en-GB-GB`.
|
|
33
|
+
*/
|
|
3
34
|
export declare function baseLanguage(code: string): string;
|
|
4
35
|
//# sourceMappingURL=locale-tag.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locale-tag.d.ts","sourceRoot":"","sources":["../../src/seo/locale-tag.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"locale-tag.d.ts","sourceRoot":"","sources":["../../src/seo/locale-tag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,mFAAmF;AACnF,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD"}
|
package/dist/seo/locale-tag.js
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale-tag spelling for SEO output.
|
|
3
|
+
*
|
|
4
|
+
* Vendure `LanguageCode` spells region and script variants with an underscore
|
|
5
|
+
* (`en_GB`, `zh_Hans`). The three places that code surfaces in a page each want a
|
|
6
|
+
* DIFFERENT spelling, so none of this can be normalized once at the source:
|
|
7
|
+
*
|
|
8
|
+
* - `hreflang` attributes are BCP 47 → hyphen (`en-GB`). Google ignores the tag
|
|
9
|
+
* entirely if it does not parse.
|
|
10
|
+
* - `og:locale` / `og:locale:alternate` are OpenGraph → underscore (`en_GB`).
|
|
11
|
+
* Normalizing these would be a regression, so they keep the raw code.
|
|
12
|
+
* - DNS labels forbid `_`, so a locale subdomain has to be hyphenated AND
|
|
13
|
+
* lower-cased (`en-gb.example.com`).
|
|
14
|
+
*
|
|
15
|
+
* URL PATH segments deliberately keep the raw Vendure spelling: the storefront
|
|
16
|
+
* reads the segment straight back out and passes it to the commerce/CMS APIs,
|
|
17
|
+
* which key on the Vendure code, so a hyphenated path would need a reverse map.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately duplicated rather than imported from `@sixtynine-digital/commerce-shared`:
|
|
20
|
+
* this package publishes to npm and must not carry a workspace-only dependency.
|
|
21
|
+
*/
|
|
22
|
+
/** Vendure `LanguageCode` → BCP 47. `en_GB` → `en-GB`. Bare codes pass through. */
|
|
1
23
|
export function toBcp47(code) {
|
|
2
24
|
return code.trim().replaceAll('_', '-');
|
|
3
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* A locale rendered as a DNS label: hyphenated and lower-cased, because
|
|
28
|
+
* hostnames are case-insensitive and reject `_`.
|
|
29
|
+
*/
|
|
4
30
|
export function toDnsLabel(code) {
|
|
5
31
|
return toBcp47(code).toLowerCase();
|
|
6
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Base language subtag, lower-cased: `en_GB` → `en`, `nl-BE` → `nl`. Used when a
|
|
35
|
+
* caller supplies a language and a country separately and the two must compose
|
|
36
|
+
* into one well-formed tag — `en_GB` + `GB` has to yield `en-GB`, not `en-GB-GB`.
|
|
37
|
+
*/
|
|
7
38
|
export function baseLanguage(code) {
|
|
8
39
|
return code.trim().toLowerCase().split(/[-_]/)[0] ?? '';
|
|
9
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locale-tag.js","sourceRoot":"","sources":["../../src/seo/locale-tag.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"locale-tag.js","sourceRoot":"","sources":["../../src/seo/locale-tag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,mFAAmF;AACnF,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1D,CAAC"}
|
package/dist/seo/meta-tags.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `metaTagsFor()` — produce the full HTML meta tag set for any page kind
|
|
3
|
+
* (product, collection, CMS item). The output is structured (not stringified)
|
|
4
|
+
* so storefront apps can render it however they like (React JSX, Astro head
|
|
5
|
+
* slot, HTML string). Title / description / canonical / robots / OpenGraph /
|
|
6
|
+
* Twitter / hreflang resolution is shared across kinds; only JSON-LD and the
|
|
7
|
+
* product-specific OpenGraph fields branch per kind.
|
|
8
|
+
*/
|
|
1
9
|
import type { AssetUrlResolver } from '../media/urls.js';
|
|
2
10
|
import type { Asset, ChannelInfo } from '../types.js';
|
|
3
11
|
import type { CmsItemMetadata, CollectionMetadata, ProductMetadata } from './metadata-types.js';
|
|
@@ -27,26 +35,41 @@ export interface MetaTagsResult {
|
|
|
27
35
|
jsonLd: object[];
|
|
28
36
|
}
|
|
29
37
|
export interface ProductSeoExtras {
|
|
38
|
+
/** Product display name — falls back when `metadata.seoTitle` is empty. */
|
|
30
39
|
name?: string;
|
|
40
|
+
/** Product HTML description — stripped + truncated for meta description fallback. */
|
|
31
41
|
description?: string;
|
|
32
42
|
sku?: string;
|
|
33
43
|
brand?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Display price + currency for `product:price:amount` / `product:price:currency`.
|
|
46
|
+
* Pass the major-unit string (e.g. "29.99") — emit-as-is, no rounding here.
|
|
47
|
+
*/
|
|
34
48
|
price?: {
|
|
35
49
|
amount: string;
|
|
36
50
|
currency: string;
|
|
37
51
|
};
|
|
52
|
+
/** "in stock" | "out of stock" | "preorder" etc. */
|
|
38
53
|
availability?: string;
|
|
54
|
+
/** Product condition. Defaults to "new" when not provided. */
|
|
39
55
|
condition?: 'new' | 'refurbished' | 'used';
|
|
56
|
+
/** Featured asset for og:image / twitter:image fallback. */
|
|
40
57
|
featuredAsset?: Asset | null;
|
|
41
58
|
}
|
|
42
59
|
export interface CollectionSeoExtras {
|
|
60
|
+
/** Collection display name — falls back when `metadata.seoTitle` is empty. */
|
|
43
61
|
name?: string;
|
|
62
|
+
/** Collection HTML description — stripped + truncated for meta description fallback. */
|
|
44
63
|
description?: string;
|
|
64
|
+
/** Featured asset for og:image / twitter:image fallback. */
|
|
45
65
|
featuredAsset?: Asset | null;
|
|
46
66
|
}
|
|
47
67
|
export interface CmsSeoExtras {
|
|
68
|
+
/** CMS item display name / heading — falls back when `metadata.seoTitle` is empty. */
|
|
48
69
|
name?: string;
|
|
70
|
+
/** CMS body / summary HTML — stripped + truncated for meta description fallback. */
|
|
49
71
|
description?: string;
|
|
72
|
+
/** Resolved OpenGraph image for the CMS page. */
|
|
50
73
|
ogImage?: Asset | null;
|
|
51
74
|
}
|
|
52
75
|
export interface ProductMetaTagsInput {
|
|
@@ -75,6 +98,12 @@ export type MetaTagsEntityInput = ProductMetaTagsInput | CollectionMetaTagsInput
|
|
|
75
98
|
interface ContextDeps {
|
|
76
99
|
cfg: UrlStrategyConfig;
|
|
77
100
|
channel: ChannelInfo;
|
|
101
|
+
/**
|
|
102
|
+
* Turns stored asset KEYS into absolute URLs. Required, not optional: an
|
|
103
|
+
* `og:image` holding a raw R2 key is a broken social card on every page,
|
|
104
|
+
* and that is exactly what shipped when this surface read the presets
|
|
105
|
+
* directly. See `createAssetUrlResolver`.
|
|
106
|
+
*/
|
|
78
107
|
resolveAssetUrl: AssetUrlResolver;
|
|
79
108
|
}
|
|
80
109
|
export declare function metaTagsFor(ctx: ContextDeps, input: MetaTagsEntityInput): MetaTagsResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-tags.d.ts","sourceRoot":"","sources":["../../src/seo/meta-tags.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"meta-tags.d.ts","sourceRoot":"","sources":["../../src/seo/meta-tags.ts"],"names":[],"mappings":"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"}
|