@shopify/hydrogen 2026.10.0-preview.2 → 2026.10.0-preview.4

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 (404) hide show
  1. package/README.md +34 -3
  2. package/dist/cli/index.mjs +578 -85
  3. package/dist/client/client.mjs +2 -2
  4. package/dist/client/errors.d.mts +19 -19
  5. package/dist/client/errors.mjs +19 -16
  6. package/dist/client/errors.mjs.map +1 -1
  7. package/dist/client/types.d.mts +91 -10
  8. package/dist/core/analytics/cart-tracker.d.mts +14 -0
  9. package/dist/core/analytics/cart-tracker.mjs +15 -1
  10. package/dist/core/analytics/cart-tracker.mjs.map +1 -1
  11. package/dist/core/analytics/custom-consent.mjs +12 -0
  12. package/dist/core/analytics/custom-consent.mjs.map +1 -0
  13. package/dist/core/analytics/events.d.mts +7 -0
  14. package/dist/core/analytics/events.mjs +7 -0
  15. package/dist/core/analytics/events.mjs.map +1 -1
  16. package/dist/core/analytics/index.d.mts +1 -1
  17. package/dist/core/analytics/types.d.mts +152 -16
  18. package/dist/core/cart/actions.d.mts +47 -3
  19. package/dist/core/cart/actions.mjs +29 -0
  20. package/dist/core/cart/actions.mjs.map +1 -1
  21. package/dist/core/cart/attach-quantity-input.d.mts +26 -0
  22. package/dist/core/cart/attach-quantity-input.mjs +26 -0
  23. package/dist/core/cart/attach-quantity-input.mjs.map +1 -1
  24. package/dist/core/cart/cart.d.mts +106 -6
  25. package/dist/core/cart/cart.mjs +57 -6
  26. package/dist/core/cart/cart.mjs.map +1 -1
  27. package/dist/core/cart/cookie.d.mts +15 -0
  28. package/dist/core/cart/cookie.mjs +15 -0
  29. package/dist/core/cart/cookie.mjs.map +1 -1
  30. package/dist/core/cart/form.d.mts +61 -0
  31. package/dist/core/cart/form.mjs +18 -0
  32. package/dist/core/cart/form.mjs.map +1 -1
  33. package/dist/core/cart/get-cart.d.mts +43 -2
  34. package/dist/core/cart/get-cart.mjs +33 -0
  35. package/dist/core/cart/get-cart.mjs.map +1 -1
  36. package/dist/core/cart/index.d.mts +1 -1
  37. package/dist/core/cart/quantity.d.mts +17 -0
  38. package/dist/core/cart/quantity.mjs +17 -0
  39. package/dist/core/cart/quantity.mjs.map +1 -1
  40. package/dist/core/cart/queries.d.mts +402 -369
  41. package/dist/core/cart/queries.mjs +2 -1
  42. package/dist/core/cart/queries.mjs.map +1 -1
  43. package/dist/core/cart/server-handlers.d.mts +69 -0
  44. package/dist/core/cart/server-handlers.mjs +2 -10
  45. package/dist/core/cart/server-handlers.mjs.map +1 -1
  46. package/dist/core/cart/state.d.mts +133 -0
  47. package/dist/core/cart/state.mjs +8 -0
  48. package/dist/core/cart/state.mjs.map +1 -1
  49. package/dist/core/collection/collection.d.mts +18 -2
  50. package/dist/core/collection/collection.mjs +11 -0
  51. package/dist/core/collection/collection.mjs.map +1 -1
  52. package/dist/core/collection/index.d.mts +1 -1
  53. package/dist/core/collection/reconciler.d.mts +13 -4
  54. package/dist/core/collection/reconciler.mjs.map +1 -1
  55. package/dist/core/collection/state.d.mts +7 -2
  56. package/dist/core/collection/state.mjs.map +1 -1
  57. package/dist/core/collection/url.d.mts +3 -0
  58. package/dist/core/collection/url.mjs.map +1 -1
  59. package/dist/core/constants.d.mts +1 -1
  60. package/dist/core/constants.mjs +2 -2
  61. package/dist/core/constants.mjs.map +1 -1
  62. package/dist/core/headers.mjs +2 -2
  63. package/dist/core/headers.mjs.map +1 -1
  64. package/dist/core/index.d.mts +5 -5
  65. package/dist/core/logging/logging.mjs.map +1 -1
  66. package/dist/core/money/format.mjs +6 -3
  67. package/dist/core/money/format.mjs.map +1 -1
  68. package/dist/core/observable.mjs +0 -3
  69. package/dist/core/observable.mjs.map +1 -1
  70. package/dist/core/predictive-search/form.d.mts +1 -0
  71. package/dist/core/predictive-search/form.mjs +1 -0
  72. package/dist/core/predictive-search/form.mjs.map +1 -1
  73. package/dist/core/predictive-search/index.d.mts +1 -1
  74. package/dist/core/predictive-search/queries.d.mts +40 -4
  75. package/dist/core/predictive-search/queries.mjs +7 -1
  76. package/dist/core/predictive-search/queries.mjs.map +1 -1
  77. package/dist/core/predictive-search/search.d.mts +43 -10
  78. package/dist/core/predictive-search/search.mjs +34 -0
  79. package/dist/core/predictive-search/search.mjs.map +1 -1
  80. package/dist/core/predictive-search/server-handlers.d.mts +31 -5
  81. package/dist/core/predictive-search/server-handlers.mjs +5 -3
  82. package/dist/core/predictive-search/server-handlers.mjs.map +1 -1
  83. package/dist/core/predictive-search/store.d.mts +3 -1
  84. package/dist/core/predictive-search/store.mjs.map +1 -1
  85. package/dist/core/predictive-search/url.d.mts +50 -8
  86. package/dist/core/predictive-search/url.mjs +9 -0
  87. package/dist/core/predictive-search/url.mjs.map +1 -1
  88. package/dist/core/product/form.d.mts +21 -2
  89. package/dist/core/product/form.mjs +13 -0
  90. package/dist/core/product/form.mjs.map +1 -1
  91. package/dist/core/product/index.d.mts +1 -1
  92. package/dist/core/product/options.d.mts +2 -2
  93. package/dist/core/product/options.mjs +2 -2
  94. package/dist/core/product/options.mjs.map +1 -1
  95. package/dist/core/product/product-form.d.mts +33 -1
  96. package/dist/core/product/product-form.mjs +30 -0
  97. package/dist/core/product/product-form.mjs.map +1 -1
  98. package/dist/core/product/state.d.mts +41 -0
  99. package/dist/core/product/url.d.mts +6 -0
  100. package/dist/core/product/url.mjs.map +1 -1
  101. package/dist/core/request-context.d.mts +8 -5
  102. package/dist/core/request-context.mjs +17 -39
  103. package/dist/core/request-context.mjs.map +1 -1
  104. package/dist/core/request-routing/handle-shopify-routes.mjs +4 -1
  105. package/dist/core/request-routing/handle-shopify-routes.mjs.map +1 -1
  106. package/dist/core/request-routing/interceptors/ajax-api.mjs +1 -1
  107. package/dist/core/request-routing/interceptors/api-proxy.mjs +1 -1
  108. package/dist/core/request-routing/interceptors/checkout.mjs +22 -6
  109. package/dist/core/request-routing/interceptors/checkout.mjs.map +1 -1
  110. package/dist/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
  111. package/dist/core/request-routing/interceptors/proxy.mjs +36 -10
  112. package/dist/core/request-routing/interceptors/proxy.mjs.map +1 -1
  113. package/dist/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
  114. package/dist/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
  115. package/dist/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
  116. package/dist/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
  117. package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
  118. package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
  119. package/dist/core/request-routing/interceptors/well-known.mjs +1 -1
  120. package/dist/core/shopify-scripts/analytics.mjs +2 -2
  121. package/dist/core/shopify-scripts/analytics.mjs.map +1 -1
  122. package/dist/core/shopify-scripts/constants.mjs +4 -2
  123. package/dist/core/shopify-scripts/constants.mjs.map +1 -1
  124. package/dist/core/shopify-scripts/global.mjs +9 -4
  125. package/dist/core/shopify-scripts/global.mjs.map +1 -1
  126. package/dist/core/shopify-scripts/index.d.mts +1 -0
  127. package/dist/core/shopify-scripts/index.mjs +14 -12
  128. package/dist/core/shopify-scripts/index.mjs.map +1 -1
  129. package/dist/core/shopify-scripts/initialize.d.mts +1 -0
  130. package/dist/core/shopify-scripts/initialize.mjs +10 -3
  131. package/dist/core/shopify-scripts/initialize.mjs.map +1 -1
  132. package/dist/core/shopify-scripts/types.d.mts +6 -0
  133. package/dist/core/url.mjs +21 -2
  134. package/dist/core/url.mjs.map +1 -1
  135. package/dist/customer-account/client.d.mts +128 -4
  136. package/dist/customer-account/client.mjs +27 -1
  137. package/dist/customer-account/client.mjs.map +1 -1
  138. package/dist/customer-account/errors.d.mts +46 -0
  139. package/dist/customer-account/errors.mjs +46 -0
  140. package/dist/customer-account/errors.mjs.map +1 -1
  141. package/dist/customer-account/graphql.d.mts +68 -0
  142. package/dist/customer-account/graphql.mjs +47 -0
  143. package/dist/customer-account/graphql.mjs.map +1 -1
  144. package/dist/customer-account/session.d.mts +114 -23
  145. package/dist/customer-account/session.mjs +27 -12
  146. package/dist/customer-account/session.mjs.map +1 -1
  147. package/dist/customer-account/type-resolver.d.mts +23 -0
  148. package/dist/customer-account-api-types.d.ts +570 -374
  149. package/dist/customer-account.schema.json +2981 -1527
  150. package/dist/development/client/client.mjs +2 -2
  151. package/dist/development/client/errors.d.mts +19 -19
  152. package/dist/development/client/errors.mjs +20 -20
  153. package/dist/development/client/errors.mjs.map +1 -1
  154. package/dist/development/client/types.d.mts +91 -10
  155. package/dist/development/core/analytics/cart-tracker.d.mts +14 -0
  156. package/dist/development/core/analytics/cart-tracker.mjs +15 -1
  157. package/dist/development/core/analytics/cart-tracker.mjs.map +1 -1
  158. package/dist/development/core/analytics/custom-consent.mjs +12 -0
  159. package/dist/development/core/analytics/custom-consent.mjs.map +1 -0
  160. package/dist/development/core/analytics/events.d.mts +7 -0
  161. package/dist/development/core/analytics/events.mjs +7 -0
  162. package/dist/development/core/analytics/events.mjs.map +1 -1
  163. package/dist/development/core/analytics/index.d.mts +1 -1
  164. package/dist/development/core/analytics/types.d.mts +152 -16
  165. package/dist/development/core/cart/actions.d.mts +47 -3
  166. package/dist/development/core/cart/actions.mjs +29 -0
  167. package/dist/development/core/cart/actions.mjs.map +1 -1
  168. package/dist/development/core/cart/attach-quantity-input.d.mts +26 -0
  169. package/dist/development/core/cart/attach-quantity-input.mjs +26 -0
  170. package/dist/development/core/cart/attach-quantity-input.mjs.map +1 -1
  171. package/dist/development/core/cart/cart.d.mts +106 -6
  172. package/dist/development/core/cart/cart.mjs +57 -6
  173. package/dist/development/core/cart/cart.mjs.map +1 -1
  174. package/dist/development/core/cart/cookie.d.mts +15 -0
  175. package/dist/development/core/cart/cookie.mjs +15 -0
  176. package/dist/development/core/cart/cookie.mjs.map +1 -1
  177. package/dist/development/core/cart/form.d.mts +61 -0
  178. package/dist/development/core/cart/form.mjs +18 -0
  179. package/dist/development/core/cart/form.mjs.map +1 -1
  180. package/dist/development/core/cart/get-cart.d.mts +43 -2
  181. package/dist/development/core/cart/get-cart.mjs +33 -0
  182. package/dist/development/core/cart/get-cart.mjs.map +1 -1
  183. package/dist/development/core/cart/index.d.mts +1 -1
  184. package/dist/development/core/cart/quantity.d.mts +17 -0
  185. package/dist/development/core/cart/quantity.mjs +17 -0
  186. package/dist/development/core/cart/quantity.mjs.map +1 -1
  187. package/dist/development/core/cart/queries.d.mts +345 -312
  188. package/dist/development/core/cart/queries.mjs +2 -1
  189. package/dist/development/core/cart/queries.mjs.map +1 -1
  190. package/dist/development/core/cart/server-handlers.d.mts +69 -0
  191. package/dist/development/core/cart/server-handlers.mjs +2 -10
  192. package/dist/development/core/cart/server-handlers.mjs.map +1 -1
  193. package/dist/development/core/cart/state.d.mts +133 -0
  194. package/dist/development/core/cart/state.mjs +8 -0
  195. package/dist/development/core/cart/state.mjs.map +1 -1
  196. package/dist/development/core/collection/collection.d.mts +18 -2
  197. package/dist/development/core/collection/collection.mjs +11 -0
  198. package/dist/development/core/collection/collection.mjs.map +1 -1
  199. package/dist/development/core/collection/index.d.mts +1 -1
  200. package/dist/development/core/collection/reconciler.d.mts +13 -4
  201. package/dist/development/core/collection/reconciler.mjs.map +1 -1
  202. package/dist/development/core/collection/state.d.mts +7 -2
  203. package/dist/development/core/collection/state.mjs.map +1 -1
  204. package/dist/development/core/collection/url.d.mts +3 -0
  205. package/dist/development/core/collection/url.mjs.map +1 -1
  206. package/dist/development/core/constants.mjs +1 -1
  207. package/dist/development/core/constants.mjs.map +1 -1
  208. package/dist/development/core/development.d.mts +5 -5
  209. package/dist/development/core/headers.mjs +2 -2
  210. package/dist/development/core/headers.mjs.map +1 -1
  211. package/dist/development/core/index.d.mts +4 -4
  212. package/dist/development/core/logging/logging.mjs.map +1 -1
  213. package/dist/development/core/money/format.mjs +6 -3
  214. package/dist/development/core/money/format.mjs.map +1 -1
  215. package/dist/development/core/observable.mjs +0 -3
  216. package/dist/development/core/observable.mjs.map +1 -1
  217. package/dist/development/core/predictive-search/form.d.mts +1 -0
  218. package/dist/development/core/predictive-search/form.mjs +1 -0
  219. package/dist/development/core/predictive-search/form.mjs.map +1 -1
  220. package/dist/development/core/predictive-search/index.d.mts +1 -1
  221. package/dist/development/core/predictive-search/queries.d.mts +38 -2
  222. package/dist/development/core/predictive-search/queries.mjs +7 -1
  223. package/dist/development/core/predictive-search/queries.mjs.map +1 -1
  224. package/dist/development/core/predictive-search/search.d.mts +43 -10
  225. package/dist/development/core/predictive-search/search.mjs +34 -0
  226. package/dist/development/core/predictive-search/search.mjs.map +1 -1
  227. package/dist/development/core/predictive-search/server-handlers.d.mts +31 -5
  228. package/dist/development/core/predictive-search/server-handlers.mjs +5 -3
  229. package/dist/development/core/predictive-search/server-handlers.mjs.map +1 -1
  230. package/dist/development/core/predictive-search/store.d.mts +3 -1
  231. package/dist/development/core/predictive-search/store.mjs.map +1 -1
  232. package/dist/development/core/predictive-search/url.d.mts +50 -8
  233. package/dist/development/core/predictive-search/url.mjs +9 -0
  234. package/dist/development/core/predictive-search/url.mjs.map +1 -1
  235. package/dist/development/core/product/form.d.mts +21 -2
  236. package/dist/development/core/product/form.mjs +13 -0
  237. package/dist/development/core/product/form.mjs.map +1 -1
  238. package/dist/development/core/product/index.d.mts +1 -1
  239. package/dist/development/core/product/options.d.mts +2 -2
  240. package/dist/development/core/product/options.mjs +2 -2
  241. package/dist/development/core/product/options.mjs.map +1 -1
  242. package/dist/development/core/product/product-form.d.mts +33 -1
  243. package/dist/development/core/product/product-form.mjs +30 -0
  244. package/dist/development/core/product/product-form.mjs.map +1 -1
  245. package/dist/development/core/product/state.d.mts +41 -0
  246. package/dist/development/core/product/url.d.mts +6 -0
  247. package/dist/development/core/product/url.mjs.map +1 -1
  248. package/dist/development/core/request-context.d.mts +8 -5
  249. package/dist/development/core/request-context.mjs +17 -39
  250. package/dist/development/core/request-context.mjs.map +1 -1
  251. package/dist/development/core/request-routing/handle-shopify-routes.mjs +4 -1
  252. package/dist/development/core/request-routing/handle-shopify-routes.mjs.map +1 -1
  253. package/dist/development/core/request-routing/interceptors/ajax-api.mjs +1 -1
  254. package/dist/development/core/request-routing/interceptors/api-proxy.mjs +1 -1
  255. package/dist/development/core/request-routing/interceptors/checkout.mjs +22 -6
  256. package/dist/development/core/request-routing/interceptors/checkout.mjs.map +1 -1
  257. package/dist/development/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
  258. package/dist/development/core/request-routing/interceptors/proxy.mjs +36 -10
  259. package/dist/development/core/request-routing/interceptors/proxy.mjs.map +1 -1
  260. package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
  261. package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
  262. package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
  263. package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
  264. package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
  265. package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
  266. package/dist/development/core/request-routing/interceptors/well-known.mjs +1 -1
  267. package/dist/development/core/shopify-scripts/analytics.mjs +2 -2
  268. package/dist/development/core/shopify-scripts/analytics.mjs.map +1 -1
  269. package/dist/development/core/shopify-scripts/constants.mjs +4 -2
  270. package/dist/development/core/shopify-scripts/constants.mjs.map +1 -1
  271. package/dist/development/core/shopify-scripts/global.mjs +9 -4
  272. package/dist/development/core/shopify-scripts/global.mjs.map +1 -1
  273. package/dist/development/core/shopify-scripts/index.d.mts +1 -0
  274. package/dist/development/core/shopify-scripts/index.mjs +14 -12
  275. package/dist/development/core/shopify-scripts/index.mjs.map +1 -1
  276. package/dist/development/core/shopify-scripts/initialize.d.mts +1 -0
  277. package/dist/development/core/shopify-scripts/initialize.mjs +10 -3
  278. package/dist/development/core/shopify-scripts/initialize.mjs.map +1 -1
  279. package/dist/development/core/shopify-scripts/types.d.mts +6 -0
  280. package/dist/development/core/url.mjs +21 -2
  281. package/dist/development/core/url.mjs.map +1 -1
  282. package/dist/development/customer-account/session.d.mts +69 -13
  283. package/dist/development/customer-account/session.mjs +4 -0
  284. package/dist/development/customer-account/session.mjs.map +1 -1
  285. package/dist/development/globals.d.mts +6 -5
  286. package/dist/development/graphql/generated/graphql-env.d.mts +459 -16
  287. package/dist/development/graphql/generated/storefront-api-types.d.mts +1 -1
  288. package/dist/development/graphql/graphql.d.mts +33 -0
  289. package/dist/development/graphql/graphql.mjs +30 -0
  290. package/dist/development/graphql/graphql.mjs.map +1 -1
  291. package/dist/development/graphql/type-resolver.d.mts +14 -0
  292. package/dist/development/react/cart.d.mts +85 -14
  293. package/dist/development/react/cart.mjs +123 -3
  294. package/dist/development/react/cart.mjs.map +1 -1
  295. package/dist/development/react/collection.d.mts +2 -4
  296. package/dist/development/react/collection.mjs.map +1 -1
  297. package/dist/development/react/index.d.mts +8 -6
  298. package/dist/development/react/index.mjs +2 -2
  299. package/dist/development/react/predictive-search.d.mts +68 -7
  300. package/dist/development/react/predictive-search.mjs +54 -12
  301. package/dist/development/react/predictive-search.mjs.map +1 -1
  302. package/dist/development/react/product.d.mts +1 -0
  303. package/dist/development/react/product.mjs +5 -2
  304. package/dist/development/react/product.mjs.map +1 -1
  305. package/dist/development/react/shopify-scripts.mjs +1 -0
  306. package/dist/development/react/shopify-scripts.mjs.map +1 -1
  307. package/dist/development/vue/cart.d.mts +85 -6
  308. package/dist/development/vue/cart.mjs +95 -0
  309. package/dist/development/vue/cart.mjs.map +1 -1
  310. package/dist/development/vue/collection.d.mts +83 -5
  311. package/dist/development/vue/collection.mjs +52 -0
  312. package/dist/development/vue/collection.mjs.map +1 -1
  313. package/dist/development/vue/index.d.mts +6 -4
  314. package/dist/development/vue/predictive-search.d.mts +53 -6
  315. package/dist/development/vue/predictive-search.mjs +33 -0
  316. package/dist/development/vue/predictive-search.mjs.map +1 -1
  317. package/dist/development/vue/product.d.mts +6 -2
  318. package/dist/development/vue/product.mjs.map +1 -1
  319. package/dist/development/vue/shopify-scripts.d.mts +30 -3
  320. package/dist/development/vue/shopify-scripts.mjs +6 -0
  321. package/dist/development/vue/shopify-scripts.mjs.map +1 -1
  322. package/dist/globals.d.mts +6 -5
  323. package/dist/graphql/generated/customer-account-graphql-env.d.mts +1717 -1090
  324. package/dist/graphql/generated/graphql-env.d.mts +459 -16
  325. package/dist/graphql/generated/storefront-api-types.d.mts +1 -1
  326. package/dist/graphql/graphql.d.mts +33 -0
  327. package/dist/graphql/graphql.mjs +30 -0
  328. package/dist/graphql/graphql.mjs.map +1 -1
  329. package/dist/graphql/type-resolver.d.mts +14 -0
  330. package/dist/react/cart.d.mts +85 -14
  331. package/dist/react/cart.mjs +123 -3
  332. package/dist/react/cart.mjs.map +1 -1
  333. package/dist/react/collection.d.mts +2 -4
  334. package/dist/react/collection.mjs.map +1 -1
  335. package/dist/react/index.d.mts +8 -6
  336. package/dist/react/index.mjs +2 -2
  337. package/dist/react/predictive-search.d.mts +68 -7
  338. package/dist/react/predictive-search.mjs +54 -12
  339. package/dist/react/predictive-search.mjs.map +1 -1
  340. package/dist/react/product.d.mts +1 -0
  341. package/dist/react/product.mjs +5 -2
  342. package/dist/react/product.mjs.map +1 -1
  343. package/dist/react/shopify-scripts.mjs +1 -0
  344. package/dist/react/shopify-scripts.mjs.map +1 -1
  345. package/dist/storefront-api-types.d.ts +165 -20
  346. package/dist/storefront.schema.json +782 -57
  347. package/dist/vite/customer-account.mjs +191 -0
  348. package/dist/vite/customer-account.mjs.map +1 -0
  349. package/dist/vite/local-https.mjs +13 -29
  350. package/dist/vite/local-https.mjs.map +1 -1
  351. package/dist/vite/mkcert.mjs +1 -1
  352. package/dist/vue/cart.d.mts +85 -6
  353. package/dist/vue/cart.mjs +95 -0
  354. package/dist/vue/cart.mjs.map +1 -1
  355. package/dist/vue/collection.d.mts +83 -5
  356. package/dist/vue/collection.mjs +52 -0
  357. package/dist/vue/collection.mjs.map +1 -1
  358. package/dist/vue/index.d.mts +6 -4
  359. package/dist/vue/predictive-search.d.mts +53 -6
  360. package/dist/vue/predictive-search.mjs +33 -0
  361. package/dist/vue/predictive-search.mjs.map +1 -1
  362. package/dist/vue/product.d.mts +6 -2
  363. package/dist/vue/product.mjs.map +1 -1
  364. package/dist/vue/shopify-scripts.d.mts +31 -4
  365. package/dist/vue/shopify-scripts.mjs +6 -0
  366. package/dist/vue/shopify-scripts.mjs.map +1 -1
  367. package/package.json +5 -2
  368. package/skills/hydrogen-analytics/SKILL.md +6 -5
  369. package/skills/hydrogen-analytics/references/custom-consent.md +52 -0
  370. package/skills/hydrogen-analytics/references/react.md +1 -1
  371. package/skills/hydrogen-cart-metafields/SKILL.md +133 -0
  372. package/skills/hydrogen-cart-ui/SKILL.md +2 -1
  373. package/skills/hydrogen-customer-account/SKILL.md +3 -3
  374. package/skills/hydrogen-local-https/SKILL.md +22 -7
  375. package/skills/hydrogen-request-handlers/SKILL.md +5 -5
  376. package/skills/hydrogen-request-handlers/references/nuxt.md +0 -6
  377. package/skills/hydrogen-setup/references/analytics.md +24 -23
  378. package/skills/hydrogen-setup/steps/1-inspect-app.md +1 -1
  379. package/skills/hydrogen-setup/steps/11-verify.md +1 -0
  380. package/skills/hydrogen-setup/steps/2-scaffold.md +22 -1
  381. package/skills/hydrogen-smoke-test/SKILL.md +2 -2
  382. package/skills/hydrogen-storefront-client/SKILL.md +4 -2
  383. package/skills/hydrogen-variant-form/SKILL.md +10 -4
  384. package/ts-plugin/package.json +5 -0
  385. package/dist/core/shopify-scripts/consent.mjs +0 -17
  386. package/dist/core/shopify-scripts/consent.mjs.map +0 -1
  387. package/dist/core/shopify-scripts/deprecated-cookies.mjs +0 -97
  388. package/dist/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
  389. package/dist/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
  390. package/dist/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
  391. package/dist/core/shopify-scripts/utils/tracking-values.mjs +0 -65
  392. package/dist/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
  393. package/dist/core/shopify-scripts/utils/uuid.mjs +0 -17
  394. package/dist/core/shopify-scripts/utils/uuid.mjs.map +0 -1
  395. package/dist/development/core/shopify-scripts/consent.mjs +0 -17
  396. package/dist/development/core/shopify-scripts/consent.mjs.map +0 -1
  397. package/dist/development/core/shopify-scripts/deprecated-cookies.mjs +0 -97
  398. package/dist/development/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
  399. package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
  400. package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
  401. package/dist/development/core/shopify-scripts/utils/tracking-values.mjs +0 -65
  402. package/dist/development/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
  403. package/dist/development/core/shopify-scripts/utils/uuid.mjs +0 -17
  404. package/dist/development/core/shopify-scripts/utils/uuid.mjs.map +0 -1
