@thorprovider/adapters 5.0.11 → 5.0.12
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/dist/index.d.mts +29 -6
- package/dist/index.d.ts +29 -6
- package/dist/index.js +294 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +294 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/providers/medusa/auth.test.ts +148 -25
- package/src/providers/medusa/auth.ts +129 -16
- package/src/providers/medusa/customer-orders.test.ts +142 -0
- package/src/providers/medusa/index.ts +108 -26
- package/src/providers/medusa/transforms.test.ts +45 -0
- package/src/providers/medusa/transforms.ts +7 -0
- package/src/providers/mock/catalog-query.test.ts +153 -17
- package/src/providers/mock/index.test.ts +19 -0
- package/src/providers/mock/mock.ts +189 -1
- package/.turbo/turbo-build.log +0 -18
- package/.turbo/turbo-test.log +0 -897
- package/.turbo/turbo-type-check.log +0 -0
- package/dist/.tsbuildinfo +0 -1
- package/publish.log +0 -93
package/dist/index.mjs
CHANGED
|
@@ -1033,6 +1033,12 @@ function transformProduct(product, currencyCode) {
|
|
|
1033
1033
|
title: product.collection.title ?? "",
|
|
1034
1034
|
handle: product.collection.handle ?? ""
|
|
1035
1035
|
}] : void 0,
|
|
1036
|
+
categories: product.categories?.map((category) => ({
|
|
1037
|
+
id: category.id,
|
|
1038
|
+
name: category.name ?? "",
|
|
1039
|
+
handle: category.handle ?? "",
|
|
1040
|
+
parentCategoryId: category.parent_category_id ?? void 0
|
|
1041
|
+
})),
|
|
1036
1042
|
productType: product.type?.value ?? void 0,
|
|
1037
1043
|
vendor: void 0,
|
|
1038
1044
|
// Medusa doesn't have vendor field
|
|
@@ -1805,6 +1811,7 @@ var MedusaAuthProvider = class {
|
|
|
1805
1811
|
if (this.authConfig.method === "jwt") {
|
|
1806
1812
|
await this.storageManager.setToken(token);
|
|
1807
1813
|
}
|
|
1814
|
+
await this.linkCustomerChannel();
|
|
1808
1815
|
const customer = await this.getCurrentCustomer();
|
|
1809
1816
|
this.logger.info("Login successful", { customerId: customer?.id });
|
|
1810
1817
|
return {
|
|
@@ -1812,6 +1819,7 @@ var MedusaAuthProvider = class {
|
|
|
1812
1819
|
customer: customer ?? void 0
|
|
1813
1820
|
};
|
|
1814
1821
|
} catch (error) {
|
|
1822
|
+
if (error instanceof ProviderAPIError) throw error;
|
|
1815
1823
|
this.logger.error("Login failed", error);
|
|
1816
1824
|
throw new ProviderAPIError(
|
|
1817
1825
|
`Authentication failed: ${error.message}`,
|
|
@@ -1823,16 +1831,14 @@ var MedusaAuthProvider = class {
|
|
|
1823
1831
|
async register(data) {
|
|
1824
1832
|
this.logger.debug("register", { email: data.email });
|
|
1825
1833
|
try {
|
|
1826
|
-
await this.sdk.auth.register("customer", "emailpass", {
|
|
1834
|
+
const registrationToken = await this.sdk.auth.register("customer", "emailpass", {
|
|
1827
1835
|
email: data.email,
|
|
1828
1836
|
password: data.password
|
|
1829
1837
|
});
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
phone: data.phone
|
|
1835
|
-
});
|
|
1838
|
+
if (this.authConfig.method === "jwt" && typeof registrationToken === "string") {
|
|
1839
|
+
await this.storageManager.setToken(registrationToken);
|
|
1840
|
+
}
|
|
1841
|
+
const linked = await this.createChannelLinkedCustomer(data);
|
|
1836
1842
|
const loginResult = await this.sdk.auth.login("customer", "emailpass", {
|
|
1837
1843
|
email: data.email,
|
|
1838
1844
|
password: data.password
|
|
@@ -1858,12 +1864,20 @@ var MedusaAuthProvider = class {
|
|
|
1858
1864
|
if (this.authConfig.method === "jwt") {
|
|
1859
1865
|
await this.storageManager.setToken(token);
|
|
1860
1866
|
}
|
|
1867
|
+
const customer = await this.getCurrentCustomer();
|
|
1868
|
+
if (!customer || customer.id !== linked.id) {
|
|
1869
|
+
throw new ProviderAPIError(
|
|
1870
|
+
"Registration created an auth identity without a linked customer.",
|
|
1871
|
+
"CUSTOMER_LINK_INCOMPLETE"
|
|
1872
|
+
);
|
|
1873
|
+
}
|
|
1861
1874
|
this.logger.info("Registration successful", { customerId: customer.id });
|
|
1862
1875
|
return {
|
|
1863
1876
|
token: this.authConfig.method === "jwt" ? token : void 0,
|
|
1864
|
-
customer
|
|
1877
|
+
customer
|
|
1865
1878
|
};
|
|
1866
1879
|
} catch (error) {
|
|
1880
|
+
if (error instanceof ProviderAPIError) throw error;
|
|
1867
1881
|
this.logger.error("Registration failed", error);
|
|
1868
1882
|
throw new ProviderAPIError(
|
|
1869
1883
|
`Registration failed: ${error.message}`,
|
|
@@ -1872,6 +1886,79 @@ var MedusaAuthProvider = class {
|
|
|
1872
1886
|
);
|
|
1873
1887
|
}
|
|
1874
1888
|
}
|
|
1889
|
+
/**
|
|
1890
|
+
* Create and channel-link a customer through `POST /store/thor/customers`.
|
|
1891
|
+
*
|
|
1892
|
+
* The request carries the registration token (read by the SDK from the
|
|
1893
|
+
* request-scoped custom storage) and the publishable key; the backend links
|
|
1894
|
+
* the customer to the publishable-key sales channel. A route that is
|
|
1895
|
+
* unavailable fails closed with a typed error — there is no silent native
|
|
1896
|
+
* fallback that would leave the customer unlinked.
|
|
1897
|
+
*/
|
|
1898
|
+
async createChannelLinkedCustomer(data) {
|
|
1899
|
+
const response = await this.fetchChannelAccountRoute(
|
|
1900
|
+
"/store/thor/customers",
|
|
1901
|
+
{
|
|
1902
|
+
method: "POST",
|
|
1903
|
+
body: {
|
|
1904
|
+
email: data.email,
|
|
1905
|
+
first_name: data.first_name,
|
|
1906
|
+
last_name: data.last_name,
|
|
1907
|
+
phone: data.phone
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
);
|
|
1911
|
+
const id = response?.customer?.id;
|
|
1912
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
1913
|
+
throw new ProviderAPIError(
|
|
1914
|
+
"Channel-linked customer creation returned no customer id.",
|
|
1915
|
+
"CUSTOMER_LINK_INCOMPLETE"
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
return { id };
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Link the authenticated customer to the publishable-key sales channel
|
|
1922
|
+
* through `POST /store/thor/customers/me/channels` (idempotent). Called after
|
|
1923
|
+
* login so history is channel-scoped for the current storefront.
|
|
1924
|
+
*/
|
|
1925
|
+
async linkCustomerChannel() {
|
|
1926
|
+
const response = await this.fetchChannelAccountRoute("/store/thor/customers/me/channels", { method: "POST" });
|
|
1927
|
+
if (response?.linked === false && response?.reason !== "already_linked") {
|
|
1928
|
+
throw new ProviderAPIError(
|
|
1929
|
+
"Customer channel link did not complete.",
|
|
1930
|
+
"CUSTOMER_LINK_INCOMPLETE"
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Call a Thor channel account route through the SDK client, which attaches
|
|
1936
|
+
* the request-scoped customer/registration token and the publishable key. A
|
|
1937
|
+
* missing client or an unavailable route (404/405/501) fails closed with a
|
|
1938
|
+
* typed `CUSTOMER_LINK_UNAVAILABLE` error.
|
|
1939
|
+
*/
|
|
1940
|
+
async fetchChannelAccountRoute(path, init) {
|
|
1941
|
+
const client = this.sdk.client;
|
|
1942
|
+
if (!client || typeof client.fetch !== "function") {
|
|
1943
|
+
throw new ProviderAPIError(
|
|
1944
|
+
`The channel account route ${path} is unavailable in this SDK.`,
|
|
1945
|
+
"CUSTOMER_LINK_UNAVAILABLE"
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
try {
|
|
1949
|
+
return await client.fetch(path, init);
|
|
1950
|
+
} catch (error) {
|
|
1951
|
+
const status = error?.status || error?.statusCode;
|
|
1952
|
+
if (status === 404 || status === 405 || status === 501) {
|
|
1953
|
+
throw new ProviderAPIError(
|
|
1954
|
+
`The channel account route ${path} is unavailable (status ${status}).`,
|
|
1955
|
+
"CUSTOMER_LINK_UNAVAILABLE",
|
|
1956
|
+
status
|
|
1957
|
+
);
|
|
1958
|
+
}
|
|
1959
|
+
throw error;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1875
1962
|
async logout() {
|
|
1876
1963
|
this.logger.debug("logout");
|
|
1877
1964
|
try {
|
|
@@ -2218,6 +2305,14 @@ async function getPaymentMethods(sdk, options) {
|
|
|
2218
2305
|
|
|
2219
2306
|
// src/providers/medusa/index.ts
|
|
2220
2307
|
var MEDUSA_CART_RETRIEVE_FIELDS = "+items,+items.variant,+items.variant.product,+items.variant.product.thumbnail,+items.variant.options,+items.variant.options.option,+items.total,+items.unit_price,+items.item_subtotal,+items.item_total,+shipping_methods,+shipping_methods.id,+shipping_methods.shipping_option_id";
|
|
2308
|
+
var CUSTOMER_ORDERS_DEFAULT_PAGE_SIZE = 10;
|
|
2309
|
+
var CUSTOMER_ORDERS_MAX_PAGE_SIZE = 100;
|
|
2310
|
+
function normalizeCustomerOrdersPage(options) {
|
|
2311
|
+
const page = Number.isInteger(options.page) && options.page > 0 ? options.page : 1;
|
|
2312
|
+
const requested = Number.isInteger(options.pageSize) && options.pageSize > 0 ? options.pageSize : CUSTOMER_ORDERS_DEFAULT_PAGE_SIZE;
|
|
2313
|
+
const pageSize = Math.min(requested, CUSTOMER_ORDERS_MAX_PAGE_SIZE);
|
|
2314
|
+
return { page, pageSize, limit: pageSize, offset: (page - 1) * pageSize };
|
|
2315
|
+
}
|
|
2221
2316
|
var MEDUSA_ORDER_COMPLETION_FIELDS = "*items";
|
|
2222
2317
|
var DEFAULT_CATALOG_PAGE_SIZE = 50;
|
|
2223
2318
|
function resolveCatalogPageSize(configured) {
|
|
@@ -2581,7 +2676,7 @@ var MedusaProvider = class {
|
|
|
2581
2676
|
}
|
|
2582
2677
|
/** Fields needed to price a product variant for the current region. */
|
|
2583
2678
|
buildProductFields(regionId) {
|
|
2584
|
-
const baseFields = "+variants,+variants.inventory_quantity,+options,+images,+tags";
|
|
2679
|
+
const baseFields = "+variants,+variants.inventory_quantity,+options,+images,+tags,+categories.*";
|
|
2585
2680
|
const pricingFields = ",+variants.calculated_price,+variants.prices";
|
|
2586
2681
|
return regionId ? baseFields + pricingFields : baseFields;
|
|
2587
2682
|
}
|
|
@@ -4139,13 +4234,17 @@ var MedusaProvider = class {
|
|
|
4139
4234
|
// Order Operations
|
|
4140
4235
|
// ============================================
|
|
4141
4236
|
/**
|
|
4142
|
-
* Get all orders for a customer (storefront).
|
|
4237
|
+
* Get all orders for a customer (storefront) as an array (legacy surface).
|
|
4143
4238
|
*
|
|
4144
|
-
*
|
|
4145
|
-
*
|
|
4239
|
+
* Delegates to the channel-scoped paginated contract
|
|
4240
|
+
* ({@link customerOrdersPage}) and returns that window's orders. The backend
|
|
4241
|
+
* (`GET /store/thor/orders`) scopes by BOTH the authenticated customer and
|
|
4242
|
+
* the sales channel resolved from the publishable key, so cross-channel
|
|
4243
|
+
* orders are excluded by the backend before pagination and **no client-side
|
|
4244
|
+
* channel filter runs**.
|
|
4146
4245
|
*
|
|
4147
|
-
* @param options - Order query options (supports
|
|
4148
|
-
* @returns Array of orders for the authenticated customer
|
|
4246
|
+
* @param options - Order query options (supports a custom fetcher)
|
|
4247
|
+
* @returns Array of orders for the authenticated customer on the store channel
|
|
4149
4248
|
*
|
|
4150
4249
|
* @example
|
|
4151
4250
|
* ```typescript
|
|
@@ -4159,32 +4258,68 @@ var MedusaProvider = class {
|
|
|
4159
4258
|
this.logger.debug("Using custom fetcher for orders");
|
|
4160
4259
|
return customFetcher(options);
|
|
4161
4260
|
}
|
|
4162
|
-
|
|
4261
|
+
const page = await this.customerOrdersPage({
|
|
4262
|
+
page: 1,
|
|
4263
|
+
pageSize: options.limit ?? CUSTOMER_ORDERS_MAX_PAGE_SIZE,
|
|
4264
|
+
salesChannelId
|
|
4265
|
+
});
|
|
4266
|
+
return page.orders;
|
|
4267
|
+
}
|
|
4268
|
+
/**
|
|
4269
|
+
* Channel-scoped, paginated order history for the authenticated customer.
|
|
4270
|
+
*
|
|
4271
|
+
* Calls the backend route `GET /store/thor/orders`, which requires the
|
|
4272
|
+
* customer session and scopes `order` by `customer_id` **and** the
|
|
4273
|
+
* `sales_channel_id` resolved from the publishable key, returning
|
|
4274
|
+
* `{ orders, count, limit, offset }` with the backend's real total computed
|
|
4275
|
+
* before pagination.
|
|
4276
|
+
*
|
|
4277
|
+
* The `salesChannelId` option is the store's explicitly configured channel.
|
|
4278
|
+
* When supplied, every returned order is asserted to belong to it; a
|
|
4279
|
+
* foreign-channel order (for example from a multi-channel publishable key that
|
|
4280
|
+
* silently resolved an arbitrary first id) fails closed with a typed error
|
|
4281
|
+
* rather than being presented as the customer's history.
|
|
4282
|
+
*
|
|
4283
|
+
* @param options - Bounded pagination window and the store's sales channel
|
|
4284
|
+
* @returns The backend's `{ orders, count, limit, offset }`
|
|
4285
|
+
*/
|
|
4286
|
+
async customerOrdersPage(options = {}) {
|
|
4287
|
+
const { page, pageSize, limit, offset } = normalizeCustomerOrdersPage(options);
|
|
4288
|
+
const { salesChannelId } = options;
|
|
4289
|
+
this.logger.info("Getting channel-scoped customer orders", { page, pageSize });
|
|
4163
4290
|
try {
|
|
4164
|
-
const
|
|
4165
|
-
async () => this.sdk.
|
|
4166
|
-
|
|
4167
|
-
|
|
4291
|
+
const response = await this.withRetry(
|
|
4292
|
+
async () => this.sdk.client.fetch("/store/thor/orders", {
|
|
4293
|
+
method: "GET",
|
|
4294
|
+
query: { limit, offset }
|
|
4168
4295
|
}),
|
|
4169
|
-
"
|
|
4296
|
+
"customerOrdersPage",
|
|
4170
4297
|
{}
|
|
4171
4298
|
);
|
|
4172
|
-
const
|
|
4173
|
-
this.logger.debug(`Found ${
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
const scoped = filterByChannel(transformed, channelId);
|
|
4178
|
-
this.logger.debug(
|
|
4179
|
-
`Channel scoping: ${scoped.length}/${transformed.length} orders match channel ${channelId}`
|
|
4299
|
+
const rawOrders = response.orders ?? [];
|
|
4300
|
+
this.logger.debug(`Found ${rawOrders.length} channel-scoped orders for customer`);
|
|
4301
|
+
if (salesChannelId) {
|
|
4302
|
+
const foreign = rawOrders.find(
|
|
4303
|
+
(order) => typeof order?.sales_channel_id === "string" && order.sales_channel_id !== salesChannelId
|
|
4180
4304
|
);
|
|
4181
|
-
|
|
4305
|
+
if (foreign) {
|
|
4306
|
+
throw new ProviderAPIError(
|
|
4307
|
+
`Channel-scoped order history returned an order outside the store's sales channel (${salesChannelId}).`,
|
|
4308
|
+
"medusa"
|
|
4309
|
+
);
|
|
4310
|
+
}
|
|
4182
4311
|
}
|
|
4183
|
-
return
|
|
4312
|
+
return {
|
|
4313
|
+
orders: rawOrders.map((order) => transformOrder(order)),
|
|
4314
|
+
count: typeof response.count === "number" ? response.count : rawOrders.length,
|
|
4315
|
+
limit: typeof response.limit === "number" ? response.limit : limit,
|
|
4316
|
+
offset: typeof response.offset === "number" ? response.offset : offset
|
|
4317
|
+
};
|
|
4184
4318
|
} catch (error) {
|
|
4319
|
+
if (error instanceof ProviderAPIError) throw error;
|
|
4185
4320
|
const status = error?.status || error?.statusCode;
|
|
4186
4321
|
const errorMessage = extractErrorMessage(error);
|
|
4187
|
-
this.logger.error("Error getting orders", { error: errorMessage });
|
|
4322
|
+
this.logger.error("Error getting channel-scoped orders", { error: errorMessage });
|
|
4188
4323
|
throw new ProviderAPIError(
|
|
4189
4324
|
`Failed to fetch orders: ${errorMessage}`,
|
|
4190
4325
|
"medusa",
|
|
@@ -9831,7 +9966,7 @@ var categoryFixtures = [
|
|
|
9831
9966
|
description: "Complementary products for cart and bundle flows."
|
|
9832
9967
|
}
|
|
9833
9968
|
];
|
|
9834
|
-
var
|
|
9969
|
+
var heroProductFixtures = [
|
|
9835
9970
|
{
|
|
9836
9971
|
id: "mock-product-aurora-jacket",
|
|
9837
9972
|
handle: "aurora-jacket",
|
|
@@ -9963,6 +10098,119 @@ var productFixtures = [
|
|
|
9963
10098
|
]
|
|
9964
10099
|
}
|
|
9965
10100
|
];
|
|
10101
|
+
var generatedBlueprints = [
|
|
10102
|
+
{
|
|
10103
|
+
categoryHandle: "jackets",
|
|
10104
|
+
noun: "Jacket",
|
|
10105
|
+
productType: "outerwear",
|
|
10106
|
+
vendor: "Thor Atelier",
|
|
10107
|
+
tags: ["outerwear", "layer"],
|
|
10108
|
+
variantName: "Size",
|
|
10109
|
+
variantValues: ["S", "M"],
|
|
10110
|
+
names: ["Alpine", "Boreal", "Cinder", "Drift", "Ember", "Fjord", "Harbor", "Meridian", "Nomad", "Tundra", "Vantage"],
|
|
10111
|
+
description: "A considered layering piece in a brushed weave.",
|
|
10112
|
+
basePrice: 118,
|
|
10113
|
+
baseHue: 210
|
|
10114
|
+
},
|
|
10115
|
+
{
|
|
10116
|
+
categoryHandle: "dresses",
|
|
10117
|
+
noun: "Dress",
|
|
10118
|
+
productType: "dress",
|
|
10119
|
+
vendor: "Thor Atelier",
|
|
10120
|
+
tags: ["dress", "editorial"],
|
|
10121
|
+
variantName: "Size",
|
|
10122
|
+
variantValues: ["S", "M"],
|
|
10123
|
+
names: ["Amber", "Camille", "Dahlia", "Elara", "Iris", "Lumen", "Nova", "Rosalind", "Vera"],
|
|
10124
|
+
description: "Cut from a fluid cloth for editorial styling.",
|
|
10125
|
+
basePrice: 142,
|
|
10126
|
+
baseHue: 330
|
|
10127
|
+
},
|
|
10128
|
+
{
|
|
10129
|
+
categoryHandle: "home",
|
|
10130
|
+
noun: "Lamp",
|
|
10131
|
+
productType: "decor",
|
|
10132
|
+
vendor: "Thor Home",
|
|
10133
|
+
tags: ["home", "decor", "interior"],
|
|
10134
|
+
variantName: "Finish",
|
|
10135
|
+
variantValues: ["Brass"],
|
|
10136
|
+
names: ["Atlas", "Copper", "Dune", "Onyx", "Quill", "Slate", "Terra", "Verdant"],
|
|
10137
|
+
description: "A calm sculptural accent for modern interiors.",
|
|
10138
|
+
basePrice: 74,
|
|
10139
|
+
baseHue: 40
|
|
10140
|
+
},
|
|
10141
|
+
{
|
|
10142
|
+
categoryHandle: "accessories",
|
|
10143
|
+
noun: "Bag",
|
|
10144
|
+
productType: "bag",
|
|
10145
|
+
vendor: "Thor Atelier",
|
|
10146
|
+
tags: ["accessory", "everyday"],
|
|
10147
|
+
variantName: "Color",
|
|
10148
|
+
variantValues: ["Juniper"],
|
|
10149
|
+
names: ["Cove", "Fable", "Juniper", "Kestrel", "Marlowe", "Piper", "Rune"],
|
|
10150
|
+
description: "An everyday carry with a textured finish.",
|
|
10151
|
+
basePrice: 68,
|
|
10152
|
+
baseHue: 280
|
|
10153
|
+
}
|
|
10154
|
+
];
|
|
10155
|
+
var GENERATED_CREATED_AT_BASE = Date.parse("2025-12-30T00:00:00.000Z");
|
|
10156
|
+
var GENERATED_COLLECTION_POOL = ["new-arrivals", "best-sellers", "studio-essentials"];
|
|
10157
|
+
function buildGeneratedProductFixtures() {
|
|
10158
|
+
const fixtures = [];
|
|
10159
|
+
let globalIndex = 0;
|
|
10160
|
+
for (const blueprint of generatedBlueprints) {
|
|
10161
|
+
const noun = blueprint.noun.toLowerCase();
|
|
10162
|
+
blueprint.names.forEach((name, localIndex) => {
|
|
10163
|
+
const handle = `${name.toLowerCase()}-${noun}`;
|
|
10164
|
+
const title = `${name} ${blueprint.noun}`;
|
|
10165
|
+
const price = blueprint.basePrice + localIndex * 7 + localIndex % 3 * 0.5;
|
|
10166
|
+
const quantityAvailable = globalIndex * 5 % 17;
|
|
10167
|
+
const primaryCollection = GENERATED_COLLECTION_POOL[globalIndex % GENERATED_COLLECTION_POOL.length];
|
|
10168
|
+
const collectionHandles = [primaryCollection];
|
|
10169
|
+
if (globalIndex % 5 === 0) {
|
|
10170
|
+
const secondary = GENERATED_COLLECTION_POOL[(globalIndex + 1) % GENERATED_COLLECTION_POOL.length];
|
|
10171
|
+
if (secondary !== primaryCollection) {
|
|
10172
|
+
collectionHandles.push(secondary);
|
|
10173
|
+
}
|
|
10174
|
+
}
|
|
10175
|
+
const createdAt = new Date(GENERATED_CREATED_AT_BASE - globalIndex * 864e5).toISOString();
|
|
10176
|
+
const variants = blueprint.variantValues.map((value, variantIndex) => ({
|
|
10177
|
+
id: `mock-variant-${handle}-${value.toLowerCase()}`,
|
|
10178
|
+
title: value,
|
|
10179
|
+
price,
|
|
10180
|
+
quantityAvailable: Math.max(0, quantityAvailable - variantIndex),
|
|
10181
|
+
selectedOptions: [{ name: blueprint.variantName, value }]
|
|
10182
|
+
}));
|
|
10183
|
+
fixtures.push({
|
|
10184
|
+
id: `mock-product-${handle}`,
|
|
10185
|
+
handle,
|
|
10186
|
+
title,
|
|
10187
|
+
description: `${title} - ${blueprint.description}`,
|
|
10188
|
+
imageLabel: title,
|
|
10189
|
+
imageHue: (blueprint.baseHue + localIndex * 13) % 360,
|
|
10190
|
+
collectionHandles,
|
|
10191
|
+
categoryHandle: blueprint.categoryHandle,
|
|
10192
|
+
productType: blueprint.productType,
|
|
10193
|
+
vendor: blueprint.vendor,
|
|
10194
|
+
tags: [...blueprint.tags],
|
|
10195
|
+
options: [
|
|
10196
|
+
{
|
|
10197
|
+
id: `${handle}-${blueprint.variantName.toLowerCase()}`,
|
|
10198
|
+
name: blueprint.variantName,
|
|
10199
|
+
values: [...blueprint.variantValues]
|
|
10200
|
+
}
|
|
10201
|
+
],
|
|
10202
|
+
variants,
|
|
10203
|
+
createdAt
|
|
10204
|
+
});
|
|
10205
|
+
globalIndex += 1;
|
|
10206
|
+
});
|
|
10207
|
+
}
|
|
10208
|
+
return fixtures;
|
|
10209
|
+
}
|
|
10210
|
+
var productFixtures = [
|
|
10211
|
+
...heroProductFixtures,
|
|
10212
|
+
...buildGeneratedProductFixtures()
|
|
10213
|
+
];
|
|
9966
10214
|
var stockLocationFixture = {
|
|
9967
10215
|
id: "mock-stock-primary",
|
|
9968
10216
|
name: "Mock Main Warehouse",
|
|
@@ -10079,6 +10327,20 @@ function buildProducts(currencyCode) {
|
|
|
10079
10327
|
title: collection.title,
|
|
10080
10328
|
handle: collection.handle
|
|
10081
10329
|
})),
|
|
10330
|
+
categories: (() => {
|
|
10331
|
+
const category = categoryFixtures.find(
|
|
10332
|
+
(item) => item.handle === product.categoryHandle
|
|
10333
|
+
);
|
|
10334
|
+
if (!category) return void 0;
|
|
10335
|
+
return [
|
|
10336
|
+
{
|
|
10337
|
+
id: category.id,
|
|
10338
|
+
name: category.name,
|
|
10339
|
+
handle: category.handle,
|
|
10340
|
+
parentCategoryId: category.parentCategoryId
|
|
10341
|
+
}
|
|
10342
|
+
];
|
|
10343
|
+
})(),
|
|
10082
10344
|
productType: product.productType,
|
|
10083
10345
|
vendor: product.vendor,
|
|
10084
10346
|
tags: product.tags,
|