@decocms/apps-vtex 7.66.3 → 7.66.5
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/package.json +5 -5
- package/src/client.ts +49 -0
- package/src/loaders/autocomplete.ts +2 -4
- package/src/loaders/intelligentSearch/productDetailsPage.ts +2 -4
- package/src/loaders/intelligentSearch/productList.ts +2 -4
- package/src/loaders/intelligentSearch/productListingPage.ts +2 -3
- package/src/loaders/intelligentSearch/suggestions.ts +2 -4
- package/src/loaders/legacy/relatedProductsLoader.ts +2 -4
- package/src/loaders/productListFull.ts +2 -4
- package/src/loaders/workflow/products.ts +2 -4
- package/src/utils/__tests__/storefrontBaseUrl.test.ts +61 -0
- package/src/utils/enrichment.ts +3 -7
- package/src/utils/similars.ts +2 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/apps-vtex",
|
|
3
|
-
"version": "7.66.
|
|
3
|
+
"version": "7.66.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco commerce app: VTEX integration",
|
|
6
6
|
"repository": {
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"lint:unused": "knip"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@decocms/blocks": "7.66.
|
|
55
|
-
"@decocms/apps-commerce": "7.66.
|
|
56
|
-
"@decocms/apps-website": "7.66.
|
|
57
|
-
"@decocms/tanstack": "7.66.
|
|
54
|
+
"@decocms/blocks": "7.66.5",
|
|
55
|
+
"@decocms/apps-commerce": "7.66.5",
|
|
56
|
+
"@decocms/apps-website": "7.66.5",
|
|
57
|
+
"@decocms/tanstack": "7.66.5"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"@tanstack/react-query": ">=5.0.0",
|
package/src/client.ts
CHANGED
|
@@ -206,6 +206,55 @@ export function vtexHost(environment: string = "vtexcommercestable", config?: Vt
|
|
|
206
206
|
return `${c.account}.${environment}.${domain}`;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Origin every product/SKU/breadcrumb URL in the schema.org payload is built
|
|
211
|
+
* from.
|
|
212
|
+
*
|
|
213
|
+
* Order matters, and the first branch is the whole point: a storefront answers
|
|
214
|
+
* on the host the shopper actually typed, and that is the host its canonical
|
|
215
|
+
* URLs, JSON-LD and sitemap entries have to name. deco-cx/apps got this from
|
|
216
|
+
* the request too (`vtex/loaders/intelligentSearch/productList.ts` passes
|
|
217
|
+
* `baseUrl: url`), and dropping it here is what made a migrated storefront
|
|
218
|
+
* publish structured data pointing somewhere else.
|
|
219
|
+
*
|
|
220
|
+
* `publicUrl` is only a fallback. It is the VTEX *checkout* host
|
|
221
|
+
* (`secure.<brand>.com`), which is a different site from the storefront, and
|
|
222
|
+
* it is stored with whatever shape the admin typed — commonly a full URL
|
|
223
|
+
* (`https://secure.example.com/`). Interpolating that into `https://${...}`
|
|
224
|
+
* produced `https://https://secure.example.com/`, which `new URL()` parses
|
|
225
|
+
* with host `https`, so every product ended up at `https://https/<slug>/p`.
|
|
226
|
+
* Normalising through `URL` removes that whole class of bug.
|
|
227
|
+
*
|
|
228
|
+
* Returns no trailing slash, so `new URL(path, baseUrl)` behaves the same for
|
|
229
|
+
* every branch.
|
|
230
|
+
*/
|
|
231
|
+
export function storefrontBaseUrl(config?: VtexConfig): string {
|
|
232
|
+
const c = config ?? getVtexConfig();
|
|
233
|
+
|
|
234
|
+
// 1. The host serving this request -- the storefront's own origin.
|
|
235
|
+
const request = RequestContext.current?.request;
|
|
236
|
+
if (request) {
|
|
237
|
+
try {
|
|
238
|
+
return new URL(request.url).origin;
|
|
239
|
+
} catch {
|
|
240
|
+
// Fall through: a synthetic Request may carry an unparseable url.
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 2. Configured public URL, normalised whether or not it carries a scheme.
|
|
245
|
+
if (c.publicUrl) {
|
|
246
|
+
const normalised = /^https?:\/\//i.test(c.publicUrl) ? c.publicUrl : `https://${c.publicUrl}`;
|
|
247
|
+
try {
|
|
248
|
+
return new URL(normalised).origin;
|
|
249
|
+
} catch {
|
|
250
|
+
// Fall through to the account host rather than publish a broken origin.
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 3. The account's VTEX host. Always well-formed; never the public brand.
|
|
255
|
+
return `https://${vtexHost("vtexcommercestable", c)}`;
|
|
256
|
+
}
|
|
257
|
+
|
|
209
258
|
function baseUrl(): string {
|
|
210
259
|
return `https://${vtexHost()}`;
|
|
211
260
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Combines /autocomplete_suggestions/ and /product_search/ in parallel,
|
|
5
5
|
* transforms IS products to schema.org via pickSku + toProduct.
|
|
6
6
|
*/
|
|
7
|
-
import { getVtexConfig, intelligentSearch as vtexIS } from "../client";
|
|
7
|
+
import { getVtexConfig, intelligentSearch as vtexIS, storefrontBaseUrl } from "../client";
|
|
8
8
|
import { pickSku, toProduct as toSchemaProduct } from "../utils/transform";
|
|
9
9
|
|
|
10
10
|
export interface AutocompleteProps {
|
|
@@ -40,9 +40,7 @@ export async function autocompleteSearch(props: AutocompleteProps): Promise<Auto
|
|
|
40
40
|
]);
|
|
41
41
|
|
|
42
42
|
const config = getVtexConfig();
|
|
43
|
-
const baseUrl = config
|
|
44
|
-
? `https://${config.publicUrl}`
|
|
45
|
-
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
43
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
46
44
|
|
|
47
45
|
return {
|
|
48
46
|
searches: suggestionsData.searches ?? [],
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ProductDetailsPage } from "@decocms/apps-commerce/types";
|
|
8
|
-
import { getVtexConfig, vtexCachedFetch } from "../../client";
|
|
8
|
+
import { getVtexConfig, storefrontBaseUrl, vtexCachedFetch } from "../../client";
|
|
9
9
|
import PDPDefaultPath from "../paths/PDPDefaultPath";
|
|
10
10
|
import { searchBySlug } from "../../utils/slugCache";
|
|
11
11
|
import { pickSku, toProductPage } from "../../utils/transform";
|
|
@@ -74,9 +74,7 @@ export default async function vtexProductDetailsPage(
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
const product = products[0];
|
|
77
|
-
const baseUrl = config
|
|
78
|
-
? `https://${config.publicUrl}`
|
|
79
|
-
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
77
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
80
78
|
|
|
81
79
|
const sku = pickSku(product, skuId);
|
|
82
80
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Product } from "@decocms/apps-commerce/types";
|
|
10
|
-
import { getVtexConfig, intelligentSearch, toFacetPath } from "../../client";
|
|
10
|
+
import { getVtexConfig, intelligentSearch, storefrontBaseUrl, toFacetPath } from "../../client";
|
|
11
11
|
import { pickSku, sortProducts, toProductShelf } from "../../utils/transform";
|
|
12
12
|
import type { Product as ProductVTEX } from "../../utils/types";
|
|
13
13
|
|
|
@@ -136,9 +136,7 @@ export default async function vtexProductListShelf(
|
|
|
136
136
|
const data = await intelligentSearch<{ products: ProductVTEX[] }>(endpoint, params);
|
|
137
137
|
|
|
138
138
|
const vtexProducts = data.products ?? [];
|
|
139
|
-
const baseUrl = config
|
|
140
|
-
? `https://${config.publicUrl}`
|
|
141
|
-
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
139
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
142
140
|
|
|
143
141
|
let products = vtexProducts.map((p) => {
|
|
144
142
|
const fetchedSkus = ids ? new Set(ids) : null;
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
intelligentSearch,
|
|
5
5
|
type PageType,
|
|
6
6
|
pageTypesFromPath,
|
|
7
|
+
storefrontBaseUrl,
|
|
7
8
|
toFacetPath,
|
|
8
9
|
} from "../../client";
|
|
9
10
|
import { pickSku, toProduct } from "../../utils/transform";
|
|
@@ -441,9 +442,7 @@ export default async function vtexProductListingPage(props: PLPProps): Promise<a
|
|
|
441
442
|
const { products: vtexProducts, pagination, recordsFiltered } = productsResult;
|
|
442
443
|
|
|
443
444
|
// 3. Transform products using shared transform pipeline (same as deco-cx/apps)
|
|
444
|
-
const baseUrl = config
|
|
445
|
-
? `https://${config.publicUrl}`
|
|
446
|
-
: `https://${config.account}.vtexcommercestable.com.br`;
|
|
445
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
447
446
|
|
|
448
447
|
const schemaProducts = (vtexProducts as ProductVTEX[]).map((p) => {
|
|
449
448
|
const sku = pickSku(p);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { Product, Suggestion } from "@decocms/apps-commerce/types";
|
|
7
|
-
import { getVtexConfig, intelligentSearch } from "../../client";
|
|
7
|
+
import { getVtexConfig, intelligentSearch, storefrontBaseUrl } from "../../client";
|
|
8
8
|
import { pickSku, toProduct } from "../../utils/transform";
|
|
9
9
|
import type { Product as ProductVTEX } from "../../utils/types";
|
|
10
10
|
|
|
@@ -29,9 +29,7 @@ export default async function vtexSuggestions(props: SuggestionsProps): Promise<
|
|
|
29
29
|
}));
|
|
30
30
|
|
|
31
31
|
const config = getVtexConfig();
|
|
32
|
-
const baseUrl = config
|
|
33
|
-
? `https://${config.publicUrl}`
|
|
34
|
-
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
32
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
35
33
|
|
|
36
34
|
const products: Product[] = (data.products ?? []).slice(0, props.count ?? 4).map((p) => {
|
|
37
35
|
const sku = pickSku(p);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Product } from "@decocms/apps-commerce/types";
|
|
11
|
-
import { getVtexConfig, vtexCachedFetch } from "../../client";
|
|
11
|
+
import { getVtexConfig, storefrontBaseUrl, vtexCachedFetch } from "../../client";
|
|
12
12
|
import { resolveProductIdBySlug } from "../../utils/slugCache";
|
|
13
13
|
import { pickSku, toProduct } from "../../utils/transform";
|
|
14
14
|
import type { LegacyProduct } from "../../utils/types";
|
|
@@ -58,9 +58,7 @@ export default async function vtexRelatedProducts(
|
|
|
58
58
|
if (!related?.length) return [];
|
|
59
59
|
|
|
60
60
|
const config = getVtexConfig();
|
|
61
|
-
const baseUrl = config
|
|
62
|
-
? `https://${config.publicUrl}`
|
|
63
|
-
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
61
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
64
62
|
|
|
65
63
|
let result = related.slice(0, count).map((p) => {
|
|
66
64
|
const sku = pickSku(p);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Product } from "@decocms/apps-commerce/types";
|
|
10
|
-
import { getVtexConfig, intelligentSearch, toFacetPath } from "../client";
|
|
10
|
+
import { getVtexConfig, intelligentSearch, storefrontBaseUrl, toFacetPath } from "../client";
|
|
11
11
|
import { pickSku, sortProducts, toProduct } from "../utils/transform";
|
|
12
12
|
import type { Product as ProductVTEX } from "../utils/types";
|
|
13
13
|
|
|
@@ -135,9 +135,7 @@ export default async function vtexProductList(props: ProductListProps): Promise<
|
|
|
135
135
|
const data = await intelligentSearch<{ products: ProductVTEX[] }>(endpoint, params);
|
|
136
136
|
|
|
137
137
|
const vtexProducts = data.products ?? [];
|
|
138
|
-
const baseUrl = config
|
|
139
|
-
? `https://${config.publicUrl}`
|
|
140
|
-
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
138
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
141
139
|
|
|
142
140
|
let products = vtexProducts.map((p) => {
|
|
143
141
|
const fetchedSkus = ids ? new Set(ids) : null;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { Product } from "@decocms/apps-commerce/types";
|
|
7
|
-
import { getVtexConfig, intelligentSearch, toFacetPath } from "../../client";
|
|
7
|
+
import { getVtexConfig, intelligentSearch, storefrontBaseUrl, toFacetPath } from "../../client";
|
|
8
8
|
import { pickSku, toProduct } from "../../utils/transform";
|
|
9
9
|
import type { Product as ProductVTEX } from "../../utils/types";
|
|
10
10
|
|
|
@@ -49,9 +49,7 @@ export default async function vtexWorkflowProducts(
|
|
|
49
49
|
const data = await intelligentSearch<{ products: ProductVTEX[] }>(endpoint, params);
|
|
50
50
|
|
|
51
51
|
const products = data.products ?? [];
|
|
52
|
-
const baseUrl = config
|
|
53
|
-
? `https://${config.publicUrl}`
|
|
54
|
-
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
52
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
55
53
|
|
|
56
54
|
return products.map((p) => {
|
|
57
55
|
const sku = pickSku(p);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { RequestContext } from "@decocms/blocks/sdk/requestContext";
|
|
2
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { configureVtex, storefrontBaseUrl } from "../../client";
|
|
5
|
+
|
|
6
|
+
const ACCOUNT = { account: "examplestore" } as const;
|
|
7
|
+
|
|
8
|
+
describe("storefrontBaseUrl", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
configureVtex({ ...ACCOUNT });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("uses the origin of the request being served", () => {
|
|
14
|
+
configureVtex({ ...ACCOUNT, publicUrl: "https://secure.example.com/" });
|
|
15
|
+
|
|
16
|
+
const origin = RequestContext.run(new Request("https://www.example.com/camisas?page=2"), () =>
|
|
17
|
+
storefrontBaseUrl(),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(origin).toBe("https://www.example.com");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("never returns the double-scheme origin a full publicUrl used to produce", () => {
|
|
24
|
+
// `https://${publicUrl}` on "https://secure.example.com/" parsed with
|
|
25
|
+
// host "https", so every product URL came out as https://https/<slug>/p.
|
|
26
|
+
configureVtex({ ...ACCOUNT, publicUrl: "https://secure.example.com/" });
|
|
27
|
+
|
|
28
|
+
const origin = storefrontBaseUrl();
|
|
29
|
+
|
|
30
|
+
expect(origin).toBe("https://secure.example.com");
|
|
31
|
+
expect(new URL("/camisa-paris/p", origin).href).toBe("https://secure.example.com/camisa-paris/p");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("accepts a publicUrl stored without a scheme", () => {
|
|
35
|
+
configureVtex({ ...ACCOUNT, publicUrl: "secure.example.com" });
|
|
36
|
+
|
|
37
|
+
expect(storefrontBaseUrl()).toBe("https://secure.example.com");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("falls back to the account host when publicUrl is absent", () => {
|
|
41
|
+
expect(storefrontBaseUrl()).toBe("https://examplestore.vtexcommercestable.com.br");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("honours a configured domain in the account-host fallback", () => {
|
|
45
|
+
configureVtex({ ...ACCOUNT, domain: "com" });
|
|
46
|
+
|
|
47
|
+
expect(storefrontBaseUrl()).toBe("https://examplestore.vtexcommercestable.com");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("falls back to the account host rather than publish an unparseable publicUrl", () => {
|
|
51
|
+
configureVtex({ ...ACCOUNT, publicUrl: "://" });
|
|
52
|
+
|
|
53
|
+
expect(storefrontBaseUrl()).toBe("https://examplestore.vtexcommercestable.com.br");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("returns an origin with no trailing slash", () => {
|
|
57
|
+
configureVtex({ ...ACCOUNT, publicUrl: "https://secure.example.com/" });
|
|
58
|
+
|
|
59
|
+
expect(storefrontBaseUrl()).not.toMatch(/\/$/);
|
|
60
|
+
});
|
|
61
|
+
});
|
package/src/utils/enrichment.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import type { Product, ProductLeaf } from "@decocms/apps-commerce/types";
|
|
27
|
-
import { getVtexConfig, vtexFetch, vtexIOGraphQL } from "../client";
|
|
27
|
+
import { getVtexConfig, storefrontBaseUrl, vtexFetch, vtexIOGraphQL } from "../client";
|
|
28
28
|
import { listBrands } from "../loaders/brands";
|
|
29
29
|
import { batch } from "./batch";
|
|
30
30
|
import { withIsSimilarTo } from "./similars";
|
|
@@ -372,9 +372,7 @@ export function withKitItems(): ProductEnricher {
|
|
|
372
372
|
if (!productIDs.size) return products;
|
|
373
373
|
|
|
374
374
|
const config = getVtexConfig();
|
|
375
|
-
const baseUrl = config
|
|
376
|
-
? `https://${config.publicUrl}`
|
|
377
|
-
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
375
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
378
376
|
|
|
379
377
|
const batches = batch([...productIDs], KIT_ITEMS_BATCH_SIZE);
|
|
380
378
|
const productsById = new Map<string, ProductLeaf>();
|
|
@@ -428,9 +426,7 @@ export function withVariants(): ProductEnricher {
|
|
|
428
426
|
if (!productIDs.size) return products;
|
|
429
427
|
|
|
430
428
|
const config = getVtexConfig();
|
|
431
|
-
const baseUrl = config
|
|
432
|
-
? `https://${config.publicUrl}`
|
|
433
|
-
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
429
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
434
430
|
|
|
435
431
|
const batches = batch([...productIDs], VARIANTS_BATCH_SIZE);
|
|
436
432
|
const productsById = new Map<string, Product>();
|
package/src/utils/similars.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Product } from "@decocms/apps-commerce/types";
|
|
2
|
-
import { getVtexConfig, vtexFetch } from "../client";
|
|
2
|
+
import { getVtexConfig, storefrontBaseUrl, vtexFetch } from "../client";
|
|
3
3
|
import { pickSku, toProduct } from "./transform";
|
|
4
4
|
import type { LegacyProduct } from "./types";
|
|
5
5
|
|
|
@@ -18,9 +18,7 @@ export const withIsSimilarTo = async (product: Product): Promise<Product> => {
|
|
|
18
18
|
if (!rawSimilars?.length) return product;
|
|
19
19
|
|
|
20
20
|
const config = getVtexConfig();
|
|
21
|
-
const baseUrl = config
|
|
22
|
-
? `https://${config.publicUrl}`
|
|
23
|
-
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
21
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
24
22
|
|
|
25
23
|
const similars = rawSimilars.map((p) => {
|
|
26
24
|
const sku = pickSku(p);
|