@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.
- package/dist/assets/hydrogen/starter/CHANGELOG.md +74 -0
- package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +67 -38
- package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +45 -7
- package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +20 -38
- package/dist/assets/hydrogen/starter/app/lib/fragments.ts +8 -0
- package/dist/assets/hydrogen/starter/app/routes/cart.tsx +2 -6
- package/dist/assets/hydrogen/starter/app/styles/app.css +21 -1
- package/dist/assets/hydrogen/starter/package.json +2 -2
- package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +89 -0
- package/dist/commands/hydrogen/deploy.js +14 -8
- package/dist/commands/hydrogen/init.d.ts +1 -1
- package/dist/lib/check-lockfile.js +16 -7
- package/dist/lib/package-managers.js +2 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -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 {
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
|
70
|
-
<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
|
-
<
|
|
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
|
|
|
81
|
-
<button type="submit"
|
|
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
|
-
|
|
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
|
-
</
|
|
165
|
+
</AddGiftCardForm>
|
|
170
166
|
</div>
|
|
171
167
|
);
|
|
172
168
|
}
|
|
173
169
|
|
|
174
|
-
function
|
|
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.
|
|
190
|
-
inputs={{
|
|
191
|
-
giftCardCodes: giftCardCodes || [],
|
|
192
|
-
}}
|
|
181
|
+
action={CartForm.ACTIONS.GiftCardCodesAdd}
|
|
193
182
|
>
|
|
194
|
-
{
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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?: "
|
|
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(
|
|
28
|
-
const lockfileList =
|
|
29
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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].
|
|
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
|
];
|
package/oclif.manifest.json
CHANGED