@wix/agent-skills 1.20.1 → 1.22.0
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/package.json +1 -1
- package/skills/wix-app/SKILL.md +71 -113
- package/skills/wix-app/references/APP_TOOLS.md +2 -1
- package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
- package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
- package/skills/wix-app/references/DASHBOARD_PAGE.md +11 -25
- package/skills/wix-app/references/DATA_COLLECTION.md +16 -219
- package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
- package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
- package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
- package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
- package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +182 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +97 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_COLLECTION.md +184 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md +203 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_SETTINGS.md +44 -0
- package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
- package/skills/wix-app/references/dashboard-page/QUERY_AND_PAGING.md +182 -0
- package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +133 -0
- package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +10 -5
- package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
- package/skills/wix-app/references/data-collection/LIFECYCLE.md +68 -0
- package/skills/wix-app/references/data-collection/PERMISSIONS.md +47 -0
- package/skills/wix-app/references/data-collection/SCHEMA.md +181 -0
- package/skills/wix-app/references/data-collection/WIX_DATA.md +38 -66
- package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
- package/skills/wix-headless-fast/SKILL.md +144 -221
- package/skills/wix-headless-fast/cold-start/bootstrap.mjs +156 -0
- package/skills/wix-headless-fast/{entry/skill.md → cold-start/cold-start.md} +15 -15
- package/skills/wix-headless-fast/guides/existing-site.md +76 -0
- package/skills/wix-headless-fast/guides/reference-mode.md +97 -0
- package/skills/wix-headless-fast/install/AGENTS.md +35 -0
- package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
- package/skills/wix-headless-fast/install/attach.mjs +161 -86
- package/skills/wix-headless-fast/install/deploy.mjs +21 -60
- package/skills/wix-headless-fast/install/lock.mjs +55 -0
- package/skills/wix-headless-fast/install/setup.mjs +259 -0
- package/skills/wix-headless-fast/install/templates.mjs +168 -0
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
- package/skills/wix-manage/references/events/create-wix-event.md +5 -1
- package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +27 -0
- package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +83 -37
- package/skills/wix-headless-fast/install/fast-path.mjs +0 -181
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +0 -301
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +0 -136
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +0 -112
- package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +0 -42
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +0 -31
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +0 -22
- package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +0 -137
- package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +0 -78
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +0 -117
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +0 -52
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +0 -47
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +0 -58
- package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +0 -62
- package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +0 -97
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +0 -24
- package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +0 -16156
- package/skills/wix-headless-fast/references/blog/rest/posts.ts +0 -42
- package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +0 -62
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +0 -81
- package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +0 -287
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +0 -275
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +0 -144
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +0 -119
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +0 -32
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +0 -23
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +0 -218
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +0 -169
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +0 -121
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +0 -92
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +0 -90
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +0 -57
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +0 -72
- package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +0 -76
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +0 -22
- package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +0 -11962
- package/skills/wix-headless-fast/references/bookings/rest/booking.ts +0 -113
- package/skills/wix-headless-fast/references/bookings/rest/services.ts +0 -55
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +0 -71
- package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +0 -302
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +0 -353
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +0 -50
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +0 -37
- package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +0 -158
- package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +0 -93
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +0 -177
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +0 -123
- package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +0 -75
- package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +0 -41
- package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +0 -11262
- package/skills/wix-headless-fast/references/cms/rest/items.ts +0 -125
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +0 -112
- package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +0 -324
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +0 -305
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +0 -66
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +0 -178
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +0 -118
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +0 -35
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +0 -24
- package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +0 -130
- package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +0 -97
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +0 -55
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +0 -113
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +0 -156
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +0 -97
- package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +0 -61
- package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +0 -12
- package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +0 -80
- package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +0 -21
- package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +0 -11884
- package/skills/wix-headless-fast/references/events/rest/events.ts +0 -57
- package/skills/wix-headless-fast/references/events/rest/registration.ts +0 -83
- package/skills/wix-headless-fast/references/events/seed/SEED.md +0 -95
- package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +0 -276
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +0 -338
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +0 -85
- package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +0 -332
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +0 -238
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +0 -54
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +0 -216
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +0 -59
- package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +0 -135
- package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +0 -10593
- package/skills/wix-headless-fast/references/forms/rest/forms.ts +0 -34
- package/skills/wix-headless-fast/references/forms/rest/submissions.ts +0 -56
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +0 -116
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +0 -327
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +0 -314
- package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +0 -60
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +0 -155
- package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +0 -78
- package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +0 -47
- package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +0 -66
- package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +0 -105
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +0 -59
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +0 -53
- package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +0 -122
- package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +0 -43
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +0 -29
- package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +0 -21
- package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +0 -42
- package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +0 -10
- package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +0 -11
- package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +0 -10329
- package/skills/wix-headless-fast/references/members/rest/auth.ts +0 -215
- package/skills/wix-headless-fast/references/members/rest/members.ts +0 -26
- package/skills/wix-headless-fast/references/members/seed/SEED.md +0 -39
- package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +0 -94
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +0 -313
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +0 -110
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +0 -90
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +0 -35
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +0 -85
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +0 -32
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +0 -21
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +0 -31
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +0 -82
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +0 -58
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +0 -177
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +0 -78
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +0 -81
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +0 -55
- package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +0 -30
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +0 -21
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +0 -29
- package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +0 -10197
- package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +0 -75
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +0 -86
- package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +0 -258
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +0 -294
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +0 -47
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +0 -101
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +0 -41
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +0 -16
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +0 -22
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +0 -54
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +0 -106
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +0 -64
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +0 -27
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +0 -43
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +0 -22
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +0 -30
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +0 -41
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +0 -40
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +0 -21
- package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +0 -11226
- package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +0 -31
- package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +0 -20
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +0 -79
- package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +0 -279
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +0 -358
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +0 -142
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +0 -26
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +0 -121
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +0 -155
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +0 -34
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +0 -43
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +0 -34
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +0 -155
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +0 -89
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +0 -71
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +0 -103
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +0 -124
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +0 -185
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +0 -193
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +0 -94
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +0 -74
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +0 -168
- package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +0 -47
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +0 -22
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +0 -12
- package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +0 -12560
- package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +0 -60
- package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +0 -169
- package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +0 -68
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +0 -89
- package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +0 -479
- package/skills/wix-headless-fast/references/shared/CONTENT.md +0 -22
- package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +0 -73
- package/skills/wix-headless-fast/references/shared/DESIGN.md +0 -26
- package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +0 -23
- package/skills/wix-headless-fast/references/shared/app/styles/global.css +0 -52
- package/skills/wix-headless-fast/references/shared/app/wix/config.ts +0 -15
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +0 -81
- package/skills/wix-headless-fast/references/shared/app/wix/money.ts +0 -24
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +0 -89
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +0 -46
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +0 -46
- package/skills/wix-headless-fast/references/shared/rest/client.ts +0 -155
- package/skills/wix-headless-fast/references/shared/rest/config.ts +0 -3
- package/skills/wix-headless-fast/references/shared/rest/media.ts +0 -65
- package/skills/wix-headless-fast/references/shared/seed/images.mjs +0 -145
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +0 -608
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +0 -26
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +0 -155
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +0 -266
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +0 -95
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +0 -136
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +0 -48
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +0 -39
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +0 -34
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +0 -121
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +0 -107
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +0 -138
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +0 -309
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +0 -133
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +0 -147
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +0 -289
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +0 -167
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +0 -65
- package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +0 -12247
- package/skills/wix-headless-fast/references/storefront/rest/cart.ts +0 -132
- package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +0 -123
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +0 -124
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +0 -525
|
@@ -1,525 +0,0 @@
|
|
|
1
|
-
// Storefront seed — a BUILD-TIME script, never shipped in the app. Run it from the project
|
|
2
|
-
// root (where wix.config.json lives) with a plan file:
|
|
3
|
-
//
|
|
4
|
-
// node <SKILL_ROOT>/references/storefront/seed/seed-store.mjs plan.json
|
|
5
|
-
//
|
|
6
|
-
// It mints its own site token via the Wix CLI (the token never leaves this process), installs
|
|
7
|
-
// the Wix Stores app if needed, waits for the V3 catalog, bulk-creates products (variants
|
|
8
|
-
// expanded, descriptions converted to rich text), creates categories (serially — the shared
|
|
9
|
-
// tree 409s on concurrent creates), assigns products, and attaches images. Prints a JSON
|
|
10
|
-
// result to stdout.
|
|
11
|
-
//
|
|
12
|
-
// Plan shape (see SEED.md):
|
|
13
|
-
// { "products": [{ "name", "description", "price", "compareAtPrice"?, "quantity",
|
|
14
|
-
// "options"?: [{ "name", "type"?: "text"|"color",
|
|
15
|
-
// "choices": ["S","M"] | [{ "name", "colorCode" }] }],
|
|
16
|
-
// "imageUrl"? | "imagePath"? | "imagePrompt"?, "altText"?,
|
|
17
|
-
// "digitalFileUrl"? | "digitalFilePath"?, "digitalFileName"? }],
|
|
18
|
-
// "categories"?: { "<category name>": ["<product name>", ...] } }
|
|
19
|
-
//
|
|
20
|
-
// Seeding is ADDITIVE — this script never deletes or overwrites existing content.
|
|
21
|
-
// If a call fails with an unexpected shape, read the live API reference (the authoritative
|
|
22
|
-
// source recipe is wix-headless/references/inline-recipes/setup-online-store.md) — never guess.
|
|
23
|
-
import { execFileSync } from "node:child_process";
|
|
24
|
-
import { basename } from "node:path";
|
|
25
|
-
import { readFileSync } from "node:fs";
|
|
26
|
-
import { resolveItemImages } from "../../shared/seed/images.mjs";
|
|
27
|
-
|
|
28
|
-
const API = "https://www.wixapis.com";
|
|
29
|
-
const STORES_APP_ID = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
|
|
30
|
-
|
|
31
|
-
// ---- auth: siteId from wix.config.json, token minted by the Wix CLI ----------------------------
|
|
32
|
-
|
|
33
|
-
export function makeCtx({ cwd = process.cwd() } = {}) {
|
|
34
|
-
const config = JSON.parse(readFileSync(`${cwd}/wix.config.json`, "utf8"));
|
|
35
|
-
const siteId = config.siteId ?? config.projectId;
|
|
36
|
-
if (!siteId) throw new Error("wix.config.json has no siteId — is this a Wix CLI project?");
|
|
37
|
-
// The CLI returns a byte-identical token within a run — mint once, reuse.
|
|
38
|
-
const token = execFileSync("npx", ["@wix/cli@latest", "token", "--site", siteId], {
|
|
39
|
-
encoding: "utf8",
|
|
40
|
-
cwd,
|
|
41
|
-
}).trim();
|
|
42
|
-
if (!token) throw new Error("The Wix CLI returned no token — run `npx @wix/cli@latest login` first.");
|
|
43
|
-
return { token, siteId };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ---- transport ----------------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
async function req(ctx, path, { method = "POST", body } = {}) {
|
|
49
|
-
// Retry while the catalog is still provisioning: right after a fresh Stores install the V3
|
|
50
|
-
// WRITE path becomes usable later than the read path, so the first bulk-create can 428 even
|
|
51
|
-
// after the read probe clears. Wait it out (~80s budget); other errors throw immediately.
|
|
52
|
-
// ⚠️ An errored bulk create (seen live with a bare 429 {}) may still have APPLIED
|
|
53
|
-
// server-side — creation is idempotent by name in setupStore for exactly that reason.
|
|
54
|
-
for (let attempt = 0; ; attempt++) {
|
|
55
|
-
const res = await fetch(API + path, {
|
|
56
|
-
method,
|
|
57
|
-
headers: {
|
|
58
|
-
Authorization: `Bearer ${ctx.token}`,
|
|
59
|
-
"wix-site-id": ctx.siteId,
|
|
60
|
-
"Content-Type": "application/json",
|
|
61
|
-
},
|
|
62
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
63
|
-
});
|
|
64
|
-
const json = await res.json().catch(() => ({}));
|
|
65
|
-
if (res.ok) return json;
|
|
66
|
-
if (isProvisioning(res.status, json) && attempt < 40) {
|
|
67
|
-
await sleep(2000);
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
throw new Error(`${method} ${path} -> ${res.status}: ${JSON.stringify(json).slice(0, 400)}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
75
|
-
|
|
76
|
-
// A freshly installed catalog signals "not writable yet" with a 428 under (at least) two codes.
|
|
77
|
-
const PROVISIONING_CODES = new Set(["CATALOG_V1_SITE_CALLING_CATALOG_V3_API", "CATALOG_V3_SITE_PROVISIONING"]);
|
|
78
|
-
|
|
79
|
-
function isProvisioning(status, json) {
|
|
80
|
-
if (PROVISIONING_CODES.has(json?.details?.applicationError?.code)) return true;
|
|
81
|
-
return status === 428 && /provision/i.test(json?.message || "");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/query-products.md
|
|
85
|
-
async function waitForCatalogV3(ctx, { attempts = 40, delayMs = 2000 } = {}) {
|
|
86
|
-
for (let i = 0; i < attempts; i++) {
|
|
87
|
-
const res = await fetch(`${API}/stores/v3/products/query`, {
|
|
88
|
-
method: "POST",
|
|
89
|
-
headers: { Authorization: `Bearer ${ctx.token}`, "wix-site-id": ctx.siteId, "Content-Type": "application/json" },
|
|
90
|
-
body: JSON.stringify({ query: { paging: { limit: 1 } } }),
|
|
91
|
-
});
|
|
92
|
-
if (res.ok) return;
|
|
93
|
-
const json = await res.json().catch(() => ({}));
|
|
94
|
-
if (!isProvisioning(res.status, json)) return;
|
|
95
|
-
await sleep(delayMs);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ---- description string -> Wix rich-text nodes --------------------------------------------------
|
|
100
|
-
// Descriptions arrive as HTML as often as not. The writable field is `description` (Ricos
|
|
101
|
-
// nodes) — the HTML `plainDescription` the storefront renders is derived from them, so markup
|
|
102
|
-
// dropped into a TEXT node comes back escaped and the PDP shows literal tags. Convert the tags
|
|
103
|
-
// a model actually emits; a tag-free string stays one paragraph.
|
|
104
|
-
|
|
105
|
-
const HTML_ENTITIES = { "&": "&", "<": "<", ">": ">", """: '"', "'": "'", " ": " " };
|
|
106
|
-
|
|
107
|
-
function decodeEntities(s) {
|
|
108
|
-
return s.replace(/&(?:amp|lt|gt|quot|#39|nbsp);/g, (m) => HTML_ENTITIES[m] ?? m);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function mkTextNodes(html) {
|
|
112
|
-
const nodes = [];
|
|
113
|
-
let bold = 0, italic = 0, last = 0, m;
|
|
114
|
-
const tag = /<(\/?)(strong|b|em|i)\s*\/?>/gi;
|
|
115
|
-
const push = (raw) => {
|
|
116
|
-
const text = decodeEntities(raw.replace(/<[^>]*>/g, ""));
|
|
117
|
-
if (!text) return;
|
|
118
|
-
const decorations = [];
|
|
119
|
-
if (bold > 0) decorations.push({ type: "BOLD" });
|
|
120
|
-
if (italic > 0) decorations.push({ type: "ITALIC" });
|
|
121
|
-
nodes.push({ type: "TEXT", textData: { text, decorations } });
|
|
122
|
-
};
|
|
123
|
-
while ((m = tag.exec(html)) !== null) {
|
|
124
|
-
push(html.slice(last, m.index));
|
|
125
|
-
const step = m[1] ? -1 : 1;
|
|
126
|
-
if (/^(strong|b)$/i.test(m[2])) bold = Math.max(0, bold + step);
|
|
127
|
-
else italic = Math.max(0, italic + step);
|
|
128
|
-
last = tag.lastIndex;
|
|
129
|
-
}
|
|
130
|
-
push(html.slice(last));
|
|
131
|
-
return nodes.length ? nodes : [{ type: "TEXT", textData: { text: "", decorations: [] } }];
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function mkDesc(text, i) {
|
|
135
|
-
const blocks = String(text ?? "").split(/<\/p\s*>|<br\s*\/?>/i).map((b) => b.trim()).filter(Boolean);
|
|
136
|
-
return {
|
|
137
|
-
nodes: (blocks.length ? blocks : [""]).map((block, n) => ({
|
|
138
|
-
type: "PARAGRAPH", id: `desc-${i}-${n}`,
|
|
139
|
-
nodes: mkTextNodes(block),
|
|
140
|
-
paragraphData: { textStyle: { textAlignment: "AUTO" } },
|
|
141
|
-
})),
|
|
142
|
-
metadata: { version: 1, id: `desc-meta-${i}` },
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ---- options / variants -------------------------------------------------------------------------
|
|
147
|
-
|
|
148
|
-
function buildOptions(options = []) {
|
|
149
|
-
return options.map((o) => {
|
|
150
|
-
const color = o.type === "color";
|
|
151
|
-
return {
|
|
152
|
-
name: o.name,
|
|
153
|
-
optionRenderType: color ? "SWATCH_CHOICES" : "TEXT_CHOICES",
|
|
154
|
-
choicesSettings: {
|
|
155
|
-
choices: o.choices.map((c) =>
|
|
156
|
-
color
|
|
157
|
-
? { choiceType: "ONE_COLOR", name: c.name, colorCode: c.colorCode }
|
|
158
|
-
: { choiceType: "CHOICE_TEXT", name: typeof c === "string" ? c : c.name }),
|
|
159
|
-
},
|
|
160
|
-
};
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Full Cartesian product, each variant priced/stocked from the product; visible:true baked in.
|
|
165
|
-
function expandVariants(options = [], { price, compareAtPrice, quantity, inStock }, digitalFileId) {
|
|
166
|
-
const base = {
|
|
167
|
-
price: {
|
|
168
|
-
actualPrice: { amount: String(price) },
|
|
169
|
-
...(compareAtPrice ? { compareAtPrice: { amount: String(compareAtPrice) } } : {}),
|
|
170
|
-
},
|
|
171
|
-
visible: true,
|
|
172
|
-
...(digitalFileId
|
|
173
|
-
? { digitalProperties: { digitalFile: { id: digitalFileId } }, inventoryItem: { inStock: true } }
|
|
174
|
-
// inStock:true == untracked stock — always buyable, no count. Otherwise track a quantity.
|
|
175
|
-
: { physicalProperties: {}, inventoryItem: inStock === true
|
|
176
|
-
? { inStock: true }
|
|
177
|
-
: { quantity: quantity ?? 0, preorderInfo: { enabled: false } } }),
|
|
178
|
-
};
|
|
179
|
-
if (!options.length) return [base];
|
|
180
|
-
let combos = [[]];
|
|
181
|
-
for (const o of options) {
|
|
182
|
-
const rt = o.type === "color" ? "SWATCH_CHOICES" : "TEXT_CHOICES";
|
|
183
|
-
const names = o.choices.map((c) => (typeof c === "string" ? c : c.name));
|
|
184
|
-
combos = combos.flatMap((combo) =>
|
|
185
|
-
names.map((choiceName) => [...combo, { optionChoiceNames: { optionName: o.name, choiceName, renderType: rt } }]));
|
|
186
|
-
}
|
|
187
|
-
return combos.map((choices) => ({ ...base, choices }));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ---- operations ---------------------------------------------------------------------------------
|
|
191
|
-
|
|
192
|
-
// docs: https://dev.wix.com/docs/api-reference/articles/work-with-wix-apis/platform/about-apps-created-by-wix.md
|
|
193
|
-
export async function installStoresApp(ctx) {
|
|
194
|
-
try {
|
|
195
|
-
await req(ctx, "/apps-installer-service/v1/app-instance/install", { body: {
|
|
196
|
-
tenant: { tenantType: "SITE", id: ctx.siteId },
|
|
197
|
-
appInstance: { appDefId: STORES_APP_ID, enabled: true },
|
|
198
|
-
} });
|
|
199
|
-
} catch {
|
|
200
|
-
// already installed is fine — the readiness wait below still confirms the V3 catalog is live
|
|
201
|
-
}
|
|
202
|
-
await waitForCatalogV3(ctx);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Existing products by exact name (for idempotent reruns). `name` is NOT filterable on the
|
|
206
|
-
// V3 query — fetch a page and match client-side (seed catalogs are small). Empty map on any
|
|
207
|
-
// failure — falling back to create-everything is the additive behavior we had before.
|
|
208
|
-
export async function queryProductsByNames(ctx, names) {
|
|
209
|
-
const out = new Map();
|
|
210
|
-
if (!names.length) return out;
|
|
211
|
-
try {
|
|
212
|
-
const wanted = new Set(names);
|
|
213
|
-
const r = await req(ctx, "/stores/v3/products/query", { body: { query: { cursorPaging: { limit: 100 } } } });
|
|
214
|
-
for (const p of r.products ?? []) {
|
|
215
|
-
if (wanted.has(p.name) && !out.has(p.name)) out.set(p.name, { id: p.id, slug: p.slug, revision: p.revision });
|
|
216
|
-
}
|
|
217
|
-
} catch (e) {
|
|
218
|
-
console.error(`product name pre-check failed (creating everything): ${String(e.message).slice(0, 120)}`);
|
|
219
|
-
}
|
|
220
|
-
return out;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// A digital variant is SELLABLE only with BOTH a file and stock: without the file the cart rejects
|
|
224
|
-
// it as ITEM_NOT_FOUND_IN_CATALOG, without stock as "exceeds available inventory" — and either way
|
|
225
|
-
// the product reads back visible and in the catalog, so nothing surfaces until a buyer tries to buy.
|
|
226
|
-
// A digitalFile* field is the only way into DIGITAL here, which makes the file-less product
|
|
227
|
-
// unbuildable. The bytes are PUT, not imported by url: an uploaded file is READY at once, while an
|
|
228
|
-
// imported one stays PENDING and the cart rejects the product until it settles.
|
|
229
|
-
// docs: https://dev.wix.com/docs/api-reference/assets/media/media-manager/files/generate-file-upload-url.md
|
|
230
|
-
const FILE_MIME = { pdf: "application/pdf", zip: "application/zip", epub: "application/epub+zip",
|
|
231
|
-
mp3: "audio/mpeg", wav: "audio/wav", mp4: "video/mp4", png: "image/png", jpg: "image/jpeg" };
|
|
232
|
-
|
|
233
|
-
async function uploadDigitalFile(ctx, { digitalFileUrl, digitalFilePath, digitalFileName }) {
|
|
234
|
-
const src = digitalFilePath ?? digitalFileUrl;
|
|
235
|
-
const fileName = digitalFileName || decodeURIComponent(basename(new URL(src, "file:").pathname));
|
|
236
|
-
const mimeType = FILE_MIME[fileName.split(".").pop().toLowerCase()];
|
|
237
|
-
if (!mimeType) throw new Error(`digitalFileName needs one of these extensions (${Object.keys(FILE_MIME).join(", ")}): ${fileName}`);
|
|
238
|
-
const bytes = digitalFilePath
|
|
239
|
-
? readFileSync(digitalFilePath)
|
|
240
|
-
: await fetch(digitalFileUrl).then((r) => {
|
|
241
|
-
// Don't invent a file and don't ship an unbuyable DIGITAL product: seed it PHYSICAL
|
|
242
|
-
// with stock (drop digitalFileUrl/digitalFilePath, set inStock or a quantity) and
|
|
243
|
-
// tell the user the download still needs a real file.
|
|
244
|
-
if (!r.ok) throw new Error(`digitalFileUrl ${digitalFileUrl} -> ${r.status}. No fetchable file: re-seed this product as PHYSICAL with stock and tell the user it needs a real file before it can be sold as a download.`);
|
|
245
|
-
return r.arrayBuffer();
|
|
246
|
-
});
|
|
247
|
-
const { uploadUrl } = await req(ctx, "/site-media/v1/files/generate-upload-url", { body: { mimeType, fileName } });
|
|
248
|
-
const res = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": mimeType }, body: bytes });
|
|
249
|
-
const json = await res.json().catch(() => ({}));
|
|
250
|
-
const id = (json.file || json)?.id;
|
|
251
|
-
if (!res.ok || !id) throw new Error(`digital file upload failed (${res.status}): ${JSON.stringify(json).slice(0, 200)}`);
|
|
252
|
-
return id;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/bulk-create-products-with-inventory.md
|
|
256
|
-
export async function bulkCreateProducts(ctx, products) {
|
|
257
|
-
const fileIds = await Promise.all(products.map((p) =>
|
|
258
|
-
p.digitalFileUrl || p.digitalFilePath ? uploadDigitalFile(ctx, p) : null));
|
|
259
|
-
const body = {
|
|
260
|
-
returnEntity: true,
|
|
261
|
-
products: products.map((p, i) => ({
|
|
262
|
-
name: p.name,
|
|
263
|
-
// DIGITAL drops physicalProperties and can't be POS-visible (DIGITAL_PRODUCT_CANNOT_BE_VISIBLE_IN_POS).
|
|
264
|
-
...(fileIds[i]
|
|
265
|
-
? { productType: "DIGITAL" }
|
|
266
|
-
: { productType: "PHYSICAL", physicalProperties: {}, visibleInPos: true }),
|
|
267
|
-
visible: true,
|
|
268
|
-
description: mkDesc(p.description, i),
|
|
269
|
-
options: buildOptions(p.options),
|
|
270
|
-
variantsInfo: { variants: expandVariants(p.options, p, fileIds[i]) },
|
|
271
|
-
})),
|
|
272
|
-
};
|
|
273
|
-
const r = await req(ctx, "/stores/v3/bulk/products-with-inventory/create", { body });
|
|
274
|
-
// NB: results nest under productResults.results[].item — NOT a top-level `results`.
|
|
275
|
-
// The bulk returns 200 even on PARTIAL failure, so never map results by array position:
|
|
276
|
-
// pair each result to its input via itemMetadata.originalIndex and drop the ones that
|
|
277
|
-
// didn't persist. Positional mapping shifts every id after a failure onto the wrong
|
|
278
|
-
// product — which then mislabels categories and attaches images to the wrong items.
|
|
279
|
-
const created = [];
|
|
280
|
-
const failures = [];
|
|
281
|
-
for (const x of r.productResults?.results ?? []) {
|
|
282
|
-
const i = x.itemMetadata?.originalIndex;
|
|
283
|
-
const src = typeof i === "number" ? products[i] : undefined;
|
|
284
|
-
if (!x.itemMetadata?.success || !x.item?.id) {
|
|
285
|
-
failures.push({
|
|
286
|
-
name: src?.name,
|
|
287
|
-
error: x.itemMetadata?.error?.description ?? x.itemMetadata?.error?.code ?? "unknown",
|
|
288
|
-
});
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
created.push({
|
|
292
|
-
id: x.item.id, slug: x.item.slug, revision: x.item.revision, name: src?.name,
|
|
293
|
-
variantId: x.item.variantsInfo?.variants?.[0]?.id,
|
|
294
|
-
hasOptions: (src?.options?.length ?? 0) > 0,
|
|
295
|
-
isDigital: !!fileIds[i],
|
|
296
|
-
quantity: src?.quantity ?? 0,
|
|
297
|
-
inStock: src?.inStock,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
await stockOptionlessProducts(ctx, created);
|
|
301
|
-
return {
|
|
302
|
-
created: created.map((p) => ({ id: p.id, slug: p.slug, revision: p.revision, name: p.name })),
|
|
303
|
-
failures,
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// The bulk create stocks a variant via its choices; an OPTION-LESS product's single default
|
|
308
|
-
// variant is NOT stocked by it and lands OUT_OF_STOCK — stock those explicitly.
|
|
309
|
-
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/query-products.md
|
|
310
|
-
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/inventory-items-v3/bulk-create-inventory-items.md
|
|
311
|
-
async function stockOptionlessProducts(ctx, created) {
|
|
312
|
-
const need = created.filter((p) => !p.hasOptions && !p.isDigital && p.id); // digital variants ship inStock from the create
|
|
313
|
-
if (!need.length) return;
|
|
314
|
-
const missing = need.filter((p) => !p.variantId).map((p) => p.id);
|
|
315
|
-
if (missing.length) {
|
|
316
|
-
const q = await req(ctx, "/stores/v3/products/query", { body: { query: { filter: { id: { $in: missing } }, paging: { limit: missing.length } } } });
|
|
317
|
-
const vById = new Map((q.products ?? []).map((p) => [p.id, p.variantsInfo?.variants?.[0]?.id]));
|
|
318
|
-
need.forEach((p) => { if (!p.variantId) p.variantId = vById.get(p.id); });
|
|
319
|
-
}
|
|
320
|
-
// inStock:true == untracked stock (always buyable, no count). Only send a quantity when the
|
|
321
|
-
// product actually tracks one, or Wix rejects the pair.
|
|
322
|
-
const inventoryItems = need
|
|
323
|
-
.filter((p) => p.variantId)
|
|
324
|
-
.map((p) => ({
|
|
325
|
-
productId: p.id,
|
|
326
|
-
variantId: p.variantId,
|
|
327
|
-
...(p.inStock === true ? { inStock: true } : { quantity: p.quantity }),
|
|
328
|
-
}));
|
|
329
|
-
if (inventoryItems.length) {
|
|
330
|
-
await req(ctx, "/stores/v3/bulk/inventory-items/create", { body: { inventoryItems } });
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Existing categories by name (for idempotent reruns) — a re-run of the seed must reuse
|
|
335
|
-
// "Donuts", not create a second one. Empty map on any failure (falls back to create).
|
|
336
|
-
export async function queryCategoriesByNames(ctx, names) {
|
|
337
|
-
const out = new Map();
|
|
338
|
-
if (!names.length) return out;
|
|
339
|
-
try {
|
|
340
|
-
const r = await req(ctx, "/categories/v1/categories/query", {
|
|
341
|
-
body: { treeReference: { appNamespace: "@wix/stores", treeKey: null }, query: { cursorPaging: { limit: 100 } } },
|
|
342
|
-
});
|
|
343
|
-
const wanted = new Set(names);
|
|
344
|
-
for (const c of r.categories ?? []) if (wanted.has(c.name) && !out.has(c.name)) out.set(c.name, c.id);
|
|
345
|
-
} catch (e) {
|
|
346
|
-
console.error(`category name pre-check failed (creating everything): ${String(e.message).slice(0, 120)}`);
|
|
347
|
-
}
|
|
348
|
-
return out;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Categories share the @wix/stores tree revision — concurrent creates 409, so: sequential.
|
|
352
|
-
// Idempotent by name: a name that already exists is reused, never duplicated.
|
|
353
|
-
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories/create-category.md
|
|
354
|
-
export async function createCategories(ctx, names) {
|
|
355
|
-
const existing = await queryCategoriesByNames(ctx, names);
|
|
356
|
-
const out = [];
|
|
357
|
-
for (const name of names) {
|
|
358
|
-
if (existing.has(name)) {
|
|
359
|
-
out.push({ id: existing.get(name), name });
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
const r = await req(ctx, "/categories/v1/categories", {
|
|
363
|
-
body: { category: { name, visible: true }, treeReference: { appNamespace: "@wix/stores", treeKey: null } },
|
|
364
|
-
});
|
|
365
|
-
out.push({ id: r.category?.id, name });
|
|
366
|
-
}
|
|
367
|
-
return out;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories/bulk-add-items-to-category.md
|
|
372
|
-
export async function addProductsToCategories(ctx, mapping) {
|
|
373
|
-
for (const [categoryId, productIds] of Object.entries(mapping)) {
|
|
374
|
-
await req(ctx, `/categories/v1/bulk/categories/${categoryId}/add-items`, {
|
|
375
|
-
body: {
|
|
376
|
-
items: productIds.map((catalogItemId) => ({ catalogItemId, appId: STORES_APP_ID })),
|
|
377
|
-
treeReference: { appNamespace: "@wix/stores", treeKey: null },
|
|
378
|
-
},
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Bulk image attach in ONE call. items: [{ id, url, altText }] — no revision to pass: the
|
|
384
|
-
// current revision is read right before the update, so attach any number of times, any pass.
|
|
385
|
-
// Wix re-hosts each url server-side; the media can take a little while to appear on read-back
|
|
386
|
-
// (propagation) — normal, not a failure.
|
|
387
|
-
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/bulk-update-products.md
|
|
388
|
-
export async function attachProductImages(ctx, items) {
|
|
389
|
-
if (!items?.length) return;
|
|
390
|
-
const ids = items.map((it) => it.id);
|
|
391
|
-
const q = await req(ctx, "/stores/v3/products/query", { body: { query: { filter: { id: { $in: ids } }, paging: { limit: ids.length } } } });
|
|
392
|
-
const revById = new Map((q.products ?? []).map((p) => [p.id, p.revision]));
|
|
393
|
-
return req(ctx, "/stores/v3/bulk/products/update", {
|
|
394
|
-
body: {
|
|
395
|
-
products: items.map((it) => ({
|
|
396
|
-
product: { id: it.id, revision: revById.get(it.id), media: { itemsInfo: { items: [{ url: it.url, altText: it.altText }] } } },
|
|
397
|
-
})),
|
|
398
|
-
},
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Reject plans the API would reject halfway through, while nothing has been created yet —
|
|
403
|
-
// a mid-batch 400 leaves a half-seeded store that the agent then has to reason about.
|
|
404
|
-
export function validateProducts(products) {
|
|
405
|
-
const problems = [];
|
|
406
|
-
const colorByName = new Map();
|
|
407
|
-
products.forEach((p, i) => {
|
|
408
|
-
const where = p.name ? `"${p.name}"` : `product #${i + 1}`;
|
|
409
|
-
if (!p.name) problems.push(`${where}: name is required`);
|
|
410
|
-
if (p.quantity != null && (!Number.isInteger(p.quantity) || p.quantity < 0)) {
|
|
411
|
-
problems.push(`${where}: quantity must be a non-negative integer (got ${p.quantity}) — omit it and set inStock:true for untracked stock`);
|
|
412
|
-
}
|
|
413
|
-
for (const opt of p.options ?? []) {
|
|
414
|
-
const seen = new Set();
|
|
415
|
-
for (const c of opt.choices ?? []) {
|
|
416
|
-
const key = typeof c === "string" ? c : c?.name;
|
|
417
|
-
if (seen.has(key)) problems.push(`${where}: option "${opt.name}" repeats the choice "${key}"`);
|
|
418
|
-
seen.add(key);
|
|
419
|
-
// Wix keys a color choice by name, so the same name with two codes collides.
|
|
420
|
-
const code = typeof c === "object" ? c?.colorCode : undefined;
|
|
421
|
-
if (code) {
|
|
422
|
-
const prev = colorByName.get(key);
|
|
423
|
-
if (prev && prev !== code) problems.push(`color "${key}" is ${prev} on one product and ${code} on another — pick one`);
|
|
424
|
-
colorByName.set(key, code);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
if (problems.length) throw new Error(`invalid seed plan:\n - ${problems.join("\n - ")}`);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Site currency, set BEFORE any product exists (see setupStore). Existing product reads can
|
|
433
|
-
// keep reporting the old currency for a short while after this returns — that lag is expected
|
|
434
|
-
// and self-resolves, so don't re-verify or retry on it.
|
|
435
|
-
// docs: https://dev.wix.com/docs/rest/business-management/site-properties/properties/update-site-properties
|
|
436
|
-
async function setSiteCurrency(ctx, currency) {
|
|
437
|
-
await req(ctx, "/site-properties/v4/properties", {
|
|
438
|
-
method: "PATCH",
|
|
439
|
-
body: { properties: { paymentCurrency: currency }, fields: ["paymentCurrency"] },
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* ONE-CALL seed: install → currency → create products → categories → attach images, ids
|
|
445
|
-
* threaded in memory. This is the default path — call it once instead of the individual
|
|
446
|
-
* functions.
|
|
447
|
-
*/
|
|
448
|
-
export async function setupStore(ctx, { products = [], categories = {}, currency } = {}) {
|
|
449
|
-
validateProducts(products);
|
|
450
|
-
await installStoresApp(ctx);
|
|
451
|
-
// Before any product exists: a product's price is stored in the site currency at create time,
|
|
452
|
-
// so switching afterwards leaves the catalog priced in the old one.
|
|
453
|
-
if (currency) await setSiteCurrency(ctx, currency);
|
|
454
|
-
|
|
455
|
-
// Idempotent by name: an errored bulk create (429/5xx) may still have applied server-side,
|
|
456
|
-
// and SKILL.md tells the agent to re-run a failed seed — creating only the names that don't
|
|
457
|
-
// exist yet makes that rerun safe instead of a duplicator.
|
|
458
|
-
const existing = await queryProductsByNames(ctx, products.map((p) => p.name));
|
|
459
|
-
const toCreate = products.filter((p) => !existing.has(p.name));
|
|
460
|
-
const { created, failures } = toCreate.length
|
|
461
|
-
? await bulkCreateProducts(ctx, toCreate)
|
|
462
|
-
: { created: [], failures: [] };
|
|
463
|
-
const createdByName = new Map(created.map((p) => [p.name, p]));
|
|
464
|
-
const withNames = products.map((p) => {
|
|
465
|
-
const hit = createdByName.get(p.name) ?? existing.get(p.name);
|
|
466
|
-
return { ...(hit ?? {}), name: p.name };
|
|
467
|
-
});
|
|
468
|
-
const idByName = new Map(withNames.map((p) => [p.name, p.id]));
|
|
469
|
-
|
|
470
|
-
const names = Object.keys(categories);
|
|
471
|
-
const cats = names.length ? await createCategories(ctx, names) : [];
|
|
472
|
-
if (cats.length) {
|
|
473
|
-
const mapping = {};
|
|
474
|
-
for (const c of cats) {
|
|
475
|
-
const ids = (categories[c.name] || []).map((n) => idByName.get(n)).filter(Boolean);
|
|
476
|
-
if (ids.length) mapping[c.id] = ids;
|
|
477
|
-
}
|
|
478
|
-
if (Object.keys(mapping).length) await addProductsToCategories(ctx, mapping);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Pass 2 — images: resolve (import by url / generate by prompt) in one parallel wave, then
|
|
482
|
-
// bulk-attach. Failures leave the product text-only; the seed's exit never depends on images.
|
|
483
|
-
const files = await resolveItemImages(ctx, withNames.map((p, i) => ({
|
|
484
|
-
url: products[i]?.imageUrl,
|
|
485
|
-
path: products[i]?.imagePath,
|
|
486
|
-
prompt: products[i]?.imagePrompt,
|
|
487
|
-
displayName: `${p.slug || "product"}.png`,
|
|
488
|
-
})));
|
|
489
|
-
// `p.id` guards this: a product that failed to create has no id, and bulk-updating an
|
|
490
|
-
// undefined id would 400 the whole batch and cost every other product its image.
|
|
491
|
-
const imageItems = withNames
|
|
492
|
-
.map((p, i) => (files[i] && p.id ? { id: p.id, url: files[i].url, altText: products[i]?.altText ?? p.slug } : null))
|
|
493
|
-
.filter(Boolean);
|
|
494
|
-
let imagesAttached = 0;
|
|
495
|
-
try {
|
|
496
|
-
if (imageItems.length) await attachProductImages(ctx, imageItems);
|
|
497
|
-
imagesAttached = imageItems.length;
|
|
498
|
-
} catch {
|
|
499
|
-
/* never block on image failure — the products stay text-only */
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// failures is part of the result, not an exception: a partial seed still leaves a usable
|
|
503
|
-
// store, and the agent needs the names to report rather than silently shipping a short
|
|
504
|
-
// catalog. Re-run the seed to retry them — existing names are skipped, not duplicated.
|
|
505
|
-
return { products: withNames, categories: cats, imagesAttached, failures };
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// ---- CLI entry ----------------------------------------------------------------------------------
|
|
509
|
-
|
|
510
|
-
const invokedDirectly = process.argv[1] && import.meta.url.endsWith(process.argv[1].split("/").pop());
|
|
511
|
-
if (invokedDirectly) {
|
|
512
|
-
const planPath = process.argv[2];
|
|
513
|
-
if (!planPath) {
|
|
514
|
-
console.error("usage: node seed-store.mjs <plan.json> (run from the project root)");
|
|
515
|
-
process.exit(1);
|
|
516
|
-
}
|
|
517
|
-
const plan = JSON.parse(readFileSync(planPath, "utf8"));
|
|
518
|
-
const ctx = makeCtx();
|
|
519
|
-
setupStore(ctx, plan)
|
|
520
|
-
.then((result) => console.log(JSON.stringify(result, null, 2)))
|
|
521
|
-
.catch((e) => {
|
|
522
|
-
console.error(e.message);
|
|
523
|
-
process.exit(1);
|
|
524
|
-
});
|
|
525
|
-
}
|