@shopify/cli-hydrogen 11.1.7 → 11.1.8

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.
@@ -1,5 +1,79 @@
1
1
  # skeleton
2
2
 
3
+ ## 2025.10.1
4
+
5
+ ### Major Changes
6
+
7
+ - Update Storefront API and Customer Account API to version 2025-10 ([#3430](https://github.com/Shopify/hydrogen/pull/3430)) by [@kdaviduik](https://github.com/kdaviduik)
8
+
9
+ ### Patch Changes
10
+
11
+ - Add support for Bun's text-based lockfile (`bun.lock`) introduced in Bun 1.2, and npm's shrinkwrap lockfile (`npm-shrinkwrap.json`), as alternatives to their respective primary lockfiles (`bun.lockb` and `package-lock.json`). ([#3430](https://github.com/Shopify/hydrogen/pull/3430)) by [@kdaviduik](https://github.com/kdaviduik)
12
+
13
+ - Add `cartGiftCardCodesAdd` mutation ([#3430](https://github.com/Shopify/hydrogen/pull/3430)) by [@kdaviduik](https://github.com/kdaviduik)
14
+
15
+ ## New Feature: cartGiftCardCodesAdd
16
+
17
+ The skeleton template has been updated to use the new `cartGiftCardCodesAdd` mutation:
18
+ - Removed `UpdateGiftCardForm` component from `CartSummary.tsx`
19
+ - Added `AddGiftCardForm` component using `CartForm.ACTIONS.GiftCardCodesAdd`
20
+
21
+ If you customized the gift card form in your project, you may want to migrate to the new `Add` action for simpler code.
22
+
23
+ ## Usage
24
+
25
+ ```typescript
26
+ import {CartForm} from '@shopify/hydrogen';
27
+
28
+ <CartForm action={CartForm.ACTIONS.GiftCardCodesAdd} inputs={{giftCardCodes: ['CODE1', 'CODE2']}}>
29
+ <button>Add Gift Cards</button>
30
+ </CartForm>
31
+ ```
32
+
33
+ Or with createCartHandler:
34
+
35
+ ```typescript
36
+ const cart = createCartHandler({storefront, getCartId, setCartId});
37
+ await cart.addGiftCardCodes(['SUMMER2025', 'WELCOME10']);
38
+ ```
39
+
40
+ - Add support for nested cart line items (warranties, gift wrapping, etc.) ([#3430](https://github.com/Shopify/hydrogen/pull/3430)) by [@kdaviduik](https://github.com/kdaviduik)
41
+
42
+ Storefront API 2025-10 introduces `parentRelationship` on cart line items, enabling parent-child relationships for add-ons. This update displays nested line items in the cart.
43
+
44
+ ### Changes
45
+ - Updates GraphQL fragments to include `parentRelationship` and `lineComponents` fields
46
+ - Updates `CartMain` and `CartLineItem` to render child line items with visual hierarchy
47
+
48
+ ### Note
49
+
50
+ This update focuses on **displaying** nested line items. To add both a product and its child (e.g., warranty) in a single action:
51
+
52
+ ```tsx
53
+ <AddToCartButton
54
+ lines={[
55
+ {merchandiseId: 'gid://shopify/ProductVariant/laptop-456', quantity: 1},
56
+ {
57
+ merchandiseId: 'gid://shopify/ProductVariant/warranty-123',
58
+ quantity: 1,
59
+ parent: {merchandiseId: 'gid://shopify/ProductVariant/laptop-456'},
60
+ },
61
+ ]}
62
+ >
63
+ Add to Cart with Warranty
64
+ </AddToCartButton>
65
+ ```
66
+
67
+ - Updated dependencies [[`722915130410086bc7af22215ba57ee77aa14156`](https://github.com/Shopify/hydrogen/commit/722915130410086bc7af22215ba57ee77aa14156)]:
68
+ - @shopify/hydrogen@2025.10.1
69
+
70
+ ## 2025.10.0
71
+
72
+ ### Patch Changes
73
+
74
+ - Updated dependencies [[`cd653456fbd1e7e1ab1f6fecff04c89a74b6cad9`](https://github.com/Shopify/hydrogen/commit/cd653456fbd1e7e1ab1f6fecff04c89a74b6cad9), [`24d26ad94e90ab0a859c274838f7f31e75a7808c`](https://github.com/Shopify/hydrogen/commit/24d26ad94e90ab0a859c274838f7f31e75a7808c), [`13a6f8987ea20d33a30a9c0329d7c11528b892ea`](https://github.com/Shopify/hydrogen/commit/13a6f8987ea20d33a30a9c0329d7c11528b892ea), [`403c1f5b6e266c3dfad30f7cfed229e3304570b0`](https://github.com/Shopify/hydrogen/commit/403c1f5b6e266c3dfad30f7cfed229e3304570b0), [`38f8a79625838a9cd4520b20c0db2e5d331f7d26`](https://github.com/Shopify/hydrogen/commit/38f8a79625838a9cd4520b20c0db2e5d331f7d26)]:
75
+ - @shopify/hydrogen@2026.0.0
76
+
3
77
  ## 2025.7.3
4
78
 
5
79
  ### Patch Changes
@@ -1,69 +1,98 @@
1
1
  import type {CartLineUpdateInput} from '@shopify/hydrogen/storefront-api-types';
2
- import type {CartLayout} from '~/components/CartMain';
2
+ import type {CartLayout, LineItemChildrenMap} from '~/components/CartMain';
3
3
  import {CartForm, Image, type OptimisticCartLine} from '@shopify/hydrogen';
4
4
  import {useVariantUrl} from '~/lib/variants';
5
5
  import {Link} from 'react-router';
6
6
  import {ProductPrice} from './ProductPrice';
7
7
  import {useAside} from './Aside';
8
- import type {CartApiQueryFragment} from 'storefrontapi.generated';
8
+ import type {
9
+ CartApiQueryFragment,
10
+ CartLineFragment,
11
+ } from 'storefrontapi.generated';
9
12
 
10
- type CartLine = OptimisticCartLine<CartApiQueryFragment>;
13
+ export type CartLine = OptimisticCartLine<CartApiQueryFragment>;
11
14
 
12
15
  /**
13
16
  * A single line item in the cart. It displays the product image, title, price.
14
17
  * It also provides controls to update the quantity or remove the line item.
18
+ * If the line is a parent line that has child components (like warranties or gift wrapping), they are
19
+ * rendered nested below the parent line.
15
20
  */
16
21
  export function CartLineItem({
17
22
  layout,
18
23
  line,
24
+ childrenMap,
19
25
  }: {
20
26
  layout: CartLayout;
21
27
  line: CartLine;
28
+ childrenMap: LineItemChildrenMap;
22
29
  }) {
23
30
  const {id, merchandise} = line;
24
31
  const {product, title, image, selectedOptions} = merchandise;
25
32
  const lineItemUrl = useVariantUrl(product.handle, selectedOptions);
26
33
  const {close} = useAside();
34
+ const lineItemChildren = childrenMap[id];
35
+ const childrenLabelId = `cart-line-children-${id}`;
27
36
 
28
37
  return (
29
38
  <li key={id} className="cart-line">
30
- {image && (
31
- <Image
32
- alt={title}
33
- aspectRatio="1/1"
34
- data={image}
35
- height={100}
36
- loading="lazy"
37
- width={100}
38
- />
39
- )}
39
+ <div className="cart-line-inner">
40
+ {image && (
41
+ <Image
42
+ alt={title}
43
+ aspectRatio="1/1"
44
+ data={image}
45
+ height={100}
46
+ loading="lazy"
47
+ width={100}
48
+ />
49
+ )}
40
50
 
41
- <div>
42
- <Link
43
- prefetch="intent"
44
- to={lineItemUrl}
45
- onClick={() => {
46
- if (layout === 'aside') {
47
- close();
48
- }
49
- }}
50
- >
51
- <p>
52
- <strong>{product.title}</strong>
53
- </p>
54
- </Link>
55
- <ProductPrice price={line?.cost?.totalAmount} />
56
- <ul>
57
- {selectedOptions.map((option) => (
58
- <li key={option.name}>
59
- <small>
60
- {option.name}: {option.value}
61
- </small>
62
- </li>
63
- ))}
64
- </ul>
65
- <CartLineQuantity line={line} />
51
+ <div>
52
+ <Link
53
+ prefetch="intent"
54
+ to={lineItemUrl}
55
+ onClick={() => {
56
+ if (layout === 'aside') {
57
+ close();
58
+ }
59
+ }}
60
+ >
61
+ <p>
62
+ <strong>{product.title}</strong>
63
+ </p>
64
+ </Link>
65
+ <ProductPrice price={line?.cost?.totalAmount} />
66
+ <ul>
67
+ {selectedOptions.map((option) => (
68
+ <li key={option.name}>
69
+ <small>
70
+ {option.name}: {option.value}
71
+ </small>
72
+ </li>
73
+ ))}
74
+ </ul>
75
+ <CartLineQuantity line={line} />
76
+ </div>
66
77
  </div>
78
+
79
+ {lineItemChildren ? (
80
+ <div>
81
+ <p id={childrenLabelId} className="sr-only">
82
+ Line items with {product.title}
83
+ </p>
84
+ <ul aria-labelledby={childrenLabelId} className="cart-line-children">
85
+ {lineItemChildren.map((childLine) => (
86
+ <CartLineItem
87
+ childrenMap={childrenMap}
88
+ key={childLine.id}
89
+ line={childLine}
90
+ layout={layout}
91
+ />
92
+ ))}
93
+ </ul>
94
+ </div>
95
+ ) : null}
67
96
  </li>
68
97
  );
69
98
  }
@@ -1,8 +1,8 @@
1
- import {useOptimisticCart} from '@shopify/hydrogen';
1
+ import {useOptimisticCart, type OptimisticCartLine} from '@shopify/hydrogen';
2
2
  import {Link} from 'react-router';
3
3
  import type {CartApiQueryFragment} from 'storefrontapi.generated';
4
4
  import {useAside} from '~/components/Aside';
5
- import {CartLineItem} from '~/components/CartLineItem';
5
+ import {CartLineItem, type CartLine} from '~/components/CartLineItem';
6
6
  import {CartSummary} from './CartSummary';
7
7
 
8
8
  export type CartLayout = 'page' | 'aside';
@@ -12,6 +12,26 @@ export type CartMainProps = {
12
12
  layout: CartLayout;
13
13
  };
14
14
 
15
+ export type LineItemChildrenMap = {[parentId: string]: CartLine[]};
16
+ /** Returns a map of all line items and their children. */
17
+ function getLineItemChildrenMap(lines: CartLine[]): LineItemChildrenMap {
18
+ const children: LineItemChildrenMap = {};
19
+ for (const line of lines) {
20
+ if ('parentRelationship' in line && line.parentRelationship?.parent) {
21
+ const parentId = line.parentRelationship.parent.id;
22
+ if (!children[parentId]) children[parentId] = [];
23
+ children[parentId].push(line);
24
+ }
25
+ if ('lineComponents' in line) {
26
+ const children = getLineItemChildrenMap(line.lineComponents);
27
+ for (const [parentId, childIds] of Object.entries(children)) {
28
+ if (!children[parentId]) children[parentId] = [];
29
+ children[parentId].push(...childIds);
30
+ }
31
+ }
32
+ }
33
+ return children;
34
+ }
15
35
  /**
16
36
  * The main cart component that displays the cart items and summary.
17
37
  * It is used by both the /cart route and the cart aside dialog.
@@ -27,16 +47,34 @@ export function CartMain({layout, cart: originalCart}: CartMainProps) {
27
47
  Boolean(cart?.discountCodes?.filter((code) => code.applicable)?.length);
28
48
  const className = `cart-main ${withDiscount ? 'with-discount' : ''}`;
29
49
  const cartHasItems = cart?.totalQuantity ? cart.totalQuantity > 0 : false;
50
+ const childrenMap = getLineItemChildrenMap(cart?.lines?.nodes ?? []);
30
51
 
31
52
  return (
32
53
  <div className={className}>
33
54
  <CartEmpty hidden={linesCount} layout={layout} />
34
55
  <div className="cart-details">
35
- <div aria-labelledby="cart-lines">
36
- <ul>
37
- {(cart?.lines?.nodes ?? []).map((line) => (
38
- <CartLineItem key={line.id} line={line} layout={layout} />
39
- ))}
56
+ <p id="cart-lines" className="sr-only">
57
+ Line items
58
+ </p>
59
+ <div>
60
+ <ul aria-labelledby="cart-lines">
61
+ {(cart?.lines?.nodes ?? []).map((line) => {
62
+ // we do not render non-parent lines at the root of the cart
63
+ if (
64
+ 'parentRelationship' in line &&
65
+ line.parentRelationship?.parent
66
+ ) {
67
+ return null;
68
+ }
69
+ return (
70
+ <CartLineItem
71
+ key={line.id}
72
+ line={line}
73
+ layout={layout}
74
+ childrenMap={childrenMap}
75
+ />
76
+ );
77
+ })}
40
78
  </ul>
41
79
  </div>
42
80
  {cartHasItems && <CartSummary cart={cart} layout={layout} />}
@@ -3,7 +3,6 @@ import type {CartLayout} from '~/components/CartMain';
3
3
  import {CartForm, Money, type OptimisticCart} from '@shopify/hydrogen';
4
4
  import {useEffect, useRef} from 'react';
5
5
  import {useFetcher} from 'react-router';
6
- import type {FetcherWithComponents} from 'react-router';
7
6
 
8
7
  type CartSummaryProps = {
9
8
  cart: OptimisticCart<CartApiQueryFragment | null>;
@@ -67,7 +66,9 @@ function CartDiscounts({
67
66
  <div className="cart-discount">
68
67
  <code>{codes?.join(', ')}</code>
69
68
  &nbsp;
70
- <button>Remove</button>
69
+ <button type="submit" aria-label="Remove discount">
70
+ Remove
71
+ </button>
71
72
  </div>
72
73
  </UpdateDiscountForm>
73
74
  </div>
@@ -76,9 +77,19 @@ function CartDiscounts({
76
77
  {/* Show an input to apply a discount */}
77
78
  <UpdateDiscountForm discountCodes={codes}>
78
79
  <div>
79
- <input type="text" name="discountCode" placeholder="Discount code" />
80
+ <label htmlFor="discount-code-input" className="sr-only">
81
+ Discount code
82
+ </label>
83
+ <input
84
+ id="discount-code-input"
85
+ type="text"
86
+ name="discountCode"
87
+ placeholder="Discount code"
88
+ />
80
89
  &nbsp;
81
- <button type="submit">Apply</button>
90
+ <button type="submit" aria-label="Apply discount code">
91
+ Apply
92
+ </button>
82
93
  </div>
83
94
  </UpdateDiscountForm>
84
95
  </div>
@@ -110,27 +121,17 @@ function CartGiftCard({
110
121
  }: {
111
122
  giftCardCodes: CartApiQueryFragment['appliedGiftCards'] | undefined;
112
123
  }) {
113
- const appliedGiftCardCodes = useRef<string[]>([]);
114
124
  const giftCardCodeInput = useRef<HTMLInputElement>(null);
115
125
  const giftCardAddFetcher = useFetcher({key: 'gift-card-add'});
116
126
 
117
- // Clear the gift card code input after the gift card is added
118
127
  useEffect(() => {
119
128
  if (giftCardAddFetcher.data) {
120
129
  giftCardCodeInput.current!.value = '';
121
130
  }
122
131
  }, [giftCardAddFetcher.data]);
123
132
 
124
- function saveAppliedCode(code: string) {
125
- const formattedCode = code.replace(/\s/g, ''); // Remove spaces
126
- if (!appliedGiftCardCodes.current.includes(formattedCode)) {
127
- appliedGiftCardCodes.current.push(formattedCode);
128
- }
129
- }
130
-
131
133
  return (
132
134
  <div>
133
- {/* Display applied gift cards with individual remove buttons */}
134
135
  {giftCardCodes && giftCardCodes.length > 0 && (
135
136
  <dl>
136
137
  <dt>Applied Gift Card(s)</dt>
@@ -148,12 +149,7 @@ function CartGiftCard({
148
149
  </dl>
149
150
  )}
150
151
 
151
- {/* Show an input to apply a gift card */}
152
- <UpdateGiftCardForm
153
- giftCardCodes={appliedGiftCardCodes.current}
154
- saveAppliedCode={saveAppliedCode}
155
- fetcherKey="gift-card-add"
156
- >
152
+ <AddGiftCardForm fetcherKey="gift-card-add">
157
153
  <div>
158
154
  <input
159
155
  type="text"
@@ -166,19 +162,15 @@ function CartGiftCard({
166
162
  Apply
167
163
  </button>
168
164
  </div>
169
- </UpdateGiftCardForm>
165
+ </AddGiftCardForm>
170
166
  </div>
171
167
  );
172
168
  }
173
169
 
174
- function UpdateGiftCardForm({
175
- giftCardCodes,
176
- saveAppliedCode,
170
+ function AddGiftCardForm({
177
171
  fetcherKey,
178
172
  children,
179
173
  }: {
180
- giftCardCodes?: string[];
181
- saveAppliedCode?: (code: string) => void;
182
174
  fetcherKey?: string;
183
175
  children: React.ReactNode;
184
176
  }) {
@@ -186,18 +178,9 @@ function UpdateGiftCardForm({
186
178
  <CartForm
187
179
  fetcherKey={fetcherKey}
188
180
  route="/cart"
189
- action={CartForm.ACTIONS.GiftCardCodesUpdate}
190
- inputs={{
191
- giftCardCodes: giftCardCodes || [],
192
- }}
181
+ action={CartForm.ACTIONS.GiftCardCodesAdd}
193
182
  >
194
- {(fetcher: FetcherWithComponents<any>) => {
195
- const code = fetcher.formData?.get('giftCardCode');
196
- if (code && saveAppliedCode) {
197
- saveAppliedCode(code as string);
198
- }
199
- return children;
200
- }}
183
+ {children}
201
184
  </CartForm>
202
185
  );
203
186
  }
@@ -221,4 +204,3 @@ function RemoveGiftCardForm({
221
204
  </CartForm>
222
205
  );
223
206
  }
224
-
@@ -54,6 +54,11 @@ export const CART_QUERY_FRAGMENT = `#graphql
54
54
  }
55
55
  }
56
56
  }
57
+ parentRelationship {
58
+ parent {
59
+ id
60
+ }
61
+ }
57
62
  }
58
63
  fragment CartLineComponent on ComponentizableCartLine {
59
64
  id
@@ -104,6 +109,9 @@ export const CART_QUERY_FRAGMENT = `#graphql
104
109
  }
105
110
  }
106
111
  }
112
+ lineComponents {
113
+ ...CartLine
114
+ }
107
115
  }
108
116
  fragment CartApiQuery on Cart {
109
117
  updatedAt
@@ -52,18 +52,14 @@ export async function action({request, context}: Route.ActionArgs) {
52
52
  result = await cart.updateDiscountCodes(discountCodes);
53
53
  break;
54
54
  }
55
- case CartForm.ACTIONS.GiftCardCodesUpdate: {
55
+ case CartForm.ACTIONS.GiftCardCodesAdd: {
56
56
  const formGiftCardCode = inputs.giftCardCode;
57
57
 
58
- // User inputted gift card code
59
58
  const giftCardCodes = (
60
59
  formGiftCardCode ? [formGiftCardCode] : []
61
60
  ) as string[];
62
61
 
63
- // Combine gift card codes already applied on cart
64
- giftCardCodes.push(...inputs.giftCardCodes);
65
-
66
- result = await cart.updateGiftCardCodes(giftCardCodes);
62
+ result = await cart.addGiftCardCodes(giftCardCodes);
67
63
  break;
68
64
  }
69
65
  case CartForm.ACTIONS.GiftCardCodesRemove: {
@@ -90,6 +90,18 @@ aside li {
90
90
  margin-bottom: 0.125rem;
91
91
  }
92
92
 
93
+ .sr-only {
94
+ position: absolute;
95
+ width: 1px;
96
+ height: 1px;
97
+ padding: 0;
98
+ margin: -1px;
99
+ overflow: hidden;
100
+ clip-path: inset(50%);
101
+ white-space: nowrap;
102
+ border-width: 0;
103
+ }
104
+
93
105
  .overlay {
94
106
  background: rgba(0, 0, 0, 0.2);
95
107
  bottom: 0;
@@ -250,10 +262,13 @@ button.reset:hover:not(:has(> *)) {
250
262
  }
251
263
 
252
264
  .cart-line {
253
- display: flex;
254
265
  padding: 0.75rem 0;
255
266
  }
256
267
 
268
+ .cart-line-inner {
269
+ display: flex;
270
+ }
271
+
257
272
  .cart-line img {
258
273
  height: 100%;
259
274
  display: block;
@@ -277,6 +292,11 @@ button.reset:hover:not(:has(> *)) {
277
292
  display: flex;
278
293
  }
279
294
 
295
+ /* Child line components (warranties, gift wrapping, etc.) */
296
+ .cart-line-children {
297
+ padding-left: 2rem;
298
+ }
299
+
280
300
  .cart-discount {
281
301
  align-items: center;
282
302
  display: flex;
@@ -2,7 +2,7 @@
2
2
  "name": "skeleton",
3
3
  "private": true,
4
4
  "sideEffects": false,
5
- "version": "2025.7.3",
5
+ "version": "2025.10.1",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "shopify hydrogen build --codegen",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "prettier": "@shopify/prettier-config",
16
16
  "dependencies": {
17
- "@shopify/hydrogen": "2025.7.3",
17
+ "@shopify/hydrogen": "2025.10.1",
18
18
  "graphql": "^16.10.0",
19
19
  "graphql-tag": "^2.12.6",
20
20
  "isbot": "^5.1.22",
@@ -36,6 +36,9 @@ export type CartLineFragment = Pick<
36
36
  Pick<StorefrontAPI.SelectedOption, 'name' | 'value'>
37
37
  >;
38
38
  };
39
+ parentRelationship?: StorefrontAPI.Maybe<{
40
+ parent: Pick<StorefrontAPI.CartLine, 'id'>;
41
+ }>;
39
42
  };
40
43
 
41
44
  export type CartLineComponentFragment = Pick<
@@ -66,6 +69,46 @@ export type CartLineComponentFragment = Pick<
66
69
  Pick<StorefrontAPI.SelectedOption, 'name' | 'value'>
67
70
  >;
68
71
  };
72
+ lineComponents: Array<
73
+ Pick<StorefrontAPI.CartLine, 'id' | 'quantity'> & {
74
+ attributes: Array<Pick<StorefrontAPI.Attribute, 'key' | 'value'>>;
75
+ cost: {
76
+ totalAmount: Pick<StorefrontAPI.MoneyV2, 'currencyCode' | 'amount'>;
77
+ amountPerQuantity: Pick<
78
+ StorefrontAPI.MoneyV2,
79
+ 'currencyCode' | 'amount'
80
+ >;
81
+ compareAtAmountPerQuantity?: StorefrontAPI.Maybe<
82
+ Pick<StorefrontAPI.MoneyV2, 'currencyCode' | 'amount'>
83
+ >;
84
+ };
85
+ merchandise: Pick<
86
+ StorefrontAPI.ProductVariant,
87
+ 'id' | 'availableForSale' | 'requiresShipping' | 'title'
88
+ > & {
89
+ compareAtPrice?: StorefrontAPI.Maybe<
90
+ Pick<StorefrontAPI.MoneyV2, 'currencyCode' | 'amount'>
91
+ >;
92
+ price: Pick<StorefrontAPI.MoneyV2, 'currencyCode' | 'amount'>;
93
+ image?: StorefrontAPI.Maybe<
94
+ Pick<
95
+ StorefrontAPI.Image,
96
+ 'id' | 'url' | 'altText' | 'width' | 'height'
97
+ >
98
+ >;
99
+ product: Pick<
100
+ StorefrontAPI.Product,
101
+ 'handle' | 'title' | 'id' | 'vendor'
102
+ >;
103
+ selectedOptions: Array<
104
+ Pick<StorefrontAPI.SelectedOption, 'name' | 'value'>
105
+ >;
106
+ };
107
+ parentRelationship?: StorefrontAPI.Maybe<{
108
+ parent: Pick<StorefrontAPI.CartLine, 'id'>;
109
+ }>;
110
+ }
111
+ >;
69
112
  };
70
113
 
71
114
  export type CartApiQueryFragment = Pick<
@@ -124,6 +167,9 @@ export type CartApiQueryFragment = Pick<
124
167
  Pick<StorefrontAPI.SelectedOption, 'name' | 'value'>
125
168
  >;
126
169
  };
170
+ parentRelationship?: StorefrontAPI.Maybe<{
171
+ parent: Pick<StorefrontAPI.CartLine, 'id'>;
172
+ }>;
127
173
  })
128
174
  | (Pick<StorefrontAPI.ComponentizableCartLine, 'id' | 'quantity'> & {
129
175
  attributes: Array<Pick<StorefrontAPI.Attribute, 'key' | 'value'>>;
@@ -159,6 +205,49 @@ export type CartApiQueryFragment = Pick<
159
205
  Pick<StorefrontAPI.SelectedOption, 'name' | 'value'>
160
206
  >;
161
207
  };
208
+ lineComponents: Array<
209
+ Pick<StorefrontAPI.CartLine, 'id' | 'quantity'> & {
210
+ attributes: Array<Pick<StorefrontAPI.Attribute, 'key' | 'value'>>;
211
+ cost: {
212
+ totalAmount: Pick<
213
+ StorefrontAPI.MoneyV2,
214
+ 'currencyCode' | 'amount'
215
+ >;
216
+ amountPerQuantity: Pick<
217
+ StorefrontAPI.MoneyV2,
218
+ 'currencyCode' | 'amount'
219
+ >;
220
+ compareAtAmountPerQuantity?: StorefrontAPI.Maybe<
221
+ Pick<StorefrontAPI.MoneyV2, 'currencyCode' | 'amount'>
222
+ >;
223
+ };
224
+ merchandise: Pick<
225
+ StorefrontAPI.ProductVariant,
226
+ 'id' | 'availableForSale' | 'requiresShipping' | 'title'
227
+ > & {
228
+ compareAtPrice?: StorefrontAPI.Maybe<
229
+ Pick<StorefrontAPI.MoneyV2, 'currencyCode' | 'amount'>
230
+ >;
231
+ price: Pick<StorefrontAPI.MoneyV2, 'currencyCode' | 'amount'>;
232
+ image?: StorefrontAPI.Maybe<
233
+ Pick<
234
+ StorefrontAPI.Image,
235
+ 'id' | 'url' | 'altText' | 'width' | 'height'
236
+ >
237
+ >;
238
+ product: Pick<
239
+ StorefrontAPI.Product,
240
+ 'handle' | 'title' | 'id' | 'vendor'
241
+ >;
242
+ selectedOptions: Array<
243
+ Pick<StorefrontAPI.SelectedOption, 'name' | 'value'>
244
+ >;
245
+ };
246
+ parentRelationship?: StorefrontAPI.Maybe<{
247
+ parent: Pick<StorefrontAPI.CartLine, 'id'>;
248
+ }>;
249
+ }
250
+ >;
162
251
  })
163
252
  >;
164
253
  };
@@ -193,15 +193,21 @@ async function runDeploy(options) {
193
193
  errorMessage += `:
194
194
 
195
195
  ${changedFiles.trimEnd()}`;
196
- packageManagers.forEach(({ name, lockfile, installCommand }) => {
197
- if (changedFiles.includes(lockfile)) {
198
- nextSteps.push([
199
- `If you are using ${name}, try running`,
200
- { command: installCommand },
201
- `to avoid changes to ${lockfile}.`
202
- ]);
196
+ packageManagers.forEach(
197
+ ({ name, lockfile, alternativeLockfiles, installCommand }) => {
198
+ const allLockfiles = [lockfile, ...alternativeLockfiles || []];
199
+ const changedLockfile = allLockfiles.find(
200
+ (lf) => changedFiles.includes(lf)
201
+ );
202
+ if (changedLockfile) {
203
+ nextSteps.push([
204
+ `If you are using ${name}, try running`,
205
+ { command: installCommand },
206
+ `to avoid changes to ${changedLockfile}.`
207
+ ]);
208
+ }
203
209
  }
204
- });
210
+ );
205
211
  }
206
212
  throw new AbortError(errorMessage, null, nextSteps);
207
213
  }
@@ -60,7 +60,7 @@ declare class Init extends Command {
60
60
  declare function runInit({ markets, ...options }?: InitOptions & {
61
61
  markets?: InitOptions['i18n'];
62
62
  }): Promise<{
63
- language?: "ts" | "js";
63
+ language?: "js" | "ts";
64
64
  packageManager: "npm" | "pnpm" | "yarn" | "bun" | "unknown";
65
65
  cssStrategy?: CssStrategy;
66
66
  cliCommand: CliCommand;
@@ -24,10 +24,12 @@ function missingLockfileWarning(shouldExit) {
24
24
  renderWarning({ headline, body, nextSteps });
25
25
  }
26
26
  }
27
- function multipleLockfilesWarning(packageManagers2, shouldExit) {
28
- const lockfileList = packageManagers2.map(({ name, lockfile }) => {
29
- return `${lockfile} (created by ${name})`;
30
- });
27
+ function multipleLockfilesWarning(foundPackageManagers, shouldExit) {
28
+ const lockfileList = foundPackageManagers.map(
29
+ ({ packageManager, foundLockfile }) => {
30
+ return `${foundLockfile} (created by ${packageManager.name})`;
31
+ }
32
+ );
31
33
  const headline = "Multiple lockfiles found";
32
34
  const body = [
33
35
  `Your project contains more than one lockfile. This can cause version conflicts when installing and deploying your app. The following lockfiles were detected:
@@ -57,8 +59,15 @@ async function checkLockfileStatus(directory, shouldExit = false) {
57
59
  if (isHydrogenMonorepo && !process.env.SHOPIFY_UNIT_TEST) return;
58
60
  const foundPackageManagers = [];
59
61
  for (const packageManager of packageManagers) {
60
- if (await fileExists(resolvePath(directory, packageManager.lockfile))) {
61
- foundPackageManagers.push(packageManager);
62
+ const allLockfiles = [
63
+ packageManager.lockfile,
64
+ ...packageManager.alternativeLockfiles || []
65
+ ];
66
+ for (const lockfile2 of allLockfiles) {
67
+ if (await fileExists(resolvePath(directory, lockfile2))) {
68
+ foundPackageManagers.push({ packageManager, foundLockfile: lockfile2 });
69
+ break;
70
+ }
62
71
  }
63
72
  }
64
73
  if (foundPackageManagers.length === 0) {
@@ -67,7 +76,7 @@ async function checkLockfileStatus(directory, shouldExit = false) {
67
76
  if (foundPackageManagers.length > 1) {
68
77
  return multipleLockfilesWarning(foundPackageManagers, shouldExit);
69
78
  }
70
- const lockfile = foundPackageManagers[0].lockfile;
79
+ const lockfile = foundPackageManagers[0].foundLockfile;
71
80
  const ignoredLockfile = await checkIfIgnoredInGitRepository(directory, [
72
81
  lockfile
73
82
  ]).catch(() => {
@@ -2,6 +2,7 @@ const packageManagers = [
2
2
  {
3
3
  name: "npm",
4
4
  lockfile: "package-lock.json",
5
+ alternativeLockfiles: ["npm-shrinkwrap.json"],
5
6
  installCommand: "npm ci"
6
7
  },
7
8
  {
@@ -17,6 +18,7 @@ const packageManagers = [
17
18
  {
18
19
  name: "bun",
19
20
  lockfile: "bun.lockb",
21
+ alternativeLockfiles: ["bun.lock"],
20
22
  installCommand: "bun install --frozen-lockfile"
21
23
  }
22
24
  ];
@@ -1683,5 +1683,5 @@
1683
1683
  ]
1684
1684
  }
1685
1685
  },
1686
- "version": "11.1.7"
1686
+ "version": "11.1.8"
1687
1687
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "@shopify:registry": "https://registry.npmjs.org"
6
6
  },
7
- "version": "11.1.7",
7
+ "version": "11.1.8",
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "repository": {