@shopify/cli-hydrogen 5.1.2 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/hydrogen/build.js +4 -1
- package/dist/commands/hydrogen/dev.js +26 -17
- package/dist/commands/hydrogen/init.js +3 -0
- package/dist/commands/hydrogen/init.test.js +2 -0
- package/dist/commands/hydrogen/preview.js +4 -3
- 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/root.tsx +24 -1
- 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 +1 -2
- 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 +2 -2
- package/dist/generator-templates/starter/remix.config.js +1 -0
- package/dist/generator-templates/starter/storefrontapi.generated.d.ts +9 -9
- 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/find-port.js +9 -0
- package/dist/lib/flags.js +3 -2
- package/dist/lib/format-code.js +3 -0
- 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/replacers.js +7 -4
- package/dist/lib/setups/i18n/replacers.js +7 -5
- package/dist/lib/setups/routes/generate.js +4 -1
- package/dist/lib/template-downloader.js +4 -0
- package/dist/lib/transpile-ts.js +1 -0
- package/dist/lib/virtual-routes.js +4 -1
- package/dist/virtual-routes/components/HydrogenLogoBaseBW.jsx +26 -4
- package/dist/virtual-routes/components/HydrogenLogoBaseColor.jsx +40 -10
- package/dist/virtual-routes/components/IconBanner.jsx +286 -44
- package/dist/virtual-routes/components/IconDiscord.jsx +17 -1
- package/dist/virtual-routes/components/IconError.jsx +55 -17
- package/dist/virtual-routes/components/IconGithub.jsx +19 -1
- package/dist/virtual-routes/components/IconTwitter.jsx +17 -1
- package/dist/virtual-routes/components/Layout.jsx +1 -1
- package/dist/virtual-routes/routes/index.jsx +110 -94
- package/dist/virtual-routes/virtual-root.jsx +7 -15
- package/oclif.manifest.json +1 -1
- package/package.json +7 -6
|
@@ -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/flags.js
CHANGED
|
@@ -6,6 +6,7 @@ import colors from '@shopify/cli-kit/node/colors';
|
|
|
6
6
|
import { SETUP_CSS_STRATEGIES } 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.",
|
|
@@ -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
|
@@ -17,6 +17,9 @@ async function getCodeFormatOptions(filePath = process.cwd()) {
|
|
|
17
17
|
function formatCode(content, config = DEFAULT_PRETTIER_CONFIG, filePath = "") {
|
|
18
18
|
const ext = extname(filePath);
|
|
19
19
|
const formattedContent = prettier.format(content, {
|
|
20
|
+
// Specify the TypeScript parser for ts/tsx files. Otherwise
|
|
21
|
+
// we need to use the babel parser because the default parser
|
|
22
|
+
// Otherwise prettier will print a warning.
|
|
20
23
|
parser: ext === ".tsx" || ext === ".ts" ? "typescript" : "babel",
|
|
21
24
|
...config
|
|
22
25
|
});
|
|
@@ -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(
|
package/dist/lib/mini-oxygen.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
import { outputToken, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
|
|
2
2
|
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
3
|
-
import { fileExists } from '@shopify/cli-kit/node/fs';
|
|
3
|
+
import { readFile, fileExists } from '@shopify/cli-kit/node/fs';
|
|
4
4
|
import colors from '@shopify/cli-kit/node/colors';
|
|
5
5
|
import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
6
|
+
import { startServer } from '@shopify/mini-oxygen';
|
|
7
|
+
import { DEFAULT_PORT } from './flags.js';
|
|
6
8
|
|
|
7
9
|
async function startMiniOxygen({
|
|
8
10
|
root,
|
|
9
|
-
port =
|
|
11
|
+
port = DEFAULT_PORT,
|
|
10
12
|
watch = false,
|
|
13
|
+
autoReload = watch,
|
|
11
14
|
buildPathWorkerFile,
|
|
12
15
|
buildPathClient,
|
|
13
16
|
env
|
|
14
17
|
}) {
|
|
15
|
-
const { default: miniOxygenImport } = await import('@shopify/mini-oxygen');
|
|
16
|
-
const miniOxygenPreview = miniOxygenImport.default ?? miniOxygenImport;
|
|
17
18
|
const dotenvPath = resolvePath(root, ".env");
|
|
18
|
-
const miniOxygen = await
|
|
19
|
-
|
|
19
|
+
const miniOxygen = await startServer({
|
|
20
|
+
script: await readFile(buildPathWorkerFile),
|
|
20
21
|
assetsDir: buildPathClient,
|
|
21
22
|
publicPath: "",
|
|
22
23
|
port,
|
|
23
24
|
watch,
|
|
24
|
-
autoReload
|
|
25
|
+
autoReload,
|
|
25
26
|
modules: true,
|
|
26
27
|
env: {
|
|
27
28
|
...env,
|
|
@@ -30,23 +31,31 @@ async function startMiniOxygen({
|
|
|
30
31
|
envPath: !env && await fileExists(dotenvPath) ? dotenvPath : void 0,
|
|
31
32
|
log: () => {
|
|
32
33
|
},
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
onResponse: (request, response) => (
|
|
35
|
+
// 'Request' and 'Response' types in MiniOxygen comes from
|
|
36
|
+
// Miniflare and are slightly different from standard types.
|
|
37
|
+
logResponse(
|
|
38
|
+
request,
|
|
39
|
+
response
|
|
40
|
+
)
|
|
37
41
|
)
|
|
38
42
|
});
|
|
39
43
|
const listeningAt = `http://localhost:${miniOxygen.port}`;
|
|
40
44
|
return {
|
|
41
45
|
listeningAt,
|
|
42
46
|
port: miniOxygen.port,
|
|
43
|
-
reload(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
async reload(options = {}) {
|
|
48
|
+
const nextOptions = {};
|
|
49
|
+
if (options.env) {
|
|
50
|
+
nextOptions.env = {
|
|
51
|
+
...options.env,
|
|
47
52
|
...process.env
|
|
48
|
-
}
|
|
49
|
-
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (options.worker) {
|
|
56
|
+
nextOptions.script = await readFile(buildPathWorkerFile);
|
|
57
|
+
}
|
|
58
|
+
return miniOxygen.reload(nextOptions);
|
|
50
59
|
},
|
|
51
60
|
showBanner(options) {
|
|
52
61
|
console.log("");
|
|
@@ -64,8 +73,9 @@ async function startMiniOxygen({
|
|
|
64
73
|
function logResponse(request, response) {
|
|
65
74
|
try {
|
|
66
75
|
const url = new URL(request.url);
|
|
67
|
-
if (["/graphiql"].includes(url.pathname))
|
|
76
|
+
if (["/graphiql"].includes(url.pathname)) {
|
|
68
77
|
return;
|
|
78
|
+
}
|
|
69
79
|
const isProxy = !!response.url && response.url !== request.url;
|
|
70
80
|
const isDataRequest = !isProxy && url.searchParams.has("_data");
|
|
71
81
|
let route = request.url.replace(url.origin, "");
|
|
@@ -3,20 +3,34 @@ import { renderWarning, renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
|
3
3
|
const REQUIRED_ROUTES = [
|
|
4
4
|
"",
|
|
5
5
|
"cart",
|
|
6
|
+
// 'products',
|
|
6
7
|
"products/:productHandle",
|
|
7
8
|
"collections",
|
|
9
|
+
// 'collections/all',
|
|
8
10
|
"collections/:collectionHandle",
|
|
11
|
+
// 'collections/:collectionHandle/:constraint',
|
|
12
|
+
// 'collections/:collectionHandle/products/:productHandle',
|
|
9
13
|
"sitemap.xml",
|
|
10
14
|
"robots.txt",
|
|
11
15
|
"pages/:pageHandle",
|
|
16
|
+
// 'blogs/:blogHandle/tagged/:tagHandle',
|
|
17
|
+
// 'blogs/:blogHandle/:articleHandle',
|
|
18
|
+
// 'blogs/:blogHandle/:articleHandle/comments',
|
|
12
19
|
"policies/:policyHandle",
|
|
20
|
+
// 'variants/:variantId',
|
|
13
21
|
"search",
|
|
22
|
+
// 'gift_cards/:storeId/:cardId',
|
|
23
|
+
// 'discount/:discountCode', => Handled in storefrontRedirect
|
|
14
24
|
"account",
|
|
15
25
|
"account/login",
|
|
16
26
|
"account/register",
|
|
27
|
+
// 'account/addresses',
|
|
28
|
+
// 'account/orders',
|
|
17
29
|
"account/orders/:orderId",
|
|
18
30
|
"account/reset/:id/:token",
|
|
19
31
|
"account/activate/:id/:token"
|
|
32
|
+
// 'password',
|
|
33
|
+
// 'opening_soon',
|
|
20
34
|
];
|
|
21
35
|
function findMissingRoutes(config, requiredRoutes = REQUIRED_ROUTES) {
|
|
22
36
|
const userRoutes = Object.values(config.routes);
|
|
@@ -40,7 +54,9 @@ function findMissingRoutes(config, requiredRoutes = REQUIRED_ROUTES) {
|
|
|
40
54
|
currentRoute.parentId = parentRoute.parentId;
|
|
41
55
|
}
|
|
42
56
|
const optionalSegment = ":?[^\\/\\?]+\\?";
|
|
43
|
-
const reString = `^(${optionalSegment}\\/)?` +
|
|
57
|
+
const reString = `^(${optionalSegment}\\/)?` + // Starts with an optional segment
|
|
58
|
+
requiredRoute.replaceAll(".", "\\.").replace(/\//g, `\\/(${optionalSegment}\\/)?`).replace(/:[^/)?]+/g, ":[^\\/]+") + // Replace params with regex
|
|
59
|
+
`(\\/${optionalSegment})?$`;
|
|
44
60
|
if (new RegExp(reString).test(currentRoute.path)) {
|
|
45
61
|
missingRoutes.delete(requiredRoute);
|
|
46
62
|
}
|
|
@@ -163,7 +163,9 @@ async function handleProjectLocation({
|
|
|
163
163
|
return {
|
|
164
164
|
name: basename(location),
|
|
165
165
|
location,
|
|
166
|
+
// User input. E.g. "./hydrogen-storefront"
|
|
166
167
|
directory,
|
|
168
|
+
// Absolute path to location
|
|
167
169
|
storefrontTitle: storefrontInfo?.title
|
|
168
170
|
};
|
|
169
171
|
}
|
|
@@ -201,6 +203,7 @@ async function handleCssStrategy(projectDir, controller, flagStyling) {
|
|
|
201
203
|
{
|
|
202
204
|
rootDirectory: projectDir,
|
|
203
205
|
appDirectory: joinPath(projectDir, "app")
|
|
206
|
+
// Default value in new projects
|
|
204
207
|
},
|
|
205
208
|
true
|
|
206
209
|
);
|
|
@@ -344,6 +347,8 @@ async function renderProjectReady(project, {
|
|
|
344
347
|
body: bodyLines.map(
|
|
345
348
|
([label, value]) => ` ${(label + ":").padEnd(padMin, " ")} ${colors.dim(value)}`
|
|
346
349
|
).join("\n") + routeSummary,
|
|
350
|
+
// Use `customSections` instead of `nextSteps` and `references`
|
|
351
|
+
// here to enforce a newline between title and items.
|
|
347
352
|
customSections: [
|
|
348
353
|
hasErrors && {
|
|
349
354
|
title: "Warnings\n",
|
|
@@ -18,7 +18,7 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
18
18
|
message: "Connect to Shopify",
|
|
19
19
|
choices: [
|
|
20
20
|
{
|
|
21
|
-
label: "Use sample data from
|
|
21
|
+
label: "Use sample data from mock.shop (You can connect a Shopify account later)",
|
|
22
22
|
value: "mock"
|
|
23
23
|
},
|
|
24
24
|
{ label: "Link your Shopify account", value: "link" }
|
|
@@ -46,17 +46,22 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
46
46
|
let backgroundWorkPromise = copy(
|
|
47
47
|
templateDir,
|
|
48
48
|
project.directory,
|
|
49
|
+
// Filter out the `app` directory, which will be generated later
|
|
49
50
|
{
|
|
50
51
|
filter: (filepath) => !/^(app|dist|node_modules)\//i.test(
|
|
51
52
|
relativePath(templateDir, filepath)
|
|
52
53
|
)
|
|
53
54
|
}
|
|
54
55
|
).then(
|
|
55
|
-
() =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
() => (
|
|
57
|
+
// Generate project entries and their file dependencies
|
|
58
|
+
generateProjectEntries({
|
|
59
|
+
rootDirectory: project.directory,
|
|
60
|
+
appDirectory: joinPath(project.directory, "app"),
|
|
61
|
+
typescript: true
|
|
62
|
+
// Will be transpiled later
|
|
63
|
+
})
|
|
64
|
+
)
|
|
60
65
|
).catch(abort);
|
|
61
66
|
const tasks = [
|
|
62
67
|
{
|
|
@@ -74,6 +79,7 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
74
79
|
];
|
|
75
80
|
backgroundWorkPromise = backgroundWorkPromise.then(() => {
|
|
76
81
|
const promises = [
|
|
82
|
+
// Add project name to package.json
|
|
77
83
|
replaceFileContent(
|
|
78
84
|
joinPath(project.directory, "package.json"),
|
|
79
85
|
false,
|
|
@@ -83,17 +89,23 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
83
89
|
)
|
|
84
90
|
)
|
|
85
91
|
];
|
|
86
|
-
const envLeadingComment = "# The variables added in this file are only available locally in MiniOxygen
|
|
92
|
+
const envLeadingComment = "# The variables added in this file are only available locally in MiniOxygen.\n# Run `h2 link` to also inject environment variables from your storefront,\n# or `h2 env pull` to populate this file.";
|
|
87
93
|
if (storefrontInfo && createStorefrontPromise) {
|
|
88
94
|
promises.push(
|
|
95
|
+
// Save linked storefront in project
|
|
89
96
|
setUserAccount(project.directory, storefrontInfo),
|
|
90
97
|
createStorefrontPromise.then(
|
|
91
|
-
(storefront) =>
|
|
98
|
+
(storefront) => (
|
|
99
|
+
// Save linked storefront in project
|
|
100
|
+
setStorefront(project.directory, storefront)
|
|
101
|
+
)
|
|
92
102
|
),
|
|
103
|
+
// Write empty dotenv file to fallback to remote Oxygen variables
|
|
93
104
|
writeFile(joinPath(project.directory, ".env"), envLeadingComment)
|
|
94
105
|
);
|
|
95
106
|
} else if (templateAction === "mock") {
|
|
96
107
|
promises.push(
|
|
108
|
+
// Set required env vars
|
|
97
109
|
writeFile(
|
|
98
110
|
joinPath(project.directory, ".env"),
|
|
99
111
|
envLeadingComment + "\n" + [
|
|
@@ -188,6 +200,7 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
188
200
|
const { setupRoutes } = await handleRouteGeneration(
|
|
189
201
|
controller,
|
|
190
202
|
options.routes || true
|
|
203
|
+
// TODO: Remove default value when multi-select UI component is available
|
|
191
204
|
);
|
|
192
205
|
setupSummary.i18n = i18nStrategy;
|
|
193
206
|
backgroundWorkPromise = backgroundWorkPromise.then(async () => {
|
package/dist/lib/remix-config.js
CHANGED
|
@@ -5,6 +5,7 @@ import { readdir } from 'node:fs/promises';
|
|
|
5
5
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
6
6
|
import { outputWarn } from '@shopify/cli-kit/node/output';
|
|
7
7
|
import { fileExists } from '@shopify/cli-kit/node/fs';
|
|
8
|
+
import { muteRemixLogs } from './log.js';
|
|
8
9
|
|
|
9
10
|
const BUILD_DIR = "dist";
|
|
10
11
|
const CLIENT_SUBDIR = "client";
|
|
@@ -25,6 +26,7 @@ function getProjectPaths(appPath, entry) {
|
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
async function getRemixConfig(root, mode = process.env.NODE_ENV) {
|
|
29
|
+
await muteRemixLogs();
|
|
28
30
|
const { readConfig } = await import('@remix-run/dev/dist/config.js');
|
|
29
31
|
const config = await readConfig(root, mode);
|
|
30
32
|
if (process.env.LOCAL_DEV) {
|
|
@@ -26,6 +26,7 @@ describe("remix-version-check", () => {
|
|
|
26
26
|
it("warns when versions are out of sync", () => {
|
|
27
27
|
const expectedVersion = "42.0.0-test";
|
|
28
28
|
vi.mocked(requireMock).mockReturnValueOnce({
|
|
29
|
+
// Hydrogen expected version
|
|
29
30
|
dependencies: { "@remix-run/dev": expectedVersion }
|
|
30
31
|
});
|
|
31
32
|
const outputMock = mockAndCaptureOutput();
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
2
|
-
import { ts, tsx, js, jsx } from '@ast-grep/napi';
|
|
3
2
|
import { findFileWithExtension, replaceFileContent } from '../../file.js';
|
|
3
|
+
import { importLangAstGrep } from '../../ast.js';
|
|
4
4
|
|
|
5
|
-
const astGrep = { ts, tsx, js, jsx };
|
|
6
5
|
async function replaceRemixConfig(rootDirectory, formatConfig, newProperties) {
|
|
7
6
|
const { filepath, astType } = await findFileWithExtension(
|
|
8
7
|
rootDirectory,
|
|
@@ -14,7 +13,8 @@ async function replaceRemixConfig(rootDirectory, formatConfig, newProperties) {
|
|
|
14
13
|
);
|
|
15
14
|
}
|
|
16
15
|
await replaceFileContent(filepath, formatConfig, async (content) => {
|
|
17
|
-
const
|
|
16
|
+
const astGrep = await importLangAstGrep(astType);
|
|
17
|
+
const root = astGrep.parse(content).root();
|
|
18
18
|
const remixConfigNode = root.find({
|
|
19
19
|
rule: {
|
|
20
20
|
kind: "object",
|
|
@@ -22,9 +22,11 @@ async function replaceRemixConfig(rootDirectory, formatConfig, newProperties) {
|
|
|
22
22
|
any: [
|
|
23
23
|
{
|
|
24
24
|
kind: "export_statement"
|
|
25
|
+
// ESM
|
|
25
26
|
},
|
|
26
27
|
{
|
|
27
28
|
kind: "assignment_expression",
|
|
29
|
+
// CJS
|
|
28
30
|
has: {
|
|
29
31
|
kind: "member_expression",
|
|
30
32
|
field: "left",
|
|
@@ -77,7 +79,8 @@ async function replaceRootLinks(appDirectory, formatConfig, importer) {
|
|
|
77
79
|
if (content.includes(importStatement.split("from")[0])) {
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
80
|
-
const
|
|
82
|
+
const astGrep = await importLangAstGrep(astType);
|
|
83
|
+
const root = astGrep.parse(content).root();
|
|
81
84
|
const lastImportNode = root.findAll({ rule: { kind: "import_statement" } }).pop();
|
|
82
85
|
const linksReturnNode = root.find({
|
|
83
86
|
utils: {
|