@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
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
6
- import { homedir } from "node:os";
7
- import { dirname, isAbsolute, join, parse, resolve } from "node:path";
6
+ import { homedir, tmpdir } from "node:os";
7
+ import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
8
+ import { createInterface } from "node:readline/promises";
8
9
  import { spawn } from "node:child_process";
9
10
  import { createHash, randomUUID } from "node:crypto";
10
11
  import { parseArgs } from "node:util";
@@ -444,14 +445,455 @@ function isObjectRecord(value) {
444
445
  }
445
446
 
446
447
  //#endregion
447
- //#region src/cli/setup.ts
448
- const PACKAGE_NAME = "@shopify/hydrogen";
449
- const PACKAGE_INSTALL_SPEC = `${PACKAGE_NAME}@preview`;
448
+ //#region src/cli/terminal.ts
449
+ function canPromptInTerminal(env = process.env) {
450
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY) && !env.CI;
451
+ }
452
+ async function confirmInTerminal(question) {
453
+ const readline = createInterface({
454
+ input: process.stdin,
455
+ output: process.stdout
456
+ });
457
+ try {
458
+ const answer = (await readline.question(`${question} [Y/n] `)).trim().toLowerCase();
459
+ return answer === "" || answer === "y" || answer === "yes";
460
+ } finally {
461
+ readline.close();
462
+ }
463
+ }
464
+
465
+ //#endregion
466
+ //#region src/cli/skills.ts
467
+ const PACKAGE_NAME$1 = "@shopify/hydrogen";
450
468
  const PACKAGE_ROOT_FROM_CLI_MODULE = "../../";
451
469
  const SKILLS_DIRECTORY_NAME = "skills";
470
+ const SKILL_FILE_NAME = "SKILL.md";
452
471
  const CLAUDE_DIRECTORY_NAME = ".claude";
453
472
  const AGENTS_DIRECTORY_NAME = ".agents";