@@ -0,0 +1,191 @@
1
+ import { join } from "node:path";
2
+ import { readFile } from "node:fs/promises";
3
+ import spawn from "cross-spawn";
4
+
5
+ //#region src/vite/customer-account.ts
6
+ const CUSTOMER_ACCOUNT_AUTHORIZE_PATH = "/account/authorize";
7
+ const MINIMUM_HYDROGEN_CLI_VERSION = "13.0.4";
8
+ const SUCCESS_EXIT_CODE = 0;
9
+ function resolveCustomerAccountUrls(host, port) {
10
+ const javascriptOrigin = `https://${host}`;
11
+ const devOrigin = `${javascriptOrigin}:${port}`;
12
+ return {
13
+ callbackUri: `${devOrigin}${CUSTOMER_ACCOUNT_AUTHORIZE_PATH}`,
14
+ devOrigin,
15
+ javascriptOrigin,
16
+ logoutUri: devOrigin
17
+ };
18
+ }
19
+ function formatCustomerAccountSettings(urls) {
20
+ return [
21
+ "",
22
+ "Customer Account API - configure these values for your storefront:",
23
+ "",
24
+ ` Callback URI(s) (required): ${urls.callbackUri}`,
25
+ ` JavaScript origin(s): ${urls.javascriptOrigin}`,
26
+ ` Logout URI: ${urls.logoutUri}`,
27
+ ""
28
+ ].join("\n");
29
+ }
30
+ function isContinuousIntegration() {
31
+ const ci = process.env.CI;
32
+ return ci !== void 0 && ci !== "" && ci !== "false" && ci !== "0";
33
+ }
34
+ async function configureCustomerAccountUrls({ logger, root, urls }, dependencies = {}) {
35
+ if ((dependencies.isCI ?? isContinuousIntegration)()) {
36
+ logger.info(formatCustomerAccountSettings(urls));
37
+ return;
38
+ }
39
+ const runCommand = dependencies.runShopifyCommand ?? runShopifyCommand;
40
+ try {
41
+ await requireCompatibleShopifyCli(root, runCommand);
42
+ } catch (error) {
43
+ const reason = error instanceof Error ? error.message : String(error);
44
+ logger.warn([
45
+ "Automatic Customer Account API setup was skipped.",
46
+ reason,
47
+ "Install the latest Shopify CLI and restart the development server:",
48
+ " npm install -g @shopify/cli@latest",
49
+ formatCustomerAccountSettings(urls)
50
+ ].join("\n"));
51
+ return;
52
+ }
53
+ const hasLinkedStorefront = dependencies.hasLinkedStorefront ?? projectHasLinkedStorefront;
54
+ try {
55
+ if (!await hasLinkedStorefront(root)) {
56
+ logger.info("No linked Hydrogen storefront found. Starting Shopify CLI linking...");
57
+ await runCommand([
58
+ "hydrogen",
59
+ "link",
60
+ "--path",
61
+ root
62
+ ], { cwd: root });
63
+ if (!await hasLinkedStorefront(root)) throw new Error("Shopify CLI finished without linking a Hydrogen storefront.");
64
+ }
65
+ logger.info("Updating Customer Account API settings with Shopify CLI...");
66
+ await runCommand([
67
+ "hydrogen",
68
+ "customer-account-push",
69
+ "--path",
70
+ root,
71
+ "--dev-origin",
72
+ urls.devOrigin,
73
+ "--javascript-origin",
74
+ urls.javascriptOrigin
75
+ ], { cwd: root });
76
+ logger.info(`Customer Account API settings updated for ${urls.devOrigin}.`);
77
+ } catch (error) {
78
+ const reason = error instanceof Error ? error.message : String(error);
79
+ logger.warn([
80
+ "Local HTTPS is ready, but Customer Account API setup could not be completed:",
81
+ ` ${reason}`,
82
+ formatCustomerAccountSettings(urls)
83
+ ].join("\n"));
84
+ }
85
+ }
86
+ var ShopifyCliRequirementError = class extends Error {};
87
+ async function requireCompatibleShopifyCli(root, runCommand) {
88
+ const hydrogenCliVersion = await getHydrogenCliVersion(root, runCommand);
89
+ if (!isVersionAtLeast(hydrogenCliVersion, MINIMUM_HYDROGEN_CLI_VERSION)) throw new ShopifyCliRequirementError(`Found @shopify/cli-hydrogen ${hydrogenCliVersion}; ${MINIMUM_HYDROGEN_CLI_VERSION} or later is required.`);
90
+ }
91
+ async function getHydrogenCliVersion(root, runCommand) {
92
+ let output;
93
+ try {
94
+ output = await runCommand([
95
+ "plugins",
96
+ "--core",
97
+ "--json"
98
+ ], {
99
+ captureOutput: true,
100
+ cwd: root
101
+ });
102
+ } catch (error) {
103
+ throw new ShopifyCliRequirementError(`A compatible Shopify CLI installation was not found. ${error instanceof Error ? error.message : String(error)}`);
104
+ }
105
+ let plugins;
106
+ try {
107
+ plugins = JSON.parse(output);
108
+ } catch {
109
+ throw new ShopifyCliRequirementError("Shopify CLI did not return valid plugin version information.");
110
+ }
111
+ if (!Array.isArray(plugins)) throw new ShopifyCliRequirementError("Shopify CLI did not return plugin version information.");
112
+ for (const plugin of plugins) {
113
+ if (!isRecord(plugin)) continue;
114
+ const packageJson = plugin.pjson;
115
+ if (!isRecord(packageJson) || packageJson.name !== "@shopify/cli") continue;
116
+ const devDependencies = packageJson.devDependencies;
117
+ if (!isRecord(devDependencies)) break;
118
+ const version = devDependencies["@shopify/cli-hydrogen"];
119
+ if (typeof version === "string") return version;
120
+ break;
121
+ }
122
+ throw new ShopifyCliRequirementError("The installed Shopify CLI does not include @shopify/cli-hydrogen.");
123
+ }
124
+ function isVersionAtLeast(version, minimum) {
125
+ const parsedVersion = parseVersion(version);
126
+ const parsedMinimum = parseVersion(minimum);
127
+ if (!parsedVersion || !parsedMinimum) return false;
128
+ for (let index = 0; index < parsedMinimum.numbers.length; index += 1) {
129
+ const difference = parsedVersion.numbers[index] - parsedMinimum.numbers[index];
130
+ if (difference !== 0) return difference > 0;
131
+ }
132
+ return parsedVersion.prerelease === void 0;
133
+ }
134
+ function parseVersion(version) {
135
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([^+]+))?(?:\+.+)?$/.exec(version);
136
+ if (!match) return;
137
+ return {
138
+ numbers: [
139
+ Number(match[1]),
140
+ Number(match[2]),
141
+ Number(match[3])
142
+ ],
143
+ prerelease: match[4]
144
+ };
145
+ }
146
+ async function projectHasLinkedStorefront(root) {
147
+ try {
148
+ const project = JSON.parse(await readFile(join(root, ".shopify", "project.json"), "utf8"));
149
+ return isRecord(project) && isRecord(project.storefront) && typeof project.storefront.id === "string" && project.storefront.id !== "";
150
+ } catch {
151
+ return false;
152
+ }
153
+ }
154
+ function isRecord(value) {
155
+ return typeof value === "object" && value !== null;
156
+ }
157
+ function runShopifyCommand(args, { captureOutput = false, cwd }) {
158
+ return new Promise((resolve, reject) => {
159
+ const child = spawn("shopify", args, {
160
+ cwd,
161
+ shell: false,
162
+ stdio: captureOutput ? [
163
+ "ignore",
164
+ "pipe",
165
+ "pipe"
166
+ ] : "inherit"
167
+ });
168
+ let output = "";
169
+ let errorOutput = "";
170
+ const collect = (target) => (chunk) => {
171
+ if (target === "output") output += chunk.toString();
172
+ else errorOutput += chunk.toString();
173
+ };
174
+ child.stdout?.on("data", collect("output"));
175
+ child.stderr?.on("data", collect("errorOutput"));
176
+ child.on("error", reject);
177
+ child.on("close", (code, signal) => {
178
+ if (code === SUCCESS_EXIT_CODE) {
179
+ resolve(output);
180
+ return;
181
+ }
182
+ const reason = signal ? `was killed by ${signal}` : `exited with code ${code}`;
183
+ const detail = errorOutput.trim();
184
+ reject(/* @__PURE__ */ new Error(`shopify ${args.join(" ")} ${reason}${detail ? `\n${detail}` : ""}`));
185
+ });
186
+ });
187
+ }
188
+
189
+ //#endregion
190
+ export { configureCustomerAccountUrls, isContinuousIntegration, resolveCustomerAccountUrls };
191
+ //# sourceMappingURL=customer-account.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-account.mjs","names":[],"sources":["../../src/vite/customer-account.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport spawn from \"cross-spawn\";\n\nconst CUSTOMER_ACCOUNT_AUTHORIZE_PATH = \"/account/authorize\";\nconst MINIMUM_HYDROGEN_CLI_VERSION = \"13.0.4\";\nconst SUCCESS_EXIT_CODE = 0;\n\ntype Logger = {\n info(message: string): void;\n warn(message: string): void;\n};\n\ntype RunShopifyCommand = (\n args: string[],\n options: { captureOutput?: boolean; cwd: string },\n) => Promise<string>;\n\ntype CustomerAccountSetupDependencies = {\n hasLinkedStorefront?: (root: string) => Promise<boolean>;\n isCI?: () => boolean;\n runShopifyCommand?: RunShopifyCommand;\n};\n\nexport type CustomerAccountUrls = {\n callbackUri: string;\n devOrigin: string;\n javascriptOrigin: string;\n logoutUri: string;\n};\n\nexport function resolveCustomerAccountUrls(host: string, port: number): CustomerAccountUrls {\n const javascriptOrigin = `https://${host}`;\n const devOrigin = `${javascriptOrigin}:${port}`;\n\n return {\n callbackUri: `${devOrigin}${CUSTOMER_ACCOUNT_AUTHORIZE_PATH}`,\n devOrigin,\n javascriptOrigin,\n logoutUri: devOrigin,\n };\n}\n\nfunction formatCustomerAccountSettings(urls: CustomerAccountUrls) {\n return [\n \"\",\n \"Customer Account API - configure these values for your storefront:\",\n \"\",\n ` Callback URI(s) (required): ${urls.callbackUri}`,\n ` JavaScript origin(s): ${urls.javascriptOrigin}`,\n ` Logout URI: ${urls.logoutUri}`,\n \"\",\n ].join(\"\\n\");\n}\n\nexport function isContinuousIntegration() {\n const ci = process.env.CI;\n return ci !== undefined && ci !== \"\" && ci !== \"false\" && ci !== \"0\";\n}\n\nexport async function configureCustomerAccountUrls(\n {\n logger,\n root,\n urls,\n }: {\n logger: Logger;\n root: string;\n urls: CustomerAccountUrls;\n },\n dependencies: CustomerAccountSetupDependencies = {},\n) {\n const isCI = dependencies.isCI ?? isContinuousIntegration;\n if (isCI()) {\n logger.info(formatCustomerAccountSettings(urls));\n return;\n }\n\n const runCommand = dependencies.runShopifyCommand ?? runShopifyCommand;\n\n try {\n await requireCompatibleShopifyCli(root, runCommand);\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n logger.warn(\n [\n \"Automatic Customer Account API setup was skipped.\",\n reason,\n \"Install the latest Shopify CLI and restart the development server:\",\n \" npm install -g @shopify/cli@latest\",\n formatCustomerAccountSettings(urls),\n ].join(\"\\n\"),\n );\n return;\n }\n\n const hasLinkedStorefront = dependencies.hasLinkedStorefront ?? projectHasLinkedStorefront;\n\n try {\n if (!(await hasLinkedStorefront(root))) {\n logger.info(\"No linked Hydrogen storefront found. Starting Shopify CLI linking...\");\n await runCommand([\"hydrogen\", \"link\", \"--path\", root], { cwd: root });\n\n if (!(await hasLinkedStorefront(root))) {\n throw new Error(\"Shopify CLI finished without linking a Hydrogen storefront.\");\n }\n }\n\n logger.info(\"Updating Customer Account API settings with Shopify CLI...\");\n await runCommand(\n [\n \"hydrogen\",\n \"customer-account-push\",\n \"--path\",\n root,\n \"--dev-origin\",\n urls.devOrigin,\n \"--javascript-origin\",\n urls.javascriptOrigin,\n ],\n { cwd: root },\n );\n logger.info(`Customer Account API settings updated for ${urls.devOrigin}.`);\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n logger.warn(\n [\n \"Local HTTPS is ready, but Customer Account API setup could not be completed:\",\n ` ${reason}`,\n formatCustomerAccountSettings(urls),\n ].join(\"\\n\"),\n );\n }\n}\n\nclass ShopifyCliRequirementError extends Error {}\n\nasync function requireCompatibleShopifyCli(root: string, runCommand: RunShopifyCommand) {\n const hydrogenCliVersion = await getHydrogenCliVersion(root, runCommand);\n if (!isVersionAtLeast(hydrogenCliVersion, MINIMUM_HYDROGEN_CLI_VERSION)) {\n throw new ShopifyCliRequirementError(\n `Found @shopify/cli-hydrogen ${hydrogenCliVersion}; ${MINIMUM_HYDROGEN_CLI_VERSION} or later is required.`,\n );\n }\n}\n\nasync function getHydrogenCliVersion(root: string, runCommand: RunShopifyCommand) {\n let output: string;\n try {\n output = await runCommand([\"plugins\", \"--core\", \"--json\"], {\n captureOutput: true,\n cwd: root,\n });\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n throw new ShopifyCliRequirementError(\n `A compatible Shopify CLI installation was not found. ${reason}`,\n );\n }\n\n let plugins: unknown;\n try {\n plugins = JSON.parse(output);\n } catch {\n throw new ShopifyCliRequirementError(\n \"Shopify CLI did not return valid plugin version information.\",\n );\n }\n\n if (!Array.isArray(plugins)) {\n throw new ShopifyCliRequirementError(\"Shopify CLI did not return plugin version information.\");\n }\n\n for (const plugin of plugins) {\n if (!isRecord(plugin)) continue;\n const packageJson = plugin.pjson;\n if (!isRecord(packageJson) || packageJson.name !== \"@shopify/cli\") continue;\n\n const devDependencies = packageJson.devDependencies;\n if (!isRecord(devDependencies)) break;\n\n const version = devDependencies[\"@shopify/cli-hydrogen\"];\n if (typeof version === \"string\") return version;\n break;\n }\n\n throw new ShopifyCliRequirementError(\n \"The installed Shopify CLI does not include @shopify/cli-hydrogen.\",\n );\n}\n\nfunction isVersionAtLeast(version: string, minimum: string) {\n const parsedVersion = parseVersion(version);\n const parsedMinimum = parseVersion(minimum);\n if (!parsedVersion || !parsedMinimum) return false;\n\n for (let index = 0; index < parsedMinimum.numbers.length; index += 1) {\n const difference = parsedVersion.numbers[index] - parsedMinimum.numbers[index];\n if (difference !== 0) return difference > 0;\n }\n\n return parsedVersion.prerelease === undefined;\n}\n\nfunction parseVersion(version: string) {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([^+]+))?(?:\\+.+)?$/.exec(version);\n if (!match) return;\n\n return {\n numbers: [Number(match[1]), Number(match[2]), Number(match[3])],\n prerelease: match[4],\n };\n}\n\nasync function projectHasLinkedStorefront(root: string) {\n try {\n const project = JSON.parse(await readFile(join(root, \".shopify\", \"project.json\"), \"utf8\"));\n return (\n isRecord(project) &&\n isRecord(project.storefront) &&\n typeof project.storefront.id === \"string\" &&\n project.storefront.id !== \"\"\n );\n } catch {\n return false;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction runShopifyCommand(\n args: string[],\n { captureOutput = false, cwd }: { captureOutput?: boolean; cwd: string },\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(\"shopify\", args, {\n cwd,\n shell: false,\n stdio: captureOutput ? [\"ignore\", \"pipe\", \"pipe\"] : \"inherit\",\n });\n let output = \"\";\n let errorOutput = \"\";\n\n const collect = (target: \"output\" | \"errorOutput\") => (chunk: Buffer) => {\n if (target === \"output\") output += chunk.toString();\n else errorOutput += chunk.toString();\n };\n\n child.stdout?.on(\"data\", collect(\"output\"));\n child.stderr?.on(\"data\", collect(\"errorOutput\"));\n child.on(\"error\", reject);\n child.on(\"close\", (code, signal) => {\n if (code === SUCCESS_EXIT_CODE) {\n resolve(output);\n return;\n }\n\n const reason = signal ? `was killed by ${signal}` : `exited with code ${code}`;\n const detail = errorOutput.trim();\n reject(new Error(`shopify ${args.join(\" \")} ${reason}${detail ? `\\n${detail}` : \"\"}`));\n });\n });\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { CUSTOMER_ACCOUNT_PATHS } from "../core/url.mjs";
2
1
  import { confirmCertificateInstallation } from "./certificate-prompt.mjs";
