@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
package/dist/cli/index.mjs
CHANGED
|
@@ -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/
|
|
448
|
-
|
|
449
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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 (
|
|
1040
|
+
if (hasHydrogenDependency(packageJson) && getInstalledPackageRoot(appRoot)) return;
|
|
1041
|
+
if (hasHydrogenDependency(packageJson)) {
|
|
596
1042
|
const packageManager = detectPackageManager(appRoot, packageJson, env);
|
|
597
|
-
log(
|
|
598
|
-
await
|
|
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
|
|
601
|
-
log(`
|
|
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 (
|
|
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"],
|