454
- const NODE_MODULES_DIRECTORY_NAME = "node_modules";
473
+ const PACKAGE_JSON_FILE_NAME$1 = "package.json";
474
+ const HASH_ALGORITHM = "sha256";
475
+ const STAGING_SUFFIX = ".hydrogen-sync";
476
+ const FRONTMATTER_DELIMITER = "---";
477
+ const FRONTMATTER_PATTERN = /^---\n([\s\S]*?)\n---(\n|$)/;
478
+ const IGNORED_FILE_NAMES = new Set([
479
+ ".DS_Store",
480
+ "Thumbs.db",
481
+ "desktop.ini"
482
+ ]);
483
+ /**
484
+ * The exact block `renderMetadataBlock` writes. Reading, verifying, and
485
+ * stripping all go through this one pattern so writer and reader cannot drift.
486
+ */
487
+ const METADATA_BLOCK_PATTERN = new RegExp(`^metadata:\\n {2}source: "${PACKAGE_NAME$1}"\\n {2}version: "([^"\\n]*)"\\n {2}hash: "([^"\\n]*)"\\n`, "m");
488
+ /**
489
+ * Hashing strips every occurrence, not just the first: a skill whose body
490
+ * documents the block would otherwise hash differently before and after
491
+ * injection and read as locally modified forever.
492
+ */
493
+ const METADATA_BLOCK_GLOBAL_PATTERN = new RegExp(METADATA_BLOCK_PATTERN.source, "gm");
494
+ function assertDirectory(directoryPath, message) {
495
+ if (!existsSync(directoryPath) || !statSync(directoryPath).isDirectory()) throw new Error(message);
496
+ }
497
+ function assertDirectoryIfPresent(directoryPath, label) {
498
+ if (existsSync(directoryPath)) assertDirectory(directoryPath, `${label} exists but is not a directory.`);
499
+ }
500
+ function getPackageRoot() {
501
+ return fileURLToPath(new URL(PACKAGE_ROOT_FROM_CLI_MODULE, import.meta.url));
502
+ }
503
+ /**
504
+ * Resolves the Hydrogen the app actually depends on, walking up parent
505
+ * node_modules the way Node itself would. This finds hoisted installs in
506
+ * monorepos, which a plain `<appRoot>/node_modules` lookup misses.
507
+ */
508
+ function getInstalledPackageRoot(appRoot) {
509
+ const require = createRequire(join(appRoot, PACKAGE_JSON_FILE_NAME$1));
510
+ try {
511
+ return dirname(require.resolve(`${PACKAGE_NAME$1}/${PACKAGE_JSON_FILE_NAME$1}`));
512
+ } catch {
513
+ return;
514
+ }
515
+ }
516
+ function readPackageVersion(packageRoot) {
517
+ const packageJsonPath = join(packageRoot, PACKAGE_JSON_FILE_NAME$1);
518
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
519
+ if (!isObjectRecord(parsed) || typeof parsed.version !== "string") throw new Error(`${packageJsonPath} must declare a string version.`);
520
+ return parsed.version;
521
+ }
522
+ /**
523
+ * Every harness dir is written unconditionally: Claude Code reads `.claude/skills`,
524
+ * while Codex, Cursor, and OpenCode read `.agents/skills`. Writing both means no
525
+ * project has to declare which agents it uses.
526
+ */
527
+ function getSkillsDestinationRoots(appRoot) {
528
+ return [CLAUDE_DIRECTORY_NAME, AGENTS_DIRECTORY_NAME].map((harnessDirectory) => {
529
+ const skillsDirectory = join(harnessDirectory, SKILLS_DIRECTORY_NAME);
530
+ assertDirectoryIfPresent(join(appRoot, harnessDirectory), harnessDirectory);
531
+ assertDirectoryIfPresent(join(appRoot, skillsDirectory), skillsDirectory);
532
+ return join(appRoot, skillsDirectory);
533
+ });
534
+ }
535
+ function listDirectoryNames(root) {
536
+ if (!existsSync(root)) return [];
537
+ return readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
538
+ }
539
+ function listFilesRecursively(root) {
540
+ const files = [];
541
+ const visit = (directory) => {
542
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
543
+ if (IGNORED_FILE_NAMES.has(entry.name)) continue;
544
+ const entryPath = join(directory, entry.name);
545
+ if (entry.isDirectory()) visit(entryPath);
546
+ else files.push(entryPath);
547
+ }
548
+ };
549
+ visit(root);
550
+ return files.map((filePath) => relative(root, filePath).split(sep).join("/")).toSorted((left, right) => left < right ? -1 : left > right ? 1 : 0);
551
+ }
552
+ function readNormalizedText(filePath) {
553
+ return readFileSync(filePath, "utf8").replaceAll("\r\n", "\n");
554
+ }
555
+ function renderMetadataBlock(metadata) {
556
+ return [
557
+ "metadata:",
558
+ ` source: "${PACKAGE_NAME$1}"`,
559
+ ` version: "${metadata.version}"`,
560
+ ` hash: "${metadata.hash}"`,
561
+ ""
562
+ ].join("\n");
563
+ }
564
+ /**
565
+ * Hashes every file in a skill directory. Line endings are normalized so a skill
566
+ * checked out through git autocrlf still verifies against the hash written on
567
+ * another platform. The injected metadata block is stripped from SKILL.md so a
568
+ * synced copy hashes to the same value as its shipped source.
569
+ */
570
+ function hashSkillDirectory(skillRoot) {
571
+ const hash = createHash(HASH_ALGORITHM);
572
+ for (const relativePath of listFilesRecursively(skillRoot)) {
573
+ let content = readNormalizedText(join(skillRoot, relativePath));
574
+ if (relativePath === SKILL_FILE_NAME) content = content.replaceAll(METADATA_BLOCK_GLOBAL_PATTERN, "");
575
+ hash.update(relativePath);
576
+ hash.update("\0");
577
+ hash.update(content);
578
+ hash.update("\0");
579
+ }
580
+ return `${HASH_ALGORITHM}:${hash.digest("hex")}`;
581
+ }
582
+ function readShippedSkill(sourceSkillsRoot, skillName, version) {
583
+ const sourceRoot = join(sourceSkillsRoot, skillName);
584
+ const skillFilePath = join(sourceRoot, SKILL_FILE_NAME);
585
+ if (!existsSync(skillFilePath)) throw new Error(`${skillFilePath} is missing.`);
586
+ const content = readNormalizedText(skillFilePath);
587
+ const frontmatterMatch = FRONTMATTER_PATTERN.exec(content);
588
+ if (!frontmatterMatch) throw new Error(`${skillFilePath} has no frontmatter to record Hydrogen metadata in.`);
589
+ const [fullMatch, frontmatter, terminator] = frontmatterMatch;
590
+ if (/^metadata:/m.test(frontmatter)) throw new Error(`${skillFilePath} already declares frontmatter metadata.`);
591
+ const metadata = {
592
+ version,
593
+ hash: hashSkillDirectory(sourceRoot)
594
+ };
595
+ return {
596
+ skillName,
597
+ sourceRoot,
598
+ metadata,
599
+ skillFile: [
600
+ FRONTMATTER_DELIMITER,
601
+ frontmatter,
602
+ renderMetadataBlock(metadata) + FRONTMATTER_DELIMITER
603
+ ].join("\n") + terminator + content.slice(fullMatch.length)
604
+ };
605
+ }
606
+ function readDestinationState(skillRoot) {
607
+ if (!existsSync(skillRoot)) return { kind: "absent" };
608
+ const skillFilePath = join(skillRoot, SKILL_FILE_NAME);
609
+ if (!existsSync(skillFilePath)) return { kind: "partial" };
610
+ const frontmatter = FRONTMATTER_PATTERN.exec(readNormalizedText(skillFilePath))?.[1];
611
+ const metadataMatch = frontmatter === void 0 ? null : METADATA_BLOCK_PATTERN.exec(frontmatter + "\n");
612
+ if (!metadataMatch) return { kind: "unmanaged" };
613
+ const metadata = {
614
+ version: metadataMatch[1] ?? "",
615
+ hash: metadataMatch[2] ?? ""
616
+ };
617
+ return {
618
+ kind: "managed",
619
+ metadata,
620
+ modified: hashSkillDirectory(skillRoot) !== metadata.hash
621
+ };
622
+ }
623
+ /** Returns undefined when the destination is user content that must not be touched. */
624
+ function decideShippedAction(state, shipped, force) {
625
+ switch (state.kind) {
626
+ case "absent": return "add";
627
+ case "partial": return "update";
628
+ case "unmanaged": return force ? "update" : void 0;
629
+ case "managed": {
630
+ const current = state.metadata.hash === shipped.hash && state.metadata.version === shipped.version;
631
+ if (!state.modified) return current ? "unchanged" : "update";
632
+ if (force) return "update";
633
+ return current ? "unchanged" : "skip";
634
+ }
635
+ }
636
+ }
637
+ /** Returns undefined when the stale directory is user content that must not be touched. */
638
+ function decideStaleAction(state, force) {
639
+ if (state.kind !== "managed") return void 0;
640
+ return !state.modified || force ? "remove" : "orphan";
641
+ }
642
+ function planDestination(destinationRoot, shippedSkills, force) {
643
+ const planned = [];
644
+ const conflicts = [];
645
+ const orphans = [];
646
+ for (const [skillName, shipped] of shippedSkills) {
647
+ const skillRoot = join(destinationRoot, skillName);
648
+ const action = decideShippedAction(readDestinationState(skillRoot), shipped.metadata, force);
649
+ if (!action) conflicts.push(skillRoot);
650
+ else if (action === "add" || action === "update") planned.push({
651
+ action,
652
+ skillName,
653
+ destinationRoot,
654
+ shipped
655
+ });
656
+ else planned.push({
657
+ action,
658
+ skillName,
659
+ destinationRoot
660
+ });
661
+ }
662
+ for (const skillName of listDirectoryNames(destinationRoot)) {
663
+ if (shippedSkills.has(skillName)) continue;
664
+ if (skillName.endsWith(STAGING_SUFFIX)) {
665
+ planned.push({
666
+ action: "remove",
667
+ skillName,
668
+ destinationRoot
669
+ });
670
+ continue;
671
+ }
672
+ const action = decideStaleAction(readDestinationState(join(destinationRoot, skillName)), force);
673
+ if (action === "orphan") orphans.push(skillName);
674
+ else if (action) planned.push({
675
+ action,
676
+ skillName,
677
+ destinationRoot
678
+ });
679
+ }
680
+ return {
681
+ destinationRoot,
682
+ planned,
683
+ conflicts,
684
+ orphans
685
+ };
686
+ }
687
+ /**
688
+ * Asks once per skill name rather than once per harness directory, since both
689
+ * copies came from the same shipped skill and the answer applies to both.
690
+ */
691
+ async function planOrphans(plans, version, confirm) {
692
+ const orphanNames = new Set(plans.flatMap((plan) => plan.orphans));
693
+ for (const skillName of orphanNames) {
694
+ const action = await confirm(`Skill ${skillName} was edited locally and Hydrogen ${version} no longer ships it. Remove it?`) ? "remove" : "keep";
695
+ for (const plan of plans) if (plan.orphans.includes(skillName)) plan.planned.push({
696
+ action,
697
+ skillName,
698
+ destinationRoot: plan.destinationRoot
699
+ });
700
+ }
701
+ }
702
+ /** Never removes user edits without an answer, so no TTY means keep. */
703
+ function confirmByDefault(question) {
704
+ return canPromptInTerminal() ? confirmInTerminal(question) : Promise.resolve(false);
705
+ }
706
+ /**
707
+ * Stages the full copy next to the destination and swaps it in last, so an
708
+ * interrupted run never leaves a half-written skill that a later sync would
709
+ * mistake for user content.
710
+ */
711
+ function installSkill(destinationRoot, skillRoot, shipped) {
712
+ const stagingRoot = skillRoot + STAGING_SUFFIX;
713
+ mkdirSync(destinationRoot, { recursive: true });
714
+ rmSync(stagingRoot, {
715
+ recursive: true,
716
+ force: true
717
+ });
718
+ cpSync(shipped.sourceRoot, stagingRoot, { recursive: true });
719
+ writeFileSync(join(stagingRoot, SKILL_FILE_NAME), shipped.skillFile);
720
+ rmSync(skillRoot, {
721
+ recursive: true,
722
+ force: true
723
+ });
724
+ renameSync(stagingRoot, skillRoot);
725
+ }
726
+ function executePlan(destinationRoot, planned) {
727
+ const result = {
728
+ root: destinationRoot,
729
+ added: 0,
730
+ updated: 0,
731
+ unchanged: 0,
732
+ removed: 0,
733
+ skipped: [],
734
+ kept: []
735
+ };
736
+ for (const skill of planned) {
737
+ const skillRoot = join(destinationRoot, skill.skillName);
738
+ switch (skill.action) {
739
+ case "skip":
740
+ result.skipped.push(skillRoot);
741
+ break;
742
+ case "keep":
743
+ result.kept.push(skillRoot);
744
+ break;
745
+ case "unchanged":
746
+ result.unchanged += 1;
747
+ break;
748
+ case "remove":
749
+ rmSync(skillRoot, {
750
+ recursive: true,
751
+ force: true
752
+ });
753
+ result.removed += 1;
754
+ break;
755
+ case "add":
756
+ installSkill(destinationRoot, skillRoot, skill.shipped);
757
+ result.added += 1;
758
+ break;
759
+ case "update":
760
+ installSkill(destinationRoot, skillRoot, skill.shipped);
761
+ result.updated += 1;
762
+ break;
763
+ }
764
+ }
765
+ return result;
766
+ }
767
+ function parseSkillsSyncArgs(args) {
768
+ const { values } = parseArgs({
769
+ args,
770
+ options: { force: {
771
+ type: "boolean",
772
+ default: false
773
+ } },
774
+ strict: true
775
+ });
776
+ return { force: values.force };
777
+ }
778
+ function planSkillsSync(appRoot, packageRoot, force) {
779
+ const resolvedPackageRoot = packageRoot ?? getInstalledPackageRoot(appRoot) ?? getPackageRoot();
780
+ const sourceSkillsRoot = join(resolvedPackageRoot, SKILLS_DIRECTORY_NAME);
781
+ assertDirectory(sourceSkillsRoot, `No packaged skills found at ${sourceSkillsRoot}.`);
782
+ const version = readPackageVersion(resolvedPackageRoot);
783
+ const shippedSkills = new Map(listDirectoryNames(sourceSkillsRoot).map((skillName) => [skillName, readShippedSkill(sourceSkillsRoot, skillName, version)]));
784
+ return {
785
+ version,
786
+ destinations: getSkillsDestinationRoots(appRoot).map((destinationRoot) => planDestination(destinationRoot, shippedSkills, force))
787
+ };
788
+ }
789
+ /** Reports what a sync would change without touching the filesystem. */
790
+ function getSkillsSyncStatus(options = {}) {
791
+ const plan = planSkillsSync(options.cwd ?? process.cwd(), options.packageRoot, false);
792
+ const names = {
793
+ add: /* @__PURE__ */ new Set(),
794
+ update: /* @__PURE__ */ new Set(),
795
+ remove: /* @__PURE__ */ new Set(),
796
+ modified: /* @__PURE__ */ new Set()
797
+ };
798
+ for (const destination of plan.destinations) {
799
+ for (const skill of destination.planned) if (skill.action === "add" || skill.action === "update" || skill.action === "remove") names[skill.action].add(skill.skillName);
800
+ else if (skill.action === "skip") names.modified.add(skill.skillName);
801
+ for (const skillName of destination.orphans) names.modified.add(skillName);
802
+ }
803
+ return {
804
+ version: plan.version,
805
+ pending: {
806
+ add: names.add.size,
807
+ update: names.update.size,
808
+ remove: names.remove.size,
809
+ modified: names.modified.size
810
+ },
811
+ conflicts: plan.destinations.flatMap((destination) => destination.conflicts)
812
+ };
813
+ }
814
+ const SYNC_COMMAND = "npx @shopify/hydrogen skills sync";
815
+ /** A plain sync clears everything except local edits, which only `--force` resets. */
816
+ function describeNextStep(pending) {
817
+ const syncClears = pending.add + pending.update + pending.remove > 0;
818
+ if (pending.modified === 0) return `Run \`${SYNC_COMMAND}\`.`;
819
+ if (!syncClears) return `Run \`${SYNC_COMMAND} --force\` to reset them.`;
820
+ return `Run \`${SYNC_COMMAND}\`, or \`${SYNC_COMMAND} --force\` to also reset the locally modified ones.`;
821
+ }
822
+ /** Explains why a status is not up to date, or returns undefined when nothing needs doing. */
823
+ function describeSkillsSyncStatus(status) {
824
+ if (status.conflicts.length > 0) return `Hydrogen skills cannot be synced: ${status.conflicts.join(", ")} were not created by Hydrogen. Remove them or run \`${SYNC_COMMAND} --force\`.`;
825
+ const { pending } = status;
826
+ const parts = [
827
+ pending.update > 0 && `${pending.update} to update`,
828
+ pending.add > 0 && `${pending.add} new`,
829
+ pending.remove > 0 && `${pending.remove} removed upstream`,
830
+ pending.modified > 0 && `${pending.modified} locally modified`
831
+ ].filter((part) => typeof part === "string");
832
+ if (parts.length === 0) return void 0;
833
+ return `Hydrogen skills are out of date with @shopify/hydrogen ${status.version} (${parts.join(", ")}). ${describeNextStep(pending)}`;
834
+ }
835
+ const CHECK_MODES = ["error", "warn"];
836
+ function isCheckSkillsMode(value) {
837
+ return CHECK_MODES.some((mode) => mode === value);
838
+ }
839
+ function parseSkillsCheckArgs(args) {
840
+ const { values } = parseArgs({
841
+ args,
842
+ options: { mode: {
843
+ type: "string",
844
+ default: "error"
845
+ } },
846
+ strict: true
847
+ });
848
+ if (!isCheckSkillsMode(values.mode)) throw new Error(`Unknown --mode '${values.mode}'. Expected one of: ${CHECK_MODES.join(", ")}.`);
849
+ return { mode: values.mode };
850
+ }
851
+ /**
852
+ * Reports whether the synced skills match the installed package without writing.
853
+ * A project with no synced skills counts as drift; running this command is the opt-in.
854
+ */
855
+ function checkSkills(options = {}) {
856
+ const mode = options.mode ?? "error";
857
+ const log = options.log ?? console.log;
858
+ const warn = options.warn ?? console.warn;
859
+ const status = getSkillsSyncStatus(options);
860
+ const problem = describeSkillsSyncStatus(status);
861
+ if (problem) {
862
+ if (mode === "error") throw new Error(problem);
863
+ warn(problem);
864
+ return;
865
+ }
866
+ if (mode === "error") log(`Hydrogen skills are up to date with @shopify/hydrogen ${status.version}.`);
867
+ }
868
+ async function syncSkills(options = {}) {
869
+ const log = options.log ?? console.log;
870
+ const confirm = options.confirm ?? confirmByDefault;
871
+ const plan = planSkillsSync(options.cwd ?? process.cwd(), options.packageRoot, options.force ?? false);
872
+ const conflicts = plan.destinations.flatMap((destination) => destination.conflicts);
873
+ if (conflicts.length > 0) throw new Error(`Skill directories exist that Hydrogen did not create: ${conflicts.join(", ")}. Remove them or rerun with --force to overwrite.`);
874
+ await planOrphans(plan.destinations, plan.version, confirm);
875
+ const roots = plan.destinations.map((destination) => executePlan(destination.destinationRoot, destination.planned));
876
+ for (const root of roots) log(`Synced Hydrogen ${plan.version} skills to ${root.root}: ${root.added} added, ${root.updated} updated, ${root.unchanged} unchanged, ${root.removed} removed.`);
877
+ const skipped = roots.flatMap((root) => root.skipped);
878
+ if (skipped.length > 0) log(`Skipped ${skipped.length} locally modified skill(s): ${skipped.join(", ")}. Rerun with --force to overwrite.`);
879
+ const kept = roots.flatMap((root) => root.kept);
880
+ if (kept.length > 0) log([
881
+ "",
882
+ `WARNING: Hydrogen ${plan.version} no longer ships these skills, but they were edited locally so they were kept:`,
883
+ ...kept.map((skillRoot) => ` - ${skillRoot}`),
884
+ "Delete them yourself, or rerun with --force to remove them.",
885
+ ""
886
+ ].join("\n"));
887
+ return {
888
+ version: plan.version,
889
+ roots
890
+ };
891
+ }
892
+
893
+ //#endregion
894
+ //#region src/cli/setup.ts
895
+ const PACKAGE_NAME = "@shopify/hydrogen";
896
+ const PACKAGE_INSTALL_SPEC = `${PACKAGE_NAME}@preview`;
455
897
  const PACKAGE_JSON_FILE_NAME = "package.json";
