@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.
- package/README.md +34 -3
- package/dist/cli/index.mjs +578 -85
- package/dist/client/client.mjs +2 -2
- package/dist/client/errors.d.mts +19 -19
- package/dist/client/errors.mjs +19 -16
- package/dist/client/errors.mjs.map +1 -1
- package/dist/client/types.d.mts +91 -10
- package/dist/core/analytics/cart-tracker.d.mts +14 -0
- package/dist/core/analytics/cart-tracker.mjs +15 -1
- package/dist/core/analytics/cart-tracker.mjs.map +1 -1
- package/dist/core/analytics/custom-consent.mjs +12 -0
- package/dist/core/analytics/custom-consent.mjs.map +1 -0
- package/dist/core/analytics/events.d.mts +7 -0
- package/dist/core/analytics/events.mjs +7 -0
- package/dist/core/analytics/events.mjs.map +1 -1
- package/dist/core/analytics/index.d.mts +1 -1
- package/dist/core/analytics/types.d.mts +152 -16
- package/dist/core/cart/actions.d.mts +47 -3
- package/dist/core/cart/actions.mjs +29 -0
- package/dist/core/cart/actions.mjs.map +1 -1
- package/dist/core/cart/attach-quantity-input.d.mts +26 -0
- package/dist/core/cart/attach-quantity-input.mjs +26 -0
- package/dist/core/cart/attach-quantity-input.mjs.map +1 -1
- package/dist/core/cart/cart.d.mts +106 -6
- package/dist/core/cart/cart.mjs +57 -6
- package/dist/core/cart/cart.mjs.map +1 -1
- package/dist/core/cart/cookie.d.mts +15 -0
- package/dist/core/cart/cookie.mjs +15 -0
- package/dist/core/cart/cookie.mjs.map +1 -1
- package/dist/core/cart/form.d.mts +61 -0
- package/dist/core/cart/form.mjs +18 -0
- package/dist/core/cart/form.mjs.map +1 -1
- package/dist/core/cart/get-cart.d.mts +43 -2
- package/dist/core/cart/get-cart.mjs +33 -0
- package/dist/core/cart/get-cart.mjs.map +1 -1
- package/dist/core/cart/index.d.mts +1 -1
- package/dist/core/cart/quantity.d.mts +17 -0
- package/dist/core/cart/quantity.mjs +17 -0
- package/dist/core/cart/quantity.mjs.map +1 -1
- package/dist/core/cart/queries.d.mts +402 -369
- package/dist/core/cart/queries.mjs +2 -1
- package/dist/core/cart/queries.mjs.map +1 -1
- package/dist/core/cart/server-handlers.d.mts +69 -0
- package/dist/core/cart/server-handlers.mjs +2 -10
- package/dist/core/cart/server-handlers.mjs.map +1 -1
- package/dist/core/cart/state.d.mts +133 -0
- package/dist/core/cart/state.mjs +8 -0
- package/dist/core/cart/state.mjs.map +1 -1
- package/dist/core/collection/collection.d.mts +18 -2
- package/dist/core/collection/collection.mjs +11 -0
- package/dist/core/collection/collection.mjs.map +1 -1
- package/dist/core/collection/index.d.mts +1 -1
- package/dist/core/collection/reconciler.d.mts +13 -4
- package/dist/core/collection/reconciler.mjs.map +1 -1
- package/dist/core/collection/state.d.mts +7 -2
- package/dist/core/collection/state.mjs.map +1 -1
- package/dist/core/collection/url.d.mts +3 -0
- package/dist/core/collection/url.mjs.map +1 -1
- package/dist/core/constants.d.mts +1 -1
- package/dist/core/constants.mjs +2 -2
- package/dist/core/constants.mjs.map +1 -1
- package/dist/core/headers.mjs +2 -2
- package/dist/core/headers.mjs.map +1 -1
- package/dist/core/index.d.mts +5 -5
- package/dist/core/logging/logging.mjs.map +1 -1
- package/dist/core/money/format.mjs +6 -3
- package/dist/core/money/format.mjs.map +1 -1
- package/dist/core/observable.mjs +0 -3
- package/dist/core/observable.mjs.map +1 -1
- package/dist/core/predictive-search/form.d.mts +1 -0
- package/dist/core/predictive-search/form.mjs +1 -0
- package/dist/core/predictive-search/form.mjs.map +1 -1
- package/dist/core/predictive-search/index.d.mts +1 -1
- package/dist/core/predictive-search/queries.d.mts +40 -4
- package/dist/core/predictive-search/queries.mjs +7 -1
- package/dist/core/predictive-search/queries.mjs.map +1 -1
- package/dist/core/predictive-search/search.d.mts +43 -10
- package/dist/core/predictive-search/search.mjs +34 -0
- package/dist/core/predictive-search/search.mjs.map +1 -1
- package/dist/core/predictive-search/server-handlers.d.mts +31 -5
- package/dist/core/predictive-search/server-handlers.mjs +5 -3
- package/dist/core/predictive-search/server-handlers.mjs.map +1 -1
- package/dist/core/predictive-search/store.d.mts +3 -1
- package/dist/core/predictive-search/store.mjs.map +1 -1
- package/dist/core/predictive-search/url.d.mts +50 -8
- package/dist/core/predictive-search/url.mjs +9 -0
- package/dist/core/predictive-search/url.mjs.map +1 -1
- package/dist/core/product/form.d.mts +21 -2
- package/dist/core/product/form.mjs +13 -0
- package/dist/core/product/form.mjs.map +1 -1
- package/dist/core/product/index.d.mts +1 -1
- package/dist/core/product/options.d.mts +2 -2
- package/dist/core/product/options.mjs +2 -2
- package/dist/core/product/options.mjs.map +1 -1
- package/dist/core/product/product-form.d.mts +33 -1
- package/dist/core/product/product-form.mjs +30 -0
- package/dist/core/product/product-form.mjs.map +1 -1
- package/dist/core/product/state.d.mts +41 -0
- package/dist/core/product/url.d.mts +6 -0
- package/dist/core/product/url.mjs.map +1 -1
- package/dist/core/request-context.d.mts +8 -5
- package/dist/core/request-context.mjs +17 -39
- package/dist/core/request-context.mjs.map +1 -1
- package/dist/core/request-routing/handle-shopify-routes.mjs +4 -1
- package/dist/core/request-routing/handle-shopify-routes.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/ajax-api.mjs +1 -1
- package/dist/core/request-routing/interceptors/api-proxy.mjs +1 -1
- package/dist/core/request-routing/interceptors/checkout.mjs +22 -6
- package/dist/core/request-routing/interceptors/checkout.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
- package/dist/core/request-routing/interceptors/proxy.mjs +36 -10
- package/dist/core/request-routing/interceptors/proxy.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
- package/dist/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
- package/dist/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
- package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
- package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
- package/dist/core/request-routing/interceptors/well-known.mjs +1 -1
- package/dist/core/shopify-scripts/analytics.mjs +2 -2
- package/dist/core/shopify-scripts/analytics.mjs.map +1 -1
- package/dist/core/shopify-scripts/constants.mjs +4 -2
- package/dist/core/shopify-scripts/constants.mjs.map +1 -1
- package/dist/core/shopify-scripts/global.mjs +9 -4
- package/dist/core/shopify-scripts/global.mjs.map +1 -1
- package/dist/core/shopify-scripts/index.d.mts +1 -0
- package/dist/core/shopify-scripts/index.mjs +14 -12
- package/dist/core/shopify-scripts/index.mjs.map +1 -1
- package/dist/core/shopify-scripts/initialize.d.mts +1 -0
- package/dist/core/shopify-scripts/initialize.mjs +10 -3
- package/dist/core/shopify-scripts/initialize.mjs.map +1 -1
- package/dist/core/shopify-scripts/types.d.mts +6 -0
- package/dist/core/url.mjs +21 -2
- package/dist/core/url.mjs.map +1 -1
- package/dist/customer-account/client.d.mts +128 -4
- package/dist/customer-account/client.mjs +27 -1
- package/dist/customer-account/client.mjs.map +1 -1
- package/dist/customer-account/errors.d.mts +46 -0
- package/dist/customer-account/errors.mjs +46 -0
- package/dist/customer-account/errors.mjs.map +1 -1
- package/dist/customer-account/graphql.d.mts +68 -0
- package/dist/customer-account/graphql.mjs +47 -0
- package/dist/customer-account/graphql.mjs.map +1 -1
- package/dist/customer-account/session.d.mts +114 -23
- package/dist/customer-account/session.mjs +27 -12
- package/dist/customer-account/session.mjs.map +1 -1
- package/dist/customer-account/type-resolver.d.mts +23 -0
- package/dist/customer-account-api-types.d.ts +570 -374
- package/dist/customer-account.schema.json +2981 -1527
- package/dist/development/client/client.mjs +2 -2
- package/dist/development/client/errors.d.mts +19 -19
- package/dist/development/client/errors.mjs +20 -20
- package/dist/development/client/errors.mjs.map +1 -1
- package/dist/development/client/types.d.mts +91 -10
- package/dist/development/core/analytics/cart-tracker.d.mts +14 -0
- package/dist/development/core/analytics/cart-tracker.mjs +15 -1
- package/dist/development/core/analytics/cart-tracker.mjs.map +1 -1
- package/dist/development/core/analytics/custom-consent.mjs +12 -0
- package/dist/development/core/analytics/custom-consent.mjs.map +1 -0
- package/dist/development/core/analytics/events.d.mts +7 -0
- package/dist/development/core/analytics/events.mjs +7 -0
- package/dist/development/core/analytics/events.mjs.map +1 -1
- package/dist/development/core/analytics/index.d.mts +1 -1
- package/dist/development/core/analytics/types.d.mts +152 -16
- package/dist/development/core/cart/actions.d.mts +47 -3
- package/dist/development/core/cart/actions.mjs +29 -0
- package/dist/development/core/cart/actions.mjs.map +1 -1
- package/dist/development/core/cart/attach-quantity-input.d.mts +26 -0
- package/dist/development/core/cart/attach-quantity-input.mjs +26 -0
- package/dist/development/core/cart/attach-quantity-input.mjs.map +1 -1
- package/dist/development/core/cart/cart.d.mts +106 -6
- package/dist/development/core/cart/cart.mjs +57 -6
- package/dist/development/core/cart/cart.mjs.map +1 -1
- package/dist/development/core/cart/cookie.d.mts +15 -0
- package/dist/development/core/cart/cookie.mjs +15 -0
- package/dist/development/core/cart/cookie.mjs.map +1 -1
- package/dist/development/core/cart/form.d.mts +61 -0
- package/dist/development/core/cart/form.mjs +18 -0
- package/dist/development/core/cart/form.mjs.map +1 -1
- package/dist/development/core/cart/get-cart.d.mts +43 -2
- package/dist/development/core/cart/get-cart.mjs +33 -0
- package/dist/development/core/cart/get-cart.mjs.map +1 -1
- package/dist/development/core/cart/index.d.mts +1 -1
- package/dist/development/core/cart/quantity.d.mts +17 -0
- package/dist/development/core/cart/quantity.mjs +17 -0
- package/dist/development/core/cart/quantity.mjs.map +1 -1
- package/dist/development/core/cart/queries.d.mts +345 -312
- package/dist/development/core/cart/queries.mjs +2 -1
- package/dist/development/core/cart/queries.mjs.map +1 -1
- package/dist/development/core/cart/server-handlers.d.mts +69 -0
- package/dist/development/core/cart/server-handlers.mjs +2 -10
- package/dist/development/core/cart/server-handlers.mjs.map +1 -1
- package/dist/development/core/cart/state.d.mts +133 -0
- package/dist/development/core/cart/state.mjs +8 -0
- package/dist/development/core/cart/state.mjs.map +1 -1
- package/dist/development/core/collection/collection.d.mts +18 -2
- package/dist/development/core/collection/collection.mjs +11 -0
- package/dist/development/core/collection/collection.mjs.map +1 -1
- package/dist/development/core/collection/index.d.mts +1 -1
- package/dist/development/core/collection/reconciler.d.mts +13 -4
- package/dist/development/core/collection/reconciler.mjs.map +1 -1
- package/dist/development/core/collection/state.d.mts +7 -2
- package/dist/development/core/collection/state.mjs.map +1 -1
- package/dist/development/core/collection/url.d.mts +3 -0
- package/dist/development/core/collection/url.mjs.map +1 -1
- package/dist/development/core/constants.mjs +1 -1
- package/dist/development/core/constants.mjs.map +1 -1
- package/dist/development/core/development.d.mts +5 -5
- package/dist/development/core/headers.mjs +2 -2
- package/dist/development/core/headers.mjs.map +1 -1
- package/dist/development/core/index.d.mts +4 -4
- package/dist/development/core/logging/logging.mjs.map +1 -1
- package/dist/development/core/money/format.mjs +6 -3
- package/dist/development/core/money/format.mjs.map +1 -1
- package/dist/development/core/observable.mjs +0 -3
- package/dist/development/core/observable.mjs.map +1 -1
- package/dist/development/core/predictive-search/form.d.mts +1 -0
- package/dist/development/core/predictive-search/form.mjs +1 -0
- package/dist/development/core/predictive-search/form.mjs.map +1 -1
- package/dist/development/core/predictive-search/index.d.mts +1 -1
- package/dist/development/core/predictive-search/queries.d.mts +38 -2
- package/dist/development/core/predictive-search/queries.mjs +7 -1
- package/dist/development/core/predictive-search/queries.mjs.map +1 -1
- package/dist/development/core/predictive-search/search.d.mts +43 -10
- package/dist/development/core/predictive-search/search.mjs +34 -0
- package/dist/development/core/predictive-search/search.mjs.map +1 -1
- package/dist/development/core/predictive-search/server-handlers.d.mts +31 -5
- package/dist/development/core/predictive-search/server-handlers.mjs +5 -3
- package/dist/development/core/predictive-search/server-handlers.mjs.map +1 -1
- package/dist/development/core/predictive-search/store.d.mts +3 -1
- package/dist/development/core/predictive-search/store.mjs.map +1 -1
- package/dist/development/core/predictive-search/url.d.mts +50 -8
- package/dist/development/core/predictive-search/url.mjs +9 -0
- package/dist/development/core/predictive-search/url.mjs.map +1 -1
- package/dist/development/core/product/form.d.mts +21 -2
- package/dist/development/core/product/form.mjs +13 -0
- package/dist/development/core/product/form.mjs.map +1 -1
- package/dist/development/core/product/index.d.mts +1 -1
- package/dist/development/core/product/options.d.mts +2 -2
- package/dist/development/core/product/options.mjs +2 -2
- package/dist/development/core/product/options.mjs.map +1 -1
- package/dist/development/core/product/product-form.d.mts +33 -1
- package/dist/development/core/product/product-form.mjs +30 -0
- package/dist/development/core/product/product-form.mjs.map +1 -1
- package/dist/development/core/product/state.d.mts +41 -0
- package/dist/development/core/product/url.d.mts +6 -0
- package/dist/development/core/product/url.mjs.map +1 -1
- package/dist/development/core/request-context.d.mts +8 -5
- package/dist/development/core/request-context.mjs +17 -39
- package/dist/development/core/request-context.mjs.map +1 -1
- package/dist/development/core/request-routing/handle-shopify-routes.mjs +4 -1
- package/dist/development/core/request-routing/handle-shopify-routes.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/ajax-api.mjs +1 -1
- package/dist/development/core/request-routing/interceptors/api-proxy.mjs +1 -1
- package/dist/development/core/request-routing/interceptors/checkout.mjs +22 -6
- package/dist/development/core/request-routing/interceptors/checkout.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
- package/dist/development/core/request-routing/interceptors/proxy.mjs +36 -10
- package/dist/development/core/request-routing/interceptors/proxy.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
- package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
- package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
- package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
- package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
- package/dist/development/core/request-routing/interceptors/well-known.mjs +1 -1
- package/dist/development/core/shopify-scripts/analytics.mjs +2 -2
- package/dist/development/core/shopify-scripts/analytics.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/constants.mjs +4 -2
- package/dist/development/core/shopify-scripts/constants.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/global.mjs +9 -4
- package/dist/development/core/shopify-scripts/global.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/index.d.mts +1 -0
- package/dist/development/core/shopify-scripts/index.mjs +14 -12
- package/dist/development/core/shopify-scripts/index.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/initialize.d.mts +1 -0
- package/dist/development/core/shopify-scripts/initialize.mjs +10 -3
- package/dist/development/core/shopify-scripts/initialize.mjs.map +1 -1
- package/dist/development/core/shopify-scripts/types.d.mts +6 -0
- package/dist/development/core/url.mjs +21 -2
- package/dist/development/core/url.mjs.map +1 -1
- package/dist/development/customer-account/session.d.mts +69 -13
- package/dist/development/customer-account/session.mjs +4 -0
- package/dist/development/customer-account/session.mjs.map +1 -1
- package/dist/development/globals.d.mts +6 -5
- package/dist/development/graphql/generated/graphql-env.d.mts +459 -16
- package/dist/development/graphql/generated/storefront-api-types.d.mts +1 -1
- package/dist/development/graphql/graphql.d.mts +33 -0
- package/dist/development/graphql/graphql.mjs +30 -0
- package/dist/development/graphql/graphql.mjs.map +1 -1
- package/dist/development/graphql/type-resolver.d.mts +14 -0
- package/dist/development/react/cart.d.mts +85 -14
- package/dist/development/react/cart.mjs +123 -3
- package/dist/development/react/cart.mjs.map +1 -1
- package/dist/development/react/collection.d.mts +2 -4
- package/dist/development/react/collection.mjs.map +1 -1
- package/dist/development/react/index.d.mts +8 -6
- package/dist/development/react/index.mjs +2 -2
- package/dist/development/react/predictive-search.d.mts +68 -7
- package/dist/development/react/predictive-search.mjs +54 -12
- package/dist/development/react/predictive-search.mjs.map +1 -1
- package/dist/development/react/product.d.mts +1 -0
- package/dist/development/react/product.mjs +5 -2
- package/dist/development/react/product.mjs.map +1 -1
- package/dist/development/react/shopify-scripts.mjs +1 -0
- package/dist/development/react/shopify-scripts.mjs.map +1 -1
- package/dist/development/vue/cart.d.mts +85 -6
- package/dist/development/vue/cart.mjs +95 -0
- package/dist/development/vue/cart.mjs.map +1 -1
- package/dist/development/vue/collection.d.mts +83 -5
- package/dist/development/vue/collection.mjs +52 -0
- package/dist/development/vue/collection.mjs.map +1 -1
- package/dist/development/vue/index.d.mts +6 -4
- package/dist/development/vue/predictive-search.d.mts +53 -6
- package/dist/development/vue/predictive-search.mjs +33 -0
- package/dist/development/vue/predictive-search.mjs.map +1 -1
- package/dist/development/vue/product.d.mts +6 -2
- package/dist/development/vue/product.mjs.map +1 -1
- package/dist/development/vue/shopify-scripts.d.mts +30 -3
- package/dist/development/vue/shopify-scripts.mjs +6 -0
- package/dist/development/vue/shopify-scripts.mjs.map +1 -1
- package/dist/globals.d.mts +6 -5
- package/dist/graphql/generated/customer-account-graphql-env.d.mts +1717 -1090
- package/dist/graphql/generated/graphql-env.d.mts +459 -16
- package/dist/graphql/generated/storefront-api-types.d.mts +1 -1
- package/dist/graphql/graphql.d.mts +33 -0
- package/dist/graphql/graphql.mjs +30 -0
- package/dist/graphql/graphql.mjs.map +1 -1
- package/dist/graphql/type-resolver.d.mts +14 -0
- package/dist/react/cart.d.mts +85 -14
- package/dist/react/cart.mjs +123 -3
- package/dist/react/cart.mjs.map +1 -1
- package/dist/react/collection.d.mts +2 -4
- package/dist/react/collection.mjs.map +1 -1
- package/dist/react/index.d.mts +8 -6
- package/dist/react/index.mjs +2 -2
- package/dist/react/predictive-search.d.mts +68 -7
- package/dist/react/predictive-search.mjs +54 -12
- package/dist/react/predictive-search.mjs.map +1 -1
- package/dist/react/product.d.mts +1 -0
- package/dist/react/product.mjs +5 -2
- package/dist/react/product.mjs.map +1 -1
- package/dist/react/shopify-scripts.mjs +1 -0
- package/dist/react/shopify-scripts.mjs.map +1 -1
- package/dist/storefront-api-types.d.ts +165 -20
- package/dist/storefront.schema.json +782 -57
- package/dist/vite/customer-account.mjs +191 -0
- package/dist/vite/customer-account.mjs.map +1 -0
- package/dist/vite/local-https.mjs +13 -29
- package/dist/vite/local-https.mjs.map +1 -1
- package/dist/vite/mkcert.mjs +1 -1
- package/dist/vue/cart.d.mts +85 -6
- package/dist/vue/cart.mjs +95 -0
- package/dist/vue/cart.mjs.map +1 -1
- package/dist/vue/collection.d.mts +83 -5
- package/dist/vue/collection.mjs +52 -0
- package/dist/vue/collection.mjs.map +1 -1
- package/dist/vue/index.d.mts +6 -4
- package/dist/vue/predictive-search.d.mts +53 -6
- package/dist/vue/predictive-search.mjs +33 -0
- package/dist/vue/predictive-search.mjs.map +1 -1
- package/dist/vue/product.d.mts +6 -2
- package/dist/vue/product.mjs.map +1 -1
- package/dist/vue/shopify-scripts.d.mts +31 -4
- package/dist/vue/shopify-scripts.mjs +6 -0
- package/dist/vue/shopify-scripts.mjs.map +1 -1
- package/package.json +5 -2
- package/skills/hydrogen-analytics/SKILL.md +6 -5
- package/skills/hydrogen-analytics/references/custom-consent.md +52 -0
- package/skills/hydrogen-analytics/references/react.md +1 -1
- package/skills/hydrogen-cart-metafields/SKILL.md +133 -0
- package/skills/hydrogen-cart-ui/SKILL.md +2 -1
- package/skills/hydrogen-customer-account/SKILL.md +3 -3
- package/skills/hydrogen-local-https/SKILL.md +22 -7
- package/skills/hydrogen-request-handlers/SKILL.md +5 -5
- package/skills/hydrogen-request-handlers/references/nuxt.md +0 -6
- package/skills/hydrogen-setup/references/analytics.md +24 -23
- package/skills/hydrogen-setup/steps/1-inspect-app.md +1 -1
- package/skills/hydrogen-setup/steps/11-verify.md +1 -0
- package/skills/hydrogen-setup/steps/2-scaffold.md +22 -1
- package/skills/hydrogen-smoke-test/SKILL.md +2 -2
- package/skills/hydrogen-storefront-client/SKILL.md +4 -2
- package/skills/hydrogen-variant-form/SKILL.md +10 -4
- package/ts-plugin/package.json +5 -0
- package/dist/core/shopify-scripts/consent.mjs +0 -17
- package/dist/core/shopify-scripts/consent.mjs.map +0 -1
- package/dist/core/shopify-scripts/deprecated-cookies.mjs +0 -97
- package/dist/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
- package/dist/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
- package/dist/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
- package/dist/core/shopify-scripts/utils/tracking-values.mjs +0 -65
- package/dist/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
- package/dist/core/shopify-scripts/utils/uuid.mjs +0 -17
- package/dist/core/shopify-scripts/utils/uuid.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/consent.mjs +0 -17
- package/dist/development/core/shopify-scripts/consent.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/deprecated-cookies.mjs +0 -97
- package/dist/development/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
- package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/utils/tracking-values.mjs +0 -65
- package/dist/development/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
- package/dist/development/core/shopify-scripts/utils/uuid.mjs +0 -17
- 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
|
|
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
|
|
208
|
+
const configureCustomerAccounts = () => {
|
|
213
209
|
const port = resolveBoundPort();
|
|
214
|
-
const settingsKey = `${settings.host}:${port}`;
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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",
|
|
223
|
-
else
|
|
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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"}
|
package/dist/vite/mkcert.mjs
CHANGED
|
@@ -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");
|
package/dist/vue/cart.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CartData, CartState } from "../core/cart/state.mjs";
|
|
2
|
-
import {
|
|
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:
|
|
35
|
-
afterSubmit?: (e:
|
|
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 {
|
|
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) => {
|