2
+ import { configureCustomerAccountUrls, isContinuousIntegration, resolveCustomerAccountUrls } from "./customer-account.mjs";
3
3
  import { provisionCertificates } from "./mkcert.mjs";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { homedir } from "node:os";
@@ -23,7 +23,7 @@ const HTTP1_ONLY_RESPONSE_HEADERS = new Set([
23
23
  "upgrade"
24
24
  ]);
25
25
  const emittedMissingCertificateWarnings = /* @__PURE__ */ new Set();
26
- const loggedCustomerAccountSettings = /* @__PURE__ */ new Set();
26
+ const startedCustomerAccountSetups = /* @__PURE__ */ new Set();
27
27
  /**
28
28
  * Configures Vite for trusted local HTTPS on Hydrogen's default development host.
29
29
  */
@@ -127,10 +127,6 @@ async function ensureCertificateFiles(settings) {
127
127
  return checkCertificateFiles(settings, `Automatic certificate provisioning failed:\n ${error instanceof Error ? error.message : String(error)}`);
128
128
  }
129
129
  }
130
- function isContinuousIntegration() {
131
- const ci = process.env.CI;
132
- return ci !== void 0 && ci !== "" && ci !== "false" && ci !== "0";
133
- }
134
130
  function checkCertificateFiles(settings, provisioningNote) {
135
131
  const missingPaths = [settings.certPath, settings.keyPath].filter((path) => !existsSync(path));
136
132
  if (missingPaths.length === 0) return true;
@@ -209,31 +205,19 @@ function configureLocalHttpsServer(server, settings) {
209
205
  stripHttp1OnlyResponseHeaders(response);
210
206
  next();
211
207
  });
