@vtex/faststore 0.0.7 → 0.0.8
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/CHANGELOG.md +43 -0
- package/dist/actions.json +152 -1
- package/dist/catalog.json +1264 -142
- package/dist/cli/index.d.ts +512 -63
- package/dist/cli/index.js +2098 -686
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/server-runtime.js +44 -24
- package/dist/cli/server-runtime.js.map +1 -1
- package/dist/client.d.ts +5 -1
- package/dist/client.js +5 -1
- package/dist/components.d.ts +34 -7
- package/dist/components.js +38 -11
- package/dist/content-types.json +523 -0
- package/dist/core/dist/define-component.d.ts +6 -1
- package/dist/docs/account-access-gate.md +4 -4
- package/dist/docs/account-address-book.md +519 -0
- package/dist/docs/account-entry-point.md +1 -1
- package/dist/docs/account-menu.md +5 -5
- package/dist/docs/alert-dialog.md +2 -2
- package/dist/docs/billing-address.md +17 -9
- package/dist/docs/card-entry.md +37 -6
- package/dist/docs/cardholder-document.md +16 -14
- package/dist/docs/cart-item.md +1 -1
- package/dist/docs/cart.md +8 -8
- package/dist/docs/client-profile-fieldset.md +7 -5
- package/dist/docs/copy-text.md +84 -0
- package/dist/docs/document-field.md +8 -8
- package/dist/docs/footer-link-group.md +2 -2
- package/dist/docs/header-root.md +1 -1
- package/dist/docs/incentives.md +2 -2
- package/dist/docs/logo.md +5 -5
- package/dist/docs/my-account-profile.md +4 -4
- package/dist/docs/navigation-link-list.md +2 -2
- package/dist/docs/navigation-link.md +3 -3
- package/dist/docs/order-cancel.md +52 -0
- package/dist/docs/order-delivery-address.md +63 -0
- package/dist/docs/order-delivery-group.md +83 -0
- package/dist/docs/order-details-back.md +41 -0
- package/dist/docs/order-details-header.md +48 -0
- package/dist/docs/order-list-row.md +46 -0
- package/dist/docs/order-list-search.md +101 -0
- package/dist/docs/order-list-views.md +97 -0
- package/dist/docs/order-list.md +94 -0
- package/dist/docs/order-payment.md +94 -0
- package/dist/docs/order-placed-date.md +49 -0
- package/dist/docs/order-placed-payment-summary.md +13 -15
- package/dist/docs/order-placed.md +225 -0
- package/dist/docs/order-progress.md +88 -0
- package/dist/docs/order-status-badge.md +91 -0
- package/dist/docs/order-summary.md +53 -0
- package/dist/docs/payment-method-badges.md +14 -3
- package/dist/docs/place-order.md +13 -9
- package/dist/docs/saved-cards.md +2 -2
- package/dist/docs/select.md +19 -6
- package/dist/docs/session.md +1 -1
- package/dist/docs/shipping-address.md +1 -1
- package/dist/docs/sign-in-redirect.md +6 -6
- package/dist/docs/toast.md +7 -3
- package/dist/index.d.ts +6 -2
- package/dist/index.js +2 -1
- package/dist/internal/client.d.ts +6 -1
- package/dist/internal/client.js +7 -2
- package/dist/internal/server.d.ts +6 -2
- package/dist/internal/server.js +6 -2
- package/dist/matter/account-access-gate/account-access-gate.d.ts +2 -1
- package/dist/matter/account-access-gate/account-access-gate.js +19 -16
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -1
- package/dist/matter/account-address-book/account-address-book-delete.d.ts +60 -0
- package/dist/matter/account-address-book/account-address-book-delete.js +184 -0
- package/dist/matter/account-address-book/account-address-book-delete.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.d.ts +19 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.js +50 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.d.ts +42 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.js +146 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.d.ts +25 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.js +72 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book.d.ts +120 -0
- package/dist/matter/account-address-book/account-address-book.js +469 -0
- package/dist/matter/account-address-book/account-address-book.js.map +1 -0
- package/dist/matter/account-address-book/account-address-country-switch.js +59 -0
- package/dist/matter/account-address-book/account-address-country-switch.js.map +1 -0
- package/dist/matter/account-address-book/account-address-country.d.ts +36 -0
- package/dist/matter/account-address-book/account-address-country.js +151 -0
- package/dist/matter/account-address-book/account-address-country.js.map +1 -0
- package/dist/matter/account-address-book/account-address-first-line.js +11 -0
- package/dist/matter/account-address-book/account-address-first-line.js.map +1 -0
- package/dist/matter/account-address-book/account-address-form-context.d.ts +55 -0
- package/dist/matter/account-address-book/account-address-form-context.js +14 -0
- package/dist/matter/account-address-book/account-address-form-context.js.map +1 -0
- package/dist/matter/account-address-book/account-address-form.d.ts +85 -0
- package/dist/matter/account-address-book/account-address-form.js +946 -0
- package/dist/matter/account-address-book/account-address-form.js.map +1 -0
- package/dist/matter/account-address-book/account-address-postal-autofill.js +189 -0
- package/dist/matter/account-address-book/account-address-postal-autofill.js.map +1 -0
- package/dist/matter/account-address-book/account-address-record.d.ts +26 -0
- package/dist/matter/account-address-book/account-address-record.js +138 -0
- package/dist/matter/account-address-book/account-address-record.js.map +1 -0
- package/dist/matter/account-entry-point/account-entry-point.js +11 -11
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +7 -3
- package/dist/matter/account-menu/account-menu.js +69 -28
- package/dist/matter/account-menu/account-menu.js.map +1 -1
- package/dist/matter/billing-address/billing-address.d.ts +30 -17
- package/dist/matter/billing-address/billing-address.js +128 -33
- package/dist/matter/billing-address/billing-address.js.map +1 -1
- package/dist/matter/breadcrumb/breadcrumb.js +2 -2
- package/dist/matter/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/matter/card-entry/card-capture.js +4 -3
- package/dist/matter/card-entry/card-capture.js.map +1 -1
- package/dist/matter/card-entry/card-entry.d.ts +5 -3
- package/dist/matter/card-entry/card-entry.js +77 -27
- package/dist/matter/card-entry/card-entry.js.map +1 -1
- package/dist/matter/cardholder-document/cardholder-document.d.ts +10 -9
- package/dist/matter/cardholder-document/cardholder-document.js +36 -16
- package/dist/matter/cardholder-document/cardholder-document.js.map +1 -1
- package/dist/matter/cart/cart.d.ts +7 -2
- package/dist/matter/cart/cart.js +29 -26
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/cart-item/cart-item.js +2 -2
- package/dist/matter/cart-item/cart-item.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences-fieldset.js +1 -1
- package/dist/matter/client-profile-form/client-profile-fieldset.js +8 -5
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -1
- package/dist/matter/identity-edit/identity-edit.js +3 -3
- package/dist/matter/identity-edit/identity-edit.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.js +2 -2
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/my-account-profile/my-account-profile.d.ts +3 -3
- package/dist/matter/my-account-profile/my-account-profile.js +4 -4
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
- package/dist/matter/order-cancel/order-cancel.d.ts +110 -0
- package/dist/matter/order-cancel/order-cancel.js +344 -0
- package/dist/matter/order-cancel/order-cancel.js.map +1 -0
- package/dist/matter/order-delivery-address/order-delivery-address.d.ts +73 -0
- package/dist/matter/order-delivery-address/order-delivery-address.js +172 -0
- package/dist/matter/order-delivery-address/order-delivery-address.js.map +1 -0
- package/dist/matter/order-delivery-group/order-delivery-group.d.ts +128 -0
- package/dist/matter/order-delivery-group/order-delivery-group.js +408 -0
- package/dist/matter/order-delivery-group/order-delivery-group.js.map +1 -0
- package/dist/matter/order-details-header/order-details-header.d.ts +104 -0
- package/dist/matter/order-details-header/order-details-header.js +190 -0
- package/dist/matter/order-details-header/order-details-header.js.map +1 -0
- package/dist/matter/order-list/customer-orders-filter.d.ts +12 -0
- package/dist/matter/order-list/customer-orders-filter.js +30 -0
- package/dist/matter/order-list/customer-orders-filter.js.map +1 -0
- package/dist/matter/order-list/order-list.d.ts +121 -0
- package/dist/matter/order-list/order-list.js +395 -0
- package/dist/matter/order-list/order-list.js.map +1 -0
- package/dist/matter/order-list-row/order-list-row.d.ts +44 -0
- package/dist/matter/order-list-row/order-list-row.js +127 -0
- package/dist/matter/order-list-row/order-list-row.js.map +1 -0
- package/dist/matter/order-list-search/order-list-search.d.ts +72 -0
- package/dist/matter/order-list-search/order-list-search.js +296 -0
- package/dist/matter/order-list-search/order-list-search.js.map +1 -0
- package/dist/matter/order-list-views/order-list-views.d.ts +65 -0
- package/dist/matter/order-list-views/order-list-views.js +154 -0
- package/dist/matter/order-list-views/order-list-views.js.map +1 -0
- package/dist/matter/order-payment/order-payment.d.ts +83 -0
- package/dist/matter/order-payment/order-payment.js +258 -0
- package/dist/matter/order-payment/order-payment.js.map +1 -0
- package/dist/matter/order-placed/order-placed-deliveries.d.ts +145 -0
- package/dist/matter/order-placed/order-placed-deliveries.js +380 -0
- package/dist/matter/order-placed/order-placed-deliveries.js.map +1 -0
- package/dist/matter/order-placed/order-placed.d.ts +122 -0
- package/dist/matter/order-placed/order-placed.js +452 -0
- package/dist/matter/order-placed/order-placed.js.map +1 -0
- package/dist/matter/order-placed-date/order-placed-date.d.ts +45 -0
- package/dist/matter/order-placed-date/order-placed-date.js +92 -0
- package/dist/matter/order-placed-date/order-placed-date.js.map +1 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +5 -8
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +29 -81
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -1
- package/dist/matter/order-progress/order-progress.d.ts +85 -0
- package/dist/matter/order-progress/order-progress.js +203 -0
- package/dist/matter/order-progress/order-progress.js.map +1 -0
- package/dist/matter/order-summary/order-summary.d.ts +81 -0
- package/dist/matter/order-summary/order-summary.js +192 -0
- package/dist/matter/order-summary/order-summary.js.map +1 -0
- package/dist/matter/payment/cardholder-document-rule.d.ts +35 -0
- package/dist/matter/payment/cardholder-document-rule.js +35 -0
- package/dist/matter/payment/cardholder-document-rule.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.js +38 -23
- package/dist/matter/payment/payment-card-capture.js.map +1 -1
- package/dist/matter/payment/payment-controller.js +11 -0
- package/dist/matter/payment/payment-controller.js.map +1 -1
- package/dist/matter/payment/payment-method-badges.js +4 -2
- package/dist/matter/payment/payment-method-badges.js.map +1 -1
- package/dist/matter/payment/payment.js +11 -0
- package/dist/matter/payment/payment.js.map +1 -1
- package/dist/matter/payment/use-payment-method-selection.d.ts +39 -0
- package/dist/matter/payment/use-payment-method-selection.js +30 -2
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -1
- package/dist/matter/payment/use-payment-submit-gate.d.ts +16 -2
- package/dist/matter/payment/use-payment-submit-gate.js +42 -14
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -1
- package/dist/matter/place-order/place-order.d.ts +12 -7
- package/dist/matter/place-order/place-order.js +126 -103
- package/dist/matter/place-order/place-order.js.map +1 -1
- package/dist/matter/pricing/pricing.js +5 -5
- package/dist/matter/pricing/pricing.js.map +1 -1
- package/dist/matter/region/region.js +1 -1
- package/dist/matter/saved-cards/saved-cards.js +26 -15
- package/dist/matter/saved-cards/saved-cards.js.map +1 -1
- package/dist/matter/session/session.js +2 -2
- package/dist/matter/session/session.js.map +1 -1
- package/dist/matter/shipping-address/country-descriptors.d.ts +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +23 -1
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.js +62 -14
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.d.ts +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/matter/sign-in-redirect/sign-in-redirect.js +48 -10
- package/dist/matter/sign-in-redirect/sign-in-redirect.js.map +1 -1
- package/dist/matter/variant-selector/variant-selector.js +2 -2
- package/dist/matter/variant-selector/variant-selector.js.map +1 -1
- package/dist/particles/address-summary/address-summary.js +2 -2
- package/dist/particles/address-summary/address-summary.js.map +1 -1
- package/dist/particles/alert-dialog/alert-dialog.d.ts +5 -1
- package/dist/particles/alert-dialog/alert-dialog.js +5 -2
- package/dist/particles/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/particles/copy-text/copy-text.d.ts +28 -0
- package/dist/particles/copy-text/copy-text.js +140 -0
- package/dist/particles/copy-text/copy-text.js.map +1 -0
- package/dist/particles/document-field/document-field.d.ts +34 -5
- package/dist/particles/document-field/document-field.js +115 -19
- package/dist/particles/document-field/document-field.js.map +1 -1
- package/dist/particles/field/field.js +1 -1
- package/dist/particles/field/field.js.map +1 -1
- package/dist/particles/footer-link-group/footer-link-group.d.ts +6 -2
- package/dist/particles/footer-link-group/footer-link-group.js +18 -7
- package/dist/particles/footer-link-group/footer-link-group.js.map +1 -1
- package/dist/particles/incentives/incentives.d.ts +6 -2
- package/dist/particles/incentives/incentives.js +7 -2
- package/dist/particles/incentives/incentives.js.map +1 -1
- package/dist/particles/logo/logo.d.ts +3 -2
- package/dist/particles/logo/logo.js +20 -8
- package/dist/particles/logo/logo.js.map +1 -1
- package/dist/particles/navigation-link/navigation-link.d.ts +2 -3
- package/dist/particles/navigation-link/navigation-link.js +4 -4
- package/dist/particles/navigation-link/navigation-link.js.map +1 -1
- package/dist/particles/navigation-link-list/navigation-link-list.d.ts +6 -2
- package/dist/particles/navigation-link-list/navigation-link-list.js +18 -7
- package/dist/particles/navigation-link-list/navigation-link-list.js.map +1 -1
- package/dist/particles/order-details-back/order-details-back.d.ts +28 -0
- package/dist/particles/order-details-back/order-details-back.js +52 -0
- package/dist/particles/order-details-back/order-details-back.js.map +1 -0
- package/dist/particles/order-status-badge/order-status-badge.d.ts +19 -0
- package/dist/particles/order-status-badge/order-status-badge.js +50 -0
- package/dist/particles/order-status-badge/order-status-badge.js.map +1 -0
- package/dist/particles/phone-field/phone-field.js +2 -2
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/select/select.js +6 -6
- package/dist/particles/select/select.js.map +1 -1
- package/dist/particles/toast/toast.d.ts +1 -1
- package/dist/particles/toast/toast.js +32 -6
- package/dist/particles/toast/toast.js.map +1 -1
- package/dist/schema.graphql +1014 -28
- package/dist/sdk/actions/address-action-provider.d.ts +16 -0
- package/dist/sdk/actions/address-action-provider.js +20 -0
- package/dist/sdk/actions/address-action-provider.js.map +1 -0
- package/dist/sdk/actions/analytics-action-provider.js +1 -1
- package/dist/sdk/actions/index.d.ts +3 -1
- package/dist/sdk/actions/index.js +3 -1
- package/dist/sdk/actions/order-action-provider.d.ts +12 -0
- package/dist/sdk/actions/order-action-provider.js +16 -0
- package/dist/sdk/actions/order-action-provider.js.map +1 -0
- package/dist/sdk/actions/types.d.ts +6 -2
- package/dist/sdk/address/address-fields.js +38 -0
- package/dist/sdk/address/address-fields.js.map +1 -0
- package/dist/sdk/address/checkout-readiness.js +17 -0
- package/dist/sdk/address/checkout-readiness.js.map +1 -0
- package/dist/sdk/address/constants.js +60 -0
- package/dist/sdk/address/constants.js.map +1 -0
- package/dist/sdk/address/country-display.js +19 -0
- package/dist/sdk/address/country-display.js.map +1 -0
- package/dist/sdk/address/country-rules.js +30 -0
- package/dist/sdk/address/country-rules.js.map +1 -0
- package/dist/sdk/address/format-address.js +18 -0
- package/dist/sdk/address/format-address.js.map +1 -0
- package/dist/sdk/address/iso-countries.js +262 -0
- package/dist/sdk/address/iso-countries.js.map +1 -0
- package/dist/sdk/address/layouts.js +56 -0
- package/dist/sdk/address/layouts.js.map +1 -0
- package/dist/sdk/address/messages.js +97 -0
- package/dist/sdk/address/messages.js.map +1 -0
- package/dist/sdk/address/operations.d.ts +54 -0
- package/dist/sdk/address/operations.js +83 -0
- package/dist/sdk/address/operations.js.map +1 -0
- package/dist/sdk/address/postal-code-lookup-operations.js +23 -0
- package/dist/sdk/address/postal-code-lookup-operations.js.map +1 -0
- package/dist/sdk/address/postal-code.js +11 -0
- package/dist/sdk/address/postal-code.js.map +1 -0
- package/dist/sdk/address/schema.js +14 -0
- package/dist/sdk/address/schema.js.map +1 -0
- package/dist/sdk/address/server-fields.js +25 -0
- package/dist/sdk/address/server-fields.js.map +1 -0
- package/dist/sdk/address/switch-country.js +57 -0
- package/dist/sdk/address/switch-country.js.map +1 -0
- package/dist/sdk/address/types.d.ts +26 -0
- package/dist/sdk/address/use-address-actions.d.ts +29 -0
- package/dist/sdk/address/use-address-actions.js +247 -0
- package/dist/sdk/address/use-address-actions.js.map +1 -0
- package/dist/sdk/address/use-address-by-postal-code.js +57 -0
- package/dist/sdk/address/use-address-by-postal-code.js.map +1 -0
- package/dist/sdk/address/validate-address.js +62 -0
- package/dist/sdk/address/validate-address.js.map +1 -0
- package/dist/sdk/cache/storefront-cache.js +29 -4
- package/dist/sdk/cache/storefront-cache.js.map +1 -1
- package/dist/sdk/client/fetch-page-data.d.ts +11 -0
- package/dist/sdk/client/fetch-page-data.js +12 -1
- package/dist/sdk/client/fetch-page-data.js.map +1 -1
- package/dist/sdk/content/create-content-runtime.js +1 -8
- package/dist/sdk/content/create-content-runtime.js.map +1 -1
- package/dist/sdk/data/failed-fields.js +23 -0
- package/dist/sdk/data/failed-fields.js.map +1 -0
- package/dist/sdk/data/index.js +2 -2
- package/dist/sdk/data/page-reactive-data-root.d.ts +45 -7
- package/dist/sdk/data/page-reactive-data-root.js +88 -12
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +2 -2
- package/dist/sdk/generated/fragment-types.js +2 -0
- package/dist/sdk/generated/fragment-types.js.map +1 -1
- package/dist/sdk/generated/gql.js +3 -1
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.d.ts +8 -0
- package/dist/sdk/generated/graphql.js +212 -1
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +138 -1
- package/dist/sdk/i18n/messages/en-US.json +138 -1
- package/dist/sdk/i18n/messages/en-US.json.d.ts +137 -0
- package/dist/sdk/i18n/messages/es-ES.js +138 -1
- package/dist/sdk/i18n/messages/es-ES.json +138 -1
- package/dist/sdk/i18n/messages/pt-BR.js +138 -1
- package/dist/sdk/i18n/messages/pt-BR.json +138 -1
- package/dist/sdk/navigation/current-page.js +30 -0
- package/dist/sdk/navigation/current-page.js.map +1 -0
- package/dist/sdk/navigation/not-found-log.d.ts +28 -0
- package/dist/sdk/navigation/not-found-log.js +133 -0
- package/dist/sdk/navigation/not-found-log.js.map +1 -0
- package/dist/sdk/navigation/path.js +25 -0
- package/dist/sdk/navigation/path.js.map +1 -0
- package/dist/sdk/navigation/redirect.d.ts +11 -0
- package/dist/sdk/navigation/redirect.js +22 -0
- package/dist/sdk/navigation/redirect.js.map +1 -0
- package/dist/sdk/navigation/report.js +70 -0
- package/dist/sdk/navigation/report.js.map +1 -0
- package/dist/sdk/navigation/resolve-storefront-href.d.ts +12 -0
- package/dist/sdk/navigation/resolve-storefront-href.js +214 -0
- package/dist/sdk/navigation/resolve-storefront-href.js.map +1 -0
- package/dist/sdk/navigation/route-identity-provider.d.ts +11 -0
- package/dist/sdk/navigation/route-identity-provider.js +20 -0
- package/dist/sdk/navigation/route-identity-provider.js.map +1 -0
- package/dist/sdk/navigation/route-map-provider.d.ts +19 -0
- package/dist/sdk/navigation/route-map-provider.js +29 -0
- package/dist/sdk/navigation/route-map-provider.js.map +1 -0
- package/dist/sdk/navigation/storefront-link.d.ts +16 -0
- package/dist/sdk/navigation/storefront-link.js +66 -0
- package/dist/sdk/navigation/storefront-link.js.map +1 -0
- package/dist/sdk/navigation/types.d.ts +45 -0
- package/dist/sdk/navigation/use-storefront-navigation.d.ts +26 -0
- package/dist/sdk/navigation/use-storefront-navigation.js +73 -0
- package/dist/sdk/navigation/use-storefront-navigation.js.map +1 -0
- package/dist/sdk/navigation/use-storefront-route.d.ts +26 -0
- package/dist/sdk/navigation/use-storefront-route.js +87 -0
- package/dist/sdk/navigation/use-storefront-route.js.map +1 -0
- package/dist/sdk/navigation/use-storefront-search-params.d.ts +11 -0
- package/dist/sdk/navigation/use-storefront-search-params.js +16 -0
- package/dist/sdk/navigation/use-storefront-search-params.js.map +1 -0
- package/dist/sdk/orders/completion-badge.d.ts +8 -0
- package/dist/sdk/orders/completion-badge.js +47 -0
- package/dist/sdk/orders/completion-badge.js.map +1 -0
- package/dist/sdk/orders/order-operations.d.ts +19 -0
- package/dist/sdk/orders/order-operations.js +21 -0
- package/dist/sdk/orders/order-operations.js.map +1 -0
- package/dist/sdk/orders/use-order-actions.d.ts +23 -0
- package/dist/sdk/orders/use-order-actions.js +62 -0
- package/dist/sdk/orders/use-order-actions.js.map +1 -0
- package/dist/sdk/payment/gateway-origin.js +26 -13
- package/dist/sdk/payment/gateway-origin.js.map +1 -1
- package/dist/sdk/payment/gateway.d.ts +14 -2
- package/dist/sdk/payment/gateway.js +32 -14
- package/dist/sdk/payment/gateway.js.map +1 -1
- package/dist/sdk/payment/operations.js +11 -0
- package/dist/sdk/payment/operations.js.map +1 -1
- package/dist/sdk/payment/submission-registry.d.ts +2 -1
- package/dist/sdk/payment/submission-registry.js +12 -8
- package/dist/sdk/payment/submission-registry.js.map +1 -1
- package/dist/sdk/payment/use-payment-actions.d.ts +5 -3
- package/dist/sdk/payment/use-payment-actions.js +5 -2
- package/dist/sdk/payment/use-payment-actions.js.map +1 -1
- package/dist/sdk/payment/use-place-order.d.ts +6 -3
- package/dist/sdk/payment/use-place-order.js +333 -143
- package/dist/sdk/payment/use-place-order.js.map +1 -1
- package/dist/sdk/seo/page-alternates.d.ts +55 -0
- package/dist/sdk/seo/page-alternates.js +132 -0
- package/dist/sdk/seo/page-alternates.js.map +1 -0
- package/dist/utils/own-value.js +19 -0
- package/dist/utils/own-value.js.map +1 -0
- package/dist/utils/safe-href.d.ts +4 -4
- package/dist/utils/safe-href.js +31 -22
- package/dist/utils/safe-href.js.map +1 -1
- package/package.json +5 -1
- package/skills/storefront-asset/SKILL.md +111 -0
- package/skills/storefront-asset/references/api-side.md +153 -0
- package/skills/storefront-asset/references/findings.md +40 -0
- package/skills/storefront-asset/references/ui-side.md +94 -0
- package/src/styles/tailwind.css +27 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { PageRef } from "../sdk/navigation/types.js";
|
|
1
2
|
//#region src/utils/safe-href.d.ts
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* The sign-in page, carrying the current logical path so the shopper can be returned where they
|
|
4
5
|
* started. The sender's half only: nothing stops a hand-crafted link, so the sign-in route is to narrow
|
|
5
6
|
* the parameter again on arrival. A path the narrowing refuses drops the parameter instead of
|
|
6
|
-
* travelling encoded, and so does a router reporting no path
|
|
7
|
-
* a nullable one.
|
|
7
|
+
* travelling encoded, and so does a router reporting no path.
|
|
8
8
|
*/
|
|
9
|
-
export declare function
|
|
9
|
+
export declare function signInTarget(path: string | null): PageRef;
|
|
10
10
|
//#endregion
|
|
11
11
|
//# sourceMappingURL=safe-href.d.ts.map
|
package/dist/utils/safe-href.js
CHANGED
|
@@ -49,9 +49,8 @@ function resolveSafeHref(raw) {
|
|
|
49
49
|
reason: "forbidden-scheme"
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
/** The storefront's own sign-in
|
|
53
|
-
|
|
54
|
-
const SIGN_IN_ROUTE = "/login";
|
|
52
|
+
/** The storefront's own sign-in page, which forwards to the platform's login. Never an authored value. */
|
|
53
|
+
const SIGN_IN_PAGE = { standard: "sign-in" };
|
|
55
54
|
/** The parameter carrying the shopper's way back, read by the sign-in route and the platform's login. */
|
|
56
55
|
const RETURN_URL_PARAM = "returnUrl";
|
|
57
56
|
/**
|
|
@@ -61,7 +60,7 @@ const RETURN_URL_PARAM = "returnUrl";
|
|
|
61
60
|
* unchanged, so a caller logging the reason still sees which spelling arrived.
|
|
62
61
|
*
|
|
63
62
|
* Both ends of the flow narrow through here rather than only the sender, because the sender cannot vouch
|
|
64
|
-
* for a URL the shopper typed: the account button builds the destination through `
|
|
63
|
+
* for a URL the shopper typed: the account button builds the destination through `signInTarget`, and
|
|
65
64
|
* the sign-in route revalidates what arrives through `resolveReturnHref`.
|
|
66
65
|
*/
|
|
67
66
|
function resolveSiteRelativeHref(raw) {
|
|
@@ -92,50 +91,60 @@ function resolveNavigableHref(raw) {
|
|
|
92
91
|
* `resolveSiteRelativeHref` narrowed further, for a return arriving at the sign-in route: the route
|
|
93
92
|
* itself is refused. Admitting it sends a shopper who just authenticated back to the trampoline, which
|
|
94
93
|
* hands them to the platform a second time — a sign-in that never lands where the parameter promised.
|
|
94
|
+
*
|
|
95
|
+
* `signInPath` is the sign-in page's logical path, since a return is one; `null` when the store has no
|
|
96
|
+
* sign-in page, and then no return is the route.
|
|
95
97
|
*/
|
|
96
|
-
function resolveReturnHref(raw) {
|
|
98
|
+
function resolveReturnHref(raw, signInPath) {
|
|
97
99
|
const destination = resolveSiteRelativeHref(raw);
|
|
98
100
|
if (!destination.safe) return destination;
|
|
99
|
-
return isSignInRoute(destination.href) ? {
|
|
101
|
+
return isSignInRoute(destination.href, signInPath) ? {
|
|
100
102
|
safe: false,
|
|
101
103
|
reason: "sign-in-route"
|
|
102
104
|
} : destination;
|
|
103
105
|
}
|
|
104
|
-
/**
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Whether a site-relative value names the sign-in page at `signInPath`, in any of its spellings. `null`
|
|
108
|
+
* is a store without a sign-in page, where nothing is the route.
|
|
109
|
+
*/
|
|
110
|
+
function isSignInRoute(href, signInPath) {
|
|
111
|
+
return signInPath !== null && pathOf(href) === pathOf(signInPath);
|
|
107
112
|
}
|
|
108
113
|
/**
|
|
109
114
|
* `resolveNavigableHref` narrowed for the address a handover targets: the storefront's own sign-in
|
|
110
|
-
* route is refused. Sending
|
|
115
|
+
* route is refused. Sending the route to itself reloads the same document, which mounts the same
|
|
111
116
|
* handover again — a loop the shopper never gets past, and the mirror of the refusal
|
|
112
117
|
* `resolveReturnHref` already makes on the way back.
|
|
113
118
|
*
|
|
114
119
|
* Only the site-relative spelling is decidable here, being same-origin by construction. An absolute
|
|
115
120
|
* address needs the document's own origin to compare against, which this module does not have and a
|
|
116
121
|
* prerender does not either, so `isSignInRoute` is exported for the caller that does. A cross-origin
|
|
117
|
-
* login page whose path happens to be
|
|
122
|
+
* login page whose path happens to be the route's is a different page and is left alone.
|
|
123
|
+
*
|
|
124
|
+
* `signInPath` is the sign-in page's public path, since the address is a document URL.
|
|
118
125
|
*/
|
|
119
|
-
function resolveLoginAddressHref(raw) {
|
|
126
|
+
function resolveLoginAddressHref(raw, signInPath) {
|
|
120
127
|
const address = resolveNavigableHref(raw);
|
|
121
128
|
if (!address.safe || !address.href.startsWith("/")) return address;
|
|
122
|
-
return isSignInRoute(address.href) ? {
|
|
129
|
+
return isSignInRoute(address.href, signInPath) ? {
|
|
123
130
|
safe: false,
|
|
124
131
|
reason: "sign-in-route"
|
|
125
132
|
} : address;
|
|
126
133
|
}
|
|
127
134
|
/**
|
|
128
|
-
*
|
|
135
|
+
* The sign-in page, carrying the current logical path so the shopper can be returned where they
|
|
129
136
|
* started. The sender's half only: nothing stops a hand-crafted link, so the sign-in route is to narrow
|
|
130
137
|
* the parameter again on arrival. A path the narrowing refuses drops the parameter instead of
|
|
131
|
-
* travelling encoded, and so does a router reporting no path
|
|
132
|
-
* a nullable one.
|
|
138
|
+
* travelling encoded, and so does a router reporting no path.
|
|
133
139
|
*/
|
|
134
|
-
function
|
|
135
|
-
if (!
|
|
136
|
-
const destination = resolveSiteRelativeHref(
|
|
137
|
-
if (!destination.safe) return
|
|
138
|
-
return
|
|
140
|
+
function signInTarget(path) {
|
|
141
|
+
if (!path) return SIGN_IN_PAGE;
|
|
142
|
+
const destination = resolveSiteRelativeHref(path);
|
|
143
|
+
if (!destination.safe) return SIGN_IN_PAGE;
|
|
144
|
+
return {
|
|
145
|
+
standard: "sign-in",
|
|
146
|
+
query: { [RETURN_URL_PARAM]: destination.href }
|
|
147
|
+
};
|
|
139
148
|
}
|
|
140
149
|
/** Opaque base for resolving a site-relative value to a path. Reserved by RFC 6761, so it never names a
|
|
141
150
|
* real host; nothing is navigated to it and only `pathname` is read back. */
|
|
@@ -164,6 +173,6 @@ function hasStrippableCharacter(value) {
|
|
|
164
173
|
return false;
|
|
165
174
|
}
|
|
166
175
|
//#endregion
|
|
167
|
-
export { RETURN_URL_PARAM, isSignInRoute, resolveLoginAddressHref, resolveNavigableHref, resolveReturnHref, resolveSafeHref,
|
|
176
|
+
export { RETURN_URL_PARAM, SIGN_IN_PAGE, isSignInRoute, resolveLoginAddressHref, resolveNavigableHref, resolveReturnHref, resolveSafeHref, resolveSiteRelativeHref, signInTarget };
|
|
168
177
|
|
|
169
178
|
//# sourceMappingURL=safe-href.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-href.js","names":[],"sources":["../../src/utils/safe-href.ts"],"sourcesContent":["export type SafeHrefRejection =\n | 'blank'\n | 'malformed'\n | 'protocol-relative'\n | 'forbidden-scheme'\n | 'absolute'\n | 'sign-in-route';\n\nexport type SafeHrefResult = { safe: true; href: string } | { safe: false; reason: SafeHrefRejection };\n\nconst ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);\n\n/**\n * The only gate on a CMS-authored destination: `http:`, `https:`, `mailto:`, `tel:`, and paths\n * starting with a single `/`. The runtime slice does not enforce a model's `pattern`, and the sukima\n * preflight that does never sees content published after a deploy, so an authored `javascript:` value\n * can reach the component intact and this is what stands between it and an `href`.\n *\n * Rejects rather than sanitizes: a destination that comes back safe is character-for-character the\n * trimmed authored value, so no value is ever silently rewritten into a different working one.\n */\nexport function resolveSafeHref(raw: string): SafeHrefResult {\n const href = raw.trim();\n if (href.length === 0) {\n return { safe: false, reason: 'blank' };\n }\n // Browsers strip ASCII tabs and newlines from a URL before parsing it, so `java<TAB>script:alert(1)`\n // executes. Refusing the whole value is the only defense that does not depend on stripping the same\n // characters in the same order the parser does.\n if (hasStrippableCharacter(href)) {\n return { safe: false, reason: 'malformed' };\n }\n // A URL parser reads `\\` as `/` under a special scheme, so `/\\evil.example` resolves against the\n // store's origin as an authority rather than a path. Any pair of leading slashes is an off-site\n // jump however it is spelled, and only `//` looks like one.\n if (/^[/\\\\]{2}/.test(href)) {\n return { safe: false, reason: 'protocol-relative' };\n }\n if (href.startsWith('/')) {\n return { safe: true, href };\n }\n\n let protocol: string;\n try {\n protocol = new URL(href).protocol;\n } catch {\n return { safe: false, reason: 'malformed' };\n }\n // `new URL` lowercases the protocol, so `JavaScript:` and `javascript:` are the same rejection.\n return ALLOWED_PROTOCOLS.has(protocol) ? { safe: true, href } : { safe: false, reason: 'forbidden-scheme' };\n}\n\n/** The storefront's own sign-in route, which forwards to the platform's login. A fixed convention,\n * never an authored value. */\nconst SIGN_IN_ROUTE = '/login';\n\n/** The parameter carrying the shopper's way back, read by the sign-in route and the platform's login. */\nexport const RETURN_URL_PARAM = 'returnUrl';\n\n/**\n * `resolveSafeHref` narrowed to the site-relative subset: the destinations a shopper may be sent back to\n * across the auth hop. The allowlist admits absolute http(s) values, which are legitimate for an authored\n * link but are exactly what would turn the hop into an open redirect. Every other rejection travels out\n * unchanged, so a caller logging the reason still sees which spelling arrived.\n *\n * Both ends of the flow narrow through here rather than only the sender, because the sender cannot vouch\n * for a URL the shopper typed: the account button builds the destination through `resolveSignInHref`, and\n * the sign-in route revalidates what arrives through `resolveReturnHref`.\n */\nexport function resolveSiteRelativeHref(raw: string): SafeHrefResult {\n const destination = resolveSafeHref(raw);\n if (!destination.safe) {\n return destination;\n }\n return destination.href.startsWith('/') ? destination : { safe: false, reason: 'absolute' };\n}\n\n/** The subset of the allowlist that resolves to a page a document can be handed over to. */\nconst NAVIGABLE_PROTOCOLS = new Set(['http:', 'https:']);\n\n/**\n * `resolveSafeHref` narrowed to the destinations that resolve to a page: site-relative, or absolute\n * `http(s)`. `mailto:` and `tel:` are legitimate for an authored link and refused here, since handing a\n * document over to one leaves the shopper in a mail client instead of a login.\n */\nexport function resolveNavigableHref(raw: string): SafeHrefResult {\n const destination = resolveSafeHref(raw);\n if (!destination.safe || destination.href.startsWith('/')) {\n return destination;\n }\n // The allowlist already parsed this value, so the protocol is readable without a second guard.\n const { protocol } = new URL(destination.href);\n return NAVIGABLE_PROTOCOLS.has(protocol) ? destination : { safe: false, reason: 'forbidden-scheme' };\n}\n\n/**\n * `resolveSiteRelativeHref` narrowed further, for a return arriving at the sign-in route: the route\n * itself is refused. Admitting it sends a shopper who just authenticated back to the trampoline, which\n * hands them to the platform a second time — a sign-in that never lands where the parameter promised.\n */\nexport function resolveReturnHref(raw: string): SafeHrefResult {\n const destination = resolveSiteRelativeHref(raw);\n if (!destination.safe) {\n return destination;\n }\n return isSignInRoute(destination.href) ? { safe: false, reason: 'sign-in-route' } : destination;\n}\n\n/** Whether a site-relative value names the storefront's own sign-in route, in any of its spellings. */\nexport function isSignInRoute(href: string): boolean {\n return pathOf(href) === SIGN_IN_ROUTE;\n}\n\n/**\n * `resolveNavigableHref` narrowed for the address a handover targets: the storefront's own sign-in\n * route is refused. Sending `/login` to `/login` reloads the same document, which mounts the same\n * handover again — a loop the shopper never gets past, and the mirror of the refusal\n * `resolveReturnHref` already makes on the way back.\n *\n * Only the site-relative spelling is decidable here, being same-origin by construction. An absolute\n * address needs the document's own origin to compare against, which this module does not have and a\n * prerender does not either, so `isSignInRoute` is exported for the caller that does. A cross-origin\n * login page whose path happens to be `/login` is a different page and is left alone.\n */\nexport function resolveLoginAddressHref(raw: string): SafeHrefResult {\n const address = resolveNavigableHref(raw);\n if (!address.safe || !address.href.startsWith('/')) {\n return address;\n }\n return isSignInRoute(address.href) ? { safe: false, reason: 'sign-in-route' } : address;\n}\n\n/**\n * Builds the sign-in destination, carrying the current path so the shopper can be returned where they\n * started. The sender's half only: nothing stops a hand-crafted link, so the sign-in route is to narrow\n * the parameter again on arrival. A path the narrowing refuses drops the parameter instead of\n * travelling encoded, and so does a router reporting no path — `usePathname()`'s type is why this takes\n * a nullable one.\n */\nexport function resolveSignInHref(pathname: string | null): string {\n if (!pathname) {\n return SIGN_IN_ROUTE;\n }\n const destination = resolveSiteRelativeHref(pathname);\n if (!destination.safe) {\n return SIGN_IN_ROUTE;\n }\n return `${SIGN_IN_ROUTE}?${RETURN_URL_PARAM}=${encodeURIComponent(destination.href)}`;\n}\n\n/** Opaque base for resolving a site-relative value to a path. Reserved by RFC 6761, so it never names a\n * real host; nothing is navigated to it and only `pathname` is read back. */\nconst PATH_COMPARISON_BASE = 'https://store.invalid';\n\n/**\n * The path of a site-relative value, without its query or fragment and without a trailing slash, so a\n * route comparison reads the route rather than one of its spellings. `/loginhelp` is a different path\n * and stays one.\n *\n * Parsed rather than sliced, because `/account/../login`, `/account\\..\\login` and `/account/%2e%2e/login`\n * are all spellings a URL parser resolves to `/login`. A slice compares them literally and admits them,\n * and the value then reaches a receiver that does resolve them — which puts the shopper back on the\n * trampoline the refusal exists to prevent. Only the comparison is normalized: the caller returns the\n * value as it arrived, so the promise that nothing is silently rewritten still holds.\n */\nfunction pathOf(href: string): string {\n const { pathname } = new URL(href, PATH_COMPARISON_BASE);\n return pathname.replace(/\\/+$/, '') || '/';\n}\n\n/** Any C0 control or space anywhere in the value — the set a URL parser removes or trims. */\nfunction hasStrippableCharacter(value: string): boolean {\n for (const character of value) {\n const code = character.codePointAt(0) ?? 0;\n if (code <= 0x20 || code === 0x7f) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":";AAUA,MAAM,oCAAoB,IAAI,IAAI;CAAC;CAAS;CAAU;CAAW;AAAM,CAAC;;;;;;;;;;AAWxE,SAAgB,gBAAgB,KAA6B;CAC3D,MAAM,OAAO,IAAI,KAAK;CACtB,IAAI,KAAK,WAAW,GAClB,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAQ;CAKxC,IAAI,uBAAuB,IAAI,GAC7B,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAY;CAK5C,IAAI,YAAY,KAAK,IAAI,GACvB,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAoB;CAEpD,IAAI,KAAK,WAAW,GAAG,GACrB,OAAO;EAAE,MAAM;EAAM;CAAK;CAG5B,IAAI;CACJ,IAAI;EACF,WAAW,IAAI,IAAI,IAAI,CAAC,CAAC;CAC3B,QAAQ;EACN,OAAO;GAAE,MAAM;GAAO,QAAQ;EAAY;CAC5C;CAEA,OAAO,kBAAkB,IAAI,QAAQ,IAAI;EAAE,MAAM;EAAM;CAAK,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAmB;AAC5G;;;AAIA,MAAM,gBAAgB;;AAGtB,MAAa,mBAAmB;;;;;;;;;;;AAYhC,SAAgB,wBAAwB,KAA6B;CACnE,MAAM,cAAc,gBAAgB,GAAG;CACvC,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO,YAAY,KAAK,WAAW,GAAG,IAAI,cAAc;EAAE,MAAM;EAAO,QAAQ;CAAW;AAC5F;;AAGA,MAAM,sCAAsB,IAAI,IAAI,CAAC,SAAS,QAAQ,CAAC;;;;;;AAOvD,SAAgB,qBAAqB,KAA6B;CAChE,MAAM,cAAc,gBAAgB,GAAG;CACvC,IAAI,CAAC,YAAY,QAAQ,YAAY,KAAK,WAAW,GAAG,GACtD,OAAO;CAGT,MAAM,EAAE,aAAa,IAAI,IAAI,YAAY,IAAI;CAC7C,OAAO,oBAAoB,IAAI,QAAQ,IAAI,cAAc;EAAE,MAAM;EAAO,QAAQ;CAAmB;AACrG;;;;;;AAOA,SAAgB,kBAAkB,KAA6B;CAC7D,MAAM,cAAc,wBAAwB,GAAG;CAC/C,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO,cAAc,YAAY,IAAI,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAgB,IAAI;AACtF;;AAGA,SAAgB,cAAc,MAAuB;CACnD,OAAO,OAAO,IAAI,MAAM;AAC1B;;;;;;;;;;;;AAaA,SAAgB,wBAAwB,KAA6B;CACnE,MAAM,UAAU,qBAAqB,GAAG;CACxC,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,KAAK,WAAW,GAAG,GAC/C,OAAO;CAET,OAAO,cAAc,QAAQ,IAAI,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAgB,IAAI;AAClF;;;;;;;;AASA,SAAgB,kBAAkB,UAAiC;CACjE,IAAI,CAAC,UACH,OAAO;CAET,MAAM,cAAc,wBAAwB,QAAQ;CACpD,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO,GAAG,cAAc,GAAG,iBAAiB,GAAG,mBAAmB,YAAY,IAAI;AACpF;;;AAIA,MAAM,uBAAuB;;;;;;;;;;;;AAa7B,SAAS,OAAO,MAAsB;CACpC,MAAM,EAAE,aAAa,IAAI,IAAI,MAAM,oBAAoB;CACvD,OAAO,SAAS,QAAQ,QAAQ,EAAE,KAAK;AACzC;;AAGA,SAAS,uBAAuB,OAAwB;CACtD,KAAK,MAAM,aAAa,OAAO;EAC7B,MAAM,OAAO,UAAU,YAAY,CAAC,KAAK;EACzC,IAAI,QAAQ,MAAQ,SAAS,KAC3B,OAAO;CAEX;CACA,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"safe-href.js","names":[],"sources":["../../src/utils/safe-href.ts"],"sourcesContent":["import type { PageRef } from '../sdk/navigation/types.js';\n\nexport type SafeHrefRejection =\n | 'blank'\n | 'malformed'\n | 'protocol-relative'\n | 'forbidden-scheme'\n | 'absolute'\n | 'sign-in-route';\n\nexport type SafeHrefResult = { safe: true; href: string } | { safe: false; reason: SafeHrefRejection };\n\nconst ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);\n\n/**\n * The only gate on a CMS-authored destination: `http:`, `https:`, `mailto:`, `tel:`, and paths\n * starting with a single `/`. The runtime slice does not enforce a model's `pattern`, and the sukima\n * preflight that does never sees content published after a deploy, so an authored `javascript:` value\n * can reach the component intact and this is what stands between it and an `href`.\n *\n * Rejects rather than sanitizes: a destination that comes back safe is character-for-character the\n * trimmed authored value, so no value is ever silently rewritten into a different working one.\n */\nexport function resolveSafeHref(raw: string): SafeHrefResult {\n const href = raw.trim();\n if (href.length === 0) {\n return { safe: false, reason: 'blank' };\n }\n // Browsers strip ASCII tabs and newlines from a URL before parsing it, so `java<TAB>script:alert(1)`\n // executes. Refusing the whole value is the only defense that does not depend on stripping the same\n // characters in the same order the parser does.\n if (hasStrippableCharacter(href)) {\n return { safe: false, reason: 'malformed' };\n }\n // A URL parser reads `\\` as `/` under a special scheme, so `/\\evil.example` resolves against the\n // store's origin as an authority rather than a path. Any pair of leading slashes is an off-site\n // jump however it is spelled, and only `//` looks like one.\n if (/^[/\\\\]{2}/.test(href)) {\n return { safe: false, reason: 'protocol-relative' };\n }\n if (href.startsWith('/')) {\n return { safe: true, href };\n }\n\n let protocol: string;\n try {\n protocol = new URL(href).protocol;\n } catch {\n return { safe: false, reason: 'malformed' };\n }\n // `new URL` lowercases the protocol, so `JavaScript:` and `javascript:` are the same rejection.\n return ALLOWED_PROTOCOLS.has(protocol) ? { safe: true, href } : { safe: false, reason: 'forbidden-scheme' };\n}\n\n/** The storefront's own sign-in page, which forwards to the platform's login. Never an authored value. */\nexport const SIGN_IN_PAGE: PageRef = { standard: 'sign-in' };\n\n/** The parameter carrying the shopper's way back, read by the sign-in route and the platform's login. */\nexport const RETURN_URL_PARAM = 'returnUrl';\n\n/**\n * `resolveSafeHref` narrowed to the site-relative subset: the destinations a shopper may be sent back to\n * across the auth hop. The allowlist admits absolute http(s) values, which are legitimate for an authored\n * link but are exactly what would turn the hop into an open redirect. Every other rejection travels out\n * unchanged, so a caller logging the reason still sees which spelling arrived.\n *\n * Both ends of the flow narrow through here rather than only the sender, because the sender cannot vouch\n * for a URL the shopper typed: the account button builds the destination through `signInTarget`, and\n * the sign-in route revalidates what arrives through `resolveReturnHref`.\n */\nexport function resolveSiteRelativeHref(raw: string): SafeHrefResult {\n const destination = resolveSafeHref(raw);\n if (!destination.safe) {\n return destination;\n }\n return destination.href.startsWith('/') ? destination : { safe: false, reason: 'absolute' };\n}\n\n/** The subset of the allowlist that resolves to a page a document can be handed over to. */\nconst NAVIGABLE_PROTOCOLS = new Set(['http:', 'https:']);\n\n/**\n * `resolveSafeHref` narrowed to the destinations that resolve to a page: site-relative, or absolute\n * `http(s)`. `mailto:` and `tel:` are legitimate for an authored link and refused here, since handing a\n * document over to one leaves the shopper in a mail client instead of a login.\n */\nexport function resolveNavigableHref(raw: string): SafeHrefResult {\n const destination = resolveSafeHref(raw);\n if (!destination.safe || destination.href.startsWith('/')) {\n return destination;\n }\n // The allowlist already parsed this value, so the protocol is readable without a second guard.\n const { protocol } = new URL(destination.href);\n return NAVIGABLE_PROTOCOLS.has(protocol) ? destination : { safe: false, reason: 'forbidden-scheme' };\n}\n\n/**\n * `resolveSiteRelativeHref` narrowed further, for a return arriving at the sign-in route: the route\n * itself is refused. Admitting it sends a shopper who just authenticated back to the trampoline, which\n * hands them to the platform a second time — a sign-in that never lands where the parameter promised.\n *\n * `signInPath` is the sign-in page's logical path, since a return is one; `null` when the store has no\n * sign-in page, and then no return is the route.\n */\nexport function resolveReturnHref(raw: string, signInPath: string | null): SafeHrefResult {\n const destination = resolveSiteRelativeHref(raw);\n if (!destination.safe) {\n return destination;\n }\n return isSignInRoute(destination.href, signInPath) ? { safe: false, reason: 'sign-in-route' } : destination;\n}\n\n/**\n * Whether a site-relative value names the sign-in page at `signInPath`, in any of its spellings. `null`\n * is a store without a sign-in page, where nothing is the route.\n */\nexport function isSignInRoute(href: string, signInPath: string | null): boolean {\n return signInPath !== null && pathOf(href) === pathOf(signInPath);\n}\n\n/**\n * `resolveNavigableHref` narrowed for the address a handover targets: the storefront's own sign-in\n * route is refused. Sending the route to itself reloads the same document, which mounts the same\n * handover again — a loop the shopper never gets past, and the mirror of the refusal\n * `resolveReturnHref` already makes on the way back.\n *\n * Only the site-relative spelling is decidable here, being same-origin by construction. An absolute\n * address needs the document's own origin to compare against, which this module does not have and a\n * prerender does not either, so `isSignInRoute` is exported for the caller that does. A cross-origin\n * login page whose path happens to be the route's is a different page and is left alone.\n *\n * `signInPath` is the sign-in page's public path, since the address is a document URL.\n */\nexport function resolveLoginAddressHref(raw: string, signInPath: string | null): SafeHrefResult {\n const address = resolveNavigableHref(raw);\n if (!address.safe || !address.href.startsWith('/')) {\n return address;\n }\n return isSignInRoute(address.href, signInPath) ? { safe: false, reason: 'sign-in-route' } : address;\n}\n\n/**\n * The sign-in page, carrying the current logical path so the shopper can be returned where they\n * started. The sender's half only: nothing stops a hand-crafted link, so the sign-in route is to narrow\n * the parameter again on arrival. A path the narrowing refuses drops the parameter instead of\n * travelling encoded, and so does a router reporting no path.\n */\nexport function signInTarget(path: string | null): PageRef {\n if (!path) {\n return SIGN_IN_PAGE;\n }\n const destination = resolveSiteRelativeHref(path);\n if (!destination.safe) {\n return SIGN_IN_PAGE;\n }\n return { standard: 'sign-in', query: { [RETURN_URL_PARAM]: destination.href } };\n}\n\n/** Opaque base for resolving a site-relative value to a path. Reserved by RFC 6761, so it never names a\n * real host; nothing is navigated to it and only `pathname` is read back. */\nconst PATH_COMPARISON_BASE = 'https://store.invalid';\n\n/**\n * The path of a site-relative value, without its query or fragment and without a trailing slash, so a\n * route comparison reads the route rather than one of its spellings. `/loginhelp` is a different path\n * and stays one.\n *\n * Parsed rather than sliced, because `/account/../login`, `/account\\..\\login` and `/account/%2e%2e/login`\n * are all spellings a URL parser resolves to `/login`. A slice compares them literally and admits them,\n * and the value then reaches a receiver that does resolve them — which puts the shopper back on the\n * trampoline the refusal exists to prevent. Only the comparison is normalized: the caller returns the\n * value as it arrived, so the promise that nothing is silently rewritten still holds.\n */\nfunction pathOf(href: string): string {\n const { pathname } = new URL(href, PATH_COMPARISON_BASE);\n return pathname.replace(/\\/+$/, '') || '/';\n}\n\n/** Any C0 control or space anywhere in the value — the set a URL parser removes or trims. */\nfunction hasStrippableCharacter(value: string): boolean {\n for (const character of value) {\n const code = character.codePointAt(0) ?? 0;\n if (code <= 0x20 || code === 0x7f) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":";AAYA,MAAM,oCAAoB,IAAI,IAAI;CAAC;CAAS;CAAU;CAAW;AAAM,CAAC;;;;;;;;;;AAWxE,SAAgB,gBAAgB,KAA6B;CAC3D,MAAM,OAAO,IAAI,KAAK;CACtB,IAAI,KAAK,WAAW,GAClB,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAQ;CAKxC,IAAI,uBAAuB,IAAI,GAC7B,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAY;CAK5C,IAAI,YAAY,KAAK,IAAI,GACvB,OAAO;EAAE,MAAM;EAAO,QAAQ;CAAoB;CAEpD,IAAI,KAAK,WAAW,GAAG,GACrB,OAAO;EAAE,MAAM;EAAM;CAAK;CAG5B,IAAI;CACJ,IAAI;EACF,WAAW,IAAI,IAAI,IAAI,CAAC,CAAC;CAC3B,QAAQ;EACN,OAAO;GAAE,MAAM;GAAO,QAAQ;EAAY;CAC5C;CAEA,OAAO,kBAAkB,IAAI,QAAQ,IAAI;EAAE,MAAM;EAAM;CAAK,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAmB;AAC5G;;AAGA,MAAa,eAAwB,EAAE,UAAU,UAAU;;AAG3D,MAAa,mBAAmB;;;;;;;;;;;AAYhC,SAAgB,wBAAwB,KAA6B;CACnE,MAAM,cAAc,gBAAgB,GAAG;CACvC,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO,YAAY,KAAK,WAAW,GAAG,IAAI,cAAc;EAAE,MAAM;EAAO,QAAQ;CAAW;AAC5F;;AAGA,MAAM,sCAAsB,IAAI,IAAI,CAAC,SAAS,QAAQ,CAAC;;;;;;AAOvD,SAAgB,qBAAqB,KAA6B;CAChE,MAAM,cAAc,gBAAgB,GAAG;CACvC,IAAI,CAAC,YAAY,QAAQ,YAAY,KAAK,WAAW,GAAG,GACtD,OAAO;CAGT,MAAM,EAAE,aAAa,IAAI,IAAI,YAAY,IAAI;CAC7C,OAAO,oBAAoB,IAAI,QAAQ,IAAI,cAAc;EAAE,MAAM;EAAO,QAAQ;CAAmB;AACrG;;;;;;;;;AAUA,SAAgB,kBAAkB,KAAa,YAA2C;CACxF,MAAM,cAAc,wBAAwB,GAAG;CAC/C,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO,cAAc,YAAY,MAAM,UAAU,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAgB,IAAI;AAClG;;;;;AAMA,SAAgB,cAAc,MAAc,YAAoC;CAC9E,OAAO,eAAe,QAAQ,OAAO,IAAI,MAAM,OAAO,UAAU;AAClE;;;;;;;;;;;;;;AAeA,SAAgB,wBAAwB,KAAa,YAA2C;CAC9F,MAAM,UAAU,qBAAqB,GAAG;CACxC,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,KAAK,WAAW,GAAG,GAC/C,OAAO;CAET,OAAO,cAAc,QAAQ,MAAM,UAAU,IAAI;EAAE,MAAM;EAAO,QAAQ;CAAgB,IAAI;AAC9F;;;;;;;AAQA,SAAgB,aAAa,MAA8B;CACzD,IAAI,CAAC,MACH,OAAO;CAET,MAAM,cAAc,wBAAwB,IAAI;CAChD,IAAI,CAAC,YAAY,MACf,OAAO;CAET,OAAO;EAAE,UAAU;EAAW,OAAO,GAAG,mBAAmB,YAAY,KAAK;CAAE;AAChF;;;AAIA,MAAM,uBAAuB;;;;;;;;;;;;AAa7B,SAAS,OAAO,MAAsB;CACpC,MAAM,EAAE,aAAa,IAAI,IAAI,MAAM,oBAAoB;CACvD,OAAO,SAAS,QAAQ,QAAQ,EAAE,KAAK;AACzC;;AAGA,SAAS,uBAAuB,OAAwB;CACtD,KAAK,MAAM,aAAa,OAAO;EAC7B,MAAM,OAAO,UAAU,YAAY,CAAC,KAAK;EACzC,IAAI,QAAQ,MAAQ,SAAS,KAC3B,OAAO;CAEX;CACA,OAAO;AACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vtex/faststore",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "FastStore AI Native component library built on Base UI with AI-editable styles",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://developers.vtex.com/docs/guides/faststore",
|
|
@@ -50,12 +50,16 @@
|
|
|
50
50
|
},
|
|
51
51
|
"files": [
|
|
52
52
|
"dist",
|
|
53
|
+
"skills",
|
|
53
54
|
"src/styles/tailwind.css",
|
|
54
55
|
"CHANGELOG.md"
|
|
55
56
|
],
|
|
56
57
|
"dependencies": {
|
|
57
58
|
"@base-ui/react": "^1.3.0",
|
|
58
59
|
"@graphql-typed-document-node/core": "^3.2.0",
|
|
60
|
+
"@vtex/country-forms": "1.14.0",
|
|
61
|
+
"@vtex/faststore-catalog": "0.0.8",
|
|
62
|
+
"esbuild": "~0.27.3",
|
|
59
63
|
"graphql": "^16.13.1",
|
|
60
64
|
"react-hook-form": "^7.85.0",
|
|
61
65
|
"react-intl": "^10.1.22",
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: storefront-asset
|
|
3
|
+
description: FastStore extension authoring with @vtex/faststore. Scaffold a storefront Asset, write its API side (defineSubgraph) or UI side (defineComponent), and fix storefront-* Findings until vtex extension validate is green. Use when creating or editing an Asset whose asset.yaml says kind storefront.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# FastStore extension
|
|
7
|
+
|
|
8
|
+
A FastStore extension is a **storefront Asset**: one package at `extensions/<ext>/<asset>/` whose
|
|
9
|
+
`asset.yaml` says `kind: storefront`. It holds at most one **API side** (fields it adds to the
|
|
10
|
+
storefront graph) and at most one **UI side** (components a store places). An extension holds at
|
|
11
|
+
most one storefront Asset. The Asset depends on `@vtex/faststore` alone, and `vtex extension` loads
|
|
12
|
+
the checks from that same package, so the code and its checks share one version.
|
|
13
|
+
|
|
14
|
+
Run the CLI from the Account Repo root as `pnpm extension <command>`, the script
|
|
15
|
+
`vtex extension init` writes. With stdout piped it prints JSON and puts its messages on stderr.
|
|
16
|
+
`pnpm extension commands --json` describes every command and flag.
|
|
17
|
+
|
|
18
|
+
The Asset is **green** when `pnpm extension validate <ext>` exits 0. Exit 1 means an error Finding
|
|
19
|
+
and exit 2 means warnings only. An `info` Finding, such as `extension-not-installed`, leaves it
|
|
20
|
+
green.
|
|
21
|
+
|
|
22
|
+
## Steps
|
|
23
|
+
|
|
24
|
+
1. **Check the SDK.** Run `pnpm extension validate <ext> --json`. A `provider-invalid` Finding on
|
|
25
|
+
the Asset's `package.json` that names `@vtex/faststore` ("is not a valid storefront provider")
|
|
26
|
+
means the installed SDK predates the storefront provider, and none of the checks below ran.
|
|
27
|
+
`provider-failed` means the provider threw. Either one is the human's to resolve: stop and ask
|
|
28
|
+
which `@vtex/faststore` to install. Done when neither code appears.
|
|
29
|
+
2. **Create the Asset when it is missing.** On pnpm 11, first set `allowBuilds:` with
|
|
30
|
+
`esbuild: false` in the root's `pnpm-workspace.yaml`, replacing any placeholder value pnpm wrote
|
|
31
|
+
there. Without it pnpm refuses esbuild's build script, and `add` ends in `tool-failed` with the
|
|
32
|
+
Asset's `package.json` but no template. Then `pnpm extension add <ext>/<asset> --kind storefront`
|
|
33
|
+
writes the API side; add `--template ui` for the UI side. When `<ext>` is new too, pass
|
|
34
|
+
`--vendor` and `--description`: outside a terminal `add` exits 3 instead of asking. `add` writes
|
|
35
|
+
`asset.yaml`, `package.json` and the template's files, and installs their packages. An Asset that
|
|
36
|
+
`add` left without its template, or one made by hand, takes its files and `package.json` fields
|
|
37
|
+
from the side's reference. Done when the folder holds every file its sides need.
|
|
38
|
+
3. **Read the side's reference, then write the change in its shapes.** A resolver, a GraphQL type,
|
|
39
|
+
a secret or data from an outside service is the API side:
|
|
40
|
+
[references/api-side.md](references/api-side.md). A React component, an editable prop or a
|
|
41
|
+
catalog key is the UI side: [references/ui-side.md](references/ui-side.md). A change that needs
|
|
42
|
+
both sides reads both, and the Asset takes the both-sides layout below.
|
|
43
|
+
4. **Validate until green.** Run `pnpm extension validate <ext> --json`. For each `storefront-*`
|
|
44
|
+
Finding, apply the fix in its row of [references/findings.md](references/findings.md), then run
|
|
45
|
+
it again. Done when it exits 0.
|
|
46
|
+
5. **Run the Asset's own checks.** In the Asset's folder, `pnpm typecheck`, and for an API side
|
|
47
|
+
`pnpm test`. An Asset made by hand may lack the scripts: run `pnpm exec tsc --noEmit` and
|
|
48
|
+
`pnpm exec vitest run`. Done when each exits 0.
|
|
49
|
+
|
|
50
|
+
## Layout
|
|
51
|
+
|
|
52
|
+
The provider reads an Asset's sides from `package.json` and two paths. A storefront `asset.yaml`
|
|
53
|
+
holds the envelope and an empty body, so nothing in it changes the layout.
|
|
54
|
+
|
|
55
|
+
| Side | What marks it | Entry |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| API | `package.json#main`, or, with neither `main` nor `exports["./catalog.json"]`, `src/index.ts` | The declaration module, which `export default`s a `defineSubgraph(...)` |
|
|
58
|
+
| UI | A `src/components/` directory, or `exports["./catalog.json"]` | Every `defineComponent` under `src/components/` |
|
|
59
|
+
|
|
60
|
+
An Asset with both sides names its declaration through `main` (for example `api/index.ts`) and
|
|
61
|
+
keeps `src/index.ts` as the component barrel. Without `main`, a `src/index.ts` beside a
|
|
62
|
+
`./catalog.json` export is the barrel.
|
|
63
|
+
|
|
64
|
+
The templates set these `package.json` fields, and an Asset made by hand sets them itself. Both
|
|
65
|
+
sides take `"type": "module"`, a `typecheck` script and `typescript@^5.9.3` in devDependencies;
|
|
66
|
+
`validate` reports `storefront-typescript-unsupported` without TypeScript 5. The API side adds
|
|
67
|
+
`graphql`, `vitest` and a `test` script. The UI side adds `@types/react`, `react` as a peer, and
|
|
68
|
+
these exports:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
"exports": {
|
|
72
|
+
".": "./src/index.ts",
|
|
73
|
+
"./catalog.json": "./dist/catalog.json"
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
A store lists the Asset's package as a dependency and compiles its components from `src/`.
|
|
78
|
+
|
|
79
|
+
## Environment
|
|
80
|
+
|
|
81
|
+
`dev` needs `VTEX_ACCOUNT` in the repo root's `.env`, or `STOREFRONT_CORE_URL` to override the core
|
|
82
|
+
URL derived from the account. `VTEX_WORKSPACE` defaults to `master`. `VTEX_TOKEN`, which is
|
|
83
|
+
optional, and the value of every secret a declaration lists go in the root's `.env.local`, which
|
|
84
|
+
git ignores. Keep the Asset's folder free of `.env` files, so one place holds every value.
|
|
85
|
+
`validate` reads none of them.
|
|
86
|
+
|
|
87
|
+
## Running it live
|
|
88
|
+
|
|
89
|
+
`dev` runs until it is stopped, so start it in the background. Start it through the CLI's bin, so a
|
|
90
|
+
stop signal reaches the CLI rather than a pnpm wrapper:
|
|
91
|
+
`node node_modules/@vtex/cli-plugin-extension/dist/bin/vtex-extension.js dev <ext>` from the repo
|
|
92
|
+
root. It serves one local router over every API side and rebuilds each UI side's
|
|
93
|
+
`dist/catalog.json` on every save under `src/`. The first run downloads the Hive Router into
|
|
94
|
+
`.vtex/storefront/`, and the Assets stay `starting` until it lands.
|
|
95
|
+
|
|
96
|
+
`.vtex/dev-session.json` at the repo root lists each Asset's `status` (`starting`, `ready` or
|
|
97
|
+
`failed`), its `findings`, and, once `ready`, its `details`: the router `url`, the `subgraph` and
|
|
98
|
+
the `catalog`. Query the router `url`; the subgraph's port changes on every reload. A Finding
|
|
99
|
+
leaves the Asset `ready`, so read `findings` after each save. The file's shape is provisional.
|
|
100
|
+
|
|
101
|
+
One session runs per repo, and a second `dev` exits 1 and names the running one. SIGTERM or SIGINT
|
|
102
|
+
stops it with exit 0 and removes the file.
|
|
103
|
+
|
|
104
|
+
## Building it
|
|
105
|
+
|
|
106
|
+
`pnpm extension build <ext>` validates first, then writes each Asset's output to
|
|
107
|
+
`.vtex/build/<ext>/<asset>/` (`--out <dir>` puts it under another root). The API side emits
|
|
108
|
+
`server.mjs` and `extension.graphql`. The UI side emits `catalog.json`, `content-types.json` when
|
|
109
|
+
there is one, and `docs/`. An Asset with an error Finding is `failed` and leaves no folder. The
|
|
110
|
+
output is what the pipeline collects; the UI side's `catalog.json` also lands in the Asset's `dist/`,
|
|
111
|
+
where its export points, so a store build finds it. `--json` prints each Asset's status.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# The API side
|
|
2
|
+
|
|
3
|
+
The API side is one declaration module: `src/index.ts`, or the file `package.json#main` names when
|
|
4
|
+
the Asset has a UI side too.
|
|
5
|
+
|
|
6
|
+
## The declaration
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { defineSubgraph, gql } from '@vtex/faststore/graphql';
|
|
10
|
+
|
|
11
|
+
const REVIEWS_BY_SLUG: Record<string, Array<{ id: string; rating: number; text: string }>> = {
|
|
12
|
+
'sample-product-slug': [{ id: '1', rating: 5, text: 'Replace this with data from your own source.' }],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default defineSubgraph({
|
|
16
|
+
vendor: 'acme',
|
|
17
|
+
|
|
18
|
+
typeDefs: gql`
|
|
19
|
+
extend type Product @key(fields: "slug") {
|
|
20
|
+
slug: String! @external
|
|
21
|
+
reviews: [Review!]!
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type Review {
|
|
25
|
+
id: ID!
|
|
26
|
+
rating: Int!
|
|
27
|
+
text: String!
|
|
28
|
+
}
|
|
29
|
+
`,
|
|
30
|
+
|
|
31
|
+
extends: ['Product'],
|
|
32
|
+
|
|
33
|
+
resolvers: {
|
|
34
|
+
Product: {
|
|
35
|
+
reviews: (product: { slug: string }) => REVIEWS_BY_SLUG[product.slug] ?? [],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`@vtex/faststore/graphql` exports `defineSubgraph`, `gql` and the types `SubgraphContext`,
|
|
42
|
+
`SubgraphDefinition` and `SubgraphResolvers`. `defineSubgraph` returns its argument unchanged; the
|
|
43
|
+
provider builds the schema from it.
|
|
44
|
+
|
|
45
|
+
| Field | What it is |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `vendor` | Required. Matches `^[a-z][a-z0-9]*$`: lowercase letters and digits, starting with a letter. It prefixes every name the Asset contributes. |
|
|
48
|
+
| `typeDefs` | SDL in a `gql` tag, with bare names. Write `@key` and `@external` on the entity you extend; the provider injects the federation preamble. |
|
|
49
|
+
| `extends` | The core entities the SDL extends. `Product`, keyed by `slug`, is the only one. |
|
|
50
|
+
| `resolvers` | Keyed by bare names. The provider renames the keys to the prefixed names and adds each entity's `__resolveReference`. |
|
|
51
|
+
| `secrets` | Optional. The environment names the resolvers read; see Secrets. |
|
|
52
|
+
| `name` | Optional. The subgraph name; the package name by default. |
|
|
53
|
+
|
|
54
|
+
## The vendor prefix
|
|
55
|
+
|
|
56
|
+
The build prefixes every name the Asset contributes with `<vendor>_`: fields added to `Product`,
|
|
57
|
+
root `Query` and `Mutation` fields, and each declared type with every reference to it. The
|
|
58
|
+
declaration above exposes `Product.acme_reviews` of type `[acme_Review!]!`. Write bare names in the
|
|
59
|
+
declaration and let the build add the prefix. The `@key` field of an extended entity keeps its core
|
|
60
|
+
name.
|
|
61
|
+
|
|
62
|
+
## The resolver context
|
|
63
|
+
|
|
64
|
+
A resolver's third argument has exactly these five keys:
|
|
65
|
+
|
|
66
|
+
| Key | Value |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `ctx.account` | The `x-vtex-account` header, `''` when absent |
|
|
69
|
+
| `ctx.workspace` | The `x-vtex-workspace` header, `''` when absent |
|
|
70
|
+
| `ctx.headers` | `x-vtex-locale`, `x-vtex-segment` and `x-vtex-session`, each only when the request carried it |
|
|
71
|
+
| `ctx.token` | The raw `authorization` header, `''` when absent |
|
|
72
|
+
| `ctx.secrets` | The declared secret names that are set, mapped to their values |
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import type { SubgraphContext } from '@vtex/faststore/graphql';
|
|
76
|
+
|
|
77
|
+
const reviews = (product: { slug: string }, _args: unknown, ctx: SubgraphContext) =>
|
|
78
|
+
fetchReviews(product.slug, { apiKey: ctx.secrets.ACME_API_KEY, account: ctx.account });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Secrets
|
|
82
|
+
|
|
83
|
+
`secrets: ['ACME_API_KEY']` declares the environment names the Asset reads. Each name matches
|
|
84
|
+
`^[A-Z][A-Z0-9_]*$`. The provider rejects `PATH`, `HOME`, `PWD`, `SHELL`, `TMPDIR` and any name
|
|
85
|
+
that starts with `VTEX_` or `NODE_`. A declared name that is not set is absent from `ctx.secrets`;
|
|
86
|
+
`dev` logs one warning for each. Values come from the repo root's `.env` and `.env.local`; keep
|
|
87
|
+
secret values in `.env.local`, which git ignores. A resolver reads them through `ctx.secrets` only.
|
|
88
|
+
|
|
89
|
+
## The capability boundary
|
|
90
|
+
|
|
91
|
+
The provider lints the declaration and every `./` or `../` module it imports. Each rule is an error
|
|
92
|
+
Finding at the line it flags:
|
|
93
|
+
|
|
94
|
+
| Rule | What it flags | What to write instead |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| `process-env` | Any `process.env` member access | Declare the name in `secrets` and read `ctx.secrets` |
|
|
97
|
+
| `fs-import` | A runtime import of `fs` or `fs/promises`, bare or `node:` | Fetch the data from a service |
|
|
98
|
+
| `child-process-import` | A runtime import of `child_process` | Call a service over HTTP |
|
|
99
|
+
|
|
100
|
+
Static `import`, `import x = require()`, `require()`, dynamic `import()` and value re-exports all
|
|
101
|
+
count; type-only imports do not. The lint does not walk packages, so route every secret through `ctx.secrets` even when a
|
|
102
|
+
dependency could read the environment: the deployed server scrubs the declared names from its
|
|
103
|
+
environment after boot.
|
|
104
|
+
|
|
105
|
+
## The test
|
|
106
|
+
|
|
107
|
+
`src/index.test.ts` sits beside the declaration. It builds the subgraph with `buildSubgraph` from
|
|
108
|
+
`@vtex/faststore/cli` and resolves the field through `_entities`, the join a federated query makes:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { buildSubgraph } from '@vtex/faststore/cli';
|
|
112
|
+
import { graphql } from 'graphql';
|
|
113
|
+
import { describe, expect, it } from 'vitest';
|
|
114
|
+
import definition from './index';
|
|
115
|
+
|
|
116
|
+
describe('acme extension', () => {
|
|
117
|
+
it('builds a subgraph and resolves the sample field via _entities', async () => {
|
|
118
|
+
const schema = buildSubgraph(definition);
|
|
119
|
+
|
|
120
|
+
const result = await graphql({
|
|
121
|
+
schema,
|
|
122
|
+
source: `
|
|
123
|
+
query ($representations: [_Any!]!) {
|
|
124
|
+
_entities(representations: $representations) {
|
|
125
|
+
... on Product {
|
|
126
|
+
acme_reviews {
|
|
127
|
+
rating
|
|
128
|
+
text
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
`,
|
|
134
|
+
variableValues: {
|
|
135
|
+
representations: [{ __typename: 'Product', slug: 'sample-product-slug' }],
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(result.errors).toBeUndefined();
|
|
140
|
+
const entities = result.data?._entities as Array<{ acme_reviews: Array<{ rating: number }> }>;
|
|
141
|
+
expect(entities[0]?.acme_reviews[0]?.rating).toBe(5);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The test sees the built schema, so it queries the prefixed name (`acme_reviews`). `buildSubgraph`
|
|
147
|
+
and `_entities` are the whole surface a test uses.
|
|
148
|
+
|
|
149
|
+
## What validate checks here
|
|
150
|
+
|
|
151
|
+
`validate` runs the capability lint, loads the declaration and builds the subgraph, which is where
|
|
152
|
+
the `vendor` grammar, the `extends` list, the secret names and the SDL fail. It then composes the
|
|
153
|
+
slice with the storefront core schema. `dev` runs the same checks at boot and on every save.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# The Findings
|
|
2
|
+
|
|
3
|
+
Every code `vtex extension validate` reports for a storefront Asset. All are errors. `file` is
|
|
4
|
+
relative to the repo root, and a Finding with a `range` points at its line and column. `dev` reports
|
|
5
|
+
the API-side and UI-side codes for the same faults and replaces them on every reload, and never the
|
|
6
|
+
two kind rules. `build` validates each Asset first, so it reports every code `validate` does before
|
|
7
|
+
it writes, plus `storefront-build-failed`.
|
|
8
|
+
|
|
9
|
+
## The Asset's code
|
|
10
|
+
|
|
11
|
+
| Code | Trigger | On | Fix |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| `storefront-typescript-unsupported` | The Asset's `typescript` is missing or has no TypeScript 5 compiler API, so the capability lint did not run | `package.json` | `pnpm add -D typescript@^5.9.3` in the Asset's folder |
|
|
14
|
+
| `storefront-capability-process-env` | A `process.env` member access in the declaration or a module it imports | The source line | Declare the name in `secrets` and read `ctx.secrets` |
|
|
15
|
+
| `storefront-capability-fs-import` | A runtime import of `fs` or `fs/promises` | The source line | Fetch the data from a service; a resolver has no filesystem |
|
|
16
|
+
| `storefront-capability-child-process-import` | A runtime import of `child_process` | The source line | Call a service; a resolver spawns no process |
|
|
17
|
+
| `storefront-declaration-invalid` | `main` names no file; the declaration does not bundle or import; it does not `export default defineSubgraph(...)`; or the build rejects it: a `vendor` outside `^[a-z][a-z0-9]*$`, an `extends` entry other than `Product`, a `@key` other than `slug`, a secret name outside the grammar, or SDL that does not parse | The declaration entry | The message names the field or the SDL error. On a UI-only Asset's `src/index.ts`, add `exports["./catalog.json"]`: without it the barrel was read as a declaration |
|
|
18
|
+
| `storefront-composition-failed` | The built slice does not compose with the storefront core schema: a type or field that collides with core's, or a federation directive the composer rejects. In `dev`, also a conflict between two Assets' slices | The declaration entry | Change the SDL as the message says. Keep `@key(fields: "slug")` on `Product` and bare names elsewhere |
|
|
19
|
+
| `storefront-catalog-export-missing` | The Asset has `src/components/` and `package.json#exports` has no `./catalog.json` key | `package.json` | Add `"./catalog.json": "./dist/catalog.json"` and `".": "./src/index.ts"` to `exports` |
|
|
20
|
+
| `storefront-catalog-export-invalid` | The `./catalog.json` export names no file, or a path that is not `./`-relative, has `..` or resolves outside the Asset through a link; `dev` and `build` write where it points, so the catalog goes to `dist/` meanwhile | `package.json` | Point the export at a file inside the Asset: `"./catalog.json": "./dist/catalog.json"` |
|
|
21
|
+
| `storefront-catalog-invalid` | The catalog build threw: no component under `src/components/` (`NoExtensionComponents`), two components with one name, a fragment spread with no definition (`UnresolvedSpread`), no `package.json#name` (`MissingPackageName`), or a top-level `defineActions` | `package.json` | The message names the file or component. Add a `defineComponent`, rename the duplicate, drop the spread, set `name`, or remove `defineActions` |
|
|
22
|
+
| `storefront-build-failed` | The build step after `validate` passed failed: no or invalid `tsconfig.json`, a TypeScript diagnostic, the `server.mjs` bundle, two component docs sharing a basename, or the catalog | `package.json` | The message is the runner's error. Fix what it names and run `pnpm extension build` again |
|
|
23
|
+
|
|
24
|
+
`storefront-declaration-invalid`, `storefront-catalog-invalid` and `storefront-build-failed` carry
|
|
25
|
+
the build's own error verbatim in their message: read it before changing code.
|
|
26
|
+
|
|
27
|
+
## The kind rules
|
|
28
|
+
|
|
29
|
+
`validate` and `build` check these before either side; `dev` does not.
|
|
30
|
+
|
|
31
|
+
| Code | Trigger | On | Fix |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| `storefront-one-per-extension` | The extension already has a storefront Asset, and this one is its second | The Asset's `asset.yaml`, at `kind` | Merge the two into one Asset with both sides, or move this one to its own extension |
|
|
34
|
+
| `storefront-store-extension-not-installed` | A store under `stores/` depends on the Asset's package, and an account that uses that store does not list the extension under `installed:` in `accounts.yaml` | The store's `package.json` | Add `<vendor>.<ext>@<range>` to each such account's `installed:`, or drop the dependency |
|
|
35
|
+
|
|
36
|
+
## The provider's own faults
|
|
37
|
+
|
|
38
|
+
`provider-invalid` on the Asset's `package.json` means the installed `@vtex/faststore` has no
|
|
39
|
+
storefront provider. `provider-failed` means the provider threw. Both belong to the SDK, so the
|
|
40
|
+
human decides which `@vtex/faststore` to install.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# The UI side
|
|
2
|
+
|
|
3
|
+
The UI side is the set of components under `src/components/`, each registered with
|
|
4
|
+
`defineComponent` from `@vtex/faststore`, and the barrel `src/index.ts` a store imports them from.
|
|
5
|
+
The provider builds a catalog of the registrations. A store reads the catalog to place the
|
|
6
|
+
components and compiles them from `src/`; the Asset ships no JavaScript build.
|
|
7
|
+
|
|
8
|
+
## A component
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { defineComponent } from '@vtex/faststore';
|
|
12
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
13
|
+
|
|
14
|
+
/** Props for {@link HeroBanner}. Extends all native `<section>` attributes. */
|
|
15
|
+
export type HeroBannerProps = ComponentPropsWithRef<'section'> & {
|
|
16
|
+
/** The headline text. */
|
|
17
|
+
title?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const HeroBannerDefinition = defineComponent<HeroBannerProps>({
|
|
21
|
+
name: 'HeroBanner',
|
|
22
|
+
importPath: '@acme/reviews-ui',
|
|
23
|
+
component: HeroBanner,
|
|
24
|
+
editor: { label: 'Hero Banner', category: 'content' },
|
|
25
|
+
schema: {
|
|
26
|
+
title: { type: 'string', label: 'Title' },
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export function HeroBanner({ title, className, ...props }: HeroBannerProps) {
|
|
31
|
+
return (
|
|
32
|
+
<section {...props} className={className} data-fs-hero-banner="">
|
|
33
|
+
<h1>{title}</h1>
|
|
34
|
+
</section>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| Field | What it is |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `name` | The exported function's name. |
|
|
42
|
+
| `importPath` | The Asset's `package.json#name`. A store imports the component from there. |
|
|
43
|
+
| `component` | The function. A function declaration hoists, so the definition may come first. |
|
|
44
|
+
| `editor` | `label`, the name an editor shows, and `category`, a free string that groups it. `description` and `icon` are optional. |
|
|
45
|
+
| `schema` | The editable props. Each has a `type` (`string`, `number`, `boolean`, `object`, `array` or a list of string literals), an optional `label` and an optional `default`; an `array` may name its allowed elements in `items`. Values are serializable: strings, numbers, booleans, objects and arrays of them. |
|
|
46
|
+
|
|
47
|
+
The provider finds definitions by a static scan of `src/components/`, recursive, skipping
|
|
48
|
+
`*.test.tsx`, `*.stories.tsx` and `generated/`. Export each one as a top-level `const` named
|
|
49
|
+
`<Component>Definition` whose argument is an object literal.
|
|
50
|
+
|
|
51
|
+
The root element carries `data-fs-<component>=""` as its identity, and `data-fs-<component>-<prop>`
|
|
52
|
+
for each prop that changes its look. The component accepts `className` and renders it. A store
|
|
53
|
+
styles it through those attributes, so the component ships no CSS.
|
|
54
|
+
|
|
55
|
+
## The barrel
|
|
56
|
+
|
|
57
|
+
`src/index.ts` re-exports every component and its definition:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
export type { HeroBannerProps } from './components/HeroBanner';
|
|
61
|
+
export { HeroBanner, HeroBannerDefinition } from './components/HeroBanner';
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
It is the `exports["."]` target. With `exports["./catalog.json"]` set, the provider reads it as the
|
|
65
|
+
barrel, never as a declaration.
|
|
66
|
+
|
|
67
|
+
## The catalog
|
|
68
|
+
|
|
69
|
+
Each registration becomes one catalog entry keyed `<packageName>:<Component>`, for example
|
|
70
|
+
`@acme/reviews-ui:HeroBanner`. The package name, taken verbatim from `package.json#name`, is the
|
|
71
|
+
namespace, so two Assets never collide.
|
|
72
|
+
|
|
73
|
+
`dev` builds `dist/catalog.json` at boot and on every save under `src/`; `exports["./catalog.json"]`
|
|
74
|
+
points there, and git ignores `dist/`. `build` writes the same file there beside its output folder,
|
|
75
|
+
so a store built after `pnpm extension build` finds it without a `dev` loop. `validate` builds the
|
|
76
|
+
catalog into the Asset's `.vtex/storefront/` scratch folder and leaves `dist/` as it is. A failed
|
|
77
|
+
build keeps the previous `catalog.json` until the next save fixes it.
|
|
78
|
+
|
|
79
|
+
A store sees the Asset as a UI side through the `./catalog.json` export alone. Without it, no store
|
|
80
|
+
sees the components, and `validate` reports `storefront-catalog-export-missing`. The export must
|
|
81
|
+
name a `./` path inside the Asset, since `dev` and `build` write the catalog where it points; one
|
|
82
|
+
that does not is `storefront-catalog-export-invalid`, and the catalog goes to `dist/` meanwhile.
|
|
83
|
+
|
|
84
|
+
## Props and content only
|
|
85
|
+
|
|
86
|
+
Keep each component to its props and editor content. A GraphQL fragment spread fails the catalog
|
|
87
|
+
build with `UnresolvedSpread` (reported as `storefront-catalog-invalid`), because no convention yet
|
|
88
|
+
names where a storefront Asset's generated fragments live.
|
|
89
|
+
|
|
90
|
+
## What validate checks here
|
|
91
|
+
|
|
92
|
+
`validate` fails a UI side whose `package.json` lacks the `./catalog.json` export, whose
|
|
93
|
+
`src/components/` declares no component, that has two components with one name, or that spreads a
|
|
94
|
+
fragment with no definition.
|
package/src/styles/tailwind.css
CHANGED
|
@@ -63,4 +63,31 @@
|
|
|
63
63
|
[data-fs-card-entry-fieldset] {
|
|
64
64
|
min-inline-size: 0;
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
/* Baseline look and viewport bound for the select popup. Nothing else styles it, and a matter-composed
|
|
68
|
+
popup is portaled out of reach of a store's descendant selectors, so unstyled it is transparent text
|
|
69
|
+
drawn over the next field, content-wide and free to run past the viewport. `--anchor-width` and
|
|
70
|
+
`--available-height` are set on the positioner by Base UI; the latter bounds the popup (not the list)
|
|
71
|
+
to the viewport. Preflight's `border-box` counts the 1px border inside that bound, and the flex column
|
|
72
|
+
hands the leftover height, minus any composed scroll arrows, to the list, so a tall popup never
|
|
73
|
+
overruns the viewport by the border or the arrows. `@layer base` so any merchant utility class (`max-h-60`,
|
|
74
|
+
`overflow-*`) outranks it under Tailwind 4's layer order. */
|
|
75
|
+
[data-fs-select-popup] {
|
|
76
|
+
max-height: var(--available-height);
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
min-width: var(--anchor-width);
|
|
80
|
+
background-color: var(--color-bg);
|
|
81
|
+
border: 1px solid var(--color-border);
|
|
82
|
+
border-radius: var(--radius-md);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* The list, not the popup, is the scroll container: that is the Select contract (`SelectList` is the
|
|
86
|
+
"scrollable container"), and Base UI's scroll arrows watch the list's scroll position, so a popup
|
|
87
|
+
that scrolled instead would leave them inert. `min-height: 0` lets the flex item shrink below its
|
|
88
|
+
content so the popup's bound reaches it. */
|
|
89
|
+
[data-fs-select-list] {
|
|
90
|
+
min-height: 0;
|
|
91
|
+
overflow-y: auto;
|
|
92
|
+
}
|
|
66
93
|
}
|