@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.
Files changed (65) hide show
  1. package/dist/commands/hydrogen/build.js +4 -1
  2. package/dist/commands/hydrogen/dev.js +26 -17
  3. package/dist/commands/hydrogen/init.js +3 -0
  4. package/dist/commands/hydrogen/init.test.js +2 -0
  5. package/dist/commands/hydrogen/preview.js +4 -3
  6. package/dist/commands/hydrogen/setup.js +3 -0
  7. package/dist/generator-templates/starter/app/components/Footer.tsx +1 -1
  8. package/dist/generator-templates/starter/app/components/Header.tsx +1 -1
  9. package/dist/generator-templates/starter/app/components/Search.tsx +3 -3
  10. package/dist/generator-templates/starter/app/root.tsx +24 -1
  11. package/dist/generator-templates/starter/app/routes/$.tsx +4 -0
  12. package/dist/generator-templates/starter/app/routes/_index.tsx +6 -2
  13. package/dist/generator-templates/starter/app/routes/account.$.tsx +1 -2
  14. package/dist/generator-templates/starter/app/routes/account.addresses.tsx +1 -1
  15. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +2 -7
  16. package/dist/generator-templates/starter/app/routes/account.profile.tsx +7 -2
  17. package/dist/generator-templates/starter/app/routes/account.tsx +4 -3
  18. package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +6 -2
  19. package/dist/generator-templates/starter/app/routes/account_.login.tsx +6 -2
  20. package/dist/generator-templates/starter/app/routes/account_.logout.tsx +2 -6
  21. package/dist/generator-templates/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +1 -2
  22. package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +1 -2
  23. package/dist/generator-templates/starter/app/routes/blogs._index.tsx +1 -2
  24. package/dist/generator-templates/starter/app/routes/cart.tsx +1 -2
  25. package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +1 -2
  26. package/dist/generator-templates/starter/app/routes/pages.$handle.tsx +1 -2
  27. package/dist/generator-templates/starter/app/routes/policies.$handle.tsx +2 -3
  28. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +23 -15
  29. package/dist/generator-templates/starter/app/routes/search.tsx +1 -2
  30. package/dist/generator-templates/starter/package.json +2 -2
  31. package/dist/generator-templates/starter/remix.config.js +1 -0
  32. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +9 -9
  33. package/dist/lib/ast.js +9 -0
  34. package/dist/lib/check-version.test.js +1 -0
  35. package/dist/lib/codegen.js +17 -7
  36. package/dist/lib/environment-variables.js +15 -11
  37. package/dist/lib/find-port.js +9 -0
  38. package/dist/lib/flags.js +3 -2
  39. package/dist/lib/format-code.js +3 -0
  40. package/dist/lib/live-reload.js +62 -0
  41. package/dist/lib/log.js +6 -1
  42. package/dist/lib/mini-oxygen.js +28 -18
  43. package/dist/lib/missing-routes.js +17 -1
  44. package/dist/lib/onboarding/common.js +5 -0
  45. package/dist/lib/onboarding/local.js +21 -8
  46. package/dist/lib/remix-config.js +2 -0
  47. package/dist/lib/remix-version-check.test.js +1 -0
  48. package/dist/lib/setups/css/replacers.js +7 -4
  49. package/dist/lib/setups/i18n/replacers.js +7 -5
  50. package/dist/lib/setups/routes/generate.js +4 -1
  51. package/dist/lib/template-downloader.js +4 -0
  52. package/dist/lib/transpile-ts.js +1 -0
  53. package/dist/lib/virtual-routes.js +4 -1
  54. package/dist/virtual-routes/components/HydrogenLogoBaseBW.jsx +26 -4
  55. package/dist/virtual-routes/components/HydrogenLogoBaseColor.jsx +40 -10
  56. package/dist/virtual-routes/components/IconBanner.jsx +286 -44
  57. package/dist/virtual-routes/components/IconDiscord.jsx +17 -1
  58. package/dist/virtual-routes/components/IconError.jsx +55 -17
  59. package/dist/virtual-routes/components/IconGithub.jsx +19 -1
  60. package/dist/virtual-routes/components/IconTwitter.jsx +17 -1
  61. package/dist/virtual-routes/components/Layout.jsx +1 -1
  62. package/dist/virtual-routes/routes/index.jsx +110 -94
  63. package/dist/virtual-routes/virtual-root.jsx +7 -15
  64. package/oclif.manifest.json +1 -1
  65. package/package.json +7 -6
