@scayle/storefront-core 8.46.0 → 8.47.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 CHANGED
@@ -1,5 +1,45 @@
1
1
  # @scayle/storefront-core
2
2
 
3
+ ## 8.47.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Enhanced basket RPC methods to support order custom data functionality.
8
+
9
+ All basket RPC methods now integrate with the `getOrderCustomData` RPC method to fetch and include order custom data in SAPI requests.
10
+ This enables developers to attach order-specific custom metadata, business rules, or additional context to orders throughout the basket lifecycle.
11
+ The existing `orderCustomData` parameter is deprecated and will be removed in the next major version.
12
+ For now, both parameters are supported and merged together, where attributes from the parameter take precedence.
13
+
14
+ On default, this RPC returns an empty object but can be overridden by the developer to return a custom object.
15
+
16
+ Example `getOrderCustomData` RPC:
17
+
18
+ ```typescript
19
+ export const getOrderCustomData: RpcHandler<Record<string, unknown>> =
20
+ defineRpcHandler<Record<string, unknown>>(async (context: RpcContext) => {
21
+ return {
22
+ customer: {
23
+ groups: context.user?.groups,
24
+ },
25
+ }
26
+ })
27
+ ```
28
+
29
+ For further information, please refer to the [Overriding Core RPC Methods](https://scayle.dev/en/core-documentation/storefront-guide/storefront-application/technical-foundation/rpc-methods?sourceText=RPC%2520Methods#overriding-core-rpc-methods).
30
+
31
+ - Updated all basket RPC methods to pass the customer access token to the SAPI client, enabling the `X-Customer-Token` header for promotion validation.
32
+
33
+ The customer access token is now automatically included in basket requests when available.
34
+ If the token has expired, it will be omitted from the request to prevent validation errors.
35
+
36
+ ### Patch Changes
37
+
38
+ **Dependencies**
39
+
40
+ - Updated dependency to @scayle/unstorage-scayle-kv-driver@2.0.7
41
+ - Updated dependency to @scayle/storefront-api@18.18.0
42
+
3
43
  ## 8.46.0
4
44
 
5
45
  ### Minor Changes
@@ -15,7 +15,7 @@ import { mergeBaskets as mergeBasketFunction } from '../../../utils/user';
15
15
  * @param params.existingItemHandling How to handle existing items, default to `'AddQuantityToExisting'`.
16
16
  * @param params.itemGroup The item group.
17
17
  * @param params.with withParams for basket.
18
- * @param params.orderCustomData Custom data for order.
18
+ * @param params.orderCustomData @deprecated - will be removed in the next major version. Use `getOrderCustomData` RPC method instead.
19
19
  * @param context The RPC context.
20
20
  *
21
21
  * @returns The updated basket, potentially with errors. It will return
@@ -36,7 +36,7 @@ export declare const addItemToBasket: RpcHandler<AddOrUpdateItemType & {
36
36
  * @param params.items An array of items to add.
37
37
  * @param params.existingItemHandling How to handle existing items.
38
38
  * @param params.with withParams for basket.
39
- * @param params.orderCustomData Order custom data.
39
+ * @param params.orderCustomData @deprecated - will be removed in the next major version. Use `getOrderCustomData` RPC method instead.
40
40
  * @param context The RPC context.
41
41
  *
42
42
  * @returns The updated basket, potentially with errors. It will return
@@ -55,7 +55,7 @@ export declare const addItemsToBasket: RpcHandler<{
55
55
  * Retrieves the current basket.
56
56
  *
57
57
  * @param options The options for retrieving the basket.
58
- * @param options.orderCustomData Custom data for the order.
58
+ * @param options.orderCustomData @deprecated - will be removed in the next major version. Use `getOrderCustomData` RPC method instead.
59
59
  * @param context The RPC context.
60
60
  *
61
61
  * @returns The basket data. It will return an `ErrorResponse` alternatively
@@ -72,7 +72,7 @@ export declare const getBasket: RpcHandler<BasketWithOptions & {
72
72
  * @param options The options for removing the item.
73
73
  * @param options.itemKey The key of the item to remove.
74
74
  * @param options.with withParams for basket.
75
- * @param options.orderCustomData Custom data for the order.
75
+ * @param options.orderCustomData @deprecated - will be removed in the next major version. Use `getOrderCustomData` RPC method instead.
76
76
  * @param context The RPC context.
77
77
  *
78
78
  * @returns The updated basket. It will return an `ErrorResponse` alternatively
@@ -101,7 +101,7 @@ export declare const clearBasket: RpcHandler<boolean>;
101
101
  * @param params.fromBasketKey The key of the source basket.
102
102
  * @param params.toBasketKey The key of the target basket.
103
103
  * @param params.with withParams for basket.
104
- * @param params.orderCustomData Custom data for the order.
104
+ * @param params.orderCustomData @deprecated - will be removed in the next major version. Use `getOrderCustomData` RPC method instead.
105
105
  * @param context The RPC context.
106
106
  *
107
107
  * @returns The new merged basket as result of the merge operation.
@@ -135,6 +135,7 @@ export interface UpdateBasketItemRequestParameter {
135
135
  * An object describing which data the returned basket should contain.
136
136
  */
137
137
  with?: BasketWithOptions;
138
+ orderCustomData?: Record<string, unknown>;
138
139
  }
139
140
  /**
140
141
  * Applies the provided update data to a specific item in a basket.
@@ -143,6 +144,7 @@ export interface UpdateBasketItemRequestParameter {
143
144
  * @param params.basketItemKey The key of the item to be updated.
144
145
  * @param params.update The update data that should be applied to the basket item.
145
146
  * @param params.with An object describing which data the returned basket should contain.
147
+ * @param params.orderCustomData Custom data for the order.
146
148
  * @param context The RPC context.
147
149
  *
148
150
  * @returns A promise that resolves with the updated basket.
@@ -6,9 +6,16 @@ import {
6
6
  HttpStatusMessage,
7
7
  MIN_WITH_PARAMS_BASKET
8
8
  } from "../../../constants/index.mjs";
9
- import { mergeBaskets as mergeBasketFunction } from "../../../utils/user.mjs";
9
+ import {
10
+ mergeBaskets as mergeBasketFunction,
11
+ getValidatedAccessToken
12
+ } from "../../../utils/user.mjs";
10
13
  import { unwrap } from "../../../utils/response.mjs";
11
- import { defineRpcHandler, wasAddedWithReducedQuantity } from "../../../utils/index.mjs";
14
+ import {
15
+ defineRpcHandler,
16
+ mergeOrderCustomData,
17
+ wasAddedWithReducedQuantity
18
+ } from "../../../utils/index.mjs";
12
19
  import { mapSAPIFetchErrorToResponse } from "../../../utils/sapi.mjs";
13
20
  const SAPI_ERROR_NAME = "SAPI ERROR";
14
21
  function getWithParams(params, context) {
@@ -35,6 +42,7 @@ export const addItemToBasket = defineRpcHandler(async ({
35
42
  }
36
43
  const { sapiClient, basketKey } = context;
37
44
  const campaignKey = await context.callRpc?.("getCampaignKey");
45
+ const _orderCustomData = await context.callRpc?.("getOrderCustomData");
38
46
  const resolvedWith = getWithParams(
39
47
  { with: _with },
40
48
  context
@@ -60,7 +68,8 @@ export const addItemToBasket = defineRpcHandler(async ({
60
68
  ],
61
69
  {
62
70
  skipAvailabilityCheck: false,
63
- orderCustomData
71
+ orderCustomData: mergeOrderCustomData(_orderCustomData, orderCustomData),
72
+ customerToken: getValidatedAccessToken(context.accessToken) ?? void 0
64
73
  },
65
74
  {
66
75
  existingItemHandling
@@ -97,6 +106,7 @@ export const addItemsToBasket = defineRpcHandler(async (params, context) => {
97
106
  }
98
107
  const { sapiClient, basketKey } = context;
99
108
  const campaignKey = await context.callRpc?.("getCampaignKey");
109
+ const _orderCustomData = await context.callRpc?.("getOrderCustomData");
100
110
  const resolvedWith = getWithParams(params, context);
101
111
  const itemsToBeAddedOrUpdated = params.items.map((item) => ({
102
112
  variantId: item.variantId,
@@ -119,7 +129,11 @@ export const addItemsToBasket = defineRpcHandler(async (params, context) => {
119
129
  {
120
130
  skipAvailabilityCheck: false,
121
131
  includeItemsWithoutProductData: resolvedWith?.includeItemsWithoutProductData,
122
- orderCustomData: params.orderCustomData
132
+ orderCustomData: mergeOrderCustomData(
133
+ _orderCustomData,
134
+ params.orderCustomData
135
+ ),
136
+ customerToken: getValidatedAccessToken(context.accessToken) ?? void 0
123
137
  },
124
138
  {
125
139
  existingItemHandling: params.existingItemHandling || ExistingItemHandling.ADD_QUANTITY_TO_EXISTING
@@ -159,6 +173,7 @@ export const getBasket = defineRpcHandler(async (options, context) => {
159
173
  }
160
174
  const { sapiClient, basketKey } = context;
161
175
  const campaignKey = await context.callRpc?.("getCampaignKey");
176
+ const _orderCustomData = await context.callRpc?.("getOrderCustomData");
162
177
  const resolvedWith = getWithParams(
163
178
  { with: options },
164
179
  context
@@ -167,7 +182,11 @@ export const getBasket = defineRpcHandler(async (options, context) => {
167
182
  with: resolvedWith,
168
183
  campaignKey,
169
184
  includeItemsWithoutProductData: resolvedWith.includeItemsWithoutProductData,
170
- orderCustomData: options?.orderCustomData
185
+ orderCustomData: mergeOrderCustomData(
186
+ _orderCustomData,
187
+ options?.orderCustomData
188
+ ),
189
+ customerToken: getValidatedAccessToken(context.accessToken) ?? void 0
171
190
  });
172
191
  if (response.type === "failure") {
173
192
  const { statusCode, message } = parseBasketError(response);
@@ -192,6 +211,7 @@ export const removeItemFromBasket = defineRpcHandler(async (options, context) =>
192
211
  }
193
212
  const { sapiClient, basketKey } = context;
194
213
  const campaignKey = await context.callRpc?.("getCampaignKey");
214
+ const _orderCustomData = await context.callRpc?.("getOrderCustomData");
195
215
  const resolvedWith = getWithParams(options, context);
196
216
  const basket = await sapiClient.basket.deleteItem(
197
217
  basketKey,
@@ -200,7 +220,11 @@ export const removeItemFromBasket = defineRpcHandler(async (options, context) =>
200
220
  with: resolvedWith,
201
221
  campaignKey,
202
222
  includeItemsWithoutProductData: resolvedWith?.includeItemsWithoutProductData,
203
- orderCustomData: options.orderCustomData
223
+ orderCustomData: mergeOrderCustomData(
224
+ _orderCustomData,
225
+ options.orderCustomData
226
+ ),
227
+ customerToken: getValidatedAccessToken(context.accessToken) ?? void 0
204
228
  }
205
229
  );
206
230
  return { basket };
@@ -226,14 +250,18 @@ export const mergeBaskets = defineRpcHandler(async ({ fromBasketKey, toBasketKey
226
250
  { with: _with },
227
251
  context
228
252
  );
253
+ const _orderCustomData = await context.callRpc?.("getOrderCustomData");
229
254
  return await mergeBasketFunction(
230
255
  fromBasketKey,
231
256
  toBasketKey,
232
- { ...resolvedWith, orderCustomData },
257
+ {
258
+ ...resolvedWith,
259
+ orderCustomData: mergeOrderCustomData(_orderCustomData, orderCustomData)
260
+ },
233
261
  context
234
262
  );
235
263
  });
236
- export const updateBasketItem = defineRpcHandler(async ({ basketItemKey, update, with: _with }, context) => {
264
+ export const updateBasketItem = defineRpcHandler(async ({ basketItemKey, update, with: _with, orderCustomData }, context) => {
237
265
  if (!hasSession(context)) {
238
266
  return new ErrorResponse(
239
267
  HttpStatusCode.BAD_REQUEST,
@@ -248,11 +276,18 @@ export const updateBasketItem = defineRpcHandler(async ({ basketItemKey, update,
248
276
  );
249
277
  const { quantity, ...updateParams } = update;
250
278
  const campaignKey = update.campaignKey ?? await context.callRpc?.("getCampaignKey");
279
+ const _orderCustomData = await context.callRpc?.("getOrderCustomData");
251
280
  const result = await sapiClient.basket.updateItem(
252
281
  basketKey,
253
282
  basketItemKey,
254
283
  quantity,
255
- { ...updateParams, campaignKey, with: resolvedWith }
284
+ {
285
+ ...updateParams,
286
+ campaignKey,
287
+ with: resolvedWith,
288
+ orderCustomData: mergeOrderCustomData(_orderCustomData, orderCustomData),
289
+ customerToken: getValidatedAccessToken(context.accessToken) ?? void 0
290
+ }
256
291
  );
257
292
  if (result.type === "success") {
258
293
  return { basket: result.basket };
@@ -287,12 +322,18 @@ export const getApplicablePromotionsByCode = defineRpcHandler(async ({ promotion
287
322
  context
288
323
  );
289
324
  const campaignKey = await context.callRpc?.("getCampaignKey");
325
+ const orderCustomData = await context.callRpc?.("getOrderCustomData");
290
326
  const result = await mapSAPIFetchErrorToResponse(
291
327
  sapiClient.basket.getApplicablePromotionsByCode
292
328
  )(
293
329
  basketKey,
294
330
  promotionCode,
295
- { campaignKey, with: resolvedWith }
331
+ {
332
+ campaignKey,
333
+ with: resolvedWith,
334
+ orderCustomData,
335
+ customerToken: getValidatedAccessToken(context.accessToken) ?? void 0
336
+ }
296
337
  );
297
338
  if (result instanceof Response || result.type === "failure") {
298
339
  context.log.info("Getting applicable promotion codes failed", {
@@ -318,6 +359,7 @@ export const updatePromotions = defineRpcHandler(async (params, context) => {
318
359
  }
319
360
  const { basketKey, sapiClient } = context;
320
361
  const campaignKey = params.campaignKey ?? await context.callRpc?.("getCampaignKey");
362
+ const orderCustomData = await context.callRpc?.("getOrderCustomData");
321
363
  const resolvedWith = getWithParams(
322
364
  { with: params.with },
323
365
  context
@@ -329,7 +371,9 @@ export const updatePromotions = defineRpcHandler(async (params, context) => {
329
371
  {
330
372
  ...params,
331
373
  campaignKey,
332
- with: resolvedWith
374
+ with: resolvedWith,
375
+ orderCustomData,
376
+ customerToken: getValidatedAccessToken(context.accessToken) ?? void 0
333
377
  }
334
378
  );
335
379
  if (result instanceof Response || result.type === "failure") {
@@ -45,6 +45,8 @@ interface CheckoutJwtPayload {
45
45
  * @param jwtPayload The payload for the JWT. Provides checkout-related data to embed within the token.
46
46
  * @param context The RPC context.
47
47
  *
48
+ * @see https://scayle.dev/en/core-documentation/storefront/checkout-guide/implementation/webcomponent#jwt-attribute
49
+ *
48
50
  * @returns An object containing the access token and checkout JWT.
49
51
  * It will return an `ErrorResponse` if no session is found, no access token is present.
50
52
  */
@@ -26,14 +26,18 @@ export const getCheckoutToken = defineRpcHandler(async (jwtPayload = {}, context
26
26
  const now = /* @__PURE__ */ new Date();
27
27
  const { voucher, customData, preferredCollectionPoint, carrier } = jwtPayload;
28
28
  const campaignKey = await context.callRpc?.("getCampaignKey");
29
+ const orderCustomData = await context.callRpc?.("getOrderCustomData");
29
30
  const checkoutJwt = await new SignJWT({
30
31
  voucher,
31
- customData,
32
32
  preferredCollectionPoint,
33
33
  carrier,
34
34
  basketId: context.basketKey,
35
- campaignKey
36
- }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"8.46.0"}`).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(secret);
35
+ campaignKey,
36
+ customData: {
37
+ ...customData,
38
+ ...orderCustomData
39
+ }
40
+ }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"8.47.0"}`).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(secret);
37
41
  return {
38
42
  accessToken: refreshedAccessToken,
39
43
  checkoutJwt
@@ -14,3 +14,4 @@ import type { Order, RpcHandler } from '../../../types';
14
14
  export declare const getOrderById: RpcHandler<{
15
15
  orderId: number;
16
16
  }, Order>;
17
+ export declare const getOrderCustomData: RpcHandler<Record<string, unknown>>;
@@ -51,3 +51,6 @@ export const getOrderById = defineRpcHandler(async ({ orderId }, context) => {
51
51
  );
52
52
  }
53
53
  });
54
+ export const getOrderCustomData = defineRpcHandler(async () => {
55
+ return {};
56
+ });
@@ -23,3 +23,44 @@ import type { AddOrUpdateItemError } from '@scayle/storefront-api';
23
23
  * ```
24
24
  */
25
25
  export declare const wasAddedWithReducedQuantity: (errors?: AddOrUpdateItemError[]) => boolean;
26
+ /**
27
+ * Merges the two orderCustomData objects when both RPC and parameter values are provided.
28
+ * If an attribute exists in both, the parameter value takes precedence.
29
+ *
30
+ * @param orderCustomDataRpc The order custom data from the RPC context.
31
+ * @param orderCustomDataParam The order custom data from the parameter.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const orderCustomDataRpc = {
36
+ * orderId: '1',
37
+ * }
38
+ * const orderCustomDataParam = {
39
+ * orderId: '2',
40
+ * }
41
+ * const mergedOrderCustomData = mergeOrderCustomData(orderCustomDataRpc, orderCustomDataParam) // { orderId: '2' }
42
+ *
43
+ * const orderCustomDataRpc = {
44
+ * orderId: '1',
45
+ * }
46
+ * const orderCustomDataParam = undefined
47
+ * const mergedOrderCustomData = mergeOrderCustomData(orderCustomDataRpc, orderCustomDataParam) // { orderId: '1' }
48
+ *
49
+ * const orderCustomDataRpc = undefined
50
+ * const orderCustomDataParam = {
51
+ * orderId: '2',
52
+ * }
53
+ * const mergedOrderCustomData = mergeOrderCustomData(orderCustomDataRpc, orderCustomDataParam) // { orderId: '2' }
54
+ *
55
+ * const orderCustomDataRpc = {
56
+ * orderId: '1',
57
+ * }
58
+ * const orderCustomDataParam = {
59
+ * orderName: 'Order 2',
60
+ * }
61
+ * const mergedOrderCustomData = mergeOrderCustomData(orderCustomDataRpc, orderCustomDataParam) // { orderId: '1', orderName: 'Order 2' }
62
+ * ```
63
+ *
64
+ * @returns The merged order custom data.
65
+ */
66
+ export declare const mergeOrderCustomData: (orderCustomDataRpc?: Record<string, unknown>, orderCustomDataParam?: Record<string, unknown>) => Record<string, unknown>;
@@ -10,3 +10,12 @@ export const wasAddedWithReducedQuantity = (errors) => {
10
10
  (error) => error.operation === "add" && error.kind === AddToBasketFailureKind.ITEM_ADDED_WITH_REDUCED_QUANTITY || error.operation === "update" && error.kind === UpdateBasketItemFailureKind.ITEM_ADDED_WITH_REDUCED_QUANTITY
11
11
  );
12
12
  };
13
+ export const mergeOrderCustomData = (orderCustomDataRpc, orderCustomDataParam) => {
14
+ if (!orderCustomDataParam) {
15
+ return orderCustomDataRpc ?? {};
16
+ }
17
+ if (!orderCustomDataRpc) {
18
+ return orderCustomDataParam ?? {};
19
+ }
20
+ return { ...orderCustomDataRpc, ...orderCustomDataParam };
21
+ };
@@ -1,5 +1,13 @@
1
1
  import type { BasketResponse, AddManyItemsBasketResponse } from '@scayle/storefront-api';
2
2
  import type { BasketWithOptions, Product, RpcContext, Variant } from '../types';
3
+ /**
4
+ * Checks if the access token is expired.
5
+ *
6
+ * @param accessToken The access token to validate.
7
+ *
8
+ * @returns The validated access token or undefined if the access token is not present or expired.
9
+ */
10
+ export declare const getValidatedAccessToken: (accessToken?: string) => string | undefined;
3
11
  /**
4
12
  * Merges two baskets. Adds the items from the `fromBasketKey` basket to the `toBasketKey` basket
5
13
  * and then clears the `fromBasketKey` basket. Handles existing items by adding quantities.
@@ -1,11 +1,23 @@
1
1
  import { ExistingItemHandling } from "@scayle/storefront-api";
2
+ import { decodeJwt } from "jose";
3
+ export const getValidatedAccessToken = (accessToken) => {
4
+ if (!accessToken) {
5
+ return;
6
+ }
7
+ const payload = decodeJwt(accessToken);
8
+ if (!payload.exp || payload.exp < Date.now() / 1e3) {
9
+ return;
10
+ }
11
+ return accessToken;
12
+ };
2
13
  export const mergeBaskets = async (fromBasketKey, toBasketKey, withOptions, context) => {
3
14
  const { sapiClient } = context;
4
15
  const campaignKey = await context.callRpc?.("getCampaignKey");
5
16
  const fromOriginBasket = await sapiClient.basket.get(fromBasketKey, {
6
17
  with: withOptions,
7
18
  campaignKey,
8
- orderCustomData: withOptions.orderCustomData
19
+ orderCustomData: withOptions.orderCustomData,
20
+ customerToken: getValidatedAccessToken(context.accessToken) ?? void 0
9
21
  });
10
22
  context.log.debug(`Merging basket ${fromBasketKey} to ${toBasketKey}`);
11
23
  if (fromBasketKey === toBasketKey) {
@@ -31,7 +43,8 @@ export const mergeBaskets = async (fromBasketKey, toBasketKey, withOptions, cont
31
43
  with: withOptions,
32
44
  campaignKey,
33
45
  includeItemsWithoutProductData: withOptions?.includeItemsWithoutProductData,
34
- orderCustomData: withOptions.orderCustomData
46
+ orderCustomData: withOptions.orderCustomData,
47
+ customerToken: getValidatedAccessToken(context.accessToken) ?? void 0
35
48
  },
36
49
  {
37
50
  existingItemHandling: ExistingItemHandling.ADD_QUANTITY_TO_EXISTING
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/storefront-core",
3
- "version": "8.46.0",
3
+ "version": "8.47.0",
4
4
  "description": "Collection of essential utilities to work with the Storefront API",
5
5
  "author": "SCAYLE Commerce Engine",
6
6
  "license": "MIT",
@@ -48,12 +48,12 @@
48
48
  "ufo": "^1.5.3",
49
49
  "uncrypto": "^0.1.3",
50
50
  "utility-types": "^3.11.0",
51
- "@scayle/storefront-api": "18.17.0",
52
- "@scayle/unstorage-scayle-kv-driver": "2.0.6"
51
+ "@scayle/storefront-api": "18.18.0",
52
+ "@scayle/unstorage-scayle-kv-driver": "2.0.7"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/crypto-js": "4.2.2",
56
- "@types/node": "22.18.12",
56
+ "@types/node": "22.18.13",
57
57
  "@types/webpack-env": "1.18.8",
58
58
  "@vitest/coverage-v8": "3.2.4",
59
59
  "dprint": "0.50.2",
@@ -66,11 +66,11 @@
66
66
  "unbuild": "3.6.1",
67
67
  "unstorage": "1.17.1",
68
68
  "vitest": "3.2.4",
69
- "@scayle/vitest-config-storefront": "1.0.0",
70
- "@scayle/eslint-config-storefront": "4.7.10"
69
+ "@scayle/eslint-config-storefront": "4.7.11",
70
+ "@scayle/vitest-config-storefront": "1.0.0"
71
71
  },
72
72
  "volta": {
73
- "node": "22.20.0"
73
+ "node": "22.21.0"
74
74
  },
75
75
  "scripts": {
76
76
  "clean": "rimraf ./dist",