@shopify/cli-hydrogen 5.1.2 → 5.2.1
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/commands/hydrogen/build.js +4 -1
- package/dist/commands/hydrogen/dev.js +25 -17
- package/dist/commands/hydrogen/generate/route.test.js +0 -1
- package/dist/commands/hydrogen/init.js +6 -3
- package/dist/commands/hydrogen/init.test.js +2 -0
- package/dist/commands/hydrogen/preview.js +2 -2
- package/dist/commands/hydrogen/setup.js +3 -0
- package/dist/generator-templates/starter/app/components/Footer.tsx +1 -1
- package/dist/generator-templates/starter/app/components/Header.tsx +1 -1
- package/dist/generator-templates/starter/app/components/Search.tsx +3 -3
- package/dist/generator-templates/starter/app/entry.server.tsx +9 -1
- package/dist/generator-templates/starter/app/root.tsx +31 -5
- package/dist/generator-templates/starter/app/routes/$.tsx +4 -0
- package/dist/generator-templates/starter/app/routes/_index.tsx +6 -2
- package/dist/generator-templates/starter/app/routes/account.$.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/account.addresses.tsx +1 -1
- package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +2 -7
- package/dist/generator-templates/starter/app/routes/account.profile.tsx +7 -2
- package/dist/generator-templates/starter/app/routes/account.tsx +4 -3
- package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +6 -2
- package/dist/generator-templates/starter/app/routes/account_.login.tsx +6 -2
- package/dist/generator-templates/starter/app/routes/account_.logout.tsx +2 -6
- package/dist/generator-templates/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/blogs._index.tsx +2 -3
- package/dist/generator-templates/starter/app/routes/cart.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/pages.$handle.tsx +1 -2
- package/dist/generator-templates/starter/app/routes/policies.$handle.tsx +2 -3
- package/dist/generator-templates/starter/app/routes/products.$handle.tsx +23 -15
- package/dist/generator-templates/starter/app/routes/search.tsx +1 -2
- package/dist/generator-templates/starter/package.json +5 -5
- package/dist/generator-templates/starter/remix.config.js +1 -0
- package/dist/generator-templates/starter/storefrontapi.generated.d.ts +9 -9
- package/dist/generator-templates/starter/tsconfig.json +1 -0
- package/dist/lib/ast.js +9 -0
- package/dist/lib/check-version.test.js +1 -0
- package/dist/lib/codegen.js +17 -7
- package/dist/lib/environment-variables.js +15 -11
- package/dist/lib/file.js +1 -1
- package/dist/lib/find-port.js +9 -0
- package/dist/lib/flags.js +6 -5
- package/dist/lib/format-code.js +7 -4
- package/dist/lib/graphql/admin/client.js +18 -0
- package/dist/lib/graphql/admin/client.test.js +28 -3
- package/dist/lib/live-reload.js +62 -0
- package/dist/lib/log.js +6 -1
- package/dist/lib/mini-oxygen.js +28 -18
- package/dist/lib/missing-routes.js +17 -1
- package/dist/lib/onboarding/common.js +5 -0
- package/dist/lib/onboarding/local.js +21 -8
- package/dist/lib/remix-config.js +2 -0
- package/dist/lib/remix-version-check.test.js +1 -0
- package/dist/lib/setups/css/index.js +4 -2
- package/dist/lib/setups/css/replacers.js +7 -4
- package/dist/lib/setups/i18n/replacers.js +7 -5
- package/dist/lib/setups/routes/generate.js +15 -29
- package/dist/lib/setups/routes/generate.test.js +1 -3
- package/dist/lib/template-downloader.js +4 -0
- package/dist/lib/transpile-ts.js +5 -3
- package/dist/lib/virtual-routes.js +4 -1
- package/dist/virtual-routes/components/HydrogenLogoBaseBW.jsx +29 -4
- package/dist/virtual-routes/components/HydrogenLogoBaseColor.jsx +44 -10
- package/dist/virtual-routes/components/IconBanner.jsx +289 -44
- package/dist/virtual-routes/components/IconDiscord.jsx +18 -1
- package/dist/virtual-routes/components/IconError.jsx +58 -17
- package/dist/virtual-routes/components/IconGithub.jsx +20 -1
- package/dist/virtual-routes/components/IconTwitter.jsx +18 -1
- package/dist/virtual-routes/components/Layout.jsx +2 -1
- package/dist/virtual-routes/routes/index.jsx +199 -94
- package/dist/virtual-routes/virtual-root.jsx +62 -16
- package/oclif.manifest.json +3 -3
- package/package.json +8 -7
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import {Suspense} from 'react';
|
|
2
|
-
import type {V2_MetaFunction} from '@shopify/remix-oxygen';
|
|
3
2
|
import {defer, redirect, type LoaderArgs} from '@shopify/remix-oxygen';
|
|
4
|
-
import
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
Await,
|
|
5
|
+
Link,
|
|
6
|
+
useLoaderData,
|
|
7
|
+
type V2_MetaFunction,
|
|
8
|
+
type FetcherWithComponents,
|
|
9
|
+
} from '@remix-run/react';
|
|
6
10
|
import type {
|
|
7
11
|
ProductFragment,
|
|
8
12
|
ProductVariantsQuery,
|
|
@@ -17,7 +21,10 @@ import {
|
|
|
17
21
|
getSelectedProductOptions,
|
|
18
22
|
CartForm,
|
|
19
23
|
} from '@shopify/hydrogen';
|
|
20
|
-
import type {
|
|
24
|
+
import type {
|
|
25
|
+
CartLineInput,
|
|
26
|
+
SelectedOption,
|
|
27
|
+
} from '@shopify/hydrogen/storefront-api-types';
|
|
21
28
|
import {getVariantUrl} from '~/utils';
|
|
22
29
|
|
|
23
30
|
export const meta: V2_MetaFunction = ({data}) => {
|
|
@@ -47,15 +54,6 @@ export async function loader({params, request, context}: LoaderArgs) {
|
|
|
47
54
|
variables: {handle, selectedOptions},
|
|
48
55
|
});
|
|
49
56
|
|
|
50
|
-
// In order to show which variants are available in the UI, we need to query
|
|
51
|
-
// all of them. But there might be a *lot*, so instead separate the variants
|
|
52
|
-
// into it's own separate query that is deferred. So there's a brief moment
|
|
53
|
-
// where variant options might show as available when they're not, but after
|
|
54
|
-
// this deffered query resolves, the UI will update.
|
|
55
|
-
const variants = storefront.query(VARIANTS_QUERY, {
|
|
56
|
-
variables: {handle},
|
|
57
|
-
});
|
|
58
|
-
|
|
59
57
|
if (!product?.id) {
|
|
60
58
|
throw new Response(null, {status: 404});
|
|
61
59
|
}
|
|
@@ -63,7 +61,8 @@ export async function loader({params, request, context}: LoaderArgs) {
|
|
|
63
61
|
const firstVariant = product.variants.nodes[0];
|
|
64
62
|
const firstVariantIsDefault = Boolean(
|
|
65
63
|
firstVariant.selectedOptions.find(
|
|
66
|
-
(option) =>
|
|
64
|
+
(option: SelectedOption) =>
|
|
65
|
+
option.name === 'Title' && option.value === 'Default Title',
|
|
67
66
|
),
|
|
68
67
|
);
|
|
69
68
|
|
|
@@ -76,6 +75,16 @@ export async function loader({params, request, context}: LoaderArgs) {
|
|
|
76
75
|
return redirectToFirstVariant({product, request});
|
|
77
76
|
}
|
|
78
77
|
}
|
|
78
|
+
|
|
79
|
+
// In order to show which variants are available in the UI, we need to query
|
|
80
|
+
// all of them. But there might be a *lot*, so instead separate the variants
|
|
81
|
+
// into it's own separate query that is deferred. So there's a brief moment
|
|
82
|
+
// where variant options might show as available when they're not, but after
|
|
83
|
+
// this deffered query resolves, the UI will update.
|
|
84
|
+
const variants = storefront.query(VARIANTS_QUERY, {
|
|
85
|
+
variables: {handle},
|
|
86
|
+
});
|
|
87
|
+
|
|
79
88
|
return defer({product, variants});
|
|
80
89
|
}
|
|
81
90
|
|
|
@@ -337,7 +346,6 @@ const PRODUCT_VARIANT_FRAGMENT = `#graphql
|
|
|
337
346
|
title
|
|
338
347
|
handle
|
|
339
348
|
}
|
|
340
|
-
quantityAvailable
|
|
341
349
|
selectedOptions {
|
|
342
350
|
name
|
|
343
351
|
value
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type {V2_MetaFunction} from '@shopify/remix-oxygen';
|
|
2
1
|
import {defer, type LoaderArgs} from '@shopify/remix-oxygen';
|
|
3
|
-
import {useLoaderData} from '@remix-run/react';
|
|
2
|
+
import {useLoaderData, type V2_MetaFunction} from '@remix-run/react';
|
|
4
3
|
import {getPaginationVariables} from '@shopify/hydrogen';
|
|
5
4
|
|
|
6
5
|
import {SearchForm, SearchResults, NoSearchResults} from '~/components/Search';
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@remix-run/react": "1.19.1",
|
|
17
17
|
"@shopify/cli": "3.48.0",
|
|
18
|
-
"@shopify/cli-hydrogen": "^5.1
|
|
19
|
-
"@shopify/hydrogen": "^2023.7.
|
|
18
|
+
"@shopify/cli-hydrogen": "^5.2.1",
|
|
19
|
+
"@shopify/hydrogen": "^2023.7.4",
|
|
20
20
|
"@shopify/remix-oxygen": "^1.1.3",
|
|
21
21
|
"graphql": "^16.6.0",
|
|
22
22
|
"graphql-tag": "^2.12.6",
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"@shopify/prettier-config": "^1.1.2",
|
|
31
31
|
"@total-typescript/ts-reset": "^0.4.2",
|
|
32
32
|
"@types/eslint": "^8.4.10",
|
|
33
|
-
"@types/react": "^18.
|
|
34
|
-
"@types/react-dom": "^18.
|
|
33
|
+
"@types/react": "^18.2.20",
|
|
34
|
+
"@types/react-dom": "^18.2.7",
|
|
35
35
|
"eslint": "^8.20.0",
|
|
36
36
|
"eslint-plugin-hydrogen": "0.12.2",
|
|
37
37
|
"prettier": "^2.8.4",
|
|
38
|
-
"typescript": "^
|
|
38
|
+
"typescript": "^5.2.2"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=16.13"
|
|
@@ -1350,7 +1350,7 @@ export type PoliciesQuery = {
|
|
|
1350
1350
|
|
|
1351
1351
|
export type ProductVariantFragment = Pick<
|
|
1352
1352
|
StorefrontAPI.ProductVariant,
|
|
1353
|
-
'availableForSale' | 'id' | '
|
|
1353
|
+
'availableForSale' | 'id' | 'sku' | 'title'
|
|
1354
1354
|
> & {
|
|
1355
1355
|
compareAtPrice?: StorefrontAPI.Maybe<
|
|
1356
1356
|
Pick<StorefrontAPI.MoneyV2, 'amount' | 'currencyCode'>
|
|
@@ -1377,7 +1377,7 @@ export type ProductFragment = Pick<
|
|
|
1377
1377
|
selectedVariant?: StorefrontAPI.Maybe<
|
|
1378
1378
|
Pick<
|
|
1379
1379
|
StorefrontAPI.ProductVariant,
|
|
1380
|
-
'availableForSale' | 'id' | '
|
|
1380
|
+
'availableForSale' | 'id' | 'sku' | 'title'
|
|
1381
1381
|
> & {
|
|
1382
1382
|
compareAtPrice?: StorefrontAPI.Maybe<
|
|
1383
1383
|
Pick<StorefrontAPI.MoneyV2, 'amount' | 'currencyCode'>
|
|
@@ -1402,7 +1402,7 @@ export type ProductFragment = Pick<
|
|
|
1402
1402
|
nodes: Array<
|
|
1403
1403
|
Pick<
|
|
1404
1404
|
StorefrontAPI.ProductVariant,
|
|
1405
|
-
'availableForSale' | 'id' | '
|
|
1405
|
+
'availableForSale' | 'id' | 'sku' | 'title'
|
|
1406
1406
|
> & {
|
|
1407
1407
|
compareAtPrice?: StorefrontAPI.Maybe<
|
|
1408
1408
|
Pick<StorefrontAPI.MoneyV2, 'amount' | 'currencyCode'>
|
|
@@ -1446,7 +1446,7 @@ export type ProductQuery = {
|
|
|
1446
1446
|
selectedVariant?: StorefrontAPI.Maybe<
|
|
1447
1447
|
Pick<
|
|
1448
1448
|
StorefrontAPI.ProductVariant,
|
|
1449
|
-
'availableForSale' | 'id' | '
|
|
1449
|
+
'availableForSale' | 'id' | 'sku' | 'title'
|
|
1450
1450
|
> & {
|
|
1451
1451
|
compareAtPrice?: StorefrontAPI.Maybe<
|
|
1452
1452
|
Pick<StorefrontAPI.MoneyV2, 'amount' | 'currencyCode'>
|
|
@@ -1471,7 +1471,7 @@ export type ProductQuery = {
|
|
|
1471
1471
|
nodes: Array<
|
|
1472
1472
|
Pick<
|
|
1473
1473
|
StorefrontAPI.ProductVariant,
|
|
1474
|
-
'availableForSale' | 'id' | '
|
|
1474
|
+
'availableForSale' | 'id' | 'sku' | 'title'
|
|
1475
1475
|
> & {
|
|
1476
1476
|
compareAtPrice?: StorefrontAPI.Maybe<
|
|
1477
1477
|
Pick<StorefrontAPI.MoneyV2, 'amount' | 'currencyCode'>
|
|
@@ -1503,7 +1503,7 @@ export type ProductVariantsFragment = {
|
|
|
1503
1503
|
nodes: Array<
|
|
1504
1504
|
Pick<
|
|
1505
1505
|
StorefrontAPI.ProductVariant,
|
|
1506
|
-
'availableForSale' | 'id' | '
|
|
1506
|
+
'availableForSale' | 'id' | 'sku' | 'title'
|
|
1507
1507
|
> & {
|
|
1508
1508
|
compareAtPrice?: StorefrontAPI.Maybe<
|
|
1509
1509
|
Pick<StorefrontAPI.MoneyV2, 'amount' | 'currencyCode'>
|
|
@@ -1539,7 +1539,7 @@ export type ProductVariantsQuery = {
|
|
|
1539
1539
|
nodes: Array<
|
|
1540
1540
|
Pick<
|
|
1541
1541
|
StorefrontAPI.ProductVariant,
|
|
1542
|
-
'availableForSale' | 'id' | '
|
|
1542
|
+
'availableForSale' | 'id' | 'sku' | 'title'
|
|
1543
1543
|
> & {
|
|
1544
1544
|
compareAtPrice?: StorefrontAPI.Maybe<
|
|
1545
1545
|
Pick<StorefrontAPI.MoneyV2, 'amount' | 'currencyCode'>
|
|
@@ -1839,11 +1839,11 @@ interface GeneratedQueryTypes {
|
|
|
1839
1839
|
return: PoliciesQuery;
|
|
1840
1840
|
variables: PoliciesQueryVariables;
|
|
1841
1841
|
};
|
|
1842
|
-
'#graphql\n query Product(\n $country: CountryCode\n $handle: String!\n $language: LanguageCode\n $selectedOptions: [SelectedOptionInput!]!\n ) @inContext(country: $country, language: $language) {\n product(handle: $handle) {\n ...Product\n }\n }\n #graphql\n fragment Product on Product {\n id\n title\n vendor\n handle\n descriptionHtml\n description\n options {\n name\n values\n }\n selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions) {\n ...ProductVariant\n }\n variants(first: 1) {\n nodes {\n ...ProductVariant\n }\n }\n seo {\n description\n title\n }\n }\n #graphql\n fragment ProductVariant on ProductVariant {\n availableForSale\n compareAtPrice {\n amount\n currencyCode\n }\n id\n image {\n __typename\n id\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n product {\n title\n handle\n }\n
|
|
1842
|
+
'#graphql\n query Product(\n $country: CountryCode\n $handle: String!\n $language: LanguageCode\n $selectedOptions: [SelectedOptionInput!]!\n ) @inContext(country: $country, language: $language) {\n product(handle: $handle) {\n ...Product\n }\n }\n #graphql\n fragment Product on Product {\n id\n title\n vendor\n handle\n descriptionHtml\n description\n options {\n name\n values\n }\n selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions) {\n ...ProductVariant\n }\n variants(first: 1) {\n nodes {\n ...ProductVariant\n }\n }\n seo {\n description\n title\n }\n }\n #graphql\n fragment ProductVariant on ProductVariant {\n availableForSale\n compareAtPrice {\n amount\n currencyCode\n }\n id\n image {\n __typename\n id\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n product {\n title\n handle\n }\n selectedOptions {\n name\n value\n }\n sku\n title\n unitPrice {\n amount\n currencyCode\n }\n }\n\n\n': {
|
|
1843
1843
|
return: ProductQuery;
|
|
1844
1844
|
variables: ProductQueryVariables;
|
|
1845
1845
|
};
|
|
1846
|
-
'#graphql\n #graphql\n fragment ProductVariants on Product {\n variants(first: 250) {\n nodes {\n ...ProductVariant\n }\n }\n }\n #graphql\n fragment ProductVariant on ProductVariant {\n availableForSale\n compareAtPrice {\n amount\n currencyCode\n }\n id\n image {\n __typename\n id\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n product {\n title\n handle\n }\n
|
|
1846
|
+
'#graphql\n #graphql\n fragment ProductVariants on Product {\n variants(first: 250) {\n nodes {\n ...ProductVariant\n }\n }\n }\n #graphql\n fragment ProductVariant on ProductVariant {\n availableForSale\n compareAtPrice {\n amount\n currencyCode\n }\n id\n image {\n __typename\n id\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n product {\n title\n handle\n }\n selectedOptions {\n name\n value\n }\n sku\n title\n unitPrice {\n amount\n currencyCode\n }\n }\n\n\n query ProductVariants(\n $country: CountryCode\n $language: LanguageCode\n $handle: String!\n ) @inContext(country: $country, language: $language) {\n product(handle: $handle) {\n ...ProductVariants\n }\n }\n': {
|
|
1847
1847
|
return: ProductVariantsQuery;
|
|
1848
1848
|
variables: ProductVariantsQueryVariables;
|
|
1849
1849
|
};
|
package/dist/lib/ast.js
ADDED
package/dist/lib/codegen.js
CHANGED
|
@@ -2,7 +2,7 @@ import { loadCodegenConfig, generate } from '@graphql-codegen/cli';
|
|
|
2
2
|
import { patchGqlPluck, pluckConfig, preset, schema } from '@shopify/hydrogen-codegen';
|
|
3
3
|
import { getCodeFormatOptions, formatCode } from './format-code.js';
|
|
4
4
|
import { renderWarning, renderFatalError } from '@shopify/cli-kit/node/ui';
|
|
5
|
-
import { joinPath } from '@shopify/cli-kit/node/path';
|
|
5
|
+
import { relativePath, joinPath } from '@shopify/cli-kit/node/path';
|
|
6
6
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
@@ -82,16 +82,22 @@ async function generateTypes({
|
|
|
82
82
|
forceSfapiVersion,
|
|
83
83
|
...dirs
|
|
84
84
|
}) {
|
|
85
|
-
const { config: codegenConfig } =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
const { config: codegenConfig } = (
|
|
86
|
+
// Load <root>/codegen.ts if available
|
|
87
|
+
await loadCodegenConfig({
|
|
88
|
+
configFilePath,
|
|
89
|
+
searchPlaces: [dirs.rootDirectory]
|
|
90
|
+
}) || // Fall back to default config
|
|
91
|
+
generateDefaultConfig(dirs, forceSfapiVersion)
|
|
92
|
+
);
|
|
89
93
|
await addHooksToHydrogenOptions(codegenConfig, dirs);
|
|
90
94
|
await generate(
|
|
91
95
|
{
|
|
92
96
|
...codegenConfig,
|
|
93
97
|
cwd: dirs.rootDirectory,
|
|
94
98
|
watch,
|
|
99
|
+
// Note: do not use `silent` without `watch`, it will swallow errors and
|
|
100
|
+
// won't hide all logs. `errorsOnly` flag doesn't work either.
|
|
95
101
|
silent: !watch
|
|
96
102
|
},
|
|
97
103
|
true
|
|
@@ -100,6 +106,7 @@ async function generateTypes({
|
|
|
100
106
|
}
|
|
101
107
|
function generateDefaultConfig({ rootDirectory, appDirectory }, forceSfapiVersion) {
|
|
102
108
|
const tsDefaultGlob = "*!(*.d).{ts,tsx}";
|
|
109
|
+
const appDirRelative = relativePath(rootDirectory, appDirectory);
|
|
103
110
|
return {
|
|
104
111
|
filepath: "virtual:codegen",
|
|
105
112
|
config: {
|
|
@@ -110,8 +117,10 @@ function generateDefaultConfig({ rootDirectory, appDirectory }, forceSfapiVersio
|
|
|
110
117
|
preset,
|
|
111
118
|
schema,
|
|
112
119
|
documents: [
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
tsDefaultGlob,
|
|
121
|
+
// E.g. ./server.ts
|
|
122
|
+
joinPath(appDirRelative, "**", tsDefaultGlob)
|
|
123
|
+
// E.g. app/routes/_index.tsx
|
|
115
124
|
],
|
|
116
125
|
...!!forceSfapiVersion && {
|
|
117
126
|
presetConfig: { importTypes: false },
|
|
@@ -142,6 +151,7 @@ async function addHooksToHydrogenOptions(codegenConfig, { rootDirectory }) {
|
|
|
142
151
|
const formatConfig = await getCodeFormatOptions(rootDirectory);
|
|
143
152
|
hydrogenOptions.hooks = {
|
|
144
153
|
beforeOneFileWrite: (file, content) => formatCode(content, formatConfig, file),
|
|
154
|
+
// Run Prettier before writing files
|
|
145
155
|
...hydrogenOptions.hooks
|
|
146
156
|
};
|
|
147
157
|
}
|
|
@@ -8,6 +8,10 @@ import colors from '@shopify/cli-kit/node/colors';
|
|
|
8
8
|
import { getStorefrontEnvVariables } from './graphql/admin/pull-variables.js';
|
|
9
9
|
import { login } from './auth.js';
|
|
10
10
|
|
|
11
|
+
const createEmptyRemoteVars = () => ({
|
|
12
|
+
remoteVariables: {},
|
|
13
|
+
remoteSecrets: {}
|
|
14
|
+
});
|
|
11
15
|
async function getAllEnvironmentVariables({
|
|
12
16
|
root,
|
|
13
17
|
envBranch,
|
|
@@ -15,7 +19,15 @@ async function getAllEnvironmentVariables({
|
|
|
15
19
|
}) {
|
|
16
20
|
const dotEnvPath = resolvePath(root, ".env");
|
|
17
21
|
const [{ remoteVariables, remoteSecrets }, { variables: localVariables }] = await Promise.all([
|
|
18
|
-
|
|
22
|
+
// Get remote vars
|
|
23
|
+
fetchRemote ? getRemoteVariables(root, envBranch).catch((error) => {
|
|
24
|
+
renderWarning({
|
|
25
|
+
headline: "Failed to load environment variables from Shopify. The development server will still start, but the following error occurred:",
|
|
26
|
+
body: [error.message, error.tryMessage, error.nextSteps].filter(Boolean).join("\n\n")
|
|
27
|
+
});
|
|
28
|
+
return createEmptyRemoteVars();
|
|
29
|
+
}) : createEmptyRemoteVars(),
|
|
30
|
+
// Get local vars
|
|
19
31
|
fileExists(dotEnvPath).then(
|
|
20
32
|
(exists) => exists ? readAndParseDotEnv(dotEnvPath) : { variables: {} }
|
|
21
33
|
)
|
|
@@ -29,6 +41,7 @@ async function getAllEnvironmentVariables({
|
|
|
29
41
|
linesToColumns([
|
|
30
42
|
...remotePublicKeys.filter((key) => !localKeys.includes(key)).map((key) => [key, "from Oxygen"]),
|
|
31
43
|
...localKeys.map((key) => [key, "from local .env"]),
|
|
44
|
+
// Ensure secret variables always get added to the bottom of the list
|
|
32
45
|
...remoteSecretKeys.filter((key) => !localKeys.includes(key)).map((key) => [
|
|
33
46
|
colors.dim(key),
|
|
34
47
|
colors.dim("from Oxygen (Marked as secret)")
|
|
@@ -44,16 +57,7 @@ async function getAllEnvironmentVariables({
|
|
|
44
57
|
}
|
|
45
58
|
async function getRemoteVariables(root, envBranch) {
|
|
46
59
|
const { session, config } = await login(root);
|
|
47
|
-
const envVariables = (await getStorefrontEnvVariables(
|
|
48
|
-
session,
|
|
49
|
-
config.storefront.id,
|
|
50
|
-
envBranch
|
|
51
|
-
).catch((error) => {
|
|
52
|
-
renderWarning({
|
|
53
|
-
headline: `Failed to load environment variables. The development server will still start, but the following error occurred:`,
|
|
54
|
-
body: error?.stack ?? error?.message ?? error
|
|
55
|
-
});
|
|
56
|
-
}))?.environmentVariables || [];
|
|
60
|
+
const envVariables = (await getStorefrontEnvVariables(session, config.storefront.id, envBranch))?.environmentVariables || [];
|
|
57
61
|
const remoteVariables = {};
|
|
58
62
|
const remoteSecrets = {};
|
|
59
63
|
for (const { key, value, isSecret } of envVariables) {
|
package/dist/lib/file.js
CHANGED
|
@@ -8,7 +8,7 @@ async function replaceFileContent(filepath, formatConfig, replacer) {
|
|
|
8
8
|
if (typeof content !== "string")
|
|
9
9
|
return;
|
|
10
10
|
if (formatConfig) {
|
|
11
|
-
content = formatCode(content, formatConfig, filepath);
|
|
11
|
+
content = await formatCode(content, formatConfig, filepath);
|
|
12
12
|
}
|
|
13
13
|
return writeFile(filepath, content);
|
|
14
14
|
}
|
package/dist/lib/flags.js
CHANGED
|
@@ -3,9 +3,10 @@ import { camelize } from '@shopify/cli-kit/common/string';
|
|
|
3
3
|
import { renderInfo } from '@shopify/cli-kit/node/ui';
|
|
4
4
|
import { normalizeStoreFqdn } from '@shopify/cli-kit/node/context/fqdn';
|
|
5
5
|
import colors from '@shopify/cli-kit/node/colors';
|
|
6
|
-
import {
|
|
6
|
+
import { STYLING_CHOICES } from './setups/css/index.js';
|
|
7
7
|
import { I18N_CHOICES } from './setups/i18n/index.js';
|
|
8
8
|
|
|
9
|
+
const DEFAULT_PORT = 3e3;
|
|
9
10
|
const commonFlags = {
|
|
10
11
|
path: Flags.string({
|
|
11
12
|
description: "The path to the directory of the Hydrogen storefront. The default is the current directory.",
|
|
@@ -14,7 +15,7 @@ const commonFlags = {
|
|
|
14
15
|
port: Flags.integer({
|
|
15
16
|
description: "Port to run the server on.",
|
|
16
17
|
env: "SHOPIFY_HYDROGEN_FLAG_PORT",
|
|
17
|
-
default:
|
|
18
|
+
default: DEFAULT_PORT
|
|
18
19
|
}),
|
|
19
20
|
force: Flags.boolean({
|
|
20
21
|
description: "Overwrite the destination directory and files if they already exist.",
|
|
@@ -49,10 +50,10 @@ const commonFlags = {
|
|
|
49
50
|
dependsOn: ["codegen-unstable"]
|
|
50
51
|
}),
|
|
51
52
|
styling: Flags.string({
|
|
52
|
-
description: `Sets the styling strategy to use. One of ${
|
|
53
|
+
description: `Sets the styling strategy to use. One of ${STYLING_CHOICES.map(
|
|
53
54
|
(item) => `\`${item}\``
|
|
54
55
|
).join(", ")}.`,
|
|
55
|
-
choices:
|
|
56
|
+
choices: STYLING_CHOICES,
|
|
56
57
|
env: "SHOPIFY_HYDROGEN_FLAG_STYLING"
|
|
57
58
|
}),
|
|
58
59
|
markets: Flags.string({
|
|
@@ -112,4 +113,4 @@ function overrideFlag(flag, extra) {
|
|
|
112
113
|
};
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
export { commonFlags, deprecated, flagsToCamelObject, overrideFlag, parseProcessFlags };
|
|
116
|
+
export { DEFAULT_PORT, commonFlags, deprecated, flagsToCamelObject, overrideFlag, parseProcessFlags };
|
package/dist/lib/format-code.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import prettier from 'prettier';
|
|
2
1
|
import { extname } from '@shopify/cli-kit/node/path';
|
|
3
2
|
|
|
4
3
|
const DEFAULT_PRETTIER_CONFIG = {
|
|
@@ -9,18 +8,22 @@ const DEFAULT_PRETTIER_CONFIG = {
|
|
|
9
8
|
};
|
|
10
9
|
async function getCodeFormatOptions(filePath = process.cwd()) {
|
|
11
10
|
try {
|
|
11
|
+
const prettier = await import('prettier');
|
|
12
12
|
return await prettier.resolveConfig(filePath) || DEFAULT_PRETTIER_CONFIG;
|
|
13
13
|
} catch {
|
|
14
14
|
return DEFAULT_PRETTIER_CONFIG;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
function formatCode(content, config = DEFAULT_PRETTIER_CONFIG, filePath = "") {
|
|
17
|
+
async function formatCode(content, config = DEFAULT_PRETTIER_CONFIG, filePath = "") {
|
|
18
18
|
const ext = extname(filePath);
|
|
19
|
-
const
|
|
19
|
+
const prettier = await import('prettier');
|
|
20
|
+
return prettier.format(content, {
|
|
21
|
+
// Specify the TypeScript parser for ts/tsx files. Otherwise
|
|
22
|
+
// we need to use the babel parser because the default parser
|
|
23
|
+
// Otherwise prettier will print a warning.
|
|
20
24
|
parser: ext === ".tsx" || ext === ".ts" ? "typescript" : "babel",
|
|
21
25
|
...config
|
|
22
26
|
});
|
|
23
|
-
return formattedContent;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export { formatCode, getCodeFormatOptions };
|
|
@@ -20,6 +20,24 @@ async function adminRequest(query, session, variables) {
|
|
|
20
20
|
"Install the Hydrogen sales channel on your store to start creating and linking Hydrogen storefronts: https://apps.shopify.com/hydrogen"
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
|
+
if (errors?.some?.(
|
|
24
|
+
(error2) => error2.message.includes(
|
|
25
|
+
"Access denied for hydrogenStorefrontCreate field"
|
|
26
|
+
)
|
|
27
|
+
)) {
|
|
28
|
+
throw new AbortError("Couldn't connect storefront to Shopify", [
|
|
29
|
+
"Common reasons for this error include:",
|
|
30
|
+
{
|
|
31
|
+
list: {
|
|
32
|
+
items: [
|
|
33
|
+
"The Hydrogen sales channel isn't installed on the store.",
|
|
34
|
+
"You don't have the required account permission to manage apps or channels.",
|
|
35
|
+
"You're trying to connect to an ineligible store type (Trial, Development store)"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
23
41
|
throw error;
|
|
24
42
|
}
|
|
25
43
|
}
|
|
@@ -16,8 +16,8 @@ describe("adminRequest", () => {
|
|
|
16
16
|
});
|
|
17
17
|
expect(response).toContain(fakeResponse);
|
|
18
18
|
});
|
|
19
|
-
describe("error response", () => {
|
|
20
|
-
it("
|
|
19
|
+
describe("when there is an unknown error response", () => {
|
|
20
|
+
it("passes along the error message", async () => {
|
|
21
21
|
const fakeGraphqlError = {
|
|
22
22
|
errors: [
|
|
23
23
|
{
|
|
@@ -32,7 +32,9 @@ describe("adminRequest", () => {
|
|
|
32
32
|
});
|
|
33
33
|
await expect(response).rejects.toContain(fakeGraphqlError);
|
|
34
34
|
});
|
|
35
|
-
|
|
35
|
+
});
|
|
36
|
+
describe("when the app isn't installed", () => {
|
|
37
|
+
it("throws an AbortError", async () => {
|
|
36
38
|
const fakeGraphqlError = {
|
|
37
39
|
errors: [
|
|
38
40
|
{
|
|
@@ -46,6 +48,29 @@ describe("adminRequest", () => {
|
|
|
46
48
|
storeFqdn: ""
|
|
47
49
|
});
|
|
48
50
|
await expect(response).rejects.toThrowError(AbortError);
|
|
51
|
+
await expect(response).rejects.toMatch(
|
|
52
|
+
/Hydrogen sales channel isn\'t installed/
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("when the user doesn't have access to hydrogenStorefrontCreate", () => {
|
|
57
|
+
it("throws an AbortError", async () => {
|
|
58
|
+
const fakeGraphqlError = {
|
|
59
|
+
errors: [
|
|
60
|
+
{
|
|
61
|
+
message: "Access denied for hydrogenStorefrontCreate field"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
vi.mocked(graphqlRequest).mockRejectedValue(fakeGraphqlError);
|
|
66
|
+
const response = adminRequest("", {
|
|
67
|
+
token: "",
|
|
68
|
+
storeFqdn: ""
|
|
69
|
+
});
|
|
70
|
+
await expect(response).rejects.toThrowError(AbortError);
|
|
71
|
+
await expect(response).rejects.toMatch(
|
|
72
|
+
/Couldn\'t connect storefront to Shopify/
|
|
73
|
+
);
|
|
49
74
|
});
|
|
50
75
|
});
|
|
51
76
|
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
|
|
3
|
+
async function setupLiveReload(devServerPort) {
|
|
4
|
+
try {
|
|
5
|
+
const [{ updates: hmrUpdates }, { serve }, { detectLoaderChanges }, { ok, err }] = await Promise.all([
|
|
6
|
+
import('@remix-run/dev/dist/devServer_unstable/hmr.js'),
|
|
7
|
+
import('@remix-run/dev/dist/devServer_unstable/socket.js'),
|
|
8
|
+
import('@remix-run/dev/dist/devServer_unstable/hdr.js'),
|
|
9
|
+
import('@remix-run/dev/dist/result.js')
|
|
10
|
+
]);
|
|
11
|
+
const state = {};
|
|
12
|
+
const server = http.createServer(function(req, res) {
|
|
13
|
+
res.writeHead(200);
|
|
14
|
+
res.end();
|
|
15
|
+
}).listen(devServerPort);
|
|
16
|
+
const socket = serve(server);
|
|
17
|
+
return {
|
|
18
|
+
onBuildStart: (ctx) => {
|
|
19
|
+
state.loaderChanges = detectLoaderChanges(ctx).then(ok, err);
|
|
20
|
+
},
|
|
21
|
+
onBuildManifest: (manifest) => {
|
|
22
|
+
state.manifest = manifest;
|
|
23
|
+
},
|
|
24
|
+
onAppReady: async (ctx) => {
|
|
25
|
+
const nextState = { prevManifest: state.manifest };
|
|
26
|
+
try {
|
|
27
|
+
const loaderChanges = await state.loaderChanges;
|
|
28
|
+
if (loaderChanges.ok) {
|
|
29
|
+
nextState.prevLoaderHashes = loaderChanges.value;
|
|
30
|
+
}
|
|
31
|
+
if (loaderChanges.ok && state.manifest && state.prevManifest) {
|
|
32
|
+
socket.hmr(
|
|
33
|
+
state.manifest,
|
|
34
|
+
hmrUpdates(
|
|
35
|
+
ctx.config,
|
|
36
|
+
state.manifest,
|
|
37
|
+
state.prevManifest,
|
|
38
|
+
loaderChanges.value,
|
|
39
|
+
state.prevLoaderHashes
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
} else if (state.prevManifest) {
|
|
43
|
+
socket.reload();
|
|
44
|
+
}
|
|
45
|
+
} finally {
|
|
46
|
+
Object.assign(state, nextState);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
close: () => {
|
|
50
|
+
socket.close();
|
|
51
|
+
server.close();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.warn(
|
|
56
|
+
"Could not start HMR server. Please make sure your Remix packages are in sync with Hydrogen. Defaulting to regular live reload.",
|
|
57
|
+
error.stack
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { setupLiveReload };
|
package/dist/lib/log.js
CHANGED
|
@@ -121,7 +121,10 @@ function enhanceH2Logs(options) {
|
|
|
121
121
|
injectLogReplacer("error");
|
|
122
122
|
injectLogReplacer(
|
|
123
123
|
"warn",
|
|
124
|
-
([first]) =>
|
|
124
|
+
([first]) => (
|
|
125
|
+
// Show createStorefrontClient warnings only once.
|
|
126
|
+
first?.includes?.("[h2:warn:createStorefrontClient]") ? true : void 0
|
|
127
|
+
)
|
|
125
128
|
);
|
|
126
129
|
addMessageReplacers("h2-warn", [
|
|
127
130
|
([first]) => {
|
|
@@ -164,7 +167,9 @@ function enhanceH2Logs(options) {
|
|
|
164
167
|
if (firstAppLineIndex > 0 && lastAppLineIndex > firstAppLineIndex) {
|
|
165
168
|
stack = [
|
|
166
169
|
stackLines[0],
|
|
170
|
+
// Error message
|
|
167
171
|
...stackLines.slice(firstAppLineIndex, lastAppLineIndex)
|
|
172
|
+
// App code
|
|
168
173
|
].join("\n").trim() || void 0;
|
|
169
174
|
}
|
|
170
175
|
const error = new BugError(
|