456
898
  const SUCCESS_EXIT_CODE = 0;
457
899
  const PACKAGE_MANAGER_LOCKFILES = [
@@ -492,13 +934,14 @@ function parsePackageManager(value) {
492
934
  if (value.startsWith(`${packageManager}/`)) return packageManager;
493
935
  }
494
936
  }
937
+ function detectPackageManagerFromEnv(env) {
938
+ return parsePackageManager(env.npm_config_user_agent);
939
+ }
495
940
  function detectPackageManager(appRoot, packageJson, env) {
496
941
  const packageJsonManager = parsePackageManager(packageJson.packageManager);
497
942
  if (packageJsonManager) return packageJsonManager;
498
943
  for (const [packageManager, lockfile] of PACKAGE_MANAGER_LOCKFILES) if (existsSync(join(appRoot, lockfile))) return packageManager;
499
- const userAgentManager = parsePackageManager(env.npm_config_user_agent);
500
- if (userAgentManager) return userAgentManager;
501
- throw new Error("Could not detect a package manager. Add a packageManager field or a lockfile before running setup.");
944
+ return detectPackageManagerFromEnv(env) ?? "npm";
502
945
  }
503
946
  function hasHydrogenDependency(packageJson) {
504
947
  return Boolean(packageJson.dependencies?.[PACKAGE_NAME] ?? packageJson.devDependencies?.[PACKAGE_NAME]);
@@ -506,70 +949,6 @@ function hasHydrogenDependency(packageJson) {
506
949
  async function installHydrogen(appRoot, packageManager, runCommand) {
507
950
  await runCommand(packageManager, [...INSTALL_ARGS[packageManager]], { cwd: appRoot });
508
951
  }
509
- function assertDirectory(directoryPath, message) {
510
- if (!existsSync(directoryPath) || !statSync(directoryPath).isDirectory()) throw new Error(message);
511
- }
512
- function getSkillsDestinationRoots(appRoot) {
513
- const destinationRoots = [];
514
- const claudeDirectory = join(appRoot, CLAUDE_DIRECTORY_NAME);
515
- if (existsSync(claudeDirectory)) {
516
- assertDirectory(claudeDirectory, `${CLAUDE_DIRECTORY_NAME} exists but is not a directory.`);
517
- destinationRoots.push(join(claudeDirectory, SKILLS_DIRECTORY_NAME));
518
- }
519
- const agentsDirectory = join(appRoot, AGENTS_DIRECTORY_NAME);
520
- if (existsSync(agentsDirectory)) {
521
- assertDirectory(agentsDirectory, `${AGENTS_DIRECTORY_NAME} exists but is not a directory.`);
522
- destinationRoots.push(join(agentsDirectory, SKILLS_DIRECTORY_NAME));
523
- }
524
- return destinationRoots.length > 0 ? destinationRoots : [join(appRoot, AGENTS_DIRECTORY_NAME, SKILLS_DIRECTORY_NAME)];
525
- }
526
- function getSkillNames(sourceSkillsRoot) {
527
- assertDirectory(sourceSkillsRoot, `No packaged skills found at ${sourceSkillsRoot}.`);
528
- return readdirSync(sourceSkillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
529
- }
530
- function findConflictingSkills(destinationRoot, skillNames) {
531
- return skillNames.filter((skillName) => existsSync(join(destinationRoot, skillName, "SKILL.md")));
532
- }
533
- function removeIncompleteSkills(destinationRoot, skillNames) {
534
- for (const skillName of skillNames) {
535
- const destinationPath = join(destinationRoot, skillName);
536
- if (!existsSync(destinationPath)) continue;
537
- if (existsSync(join(destinationPath, "SKILL.md"))) continue;
538
- rmSync(destinationPath, {
539
- recursive: true,
540
- force: true
541
- });
542
- }
543
- }
544
- function copySkills(packageRoot, appRoot) {
545
- const sourceSkillsRoot = join(packageRoot, SKILLS_DIRECTORY_NAME);
546
- const destinationRoots = getSkillsDestinationRoots(appRoot);
547
- const skillNames = getSkillNames(sourceSkillsRoot);
548
- const conflictingSkills = destinationRoots.flatMap((destinationRoot) => findConflictingSkills(destinationRoot, skillNames).map((skillName) => join(destinationRoot, skillName)));
549
- if (conflictingSkills.length > 0) throw new Error(`Skill directories already exist: ${conflictingSkills.join(", ")}. Remove them and rerun setup.`);
550
- for (const destinationRoot of destinationRoots) {
551
- removeIncompleteSkills(destinationRoot, skillNames);
552
- mkdirSync(destinationRoot, { recursive: true });
553
- for (const skillName of skillNames) cpSync(join(sourceSkillsRoot, skillName), join(destinationRoot, skillName), {
554
- recursive: true,
555
- errorOnExist: true,
556
- force: false
557
- });
558
- }
559
- return {
560
- destinationRoots,
561
- copiedCount: skillNames.length * destinationRoots.length
562
- };
563
- }
564
- function getPackageRoot() {
565
- return fileURLToPath(new URL(PACKAGE_ROOT_FROM_CLI_MODULE, import.meta.url));
566
- }
567
- function getLocalPackageRoot(appRoot) {
568
- const localPackageRoot = join(appRoot, NODE_MODULES_DIRECTORY_NAME, PACKAGE_NAME);
569
- if (!existsSync(localPackageRoot)) return void 0;
570
- assertDirectory(localPackageRoot, `${localPackageRoot} exists but is not a directory.`);
571
- return realpathSync(localPackageRoot);
572
- }
573
952
  function spawnRunCommand(command, args, options) {
574
953
  return new Promise((resolve, reject) => {
575
954
  const child = spawn(command, args, {
@@ -586,19 +965,121 @@ function spawnRunCommand(command, args, options) {
586
965
  });
587
966
  });
588
967
  }
589
- async function setupHydrogen(options = {}) {
590
- const appRoot = options.cwd ?? process.cwd();
591
- const env = options.env ?? process.env;
592
- const runCommand = options.runCommand ?? spawnRunCommand;
593
- const log = options.log ?? console.log;
968
+ const TEMPLATE_TARBALL_URL = "https://codeload.github.com/Shopify/hydrogen/tar.gz/dist-preview";
969
+ const TEMPLATE_TARBALL_PREFIX = "hydrogen-dist-preview/templates/react-router/";
970
+ const TEMPLATE_TARBALL_STRIP_COMPONENTS = TEMPLATE_TARBALL_PREFIX.split("/").filter(Boolean).length;
971
+ async function promptSetupChoice(log) {
972
+ log("\nNo project found in this directory.\n");
973
+ log(" 1) Create a new Hydrogen storefront (React Router)");
974
+ log(" 2) Just install Hydrogen skills\n");
975
+ const readline = createInterface({
976
+ input: process.stdin,
977
+ output: process.stdout
978
+ });
979
+ try {
980
+ while (true) {
981
+ const answer = (await readline.question("Your choice [1] ")).trim();
982
+ if (answer === "" || answer === "1") return "scaffold";
983
+ if (answer === "2") return "skills";
984
+ log("Please enter \"1\" or \"2\".");
985
+ }
986
+ } finally {
987
+ readline.close();
988
+ }
989
+ }
990
+ function hasPackageJson(appRoot) {
991
+ return existsSync(join(appRoot, PACKAGE_JSON_FILE_NAME));
992
+ }
993
+ async function downloadAndExtractTemplate(appRoot, runCommand) {
994
+ const response = await fetch(TEMPLATE_TARBALL_URL, { signal: AbortSignal.timeout(3e4) });
995
+ if (!response.ok) throw new Error(`Failed to download template (${response.status}). Check your internet connection.`);
996
+ const tempDir = mkdtempSync(join(tmpdir(), "hydrogen-template-"));
997
+ const tempFile = "template.tar.gz";
998
+ try {
999
+ writeFileSync(join(tempDir, tempFile), Buffer.from(await response.arrayBuffer()));
1000
+ await runCommand("tar", [
1001
+ "xzf",
1002
+ tempFile,
1003
+ `--strip-components=${TEMPLATE_TARBALL_STRIP_COMPONENTS}`,
1004
+ "-C",
1005
+ appRoot,
1006
+ TEMPLATE_TARBALL_PREFIX
1007
+ ], { cwd: tempDir });
1008
+ } finally {
1009
+ rmSync(tempDir, {
1010
+ recursive: true,
1011
+ force: true
1012
+ });
1013
+ }
1014
+ }
1015
+ function updateScaffoldedPackageJson(appRoot) {
1016
+ const packageJsonPath = join(appRoot, PACKAGE_JSON_FILE_NAME);
1017
+ const raw = readFileSync(packageJsonPath, "utf8");
1018
+ const parsed = JSON.parse(raw);
1019
+ if (!isObjectRecord(parsed)) return;
1020
+ parsed.name = basename(appRoot);
1021
+ delete parsed.private;
1022
+ delete parsed.packageManager;
1023
+ writeFileSync(packageJsonPath, JSON.stringify(parsed, null, 2) + "\n");
1024
+ }
1025
+ async function scaffoldTemplate(appRoot, env, runCommand, log) {
1026
+ log("Downloading Hydrogen template...");
1027
+ await downloadAndExtractTemplate(appRoot, runCommand);
1028
+ updateScaffoldedPackageJson(appRoot);
1029
+ const packageManager = detectPackageManagerFromEnv(env) ?? "npm";
1030
+ log(`Installing dependencies with ${packageManager}...`);
1031
+ try {
1032
+ await runCommand(packageManager, ["install"], { cwd: appRoot });
1033
+ } catch (error) {
1034
+ throw new Error(`Template was created but dependency install failed. Run \`${packageManager} install\` or re-run \`npx @shopify/hydrogen setup\` in ${appRoot} to finish.`, { cause: error });
1035
+ }
1036
+ return packageManager;
1037
+ }
1038
+ async function ensureHydrogenInstalled(appRoot, env, runCommand, log) {
594
1039
  const packageJson = readPackageJson(appRoot);
595
- if (!hasHydrogenDependency(packageJson)) {
1040
+ if (hasHydrogenDependency(packageJson) && getInstalledPackageRoot(appRoot)) return;
1041
+ if (hasHydrogenDependency(packageJson)) {
596
1042
  const packageManager = detectPackageManager(appRoot, packageJson, env);
597
- log(`Installing ${PACKAGE_NAME} with ${packageManager}...`);
598
- await installHydrogen(appRoot, packageManager, runCommand);
1043
+ log(`${PACKAGE_NAME} is declared but not installed. Running ${packageManager} install...`);
1044
+ await runCommand(packageManager, ["install"], { cwd: appRoot });
1045
+ return;
599
1046
  }
600
- const result = copySkills(options.packageRoot ?? getLocalPackageRoot(appRoot) ?? getPackageRoot(), appRoot);
601
- log(`Copied ${result.copiedCount} Hydrogen skills to ${result.destinationRoots.join(", ")}.`);
1047
+ const packageManager = detectPackageManager(appRoot, packageJson, env);
1048
+ log(`Installing ${PACKAGE_NAME} with ${packageManager}...`);
1049
+ await installHydrogen(appRoot, packageManager, runCommand);
1050
+ }
1051
+ function resolveSetupOptions(options) {
1052
+ const env = options.env ?? process.env;
1053
+ return {
1054
+ appRoot: options.cwd ?? process.cwd(),
1055
+ env,
1056
+ runCommand: options.runCommand ?? spawnRunCommand,
1057
+ log: options.log ?? console.log,
1058
+ prompt: options.prompt ?? (canPromptInTerminal(env) ? promptSetupChoice : void 0)
1059
+ };
1060
+ }
1061
+ const IGNORED_DIRECTORY_ENTRIES = new Set([".git", ".DS_Store"]);
1062
+ function isEmptyDirectory(dir) {
1063
+ return readdirSync(dir).every((entry) => IGNORED_DIRECTORY_ENTRIES.has(entry));
1064
+ }
1065
+ async function setupHydrogen(options = {}) {
1066
+ const { appRoot, env, runCommand, log, prompt } = resolveSetupOptions(options);
1067
+ if (hasPackageJson(appRoot)) await ensureHydrogenInstalled(appRoot, env, runCommand, log);
1068
+ else if (isEmptyDirectory(appRoot)) {
1069
+ let choice;
1070
+ if (prompt) choice = await prompt(log);
1071
+ else {
1072
+ log("No package.json found and no terminal to prompt; installing skills only.");
1073
+ choice = "skills";
1074
+ }
1075
+ if (choice === "scaffold") log(`\nHydrogen storefront created. Run \`${await scaffoldTemplate(appRoot, env, runCommand, log)} run dev\` to start (uses mock.shop by default).\n`);
1076
+ } else throw new Error("No package.json found. Run this command from your project root.");
1077
+ await syncSkills({
1078
+ force: options.force,
1079
+ cwd: appRoot,
1080
+ packageRoot: options.packageRoot,
1081
+ log
1082
+ });
602
1083
  }
603
1084
 
604
1085
  //#endregion
@@ -616,7 +1097,19 @@ const COMMANDS = [
616
1097
  },
617
1098
  {
618
1099
  path: ["setup"],
619
- run: async (_args) => setupHydrogen()
1100
+ run: async (args) => setupHydrogen(parseSkillsSyncArgs(args))
1101
+ },
1102
+ {
1103
+ path: ["skills", "check"],
1104
+ run: async (args) => {
1105
+ checkSkills(parseSkillsCheckArgs(args));
1106
+ }
1107
+ },
1108
+ {
1109
+ path: ["skills", "sync"],
1110
+ run: async (args) => {
1111
+ await syncSkills(parseSkillsSyncArgs(args));
1112
+ }
620
1113
  },
621
1114
  {
622
1115
  path: ["gql", "check"],