212
- const logSettings = () => {
208
+ const configureCustomerAccounts = () => {
213
209
  const port = resolveBoundPort();
214
- const settingsKey = `${settings.host}:${port}`;
215
- if (loggedCustomerAccountSettings.has(settingsKey)) return;
216
- loggedCustomerAccountSettings.add(settingsKey);
217
- server.config.logger.info(formatCustomerAccountSettings({
218
- host: settings.host,
219
- port
220
- }));
210
+ const settingsKey = `${server.config.root}:${settings.host}:${port}`;
211
+ if (startedCustomerAccountSetups.has(settingsKey)) return;
212
+ startedCustomerAccountSetups.add(settingsKey);
213
+ configureCustomerAccountUrls({
214
+ logger: server.config.logger,
215
+ root: server.config.root,
216
+ urls: resolveCustomerAccountUrls(settings.host, port)
217
+ });
221
218
  };
222
- if (server.httpServer) server.httpServer.once("listening", logSettings);
223
- else logSettings();
224
- }
225
- function formatCustomerAccountSettings({ host, port }) {
226
- const origin = `https://${host}`;
227
- const portfulOrigin = `${origin}:${port}`;
228
- return [
229
- "",
230
- "Customer Account API — make sure these values are configured for your storefront:",
231
- "",
232
- ` Callback URI(s) (required): ${portfulOrigin}${CUSTOMER_ACCOUNT_PATHS.authorize}`,
233
- ` JavaScript origin(s): ${origin}`,
234
- ` Logout URI: ${portfulOrigin}`,
235
- ""
236
- ].join("\n");
219
+ if (server.httpServer) server.httpServer.once("listening", configureCustomerAccounts);
220
+ else configureCustomerAccounts();
237
221
  }
