@scayle/storefront-product-detail 1.3.0 → 1.4.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/CHANGELOG.md +7 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +8 -2
- package/dist/runtime/composables/useAllShopProductsForId.d.ts +4 -0
- package/dist/runtime/composables/useAllShopProductsForId.js +8 -0
- package/dist/runtime/rpc/methods/products.d.ts +16 -0
- package/dist/runtime/rpc/methods/products.js +32 -0
- package/dist/runtime/utils/seo.d.ts +19 -1
- package/dist/runtime/utils/seo.js +24 -0
- package/package.json +14 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @scayle/storefront-product-detail
|
|
2
2
|
|
|
3
|
+
## 1.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **\[PDP\]** Added `generateProductHreflangLinks` utils to generate hreflang links for a product for all shops.
|
|
8
|
+
- **\[PDP\]** Added RPC method `getAllShopProductsForId` and the composable `useAllShopProductsForId` for product detail page to fetch product for all shops. This is used to generate hreflang links for the product for all shops.
|
|
9
|
+
|
|
3
10
|
## 1.3.0
|
|
4
11
|
|
|
5
12
|
### Minor Changes
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver, addImportsDir } from '@nuxt/kit';
|
|
1
|
+
import { defineNuxtModule, createResolver, resolvePath, addImportsDir } from '@nuxt/kit';
|
|
2
2
|
|
|
3
3
|
const PACKAGE_NAME = "@scayle/storefront-product-detail";
|
|
4
|
-
const PACKAGE_VERSION = "1.
|
|
4
|
+
const PACKAGE_VERSION = "1.4.0";
|
|
5
5
|
const module = defineNuxtModule({
|
|
6
6
|
meta: {
|
|
7
7
|
name: PACKAGE_NAME,
|
|
@@ -17,6 +17,12 @@ const module = defineNuxtModule({
|
|
|
17
17
|
const { resolve } = createResolver(import.meta.url);
|
|
18
18
|
nuxt.options.alias["#storefront-product-detail"] = resolve("./runtime");
|
|
19
19
|
nuxt.options.build.transpile.push("#storefront-product-detail/runtime");
|
|
20
|
+
nuxt.hook("storefront:custom-rpc:extend", async (customRpcs) => {
|
|
21
|
+
customRpcs.push({
|
|
22
|
+
source: await resolvePath("./runtime/rpc/methods/products"),
|
|
23
|
+
names: ["getAllShopProductsForId"]
|
|
24
|
+
});
|
|
25
|
+
});
|
|
20
26
|
if (options.autoImports) {
|
|
21
27
|
addImportsDir(resolve("./runtime/composables"));
|
|
22
28
|
addImportsDir(resolve("./runtime/utils"));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { KeysOf, NormalizedRpcResponse, UseRpcReturn } from '@scayle/storefront-nuxt/composables';
|
|
2
|
+
import type { MaybeRefOrGetter } from 'vue';
|
|
3
|
+
import type { RpcMethodParameters } from '@scayle/storefront-nuxt';
|
|
4
|
+
export declare function useAllShopProductsById<DataT = NormalizedRpcResponse<'getAllShopProductsForId'>, PickKeys extends KeysOf<DataT> = KeysOf<DataT>, DefaultT = null>(params: MaybeRefOrGetter<RpcMethodParameters<'getAllShopProductsForId'>>, key?: string): UseRpcReturn<'getAllShopProductsForId', DataT, PickKeys, DefaultT>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FetchProductParams, Product, RpcHandler, ShopConfig } from '@scayle/storefront-nuxt';
|
|
2
|
+
type ShopProduct = Pick<ShopConfig, 'locale' | 'path'> & {
|
|
3
|
+
product: Product;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Fetches all shop products for a given product ID.
|
|
7
|
+
*
|
|
8
|
+
* This function retrieves all products from all shops in the runtime configuration.
|
|
9
|
+
* It uses the SAPI client to fetch the product details for each shop.
|
|
10
|
+
*
|
|
11
|
+
* @param options - The parameters for fetching the product.
|
|
12
|
+
*
|
|
13
|
+
* @returns An array of objects containing the products for each shop.
|
|
14
|
+
*/
|
|
15
|
+
export declare const getAllShopProductsForId: RpcHandler<Pick<FetchProductParams, 'id' | 'with'>, ShopProduct[]>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const getAllShopProductsForId = async function getAllShopProductsForId2(options, context) {
|
|
2
|
+
const { runtimeConfiguration, sapiClient, log, cached } = context;
|
|
3
|
+
const fetchAllProducts = async () => {
|
|
4
|
+
const products = [];
|
|
5
|
+
const shops = Object.values(
|
|
6
|
+
runtimeConfiguration.storefront.shops
|
|
7
|
+
);
|
|
8
|
+
await Promise.all(
|
|
9
|
+
shops.map(async (shop) => {
|
|
10
|
+
try {
|
|
11
|
+
const product = await sapiClient.clone({ shopId: shop.shopId }).products.getById(options.id, {
|
|
12
|
+
with: options.with
|
|
13
|
+
});
|
|
14
|
+
products.push({
|
|
15
|
+
locale: shop.locale,
|
|
16
|
+
path: Array.isArray(shop.path) ? shop.path[0] : shop.path,
|
|
17
|
+
product
|
|
18
|
+
});
|
|
19
|
+
} catch (error) {
|
|
20
|
+
log.error(error instanceof Error ? error : String(error));
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
);
|
|
24
|
+
return products;
|
|
25
|
+
};
|
|
26
|
+
const result = await cached(fetchAllProducts, {
|
|
27
|
+
cacheKeyPrefix: `getAllShopProductsForId-${options.id}`,
|
|
28
|
+
ttl: 12 * 60 * 60
|
|
29
|
+
// 12 hours
|
|
30
|
+
})();
|
|
31
|
+
return result;
|
|
32
|
+
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import type { Variant } from '@scayle/storefront-nuxt';
|
|
1
|
+
import type { ShopConfig, Variant } from '@scayle/storefront-nuxt';
|
|
2
2
|
import type { Product, WithContext, ProductGroup } from 'schema-dts';
|
|
3
|
+
import type { MaybeRefOrGetter } from 'vue';
|
|
4
|
+
type ShopProductHref = Pick<ShopConfig, 'locale' | 'path'> & {
|
|
5
|
+
productHref: string;
|
|
6
|
+
};
|
|
3
7
|
/**
|
|
4
8
|
* Generates an object following the schema.org `Product` type with context.
|
|
5
9
|
*
|
|
@@ -45,3 +49,17 @@ export declare const generateProductGroupSchema: ({ productId, name, description
|
|
|
45
49
|
variants: Product[];
|
|
46
50
|
variesBy?: ProductGroup["variesBy"];
|
|
47
51
|
}) => WithContext<ProductGroup>;
|
|
52
|
+
/**
|
|
53
|
+
* Generates hreflang links for a product.
|
|
54
|
+
*
|
|
55
|
+
* @param _productsForAllShops - A list of product links for all shops.
|
|
56
|
+
* @param defaultLocale - The default locale of the store.
|
|
57
|
+
*
|
|
58
|
+
* @returns An array of hreflang links.
|
|
59
|
+
*/
|
|
60
|
+
export declare const generateProductHreflangLinks: (_productsForAllShops: MaybeRefOrGetter<ShopProductHref[]>, defaultLocale: string) => {
|
|
61
|
+
rel: string;
|
|
62
|
+
hreflang: string;
|
|
63
|
+
href: string;
|
|
64
|
+
}[];
|
|
65
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isInStock } from "@scayle/storefront-nuxt";
|
|
2
|
+
import { toValue } from "vue";
|
|
2
3
|
const generateSchemaProductOffer = ({
|
|
3
4
|
variant,
|
|
4
5
|
baseUrl
|
|
@@ -56,3 +57,26 @@ export const generateProductGroupSchema = ({
|
|
|
56
57
|
...variesBy ? { variesBy } : {}
|
|
57
58
|
};
|
|
58
59
|
};
|
|
60
|
+
export const generateProductHreflangLinks = (_productsForAllShops, defaultLocale) => {
|
|
61
|
+
const productsForAllShops = toValue(_productsForAllShops);
|
|
62
|
+
if (!productsForAllShops) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
return productsForAllShops.flatMap(({ productHref, path, locale }) => {
|
|
66
|
+
const links = [
|
|
67
|
+
{
|
|
68
|
+
rel: "alternate",
|
|
69
|
+
hreflang: locale,
|
|
70
|
+
href: productHref
|
|
71
|
+
}
|
|
72
|
+
];
|
|
73
|
+
if (path === defaultLocale) {
|
|
74
|
+
links.push({
|
|
75
|
+
rel: "alternate",
|
|
76
|
+
hreflang: "x-default",
|
|
77
|
+
href: productHref
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return links;
|
|
81
|
+
});
|
|
82
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scayle/storefront-product-detail",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Collection of essential composables and utilities to work with product detail",
|
|
5
5
|
"author": "SCAYLE Commerce Engine",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"lint:fix": "eslint . --fix",
|
|
38
38
|
"format": "dprint check",
|
|
39
39
|
"format:fix": "dprint fmt",
|
|
40
|
-
"test": "vitest --run
|
|
41
|
-
"test:watch": "vitest
|
|
42
|
-
"test:ci": "vitest --run --
|
|
40
|
+
"test": "vitest --run",
|
|
41
|
+
"test:watch": "vitest",
|
|
42
|
+
"test:ci": "vitest --run --coverage --reporter=default --reporter=junit --outputFile=./coverage/junit.xml",
|
|
43
43
|
"typecheck": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
|
|
44
44
|
"package:lint": "publint",
|
|
45
45
|
"verify-packaging": "attw --pack . --profile esm-only"
|
|
@@ -54,28 +54,28 @@
|
|
|
54
54
|
"vue": "^3.5.13"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@arethetypeswrong/cli": "0.18.
|
|
57
|
+
"@arethetypeswrong/cli": "0.18.1",
|
|
58
58
|
"@nuxt/kit": "3.16.2",
|
|
59
59
|
"@nuxt/module-builder": "1.0.1",
|
|
60
60
|
"@nuxt/schema": "3.16.2",
|
|
61
61
|
"@nuxt/test-utils": "3.18.0",
|
|
62
|
-
"@scayle/eslint-config-storefront": "4.5.
|
|
63
|
-
"@scayle/storefront-nuxt": "8.
|
|
64
|
-
"@types/node": "22.15.
|
|
62
|
+
"@scayle/eslint-config-storefront": "4.5.2",
|
|
63
|
+
"@scayle/storefront-nuxt": "8.27.0",
|
|
64
|
+
"@types/node": "22.15.21",
|
|
65
65
|
"@vue/test-utils": "2.4.6",
|
|
66
|
-
"@vueuse/core": "13.
|
|
67
|
-
"dprint": "0.
|
|
66
|
+
"@vueuse/core": "13.2.0",
|
|
67
|
+
"dprint": "0.50.0",
|
|
68
68
|
"eslint-formatter-gitlab": "6.0.0",
|
|
69
|
-
"eslint": "9.
|
|
70
|
-
"happy-dom": "17.4.
|
|
69
|
+
"eslint": "9.27.0",
|
|
70
|
+
"happy-dom": "17.4.7",
|
|
71
71
|
"nuxt": "3.16.2",
|
|
72
72
|
"publint": "0.2.12",
|
|
73
73
|
"schema-dts": "1.1.5",
|
|
74
74
|
"typescript": "5.8.3",
|
|
75
75
|
"unbuild": "3.5.0",
|
|
76
|
-
"vitest": "3.1.
|
|
76
|
+
"vitest": "3.1.4",
|
|
77
77
|
"vue-router": "4.5.1",
|
|
78
78
|
"vue-tsc": "2.2.10",
|
|
79
|
-
"vue": "3.5.
|
|
79
|
+
"vue": "3.5.14"
|
|
80
80
|
}
|
|
81
81
|
}
|