@@ -1,7 +1,7 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import Command from '@shopify/cli-kit/node/base-command';
3
3
  import { outputInfo, outputContent, outputToken, outputWarn } from '@shopify/cli-kit/node/output';
4
- import { rmdir, fileSize, glob, removeFile, copyFile } from '@shopify/cli-kit/node/fs';
4
+ import { rmdir, fileSize, glob, removeFile, fileExists, copyFile } from '@shopify/cli-kit/node/fs';
5
5
  import { resolvePath, relativePath, joinPath } from '@shopify/cli-kit/node/path';
6
6
  import { getPackageManager } from '@shopify/cli-kit/node/node-package-manager';
7
7
  import colors from '@shopify/cli-kit/node/colors';
@@ -130,6 +130,9 @@ This build is missing ${missingRoutes.length} route${missingRoutes.length > 1 ?
130
130
  }
131
131
  }
132
132
  async function copyPublicFiles(publicPath, buildPathClient) {
133
+ if (!await fileExists(publicPath)) {
134
+ return;
135
+ }
133
136
  return copyFile(publicPath, buildPathClient);
134
137
  }
135
138
 
@@ -6,8 +6,8 @@ import { renderFatalError } from '@shopify/cli-kit/node/ui';
6
6
  import colors from '@shopify/cli-kit/node/colors';
7
7
  import { copyPublicFiles } from './build.js';
8
8
  import { getProjectPaths, assertOxygenChecks, getRemixConfig } from '../../lib/remix-config.js';
9
- import { muteDevLogs, muteRemixLogs, createRemixLogger, enhanceH2Logs } from '../../lib/log.js';
10
- import { commonFlags, deprecated, flagsToCamelObject } from '../../lib/flags.js';
9
+ import { muteDevLogs, createRemixLogger, enhanceH2Logs } from '../../lib/log.js';
10
+ import { commonFlags, deprecated, flagsToCamelObject, DEFAULT_PORT } from '../../lib/flags.js';
11
11
  import Command from '@shopify/cli-kit/node/base-command';
12
12
  import { Flags } from '@oclif/core';
13
13
  import { startMiniOxygen } from '../../lib/mini-oxygen.js';
@@ -16,6 +16,8 @@ import { addVirtualRoutes } from '../../lib/virtual-routes.js';
16
16
  import { spawnCodegenProcess } from '../../lib/codegen.js';
17
17
  import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
18
18
  import { getConfig } from '../../lib/shopify-config.js';
19
+ import { findPort } from '../../lib/find-port.js';
20
+ import { setupLiveReload } from '../../lib/live-reload.js';
19
21
  import { checkRemixVersions } from '../../lib/remix-version-check.js';
20
22
 
21
23
  const LOG_REBUILDING = "\u{1F9F1} Rebuilding...";
@@ -56,7 +58,7 @@ class Dev extends Command {
56
58
  }
57
59
  }
