@sixtyninecommerce/client 0.23.1 → 0.25.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 +25 -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/index.d.ts +17 -1
- package/dist/commerce/index.d.ts.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 +95 -2
- package/dist/commerce/products.js.map +1 -1
- package/dist/commerce/queries.d.ts +114 -27
- package/dist/commerce/queries.d.ts.map +1 -1
- package/dist/commerce/queries.js +219 -1
- 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/subscriptions.d.ts +32 -8
- package/dist/commerce/subscriptions.d.ts.map +1 -1
- package/dist/commerce/subscriptions.js +90 -2
- package/dist/commerce/subscriptions.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 +50 -3
- package/dist/errors.js.map +1 -1
- package/dist/events/envelope.d.ts +36 -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 +13 -0
- 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 +8 -1
- 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 +1283 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsed, strict types for the per-product SEO/social/AI metadata exposed by
|
|
3
|
+
* `Product.metadata` on the shop API. The raw GraphQL payload uses `JSON`
|
|
4
|
+
* scalars for several fields — we parse and validate at the SDK boundary so
|
|
5
|
+
* business logic only ever sees these typed shapes.
|
|
6
|
+
*/
|
|
1
7
|
import type { Asset } from '../types.js';
|
|
2
8
|
export type MetaRobotsIndexDirective = 'index' | 'noindex';
|
|
3
9
|
export type MetaRobotsFollowDirective = 'follow' | 'nofollow';
|
|
@@ -11,6 +17,7 @@ export interface MetaRobots {
|
|
|
11
17
|
maxSnippet: number | null;
|
|
12
18
|
maxImagePreview: MetaRobotsMaxImagePreview;
|
|
13
19
|
maxVideoPreview: number | null;
|
|
20
|
+
/** ISO-8601 instant with timezone, or null. */
|
|
14
21
|
unavailableAfter: string | null;
|
|
15
22
|
}
|
|
16
23
|
export interface QAPair {
|
|
@@ -48,6 +55,13 @@ export interface ProductMetadataAi {
|
|
|
48
55
|
emitFaqpageJsonld: boolean;
|
|
49
56
|
resolvedBotDirectives: Record<string, BotDirective>;
|
|
50
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* The SEO/social metadata subset shared by every page kind (product,
|
|
60
|
+
* collection, CMS). `metaTagsFor()` operates on this shape so title,
|
|
61
|
+
* description, canonical, robots, OpenGraph and Twitter resolution is written
|
|
62
|
+
* once and reused across kinds. Product-only extras (AI directives, review
|
|
63
|
+
* JSON-LD, hreflang overrides, breadcrumb overrides) live on `ProductMetadata`.
|
|
64
|
+
*/
|
|
51
65
|
export interface CommonSeoMetadata {
|
|
52
66
|
seoTitle: string | null;
|
|
53
67
|
seoDescription: string | null;
|
|
@@ -62,7 +76,9 @@ export interface ProductMetadata extends CommonSeoMetadata {
|
|
|
62
76
|
breadcrumbOverride: BreadcrumbOverrideEntry[];
|
|
63
77
|
includeReviewsInJsonld: boolean;
|
|
64
78
|
}
|
|
79
|
+
/** Per-collection SEO metadata (subset of the product shape). */
|
|
65
80
|
export type CollectionMetadata = CommonSeoMetadata;
|
|
81
|
+
/** Per-CMS-item SEO metadata: the shared subset plus an optional AI summary. */
|
|
66
82
|
export interface CmsItemMetadata extends CommonSeoMetadata {
|
|
67
83
|
aiSummary: string | null;
|
|
68
84
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata-types.d.ts","sourceRoot":"","sources":["../../src/seo/metadata-types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"metadata-types.d.ts","sourceRoot":"","sources":["../../src/seo/metadata-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE rule for where a product's canonical URL lives.
|
|
3
|
+
*
|
|
4
|
+
* Three surfaces used to answer this independently, and for a product in
|
|
5
|
+
* several collections with none flagged canonical they gave three different
|
|
6
|
+
* answers for the same product at the same moment:
|
|
7
|
+
*
|
|
8
|
+
* - `seo.metaTagsFor` / `canonicalFor` → /products/{slug} (hardcoded)
|
|
9
|
+
* - `navigation.getRoutes` → shallowest-then-lexicographic
|
|
10
|
+
* collection path
|
|
11
|
+
* - the sitemap artifact → whichever collection path the
|
|
12
|
+
* Shop API happened to return first
|
|
13
|
+
*
|
|
14
|
+
* None of them resolved on the storefronts they were generated for, and a
|
|
15
|
+
* canonical pointing at a 404 is worse than emitting none. This module holds
|
|
16
|
+
* the decision so the surfaces cannot drift again; `services/plugins` imports
|
|
17
|
+
* it from the published client rather than reimplementing it.
|
|
18
|
+
*
|
|
19
|
+
* The rule:
|
|
20
|
+
* 1. If the product belongs to a ROOT collection flagged
|
|
21
|
+
* `customFields.isCanonical`, its canonical URL nests under that
|
|
22
|
+
* collection. Lowest `position` wins; a caller-supplied key breaks ties so
|
|
23
|
+
* the choice is stable across runs and across surfaces.
|
|
24
|
+
* 2. Otherwise the canonical URL is FLAT: `/{productPathSegment}/{slug}`.
|
|
25
|
+
* Never a collection path — which collection a product happens to sit in
|
|
26
|
+
* is not a property of the product, and letting it decide the canonical is
|
|
27
|
+
* what made the three answers diverge.
|
|
28
|
+
*/
|
|
29
|
+
/** Used when a channel sets no `productPathSegment`. */
|
|
1
30
|
export declare const DEFAULT_PRODUCT_PATH_SEGMENT = "products";
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the channel's configured product path segment.
|
|
33
|
+
*
|
|
34
|
+
* NULL, empty and whitespace all mean "unset" → the default, so a channel that
|
|
35
|
+
* never sets the field keeps the shape it had before the field existed. Leading
|
|
36
|
+
* and trailing slashes are tolerated and stripped: a merchant typing
|
|
37
|
+
* `/product/` means the same thing as `product`.
|
|
38
|
+
*
|
|
39
|
+
* Lowercased, because storefront routes are. A merchant typing `Products`
|
|
40
|
+
* would otherwise get `https://site/Products/x` as the canonical for a page
|
|
41
|
+
* served at `/products/x` — a canonical pointing at a 404, plus a case-variant
|
|
42
|
+
* duplicate for crawlers. The channel field's validator accepts either case
|
|
43
|
+
* precisely because this normalizes it.
|
|
44
|
+
*/
|
|
2
45
|
export declare function normalizeProductPathSegment(raw: string | null | undefined): string;
|
|
46
|
+
/**
|
|
47
|
+
* Index of the canonical-root entry, or null when the product has none and the
|
|
48
|
+
* caller should therefore use the flat product path.
|
|
49
|
+
*
|
|
50
|
+
* `tiebreak` keeps the result deterministic when two roots share a `position`.
|
|
51
|
+
* Every caller MUST pass the collection's **id**, and the reason is that the id
|
|
52
|
+
* is the only candidate that is language-invariant:
|
|
53
|
+
*
|
|
54
|
+
* - the route manifest fetches collections PER LANGUAGE, so its `slug` is the
|
|
55
|
+
* nl slug on the nl pass;
|
|
56
|
+
* - the SDK holds ONE fetched entity and emits every locale's URL from it, so
|
|
57
|
+
* its `slug` is whichever language the storefront fetched in.
|
|
58
|
+
*
|
|
59
|
+
* Tiebreaking on a slug therefore picks `wijnen` on one side and `deals` on the
|
|
60
|
+
* other for the same product, and the sitemap ends up flagging a different URL
|
|
61
|
+
* canonical than the page's own `<link rel="canonical">`. The path is worse
|
|
62
|
+
* still: `-` (0x2D) sorts below `/` (0x2F), so `shop` vs `shop-nl` diverges even
|
|
63
|
+
* in one language.
|
|
64
|
+
*
|
|
65
|
+
* The cost is that an equal-`position` tie resolves by an opaque id rather than
|
|
66
|
+
* something a merchant can predict. That is acceptable because it is only
|
|
67
|
+
* reachable by flagging two root collections canonical at the SAME position —
|
|
68
|
+
* give them distinct positions to choose deliberately.
|
|
69
|
+
*/
|
|
3
70
|
export declare function pickCanonicalRootIndex(entries: ReadonlyArray<{
|
|
4
71
|
position: number;
|
|
5
72
|
tiebreak: string;
|
|
6
73
|
}>): number | null;
|
|
74
|
+
/** Vendure's synthetic tree root. Never a user-facing collection. */
|
|
7
75
|
export declare const ROOT_COLLECTION_SLUG = "__root_collection__";
|
|
76
|
+
/**
|
|
77
|
+
* Whether a collection is a USER-FACING root — i.e. `isCanonical` can mean
|
|
78
|
+
* something on it.
|
|
79
|
+
*
|
|
80
|
+
* ONLY a parent that IS the sentinel counts. Two wrong answers to avoid:
|
|
81
|
+
*
|
|
82
|
+
* - `parent == null` was the original predicate and matched nothing real.
|
|
83
|
+
* Vendure's Shop API resolves a top-level collection's parent to the
|
|
84
|
+
* `__root_collection__` sentinel, not to null (verified against production:
|
|
85
|
+
* `collection(id: <top-level>)` returns
|
|
86
|
+
* `parent: { slug: "__root_collection__" }`), so canonical roots were never
|
|
87
|
+
* found and every product fell back to the flat path.
|
|
88
|
+
*
|
|
89
|
+
* - Treating null as root is ALSO wrong, and is why the null branch is gone.
|
|
90
|
+
* On the Shop API `parent` is `undefined` exactly when the real parent is
|
|
91
|
+
* PRIVATE (`CollectionEntityResolver.parent`: `apiType === 'shop' &&
|
|
92
|
+
* parent?.isPrivate ? undefined : parent`). A public collection under a
|
|
93
|
+
* private parent is not a root — and `CollectionService.getBreadcrumbs`
|
|
94
|
+
* does NOT filter private ancestors, so the routes manifest sees the full
|
|
95
|
+
* chain and does not treat it as one either. Calling it a root here would
|
|
96
|
+
* make the page self-declare `/{child}/{product}` while the manifest
|
|
97
|
+
* advertises the flat path: a canonical pointing at a URL no route serves.
|
|
98
|
+
*
|
|
99
|
+
* A parent with no `slug` selected is likewise not provably a root, so it takes
|
|
100
|
+
* the flat path. That is the conservative direction throughout — a wrong
|
|
101
|
+
* nesting is worse than a flat canonical — and the SDK's own product queries
|
|
102
|
+
* select `parent { id slug }` so its callers get the right answer by default.
|
|
103
|
+
*/
|
|
8
104
|
export declare function isUserFacingRoot(collection: {
|
|
9
105
|
parent?: {
|
|
10
106
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"product-path.d.ts","sourceRoot":"","sources":["../../src/seo/product-path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"product-path.d.ts","sourceRoot":"","sources":["../../src/seo/product-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,wDAAwD;AACxD,eAAO,MAAM,4BAA4B,aAAa,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAQlF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,aAAa,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7D,MAAM,GAAG,IAAI,CAiBf;AAED,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,wBAAwB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE;IAC3C,MAAM,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC/C,GAAG,OAAO,CAEV"}
|
package/dist/seo/product-path.js
CHANGED
|
@@ -1,4 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE rule for where a product's canonical URL lives.
|
|
3
|
+
*
|
|
4
|
+
* Three surfaces used to answer this independently, and for a product in
|
|
5
|
+
* several collections with none flagged canonical they gave three different
|
|
6
|
+
* answers for the same product at the same moment:
|
|
7
|
+
*
|
|
8
|
+
* - `seo.metaTagsFor` / `canonicalFor` → /products/{slug} (hardcoded)
|
|
9
|
+
* - `navigation.getRoutes` → shallowest-then-lexicographic
|
|
10
|
+
* collection path
|
|
11
|
+
* - the sitemap artifact → whichever collection path the
|
|
12
|
+
* Shop API happened to return first
|
|
13
|
+
*
|
|
14
|
+
* None of them resolved on the storefronts they were generated for, and a
|
|
15
|
+
* canonical pointing at a 404 is worse than emitting none. This module holds
|
|
16
|
+
* the decision so the surfaces cannot drift again; `services/plugins` imports
|
|
17
|
+
* it from the published client rather than reimplementing it.
|
|
18
|
+
*
|
|
19
|
+
* The rule:
|
|
20
|
+
* 1. If the product belongs to a ROOT collection flagged
|
|
21
|
+
* `customFields.isCanonical`, its canonical URL nests under that
|
|
22
|
+
* collection. Lowest `position` wins; a caller-supplied key breaks ties so
|
|
23
|
+
* the choice is stable across runs and across surfaces.
|
|
24
|
+
* 2. Otherwise the canonical URL is FLAT: `/{productPathSegment}/{slug}`.
|
|
25
|
+
* Never a collection path — which collection a product happens to sit in
|
|
26
|
+
* is not a property of the product, and letting it decide the canonical is
|
|
27
|
+
* what made the three answers diverge.
|
|
28
|
+
*/
|
|
29
|
+
/** Used when a channel sets no `productPathSegment`. */
|
|
1
30
|
export const DEFAULT_PRODUCT_PATH_SEGMENT = 'products';
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the channel's configured product path segment.
|
|
33
|
+
*
|
|
34
|
+
* NULL, empty and whitespace all mean "unset" → the default, so a channel that
|
|
35
|
+
* never sets the field keeps the shape it had before the field existed. Leading
|
|
36
|
+
* and trailing slashes are tolerated and stripped: a merchant typing
|
|
37
|
+
* `/product/` means the same thing as `product`.
|
|
38
|
+
*
|
|
39
|
+
* Lowercased, because storefront routes are. A merchant typing `Products`
|
|
40
|
+
* would otherwise get `https://site/Products/x` as the canonical for a page
|
|
41
|
+
* served at `/products/x` — a canonical pointing at a 404, plus a case-variant
|
|
42
|
+
* duplicate for crawlers. The channel field's validator accepts either case
|
|
43
|
+
* precisely because this normalizes it.
|
|
44
|
+
*/
|
|
2
45
|
export function normalizeProductPathSegment(raw) {
|
|
3
46
|
if (typeof raw !== 'string')
|
|
4
47
|
return DEFAULT_PRODUCT_PATH_SEGMENT;
|
|
@@ -9,6 +52,30 @@ export function normalizeProductPathSegment(raw) {
|
|
|
9
52
|
.toLowerCase();
|
|
10
53
|
return trimmed === '' ? DEFAULT_PRODUCT_PATH_SEGMENT : trimmed;
|
|
11
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Index of the canonical-root entry, or null when the product has none and the
|
|
57
|
+
* caller should therefore use the flat product path.
|
|
58
|
+
*
|
|
59
|
+
* `tiebreak` keeps the result deterministic when two roots share a `position`.
|
|
60
|
+
* Every caller MUST pass the collection's **id**, and the reason is that the id
|
|
61
|
+
* is the only candidate that is language-invariant:
|
|
62
|
+
*
|
|
63
|
+
* - the route manifest fetches collections PER LANGUAGE, so its `slug` is the
|
|
64
|
+
* nl slug on the nl pass;
|
|
65
|
+
* - the SDK holds ONE fetched entity and emits every locale's URL from it, so
|
|
66
|
+
* its `slug` is whichever language the storefront fetched in.
|
|
67
|
+
*
|
|
68
|
+
* Tiebreaking on a slug therefore picks `wijnen` on one side and `deals` on the
|
|
69
|
+
* other for the same product, and the sitemap ends up flagging a different URL
|
|
70
|
+
* canonical than the page's own `<link rel="canonical">`. The path is worse
|
|
71
|
+
* still: `-` (0x2D) sorts below `/` (0x2F), so `shop` vs `shop-nl` diverges even
|
|
72
|
+
* in one language.
|
|
73
|
+
*
|
|
74
|
+
* The cost is that an equal-`position` tie resolves by an opaque id rather than
|
|
75
|
+
* something a merchant can predict. That is acceptable because it is only
|
|
76
|
+
* reachable by flagging two root collections canonical at the SAME position —
|
|
77
|
+
* give them distinct positions to choose deliberately.
|
|
78
|
+
*/
|
|
12
79
|
export function pickCanonicalRootIndex(entries) {
|
|
13
80
|
let best = null;
|
|
14
81
|
for (let i = 0; i < entries.length; i += 1) {
|
|
@@ -25,7 +92,36 @@ export function pickCanonicalRootIndex(entries) {
|
|
|
25
92
|
}
|
|
26
93
|
return best;
|
|
27
94
|
}
|
|
95
|
+
/** Vendure's synthetic tree root. Never a user-facing collection. */
|
|
28
96
|
export const ROOT_COLLECTION_SLUG = '__root_collection__';
|
|
97
|
+
/**
|
|
98
|
+
* Whether a collection is a USER-FACING root — i.e. `isCanonical` can mean
|
|
99
|
+
* something on it.
|
|
100
|
+
*
|
|
101
|
+
* ONLY a parent that IS the sentinel counts. Two wrong answers to avoid:
|
|
102
|
+
*
|
|
103
|
+
* - `parent == null` was the original predicate and matched nothing real.
|
|
104
|
+
* Vendure's Shop API resolves a top-level collection's parent to the
|
|
105
|
+
* `__root_collection__` sentinel, not to null (verified against production:
|
|
106
|
+
* `collection(id: <top-level>)` returns
|
|
107
|
+
* `parent: { slug: "__root_collection__" }`), so canonical roots were never
|
|
108
|
+
* found and every product fell back to the flat path.
|
|
109
|
+
*
|
|
110
|
+
* - Treating null as root is ALSO wrong, and is why the null branch is gone.
|
|
111
|
+
* On the Shop API `parent` is `undefined` exactly when the real parent is
|
|
112
|
+
* PRIVATE (`CollectionEntityResolver.parent`: `apiType === 'shop' &&
|
|
113
|
+
* parent?.isPrivate ? undefined : parent`). A public collection under a
|
|
114
|
+
* private parent is not a root — and `CollectionService.getBreadcrumbs`
|
|
115
|
+
* does NOT filter private ancestors, so the routes manifest sees the full
|
|
116
|
+
* chain and does not treat it as one either. Calling it a root here would
|
|
117
|
+
* make the page self-declare `/{child}/{product}` while the manifest
|
|
118
|
+
* advertises the flat path: a canonical pointing at a URL no route serves.
|
|
119
|
+
*
|
|
120
|
+
* A parent with no `slug` selected is likewise not provably a root, so it takes
|
|
121
|
+
* the flat path. That is the conservative direction throughout — a wrong
|
|
122
|
+
* nesting is worse than a flat canonical — and the SDK's own product queries
|
|
123
|
+
* select `parent { id slug }` so its callers get the right answer by default.
|
|
124
|
+
*/
|
|
29
125
|
export function isUserFacingRoot(collection) {
|
|
30
126
|
return collection.parent?.slug === ROOT_COLLECTION_SLUG;
|
|
31
127
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"product-path.js","sourceRoot":"","sources":["../../src/seo/product-path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"product-path.js","sourceRoot":"","sources":["../../src/seo/product-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,4BAA4B,GAAG,UAAU,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,2BAA2B,CAAC,GAA8B;IACxE,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,4BAA4B,CAAC;IACjE,MAAM,OAAO,GAAG,GAAG;SAChB,IAAI,EAAE;SACN,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;SACzB,IAAI,EAAE;SACN,WAAW,EAAE,CAAC;IACjB,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,OAAO,CAAC;AACjE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAA8D;IAE9D,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QAC1B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,IAAI,GAAG,CAAC,CAAC;YACT,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAE,CAAC;QACjC,IACE,KAAK,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ;YACnC,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC,EAC9E,CAAC;YACD,IAAI,GAAG,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,MAAM,oBAAoB,GAAG,qBAAqB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAEhC;IACC,OAAO,UAAU,CAAC,MAAM,EAAE,IAAI,KAAK,oBAAoB,CAAC;AAC1D,CAAC"}
|
package/dist/seo/robots.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Robots / X-Robots directive composers. Given the parsed `metaRobots` block,
|
|
3
|
+
* produce the strings & tag descriptors a storefront needs to render in
|
|
4
|
+
* `<head>` or to attach to HTTP responses for non-HTML resources.
|
|
5
|
+
*/
|
|
1
6
|
import type { BotDirective, MetaRobots, ProductMetadata } from './metadata-types.js';
|
|
2
7
|
export interface RobotsMetaTag {
|
|
3
8
|
name: string;
|
|
@@ -16,6 +21,18 @@ export interface PerBotDirectives {
|
|
|
16
21
|
metaTags: RobotsMetaTag[];
|
|
17
22
|
xRobotsHeaders: RobotsXRobotsHeader[];
|
|
18
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Compose the `<meta name="robots">` value from a parsed `metaRobots` block.
|
|
26
|
+
* Order matches Google's documented preference: index/noindex, follow/nofollow,
|
|
27
|
+
* boolean flags, then `max-*` keyed values, then `unavailable_after`.
|
|
28
|
+
*/
|
|
19
29
|
export declare function composeRobotsContent(robots: MetaRobots): string;
|
|
30
|
+
/**
|
|
31
|
+
* Compose per-bot directives. Each "deny" entry gets a `noindex, nofollow`
|
|
32
|
+
* directive both as a `<meta>` tag (for HTML pages) and as an `X-Robots-Tag`
|
|
33
|
+
* header (for non-HTML resources). "allow" entries are not emitted — that's
|
|
34
|
+
* the implicit default; explicit allow tags would override stricter
|
|
35
|
+
* site-wide rules.
|
|
36
|
+
*/
|
|
20
37
|
export declare function composePerBotDirectives(metadata: ProductMetadata): PerBotDirectives;
|
|
21
38
|
//# sourceMappingURL=robots.d.ts.map
|
package/dist/seo/robots.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../../src/seo/robots.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../../src/seo/robots.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAErF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACnC,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,cAAc,EAAE,mBAAmB,EAAE,CAAC;CACvC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAY/D;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,gBAAgB,CAkBnF"}
|
package/dist/seo/robots.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose the `<meta name="robots">` value from a parsed `metaRobots` block.
|
|
3
|
+
* Order matches Google's documented preference: index/noindex, follow/nofollow,
|
|
4
|
+
* boolean flags, then `max-*` keyed values, then `unavailable_after`.
|
|
5
|
+
*/
|
|
1
6
|
export function composeRobotsContent(robots) {
|
|
2
7
|
const parts = [robots.index, robots.follow];
|
|
3
8
|
if (robots.noarchive)
|
|
@@ -16,6 +21,13 @@ export function composeRobotsContent(robots) {
|
|
|
16
21
|
}
|
|
17
22
|
return parts.join(', ');
|
|
18
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Compose per-bot directives. Each "deny" entry gets a `noindex, nofollow`
|
|
26
|
+
* directive both as a `<meta>` tag (for HTML pages) and as an `X-Robots-Tag`
|
|
27
|
+
* header (for non-HTML resources). "allow" entries are not emitted — that's
|
|
28
|
+
* the implicit default; explicit allow tags would override stricter
|
|
29
|
+
* site-wide rules.
|
|
30
|
+
*/
|
|
19
31
|
export function composePerBotDirectives(metadata) {
|
|
20
32
|
const bots = metadata.ai.resolvedBotDirectives;
|
|
21
33
|
const metaTags = [];
|
package/dist/seo/robots.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/seo/robots.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/seo/robots.ts"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAkB;IACrD,MAAM,KAAK,GAAa,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACtD,IAAI,MAAM,CAAC,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC9C,IAAI,MAAM,CAAC,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC9C,IAAI,MAAM,CAAC,YAAY;QAAE,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACpD,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IAC/E,KAAK,CAAC,IAAI,CAAC,qBAAqB,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC;IAC1D,IAAI,MAAM,CAAC,eAAe,KAAK,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC;IAC/F,IAAI,MAAM,CAAC,gBAAgB,KAAK,IAAI,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,qBAAqB,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,QAAyB;IAC/D,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC,qBAAqB,CAAC;IAC/C,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,cAAc,GAA0B,EAAE,CAAC;IACjD,KAAK,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,IAAI,SAAS,KAAK,MAAM;YAAE,SAAS;QACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC;QAC3D,cAAc,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,GAAG,GAAG,qBAAqB,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,OAAO;QACL,OAAO,EAAE;YACP,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,KAAK,KAAK,OAAO;YAC5C,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,MAAM,KAAK,QAAQ;SAChD;QACD,IAAI;QACJ,QAAQ;QACR,cAAc;KACf,CAAC;AACJ,CAAC"}
|
package/dist/seo/types.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export type UrlLocaleStrategy = 'path-prefix' | 'subdomain' | 'none';
|
|
2
|
+
/** Locale-aware slug for any translatable entity. */
|
|
2
3
|
export interface TranslatedSlug {
|
|
3
4
|
languageCode: string;
|
|
4
5
|
slug: string;
|
|
5
6
|
}
|
|
7
|
+
/** Minimal Collection shape required for canonical / hreflang resolution. */
|
|
6
8
|
export interface CollectionForSeo {
|
|
7
9
|
id: string;
|
|
8
10
|
slug: string;
|
|
@@ -17,15 +19,20 @@ export interface CollectionForSeo {
|
|
|
17
19
|
isCanonical?: boolean | null;
|
|
18
20
|
} | null;
|
|
19
21
|
}
|
|
22
|
+
/** Product view needed for SEO resolution. Supply the slug + every translation you have. */
|
|
20
23
|
export interface ProductForSeo {
|
|
21
24
|
slug: string;
|
|
22
25
|
translations?: TranslatedSlug[];
|
|
23
26
|
collections: CollectionForSeo[];
|
|
24
27
|
}
|
|
28
|
+
/** CMS resolve-layer item shape for SEO resolution. */
|
|
25
29
|
export interface CmsItemForSeo {
|
|
30
|
+
/** Path already resolved via CMS ResolveService (locale-free canonical path). */
|
|
26
31
|
path: string;
|
|
32
|
+
/** Per-language translated paths. `slug_translations` from the resolve response. */
|
|
27
33
|
slug_translations?: Record<string, string> | null;
|
|
28
34
|
}
|
|
35
|
+
/** Discriminated input to the SEO module methods. */
|
|
29
36
|
export type SeoEntityInput = ({
|
|
30
37
|
kind: 'product';
|
|
31
38
|
} & ProductForSeo) | ({
|
|
@@ -34,8 +41,14 @@ export type SeoEntityInput = ({
|
|
|
34
41
|
kind: 'cms';
|
|
35
42
|
} & CmsItemForSeo);
|
|
36
43
|
export interface HreflangLink {
|
|
44
|
+
/** BCP 47 spelling, ready to put in an `hreflang` attribute (`en-GB`). */
|
|
37
45
|
hreflang: string;
|
|
38
46
|
href: string;
|
|
47
|
+
/**
|
|
48
|
+
* The channel's raw Vendure `LanguageCode` for this row (`en_GB`) — the same
|
|
49
|
+
* locale as `hreflang`, in the spelling the commerce API and OpenGraph use.
|
|
50
|
+
* Optional so existing callers constructing this type keep compiling.
|
|
51
|
+
*/
|
|
39
52
|
languageCode?: string;
|
|
40
53
|
}
|
|
41
54
|
export interface SeoMeta {
|
|
@@ -44,18 +57,44 @@ export interface SeoMeta {
|
|
|
44
57
|
xDefault: string;
|
|
45
58
|
}
|
|
46
59
|
export interface SeoModule {
|
|
60
|
+
/** Resolve the canonical URL for an entity (optionally per-locale). */
|
|
47
61
|
canonicalFor(entity: SeoEntityInput, locale?: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Enumerate hreflang alternates. Only languages the entity actually has a
|
|
64
|
+
* translation in are emitted (plus `x-default`) — Google drops broken pairs.
|
|
65
|
+
*/
|
|
48
66
|
alternatesFor(entity: SeoEntityInput): HreflangLink[];
|
|
67
|
+
/** Combined meta bundle: canonical + alternates + xDefault. */
|
|
49
68
|
metaFor(entity: SeoEntityInput): SeoMeta;
|
|
69
|
+
/** Low-level URL builder — composes `{storefrontUrl}{localePrefix}/{segments...}`. */
|
|
50
70
|
buildUrl(segments: string[], locale?: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Full structured meta tag set for an entity: title, description, canonical,
|
|
73
|
+
* robots, OpenGraph, Twitter, hreflang, JSON-LD. Resolves fallbacks for
|
|
74
|
+
* missing per-product metadata.
|
|
75
|
+
*/
|
|
51
76
|
metaTagsFor(entity: import('./meta-tags.js').MetaTagsEntityInput): import('./meta-tags.js').MetaTagsResult;
|
|
77
|
+
/**
|
|
78
|
+
* Per-bot robots directives: composed `<meta>` tags and `X-Robots-Tag`
|
|
79
|
+
* headers based on the product's resolved bot directives.
|
|
80
|
+
*/
|
|
52
81
|
robotsDirectiveFor(entity: import('./ai.js').AiSummaryInput): import('./robots.js').PerBotDirectives;
|
|
82
|
+
/** AI summary string for storefronts to render as a head meta tag or inline section. */
|
|
53
83
|
aiSummaryFor(entity: import('./ai.js').AiSummaryInput): string | null;
|
|
84
|
+
/** One markdown bullet for the channel `/llms.txt` file. */
|
|
54
85
|
llmsTxtEntryFor(entity: import('./ai.js').LlmsTxtEntryInput): string;
|
|
86
|
+
/** Platform URL of the rendered sitemap index (or one child file). Proxy at /sitemap.xml. */
|
|
55
87
|
sitemapUrl(childFile?: string): string;
|
|
88
|
+
/** Platform URL of the rendered robots.txt. Proxy at /robots.txt. */
|
|
56
89
|
robotsUrl(): string;
|
|
90
|
+
/** Platform URL of the rendered llms.txt (404 unless enabled). Proxy at /llms.txt. */
|
|
57
91
|
llmsTxtUrl(): string;
|
|
92
|
+
/** Platform URL of the IndexNow key file. Proxy at /{key}.txt. */
|
|
58
93
|
indexnowKeyUrl(): string;
|
|
94
|
+
/**
|
|
95
|
+
* Fire-and-forget 404 beacon — call from the storefront's not-found handler
|
|
96
|
+
* so the platform can suggest redirects. Never throws, never blocks.
|
|
97
|
+
*/
|
|
59
98
|
reportNotFound(path: string, options?: {
|
|
60
99
|
referrer?: string;
|
|
61
100
|
}): void;
|
package/dist/seo/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/seo/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/seo/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,12 +5,43 @@ export interface UrlStrategyConfig {
|
|
|
5
5
|
urlLocaleStrategy: UrlLocaleStrategy;
|
|
6
6
|
urlLocalePrefixDefault: boolean;
|
|
7
7
|
defaultLanguageCode: string;
|
|
8
|
+
/**
|
|
9
|
+
* Segment canonical product URLs sit under. Optional on purpose: this type is
|
|
10
|
+
* public API, and a hand-built config from before the field existed must keep
|
|
11
|
+
* working — absent normalizes to `products`, the previous hardcoded value.
|
|
12
|
+
*/
|
|
8
13
|
productPathSegment?: string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Path prefix the storefront serves ALL commerce routes under, e.g. `/store`.
|
|
16
|
+
* Applies to products and collections, never to CMS pages — the same rule
|
|
17
|
+
* the routes manifest applies. Optional for the same compatibility reason.
|
|
18
|
+
*/
|
|
9
19
|
commercePathPrefix?: string | null;
|
|
20
|
+
/** Per-language `commercePathPrefix` overrides, `{ nl: '/winkel' }`. */
|
|
10
21
|
commercePathPrefixTranslations?: Record<string, string> | null;
|
|
11
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the URL-strategy config from the channel's customFields. Throws a
|
|
25
|
+
* structured error when `storefrontUrl` is missing — canonical URLs must be
|
|
26
|
+
* absolute so there's no safe default.
|
|
27
|
+
*/
|
|
12
28
|
export declare function readUrlStrategy(ctx: StoreContext): UrlStrategyConfig;
|
|
29
|
+
/**
|
|
30
|
+
* {@link readUrlStrategy} for a channel read from anywhere — e.g. a fresh
|
|
31
|
+
* `activeChannel` query, whose custom fields can be newer than the snapshot the
|
|
32
|
+
* client took at startup.
|
|
33
|
+
*/
|
|
13
34
|
export declare function urlStrategyFromChannel(customFields: ChannelCustomFields | null | undefined, defaultLanguageCode: string): UrlStrategyConfig;
|
|
35
|
+
/**
|
|
36
|
+
* The commerce path prefix for a locale: the per-language override when one
|
|
37
|
+
* exists, else the channel default. Mirrors `RoutesService.applyCommercePrefix`
|
|
38
|
+
* exactly, including that an empty string disables prefixing for that locale.
|
|
39
|
+
*/
|
|
14
40
|
export declare function commercePrefixFor(cfg: UrlStrategyConfig, locale: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Compose an absolute URL for a given path (segments) and locale, respecting
|
|
43
|
+
* the channel's URL-locale strategy. Segments are joined with `/` and
|
|
44
|
+
* individually encoded; empty segments are dropped.
|
|
45
|
+
*/
|
|
15
46
|
export declare function buildUrl(cfg: UrlStrategyConfig, segments: string[], locale?: string): string;
|
|
16
47
|
//# sourceMappingURL=url-builder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url-builder.d.ts","sourceRoot":"","sources":["../../src/seo/url-builder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,sBAAsB,EAAE,OAAO,CAAC;IAChC,mBAAmB,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"url-builder.d.ts","sourceRoot":"","sources":["../../src/seo/url-builder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,sBAAsB,EAAE,OAAO,CAAC;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,wEAAwE;IACxE,8BAA8B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CAChE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAEpE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,EACpD,mBAAmB,EAAE,MAAM,GAC1B,iBAAiB,CA2BnB;AAsBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOhF;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAwC5F"}
|
package/dist/seo/url-builder.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { StorefrontError } from '../errors.js';
|
|
2
2
|
import { toDnsLabel } from './locale-tag.js';
|
|
3
3
|
import { normalizeProductPathSegment } from './product-path.js';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the URL-strategy config from the channel's customFields. Throws a
|
|
6
|
+
* structured error when `storefrontUrl` is missing — canonical URLs must be
|
|
7
|
+
* absolute so there's no safe default.
|
|
8
|
+
*/
|
|
4
9
|
export function readUrlStrategy(ctx) {
|
|
5
10
|
return urlStrategyFromChannel(ctx.channel.customFields, ctx.channel.defaultLanguageCode);
|
|
6
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* {@link readUrlStrategy} for a channel read from anywhere — e.g. a fresh
|
|
14
|
+
* `activeChannel` query, whose custom fields can be newer than the snapshot the
|
|
15
|
+
* client took at startup.
|
|
16
|
+
*/
|
|
7
17
|
export function urlStrategyFromChannel(customFields, defaultLanguageCode) {
|
|
8
18
|
const cf = customFields ?? {};
|
|
9
19
|
const storefrontUrl = typeof cf.storefrontUrl === 'string' ? cf.storefrontUrl.trim() : '';
|
|
@@ -24,6 +34,11 @@ export function urlStrategyFromChannel(customFields, defaultLanguageCode) {
|
|
|
24
34
|
: null),
|
|
25
35
|
};
|
|
26
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Parse the JSON-encoded per-language prefix overrides. Invalid JSON yields
|
|
39
|
+
* null rather than throwing — a malformed channel setting must not take the
|
|
40
|
+
* canonical URL down with it.
|
|
41
|
+
*/
|
|
27
42
|
function parsePrefixTranslations(raw) {
|
|
28
43
|
if (!raw)
|
|
29
44
|
return null;
|
|
@@ -42,6 +57,11 @@ function parsePrefixTranslations(raw) {
|
|
|
42
57
|
return null;
|
|
43
58
|
}
|
|
44
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* The commerce path prefix for a locale: the per-language override when one
|
|
62
|
+
* exists, else the channel default. Mirrors `RoutesService.applyCommercePrefix`
|
|
63
|
+
* exactly, including that an empty string disables prefixing for that locale.
|
|
64
|
+
*/
|
|
45
65
|
export function commercePrefixFor(cfg, locale) {
|
|
46
66
|
const translations = cfg.commercePathPrefixTranslations;
|
|
47
67
|
const raw = translations && Object.hasOwn(translations, locale)
|
|
@@ -49,6 +69,11 @@ export function commercePrefixFor(cfg, locale) {
|
|
|
49
69
|
: cfg.commercePathPrefix;
|
|
50
70
|
return (raw ?? '').replace(/\/+$/, '');
|
|
51
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Compose an absolute URL for a given path (segments) and locale, respecting
|
|
74
|
+
* the channel's URL-locale strategy. Segments are joined with `/` and
|
|
75
|
+
* individually encoded; empty segments are dropped.
|
|
76
|
+
*/
|
|
52
77
|
export function buildUrl(cfg, segments, locale) {
|
|
53
78
|
const resolvedLocale = locale ?? cfg.defaultLanguageCode;
|
|
54
79
|
const clean = segments
|
|
@@ -57,16 +82,27 @@ export function buildUrl(cfg, segments, locale) {
|
|
|
57
82
|
.map((s) => encodeURI(s));
|
|
58
83
|
const path = clean.length > 0 ? `/${clean.join('/')}` : '';
|
|
59
84
|
const isDefault = resolvedLocale === cfg.defaultLanguageCode;
|
|
85
|
+
// A blank locale emits no segment at all. The previous implementation got this
|
|
86
|
+
// for free because it tested the encoded prefix STRING for emptiness; deriving
|
|
87
|
+
// the flag from `isDefault` alone would turn `locale: ''` into `//path` and, in
|
|
88
|
+
// subdomain mode, the invalid host `https://.example.com`. `??` above does not
|
|
89
|
+
// catch `''`, and this is published API a caller may reach with `locale ?? ''`.
|
|
60
90
|
const emitLocale = resolvedLocale !== '' && (!isDefault || cfg.urlLocalePrefixDefault);
|
|
61
91
|
switch (cfg.urlLocaleStrategy) {
|
|
62
92
|
case 'path-prefix': {
|
|
93
|
+
// The PATH segment keeps the raw Vendure spelling (`/en_GB/...`): the
|
|
94
|
+
// storefront reads this segment back out and passes it straight to the
|
|
95
|
+
// commerce and CMS APIs, which key on the Vendure code.
|
|
63
96
|
const localePart = emitLocale ? `/${encodeURIComponent(resolvedLocale)}` : '';
|
|
64
97
|
return `${cfg.storefrontUrl}${localePart}${path}`;
|
|
65
98
|
}
|
|
66
99
|
case 'subdomain': {
|
|
67
100
|
if (!emitLocale)
|
|
68
101
|
return `${cfg.storefrontUrl}${path}`;
|
|
102
|
+
// A hostname label may not contain `_`, so a region tag has to be
|
|
103
|
+
// hyphenated and lower-cased here: `en_GB` → `en-gb.example.com`.
|
|
69
104
|
const label = encodeURIComponent(toDnsLabel(resolvedLocale));
|
|
105
|
+
// Insert subdomain after the protocol: https://host → https://nl.host
|
|
70
106
|
return (cfg.storefrontUrl.replace(/^(https?:\/\/)([^/]+)/, (_match, scheme, host) => `${scheme}${label}.${host}`) + path);
|
|
71
107
|
}
|
|
72
108
|
case 'none':
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url-builder.js","sourceRoot":"","sources":["../../src/seo/url-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"url-builder.js","sourceRoot":"","sources":["../../src/seo/url-builder.ts"],"names":[],"mappings":"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"}
|