@shopify/cli 3.85.3 → 3.85.5

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 (153) hide show
  1. package/dist/assets/hydrogen/bundle/analyzer.html +155 -148
  2. package/dist/assets/hydrogen/starter/CHANGELOG.md +125 -49
  3. package/dist/assets/hydrogen/starter/app/components/AddToCartButton.tsx +1 -1
  4. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +1 -1
  5. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +1 -1
  6. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +62 -29
  7. package/dist/assets/hydrogen/starter/app/components/Header.tsx +1 -1
  8. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +1 -1
  9. package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +2 -2
  10. package/dist/assets/hydrogen/starter/app/components/SearchForm.tsx +1 -1
  11. package/dist/assets/hydrogen/starter/app/components/SearchFormPredictive.tsx +8 -3
  12. package/dist/assets/hydrogen/starter/app/components/SearchResults.tsx +3 -11
  13. package/dist/assets/hydrogen/starter/app/components/SearchResultsPredictive.tsx +2 -6
  14. package/dist/assets/hydrogen/starter/app/entry.client.tsx +10 -2
  15. package/dist/assets/hydrogen/starter/app/entry.server.tsx +5 -3
  16. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerAddressMutations.ts +7 -4
  17. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +1 -1
  18. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrderQuery.ts +4 -1
  19. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +10 -5
  20. package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +3 -2
  21. package/dist/assets/hydrogen/starter/app/lib/context.ts +34 -17
  22. package/dist/assets/hydrogen/starter/app/lib/fragments.ts +1 -0
  23. package/dist/assets/hydrogen/starter/app/lib/orderFilters.ts +90 -0
  24. package/dist/assets/hydrogen/starter/app/lib/redirect.ts +1 -1
  25. package/dist/assets/hydrogen/starter/app/lib/session.ts +1 -1
  26. package/dist/assets/hydrogen/starter/app/lib/variants.ts +1 -1
  27. package/dist/assets/hydrogen/starter/app/root.tsx +23 -18
  28. package/dist/assets/hydrogen/starter/app/routes/$.tsx +2 -2
  29. package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +2 -2
  30. package/dist/assets/hydrogen/starter/app/routes/[sitemap.xml].tsx +2 -3
  31. package/dist/assets/hydrogen/starter/app/routes/_index.tsx +12 -8
  32. package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +4 -3
  33. package/dist/assets/hydrogen/starter/app/routes/account._index.tsx +1 -1
  34. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +15 -11
  35. package/dist/assets/hydrogen/starter/app/routes/account.orders.$id.tsx +47 -22
  36. package/dist/assets/hydrogen/starter/app/routes/account.orders._index.tsx +152 -23
  37. package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +11 -8
  38. package/dist/assets/hydrogen/starter/app/routes/account.tsx +16 -4
  39. package/dist/assets/hydrogen/starter/app/routes/account_.authorize.tsx +2 -2
  40. package/dist/assets/hydrogen/starter/app/routes/account_.login.tsx +5 -3
  41. package/dist/assets/hydrogen/starter/app/routes/account_.logout.tsx +3 -2
  42. package/dist/assets/hydrogen/starter/app/routes/api.$version.[graphql.json].tsx +2 -2
  43. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +6 -10
  44. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +10 -7
  45. package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +13 -7
  46. package/dist/assets/hydrogen/starter/app/routes/cart.$lines.tsx +3 -2
  47. package/dist/assets/hydrogen/starter/app/routes/cart.tsx +13 -9
  48. package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +8 -11
  49. package/dist/assets/hydrogen/starter/app/routes/collections._index.tsx +6 -6
  50. package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +10 -7
  51. package/dist/assets/hydrogen/starter/app/routes/discount.$code.tsx +3 -2
  52. package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +8 -6
  53. package/dist/assets/hydrogen/starter/app/routes/policies.$handle.tsx +7 -4
  54. package/dist/assets/hydrogen/starter/app/routes/policies._index.tsx +19 -13
  55. package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +9 -6
  56. package/dist/assets/hydrogen/starter/app/routes/search.tsx +14 -14
  57. package/dist/assets/hydrogen/starter/app/routes/sitemap.$type.$page[.xml].tsx +2 -3
  58. package/dist/assets/hydrogen/starter/app/routes.ts +1 -1
  59. package/dist/assets/hydrogen/starter/app/styles/app.css +53 -1
  60. package/dist/assets/hydrogen/starter/customer-accountapi.generated.d.ts +47 -13
  61. package/dist/assets/hydrogen/starter/env.d.ts +1 -39
  62. package/dist/assets/hydrogen/starter/eslint.config.js +35 -52
  63. package/dist/assets/hydrogen/starter/package.json +14 -15
  64. package/dist/assets/hydrogen/starter/react-router.config.ts +9 -3
  65. package/dist/assets/hydrogen/starter/server.ts +7 -7
  66. package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +1 -1
  67. package/dist/assets/hydrogen/starter/tsconfig.json +17 -13
  68. package/dist/assets/hydrogen/starter/vite.config.ts +3 -0
  69. package/dist/assets/hydrogen/virtual-routes/components/RequestDetails.jsx +13 -20
  70. package/dist/assets/hydrogen/virtual-routes/routes/[.]well-known.appspecific.com[.]chrome[.]devtools[.]json.jsx +37 -0
  71. package/dist/{chokidar-TTCYG5AA.js → chokidar-HTKREZL3.js} +482 -7
  72. package/dist/{chunk-RPU6TIF2.js → chunk-3ULYQTX3.js} +7 -7
  73. package/dist/{chunk-IHXRXBUN.js → chunk-AVSSZ6MY.js} +6 -6
  74. package/dist/{chunk-I4ATBX6D.js → chunk-CHLX44DR.js} +4 -4
  75. package/dist/{chunk-UW4ASA6Y.js → chunk-CQSO2B3C.js} +2 -2
  76. package/dist/{chunk-OQKAZQIC.js → chunk-DHW4GI57.js} +5 -5
  77. package/dist/{chunk-HTTZVPR6.js → chunk-FGSQR4VQ.js} +3 -3
  78. package/dist/{chunk-3XWYM7QS.js → chunk-FMD2ETAR.js} +3 -3
  79. package/dist/{chunk-FGEKMLLA.js → chunk-FRHZJBJZ.js} +5 -5
  80. package/dist/{chunk-Y4H4HMEZ.js → chunk-G2I4CD5D.js} +2 -2
  81. package/dist/{chunk-HS2O5IHL.js → chunk-GG2ELY5O.js} +2 -3
  82. package/dist/{chunk-F7TU455C.js → chunk-GQ63RYWN.js} +2 -2
  83. package/dist/{chunk-VVUZFYUK.js → chunk-IAX3IWUQ.js} +4 -4
  84. package/dist/{chunk-EWEA4LRT.js → chunk-J6T7KVRL.js} +2 -2
  85. package/dist/{chunk-GRVHLYQS.js → chunk-JF6DQIJR.js} +3 -3
  86. package/dist/{chunk-JAUHWNMJ.js → chunk-MDDWB46W.js} +5 -5
  87. package/dist/{chunk-QHKSKL4E.js → chunk-MR763CPY.js} +3 -3
  88. package/dist/{chunk-6A6GDV25.js → chunk-MUZ7NPCX.js} +4 -4
  89. package/dist/{chunk-NLFRHIZY.js → chunk-NMGJYSTC.js} +5 -5
  90. package/dist/{chunk-D5DJSKHK.js → chunk-NTCXWD2Q.js} +111 -112
  91. package/dist/{chunk-HBANZKAF.js → chunk-O2O5GRI6.js} +3 -3
  92. package/dist/{chunk-CAONVM2S.js → chunk-ODJ7LSLO.js} +3 -3
  93. package/dist/{chunk-5RNGA7FX.js → chunk-P46WMXBU.js} +5 -5
  94. package/dist/{chunk-LJXXOFEJ.js → chunk-PFBQBDNU.js} +2 -2
  95. package/dist/{chunk-OURS5IQY.js → chunk-PZUWEJO3.js} +3 -3
  96. package/dist/{chunk-VR6Z7LKU.js → chunk-R6BNSDGA.js} +3 -3
  97. package/dist/{chunk-AMWSD3HH.js → chunk-RAZVOMJW.js} +3 -3
  98. package/dist/{chunk-C45MKMJT.js → chunk-RZPYG7LO.js} +28 -25
  99. package/dist/{chunk-EDXQ22O4.js → chunk-SMBX232U.js} +6 -6
  100. package/dist/{chunk-QP2MOS2Y.js → chunk-TE4CUUT4.js} +2 -2
  101. package/dist/{chunk-V5ONTA7N.js → chunk-TXX6R3WL.js} +2 -2
  102. package/dist/{chunk-L54PNQGV.js → chunk-TYHJPUOR.js} +2 -2
  103. package/dist/{chunk-XJBIASMX.js → chunk-UF2X5VHR.js} +3 -3
  104. package/dist/{chunk-ZHKIKKU7.js → chunk-UQT7B7DM.js} +3 -3
  105. package/dist/{chunk-6RJ7HBOQ.js → chunk-VVDGGMKJ.js} +3 -3
  106. package/dist/cli/commands/auth/login.js +10 -10
  107. package/dist/cli/commands/auth/login.test.js +11 -11
  108. package/dist/cli/commands/auth/logout.js +10 -10
  109. package/dist/cli/commands/auth/logout.test.js +11 -11
  110. package/dist/cli/commands/cache/clear.js +10 -10
  111. package/dist/cli/commands/debug/command-flags.js +10 -10
  112. package/dist/cli/commands/docs/generate.js +10 -10
  113. package/dist/cli/commands/docs/generate.test.js +10 -10
  114. package/dist/cli/commands/help.js +10 -10
  115. package/dist/cli/commands/kitchen-sink/async.js +11 -11
  116. package/dist/cli/commands/kitchen-sink/async.test.js +11 -11
  117. package/dist/cli/commands/kitchen-sink/index.js +13 -13
  118. package/dist/cli/commands/kitchen-sink/index.test.js +13 -13
  119. package/dist/cli/commands/kitchen-sink/prompts.js +11 -11
  120. package/dist/cli/commands/kitchen-sink/prompts.test.js +11 -11
  121. package/dist/cli/commands/kitchen-sink/static.js +11 -11
  122. package/dist/cli/commands/kitchen-sink/static.test.js +11 -11
  123. package/dist/cli/commands/notifications/generate.js +11 -11
  124. package/dist/cli/commands/notifications/list.js +11 -11
  125. package/dist/cli/commands/search.js +11 -11
  126. package/dist/cli/commands/upgrade.js +11 -11
  127. package/dist/cli/commands/version.js +11 -11
  128. package/dist/cli/commands/version.test.js +11 -11
  129. package/dist/cli/services/commands/notifications.js +6 -6
  130. package/dist/cli/services/commands/search.js +2 -2
  131. package/dist/cli/services/commands/search.test.js +2 -2
  132. package/dist/cli/services/commands/version.js +3 -3
  133. package/dist/cli/services/commands/version.test.js +4 -4
  134. package/dist/cli/services/kitchen-sink/async.js +2 -2
  135. package/dist/cli/services/kitchen-sink/prompts.js +2 -2
  136. package/dist/cli/services/kitchen-sink/static.js +2 -2
  137. package/dist/cli/services/upgrade.js +3 -3
  138. package/dist/cli/services/upgrade.test.js +4 -4
  139. package/dist/{custom-oclif-loader-YDKLB47A.js → custom-oclif-loader-X7GLA66E.js} +2 -2
  140. package/dist/{error-handler-BYZU2C5C.js → error-handler-QOTWDLMF.js} +8 -8
  141. package/dist/hooks/postrun.js +6 -6
  142. package/dist/hooks/prerun.js +7 -7
  143. package/dist/index.js +1800 -3480
  144. package/dist/{local-XLJD5WYP.js → local-FS3UI7PE.js} +2 -2
  145. package/dist/{morph-5D7H6MU2.js → morph-D4BXY376.js} +9 -9
  146. package/dist/{node-package-manager-I7AWSWJ4.js → node-package-manager-TX3WZQGI.js} +3 -3
  147. package/dist/tsconfig.tsbuildinfo +1 -1
  148. package/dist/{ui-2AJAFJYY.js → ui-PUN5STUM.js} +2 -2
  149. package/dist/{workerd-4DJKRJUB.js → workerd-GJFM5MYN.js} +12 -12
  150. package/oclif.manifest.json +11 -56
  151. package/package.json +8 -8
  152. package/dist/chokidar-XUA2BN3J.js +0 -1120
  153. package/dist/chunk-F7J5CUMZ.js +0 -497