58
60
  async function runDev({
59
- port,
61
+ port: portFlag = DEFAULT_PORT,
60
62
  path: appPath,
61
63
  useCodegen = false,
62
64
  codegenConfigPath,
@@ -68,7 +70,6 @@ async function runDev({
68
70
  if (!process.env.NODE_ENV)
69
71
  process.env.NODE_ENV = "development";
70
72
  muteDevLogs();
71
- await muteRemixLogs();
72
73
  if (debug)
73
74
  (await import('node:inspector')).open();
74
75
  const { root, publicPath, buildPathClient, buildPathWorkerFile } = getProjectPaths(appPath);
@@ -102,14 +103,15 @@ async function runDev({
102
103
  let isInitialBuild = true;
103
104
  let initialBuildDurationMs = 0;
104
105
  let initialBuildStartTimeMs = Date.now();
106
+ const liveReload = remixConfig.future.v2_dev ? await setupLiveReload(remixConfig.devServerPort) : void 0;
105
107
  let miniOxygen;
106
108
  async function safeStartMiniOxygen() {
107
109
  if (miniOxygen)
108
110
  return;
109
111
  miniOxygen = await startMiniOxygen({
110
112
  root,
111
- port,
112
- watch: true,
113
+ port: await findPort(portFlag),
114
+ watch: !liveReload,
113
115
  buildPathWorkerFile,
114
116
  buildPathClient,
115
117
  env: await envPromise
@@ -145,13 +147,15 @@ View GraphiQL API browser: ${graphiqlUrl}`)]
145
147
  },
146
148
  {
147
149
  reloadConfig,
148
- onBuildStart() {
150
+ onBuildStart(ctx) {
149
151
  if (!isInitialBuild && !skipRebuildLogs) {
150
152
  outputInfo(LOG_REBUILDING);
151
153
  console.time(LOG_REBUILT);
152
154
  }
155
+ liveReload?.onBuildStart(ctx);
153
156
  },
154
- async onBuildFinish() {
157
+ onBuildManifest: liveReload?.onBuildManifest,
158
+ async onBuildFinish(context, duration, succeeded) {
155
159
  if (isInitialBuild) {
156
160
  await copyingFiles;
157
161
  initialBuildDurationMs = Date.now() - initialBuildStartTimeMs;
@@ -162,16 +166,21 @@ View GraphiQL API browser: ${graphiqlUrl}`)]
162
166
  if (!miniOxygen)
163
167
  console.log("");
164
168
  }
165
- if (!miniOxygen) {
166
- if (!await serverBundleExists()) {
167
- return renderFatalError({
168
- name: "BuildError",
169
- type: 0,
170
- message: "MiniOxygen cannot start because the server bundle has not been generated.",
171
- tryMessage: "This is likely due to an error in your app and Remix is unable to compile. Try fixing the app and MiniOxygen will start."
172
- });
169
+ if (!miniOxygen && !await serverBundleExists()) {
170
+ return renderFatalError({
171
+ name: "BuildError",
172
+ type: 0,
173
+ message: "MiniOxygen cannot start because the server bundle has not been generated.",
174
+ tryMessage: "This is likely due to an error in your app and Remix is unable to compile. Try fixing the app and MiniOxygen will start."
175
+ });
176
+ }
177
+ if (succeeded) {
178
+ if (!miniOxygen) {
179
+ await safeStartMiniOxygen();
180
+ } else if (liveReload) {
181
+ await miniOxygen.reload({ worker: true });
173
182
  }
174
- await safeStartMiniOxygen();
183
+ liveReload?.onAppReady(context);
175
184
  }
176
185
  },
177
186
  async onFileCreated(file) {
@@ -70,6 +70,9 @@ async function runInit(options = parseProcessFlags(process.argv, FLAG_MAP)) {
70
70
  supressNodeExperimentalWarnings();
71
71
  options.git ??= true;
72
72
  const showUpgrade = await checkHydrogenVersion(
73
+ // Resolving the CLI package from a local directory might fail because
74
+ // this code could be run from a global dependency (e.g. on `npm create`).
75
+ // Therefore, pass the known path to the package.json directly from here:
73
76
  fileURLToPath(new URL("../../../package.json", import.meta.url)),
74
77
  "cli"
75
78
  );
@@ -145,6 +145,7 @@ describe("init", () => {
145
145
  expect(output).toMatch("Help");
146
146
  expect(output).toMatch("Next steps");
147
147
  expect(output).toMatch(
148
+ // Output contains banner characters. USe [^\w]*? to match them.
148
149
  /Run `cd .*? &&[^\w]*?npm[^\w]*?install[^\w]*?&&[^\w]*?npm[^\w]*?run[^\w]*?dev`/ims
149
150
  );
150
151
  });
@@ -220,6 +221,7 @@ describe("init", () => {
220
221
  expect(output).toMatch("Help");
221
222
  expect(output).toMatch("Next steps");
222
223
  expect(output).toMatch(
224
+ // Output contains banner characters. USe [^\w]*? to match them.
223
225
  /Run `cd .*? &&[^\w]*?npm[^\w]*?install[^\w]*?&&[^\w]*?npm[^\w]*?run[^\w]*?dev`/ims
224
226
  );
225
227
  });
@@ -1,8 +1,9 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
2
  import { muteDevLogs } from '../../lib/log.js';
3
3
  import { getProjectPaths } from '../../lib/remix-config.js';
4
- import { commonFlags } from '../../lib/flags.js';
4
+ import { commonFlags, DEFAULT_PORT } from '../../lib/flags.js';
5
5
  import { startMiniOxygen } from '../../lib/mini-oxygen.js';
6
+ import { findPort } from '../../lib/find-port.js';
6
7
 
7
8
  class Preview extends Command {
8
9
  static description = "Runs a Hydrogen storefront in an Oxygen worker for production.";
@@ -16,7 +17,7 @@ class Preview extends Command {
16
17
  }
17
18
  }
18
19
  async function runPreview({
19
- port,
20
+ port = DEFAULT_PORT,
20
21
  path: appPath
21
22
  }) {
22
23
  if (!process.env.NODE_ENV)
@@ -25,7 +26,7 @@ async function runPreview({
25
26
  const { root, buildPathWorkerFile, buildPathClient } = getProjectPaths(appPath);
26
27
  const miniOxygen = await startMiniOxygen({
27
28
  root,
28
- port,
29
+ port: await findPort(port),
29
30
  buildPathClient,
30
31
  buildPathWorkerFile
31
32
  });
@@ -57,6 +57,8 @@ async function runSetup(options) {
57
57
  const typescript = !!remixConfig.tsconfigPath;
58
58
  backgroundWorkPromise = backgroundWorkPromise.then(
59
59
  () => Promise.all([
60
+ // When starting from hello-world, the server entry point won't
61
+ // include all the cart logic from skeleton, so we need to copy it.
60
62
  generateProjectFile("../server.ts", { ...remixConfig, typescript }),
61
63
  ...typescript ? [
62
64
  copyFile(
@@ -71,6 +73,7 @@ async function runSetup(options) {
71
73
  )
72
74
  )
73
75
  ] : [],
76
+ // Copy app entries
74
77
  generateProjectEntries({
75
78
  rootDirectory: remixConfig.rootDirectory,
76
79
  appDirectory: remixConfig.appDirectory,
@@ -93,7 +93,7 @@ function activeLinkStyle({
93
93
  isPending: boolean;
94
94
  }) {
95
95
  return {
96
- fontWeight: isActive ? 'bold' : '',
96
+ fontWeight: isActive ? 'bold' : undefined,
97
97
  color: isPending ? 'grey' : 'white',
98
98
  };
99
99
  }
@@ -172,7 +172,7 @@ function activeLinkStyle({
172
172
  isPending: boolean;
173
173
  }) {
174
174
  return {
175
- fontWeight: isActive ? 'bold' : '',
175
+ fontWeight: isActive ? 'bold' : undefined,
176
176
  color: isPending ? 'grey' : 'black',
177
177
  };
178
178
  }
@@ -1,13 +1,13 @@
1
1
  import {
2
- useParams,
3
- useFetcher,
4
2
  Link,
5
3
  Form,
4
+ useParams,
5
+ useFetcher,
6
+ useFetchers,
6
7
  type FormProps,
7
8
  } from '@remix-run/react';
8
9
  import {Image, Money, Pagination} from '@shopify/hydrogen';
9
10
  import React, {useRef, useEffect} from 'react';
10
- import {useFetchers} from '@remix-run/react';
11
11
 
12
12
  import type {
13
13
  PredictiveProductFragment,
@@ -5,19 +5,40 @@ import {
5
5
  Outlet,
6
6
  Scripts,
7
7
  useCatch,
8
+ LiveReload,
8
9
  useMatches,
9
10
  useRouteError,
10
11
  useLoaderData,
11
12
  ScrollRestoration,
12
13
  isRouteErrorResponse,
14
+ type ShouldRevalidateFunction,
13
15
  } from '@remix-run/react';
14
- import type {CustomerAccessToken} from '@shopify/hydrogen-react/storefront-api-types';
16
+ import type {CustomerAccessToken} from '@shopify/hydrogen/storefront-api-types';
15
17
  import type {HydrogenSession} from '../server';
16
18
  import favicon from '../public/favicon.svg';
17
19
  import resetStyles from './styles/reset.css';
18
20
  import appStyles from './styles/app.css';
19
21
  import {Layout} from '~/components/Layout';
20
22
 
23
+ // This is important to avoid re-fetching root queries on sub-navigations
24
+ export const shouldRevalidate: ShouldRevalidateFunction = ({
25
+ formMethod,
26
+ currentUrl,
27
+ nextUrl,
28
+ }) => {
29
+ // revalidate when a mutation is performed e.g add to cart, login...
30
+ if (formMethod && formMethod !== 'GET') {
31
+ return true;
32
+ }
33
+
34
+ // revalidate when manually revalidating via useRevalidator
35
+ if (currentUrl.toString() === nextUrl.toString()) {
36
+ return true;
37
+ }
38
+
39
+ return false;
40
+ };
41
+
21
42
  export function links() {
22
43
  return [
23
44
  {rel: 'stylesheet', href: resetStyles},
@@ -93,6 +114,7 @@ export default function App() {
93
114
  </Layout>
94
115
  <ScrollRestoration />
95
116
  <Scripts />
117
+ <LiveReload />
96
118
  </body>
97
119
  </html>
98
120
  );
@@ -133,6 +155,7 @@ export function ErrorBoundary() {
133
155
  </Layout>
134
156
  <ScrollRestoration />
135
157
  <Scripts />
158
+ <LiveReload />
136
159
  </body>
137
160
  </html>
138
161
  );
@@ -5,3 +5,7 @@ export async function loader({request}: LoaderArgs) {
5
5
  status: 404,
6
6
  });
7
7
  }
8
+
9
+ export default function CatchAllPage() {
10
+ return null;
11
+ }
@@ -1,6 +1,10 @@
1
- import type {V2_MetaFunction} from '@shopify/remix-oxygen';
2
1
  import {defer, type LoaderArgs} from '@shopify/remix-oxygen';
3
- import {Await, useLoaderData, Link} from '@remix-run/react';
2
+ import {
3
+ Await,
4
+ useLoaderData,
5
+ Link,
6
+ type V2_MetaFunction,
7
+ } from '@remix-run/react';
4
8
  import {Suspense} from 'react';
5
9
  import {Image, Money} from '@shopify/hydrogen';
6
10
  import type {
@@ -1,5 +1,4 @@
1
- import type {LoaderArgs} from '@shopify/remix-oxygen';
2
- import {redirect} from '@shopify/remix-oxygen';
1
+ import {redirect, type LoaderArgs} from '@shopify/remix-oxygen';
3
2
 
4
3
  export async function loader({context}: LoaderArgs) {
5
4
  if (await context.session.get('customerAccessToken')) {
@@ -5,13 +5,13 @@ import {
5
5
  redirect,
6
6
  type ActionArgs,
7
7
  type LoaderArgs,
8
- type V2_MetaFunction,
9
8
  } from '@shopify/remix-oxygen';
10
9
  import {
11
10
  Form,
12
11
  useActionData,
13
12
  useNavigation,
14
13
  useOutletContext,
14
+ type V2_MetaFunction,
15
15
  } from '@remix-run/react';
16
16
 
17
17
  export type ActionResponse = {
@@ -1,11 +1,6 @@
1
- import {Link, useLoaderData} from '@remix-run/react';
1
+ import {Link, useLoaderData, type V2_MetaFunction} from '@remix-run/react';
2
2
  import {Money, Pagination, getPaginationVariables} from '@shopify/hydrogen';
3
- import {
4
- json,
5
- redirect,
6
- type LoaderArgs,
7
- type V2_MetaFunction,
8
- } from '@shopify/remix-oxygen';
3
+ import {json, redirect, type LoaderArgs} from '@shopify/remix-oxygen';
9
4
  import type {
10
5
  CustomerOrdersFragment,
11
6
  OrderItemFragment,
@@ -1,12 +1,17 @@
1
1
  import type {CustomerFragment} from 'storefrontapi.generated';
2
2
  import type {CustomerUpdateInput} from '@shopify/hydrogen/storefront-api-types';
3
- import type {ActionArgs, LoaderArgs} from '@shopify/remix-oxygen';
4
- import {json, redirect, type V2_MetaFunction} from '@shopify/remix-oxygen';
3
+ import {
4
+ json,
5
+ redirect,
6
+ type ActionArgs,
7
+ type LoaderArgs,
8
+ } from '@shopify/remix-oxygen';
5
9
  import {
6
10
  Form,
7
11
  useActionData,
8
12
  useNavigation,
9
13
  useOutletContext,
14
+ type V2_MetaFunction,
10
15
  } from '@remix-run/react';
11
16
 
12
17
  export type ActionResponse = {
@@ -109,13 +109,13 @@ function AccountLayout({
109
109
  <div className="account">
110
110
  <h1>{heading}</h1>
111
111
  <br />
112
- <AcccountMenu />
112
+ <AccountMenu />
113
113
  {children}
114
114
  </div>
115
115
  );
116
116
  }
117
117
 
118
- function AcccountMenu() {
118
+ function AccountMenu() {
119
119
  function isActiveStyle({
120
120
  isActive,
121
121
  isPending,
@@ -124,10 +124,11 @@ function AcccountMenu() {
124
124
  isPending: boolean;
125
125
  }) {
126
126
  return {
127
- fontWeight: isActive ? 'bold' : '',
127
+ fontWeight: isActive ? 'bold' : undefined,
128
128
  color: isPending ? 'grey' : 'black',
129
129
  };
130
130
  }
131
+
131
132
  return (
132
133
  <nav role="navigation">
133
134
  <NavLink to="/account/orders" style={isActiveStyle}>
@@ -1,5 +1,9 @@
1
- import type {ActionArgs, LoaderArgs} from '@shopify/remix-oxygen';
2
- import {json, redirect} from '@shopify/remix-oxygen';
1
+ import {
2
+ json,
3
+ redirect,
4
+ type ActionArgs,
5
+ type LoaderArgs,
6
+ } from '@shopify/remix-oxygen';
3
7
  import {Form, useActionData, type V2_MetaFunction} from '@remix-run/react';
4
8
 
5
9
  type ActionResponse = {
@@ -3,9 +3,13 @@ import {
3
3
  redirect,
4
4
  type ActionArgs,
5
5
  type LoaderArgs,
6
- type V2_MetaFunction,
7
6
  } from '@shopify/remix-oxygen';
8
- import {Form, Link, useActionData} from '@remix-run/react';
7
+ import {
8
+ Form,
9
+ Link,
10
+ useActionData,
11
+ type V2_MetaFunction,
12
+ } from '@remix-run/react';
9
13
 
10
14
  type ActionResponse = {
11
15
  error: string | null;
@@ -1,9 +1,5 @@
1
- import {
2
- json,
3
- redirect,
4
- type ActionArgs,
5
- type V2_MetaFunction,
6
- } from '@shopify/remix-oxygen';
1
+ import {json, redirect, type ActionArgs} from '@shopify/remix-oxygen';
2
+ import {type V2_MetaFunction} from '@remix-run/react';
7
3
 
8
4
  export const meta: V2_MetaFunction = () => {
9
5
  return [{title: 'Logout'}];
@@ -1,6 +1,5 @@
1
- import type {V2_MetaFunction} from '@shopify/remix-oxygen';
2
1
  import {json, 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 {Image} from '@shopify/hydrogen';
5
4
 
6
5
  export const meta: V2_MetaFunction = ({data}) => {
@@ -1,6 +1,5 @@
1
- import type {V2_MetaFunction} from '@shopify/remix-oxygen';
2
1
  import {json, type LoaderArgs} from '@shopify/remix-oxygen';
3
- import {Link, useLoaderData} from '@remix-run/react';
2
+ import {Link, useLoaderData, type V2_MetaFunction} from '@remix-run/react';
4
3
  import {Image, Pagination, getPaginationVariables} from '@shopify/hydrogen';
5
4
  import type {ArticleItemFragment} from 'storefrontapi.generated';
6
5
 
@@ -1,6 +1,5 @@
1
- import type {V2_MetaFunction} from '@shopify/remix-oxygen';
2
1
  import {json, type LoaderArgs} from '@shopify/remix-oxygen';
3
- import {Link, useLoaderData} from '@remix-run/react';
2
+ import {Link, useLoaderData, type V2_MetaFunction} from '@remix-run/react';
4
3
  import {Pagination, getPaginationVariables} from '@shopify/hydrogen';
5
4
 
6
5
  export const meta: V2_MetaFunction = () => {
@@ -1,8 +1,7 @@
1
- import {Await, useMatches} from '@remix-run/react';
1
+ import {Await, useMatches, type V2_MetaFunction} from '@remix-run/react';
2
2
  import {Suspense} from 'react';
3
3
  import type {CartQueryData} from '@shopify/hydrogen';
4
4
  import {CartForm} from '@shopify/hydrogen';
5
- import type {V2_MetaFunction} from '@shopify/remix-oxygen';
6
5
  import {type ActionArgs, json} from '@shopify/remix-oxygen';
7
6
  import type {CartApiQueryFragment} from 'storefrontapi.generated';
8
7
  import {CartMain} from '~/components/Cart';
@@ -1,6 +1,5 @@
1
- import type {V2_MetaFunction} from '@shopify/remix-oxygen';
2
1
  import {json, redirect, type LoaderArgs} from '@shopify/remix-oxygen';
3
- import {useLoaderData, Link} from '@remix-run/react';
2
+ import {useLoaderData, Link, type V2_MetaFunction} from '@remix-run/react';
4
3
  import {
5
4
  Pagination,
6
5
  getPaginationVariables,
@@ -1,6 +1,5 @@
1
- import type {V2_MetaFunction} from '@shopify/remix-oxygen';
2
1
  import {json, 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
 
5
4
  export const meta: V2_MetaFunction = ({data}) => {
6
5
  return [{title: `Hydrogen | ${data.page.title}`}];
@@ -1,7 +1,6 @@
1
- import type {V2_MetaFunction} from '@shopify/remix-oxygen';
2
1
  import {json, type LoaderArgs} from '@shopify/remix-oxygen';
3
- import {Link, useLoaderData} from '@remix-run/react';
4
- import {type Shop} from '@shopify/hydrogen-react/storefront-api-types';
2
+ import {Link, useLoaderData, type V2_MetaFunction} from '@remix-run/react';
3
+ import {type Shop} from '@shopify/hydrogen/storefront-api-types';
5
4
 
6
5
  type SelectedPolicies = keyof Pick<
7
6
  Shop,
@@ -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 type {FetcherWithComponents} from '@remix-run/react';
5
- import {Await, Link, useLoaderData} from '@remix-run/react';
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 {CartLineInput} from '@shopify/hydrogen/storefront-api-types';
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) => option.name === 'Title' && option.value === 'Default Title',
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.2",
19
- "@shopify/hydrogen": "^2023.7.2",
18
+ "@shopify/cli-hydrogen": "^5.2.0",
19
+ "@shopify/hydrogen": "^2023.7.3",
20
20
  "@shopify/remix-oxygen": "^1.1.3",
21
21
  "graphql": "^16.6.0",
22
22
  "graphql-tag": "^2.12.6",
@@ -17,6 +17,7 @@ module.exports = {
17
17
  serverPlatform: 'neutral',
18
18
  serverMinify: process.env.NODE_ENV === 'production',
19
19
  future: {
20
+ v2_dev: true,
20
21
  v2_meta: true,
21
22
  v2_headers: true,
22
23
  v2_errorBoundary: true,