238
222
  function stripHttp1OnlyResponseHeaders(response) {
239
223
  const originalWriteHead = response.writeHead.bind(response);
@@ -1 +1 @@
1
- {"version":3,"file":"local-https.mjs","names":[],"sources":["../../src/vite/local-https.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport type { OutgoingHttpHeader, OutgoingHttpHeaders, ServerResponse } from \"node:http\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type { ConfigEnv, Plugin, ViteDevServer } from \"vite\";\n\nimport { CUSTOMER_ACCOUNT_PATHS } from \"../core/url\";\nimport { confirmCertificateInstallation } from \"./certificate-prompt\";\nimport { provisionCertificates } from \"./mkcert\";\n\nexport const LOCAL_HTTPS_DEFAULTS = {\n host: \"local.tryhydrogen.dev\",\n port: 5_173,\n} as const;\n\nconst HEADER_PAIR_SIZE = 2;\nconst HTTP1_ONLY_RESPONSE_HEADERS = new Set([\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n]);\nconst emittedMissingCertificateWarnings = new Set<string>();\nconst loggedCustomerAccountSettings = new Set<string>();\n\n/** Options for Hydrogen's local HTTPS Vite plugin. */\nexport type LocalHttpsOptions = {\n /** Enable trusted local HTTPS for the dev server. */\n enabled: boolean;\n /** Local hostname registered in Shopify admin. Defaults to `local.tryhydrogen.dev`. */\n host?: string;\n /** Local port registered in Shopify admin. Defaults to `5173`. */\n port?: number;\n /** Certificate file path. Defaults to Hydrogen's mkcert path under `~/.shopify/hydrogen/certs`. */\n certPath?: string | URL;\n /** Private key file path. Defaults to Hydrogen's mkcert path under `~/.shopify/hydrogen/certs`. */\n keyPath?: string | URL;\n};\n\nexport type LocalHttpsPlugin = Plugin & {\n api: {\n /** Returns host, port, and TLS file paths for frameworks that terminate HTTPS outside Vite. */\n getDevServerConfig(): LocalHttpsDevServerConfig | undefined;\n };\n};\n\nexport type LocalHttpsDevServerConfig = {\n host: string;\n port: number;\n https: {\n cert: string;\n key: string;\n };\n};\n\n/**\n * Configures Vite for trusted local HTTPS on Hydrogen's default development host.\n */\nexport function localHttps(options: LocalHttpsOptions): LocalHttpsPlugin {\n const settings = options.enabled ? resolveLocalHttpsSettings(options) : undefined;\n let certificateFiles: LocalHttpsCertificateFiles | undefined;\n\n const getCertificateFiles = () => {\n if (!settings) return;\n\n certificateFiles ??= readCertificateFiles(settings);\n return certificateFiles;\n };\n\n const getDevServerConfig = () => {\n if (!settings || !checkCertificateFiles(settings)) return;\n\n getCertificateFiles();\n\n return {\n host: settings.host,\n port: settings.port,\n https: {\n cert: settings.certPath,\n key: settings.keyPath,\n },\n };\n };\n\n return {\n name: \"hydrogen-local-https\",\n api: { getDevServerConfig },\n async config(_config: unknown, env: ConfigEnv) {\n if (settings && env.command === \"serve\" && !env.isPreview) {\n const certificatesAvailable = await ensureCertificateFiles(settings);\n if (!certificatesAvailable) return;\n } else if (settings && !checkCertificateFiles(settings)) {\n return;\n }\n\n const certificates = getCertificateFiles();\n if (!settings || !certificates) return;\n\n return {\n server: {\n allowedHosts: [settings.host],\n host: settings.host,\n port: settings.port,\n strictPort: true,\n https: {\n // Several framework dev servers fail when Vite negotiates HTTP/2.\n ALPNProtocols: [\"http/1.1\"],\n cert: certificates.cert,\n key: certificates.key,\n },\n hmr: {\n host: settings.host,\n protocol: \"wss\",\n },\n },\n };\n },\n configureServer(server) {\n if (!settings || !checkCertificateFiles(settings)) return;\n\n getCertificateFiles();\n configureLocalHttpsServer(server, settings);\n },\n };\n}\n\nexport type ProvisionLocalHttpsOptions = Omit<LocalHttpsOptions, \"enabled\" | \"port\">;\n\n/**\n * Downloads a pinned, checksum-verified mkcert release and generates the\n * trusted local certificate files when they do not exist yet. The Vite plugin\n * runs this automatically on `vite dev`; call it directly for frameworks that\n * read certificate paths before Vite starts or from setup scripts.\n */\nexport async function provisionLocalHttps(options: ProvisionLocalHttpsOptions = {}) {\n const settings = resolveLocalHttpsSettings({ enabled: true, ...options });\n\n if (!certificateFilesExist(settings)) {\n await provisionCertificates(settings);\n }\n\n return {\n host: settings.host,\n certPath: settings.certPath,\n keyPath: settings.keyPath,\n };\n}\n\ntype LocalHttpsSettings = {\n host: string;\n port: number;\n certPath: string;\n keyPath: string;\n};\n\ntype LocalHttpsCertificateFiles = {\n cert: Buffer;\n key: Buffer;\n};\n\nfunction resolveLocalHttpsSettings(options: LocalHttpsOptions): LocalHttpsSettings {\n const host = options.host ?? LOCAL_HTTPS_DEFAULTS.host;\n const certificateDirectory = join(homedir(), \".shopify\", \"hydrogen\", \"certs\");\n\n return {\n host,\n port: options.port ?? LOCAL_HTTPS_DEFAULTS.port,\n certPath: resolveCertificatePath(options.certPath ?? join(certificateDirectory, `${host}.pem`)),\n keyPath: resolveCertificatePath(\n options.keyPath ?? join(certificateDirectory, `${host}-key.pem`),\n ),\n };\n}\n\nfunction resolveCertificatePath(path: string | URL) {\n if (path instanceof URL) return fileURLToPath(path);\n if (path === \"~\") return homedir();\n if (path.startsWith(\"~/\")) return resolve(homedir(), path.slice(2));\n return isAbsolute(path) ? path : resolve(path);\n}\n\nfunction certificateFilesExist(settings: LocalHttpsSettings) {\n return existsSync(settings.certPath) && existsSync(settings.keyPath);\n}\n\nasync function ensureCertificateFiles(settings: LocalHttpsSettings): Promise<boolean> {\n if (certificateFilesExist(settings)) return true;\n\n // Installing the mkcert certificate authority needs an interactive trust\n // prompt on first run, which hangs or fails on CI runners.\n if (isContinuousIntegration()) {\n return checkCertificateFiles(\n settings,\n \"Automatic certificate provisioning is skipped in CI environments (the CI environment variable is set).\",\n );\n }\n\n if (!(await confirmCertificateInstallation(settings.host))) {\n return checkCertificateFiles(\n settings,\n \"Automatic certificate provisioning requires confirmation in an interactive terminal.\",\n );\n }\n\n try {\n await provisionCertificates(settings);\n return true;\n } catch (error) {\n return checkCertificateFiles(\n settings,\n `Automatic certificate provisioning failed:\\n ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n}\n\nfunction isContinuousIntegration() {\n const ci = process.env.CI;\n return ci !== undefined && ci !== \"\" && ci !== \"false\" && ci !== \"0\";\n}\n\nfunction checkCertificateFiles(settings: LocalHttpsSettings, provisioningNote?: string) {\n const missingPaths = [settings.certPath, settings.keyPath].filter((path) => !existsSync(path));\n if (missingPaths.length === 0) return true;\n\n const warning = formatMissingCertificateWarning(settings, missingPaths, provisioningNote);\n if (!emittedMissingCertificateWarnings.has(warning)) {\n emittedMissingCertificateWarnings.add(warning);\n process.emitWarning(warning, { type: \"HydrogenLocalHttpsWarning\" });\n }\n return false;\n}\n\nfunction formatMissingCertificateWarning(\n { certPath, host, keyPath }: LocalHttpsSettings,\n missingPaths: string[],\n provisioningNote?: string,\n) {\n const certificateDirectories = [...new Set([dirname(certPath), dirname(keyPath)])];\n const provisioningFailure = provisioningNote === undefined ? [] : [\"\", provisioningNote];\n\n return [\n \"Local HTTPS is disabled because certificate files are missing:\",\n ...missingPaths.map((path) => ` ${path}`),\n ...provisioningFailure,\n \"\",\n \"Expected certificate files:\",\n ` Certificate: ${certPath}`,\n ` Private key: ${keyPath}`,\n \"\",\n \"Run the automatic setup:\",\n \" npx hydrogen certs install\",\n \"\",\n \"Or install and configure mkcert, then generate the certificate:\",\n \" macOS: brew install mkcert\",\n \" mkcert -install\",\n ` mkdir -p ${certificateDirectories.map(shellQuote).join(\" \")}`,\n ` mkcert -cert-file ${shellQuote(certPath)} -key-file ${shellQuote(keyPath)} ${shellQuote(host)}`,\n ].join(\"\\n\");\n}\n\nfunction readCertificateFiles(settings: LocalHttpsSettings): LocalHttpsCertificateFiles {\n return {\n cert: readCertificateFile(settings, \"certificate\", settings.certPath),\n key: readCertificateFile(settings, \"private key\", settings.keyPath),\n };\n}\n\nfunction readCertificateFile(settings: LocalHttpsSettings, label: string, path: string) {\n try {\n return readFileSync(path);\n } catch (error) {\n throw new Error(formatCertificateReadError(settings, label, path, error), { cause: error });\n }\n}\n\nfunction formatCertificateReadError(\n { certPath, host, keyPath }: LocalHttpsSettings,\n label: string,\n path: string,\n error: unknown,\n) {\n const certificateDirectories = [...new Set([dirname(certPath), dirname(keyPath)])];\n const reason = error instanceof Error ? error.message : String(error);\n\n return [\n `Local HTTPS requires a readable ${label} file:`,\n ` ${path}`,\n `Reason: ${reason}`,\n \"\",\n \"Expected certificate files:\",\n ` Certificate: ${certPath}`,\n ` Private key: ${keyPath}`,\n \"\",\n \"Install and configure mkcert, then generate the certificate:\",\n \" macOS: brew install mkcert\",\n \" mkcert -install\",\n ` mkdir -p ${certificateDirectories.map(shellQuote).join(\" \")}`,\n ` mkcert -cert-file ${shellQuote(certPath)} -key-file ${shellQuote(keyPath)} ${shellQuote(host)}`,\n ].join(\"\\n\");\n}\n\nfunction shellQuote(value: string) {\n return `'${value.replaceAll(\"'\", `'\\\\''`)}'`;\n}\n\nfunction configureLocalHttpsServer(server: ViteDevServer, settings: LocalHttpsSettings) {\n // The configured port is only a fallback: frameworks that own the listener\n // (or auto-incremented ports) can bind somewhere else, and the Customer\n // Account URLs registered in admin must match the port actually served.\n const resolveBoundPort = () => {\n const address = server.httpServer?.address();\n return address && typeof address === \"object\" ? address.port : settings.port;\n };\n\n server.middlewares.use((request, response, next) => {\n request.headers[\"x-forwarded-host\"] ??=\n request.headers.host ?? `${settings.host}:${resolveBoundPort()}`;\n request.headers[\"x-forwarded-proto\"] ??= \"https\";\n stripHttp1OnlyResponseHeaders(response);\n next();\n });\n\n const logSettings = () => {\n const port = resolveBoundPort();\n const settingsKey = `${settings.host}:${port}`;\n if (loggedCustomerAccountSettings.has(settingsKey)) return;\n loggedCustomerAccountSettings.add(settingsKey);\n server.config.logger.info(formatCustomerAccountSettings({ host: settings.host, port }));\n };\n\n if (server.httpServer) {\n server.httpServer.once(\"listening\", logSettings);\n } else {\n logSettings();\n }\n}\n\nfunction formatCustomerAccountSettings({ host, port }: Pick<LocalHttpsSettings, \"host\" | \"port\">) {\n const origin = `https://${host}`;\n const portfulOrigin = `${origin}:${port}`;\n\n return [\n \"\",\n \"Customer Account API — make sure these values are configured for your storefront:\",\n \"\",\n ` Callback URI(s) (required): ${portfulOrigin}${CUSTOMER_ACCOUNT_PATHS.authorize}`,\n // Shopify's server-side validation rejects JavaScript origins containing a port.\n ` JavaScript origin(s): ${origin}`,\n ` Logout URI: ${portfulOrigin}`,\n \"\",\n ].join(\"\\n\");\n}\n\nfunction stripHttp1OnlyResponseHeaders(response: ServerResponse) {\n const originalWriteHead = response.writeHead.bind(response);\n\n response.writeHead = (\n statusCode: number,\n statusMessageOrHeaders?: string | OutgoingHttpHeaders | OutgoingHttpHeader[],\n headers?: OutgoingHttpHeaders | OutgoingHttpHeader[],\n ) => {\n removeStoredHttp1OnlyHeaders(response);\n\n if (typeof statusMessageOrHeaders === \"string\") {\n return originalWriteHead(\n statusCode,\n statusMessageOrHeaders,\n sanitizeWriteHeadHeaders(headers),\n );\n }\n\n return originalWriteHead(statusCode, sanitizeWriteHeadHeaders(statusMessageOrHeaders));\n };\n}\n\nfunction removeStoredHttp1OnlyHeaders(response: ServerResponse) {\n for (const header of HTTP1_ONLY_RESPONSE_HEADERS) {\n response.removeHeader(header);\n }\n}\n\nfunction sanitizeWriteHeadHeaders(headers: OutgoingHttpHeaders | OutgoingHttpHeader[] | undefined) {\n if (Array.isArray(headers)) return sanitizeHeaderArray(headers);\n if (headers) return sanitizeHeaderObject(headers);\n return headers;\n}\n\nfunction sanitizeHeaderObject(headers: OutgoingHttpHeaders) {\n const sanitizedHeaders: OutgoingHttpHeaders = {};\n\n for (const [name, value] of Object.entries(headers)) {\n if (!isHttp1OnlyHeader(name)) sanitizedHeaders[name] = value;\n }\n\n return sanitizedHeaders;\n}\n\nfunction sanitizeHeaderArray(headers: OutgoingHttpHeader[]) {\n const sanitizedHeaders: OutgoingHttpHeader[] = [];\n\n for (let index = 0; index < headers.length; index += HEADER_PAIR_SIZE) {\n const name = headers[index];\n const value = headers[index + 1];\n if (typeof name === \"string\" && isHttp1OnlyHeader(name)) continue;\n\n sanitizedHeaders.push(name);\n if (value !== undefined) sanitizedHeaders.push(value);\n }\n\n return sanitizedHeaders;\n}\n\nfunction isHttp1OnlyHeader(name: string) {\n return HTTP1_ONLY_RESPONSE_HEADERS.has(name.toLowerCase());\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"local-https.mjs","names":[],"sources":["../../src/vite/local-https.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport type { OutgoingHttpHeader, OutgoingHttpHeaders, ServerResponse } from \"node:http\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type { ConfigEnv, Plugin, ViteDevServer } from \"vite\";\n\nimport { confirmCertificateInstallation } from \"./certificate-prompt\";\nimport {\n configureCustomerAccountUrls,\n isContinuousIntegration,\n resolveCustomerAccountUrls,\n} from \"./customer-account\";\nimport { provisionCertificates } from \"./mkcert\";\n\nexport const LOCAL_HTTPS_DEFAULTS = {\n host: \"local.tryhydrogen.dev\",\n port: 5_173,\n} as const;\n\nconst HEADER_PAIR_SIZE = 2;\nconst HTTP1_ONLY_RESPONSE_HEADERS = new Set([\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n]);\nconst emittedMissingCertificateWarnings = new Set<string>();\nconst startedCustomerAccountSetups = new Set<string>();\n\n/** Options for Hydrogen's local HTTPS Vite plugin. */\nexport type LocalHttpsOptions = {\n /** Enable trusted local HTTPS for the dev server. */\n enabled: boolean;\n /** Local hostname registered in Shopify admin. Defaults to `local.tryhydrogen.dev`. */\n host?: string;\n /** Local port registered in Shopify admin. Defaults to `5173`. */\n port?: number;\n /** Certificate file path. Defaults to Hydrogen's mkcert path under `~/.shopify/hydrogen/certs`. */\n certPath?: string | URL;\n /** Private key file path. Defaults to Hydrogen's mkcert path under `~/.shopify/hydrogen/certs`. */\n keyPath?: string | URL;\n};\n\nexport type LocalHttpsPlugin = Plugin & {\n api: {\n /** Returns host, port, and TLS file paths for frameworks that terminate HTTPS outside Vite. */\n getDevServerConfig(): LocalHttpsDevServerConfig | undefined;\n };\n};\n\nexport type LocalHttpsDevServerConfig = {\n host: string;\n port: number;\n https: {\n cert: string;\n key: string;\n };\n};\n\n/**\n * Configures Vite for trusted local HTTPS on Hydrogen's default development host.\n */\nexport function localHttps(options: LocalHttpsOptions): LocalHttpsPlugin {\n const settings = options.enabled ? resolveLocalHttpsSettings(options) : undefined;\n let certificateFiles: LocalHttpsCertificateFiles | undefined;\n\n const getCertificateFiles = () => {\n if (!settings) return;\n\n certificateFiles ??= readCertificateFiles(settings);\n return certificateFiles;\n };\n\n const getDevServerConfig = () => {\n if (!settings || !checkCertificateFiles(settings)) return;\n\n getCertificateFiles();\n\n return {\n host: settings.host,\n port: settings.port,\n https: {\n cert: settings.certPath,\n key: settings.keyPath,\n },\n };\n };\n\n return {\n name: \"hydrogen-local-https\",\n api: { getDevServerConfig },\n async config(_config: unknown, env: ConfigEnv) {\n if (settings && env.command === \"serve\" && !env.isPreview) {\n const certificatesAvailable = await ensureCertificateFiles(settings);\n if (!certificatesAvailable) return;\n } else if (settings && !checkCertificateFiles(settings)) {\n return;\n }\n\n const certificates = getCertificateFiles();\n if (!settings || !certificates) return;\n\n return {\n server: {\n allowedHosts: [settings.host],\n host: settings.host,\n port: settings.port,\n strictPort: true,\n https: {\n // Several framework dev servers fail when Vite negotiates HTTP/2.\n ALPNProtocols: [\"http/1.1\"],\n cert: certificates.cert,\n key: certificates.key,\n },\n hmr: {\n host: settings.host,\n protocol: \"wss\",\n },\n },\n };\n },\n configureServer(server) {\n if (!settings || !checkCertificateFiles(settings)) return;\n\n getCertificateFiles();\n configureLocalHttpsServer(server, settings);\n },\n };\n}\n\nexport type ProvisionLocalHttpsOptions = Omit<LocalHttpsOptions, \"enabled\" | \"port\">;\n\n/**\n * Downloads a pinned, checksum-verified mkcert release and generates the\n * trusted local certificate files when they do not exist yet. The Vite plugin\n * runs this automatically on `vite dev`; call it directly for frameworks that\n * read certificate paths before Vite starts or from setup scripts.\n */\nexport async function provisionLocalHttps(options: ProvisionLocalHttpsOptions = {}) {\n const settings = resolveLocalHttpsSettings({ enabled: true, ...options });\n\n if (!certificateFilesExist(settings)) {\n await provisionCertificates(settings);\n }\n\n return {\n host: settings.host,\n certPath: settings.certPath,\n keyPath: settings.keyPath,\n };\n}\n\ntype LocalHttpsSettings = {\n host: string;\n port: number;\n certPath: string;\n keyPath: string;\n};\n\ntype LocalHttpsCertificateFiles = {\n cert: Buffer;\n key: Buffer;\n};\n\nfunction resolveLocalHttpsSettings(options: LocalHttpsOptions): LocalHttpsSettings {\n const host = options.host ?? LOCAL_HTTPS_DEFAULTS.host;\n const certificateDirectory = join(homedir(), \".shopify\", \"hydrogen\", \"certs\");\n\n return {\n host,\n port: options.port ?? LOCAL_HTTPS_DEFAULTS.port,\n certPath: resolveCertificatePath(options.certPath ?? join(certificateDirectory, `${host}.pem`)),\n keyPath: resolveCertificatePath(\n options.keyPath ?? join(certificateDirectory, `${host}-key.pem`),\n ),\n };\n}\n\nfunction resolveCertificatePath(path: string | URL) {\n if (path instanceof URL) return fileURLToPath(path);\n if (path === \"~\") return homedir();\n if (path.startsWith(\"~/\")) return resolve(homedir(), path.slice(2));\n return isAbsolute(path) ? path : resolve(path);\n}\n\nfunction certificateFilesExist(settings: LocalHttpsSettings) {\n return existsSync(settings.certPath) && existsSync(settings.keyPath);\n}\n\nasync function ensureCertificateFiles(settings: LocalHttpsSettings): Promise<boolean> {\n if (certificateFilesExist(settings)) return true;\n\n // Installing the mkcert certificate authority needs an interactive trust\n // prompt on first run, which hangs or fails on CI runners.\n if (isContinuousIntegration()) {\n return checkCertificateFiles(\n settings,\n \"Automatic certificate provisioning is skipped in CI environments (the CI environment variable is set).\",\n );\n }\n\n if (!(await confirmCertificateInstallation(settings.host))) {\n return checkCertificateFiles(\n settings,\n \"Automatic certificate provisioning requires confirmation in an interactive terminal.\",\n );\n }\n\n try {\n await provisionCertificates(settings);\n return true;\n } catch (error) {\n return checkCertificateFiles(\n settings,\n `Automatic certificate provisioning failed:\\n ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n}\n\nfunction checkCertificateFiles(settings: LocalHttpsSettings, provisioningNote?: string) {\n const missingPaths = [settings.certPath, settings.keyPath].filter((path) => !existsSync(path));\n if (missingPaths.length === 0) return true;\n\n const warning = formatMissingCertificateWarning(settings, missingPaths, provisioningNote);\n if (!emittedMissingCertificateWarnings.has(warning)) {\n emittedMissingCertificateWarnings.add(warning);\n process.emitWarning(warning, { type: \"HydrogenLocalHttpsWarning\" });\n }\n return false;\n}\n\nfunction formatMissingCertificateWarning(\n { certPath, host, keyPath }: LocalHttpsSettings,\n missingPaths: string[],\n provisioningNote?: string,\n) {\n const certificateDirectories = [...new Set([dirname(certPath), dirname(keyPath)])];\n const provisioningFailure = provisioningNote === undefined ? [] : [\"\", provisioningNote];\n\n return [\n \"Local HTTPS is disabled because certificate files are missing:\",\n ...missingPaths.map((path) => ` ${path}`),\n ...provisioningFailure,\n \"\",\n \"Expected certificate files:\",\n ` Certificate: ${certPath}`,\n ` Private key: ${keyPath}`,\n \"\",\n \"Run the automatic setup:\",\n \" npx hydrogen certs install\",\n \"\",\n \"Or install and configure mkcert, then generate the certificate:\",\n \" macOS: brew install mkcert\",\n \" mkcert -install\",\n ` mkdir -p ${certificateDirectories.map(shellQuote).join(\" \")}`,\n ` mkcert -cert-file ${shellQuote(certPath)} -key-file ${shellQuote(keyPath)} ${shellQuote(host)}`,\n ].join(\"\\n\");\n}\n\nfunction readCertificateFiles(settings: LocalHttpsSettings): LocalHttpsCertificateFiles {\n return {\n cert: readCertificateFile(settings, \"certificate\", settings.certPath),\n key: readCertificateFile(settings, \"private key\", settings.keyPath),\n };\n}\n\nfunction readCertificateFile(settings: LocalHttpsSettings, label: string, path: string) {\n try {\n return readFileSync(path);\n } catch (error) {\n throw new Error(formatCertificateReadError(settings, label, path, error), { cause: error });\n }\n}\n\nfunction formatCertificateReadError(\n { certPath, host, keyPath }: LocalHttpsSettings,\n label: string,\n path: string,\n error: unknown,\n) {\n const certificateDirectories = [...new Set([dirname(certPath), dirname(keyPath)])];\n const reason = error instanceof Error ? error.message : String(error);\n\n return [\n `Local HTTPS requires a readable ${label} file:`,\n ` ${path}`,\n `Reason: ${reason}`,\n \"\",\n \"Expected certificate files:\",\n ` Certificate: ${certPath}`,\n ` Private key: ${keyPath}`,\n \"\",\n \"Install and configure mkcert, then generate the certificate:\",\n \" macOS: brew install mkcert\",\n \" mkcert -install\",\n ` mkdir -p ${certificateDirectories.map(shellQuote).join(\" \")}`,\n ` mkcert -cert-file ${shellQuote(certPath)} -key-file ${shellQuote(keyPath)} ${shellQuote(host)}`,\n ].join(\"\\n\");\n}\n\nfunction shellQuote(value: string) {\n return `'${value.replaceAll(\"'\", `'\\\\''`)}'`;\n}\n\nfunction configureLocalHttpsServer(server: ViteDevServer, settings: LocalHttpsSettings) {\n // The configured port is only a fallback: frameworks that own the listener\n // (or auto-incremented ports) can bind somewhere else, and the Customer\n // Account URLs registered in admin must match the port actually served.\n const resolveBoundPort = () => {\n const address = server.httpServer?.address();\n return address && typeof address === \"object\" ? address.port : settings.port;\n };\n\n server.middlewares.use((request, response, next) => {\n request.headers[\"x-forwarded-host\"] ??=\n request.headers.host ?? `${settings.host}:${resolveBoundPort()}`;\n request.headers[\"x-forwarded-proto\"] ??= \"https\";\n stripHttp1OnlyResponseHeaders(response);\n next();\n });\n\n const configureCustomerAccounts = () => {\n const port = resolveBoundPort();\n const settingsKey = `${server.config.root}:${settings.host}:${port}`;\n if (startedCustomerAccountSetups.has(settingsKey)) return;\n startedCustomerAccountSetups.add(settingsKey);\n\n void configureCustomerAccountUrls({\n logger: server.config.logger,\n root: server.config.root,\n urls: resolveCustomerAccountUrls(settings.host, port),\n });\n };\n\n if (server.httpServer) {\n server.httpServer.once(\"listening\", configureCustomerAccounts);\n } else {\n configureCustomerAccounts();\n }\n}\n\nfunction stripHttp1OnlyResponseHeaders(response: ServerResponse) {\n const originalWriteHead = response.writeHead.bind(response);\n\n response.writeHead = (\n statusCode: number,\n statusMessageOrHeaders?: string | OutgoingHttpHeaders | OutgoingHttpHeader[],\n headers?: OutgoingHttpHeaders | OutgoingHttpHeader[],\n ) => {\n removeStoredHttp1OnlyHeaders(response);\n\n if (typeof statusMessageOrHeaders === \"string\") {\n return originalWriteHead(\n statusCode,\n statusMessageOrHeaders,\n sanitizeWriteHeadHeaders(headers),\n );\n }\n\n return originalWriteHead(statusCode, sanitizeWriteHeadHeaders(statusMessageOrHeaders));\n };\n}\n\nfunction removeStoredHttp1OnlyHeaders(response: ServerResponse) {\n for (const header of HTTP1_ONLY_RESPONSE_HEADERS) {\n response.removeHeader(header);\n }\n}\n\nfunction sanitizeWriteHeadHeaders(headers: OutgoingHttpHeaders | OutgoingHttpHeader[] | undefined) {\n if (Array.isArray(headers)) return sanitizeHeaderArray(headers);\n if (headers) return sanitizeHeaderObject(headers);\n return headers;\n}\n\nfunction sanitizeHeaderObject(headers: OutgoingHttpHeaders) {\n const sanitizedHeaders: OutgoingHttpHeaders = {};\n\n for (const [name, value] of Object.entries(headers)) {\n if (!isHttp1OnlyHeader(name)) sanitizedHeaders[name] = value;\n }\n\n return sanitizedHeaders;\n}\n\nfunction sanitizeHeaderArray(headers: OutgoingHttpHeader[]) {\n const sanitizedHeaders: OutgoingHttpHeader[] = [];\n\n for (let index = 0; index < headers.length; index += HEADER_PAIR_SIZE) {\n const name = headers[index];\n const value = headers[index + 1];\n if (typeof name === \"string\" && isHttp1OnlyHeader(name)) continue;\n\n sanitizedHeaders.push(name);\n if (value !== undefined) sanitizedHeaders.push(value);\n }\n\n return sanitizedHeaders;\n}\n\nfunction isHttp1OnlyHeader(name: string) {\n return HTTP1_ONLY_RESPONSE_HEADERS.has(name.toLowerCase());\n}\n"],"mappings":"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"}
@@ -2,9 +2,9 @@ import { consoleLogger, getLogger } from "../core/logging/logging.mjs";
2
2
  import { existsSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
+ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
5
6
  import { spawn } from "node:child_process";
6
7
  import { createHash } from "node:crypto";
7
- import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
8
8
 
9
9
  //#region src/vite/mkcert.ts
10
10
  const log = getLogger("local-https");
@@ -1,5 +1,5 @@
1
1
  import { CartData, CartState } from "../core/cart/state.mjs";
2
- import { CartStore, CreateCartStoreOptions } from "../core/cart/cart.mjs";
2
+ import { CartActions, CreateCartStoreOptions } from "../core/cart/cart.mjs";
3
3
  import { CartFormRegister } from "../core/cart/form.mjs";
4
4
  import { CartDataFromHandlers } from "../core/cart/server-handlers.mjs";
5
5
  import { ComputedRef, PropType, ShallowRef } from "vue";
@@ -18,8 +18,6 @@ type TypedCartProvider<TData extends CartData> = {
18
18
  };
19
19
  };
20
20
  };
