@shopify/cli-hydrogen 11.1.9 → 11.1.10
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 +11 -0
- package/dist/assets/hydrogen/starter/app/components/MockShopNotice.tsx +20 -0
- package/dist/assets/hydrogen/starter/app/entry.client.tsx +2 -3
- package/dist/assets/hydrogen/starter/app/routes/_index.tsx +4 -5
- package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +12 -12
- package/dist/assets/hydrogen/starter/app/routes/account.tsx +4 -7
- package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +2 -9
- package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +1 -4
- package/dist/assets/hydrogen/starter/app/routes/cart.tsx +1 -5
- package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +1 -3
- package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +2 -8
- package/dist/assets/hydrogen/starter/app/routes/policies.$handle.tsx +1 -4
- package/dist/assets/hydrogen/starter/app/routes/policies._index.tsx +1 -1
- package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +2 -9
- package/dist/assets/hydrogen/starter/app/routes/search.tsx +18 -12
- package/dist/assets/hydrogen/starter/app/styles/app.css +51 -0
- package/dist/assets/hydrogen/starter/guides/predictiveSearch/predictiveSearch.md +3 -3
- package/dist/assets/hydrogen/starter/guides/search/search.md +1 -1
- package/dist/assets/hydrogen/starter/package.json +9 -9
- package/dist/commands/hydrogen/dev.js +0 -4
- package/dist/commands/hydrogen/init.d.ts +2 -4
- package/dist/commands/hydrogen/init.js +2 -8
- package/dist/commands/hydrogen/preview.js +1 -1
- package/dist/commands/hydrogen/setup/css.js +1 -1
- package/dist/commands/hydrogen/setup/vite.js +1 -1
- package/dist/commands/hydrogen/setup.js +31 -33
- package/dist/commands/hydrogen/upgrade.js +7 -0
- package/dist/lib/build.js +4 -5
- package/dist/lib/codegen.js +2 -2
- package/dist/lib/format-code.js +3 -3
- package/dist/lib/mini-oxygen/workerd.js +3 -5
- package/dist/lib/onboarding/common.js +19 -26
- package/dist/lib/onboarding/local.js +46 -59
- package/dist/lib/template-pack.js +84 -0
- package/oclif.manifest.json +2 -9
- package/package.json +18 -15
- package/dist/lib/react-router-version-check.js +0 -82
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# skeleton
|
|
2
2
|
|
|
3
|
+
## 2026.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Added a notice with relevant information on how to link a store in case there is no store linked yet ([#3448](https://github.com/Shopify/hydrogen/pull/3448)) by [@fredericoo](https://github.com/fredericoo)
|
|
8
|
+
|
|
9
|
+
- Fixed an issue where users without addresses could not add the first one ([#3456](https://github.com/Shopify/hydrogen/pull/3456)) by [@fredericoo](https://github.com/fredericoo)
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`ff93a1daf2207e52e1f8331f9ff2ccd1f9b7fed6`](https://github.com/Shopify/hydrogen/commit/ff93a1daf2207e52e1f8331f9ff2ccd1f9b7fed6)]:
|
|
12
|
+
- @shopify/hydrogen@2026.1.1
|
|
13
|
+
|
|
3
14
|
## 2026.1.0
|
|
4
15
|
|
|
5
16
|
### Major Changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function MockShopNotice() {
|
|
2
|
+
return (
|
|
3
|
+
<section
|
|
4
|
+
className="mock-shop-notice"
|
|
5
|
+
aria-labelledby="mock-shop-notice-heading"
|
|
6
|
+
>
|
|
7
|
+
<div className="inner">
|
|
8
|
+
<h2 id="mock-shop-notice-heading">Welcome to Hydrogen!</h2>
|
|
9
|
+
<p>
|
|
10
|
+
You’re seeing mocked products because no store is connected to
|
|
11
|
+
this project yet.
|
|
12
|
+
</p>
|
|
13
|
+
<p>
|
|
14
|
+
Link a store by running <code>npx shopify hydrogen link</code> in your
|
|
15
|
+
terminal.
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
</section>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -6,9 +6,8 @@ import {NonceProvider} from '@shopify/hydrogen';
|
|
|
6
6
|
if (!window.location.origin.includes('webcache.googleusercontent.com')) {
|
|
7
7
|
startTransition(() => {
|
|
8
8
|
// Extract nonce from existing script tags
|
|
9
|
-
const existingNonce =
|
|
10
|
-
.querySelector<HTMLScriptElement>('script[nonce]')
|
|
11
|
-
?.nonce;
|
|
9
|
+
const existingNonce =
|
|
10
|
+
document.querySelector<HTMLScriptElement>('script[nonce]')?.nonce;
|
|
12
11
|
|
|
13
12
|
hydrateRoot(
|
|
14
13
|
document,
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Await,
|
|
3
|
-
useLoaderData,
|
|
4
|
-
Link,
|
|
5
|
-
} from 'react-router';
|
|
1
|
+
import {Await, useLoaderData, Link} from 'react-router';
|
|
6
2
|
import type {Route} from './+types/_index';
|
|
7
3
|
import {Suspense} from 'react';
|
|
8
4
|
import {Image} from '@shopify/hydrogen';
|
|
@@ -11,6 +7,7 @@ import type {
|
|
|
11
7
|
RecommendedProductsQuery,
|
|
12
8
|
} from 'storefrontapi.generated';
|
|
13
9
|
import {ProductItem} from '~/components/ProductItem';
|
|
10
|
+
import {MockShopNotice} from '~/components/MockShopNotice';
|
|
14
11
|
|
|
15
12
|
export const meta: Route.MetaFunction = () => {
|
|
16
13
|
return [{title: 'Hydrogen | Home'}];
|
|
@@ -37,6 +34,7 @@ async function loadCriticalData({context}: Route.LoaderArgs) {
|
|
|
37
34
|
]);
|
|
38
35
|
|
|
39
36
|
return {
|
|
37
|
+
isShopLinked: Boolean(context.env.PUBLIC_STORE_DOMAIN),
|
|
40
38
|
featuredCollection: collections.nodes[0],
|
|
41
39
|
};
|
|
42
40
|
}
|
|
@@ -64,6 +62,7 @@ export default function Homepage() {
|
|
|
64
62
|
const data = useLoaderData<typeof loader>();
|
|
65
63
|
return (
|
|
66
64
|
<div className="home">
|
|
65
|
+
{data.isShopLinked ? null : <MockShopNotice />}
|
|
67
66
|
<FeaturedCollection collection={data.featuredCollection} />
|
|
68
67
|
<RecommendedProducts products={data.recommendedProducts} />
|
|
69
68
|
</div>
|
|
@@ -264,23 +264,23 @@ export default function Addresses() {
|
|
|
264
264
|
<div className="account-addresses">
|
|
265
265
|
<h2>Addresses</h2>
|
|
266
266
|
<br />
|
|
267
|
-
|
|
268
|
-
<p>You have no addresses saved.</p>
|
|
269
|
-
) : (
|
|
267
|
+
<div>
|
|
270
268
|
<div>
|
|
271
|
-
<
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
269
|
+
<legend>Create address</legend>
|
|
270
|
+
<NewAddressForm key={addresses.nodes.length} />
|
|
271
|
+
</div>
|
|
272
|
+
<br />
|
|
273
|
+
<hr />
|
|
274
|
+
<br />
|
|
275
|
+
{!addresses.nodes.length ? (
|
|
276
|
+
<p>You have no addresses saved.</p>
|
|
277
|
+
) : (
|
|
278
278
|
<ExistingAddresses
|
|
279
279
|
addresses={addresses}
|
|
280
280
|
defaultAddress={defaultAddress}
|
|
281
281
|
/>
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
)}
|
|
283
|
+
</div>
|
|
284
284
|
</div>
|
|
285
285
|
);
|
|
286
286
|
}
|
|
@@ -14,14 +14,11 @@ export function shouldRevalidate() {
|
|
|
14
14
|
|
|
15
15
|
export async function loader({context}: Route.LoaderArgs) {
|
|
16
16
|
const {customerAccount} = context;
|
|
17
|
-
const {data, errors} = await customerAccount.query(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
variables: {
|
|
21
|
-
language: customerAccount.i18n.language,
|
|
22
|
-
},
|
|
17
|
+
const {data, errors} = await customerAccount.query(CUSTOMER_DETAILS_QUERY, {
|
|
18
|
+
variables: {
|
|
19
|
+
language: customerAccount.i18n.language,
|
|
23
20
|
},
|
|
24
|
-
);
|
|
21
|
+
});
|
|
25
22
|
|
|
26
23
|
if (errors?.length || !data?.customer) {
|
|
27
24
|
throw new Error('Customer not found');
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Link,
|
|
3
|
-
useLoaderData,
|
|
4
|
-
} from 'react-router';
|
|
1
|
+
import {Link, useLoaderData} from 'react-router';
|
|
5
2
|
import type {Route} from './+types/blogs.$blogHandle._index';
|
|
6
3
|
import {Image, getPaginationVariables} from '@shopify/hydrogen';
|
|
7
4
|
import type {ArticleItemFragment} from 'storefrontapi.generated';
|
|
@@ -26,11 +23,7 @@ export async function loader(args: Route.LoaderArgs) {
|
|
|
26
23
|
* Load data necessary for rendering content above the fold. This is the critical data
|
|
27
24
|
* needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
|
|
28
25
|
*/
|
|
29
|
-
async function loadCriticalData({
|
|
30
|
-
context,
|
|
31
|
-
request,
|
|
32
|
-
params,
|
|
33
|
-
}: Route.LoaderArgs) {
|
|
26
|
+
async function loadCriticalData({context, request, params}: Route.LoaderArgs) {
|
|
34
27
|
const paginationVariables = getPaginationVariables(request, {
|
|
35
28
|
pageBy: 4,
|
|
36
29
|
});
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Link,
|
|
3
|
-
useLoaderData,
|
|
4
|
-
} from 'react-router';
|
|
1
|
+
import {Link, useLoaderData} from 'react-router';
|
|
5
2
|
import type {Route} from './+types/blogs._index';
|
|
6
3
|
import {getPaginationVariables} from '@shopify/hydrogen';
|
|
7
4
|
import {PaginatedResourceSection} from '~/components/PaginatedResourceSection';
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useLoaderData,
|
|
3
|
-
data,
|
|
4
|
-
type HeadersFunction,
|
|
5
|
-
} from 'react-router';
|
|
1
|
+
import {useLoaderData, data, type HeadersFunction} from 'react-router';
|
|
6
2
|
import type {Route} from './+types/cart';
|
|
7
3
|
import type {CartQueryDataReturn} from '@shopify/hydrogen';
|
|
8
4
|
import {CartForm} from '@shopify/hydrogen';
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type {Route} from './+types/collections.all';
|
|
2
|
-
import {
|
|
3
|
-
useLoaderData,
|
|
4
|
-
} from 'react-router';
|
|
2
|
+
import {useLoaderData} from 'react-router';
|
|
5
3
|
import {getPaginationVariables, Image, Money} from '@shopify/hydrogen';
|
|
6
4
|
import {PaginatedResourceSection} from '~/components/PaginatedResourceSection';
|
|
7
5
|
import {ProductItem} from '~/components/ProductItem';
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useLoaderData,
|
|
3
|
-
} from 'react-router';
|
|
1
|
+
import {useLoaderData} from 'react-router';
|
|
4
2
|
import type {Route} from './+types/pages.$handle';
|
|
5
3
|
import {redirectIfHandleIsLocalized} from '~/lib/redirect';
|
|
6
4
|
|
|
@@ -22,11 +20,7 @@ export async function loader(args: Route.LoaderArgs) {
|
|
|
22
20
|
* Load data necessary for rendering content above the fold. This is the critical data
|
|
23
21
|
* needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
|
|
24
22
|
*/
|
|
25
|
-
async function loadCriticalData({
|
|
26
|
-
context,
|
|
27
|
-
request,
|
|
28
|
-
params,
|
|
29
|
-
}: Route.LoaderArgs) {
|
|
23
|
+
async function loadCriticalData({context, request, params}: Route.LoaderArgs) {
|
|
30
24
|
if (!params.handle) {
|
|
31
25
|
throw new Error('Missing page handle');
|
|
32
26
|
}
|
|
@@ -4,7 +4,7 @@ import type {PoliciesQuery, PolicyItemFragment} from 'storefrontapi.generated';
|
|
|
4
4
|
|
|
5
5
|
export async function loader({context}: Route.LoaderArgs) {
|
|
6
6
|
const data: PoliciesQuery = await context.storefront.query(POLICIES_QUERY);
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
const shopPolicies = data.shop;
|
|
9
9
|
const policies: PolicyItemFragment[] = [
|
|
10
10
|
shopPolicies?.privacyPolicy,
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
redirect,
|
|
3
|
-
useLoaderData,
|
|
4
|
-
} from 'react-router';
|
|
1
|
+
import {redirect, useLoaderData} from 'react-router';
|
|
5
2
|
import type {Route} from './+types/products.$handle';
|
|
6
3
|
import {
|
|
7
4
|
getSelectedProductOptions,
|
|
@@ -40,11 +37,7 @@ export async function loader(args: Route.LoaderArgs) {
|
|
|
40
37
|
* Load data necessary for rendering content above the fold. This is the critical data
|
|
41
38
|
* needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
|
|
42
39
|
*/
|
|
43
|
-
async function loadCriticalData({
|
|
44
|
-
context,
|
|
45
|
-
params,
|
|
46
|
-
request,
|
|
47
|
-
}: Route.LoaderArgs) {
|
|
40
|
+
async function loadCriticalData({context, params, request}: Route.LoaderArgs) {
|
|
48
41
|
const {handle} = params;
|
|
49
42
|
const {storefront} = context;
|
|
50
43
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useLoaderData,
|
|
3
|
-
} from 'react-router';
|
|
1
|
+
import {useLoaderData} from 'react-router';
|
|
4
2
|
import type {Route} from './+types/search';
|
|
5
3
|
import {getPaginationVariables, Analytics} from '@shopify/hydrogen';
|
|
6
4
|
import {SearchForm} from '~/components/SearchForm';
|
|
@@ -10,7 +8,10 @@ import {
|
|
|
10
8
|
type PredictiveSearchReturn,
|
|
11
9
|
getEmptyPredictiveSearchResult,
|
|
12
10
|
} from '~/lib/search';
|
|
13
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
RegularSearchQuery,
|
|
13
|
+
PredictiveSearchQuery,
|
|
14
|
+
} from 'storefrontapi.generated';
|
|
14
15
|
|
|
15
16
|
export const meta: Route.MetaFunction = () => {
|
|
16
17
|
return [{title: `Hydrogen | Search`}];
|
|
@@ -225,9 +226,13 @@ async function regularSearch({
|
|
|
225
226
|
const term = String(url.searchParams.get('q') || '');
|
|
226
227
|
|
|
227
228
|
// Search articles, pages, and products for the `q` term
|
|
228
|
-
const {
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
const {
|
|
230
|
+
errors,
|
|
231
|
+
...items
|
|
232
|
+
}: {errors?: Array<{message: string}>} & RegularSearchQuery =
|
|
233
|
+
await storefront.query(SEARCH_QUERY, {
|
|
234
|
+
variables: {...variables, term},
|
|
235
|
+
});
|
|
231
236
|
|
|
232
237
|
if (!items) {
|
|
233
238
|
throw new Error('No search data returned from Shopify API');
|
|
@@ -389,17 +394,18 @@ async function predictiveSearch({
|
|
|
389
394
|
if (!term) return {type, term, result: getEmptyPredictiveSearchResult()};
|
|
390
395
|
|
|
391
396
|
// Predictively search articles, collections, pages, products, and queries (suggestions)
|
|
392
|
-
const {
|
|
393
|
-
|
|
394
|
-
|
|
397
|
+
const {
|
|
398
|
+
predictiveSearch: items,
|
|
399
|
+
errors,
|
|
400
|
+
}: PredictiveSearchQuery & {errors?: Array<{message: string}>} =
|
|
401
|
+
await storefront.query(PREDICTIVE_SEARCH_QUERY, {
|
|
395
402
|
variables: {
|
|
396
403
|
// customize search options as needed
|
|
397
404
|
limit,
|
|
398
405
|
limitScope: 'EACH',
|
|
399
406
|
term,
|
|
400
407
|
},
|
|
401
|
-
}
|
|
402
|
-
);
|
|
408
|
+
});
|
|
403
409
|
|
|
404
410
|
if (errors) {
|
|
405
411
|
throw new Error(
|
|
@@ -22,6 +22,57 @@ img {
|
|
|
22
22
|
cursor: pointer;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/*
|
|
26
|
+
* --------------------------------------------------
|
|
27
|
+
* components/MockShopNotice
|
|
28
|
+
* --------------------------------------------------
|
|
29
|
+
*/
|
|
30
|
+
.mock-shop-notice {
|
|
31
|
+
background: var(--color-light);
|
|
32
|
+
border: 1px solid var(--color-dark);
|
|
33
|
+
border-left-width: 0.5rem;
|
|
34
|
+
margin: 1rem 0;
|
|
35
|
+
padding: 0;
|
|
36
|
+
|
|
37
|
+
.inner {
|
|
38
|
+
padding: 0.875rem 1rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
h2 {
|
|
42
|
+
font-size: 1.6rem;
|
|
43
|
+
font-weight: 700;
|
|
44
|
+
line-height: 1.4;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
p {
|
|
48
|
+
font-size: 1rem;
|
|
49
|
+
line-height: 1.4;
|
|
50
|
+
margin-bottom: 0.5rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
footer {
|
|
54
|
+
background: rgba(0, 0, 0, 0.06);
|
|
55
|
+
padding: 0.5rem 1rem;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.small {
|
|
59
|
+
font-size: 0.875rem;
|
|
60
|
+
line-height: 1.4;
|
|
61
|
+
margin-bottom: 0.5rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
code {
|
|
65
|
+
background: rgba(0, 0, 0, 0.06);
|
|
66
|
+
border-radius: 0.25rem;
|
|
67
|
+
padding: 0.1em 0.3em;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
a {
|
|
71
|
+
text-decoration: underline;
|
|
72
|
+
text-underline-offset: 6px;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
25
76
|
/*
|
|
26
77
|
* --------------------------------------------------
|
|
27
78
|
* components/Aside
|
|
@@ -15,7 +15,7 @@ This integration uses the storefront API (SFAPI) [predictiveSearch](https://shop
|
|
|
15
15
|
|
|
16
16
|
| File | Description |
|
|
17
17
|
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
18
|
-
| [`app/components/SearchFormPredictive.tsx`](../../app/components/SearchFormPredictive.tsx) | A fully customizable form component configured to make form `GET` requests to the `/search` route.
|
|
18
|
+
| [`app/components/SearchFormPredictive.tsx`](../../app/components/SearchFormPredictive.tsx) | A fully customizable form component configured to make form `GET` requests to the `/search` route. |
|
|
19
19
|
| [`app/components/SearchResultsPredictive.tsx`](../../app/components/SearchResultsPredictive.tsx) | A fully customizable search results wrapper, that provides compound components to render `articles`, `pages`, `products`, `collections` and `queries`. |
|
|
20
20
|
|
|
21
21
|
## Instructions
|
|
@@ -217,10 +217,10 @@ export async function loader({request, context}: LoaderFunctionArgs) {
|
|
|
217
217
|
const isPredictive = url.searchParams.has('predictive');
|
|
218
218
|
|
|
219
219
|
if (!isPredictive) {
|
|
220
|
-
return {}
|
|
220
|
+
return {};
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
const searchPromise = predictiveSearch({request, context})
|
|
223
|
+
const searchPromise = predictiveSearch({request, context});
|
|
224
224
|
|
|
225
225
|
searchPromise.catch((error: Error) => {
|
|
226
226
|
console.error(error);
|
|
@@ -212,7 +212,7 @@ export async function loader({request, context}: LoaderFunctionArgs) {
|
|
|
212
212
|
const isRegular = !url.searchParams.has('predictive');
|
|
213
213
|
|
|
214
214
|
if (!isRegular) {
|
|
215
|
-
return {}
|
|
215
|
+
return {};
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
const searchPromise = regularSearch({request, context});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "skeleton",
|
|
3
3
|
"private": true,
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "2026.1.
|
|
5
|
+
"version": "2026.1.1",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "shopify hydrogen build --codegen",
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
},
|
|
15
15
|
"prettier": "@shopify/prettier-config",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@shopify/hydrogen": "2026.1.
|
|
17
|
+
"@shopify/hydrogen": "*2026.1.1",
|
|
18
18
|
"graphql": "^16.10.0",
|
|
19
19
|
"graphql-tag": "^2.12.6",
|
|
20
20
|
"isbot": "^5.1.22",
|
|
21
|
-
"react": "18.3.1",
|
|
22
|
-
"react-dom": "18.3.1",
|
|
21
|
+
"react": "^18.3.1",
|
|
22
|
+
"react-dom": "^18.3.1",
|
|
23
23
|
"react-router": "7.12.0",
|
|
24
24
|
"react-router-dom": "7.12.0"
|
|
25
25
|
},
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"@react-router/dev": "7.12.0",
|
|
32
32
|
"@react-router/fs-routes": "7.12.0",
|
|
33
33
|
"@shopify/cli": "3.85.4",
|
|
34
|
-
"@shopify/hydrogen-codegen": "
|
|
35
|
-
"@shopify/mini-oxygen": "
|
|
34
|
+
"@shopify/hydrogen-codegen": "0.3.3",
|
|
35
|
+
"@shopify/mini-oxygen": "4.0.1",
|
|
36
36
|
"@shopify/oxygen-workers-types": "^4.1.6",
|
|
37
37
|
"@shopify/prettier-config": "^1.1.2",
|
|
38
38
|
"@total-typescript/ts-reset": "^0.6.1",
|
|
39
39
|
"@types/eslint": "^9.6.1",
|
|
40
|
-
"@types/react": "^18.
|
|
41
|
-
"@types/react-dom": "^18.
|
|
40
|
+
"@types/react": "^18.3.28",
|
|
41
|
+
"@types/react-dom": "^18.3.7",
|
|
42
42
|
"@typescript-eslint/eslint-plugin": "^8.21.0",
|
|
43
43
|
"@typescript-eslint/parser": "^8.21.0",
|
|
44
44
|
"eslint": "^9.18.0",
|
|
@@ -58,6 +58,6 @@
|
|
|
58
58
|
"vite-tsconfig-paths": "^4.3.1"
|
|
59
59
|
},
|
|
60
60
|
"engines": {
|
|
61
|
-
"node": "
|
|
61
|
+
"node": "^22 || ^24"
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -11,7 +11,6 @@ import { commonFlags, overrideFlag, flagsToCamelObject, DEFAULT_INSPECTOR_PORT,
|
|
|
11
11
|
import { spawnCodegenProcess } from '../../lib/codegen.js';
|
|
12
12
|
import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
|
|
13
13
|
import { displayDevUpgradeNotice } from './upgrade.js';
|
|
14
|
-
import { checkReactRouterVersions } from '../../lib/react-router-version-check.js';
|
|
15
14
|
import { getDevConfigInBackground, TUNNEL_DOMAIN, startTunnelAndPushConfig, isMockShop, notifyIssueWithTunnelAndMockShop, getUtilityBannerlines, getDebugBannerLine } from '../../lib/dev-shared.js';
|
|
16
15
|
import { getCliCommand } from '../../lib/shell.js';
|
|
17
16
|
import { findPort } from '../../lib/find-port.js';
|
|
@@ -238,9 +237,6 @@ async function runDev({
|
|
|
238
237
|
viteServer.bindCLIShortcuts({ print: true });
|
|
239
238
|
console.log("\n");
|
|
240
239
|
const storefrontTitle = (await backgroundPromise).storefrontTitle;
|
|
241
|
-
if (!disableVersionCheck) {
|
|
242
|
-
await checkReactRouterVersions(root);
|
|
243
|
-
}
|
|
244
240
|
showSuccessBanner({
|
|
245
241
|
disableVirtualRoutes,
|
|
246
242
|
debug,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { PackageManager } from '@shopify/cli-kit/node/node-package-manager';
|
|
1
2
|
import * as _oclif_core_lib_interfaces_parser_js from '@oclif/core/lib/interfaces/parser.js';
|
|
2
3
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
3
|
-
import { PackageManager } from '@shopify/cli-kit/node/node-package-manager';
|
|
4
4
|
|
|
5
5
|
declare const SETUP_I18N_STRATEGIES: readonly ["subfolders", "domains", "subdomains"];
|
|
6
6
|
type I18nStrategy = (typeof SETUP_I18N_STRATEGIES)[number];
|
|
@@ -24,7 +24,6 @@ type InitOptions = {
|
|
|
24
24
|
i18n?: I18nChoice;
|
|
25
25
|
token?: string;
|
|
26
26
|
force?: boolean;
|
|
27
|
-
routes?: boolean;
|
|
28
27
|
shortcut?: boolean;
|
|
29
28
|
installDeps?: boolean;
|
|
30
29
|
git?: boolean;
|
|
@@ -41,7 +40,6 @@ declare class Init extends Command {
|
|
|
41
40
|
static descriptionWithMarkdown: string;
|
|
42
41
|
static description: string;
|
|
43
42
|
static flags: {
|
|
44
|
-
routes: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
45
43
|
git: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
46
44
|
quickstart: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
47
45
|
'package-manager': _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
@@ -60,7 +58,7 @@ declare class Init extends Command {
|
|
|
60
58
|
declare function runInit({ markets, ...options }?: InitOptions & {
|
|
61
59
|
markets?: InitOptions['i18n'];
|
|
62
60
|
}): Promise<{
|
|
63
|
-
language?:
|
|
61
|
+
language?: Language;
|
|
64
62
|
packageManager: "npm" | "pnpm" | "yarn" | "bun" | "unknown";
|
|
65
63
|
cssStrategy?: CssStrategy;
|
|
66
64
|
cliCommand: CliCommand;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
2
2
|
import { Flags } from '@oclif/core';
|
|
3
3
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
4
|
-
import { commonFlags,
|
|
4
|
+
import { commonFlags, flagsToCamelObject, parseProcessFlags } from '../../lib/flags.js';
|
|
5
5
|
import { checkCurrentCLIVersion } from '../../lib/check-cli-version.js';
|
|
6
6
|
import { STYLING_CHOICES } from '../../lib/setups/css/index.js';
|
|
7
7
|
import { I18N_CHOICES } from '../../lib/setups/i18n/index.js';
|
|
@@ -36,11 +36,6 @@ class Init extends Command {
|
|
|
36
36
|
...commonFlags.styling,
|
|
37
37
|
...commonFlags.markets,
|
|
38
38
|
...commonFlags.shortcut,
|
|
39
|
-
routes: Flags.boolean({
|
|
40
|
-
description: "Generate routes for all pages.",
|
|
41
|
-
env: "SHOPIFY_HYDROGEN_FLAG_ROUTES",
|
|
42
|
-
allowNo: true
|
|
43
|
-
}),
|
|
44
39
|
git: Flags.boolean({
|
|
45
40
|
description: "Init Git and create initial commits.",
|
|
46
41
|
env: "SHOPIFY_HYDROGEN_FLAG_GIT",
|
|
@@ -48,7 +43,7 @@ class Init extends Command {
|
|
|
48
43
|
allowNo: true
|
|
49
44
|
}),
|
|
50
45
|
quickstart: Flags.boolean({
|
|
51
|
-
description: "Scaffolds a new Hydrogen project with a set of sensible defaults. Equivalent to `shopify hydrogen init --path hydrogen-quickstart --mock-shop --language js --shortcut --
|
|
46
|
+
description: "Scaffolds a new Hydrogen project with a set of sensible defaults. Equivalent to `shopify hydrogen init --path hydrogen-quickstart --mock-shop --language js --shortcut --markets none`",
|
|
52
47
|
env: "SHOPIFY_HYDROGEN_FLAG_QUICKSTART",
|
|
53
48
|
default: false
|
|
54
49
|
}),
|
|
@@ -91,7 +86,6 @@ async function runInit({
|
|
|
91
86
|
options.language ??= "js";
|
|
92
87
|
options.mockShop ??= true;
|
|
93
88
|
options.path ??= "./hydrogen-quickstart";
|
|
94
|
-
options.routes ??= true;
|
|
95
89
|
options.shortcut ??= true;
|
|
96
90
|
options.styling ??= "none";
|
|
97
91
|
}
|
|
@@ -5,7 +5,7 @@ import { outputInfo } from '@shopify/cli-kit/node/output';
|
|
|
5
5
|
import { resolvePath, joinPath } from '@shopify/cli-kit/node/path';
|
|
6
6
|
import { setH2OVerbose, isH2Verbose, muteDevLogs } from '../../lib/log.js';
|
|
7
7
|
import { getProjectPaths, isClassicProject } from '../../lib/remix-config.js';
|
|
8
|
-
import {
|
|
8
|
+
import { commonFlags, overrideFlag, flagsToCamelObject, DEFAULT_APP_PORT } from '../../lib/flags.js';
|
|
9
9
|
import { startMiniOxygen } from '../../lib/mini-oxygen/index.js';
|
|
10
10
|
import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
|
|
11
11
|
import { getConfig } from '../../lib/shopify-config.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
2
|
-
import {
|
|
2
|
+
import { commonFlags, overrideFlag, flagsToCamelObject } from '../../../lib/flags.js';
|
|
3
3
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
4
4
|
import { renderSuccess, renderTasks, renderWarning } from '@shopify/cli-kit/node/ui';
|
|
5
5
|
import { getPackageManager, installNodeModules } from '@shopify/cli-kit/node/node-package-manager';
|
|
@@ -5,7 +5,7 @@ import { removeFile, moveFile, readFile, writeFile, fileExists } from '@shopify/
|
|
|
5
5
|
import { readAndParsePackageJson, installNodeModules, getPackageManager } from '@shopify/cli-kit/node/node-package-manager';
|
|
6
6
|
import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
|
|
7
7
|
import { getRawRemixConfig } from '../../../lib/remix-config.js';
|
|
8
|
-
import {
|
|
8
|
+
import { replaceFileContent, mergePackageJson } from '../../../lib/file.js';
|
|
9
9
|
import { importLangAstGrep } from '../../../lib/ast.js';
|
|
10
10
|
import { getAssetsDir } from '../../../lib/build.js';
|
|
11
11
|
import { getCodeFormatOptions, formatCode } from '../../../lib/format-code.js';
|
|
@@ -3,7 +3,7 @@ import { AbortController } from '@shopify/cli-kit/node/abort';
|
|
|
3
3
|
import { renderTasks } from '@shopify/cli-kit/node/ui';
|
|
4
4
|
import { resolvePath, basename, joinPath } from '@shopify/cli-kit/node/path';
|
|
5
5
|
import { fileExists, glob, copyFile } from '@shopify/cli-kit/node/fs';
|
|
6
|
-
import {
|
|
6
|
+
import { commonFlags, overrideFlag, flagsToCamelObject } from '../../lib/flags.js';
|
|
7
7
|
import { renderI18nPrompt, setupI18nStrategy } from '../../lib/setups/i18n/index.js';
|
|
8
8
|
import { getRemixConfig } from '../../lib/remix-config.js';
|
|
9
9
|
import { handleRouteGeneration, generateProjectEntries, handleCliShortcut, renderProjectReady } from '../../lib/onboarding/common.js';
|
|
@@ -49,39 +49,37 @@ async function runSetup(options) {
|
|
|
49
49
|
extraChoices: { none: "Set up later" }
|
|
50
50
|
});
|
|
51
51
|
const i18n = i18nStrategy === "none" ? void 0 : i18nStrategy;
|
|
52
|
-
const {
|
|
52
|
+
const { setupRoutes } = handleRouteGeneration(controller);
|
|
53
53
|
let routes;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
(filename)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
overwriteFileDeps: true
|
|
82
|
-
});
|
|
54
|
+
const templateRoot = await getTemplateAppFile("..");
|
|
55
|
+
const [typescript, dtsFiles] = await Promise.all([
|
|
56
|
+
fileExists(joinPath(rootDirectory, "tsconfig.json")),
|
|
57
|
+
glob("*.d.ts", { cwd: templateRoot })
|
|
58
|
+
]);
|
|
59
|
+
backgroundWorkPromise = backgroundWorkPromise.then(
|
|
60
|
+
() => Promise.all([
|
|
61
|
+
...dtsFiles.map(
|
|
62
|
+
(filename) => copyFile(
|
|
63
|
+
joinPath(templateRoot, filename),
|
|
64
|
+
resolvePath(rootDirectory, filename)
|
|
65
|
+
)
|
|
66
|
+
),
|
|
67
|
+
// Copy app entries
|
|
68
|
+
generateProjectEntries({
|
|
69
|
+
rootDirectory,
|
|
70
|
+
appDirectory,
|
|
71
|
+
typescript
|
|
72
|
+
})
|
|
73
|
+
])
|
|
74
|
+
).then(async () => {
|
|
75
|
+
routes = await setupRoutes(rootDirectory, typescript ? "ts" : "js", {
|
|
76
|
+
i18nStrategy: i18n,
|
|
77
|
+
// User might have added files before running this command.
|
|
78
|
+
// We should overwrite them to ensure the routes are set up correctly.
|
|
79
|
+
// Relies on Git to restore the files if needed.
|
|
80
|
+
overwriteFileDeps: true
|
|
83
81
|
});
|
|
84
|
-
}
|
|
82
|
+
});
|
|
85
83
|
if (i18n) {
|
|
86
84
|
backgroundWorkPromise = backgroundWorkPromise.then(
|
|
87
85
|
() => setupI18nStrategy(i18n, { rootDirectory })
|
|
@@ -93,7 +91,7 @@ async function runSetup(options) {
|
|
|
93
91
|
cliCommand,
|
|
94
92
|
options.shortcut
|
|
95
93
|
);
|
|
96
|
-
if (!i18n && !
|
|
94
|
+
if (!i18n && !createShortcut) return;
|
|
97
95
|
if (createShortcut) {
|
|
98
96
|
backgroundWorkPromise = backgroundWorkPromise.then(async () => {
|
|
99
97
|
if (await createShortcut()) {
|
|
@@ -859,6 +859,13 @@ async function displayDevUpgradeNotice({
|
|
|
859
859
|
const { currentVersion, currentDependencies } = await getHydrogenVersion({
|
|
860
860
|
appPath
|
|
861
861
|
});
|
|
862
|
+
if (currentVersion.startsWith("workspace:")) {
|
|
863
|
+
renderInfo({
|
|
864
|
+
headline: "Using monorepo @shopify/hydrogen dependency",
|
|
865
|
+
body: "This project uses the workspace protocol for @shopify/hydrogen, so upgrade notices are skipped during local monorepo development."
|
|
866
|
+
});
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
862
869
|
const isPrerelease = semver.prerelease(currentVersion);
|
|
863
870
|
if (isPrerelease || /^[a-z]+$/i.test(currentVersion)) {
|
|
864
871
|
return;
|
package/dist/lib/build.js
CHANGED
|
@@ -69,12 +69,11 @@ function getSkeletonNodeModules() {
|
|
|
69
69
|
return joinPath(getSkeletonSourceDir(), "node_modules");
|
|
70
70
|
}
|
|
71
71
|
async function getRepoNodeModules() {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (!nodeModulesPath && isHydrogenMonorepo) {
|
|
75
|
-
nodeModulesPath = joinPath(dirname(monorepoPackagesPath), "node_modules");
|
|
72
|
+
if (isHydrogenMonorepo) {
|
|
73
|
+
return getSkeletonNodeModules();
|
|
76
74
|
}
|
|
77
|
-
|
|
75
|
+
const { stdout } = await execAsync("npm root");
|
|
76
|
+
return stdout.trim();
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
export { ASSETS_DIR_PREFIX, ASSETS_STARTER_DIR, ASSETS_STARTER_DIR_ROUTES, getAssetsDir, getPkgJsonPath, getRepoNodeModules, getSkeletonNodeModules, getSkeletonSourceDir, getStarterDir, getTemplateAppFile, hydrogenPackagesPath, isHydrogenMonorepo };
|
package/dist/lib/codegen.js
CHANGED
|
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import { getCodeFormatOptions, formatCode } from './format-code.js';
|
|
4
4
|
import { renderInfo, renderWarning } from '@shopify/cli-kit/node/ui';
|
|
5
|
-
import { relativePath,
|
|
5
|
+
import { relativePath, joinPath, resolvePath, basename } from '@shopify/cli-kit/node/path';
|
|
6
6
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
7
7
|
import { importLocal } from './import-utils.js';
|
|
8
8
|
|
|
@@ -283,7 +283,7 @@ function findGqlProject(schemaFilepath, gqlConfig) {
|
|
|
283
283
|
);
|
|
284
284
|
}
|
|
285
285
|
async function addHooksToHydrogenOptions(codegenConfig, { rootDirectory }) {
|
|
286
|
-
const name = Symbol.for("name");
|
|
286
|
+
const name = /* @__PURE__ */ Symbol.for("name");
|
|
287
287
|
const hydrogenProjectsOptions = Object.values(codegenConfig.generates).filter(
|
|
288
288
|
(value) => {
|
|
289
289
|
const foundPreset = (Array.isArray(value) ? value[0] : value)?.preset;
|
package/dist/lib/format-code.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { extname } from '@shopify/cli-kit/node/path';
|
|
2
|
-
import
|
|
2
|
+
import { resolveConfig, format } from 'prettier';
|
|
3
3
|
import * as FS from 'fs/promises';
|
|
4
4
|
import * as Path from 'path';
|
|
5
5
|
|
|
@@ -12,14 +12,14 @@ const DEFAULT_PRETTIER_CONFIG = {
|
|
|
12
12
|
async function getCodeFormatOptions(filePath = process.cwd()) {
|
|
13
13
|
const pathToUse = (await FS.lstat(filePath)).isFile() ? filePath : Path.resolve(filePath, "prettier.file");
|
|
14
14
|
try {
|
|
15
|
-
return await
|
|
15
|
+
return await resolveConfig(pathToUse) || DEFAULT_PRETTIER_CONFIG;
|
|
16
16
|
} catch {
|
|
17
17
|
return DEFAULT_PRETTIER_CONFIG;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
async function formatCode(content, config = DEFAULT_PRETTIER_CONFIG, filePath = "") {
|
|
21
21
|
const ext = extname(filePath);
|
|
22
|
-
return
|
|
22
|
+
return format(content, {
|
|
23
23
|
// Specify the TypeScript parser for ts/tsx files. Otherwise
|
|
24
24
|
// we need to use the babel parser instead of the default parser,
|
|
25
25
|
// because prettier will print a warning.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { resolvePath, dirname } from '@shopify/cli-kit/node/path';
|
|
3
|
-
import {
|
|
3
|
+
import { createFileReadStream, readFile } from '@shopify/cli-kit/node/fs';
|
|
4
4
|
import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
5
5
|
import { outputNewline } from '@shopify/cli-kit/node/output';
|
|
6
6
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
7
7
|
import colors from '@shopify/cli-kit/node/colors';
|
|
8
8
|
import { handleMiniOxygenImportFail, SUBREQUEST_PROFILER_ENDPOINT, logRequestLine } from './common.js';
|
|
9
9
|
import { TUNNEL_DOMAIN, getUtilityBannerlines, getDebugBannerLine } from '../dev-shared.js';
|
|
10
|
-
import { setConstructors, handleDebugNetworkRequest,
|
|
10
|
+
import { setConstructors, handleDebugNetworkRequest, H2O_BINDING_NAME, createLogRequestEvent } from '../request-events.js';
|
|
11
11
|
import { importLocal } from '../import-utils.js';
|
|
12
12
|
|
|
13
13
|
async function startWorkerdServer({
|
|
@@ -28,9 +28,7 @@ async function startWorkerdServer({
|
|
|
28
28
|
setConstructors({ Response });
|
|
29
29
|
async function handleCustomerAccountSchema() {
|
|
30
30
|
const require2 = createRequire(import.meta.url);
|
|
31
|
-
const filePath = require2.resolve(
|
|
32
|
-
"@shopify/hydrogen/customer-account.schema.json"
|
|
33
|
-
);
|
|
31
|
+
const filePath = require2.resolve("@shopify/hydrogen/customer-account.schema.json");
|
|
34
32
|
return new Response(createFileReadStream(filePath), {
|
|
35
33
|
headers: { "Content-Type": "application/json" }
|
|
36
34
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { symlink, readdir } from 'node:fs/promises';
|
|
2
2
|
import { packageManagerFromUserAgent, installNodeModules } from '@shopify/cli-kit/node/node-package-manager';
|
|
3
|
-
import { renderConfirmationPrompt, renderInfo, renderTextPrompt, renderSelectPrompt, renderWarning, renderSuccess
|
|
3
|
+
import { renderConfirmationPrompt, renderInfo, renderTextPrompt, renderSelectPrompt, renderFatalError, renderWarning, renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
4
4
|
import { hyphenate, capitalize } from '@shopify/cli-kit/common/string';
|
|
5
5
|
import { joinPath, resolvePath, basename } from '@shopify/cli-kit/node/path';
|
|
6
6
|
import { initializeGitRepository, addAllToGitFromDirectory, createGitCommit } from '@shopify/cli-kit/node/git';
|
|
@@ -15,7 +15,7 @@ import { titleize } from '../string.js';
|
|
|
15
15
|
import { ALIAS_NAME, createPlatformShortcut } from '../shell.js';
|
|
16
16
|
import { transpileProject } from '../transpile/index.js';
|
|
17
17
|
import { renderCssPrompt, setupCssStrategy, CSS_STRATEGY_NAME_MAP } from '../setups/css/index.js';
|
|
18
|
-
import {
|
|
18
|
+
import { generateRoutes, generateProjectFile } from '../setups/routes/generate.js';
|
|
19
19
|
import { execAsync } from '../process.js';
|
|
20
20
|
import { getStorefronts } from '../graphql/admin/link-storefront.js';
|
|
21
21
|
import { isHydrogenMonorepo, getSkeletonSourceDir, getRepoNodeModules } from '../build.js';
|
|
@@ -42,32 +42,25 @@ async function handleI18n(controller, cliCommand, flagI18n) {
|
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
const routesToScaffold = flagRoutes === true ? "all" : flagRoutes === false ? [] : await renderRoutePrompt({
|
|
47
|
-
abortSignal: controller.signal
|
|
48
|
-
});
|
|
49
|
-
const needsRouteGeneration = routesToScaffold === "all" || routesToScaffold.length > 0;
|
|
45
|
+
function handleRouteGeneration(controller) {
|
|
50
46
|
return {
|
|
51
|
-
needsRouteGeneration,
|
|
52
47
|
setupRoutes: async (directory, language, options) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return result.routeGroups;
|
|
70
|
-
}
|
|
48
|
+
const result = await generateRoutes(
|
|
49
|
+
{
|
|
50
|
+
routeName: "all",
|
|
51
|
+
directory,
|
|
52
|
+
force: true,
|
|
53
|
+
typescript: language === "ts",
|
|
54
|
+
localePrefix: options?.i18nStrategy === "subfolders" ? "locale" : false,
|
|
55
|
+
signal: controller.signal,
|
|
56
|
+
...options
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
rootDirectory: directory,
|
|
60
|
+
appDirectory: joinPath(directory, "app")
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
return result.routeGroups;
|
|
71
64
|
}
|
|
72
65
|
};
|
|
73
66
|
}
|
|
@@ -2,8 +2,7 @@ import { cp } from 'node:fs/promises';
|
|
|
2
2
|
import { writeFile } from '@shopify/cli-kit/node/fs';
|
|
3
3
|
import { relativePath, joinPath } from '@shopify/cli-kit/node/path';
|
|
4
4
|
import { hyphenate } from '@shopify/cli-kit/common/string';
|
|
5
|
-
import
|
|
6
|
-
import { renderSelectPrompt, renderSuccess, renderConfirmationPrompt, renderTasks } from '@shopify/cli-kit/node/ui';
|
|
5
|
+
import { renderSelectPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
|
|
7
6
|
import { handleStorefrontLink, handleProjectLocation, createAbortHandler, generateProjectEntries, handleLanguage, createInitialCommit, handleCssStrategy, commitAll, handleDependencies, handleCliShortcut, handleI18n, handleRouteGeneration, renderProjectReady } from './common.js';
|
|
8
7
|
import { createStorefront } from '../graphql/admin/create-storefront.js';
|
|
9
8
|
import { waitForJob } from '../graphql/admin/fetch-job.js';
|
|
@@ -49,7 +48,7 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
49
48
|
{
|
|
50
49
|
force: true,
|
|
51
50
|
recursive: true,
|
|
52
|
-
filter: (filepath) => !/^(app
|
|
51
|
+
filter: (filepath) => !/^(app(\/|$)|dist(\/|$)|node_modules(\/|$)|server\.ts$|\.shopify(\/|$))/i.test(
|
|
53
52
|
relativePath(templateDir, filepath)
|
|
54
53
|
)
|
|
55
54
|
}
|
|
@@ -217,65 +216,53 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
217
216
|
]
|
|
218
217
|
});
|
|
219
218
|
}
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
overwriteFileDeps: false
|
|
255
|
-
}).then(async (routes) => {
|
|
256
|
-
setupSummary.routes = routes;
|
|
257
|
-
if (language === "ts" && setupSummary.depsInstalled) {
|
|
258
|
-
try {
|
|
259
|
-
await execAsync("npx react-router typegen", {
|
|
260
|
-
cwd: project.directory
|
|
261
|
-
});
|
|
262
|
-
} catch (error) {
|
|
263
|
-
outputWarn(
|
|
264
|
-
"Failed to generate React Router types after route generation. You may need to run `npx react-router typegen` manually."
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (options.git && routes) {
|
|
269
|
-
return commitAll(
|
|
270
|
-
project.directory,
|
|
271
|
-
`Generate routes for core functionality`
|
|
219
|
+
const { i18nStrategy, setupI18n } = await handleI18n(
|
|
220
|
+
controller,
|
|
221
|
+
setupSummary.cliCommand,
|
|
222
|
+
options.i18n
|
|
223
|
+
);
|
|
224
|
+
const { setupRoutes } = handleRouteGeneration(controller);
|
|
225
|
+
setupSummary.i18n = i18nStrategy;
|
|
226
|
+
backgroundWorkPromise = backgroundWorkPromise.then(async () => {
|
|
227
|
+
await setupI18n({
|
|
228
|
+
rootDirectory: project.directory,
|
|
229
|
+
contextCreate: language === "ts" ? "app/lib/context.ts" : "app/lib/context.js"
|
|
230
|
+
}).then(
|
|
231
|
+
() => options.git ? commitAll(
|
|
232
|
+
project.directory,
|
|
233
|
+
`Setup markets support using ${i18nStrategy}`
|
|
234
|
+
) : void 0
|
|
235
|
+
).catch((error) => {
|
|
236
|
+
setupSummary.i18nError = error;
|
|
237
|
+
});
|
|
238
|
+
await setupRoutes(project.directory, language, {
|
|
239
|
+
i18nStrategy,
|
|
240
|
+
// The init process might have added and modified files. Do not overwrite them.
|
|
241
|
+
// E.g. CSS imports might have been added to the root.
|
|
242
|
+
overwriteFileDeps: false
|
|
243
|
+
}).then(async (routes) => {
|
|
244
|
+
setupSummary.routes = routes;
|
|
245
|
+
if (language === "ts" && setupSummary.depsInstalled) {
|
|
246
|
+
try {
|
|
247
|
+
await execAsync("npx react-router typegen", {
|
|
248
|
+
cwd: project.directory
|
|
249
|
+
});
|
|
250
|
+
} catch (error) {
|
|
251
|
+
outputWarn(
|
|
252
|
+
"Failed to generate React Router types after route generation. You may need to run `npx react-router typegen` manually."
|
|
272
253
|
);
|
|
273
254
|
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
255
|
+
}
|
|
256
|
+
if (options.git && routes) {
|
|
257
|
+
return commitAll(
|
|
258
|
+
project.directory,
|
|
259
|
+
`Generate routes for core functionality`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}).catch((error) => {
|
|
263
|
+
setupSummary.routesError = error;
|
|
277
264
|
});
|
|
278
|
-
}
|
|
265
|
+
});
|
|
279
266
|
await renderTasks(tasks);
|
|
280
267
|
if (options.git) {
|
|
281
268
|
await commitAll(project.directory, "Lockfile");
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { readFile, writeFile, mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
|
|
6
|
+
const DEPENDENCY_SECTIONS = [
|
|
7
|
+
"dependencies",
|
|
8
|
+
"devDependencies",
|
|
9
|
+
"peerDependencies",
|
|
10
|
+
"optionalDependencies"
|
|
11
|
+
];
|
|
12
|
+
function hasReplaceableProtocol(version) {
|
|
13
|
+
return version.startsWith("workspace:") || version.startsWith("catalog:");
|
|
14
|
+
}
|
|
15
|
+
async function getPackedTemplatePackageJson(sourceTemplateDir) {
|
|
16
|
+
const tempDir = await mkdtemp(join(tmpdir(), "hydrogen-template-pack-"));
|
|
17
|
+
try {
|
|
18
|
+
const rawPackResult = execFileSync(
|
|
19
|
+
"pnpm",
|
|
20
|
+
["pack", "--pack-destination", tempDir, "--json"],
|
|
21
|
+
{
|
|
22
|
+
cwd: sourceTemplateDir,
|
|
23
|
+
encoding: "utf8"
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
const parsedResult = JSON.parse(rawPackResult.trim());
|
|
27
|
+
const packedTarball = Array.isArray(parsedResult) ? parsedResult[0]?.filename : parsedResult.filename;
|
|
28
|
+
if (!packedTarball) {
|
|
29
|
+
throw new Error("pnpm pack did not return a tarball filename.");
|
|
30
|
+
}
|
|
31
|
+
const packedManifestRaw = execFileSync(
|
|
32
|
+
"tar",
|
|
33
|
+
[
|
|
34
|
+
"-xOf",
|
|
35
|
+
packedTarball.startsWith("/") ? packedTarball : join(tempDir, packedTarball),
|
|
36
|
+
"package/package.json"
|
|
37
|
+
],
|
|
38
|
+
{ encoding: "utf8" }
|
|
39
|
+
);
|
|
40
|
+
return JSON.parse(packedManifestRaw);
|
|
41
|
+
} finally {
|
|
42
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function replaceWorkspaceProtocolVersionsInPackageJson({
|
|
46
|
+
packedManifest,
|
|
47
|
+
targetPackageJson
|
|
48
|
+
}) {
|
|
49
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
50
|
+
const targetDeps = targetPackageJson[section];
|
|
51
|
+
if (!targetDeps) continue;
|
|
52
|
+
for (const [name, version] of Object.entries(targetDeps)) {
|
|
53
|
+
if (!hasReplaceableProtocol(version)) continue;
|
|
54
|
+
const packedVersion = packedManifest[section]?.[name];
|
|
55
|
+
if (!packedVersion) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Unable to resolve ${name} from ${section} in packed template manifest.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
targetDeps[name] = packedVersion;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function replaceWorkspaceProtocolVersions({
|
|
65
|
+
sourceTemplateDir,
|
|
66
|
+
targetTemplateDir
|
|
67
|
+
}) {
|
|
68
|
+
const packedManifest = await getPackedTemplatePackageJson(sourceTemplateDir);
|
|
69
|
+
const targetPackageJsonPath = join(targetTemplateDir, "package.json");
|
|
70
|
+
const targetPackageJson = JSON.parse(
|
|
71
|
+
await readFile(targetPackageJsonPath, "utf8")
|
|
72
|
+
);
|
|
73
|
+
replaceWorkspaceProtocolVersionsInPackageJson({
|
|
74
|
+
packedManifest,
|
|
75
|
+
targetPackageJson
|
|
76
|
+
});
|
|
77
|
+
await writeFile(
|
|
78
|
+
targetPackageJsonPath,
|
|
79
|
+
`${JSON.stringify(targetPackageJson, null, 2)}
|
|
80
|
+
`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { replaceWorkspaceProtocolVersions };
|
package/oclif.manifest.json
CHANGED
|
@@ -1048,13 +1048,6 @@
|
|
|
1048
1048
|
"allowNo": true,
|
|
1049
1049
|
"type": "boolean"
|
|
1050
1050
|
},
|
|
1051
|
-
"routes": {
|
|
1052
|
-
"description": "Generate routes for all pages.",
|
|
1053
|
-
"env": "SHOPIFY_HYDROGEN_FLAG_ROUTES",
|
|
1054
|
-
"name": "routes",
|
|
1055
|
-
"allowNo": true,
|
|
1056
|
-
"type": "boolean"
|
|
1057
|
-
},
|
|
1058
1051
|
"git": {
|
|
1059
1052
|
"description": "Init Git and create initial commits.",
|
|
1060
1053
|
"env": "SHOPIFY_HYDROGEN_FLAG_GIT",
|
|
@@ -1063,7 +1056,7 @@
|
|
|
1063
1056
|
"type": "boolean"
|
|
1064
1057
|
},
|
|
1065
1058
|
"quickstart": {
|
|
1066
|
-
"description": "Scaffolds a new Hydrogen project with a set of sensible defaults. Equivalent to `shopify hydrogen init --path hydrogen-quickstart --mock-shop --language js --shortcut --
|
|
1059
|
+
"description": "Scaffolds a new Hydrogen project with a set of sensible defaults. Equivalent to `shopify hydrogen init --path hydrogen-quickstart --mock-shop --language js --shortcut --markets none`",
|
|
1067
1060
|
"env": "SHOPIFY_HYDROGEN_FLAG_QUICKSTART",
|
|
1068
1061
|
"name": "quickstart",
|
|
1069
1062
|
"allowNo": false,
|
|
@@ -1683,5 +1676,5 @@
|
|
|
1683
1676
|
]
|
|
1684
1677
|
}
|
|
1685
1678
|
},
|
|
1686
|
-
"version": "11.1.
|
|
1679
|
+
"version": "11.1.10"
|
|
1687
1680
|
}
|
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
|
+
"version": "11.1.10",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"repository": {
|
|
@@ -12,18 +12,12 @@
|
|
|
12
12
|
"url": "git+https://github.com/Shopify/hydrogen.git",
|
|
13
13
|
"directory": "packages/cli-hydrogen"
|
|
14
14
|
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsup && node scripts/build-check.mjs",
|
|
17
|
-
"dev": "tsup --watch ./src --watch ../../templates/skeleton",
|
|
18
|
-
"typecheck": "tsc --noEmit",
|
|
19
|
-
"generate:manifest": "node scripts/generate-manifest.mjs",
|
|
20
|
-
"test": "cross-env SHOPIFY_UNIT_TEST=1 LANG=en-US.UTF-8 vitest run --test-timeout=60000",
|
|
21
|
-
"test:watch": "cross-env SHOPIFY_UNIT_TEST=1 LANG=en-US.UTF-8 vitest --test-timeout=60000"
|
|
22
|
-
},
|
|
23
15
|
"devDependencies": {
|
|
24
16
|
"@react-router/dev": "7.12.0",
|
|
25
17
|
"@types/diff": "^5.0.2",
|
|
26
18
|
"@types/gunzip-maybe": "^1.4.0",
|
|
19
|
+
"@types/node": "^22",
|
|
20
|
+
"esbuild": "^0.25.0",
|
|
27
21
|
"@types/prettier": "^2.7.2",
|
|
28
22
|
"@types/source-map-support": "^0.5.10",
|
|
29
23
|
"@types/tar-fs": "^2.0.1",
|
|
@@ -46,10 +40,11 @@
|
|
|
46
40
|
"chokidar": "3.5.3",
|
|
47
41
|
"cli-truncate": "^4.0.0",
|
|
48
42
|
"diff": "^5.1.0",
|
|
43
|
+
"esbuild": "^0.25.12",
|
|
49
44
|
"get-east-asian-width": "^1.3.0",
|
|
50
45
|
"get-port": "^7.0.0",
|
|
51
46
|
"gunzip-maybe": "^1.4.2",
|
|
52
|
-
"prettier": "^
|
|
47
|
+
"prettier": "^3.4.2",
|
|
53
48
|
"semver": "^7.7.1",
|
|
54
49
|
"source-map": "^0.7.4",
|
|
55
50
|
"source-map-support": "^0.5.21",
|
|
@@ -61,10 +56,10 @@
|
|
|
61
56
|
"peerDependencies": {
|
|
62
57
|
"@graphql-codegen/cli": "^5.0.2",
|
|
63
58
|
"@react-router/dev": "7.12.0",
|
|
64
|
-
"@shopify/hydrogen-codegen": "^0.3.3",
|
|
65
|
-
"@shopify/mini-oxygen": "^4.0.0",
|
|
66
59
|
"graphql-config": "^5.0.3",
|
|
67
|
-
"vite": "^5.1.0 || ^6.2.0"
|
|
60
|
+
"vite": "^5.1.0 || ^6.2.0",
|
|
61
|
+
"@shopify/hydrogen-codegen": "0.3.3",
|
|
62
|
+
"@shopify/mini-oxygen": "4.0.1"
|
|
68
63
|
},
|
|
69
64
|
"peerDependenciesMeta": {
|
|
70
65
|
"@graphql-codegen/cli": {
|
|
@@ -103,7 +98,7 @@
|
|
|
103
98
|
"oclif.manifest.json"
|
|
104
99
|
],
|
|
105
100
|
"engines": {
|
|
106
|
-
"node": "
|
|
101
|
+
"node": "^20 || ^22 || ^24"
|
|
107
102
|
},
|
|
108
103
|
"oclif": {
|
|
109
104
|
"commands": "dist/commands",
|
|
@@ -130,5 +125,13 @@
|
|
|
130
125
|
"plugins": [
|
|
131
126
|
"@shopify/plugin-cloudflare"
|
|
132
127
|
]
|
|
128
|
+
},
|
|
129
|
+
"scripts": {
|
|
130
|
+
"build": "tsup && node scripts/build-check.mjs",
|
|
131
|
+
"dev": "tsup --watch ./src --watch ../../templates/skeleton",
|
|
132
|
+
"typecheck": "tsc --noEmit",
|
|
133
|
+
"generate:manifest": "node scripts/generate-manifest.mjs",
|
|
134
|
+
"test": "cross-env SHOPIFY_UNIT_TEST=1 LANG=en-US.UTF-8 vitest run --test-timeout=60000",
|
|
135
|
+
"test:watch": "cross-env SHOPIFY_UNIT_TEST=1 LANG=en-US.UTF-8 vitest --test-timeout=60000"
|
|
133
136
|
}
|
|
134
|
-
}
|
|
137
|
+
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { readFile } from '@shopify/cli-kit/node/fs';
|
|
2
|
-
import { joinPath } from '@shopify/cli-kit/node/path';
|
|
3
|
-
import { renderWarning } from '@shopify/cli-kit/node/ui';
|
|
4
|
-
import { outputDebug } from '@shopify/cli-kit/node/output';
|
|
5
|
-
import semver from 'semver';
|
|
6
|
-
|
|
7
|
-
const REACT_ROUTER_PACKAGES = [
|
|
8
|
-
"react-router",
|
|
9
|
-
"react-router-dom",
|
|
10
|
-
"@react-router/dev",
|
|
11
|
-
"@react-router/fs-routes"
|
|
12
|
-
];
|
|
13
|
-
const EXPECTED_VERSION = "7.12.0";
|
|
14
|
-
async function checkReactRouterVersions(appPath) {
|
|
15
|
-
const mismatches = [];
|
|
16
|
-
const packageJsonPath = joinPath(appPath, "package.json");
|
|
17
|
-
let packageJson;
|
|
18
|
-
try {
|
|
19
|
-
packageJson = JSON.parse(await readFile(packageJsonPath));
|
|
20
|
-
} catch (error) {
|
|
21
|
-
outputDebug(
|
|
22
|
-
`Unable to read package.json for React Router version check: ${error}`
|
|
23
|
-
);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
const allDeps = {
|
|
27
|
-
...packageJson.dependencies,
|
|
28
|
-
...packageJson.devDependencies,
|
|
29
|
-
...packageJson.peerDependencies
|
|
30
|
-
};
|
|
31
|
-
for (const pkg of REACT_ROUTER_PACKAGES) {
|
|
32
|
-
const installedVersion = allDeps[pkg];
|
|
33
|
-
if (!installedVersion) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
installedVersion.replace(/^[\^~]/, "");
|
|
37
|
-
if (!semver.satisfies(EXPECTED_VERSION, installedVersion)) {
|
|
38
|
-
mismatches.push({
|
|
39
|
-
package: pkg,
|
|
40
|
-
installed: installedVersion,
|
|
41
|
-
expected: EXPECTED_VERSION
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (mismatches.length > 0) {
|
|
46
|
-
const mismatchList = mismatches.map(
|
|
47
|
-
(m) => ` \u2022 ${m.package}: installed ${m.installed}, expected ${m.expected}`
|
|
48
|
-
).join("\n");
|
|
49
|
-
const devPackages = ["@react-router/dev", "@react-router/fs-routes"];
|
|
50
|
-
const regularMismatches = mismatches.filter(
|
|
51
|
-
(m) => !devPackages.includes(m.package)
|
|
52
|
-
);
|
|
53
|
-
const devMismatches = mismatches.filter(
|
|
54
|
-
(m) => devPackages.includes(m.package)
|
|
55
|
-
);
|
|
56
|
-
const commands = [];
|
|
57
|
-
if (regularMismatches.length > 0) {
|
|
58
|
-
const regularList = regularMismatches.map((m) => `${m.package}@${EXPECTED_VERSION}`).join(" ");
|
|
59
|
-
commands.push(`npm install ${regularList}`);
|
|
60
|
-
}
|
|
61
|
-
if (devMismatches.length > 0) {
|
|
62
|
-
const devList = devMismatches.map((m) => `${m.package}@${EXPECTED_VERSION}`).join(" ");
|
|
63
|
-
commands.push(`npm install -D ${devList}`);
|
|
64
|
-
}
|
|
65
|
-
renderWarning({
|
|
66
|
-
headline: "React Router version mismatch detected",
|
|
67
|
-
body: [
|
|
68
|
-
"Hydrogen requires React Router 7.12.0 for proper functionality.",
|
|
69
|
-
"",
|
|
70
|
-
"Version mismatches found:",
|
|
71
|
-
mismatchList,
|
|
72
|
-
"",
|
|
73
|
-
"To fix this issue, run:",
|
|
74
|
-
...commands.map((cmd) => ` ${cmd}`),
|
|
75
|
-
"",
|
|
76
|
-
"This may cause issues with routing, code splitting, and other features."
|
|
77
|
-
].join("\n")
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export { checkReactRouterVersions };
|