@@ -1,13 +1,21 @@
1
- import { HydratedRouter } from 'react-router/dom';
1
+ import {HydratedRouter} from 'react-router/dom';
2
2
  import {startTransition, StrictMode} from 'react';
3
3
  import {hydrateRoot} from 'react-dom/client';
4
+ import {NonceProvider} from '@shopify/hydrogen';
4
5
 
5
6
  if (!window.location.origin.includes('webcache.googleusercontent.com')) {
6
7
  startTransition(() => {
8
+ // Extract nonce from existing script tags
9
+ const existingNonce = document
10
+ .querySelector<HTMLScriptElement>('script[nonce]')
11
+ ?.nonce;
12
+
7
13
  hydrateRoot(
8
14
  document,
9
15
  <StrictMode>
10
- <HydratedRouter />
16
+ <NonceProvider value={existingNonce}>
17
+ <HydratedRouter />
18
+ </NonceProvider>
11
19
  </StrictMode>,
12
20
  );
13
21
  });
@@ -1,8 +1,10 @@
1
- import type {AppLoadContext} from '@shopify/remix-oxygen';
2
1
  import {ServerRouter} from 'react-router';
3
2
  import {isbot} from 'isbot';
4
3
  import {renderToReadableStream} from 'react-dom/server';
5
- import {createContentSecurityPolicy} from '@shopify/hydrogen';
4
+ import {
5
+ createContentSecurityPolicy,
6
+ type HydrogenRouterContextProvider,
7
+ } from '@shopify/hydrogen';
6
8
  import type {EntryContext} from 'react-router';
7
9
 
8
10
  export default async function handleRequest(
@@ -10,7 +12,7 @@ export default async function handleRequest(
10
12
  responseStatusCode: number,
11
13
  responseHeaders: Headers,
12
14
  reactRouterContext: EntryContext,
13
- context: AppLoadContext,
15
+ context: HydrogenRouterContextProvider,
14
16
  ) {
15
17
  const {nonce, header, NonceProvider} = createContentSecurityPolicy({
16
18
  shop: {
@@ -4,7 +4,8 @@ export const UPDATE_ADDRESS_MUTATION = `#graphql
4
4
  $address: CustomerAddressInput!
5
5
  $addressId: ID!
6
6
  $defaultAddress: Boolean
7
- ) {
7
+ $language: LanguageCode
8
+ ) @inContext(language: $language) {
8
9
  customerAddressUpdate(
9
10
  address: $address
10
11
  addressId: $addressId
@@ -25,8 +26,9 @@ export const UPDATE_ADDRESS_MUTATION = `#graphql
25
26
  // NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerAddressDelete
26
27
  export const DELETE_ADDRESS_MUTATION = `#graphql
27
28
  mutation customerAddressDelete(
28
- $addressId: ID!,
29
- ) {
29
+ $addressId: ID!
30
+ $language: LanguageCode
31
+ ) @inContext(language: $language) {
30
32
  customerAddressDelete(addressId: $addressId) {
31
33
  deletedAddressId
32
34
  userErrors {
@@ -43,7 +45,8 @@ export const CREATE_ADDRESS_MUTATION = `#graphql
43
45
  mutation customerAddressCreate(
44
46
  $address: CustomerAddressInput!
45
47
  $defaultAddress: Boolean
46
- ) {
48
+ $language: LanguageCode
49
+ ) @inContext(language: $language) {
47
50
  customerAddressCreate(
48
51
  address: $address
49
52
  defaultAddress: $defaultAddress
@@ -31,7 +31,7 @@ export const CUSTOMER_FRAGMENT = `#graphql
31
31
 
32
32
  // NOTE: https://shopify.dev/docs/api/customer/latest/queries/customer
33
33
  export const CUSTOMER_DETAILS_QUERY = `#graphql
34
- query CustomerDetails {
34
+ query CustomerDetails($language: LanguageCode) @inContext(language: $language) {
35
35
  customer {
36
36
  ...Customer
37
37
  }
@@ -45,7 +45,9 @@ export const CUSTOMER_ORDER_QUERY = `#graphql
45
45
  fragment Order on Order {
46
46
  id
47
47
  name
48
+ confirmationNumber
48
49
  statusPageUrl
50
+ fulfillmentStatus
49
51
  processedAt
50
52
  fulfillments(first: 1) {
51
53
  nodes {
@@ -77,7 +79,8 @@ export const CUSTOMER_ORDER_QUERY = `#graphql
77
79
  }
78
80
  }
79
81
  }
80
- query Order($orderId: ID!) {
82
+ query Order($orderId: ID!, $language: LanguageCode)
83
+ @inContext(language: $language) {
81
84
  order(id: $orderId) {
82
85
  ... on Order {
83
86
  ...Order
@@ -1,4 +1,4 @@
1
- // https://shopify.dev/docs/api/customer/latest/objects/Order
1
+ // NOTE: https://shopify.dev/docs/api/customer/latest/objects/Order
2
2
  export const ORDER_ITEM_FRAGMENT = `#graphql
3
3
  fragment OrderItem on Order {
4
4
  totalPrice {
@@ -6,6 +6,7 @@ export const ORDER_ITEM_FRAGMENT = `#graphql
6
6
  currencyCode
7
7
  }
8
8
  financialStatus
9
+ fulfillmentStatus
9
10
  fulfillments(first: 1) {
10
11
  nodes {
11
12
  status
@@ -13,11 +14,12 @@ export const ORDER_ITEM_FRAGMENT = `#graphql
13
14
  }
14
15
  id
15
16
  number
17
+ confirmationNumber
16
18
  processedAt
17
19
  }
18
20
  ` as const;
19
21
 
20
- // https://shopify.dev/docs/api/customer/latest/objects/Customer
22
+ // NOTE: https://shopify.dev/docs/api/customer/latest/objects/Customer
21
23
  export const CUSTOMER_ORDERS_FRAGMENT = `#graphql
22
24
  fragment CustomerOrders on Customer {
23
25
  orders(
@@ -26,7 +28,8 @@ export const CUSTOMER_ORDERS_FRAGMENT = `#graphql
26
28
  first: $first,
27
29
  last: $last,
28
30
  before: $startCursor,
29
- after: $endCursor
31
+ after: $endCursor,
32
+ query: $query
30
33
  ) {
31
34
  nodes {
32
35
  ...OrderItem
@@ -42,7 +45,7 @@ export const CUSTOMER_ORDERS_FRAGMENT = `#graphql
42
45
  ${ORDER_ITEM_FRAGMENT}
43
46
  ` as const;
44
47
 
45
- // https://shopify.dev/docs/api/customer/latest/queries/customer
48
+ // NOTE: https://shopify.dev/docs/api/customer/latest/queries/customer
46
49
  export const CUSTOMER_ORDERS_QUERY = `#graphql
47
50
  ${CUSTOMER_ORDERS_FRAGMENT}
48
51
  query CustomerOrders(
@@ -50,7 +53,9 @@ export const CUSTOMER_ORDERS_QUERY = `#graphql
50
53
  $first: Int
51
54
  $last: Int
52
55
  $startCursor: String
53
- ) {
56
+ $query: String
57
+ $language: LanguageCode
58
+ ) @inContext(language: $language) {
54
59
  customer {
55
60
  ...CustomerOrders
56
61
  }
@@ -1,8 +1,9 @@
1
+ // NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerUpdate
1
2
  export const CUSTOMER_UPDATE_MUTATION = `#graphql
2
- # https://shopify.dev/docs/api/customer/latest/mutations/customerUpdate
3
3
  mutation customerUpdate(
4
4
  $customer: CustomerUpdateInput!
5
- ){
5
+ $language: LanguageCode
6
+ ) @inContext(language: $language) {
6
7
  customerUpdate(input: $customer) {
7
8
  customer {
8
9
  firstName
@@ -2,11 +2,27 @@ import {createHydrogenContext} from '@shopify/hydrogen';
2
2
  import {AppSession} from '~/lib/session';
3
3
  import {CART_QUERY_FRAGMENT} from '~/lib/fragments';
4
4
 
5
+ // Define the additional context object
6
+ const additionalContext = {
7
+ // Additional context for custom properties, CMS clients, 3P SDKs, etc.
8
+ // These will be available as both context.propertyName and context.get(propertyContext)
9
+ // Example of complex objects that could be added:
10
+ // cms: await createCMSClient(env),
11
+ // reviews: await createReviewsClient(env),
12
+ } as const;
13
+
14
+ // Automatically augment HydrogenAdditionalContext with the additional context type
15
+ type AdditionalContextType = typeof additionalContext;
16
+
17
+ declare global {
18
+ interface HydrogenAdditionalContext extends AdditionalContextType {}
19
+ }
20
+
5
21
  /**
6
- * The context implementation is separate from server.ts
7
- * so that type can be extracted for AppLoadContext
22
+ * Creates Hydrogen context for React Router 7.9.x
23
+ * Returns HydrogenRouterContextProvider with hybrid access patterns
8
24
  * */
9
- export async function createAppLoadContext(
25
+ export async function createHydrogenRouterContext(
10
26
  request: Request,
11
27
  env: Env,
12
28
  executionContext: ExecutionContext,
@@ -24,20 +40,21 @@ export async function createAppLoadContext(
24
40
  AppSession.init(request, [env.SESSION_SECRET]),
25
41
  ]);
26
42
 
27
- const hydrogenContext = createHydrogenContext({
28
- env,
29
- request,
30
- cache,
31
- waitUntil,
32
- session,
33
- i18n: {language: 'EN', country: 'US'},
34
- cart: {
35
- queryFragment: CART_QUERY_FRAGMENT,
43
+ const hydrogenContext = createHydrogenContext(
44
+ {
45
+ env,
46
+ request,
47
+ cache,
48
+ waitUntil,
49
+ session,
50
+ // Or detect from URL path based on locale subpath, cookies, or any other strategy
51
+ i18n: {language: 'EN', country: 'US'},
52
+ cart: {
53
+ queryFragment: CART_QUERY_FRAGMENT,
54
+ },
36
55
  },
37
- });
56
+ additionalContext,
57
+ );
38
58
 
39
- return {
40
- ...hydrogenContext,
41
- // declare additional Remix loader context
42
- };
59
+ return hydrogenContext;
43
60
  }
@@ -109,6 +109,7 @@ export const CART_QUERY_FRAGMENT = `#graphql
109
109
  updatedAt
110
110
  id
111
111
  appliedGiftCards {
112
+ id
112
113
  lastCharacters
113
114
  amountUsed {
114
115
  ...Money
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Field name constants for order filtering
3
+ */
4
+ export const ORDER_FILTER_FIELDS = {
5
+ NAME: 'name',
6
+ CONFIRMATION_NUMBER: 'confirmation_number',
7
+ } as const;
8
+
9
+ /**
10
+ * Parameters for filtering customer orders, see: https://shopify.dev/docs/api/customer/latest/queries/customer#returns-Customer.fields.orders.arguments.query
11
+ */
12
+ export interface OrderFilterParams {
13
+ /** Order name or number (e.g., "#1001" or "1001") */
14
+ name?: string;
15
+ /** Order confirmation number */
16
+ confirmationNumber?: string;
17
+ }
18
+
19
+ /**
20
+ * Sanitizes a filter value to prevent injection attacks or malformed queries.
21
+ * Allows only alphanumeric characters, underscore, and dash.
22
+ * @param value - The input string to sanitize
23
+ * @returns The sanitized string
24
+ */
25
+ function sanitizeFilterValue(value: string): string {
26
+ // Only allow alphanumeric, underscore, and dash
27
+ // Remove anything else to prevent injection
28
+ return value.replace(/[^a-zA-Z0-9_\-]/g, '');
29
+ }
30
+
31
+ /**
32
+ * Builds a query string for filtering customer orders using the Customer Account API
33
+ * @param filters - The filter parameters
34
+ * @returns A formatted query string for the GraphQL query parameter, or undefined if no filters
35
+ * @example
36
+ * buildOrderSearchQuery(\{ name: '1001' \}) // returns "name:1001"
37
+ * buildOrderSearchQuery(\{ name: '1001', confirmationNumber: 'ABC123' \}) // returns "name:1001 AND confirmation_number:ABC123"
38
+ */
39
+ export function buildOrderSearchQuery(
40
+ filters: OrderFilterParams,
41
+ ): string | undefined {
42
+ const queryParts: string[] = [];
43
+
44
+ if (filters.name) {
45
+ // Remove # if present and trim
46
+ const cleanName = filters.name.replace(/^#/, '').trim();
47
+ const sanitizedName = sanitizeFilterValue(cleanName);
48
+ if (sanitizedName) {
49
+ queryParts.push(`name:${sanitizedName}`);
50
+ }
51
+ }
52
+
53
+ if (filters.confirmationNumber) {
54
+ const cleanConfirmation = filters.confirmationNumber.trim();
55
+ const sanitizedConfirmation = sanitizeFilterValue(cleanConfirmation);
56
+ if (sanitizedConfirmation) {
57
+ queryParts.push(`confirmation_number:${sanitizedConfirmation}`);
58
+ }
59
+ }
60
+
61
+ return queryParts.length > 0 ? queryParts.join(' AND ') : undefined;
62
+ }
63
+
64
+ /**
65
+ * Parses order filter parameters from URLSearchParams
66
+ * @param searchParams - The URL search parameters
67
+ * @returns Parsed filter parameters
68
+ * @example
69
+ * const url = new URL('https://example.com/orders?name=1001&confirmation_number=ABC123');
70
+ * parseOrderFilters(url.searchParams) // returns \{ name: '1001', confirmationNumber: 'ABC123' \}
71
+ */
72
+ export function parseOrderFilters(
73
+ searchParams: URLSearchParams,
74
+ ): OrderFilterParams {
75
+ const filters: OrderFilterParams = {};
76
+
77
+ const name = searchParams.get(ORDER_FILTER_FIELDS.NAME);
78
+ if (name) {
79
+ filters.name = name;
80
+ }
81
+
82
+ const confirmationNumber = searchParams.get(
83
+ ORDER_FILTER_FIELDS.CONFIRMATION_NUMBER,
84
+ );
85
+ if (confirmationNumber) {
86
+ filters.confirmationNumber = confirmationNumber;
87
+ }
88
+
89
+ return filters;
90
+ }
@@ -1,4 +1,4 @@
1
- import {redirect} from '@shopify/remix-oxygen';
1
+ import {redirect} from 'react-router';
2
2
 
3
3
  export function redirectIfHandleIsLocalized(
4
4
  request: Request,
@@ -3,7 +3,7 @@ import {
3
3
  createCookieSessionStorage,
4
4
  type SessionStorage,
5
5
  type Session,
6
- } from '@shopify/remix-oxygen';
6
+ } from 'react-router';
7
7
 
8
8
  /**
9
9
  * This is a custom session implementation for your Hydrogen shop.
@@ -1,4 +1,4 @@
1
- import { useLocation } from 'react-router';
1
+ import {useLocation} from 'react-router';
2
2
  import type {SelectedOption} from '@shopify/hydrogen/storefront-api-types';
3
3
  import {useMemo} from 'react';
4
4
 
@@ -1,5 +1,4 @@
1
1
  import {Analytics, getShopAnalytics, useNonce} from '@shopify/hydrogen';
2
- import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
3
2
  import {
4
3
  Outlet,
5
4
  useRouteError,
@@ -11,6 +10,7 @@ import {
11
10
  ScrollRestoration,
12
11
  useRouteLoaderData,
13
12
  } from 'react-router';
13
+ import type {Route} from './+types/root';
14
14
  import favicon from '~/assets/favicon.svg';
15
15
  import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments';
16
16
  import resetStyles from '~/styles/reset.css?url';
@@ -65,7 +65,7 @@ export function links() {
65
65
  ];
66
66
  }
67
67
 
68
- export async function loader(args: LoaderFunctionArgs) {
68
+ export async function loader(args: Route.LoaderArgs) {
69
69
  // Start fetching non-critical data without blocking time to first byte
70
70
  const deferredData = loadDeferredData(args);
71
71
 
@@ -97,7 +97,7 @@ export async function loader(args: LoaderFunctionArgs) {
97
97
  * Load data necessary for rendering content above the fold. This is the critical data
98
98
  * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
99
99
  */
100
- async function loadCriticalData({context}: LoaderFunctionArgs) {
100
+ async function loadCriticalData({context}: Route.LoaderArgs) {
101
101
  const {storefront} = context;
102
102
 
103
103
  const [header] = await Promise.all([
@@ -118,7 +118,7 @@ async function loadCriticalData({context}: LoaderFunctionArgs) {
118
118
  * fetched after the initial page load. If it's unavailable, the page should still 200.
119
119
  * Make sure to not throw any errors here, as it will cause the page to 500.
120
120
  */
121
- function loadDeferredData({context}: LoaderFunctionArgs) {
121
+ function loadDeferredData({context}: Route.LoaderArgs) {
122
122
  const {storefront, customerAccount, cart} = context;
123
123
 
124
124
  // defer the footer query (below the fold)
@@ -129,7 +129,7 @@ function loadDeferredData({context}: LoaderFunctionArgs) {
129
129
  footerMenuHandle: 'footer', // Adjust to your footer menu handle
130
130
  },
131
131
  })
132
- .catch((error) => {
132
+ .catch((error: Error) => {
133
133
  // Log query errors, but don't throw them so the page can still render
134
134
  console.error(error);
135
135
  return null;
@@ -143,7 +143,6 @@ function loadDeferredData({context}: LoaderFunctionArgs) {
143
143
 
144
144
  export function Layout({children}: {children?: React.ReactNode}) {
145
145
  const nonce = useNonce();
146
- const data = useRouteLoaderData<RootLoader>('root');
147
146
 
148
147
  return (
149
148
  <html lang="en">
@@ -156,17 +155,7 @@ export function Layout({children}: {children?: React.ReactNode}) {
156
155
  <Links />
157
156
  </head>
158
157
  <body>
159
- {data ? (
160
- <Analytics.Provider
161
- cart={data.cart}
162
- shop={data.shop}
163
- consent={data.consent}
164
- >
165
- <PageLayout {...data}>{children}</PageLayout>
166
- </Analytics.Provider>
167
- ) : (
168
- children
169
- )}
158
+ {children}
170
159
  <ScrollRestoration nonce={nonce} />
171
160
  <Scripts nonce={nonce} />
172
161
  </body>
@@ -175,7 +164,23 @@ export function Layout({children}: {children?: React.ReactNode}) {
175
164
  }
176
165
 
177
166
  export default function App() {
178
- return <Outlet />;
167
+ const data = useRouteLoaderData<RootLoader>('root');
168
+
169
+ if (!data) {
170
+ return <Outlet />;
171
+ }
172
+
173
+ return (
174
+ <Analytics.Provider
175
+ cart={data.cart}
176
+ shop={data.shop}
177
+ consent={data.consent}
178
+ >
179
+ <PageLayout {...data}>
180
+ <Outlet />
181
+ </PageLayout>
182
+ </Analytics.Provider>
183
+ );
179
184
  }
180
185
 
181
186
  export function ErrorBoundary() {
@@ -1,6 +1,6 @@
1
- import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import type {Route} from './+types/$';
2
2
 
3
- export async function loader({request}: LoaderFunctionArgs) {
3
+ export async function loader({request}: Route.LoaderArgs) {
4
4
  throw new Response(`${new URL(request.url).pathname} not found`, {
5
5
  status: 404,
6
6
  });
@@ -1,7 +1,7 @@
1
- import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import type {Route} from './+types/[robots.txt]';
2
2
  import {parseGid} from '@shopify/hydrogen';
3
3
 
4
- export async function loader({request, context}: LoaderFunctionArgs) {
4
+ export async function loader({request, context}: Route.LoaderArgs) {
5
5
  const url = new URL(request.url);
6
6
 
7
7
  const {shop} = await context.storefront.query(ROBOTS_QUERY);
@@ -1,10 +1,10 @@
1
- import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import type {Route} from './+types/[sitemap.xml]';
2
2
  import {getSitemapIndex} from '@shopify/hydrogen';
3
3
 
4
4
  export async function loader({
5
5
  request,
6
6
  context: {storefront},
7
- }: LoaderFunctionArgs) {
7
+ }: Route.LoaderArgs) {
8
8
  const response = await getSitemapIndex({
9
9
  storefront,
10
10
  request,
@@ -14,4 +14,3 @@ export async function loader({
14
14
 
15
15
  return response;
16
16
  }
17
-
@@ -1,18 +1,22 @@
1
- import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
- import { Await, useLoaderData, Link, type MetaFunction } from 'react-router';
1
+ import {
2
+ Await,
3
+ useLoaderData,
4
+ Link,
5
+ } from 'react-router';
6
+ import type {Route} from './+types/_index';
3
7
  import {Suspense} from 'react';
4
- import {Image, Money} from '@shopify/hydrogen';
8
+ import {Image} from '@shopify/hydrogen';
5
9
  import type {
6
10
  FeaturedCollectionFragment,
7
11
  RecommendedProductsQuery,
8
12
  } from 'storefrontapi.generated';
9
13
  import {ProductItem} from '~/components/ProductItem';
10
14
 
11
- export const meta: MetaFunction = () => {
15
+ export const meta: Route.MetaFunction = () => {
12
16
  return [{title: 'Hydrogen | Home'}];
13
17
  };
14
18
 
15
- export async function loader(args: LoaderFunctionArgs) {
19
+ export async function loader(args: Route.LoaderArgs) {
16
20
  // Start fetching non-critical data without blocking time to first byte
17
21
  const deferredData = loadDeferredData(args);
18
22
 
@@ -26,7 +30,7 @@ export async function loader(args: LoaderFunctionArgs) {
26
30
  * Load data necessary for rendering content above the fold. This is the critical data
27
31
  * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
28
32
  */
29
- async function loadCriticalData({context}: LoaderFunctionArgs) {
33
+ async function loadCriticalData({context}: Route.LoaderArgs) {
30
34
  const [{collections}] = await Promise.all([
31
35
  context.storefront.query(FEATURED_COLLECTION_QUERY),
32
36
  // Add other queries here, so that they are loaded in parallel
@@ -42,10 +46,10 @@ async function loadCriticalData({context}: LoaderFunctionArgs) {
42
46
  * fetched after the initial page load. If it's unavailable, the page should still 200.
43
47
  * Make sure to not throw any errors here, as it will cause the page to 500.
44
48
  */
45
- function loadDeferredData({context}: LoaderFunctionArgs) {
49
+ function loadDeferredData({context}: Route.LoaderArgs) {
46
50
  const recommendedProducts = context.storefront
47
51
  .query(RECOMMENDED_PRODUCTS_QUERY)
48
- .catch((error) => {
52
+ .catch((error: Error) => {
49
53
  // Log query errors, but don't throw them so the page can still render
50
54
  console.error(error);
51
55
  return null;
@@ -1,8 +1,9 @@
1
- import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import {redirect} from 'react-router';
2
+ import type {Route} from './+types/account.$';
2
3
 
3
4
  // fallback wild card for all unauthenticated routes in account section
4
- export async function loader({context}: LoaderFunctionArgs) {
5
- await context.customerAccount.handleAuthStatus();
5
+ export async function loader({context}: Route.LoaderArgs) {
6
+ context.customerAccount.handleAuthStatus();
6
7
 
7
8
  return redirect('/account');
8
9
  }
@@ -1,4 +1,4 @@
1
- import {redirect} from '@shopify/remix-oxygen';
1
+ import {redirect} from 'react-router';
2
2
 
3
3
  export async function loader() {
4
4
  return redirect('/account/orders');
@@ -5,17 +5,13 @@ import type {
5
5
  } from 'customer-accountapi.generated';
6
6
  import {
7
7
  data,
8
- type ActionFunctionArgs,
9
- type LoaderFunctionArgs,
10
- } from '@shopify/remix-oxygen';
11
- import {
12
8
  Form,
13
9
  useActionData,
14
10
  useNavigation,
15
11
  useOutletContext,
16
- type MetaFunction,
17
12
  type Fetcher,
18
13
  } from 'react-router';
14
+ import type {Route} from './+types/account.addresses';
19
15
  import {
20
16
  UPDATE_ADDRESS_MUTATION,
21
17
  DELETE_ADDRESS_MUTATION,
@@ -31,17 +27,17 @@ export type ActionResponse = {
31
27
  updatedAddress?: AddressFragment;
32
28
  };
33
29
 
34
- export const meta: MetaFunction = () => {
30
+ export const meta: Route.MetaFunction = () => {
35
31
  return [{title: 'Addresses'}];
36
32
  };
37
33
 
38
- export async function loader({context}: LoaderFunctionArgs) {
39
- await context.customerAccount.handleAuthStatus();
34
+ export async function loader({context}: Route.LoaderArgs) {
35
+ context.customerAccount.handleAuthStatus();
40
36
 
41
37
  return {};
42
38
  }
43
39
 
44
- export async function action({request, context}: ActionFunctionArgs) {
40
+ export async function action({request, context}: Route.ActionArgs) {
45
41
  const {customerAccount} = context;
46
42
 
47
43
  try {
@@ -96,7 +92,11 @@ export async function action({request, context}: ActionFunctionArgs) {
96
92
  const {data, errors} = await customerAccount.mutate(
97
93
  CREATE_ADDRESS_MUTATION,
98
94
  {
99
- variables: {address, defaultAddress},
95
+ variables: {
96
+ address,
97
+ defaultAddress,
98
+ language: customerAccount.i18n.language,
99
+ },
100
100
  },
101
101
  );
102
102
 
@@ -145,6 +145,7 @@ export async function action({request, context}: ActionFunctionArgs) {
145
145
  address,
146
146
  addressId: decodeURIComponent(addressId),
147
147
  defaultAddress,
148
+ language: customerAccount.i18n.language,
148
149
  },
149
150
  },
150
151
  );
@@ -190,7 +191,10 @@ export async function action({request, context}: ActionFunctionArgs) {
190
191
  const {data, errors} = await customerAccount.mutate(
191
192
  DELETE_ADDRESS_MUTATION,
192
193
  {
193
- variables: {addressId: decodeURIComponent(addressId)},
194
+ variables: {
195
+ addressId: decodeURIComponent(addressId),
196
+ language: customerAccount.i18n.language,
197
+ },
194
198
  },
195
199
  );
196
200