21
- /** Actions for reconciling cart state after updates outside Standard Actions. */
22
- type CartActions = Pick<CartStore, "refresh">;
23
21
  type TypedCartComponents<TData extends CartData> = {
24
22
  CartProvider: TypedCartProvider<TData>;
25
23
  useCart: TypedUseCart<TData>;
@@ -27,12 +25,73 @@ type TypedCartComponents<TData extends CartData> = {
27
25
  useCartActions: typeof useCartActions;
28
26
  useCartForm: typeof useCartForm;
29
27
  };
28
+ /**
29
+ * Returns cart actions for reconciling state after out-of-band mutations.
30
+ *
31
+ * Currently exposes {@link CartStore.refresh} — call it after server-side cart
32
+ * mutations that bypass the form system.
33
+ *
34
+ * @example
35
+ * ```vue
36
+ * <script setup>
37
+ * const { refresh } = useCartActions();
38
+ *
39
+ * async function handleServerAction() {
40
+ * await createCartOnServer();
41
+ * refresh();
42
+ * }
43
+ * </script>
44
+ * ```
45
+ */
30
46
  declare function useCartActions(): CartActions;
47
+ /**
48
+ * Subscribes the {@link CartStore} to the analytics event dispatcher.
49
+ *
50
+ * Call once near the root of your app. Starts tracking on mount via `onMounted`
51
+ * and cleans up via `onScopeDispose`.
52
+ *
53
+ * @example
54
+ * ```vue
55
+ * <script setup>
56
+ * useCartAnalytics();
57
+ * </script>
58
+ * ```
59
+ */
31
60
  declare function useCartAnalytics(): void;
61
+ /**
62
+ * Returns form props, a field register function, and reactive pending state
63
+ * for building cart forms in Vue.
64
+ *
65
+ * Unlike the React hook, the Vue variant includes `isPending` — a reactive
66
+ * object with `initial` (`ComputedRef<boolean>`) for the initial cart load
67
+ * and `lines(lineId?)` for checking per-line or any-line pending state.
68
+ *
69
+ * Note: `interactive: true` returns numeric input attributes but does **not**
70
+ * auto-submit on change — that behavior requires `attachQuantityInput`, which
71
+ * is only wired by the React adapter.
72
+ *
73
+ * @example
74
+ * ```vue
75
+ * <script setup>
76
+ * const { formProps, register, isPending } = useCartForm();
77
+ * </script>
78
+ *
79
+ * <template>
80
+ * <form v-bind="formProps()">
81
+ * <input v-bind="register('lineId', { value: line.id })" />
82
+ * <input v-bind="register('quantity', { value: line.quantity })" />
83
+ * <button v-bind="register('set')" />
84
+ * <button v-bind="register('increase')">+</button>
85
+ * <button v-bind="register('decrease')">−</button>
86
+ * <span v-if="isPending.lines(line.id)">Updating…</span>
87
+ * </form>
88
+ * </template>
89
+ * ```
90
+ */
32
91
  declare function useCartForm(): {
33
92
  formProps: (opts?: {
34
- beforeSubmit?: (e: Event) => void;
35
- afterSubmit?: (e: Event) => void;
93
+ beforeSubmit?: (e: SubmitEvent) => void;
94
+ afterSubmit?: (e: SubmitEvent) => void;
36
95
  }) => Record<string, unknown>;
37
96
  register: CartFormRegister;
38
97
  isPending: {
@@ -40,7 +99,27 @@ declare function useCartForm(): {
40
99
  lines: (lineId?: string) => boolean;
41
100
  };
42
101
  };
102
+ /**
103
+ * Factory that returns typed cart components and composables matched to your
104
+ * server handler's cart query shape.
105
+ *
106
+ * The generic `THandlers` parameter is inferred from your `createCartServerHandlers`
107
+ * call, so every composable's {@link CartState} carries your custom cart fields.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * import type { cartServerHandlers } from "./server/cart.server";
112
+ *
113
+ * const {
114
+ * CartProvider,
115
+ * useCart,
116
+ * useOptionalCart,
117
+ * useCartActions,
118
+ * useCartForm,
119
+ * } = createCartComponents<typeof cartServerHandlers>();
120
+ * ```
121
+ */
43
122
  declare function createCartComponents<THandlers>(): TypedCartComponents<CartDataFromHandlers<THandlers>>;
44
123
  //#endregion
45
- export { CartActions, createCartComponents, useCartActions, useCartAnalytics };
124
+ export { createCartComponents, useCartActions, useCartAnalytics };
46
125
  //# sourceMappingURL=cart.d.mts.map
package/dist/vue/cart.mjs CHANGED
@@ -25,6 +25,20 @@ function useCartStore(composableName = "useCartStore") {
25
25
  function useOptionalCartStore() {
26
26
  return inject(CartStoreKey, null);
27
27
  }
28
+ /**
29
+ * Vue component that creates and manages a {@link CartStore} instance.
30
+ *
31
+ * Calls {@link CartStore.connect} on mount and {@link CartStore.destroy} on unmount.
32
+ * All cart composables (`useCart`, `useCartForm`, `useCartActions`) must be
33
+ * descendants of this component.
34
+ *
35
+ * @example
36
+ * ```vue
37
+ * <CartProvider :initialData="{ cart: loaderData.cart }">
38
+ * <App />
39
+ * </CartProvider>
40
+ * ```
41
+ */
28
42
  const CartProvider = defineComponent({
29
43
  name: "CartProvider",
30
44
  props: { initialData: {
@@ -47,9 +61,40 @@ const CartProvider = defineComponent({
47
61
  function useCart(selector, isEqual) {
48
62
  return useCartSelector(useCartStore("useCart"), selector ?? ((state) => state), isEqual);
49
63
  }
64
+ /**
65
+ * Returns cart actions for reconciling state after out-of-band mutations.
66
+ *
67
+ * Currently exposes {@link CartStore.refresh} — call it after server-side cart
68
+ * mutations that bypass the form system.
69
+ *
70
+ * @example
71
+ * ```vue
72
+ * <script setup>
73
+ * const { refresh } = useCartActions();
74
+ *
75
+ * async function handleServerAction() {
76
+ * await createCartOnServer();
77
+ * refresh();
78
+ * }
79
+ * <\/script>
80
+ * ```
81
+ */
50
82
  function useCartActions() {
51
83
  return { refresh: useCartStore("useCartActions").refresh };
52
84
  }
85
+ /**
86
+ * Subscribes the {@link CartStore} to the analytics event dispatcher.
87
+ *
88
+ * Call once near the root of your app. Starts tracking on mount via `onMounted`
89
+ * and cleans up via `onScopeDispose`.
90
+ *
91
+ * @example
92
+ * ```vue
93
+ * <script setup>
94
+ * useCartAnalytics();
95
+ * <\/script>
96
+ * ```
97
+ */
53
98
  function useCartAnalytics() {
54
99
  const store = useCartStore("useCartAnalytics");
55
100
  let stopTracking;
@@ -76,6 +121,36 @@ function useCartSelector(store, selector, isEqual) {
76
121
  }));
77
122
  return selected;
78
123
  }
124
+ /**
125
+ * Returns form props, a field register function, and reactive pending state
126
+ * for building cart forms in Vue.
127
+ *
128
+ * Unlike the React hook, the Vue variant includes `isPending` — a reactive
129
+ * object with `initial` (`ComputedRef<boolean>`) for the initial cart load
130
+ * and `lines(lineId?)` for checking per-line or any-line pending state.
131
+ *
132
+ * Note: `interactive: true` returns numeric input attributes but does **not**
133
+ * auto-submit on change — that behavior requires `attachQuantityInput`, which
134
+ * is only wired by the React adapter.
135
+ *
136
+ * @example
137
+ * ```vue
138
+ * <script setup>
139
+ * const { formProps, register, isPending } = useCartForm();
140
+ * <\/script>
141
+ *
142
+ * <template>
143
+ * <form v-bind="formProps()">
144
+ * <input v-bind="register('lineId', { value: line.id })" />
145
+ * <input v-bind="register('quantity', { value: line.quantity })" />
146
+ * <button v-bind="register('set')" />
147
+ * <button v-bind="register('increase')">+</button>
148
+ * <button v-bind="register('decrease')">−</button>
149
+ * <span v-if="isPending.lines(line.id)">Updating…</span>
150
+ * </form>
151
+ * </template>
152
+ * ```
153
+ */
79
154
  function useCartForm() {
80
155
  const store = useCartStore("useCartForm");
81
156
  const loading = useCart((s) => s.loading);
@@ -101,6 +176,26 @@ function useCartForm() {
101
176
  }
102
177
  };
103
178
  }
179
+ /**
180
+ * Factory that returns typed cart components and composables matched to your
181
+ * server handler's cart query shape.
182
+ *
183
+ * The generic `THandlers` parameter is inferred from your `createCartServerHandlers`
184
+ * call, so every composable's {@link CartState} carries your custom cart fields.
185
+ *
186
+ * @example
187
+ * ```ts
188
+ * import type { cartServerHandlers } from "./server/cart.server";
189
+ *
190
+ * const {
191
+ * CartProvider,
192
+ * useCart,
193
+ * useOptionalCart,
194
+ * useCartActions,
195
+ * useCartForm,
196
+ * } = createCartComponents<typeof cartServerHandlers>();
197
+ * ```
198
+ */
104
199
  function createCartComponents() {
105
200
  const TypedCartProvider = createTypedCartProvider();
106
201
  const useTypedCart = ((selector, isEqual) => {